class ExternalPythonShell(ExternalShellBase): """External Shell widget: execute Python script in a separate process""" SHELL_CLASS = ExtPyQsciShell def __init__(self, parent=None, fname=None, wdir=None, commands=[], interact=False, debug=False, path=[]): ExternalShellBase.__init__(self, parent, wdir, history_filename='.history_ec.py') self.shell.set_externalshell(self) self.toggle_globals_explorer(False) self.interact_check.setChecked(interact) self.debug_check.setChecked(debug) self.monitor_socket = None self.interpreter = fname is None self.fname = startup.__file__ if fname is None else fname if self.interpreter: self.interact_check.hide() self.debug_check.hide() self.terminate_button.hide() self.commands = ["import sys", "sys.path.insert(0, '')"] + commands # Additional python path list self.path = path def get_toolbar_buttons(self): ExternalShellBase.get_toolbar_buttons(self) self.globalsexplorer_button = create_toolbutton(self, get_icon('dictedit.png'), self.tr("Variables"), tip=self.tr("Show/hide global variables explorer"), toggled=self.toggle_globals_explorer) self.terminate_button = create_toolbutton(self, get_icon('terminate.png'), self.tr("Terminate"), tip=self.tr("Attempts to terminate the process.\n" "The process may not exit as a result of clicking " "this button\n(it is given the chance to prompt " "the user for any unsaved files, etc).")) self.interact_check = QCheckBox(self.tr("Interact"), self) self.debug_check = QCheckBox(self.tr("Debug"), self) return [self.interact_check, self.debug_check, self.globalsexplorer_button, self.run_button, self.terminate_button, self.kill_button] def get_shell_widget(self): # Globals explorer self.globalsexplorer = GlobalsExplorer(self) self.connect(self.globalsexplorer, SIGNAL('collapse()'), lambda: self.toggle_globals_explorer(False)) # Shell splitter self.splitter = splitter = QSplitter(Qt.Vertical, self) self.connect(self.splitter, SIGNAL('splitterMoved(int, int)'), self.splitter_moved) splitter.addWidget(self.shell) splitter.setCollapsible(0, False) splitter.addWidget(self.globalsexplorer) splitter.setStretchFactor(0, 2) splitter.setStretchFactor(1, 1) return splitter def get_icon(self): return get_icon('python.png') def set_buttons_runnning_state(self, state): ExternalShellBase.set_buttons_runnning_state(self, state) self.interact_check.setEnabled(not state) self.debug_check.setEnabled(not state) self.terminate_button.setEnabled(state) if not state: self.toggle_globals_explorer(False) self.globalsexplorer_button.setEnabled(state) def create_process(self): self.shell.clear() self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.MergedChannels) self.connect(self.shell, SIGNAL("wait_for_ready_read()"), 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 self.interact_check.isChecked(): p_args.append('-i') if self.debug_check.isChecked(): p_args.extend(['-m', 'pdb']) p_args.append(self.fname) env = self.process.systemEnvironment() # Monitor env.append('SHELL_ID=%s' % id(self)) from spyderlib.widgets.externalshell.monitor import start_server server, port = start_server() self.notification_thread = server.register(str(id(self)), self) self.connect(self.notification_thread, SIGNAL('refresh()'), self.globalsexplorer.refresh_table) env.append('SPYDER_PORT=%d' % port) # Python init commands (interpreter only) if self.commands and self.interpreter: env.append('PYTHONINITCOMMANDS=%s' % ';'.join(self.commands)) self.process.setEnvironment(env) pathlist = [] # Fix encoding with custom "sitecustomize.py" scpath = osp.dirname(osp.abspath(__file__)) pathlist.append(scpath) # Adding Spyder path pathlist += self.path # Adding path list to PYTHONPATH environment variable pypath = "PYTHONPATH" pathstr = os.pathsep.join(pathlist) if os.environ.get(pypath) is not None: env.replaceInStrings(pypath+'=', pypath+'='+pathstr+os.pathsep, Qt.CaseSensitive) else: env.append(pypath+'='+pathstr) self.process.setEnvironment(env) #-------------------------Python specific------------------------------- if self.arguments: p_args.extend( self.arguments.split(' ') ) self.connect(self.process, SIGNAL("readyReadStandardOutput()"), self.write_output) self.connect(self.process, SIGNAL("finished(int,QProcess::ExitStatus)"), self.finished) self.connect(self.terminate_button, SIGNAL("clicked()"), self.process.terminate) self.connect(self.kill_button, SIGNAL("clicked()"), self.process.kill) #-------------------------Python specific------------------------------- self.process.start(sys.executable, p_args) #-------------------------Python specific------------------------------- running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, self.tr("Error"), self.tr("Process failed to start")) else: self.shell.setFocus() self.emit(SIGNAL('started()')) return self.process #=============================================================================== # Input/Output #=============================================================================== def _write_error(self, text, findstr): pos = text.find(findstr) if pos != -1: self.shell.write(text[:pos]) if text.endswith(">>> "): self.shell.write_error(text[pos:-5]) self.shell.write(text[-5:], flush=True) else: self.shell.write_error(text[pos:]) return True return False def write_output(self): text = self.get_stdout() if not self._write_error(text, 'Traceback (most recent call last):') \ and not self._write_error(text, 'File "<stdin>", line 1'): self.shell.write(text) QApplication.processEvents() def send_to_process(self, qstr): if not isinstance(qstr, QString): qstr = QString(qstr) if not qstr.endsWith('\n'): qstr.append('\n') self.process.write(qstr.toLocal8Bit()) self.process.waitForBytesWritten(-1) def keyboard_interrupt(self): communicate(self.monitor_socket, "thread.interrupt_main()") #=============================================================================== # Globals explorer #=============================================================================== def toggle_globals_explorer(self, state): self.splitter.setSizes([1, 1 if state else 0]) self.globalsexplorer_button.setChecked(state) if state: self.globalsexplorer.refresh_table() def splitter_moved(self, pos, index): self.globalsexplorer_button.setChecked( self.splitter.sizes()[1] )
class ExternalPythonShell(ExternalShellBase): """External Shell widget: execute Python script in a separate process""" SHELL_CLASS = ExtPythonShellWidget def __init__(self, parent=None, fname=None, wdir=None, commands=[], interact=False, debug=False, path=[], ipython=False, arguments=None, stand_alone=True, umd_enabled=True, umd_namelist=[], umd_verbose=True): self.namespacebrowser = None # namespace browser widget! self.fname = startup.__file__ if fname is None else fname self.stand_alone = stand_alone self.umd_enabled = umd_enabled self.umd_namelist = umd_namelist self.umd_verbose = umd_verbose self.namespacebrowser_button = None self.cwd_button = None self.terminate_button = None ExternalShellBase.__init__(self, parent, wdir, history_filename='.history.py') self.nsb_timer = QTimer(self) # Namespace browser auto-refresh timer self.nsb_timer.setInterval(3000) if arguments is not None: assert isinstance(arguments, basestring) self.arguments = arguments self.ipython = ipython if self.ipython: interact = False self.shell.set_externalshell(self) self.toggle_globals_explorer(False) self.interact_action.setChecked(interact) self.debug_action.setChecked(debug) self.monitor_socket = None self.interpreter = fname is None if self.interpreter: self.terminate_button.hide() self.commands = ["import sys", "sys.path.insert(0, '')"] + commands # Additional python path list self.path = path def closeEvent(self, event): ExternalShellBase.closeEvent(self, event) self.disconnect(self.nsb_timer, SIGNAL("timeout()"), self.namespacebrowser.refresh_table) def get_toolbar_buttons(self): ExternalShellBase.get_toolbar_buttons(self) if self.namespacebrowser_button is None and self.stand_alone: self.namespacebrowser_button = create_toolbutton(self, get_icon('dictedit.png'), self.tr("Variables"), tip=self.tr("Show/hide global variables explorer"), toggled=self.toggle_globals_explorer) if self.cwd_button is None: self.cwd_button = create_toolbutton(self, get_std_icon('DirOpenIcon'), self.tr("Working directory"), tip=self.tr("Set current working directory"), triggered=self.set_current_working_directory) if self.terminate_button is None: self.terminate_button = create_toolbutton(self, get_icon('terminate.png'), self.tr("Terminate"), tip=self.tr("Attempts to terminate the process.\n" "The process may not exit as a result of " "clicking this button\n" "(it is given the chance to prompt " "the user for any unsaved files, etc).")) buttons = [self.cwd_button] if self.namespacebrowser_button is not None: buttons.append(self.namespacebrowser_button) buttons += [self.run_button, self.options_button, self.terminate_button, self.kill_button] return buttons def get_options_menu(self): self.interact_action = create_action(self, self.tr("Interact")) self.interact_action.setCheckable(True) self.debug_action = create_action(self, self.tr("Debug")) self.debug_action.setCheckable(True) self.args_action = create_action(self, self.tr("Arguments..."), triggered=self.get_arguments) return [self.interact_action, self.debug_action, self.args_action] def is_interpreter(self): """Return True if shellwidget is a Python/IPython interpreter""" return self.interpreter def set_namespacebrowser(self, namespacebrowser): """Set namespace browser *widget*""" self.namespacebrowser = namespacebrowser def get_shell_widget(self): if self.stand_alone: self.namespacebrowser = NamespaceBrowser(self) self.namespacebrowser.set_shellwidget(self) self.connect(self.namespacebrowser, SIGNAL('collapse()'), lambda: self.toggle_globals_explorer(False)) # Shell splitter self.splitter = splitter = QSplitter(Qt.Vertical, self) self.connect(self.splitter, SIGNAL('splitterMoved(int, int)'), 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 else: return self.shell def get_icon(self): return get_icon('python.png') def set_buttons_runnning_state(self, state): ExternalShellBase.set_buttons_runnning_state(self, state) self.interact_action.setEnabled(not state and not self.interpreter) self.debug_action.setEnabled(not state and not self.interpreter) self.args_action.setEnabled(not state and (not self.interpreter or self.ipython)) if state: if self.arguments: argstr = self.tr("Arguments: %1").arg(self.arguments) else: argstr = self.tr("No argument") else: argstr = self.tr("Arguments...") self.args_action.setText(argstr) self.terminate_button.setEnabled(state) if not state: self.toggle_globals_explorer(False) self.cwd_button.setEnabled(state) if self.namespacebrowser_button is not None: self.namespacebrowser_button.setEnabled(state) def create_process(self): self.shell.clear() self.process = QProcess(self) if self.ipython: self.process.setProcessChannelMode(QProcess.MergedChannels) else: self.process.setProcessChannelMode(QProcess.SeparateChannels) self.connect(self.shell, SIGNAL("wait_for_ready_read()"), 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 self.interact_action.isChecked(): p_args.append('-i') if self.debug_action.isChecked(): p_args.extend(['-m', 'pdb']) if os.name == 'nt': # When calling pdb on Windows, one has to double the backslashes # to help Python escaping these characters (otherwise, for example, # '\t' will be interpreted as a tabulation): p_args.append(self.fname.replace(os.sep, os.sep*2)) else: p_args.append(self.fname) env = self.process.systemEnvironment() # Monitor env.append('SHELL_ID=%s' % id(self)) from spyderlib.widgets.externalshell.monitor import start_server server, port = start_server() self.notification_thread = server.register(str(id(self)), self) self.connect(self.notification_thread, SIGNAL('refresh()'), self.namespacebrowser.refresh_table) env.append('SPYDER_PORT=%d' % port) # Python init commands (interpreter only) if self.commands and self.interpreter: env.append('PYTHONINITCOMMANDS=%s' % ';'.join(self.commands)) self.process.setEnvironment(env) # User Module Deleter if self.interpreter: env.append('UMD_ENABLED=%r' % self.umd_enabled) env.append('UMD_NAMELIST=%s' % ','.join(self.umd_namelist)) env.append('UMD_VERBOSE=%r' % self.umd_verbose) # IPython related configuration if self.ipython: env.append('IPYTHON=True') # Do not call msvcrt.getch in IPython.genutils.page_more: env.append('TERM=emacs') pathlist = [] # Fix encoding with custom "sitecustomize.py" 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) self.process.setEnvironment(env) #-------------------------Python specific------------------------------- if self.arguments: p_args.extend( self.arguments.split(' ') ) self.connect(self.process, SIGNAL("readyReadStandardOutput()"), self.write_output) self.connect(self.process, SIGNAL("readyReadStandardError()"), self.write_error) self.connect(self.process, SIGNAL("finished(int,QProcess::ExitStatus)"), self.finished) self.connect(self.terminate_button, SIGNAL("clicked()"), self.process.terminate) self.connect(self.kill_button, SIGNAL("clicked()"), self.process.kill) #-------------------------Python specific------------------------------- self.process.start(sys.executable, p_args) #-------------------------Python specific------------------------------- running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, self.tr("Error"), self.tr("Process failed to start")) else: self.shell.setFocus() self.emit(SIGNAL('started()')) self.connect(self.nsb_timer, SIGNAL("timeout()"), self.namespacebrowser.auto_refresh) self.nsb_timer.start() return self.process #=============================================================================== # Input/Output #=============================================================================== def write_error(self): #---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, qstr): if not isinstance(qstr, QString): qstr = QString(qstr) if not qstr.endsWith('\n'): qstr.append('\n') self.process.write(qstr.toLocal8Bit()) 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): communicate(self.monitor_socket, "thread.interrupt_main()") #=============================================================================== # Globals explorer #=============================================================================== def toggle_globals_explorer(self, state): if self.stand_alone: self.splitter.setSizes([1, 1 if state else 0]) self.namespacebrowser_button.setChecked(state) if state: self.namespacebrowser.refresh_table() def splitter_moved(self, pos, index): self.namespacebrowser_button.setChecked( self.splitter.sizes()[1] ) #=============================================================================== # Current working directory #=============================================================================== def set_current_working_directory(self): cwd = self.shell.get_cwd() self.emit(SIGNAL('redirect_stdio(bool)'), False) directory = QFileDialog.getExistingDirectory(self, self.tr("Select directory"), cwd) if not directory.isEmpty(): self.shell.set_cwd(unicode(directory)) self.emit(SIGNAL('redirect_stdio(bool)'), True)
class ExternalSystemShell(ExternalShellBase): """External Shell widget: execute Python script in a separate process""" SHELL_CLASS = TerminalWidget 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 get_icon('cmdprompt.png') 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 = [unicode(_path) for _path in self.process.systemEnvironment()] add_pathlist_to_PYTHONPATH(env, self.path) self.process.setEnvironment(env) # 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.connect(self.process, SIGNAL("readyReadStandardOutput()"), self.write_output) self.connect(self.process, SIGNAL("finished(int,QProcess::ExitStatus)"), self.finished) self.connect(self.kill_button, SIGNAL("clicked()"), 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.emit(SIGNAL('started()')) return self.process #=============================================================================== # Input/Output #=============================================================================== def transcode(self, bytes): if os.name == 'nt': return encoding.transcode(str(bytes.data()), 'cp850') else: return ExternalShellBase.transcode(self, bytes) def send_to_process(self, text): if not isinstance(text, basestring): text = unicode(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') # # The following code will soon be removed: # # (last attempt to send a Ctrl+C on Windows) # if os.name == 'nt': # pid = int(self.process.pid()) # import ctypes, win32api, win32con # class _PROCESS_INFORMATION(ctypes.Structure): # _fields_ = [("hProcess", ctypes.c_int), # ("hThread", ctypes.c_int), # ("dwProcessID", ctypes.c_int), # ("dwThreadID", ctypes.c_int)] # x = ctypes.cast( ctypes.c_void_p(pid), # ctypes.POINTER(_PROCESS_INFORMATION) ) # win32api.GenerateConsoleCtrlEvent(win32con.CTRL_C_EVENT, # x.dwProcessID) # else: # self.send_ctrl_to_process('c')
class ExternalPythonShell(ExternalShellBase): """External Shell widget: execute Python script in a separate process""" SHELL_CLASS = ExtPythonShellWidget def __init__(self, parent=None, fname=None, wdir=None, interact=False, debug=False, path=[], python_args='', ipykernel=False, arguments='', stand_alone=None, umd_enabled=True, umd_namelist=[], umd_verbose=True, pythonstartup=None, pythonexecutable=None, monitor_enabled=True, mpl_patch_enabled=True, mpl_backend=None, ets_backend='qt4', qt_api=None, pyqt_api=0, install_qt_inputhook=True, ignore_sip_setapi_errors=False, 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') self.namespacebrowser = None # namespace browser widget! self.dialog_manager = DialogManager() self.stand_alone = stand_alone # stand alone settings (None: plugin) self.pythonstartup = pythonstartup self.pythonexecutable = pythonexecutable self.monitor_enabled = monitor_enabled self.mpl_patch_enabled = mpl_patch_enabled self.mpl_backend = mpl_backend self.ets_backend = ets_backend self.qt_api = qt_api self.pyqt_api = pyqt_api self.install_qt_inputhook = install_qt_inputhook self.ignore_sip_setapi_errors = ignore_sip_setapi_errors self.merge_output_channels = merge_output_channels self.colorize_sys_stderr = colorize_sys_stderr self.umd_enabled = umd_enabled self.umd_namelist = umd_namelist self.umd_verbose = umd_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 isinstance(python_args, basestring) self.python_args = python_args assert isinstance(arguments, basestring) self.arguments = arguments self.connection_file = None self.is_ipykernel = ipykernel if self.is_ipykernel: interact = False # Running our custom startup script for IPython kernels: # (see spyderlib/widgets/externalshell/start_ipython_kernel.py) self.fname = get_module_source_path( 'SMlib.widgets.externalshell', 'start_ipython_kernel.py') self.shell.set_externalshell(self) self.toggle_globals_explorer(False) self.interact_action.setChecked(interact) self.debug_action.setChecked(debug) self.introspection_socket = None self.is_interpreter = fname is None if self.is_interpreter: self.terminate_button.hide() # 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=get_icon('dictedit.png'), 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=get_icon('terminate.png'), tip=_("""Attempts to terminate the process. The process may not exit as a result of clicking this button (it is given the chance to prompt 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.options_button, self.terminate_button, self.kill_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) run_settings_menu = QMenu(_("Run settings"), self) add_actions(run_settings_menu, (self.interact_action, self.debug_action, self.args_action)) self.cwd_button = create_action(self, _("Working directory"), icon=get_std_icon('DirOpenIcon'), tip=_("Set current working directory"), triggered=self.set_current_working_directory) self.env_button = create_action(self, _("Environment variables"), icon=get_icon('environ.png'), triggered=self.show_env) self.syspath_button = create_action(self, _("Show sys.path contents"), icon=get_icon('syspath.png'), 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.connect(self.namespacebrowser, SIGNAL('collapse()'), lambda: self.toggle_globals_explorer(False)) # Shell splitter self.splitter = splitter = QSplitter(Qt.Vertical, self) self.connect(self.splitter, SIGNAL('splitterMoved(int, int)'), 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 get_icon('python.png') 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) 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.connect(self.notification_thread, SIGNAL('refresh_namespace_browser()'), self.namespacebrowser.refresh_table) signal = self.notification_thread.sig_process_remote_view signal.connect(self.namespacebrowser.process_remote_view) 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.connect(self.shell, SIGNAL("wait_for_ready_read()"), 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 = [unicode(_path) for _path in self.process.systemEnvironment()] if self.pythonstartup: env.append('PYTHONSTARTUP=%s' % self.pythonstartup) # 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 SMlib.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.connect(self.notification_thread, SIGNAL('pdb(QString,int)'), lambda fname, lineno: self.emit(SIGNAL('pdb(QString,int)'), fname, lineno)) self.connect(self.notification_thread, SIGNAL('new_ipython_kernel(QString)'), lambda args: self.emit(SIGNAL('create_ipython_client(QString)'), args)) self.connect(self.notification_thread, SIGNAL('open_file(QString,int)'), lambda fname, lineno: self.emit(SIGNAL('open_file(QString,int)'), 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) env.append('MATPLOTLIB_PATCH=%r' % self.mpl_patch_enabled) if self.mpl_backend: env.append('MATPLOTLIB_BACKEND=%s' % self.mpl_backend) if self.qt_api: env.append('QT_API=%s' % self.qt_api) env.append('INSTALL_QT_INPUTHOOK=%s' % self.install_qt_inputhook) 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))) if self.pyqt_api: env.append('PYQT_API=%d' % self.pyqt_api) env.append('IGNORE_SIP_SETAPI_ERRORS=%s' % self.ignore_sip_setapi_errors) # User Module Deleter if self.is_interpreter: env.append('UMD_ENABLED=%r' % self.umd_enabled) env.append('UMD_NAMELIST=%s' % ','.join(self.umd_namelist)) env.append('UMD_VERBOSE=%r' % self.umd_verbose) # IPython kernel env.append('IPYTHON_KERNEL=%r' % self.is_ipykernel) pathlist = [] # Fix encoding with custom "sitecustomize.py" 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.connect(self.process, SIGNAL("readyReadStandardOutput()"), self.write_output) self.connect(self.process, SIGNAL("readyReadStandardError()"), self.write_error) self.connect(self.process, SIGNAL("finished(int,QProcess::ExitStatus)"), self.finished) self.connect(self, SIGNAL('finished()'), self.dialog_manager.close_all) self.connect(self.terminate_button, SIGNAL("clicked()"), self.process.terminate) self.connect(self.kill_button, SIGNAL("clicked()"), 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. Add this file's dir to PYTHONPATH. This will make every external # interpreter to use our sitecustomize script. # 3. Remove PYTHONOPTIMIZE from env so that we can have assert # statements working with our interpreters (See Issue 1281) if sys.platform == 'darwin' and 'Spyder.app' in __file__: env.append('SPYDER_INTERPRETER=%s' % self.pythonexecutable) if 'Spyder.app' not in self.pythonexecutable: env = [p for p in env if not (p.startswith('PYTHONPATH') or \ p.startswith('PYTHONHOME'))] # 1. env.append('PYTHONPATH=%s' % osp.dirname(__file__)) # 2. env = [p for p in env if not p.startswith('PYTHONOPTIMIZE')] # 3. self.process.setEnvironment(env) 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 or IPython Console failed to start!")) else: self.shell.setFocus() self.emit(SIGNAL('started()')) 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 isinstance(text, basestring): text = unicode(text) if self.install_qt_inputhook 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 text.startswith(('%', '!')): text = 'evalsc(r"%s")\n' % text if not text.endswith('\n'): text += '\n' self.process.write(locale_codec.fromUnicode(text)) 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 #=============================================================================== 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. #=============================================================================== def set_current_working_directory(self): """Set current working directory""" cwd = self.shell.get_cwd() self.emit(SIGNAL('redirect_stdio(bool)'), False) directory = getexistingdirectory(self, _("Select directory"), cwd) if directory: self.shell.set_cwd(directory) self.emit(SIGNAL('redirect_stdio(bool)'), True) 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)) def show_syspath(self): """Show sys.path contents""" editor = DictEditor() editor.setup(self.shell.get_syspath(), title="sys.path", readonly=True, width=600, icon='syspath.png') self.dialog_manager.show(editor)
class ExternalSystemShell(ExternalShellBase): """External Shell widget: execute Python script in a separate process""" SHELL_CLASS = TerminalWidget 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 get_icon('cmdprompt.png') 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 = [unicode(_path) for _path in self.process.systemEnvironment()] add_pathlist_to_PYTHONPATH(env, self.path) self.process.setEnvironment(env) # 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.connect(self.process, SIGNAL("readyReadStandardOutput()"), self.write_output) self.connect(self.process, SIGNAL("finished(int,QProcess::ExitStatus)"), self.finished) self.connect(self.kill_button, SIGNAL("clicked()"), 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.emit(SIGNAL('started()')) return self.process #=============================================================================== # Input/Output #=============================================================================== def transcode(self, bytes): if os.name == 'nt': return encoding.transcode(str(bytes.data()), 'cp850') else: return ExternalShellBase.transcode(self, bytes) def send_to_process(self, text): if not isinstance(text, basestring): text = unicode(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 ExternalPythonShell(ExternalShellBase): """External Shell widget: execute Python script in a separate process""" SHELL_CLASS = ExtPyQsciShell def __init__(self, parent=None, fname=None, wdir=None, commands=[], interact=False, debug=False, path=[]): ExternalShellBase.__init__(self, parent, wdir, history_filename='.history_ec.py') self.shell.set_externalshell(self) self.toggle_globals_explorer(False) self.interact_check.setChecked(interact) self.debug_check.setChecked(debug) self.monitor_socket = None self.interpreter = fname is None self.fname = startup.__file__ if fname is None else fname if self.interpreter: self.interact_check.hide() self.debug_check.hide() self.terminate_button.hide() self.commands = ["import sys", "sys.path.insert(0, '')"] + commands # Additional python path list self.path = path def get_toolbar_buttons(self): ExternalShellBase.get_toolbar_buttons(self) self.globalsexplorer_button = create_toolbutton( self, get_icon('dictedit.png'), self.tr("Variables"), tip=self.tr("Show/hide global variables explorer"), toggled=self.toggle_globals_explorer) self.terminate_button = create_toolbutton( self, get_icon('terminate.png'), self.tr("Terminate"), tip=self.tr("Attempts to terminate the process.\n" "The process may not exit as a result of clicking " "this button\n(it is given the chance to prompt " "the user for any unsaved files, etc).")) self.interact_check = QCheckBox(self.tr("Interact"), self) self.debug_check = QCheckBox(self.tr("Debug"), self) return [ self.interact_check, self.debug_check, self.globalsexplorer_button, self.run_button, self.terminate_button, self.kill_button ] def get_shell_widget(self): # Globals explorer self.globalsexplorer = GlobalsExplorer(self) self.connect(self.globalsexplorer, SIGNAL('collapse()'), lambda: self.toggle_globals_explorer(False)) # Shell splitter self.splitter = splitter = QSplitter(Qt.Vertical, self) self.connect(self.splitter, SIGNAL('splitterMoved(int, int)'), self.splitter_moved) splitter.addWidget(self.shell) splitter.setCollapsible(0, False) splitter.addWidget(self.globalsexplorer) splitter.setStretchFactor(0, 2) splitter.setStretchFactor(1, 1) return splitter def get_icon(self): return get_icon('python.png') def set_buttons_runnning_state(self, state): ExternalShellBase.set_buttons_runnning_state(self, state) self.interact_check.setEnabled(not state) self.debug_check.setEnabled(not state) self.terminate_button.setEnabled(state) if not state: self.toggle_globals_explorer(False) self.globalsexplorer_button.setEnabled(state) def create_process(self): self.shell.clear() self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.MergedChannels) self.connect(self.shell, SIGNAL("wait_for_ready_read()"), 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 self.interact_check.isChecked(): p_args.append('-i') if self.debug_check.isChecked(): p_args.extend(['-m', 'pdb']) p_args.append(self.fname) env = self.process.systemEnvironment() # Monitor env.append('SHELL_ID=%s' % id(self)) from spyderlib.widgets.externalshell.monitor import start_server server, port = start_server() self.notification_thread = server.register(str(id(self)), self) self.connect(self.notification_thread, SIGNAL('refresh()'), self.globalsexplorer.refresh_table) env.append('SPYDER_PORT=%d' % port) # Python init commands (interpreter only) if self.commands and self.interpreter: env.append('PYTHONINITCOMMANDS=%s' % ';'.join(self.commands)) self.process.setEnvironment(env) pathlist = [] # Fix encoding with custom "sitecustomize.py" scpath = osp.dirname(osp.abspath(__file__)) pathlist.append(scpath) # Adding Spyder path pathlist += self.path # Adding path list to PYTHONPATH environment variable pypath = "PYTHONPATH" pathstr = os.pathsep.join(pathlist) if os.environ.get(pypath) is not None: env.replaceInStrings(pypath + '=', pypath + '=' + pathstr + os.pathsep, Qt.CaseSensitive) else: env.append(pypath + '=' + pathstr) self.process.setEnvironment(env) #-------------------------Python specific------------------------------- if self.arguments: p_args.extend(self.arguments.split(' ')) self.connect(self.process, SIGNAL("readyReadStandardOutput()"), self.write_output) self.connect(self.process, SIGNAL("finished(int,QProcess::ExitStatus)"), self.finished) self.connect(self.terminate_button, SIGNAL("clicked()"), self.process.terminate) self.connect(self.kill_button, SIGNAL("clicked()"), self.process.kill) #-------------------------Python specific------------------------------- self.process.start(sys.executable, p_args) #-------------------------Python specific------------------------------- running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, self.tr("Error"), self.tr("Process failed to start")) else: self.shell.setFocus() self.emit(SIGNAL('started()')) return self.process #=============================================================================== # Input/Output #=============================================================================== def _write_error(self, text, findstr): pos = text.find(findstr) if pos != -1: self.shell.write(text[:pos]) if text.endswith(">>> "): self.shell.write_error(text[pos:-5]) self.shell.write(text[-5:], flush=True) else: self.shell.write_error(text[pos:]) return True return False def write_output(self): text = self.get_stdout() if not self._write_error(text, 'Traceback (most recent call last):') \ and not self._write_error(text, 'File "<stdin>", line 1'): self.shell.write(text) QApplication.processEvents() def send_to_process(self, qstr): if not isinstance(qstr, QString): qstr = QString(qstr) if not qstr.endsWith('\n'): qstr.append('\n') self.process.write(qstr.toLocal8Bit()) self.process.waitForBytesWritten(-1) def keyboard_interrupt(self): communicate(self.monitor_socket, "thread.interrupt_main()") #=============================================================================== # Globals explorer #=============================================================================== def toggle_globals_explorer(self, state): self.splitter.setSizes([1, 1 if state else 0]) self.globalsexplorer_button.setChecked(state) if state: self.globalsexplorer.refresh_table() def splitter_moved(self, pos, index): self.globalsexplorer_button.setChecked(self.splitter.sizes()[1])
class ScriptRunner(QObject): def __init__(self, caller, parent=None, env=None, addEnv=None): QObject.__init__(self, parent) self._proc = None self._caller = caller self._decode = None if env is None: env = os.environ.copy() self._env = env if addEnv: self._env.update(addEnv) def stop(self, kill=False): if self._proc is None: return if kill: self._proc.kill() else: self._proc.terminate() def run(self, scriptFile, args=None, env=None, wd=None): if self._proc: return self._proc = QProcess(self) if wd: self._proc.setWorkingDirectory(wd) if not env is None: self._env.update(env) envList = dict2qlst(self._env) if args is None: args = [] script = quote(scriptFile) if not os.path.exists(script): script = find_script(script) if not script or not os.path.exists(script): raise PublishException("Script '%s' cannot be found" % script) self._caller.handleScriptOutput("%s %s" % (scriptFile, ' '.join(args))) self._proc.setEnvironment(envList) self._proc.setProcessChannelMode(QProcess.MergedChannels) QObject.connect(self._proc, QtCore.SIGNAL("readyReadStandardOutput()"), self.readScriptOutput) QObject.connect(self._proc, QtCore.SIGNAL("finished(int, " \ "QProcess::ExitStatus)"), self.scriptFinished) self._proc.start(script, args, QIODevice.ReadOnly) def scriptFinished(self, exitCode, exitStatus): if self._caller: self.readScriptOutput() self._caller.scriptFinished(exitCode, exitStatus == QProcess.Crashed) self._proc = None def readScriptOutput(self): if self._proc is None: return qba = self._proc.readAllStandardOutput() if self._caller and qba.length(): if not self._decode: lang, deflocale = locale.getdefaultlocale() self._decode = codecs.getdecoder(deflocale) data, datalen = self._decode(qba.data()) self._caller.handleScriptOutput(data)
class ExternalPythonShell(ExternalShellBase): """External Shell widget: execute Python script in a separate process""" SHELL_CLASS = ExtPythonShellWidget def __init__(self, parent=None, fname=None, wdir=None, interact=False, debug=False, path=[], python_args='', ipykernel=False, arguments='', stand_alone=None, umd_enabled=True, umd_namelist=[], umd_verbose=True, pythonstartup=None, pythonexecutable=None, monitor_enabled=True, mpl_patch_enabled=True, mpl_backend=None, ets_backend='qt4', qt_api=None, pyqt_api=0, install_qt_inputhook=True, ignore_sip_setapi_errors=False, 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') self.namespacebrowser = None # namespace browser widget! self.dialog_manager = DialogManager() self.stand_alone = stand_alone # stand alone settings (None: plugin) self.pythonstartup = pythonstartup self.pythonexecutable = pythonexecutable self.monitor_enabled = monitor_enabled self.mpl_patch_enabled = mpl_patch_enabled self.mpl_backend = mpl_backend self.ets_backend = ets_backend self.qt_api = qt_api self.pyqt_api = pyqt_api self.install_qt_inputhook = install_qt_inputhook self.ignore_sip_setapi_errors = ignore_sip_setapi_errors self.merge_output_channels = merge_output_channels self.colorize_sys_stderr = colorize_sys_stderr self.umd_enabled = umd_enabled self.umd_namelist = umd_namelist self.umd_verbose = umd_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 isinstance(python_args, basestring) self.python_args = python_args assert isinstance(arguments, basestring) self.arguments = arguments self.connection_file = None self.is_ipykernel = ipykernel if self.is_ipykernel: interact = False # Running our custom startup script for IPython kernels: # (see spyderlib/widgets/externalshell/start_ipython_kernel.py) self.fname = get_module_source_path('SMlib.widgets.externalshell', 'start_ipython_kernel.py') self.shell.set_externalshell(self) self.toggle_globals_explorer(False) self.interact_action.setChecked(interact) self.debug_action.setChecked(debug) self.introspection_socket = None self.is_interpreter = fname is None if self.is_interpreter: self.terminate_button.hide() # 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=get_icon('dictedit.png'), 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=get_icon('terminate.png'), tip=_("""Attempts to terminate the process. The process may not exit as a result of clicking this button (it is given the chance to prompt 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.options_button, self.terminate_button, self.kill_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) run_settings_menu = QMenu(_("Run settings"), self) add_actions( run_settings_menu, (self.interact_action, self.debug_action, self.args_action)) self.cwd_button = create_action( self, _("Working directory"), icon=get_std_icon('DirOpenIcon'), tip=_("Set current working directory"), triggered=self.set_current_working_directory) self.env_button = create_action(self, _("Environment variables"), icon=get_icon('environ.png'), triggered=self.show_env) self.syspath_button = create_action(self, _("Show sys.path contents"), icon=get_icon('syspath.png'), 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.connect(self.namespacebrowser, SIGNAL('collapse()'), lambda: self.toggle_globals_explorer(False)) # Shell splitter self.splitter = splitter = QSplitter(Qt.Vertical, self) self.connect(self.splitter, SIGNAL('splitterMoved(int, int)'), 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 get_icon('python.png') 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) 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.connect(self.notification_thread, SIGNAL('refresh_namespace_browser()'), self.namespacebrowser.refresh_table) signal = self.notification_thread.sig_process_remote_view signal.connect(self.namespacebrowser.process_remote_view) 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.connect(self.shell, SIGNAL("wait_for_ready_read()"), 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 = [unicode(_path) for _path in self.process.systemEnvironment()] if self.pythonstartup: env.append('PYTHONSTARTUP=%s' % self.pythonstartup) # 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 SMlib.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.connect( self.notification_thread, SIGNAL('pdb(QString,int)'), lambda fname, lineno: self.emit( SIGNAL('pdb(QString,int)'), fname, lineno)) self.connect( self.notification_thread, SIGNAL('new_ipython_kernel(QString)'), lambda args: self.emit( SIGNAL('create_ipython_client(QString)'), args)) self.connect( self.notification_thread, SIGNAL('open_file(QString,int)'), lambda fname, lineno: self.emit( SIGNAL('open_file(QString,int)'), 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) env.append('MATPLOTLIB_PATCH=%r' % self.mpl_patch_enabled) if self.mpl_backend: env.append('MATPLOTLIB_BACKEND=%s' % self.mpl_backend) if self.qt_api: env.append('QT_API=%s' % self.qt_api) env.append('INSTALL_QT_INPUTHOOK=%s' % self.install_qt_inputhook) 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))) if self.pyqt_api: env.append('PYQT_API=%d' % self.pyqt_api) env.append('IGNORE_SIP_SETAPI_ERRORS=%s' % self.ignore_sip_setapi_errors) # User Module Deleter if self.is_interpreter: env.append('UMD_ENABLED=%r' % self.umd_enabled) env.append('UMD_NAMELIST=%s' % ','.join(self.umd_namelist)) env.append('UMD_VERBOSE=%r' % self.umd_verbose) # IPython kernel env.append('IPYTHON_KERNEL=%r' % self.is_ipykernel) pathlist = [] # Fix encoding with custom "sitecustomize.py" 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.connect(self.process, SIGNAL("readyReadStandardOutput()"), self.write_output) self.connect(self.process, SIGNAL("readyReadStandardError()"), self.write_error) self.connect(self.process, SIGNAL("finished(int,QProcess::ExitStatus)"), self.finished) self.connect(self, SIGNAL('finished()'), self.dialog_manager.close_all) self.connect(self.terminate_button, SIGNAL("clicked()"), self.process.terminate) self.connect(self.kill_button, SIGNAL("clicked()"), 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. Add this file's dir to PYTHONPATH. This will make every external # interpreter to use our sitecustomize script. # 3. Remove PYTHONOPTIMIZE from env so that we can have assert # statements working with our interpreters (See Issue 1281) if sys.platform == 'darwin' and 'Spyder.app' in __file__: env.append('SPYDER_INTERPRETER=%s' % self.pythonexecutable) if 'Spyder.app' not in self.pythonexecutable: env = [p for p in env if not (p.startswith('PYTHONPATH') or \ p.startswith('PYTHONHOME'))] # 1. env.append('PYTHONPATH=%s' % osp.dirname(__file__)) # 2. env = [p for p in env if not p.startswith('PYTHONOPTIMIZE')] # 3. self.process.setEnvironment(env) 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 or IPython Console failed to start!")) else: self.shell.setFocus() self.emit(SIGNAL('started()')) 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 isinstance(text, basestring): text = unicode(text) if self.install_qt_inputhook 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 text.startswith(('%', '!')): text = 'evalsc(r"%s")\n' % text if not text.endswith('\n'): text += '\n' self.process.write(locale_codec.fromUnicode(text)) 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 #=============================================================================== 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. #=============================================================================== def set_current_working_directory(self): """Set current working directory""" cwd = self.shell.get_cwd() self.emit(SIGNAL('redirect_stdio(bool)'), False) directory = getexistingdirectory(self, _("Select directory"), cwd) if directory: self.shell.set_cwd(directory) self.emit(SIGNAL('redirect_stdio(bool)'), True) 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)) def show_syspath(self): """Show sys.path contents""" editor = DictEditor() editor.setup(self.shell.get_syspath(), title="sys.path", readonly=True, width=600, icon='syspath.png') self.dialog_manager.show(editor)