def _create_process( self, installer: InstallerTypes = "pip", ): process = QProcess() if installer != "pip": process.setProgram(installer) else: process.setProgram(self._sys_executable_or_bundled_python()) process.setProcessChannelMode(QProcess.MergedChannels) process.readyReadStandardOutput.connect( lambda process=process: self._on_stdout_ready(process) ) # setup process path env = QProcessEnvironment() combined_paths = os.pathsep.join( [user_site_packages(), env.systemEnvironment().value("PYTHONPATH")] ) env.insert("PYTHONPATH", combined_paths) # use path of parent process env.insert( "PATH", QProcessEnvironment.systemEnvironment().value("PATH") ) process.setProcessEnvironment(env) self.set_output_widget(self._output_widget) process.finished.connect( lambda ec, es: self._on_process_finished(process, ec, es) ) # FIXME connecting lambda to finished signal is bug creating and may end with segfault when garbage # collection will consume Installer object before process end. return process
def _create_process( self, installer: InstallerTypes = "pip", ): process = QProcess() process.setProcessChannelMode(QProcess.MergedChannels) process.readyReadStandardOutput.connect( lambda process=process: self._on_stdout_ready(process)) env = QProcessEnvironment.systemEnvironment() if installer == "pip": process.setProgram(self._sys_executable_or_bundled_python()) # patch process path combined_paths = os.pathsep.join([ user_site_packages(), env.systemEnvironment().value("PYTHONPATH"), ]) env.insert("PYTHONPATH", combined_paths) else: process.setProgram(installer) if installer == "mamba": from ..._version import version_tuple # To avoid napari version changing when installing a plugin, we # add a pin to the current napari version, that way we can # restrict any changes to the actual napari application. # Conda/mamba also pin python by default, so we effectively # constrain python and napari versions from changing, when # installing plugins inside the constructor bundled application. # See: https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-pkgs.html#preventing-packages-from-updating-pinning napari_version = ".".join(str(v) for v in version_tuple[:3]) if env.contains("CONDA_PINNED_PACKAGES"): # str delimiter is '&' system_pins = f"&{env.value('CONDA_PINNED_PACKAGES')}" else: system_pins = "" env.insert( "CONDA_PINNED_PACKAGES", f"napari={napari_version}{system_pins}", ) if os.name == "nt": # workaround https://github.com/napari/napari/issues/4247, 4484 if not env.contains("TEMP"): temp = gettempdir() env.insert("TMP", temp) env.insert("TEMP", temp) if not env.contains("USERPROFILE"): env.insert("HOME", os.path.expanduser("~")) env.insert("USERPROFILE", os.path.expanduser("~")) process.setProcessEnvironment(env) self.set_output_widget(self._output_widget) process.finished.connect( lambda ec, es: self._on_process_finished(process, ec, es) ) # FIXME connecting lambda to finished signal is bug creating and may end with segfault when garbage # collection will consume Installer object before process end. return process
class Installer: def __init__(self, output_widget: QTextEdit = None): from ...plugins import plugin_manager # create install process self._output_widget = None self.process = QProcess() self.process.setProgram(sys.executable) self.process.setProcessChannelMode(QProcess.MergedChannels) self.process.readyReadStandardOutput.connect(self._on_stdout_ready) # setup process path env = QProcessEnvironment() combined_paths = os.pathsep.join([ user_site_packages(), env.systemEnvironment().value("PYTHONPATH") ]) env.insert("PYTHONPATH", combined_paths) # use path of parent process env.insert("PATH", QProcessEnvironment.systemEnvironment().value("PATH")) self.process.setProcessEnvironment(env) self.process.finished.connect(lambda: plugin_manager.discover()) self.process.finished.connect(lambda: plugin_manager.prune()) self.set_output_widget(output_widget) def set_output_widget(self, output_widget: QTextEdit): if output_widget: self._output_widget = output_widget self.process.setParent(output_widget) def _on_stdout_ready(self): if self._output_widget: text = self.process.readAllStandardOutput().data().decode() self._output_widget.append(text) def install(self, pkg_list: Sequence[str]): cmd = ['-m', 'pip', 'install', '--upgrade'] if running_as_bundled_app() and sys.platform.startswith('linux'): cmd += [ '--no-warn-script-location', '--prefix', user_plugin_dir(), ] self.process.setArguments(cmd + list(pkg_list)) if self._output_widget: self._output_widget.clear() self.process.start() def uninstall(self, pkg_list: Sequence[str]): args = ['-m', 'pip', 'uninstall', '-y'] self.process.setArguments(args + list(pkg_list)) if self._output_widget: self._output_widget.clear() self.process.start() for pkg in pkg_list: plugin_manager.unregister(pkg)
def start_server(self, filename, interpreter): """ Start a notebook server asynchronously. Start a server which can render the given notebook and return immediately. Assume the server uses the given interpreter. The manager will check periodically whether the server is accepting requests and emit `sig_server_started` or `sig_server_timed_out` when appropriate. Parameters ---------- filename : str File name of notebook to be rendered by the server. interpreter : str File name of Python interpreter to be used. """ home_dir = get_home_dir() if filename.startswith(home_dir): nbdir = home_dir else: nbdir = osp.dirname(filename) logger.debug('Starting new notebook server for %s', nbdir) process = QProcess(None) serverscript = osp.join(osp.dirname(__file__), '../server/main.py') serverscript = osp.normpath(serverscript) arguments = [ serverscript, '--no-browser', '--notebook-dir={}'.format(nbdir), '--NotebookApp.password='******'--KernelSpecManager.kernel_spec_class={}'.format(KERNELSPEC) ] if self.dark_theme: arguments.append('--dark') logger.debug('Arguments: %s', repr(arguments)) if DEV: env = QProcessEnvironment.systemEnvironment() env.insert('PYTHONPATH', osp.dirname(get_module_path('spyder'))) process.setProcessEnvironment(env) server_process = ServerProcess(process, notebook_dir=nbdir, interpreter=interpreter) process.setProcessChannelMode(QProcess.MergedChannels) process.readyReadStandardOutput.connect( lambda: self.read_server_output(server_process)) process.errorOccurred.connect( lambda error: self.handle_error(server_process, error)) process.finished.connect(lambda code, status: self.handle_finished( server_process, code, status)) process.start(sys.executable, arguments) self.servers.append(server_process) self._check_server_started(server_process)
def _prepare_process(self, config, pythonpath): """ Prepare and return process for running the unit test suite. This sets the working directory and environment. """ process = QProcess(self) process.setProcessChannelMode(QProcess.MergedChannels) process.setWorkingDirectory(config.wdir) process.finished.connect(self.finished) if pythonpath: env = QProcessEnvironment.systemEnvironment() old_python_path = env.value('PYTHONPATH', None) python_path_str = os.pathsep.join(pythonpath) if old_python_path: python_path_str += os.pathsep + old_python_path env.insert('PYTHONPATH', python_path_str) process.setProcessEnvironment(env) return process
def _prepare_process(self, config, pythonpath): """ Prepare and return process for running the unit test suite. This sets the working directory and environment. """ process = QProcess(self) process.setProcessChannelMode(QProcess.MergedChannels) process.setWorkingDirectory(config.wdir) process.finished.connect(self.finished) if pythonpath is not None: env = [ to_text_string(_pth) for _pth in process.systemEnvironment() ] add_pathlist_to_PYTHONPATH(env, pythonpath) processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) process.setProcessEnvironment(processEnvironment) return process
class ExternalPythonShell(ExternalShellBase): """External Shell widget: execute Python script in a separate process""" SHELL_CLASS = ExtPythonShellWidget sig_pdb = Signal(str, int) open_file = Signal(str, int) started = Signal() sig_finished = Signal() def __init__(self, parent=None, fname=None, wdir=None, interact=False, debug=False, post_mortem=False, path=[], python_args='', arguments='', stand_alone=None, umr_enabled=True, umr_namelist=[], umr_verbose=True, pythonstartup=None, pythonexecutable=None, external_interpreter=False, monitor_enabled=True, mpl_backend=None, ets_backend='qt4', qt_api=None, merge_output_channels=False, colorize_sys_stderr=False, autorefresh_timeout=3000, autorefresh_state=True, light_background=True, menu_actions=None, show_buttons_inside=True, show_elapsed_time=True): assert qt_api in (None, 'pyqt', 'pyside', 'pyqt5') self.namespacebrowser = None # namespace browser widget! self.dialog_manager = DialogManager() self.stand_alone = stand_alone # stand alone settings (None: plugin) self.interact = interact self.pythonstartup = pythonstartup self.pythonexecutable = pythonexecutable self.external_interpreter = external_interpreter self.monitor_enabled = monitor_enabled self.mpl_backend = mpl_backend self.ets_backend = ets_backend self.qt_api = qt_api self.merge_output_channels = merge_output_channels self.colorize_sys_stderr = colorize_sys_stderr self.umr_enabled = umr_enabled self.umr_namelist = umr_namelist self.umr_verbose = umr_verbose self.autorefresh_timeout = autorefresh_timeout self.autorefresh_state = autorefresh_state self.namespacebrowser_button = None self.cwd_button = None self.env_button = None self.syspath_button = None self.terminate_button = None self.notification_thread = None ExternalShellBase.__init__(self, parent=parent, fname=fname, wdir=wdir, history_filename='history.py', light_background=light_background, menu_actions=menu_actions, show_buttons_inside=show_buttons_inside, show_elapsed_time=show_elapsed_time) if self.pythonexecutable is None: self.pythonexecutable = get_python_executable() self.python_args = None if python_args: assert is_text_string(python_args) self.python_args = python_args assert is_text_string(arguments) self.arguments = arguments self.connection_file = None self.shell.set_externalshell(self) self.toggle_globals_explorer(False) self.interact_action.setChecked(self.interact) self.debug_action.setChecked(debug) self.introspection_socket = None self.is_interpreter = fname is None if self.is_interpreter: self.terminate_button.hide() self.post_mortem_action.setChecked(post_mortem and not self.is_interpreter) # Additional python path list self.path = path self.shell.path = path def set_introspection_socket(self, introspection_socket): self.introspection_socket = introspection_socket if self.namespacebrowser is not None: settings = self.namespacebrowser.get_view_settings() communicate(introspection_socket, 'set_remote_view_settings()', settings=[settings]) def set_autorefresh_timeout(self, interval): if self.introspection_socket is not None: try: communicate(self.introspection_socket, "set_monitor_timeout(%d)" % interval) except socket.error: pass def closeEvent(self, event): self.quit_monitor() ExternalShellBase.closeEvent(self, event) def get_toolbar_buttons(self): ExternalShellBase.get_toolbar_buttons(self) if self.namespacebrowser_button is None \ and self.stand_alone is not None: self.namespacebrowser_button = create_toolbutton( self, text=_("Variables"), icon=ima.icon('dictedit'), tip=_("Show/hide global variables explorer"), toggled=self.toggle_globals_explorer, text_beside_icon=True) if self.terminate_button is None: self.terminate_button = create_toolbutton( self, text=_("Terminate"), icon=ima.icon('stop'), tip=_("Attempts to stop the process. The process\n" "may not exit as a result of clicking this\n" "button (it is given the chance to prompt\n" "the user for any unsaved files, etc).")) buttons = [] if self.namespacebrowser_button is not None: buttons.append(self.namespacebrowser_button) buttons += [ self.run_button, self.terminate_button, self.kill_button, self.options_button ] return buttons def get_options_menu(self): ExternalShellBase.get_options_menu(self) self.interact_action = create_action(self, _("Interact")) self.interact_action.setCheckable(True) self.debug_action = create_action(self, _("Debug")) self.debug_action.setCheckable(True) self.args_action = create_action(self, _("Arguments..."), triggered=self.get_arguments) self.post_mortem_action = create_action(self, _("Post Mortem Debug")) self.post_mortem_action.setCheckable(True) run_settings_menu = QMenu(_("Run settings"), self) add_actions(run_settings_menu, (self.interact_action, self.debug_action, self.args_action, self.post_mortem_action)) self.cwd_button = create_action( self, _("Working directory"), icon=ima.icon('DirOpenIcon'), tip=_("Set current working directory"), triggered=self.set_current_working_directory) self.env_button = create_action(self, _("Environment variables"), icon=ima.icon('environ'), triggered=self.show_env) self.syspath_button = create_action(self, _("Show sys.path contents"), icon=ima.icon('syspath'), triggered=self.show_syspath) actions = [ run_settings_menu, self.show_time_action, None, self.cwd_button, self.env_button, self.syspath_button ] if self.menu_actions is not None: actions += [None] + self.menu_actions return actions def is_interpreter(self): """Return True if shellwidget is a Python interpreter""" return self.is_interpreter def get_shell_widget(self): if self.stand_alone is None: return self.shell else: self.namespacebrowser = NamespaceBrowser(self) settings = self.stand_alone self.namespacebrowser.set_shellwidget(self) self.namespacebrowser.setup(**settings) self.namespacebrowser.sig_collapse.connect( lambda: self.toggle_globals_explorer(False)) # Shell splitter self.splitter = splitter = QSplitter(Qt.Vertical, self) self.splitter.splitterMoved.connect(self.splitter_moved) splitter.addWidget(self.shell) splitter.setCollapsible(0, False) splitter.addWidget(self.namespacebrowser) splitter.setStretchFactor(0, 1) splitter.setStretchFactor(1, 0) splitter.setHandleWidth(5) splitter.setSizes([2, 1]) return splitter def get_icon(self): return ima.icon('python') def set_buttons_runnning_state(self, state): ExternalShellBase.set_buttons_runnning_state(self, state) self.interact_action.setEnabled(not state and not self.is_interpreter) self.debug_action.setEnabled(not state and not self.is_interpreter) self.args_action.setEnabled(not state and not self.is_interpreter) self.post_mortem_action.setEnabled(not state and not self.is_interpreter) if state: if self.arguments: argstr = _("Arguments: %s") % self.arguments else: argstr = _("No argument") else: argstr = _("Arguments...") self.args_action.setText(argstr) self.terminate_button.setVisible(not self.is_interpreter and state) if not state: self.toggle_globals_explorer(False) for btn in (self.cwd_button, self.env_button, self.syspath_button): btn.setEnabled(state and self.monitor_enabled) if self.namespacebrowser_button is not None: self.namespacebrowser_button.setEnabled(state) def set_namespacebrowser(self, namespacebrowser): """ Set namespace browser *widget* Note: this method is not used in stand alone mode """ self.namespacebrowser = namespacebrowser self.configure_namespacebrowser() def configure_namespacebrowser(self): """Connect the namespace browser to the notification thread""" if self.notification_thread is not None: self.notification_thread.refresh_namespace_browser.connect( self.namespacebrowser.refresh_table) signal = self.notification_thread.sig_process_remote_view signal.connect( lambda data: self.namespacebrowser.process_remote_view(data)) def create_process(self): self.shell.clear() self.process = QProcess(self) if self.merge_output_channels: self.process.setProcessChannelMode(QProcess.MergedChannels) else: self.process.setProcessChannelMode(QProcess.SeparateChannels) self.shell.wait_for_ready_read.connect( lambda: self.process.waitForReadyRead(250)) # Working directory if self.wdir is not None: self.process.setWorkingDirectory(self.wdir) #-------------------------Python specific------------------------------ # Python arguments p_args = ['-u'] if DEBUG >= 3: p_args += ['-v'] p_args += get_python_args(self.fname, self.python_args, self.interact_action.isChecked(), self.debug_action.isChecked(), self.arguments) env = [ to_text_string(_path) for _path in self.process.systemEnvironment() ] if self.pythonstartup: env.append('PYTHONSTARTUP=%s' % self.pythonstartup) #-------------------------Python specific------------------------------- # Post mortem debugging if self.post_mortem_action.isChecked(): env.append('SPYDER_EXCEPTHOOK=True') # Set standard input/output encoding for Python consoles # See http://stackoverflow.com/q/26312400/438386, specifically # the comments of Martijn Pieters env.append('PYTHONIOENCODING=UTF-8') # Monitor if self.monitor_enabled: env.append('SPYDER_SHELL_ID=%s' % id(self)) env.append('SPYDER_AR_TIMEOUT=%d' % self.autorefresh_timeout) env.append('SPYDER_AR_STATE=%r' % self.autorefresh_state) from spyder.widgets.externalshell import introspection introspection_server = introspection.start_introspection_server() introspection_server.register(self) notification_server = introspection.start_notification_server() self.notification_thread = notification_server.register(self) self.notification_thread.sig_pdb.connect( lambda fname, lineno: self.sig_pdb.emit(fname, lineno)) self.notification_thread.open_file.connect( lambda fname, lineno: self.open_file.emit(fname, lineno)) if self.namespacebrowser is not None: self.configure_namespacebrowser() env.append('SPYDER_I_PORT=%d' % introspection_server.port) env.append('SPYDER_N_PORT=%d' % notification_server.port) # External modules options env.append('ETS_TOOLKIT=%s' % self.ets_backend) if self.mpl_backend is not None: backends = {0: 'Automatic', 1: 'None', 2: 'TkAgg'} env.append('SPY_MPL_BACKEND=%s' % backends[self.mpl_backend]) if self.qt_api: env.append('QT_API=%s' % self.qt_api) env.append('COLORIZE_SYS_STDERR=%s' % self.colorize_sys_stderr) # # Socket-based alternative (see input hook in sitecustomize.py): # if self.install_qt_inputhook: # from PyQt4.QtNetwork import QLocalServer # self.local_server = QLocalServer() # self.local_server.listen(str(id(self))) # User Module Deleter if self.is_interpreter: env.append('UMR_ENABLED=%r' % self.umr_enabled) env.append('UMR_NAMELIST=%s' % ','.join(self.umr_namelist)) env.append('UMR_VERBOSE=%r' % self.umr_verbose) env.append('MATPLOTLIB_ION=True') else: if self.interact: env.append('MATPLOTLIB_ION=True') else: env.append('MATPLOTLIB_ION=False') # External interpreter env.append('EXTERNAL_INTERPRETER=%r' % self.external_interpreter) # Add sitecustomize path to path list pathlist = [] spy_path = get_module_source_path('spyder') sc_path = osp.join(spy_path, 'utils', 'site') pathlist.append(sc_path) # Adding Spyder path pathlist += self.path # Adding path list to PYTHONPATH environment variable add_pathlist_to_PYTHONPATH(env, pathlist) #-------------------------Python specific------------------------------ self.process.readyReadStandardOutput.connect(self.write_output) self.process.readyReadStandardError.connect(self.write_error) self.process.finished.connect( lambda ec, es=QProcess.ExitStatus: self.finished(ec, es)) self.sig_finished.connect(self.dialog_manager.close_all) self.terminate_button.clicked.connect(self.process.terminate) self.kill_button.clicked.connect(self.process.kill) #-------------------------Python specific------------------------------ # Fixes for our Mac app: # 1. PYTHONPATH and PYTHONHOME are set while bootstrapping the app, # but their values are messing sys.path for external interpreters # (e.g. EPD) so we need to remove them from the environment. # 2. Set PYTHONPATH again but without grabbing entries defined in the # environment (Fixes Issue 1321) # 3. Remove PYTHONOPTIMIZE from env so that we can have assert # statements working with our interpreters (See Issue 1281) if running_in_mac_app(): if MAC_APP_NAME not in self.pythonexecutable: env = [p for p in env if not (p.startswith('PYTHONPATH') or \ p.startswith('PYTHONHOME'))] # 1. add_pathlist_to_PYTHONPATH(env, pathlist, drop_env=True) # 2. env = [p for p in env if not p.startswith('PYTHONOPTIMIZE')] # 3. processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) self.process.start(self.pythonexecutable, p_args) #-------------------------Python specific------------------------------ running = self.process.waitForStarted(3000) self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("A Python console failed to start!")) else: self.shell.setFocus() self.started.emit() return self.process def finished(self, exit_code, exit_status): """Reimplement ExternalShellBase method""" ExternalShellBase.finished(self, exit_code, exit_status) self.introspection_socket = None #============================================================================== # Input/Output #============================================================================== def write_error(self): if os.name == 'nt': #---This is apparently necessary only on Windows (not sure though): # emptying standard output buffer before writing error output self.process.setReadChannel(QProcess.StandardOutput) if self.process.waitForReadyRead(1): self.write_output() self.shell.write_error(self.get_stderr()) QApplication.processEvents() def send_to_process(self, text): if not self.is_running(): return if not is_text_string(text): text = to_text_string(text) if self.mpl_backend == 0 and os.name == 'nt' and \ self.introspection_socket is not None: communicate(self.introspection_socket, "toggle_inputhook_flag(True)") # # Socket-based alternative (see input hook in sitecustomize.py): # while self.local_server.hasPendingConnections(): # self.local_server.nextPendingConnection().write('go!') if any([text == cmd for cmd in ['%ls', '%pwd', '%scientific']]) or \ any([text.startswith(cmd) for cmd in ['%cd ', '%clear ']]): text = 'evalsc(r"%s")\n' % text if not text.endswith('\n'): text += '\n' self.process.write(to_binary_string(text, 'utf8')) self.process.waitForBytesWritten(-1) # Eventually write prompt faster (when hitting Enter continuously) # -- necessary/working on Windows only: if os.name == 'nt': self.write_error() def keyboard_interrupt(self): if self.introspection_socket is not None: communicate(self.introspection_socket, "thread.interrupt_main()") def quit_monitor(self): if self.introspection_socket is not None: try: write_packet(self.introspection_socket, "thread.exit()") except socket.error: pass #============================================================================== # Globals explorer #============================================================================== @Slot(bool) def toggle_globals_explorer(self, state): if self.stand_alone is not None: self.splitter.setSizes([1, 1 if state else 0]) self.namespacebrowser_button.setChecked(state) if state and self.namespacebrowser is not None: self.namespacebrowser.refresh_table() def splitter_moved(self, pos, index): self.namespacebrowser_button.setChecked(self.splitter.sizes()[1]) #============================================================================== # Misc. #============================================================================== @Slot() def set_current_working_directory(self): """Set current working directory""" cwd = self.shell.get_cwd() self.redirect_stdio.emit(False) directory = getexistingdirectory(self, _("Select directory"), cwd) if directory: self.shell.set_cwd(directory) self.redirect_stdio.emit(True) @Slot() def show_env(self): """Show environment variables""" get_func = self.shell.get_env set_func = self.shell.set_env self.dialog_manager.show(RemoteEnvDialog(get_func, set_func)) @Slot() def show_syspath(self): """Show sys.path contents""" editor = CollectionsEditor() editor.setup(self.shell.get_syspath(), title="sys.path", readonly=True, width=600, icon=ima.icon('syspath')) self.dialog_manager.show(editor)
class PylintWidget(QWidget): """ Pylint widget """ DATAPATH = get_conf_path('pylint.results') VERSION = '1.1.0' redirect_stdio = Signal(bool) def __init__(self, parent, max_entries=100): QWidget.__init__(self, parent) self.setWindowTitle("Pylint") self.output = None self.error_output = None self.max_entries = max_entries self.rdata = [] if osp.isfile(self.DATAPATH): try: data = pickle.loads(open(self.DATAPATH, 'rb').read()) if data[0] == self.VERSION: self.rdata = data[1:] except (EOFError, ImportError): pass self.filecombo = PythonModulesComboBox(self) if self.rdata: self.remove_obsolete_items() self.filecombo.addItems(self.get_filenames()) self.start_button = create_toolbutton(self, icon=ima.icon('run'), text=_("Analyze"), tip=_("Run analysis"), triggered=self.start, text_beside_icon=True) self.stop_button = create_toolbutton(self, icon=ima.icon('stop'), text=_("Stop"), tip=_("Stop current analysis"), text_beside_icon=True) self.filecombo.valid.connect(self.start_button.setEnabled) self.filecombo.valid.connect(self.show_data) browse_button = create_toolbutton(self, icon=ima.icon('fileopen'), tip=_('Select Python file'), triggered=self.select_file) self.ratelabel = QLabel() self.datelabel = QLabel() self.log_button = create_toolbutton(self, icon=ima.icon('log'), text=_("Output"), text_beside_icon=True, tip=_("Complete output"), triggered=self.show_log) self.treewidget = ResultsTree(self) hlayout1 = QHBoxLayout() hlayout1.addWidget(self.filecombo) hlayout1.addWidget(browse_button) hlayout1.addWidget(self.start_button) hlayout1.addWidget(self.stop_button) hlayout2 = QHBoxLayout() hlayout2.addWidget(self.ratelabel) hlayout2.addStretch() hlayout2.addWidget(self.datelabel) hlayout2.addStretch() hlayout2.addWidget(self.log_button) layout = QVBoxLayout() layout.addLayout(hlayout1) layout.addLayout(hlayout2) layout.addWidget(self.treewidget) self.setLayout(layout) self.process = None self.set_running_state(False) self.show_data() def analyze(self, filename): filename = to_text_string(filename) # filename is a QString instance self.kill_if_running() index, _data = self.get_data(filename) if index is None: self.filecombo.addItem(filename) self.filecombo.setCurrentIndex(self.filecombo.count()-1) else: self.filecombo.setCurrentIndex(self.filecombo.findText(filename)) self.filecombo.selected() if self.filecombo.is_valid(): self.start() @Slot() def select_file(self): self.redirect_stdio.emit(False) filename, _selfilter = getopenfilename(self, _("Select Python file"), getcwd(), _("Python files")+" (*.py ; *.pyw)") self.redirect_stdio.emit(True) if filename: self.analyze(filename) def remove_obsolete_items(self): """Removing obsolete items""" self.rdata = [(filename, data) for filename, data in self.rdata if is_module_or_package(filename)] def get_filenames(self): return [filename for filename, _data in self.rdata] def get_data(self, filename): filename = osp.abspath(filename) for index, (fname, data) in enumerate(self.rdata): if fname == filename: return index, data else: return None, None def set_data(self, filename, data): filename = osp.abspath(filename) index, _data = self.get_data(filename) if index is not None: self.rdata.pop(index) self.rdata.insert(0, (filename, data)) self.save() def save(self): while len(self.rdata) > self.max_entries: self.rdata.pop(-1) pickle.dump([self.VERSION]+self.rdata, open(self.DATAPATH, 'wb'), 2) @Slot() def show_log(self): if self.output: TextEditor(self.output, title=_("Pylint output"), readonly=True, size=(700, 500)).exec_() @Slot() def start(self): filename = to_text_string(self.filecombo.currentText()) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(osp.dirname(filename)) self.process.readyReadStandardOutput.connect(self.read_output) self.process.readyReadStandardError.connect( lambda: self.read_output(error=True)) self.process.finished.connect(lambda ec, es=QProcess.ExitStatus: self.finished(ec, es)) self.stop_button.clicked.connect(self.process.kill) self.output = '' self.error_output = '' plver = PYLINT_VER if plver is not None: p_args = ['-m', 'pylint', '--output-format=text'] if plver.split('.')[0] == '0': p_args += ['-i', 'yes'] else: # Option '-i' (alias for '--include-ids') was removed in pylint # 1.0 p_args += ["--msg-template='{msg_id}:{line:3d},"\ "{column}: {obj}: {msg}"] p_args += [osp.basename(filename)] else: p_args = [osp.basename(filename)] self.process.start(sys.executable, p_args) running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start")) def set_running_state(self, state=True): self.start_button.setEnabled(not state) self.stop_button.setEnabled(state) def read_output(self, error=False): if error: self.process.setReadChannel(QProcess.StandardError) else: self.process.setReadChannel(QProcess.StandardOutput) qba = QByteArray() while self.process.bytesAvailable(): if error: qba += self.process.readAllStandardError() else: qba += self.process.readAllStandardOutput() text = to_text_string( locale_codec.toUnicode(qba.data()) ) if error: self.error_output += text else: self.output += text def finished(self, exit_code, exit_status): self.set_running_state(False) if not self.output: if self.error_output: QMessageBox.critical(self, _("Error"), self.error_output) print("pylint error:\n\n" + self.error_output, file=sys.stderr) return # Convention, Refactor, Warning, Error results = {'C:': [], 'R:': [], 'W:': [], 'E:': []} txt_module = '************* Module ' module = '' # Should not be needed - just in case something goes wrong for line in self.output.splitlines(): if line.startswith(txt_module): # New module module = line[len(txt_module):] continue # Supporting option include-ids: ('R3873:' instead of 'R:') if not re.match('^[CRWE]+([0-9]{4})?:', line): continue i1 = line.find(':') if i1 == -1: continue msg_id = line[:i1] i2 = line.find(':', i1+1) if i2 == -1: continue line_nb = line[i1+1:i2].strip() if not line_nb: continue line_nb = int(line_nb.split(',')[0]) message = line[i2+1:] item = (module, line_nb, message, msg_id) results[line[0]+':'].append(item) # Rate rate = None txt_rate = 'Your code has been rated at ' i_rate = self.output.find(txt_rate) if i_rate > 0: i_rate_end = self.output.find('/10', i_rate) if i_rate_end > 0: rate = self.output[i_rate+len(txt_rate):i_rate_end] # Previous run previous = '' if rate is not None: txt_prun = 'previous run: ' i_prun = self.output.find(txt_prun, i_rate_end) if i_prun > 0: i_prun_end = self.output.find('/10', i_prun) previous = self.output[i_prun+len(txt_prun):i_prun_end] filename = to_text_string(self.filecombo.currentText()) self.set_data(filename, (time.localtime(), rate, previous, results)) self.output = self.error_output + self.output self.show_data(justanalyzed=True) def kill_if_running(self): if self.process is not None: if self.process.state() == QProcess.Running: self.process.kill() self.process.waitForFinished() def show_data(self, justanalyzed=False): if not justanalyzed: self.output = None self.log_button.setEnabled(self.output is not None \ and len(self.output) > 0) self.kill_if_running() filename = to_text_string(self.filecombo.currentText()) if not filename: return _index, data = self.get_data(filename) if data is None: text = _('Source code has not been rated yet.') self.treewidget.clear_results() date_text = '' else: datetime, rate, previous_rate, results = data if rate is None: text = _('Analysis did not succeed ' '(see output for more details).') self.treewidget.clear_results() date_text = '' else: text_style = "<span style=\'color: #444444\'><b>%s </b></span>" rate_style = "<span style=\'color: %s\'><b>%s</b></span>" prevrate_style = "<span style=\'color: #666666\'>%s</span>" color = "#FF0000" if float(rate) > 5.: color = "#22AA22" elif float(rate) > 3.: color = "#EE5500" text = _('Global evaluation:') text = (text_style % text)+(rate_style % (color, ('%s/10' % rate))) if previous_rate: text_prun = _('previous run:') text_prun = ' (%s %s/10)' % (text_prun, previous_rate) text += prevrate_style % text_prun self.treewidget.set_results(filename, results) date = to_text_string(time.strftime("%d %b %Y %H:%M", datetime), encoding='utf8') date_text = text_style % date self.ratelabel.setText(text) self.datelabel.setText(date_text)
class LineProfilerWidget(QWidget): """ Line profiler widget. """ DATAPATH = get_conf_path('lineprofiler.results') VERSION = '0.0.1' redirect_stdio = Signal(bool) def __init__(self, parent): QWidget.__init__(self, parent) self.setWindowTitle("Line profiler") self.output = None self.error_output = None self.use_colors = True self._last_wdir = None self._last_args = None self._last_pythonpath = None self.filecombo = PythonModulesComboBox(self) self.start_button = create_toolbutton( self, icon=get_icon('run.png'), text=_("Profile by line"), tip=_("Run line profiler"), triggered=self.start, text_beside_icon=True) self.stop_button = create_toolbutton( self, icon=get_icon('terminate.png'), text=_("Stop"), tip=_("Stop current profiling"), text_beside_icon=True) self.filecombo.valid.connect(self.start_button.setEnabled) #self.filecombo.valid.connect(self.show_data) # FIXME: The combobox emits this signal on almost any event # triggering show_data() too early, too often. browse_button = create_toolbutton( self, icon=get_icon('fileopen.png'), tip=_('Select Python script'), triggered=self.select_file) self.datelabel = QLabel() self.log_button = create_toolbutton( self, icon=get_icon('log.png'), text=_("Output"), text_beside_icon=True, tip=_("Show program's output"), triggered=self.show_log) self.datatree = LineProfilerDataTree(self) self.collapse_button = create_toolbutton( self, icon=get_icon('collapse.png'), triggered=lambda dD=-1: self.datatree.collapseAll(), tip=_('Collapse all')) self.expand_button = create_toolbutton( self, icon=get_icon('expand.png'), triggered=lambda dD=1: self.datatree.expandAll(), tip=_('Expand all')) hlayout1 = QHBoxLayout() hlayout1.addWidget(self.filecombo) hlayout1.addWidget(browse_button) hlayout1.addWidget(self.start_button) hlayout1.addWidget(self.stop_button) hlayout2 = QHBoxLayout() hlayout2.addWidget(self.collapse_button) hlayout2.addWidget(self.expand_button) hlayout2.addStretch() hlayout2.addWidget(self.datelabel) hlayout2.addStretch() hlayout2.addWidget(self.log_button) layout = QVBoxLayout() layout.addLayout(hlayout1) layout.addLayout(hlayout2) layout.addWidget(self.datatree) self.setLayout(layout) self.process = None self.set_running_state(False) self.start_button.setEnabled(False) if not is_lineprofiler_installed(): for widget in (self.datatree, self.filecombo, self.log_button, self.start_button, self.stop_button, browse_button, self.collapse_button, self.expand_button): widget.setDisabled(True) text = _( '<b>Please install the <a href="%s">line_profiler module</a></b>' ) % WEBSITE_URL self.datelabel.setText(text) self.datelabel.setOpenExternalLinks(True) else: pass # self.show_data() def analyze(self, filename, wdir=None, args=None, pythonpath=None, use_colors=True): self.use_colors = use_colors if not is_lineprofiler_installed(): return self.kill_if_running() #index, _data = self.get_data(filename) index = None # FIXME: storing data is not implemented yet if index is None: self.filecombo.addItem(filename) self.filecombo.setCurrentIndex(self.filecombo.count()-1) else: self.filecombo.setCurrentIndex(self.filecombo.findText(filename)) self.filecombo.selected() if self.filecombo.is_valid(): if wdir is None: wdir = osp.dirname(filename) self.start(wdir, args, pythonpath) def select_file(self): self.redirect_stdio.emit(False) filename, _selfilter = getopenfilename( self, _("Select Python script"), getcwd(), _("Python scripts")+" (*.py ; *.pyw)") self.redirect_stdio.emit(False) if filename: self.analyze(filename) def show_log(self): if self.output: TextEditor(self.output, title=_("Line profiler output"), readonly=True, size=(700, 500)).exec_() def show_errorlog(self): if self.error_output: TextEditor(self.error_output, title=_("Line profiler output"), readonly=True, size=(700, 500)).exec_() def start(self, wdir=None, args=None, pythonpath=None): filename = to_text_string(self.filecombo.currentText()) if wdir is None: wdir = self._last_wdir if wdir is None: wdir = osp.basename(filename) if args is None: args = self._last_args if args is None: args = [] if pythonpath is None: pythonpath = self._last_pythonpath self._last_wdir = wdir self._last_args = args self._last_pythonpath = pythonpath self.datelabel.setText(_('Profiling, please wait...')) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(wdir) self.process.readyReadStandardOutput.connect(self.read_output) self.process.readyReadStandardError.connect( lambda: self.read_output(error=True)) self.process.finished.connect(self.finished) self.stop_button.clicked.connect(self.process.kill) if pythonpath is not None: env = [to_text_string(_pth) for _pth in self.process.systemEnvironment()] add_pathlist_to_PYTHONPATH(env, pythonpath) processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) self.output = '' self.error_output = '' if os.name == 'nt': # On Windows, one has to replace backslashes by slashes to avoid # confusion with escape characters (otherwise, for example, '\t' # will be interpreted as a tabulation): filename = osp.normpath(filename).replace(os.sep, '/') p_args = ['-lvb', '-o', '"' + self.DATAPATH + '"', '"' + filename + '"'] if args: p_args.extend(programs.shell_split(args)) executable = '"' + programs.find_program('kernprof') + '"' executable += ' ' + ' '.join(p_args) executable = executable.replace(os.sep, '/') self.process.start(executable) else: p_args = ['-lvb', '-o', self.DATAPATH, filename] if args: p_args.extend(programs.shell_split(args)) executable = 'kernprof' self.process.start(executable, p_args) running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start")) def set_running_state(self, state=True): self.start_button.setEnabled(not state) self.stop_button.setEnabled(state) def read_output(self, error=False): if error: self.process.setReadChannel(QProcess.StandardError) else: self.process.setReadChannel(QProcess.StandardOutput) qba = QByteArray() while self.process.bytesAvailable(): if error: qba += self.process.readAllStandardError() else: qba += self.process.readAllStandardOutput() text = to_text_string(locale_codec.toUnicode(qba.data())) if error: self.error_output += text else: self.output += text def finished(self): self.set_running_state(False) self.show_errorlog() # If errors occurred, show them. self.output = self.error_output + self.output # FIXME: figure out if show_data should be called here or # as a signal from the combobox self.show_data(justanalyzed=True) def kill_if_running(self): if self.process is not None: if self.process.state() == QProcess.Running: self.process.kill() self.process.waitForFinished() def show_data(self, justanalyzed=False): if not justanalyzed: self.output = None self.log_button.setEnabled( self.output is not None and len(self.output) > 0) self.kill_if_running() filename = to_text_string(self.filecombo.currentText()) if not filename: return self.datatree.load_data(self.DATAPATH) self.datelabel.setText(_('Sorting data, please wait...')) QApplication.processEvents() self.datatree.show_tree() text_style = "<span style=\'color: #444444\'><b>%s </b></span>" date_text = text_style % time.strftime("%d %b %Y %H:%M", time.localtime()) self.datelabel.setText(date_text)
class ProfilerWidget(QWidget): """ Profiler widget """ DATAPATH = get_conf_path('profiler.results') VERSION = '0.0.1' redirect_stdio = Signal(bool) def __init__(self, parent, max_entries=100): QWidget.__init__(self, parent) self.setWindowTitle("Profiler") self.output = None self.error_output = None self._last_wdir = None self._last_args = None self._last_pythonpath = None self.filecombo = PythonModulesComboBox(self) self.start_button = create_toolbutton(self, icon=ima.icon('run'), text=_("Profile"), tip=_("Run profiler"), triggered=lambda : self.start(), text_beside_icon=True) self.stop_button = create_toolbutton(self, icon=ima.icon('stop'), text=_("Stop"), tip=_("Stop current profiling"), text_beside_icon=True) self.filecombo.valid.connect(self.start_button.setEnabled) #self.connect(self.filecombo, SIGNAL('valid(bool)'), self.show_data) # FIXME: The combobox emits this signal on almost any event # triggering show_data() too early, too often. browse_button = create_toolbutton(self, icon=ima.icon('fileopen'), tip=_('Select Python script'), triggered=self.select_file) self.datelabel = QLabel() self.log_button = create_toolbutton(self, icon=ima.icon('log'), text=_("Output"), text_beside_icon=True, tip=_("Show program's output"), triggered=self.show_log) self.datatree = ProfilerDataTree(self) self.collapse_button = create_toolbutton(self, icon=ima.icon('collapse'), triggered=lambda dD: self.datatree.change_view(-1), tip=_('Collapse one level up')) self.expand_button = create_toolbutton(self, icon=ima.icon('expand'), triggered=lambda dD: self.datatree.change_view(1), tip=_('Expand one level down')) self.save_button = create_toolbutton(self, text_beside_icon=True, text=_("Save data"), icon=ima.icon('filesave'), triggered=self.save_data, tip=_('Save profiling data')) self.load_button = create_toolbutton(self, text_beside_icon=True, text=_("Load data"), icon=ima.icon('fileimport'), triggered=self.compare, tip=_('Load profiling data for comparison')) self.clear_button = create_toolbutton(self, text_beside_icon=True, text=_("Clear comparison"), icon=ima.icon('editdelete'), triggered=self.clear) hlayout1 = QHBoxLayout() hlayout1.addWidget(self.filecombo) hlayout1.addWidget(browse_button) hlayout1.addWidget(self.start_button) hlayout1.addWidget(self.stop_button) hlayout2 = QHBoxLayout() hlayout2.addWidget(self.collapse_button) hlayout2.addWidget(self.expand_button) hlayout2.addStretch() hlayout2.addWidget(self.datelabel) hlayout2.addStretch() hlayout2.addWidget(self.log_button) hlayout2.addWidget(self.save_button) hlayout2.addWidget(self.load_button) hlayout2.addWidget(self.clear_button) layout = QVBoxLayout() layout.addLayout(hlayout1) layout.addLayout(hlayout2) layout.addWidget(self.datatree) self.setLayout(layout) self.process = None self.set_running_state(False) self.start_button.setEnabled(False) self.clear_button.setEnabled(False) if not is_profiler_installed(): # This should happen only on certain GNU/Linux distributions # or when this a home-made Python build because the Python # profilers are included in the Python standard library for widget in (self.datatree, self.filecombo, self.start_button, self.stop_button): widget.setDisabled(True) url = 'http://docs.python.org/library/profile.html' text = '%s <a href=%s>%s</a>' % (_('Please install'), url, _("the Python profiler modules")) self.datelabel.setText(text) else: pass # self.show_data() def save_data(self): """Save data""" title = _( "Save profiler result") filename, _selfilter = getsavefilename(self, title, getcwd(), _("Profiler result")+" (*.Result)") if filename: self.datatree.save_data(filename) def compare(self): filename, _selfilter = getopenfilename(self, _("Select script to compare"), getcwd(), _("Profiler result")+" (*.Result)") if filename: self.datatree.compare(filename) self.show_data() self.clear_button.setEnabled(True) def clear(self): self.datatree.compare(None) self.datatree.hide_diff_cols(True) self.show_data() self.clear_button.setEnabled(False) def analyze(self, filename, wdir=None, args=None, pythonpath=None): if not is_profiler_installed(): return self.kill_if_running() #index, _data = self.get_data(filename) index = None # FIXME: storing data is not implemented yet if index is None: self.filecombo.addItem(filename) self.filecombo.setCurrentIndex(self.filecombo.count()-1) else: self.filecombo.setCurrentIndex(self.filecombo.findText(filename)) self.filecombo.selected() if self.filecombo.is_valid(): if wdir is None: wdir = osp.dirname(filename) self.start(wdir, args, pythonpath) def select_file(self): self.redirect_stdio.emit(False) filename, _selfilter = getopenfilename(self, _("Select Python script"), getcwd(), _("Python scripts")+" (*.py ; *.pyw)") self.redirect_stdio.emit(True) if filename: self.analyze(filename) def show_log(self): if self.output: TextEditor(self.output, title=_("Profiler output"), readonly=True, size=(700, 500)).exec_() def show_errorlog(self): if self.error_output: TextEditor(self.error_output, title=_("Profiler output"), readonly=True, size=(700, 500)).exec_() def start(self, wdir=None, args=None, pythonpath=None): filename = to_text_string(self.filecombo.currentText()) if wdir is None: wdir = self._last_wdir if wdir is None: wdir = osp.basename(filename) if args is None: args = self._last_args if args is None: args = [] if pythonpath is None: pythonpath = self._last_pythonpath self._last_wdir = wdir self._last_args = args self._last_pythonpath = pythonpath self.datelabel.setText(_('Profiling, please wait...')) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(wdir) self.process.readyReadStandardOutput.connect(self.read_output) self.process.readyReadStandardError.connect( lambda: self.read_output(error=True)) self.process.finished.connect(lambda ec, es=QProcess.ExitStatus: self.finished(ec, es)) self.stop_button.clicked.connect(self.process.kill) if pythonpath is not None: env = [to_text_string(_pth) for _pth in self.process.systemEnvironment()] baseshell.add_pathlist_to_PYTHONPATH(env, pythonpath) processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) self.output = '' self.error_output = '' p_args = ['-m', 'cProfile', '-o', self.DATAPATH] if os.name == 'nt': # On Windows, one has to replace backslashes by slashes to avoid # confusion with escape characters (otherwise, for example, '\t' # will be interpreted as a tabulation): p_args.append(osp.normpath(filename).replace(os.sep, '/')) else: p_args.append(filename) if args: p_args.extend(shell_split(args)) executable = sys.executable if executable.endswith("spyder.exe"): # py2exe distribution executable = "python.exe" self.process.start(executable, p_args) running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start")) def set_running_state(self, state=True): self.start_button.setEnabled(not state) self.stop_button.setEnabled(state) def read_output(self, error=False): if error: self.process.setReadChannel(QProcess.StandardError) else: self.process.setReadChannel(QProcess.StandardOutput) qba = QByteArray() while self.process.bytesAvailable(): if error: qba += self.process.readAllStandardError() else: qba += self.process.readAllStandardOutput() text = to_text_string( locale_codec.toUnicode(qba.data()) ) if error: self.error_output += text else: self.output += text def finished(self, exit_code, exit_status): self.set_running_state(False) self.show_errorlog() # If errors occurred, show them. self.output = self.error_output + self.output # FIXME: figure out if show_data should be called here or # as a signal from the combobox self.show_data(justanalyzed=True) def kill_if_running(self): if self.process is not None: if self.process.state() == QProcess.Running: self.process.kill() self.process.waitForFinished() def show_data(self, justanalyzed=False): if not justanalyzed: self.output = None self.log_button.setEnabled(self.output is not None \ and len(self.output) > 0) self.kill_if_running() filename = to_text_string(self.filecombo.currentText()) if not filename: return self.datelabel.setText(_('Sorting data, please wait...')) QApplication.processEvents() self.datatree.load_data(self.DATAPATH) self.datatree.show_tree() text_style = "<span style=\'color: #444444\'><b>%s </b></span>" date_text = text_style % time.strftime("%d %b %Y %H:%M", time.localtime()) self.datelabel.setText(date_text)
class PylintWidget(QWidget): """ Pylint widget """ DATAPATH = get_conf_path('pylint.results') VERSION = '1.1.0' redirect_stdio = Signal(bool) start_analysis = Signal() def __init__(self, parent, max_entries=100, options_button=None, text_color=None, prevrate_color=None): super().__init__(parent) self.setWindowTitle("Pylint") self.output = None self.error_output = None self.filename = None self.text_color = text_color self.prevrate_color = prevrate_color self.max_entries = max_entries self.rdata = [] if osp.isfile(self.DATAPATH): try: data = pickle.loads(open(self.DATAPATH, 'rb').read()) if data[0] == self.VERSION: self.rdata = data[1:] except (EOFError, ImportError): pass self.filecombo = PythonModulesComboBox(self) self.start_button = create_toolbutton( self, icon=ima.icon('run'), text=_("Analyze"), tip=_("Run analysis"), triggered=self.analyze_button_handler, text_beside_icon=True) self.stop_button = create_toolbutton(self, icon=ima.icon('stop'), text=_("Stop"), tip=_("Stop current analysis"), text_beside_icon=True) self.filecombo.valid.connect(self.start_button.setEnabled) self.filecombo.valid.connect(self.check_new_file) browse_button = create_toolbutton(self, icon=ima.icon('fileopen'), tip=_('Select Python file'), triggered=self.select_file) self.ratelabel = QLabel() self.datelabel = QLabel() self.log_button = create_toolbutton(self, icon=ima.icon('log'), text=_("Output"), text_beside_icon=True, tip=_("Complete output"), triggered=self.show_log) self.treewidget = ResultsTree(self) hlayout1 = QHBoxLayout() hlayout1.addWidget(self.filecombo) hlayout1.addWidget(browse_button) hlayout1.addWidget(self.start_button) hlayout1.addWidget(self.stop_button) if options_button: hlayout1.addWidget(options_button) hlayout2 = QHBoxLayout() hlayout2.addWidget(self.ratelabel) hlayout2.addStretch() hlayout2.addWidget(self.datelabel) hlayout2.addStretch() hlayout2.addWidget(self.log_button) layout = QVBoxLayout() layout.addLayout(hlayout1) layout.addLayout(hlayout2) layout.addWidget(self.treewidget) self.setLayout(layout) self.process = None self.set_running_state(False) self.show_data() if self.rdata: self.remove_obsolete_items() self.filecombo.addItems(self.get_filenames()) self.start_button.setEnabled(self.filecombo.is_valid()) else: self.start_button.setEnabled(False) def check_new_file(self): fname = self.get_filename() if fname != self.filename: self.filename = fname self.show_data() def get_filename(self): """Get current filename in combobox.""" return self.filecombo.currentText() @Slot(str) def set_filename(self, filename): """Set filename without performing code analysis.""" filename = str(filename) # filename is a QString instance self.kill_if_running() index, _data = self.get_data(filename) if index is None: self.filecombo.addItem(filename) self.filecombo.setCurrentIndex(self.filecombo.count() - 1) else: self.filecombo.setCurrentIndex(self.filecombo.findText(filename)) self.filecombo.selected() def analyze(self, filename=None): """ Perform code analysis for given `filename`. If `filename` is None default to current filename in combobox. """ if filename is not None: self.set_filename(filename) if self.filecombo.is_valid(): self.start() @Slot() def select_file(self): self.redirect_stdio.emit(False) filename, _selfilter = getopenfilename( self, _("Select Python file"), getcwd_or_home(), _("Python files") + " (*.py ; *.pyw)") self.redirect_stdio.emit(True) if filename: self.analyze(filename) def remove_obsolete_items(self): """Removing obsolete items""" self.rdata = [(filename, data) for filename, data in self.rdata if is_module_or_package(filename)] def get_filenames(self): return [filename for filename, _data in self.rdata] def get_data(self, filename): filename = osp.abspath(filename) for index, (fname, data) in enumerate(self.rdata): if fname == filename: return index, data else: return None, None def set_data(self, filename, data): filename = osp.abspath(filename) index, _data = self.get_data(filename) if index is not None: self.rdata.pop(index) self.rdata.insert(0, (filename, data)) self.save() def save(self): while len(self.rdata) > self.max_entries: self.rdata.pop(-1) pickle.dump([self.VERSION] + self.rdata, open(self.DATAPATH, 'wb'), 2) @Slot() def show_log(self): if self.output: output_dialog = TextEditor(self.output, title=_("Pylint output"), parent=self, readonly=True) output_dialog.resize(700, 500) output_dialog.exec_() @Slot() def analyze_button_handler(self): """Try to start code analysis when Analyze button pressed.""" self.start_analysis.emit() def get_pylintrc_path(self, filename): """Get the path to the most proximate pylintrc config to the file.""" parent = self.parentWidget() if parent is not None: project_dir = parent.main.projects.get_active_project_path() else: project_dir = None search_paths = [ osp.dirname(filename), # File's directory getcwd_or_home(), # Working directory project_dir, # Project directory osp.expanduser("~"), # Home directory ] return get_pylintrc_path(search_paths=search_paths) @Slot() def start(self): """Start the code analysis.""" filename = str(self.filecombo.currentText()) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(getcwd_or_home()) self.process.readyReadStandardOutput.connect(self.read_output) self.process.readyReadStandardError.connect( lambda: self.read_output(error=True)) self.process.finished.connect( lambda ec, es=QProcess.ExitStatus: self.finished(ec, es)) self.stop_button.clicked.connect(self.process.kill) self.output = '' self.error_output = '' if PYLINT_VER is not None: pylint_args = [ '-m', 'pylint', '--output-format=text', '--msg-template=' "'{msg_id}:{symbol}:{line:3d},{column}: {msg}'" ] pylintrc_path = self.get_pylintrc_path(filename=filename) if pylintrc_path is not None: pylint_args += ['--rcfile={}'.format(pylintrc_path)] pylint_args.append(filename) processEnvironment = QProcessEnvironment() processEnvironment.insert("PYTHONIOENCODING", "utf8") self.process.setProcessEnvironment(processEnvironment) self.process.start(sys.executable, pylint_args) running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start")) def set_running_state(self, state=True): self.start_button.setEnabled(not state) self.stop_button.setEnabled(state) def read_output(self, error=False): if error: self.process.setReadChannel(QProcess.StandardError) else: self.process.setReadChannel(QProcess.StandardOutput) qba = QByteArray() while self.process.bytesAvailable(): if error: qba += self.process.readAllStandardError() else: qba += self.process.readAllStandardOutput() text = str(qba.data(), 'utf-8') if error: self.error_output += text else: self.output += text def finished(self, exit_code, exit_status): self.set_running_state(False) if not self.output: if self.error_output: QMessageBox.critical(self, _("Error"), self.error_output) print("pylint error:\n\n" + self.error_output, file=sys.stderr) return # Convention, Refactor, Warning, Error results = {'C:': [], 'R:': [], 'W:': [], 'E:': []} txt_module = '************* Module ' module = '' # Should not be needed - just in case something goes wrong for line in self.output.splitlines(): if line.startswith(txt_module): # New module module = line[len(txt_module):] continue # Supporting option include-ids: ('R3873:' instead of 'R:') if not re.match(r'^[CRWE]+([0-9]{4})?:', line): continue items = {} idx_0 = 0 idx_1 = 0 key_names = ["msg_id", "message_name", "line_nb", "message"] for key_idx, key_name in enumerate(key_names): if key_idx == len(key_names) - 1: idx_1 = len(line) else: idx_1 = line.find(":", idx_0) if idx_1 < 0: break item = line[(idx_0):idx_1] if not item: break if key_name == "line_nb": item = int(item.split(',')[0]) items[key_name] = item idx_0 = idx_1 + 1 else: pylint_item = (module, items["line_nb"], items["message"], items["msg_id"], items["message_name"]) results[line[0] + ':'].append(pylint_item) # Rate rate = None txt_rate = 'Your code has been rated at ' i_rate = self.output.find(txt_rate) if i_rate > 0: i_rate_end = self.output.find('/10', i_rate) if i_rate_end > 0: rate = self.output[i_rate + len(txt_rate):i_rate_end] # Previous run previous = '' if rate is not None: txt_prun = 'previous run: ' i_prun = self.output.find(txt_prun, i_rate_end) if i_prun > 0: i_prun_end = self.output.find('/10', i_prun) previous = self.output[i_prun + len(txt_prun):i_prun_end] filename = str(self.filecombo.currentText()) self.set_data(filename, (time.localtime(), rate, previous, results)) self.output = self.error_output + self.output self.show_data(justanalyzed=True) def kill_if_running(self): if self.process is not None: if self.process.state() == QProcess.Running: self.process.kill() self.process.waitForFinished() def show_data(self, justanalyzed=False): if not justanalyzed: self.output = None self.log_button.setEnabled(self.output is not None and len(self.output) > 0) self.kill_if_running() filename = str(self.filecombo.currentText()) if not filename: return _index, data = self.get_data(filename) if data is None: text = _('Source code has not been rated yet.') self.treewidget.clear_results() date_text = '' else: datetime, rate, previous_rate, results = data if rate is None: text = _('Analysis did not succeed ' '(see output for more details).') self.treewidget.clear_results() date_text = '' else: text_style = "<span style=\'color: %s\'><b>%s </b></span>" rate_style = "<span style=\'color: %s\'><b>%s</b></span>" prevrate_style = "<span style=\'color: %s\'>%s</span>" color = "#FF0000" if float(rate) > 5.: color = "#22AA22" elif float(rate) > 3.: color = "#EE5500" text = _('Global evaluation:') text = ((text_style % (self.text_color, text)) + (rate_style % (color, ('%s/10' % rate)))) if previous_rate: text_prun = _('previous run:') text_prun = ' (%s %s/10)' % (text_prun, previous_rate) text += prevrate_style % (self.prevrate_color, text_prun) self.treewidget.set_results(filename, results) date = time.strftime("%Y-%m-%d %H:%M:%S", datetime) date_text = text_style % (self.text_color, date) self.ratelabel.setText(text) self.datelabel.setText(date_text)
class ProfilerWidget(QWidget): """ Profiler widget """ DATAPATH = get_conf_path('profiler.results') VERSION = '0.0.1' redirect_stdio = Signal(bool) def __init__(self, parent, max_entries=100): QWidget.__init__(self, parent) self.setWindowTitle("Profiler") self.output = None self.error_output = None self._last_wdir = None self._last_args = None self._last_pythonpath = None self.filecombo = PythonModulesComboBox(self) self.start_button = create_toolbutton(self, icon=ima.icon('run'), text=_("Profile"), tip=_("Run profiler"), triggered=lambda: self.start(), text_beside_icon=True) self.stop_button = create_toolbutton(self, icon=ima.icon('stop'), text=_("Stop"), tip=_("Stop current profiling"), text_beside_icon=True) self.filecombo.valid.connect(self.start_button.setEnabled) #self.connect(self.filecombo, SIGNAL('valid(bool)'), self.show_data) # FIXME: The combobox emits this signal on almost any event # triggering show_data() too early, too often. browse_button = create_toolbutton(self, icon=ima.icon('fileopen'), tip=_('Select Python script'), triggered=self.select_file) self.datelabel = QLabel() self.log_button = create_toolbutton(self, icon=ima.icon('log'), text=_("Output"), text_beside_icon=True, tip=_("Show program's output"), triggered=self.show_log) self.datatree = ProfilerDataTree(self) self.collapse_button = create_toolbutton( self, icon=ima.icon('collapse'), triggered=lambda dD: self.datatree.change_view(-1), tip=_('Collapse one level up')) self.expand_button = create_toolbutton( self, icon=ima.icon('expand'), triggered=lambda dD: self.datatree.change_view(1), tip=_('Expand one level down')) self.save_button = create_toolbutton(self, text_beside_icon=True, text=_("Save data"), icon=ima.icon('filesave'), triggered=self.save_data, tip=_('Save profiling data')) self.load_button = create_toolbutton( self, text_beside_icon=True, text=_("Load data"), icon=ima.icon('fileimport'), triggered=self.compare, tip=_('Load profiling data for comparison')) self.clear_button = create_toolbutton(self, text_beside_icon=True, text=_("Clear comparison"), icon=ima.icon('editdelete'), triggered=self.clear) hlayout1 = QHBoxLayout() hlayout1.addWidget(self.filecombo) hlayout1.addWidget(browse_button) hlayout1.addWidget(self.start_button) hlayout1.addWidget(self.stop_button) hlayout2 = QHBoxLayout() hlayout2.addWidget(self.collapse_button) hlayout2.addWidget(self.expand_button) hlayout2.addStretch() hlayout2.addWidget(self.datelabel) hlayout2.addStretch() hlayout2.addWidget(self.log_button) hlayout2.addWidget(self.save_button) hlayout2.addWidget(self.load_button) hlayout2.addWidget(self.clear_button) layout = QVBoxLayout() layout.addLayout(hlayout1) layout.addLayout(hlayout2) layout.addWidget(self.datatree) self.setLayout(layout) self.process = None self.set_running_state(False) self.start_button.setEnabled(False) self.clear_button.setEnabled(False) if not is_profiler_installed(): # This should happen only on certain GNU/Linux distributions # or when this a home-made Python build because the Python # profilers are included in the Python standard library for widget in (self.datatree, self.filecombo, self.start_button, self.stop_button): widget.setDisabled(True) url = 'http://docs.python.org/library/profile.html' text = '%s <a href=%s>%s</a>' % (_('Please install'), url, _("the Python profiler modules")) self.datelabel.setText(text) else: pass # self.show_data() def save_data(self): """Save data""" title = _("Save profiler result") filename, _selfilter = getsavefilename( self, title, getcwd_or_home(), _("Profiler result") + " (*.Result)") if filename: self.datatree.save_data(filename) def compare(self): filename, _selfilter = getopenfilename( self, _("Select script to compare"), getcwd_or_home(), _("Profiler result") + " (*.Result)") if filename: self.datatree.compare(filename) self.show_data() self.clear_button.setEnabled(True) def clear(self): self.datatree.compare(None) self.datatree.hide_diff_cols(True) self.show_data() self.clear_button.setEnabled(False) def analyze(self, filename, wdir=None, args=None, pythonpath=None): if not is_profiler_installed(): return self.kill_if_running() #index, _data = self.get_data(filename) index = None # FIXME: storing data is not implemented yet if index is None: self.filecombo.addItem(filename) self.filecombo.setCurrentIndex(self.filecombo.count() - 1) else: self.filecombo.setCurrentIndex(self.filecombo.findText(filename)) self.filecombo.selected() if self.filecombo.is_valid(): if wdir is None: wdir = osp.dirname(filename) self.start(wdir, args, pythonpath) def select_file(self): self.redirect_stdio.emit(False) filename, _selfilter = getopenfilename( self, _("Select Python script"), getcwd_or_home(), _("Python scripts") + " (*.py ; *.pyw)") self.redirect_stdio.emit(True) if filename: self.analyze(filename) def show_log(self): if self.output: TextEditor(self.output, title=_("Profiler output"), readonly=True, size=(700, 500)).exec_() def show_errorlog(self): if self.error_output: TextEditor(self.error_output, title=_("Profiler output"), readonly=True, size=(700, 500)).exec_() def start(self, wdir=None, args=None, pythonpath=None): filename = to_text_string(self.filecombo.currentText()) if wdir is None: wdir = self._last_wdir if wdir is None: wdir = osp.basename(filename) if args is None: args = self._last_args if args is None: args = [] if pythonpath is None: pythonpath = self._last_pythonpath self._last_wdir = wdir self._last_args = args self._last_pythonpath = pythonpath self.datelabel.setText(_('Profiling, please wait...')) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(wdir) self.process.readyReadStandardOutput.connect(self.read_output) self.process.readyReadStandardError.connect( lambda: self.read_output(error=True)) self.process.finished.connect( lambda ec, es=QProcess.ExitStatus: self.finished(ec, es)) self.stop_button.clicked.connect(self.process.kill) if pythonpath is not None: env = [ to_text_string(_pth) for _pth in self.process.systemEnvironment() ] add_pathlist_to_PYTHONPATH(env, pythonpath) processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) self.output = '' self.error_output = '' p_args = ['-m', 'cProfile', '-o', self.DATAPATH] if os.name == 'nt': # On Windows, one has to replace backslashes by slashes to avoid # confusion with escape characters (otherwise, for example, '\t' # will be interpreted as a tabulation): p_args.append(osp.normpath(filename).replace(os.sep, '/')) else: p_args.append(filename) if args: p_args.extend(shell_split(args)) executable = sys.executable if executable.endswith("spyder.exe"): # py2exe distribution executable = "python.exe" self.process.start(executable, p_args) running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start")) def set_running_state(self, state=True): self.start_button.setEnabled(not state) self.stop_button.setEnabled(state) def read_output(self, error=False): if error: self.process.setReadChannel(QProcess.StandardError) else: self.process.setReadChannel(QProcess.StandardOutput) qba = QByteArray() while self.process.bytesAvailable(): if error: qba += self.process.readAllStandardError() else: qba += self.process.readAllStandardOutput() text = to_text_string(locale_codec.toUnicode(qba.data())) if error: self.error_output += text else: self.output += text def finished(self, exit_code, exit_status): self.set_running_state(False) self.show_errorlog() # If errors occurred, show them. self.output = self.error_output + self.output # FIXME: figure out if show_data should be called here or # as a signal from the combobox self.show_data(justanalyzed=True) def kill_if_running(self): if self.process is not None: if self.process.state() == QProcess.Running: self.process.kill() self.process.waitForFinished() def show_data(self, justanalyzed=False): if not justanalyzed: self.output = None self.log_button.setEnabled(self.output is not None \ and len(self.output) > 0) self.kill_if_running() filename = to_text_string(self.filecombo.currentText()) if not filename: return self.datelabel.setText(_('Sorting data, please wait...')) QApplication.processEvents() self.datatree.load_data(self.DATAPATH) self.datatree.show_tree() text_style = "<span style=\'color: #444444\'><b>%s </b></span>" date_text = text_style % time.strftime("%d %b %Y %H:%M", time.localtime()) self.datelabel.setText(date_text)
class ExternalSystemShell(ExternalShellBase): """External Shell widget: execute Python script in a separate process""" SHELL_CLASS = TerminalWidget started = Signal() def __init__(self, parent=None, wdir=None, path=[], light_background=True, menu_actions=None, show_buttons_inside=True, show_elapsed_time=True): ExternalShellBase.__init__(self, parent=parent, fname=None, wdir=wdir, history_filename='.history', light_background=light_background, menu_actions=menu_actions, show_buttons_inside=show_buttons_inside, show_elapsed_time=show_elapsed_time) # Additional python path list self.path = path # For compatibility with the other shells that can live in the external # console self.is_ipykernel = False self.connection_file = None def get_icon(self): return ima.icon('cmdprompt') def finish_process(self): while not self.process.waitForFinished(100): self.process.kill(); def create_process(self): self.shell.clear() self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.MergedChannels) # PYTHONPATH (in case we use Python in this terminal, e.g. py2exe) env = [to_text_string(_path) for _path in self.process.systemEnvironment()] processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) add_pathlist_to_PYTHONPATH(env, self.path) self.process.setProcessEnvironment(processEnvironment) # Working directory if self.wdir is not None: self.process.setWorkingDirectory(self.wdir) # Shell arguments if os.name == 'nt': p_args = ['/Q'] else: p_args = ['-i'] if self.arguments: p_args.extend( shell_split(self.arguments) ) self.process.readyReadStandardOutput.connect(self.write_output) self.process.finished.connect(self.finished) self.kill_button.clicked.connect(self.process.kill) if os.name == 'nt': self.process.start('cmd.exe', p_args) else: # Using bash: self.process.start('bash', p_args) self.send_to_process('PS1="\\u@\\h:\\w> "\n') running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start")) else: self.shell.setFocus() self.started.emit() return self.process #=============================================================================== # Input/Output #=============================================================================== def transcode(self, qba): if os.name == 'nt': return to_text_string( CP850_CODEC.toUnicode(qba.data()) ) else: return ExternalShellBase.transcode(self, qba) def send_to_process(self, text): if not is_text_string(text): text = to_text_string(text) if text[:-1] in ["clear", "cls", "CLS"]: self.shell.clear() self.send_to_process(os.linesep) return if not text.endswith('\n'): text += '\n' if os.name == 'nt': self.process.write(text.encode('cp850')) else: self.process.write(LOCALE_CODEC.fromUnicode(text)) self.process.waitForBytesWritten(-1) def keyboard_interrupt(self): # This does not work on Windows: # (unfortunately there is no easy way to send a Ctrl+C to cmd.exe) self.send_ctrl_to_process('c')
class ExternalSystemShell(ExternalShellBase): """External Shell widget: execute Python script in a separate process""" SHELL_CLASS = TerminalWidget started = Signal() def __init__(self, parent=None, wdir=None, path=[], light_background=True, menu_actions=None, show_buttons_inside=True, show_elapsed_time=True): ExternalShellBase.__init__(self, parent=parent, fname=None, wdir=wdir, history_filename='.history', light_background=light_background, menu_actions=menu_actions, show_buttons_inside=show_buttons_inside, show_elapsed_time=show_elapsed_time) # Additional python path list self.path = path # For compatibility with the other shells that can live in the external # console self.is_ipykernel = False self.connection_file = None def get_icon(self): return ima.icon('cmdprompt') def finish_process(self): while not self.process.waitForFinished(100): self.process.kill() def create_process(self): self.shell.clear() self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.MergedChannels) # PYTHONPATH (in case we use Python in this terminal, e.g. py2exe) env = [ to_text_string(_path) for _path in self.process.systemEnvironment() ] processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) add_pathlist_to_PYTHONPATH(env, self.path) self.process.setProcessEnvironment(processEnvironment) # Working directory if self.wdir is not None: self.process.setWorkingDirectory(self.wdir) # Shell arguments if os.name == 'nt': p_args = ['/Q'] else: p_args = ['-i'] if self.arguments: p_args.extend(shell_split(self.arguments)) self.process.readyReadStandardOutput.connect(self.write_output) self.process.finished.connect(self.finished) self.kill_button.clicked.connect(self.process.kill) if os.name == 'nt': self.process.start('cmd.exe', p_args) else: # Using bash: self.process.start('bash', p_args) self.send_to_process('PS1="\\u@\\h:\\w> "\n') running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start")) else: self.shell.setFocus() self.started.emit() return self.process #=============================================================================== # Input/Output #=============================================================================== def transcode(self, qba): if os.name == 'nt': return to_text_string(CP850_CODEC.toUnicode(qba.data())) else: return ExternalShellBase.transcode(self, qba) def send_to_process(self, text): if not is_text_string(text): text = to_text_string(text) if text[:-1] in ["clear", "cls", "CLS"]: self.shell.clear() self.send_to_process(os.linesep) return if not text.endswith('\n'): text += '\n' if os.name == 'nt': self.process.write(text.encode('cp850')) else: self.process.write(LOCALE_CODEC.fromUnicode(text)) self.process.waitForBytesWritten(-1) def keyboard_interrupt(self): # This does not work on Windows: # (unfortunately there is no easy way to send a Ctrl+C to cmd.exe) self.send_ctrl_to_process('c')
class QtPipDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.setup_ui() self.setAttribute(Qt.WA_DeleteOnClose) # create install process self.process = QProcess(self) self.process.setProgram(sys.executable) self.process.setProcessChannelMode(QProcess.MergedChannels) # setup process path env = QProcessEnvironment() combined_paths = os.pathsep.join( [user_site_packages(), env.systemEnvironment().value("PYTHONPATH")] ) env.insert("PYTHONPATH", combined_paths) self.process.setProcessEnvironment(env) # connections self.install_button.clicked.connect(self._install) self.uninstall_button.clicked.connect(self._uninstall) self.process.readyReadStandardOutput.connect(self._on_stdout_ready) from ..plugins import plugin_manager self.process.finished.connect(plugin_manager.discover) self.process.finished.connect(plugin_manager.prune) def setup_ui(self): layout = QVBoxLayout() self.setLayout(layout) title = QLabel("Install/Uninstall Packages:") title.setObjectName("h2") self.line_edit = QLineEdit() self.install_button = QPushButton("install", self) self.uninstall_button = QPushButton("uninstall", self) self.text_area = QTextEdit(self, readOnly=True) hlay = QHBoxLayout() hlay.addWidget(self.line_edit) hlay.addWidget(self.install_button) hlay.addWidget(self.uninstall_button) layout.addWidget(title) layout.addLayout(hlay) layout.addWidget(self.text_area) self.setFixedSize(700, 400) self.setMaximumHeight(800) self.setMaximumWidth(1280) def _install(self): cmd = ['-m', 'pip', 'install'] if running_as_bundled_app() and sys.platform.startswith('linux'): cmd += [ '--no-warn-script-location', '--prefix', user_plugin_dir(), ] self.process.setArguments(cmd + self.line_edit.text().split()) self.text_area.clear() self.process.start() def _uninstall(self): args = ['-m', 'pip', 'uninstall', '-y'] self.process.setArguments(args + self.line_edit.text().split()) self.text_area.clear() self.process.start() def _on_stdout_ready(self): text = self.process.readAllStandardOutput().data().decode() self.text_area.append(text)
class CoalaWidget(QWidget): """ coala Widget """ DATAPATH = get_conf_path('coala.results') VERSION = '' redirect_stdio = Signal(bool) def __init__(self, parent, max_entries=100, options_button=None, text_color=None, prevrate_color=None): QWidget.__init__(self, parent) self.setWindowTitle('coala') self.output = None self.error_output = None self.text_color = text_color self.prevrate_color = prevrate_color self.max_entries = max_entries self.rdata = [] if osp.isfile(self.DATAPATH): try: data = pickle.loads(open(self.DATAPATH, 'rb').read()) self.rdata = data[:] except (EOFError, ImportError): print('error!!') pass self.filecombo = PythonModulesComboBox(self) self.start_button = create_toolbutton(self, icon=ima.icon('run'), text=_("Analyze"), tip=_("Run analysis"), triggered=self.start, text_beside_icon=True) self.stop_button = create_toolbutton(self, icon=ima.icon('stop'), text=_("Stop"), tip=_("Stop current analysis"), text_beside_icon=True) self.filecombo.valid.connect(self.start_button.setEnabled) self.filecombo.valid.connect(self.show_data) browse_button = create_toolbutton(self, icon=ima.icon('fileopen'), tip=_('Select Python file'), triggered=self.select_file) self.ratelabel = QLabel() self.datelabel = QLabel() self.log_button = create_toolbutton(self, icon=ima.icon('log'), text=_("Output"), text_beside_icon=True, tip=_("Complete output"), triggered=self.show_log) self.treewidget = ResultsTree(self) hlayout1 = QHBoxLayout() hlayout1.addWidget(self.filecombo) hlayout1.addWidget(browse_button) hlayout1.addWidget(self.start_button) hlayout1.addWidget(self.stop_button) if options_button: hlayout1.addWidget(options_button) hlayout2 = QHBoxLayout() hlayout2.addWidget(self.ratelabel) hlayout2.addStretch() hlayout2.addWidget(self.datelabel) hlayout2.addStretch() hlayout2.addWidget(self.log_button) layout = QVBoxLayout() layout.addLayout(hlayout1) layout.addLayout(hlayout2) layout.addWidget(self.treewidget) self.setLayout(layout) self.process = None self.set_running_state(False) self.show_data() if self.rdata: self.remove_obsolete_items() self.filecombo.addItems(self.get_filenames()) self.start_button.setEnabled(self.filecombo.is_valid()) else: self.start_button.setEnabled(False) def analyze(self, filename): filename = to_text_string(filename) # filename is a QString instance self.kill_if_running() index, _data = self.get_data(filename) if index is None: self.filecombo.addItem(filename) self.filecombo.setCurrentIndex(self.filecombo.count() - 1) else: self.filecombo.setCurrentIndex(self.filecombo.findText(filename)) self.filecombo.selected() if self.filecombo.is_valid(): self.start() @Slot() def select_file(self): self.redirect_stdio.emit(False) filename, _selfilter = getopenfilename( self, _("Select Python file"), getcwd_or_home(), _("Python files") + " (*.py ; *.pyw)") self.redirect_stdio.emit(True) if filename: self.analyze(filename) def remove_obsolete_items(self): """Removing obsolete items""" self.rdata = [(filename, data) for filename, data in self.rdata if is_module_or_package(filename)] def get_filenames(self): return [filename for filename, _data in self.rdata] def get_data(self, filename): filename = osp.abspath(filename) for index, (fname, data) in enumerate(self.rdata): if fname == filename: return index, data else: return None, None def set_data(self, filename, data): filename = osp.abspath(filename) index, _data = self.get_data(filename) if index is not None: self.rdata.pop(index) self.rdata.insert(0, (filename, data)) self.save() def save(self): while len(self.rdata) > self.max_entries: self.rdata.pop(-1) pickle.dump([self.VERSION] + self.rdata, open(self.DATAPATH, 'wb'), 2) @Slot() def show_log(self): if self.output: TextEditor(self.output, title=_("coala output"), readonly=True, size=(700, 500)).exec_() @Slot() def start(self): filename = to_text_string(self.filecombo.currentText()) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(osp.dirname(filename)) self.process.readyReadStandardOutput.connect(self.read_output) self.process.readyReadStandardError.connect( lambda: self.read_output(error=True)) self.process.finished.connect( lambda ec, es=QProcess.ExitStatus: self.finished(ec, es)) self.stop_button.clicked.connect(self.process.kill) self.output = '' self.error_output = '' clver = COALA_VER if clver is not None: c_args = ['-m', 'run_coala'] self.process.start(sys.executable, c_args) running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start")) def set_running_state(self, state=True): self.start_button.setEnabled(not state) self.stop_button.setEnabled(state) def read_output(self, error=False): if error: self.process.setReadChannel(QProcess.StandardError) else: self.process.setReadChannel(QProcess.StandardOutput) qba = QByteArray() while self.process.bytesAvailable(): if error: qba += self.process.readAllStandardError() else: qba += self.process.readAllStandardOutput() text = to_text_string(locale_codec.toUnicode(qba.data())) if error: self.error_output += text else: self.output += text def finished(self, exit_code, exit_status): self.set_running_state(False) if not self.output: if self.error_output: QMessageBox.critical(self, _("Error"), self.error_output) print("coala error:\n\n" + self.error_output, file=sys.stderr) return results = {'C:': []} literal_dict = ast.literal_eval(self.output) line_numbers = [] char_numbers = [] bear_values = [] msg_values = [] for line in literal_dict['C']: print(line) for i in line: line_num = re.compile('(.+)~') val = line_num.findall(i) for line_nb in val: if line_nb: line_numbers.append(line_nb) for j in line: char_num = re.compile('(.*);') val = char_num.findall(j) for char_nm in val: if char_nm: char_numbers.append(char_nm) for k in line: bear_val = re.compile('(.*):') val = bear_val.findall(k) for bear_val in val: if bear_val: bear_values.append(bear_val) for m in line: msg_val = re.compile(':(.*)') val = msg_val.findall(m) for msg_val in val: if msg_val: msg_values.append(msg_val) item = list(zip(line_numbers, char_numbers, bear_values, msg_values)) for i in item: results['C:'].append(i) filename = to_text_string(self.filecombo.currentText()) self.set_data(filename, results) self.output = self.error_output + self.output self.show_data(justanalyzed=True) def kill_if_running(self): if self.process is not None: if self.process.state() == QProcess.Running: self.process.kill() self.process.waitForFinished() def show_data(self, justanalyzed=False): if not justanalyzed: self.output = None self.log_button.setEnabled(self.output is not None \ and len(self.output) > 0) self.kill_if_running() filename = to_text_string(self.filecombo.currentText()) if not filename: return _index, data = self.get_data(filename) if data is None: self.treewidget.clear_results() else: results = data self.treewidget.set_results(filename, results)
class ExternalPythonShell(ExternalShellBase): """External Shell widget: execute Python script in a separate process""" SHELL_CLASS = ExtPythonShellWidget sig_pdb = Signal(str, int) open_file = Signal(str, int) ipython_kernel_start_error = Signal(str) create_ipython_client = Signal(str) started = Signal() sig_finished = Signal() def __init__(self, parent=None, fname=None, wdir=None, interact=False, debug=False, post_mortem=False, path=[], python_args='', ipykernel=False, arguments='', stand_alone=None, umr_enabled=True, umr_namelist=[], umr_verbose=True, pythonstartup=None, pythonexecutable=None, external_interpreter=False, monitor_enabled=True, mpl_backend=None, ets_backend='qt4', qt_api=None, merge_output_channels=False, colorize_sys_stderr=False, autorefresh_timeout=3000, autorefresh_state=True, light_background=True, menu_actions=None, show_buttons_inside=True, show_elapsed_time=True): assert qt_api in (None, 'pyqt', 'pyside', 'pyqt5') self.namespacebrowser = None # namespace browser widget! self.dialog_manager = DialogManager() self.stand_alone = stand_alone # stand alone settings (None: plugin) self.interact = interact self.is_ipykernel = ipykernel self.pythonstartup = pythonstartup self.pythonexecutable = pythonexecutable self.external_interpreter = external_interpreter self.monitor_enabled = monitor_enabled self.mpl_backend = mpl_backend self.ets_backend = ets_backend self.qt_api = qt_api self.merge_output_channels = merge_output_channels self.colorize_sys_stderr = colorize_sys_stderr self.umr_enabled = umr_enabled self.umr_namelist = umr_namelist self.umr_verbose = umr_verbose self.autorefresh_timeout = autorefresh_timeout self.autorefresh_state = autorefresh_state self.namespacebrowser_button = None self.cwd_button = None self.env_button = None self.syspath_button = None self.terminate_button = None self.notification_thread = None ExternalShellBase.__init__(self, parent=parent, fname=fname, wdir=wdir, history_filename='history.py', light_background=light_background, menu_actions=menu_actions, show_buttons_inside=show_buttons_inside, show_elapsed_time=show_elapsed_time) if self.pythonexecutable is None: self.pythonexecutable = get_python_executable() self.python_args = None if python_args: assert is_text_string(python_args) self.python_args = python_args assert is_text_string(arguments) self.arguments = arguments self.connection_file = None if self.is_ipykernel: self.interact = False # Running our custom startup script for IPython kernels: # (see spyderlib/widgets/externalshell/start_ipython_kernel.py) self.fname = get_module_source_path( 'spyderlib.widgets.externalshell', 'start_ipython_kernel.py') self.shell.set_externalshell(self) self.toggle_globals_explorer(False) self.interact_action.setChecked(self.interact) self.debug_action.setChecked(debug) self.introspection_socket = None self.is_interpreter = fname is None if self.is_interpreter: self.terminate_button.hide() self.post_mortem_action.setChecked(post_mortem and not self.is_interpreter) # Additional python path list self.path = path self.shell.path = path def set_introspection_socket(self, introspection_socket): self.introspection_socket = introspection_socket if self.namespacebrowser is not None: settings = self.namespacebrowser.get_view_settings() communicate(introspection_socket, 'set_remote_view_settings()', settings=[settings]) def set_autorefresh_timeout(self, interval): if self.introspection_socket is not None: try: communicate(self.introspection_socket, "set_monitor_timeout(%d)" % interval) except socket.error: pass def closeEvent(self, event): self.quit_monitor() ExternalShellBase.closeEvent(self, event) def get_toolbar_buttons(self): ExternalShellBase.get_toolbar_buttons(self) if self.namespacebrowser_button is None \ and self.stand_alone is not None: self.namespacebrowser_button = create_toolbutton(self, text=_("Variables"), icon=ima.icon('dictedit'), tip=_("Show/hide global variables explorer"), toggled=self.toggle_globals_explorer, text_beside_icon=True) if self.terminate_button is None: self.terminate_button = create_toolbutton(self, text=_("Terminate"), icon=ima.icon('stop'), tip=_("Attempts to stop the process. The process\n" "may not exit as a result of clicking this\n" "button (it is given the chance to prompt\n" "the user for any unsaved files, etc).")) buttons = [] if self.namespacebrowser_button is not None: buttons.append(self.namespacebrowser_button) buttons += [self.run_button, self.terminate_button, self.kill_button, self.options_button] return buttons def get_options_menu(self): ExternalShellBase.get_options_menu(self) self.interact_action = create_action(self, _("Interact")) self.interact_action.setCheckable(True) self.debug_action = create_action(self, _("Debug")) self.debug_action.setCheckable(True) self.args_action = create_action(self, _("Arguments..."), triggered=self.get_arguments) self.post_mortem_action = create_action(self, _("Post Mortem Debug")) self.post_mortem_action.setCheckable(True) run_settings_menu = QMenu(_("Run settings"), self) add_actions(run_settings_menu, (self.interact_action, self.debug_action, self.args_action, self.post_mortem_action)) self.cwd_button = create_action(self, _("Working directory"), icon=ima.icon('DirOpenIcon'), tip=_("Set current working directory"), triggered=self.set_current_working_directory) self.env_button = create_action(self, _("Environment variables"), icon=ima.icon('environ'), triggered=self.show_env) self.syspath_button = create_action(self, _("Show sys.path contents"), icon=ima.icon('syspath'), triggered=self.show_syspath) actions = [run_settings_menu, self.show_time_action, None, self.cwd_button, self.env_button, self.syspath_button] if self.menu_actions is not None: actions += [None]+self.menu_actions return actions def is_interpreter(self): """Return True if shellwidget is a Python interpreter""" return self.is_interpreter def get_shell_widget(self): if self.stand_alone is None: return self.shell else: self.namespacebrowser = NamespaceBrowser(self) settings = self.stand_alone self.namespacebrowser.set_shellwidget(self) self.namespacebrowser.setup(**settings) self.namespacebrowser.sig_collapse.connect( lambda: self.toggle_globals_explorer(False)) # Shell splitter self.splitter = splitter = QSplitter(Qt.Vertical, self) self.splitter.splitterMoved.connect(self.splitter_moved) splitter.addWidget(self.shell) splitter.setCollapsible(0, False) splitter.addWidget(self.namespacebrowser) splitter.setStretchFactor(0, 1) splitter.setStretchFactor(1, 0) splitter.setHandleWidth(5) splitter.setSizes([2, 1]) return splitter def get_icon(self): return ima.icon('python') def set_buttons_runnning_state(self, state): ExternalShellBase.set_buttons_runnning_state(self, state) self.interact_action.setEnabled(not state and not self.is_interpreter) self.debug_action.setEnabled(not state and not self.is_interpreter) self.args_action.setEnabled(not state and not self.is_interpreter) self.post_mortem_action.setEnabled(not state and not self.is_interpreter) if state: if self.arguments: argstr = _("Arguments: %s") % self.arguments else: argstr = _("No argument") else: argstr = _("Arguments...") self.args_action.setText(argstr) self.terminate_button.setVisible(not self.is_interpreter and state) if not state: self.toggle_globals_explorer(False) for btn in (self.cwd_button, self.env_button, self.syspath_button): btn.setEnabled(state and self.monitor_enabled) if self.namespacebrowser_button is not None: self.namespacebrowser_button.setEnabled(state) def set_namespacebrowser(self, namespacebrowser): """ Set namespace browser *widget* Note: this method is not used in stand alone mode """ self.namespacebrowser = namespacebrowser self.configure_namespacebrowser() def configure_namespacebrowser(self): """Connect the namespace browser to the notification thread""" if self.notification_thread is not None: self.notification_thread.refresh_namespace_browser.connect( self.namespacebrowser.refresh_table) signal = self.notification_thread.sig_process_remote_view signal.connect(lambda data: self.namespacebrowser.process_remote_view(data)) def create_process(self): self.shell.clear() self.process = QProcess(self) if self.merge_output_channels: self.process.setProcessChannelMode(QProcess.MergedChannels) else: self.process.setProcessChannelMode(QProcess.SeparateChannels) self.shell.wait_for_ready_read.connect( lambda: self.process.waitForReadyRead(250)) # Working directory if self.wdir is not None: self.process.setWorkingDirectory(self.wdir) #-------------------------Python specific------------------------------ # Python arguments p_args = ['-u'] if DEBUG >= 3: p_args += ['-v'] p_args += get_python_args(self.fname, self.python_args, self.interact_action.isChecked(), self.debug_action.isChecked(), self.arguments) env = [to_text_string(_path) for _path in self.process.systemEnvironment()] if self.pythonstartup: env.append('PYTHONSTARTUP=%s' % self.pythonstartup) #-------------------------Python specific------------------------------- # Post mortem debugging if self.post_mortem_action.isChecked(): env.append('SPYDER_EXCEPTHOOK=True') # Set standard input/output encoding for Python consoles # (IPython handles it on its own) # See http://stackoverflow.com/q/26312400/438386, specifically # the comments of Martijn Pieters if not self.is_ipykernel: env.append('PYTHONIOENCODING=UTF-8') # Monitor if self.monitor_enabled: env.append('SPYDER_SHELL_ID=%s' % id(self)) env.append('SPYDER_AR_TIMEOUT=%d' % self.autorefresh_timeout) env.append('SPYDER_AR_STATE=%r' % self.autorefresh_state) from spyderlib.widgets.externalshell import introspection introspection_server = introspection.start_introspection_server() introspection_server.register(self) notification_server = introspection.start_notification_server() self.notification_thread = notification_server.register(self) self.notification_thread.sig_pdb.connect( lambda fname, lineno: self.sig_pdb.emit(fname, lineno)) self.notification_thread.new_ipython_kernel.connect( lambda args: self.create_ipython_client.emit(args)) self.notification_thread.open_file.connect( lambda fname, lineno: self.open_file.emit(fname, lineno)) if self.namespacebrowser is not None: self.configure_namespacebrowser() env.append('SPYDER_I_PORT=%d' % introspection_server.port) env.append('SPYDER_N_PORT=%d' % notification_server.port) # External modules options if not self.is_ipykernel: env.append('ETS_TOOLKIT=%s' % self.ets_backend) if self.mpl_backend is not None: backends = {0: 'Automatic', 1: 'None', 2: 'TkAgg'} env.append('SPY_MPL_BACKEND=%s' % backends[self.mpl_backend]) if self.qt_api and not self.is_ipykernel: env.append('QT_API=%s' % self.qt_api) env.append('COLORIZE_SYS_STDERR=%s' % self.colorize_sys_stderr) # # Socket-based alternative (see input hook in sitecustomize.py): # if self.install_qt_inputhook: # from PyQt4.QtNetwork import QLocalServer # self.local_server = QLocalServer() # self.local_server.listen(str(id(self))) # User Module Deleter if self.is_interpreter: env.append('UMR_ENABLED=%r' % self.umr_enabled) env.append('UMR_NAMELIST=%s' % ','.join(self.umr_namelist)) env.append('UMR_VERBOSE=%r' % self.umr_verbose) env.append('MATPLOTLIB_ION=True') else: if self.interact: env.append('MATPLOTLIB_ION=True') else: env.append('MATPLOTLIB_ION=False') # IPython kernel env.append('IPYTHON_KERNEL=%r' % self.is_ipykernel) # External interpreter env.append('EXTERNAL_INTERPRETER=%r' % self.external_interpreter) # Add sitecustomize path to path list pathlist = [] scpath = osp.dirname(osp.abspath(__file__)) pathlist.append(scpath) # Adding Spyder path pathlist += self.path # Adding path list to PYTHONPATH environment variable add_pathlist_to_PYTHONPATH(env, pathlist) #-------------------------Python specific------------------------------ self.process.readyReadStandardOutput.connect(self.write_output) self.process.readyReadStandardError.connect(self.write_error) self.process.finished.connect(lambda ec, es=QProcess.ExitStatus: self.finished(ec, es)) self.sig_finished.connect(self.dialog_manager.close_all) self.terminate_button.clicked.connect(self.process.terminate) self.kill_button.clicked.connect(self.process.kill) #-------------------------Python specific------------------------------ # Fixes for our Mac app: # 1. PYTHONPATH and PYTHONHOME are set while bootstrapping the app, # but their values are messing sys.path for external interpreters # (e.g. EPD) so we need to remove them from the environment. # 2. Set PYTHONPATH again but without grabbing entries defined in the # environment (Fixes Issue 1321) # 3. Remove PYTHONOPTIMIZE from env so that we can have assert # statements working with our interpreters (See Issue 1281) if running_in_mac_app(): if MAC_APP_NAME not in self.pythonexecutable: env = [p for p in env if not (p.startswith('PYTHONPATH') or \ p.startswith('PYTHONHOME'))] # 1. add_pathlist_to_PYTHONPATH(env, pathlist, drop_env=True) # 2. env = [p for p in env if not p.startswith('PYTHONOPTIMIZE')] # 3. processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) self.process.start(self.pythonexecutable, p_args) #-------------------------Python specific------------------------------ running = self.process.waitForStarted(3000) self.set_running_state(running) if not running: if self.is_ipykernel: self.ipython_kernel_start_error.emit( _("The kernel failed to start!! That's all we know... " "Please close this console and open a new one.")) else: QMessageBox.critical(self, _("Error"), _("A Python console failed to start!")) else: self.shell.setFocus() self.started.emit() return self.process def finished(self, exit_code, exit_status): """Reimplement ExternalShellBase method""" if self.is_ipykernel and exit_code == 1: self.ipython_kernel_start_error.emit(self.shell.get_text_with_eol()) ExternalShellBase.finished(self, exit_code, exit_status) self.introspection_socket = None #============================================================================== # Input/Output #============================================================================== def write_error(self): if os.name == 'nt': #---This is apparently necessary only on Windows (not sure though): # emptying standard output buffer before writing error output self.process.setReadChannel(QProcess.StandardOutput) if self.process.waitForReadyRead(1): self.write_output() self.shell.write_error(self.get_stderr()) QApplication.processEvents() def send_to_process(self, text): if not self.is_running(): return if not is_text_string(text): text = to_text_string(text) if self.mpl_backend == 0 and os.name == 'nt' and \ self.introspection_socket is not None: communicate(self.introspection_socket, "toggle_inputhook_flag(True)") # # Socket-based alternative (see input hook in sitecustomize.py): # while self.local_server.hasPendingConnections(): # self.local_server.nextPendingConnection().write('go!') if any([text == cmd for cmd in ['%ls', '%pwd', '%scientific']]) or \ any([text.startswith(cmd) for cmd in ['%cd ', '%clear ']]): text = 'evalsc(r"%s")\n' % text if not text.endswith('\n'): text += '\n' self.process.write(to_binary_string(text, 'utf8')) self.process.waitForBytesWritten(-1) # Eventually write prompt faster (when hitting Enter continuously) # -- necessary/working on Windows only: if os.name == 'nt': self.write_error() def keyboard_interrupt(self): if self.introspection_socket is not None: communicate(self.introspection_socket, "thread.interrupt_main()") def quit_monitor(self): if self.introspection_socket is not None: try: write_packet(self.introspection_socket, "thread.exit()") except socket.error: pass #============================================================================== # Globals explorer #============================================================================== @Slot(bool) def toggle_globals_explorer(self, state): if self.stand_alone is not None: self.splitter.setSizes([1, 1 if state else 0]) self.namespacebrowser_button.setChecked(state) if state and self.namespacebrowser is not None: self.namespacebrowser.refresh_table() def splitter_moved(self, pos, index): self.namespacebrowser_button.setChecked( self.splitter.sizes()[1] ) #============================================================================== # Misc. #============================================================================== @Slot() def set_current_working_directory(self): """Set current working directory""" cwd = self.shell.get_cwd() self.redirect_stdio.emit(False) directory = getexistingdirectory(self, _("Select directory"), cwd) if directory: self.shell.set_cwd(directory) self.redirect_stdio.emit(True) @Slot() def show_env(self): """Show environment variables""" get_func = self.shell.get_env set_func = self.shell.set_env self.dialog_manager.show(RemoteEnvDialog(get_func, set_func)) @Slot() def show_syspath(self): """Show sys.path contents""" editor = CollectionsEditor() editor.setup(self.shell.get_syspath(), title="sys.path", readonly=True, width=600, icon=ima.icon('syspath')) self.dialog_manager.show(editor)
class TestRunner(QObject): """ Class for running tests with py.test or nose. All communication back to the caller is done via signals. Attributes ---------- process : QProcess or None Process running the unit test suite. resultfilename : str Name of file in which test results are stored. Signals ------- sig_finished(list of TestResult, str) Emitted when test process finishes. First argument contains the test results, second argument contains the output of the test process. """ sig_finished = Signal(object, str) def __init__(self, widget, resultfilename=None): """ Construct test runner. Parameters ---------- widget : UnitTestWidget Unit test widget which constructs the test runner. resultfilename : str or None Name of file in which to store test results. If None, use default. """ QObject.__init__(self, widget) self.process = None if resultfilename is None: self.resultfilename = os.path.join(tempfile.gettempdir(), 'unittest.results') else: self.resultfilename = resultfilename def start(self, config, pythonpath): """ Start process which will run the unit test suite. The process is run in the working directory specified in 'config', with the directories in `pythonpath` added to the Python path for the test process. The test results are written to the file `self.resultfilename`. The standard output and error are also recorded. Once the process is finished, `self.finished()` will be called. Parameters ---------- config : TestConfig Unit test configuration. pythonpath : list of str List of directories to be added to the Python path Raises ------ RuntimeError If process failed to start. """ framework = config.framework wdir = config.wdir self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.MergedChannels) self.process.setWorkingDirectory(wdir) self.process.finished.connect(self.finished) if pythonpath is not None: env = [ to_text_string(_pth) for _pth in self.process.systemEnvironment() ] add_pathlist_to_PYTHONPATH(env, pythonpath) processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) if framework == 'nose': executable = 'nosetests' p_args = [ '--with-xunit', '--xunit-file={}'.format(self.resultfilename) ] elif framework == 'py.test': executable = 'py.test' p_args = ['--junit-xml', self.resultfilename] else: raise ValueError('Unknown framework') if os.name == 'nt': executable += '.exe' try: os.remove(self.resultfilename) except OSError: pass self.process.start(executable, p_args) running = self.process.waitForStarted() if not running: raise RuntimeError def finished(self): """ Called when the unit test process has finished. This function reads the results and emits `sig_finished`. """ qbytearray = self.process.readAllStandardOutput() locale_codec = QTextCodec.codecForLocale() output = to_text_string(locale_codec.toUnicode(qbytearray.data())) testresults = self.load_data() self.sig_finished.emit(testresults, output) def kill_if_running(self): """Kill testing process if it is running.""" if self.process and self.process.state() == QProcess.Running: self.process.kill() def load_data(self): """ Read and parse unit test results. This function reads the unit test results from the file with name `self.resultfilename` and parses them. The file should contain the test results in JUnitXML format. Returns ------- list of TestResult Unit test results. """ try: data = etree.parse(self.resultfilename).getroot() except OSError: data = [] testresults = [] for testcase in data: category = Category.OK status = 'ok' name = '{0}.{1}'.format(testcase.get('classname'), testcase.get('name')) message = '' time = float(testcase.get('time')) extras = [] for child in testcase: if child.tag in ('error', 'failure', 'skipped'): if child.tag == 'skipped': category = Category.SKIP else: category = Category.FAIL status = child.tag type_ = child.get('type') message = child.get('message', default='') if type_ and message: message = '{0}: {1}'.format(type_, message) elif type_: message = type_ if child.text: extras.append(child.text) elif child.tag in ('system-out', 'system-err'): if child.tag == 'system-out': heading = _('Captured stdout') else: heading = _('Captured stderr') contents = child.text.rstrip('\n') extras.append('----- {} -----\n{}'.format( heading, contents)) extra_text = '\n\n'.join(extras) testresults.append( TestResult(category, status, name, message, time, extra_text)) return testresults
class MemoryProfilerWidget(QWidget): """ Memory profiler widget. """ DATAPATH = get_conf_path('memoryprofiler.results') VERSION = '0.0.1' redirect_stdio = Signal(bool) sig_finished = Signal() def __init__(self, parent): QWidget.__init__(self, parent) self.setWindowTitle("Memory profiler") self.output = None self.error_output = None self.use_colors = True self._last_wdir = None self._last_args = None self._last_pythonpath = None self.filecombo = PythonModulesComboBox(self) self.start_button = create_toolbutton(self, icon=get_icon('run.png'), text=_("Profile memory usage"), tip=_("Run memory profiler"), triggered=self.start, text_beside_icon=True) self.stop_button = create_toolbutton(self, icon=get_icon('terminate.png'), text=_("Stop"), tip=_("Stop current profiling"), text_beside_icon=True) self.filecombo.valid.connect(self.start_button.setEnabled) #self.connect(self.filecombo, SIGNAL('valid(bool)'), self.show_data) # FIXME: The combobox emits this signal on almost any event # triggering show_data() too early, too often. browse_button = create_toolbutton(self, icon=get_icon('fileopen.png'), tip=_('Select Python script'), triggered=self.select_file) self.datelabel = QLabel() self.log_button = create_toolbutton(self, icon=get_icon('log.png'), text=_("Output"), text_beside_icon=True, tip=_("Show program's output"), triggered=self.show_log) self.datatree = MemoryProfilerDataTree(self) self.collapse_button = create_toolbutton( self, icon=get_icon('collapse.png'), triggered=lambda dD=-1: self.datatree.collapseAll(), tip=_('Collapse all')) self.expand_button = create_toolbutton( self, icon=get_icon('expand.png'), triggered=lambda dD=1: self.datatree.expandAll(), tip=_('Expand all')) hlayout1 = QHBoxLayout() hlayout1.addWidget(self.filecombo) hlayout1.addWidget(browse_button) hlayout1.addWidget(self.start_button) hlayout1.addWidget(self.stop_button) hlayout2 = QHBoxLayout() hlayout2.addWidget(self.collapse_button) hlayout2.addWidget(self.expand_button) hlayout2.addStretch() hlayout2.addWidget(self.datelabel) hlayout2.addStretch() hlayout2.addWidget(self.log_button) layout = QVBoxLayout() layout.addLayout(hlayout1) layout.addLayout(hlayout2) layout.addWidget(self.datatree) self.setLayout(layout) self.process = None self.set_running_state(False) self.start_button.setEnabled(False) if not is_memoryprofiler_installed(): for widget in (self.datatree, self.filecombo, self.log_button, self.start_button, self.stop_button, browse_button, self.collapse_button, self.expand_button): widget.setDisabled(True) text = _( '<b>Please install the <a href="%s">memory_profiler module</a></b>' ) % WEBSITE_URL self.datelabel.setText(text) self.datelabel.setOpenExternalLinks(True) else: pass # self.show_data() def analyze(self, filename, wdir=None, args=None, pythonpath=None, use_colors=True): self.use_colors = use_colors if not is_memoryprofiler_installed(): return self.kill_if_running() #index, _data = self.get_data(filename) index = None # FIXME: storing data is not implemented yet if index is None: self.filecombo.addItem(filename) self.filecombo.setCurrentIndex(self.filecombo.count() - 1) else: self.filecombo.setCurrentIndex(self.filecombo.findText(filename)) self.filecombo.selected() if self.filecombo.is_valid(): if wdir is None: wdir = osp.dirname(filename) self.start(wdir, args, pythonpath) def select_file(self): self.redirect_stdio.emit(False) filename, _selfilter = getopenfilename( self, _("Select Python script"), getcwd(), _("Python scripts") + " (*.py ; *.pyw)") self.redirect_stdio.emit(False) if filename: self.analyze(filename) def show_log(self): if self.output: TextEditor(self.output, title=_("Memory profiler output"), readonly=True, size=(700, 500)).exec_() def show_errorlog(self): if self.error_output: TextEditor(self.error_output, title=_("Memory profiler output"), readonly=True, size=(700, 500)).exec_() def start(self, wdir=None, args=None, pythonpath=None): filename = to_text_string(self.filecombo.currentText()) if wdir is None: wdir = self._last_wdir if wdir is None: wdir = osp.basename(filename) if args is None: args = self._last_args if args is None: args = [] if pythonpath is None: pythonpath = self._last_pythonpath self._last_wdir = wdir self._last_args = args self._last_pythonpath = pythonpath self.datelabel.setText(_('Profiling, please wait...')) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(wdir) self.process.readyReadStandardOutput.connect(self.read_output) self.process.readyReadStandardError.connect( lambda: self.read_output(error=True)) self.process.finished.connect(self.finished) self.stop_button.clicked.connect(self.process.kill) if pythonpath is not None: env = [ to_text_string(_pth) for _pth in self.process.systemEnvironment() ] add_pathlist_to_PYTHONPATH(env, pythonpath) processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) self.output = '' self.error_output = '' # remove previous results, since memory_profiler appends to output file # instead of replacing if osp.isfile(self.DATAPATH): os.remove(self.DATAPATH) if os.name == 'nt': # On Windows, one has to replace backslashes by slashes to avoid # confusion with escape characters (otherwise, for example, '\t' # will be interpreted as a tabulation): filename = osp.normpath(filename).replace(os.sep, '/') p_args = [ '-m', 'memory_profiler', '-o', '"' + self.DATAPATH + '"', '"' + filename + '"' ] if args: p_args.extend(programs.shell_split(args)) executable = get_python_executable() executable += ' ' + ' '.join(p_args) executable = executable.replace(os.sep, '/') self.process.start(executable) else: p_args = ['-m', 'memory_profiler', '-o', self.DATAPATH, filename] if args: p_args.extend(programs.shell_split(args)) executable = get_python_executable() self.process.start(executable, p_args) running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start")) def set_running_state(self, state=True): self.start_button.setEnabled(not state) self.stop_button.setEnabled(state) def read_output(self, error=False): if error: self.process.setReadChannel(QProcess.StandardError) else: self.process.setReadChannel(QProcess.StandardOutput) qba = QByteArray() while self.process.bytesAvailable(): if error: qba += self.process.readAllStandardError() else: qba += self.process.readAllStandardOutput() text = to_text_string(locale_codec.toUnicode(qba.data())) if error: self.error_output += text else: self.output += text def finished(self): self.set_running_state(False) self.show_errorlog() # If errors occurred, show them. self.output = self.error_output + self.output # FIXME: figure out if show_data should be called here or # as a signal from the combobox self.show_data(justanalyzed=True) self.sig_finished.emit() def kill_if_running(self): if self.process is not None: if self.process.state() == QProcess.Running: self.process.kill() self.process.waitForFinished() def show_data(self, justanalyzed=False): if not justanalyzed: self.output = None self.log_button.setEnabled(self.output is not None and len(self.output) > 0) self.kill_if_running() filename = to_text_string(self.filecombo.currentText()) if not filename: return self.datatree.load_data(self.DATAPATH) self.datelabel.setText(_('Sorting data, please wait...')) QApplication.processEvents() self.datatree.show_tree() text_style = "<span style=\'color: #444444\'><b>%s </b></span>" date_text = text_style % time.strftime("%d %b %Y %H:%M", time.localtime()) self.datelabel.setText(date_text)
class PylintWidget(QWidget): """ Pylint widget """ DATAPATH = get_conf_path('pylint.results') VERSION = '1.1.0' redirect_stdio = Signal(bool) def __init__(self, parent, max_entries=100): QWidget.__init__(self, parent) self.setWindowTitle("Pylint") self.output = None self.error_output = None self.max_entries = max_entries self.rdata = [] if osp.isfile(self.DATAPATH): try: data = pickle.loads(open(self.DATAPATH, 'rb').read()) if data[0] == self.VERSION: self.rdata = data[1:] except (EOFError, ImportError): pass self.filecombo = PythonModulesComboBox(self) self.start_button = create_toolbutton(self, icon=ima.icon('run'), text=_("Analyze"), tip=_("Run analysis"), triggered=self.start, text_beside_icon=True) self.stop_button = create_toolbutton(self, icon=ima.icon('stop'), text=_("Stop"), tip=_("Stop current analysis"), text_beside_icon=True) self.filecombo.valid.connect(self.start_button.setEnabled) self.filecombo.valid.connect(self.show_data) browse_button = create_toolbutton(self, icon=ima.icon('fileopen'), tip=_('Select Python file'), triggered=self.select_file) self.ratelabel = QLabel() self.datelabel = QLabel() self.log_button = create_toolbutton(self, icon=ima.icon('log'), text=_("Output"), text_beside_icon=True, tip=_("Complete output"), triggered=self.show_log) self.treewidget = ResultsTree(self) hlayout1 = QHBoxLayout() hlayout1.addWidget(self.filecombo) hlayout1.addWidget(browse_button) hlayout1.addWidget(self.start_button) hlayout1.addWidget(self.stop_button) hlayout2 = QHBoxLayout() hlayout2.addWidget(self.ratelabel) hlayout2.addStretch() hlayout2.addWidget(self.datelabel) hlayout2.addStretch() hlayout2.addWidget(self.log_button) layout = QVBoxLayout() layout.addLayout(hlayout1) layout.addLayout(hlayout2) layout.addWidget(self.treewidget) self.setLayout(layout) self.process = None self.set_running_state(False) self.show_data() if self.rdata: self.remove_obsolete_items() self.filecombo.addItems(self.get_filenames()) else: self.start_button.setEnabled(False) def analyze(self, filename): filename = to_text_string(filename) # filename is a QString instance self.kill_if_running() index, _data = self.get_data(filename) if index is None: self.filecombo.addItem(filename) self.filecombo.setCurrentIndex(self.filecombo.count() - 1) else: self.filecombo.setCurrentIndex(self.filecombo.findText(filename)) self.filecombo.selected() if self.filecombo.is_valid(): self.start() @Slot() def select_file(self): self.redirect_stdio.emit(False) filename, _selfilter = getopenfilename( self, _("Select Python file"), getcwd_or_home(), _("Python files") + " (*.py ; *.pyw)") self.redirect_stdio.emit(True) if filename: self.analyze(filename) def remove_obsolete_items(self): """Removing obsolete items""" self.rdata = [(filename, data) for filename, data in self.rdata if is_module_or_package(filename)] def get_filenames(self): return [filename for filename, _data in self.rdata] def get_data(self, filename): filename = osp.abspath(filename) for index, (fname, data) in enumerate(self.rdata): if fname == filename: return index, data else: return None, None def set_data(self, filename, data): filename = osp.abspath(filename) index, _data = self.get_data(filename) if index is not None: self.rdata.pop(index) self.rdata.insert(0, (filename, data)) self.save() def save(self): while len(self.rdata) > self.max_entries: self.rdata.pop(-1) pickle.dump([self.VERSION] + self.rdata, open(self.DATAPATH, 'wb'), 2) @Slot() def show_log(self): if self.output: TextEditor(self.output, title=_("Pylint output"), readonly=True, size=(700, 500)).exec_() @Slot() def start(self): filename = to_text_string(self.filecombo.currentText()) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(osp.dirname(filename)) self.process.readyReadStandardOutput.connect(self.read_output) self.process.readyReadStandardError.connect( lambda: self.read_output(error=True)) self.process.finished.connect( lambda ec, es=QProcess.ExitStatus: self.finished(ec, es)) self.stop_button.clicked.connect(self.process.kill) self.output = '' self.error_output = '' plver = PYLINT_VER if plver is not None: p_args = ['-m', 'pylint', '--output-format=text'] if plver.split('.')[0] == '0': p_args += ['-i', 'yes'] else: # Option '-i' (alias for '--include-ids') was removed in pylint # 1.0 p_args += ["--msg-template='{msg_id}:{line:3d},"\ "{column}: {obj}: {msg}"] p_args += [osp.basename(filename)] else: p_args = [osp.basename(filename)] self.process.start(sys.executable, p_args) running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start")) def set_running_state(self, state=True): self.start_button.setEnabled(not state) self.stop_button.setEnabled(state) def read_output(self, error=False): if error: self.process.setReadChannel(QProcess.StandardError) else: self.process.setReadChannel(QProcess.StandardOutput) qba = QByteArray() while self.process.bytesAvailable(): if error: qba += self.process.readAllStandardError() else: qba += self.process.readAllStandardOutput() text = to_text_string(locale_codec.toUnicode(qba.data())) if error: self.error_output += text else: self.output += text def finished(self, exit_code, exit_status): self.set_running_state(False) if not self.output: if self.error_output: QMessageBox.critical(self, _("Error"), self.error_output) print("pylint error:\n\n" + self.error_output, file=sys.stderr) return # Convention, Refactor, Warning, Error results = {'C:': [], 'R:': [], 'W:': [], 'E:': []} txt_module = '************* Module ' module = '' # Should not be needed - just in case something goes wrong for line in self.output.splitlines(): if line.startswith(txt_module): # New module module = line[len(txt_module):] continue # Supporting option include-ids: ('R3873:' instead of 'R:') if not re.match('^[CRWE]+([0-9]{4})?:', line): continue i1 = line.find(':') if i1 == -1: continue msg_id = line[:i1] i2 = line.find(':', i1 + 1) if i2 == -1: continue line_nb = line[i1 + 1:i2].strip() if not line_nb: continue line_nb = int(line_nb.split(',')[0]) message = line[i2 + 1:] item = (module, line_nb, message, msg_id) results[line[0] + ':'].append(item) # Rate rate = None txt_rate = 'Your code has been rated at ' i_rate = self.output.find(txt_rate) if i_rate > 0: i_rate_end = self.output.find('/10', i_rate) if i_rate_end > 0: rate = self.output[i_rate + len(txt_rate):i_rate_end] # Previous run previous = '' if rate is not None: txt_prun = 'previous run: ' i_prun = self.output.find(txt_prun, i_rate_end) if i_prun > 0: i_prun_end = self.output.find('/10', i_prun) previous = self.output[i_prun + len(txt_prun):i_prun_end] filename = to_text_string(self.filecombo.currentText()) self.set_data(filename, (time.localtime(), rate, previous, results)) self.output = self.error_output + self.output self.show_data(justanalyzed=True) def kill_if_running(self): if self.process is not None: if self.process.state() == QProcess.Running: self.process.kill() self.process.waitForFinished() def show_data(self, justanalyzed=False): if not justanalyzed: self.output = None self.log_button.setEnabled(self.output is not None \ and len(self.output) > 0) self.kill_if_running() filename = to_text_string(self.filecombo.currentText()) if not filename: return _index, data = self.get_data(filename) if data is None: text = _('Source code has not been rated yet.') self.treewidget.clear_results() date_text = '' else: datetime, rate, previous_rate, results = data if rate is None: text = _('Analysis did not succeed ' '(see output for more details).') self.treewidget.clear_results() date_text = '' else: text_style = "<span style=\'color: #444444\'><b>%s </b></span>" rate_style = "<span style=\'color: %s\'><b>%s</b></span>" prevrate_style = "<span style=\'color: #666666\'>%s</span>" color = "#FF0000" if float(rate) > 5.: color = "#22AA22" elif float(rate) > 3.: color = "#EE5500" text = _('Global evaluation:') text = (text_style % text) + (rate_style % (color, ('%s/10' % rate))) if previous_rate: text_prun = _('previous run:') text_prun = ' (%s %s/10)' % (text_prun, previous_rate) text += prevrate_style % text_prun self.treewidget.set_results(filename, results) date = to_text_string(time.strftime("%d %b %Y %H:%M", datetime), encoding='utf8') date_text = text_style % date self.ratelabel.setText(text) self.datelabel.setText(date_text)
class LSPClient(QObject, LSPMethodProviderMixIn): """Language Server Protocol v3.0 client implementation.""" #: Signal to inform the editor plugin that the client has # started properly and it's ready to be used. sig_initialize = Signal(dict, str) #: Signal to report internal server errors through Spyder's # facilities. sig_server_error = Signal(str) #: Signal to warn the user when either the transport layer or the # server went down sig_went_down = Signal(str) def __init__(self, parent, server_settings={}, folder=getcwd_or_home(), language='python'): QObject.__init__(self) self.manager = parent self.zmq_in_socket = None self.zmq_out_socket = None self.zmq_in_port = None self.zmq_out_port = None self.transport = None self.server = None self.stdio_pid = None self.notifier = None self.language = language self.initialized = False self.ready_to_close = False self.request_seq = 1 self.req_status = {} self.watched_files = {} self.watched_folders = {} self.req_reply = {} self.server_unresponsive = False self.transport_unresponsive = False # Select a free port to start the server. # NOTE: Don't use the new value to set server_setttings['port']!! # That's not required because this doesn't really correspond to a # change in the config settings of the server. Else a server # restart would be generated when doing a # workspace/didChangeConfiguration request. if not server_settings['external']: self.server_port = select_port( default_port=server_settings['port']) else: self.server_port = server_settings['port'] self.server_host = server_settings['host'] self.external_server = server_settings.get('external', False) self.stdio = server_settings.get('stdio', False) # Setting stdio on implies that external_server is off if self.stdio and self.external_server: error = ('If server is set to use stdio communication, ' 'then it cannot be an external server') logger.error(error) raise AssertionError(error) self.folder = folder self.configurations = server_settings.get('configurations', {}) self.client_capabilites = CLIENT_CAPABILITES self.server_capabilites = SERVER_CAPABILITES self.context = zmq.Context() # To set server args self._server_args = server_settings.get('args', '') self._server_cmd = server_settings['cmd'] # Save requests name and id. This is only necessary for testing. self._requests = [] def _get_log_filename(self, kind): """ Get filename to redirect server or transport logs to in debugging mode. Parameters ---------- kind: str It can be "server" or "transport". """ if get_debug_level() == 0: return None fname = '{0}_{1}_{2}.log'.format(kind, self.language, os.getpid()) location = get_conf_path(osp.join('lsp_logs', fname)) # Create directory that contains the file, in case it doesn't # exist if not osp.exists(osp.dirname(location)): os.makedirs(osp.dirname(location)) return location @property def server_log_file(self): """ Filename to redirect the server process stdout/stderr output. """ return self._get_log_filename('server') @property def transport_log_file(self): """ Filename to redirect the transport process stdout/stderr output. """ return self._get_log_filename('transport') @property def server_args(self): """Arguments for the server process.""" args = [] if self.language == 'python': args += [sys.executable, '-m'] args += [self._server_cmd] # Replace host and port placeholders host_and_port = self._server_args.format( host=self.server_host, port=self.server_port) if len(host_and_port) > 0: args += host_and_port.split(' ') if self.language == 'python' and get_debug_level() > 0: args += ['--log-file', self.server_log_file] if get_debug_level() == 2: args.append('-v') elif get_debug_level() == 3: args.append('-vv') return args @property def transport_args(self): """Arguments for the transport process.""" args = [ sys.executable, '-u', osp.join(LOCATION, 'transport', 'main.py'), '--folder', self.folder, '--transport-debug', str(get_debug_level()) ] # Replace host and port placeholders host_and_port = '--server-host {host} --server-port {port} '.format( host=self.server_host, port=self.server_port) args += host_and_port.split(' ') # Add socket ports args += ['--zmq-in-port', str(self.zmq_out_port), '--zmq-out-port', str(self.zmq_in_port)] # Adjustments for stdio/tcp if self.stdio: args += ['--stdio-server'] if get_debug_level() > 0: args += ['--server-log-file', self.server_log_file] args += self.server_args else: args += ['--external-server'] return args def create_transport_sockets(self): """Create PyZMQ sockets for transport.""" self.zmq_out_socket = self.context.socket(zmq.PAIR) self.zmq_out_port = self.zmq_out_socket.bind_to_random_port( 'tcp://{}'.format(LOCALHOST)) self.zmq_in_socket = self.context.socket(zmq.PAIR) self.zmq_in_socket.set_hwm(0) self.zmq_in_port = self.zmq_in_socket.bind_to_random_port( 'tcp://{}'.format(LOCALHOST)) @Slot(QProcess.ProcessError) def handle_process_errors(self, error): """Handle errors with the transport layer or server processes.""" self.sig_went_down.emit(self.language) def start_server(self): """Start server.""" # This is not necessary if we're trying to connect to an # external server if self.external_server or self.stdio: return logger.info('Starting server: {0}'.format(' '.join(self.server_args))) # Create server process self.server = QProcess(self) env = self.server.processEnvironment() # Use local PyLS instead of site-packages one. if DEV or running_under_pytest(): running_in_ci = bool(os.environ.get('CI')) if os.name != 'nt' or os.name == 'nt' and not running_in_ci: env.insert('PYTHONPATH', os.pathsep.join(sys.path)[:]) # Adjustments for the Python language server. if self.language == 'python': # Set the PyLS current working to an empty dir inside # our config one. This avoids the server to pick up user # files such as random.py or string.py instead of the # standard library modules named the same. cwd = osp.join(get_conf_path(), 'lsp_paths', 'cwd') if not osp.exists(cwd): os.makedirs(cwd) # On Windows, some modules (notably Matplotlib) # cause exceptions if they cannot get the user home. # So, we need to pass the USERPROFILE env variable to # the PyLS. if os.name == "nt" and "USERPROFILE" in os.environ: env.insert("USERPROFILE", os.environ["USERPROFILE"]) else: # There's no need to define a cwd for other servers. cwd = None # Most LSP servers spawn other processes, which may require # some environment variables. for var in os.environ: env.insert(var, os.environ[var]) logger.info('Server process env variables: {0}'.format(env.keys())) # Setup server self.server.setProcessEnvironment(env) self.server.errorOccurred.connect(self.handle_process_errors) self.server.setWorkingDirectory(cwd) self.server.setProcessChannelMode(QProcess.MergedChannels) if self.server_log_file is not None: self.server.setStandardOutputFile(self.server_log_file) # Start server self.server.start(self.server_args[0], self.server_args[1:]) def start_transport(self): """Start transport layer.""" logger.info('Starting transport for {1}: {0}' .format(' '.join(self.transport_args), self.language)) # Create transport process self.transport = QProcess(self) env = self.transport.processEnvironment() # Most LSP servers spawn other processes other than Python, which may # require some environment variables if self.language != 'python' and self.stdio: for var in os.environ: env.insert(var, os.environ[var]) logger.info('Transport process env variables: {0}'.format( env.keys())) self.transport.setProcessEnvironment(env) # Modifying PYTHONPATH to run transport in development mode or # tests if DEV or running_under_pytest(): if running_under_pytest(): env.insert('PYTHONPATH', os.pathsep.join(sys.path)[:]) else: env.insert('PYTHONPATH', os.pathsep.join(sys.path)[1:]) self.transport.setProcessEnvironment(env) # Set up transport self.transport.errorOccurred.connect(self.handle_process_errors) if self.stdio: self.transport.setProcessChannelMode(QProcess.SeparateChannels) if self.transport_log_file is not None: self.transport.setStandardErrorFile(self.transport_log_file) else: self.transport.setProcessChannelMode(QProcess.MergedChannels) if self.transport_log_file is not None: self.transport.setStandardOutputFile(self.transport_log_file) # Start transport self.transport.start(self.transport_args[0], self.transport_args[1:]) def start(self): """Start client.""" # NOTE: DO NOT change the order in which these methods are called. self.create_transport_sockets() self.start_server() self.start_transport() # Create notifier fid = self.zmq_in_socket.getsockopt(zmq.FD) self.notifier = QSocketNotifier(fid, QSocketNotifier.Read, self) self.notifier.activated.connect(self.on_msg_received) # This is necessary for tests to pass locally! logger.debug('LSP {} client started!'.format(self.language)) def stop(self): """Stop transport and server.""" logger.info('Stopping {} client...'.format(self.language)) if self.notifier is not None: self.notifier.activated.disconnect(self.on_msg_received) self.notifier.setEnabled(False) self.notifier = None if self.transport is not None: self.transport.kill() self.context.destroy() if self.server is not None: self.server.kill() def is_transport_alive(self): """Detect if transport layer is alive.""" state = self.transport.state() return state != QProcess.NotRunning def is_stdio_alive(self): """Check if an stdio server is alive.""" alive = True if not psutil.pid_exists(self.stdio_pid): alive = False else: try: pid_status = psutil.Process(self.stdio_pid).status() except psutil.NoSuchProcess: pid_status = '' if pid_status == psutil.STATUS_ZOMBIE: alive = False return alive def is_server_alive(self): """Detect if a tcp server is alive.""" state = self.server.state() return state != QProcess.NotRunning def is_down(self): """ Detect if the transport layer or server are down to inform our users about it. """ is_down = False if self.transport and not self.is_transport_alive(): logger.debug( "Transport layer for {} is down!!".format(self.language)) if not self.transport_unresponsive: self.transport_unresponsive = True self.sig_went_down.emit(self.language) is_down = True if self.server and not self.is_server_alive(): logger.debug("LSP server for {} is down!!".format(self.language)) if not self.server_unresponsive: self.server_unresponsive = True self.sig_went_down.emit(self.language) is_down = True if self.stdio_pid and not self.is_stdio_alive(): logger.debug("LSP server for {} is down!!".format(self.language)) if not self.server_unresponsive: self.server_unresponsive = True self.sig_went_down.emit(self.language) is_down = True return is_down def send(self, method, params, kind): """Send message to transport.""" if self.is_down(): return if ClientConstants.CANCEL in params: return _id = self.request_seq if kind == MessageKind.REQUEST: msg = { 'id': self.request_seq, 'method': method, 'params': params } self.req_status[self.request_seq] = method elif kind == MessageKind.RESPONSE: msg = { 'id': self.request_seq, 'result': params } elif kind == MessageKind.NOTIFICATION: msg = { 'method': method, 'params': params } logger.debug('Perform request {0} with id {1}'.format(method, _id)) # Save requests to check their ordering. if running_under_pytest(): self._requests.append((_id, method)) # Try sending a message. If the send queue is full, keep trying for a # a second before giving up. timeout = 1 start_time = time.time() timeout_time = start_time + timeout while True: try: self.zmq_out_socket.send_pyobj(msg, flags=zmq.NOBLOCK) self.request_seq += 1 return int(_id) except zmq.error.Again: if time.time() > timeout_time: self.sig_went_down.emit(self.language) return # The send queue is full! wait 0.1 seconds before retrying. if self.initialized: logger.warning("The send queue is full! Retrying...") time.sleep(.1) @Slot() def on_msg_received(self): """Process received messages.""" self.notifier.setEnabled(False) while True: try: # events = self.zmq_in_socket.poll(1500) resp = self.zmq_in_socket.recv_pyobj(flags=zmq.NOBLOCK) try: method = resp['method'] logger.debug( '{} response: {}'.format(self.language, method)) except KeyError: pass if 'error' in resp: logger.debug('{} Response error: {}' .format(self.language, repr(resp['error']))) if self.language == 'python': # Show PyLS errors in our error report dialog only in # debug or development modes if get_debug_level() > 0 or DEV: message = resp['error'].get('message', '') traceback = (resp['error'].get('data', {}). get('traceback')) if traceback is not None: traceback = ''.join(traceback) traceback = traceback + '\n' + message self.sig_server_error.emit(traceback) req_id = resp['id'] if req_id in self.req_reply: self.req_reply[req_id](None, {'params': []}) elif 'method' in resp: if resp['method'][0] != '$': if 'id' in resp: self.request_seq = int(resp['id']) if resp['method'] in self.handler_registry: handler_name = ( self.handler_registry[resp['method']]) handler = getattr(self, handler_name) handler(resp['params']) elif 'result' in resp: if resp['result'] is not None: req_id = resp['id'] if req_id in self.req_status: req_type = self.req_status[req_id] if req_type in self.handler_registry: handler_name = self.handler_registry[req_type] handler = getattr(self, handler_name) handler(resp['result'], req_id) self.req_status.pop(req_id) if req_id in self.req_reply: self.req_reply.pop(req_id) except RuntimeError: # This is triggered when a codeeditor instance has been # removed before the response can be processed. pass except zmq.ZMQError: self.notifier.setEnabled(True) return def perform_request(self, method, params): if method in self.sender_registry: handler_name = self.sender_registry[method] handler = getattr(self, handler_name) _id = handler(params) if 'response_callback' in params: if params['requires_response']: self.req_reply[_id] = params['response_callback'] return _id # ------ LSP initialization methods -------------------------------- @handles(SERVER_READY) @send_request(method=LSPRequestTypes.INITIALIZE) def initialize(self, params, *args, **kwargs): self.stdio_pid = params['pid'] pid = self.transport.processId() if not self.external_server else None params = { 'processId': pid, 'rootUri': pathlib.Path(osp.abspath(self.folder)).as_uri(), 'capabilities': self.client_capabilites, 'trace': TRACE } return params @send_request(method=LSPRequestTypes.SHUTDOWN) def shutdown(self): params = {} return params @handles(LSPRequestTypes.SHUTDOWN) def handle_shutdown(self, response, *args): self.ready_to_close = True @send_notification(method=LSPRequestTypes.EXIT) def exit(self): params = {} return params @handles(LSPRequestTypes.INITIALIZE) def process_server_capabilities(self, server_capabilites, *args): """ Register server capabilities and inform other plugins that it's available. """ # Update server capabilities with the info sent by the server. server_capabilites = server_capabilites['capabilities'] if isinstance(server_capabilites['textDocumentSync'], int): kind = server_capabilites['textDocumentSync'] server_capabilites['textDocumentSync'] = TEXT_DOCUMENT_SYNC_OPTIONS server_capabilites['textDocumentSync']['change'] = kind if server_capabilites['textDocumentSync'] is None: server_capabilites.pop('textDocumentSync') self.server_capabilites.update(server_capabilites) # The initialized notification needs to be the first request sent by # the client according to the protocol. self.initialized = True self.initialized_call() # This sends a DidChangeConfiguration request to pass to the server # the configurations set by the user in our config system. self.send_configurations(self.configurations) # Inform other plugins that the server is up. self.sig_initialize.emit(self.server_capabilites, self.language) @send_notification(method=LSPRequestTypes.INITIALIZED) def initialized_call(self): params = {} return params # ------ Settings queries -------------------------------- @property def support_multiple_workspaces(self): workspace_settings = self.server_capabilites['workspace'] return workspace_settings['workspaceFolders']['supported'] @property def support_workspace_update(self): workspace_settings = self.server_capabilites['workspace'] return workspace_settings['workspaceFolders']['changeNotifications']
class ProfilerWidget(PluginMainWidget): """ Profiler widget. """ ENABLE_SPINNER = True DATAPATH = get_conf_path('profiler.results') # --- Signals # ------------------------------------------------------------------------ sig_edit_goto_requested = Signal(str, int, str) """ This signal will request to open a file in a given row and column using a code editor. Parameters ---------- path: str Path to file. row: int Cursor starting row position. word: str Word to select on given row. """ sig_redirect_stdio_requested = Signal(bool) """ This signal is emitted to request the main application to redirect standard output/error when using Open/Save/Browse dialogs within widgets. Parameters ---------- redirect: bool Start redirect (True) or stop redirect (False). """ sig_started = Signal() """This signal is emitted to inform the profiling process has started.""" sig_finished = Signal() """This signal is emitted to inform the profile profiling has finished.""" def __init__(self, name=None, plugin=None, parent=None): super().__init__(name, plugin, parent) self.set_conf('text_color', MAIN_TEXT_COLOR) # Attributes self._last_wdir = None self._last_args = None self._last_pythonpath = None self.error_output = None self.output = None self.running = False self.text_color = self.get_conf('text_color') # Widgets self.process = None self.filecombo = PythonModulesComboBox( self, id_=ProfilerWidgetMainToolbarItems.FileCombo) self.datatree = ProfilerDataTree(self) self.datelabel = QLabel() self.datelabel.ID = ProfilerWidgetInformationToolbarItems.DateLabel # Layout layout = QVBoxLayout() layout.addWidget(self.datatree) self.setLayout(layout) # Signals self.datatree.sig_edit_goto_requested.connect( self.sig_edit_goto_requested) # --- PluginMainWidget API # ------------------------------------------------------------------------ def get_title(self): return _('Profiler') def get_focus_widget(self): return self.datatree def setup(self): self.start_action = self.create_action( ProfilerWidgetActions.Run, text=_("Run profiler"), tip=_("Run profiler"), icon=self.create_icon('run'), triggered=self.run, ) browse_action = self.create_action( ProfilerWidgetActions.Browse, text='', tip=_('Select Python script'), icon=self.create_icon('fileopen'), triggered=lambda x: self.select_file(), ) self.log_action = self.create_action( ProfilerWidgetActions.ShowOutput, text=_("Output"), tip=_("Show program's output"), icon=self.create_icon('log'), triggered=self.show_log, ) self.collapse_action = self.create_action( ProfilerWidgetActions.Collapse, text=_('Collapse'), tip=_('Collapse one level up'), icon=self.create_icon('collapse'), triggered=lambda x=None: self.datatree.change_view(-1), ) self.expand_action = self.create_action( ProfilerWidgetActions.Expand, text=_('Expand'), tip=_('Expand one level down'), icon=self.create_icon('expand'), triggered=lambda x=None: self.datatree.change_view(1), ) self.save_action = self.create_action( ProfilerWidgetActions.SaveData, text=_("Save data"), tip=_('Save profiling data'), icon=self.create_icon('filesave'), triggered=self.save_data, ) self.load_action = self.create_action( ProfilerWidgetActions.LoadData, text=_("Load data"), tip=_('Load profiling data for comparison'), icon=self.create_icon('fileimport'), triggered=self.compare, ) self.clear_action = self.create_action( ProfilerWidgetActions.Clear, text=_("Clear comparison"), tip=_("Clear comparison"), icon=self.create_icon('editdelete'), triggered=self.clear, ) self.clear_action.setEnabled(False) # Main Toolbar toolbar = self.get_main_toolbar() for item in [self.filecombo, browse_action, self.start_action]: self.add_item_to_toolbar( item, toolbar=toolbar, section=ProfilerWidgetMainToolbarSections.Main, ) # Secondary Toolbar secondary_toolbar = self.create_toolbar( ProfilerWidgetToolbars.Information) for item in [ self.collapse_action, self.expand_action, self.create_stretcher( id_=ProfilerWidgetInformationToolbarItems.Stretcher1), self.datelabel, self.create_stretcher( id_=ProfilerWidgetInformationToolbarItems.Stretcher2), self.log_action, self.save_action, self.load_action, self.clear_action ]: self.add_item_to_toolbar( item, toolbar=secondary_toolbar, section=ProfilerWidgetInformationToolbarSections.Main, ) # Setup if not is_profiler_installed(): # This should happen only on certain GNU/Linux distributions # or when this a home-made Python build because the Python # profilers are included in the Python standard library for widget in (self.datatree, self.filecombo, self.start_action): widget.setDisabled(True) url = 'https://docs.python.org/3/library/profile.html' text = '%s <a href=%s>%s</a>' % (_('Please install'), url, _("the Python profiler modules")) self.datelabel.setText(text) def update_actions(self): if self.running: icon = self.create_icon('stop') else: icon = self.create_icon('run') self.start_action.setIcon(icon) self.start_action.setEnabled(bool(self.filecombo.currentText())) # --- Private API # ------------------------------------------------------------------------ def _kill_if_running(self): """Kill the profiling process if it is running.""" if self.process is not None: if self.process.state() == QProcess.Running: self.process.close() self.process.waitForFinished(1000) self.update_actions() def _finished(self, exit_code, exit_status): """ Parse results once the profiling process has ended. Parameters ---------- exit_code: int QProcess exit code. exit_status: str QProcess exit status. """ self.running = False self.show_errorlog() # If errors occurred, show them. self.output = self.error_output + self.output self.datelabel.setText('') self.show_data(justanalyzed=True) self.update_actions() def _read_output(self, error=False): """ Read otuput from QProcess. Parameters ---------- error: bool, optional Process QProcess output or error channels. Default is False. """ if error: self.process.setReadChannel(QProcess.StandardError) else: self.process.setReadChannel(QProcess.StandardOutput) qba = QByteArray() while self.process.bytesAvailable(): if error: qba += self.process.readAllStandardError() else: qba += self.process.readAllStandardOutput() text = to_text_string(qba.data(), encoding='utf-8') if error: self.error_output += text else: self.output += text # --- Public API # ------------------------------------------------------------------------ def save_data(self): """Save data.""" title = _("Save profiler result") filename, _selfilter = getsavefilename( self, title, getcwd_or_home(), _("Profiler result") + " (*.Result)", ) if filename: self.datatree.save_data(filename) def compare(self): """Compare previous saved run with last run.""" filename, _selfilter = getopenfilename( self, _("Select script to compare"), getcwd_or_home(), _("Profiler result") + " (*.Result)", ) if filename: self.datatree.compare(filename) self.show_data() self.clear_action.setEnabled(True) def clear(self): """Clear data in tree.""" self.datatree.compare(None) self.datatree.hide_diff_cols(True) self.show_data() self.clear_action.setEnabled(False) def analyze(self, filename, wdir=None, args=None, pythonpath=None): """ Start the profiling process. Parameters ---------- wdir: str Working directory path string. Default is None. args: list Arguments to pass to the profiling process. Default is None. pythonpath: str Python path string. Default is None. """ if not is_profiler_installed(): return self._kill_if_running() # TODO: storing data is not implemented yet # index, _data = self.get_data(filename) combo = self.filecombo items = [combo.itemText(idx) for idx in range(combo.count())] index = None if index is None and filename not in items: self.filecombo.addItem(filename) self.filecombo.setCurrentIndex(self.filecombo.count() - 1) else: self.filecombo.setCurrentIndex(self.filecombo.findText(filename)) self.filecombo.selected() if self.filecombo.is_valid(): if wdir is None: wdir = osp.dirname(filename) self.start(wdir, args, pythonpath) def select_file(self, filename=None): """ Select filename to profile. Parameters ---------- filename: str, optional Path to filename to profile. default is None. Notes ----- If no `filename` is provided an open filename dialog will be used. """ if filename is None: self.sig_redirect_stdio_requested.emit(False) filename, _selfilter = getopenfilename( self, _("Select Python script"), getcwd_or_home(), _("Python scripts") + " (*.py ; *.pyw)") self.sig_redirect_stdio_requested.emit(True) if filename: self.analyze(filename) def show_log(self): """Show process output log.""" if self.output: output_dialog = TextEditor( self.output, title=_("Profiler output"), readonly=True, parent=self, ) output_dialog.resize(700, 500) output_dialog.exec_() def show_errorlog(self): """Show process error log.""" if self.error_output: output_dialog = TextEditor( self.error_output, title=_("Profiler output"), readonly=True, parent=self, ) output_dialog.resize(700, 500) output_dialog.exec_() def start(self, wdir=None, args=None, pythonpath=None): """ Start the profiling process. Parameters ---------- wdir: str Working directory path string. Default is None. args: list Arguments to pass to the profiling process. Default is None. pythonpath: str Python path string. Default is None. """ filename = to_text_string(self.filecombo.currentText()) if wdir is None: wdir = self._last_wdir if wdir is None: wdir = osp.basename(filename) if args is None: args = self._last_args if args is None: args = [] if pythonpath is None: pythonpath = self._last_pythonpath self._last_wdir = wdir self._last_args = args self._last_pythonpath = pythonpath self.datelabel.setText(_('Profiling, please wait...')) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(wdir) self.process.readyReadStandardOutput.connect(self._read_output) self.process.readyReadStandardError.connect( lambda: self._read_output(error=True)) self.process.finished.connect( lambda ec, es=QProcess.ExitStatus: self._finished(ec, es)) self.process.finished.connect(self.stop_spinner) if pythonpath is not None: env = [ to_text_string(_pth) for _pth in self.process.systemEnvironment() ] add_pathlist_to_PYTHONPATH(env, pythonpath) processEnvironment = QProcessEnvironment() for envItem in env: envName, __, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) processEnvironment.insert("PYTHONIOENCODING", "utf8") self.process.setProcessEnvironment(processEnvironment) self.output = '' self.error_output = '' self.running = True self.start_spinner() p_args = ['-m', 'cProfile', '-o', self.DATAPATH] if os.name == 'nt': # On Windows, one has to replace backslashes by slashes to avoid # confusion with escape characters (otherwise, for example, '\t' # will be interpreted as a tabulation): p_args.append(osp.normpath(filename).replace(os.sep, '/')) else: p_args.append(filename) if args: p_args.extend(shell_split(args)) executable = self.get_conf('executable', section='main_interpreter') self.process.start(executable, p_args) running = self.process.waitForStarted() if not running: QMessageBox.critical( self, _("Error"), _("Process failed to start"), ) self.update_actions() def stop(self): """Stop the running process.""" self.running = False self.process.close() self.process.waitForFinished(1000) self.stop_spinner() self.update_actions() def run(self): """Toggle starting or running the profiling process.""" if self.running: self.stop() else: self.start() def show_data(self, justanalyzed=False): """ Show analyzed data on results tree. Parameters ---------- justanalyzed: bool, optional Default is False. """ if not justanalyzed: self.output = None self.log_action.setEnabled(self.output is not None and len(self.output) > 0) self._kill_if_running() filename = to_text_string(self.filecombo.currentText()) if not filename: return self.datelabel.setText(_('Sorting data, please wait...')) QApplication.processEvents() self.datatree.load_data(self.DATAPATH) self.datatree.show_tree() text_style = "<span style=\'color: %s\'><b>%s </b></span>" date_text = text_style % (self.text_color, time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) self.datelabel.setText(date_text)
class TerminalMainWidget(PluginMainWidget): """ Terminal plugin main widget. """ MAX_SERVER_CONTACT_RETRIES = 40 URL_ISSUES = ' https://github.com/spyder-ide/spyder-terminal/issues' # --- Signals # ------------------------------------------------------------------------ sig_server_is_ready = Signal() """ This signal is emitted when the server is ready to connect. """ def __init__(self, name, plugin, parent): """Widget constructor.""" self.terms = [] super().__init__(name, plugin, parent) # Attributes self.tab_widget = None self.menu_actions = None self.server_retries = 0 self.server_ready = False self.font = None self.port = select_port(default_port=8071) self.stdout_file = None self.stderr_file = None if get_debug_level() > 0: self.stdout_file = osp.join(os.getcwd(), 'spyder_terminal_out.log') self.stderr_file = osp.join(os.getcwd(), 'spyder_terminal_err.log') self.project_path = None self.current_file_path = None self.current_cwd = os.getcwd() # Widgets self.main = parent self.find_widget = FindTerminal(self) self.find_widget.hide() layout = QVBoxLayout() # Tab Widget self.tabwidget = Tabs(self, rename_tabs=True) self.tabwidget.currentChanged.connect(self.refresh_plugin) self.tabwidget.move_data.connect(self.move_tab) self.tabwidget.set_close_function(self.close_term) if (hasattr(self.tabwidget, 'setDocumentMode') and not sys.platform == 'darwin'): # Don't set document mode to true on OSX because it generates # a crash when the console is detached from the main window # Fixes Issue 561 self.tabwidget.setDocumentMode(True) layout.addWidget(self.tabwidget) layout.addWidget(self.find_widget) self.setLayout(layout) css = qstylizer.style.StyleSheet() css.QTabWidget.pane.setValues(border=0) self.setStyleSheet(css.toString()) self.__wait_server_to_start() # ---- PluginMainWidget API # ------------------------------------------------------------------------ def get_focus_widget(self): """ Set focus on current selected terminal. Return the widget to give focus to when this plugin's dockwidget is raised on top-level. """ term = self.tabwidget.currentWidget() if term is not None: return term.view def get_title(self): """Define the title of the widget.""" return _('Terminal') def setup(self): """Perform the setup of plugin's main menu and signals.""" self.cmd = find_program(self.get_conf('shell')) server_args = [ sys.executable, '-m', 'spyder_terminal.server', '--port', str(self.port), '--shell', self.cmd] self.server = QProcess(self) env = self.server.processEnvironment() for var in os.environ: env.insert(var, os.environ[var]) self.server.setProcessEnvironment(env) self.server.errorOccurred.connect(self.handle_process_errors) self.server.setProcessChannelMode(QProcess.SeparateChannels) if self.stdout_file and self.stderr_file: self.server.setStandardOutputFile(self.stdout_file) self.server.setStandardErrorFile(self.stderr_file) self.server.start(server_args[0], server_args[1:]) self.color_scheme = self.get_conf('appearance', 'ui_theme') self.theme = self.get_conf('appearance', 'selected') # Menu menu = self.get_options_menu() # Actions new_terminal_toolbar_action = self.create_toolbutton( TerminalMainWidgetToolbarSections.New, text=_("Open a new terminal"), icon=self.create_icon('expand_selection'), triggered=lambda: self.create_new_term(), ) self.add_corner_widget( TerminalMainWidgetCornerToolbar.NewTerm, new_terminal_toolbar_action) new_terminal_cwd = self.create_action( TerminalMainWidgetActions.NewTerminalForCWD, text=_("New terminal in current working directory"), tip=_("Sets the pwd at the current working directory"), triggered=lambda: self.create_new_term(), shortcut_context='terminal', register_shortcut=True) self.new_terminal_project = self.create_action( TerminalMainWidgetActions.NewTerminalForProject, text=_("New terminal in current project"), tip=_("Sets the pwd at the current project directory"), triggered=lambda: self.create_new_term(path=self.project_path)) new_terminal_file = self.create_action( TerminalMainWidgetActions.NewTerminalForFile, text=_("New terminal in current Editor file"), tip=_("Sets the pwd at the directory that contains the current " "opened file"), triggered=lambda: self.create_new_term( path=self.current_file_path)) rename_tab_action = self.create_action( TerminalMainWidgetActions.RenameTab, text=_("Rename terminal"), triggered=lambda: self.tab_name_editor()) # Context menu actions self.create_action( TerminalMainWidgetActions.Copy, text=_('Copy text'), icon=self.create_icon('editcopy'), shortcut_context='terminal', triggered=lambda: self.copy(), register_shortcut=True) self.create_action( TerminalMainWidgetActions.Paste, text=_('Paste text'), icon=self.create_icon('editpaste'), shortcut_context='terminal', triggered=lambda: self.paste(), register_shortcut=True) self.create_action( TerminalMainWidgetActions.Clear, text=_('Clear terminal'), shortcut_context='terminal', triggered=lambda: self.clear(), register_shortcut=True) self.create_action( TerminalMainWidgetActions.ZoomIn, text=_('Zoom in'), shortcut_context='terminal', triggered=lambda: self.increase_font(), register_shortcut=True) self.create_action( TerminalMainWidgetActions.ZoomOut, text=_('Zoom out'), shortcut_context='terminal', triggered=lambda: self.decrease_font(), register_shortcut=True) # Create context menu self.create_menu(TermViewMenus.Context) # Add actions to options menu for item in [new_terminal_cwd, self.new_terminal_project, new_terminal_file]: self.add_item_to_menu( item, menu=menu, section=TerminalMainWidgetMenuSections.New) self.add_item_to_menu( rename_tab_action, menu=menu, section=TerminalMainWidgetMenuSections.TabActions) def update_actions(self): """Setup and update the actions in the options menu.""" if self.project_path is None: self.new_terminal_project.setEnabled(False) # ------ Private API ------------------------------------------ def copy(self): if self.get_focus_widget(): self.get_focus_widget().copy() def paste(self): if self.get_focus_widget(): self.get_focus_widget().paste() def clear(self): if self.get_focus_widget(): self.get_focus_widget().clear() def increase_font(self): if self.get_focus_widget(): self.get_focus_widget().increase_font() def decrease_font(self): if self.get_focus_widget(): self.get_focus_widget().decrease_font() def __wait_server_to_start(self): try: code = requests.get('http://127.0.0.1:{0}'.format( self.port)).status_code except: code = 500 if self.server_retries == self.MAX_SERVER_CONTACT_RETRIES: QMessageBox.critical(self, _('Spyder Terminal Error'), _("Terminal server could not be located at " '<a href="http://127.0.0.1:{0}">' 'http://127.0.0.1:{0}</a>,' ' please restart Spyder on debugging mode ' "and open an issue with the contents of " "<tt>{1}</tt> and <tt>{2}</tt> " "files at {3}.").format(self.port, self.stdout_file, self.stderr_file, self.URL_ISSUES), QMessageBox.Ok) elif code != 200: self.server_retries += 1 QTimer.singleShot(250, self.__wait_server_to_start) elif code == 200: self.sig_server_is_ready.emit() self.server_ready = True self.create_new_term(give_focus=False) # ------ Plugin API -------------------------------- def update_font(self, font): """Update font from Preferences.""" self.font = font for term in self.terms: term.set_font(font.family()) def on_close(self, cancelable=False): """Perform actions before parent main window is closed.""" for term in self.terms: term.close() self.server.kill() return True def refresh_plugin(self): """Refresh tabwidget.""" term = None if self.tabwidget.count(): term = self.tabwidget.currentWidget() term.view.setFocus() else: term = None @on_conf_change def apply_plugin_settings(self, options): """Apply the config settings.""" term_options = {} for option in options: if option == 'color_scheme_name': term_options[option] = option else: term_options[option] = self.get_conf(option) for term in self.get_terms(): term.apply_settings(term_options) # ------ Public API (for terminals) ------------------------- def get_terms(self): """Return terminal list.""" return [cl for cl in self.terms if isinstance(cl, TerminalWidget)] def get_current_term(self): """Return the currently selected terminal.""" try: terminal = self.tabwidget.currentWidget() except AttributeError: terminal = None if terminal is not None: return terminal def create_new_term(self, name=None, give_focus=True, path=None): """Add a new terminal tab.""" if path is None: path = self.current_cwd if self.project_path is not None: path = self.project_path path = path.replace('\\', '/') term = TerminalWidget( self, self.port, path=path, font=self.font.family(), theme=self.theme, color_scheme=self.color_scheme) self.add_tab(term) term.terminal_closed.connect(lambda: self.close_term(term=term)) def close_term(self, index=None, term=None): """Close a terminal tab.""" if not self.tabwidget.count(): return if term is not None: index = self.tabwidget.indexOf(term) if index is None and term is None: index = self.tabwidget.currentIndex() if index is not None: term = self.tabwidget.widget(index) if term: term.close() self.tabwidget.removeTab(self.tabwidget.indexOf(term)) if term in self.terms: self.terms.remove(term) if self.tabwidget.count() == 0: self.create_new_term() def set_project_path(self, path): """Refresh current project path.""" self.project_path = path self.new_terminal_project.setEnabled(True) def set_current_opened_file(self, path): """Get path of current opened file in editor.""" self.current_file_path = osp.dirname(path) def unset_project_path(self): """Refresh current project path.""" self.project_path = None self.new_terminal_project.setEnabled(False) @Slot(str) def set_current_cwd(self, cwd): """Update current working directory.""" self.current_cwd = cwd def server_is_ready(self): """Return server status.""" return self.server_ready def search_next(self, text, case=False, regex=False, word=False):