def __init__(self, parent=None, namespace=None, commands=[], message=None, exitfunc=None, profile=False, multithreaded=False): if PYQT5: SpyderPluginWidget.__init__(self, parent, main=parent) else: SpyderPluginWidget.__init__(self, parent) debug_print(" ..internal console: initializing") self.dialog_manager = DialogManager() # Shell light_background = self.get_option('light_background') self.shell = InternalShell(parent, namespace, commands, message, self.get_option('max_line_count'), self.get_plugin_font(), exitfunc, profile, multithreaded, light_background=light_background) self.shell.status.connect(lambda msg: self.show_message.emit(msg, 0)) self.shell.go_to_error.connect(self.go_to_error) self.shell.focus_changed.connect(lambda: self.focus_changed.emit()) # Redirecting some signals: self.shell.redirect_stdio.connect( lambda state: self.redirect_stdio.emit(state)) # Initialize plugin self.initialize_plugin() # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.set_editor(self.shell) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) # Main layout layout = QVBoxLayout() layout.addWidget(self.shell) layout.addWidget(self.find_widget) self.setLayout(layout) # Parameters self.shell.toggle_wrap_mode(self.get_option('wrap')) # Accepting drops self.setAcceptDrops(True) # Traceback MessageBox self.msgbox_traceback = None self.error_traceback = ""
def __init__(self, name, plugin, parent=None): super().__init__(name, plugin, parent) logger.info("Initializing...") # Traceback MessageBox self.error_traceback = '' self.dismiss_error = False # Header message message = _( "Spyder Internal Console\n\n" "This console is used to report application\n" "internal errors and to inspect Spyder\n" "internals with the following commands:\n" " spy.app, spy.window, dir(spy)\n\n" "Please do not use it to run your code\n\n" ) # Options that come from the command line cli_options = plugin.get_command_line_options() profile = cli_options.profile multithreaded = cli_options.multithreaded # Widgets self.dialog_manager = DialogManager() self.error_dlg = None self.shell = InternalShell( # TODO: Move to use SpyderWidgetMixin? commands=[], message=message, max_line_count=self.get_conf('max_line_count'), profile=profile, multithreaded=multithreaded, ) self.find_widget = FindReplace(self) # Setup self.setAcceptDrops(True) self.find_widget.set_editor(self.shell) self.find_widget.hide() self.shell.toggle_wrap_mode(self.get_conf('wrap')) # Layout layout = QVBoxLayout() layout.addWidget(self.shell) layout.addWidget(self.find_widget) self.setLayout(layout) # Signals self.shell.sig_help_requested.connect(self.sig_help_requested) self.shell.sig_exception_occurred.connect(self.handle_exception) self.shell.sig_focus_changed.connect(self.sig_focus_changed) self.shell.sig_go_to_error_requested.connect(self.go_to_error) self.shell.sig_redirect_stdio_requested.connect( self.sig_redirect_stdio_requested) self.shell.sig_refreshed.connect(self.sig_refreshed) self.shell.sig_show_status_requested.connect( lambda msg: self.sig_show_status_message.emit(msg, 0))
def __init__(self, plugin, id_, history_filename, config_options, additional_options, interpreter_versions, connection_file=None, hostname=None, menu_actions=None, slave=False, external_kernel=False, given_name=None, options_button=None): super(ClientWidget, self).__init__(plugin) SaveHistoryMixin.__init__(self, history_filename) # --- Init attrs self.id_ = id_ self.connection_file = connection_file self.hostname = hostname self.menu_actions = menu_actions self.slave = slave self.given_name = given_name # --- Other attrs self.options_button = options_button self.stop_button = None self.stop_icon = ima.icon('stop') self.history = [] self.allow_rename = True self.stderr_dir = None # --- Widgets self.shellwidget = ShellWidget(config=config_options, ipyclient=self, additional_options=additional_options, interpreter_versions=interpreter_versions, external_kernel=external_kernel, local_kernel=True) self.infowidget = WebView(self) self.set_infowidget_font() self.loading_page = self._create_loading_page() self._show_loading_page() # --- Layout vlayout = QVBoxLayout() toolbar_buttons = self.get_toolbar_buttons() hlayout = QHBoxLayout() for button in toolbar_buttons: hlayout.addWidget(button) vlayout.addLayout(hlayout) vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addWidget(self.shellwidget) vlayout.addWidget(self.infowidget) self.setLayout(vlayout) # --- Exit function self.exit_callback = lambda: plugin.close_client(client=self) # --- Dialog manager self.dialog_manager = DialogManager()
def __init__(self, parent=None, namespace=None, commands=[], message=None, exitfunc=None, profile=False, multithreaded=False): SpyderPluginWidget.__init__(self, parent) logger.info("Initializing...") self.dialog_manager = DialogManager() # Shell self.shell = InternalShell(parent, namespace, commands, message, self.get_option('max_line_count'), self.get_plugin_font(), exitfunc, profile, multithreaded) self.shell.status.connect(lambda msg: self.show_message.emit(msg, 0)) self.shell.go_to_error.connect(self.go_to_error) self.shell.focus_changed.connect(lambda: self.focus_changed.emit()) # Redirecting some signals: self.shell.redirect_stdio.connect( lambda state: self.redirect_stdio.emit(state)) # Initialize plugin self.initialize_plugin() # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.set_editor(self.shell) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) # Main layout btn_layout = QHBoxLayout() btn_layout.setAlignment(Qt.AlignLeft) btn_layout.addStretch() btn_layout.addWidget(self.options_button, Qt.AlignRight) layout = create_plugin_layout(btn_layout) layout.addWidget(self.shell) layout.addWidget(self.find_widget) self.setLayout(layout) # Parameters self.shell.toggle_wrap_mode(self.get_option('wrap')) # Accepting drops self.setAcceptDrops(True) # Traceback MessageBox self.error_dlg = None self.error_traceback = "" self.dismiss_error = False
def __init__(self, plugin, id_, history_filename, config_options, additional_options, interpreter_versions, connection_file=None, hostname=None, menu_actions=None, slave=False, external_kernel=False, given_name=None): super(ClientWidget, self).__init__(plugin) SaveHistoryMixin.__init__(self, history_filename) # --- Init attrs self.id_ = id_ self.connection_file = connection_file self.hostname = hostname self.menu_actions = menu_actions self.slave = slave self.given_name = given_name # --- Other attrs self.options_button = None self.stop_button = None self.stop_icon = ima.icon('stop') self.history = [] self.allow_rename = True self.stderr_dir = None # --- Widgets self.shellwidget = ShellWidget(config=config_options, ipyclient=self, additional_options=additional_options, interpreter_versions=interpreter_versions, external_kernel=external_kernel, local_kernel=True) self.infowidget = WebView(self) self.set_infowidget_font() self.loading_page = self._create_loading_page() self._show_loading_page() # --- Layout vlayout = QVBoxLayout() toolbar_buttons = self.get_toolbar_buttons() hlayout = QHBoxLayout() for button in toolbar_buttons: hlayout.addWidget(button) vlayout.addLayout(hlayout) vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addWidget(self.shellwidget) vlayout.addWidget(self.infowidget) self.setLayout(vlayout) # --- Exit function self.exit_callback = lambda: plugin.close_client(client=self) # --- Signals # As soon as some content is printed in the console, stop # our loading animation document = self.get_control().document() document.contentsChange.connect(self._hide_loading_page) # --- Dialog manager self.dialog_manager = DialogManager()
def __init__(self, name, plugin, parent=None): super().__init__(name, plugin, parent) logger.info("Initializing...") # Traceback MessageBox self.error_traceback = '' self.dismiss_error = False # Widgets self.dialog_manager = DialogManager() self.error_dlg = None self.shell = InternalShell( # TODO: Move to use SpyderWidgetMixin? parent=parent, namespace=self.get_conf('namespace', {}), commands=self.get_conf('commands', []), message=self.get_conf('message', ''), max_line_count=self.get_conf('max_line_count'), profile=self.get_conf('profile', False), multithreaded=self.get_conf('multithreaded', False), ) self.find_widget = FindReplace(self) # Setup self.setAcceptDrops(True) self.find_widget.set_editor(self.shell) self.find_widget.hide() self.shell.toggle_wrap_mode(self.get_conf('wrap')) # Layout layout = QVBoxLayout() layout.addWidget(self.shell) layout.addWidget(self.find_widget) self.setLayout(layout) # Signals self.shell.sig_help_requested.connect(self.sig_help_requested) self.shell.sig_exception_occurred.connect(self.handle_exception) self.shell.sig_focus_changed.connect(self.sig_focus_changed) self.shell.sig_go_to_error_requested.connect(self.go_to_error) self.shell.sig_redirect_stdio_requested.connect( self.sig_redirect_stdio_requested) self.shell.sig_refreshed.connect(self.sig_refreshed) self.shell.sig_show_status_requested.connect( lambda msg: self.sig_show_status_message.emit(msg, 0))
def __init__( self, parent=None, namespace=None, commands=[], message=None, exitfunc=None, profile=False, multithreaded=False ): if PYQT5: SpyderPluginWidget.__init__(self, parent, main=parent) else: SpyderPluginWidget.__init__(self, parent) debug_print(" ..internal console: initializing") self.dialog_manager = DialogManager() # Shell light_background = self.get_option("light_background") self.shell = InternalShell( parent, namespace, commands, message, self.get_option("max_line_count"), self.get_plugin_font(), exitfunc, profile, multithreaded, light_background=light_background, ) self.shell.status.connect(lambda msg: self.show_message.emit(msg, 0)) self.shell.go_to_error.connect(self.go_to_error) self.shell.focus_changed.connect(lambda: self.focus_changed.emit()) # Redirecting some signals: self.shell.redirect_stdio.connect(lambda state: self.redirect_stdio.emit(state)) # Initialize plugin self.initialize_plugin() # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.set_editor(self.shell) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) # Main layout layout = QVBoxLayout() layout.addWidget(self.shell) layout.addWidget(self.find_widget) self.setLayout(layout) # Parameters self.shell.toggle_wrap_mode(self.get_option("wrap")) # Accepting drops self.setAcceptDrops(True)
def __init__(self, parent=None, namespace=None, commands=[], message=None, exitfunc=None, profile=False, multithreaded=False): SpyderPluginWidget.__init__(self, parent) logger.info("Initializing...") self.dialog_manager = DialogManager() # Shell light_background = self.get_option('light_background') self.shell = InternalShell(parent, namespace, commands, message, self.get_option('max_line_count'), self.get_plugin_font(), exitfunc, profile, multithreaded, light_background=light_background) self.shell.status.connect(lambda msg: self.show_message.emit(msg, 0)) self.shell.go_to_error.connect(self.go_to_error) self.shell.focus_changed.connect(lambda: self.focus_changed.emit()) # Redirecting some signals: self.shell.redirect_stdio.connect(lambda state: self.redirect_stdio.emit(state)) # Initialize plugin self.initialize_plugin() # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.set_editor(self.shell) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) # Main layout btn_layout = QHBoxLayout() btn_layout.setAlignment(Qt.AlignLeft) btn_layout.addStretch() btn_layout.addWidget(self.options_button, Qt.AlignRight) layout = create_plugin_layout(btn_layout) layout.addWidget(self.shell) layout.addWidget(self.find_widget) self.setLayout(layout) # Parameters self.shell.toggle_wrap_mode(self.get_option('wrap')) # Accepting drops self.setAcceptDrops(True) # Traceback MessageBox self.error_dlg = None self.error_traceback = "" self.dismiss_error = False
class Console(SpyderPluginWidget): """ Console widget """ CONF_SECTION = 'internal_console' focus_changed = Signal() redirect_stdio = Signal(bool) edit_goto = Signal(str, int, str) def __init__(self, parent=None, namespace=None, commands=[], message=None, exitfunc=None, profile=False, multithreaded=False): SpyderPluginWidget.__init__(self, parent) logger.info("Initializing...") self.dialog_manager = DialogManager() # Shell light_background = self.get_option('light_background') self.shell = InternalShell(parent, namespace, commands, message, self.get_option('max_line_count'), self.get_plugin_font(), exitfunc, profile, multithreaded, light_background=light_background) self.shell.status.connect(lambda msg: self.show_message.emit(msg, 0)) self.shell.go_to_error.connect(self.go_to_error) self.shell.focus_changed.connect(lambda: self.focus_changed.emit()) # Redirecting some signals: self.shell.redirect_stdio.connect(lambda state: self.redirect_stdio.emit(state)) # Initialize plugin self.initialize_plugin() # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.set_editor(self.shell) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) # Main layout btn_layout = QHBoxLayout() btn_layout.setAlignment(Qt.AlignLeft) btn_layout.addStretch() btn_layout.addWidget(self.options_button, Qt.AlignRight) layout = create_plugin_layout(btn_layout) layout.addWidget(self.shell) layout.addWidget(self.find_widget) self.setLayout(layout) # Parameters self.shell.toggle_wrap_mode(self.get_option('wrap')) # Accepting drops self.setAcceptDrops(True) # Traceback MessageBox self.error_dlg = None self.error_traceback = "" self.dismiss_error = False #------ Private API -------------------------------------------------------- def set_historylog(self, historylog): """Bind historylog instance to this console Not used anymore since v2.0""" historylog.add_history(self.shell.history_filename) self.shell.append_to_history.connect(historylog.append_to_history) def set_help(self, help_plugin): """Bind help instance to this console""" self.shell.help = help_plugin #------ SpyderPluginWidget API --------------------------------------------- def get_plugin_title(self): """Return widget title""" return _('Internal console') def get_focus_widget(self): """ Return the widget to give focus to when this plugin's dockwidget is raised on top-level """ return self.shell def update_font(self): """Update font from Preferences""" font = self.get_plugin_font() self.shell.set_font(font) def closing_plugin(self, cancelable=False): """Perform actions before parent main window is closed""" self.dialog_manager.close_all() self.shell.exit_interpreter() return True def refresh_plugin(self): pass def get_plugin_actions(self): """Return a list of actions related to plugin""" quit_action = create_action(self, _("&Quit"), icon=ima.icon('exit'), tip=_("Quit"), triggered=self.quit) self.register_shortcut(quit_action, "_", "Quit", "Ctrl+Q") run_action = create_action(self, _("&Run..."), None, ima.icon('run_small'), _("Run a Python script"), triggered=self.run_script) environ_action = create_action(self, _("Environment variables..."), icon=ima.icon('environ'), tip=_("Show and edit environment variables" " (for current session)"), triggered=self.show_env) syspath_action = create_action(self, _("Show sys.path contents..."), icon=ima.icon('syspath'), tip=_("Show (read-only) sys.path"), triggered=self.show_syspath) buffer_action = create_action(self, _("Buffer..."), None, tip=_("Set maximum line count"), triggered=self.change_max_line_count) exteditor_action = create_action(self, _("External editor path..."), None, None, _("Set external editor executable path"), triggered=self.change_exteditor) wrap_action = create_action(self, _("Wrap lines"), toggled=self.toggle_wrap_mode) wrap_action.setChecked(self.get_option('wrap')) calltips_action = create_action(self, _("Display balloon tips"), toggled=self.toggle_calltips) calltips_action.setChecked(self.get_option('calltips')) codecompletion_action = create_action(self, _("Automatic code completion"), toggled=self.toggle_codecompletion) codecompletion_action.setChecked(self.get_option('codecompletion/auto')) codecompenter_action = create_action(self, _("Enter key selects completion"), toggled=self.toggle_codecompletion_enter) codecompenter_action.setChecked(self.get_option( 'codecompletion/enter_key')) option_menu = QMenu(_('Internal console settings'), self) option_menu.setIcon(ima.icon('tooloptions')) add_actions(option_menu, (buffer_action, wrap_action, calltips_action, codecompletion_action, codecompenter_action, exteditor_action)) plugin_actions = [None, run_action, environ_action, syspath_action, option_menu, MENU_SEPARATOR, quit_action, self.undock_action] return plugin_actions def register_plugin(self): """Register plugin in Spyder's main window""" self.focus_changed.connect(self.main.plugin_focus_changed) self.main.add_dockwidget(self) # Connecting the following signal once the dockwidget has been created: self.shell.exception_occurred.connect(self.exception_occurred) def exception_occurred(self, text, is_traceback): """ Exception ocurred in the internal console. Show a QDialog or the internal console to warn the user. """ # Skip errors without traceback or dismiss if (not is_traceback and self.error_dlg is None) or self.dismiss_error: return if CONF.get('main', 'show_internal_errors'): if self.error_dlg is None: self.error_dlg = SpyderErrorDialog(self) self.error_dlg.close_btn.clicked.connect(self.close_error_dlg) self.error_dlg.rejected.connect(self.remove_error_dlg) self.error_dlg.details.go_to_error.connect(self.go_to_error) self.error_dlg.show() self.error_dlg.append_traceback(text) elif DEV or get_debug_level(): self.dockwidget.show() self.dockwidget.raise_() def close_error_dlg(self): """Close error dialog.""" if self.error_dlg.dismiss_box.isChecked(): self.dismiss_error = True self.error_dlg.reject() def remove_error_dlg(self): """Remove error dialog.""" self.error_dlg = None #------ Public API --------------------------------------------------------- @Slot() def quit(self): """Quit mainwindow""" self.main.close() @Slot() def show_env(self): """Show environment variables""" self.dialog_manager.show(EnvDialog()) @Slot() def show_syspath(self): """Show sys.path""" editor = CollectionsEditor() editor.setup(sys.path, title="sys.path", readonly=True, width=600, icon=ima.icon('syspath')) self.dialog_manager.show(editor) @Slot() def run_script(self, filename=None, silent=False, set_focus=False, args=None): """Run a Python script""" if filename is None: self.shell.interpreter.restore_stds() filename, _selfilter = getopenfilename( self, _("Run Python script"), getcwd_or_home(), _("Python scripts")+" (*.py ; *.pyw ; *.ipy)") self.shell.interpreter.redirect_stds() if filename: os.chdir( osp.dirname(filename) ) filename = osp.basename(filename) else: return logger.debug("Running script with %s", args) filename = osp.abspath(filename) rbs = remove_backslashes command = "runfile('%s', args='%s')" % (rbs(filename), rbs(args)) if set_focus: self.shell.setFocus() if self.dockwidget and not self.ismaximized: self.dockwidget.setVisible(True) self.dockwidget.raise_() self.shell.write(command+'\n') self.shell.run_command(command) def go_to_error(self, text): """Go to error if relevant""" match = get_error_match(to_text_string(text)) if match: fname, lnb = match.groups() self.edit_script(fname, int(lnb)) def edit_script(self, filename=None, goto=-1): """Edit script""" # Called from InternalShell if not hasattr(self, 'main') \ or not hasattr(self.main, 'editor'): self.shell.external_editor(filename, goto) return if filename is not None: self.edit_goto.emit(osp.abspath(filename), goto, '') def execute_lines(self, lines): """Execute lines and give focus to shell""" self.shell.execute_lines(to_text_string(lines)) self.shell.setFocus() @Slot() def change_max_line_count(self): "Change maximum line count""" mlc, valid = QInputDialog.getInt(self, _('Buffer'), _('Maximum line count'), self.get_option('max_line_count'), 0, 1000000) if valid: self.shell.setMaximumBlockCount(mlc) self.set_option('max_line_count', mlc) @Slot() def change_exteditor(self): """Change external editor path""" path, valid = QInputDialog.getText(self, _('External editor'), _('External editor executable path:'), QLineEdit.Normal, self.get_option('external_editor/path')) if valid: self.set_option('external_editor/path', to_text_string(path)) @Slot(bool) def toggle_wrap_mode(self, checked): """Toggle wrap mode""" self.shell.toggle_wrap_mode(checked) self.set_option('wrap', checked) @Slot(bool) def toggle_calltips(self, checked): """Toggle calltips""" self.shell.set_calltips(checked) self.set_option('calltips', checked) @Slot(bool) def toggle_codecompletion(self, checked): """Toggle automatic code completion""" self.shell.set_codecompletion_auto(checked) self.set_option('codecompletion/auto', checked) @Slot(bool) def toggle_codecompletion_enter(self, checked): """Toggle Enter key for code completion""" self.shell.set_codecompletion_enter(checked) self.set_option('codecompletion/enter_key', checked) #----Drag and drop def dragEnterEvent(self, event): """Reimplement Qt method Inform Qt about the types of data that the widget accepts""" source = event.mimeData() if source.hasUrls(): if mimedata2url(source): event.acceptProposedAction() else: event.ignore() elif source.hasText(): event.acceptProposedAction() def dropEvent(self, event): """Reimplement Qt method Unpack dropped data and handle it""" source = event.mimeData() if source.hasUrls(): pathlist = mimedata2url(source) self.shell.drop_pathlist(pathlist) elif source.hasText(): lines = to_text_string(source.text()) self.shell.set_cursor_position('eof') self.shell.execute_lines(lines) event.acceptProposedAction()
class ConsoleWidget(PluginMainWidget): # --- Signals # This signal emits a parsed error traceback text so we can then # request opening the file that traceback comes from in the Editor. sig_edit_goto_requested = Signal(str, int, str) # TODO: I do not think we use this? sig_focus_changed = Signal() # Emit this when the interpreter buffer is flushed sig_refreshed = Signal() # Request to show a status message on the main window sig_show_status_requested = Signal(str) # Request the main application to quit. sig_quit_requested = Signal() sig_help_requested = Signal(dict) """ This signal is emitted to request help on a given object `name`. Parameters ---------- help_data: dict Example `{'name': str, 'ignore_unknown': bool}`. """ def __init__(self, name, plugin, parent=None): super().__init__(name, plugin, parent) logger.info("Initializing...") # Traceback MessageBox self.error_traceback = '' self.dismiss_error = False # Widgets self.dialog_manager = DialogManager() self.error_dlg = None self.shell = InternalShell( # TODO: Move to use SpyderWidgetMixin? parent=parent, namespace=self.get_conf('namespace', {}), commands=self.get_conf('commands', []), message=self.get_conf('message', ''), max_line_count=self.get_conf('max_line_count'), profile=self.get_conf('profile', False), multithreaded=self.get_conf('multithreaded', False), ) self.find_widget = FindReplace(self) # Setup self.setAcceptDrops(True) self.find_widget.set_editor(self.shell) self.find_widget.hide() self.shell.toggle_wrap_mode(self.get_conf('wrap')) # Layout layout = QVBoxLayout() layout.addWidget(self.shell) layout.addWidget(self.find_widget) self.setLayout(layout) # Signals self.shell.sig_help_requested.connect(self.sig_help_requested) self.shell.sig_exception_occurred.connect(self.handle_exception) self.shell.sig_focus_changed.connect(self.sig_focus_changed) self.shell.sig_go_to_error_requested.connect(self.go_to_error) self.shell.sig_redirect_stdio_requested.connect( self.sig_redirect_stdio_requested) self.shell.sig_refreshed.connect(self.sig_refreshed) self.shell.sig_show_status_requested.connect( lambda msg: self.sig_show_status_message.emit(msg, 0)) # --- PluginMainWidget API # ------------------------------------------------------------------------ def get_title(self): return _('Internal console') def setup(self): # TODO: Move this to the shell self.quit_action = self.create_action( ConsoleWidgetActions.Quit, text=_("&Quit"), tip=_("Quit"), icon=self.create_icon('exit'), triggered=self.sig_quit_requested, context=Qt.ApplicationShortcut, shortcut_context="_", register_shortcut=True) run_action = self.create_action( ConsoleWidgetActions.Run, text=_("&Run..."), tip=_("Run a Python script"), icon=self.create_icon('run_small'), triggered=self.run_script, ) environ_action = self.create_action( ConsoleWidgetActions.Environment, text=_("Environment variables..."), tip=_("Show and edit environment variables (for current " "session)"), icon=self.create_icon('environ'), triggered=self.show_env, ) syspath_action = self.create_action( ConsoleWidgetActions.SysPath, text=_("Show sys.path contents..."), tip=_("Show (read-only) sys.path"), icon=self.create_icon('syspath'), triggered=self.show_syspath, ) buffer_action = self.create_action( ConsoleWidgetActions.MaxLineCount, text=_("Buffer..."), tip=_("Set maximum line count"), triggered=self.change_max_line_count, ) exteditor_action = self.create_action( ConsoleWidgetActions.ExternalEditor, text=_("External editor path..."), tip=_("Set external editor executable path"), triggered=self.change_exteditor, ) wrap_action = self.create_action( ConsoleWidgetActions.ToggleWrap, text=_("Wrap lines"), toggled=lambda val: self.set_conf('wrap', val), initial=self.get_conf('wrap'), ) codecompletion_action = self.create_action( ConsoleWidgetActions.ToggleCodeCompletion, text=_("Automatic code completion"), toggled=lambda val: self.set_conf('codecompletion/auto', val), initial=self.get_conf('codecompletion/auto'), ) # Submenu internal_settings_menu = self.create_menu( ConsoleWidgetMenus.InternalSettings, _('Internal console settings'), icon=self.create_icon('tooloptions'), ) for item in [ buffer_action, wrap_action, codecompletion_action, exteditor_action ]: self.add_item_to_menu( item, menu=internal_settings_menu, section=ConsoleWidgetInternalSettingsSubMenuSections.Main, ) # Options menu options_menu = self.get_options_menu() for item in [ run_action, environ_action, syspath_action, internal_settings_menu ]: self.add_item_to_menu( item, menu=options_menu, section=ConsoleWidgetOptionsMenuSections.Run, ) self.add_item_to_menu( self.quit_action, menu=options_menu, section=ConsoleWidgetOptionsMenuSections.Quit, ) self.shell.set_external_editor(self.get_conf('external_editor/path'), '') @on_conf_change(option='max_line_count') def max_line_count_update(self, value): self.shell.setMaximumBlockCount(value) @on_conf_change(option='wrap') def wrap_mode_update(self, value): self.shell.toggle_wrap_mode(value) @on_conf_change(option='external_editor/path') def external_editor_update(self, value): self.shell.set_external_editor(value, '') def update_actions(self): pass def get_focus_widget(self): return self.shell # --- Qt overrides # ------------------------------------------------------------------------ def dragEnterEvent(self, event): """ Reimplement Qt method. Inform Qt about the types of data that the widget accepts. """ source = event.mimeData() if source.hasUrls(): if mimedata2url(source): event.acceptProposedAction() else: event.ignore() elif source.hasText(): event.acceptProposedAction() def dropEvent(self, event): """ Reimplement Qt method. Unpack dropped data and handle it. """ source = event.mimeData() if source.hasUrls(): pathlist = mimedata2url(source) self.shell.drop_pathlist(pathlist) elif source.hasText(): lines = to_text_string(source.text()) self.shell.set_cursor_position('eof') self.shell.execute_lines(lines) event.acceptProposedAction() # --- Public API # ------------------------------------------------------------------------ def start_interpreter(self, namespace): """ Start internal console interpreter. """ self.shell.start_interpreter(namespace) def set_historylog(self, historylog): """ Bind historylog instance to this console. Not used anymore since v2.0. """ historylog.add_history(self.shell.history_filename) self.shell.sig_append_to_history_requested.connect( historylog.append_to_history) def set_help(self, help_plugin): """ Bind help instance to this console. """ self.shell.help = help_plugin def report_issue(self): """Report an issue with the SpyderErrorDialog.""" self._report_dlg = SpyderErrorDialog(self, is_report=True) self._report_dlg.set_color_scheme( self.get_conf('selected', section='appearance')) self._report_dlg.show() @Slot(dict) def handle_exception(self, error_data, sender=None, internal_plugins=None): """ Exception ocurred in the internal console. Show a QDialog or the internal console to warn the user. Handle any exception that occurs during Spyder usage. Parameters ---------- error_data: dict The dictionary containing error data. The expected keys are: >>> error_data= { "text": str, "is_traceback": bool, "repo": str, "title": str, "label": str, "steps": str, } sender: spyder.api.plugins.SpyderPluginV2, optional The sender plugin. Default is None. Notes ----- The `is_traceback` key indicates if `text` contains plain text or a Python error traceback. The `title` and `repo` keys indicate how the error data should customize the report dialog and Github error submission. The `label` and `steps` keys allow customizing the content of the error dialog. """ text = error_data.get("text", None) is_traceback = error_data.get("is_traceback", False) title = error_data.get("title", "") label = error_data.get("label", "") steps = error_data.get("steps", "") # Skip errors without traceback (and no text) or dismiss if ((not text and not is_traceback and self.error_dlg is None) or self.dismiss_error): return # Retrieve internal plugins internal_plugins = PLUGIN_REGISTRY.internal_plugins # Get if sender is internal or not is_internal_plugin = True if sender is not None: sender_name = getattr(sender, 'NAME', getattr(sender, 'CONF_SECTION')) is_internal_plugin = sender_name in internal_plugins # Set repo repo = "spyder-ide/spyder" if not is_internal_plugin: repo = error_data.get("repo", None) if repo is None: raise SpyderAPIError( f"External plugin '{sender_name}' does not define 'repo' " "key in the 'error_data' dictionary in the form " "my-org/my-repo (only Github is supported).") if repo == 'spyder-ide/spyder': raise SpyderAPIError( f"External plugin '{sender_name}' 'repo' key needs to be " "different from the main Spyder repo.") if self.get_conf('show_internal_errors', section='main'): if self.error_dlg is None: self.error_dlg = SpyderErrorDialog(self) self.error_dlg.set_color_scheme( self.get_conf('selected', section='appearance')) self.error_dlg.close_btn.clicked.connect(self.close_error_dlg) self.error_dlg.rejected.connect(self.remove_error_dlg) self.error_dlg.details.sig_go_to_error_requested.connect( self.go_to_error) # Set the report repository self.error_dlg.set_github_repo_org(repo) if title: self.error_dlg.set_title(title) self.error_dlg.title.setEnabled(False) if label: self.error_dlg.main_label.setText(label) self.error_dlg.submit_btn.setEnabled(True) if steps: self.error_dlg.steps_text.setText(steps) self.error_dlg.set_require_minimum_length(False) self.error_dlg.append_traceback(text) self.error_dlg.show() elif DEV or get_debug_level(): self.change_visibility(True, True) def close_error_dlg(self): """ Close error dialog. """ if self.error_dlg.dismiss_box.isChecked(): self.dismiss_error = True self.error_dlg.reject() def remove_error_dlg(self): """ Remove error dialog. """ self.error_dlg = None @Slot() def show_env(self): """ Show environment variables. """ self.dialog_manager.show(EnvDialog(parent=self)) def get_sys_path(self): """ Return the `sys.path`. """ return sys.path @Slot() def show_syspath(self): """ Show `sys.path`. """ editor = CollectionsEditor(parent=self) editor.setup( sys.path, title="sys.path", readonly=True, icon=self.create_icon('syspath'), ) self.dialog_manager.show(editor) @Slot() def run_script(self, filename=None, silent=False, set_focus=False, args=None): """ Run a Python script. """ if filename is None: self.shell.interpreter.restore_stds() filename, _selfilter = getopenfilename( self, _("Run Python script"), getcwd_or_home(), _("Python scripts") + " (*.py ; *.pyw ; *.ipy)", ) self.shell.interpreter.redirect_stds() if filename: os.chdir(osp.dirname(filename)) filename = osp.basename(filename) else: return logger.debug("Running script with %s", args) filename = osp.abspath(filename) rbs = remove_backslashes command = "runfile('%s', args='%s')" % (rbs(filename), rbs(args)) if set_focus: self.shell.setFocus() self.change_visibility(True, True) self.shell.write(command + '\n') self.shell.run_command(command) def go_to_error(self, text): """ Go to error if relevant. """ match = get_error_match(to_text_string(text)) if match: fname, lnb = match.groups() self.edit_script(fname, int(lnb)) def edit_script(self, filename=None, goto=-1): """ Edit script. """ if filename is not None: # Called from InternalShell self.shell.external_editor(filename, goto) self.sig_edit_goto_requested.emit(osp.abspath(filename), goto, '') def execute_lines(self, lines): """ Execute lines and give focus to shell. """ self.shell.execute_lines(to_text_string(lines)) self.shell.setFocus() @Slot() def change_max_line_count(self, value=None): """" Change maximum line count. """ valid = True if value is None: value, valid = QInputDialog.getInt( self, _('Buffer'), _('Maximum line count'), self.get_conf('max_line_count'), 0, 1000000, ) if valid: self.set_conf('max_line_count', value) @Slot() def change_exteditor(self, path=None): """ Change external editor path. """ valid = True if path is None: path, valid = QInputDialog.getText( self, _('External editor'), _('External editor executable path:'), QLineEdit.Normal, self.get_conf('external_editor/path'), ) if valid: self.set_conf('external_editor/path', to_text_string(path)) def set_exit_function(self, func): """ Set the callback function to execute when the `exit_interpreter` is called. """ self.shell.exitfunc = func def set_font(self, font): """ Set font of the internal shell. """ self.shell.set_font(font) def redirect_stds(self): """ Redirect stdout and stderr when using open file dialogs. """ self.shell.interpreter.redirect_stds() def restore_stds(self): """ Restore stdout and stderr when using open file dialogs. """ self.shell.interpreter.restore_stds() def set_namespace_item(self, name, item): """ Add an object to the namespace dictionary of the internal console. """ self.shell.interpreter.namespace[name] = item def exit_interpreter(self): """ Exit the internal console interpreter. This is equivalent to requesting the main application to quit. """ self.shell.exit_interpreter()
class ConsoleWidget(PluginMainWidget): DEFAULT_OPTIONS = { 'codecompletion/auto': True, 'commands': [], 'external_editor/gotoline': '', 'external_editor/path': '', 'max_line_count': 300, 'message': 'Internal console\n\n', 'multithreaded': False, 'namespace': None, 'profile': False, 'show_internal_errors': True, 'wrap': True, # From appearance 'color_theme': 'spyder/dark', } # --- Signals # This signal emits a parsed error traceback text so we can then # request opening the file that traceback comes from in the Editor. sig_edit_goto_requested = Signal(str, int, str) # TODO: I do not think we use this? sig_focus_changed = Signal() # Emit this when the interpreter buffer is flushed sig_refreshed = Signal() # Request to show a status message on the main window sig_show_status_requested = Signal(str) # Request the main application to quit. sig_quit_requested = Signal() sig_help_requested = Signal(dict) """ This signal is emitted to request help on a given object `name`. Parameters ---------- help_data: dict Example `{'name': str, 'ignore_unknown': bool}`. """ def __init__(self, name, plugin, parent=None, options=DEFAULT_OPTIONS): super().__init__(name, plugin, parent, options) logger.info("Initializing...") # Traceback MessageBox self.error_traceback = '' self.dismiss_error = False # Widgets self.dialog_manager = DialogManager() self.error_dlg = None self.shell = InternalShell( # TODO: Move to use SpyderWidgetMixin? parent=parent, namespace=self.get_option('namespace'), commands=self.get_option('commands'), message=self.get_option('message'), max_line_count=self.get_option('max_line_count'), profile=self.get_option('profile'), multithreaded=self.get_option('multithreaded'), ) self.find_widget = FindReplace(self) # Setup self.setAcceptDrops(True) self.find_widget.set_editor(self.shell) self.find_widget.hide() self.shell.toggle_wrap_mode(self.get_option('wrap')) # Layout layout = QVBoxLayout() layout.addWidget(self.shell) layout.addWidget(self.find_widget) self.setLayout(layout) # Signals self.shell.sig_help_requested.connect(self.sig_help_requested) self.shell.sig_exception_occurred.connect(self.handle_exception) self.shell.sig_focus_changed.connect(self.sig_focus_changed) self.shell.sig_go_to_error_requested.connect(self.go_to_error) self.shell.sig_redirect_stdio_requested.connect( self.sig_redirect_stdio_requested) self.shell.sig_refreshed.connect(self.sig_refreshed) self.shell.sig_show_status_requested.connect( lambda msg: self.sig_show_status_message.emit(msg, 0)) # --- PluginMainWidget API # ------------------------------------------------------------------------ def get_title(self): return _('Internal console') def setup(self, options): # TODO: Move this to the shell quit_action = self.create_action( ConsoleWidgetActions.Quit, text=_("&Quit"), tip=_("Quit"), icon=self.create_icon('exit'), triggered=self.sig_quit_requested, context=Qt.ApplicationShortcut, ) run_action = self.create_action( ConsoleWidgetActions.Run, text=_("&Run..."), tip=_("Run a Python script"), icon=self.create_icon('run_small'), triggered=self.run_script, ) environ_action = self.create_action( ConsoleWidgetActions.Environment, text=_("Environment variables..."), tip=_("Show and edit environment variables (for current " "session)"), icon=self.create_icon('environ'), triggered=self.show_env, ) syspath_action = self.create_action( ConsoleWidgetActions.SysPath, text=_("Show sys.path contents..."), tip=_("Show (read-only) sys.path"), icon=self.create_icon('syspath'), triggered=self.show_syspath, ) buffer_action = self.create_action( ConsoleWidgetActions.MaxLineCount, text=_("Buffer..."), tip=_("Set maximum line count"), triggered=self.change_max_line_count, ) exteditor_action = self.create_action( ConsoleWidgetActions.ExternalEditor, text=_("External editor path..."), tip=_("Set external editor executable path"), triggered=self.change_exteditor, ) wrap_action = self.create_action( ConsoleWidgetActions.ToggleWrap, text=_("Wrap lines"), toggled=lambda val: self.set_option('wrap', val), initial=self.get_option('wrap'), ) codecompletion_action = self.create_action( ConsoleWidgetActions.ToggleCodeCompletion, text=_("Automatic code completion"), toggled=lambda val: self.set_option('codecompletion/auto', val), initial=self.get_option('codecompletion/auto'), ) # Submenu internal_settings_menu = self.create_menu( ConsoleWidgetMenus.InternalSettings, _('Internal console settings'), ) for item in [buffer_action, wrap_action, codecompletion_action, exteditor_action]: self.add_item_to_menu( item, menu=internal_settings_menu, section=ConsoleWidgetInternalSettingsSubMenuSections.Main, ) # Options menu options_menu = self.get_options_menu() for item in [run_action, environ_action, syspath_action, internal_settings_menu]: self.add_item_to_menu( item, menu=options_menu, section=ConsoleWidgetOptionsMenuSections.Run, ) self.add_item_to_menu( quit_action, menu=options_menu, section=ConsoleWidgetOptionsMenuSections.Quit, ) self.shell.set_external_editor( self.get_option('external_editor/path'), '') def on_option_update(self, option, value): if option == 'max_line_count': self.shell.setMaximumBlockCount(value) elif option == 'wrap': self.shell.toggle_wrap_mode(value) elif option == 'codecompletion/auto': self.shell.set_codecompletion_auto(value) elif option == 'external_editor/path': self.shell.set_external_editor(value, '') def update_actions(self): # This method is a required part of the PluginMainWidget API. On this # widget it is not currently used. pass def get_focus_widget(self): return self.shell # --- Qt overrides # ------------------------------------------------------------------------ def dragEnterEvent(self, event): """ Reimplement Qt method. Inform Qt about the types of data that the widget accepts. """ source = event.mimeData() if source.hasUrls(): if mimedata2url(source): event.acceptProposedAction() else: event.ignore() elif source.hasText(): event.acceptProposedAction() def dropEvent(self, event): """ Reimplement Qt method. Unpack dropped data and handle it. """ source = event.mimeData() if source.hasUrls(): pathlist = mimedata2url(source) self.shell.drop_pathlist(pathlist) elif source.hasText(): lines = to_text_string(source.text()) self.shell.set_cursor_position('eof') self.shell.execute_lines(lines) event.acceptProposedAction() # --- Public API # ------------------------------------------------------------------------ def start_interpreter(self, namespace): """ Start internal console interpreter. """ self.shell.start_interpreter(namespace) def set_historylog(self, historylog): """ Bind historylog instance to this console. Not used anymore since v2.0. """ historylog.add_history(self.shell.history_filename) self.shell.append_to_history.connect(historylog.append_to_history) def set_help(self, help_plugin): """ Bind help instance to this console. """ self.shell.help = help_plugin def handle_exception(self, text, is_traceback, is_pyls_error=False, is_faulthandler_report=False): """ Exception ocurred in the internal console. Show a QDialog or the internal console to warn the user. """ # Skip errors without traceback or dismiss if (not is_traceback and self.error_dlg is None) or self.dismiss_error: return if self.get_option('show_internal_errors'): if self.error_dlg is None: self.error_dlg = SpyderErrorDialog(self) self.error_dlg.set_color_scheme(self.get_option('color_theme')) self.error_dlg.close_btn.clicked.connect(self.close_error_dlg) self.error_dlg.rejected.connect(self.remove_error_dlg) self.error_dlg.details.go_to_error.connect(self.go_to_error) if is_pyls_error: title = "Internal Python Language Server error" self.error_dlg.set_title(title) self.error_dlg.title.setEnabled(False) if is_faulthandler_report: title = "Segmentation fault crash" self.error_dlg.set_title(title) self.error_dlg.title.setEnabled(False) self.error_dlg.main_label.setText( _("<h3>Spyder crashed during last session</h3>")) self.error_dlg.submit_btn.setEnabled(True) self.error_dlg.steps_text.setText( _("Please provide any additional information you " "might have about the crash.")) self.error_dlg.set_require_minimum_length(False) self.error_dlg.append_traceback(text) self.error_dlg.show() elif DEV or get_debug_level(): self.change_visibility(True, True) def close_error_dlg(self): """ Close error dialog. """ if self.error_dlg.dismiss_box.isChecked(): self.dismiss_error = True self.error_dlg.reject() def remove_error_dlg(self): """ Remove error dialog. """ self.error_dlg = None @Slot() def show_env(self): """ Show environment variables. """ self.dialog_manager.show(EnvDialog(parent=self)) def get_sys_path(self): """ Return the `sys.path`. """ return sys.path @Slot() def show_syspath(self): """ Show `sys.path`. """ editor = CollectionsEditor(parent=self) editor.setup( sys.path, title="sys.path", readonly=True, icon=self.create_icon('syspath'), ) self.dialog_manager.show(editor) @Slot() def run_script(self, filename=None, silent=False, set_focus=False, args=None): """ Run a Python script. """ if filename is None: self.shell.interpreter.restore_stds() filename, _selfilter = getopenfilename( self, _("Run Python script"), getcwd_or_home(), _("Python scripts") + " (*.py ; *.pyw ; *.ipy)", ) self.shell.interpreter.redirect_stds() if filename: os.chdir(osp.dirname(filename)) filename = osp.basename(filename) else: return logger.debug("Running script with %s", args) filename = osp.abspath(filename) rbs = remove_backslashes command = "runfile('%s', args='%s')" % (rbs(filename), rbs(args)) if set_focus: self.shell.setFocus() self.change_visibility(True, True) self.shell.write(command+'\n') self.shell.run_command(command) def go_to_error(self, text): """ Go to error if relevant. """ match = get_error_match(to_text_string(text)) if match: fname, lnb = match.groups() self.edit_script(fname, int(lnb)) def edit_script(self, filename=None, goto=-1): """ Edit script. """ if filename is not None: # Called from InternalShell self.shell.external_editor(filename, goto) self.sig_edit_goto_requested.emit(osp.abspath(filename), goto, '') def execute_lines(self, lines): """ Execute lines and give focus to shell. """ self.shell.execute_lines(to_text_string(lines)) self.shell.setFocus() @Slot() def change_max_line_count(self, value=None): """" Change maximum line count. """ valid = True if value is None: value, valid = QInputDialog.getInt( self, _('Buffer'), _('Maximum line count'), self.get_option('max_line_count'), 0, 1000000, ) if valid: self.set_option('max_line_count', value) @Slot() def change_exteditor(self, path=None): """ Change external editor path. """ valid = True if path is None: path, valid = QInputDialog.getText( self, _('External editor'), _('External editor executable path:'), QLineEdit.Normal, self.get_option('external_editor/path'), ) if valid: self.set_option('external_editor/path', to_text_string(path)) def set_exit_function(self, func): """ Set the callback function to execute when the `exit_interpreter` is called. """ self.shell.exitfunc = func def set_font(self, font): """ Set font of the internal shell. """ self.shell.set_font(font) def redirect_stds(self): """ Redirect stdout and stderr when using open file dialogs. """ self.shell.interpreter.redirect_stds() def restore_stds(self): """ Restore stdout and stderr when using open file dialogs. """ self.shell.interpreter.restore_stds() def set_namespace_item(self, name, item): """ Add an object to the namespace dictionary of the internal console. """ self.shell.interpreter.namespace[name] = item def exit_interpreter(self): """ Exit the internal console interpreter. This is equivalent to requesting the main application to quit. """ self.shell.exit_interpreter()
class ClientWidget(QWidget, SaveHistoryMixin): """ Client widget for the IPython Console This is a widget composed of a shell widget and a WebView info widget to print different messages there. """ SEPARATOR = '{0}## ---({1})---'.format(os.linesep * 2, time.ctime()) INITHISTORY = [ '# -*- coding: utf-8 -*-', '# *** Spyder Python Console History Log ***', ] append_to_history = Signal(str, str) def __init__(self, plugin, id_, history_filename, config_options, additional_options, interpreter_versions, connection_file=None, hostname=None, menu_actions=None, slave=False, external_kernel=False, given_name=None, options_button=None, show_elapsed_time=False, reset_warning=True, ask_before_restart=True, css_path=None): super(ClientWidget, self).__init__(plugin) SaveHistoryMixin.__init__(self, history_filename) # --- Init attrs self.id_ = id_ self.connection_file = connection_file self.hostname = hostname self.menu_actions = menu_actions self.slave = slave self.external_kernel = external_kernel self.given_name = given_name self.show_elapsed_time = show_elapsed_time self.reset_warning = reset_warning self.ask_before_restart = ask_before_restart # --- Other attrs self.options_button = options_button self.stop_button = None self.reset_button = None self.stop_icon = ima.icon('stop') self.history = [] self.allow_rename = True self.stderr_dir = None self.is_error_shown = False if css_path is None: self.css_path = CSS_PATH else: self.css_path = css_path # --- Widgets self.shellwidget = ShellWidget( config=config_options, ipyclient=self, additional_options=additional_options, interpreter_versions=interpreter_versions, external_kernel=external_kernel, local_kernel=True) self.infowidget = WebView(self) self.set_infowidget_font() self.loading_page = self._create_loading_page() self._show_loading_page() # Elapsed time self.time_label = None self.t0 = time.monotonic() self.timer = QTimer(self) self.show_time_action = create_action( self, _("Show elapsed time"), toggled=self.set_elapsed_time_visible) # --- Layout vlayout = QVBoxLayout() toolbar_buttons = self.get_toolbar_buttons() hlayout = QHBoxLayout() hlayout.addWidget(self.create_time_label()) hlayout.addStretch(0) for button in toolbar_buttons: hlayout.addWidget(button) vlayout.addLayout(hlayout) vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addWidget(self.shellwidget) vlayout.addWidget(self.infowidget) self.setLayout(vlayout) # --- Exit function self.exit_callback = lambda: plugin.close_client(client=self) # --- Dialog manager self.dialog_manager = DialogManager() # Show timer self.update_time_label_visibility() #------ Public API -------------------------------------------------------- @property def kernel_id(self): """Get kernel id""" if self.connection_file is not None: json_file = osp.basename(self.connection_file) return json_file.split('.json')[0] @property def stderr_file(self): """Filename to save kernel stderr output.""" stderr_file = None if self.connection_file is not None: stderr_file = self.kernel_id + '.stderr' if self.stderr_dir is not None: stderr_file = osp.join(self.stderr_dir, stderr_file) else: try: stderr_file = osp.join(get_temp_dir(), stderr_file) except (IOError, OSError): stderr_file = None return stderr_file @property def stderr_handle(self): """Get handle to stderr_file.""" if self.stderr_file is not None: # Needed to prevent any error that could appear. # See issue 6267 try: handle = codecs.open(self.stderr_file, 'w', encoding='utf-8') except Exception: handle = None else: handle = None return handle def remove_stderr_file(self): """Remove stderr_file associated with the client.""" try: # Defer closing the stderr_handle until the client # is closed because jupyter_client needs it open # while it tries to restart the kernel self.stderr_handle.close() os.remove(self.stderr_file) except Exception: pass def configure_shellwidget(self, give_focus=True): """Configure shellwidget after kernel is started""" if give_focus: self.get_control().setFocus() # Set exit callback self.shellwidget.set_exit_callback() # To save history self.shellwidget.executing.connect(self.add_to_history) # For Mayavi to run correctly self.shellwidget.executing.connect( self.shellwidget.set_backend_for_mayavi) # To update history after execution self.shellwidget.executed.connect(self.update_history) # To update the Variable Explorer after execution self.shellwidget.executed.connect( self.shellwidget.refresh_namespacebrowser) # To enable the stop button when executing a process self.shellwidget.executing.connect(self.enable_stop_button) # To disable the stop button after execution stopped self.shellwidget.executed.connect(self.disable_stop_button) # To show kernel restarted/died messages self.shellwidget.sig_kernel_restarted.connect( self.kernel_restarted_message) # To correctly change Matplotlib backend interactively self.shellwidget.executing.connect(self.shellwidget.change_mpl_backend) # To show env and sys.path contents self.shellwidget.sig_show_syspath.connect(self.show_syspath) self.shellwidget.sig_show_env.connect(self.show_env) # To sync with working directory toolbar self.shellwidget.executed.connect(self.shellwidget.get_cwd) # To apply style self.set_color_scheme(self.shellwidget.syntax_style, reset=False) # To hide the loading page self.shellwidget.sig_prompt_ready.connect(self._hide_loading_page) # Show possible errors when setting Matplotlib backend self.shellwidget.sig_prompt_ready.connect( self._show_mpl_backend_errors) def enable_stop_button(self): self.stop_button.setEnabled(True) def disable_stop_button(self): # This avoids disabling automatically the button when # re-running files on dedicated consoles. # See issue #5958 if not self.shellwidget._executing: self.stop_button.setDisabled(True) @Slot() def stop_button_click_handler(self): """Method to handle what to do when the stop button is pressed""" self.stop_button.setDisabled(True) # Interrupt computations or stop debugging if not self.shellwidget._reading: self.interrupt_kernel() else: self.shellwidget.write_to_stdin('exit') def show_kernel_error(self, error): """Show kernel initialization errors in infowidget.""" # Replace end of line chars with <br> eol = sourcecode.get_eol_chars(error) if eol: error = error.replace(eol, '<br>') # Don't break lines in hyphens # From https://stackoverflow.com/q/7691569/438386 error = error.replace('-', '‑') # Create error page message = _("An error ocurred while starting the kernel") kernel_error_template = Template(KERNEL_ERROR) page = kernel_error_template.substitute(css_path=self.css_path, message=message, error=error) # Show error self.infowidget.setHtml(page, QUrl.fromLocalFile(self.css_path)) self.shellwidget.hide() self.infowidget.show() # Tell the client we're in error mode self.is_error_shown = True def get_name(self): """Return client name""" if self.given_name is None: # Name according to host if self.hostname is None: name = _("Console") else: name = self.hostname # Adding id to name client_id = self.id_['int_id'] + u'/' + self.id_['str_id'] name = name + u' ' + client_id elif self.given_name in ["Pylab", "SymPy", "Cython"]: client_id = self.id_['int_id'] + u'/' + self.id_['str_id'] name = self.given_name + u' ' + client_id else: name = self.given_name + u'/' + self.id_['str_id'] return name def get_control(self): """Return the text widget (or similar) to give focus to""" # page_control is the widget used for paging page_control = self.shellwidget._page_control if page_control and page_control.isVisible(): return page_control else: return self.shellwidget._control def get_kernel(self): """Get kernel associated with this client""" return self.shellwidget.kernel_manager def get_options_menu(self): """Return options menu""" reset_action = create_action(self, _("Remove all variables"), icon=ima.icon('editdelete'), triggered=self.reset_namespace) env_action = create_action(self, _("Show environment variables"), icon=ima.icon('environ'), triggered=self.shellwidget.get_env) syspath_action = create_action(self, _("Show sys.path contents"), icon=ima.icon('syspath'), triggered=self.shellwidget.get_syspath) self.show_time_action.setChecked(self.show_elapsed_time) additional_actions = [ reset_action, MENU_SEPARATOR, env_action, syspath_action, self.show_time_action ] if self.menu_actions is not None: return self.menu_actions + additional_actions else: return additional_actions def get_toolbar_buttons(self): """Return toolbar buttons list.""" buttons = [] # Code to add the stop button if self.stop_button is None: self.stop_button = create_toolbutton( self, text=_("Stop"), icon=self.stop_icon, tip=_("Stop the current command")) self.disable_stop_button() # set click event handler self.stop_button.clicked.connect(self.stop_button_click_handler) if self.stop_button is not None: buttons.append(self.stop_button) # Reset namespace button if self.reset_button is None: self.reset_button = create_toolbutton( self, text=_("Remove"), icon=ima.icon('editdelete'), tip=_("Remove all variables"), triggered=self.reset_namespace) if self.reset_button is not None: buttons.append(self.reset_button) if self.options_button is None: options = self.get_options_menu() if options: self.options_button = create_toolbutton( self, text=_('Options'), icon=ima.icon('tooloptions')) self.options_button.setPopupMode(QToolButton.InstantPopup) menu = QMenu(self) add_actions(menu, options) self.options_button.setMenu(menu) if self.options_button is not None: buttons.append(self.options_button) return buttons def add_actions_to_context_menu(self, menu): """Add actions to IPython widget context menu""" inspect_action = create_action( self, _("Inspect current object"), QKeySequence(get_shortcut('console', 'inspect current object')), icon=ima.icon('MessageBoxInformation'), triggered=self.inspect_object) clear_line_action = create_action( self, _("Clear line or block"), QKeySequence(get_shortcut('console', 'clear line')), triggered=self.clear_line) reset_namespace_action = create_action( self, _("Remove all variables"), QKeySequence(get_shortcut('ipython_console', 'reset namespace')), icon=ima.icon('editdelete'), triggered=self.reset_namespace) clear_console_action = create_action( self, _("Clear console"), QKeySequence(get_shortcut('console', 'clear shell')), triggered=self.clear_console) quit_action = create_action(self, _("&Quit"), icon=ima.icon('exit'), triggered=self.exit_callback) add_actions( menu, (None, inspect_action, clear_line_action, clear_console_action, reset_namespace_action, None, quit_action)) return menu def set_font(self, font): """Set IPython widget's font""" self.shellwidget._control.setFont(font) self.shellwidget.font = font def set_infowidget_font(self): """Set font for infowidget""" font = get_font(option='rich_font') self.infowidget.set_font(font) def set_color_scheme(self, color_scheme, reset=True): """Set IPython color scheme.""" # Needed to handle not initialized kernel_client # See issue 6996 try: self.shellwidget.set_color_scheme(color_scheme, reset) except AttributeError: pass def shutdown(self): """Shutdown kernel""" if self.get_kernel() is not None and not self.slave: self.shellwidget.kernel_manager.shutdown_kernel() if self.shellwidget.kernel_client is not None: background(self.shellwidget.kernel_client.stop_channels) def interrupt_kernel(self): """Interrupt the associanted Spyder kernel if it's running""" # Needed to prevent a crash when a kernel is not running. # See issue 6299 try: self.shellwidget.request_interrupt_kernel() except RuntimeError: pass @Slot() def restart_kernel(self): """ Restart the associated kernel. Took this code from the qtconsole project Licensed under the BSD license """ sw = self.shellwidget if not running_under_pytest() and self.ask_before_restart: message = _('Are you sure you want to restart the kernel?') buttons = QMessageBox.Yes | QMessageBox.No result = QMessageBox.question(self, _('Restart kernel?'), message, buttons) else: result = None if (result == QMessageBox.Yes or running_under_pytest() or not self.ask_before_restart): if sw.kernel_manager: if self.infowidget.isVisible(): self.infowidget.hide() sw.show() try: sw.kernel_manager.restart_kernel(stderr=self.stderr_handle) except RuntimeError as e: sw._append_plain_text(_('Error restarting kernel: %s\n') % e, before_prompt=True) else: # For issue 6235. IPython was changing the setting of # %colors on windows by assuming it was using a dark # background. This corrects it based on the scheme. self.set_color_scheme(sw.syntax_style) sw._append_html(_("<br>Restarting kernel...\n<hr><br>"), before_prompt=False) else: sw._append_plain_text( _('Cannot restart a kernel not started by Spyder\n'), before_prompt=True) @Slot(str) def kernel_restarted_message(self, msg): """Show kernel restarted/died messages.""" if not self.is_error_shown: # If there are kernel creation errors, jupyter_client will # try to restart the kernel and qtconsole prints a # message about it. # So we read the kernel's stderr_file and display its # contents in the client instead of the usual message shown # by qtconsole. try: stderr = self._read_stderr() except Exception: stderr = None if stderr: self.show_kernel_error('<tt>%s</tt>' % stderr) else: self.shellwidget._append_html("<br>%s<hr><br>" % msg, before_prompt=False) @Slot() def inspect_object(self): """Show how to inspect an object with our Help plugin""" self.shellwidget._control.inspect_current_object() @Slot() def clear_line(self): """Clear a console line""" self.shellwidget._keyboard_quit() @Slot() def clear_console(self): """Clear the whole console""" self.shellwidget.clear_console() @Slot() def reset_namespace(self): """Resets the namespace by removing all names defined by the user""" self.shellwidget.reset_namespace(warning=self.reset_warning, message=True) def update_history(self): self.history = self.shellwidget._history @Slot(object) def show_syspath(self, syspath): """Show sys.path contents.""" if syspath is not None: editor = CollectionsEditor() editor.setup(syspath, title="sys.path contents", readonly=True, width=600, icon=ima.icon('syspath')) self.dialog_manager.show(editor) else: return @Slot(object) def show_env(self, env): """Show environment variables.""" self.dialog_manager.show(RemoteEnvDialog(env)) def create_time_label(self): """Create elapsed time label widget (if necessary) and return it""" if self.time_label is None: self.time_label = QLabel() return self.time_label def show_time(self, end=False): """Text to show in time_label.""" if self.time_label is None: return elapsed_time = time.monotonic() - self.t0 # System time changed to past date, so reset start. if elapsed_time < 0: self.t0 = time.monotonic() elapsed_time = 0 if elapsed_time > 24 * 3600: # More than a day...! fmt = "%d %H:%M:%S" else: fmt = "%H:%M:%S" if end: color = "#AAAAAA" else: color = "#AA6655" text = "<span style=\'color: %s\'><b>%s" \ "</b></span>" % (color, time.strftime(fmt, time.gmtime(elapsed_time))) self.time_label.setText(text) def update_time_label_visibility(self): """Update elapsed time visibility.""" self.time_label.setVisible(self.show_elapsed_time) @Slot(bool) def set_elapsed_time_visible(self, state): """Slot to show/hide elapsed time label.""" self.show_elapsed_time = state if self.time_label is not None: self.time_label.setVisible(state) #------ Private API ------------------------------------------------------- def _create_loading_page(self): """Create html page to show while the kernel is starting""" loading_template = Template(LOADING) loading_img = get_image_path('loading_sprites.png') if os.name == 'nt': loading_img = loading_img.replace('\\', '/') message = _("Connecting to kernel...") page = loading_template.substitute(css_path=self.css_path, loading_img=loading_img, message=message) return page def _show_loading_page(self): """Show animation while the kernel is loading.""" self.shellwidget.hide() self.infowidget.show() self.infowidget.setHtml(self.loading_page, QUrl.fromLocalFile(self.css_path)) def _hide_loading_page(self): """Hide animation shown while the kernel is loading.""" self.infowidget.hide() self.shellwidget.show() self.infowidget.setHtml(BLANK, QUrl.fromLocalFile(self.css_path)) self.shellwidget.sig_prompt_ready.disconnect(self._hide_loading_page) def _read_stderr(self): """Read the stderr file of the kernel.""" f = open(self.stderr_file, 'rb') try: stderr_text = f.read() # This is needed since the stderr file could be encoded # in something different to utf-8. # See issue 4191 encoding = get_coding(stderr_text) stderr_text = to_text_string(stderr_text, encoding) return stderr_text finally: f.close() def _show_mpl_backend_errors(self): """ Show possible errors when setting the selected Matplotlib backend. """ if not self.external_kernel: self.shellwidget.silent_execute( "get_ipython().kernel._show_mpl_backend_errors()") self.shellwidget.sig_prompt_ready.disconnect( self._show_mpl_backend_errors)
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 spyder/widgets/externalshell/start_ipython_kernel.py) self.fname = get_module_source_path( 'spyder.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 setup(self): # Compute dependencies in a thread to not block the interface. self.dependencies_thread = QThread(None) # Attributes self.dialog_manager = DialogManager() self.give_updates_feedback = False self.thread_updates = None self.worker_updates = None self.updates_timer = None # Actions # Documentation actions self.documentation_action = self.create_action( ApplicationActions.SpyderDocumentationAction, text=_("Spyder documentation"), icon=self.create_icon("DialogHelpButton"), triggered=lambda: start_file(__docs_url__), context=Qt.ApplicationShortcut, register_shortcut=True, shortcut_context="_") spyder_video_url = ("https://www.youtube.com/playlist" "?list=PLPonohdiDqg9epClEcXoAPUiK0pN5eRoc") self.video_action = self.create_action( ApplicationActions.SpyderDocumentationVideoAction, text=_("Tutorial videos"), icon=self.create_icon("VideoIcon"), triggered=lambda: start_file(spyder_video_url)) # Support actions self.trouble_action = self.create_action( ApplicationActions.SpyderTroubleshootingAction, _("Troubleshooting..."), triggered=lambda: start_file(__trouble_url__)) self.report_action = self.create_action( ConsoleActions.SpyderReportAction, _("Report issue..."), icon=self.create_icon('bug'), triggered=self.sig_report_issue_requested) self.dependencies_action = self.create_action( ApplicationActions.SpyderDependenciesAction, _("Dependencies..."), triggered=self.show_dependencies, icon=self.create_icon('advanced')) self.check_updates_action = self.create_action( ApplicationActions.SpyderCheckUpdatesAction, _("Check for updates..."), triggered=self.check_updates) self.support_group_action = self.create_action( ApplicationActions.SpyderSupportAction, _("Spyder support..."), triggered=lambda: start_file(__forum_url__)) # About action self.about_action = self.create_action( ApplicationActions.SpyderAbout, _("About %s...") % "Spyder", icon=self.create_icon('MessageBoxInformation'), triggered=self.show_about, menurole=QAction.AboutRole) # Tools actions if WinUserEnvDialog is not None: self.winenv_action = self.create_action( ApplicationActions.SpyderWindowsEnvVariables, _("Current user environment variables..."), icon=self.create_icon('win_env'), tip=_("Show and edit current user environment " "variables in Windows registry " "(i.e. for all sessions)"), triggered=self.show_windows_env_variables) else: self.winenv_action = None # Application base actions self.restart_action = self.create_action( ApplicationActions.SpyderRestart, _("&Restart"), icon=self.create_icon('restart'), tip=_("Restart"), triggered=self.restart_normal, context=Qt.ApplicationShortcut, shortcut_context="_", register_shortcut=True) self.restart_debug_action = self.create_action( ApplicationActions.SpyderRestartDebug, _("&Restart in debug mode"), tip=_("Restart in debug mode"), triggered=self.restart_debug, context=Qt.ApplicationShortcut, shortcut_context="_", register_shortcut=True) # Debug logs if get_debug_level() >= 2: self.menu_debug_logs = self.create_menu( ApplicationPluginMenus.DebugLogsMenu, _("Debug logs")) # The menu can't be built at startup because Completions can # start after Application. self.menu_debug_logs.aboutToShow.connect( self.create_debug_log_actions)
class ClientWidget(QWidget, SaveHistoryMixin): """ Client widget for the IPython Console This is a widget composed of a shell widget and a WebView info widget to print different messages there. """ SEPARATOR = '{0}## ---({1})---'.format(os.linesep*2, ctime()) INITHISTORY = ['# -*- coding: utf-8 -*-', '# *** Spyder Python Console History Log ***',] append_to_history = Signal(str, str) def __init__(self, plugin, id_, history_filename, config_options, additional_options, interpreter_versions, connection_file=None, hostname=None, menu_actions=None, slave=False, external_kernel=False, given_name=None, options_button=None, show_elapsed_time=False, reset_warning=True): super(ClientWidget, self).__init__(plugin) SaveHistoryMixin.__init__(self, history_filename) # --- Init attrs self.id_ = id_ self.connection_file = connection_file self.hostname = hostname self.menu_actions = menu_actions self.slave = slave self.external_kernel = external_kernel self.given_name = given_name self.show_elapsed_time = show_elapsed_time self.reset_warning = reset_warning # --- Other attrs self.options_button = options_button self.stop_button = None self.reset_button = None self.stop_icon = ima.icon('stop') self.history = [] self.allow_rename = True self.stderr_dir = None # --- Widgets self.shellwidget = ShellWidget(config=config_options, ipyclient=self, additional_options=additional_options, interpreter_versions=interpreter_versions, external_kernel=external_kernel, local_kernel=True) self.infowidget = WebView(self) self.set_infowidget_font() self.loading_page = self._create_loading_page() self._show_loading_page() # Elapsed time self.time_label = None self.t0 = None self.timer = QTimer(self) # --- Layout vlayout = QVBoxLayout() toolbar_buttons = self.get_toolbar_buttons() hlayout = QHBoxLayout() hlayout.addWidget(self.create_time_label()) hlayout.addStretch(0) for button in toolbar_buttons: hlayout.addWidget(button) vlayout.addLayout(hlayout) vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addWidget(self.shellwidget) vlayout.addWidget(self.infowidget) self.setLayout(vlayout) # --- Exit function self.exit_callback = lambda: plugin.close_client(client=self) # --- Dialog manager self.dialog_manager = DialogManager() # Show timer self.update_time_label_visibility() #------ Public API -------------------------------------------------------- @property def kernel_id(self): """Get kernel id""" if self.connection_file is not None: json_file = osp.basename(self.connection_file) return json_file.split('.json')[0] @property def stderr_file(self): """Filename to save kernel stderr output.""" stderr_file = None if self.connection_file is not None: stderr_file = self.kernel_id + '.stderr' if self.stderr_dir is not None: stderr_file = osp.join(self.stderr_dir, stderr_file) else: try: if not osp.isdir(TEMPDIR): os.makedirs(TEMPDIR) stderr_file = osp.join(TEMPDIR, stderr_file) except (IOError, OSError): stderr_file = None return stderr_file def configure_shellwidget(self, give_focus=True): """Configure shellwidget after kernel is started""" if give_focus: self.get_control().setFocus() # Set exit callback self.shellwidget.set_exit_callback() # To save history self.shellwidget.executing.connect(self.add_to_history) # For Mayavi to run correctly self.shellwidget.executing.connect( self.shellwidget.set_backend_for_mayavi) # To update history after execution self.shellwidget.executed.connect(self.update_history) # To update the Variable Explorer after execution self.shellwidget.executed.connect( self.shellwidget.refresh_namespacebrowser) # To enable the stop button when executing a process self.shellwidget.executing.connect(self.enable_stop_button) # To disable the stop button after execution stopped self.shellwidget.executed.connect(self.disable_stop_button) # To show kernel restarted/died messages self.shellwidget.sig_kernel_restarted.connect( self.kernel_restarted_message) # To correctly change Matplotlib backend interactively self.shellwidget.executing.connect( self.shellwidget.change_mpl_backend) # To show env and sys.path contents self.shellwidget.sig_show_syspath.connect(self.show_syspath) self.shellwidget.sig_show_env.connect(self.show_env) # To sync with working directory toolbar self.shellwidget.executed.connect(self.shellwidget.get_cwd) # To apply style self.set_color_scheme(self.shellwidget.syntax_style, reset=False) # To hide the loading page self.shellwidget.sig_prompt_ready.connect(self._hide_loading_page) # Show possible errors when setting Matplotlib backend self.shellwidget.sig_prompt_ready.connect( self._show_mpl_backend_errors) def enable_stop_button(self): self.stop_button.setEnabled(True) def disable_stop_button(self): self.stop_button.setDisabled(True) @Slot() def stop_button_click_handler(self): """Method to handle what to do when the stop button is pressed""" self.stop_button.setDisabled(True) # Interrupt computations or stop debugging if not self.shellwidget._reading: self.interrupt_kernel() else: self.shellwidget.write_to_stdin('exit') def show_kernel_error(self, error): """Show kernel initialization errors in infowidget.""" # Replace end of line chars with <br> eol = sourcecode.get_eol_chars(error) if eol: error = error.replace(eol, '<br>') # Don't break lines in hyphens # From http://stackoverflow.com/q/7691569/438386 error = error.replace('-', '‑') # Create error page message = _("An error ocurred while starting the kernel") kernel_error_template = Template(KERNEL_ERROR) page = kernel_error_template.substitute(css_path=CSS_PATH, message=message, error=error) # Show error self.infowidget.setHtml(page) self.shellwidget.hide() self.infowidget.show() def get_name(self): """Return client name""" if self.given_name is None: # Name according to host if self.hostname is None: name = _("Console") else: name = self.hostname # Adding id to name client_id = self.id_['int_id'] + u'/' + self.id_['str_id'] name = name + u' ' + client_id else: name = self.given_name + u'/' + self.id_['str_id'] return name def get_control(self): """Return the text widget (or similar) to give focus to""" # page_control is the widget used for paging page_control = self.shellwidget._page_control if page_control and page_control.isVisible(): return page_control else: return self.shellwidget._control def get_kernel(self): """Get kernel associated with this client""" return self.shellwidget.kernel_manager def get_options_menu(self): """Return options menu""" reset_action = create_action(self, _("Remove all variables"), icon=ima.icon('editdelete'), triggered=self.reset_namespace) self.show_time_action = create_action(self, _("Show elapsed time"), toggled=self.set_elapsed_time_visible) env_action = create_action( self, _("Show environment variables"), icon=ima.icon('environ'), triggered=self.shellwidget.get_env ) syspath_action = create_action( self, _("Show sys.path contents"), icon=ima.icon('syspath'), triggered=self.shellwidget.get_syspath ) self.show_time_action.setChecked(self.show_elapsed_time) additional_actions = [reset_action, MENU_SEPARATOR, env_action, syspath_action, self.show_time_action] if self.menu_actions is not None: return self.menu_actions + additional_actions else: return additional_actions def get_toolbar_buttons(self): """Return toolbar buttons list.""" buttons = [] # Code to add the stop button if self.stop_button is None: self.stop_button = create_toolbutton( self, text=_("Stop"), icon=self.stop_icon, tip=_("Stop the current command")) self.disable_stop_button() # set click event handler self.stop_button.clicked.connect(self.stop_button_click_handler) if self.stop_button is not None: buttons.append(self.stop_button) # Reset namespace button if self.reset_button is None: self.reset_button = create_toolbutton( self, text=_("Remove"), icon=ima.icon('editdelete'), tip=_("Remove all variables"), triggered=self.reset_namespace) if self.reset_button is not None: buttons.append(self.reset_button) if self.options_button is None: options = self.get_options_menu() if options: self.options_button = create_toolbutton(self, text=_('Options'), icon=ima.icon('tooloptions')) self.options_button.setPopupMode(QToolButton.InstantPopup) menu = QMenu(self) add_actions(menu, options) self.options_button.setMenu(menu) if self.options_button is not None: buttons.append(self.options_button) return buttons def add_actions_to_context_menu(self, menu): """Add actions to IPython widget context menu""" inspect_action = create_action(self, _("Inspect current object"), QKeySequence(get_shortcut('console', 'inspect current object')), icon=ima.icon('MessageBoxInformation'), triggered=self.inspect_object) clear_line_action = create_action(self, _("Clear line or block"), QKeySequence(get_shortcut( 'console', 'clear line')), triggered=self.clear_line) reset_namespace_action = create_action(self, _("Remove all variables"), QKeySequence(get_shortcut( 'ipython_console', 'reset namespace')), icon=ima.icon('editdelete'), triggered=self.reset_namespace) clear_console_action = create_action(self, _("Clear console"), QKeySequence(get_shortcut('console', 'clear shell')), triggered=self.clear_console) quit_action = create_action(self, _("&Quit"), icon=ima.icon('exit'), triggered=self.exit_callback) add_actions(menu, (None, inspect_action, clear_line_action, clear_console_action, reset_namespace_action, None, quit_action)) return menu def set_font(self, font): """Set IPython widget's font""" self.shellwidget._control.setFont(font) self.shellwidget.font = font def set_infowidget_font(self): """Set font for infowidget""" font = get_font(option='rich_font') self.infowidget.set_font(font) def set_color_scheme(self, color_scheme, reset=True): """Set IPython color scheme.""" self.shellwidget.set_color_scheme(color_scheme, reset) def shutdown(self): """Shutdown kernel""" if self.get_kernel() is not None and not self.slave: self.shellwidget.kernel_manager.shutdown_kernel() if self.shellwidget.kernel_client is not None: background(self.shellwidget.kernel_client.stop_channels) def interrupt_kernel(self): """Interrupt the associanted Spyder kernel if it's running""" # Needed to prevent a crash when a kernel is not running. # See issue 6299 try: self.shellwidget.request_interrupt_kernel() except RuntimeError: pass @Slot() def restart_kernel(self): """ Restart the associated kernel. Took this code from the qtconsole project Licensed under the BSD license """ sw = self.shellwidget message = _('Are you sure you want to restart the kernel?') buttons = QMessageBox.Yes | QMessageBox.No result = QMessageBox.question(self, _('Restart kernel?'), message, buttons) if result == QMessageBox.Yes: if sw.kernel_manager: if self.infowidget.isVisible(): self.infowidget.hide() sw.show() try: sw.kernel_manager.restart_kernel() except RuntimeError as e: sw._append_plain_text( _('Error restarting kernel: %s\n') % e, before_prompt=True ) else: # For issue 6235. IPython was changing the setting of # %colors on windows by assuming it was using a dark # background. This corrects it based on the scheme. self.set_color_scheme(sw.syntax_style) sw._append_html(_("<br>Restarting kernel...\n<hr><br>"), before_prompt=False) else: sw._append_plain_text( _('Cannot restart a kernel not started by Spyder\n'), before_prompt=True ) @Slot(str) def kernel_restarted_message(self, msg): """Show kernel restarted/died messages.""" try: stderr = codecs.open(self.stderr_file, 'r', encoding='utf-8').read() except UnicodeDecodeError: # This is needed since the stderr file could be encoded # in something different to utf-8. # See issue 4191 try: stderr = self._read_stderr() except: stderr = None except (OSError, IOError): stderr = None if stderr: self.show_kernel_error('<tt>%s</tt>' % stderr) else: self.shellwidget._append_html("<br>%s<hr><br>" % msg, before_prompt=False) @Slot() def inspect_object(self): """Show how to inspect an object with our Help plugin""" self.shellwidget._control.inspect_current_object() @Slot() def clear_line(self): """Clear a console line""" self.shellwidget._keyboard_quit() @Slot() def clear_console(self): """Clear the whole console""" self.shellwidget.clear_console() @Slot() def reset_namespace(self): """Resets the namespace by removing all names defined by the user""" self.shellwidget.reset_namespace(warning=self.reset_warning, message=True) def update_history(self): self.history = self.shellwidget._history @Slot(object) def show_syspath(self, syspath): """Show sys.path contents.""" if syspath is not None: editor = CollectionsEditor() editor.setup(syspath, title="sys.path contents", readonly=True, width=600, icon=ima.icon('syspath')) self.dialog_manager.show(editor) else: return @Slot(object) def show_env(self, env): """Show environment variables.""" self.dialog_manager.show(RemoteEnvDialog(env)) def create_time_label(self): """Create elapsed time label widget (if necessary) and return it""" if self.time_label is None: self.time_label = QLabel() return self.time_label def show_time(self, end=False): """Text to show in time_label.""" if self.time_label is None: return elapsed_time = time() - self.t0 if elapsed_time > 24 * 3600: # More than a day...! fmt = "%d %H:%M:%S" else: fmt = "%H:%M:%S" if end: color = "#AAAAAA" else: color = "#AA6655" text = "<span style=\'color: %s\'><b>%s" \ "</b></span>" % (color, strftime(fmt, gmtime(elapsed_time))) self.time_label.setText(text) def update_time_label_visibility(self): """Update elapsed time visibility.""" self.time_label.setVisible(self.show_elapsed_time) @Slot(bool) def set_elapsed_time_visible(self, state): """Slot to show/hide elapsed time label.""" self.show_elapsed_time = state if self.time_label is not None: self.time_label.setVisible(state) #------ Private API ------------------------------------------------------- def _create_loading_page(self): """Create html page to show while the kernel is starting""" loading_template = Template(LOADING) loading_img = get_image_path('loading_sprites.png') if os.name == 'nt': loading_img = loading_img.replace('\\', '/') message = _("Connecting to kernel...") page = loading_template.substitute(css_path=CSS_PATH, loading_img=loading_img, message=message) return page def _show_loading_page(self): """Show animation while the kernel is loading.""" self.shellwidget.hide() self.infowidget.show() self.infowidget.setHtml(self.loading_page, QUrl.fromLocalFile(CSS_PATH)) def _hide_loading_page(self): """Hide animation shown while the kernel is loading.""" self.infowidget.hide() self.shellwidget.show() self.infowidget.setHtml(BLANK) self.shellwidget.sig_prompt_ready.disconnect(self._hide_loading_page) def _read_stderr(self): """Read the stderr file of the kernel.""" stderr_text = open(self.stderr_file, 'rb').read() encoding = get_coding(stderr_text) stderr = to_text_string(stderr_text, encoding) return stderr def _show_mpl_backend_errors(self): """ Show possible errors when setting the selected Matplotlib backend. """ if not self.external_kernel: self.shellwidget.silent_execute( "get_ipython().kernel._show_mpl_backend_errors()") self.shellwidget.sig_prompt_ready.disconnect( self._show_mpl_backend_errors)
def setup(self): # Attributes self.dialog_manager = DialogManager() self.give_updates_feedback = False self.thread_updates = None self.worker_updates = None # Actions # Documentation actions self.documentation_action = self.create_action( ApplicationActions.SpyderDocumentationAction, text=_("Spyder documentation"), icon=self.create_icon("DialogHelpButton"), triggered=lambda: start_file(__docs_url__), context=Qt.ApplicationShortcut, register_shortcut=True, shortcut_context="_") spyder_video_url = ("https://www.youtube.com/playlist" "?list=PLPonohdiDqg9epClEcXoAPUiK0pN5eRoc") self.video_action = self.create_action( ApplicationActions.SpyderDocumentationVideoAction, text=_("Tutorial videos"), icon=self.create_icon("VideoIcon"), triggered=lambda: start_file(spyder_video_url)) # Support actions self.trouble_action = self.create_action( ApplicationActions.SpyderTroubleshootingAction, _("Troubleshooting..."), triggered=lambda: start_file(__trouble_url__)) self.dependencies_action = self.create_action( ApplicationActions.SpyderDependenciesAction, _("Dependencies..."), triggered=self.show_dependencies, icon=self.create_icon('advanced')) self.check_updates_action = self.create_action( ApplicationActions.SpyderCheckUpdatesAction, _("Check for updates..."), triggered=self.check_updates) self.support_group_action = self.create_action( ApplicationActions.SpyderSupportAction, _("Spyder support..."), triggered=lambda: start_file(__forum_url__)) # About action self.about_action = self.create_action( ApplicationActions.SpyderAbout, _("About %s...") % "Spyder", icon=self.create_icon('MessageBoxInformation'), triggered=self.show_about) # Tools actions if WinUserEnvDialog is not None: self.winenv_action = self.create_action( ApplicationActions.SpyderWindowsEnvVariables, _("Current user environment variables..."), icon=self.create_icon('win_env.png', image_file=True), tip=_("Show and edit current user environment " "variables in Windows registry " "(i.e. for all sessions)"), triggered=self.show_windows_env_variables) else: self.winenv_action = None # Application base actions self.restart_action = self.create_action( ApplicationActions.SpyderRestart, _("&Restart"), icon=self.create_icon('restart'), tip=_("Restart"), triggered=self.sig_restart_requested, context=Qt.ApplicationShortcut, shortcut_context="_", register_shortcut=True) # Initialize if DEV is None and self.get_conf('check_updates_on_startup'): self.give_updates_feedback = False self.check_updates(startup=True)
class ApplicationContainer(PluginMainContainer): def setup(self): # Attributes self.dialog_manager = DialogManager() self.give_updates_feedback = False self.thread_updates = None self.worker_updates = None # Actions # Documentation actions self.documentation_action = self.create_action( ApplicationActions.SpyderDocumentationAction, text=_("Spyder documentation"), icon=self.create_icon("DialogHelpButton"), triggered=lambda: start_file(__docs_url__), context=Qt.ApplicationShortcut, register_shortcut=True, shortcut_context="_") spyder_video_url = ("https://www.youtube.com/playlist" "?list=PLPonohdiDqg9epClEcXoAPUiK0pN5eRoc") self.video_action = self.create_action( ApplicationActions.SpyderDocumentationVideoAction, text=_("Tutorial videos"), icon=self.create_icon("VideoIcon"), triggered=lambda: start_file(spyder_video_url)) # Support actions self.trouble_action = self.create_action( ApplicationActions.SpyderTroubleshootingAction, _("Troubleshooting..."), triggered=lambda: start_file(__trouble_url__)) self.dependencies_action = self.create_action( ApplicationActions.SpyderDependenciesAction, _("Dependencies..."), triggered=self.show_dependencies, icon=self.create_icon('advanced')) self.check_updates_action = self.create_action( ApplicationActions.SpyderCheckUpdatesAction, _("Check for updates..."), triggered=self.check_updates) self.support_group_action = self.create_action( ApplicationActions.SpyderSupportAction, _("Spyder support..."), triggered=lambda: start_file(__forum_url__)) # About action self.about_action = self.create_action( ApplicationActions.SpyderAbout, _("About %s...") % "Spyder", icon=self.create_icon('MessageBoxInformation'), triggered=self.show_about) # Tools actions if WinUserEnvDialog is not None: self.winenv_action = self.create_action( ApplicationActions.SpyderWindowsEnvVariables, _("Current user environment variables..."), icon=self.create_icon('win_env.png', image_file=True), tip=_("Show and edit current user environment " "variables in Windows registry " "(i.e. for all sessions)"), triggered=self.show_windows_env_variables) else: self.winenv_action = None # Application base actions self.restart_action = self.create_action( ApplicationActions.SpyderRestart, _("&Restart"), icon=self.create_icon('restart'), tip=_("Restart"), triggered=self.sig_restart_requested, context=Qt.ApplicationShortcut, shortcut_context="_", register_shortcut=True) # Initialize if DEV is None and self.get_conf('check_updates_on_startup'): self.give_updates_feedback = False self.check_updates(startup=True) def update_actions(self): pass def on_close(self): self.dialog_manager.close_all() def _check_updates_ready(self): """Show results of the Spyder update checking process.""" # `feedback` = False is used on startup, so only positive feedback is # given. `feedback` = True is used when after startup (when using the # menu action, and gives feeback if updates are, or are not found. feedback = self.give_updates_feedback # Get results from worker update_available = self.worker_updates.update_available latest_release = self.worker_updates.latest_release error_msg = self.worker_updates.error # Release url url_r = __project_url__ + '/releases/tag/v{}'.format(latest_release) url_i = 'https://docs.spyder-ide.org/installation.html' # Define the custom QMessageBox box = MessageCheckBox(icon=QMessageBox.Information, parent=self) box.setWindowTitle(_("New Spyder version")) box.set_checkbox_text(_("Check for updates at startup")) box.setStandardButtons(QMessageBox.Ok) box.setDefaultButton(QMessageBox.Ok) # Adjust the checkbox depending on the stored configuration option = 'check_updates_on_startup' check_updates = self.get_conf(option) box.set_checked(check_updates) if error_msg is not None: msg = error_msg box.setText(msg) box.set_check_visible(False) box.exec_() check_updates = box.is_checked() else: if update_available: header = _("<b>Spyder {} is available!</b><br><br>").format( latest_release) footer = _( "For more information visit our " "<a href=\"{}\">installation guide</a>.").format(url_i) if is_anaconda(): content = _( "<b>Important note:</b> Since you installed " "Spyder with Anaconda, please <b>don't</b> use " "<code>pip</code> to update it as that will break " "your installation.<br><br>" "Instead, run the following commands in a " "terminal:<br>" "<code>conda update anaconda</code><br>" "<code>conda install spyder={}</code><br><br>").format( latest_release) else: content = _("Please go to <a href=\"{}\">this page</a> to " "download it.<br><br>").format(url_r) msg = header + content + footer box.setText(msg) box.set_check_visible(True) box.exec_() check_updates = box.is_checked() elif feedback: msg = _("Spyder is up to date.") box.setText(msg) box.set_check_visible(False) box.exec_() check_updates = box.is_checked() # Update checkbox based on user interaction self.set_conf(option, check_updates) # Enable check_updates_action after the thread has finished self.check_updates_action.setDisabled(False) # Provide feeback when clicking menu if check on startup is on self.give_updates_feedback = True @Slot() def check_updates(self, startup=False): """Check for spyder updates on github releases using a QThread.""" # Disable check_updates_action while the thread is working self.check_updates_action.setDisabled(True) if self.thread_updates is not None: self.thread_updates.terminate() self.thread_updates = QThread(self) self.worker_updates = WorkerUpdates(self, startup=startup) self.worker_updates.sig_ready.connect(self._check_updates_ready) self.worker_updates.sig_ready.connect(self.thread_updates.quit) self.worker_updates.moveToThread(self.thread_updates) self.thread_updates.started.connect(self.worker_updates.start) self.thread_updates.start() @Slot() def show_dependencies(self): """Show Spyder Dependencies dialog.""" dlg = DependenciesDialog(self) dlg.set_data(dependencies.DEPENDENCIES) dlg.show() @Slot() def show_about(self): """Show Spyder About dialog.""" abt = AboutDialog(self) abt.show() @Slot() def show_windows_env_variables(self): """Show Windows current user environment variables.""" self.dialog_manager.show(WinUserEnvDialog(self)) @Slot() def report_missing_dependencies(self): """Show a QMessageBox with a list of missing hard dependencies.""" # Declare dependencies before trying to detect the missing ones dependencies.declare_dependencies() missing_deps = dependencies.missing_dependencies() if missing_deps: # We change '<br>' by '\n', in order to replace the '<' # that appear in our deps by '<' (to not break html # formatting) and finally we restore '<br>' again. missing_deps = (missing_deps.replace('<br>', '\n').replace( '<', '<').replace('\n', '<br>')) QMessageBox.critical( self, _('Error'), _("<b>You have missing dependencies!</b>" "<br><br><tt>%s</tt><br>" "<b>Please install them to avoid this message.</b>" "<br><br>" "<i>Note</i>: Spyder could work without some of these " "dependencies, however to have a smooth experience when " "using Spyder we <i>strongly</i> recommend you to install " "all the listed missing dependencies.<br><br>" "Failing to install these dependencies might result in bugs." " Please be sure that any found bugs are not the direct " "result of missing dependencies, prior to reporting a new " "issue.") % missing_deps, QMessageBox.Ok)
class Console(SpyderPluginWidget): """ Console widget """ CONF_SECTION = 'internal_console' CONF_FILE = False focus_changed = Signal() redirect_stdio = Signal(bool) edit_goto = Signal(str, int, str) def __init__(self, parent=None, namespace=None, commands=[], message=None, exitfunc=None, profile=False, multithreaded=False): SpyderPluginWidget.__init__(self, parent) logger.info("Initializing...") self.dialog_manager = DialogManager() # Shell self.shell = InternalShell(parent, namespace, commands, message, self.get_option('max_line_count'), self.get_font(), exitfunc, profile, multithreaded) self.shell.status.connect( lambda msg: self.sig_show_status_message.emit(msg, 0)) self.shell.go_to_error.connect(self.go_to_error) self.shell.focus_changed.connect(lambda: self.focus_changed.emit()) # Redirecting some signals: self.shell.redirect_stdio.connect( lambda state: self.redirect_stdio.emit(state)) # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.set_editor(self.shell) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) # Main layout btn_layout = QHBoxLayout() btn_layout.setAlignment(Qt.AlignLeft) btn_layout.addStretch() btn_layout.addWidget(self.options_button, Qt.AlignRight) layout = create_plugin_layout(btn_layout) layout.addWidget(self.shell) layout.addWidget(self.find_widget) self.setLayout(layout) # Parameters self.shell.toggle_wrap_mode(self.get_option('wrap')) # Accepting drops self.setAcceptDrops(True) # Traceback MessageBox self.error_dlg = None self.error_traceback = "" self.dismiss_error = False #------ Private API -------------------------------------------------------- def set_historylog(self, historylog): """Bind historylog instance to this console Not used anymore since v2.0""" historylog.add_history(self.shell.history_filename) self.shell.append_to_history.connect(historylog.append_to_history) def set_help(self, help_plugin): """Bind help instance to this console""" self.shell.help = help_plugin #------ SpyderPluginWidget API --------------------------------------------- def get_plugin_title(self): """Return widget title""" return _('Internal console') def get_focus_widget(self): """ Return the widget to give focus to when this plugin's dockwidget is raised on top-level """ return self.shell def update_font(self): """Update font from Preferences""" font = self.get_font() self.shell.set_font(font) def closing_plugin(self, cancelable=False): """Perform actions before parent main window is closed""" self.dialog_manager.close_all() self.shell.exit_interpreter() return True def get_plugin_actions(self): """Return a list of actions related to plugin""" quit_action = create_action(self, _("&Quit"), icon=ima.icon('exit'), tip=_("Quit"), triggered=self.quit) self.register_shortcut(quit_action, "_", "Quit", "Ctrl+Q") run_action = create_action(self, _("&Run..."), None, ima.icon('run_small'), _("Run a Python script"), triggered=self.run_script) environ_action = create_action( self, _("Environment variables..."), icon=ima.icon('environ'), tip=_("Show and edit environment variables" " (for current session)"), triggered=self.show_env) syspath_action = create_action(self, _("Show sys.path contents..."), icon=ima.icon('syspath'), tip=_("Show (read-only) sys.path"), triggered=self.show_syspath) buffer_action = create_action(self, _("Buffer..."), None, tip=_("Set maximum line count"), triggered=self.change_max_line_count) exteditor_action = create_action( self, _("External editor path..."), None, None, _("Set external editor executable path"), triggered=self.change_exteditor) wrap_action = create_action(self, _("Wrap lines"), toggled=self.toggle_wrap_mode) wrap_action.setChecked(self.get_option('wrap')) codecompletion_action = create_action( self, _("Automatic code completion"), toggled=self.toggle_codecompletion) codecompletion_action.setChecked( self.get_option('codecompletion/auto')) option_menu = QMenu(_('Internal console settings'), self) option_menu.setIcon(ima.icon('tooloptions')) add_actions(option_menu, (buffer_action, wrap_action, codecompletion_action, exteditor_action)) plugin_actions = [ None, run_action, environ_action, syspath_action, option_menu, MENU_SEPARATOR, quit_action ] return plugin_actions def register_plugin(self): """Register plugin in Spyder's main window""" self.focus_changed.connect(self.main.plugin_focus_changed) self.add_dockwidget() # Connecting the following signal once the dockwidget has been created: self.shell.exception_occurred.connect(self.exception_occurred) def exception_occurred(self, text, is_traceback, is_pyls_error=False): """ Exception ocurred in the internal console. Show a QDialog or the internal console to warn the user. """ # Skip errors without traceback or dismiss if (not is_traceback and self.error_dlg is None) or self.dismiss_error: return if CONF.get('main', 'show_internal_errors'): if self.error_dlg is None: self.error_dlg = SpyderErrorDialog(self) self.error_dlg.set_color_scheme( CONF.get('appearance', 'selected')) self.error_dlg.close_btn.clicked.connect(self.close_error_dlg) self.error_dlg.rejected.connect(self.remove_error_dlg) self.error_dlg.details.go_to_error.connect(self.go_to_error) if is_pyls_error: title = "Internal Python Language Server error" self.error_dlg.set_title(title) self.error_dlg.title.setEnabled(False) self.error_dlg.append_traceback(text) self.error_dlg.show() elif DEV or get_debug_level(): self.switch_to_plugin() def close_error_dlg(self): """Close error dialog.""" if self.error_dlg.dismiss_box.isChecked(): self.dismiss_error = True self.error_dlg.reject() def remove_error_dlg(self): """Remove error dialog.""" self.error_dlg = None #------ Public API --------------------------------------------------------- @Slot() def quit(self): """Quit mainwindow""" self.main.close() @Slot() def show_env(self): """Show environment variables""" self.dialog_manager.show(EnvDialog(parent=self)) def get_sys_path(self): """Return the `sys.path`.""" return sys.path @Slot() def show_syspath(self): """Show sys.path""" editor = CollectionsEditor(parent=self) editor.setup(sys.path, title="sys.path", readonly=True, width=600, icon=ima.icon('syspath')) self.dialog_manager.show(editor) @Slot() def run_script(self, filename=None, silent=False, set_focus=False, args=None): """Run a Python script""" if filename is None: self.shell.interpreter.restore_stds() filename, _selfilter = getopenfilename( self, _("Run Python script"), getcwd_or_home(), _("Python scripts") + " (*.py ; *.pyw ; *.ipy)") self.shell.interpreter.redirect_stds() if filename: os.chdir(osp.dirname(filename)) filename = osp.basename(filename) else: return logger.debug("Running script with %s", args) filename = osp.abspath(filename) rbs = remove_backslashes command = "runfile('%s', args='%s')" % (rbs(filename), rbs(args)) if set_focus: self.shell.setFocus() if self.dockwidget: self.switch_to_plugin() self.shell.write(command + '\n') self.shell.run_command(command) def go_to_error(self, text): """Go to error if relevant""" match = get_error_match(to_text_string(text)) if match: fname, lnb = match.groups() self.edit_script(fname, int(lnb)) def edit_script(self, filename=None, goto=-1): """Edit script""" # Called from InternalShell if not hasattr(self, 'main') \ or not hasattr(self.main, 'editor'): self.shell.external_editor(filename, goto) return if filename is not None: self.edit_goto.emit(osp.abspath(filename), goto, '') def execute_lines(self, lines): """Execute lines and give focus to shell""" self.shell.execute_lines(to_text_string(lines)) self.shell.setFocus() @Slot() def change_max_line_count(self): "Change maximum line count" "" mlc, valid = QInputDialog.getInt(self, _('Buffer'), _('Maximum line count'), self.get_option('max_line_count'), 0, 1000000) if valid: self.shell.setMaximumBlockCount(mlc) self.set_option('max_line_count', mlc) @Slot() def change_exteditor(self): """Change external editor path""" path, valid = QInputDialog.getText( self, _('External editor'), _('External editor executable path:'), QLineEdit.Normal, self.get_option('external_editor/path')) if valid: self.set_option('external_editor/path', to_text_string(path)) @Slot(bool) def toggle_wrap_mode(self, checked): """Toggle wrap mode""" self.shell.toggle_wrap_mode(checked) self.set_option('wrap', checked) @Slot(bool) def toggle_codecompletion(self, checked): """Toggle automatic code completion""" self.shell.set_codecompletion_auto(checked) self.set_option('codecompletion/auto', checked) #----Drag and drop def dragEnterEvent(self, event): """Reimplement Qt method Inform Qt about the types of data that the widget accepts""" source = event.mimeData() if source.hasUrls(): if mimedata2url(source): event.acceptProposedAction() else: event.ignore() elif source.hasText(): event.acceptProposedAction() def dropEvent(self, event): """Reimplement Qt method Unpack dropped data and handle it""" source = event.mimeData() if source.hasUrls(): pathlist = mimedata2url(source) self.shell.drop_pathlist(pathlist) elif source.hasText(): lines = to_text_string(source.text()) self.shell.set_cursor_position('eof') self.shell.execute_lines(lines) event.acceptProposedAction()
def __init__(self, plugin, id_, history_filename, config_options, additional_options, interpreter_versions, connection_file=None, hostname=None, menu_actions=None, slave=False, external_kernel=False, given_name=None, options_button=None, show_elapsed_time=False, reset_warning=True, ask_before_restart=True, css_path=None): super(ClientWidget, self).__init__(plugin) SaveHistoryMixin.__init__(self, history_filename) # --- Init attrs self.id_ = id_ self.connection_file = connection_file self.hostname = hostname self.menu_actions = menu_actions self.slave = slave self.external_kernel = external_kernel self.given_name = given_name self.show_elapsed_time = show_elapsed_time self.reset_warning = reset_warning self.ask_before_restart = ask_before_restart # --- Other attrs self.options_button = options_button self.stop_button = None self.reset_button = None self.stop_icon = ima.icon('stop') self.history = [] self.allow_rename = True self.stderr_dir = None self.is_error_shown = False if css_path is None: self.css_path = CSS_PATH else: self.css_path = css_path # --- Widgets self.shellwidget = ShellWidget(config=config_options, ipyclient=self, additional_options=additional_options, interpreter_versions=interpreter_versions, external_kernel=external_kernel, local_kernel=True) self.infowidget = plugin.infowidget self.blank_page = self._create_blank_page() self.loading_page = self._create_loading_page() # To keep a reference to the page to be displayed # in infowidget self.info_page = None self._show_loading_page() # Elapsed time self.time_label = None self.t0 = time.monotonic() self.timer = QTimer(self) self.show_time_action = create_action(self, _("Show elapsed time"), toggled=self.set_elapsed_time_visible) # --- Layout self.layout = QVBoxLayout() toolbar_buttons = self.get_toolbar_buttons() hlayout = QHBoxLayout() hlayout.addWidget(self.create_time_label()) hlayout.addStretch(0) for button in toolbar_buttons: hlayout.addWidget(button) self.layout.addLayout(hlayout) self.layout.setContentsMargins(0, 0, 0, 0) self.layout.addWidget(self.shellwidget) self.layout.addWidget(self.infowidget) self.setLayout(self.layout) # --- Exit function self.exit_callback = lambda: plugin.close_client(client=self) # --- Dialog manager self.dialog_manager = DialogManager() # Show timer self.update_time_label_visibility()
def __init__(self, plugin, name, history_filename, config_options, additional_options, interpreter_versions, connection_file=None, hostname=None, menu_actions=None, slave=False, external_kernel=False): super(ClientWidget, self).__init__(plugin) SaveHistoryMixin.__init__(self, history_filename) # --- Init attrs self.name = name self.connection_file = connection_file self.hostname = hostname self.menu_actions = menu_actions self.slave = slave # --- Other attrs self.options_button = None self.stop_button = None self.stop_icon = ima.icon('stop') self.history = [] # --- Widgets self.shellwidget = ShellWidget( config=config_options, ipyclient=self, additional_options=additional_options, interpreter_versions=interpreter_versions, external_kernel=external_kernel, local_kernel=True) self.infowidget = WebView(self) self.set_infowidget_font() self.loading_page = self._create_loading_page() self._show_loading_page() # --- Layout vlayout = QVBoxLayout() toolbar_buttons = self.get_toolbar_buttons() hlayout = QHBoxLayout() for button in toolbar_buttons: hlayout.addWidget(button) vlayout.addLayout(hlayout) vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addWidget(self.shellwidget) vlayout.addWidget(self.infowidget) self.setLayout(vlayout) # --- Exit function self.exit_callback = lambda: plugin.close_client(client=self) # --- Signals # As soon as some content is printed in the console, stop # our loading animation document = self.get_control().document() document.contentsChange.connect(self._hide_loading_page) # --- Dialog manager self.dialog_manager = DialogManager()
class ApplicationContainer(PluginMainContainer): sig_report_issue_requested = Signal() """ Signal to request reporting an issue to Github. """ sig_load_log_file = Signal(str) """ Signal to load a log file """ def __init__(self, name, plugin, parent=None): super().__init__(name, plugin, parent) # Keep track of dpi message self.current_dpi = None self.dpi_messagebox = None # ---- PluginMainContainer API # ------------------------------------------------------------------------- def setup(self): # Compute dependencies in a thread to not block the interface. self.dependencies_thread = QThread(None) # Attributes self.dialog_manager = DialogManager() self.give_updates_feedback = False self.thread_updates = None self.worker_updates = None self.updates_timer = None # Actions # Documentation actions self.documentation_action = self.create_action( ApplicationActions.SpyderDocumentationAction, text=_("Spyder documentation"), icon=self.create_icon("DialogHelpButton"), triggered=lambda: start_file(__docs_url__), context=Qt.ApplicationShortcut, register_shortcut=True, shortcut_context="_") spyder_video_url = ("https://www.youtube.com/playlist" "?list=PLPonohdiDqg9epClEcXoAPUiK0pN5eRoc") self.video_action = self.create_action( ApplicationActions.SpyderDocumentationVideoAction, text=_("Tutorial videos"), icon=self.create_icon("VideoIcon"), triggered=lambda: start_file(spyder_video_url)) # Support actions self.trouble_action = self.create_action( ApplicationActions.SpyderTroubleshootingAction, _("Troubleshooting..."), triggered=lambda: start_file(__trouble_url__)) self.report_action = self.create_action( ConsoleActions.SpyderReportAction, _("Report issue..."), icon=self.create_icon('bug'), triggered=self.sig_report_issue_requested) self.dependencies_action = self.create_action( ApplicationActions.SpyderDependenciesAction, _("Dependencies..."), triggered=self.show_dependencies, icon=self.create_icon('advanced')) self.check_updates_action = self.create_action( ApplicationActions.SpyderCheckUpdatesAction, _("Check for updates..."), triggered=self.check_updates) self.support_group_action = self.create_action( ApplicationActions.SpyderSupportAction, _("Spyder support..."), triggered=lambda: start_file(__forum_url__)) # About action self.about_action = self.create_action( ApplicationActions.SpyderAbout, _("About %s...") % "Spyder", icon=self.create_icon('MessageBoxInformation'), triggered=self.show_about, menurole=QAction.AboutRole) # Tools actions if WinUserEnvDialog is not None: self.winenv_action = self.create_action( ApplicationActions.SpyderWindowsEnvVariables, _("Current user environment variables..."), icon=self.create_icon('win_env'), tip=_("Show and edit current user environment " "variables in Windows registry " "(i.e. for all sessions)"), triggered=self.show_windows_env_variables) else: self.winenv_action = None # Application base actions self.restart_action = self.create_action( ApplicationActions.SpyderRestart, _("&Restart"), icon=self.create_icon('restart'), tip=_("Restart"), triggered=self.restart_normal, context=Qt.ApplicationShortcut, shortcut_context="_", register_shortcut=True) self.restart_debug_action = self.create_action( ApplicationActions.SpyderRestartDebug, _("&Restart in debug mode"), tip=_("Restart in debug mode"), triggered=self.restart_debug, context=Qt.ApplicationShortcut, shortcut_context="_", register_shortcut=True) # Debug logs if get_debug_level() >= 2: self.menu_debug_logs = self.create_menu( ApplicationPluginMenus.DebugLogsMenu, _("Debug logs")) # The menu can't be built at startup because Completions can # start after Application. self.menu_debug_logs.aboutToShow.connect( self.create_debug_log_actions) def update_actions(self): pass # ---- Other functionality # ------------------------------------------------------------------------- def on_close(self): """To call from Spyder when the plugin is closed.""" self.dialog_manager.close_all() if self.updates_timer is not None: self.updates_timer.stop() if self.thread_updates is not None: self.thread_updates.quit() self.thread_updates.wait() if self.dependencies_thread is not None: self.dependencies_thread.quit() self.dependencies_thread.wait() @Slot() def show_about(self): """Show Spyder About dialog.""" abt = AboutDialog(self) abt.show() @Slot() def show_windows_env_variables(self): """Show Windows current user environment variables.""" self.dialog_manager.show(WinUserEnvDialog(self)) # ---- Updates # ------------------------------------------------------------------------- def _check_updates_ready(self): """Show results of the Spyder update checking process.""" # `feedback` = False is used on startup, so only positive feedback is # given. `feedback` = True is used when after startup (when using the # menu action, and gives feeback if updates are, or are not found. feedback = self.give_updates_feedback # Get results from worker update_available = self.worker_updates.update_available latest_release = self.worker_updates.latest_release error_msg = self.worker_updates.error # Release url if sys.platform == 'darwin': url_r = ('https://github.com/spyder-ide/spyder/releases/latest/' 'download/Spyder.dmg') else: url_r = ('https://github.com/spyder-ide/spyder/releases/latest/' 'download/Spyder_64bit_full.exe') url_i = 'https://docs.spyder-ide.org/installation.html' # Define the custom QMessageBox box = MessageCheckBox(icon=QMessageBox.Information, parent=self) box.setWindowTitle(_("New Spyder version")) box.setAttribute(Qt.WA_ShowWithoutActivating) box.set_checkbox_text(_("Check for updates at startup")) box.setStandardButtons(QMessageBox.Ok) box.setDefaultButton(QMessageBox.Ok) # Adjust the checkbox depending on the stored configuration option = 'check_updates_on_startup' check_updates = self.get_conf(option) box.set_checked(check_updates) if error_msg is not None: msg = error_msg box.setText(msg) box.set_check_visible(False) box.exec_() check_updates = box.is_checked() else: if update_available: header = _("<b>Spyder {} is available!</b><br><br>").format( latest_release) footer = _( "For more information visit our " "<a href=\"{}\">installation guide</a>.").format(url_i) if is_anaconda(): content = _( "<b>Important note:</b> Since you installed " "Spyder with Anaconda, please <b>don't</b> use " "<code>pip</code> to update it as that will break " "your installation.<br><br>" "Instead, run the following commands in a " "terminal:<br>" "<code>conda update anaconda</code><br>" "<code>conda install spyder={}</code><br><br>").format( latest_release) else: content = _("Click <a href=\"{}\">this link</a> to " "download it.<br><br>").format(url_r) msg = header + content + footer box.setText(msg) box.set_check_visible(True) box.show() check_updates = box.is_checked() elif feedback: msg = _("Spyder is up to date.") box.setText(msg) box.set_check_visible(False) box.exec_() check_updates = box.is_checked() # Update checkbox based on user interaction self.set_conf(option, check_updates) # Enable check_updates_action after the thread has finished self.check_updates_action.setDisabled(False) # Provide feeback when clicking menu if check on startup is on self.give_updates_feedback = True @Slot() def check_updates(self, startup=False): """Check for spyder updates on github releases using a QThread.""" # Disable check_updates_action while the thread is working self.check_updates_action.setDisabled(True) if self.thread_updates is not None: self.thread_updates.quit() self.thread_updates.wait() self.thread_updates = QThread(None) self.worker_updates = WorkerUpdates(self, startup=startup) self.worker_updates.sig_ready.connect(self._check_updates_ready) self.worker_updates.sig_ready.connect(self.thread_updates.quit) self.worker_updates.moveToThread(self.thread_updates) self.thread_updates.started.connect(self.worker_updates.start) # Delay starting this check to avoid blocking the main window # while loading. # Fixes spyder-ide/spyder#15839 self.updates_timer = QTimer(self) self.updates_timer.setInterval(3000) self.updates_timer.setSingleShot(True) self.updates_timer.timeout.connect(self.thread_updates.start) self.updates_timer.start() # ---- Dependencies # ------------------------------------------------------------------------- @Slot() def show_dependencies(self): """Show Spyder Dependencies dialog.""" # This is here in case the user tries to display the dialog before # dependencies_thread has finished. if not dependencies.DEPENDENCIES: dependencies.declare_dependencies() dlg = DependenciesDialog(self) dlg.set_data(dependencies.DEPENDENCIES) dlg.show() def _compute_dependencies(self): """Compute dependencies without errors.""" # Skip error when trying to register dependencies several times. # This can happen if the user tries to display the dependencies # dialog before dependencies_thread has finished. try: dependencies.declare_dependencies() except ValueError: pass def compute_dependencies(self): """Compute dependencies.""" self.dependencies_thread.run = self._compute_dependencies self.dependencies_thread.finished.connect( self.report_missing_dependencies) # This avoids computing missing deps before the window is fully up dependencies_timer = QTimer(self) dependencies_timer.setInterval(10000) dependencies_timer.setSingleShot(True) dependencies_timer.timeout.connect(self.dependencies_thread.start) dependencies_timer.start() @Slot() def report_missing_dependencies(self): """Show a QMessageBox with a list of missing hard dependencies.""" missing_deps = dependencies.missing_dependencies() if missing_deps: InstallerMissingDependencies(missing_deps) # We change '<br>' by '\n', in order to replace the '<' # that appear in our deps by '<' (to not break html # formatting) and finally we restore '<br>' again. missing_deps = (missing_deps.replace('<br>', '\n').replace( '<', '<').replace('\n', '<br>')) message = ( _("<b>You have missing dependencies!</b>" "<br><br><tt>%s</tt><br>" "<b>Please install them to avoid this message.</b>" "<br><br>" "<i>Note</i>: Spyder could work without some of these " "dependencies, however to have a smooth experience when " "using Spyder we <i>strongly</i> recommend you to install " "all the listed missing dependencies.<br><br>" "Failing to install these dependencies might result in bugs." " Please be sure that any found bugs are not the direct " "result of missing dependencies, prior to reporting a new " "issue.") % missing_deps) message_box = QMessageBox(self) message_box.setIcon(QMessageBox.Critical) message_box.setAttribute(Qt.WA_DeleteOnClose) message_box.setAttribute(Qt.WA_ShowWithoutActivating) message_box.setStandardButtons(QMessageBox.Ok) message_box.setWindowModality(Qt.NonModal) message_box.setWindowTitle(_('Error')) message_box.setText(message) message_box.show() # ---- Restart # ------------------------------------------------------------------------- @Slot() def restart_normal(self): """Restart in standard mode.""" os.environ['SPYDER_DEBUG'] = '' self.sig_restart_requested.emit() @Slot() def restart_debug(self): """Restart in debug mode.""" box = QMessageBox(self) box.setWindowTitle(_("Question")) box.setIcon(QMessageBox.Question) box.setText(_("Which debug mode do you want Spyder to restart in?")) button_verbose = QPushButton(_('Verbose')) button_minimal = QPushButton(_('Minimal')) box.addButton(button_verbose, QMessageBox.AcceptRole) box.addButton(button_minimal, QMessageBox.AcceptRole) box.setStandardButtons(QMessageBox.Cancel) box.exec_() if box.clickedButton() == button_minimal: os.environ['SPYDER_DEBUG'] = '2' elif box.clickedButton() == button_verbose: os.environ['SPYDER_DEBUG'] = '3' else: return self.sig_restart_requested.emit() # ---- Log files # ------------------------------------------------------------------------- def create_debug_log_actions(self): """Create an action for each lsp and debug log file.""" self.menu_debug_logs.clear_actions() files = [os.environ['SPYDER_DEBUG_FILE']] files += glob.glob(os.path.join(get_conf_path('lsp_logs'), '*.log')) debug_logs_actions = [] for file in files: action = self.create_action( file, os.path.basename(file), tip=file, triggered=lambda _, file=file: self.load_log_file(file), overwrite=True, register_action=False) debug_logs_actions.append(action) # Add Spyder log on its own section self.add_item_to_menu(debug_logs_actions[0], self.menu_debug_logs, section=LogsMenuSections.SpyderLogSection) # Add LSP logs for action in debug_logs_actions[1:]: self.add_item_to_menu(action, self.menu_debug_logs, section=LogsMenuSections.LSPLogsSection) # Render menu self.menu_debug_logs._render() def load_log_file(self, file): """Load log file in editor""" self.sig_load_log_file.emit(file) # ---- DPI changes # ------------------------------------------------------------------------- def set_window(self, window): """Set window property of main window.""" self._window = window def handle_new_screen(self, new_screen): """Connect DPI signals for new screen.""" if new_screen is not None: new_screen_dpi = new_screen.logicalDotsPerInch() if self.current_dpi != new_screen_dpi: self.show_dpi_change_message(new_screen_dpi) else: new_screen.logicalDotsPerInchChanged.connect( self.show_dpi_change_message) def handle_dpi_change_response(self, result, dpi): """Handle dpi change message dialog result.""" if self.dpi_messagebox.is_checked(): self.set_conf('show_dpi_message', False) self.dpi_messagebox = None if result == 0: # Restart button was clicked # Activate HDPI auto-scaling option since is needed for a # proper display when using OS scaling self.set_conf('normal_screen_resolution', False) self.set_conf('high_dpi_scaling', True) self.set_conf('high_dpi_custom_scale_factor', False) self.sig_restart_requested.emit() else: # Update current dpi for future checks self.current_dpi = dpi def show_dpi_change_message(self, dpi): """Show message to restart Spyder since the DPI scale changed.""" if not self.get_conf('show_dpi_message'): return if self.current_dpi != dpi: # Check the window state to not show the message if the window # is in fullscreen mode. window = self._window.windowHandle() if (window.windowState() == Qt.WindowFullScreen and sys.platform == 'darwin'): return if self.get_conf('high_dpi_scaling'): return if self.dpi_messagebox is not None: self.dpi_messagebox.activateWindow() self.dpi_messagebox.raise_() return self.dpi_messagebox = MessageCheckBox(icon=QMessageBox.Warning, parent=self) self.dpi_messagebox.set_checkbox_text(_("Don't show again.")) self.dpi_messagebox.set_checked(False) self.dpi_messagebox.set_check_visible(True) self.dpi_messagebox.setText( _("A monitor scale change was detected. <br><br>" "We recommend restarting Spyder to ensure that it's properly " "displayed. If you don't want to do that, please be sure to " "activate the option<br><br><tt>Enable auto high DPI scaling" "</tt><br><br>in <tt>Preferences > Application > " "Interface</tt>, in case Spyder is not displayed " "correctly.<br><br>" "Do you want to restart Spyder?")) self.dpi_messagebox.addButton(_('Restart now'), QMessageBox.NoRole) dismiss_button = self.dpi_messagebox.addButton( _('Dismiss'), QMessageBox.NoRole) self.dpi_messagebox.setDefaultButton(dismiss_button) self.dpi_messagebox.finished.connect( lambda result: self.handle_dpi_change_response(result, dpi)) self.dpi_messagebox.open() # Show dialog always in the primary screen to prevent not being # able to see it if a screen gets disconnected while # in suspended state. See spyder-ide/spyder#16390 dpi_messagebox_width = self.dpi_messagebox.rect().width() dpi_messagebox_height = self.dpi_messagebox.rect().height() screen_geometry = QGuiApplication.primaryScreen().geometry() x = (screen_geometry.width() - dpi_messagebox_width) / 2 y = (screen_geometry.height() - dpi_messagebox_height) / 2 # Convert coordinates to int to avoid a TypeError in Python 3.10 # Fixes spyder-ide/spyder#17677 self.dpi_messagebox.move(int(x), int(y)) self.dpi_messagebox.adjustSize()
class ClientWidget(QWidget, SaveHistoryMixin): """ Client widget for the IPython Console This is a widget composed of a shell widget and a WebView info widget to print different messages there. """ SEPARATOR = '{0}## ---({1})---'.format(os.linesep * 2, time.ctime()) INITHISTORY = [ '# -*- coding: utf-8 -*-', '# *** Spyder Python Console History Log ***', ] append_to_history = Signal(str, str) def __init__(self, plugin, id_, history_filename, config_options, additional_options, interpreter_versions, connection_file=None, hostname=None, menu_actions=None, slave=False, external_kernel=False, given_name=None): super(ClientWidget, self).__init__(plugin) SaveHistoryMixin.__init__(self, history_filename) # --- Init attrs self.id_ = id_ self.connection_file = connection_file self.hostname = hostname self.menu_actions = menu_actions self.slave = slave self.given_name = given_name # --- Other attrs self.options_button = None self.stop_button = None self.stop_icon = ima.icon('stop') self.history = [] self.allow_rename = True self.stderr_dir = None # --- Widgets self.shellwidget = ShellWidget( config=config_options, ipyclient=self, additional_options=additional_options, interpreter_versions=interpreter_versions, external_kernel=external_kernel, local_kernel=True) self.infowidget = WebView(self) self.set_infowidget_font() self.loading_page = self._create_loading_page() self._show_loading_page() # --- Layout vlayout = QVBoxLayout() toolbar_buttons = self.get_toolbar_buttons() hlayout = QHBoxLayout() for button in toolbar_buttons: hlayout.addWidget(button) vlayout.addLayout(hlayout) vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addWidget(self.shellwidget) vlayout.addWidget(self.infowidget) self.setLayout(vlayout) # --- Exit function self.exit_callback = lambda: plugin.close_client(client=self) # --- Dialog manager self.dialog_manager = DialogManager() #------ Public API -------------------------------------------------------- @property def kernel_id(self): """Get kernel id""" if self.connection_file is not None: json_file = osp.basename(self.connection_file) return json_file.split('.json')[0] @property def stderr_file(self): """Filename to save kernel stderr output.""" stderr_file = None if self.connection_file is not None: stderr_file = self.kernel_id + '.stderr' if self.stderr_dir is not None: stderr_file = osp.join(self.stderr_dir, stderr_file) else: try: if not osp.isdir(TEMPDIR): os.makedirs(TEMPDIR) stderr_file = osp.join(TEMPDIR, stderr_file) except (IOError, OSError): stderr_file = None return stderr_file def configure_shellwidget(self, give_focus=True): """Configure shellwidget after kernel is started""" if give_focus: self.get_control().setFocus() # Set exit callback self.shellwidget.set_exit_callback() # To save history self.shellwidget.executing.connect(self.add_to_history) # For Mayavi to run correctly self.shellwidget.executing.connect( self.shellwidget.set_backend_for_mayavi) # To update history after execution self.shellwidget.executed.connect(self.update_history) # To update the Variable Explorer after execution self.shellwidget.executed.connect( self.shellwidget.refresh_namespacebrowser) # To enable the stop button when executing a process self.shellwidget.executing.connect(self.enable_stop_button) # To disable the stop button after execution stopped self.shellwidget.executed.connect(self.disable_stop_button) # To show kernel restarted/died messages self.shellwidget.sig_kernel_restarted.connect( self.kernel_restarted_message) # To correctly change Matplotlib backend interactively self.shellwidget.executing.connect(self.shellwidget.change_mpl_backend) # To show env and sys.path contents self.shellwidget.sig_show_syspath.connect(self.show_syspath) self.shellwidget.sig_show_env.connect(self.show_env) # To sync with working directory toolbar self.shellwidget.executed.connect(self.shellwidget.get_cwd) # To apply style if not create_qss_style(self.shellwidget.syntax_style)[1]: self.shellwidget.silent_execute("%colors linux") else: self.shellwidget.silent_execute("%colors lightbg") # To hide the loading page self.shellwidget.sig_prompt_ready.connect(self._hide_loading_page) def enable_stop_button(self): self.stop_button.setEnabled(True) def disable_stop_button(self): self.stop_button.setDisabled(True) @Slot() def stop_button_click_handler(self): """Method to handle what to do when the stop button is pressed""" self.stop_button.setDisabled(True) # Interrupt computations or stop debugging if not self.shellwidget._reading: self.interrupt_kernel() else: self.shellwidget.write_to_stdin('exit') def show_kernel_error(self, error): """Show kernel initialization errors in infowidget.""" # Replace end of line chars with <br> eol = sourcecode.get_eol_chars(error) if eol: error = error.replace(eol, '<br>') # Don't break lines in hyphens # From http://stackoverflow.com/q/7691569/438386 error = error.replace('-', '‑') # Create error page message = _("An error ocurred while starting the kernel") kernel_error_template = Template(KERNEL_ERROR) page = kernel_error_template.substitute(css_path=CSS_PATH, message=message, error=error) # Show error self.infowidget.setHtml(page) self.shellwidget.hide() self.infowidget.show() def get_name(self): """Return client name""" if self.given_name is None: # Name according to host if self.hostname is None: name = _("Console") else: name = self.hostname # Adding id to name client_id = self.id_['int_id'] + u'/' + self.id_['str_id'] name = name + u' ' + client_id else: name = self.given_name + u'/' + self.id_['str_id'] return name def get_control(self): """Return the text widget (or similar) to give focus to""" # page_control is the widget used for paging page_control = self.shellwidget._page_control if page_control and page_control.isVisible(): return page_control else: return self.shellwidget._control def get_kernel(self): """Get kernel associated with this client""" return self.shellwidget.kernel_manager def get_options_menu(self): """Return options menu""" env_action = create_action(self, _("Show environment variables"), icon=ima.icon('environ'), triggered=self.shellwidget.get_env) syspath_action = create_action(self, _("Show sys.path contents"), icon=ima.icon('syspath'), triggered=self.shellwidget.get_syspath) additional_actions = [MENU_SEPARATOR, env_action, syspath_action] if self.menu_actions is not None: return self.menu_actions + additional_actions else: return additional_actions def get_toolbar_buttons(self): """Return toolbar buttons list.""" buttons = [] # Code to add the stop button if self.stop_button is None: self.stop_button = create_toolbutton( self, text=_("Stop"), icon=self.stop_icon, tip=_("Stop the current command")) self.disable_stop_button() # set click event handler self.stop_button.clicked.connect(self.stop_button_click_handler) if self.stop_button is not None: buttons.append(self.stop_button) if self.options_button is None: options = self.get_options_menu() if options: self.options_button = create_toolbutton( self, text=_('Options'), icon=ima.icon('tooloptions')) self.options_button.setPopupMode(QToolButton.InstantPopup) menu = QMenu(self) add_actions(menu, options) self.options_button.setMenu(menu) if self.options_button is not None: buttons.append(self.options_button) return buttons def add_actions_to_context_menu(self, menu): """Add actions to IPython widget context menu""" inspect_action = create_action( self, _("Inspect current object"), QKeySequence(get_shortcut('console', 'inspect current object')), icon=ima.icon('MessageBoxInformation'), triggered=self.inspect_object) clear_line_action = create_action(self, _("Clear line or block"), QKeySequence("Shift+Escape"), icon=ima.icon('editdelete'), triggered=self.clear_line) reset_namespace_action = create_action(self, _("Reset namespace"), QKeySequence("Ctrl+Alt+R"), triggered=self.reset_namespace) clear_console_action = create_action( self, _("Clear console"), QKeySequence(get_shortcut('console', 'clear shell')), icon=ima.icon('editclear'), triggered=self.clear_console) quit_action = create_action(self, _("&Quit"), icon=ima.icon('exit'), triggered=self.exit_callback) add_actions( menu, (None, inspect_action, clear_line_action, clear_console_action, reset_namespace_action, None, quit_action)) return menu def set_font(self, font): """Set IPython widget's font""" self.shellwidget._control.setFont(font) self.shellwidget.font = font def set_infowidget_font(self): """Set font for infowidget""" font = get_font(option='rich_font') self.infowidget.set_font(font) def set_color_scheme(self, color_scheme): """Set IPython color scheme.""" self.shellwidget.set_color_scheme(color_scheme) def shutdown(self): """Shutdown kernel""" if self.get_kernel() is not None and not self.slave: self.shellwidget.kernel_manager.shutdown_kernel() if self.shellwidget.kernel_client is not None: background(self.shellwidget.kernel_client.stop_channels) def interrupt_kernel(self): """Interrupt the associanted Spyder kernel if it's running""" self.shellwidget.request_interrupt_kernel() @Slot() def restart_kernel(self): """ Restart the associanted kernel Took this code from the qtconsole project Licensed under the BSD license """ sw = self.shellwidget message = _('Are you sure you want to restart the kernel?') buttons = QMessageBox.Yes | QMessageBox.No result = QMessageBox.question(self, _('Restart kernel?'), message, buttons) if result == QMessageBox.Yes: if sw.kernel_manager: if self.infowidget.isVisible(): self.infowidget.hide() sw.show() try: sw.kernel_manager.restart_kernel() except RuntimeError as e: sw._append_plain_text(_('Error restarting kernel: %s\n') % e, before_prompt=True) else: sw.reset(clear=True) sw._append_html(_("<br>Restarting kernel...\n<hr><br>"), before_prompt=False) else: sw._append_plain_text( _('Cannot restart a kernel not started by Spyder\n'), before_prompt=True) @Slot(str) def kernel_restarted_message(self, msg): """Show kernel restarted/died messages.""" try: stderr = codecs.open(self.stderr_file, 'r', encoding='utf-8').read() except UnicodeDecodeError: # This is needed since the stderr file could be encoded # in something different to utf-8. # See issue 4191 try: stderr = self._read_stderr() except: stderr = None except (OSError, IOError): stderr = None if stderr: self.show_kernel_error('<tt>%s</tt>' % stderr) else: self.shellwidget._append_html("<br>%s<hr><br>" % msg, before_prompt=False) @Slot() def inspect_object(self): """Show how to inspect an object with our Help plugin""" self.shellwidget._control.inspect_current_object() @Slot() def clear_line(self): """Clear a console line""" self.shellwidget._keyboard_quit() @Slot() def clear_console(self): """Clear the whole console""" self.shellwidget.clear_console() @Slot() def reset_namespace(self): """Resets the namespace by removing all names defined by the user""" self.shellwidget.reset_namespace() def update_history(self): self.history = self.shellwidget._history @Slot(object) def show_syspath(self, syspath): """Show sys.path contents.""" if syspath is not None: editor = CollectionsEditor() editor.setup(syspath, title="sys.path contents", readonly=True, width=600, icon=ima.icon('syspath')) self.dialog_manager.show(editor) else: return @Slot(object) def show_env(self, env): """Show environment variables.""" self.dialog_manager.show(RemoteEnvDialog(env)) #------ Private API ------------------------------------------------------- def _create_loading_page(self): """Create html page to show while the kernel is starting""" loading_template = Template(LOADING) loading_img = get_image_path('loading_sprites.png') if os.name == 'nt': loading_img = loading_img.replace('\\', '/') message = _("Connecting to kernel...") page = loading_template.substitute(css_path=CSS_PATH, loading_img=loading_img, message=message) return page def _show_loading_page(self): """Show animation while the kernel is loading.""" self.shellwidget.hide() self.infowidget.show() self.infowidget.setHtml(self.loading_page, QUrl.fromLocalFile(CSS_PATH)) def _hide_loading_page(self): """Hide animation shown while the kernel is loading.""" self.infowidget.hide() self.shellwidget.show() self.infowidget.setHtml(BLANK) self.shellwidget.sig_prompt_ready.disconnect(self._hide_loading_page) def _read_stderr(self): """Read the stderr file of the kernel.""" stderr_text = open(self.stderr_file, 'rb').read() encoding = get_coding(stderr_text) stderr = to_text_string(stderr_text, encoding) return stderr
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
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 spyder/widgets/externalshell/start_ipython_kernel.py) self.fname = get_module_source_path( 'spyder.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 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.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)
def __init__(self, plugin, id_, history_filename, config_options, additional_options, interpreter_versions, connection_file=None, hostname=None, menu_actions=None, slave=False, external_kernel=False, given_name=None, options_button=None, show_elapsed_time=False, reset_warning=True): super(ClientWidget, self).__init__(plugin) SaveHistoryMixin.__init__(self, history_filename) # --- Init attrs self.id_ = id_ self.connection_file = connection_file self.hostname = hostname self.menu_actions = menu_actions self.slave = slave self.external_kernel = external_kernel self.given_name = given_name self.show_elapsed_time = show_elapsed_time self.reset_warning = reset_warning # --- Other attrs self.options_button = options_button self.stop_button = None self.reset_button = None self.stop_icon = ima.icon('stop') self.history = [] self.allow_rename = True self.stderr_dir = None # --- Widgets self.shellwidget = ShellWidget(config=config_options, ipyclient=self, additional_options=additional_options, interpreter_versions=interpreter_versions, external_kernel=external_kernel, local_kernel=True) self.infowidget = WebView(self) self.set_infowidget_font() self.loading_page = self._create_loading_page() self._show_loading_page() # Elapsed time self.time_label = None self.t0 = None self.timer = QTimer(self) # --- Layout vlayout = QVBoxLayout() toolbar_buttons = self.get_toolbar_buttons() hlayout = QHBoxLayout() hlayout.addWidget(self.create_time_label()) hlayout.addStretch(0) for button in toolbar_buttons: hlayout.addWidget(button) vlayout.addLayout(hlayout) vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addWidget(self.shellwidget) vlayout.addWidget(self.infowidget) self.setLayout(vlayout) # --- Exit function self.exit_callback = lambda: plugin.close_client(client=self) # --- Dialog manager self.dialog_manager = DialogManager() # Show timer self.update_time_label_visibility()
class ClientWidget(QWidget, SaveHistoryMixin, SpyderWidgetMixin): """ Client widget for the IPython Console This widget is necessary to handle the interaction between the plugin and each shell widget. """ sig_append_to_history_requested = Signal(str, str) sig_execution_state_changed = Signal() CONF_SECTION = 'ipython_console' SEPARATOR = '{0}## ---({1})---'.format(os.linesep*2, time.ctime()) INITHISTORY = ['# -*- coding: utf-8 -*-', '# *** Spyder Python Console History Log ***', ] def __init__(self, parent, id_, history_filename, config_options, additional_options, interpreter_versions, connection_file=None, hostname=None, context_menu_actions=(), menu_actions=None, is_external_kernel=False, is_spyder_kernel=True, given_name=None, give_focus=True, options_button=None, time_label=None, show_elapsed_time=False, reset_warning=True, ask_before_restart=True, ask_before_closing=False, css_path=None, handlers={}, stderr_obj=None, stdout_obj=None, fault_obj=None): super(ClientWidget, self).__init__(parent) SaveHistoryMixin.__init__(self, history_filename) # --- Init attrs self.container = parent self.id_ = id_ self.connection_file = connection_file self.hostname = hostname self.menu_actions = menu_actions self.is_external_kernel = is_external_kernel self.given_name = given_name self.show_elapsed_time = show_elapsed_time self.reset_warning = reset_warning self.ask_before_restart = ask_before_restart self.ask_before_closing = ask_before_closing # --- Other attrs self.context_menu_actions = context_menu_actions self.time_label = time_label self.options_button = options_button self.history = [] self.allow_rename = True self.is_error_shown = False self.error_text = None self.restart_thread = None self.give_focus = give_focus if css_path is None: self.css_path = CSS_PATH else: self.css_path = css_path # --- Widgets self.shellwidget = ShellWidget( config=config_options, ipyclient=self, additional_options=additional_options, interpreter_versions=interpreter_versions, is_external_kernel=is_external_kernel, is_spyder_kernel=is_spyder_kernel, handlers=handlers, local_kernel=True ) self.infowidget = self.container.infowidget self.blank_page = self._create_blank_page() self.loading_page = self._create_loading_page() # To keep a reference to the page to be displayed # in infowidget self.info_page = None self._before_prompt_is_ready() # Elapsed time self.t0 = time.monotonic() self.timer = QTimer(self) # --- Layout self.layout = QVBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.addWidget(self.shellwidget) if self.infowidget is not None: self.layout.addWidget(self.infowidget) self.setLayout(self.layout) # --- Exit function self.exit_callback = lambda: self.container.close_client(client=self) # --- Dialog manager self.dialog_manager = DialogManager() # --- Standard files handling self.stderr_obj = stderr_obj self.stdout_obj = stdout_obj self.fault_obj = fault_obj self.std_poll_timer = None if self.stderr_obj is not None or self.stdout_obj is not None: self.std_poll_timer = QTimer(self) self.std_poll_timer.timeout.connect(self.poll_std_file_change) self.std_poll_timer.setInterval(1000) self.std_poll_timer.start() self.shellwidget.executed.connect(self.poll_std_file_change) self.start_successful = False def __del__(self): """Close threads to avoid segfault.""" if (self.restart_thread is not None and self.restart_thread.isRunning()): self.restart_thread.quit() self.restart_thread.wait() # ----- Private methods --------------------------------------------------- def _before_prompt_is_ready(self, show_loading_page=True): """Configuration before kernel is connected.""" if show_loading_page: self._show_loading_page() self.shellwidget.sig_prompt_ready.connect( self._when_prompt_is_ready) # If remote execution, the loading page should be hidden as well self.shellwidget.sig_remote_execute.connect( self._when_prompt_is_ready) def _when_prompt_is_ready(self): """Configuration after the prompt is shown.""" self.start_successful = True # To hide the loading page self._hide_loading_page() # Show possible errors when setting Matplotlib backend self._show_mpl_backend_errors() # To show if special console is valid self._check_special_console_error() # Set the initial current working directory self._set_initial_cwd() self.shellwidget.sig_prompt_ready.disconnect( self._when_prompt_is_ready) self.shellwidget.sig_remote_execute.disconnect( self._when_prompt_is_ready) # It's necessary to do this at this point to avoid giving # focus to _control at startup. self._connect_control_signals() if self.give_focus: self.shellwidget._control.setFocus() def _create_loading_page(self): """Create html page to show while the kernel is starting""" loading_template = Template(LOADING) loading_img = get_image_path('loading_sprites') if os.name == 'nt': loading_img = loading_img.replace('\\', '/') message = _("Connecting to kernel...") page = loading_template.substitute(css_path=self.css_path, loading_img=loading_img, message=message) return page def _create_blank_page(self): """Create html page to show while the kernel is starting""" loading_template = Template(BLANK) page = loading_template.substitute(css_path=self.css_path) return page def _show_loading_page(self): """Show animation while the kernel is loading.""" if self.infowidget is not None: self.shellwidget.hide() self.infowidget.show() self.info_page = self.loading_page self.set_info_page() def _hide_loading_page(self): """Hide animation shown while the kernel is loading.""" if self.infowidget is not None: self.infowidget.hide() self.info_page = self.blank_page self.set_info_page() self.shellwidget.show() def _read_stderr(self): """Read the stderr file of the kernel.""" # We need to read stderr_file as bytes to be able to # detect its encoding with chardet f = open(self.stderr_file, 'rb') try: stderr_text = f.read() # This is needed to avoid showing an empty error message # when the kernel takes too much time to start. # See spyder-ide/spyder#8581. if not stderr_text: return '' # This is needed since the stderr file could be encoded # in something different to utf-8. # See spyder-ide/spyder#4191. encoding = get_coding(stderr_text) stderr_text = to_text_string(stderr_text, encoding) return stderr_text finally: f.close() def _show_mpl_backend_errors(self): """ Show possible errors when setting the selected Matplotlib backend. """ if self.shellwidget.is_spyder_kernel: self.shellwidget.call_kernel().show_mpl_backend_errors() def _check_special_console_error(self): """Check if the dependecies for special consoles are available.""" self.shellwidget.call_kernel( callback=self._show_special_console_error ).is_special_kernel_valid() def _show_special_console_error(self, missing_dependency): if missing_dependency is not None: error_message = _( "Your Python environment or installation doesn't have the " "<tt>{missing_dependency}</tt> module installed or it " "occurred a problem importing it. Due to that, it is not " "possible for Spyder to create this special console for " "you." ).format(missing_dependency=missing_dependency) self.show_kernel_error(error_message) def _abort_kernel_restart(self): """ Abort kernel restart if there are errors while starting it. We also ignore errors about comms, which are irrelevant. """ if self.start_successful: return False stderr = self.stderr_obj.get_contents() if not stderr: return False # There is an error. If it is benign, ignore. for line in stderr.splitlines(): if line and not self.is_benign_error(line): return True return False def _connect_control_signals(self): """Connect signals of control widgets.""" control = self.shellwidget._control page_control = self.shellwidget._page_control control.sig_focus_changed.connect( self.container.sig_focus_changed) page_control.sig_focus_changed.connect( self.container.sig_focus_changed) control.sig_visibility_changed.connect( self.container.refresh_container) page_control.sig_visibility_changed.connect( self.container.refresh_container) page_control.sig_show_find_widget_requested.connect( self.container.find_widget.show) def _set_initial_cwd(self): """Set initial cwd according to preferences.""" logger.debug("Setting initial working directory") cwd_path = get_home_dir() project_path = self.container.get_active_project_path() # This is for the first client if self.id_['int_id'] == '1': if self.get_conf( 'startup/use_project_or_home_directory', section='workingdir' ): cwd_path = get_home_dir() if project_path is not None: cwd_path = project_path elif self.get_conf( 'startup/use_fixed_directory', section='workingdir' ): cwd_path = self.get_conf( 'startup/fixed_directory', default=get_home_dir(), section='workingdir' ) else: # For new clients if self.get_conf( 'console/use_project_or_home_directory', section='workingdir' ): cwd_path = get_home_dir() if project_path is not None: cwd_path = project_path elif self.get_conf('console/use_cwd', section='workingdir'): cwd_path = self.container.get_working_directory() elif self.get_conf( 'console/use_fixed_directory', section='workingdir' ): cwd_path = self.get_conf( 'console/fixed_directory', default=get_home_dir(), section='workingdir' ) if osp.isdir(cwd_path): self.shellwidget.set_cwd(cwd_path) # ----- Public API -------------------------------------------------------- @property def kernel_id(self): """Get kernel id.""" if self.connection_file is not None: json_file = osp.basename(self.connection_file) return json_file.split('.json')[0] def remove_std_files(self, is_last_client=True): """Remove stderr_file associated with the client.""" try: self.shellwidget.executed.disconnect(self.poll_std_file_change) except TypeError: pass if self.std_poll_timer is not None: self.std_poll_timer.stop() if is_last_client: if self.stderr_obj is not None: self.stderr_obj.remove() if self.stdout_obj is not None: self.stdout_obj.remove() if self.fault_obj is not None: self.fault_obj.remove() @Slot() def poll_std_file_change(self): """Check if the stderr or stdout file just changed.""" self.shellwidget.call_kernel().flush_std() starting = self.shellwidget._starting if self.stderr_obj is not None: stderr = self.stderr_obj.poll_file_change() if stderr: if self.is_benign_error(stderr): return if self.shellwidget.isHidden(): # Avoid printing the same thing again if self.error_text != '<tt>%s</tt>' % stderr: full_stderr = self.stderr_obj.get_contents() self.show_kernel_error('<tt>%s</tt>' % full_stderr) if starting: self.shellwidget.banner = ( stderr + '\n' + self.shellwidget.banner) else: self.shellwidget._append_plain_text( '\n' + stderr, before_prompt=True) if self.stdout_obj is not None: stdout = self.stdout_obj.poll_file_change() if stdout: if starting: self.shellwidget.banner = ( stdout + '\n' + self.shellwidget.banner) else: self.shellwidget._append_plain_text( '\n' + stdout, before_prompt=True) def configure_shellwidget(self, give_focus=True): """Configure shellwidget after kernel is connected.""" self.give_focus = give_focus # Make sure the kernel sends the comm config over self.shellwidget.call_kernel()._send_comm_config() # Set exit callback self.shellwidget.set_exit_callback() # To save history self.shellwidget.executing.connect(self.add_to_history) # For Mayavi to run correctly self.shellwidget.executing.connect( self.shellwidget.set_backend_for_mayavi) # To update history after execution self.shellwidget.executed.connect(self.update_history) # To update the Variable Explorer after execution self.shellwidget.executed.connect( self.shellwidget.refresh_namespacebrowser) # To enable the stop button when executing a process self.shellwidget.executing.connect( self.sig_execution_state_changed) # To disable the stop button after execution stopped self.shellwidget.executed.connect( self.sig_execution_state_changed) # To show kernel restarted/died messages self.shellwidget.sig_kernel_restarted_message.connect( self.kernel_restarted_message) self.shellwidget.sig_kernel_restarted.connect( self._finalise_restart) # To correctly change Matplotlib backend interactively self.shellwidget.executing.connect( self.shellwidget.change_mpl_backend) # To show env and sys.path contents self.shellwidget.sig_show_syspath.connect(self.show_syspath) self.shellwidget.sig_show_env.connect(self.show_env) # To sync with working directory toolbar self.shellwidget.executed.connect(self.shellwidget.update_cwd) # To apply style self.set_color_scheme(self.shellwidget.syntax_style, reset=False) if self.fault_obj is not None: # To display faulthandler self.shellwidget.call_kernel().enable_faulthandler( self.fault_obj.filename) def add_to_history(self, command): """Add command to history""" if self.shellwidget.is_debugging(): return return super(ClientWidget, self).add_to_history(command) def is_client_executing(self): return (self.shellwidget._executing or self.shellwidget.is_waiting_pdb_input()) @Slot() def stop_button_click_handler(self): """Method to handle what to do when the stop button is pressed""" # Interrupt computations or stop debugging if not self.shellwidget.is_waiting_pdb_input(): self.interrupt_kernel() else: self.shellwidget.pdb_execute_command('exit') def show_kernel_error(self, error): """Show kernel initialization errors in infowidget.""" self.error_text = error if self.is_benign_error(error): return InstallerIPythonKernelError(error) # Replace end of line chars with <br> eol = sourcecode.get_eol_chars(error) if eol: error = error.replace(eol, '<br>') # Don't break lines in hyphens # From https://stackoverflow.com/q/7691569/438386 error = error.replace('-', '‑') # Create error page message = _("An error ocurred while starting the kernel") kernel_error_template = Template(KERNEL_ERROR) self.info_page = kernel_error_template.substitute( css_path=self.css_path, message=message, error=error) # Show error if self.infowidget is not None: self.set_info_page() self.shellwidget.hide() self.infowidget.show() # Tell the client we're in error mode self.is_error_shown = True # Stop shellwidget self.shellwidget.shutdown() self.remove_std_files(is_last_client=False) def is_benign_error(self, error): """Decide if an error is benign in order to filter it.""" benign_errors = [ # Error when switching from the Qt5 backend to the Tk one. # See spyder-ide/spyder#17488 "KeyboardInterrupt caught in kernel", "QSocketNotifier: Multiple socket notifiers for same socket", # Error when switching from the Tk backend to the Qt5 one. # See spyder-ide/spyder#17488 "Tcl_AsyncDelete async handler deleted by the wrong thread", "error in background error handler:", " while executing", '"::tcl::Bgerror', # Avoid showing this warning because it was up to the user to # disable secure writes. "WARNING: Insecure writes have been enabled via environment", # Old error "No such comm" ] return any([err in error for err in benign_errors]) def get_name(self): """Return client name""" if self.given_name is None: # Name according to host if self.hostname is None: name = _("Console") else: name = self.hostname # Adding id to name client_id = self.id_['int_id'] + u'/' + self.id_['str_id'] name = name + u' ' + client_id elif self.given_name in ["Pylab", "SymPy", "Cython"]: client_id = self.id_['int_id'] + u'/' + self.id_['str_id'] name = self.given_name + u' ' + client_id else: name = self.given_name + u'/' + self.id_['str_id'] return name def get_control(self): """Return the text widget (or similar) to give focus to""" # page_control is the widget used for paging page_control = self.shellwidget._page_control if page_control and page_control.isVisible(): return page_control else: return self.shellwidget._control def get_kernel(self): """Get kernel associated with this client""" return self.shellwidget.kernel_manager def add_actions_to_context_menu(self, menu): """Add actions to IPython widget context menu""" add_actions(menu, self.context_menu_actions) return menu def set_font(self, font): """Set IPython widget's font""" self.shellwidget._control.setFont(font) self.shellwidget.font = font def set_color_scheme(self, color_scheme, reset=True): """Set IPython color scheme.""" # Needed to handle not initialized kernel_client # See spyder-ide/spyder#6996. try: self.shellwidget.set_color_scheme(color_scheme, reset) except AttributeError: pass def shutdown(self, is_last_client): """Shutdown connection and kernel if needed.""" self.dialog_manager.close_all() if (self.restart_thread is not None and self.restart_thread.isRunning()): self.restart_thread.finished.disconnect() self.restart_thread.quit() self.restart_thread.wait() shutdown_kernel = ( is_last_client and not self.is_external_kernel and not self.is_error_shown) self.shellwidget.shutdown(shutdown_kernel) self.remove_std_files(shutdown_kernel) def interrupt_kernel(self): """Interrupt the associanted Spyder kernel if it's running""" # Needed to prevent a crash when a kernel is not running. # See spyder-ide/spyder#6299. try: self.shellwidget.request_interrupt_kernel() except RuntimeError: pass @Slot() def restart_kernel(self): """ Restart the associated kernel. Took this code from the qtconsole project Licensed under the BSD license """ sw = self.shellwidget if not running_under_pytest() and self.ask_before_restart: message = _('Are you sure you want to restart the kernel?') buttons = QMessageBox.Yes | QMessageBox.No result = QMessageBox.question(self, _('Restart kernel?'), message, buttons) else: result = None if (result == QMessageBox.Yes or running_under_pytest() or not self.ask_before_restart): if sw.kernel_manager: if self.infowidget is not None: if self.infowidget.isVisible(): self.infowidget.hide() if self._abort_kernel_restart(): sw.spyder_kernel_comm.close() return self._show_loading_page() # Close comm sw.spyder_kernel_comm.close() # Stop autorestart mechanism sw.kernel_manager.stop_restarter() sw.kernel_manager.autorestart = False # Reconfigure client before the new kernel is connected again. self._before_prompt_is_ready(show_loading_page=False) # Create and run restarting thread if (self.restart_thread is not None and self.restart_thread.isRunning()): self.restart_thread.finished.disconnect() self.restart_thread.quit() self.restart_thread.wait() self.restart_thread = QThread(None) self.restart_thread.run = self._restart_thread_main self.restart_thread.error = None self.restart_thread.finished.connect( lambda: self._finalise_restart(True)) self.restart_thread.start() else: sw._append_plain_text( _('Cannot restart a kernel not started by Spyder\n'), before_prompt=True ) self._hide_loading_page() def _restart_thread_main(self): """Restart the kernel in a thread.""" try: self.shellwidget.kernel_manager.restart_kernel( stderr=self.stderr_obj.handle, stdout=self.stdout_obj.handle) except RuntimeError as e: self.restart_thread.error = e def _finalise_restart(self, reset=False): """Finishes the restarting of the kernel.""" sw = self.shellwidget if self._abort_kernel_restart(): sw.spyder_kernel_comm.close() return if self.restart_thread and self.restart_thread.error is not None: sw._append_plain_text( _('Error restarting kernel: %s\n') % self.restart_thread.error, before_prompt=True ) else: if self.fault_obj is not None: fault = self.fault_obj.get_contents() if fault: fault = self.filter_fault(fault) self.shellwidget._append_plain_text( '\n' + fault, before_prompt=True) # Reset Pdb state and reopen comm sw._pdb_in_loop = False sw.spyder_kernel_comm.remove() try: sw.spyder_kernel_comm.open_comm(sw.kernel_client) except AttributeError: # An error occurred while opening our comm channel. # Aborting! return # Start autorestart mechanism sw.kernel_manager.autorestart = True sw.kernel_manager.start_restarter() # For spyder-ide/spyder#6235, IPython was changing the # setting of %colors on windows by assuming it was using a # dark background. This corrects it based on the scheme. self.set_color_scheme(sw.syntax_style, reset=reset) sw._append_html(_("<br>Restarting kernel...<br>"), before_prompt=True) sw.insert_horizontal_ruler() if self.fault_obj is not None: self.shellwidget.call_kernel().enable_faulthandler( self.fault_obj.filename) self._hide_loading_page() self.restart_thread = None self.sig_execution_state_changed.emit() def filter_fault(self, fault): """Get a fault from a previous session.""" thread_regex = ( r"(Current thread|Thread) " r"(0x[\da-f]+) \(most recent call first\):" r"(?:.|\r\n|\r|\n)+?(?=Current thread|Thread|\Z)") # Keep line for future improvments # files_regex = r"File \"([^\"]+)\", line (\d+) in (\S+)" main_re = "Main thread id:(?:\r\n|\r|\n)(0x[0-9a-f]+)" main_id = 0 for match in re.finditer(main_re, fault): main_id = int(match.group(1), base=16) system_re = ("System threads ids:" "(?:\r\n|\r|\n)(0x[0-9a-f]+(?: 0x[0-9a-f]+)+)") ignore_ids = [] start_idx = 0 for match in re.finditer(system_re, fault): ignore_ids = [int(i, base=16) for i in match.group(1).split()] start_idx = match.span()[1] text = "" for idx, match in enumerate(re.finditer(thread_regex, fault)): if idx == 0: text += fault[start_idx:match.span()[0]] thread_id = int(match.group(2), base=16) if thread_id != main_id: if thread_id in ignore_ids: continue if "wurlitzer.py" in match.group(0): # Wurlitzer threads are launched later continue text += "\n" + match.group(0) + "\n" else: try: pattern = (r".*(?:/IPython/core/interactiveshell\.py|" r"\\IPython\\core\\interactiveshell\.py).*") match_internal = next(re.finditer(pattern, match.group(0))) end_idx = match_internal.span()[0] except StopIteration: end_idx = None text += "\nMain thread:\n" + match.group(0)[:end_idx] + "\n" return text @Slot(str) def kernel_restarted_message(self, msg): """Show kernel restarted/died messages.""" if self.stderr_obj is not None: # If there are kernel creation errors, jupyter_client will # try to restart the kernel and qtconsole prints a # message about it. # So we read the kernel's stderr_file and display its # contents in the client instead of the usual message shown # by qtconsole. self.poll_std_file_change() else: self.shellwidget._append_html("<br>%s<hr><br>" % msg, before_prompt=False) @Slot() def enter_array_inline(self): """Enter and show the array builder on inline mode.""" self.shellwidget._control.enter_array_inline() @Slot() def enter_array_table(self): """Enter and show the array builder on table.""" self.shellwidget._control.enter_array_table() @Slot() def inspect_object(self): """Show how to inspect an object with our Help plugin""" self.shellwidget._control.inspect_current_object() @Slot() def clear_line(self): """Clear a console line""" self.shellwidget._keyboard_quit() @Slot() def clear_console(self): """Clear the whole console""" self.shellwidget.clear_console() @Slot() def reset_namespace(self): """Resets the namespace by removing all names defined by the user""" self.shellwidget.reset_namespace(warning=self.reset_warning, message=True) def update_history(self): self.history = self.shellwidget._history @Slot(object) def show_syspath(self, syspath): """Show sys.path contents.""" if syspath is not None: editor = CollectionsEditor(self) editor.setup(syspath, title="sys.path contents", readonly=True, icon=ima.icon('syspath')) self.dialog_manager.show(editor) else: return @Slot(object) def show_env(self, env): """Show environment variables.""" self.dialog_manager.show(RemoteEnvDialog(env, parent=self)) def show_time(self, end=False): """Text to show in time_label.""" if self.time_label is None: return elapsed_time = time.monotonic() - self.t0 # System time changed to past date, so reset start. if elapsed_time < 0: self.t0 = time.monotonic() elapsed_time = 0 if elapsed_time > 24 * 3600: # More than a day...! fmt = "%d %H:%M:%S" else: fmt = "%H:%M:%S" if end: color = QStylePalette.COLOR_TEXT_3 else: color = QStylePalette.COLOR_ACCENT_4 text = "<span style=\'color: %s\'><b>%s" \ "</b></span>" % (color, time.strftime(fmt, time.gmtime(elapsed_time))) if self.show_elapsed_time: self.time_label.setText(text) else: self.time_label.setText("") @Slot(bool) def set_show_elapsed_time(self, state): """Slot to show/hide elapsed time label.""" self.show_elapsed_time = state def set_info_page(self): """Set current info_page.""" if self.infowidget is not None and self.info_page is not None: self.infowidget.setHtml( self.info_page, QUrl.fromLocalFile(self.css_path) )
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)
def __init__(self, parent, id_, history_filename, config_options, additional_options, interpreter_versions, connection_file=None, hostname=None, context_menu_actions=(), menu_actions=None, is_external_kernel=False, is_spyder_kernel=True, given_name=None, give_focus=True, options_button=None, time_label=None, show_elapsed_time=False, reset_warning=True, ask_before_restart=True, ask_before_closing=False, css_path=None, handlers={}, stderr_obj=None, stdout_obj=None, fault_obj=None): super(ClientWidget, self).__init__(parent) SaveHistoryMixin.__init__(self, history_filename) # --- Init attrs self.container = parent self.id_ = id_ self.connection_file = connection_file self.hostname = hostname self.menu_actions = menu_actions self.is_external_kernel = is_external_kernel self.given_name = given_name self.show_elapsed_time = show_elapsed_time self.reset_warning = reset_warning self.ask_before_restart = ask_before_restart self.ask_before_closing = ask_before_closing # --- Other attrs self.context_menu_actions = context_menu_actions self.time_label = time_label self.options_button = options_button self.history = [] self.allow_rename = True self.is_error_shown = False self.error_text = None self.restart_thread = None self.give_focus = give_focus if css_path is None: self.css_path = CSS_PATH else: self.css_path = css_path # --- Widgets self.shellwidget = ShellWidget( config=config_options, ipyclient=self, additional_options=additional_options, interpreter_versions=interpreter_versions, is_external_kernel=is_external_kernel, is_spyder_kernel=is_spyder_kernel, handlers=handlers, local_kernel=True ) self.infowidget = self.container.infowidget self.blank_page = self._create_blank_page() self.loading_page = self._create_loading_page() # To keep a reference to the page to be displayed # in infowidget self.info_page = None self._before_prompt_is_ready() # Elapsed time self.t0 = time.monotonic() self.timer = QTimer(self) # --- Layout self.layout = QVBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.addWidget(self.shellwidget) if self.infowidget is not None: self.layout.addWidget(self.infowidget) self.setLayout(self.layout) # --- Exit function self.exit_callback = lambda: self.container.close_client(client=self) # --- Dialog manager self.dialog_manager = DialogManager() # --- Standard files handling self.stderr_obj = stderr_obj self.stdout_obj = stdout_obj self.fault_obj = fault_obj self.std_poll_timer = None if self.stderr_obj is not None or self.stdout_obj is not None: self.std_poll_timer = QTimer(self) self.std_poll_timer.timeout.connect(self.poll_std_file_change) self.std_poll_timer.setInterval(1000) self.std_poll_timer.start() self.shellwidget.executed.connect(self.poll_std_file_change) self.start_successful = False
def __init__(self, plugin, id_, history_filename, config_options, additional_options, interpreter_versions, connection_file=None, hostname=None, menu_actions=None, slave=False, external_kernel=False, given_name=None, options_button=None, show_elapsed_time=False, reset_warning=True, ask_before_restart=True, css_path=None): super(ClientWidget, self).__init__(plugin) SaveHistoryMixin.__init__(self, history_filename) # --- Init attrs self.id_ = id_ self.connection_file = connection_file self.hostname = hostname self.menu_actions = menu_actions self.slave = slave self.external_kernel = external_kernel self.given_name = given_name self.show_elapsed_time = show_elapsed_time self.reset_warning = reset_warning self.ask_before_restart = ask_before_restart # --- Other attrs self.options_button = options_button self.stop_button = None self.reset_button = None self.stop_icon = ima.icon('stop') self.history = [] self.allow_rename = True self.stderr_dir = None self.is_error_shown = False if css_path is None: self.css_path = CSS_PATH else: self.css_path = css_path # --- Widgets self.shellwidget = ShellWidget( config=config_options, ipyclient=self, additional_options=additional_options, interpreter_versions=interpreter_versions, external_kernel=external_kernel, local_kernel=True) self.infowidget = WebView(self) self.set_infowidget_font() self.loading_page = self._create_loading_page() self._show_loading_page() # Elapsed time self.time_label = None self.t0 = time.monotonic() self.timer = QTimer(self) self.show_time_action = create_action( self, _("Show elapsed time"), toggled=self.set_elapsed_time_visible) # --- Layout vlayout = QVBoxLayout() toolbar_buttons = self.get_toolbar_buttons() hlayout = QHBoxLayout() hlayout.addWidget(self.create_time_label()) hlayout.addStretch(0) for button in toolbar_buttons: hlayout.addWidget(button) vlayout.addLayout(hlayout) vlayout.setContentsMargins(0, 0, 0, 0) vlayout.addWidget(self.shellwidget) vlayout.addWidget(self.infowidget) self.setLayout(vlayout) # --- Exit function self.exit_callback = lambda: plugin.close_client(client=self) # --- Dialog manager self.dialog_manager = DialogManager() # Show timer self.update_time_label_visibility()
def __init__(self, plugin, id_, history_filename, config_options, additional_options, interpreter_versions, connection_file=None, hostname=None, menu_actions=None, slave=False, external_kernel=False, given_name="MxConsole", show_elapsed_time=False, reset_warning=True, **kwargs): super(ClientWidget, self).__init__(plugin) SaveHistoryMixin.__init__(self, history_filename) # --- Init attrs self.plugin = plugin self.id_ = id_ self.connection_file = connection_file self.hostname = hostname self.menu_actions = menu_actions self.slave = slave self.external_kernel = external_kernel self.given_name = given_name self.show_elapsed_time = show_elapsed_time self.reset_warning = reset_warning if "ask_before_restart" in kwargs: self.ask_before_restart = kwargs["ask_before_restart"] # --- Other attrs if spyder.version_info > (4, ) and "options_button" in kwargs: self.options_button = kwargs["options_button"] else: self.options_button = None self.stop_button = None self.reset_button = None self.stop_icon = ima.icon('stop') self.history = [] self.allow_rename = True self.stderr_dir = None self.is_error_shown = False self.restart_thread = None if "css_path" in kwargs: if kwargs["css_path"] is None: self.css_path = CSS_PATH else: self.css_path = kwargs["css_path"] # --- Widgets self.shellwidget = MxShellWidget( config=config_options, ipyclient=self, additional_options=additional_options, interpreter_versions=interpreter_versions, external_kernel=external_kernel, local_kernel=True) if spyder.version_info < (4, ): self.infowidget = WebView(self) self.set_infowidget_font() else: self.infowidget = plugin.infowidget self.blank_page = self._create_blank_page() self.loading_page = self._create_loading_page() # To keep a reference to the page to be displayed # in infowidget self.info_page = None if spyder.version_info < (4, 1): self._show_loading_page() else: self._before_prompt_is_ready() # Elapsed time self.time_label = None if spyder.version_info < (4, ): self.t0 = None else: self.t0 = time.monotonic() self.timer = QTimer(self) if spyder.version_info > (4, ): self.show_time_action = create_action( self, _("Show elapsed time"), toggled=self.set_elapsed_time_visible) # --- Layout self.layout = QVBoxLayout() toolbar_buttons = self.get_toolbar_buttons() hlayout = QHBoxLayout() hlayout.addWidget(self.create_time_label()) hlayout.addStretch(0) for button in toolbar_buttons: hlayout.addWidget(button) self.layout.addLayout(hlayout) self.layout.setContentsMargins(0, 0, 0, 0) self.layout.addWidget(self.shellwidget) self.layout.addWidget(self.infowidget) self.setLayout(self.layout) # --- Exit function self.exit_callback = lambda: plugin.close_client(client=self) # --- Dialog manager self.dialog_manager = DialogManager() # Show timer self.update_time_label_visibility()
class Console(SpyderPluginWidget): """ Console widget """ CONF_SECTION = 'internal_console' focus_changed = Signal() redirect_stdio = Signal(bool) edit_goto = Signal(str, int, str) def __init__(self, parent=None, namespace=None, commands=[], message=None, exitfunc=None, profile=False, multithreaded=False): SpyderPluginWidget.__init__(self, parent) debug_print(" ..internal console: initializing") self.dialog_manager = DialogManager() # Shell light_background = self.get_option('light_background') self.shell = InternalShell(parent, namespace, commands, message, self.get_option('max_line_count'), self.get_plugin_font(), exitfunc, profile, multithreaded, light_background=light_background) self.shell.status.connect(lambda msg: self.show_message.emit(msg, 0)) self.shell.go_to_error.connect(self.go_to_error) self.shell.focus_changed.connect(lambda: self.focus_changed.emit()) # Redirecting some signals: self.shell.redirect_stdio.connect( lambda state: self.redirect_stdio.emit(state)) # Initialize plugin self.initialize_plugin() # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.set_editor(self.shell) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) # Main layout btn_layout = QHBoxLayout() btn_layout.setAlignment(Qt.AlignLeft) btn_layout.addStretch() btn_layout.addWidget(self.options_button, Qt.AlignRight) layout = create_plugin_layout(btn_layout) layout.addWidget(self.shell) layout.addWidget(self.find_widget) self.setLayout(layout) # Parameters self.shell.toggle_wrap_mode(self.get_option('wrap')) # Accepting drops self.setAcceptDrops(True) # Traceback MessageBox self.msgbox_traceback = None self.error_traceback = "" #------ Private API -------------------------------------------------------- def set_historylog(self, historylog): """Bind historylog instance to this console Not used anymore since v2.0""" historylog.add_history(self.shell.history_filename) self.shell.append_to_history.connect(historylog.append_to_history) def set_help(self, help_plugin): """Bind help instance to this console""" self.shell.help = help_plugin #------ SpyderPluginWidget API --------------------------------------------- def get_plugin_title(self): """Return widget title""" return _('Internal console') def get_focus_widget(self): """ Return the widget to give focus to when this plugin's dockwidget is raised on top-level """ return self.shell def update_font(self): """Update font from Preferences""" font = self.get_plugin_font() self.shell.set_font(font) def closing_plugin(self, cancelable=False): """Perform actions before parent main window is closed""" self.dialog_manager.close_all() self.shell.exit_interpreter() return True def refresh_plugin(self): pass def get_plugin_actions(self): """Return a list of actions related to plugin""" quit_action = create_action(self, _("&Quit"), icon=ima.icon('exit'), tip=_("Quit"), triggered=self.quit) self.register_shortcut(quit_action, "_", "Quit", "Ctrl+Q") run_action = create_action(self, _("&Run..."), None, ima.icon('run_small'), _("Run a Python script"), triggered=self.run_script) environ_action = create_action( self, _("Environment variables..."), icon=ima.icon('environ'), tip=_("Show and edit environment variables" " (for current session)"), triggered=self.show_env) syspath_action = create_action(self, _("Show sys.path contents..."), icon=ima.icon('syspath'), tip=_("Show (read-only) sys.path"), triggered=self.show_syspath) buffer_action = create_action(self, _("Buffer..."), None, tip=_("Set maximum line count"), triggered=self.change_max_line_count) exteditor_action = create_action( self, _("External editor path..."), None, None, _("Set external editor executable path"), triggered=self.change_exteditor) wrap_action = create_action(self, _("Wrap lines"), toggled=self.toggle_wrap_mode) wrap_action.setChecked(self.get_option('wrap')) calltips_action = create_action(self, _("Display balloon tips"), toggled=self.toggle_calltips) calltips_action.setChecked(self.get_option('calltips')) codecompletion_action = create_action( self, _("Automatic code completion"), toggled=self.toggle_codecompletion) codecompletion_action.setChecked( self.get_option('codecompletion/auto')) codecompenter_action = create_action( self, _("Enter key selects completion"), toggled=self.toggle_codecompletion_enter) codecompenter_action.setChecked( self.get_option('codecompletion/enter_key')) option_menu = QMenu(_('Internal console settings'), self) option_menu.setIcon(ima.icon('tooloptions')) add_actions( option_menu, (buffer_action, wrap_action, calltips_action, codecompletion_action, codecompenter_action, exteditor_action)) plugin_actions = [ None, run_action, environ_action, syspath_action, option_menu, MENU_SEPARATOR, quit_action, self.undock_action ] return plugin_actions def register_plugin(self): """Register plugin in Spyder's main window""" self.focus_changed.connect(self.main.plugin_focus_changed) self.main.add_dockwidget(self) # Connecting the following signal once the dockwidget has been created: self.shell.exception_occurred.connect(self.exception_occurred) def exception_occurred(self, text, is_traceback): """Exception ocurred in the internal console. Show a QMessageBox or the internal console to warn the user""" # Skip errors without traceback if not is_traceback and self.msgbox_traceback is None: return if CONF.get('main', 'show_internal_console_if_traceback', False): self.dockwidget.show() self.dockwidget.raise_() else: if self.msgbox_traceback is None: self.msgbox_traceback = QMessageBox( QMessageBox.Critical, _('Error'), _("<b>Spyder has encountered a problem.</b><br>" "Sorry for the inconvenience." "<br><br>" "You can automatically submit this error to our Github " "issues tracker.<br><br>" "<i>Note:</i> You need a Github account for that."), QMessageBox.Ok, parent=self) self.submit_btn = self.msgbox_traceback.addButton( _('Submit to Github'), QMessageBox.YesRole) self.submit_btn.pressed.connect(self.press_submit_btn) self.msgbox_traceback.setWindowModality(Qt.NonModal) self.error_traceback = "" self.msgbox_traceback.show() self.msgbox_traceback.finished.connect(self.close_msg) self.msgbox_traceback.setDetailedText(' ') # open show details (iterate over all buttons and click it) for button in self.msgbox_traceback.buttons(): if (self.msgbox_traceback.buttonRole(button) == QMessageBox.ActionRole): button.click() break self.error_traceback += text self.msgbox_traceback.setDetailedText(self.error_traceback) def close_msg(self): self.msgbox_traceback = None def press_submit_btn(self): self.main.report_issue(self.error_traceback) self.msgbox_traceback = None #------ Public API --------------------------------------------------------- @Slot() def quit(self): """Quit mainwindow""" self.main.close() @Slot() def show_env(self): """Show environment variables""" self.dialog_manager.show(EnvDialog()) @Slot() def show_syspath(self): """Show sys.path""" editor = CollectionsEditor() editor.setup(sys.path, title="sys.path", readonly=True, width=600, icon=ima.icon('syspath')) self.dialog_manager.show(editor) @Slot() def run_script(self, filename=None, silent=False, set_focus=False, args=None): """Run a Python script""" if filename is None: self.shell.interpreter.restore_stds() filename, _selfilter = getopenfilename( self, _("Run Python script"), getcwd_or_home(), _("Python scripts") + " (*.py ; *.pyw ; *.ipy)") self.shell.interpreter.redirect_stds() if filename: os.chdir(osp.dirname(filename)) filename = osp.basename(filename) else: return debug_print(args) filename = osp.abspath(filename) rbs = remove_backslashes command = "runfile('%s', args='%s')" % (rbs(filename), rbs(args)) if set_focus: self.shell.setFocus() if self.dockwidget and not self.ismaximized: self.dockwidget.setVisible(True) self.dockwidget.raise_() self.shell.write(command + '\n') self.shell.run_command(command) def go_to_error(self, text): """Go to error if relevant""" match = get_error_match(to_text_string(text)) if match: fname, lnb = match.groups() self.edit_script(fname, int(lnb)) def edit_script(self, filename=None, goto=-1): """Edit script""" # Called from InternalShell if not hasattr(self, 'main') \ or not hasattr(self.main, 'editor'): self.shell.external_editor(filename, goto) return if filename is not None: self.edit_goto.emit(osp.abspath(filename), goto, '') def execute_lines(self, lines): """Execute lines and give focus to shell""" self.shell.execute_lines(to_text_string(lines)) self.shell.setFocus() @Slot() def change_max_line_count(self): "Change maximum line count" "" mlc, valid = QInputDialog.getInt(self, _('Buffer'), _('Maximum line count'), self.get_option('max_line_count'), 0, 1000000) if valid: self.shell.setMaximumBlockCount(mlc) self.set_option('max_line_count', mlc) @Slot() def change_exteditor(self): """Change external editor path""" path, valid = QInputDialog.getText( self, _('External editor'), _('External editor executable path:'), QLineEdit.Normal, self.get_option('external_editor/path')) if valid: self.set_option('external_editor/path', to_text_string(path)) @Slot(bool) def toggle_wrap_mode(self, checked): """Toggle wrap mode""" self.shell.toggle_wrap_mode(checked) self.set_option('wrap', checked) @Slot(bool) def toggle_calltips(self, checked): """Toggle calltips""" self.shell.set_calltips(checked) self.set_option('calltips', checked) @Slot(bool) def toggle_codecompletion(self, checked): """Toggle automatic code completion""" self.shell.set_codecompletion_auto(checked) self.set_option('codecompletion/auto', checked) @Slot(bool) def toggle_codecompletion_enter(self, checked): """Toggle Enter key for code completion""" self.shell.set_codecompletion_enter(checked) self.set_option('codecompletion/enter_key', checked) #----Drag and drop def dragEnterEvent(self, event): """Reimplement Qt method Inform Qt about the types of data that the widget accepts""" source = event.mimeData() if source.hasUrls(): if mimedata2url(source): event.acceptProposedAction() else: event.ignore() elif source.hasText(): event.acceptProposedAction() def dropEvent(self, event): """Reimplement Qt method Unpack dropped data and handle it""" source = event.mimeData() if source.hasUrls(): pathlist = mimedata2url(source) self.shell.drop_pathlist(pathlist) elif source.hasText(): lines = to_text_string(source.text()) self.shell.set_cursor_position('eof') self.shell.execute_lines(lines) event.acceptProposedAction()
class ClientWidget(QWidget, SaveHistoryMixin, SpyderConfigurationAccessor): """ Client widget for the IPython Console This widget is necessary to handle the interaction between the plugin and each shell widget. """ sig_append_to_history_requested = Signal(str, str) CONF_SECTION = 'ipython_console' SEPARATOR = '{0}## ---({1})---'.format(os.linesep * 2, time.ctime()) INITHISTORY = [ '# -*- coding: utf-8 -*-', '# *** Spyder Python Console History Log ***', ] def __init__(self, plugin, id_, history_filename, config_options, additional_options, interpreter_versions, connection_file=None, hostname=None, menu_actions=None, slave=False, external_kernel=False, given_name=None, options_button=None, show_elapsed_time=False, reset_warning=True, ask_before_restart=True, ask_before_closing=False, css_path=None): super(ClientWidget, self).__init__(plugin) SaveHistoryMixin.__init__(self, history_filename) # --- Init attrs self.plugin = plugin self.id_ = id_ self.connection_file = connection_file self.hostname = hostname self.menu_actions = menu_actions self.slave = slave self.external_kernel = external_kernel self.given_name = given_name self.show_elapsed_time = show_elapsed_time self.reset_warning = reset_warning self.ask_before_restart = ask_before_restart self.ask_before_closing = ask_before_closing # --- Other attrs self.options_button = options_button self.stop_button = None self.reset_button = None self.stop_icon = ima.icon('stop') self.history = [] self.allow_rename = True self.stderr_dir = None self.is_error_shown = False self.restart_thread = None self.give_focus = True if css_path is None: self.css_path = CSS_PATH else: self.css_path = css_path # --- Widgets self.shellwidget = ShellWidget( config=config_options, ipyclient=self, additional_options=additional_options, interpreter_versions=interpreter_versions, external_kernel=external_kernel, local_kernel=True) self.infowidget = plugin.infowidget self.blank_page = self._create_blank_page() self.loading_page = self._create_loading_page() # To keep a reference to the page to be displayed # in infowidget self.info_page = None self._before_prompt_is_ready() # Elapsed time self.time_label = None self.t0 = time.monotonic() self.timer = QTimer(self) self.show_time_action = create_action( self, _("Show elapsed time"), toggled=self.set_elapsed_time_visible) # --- Layout self.layout = QVBoxLayout() toolbar_buttons = self.get_toolbar_buttons() hlayout = QHBoxLayout() hlayout.addWidget(self.create_time_label()) hlayout.addStretch(0) for button in toolbar_buttons: hlayout.addWidget(button) self.layout.addLayout(hlayout) self.layout.setContentsMargins(0, 0, 0, 0) self.layout.addWidget(self.shellwidget) self.layout.addWidget(self.infowidget) self.setLayout(self.layout) # --- Exit function self.exit_callback = lambda: plugin.close_client(client=self) # --- Dialog manager self.dialog_manager = DialogManager() # Show timer self.update_time_label_visibility() # Poll for stderr changes self.stderr_mtime = 0 self.stderr_timer = QTimer(self) self.stderr_timer.timeout.connect(self.poll_stderr_file_change) self.stderr_timer.setInterval(1000) self.stderr_timer.start() def __del__(self): """Close threads to avoid segfault""" if (self.restart_thread is not None and self.restart_thread.isRunning()): self.restart_thread.wait() #------ Public API -------------------------------------------------------- @property def kernel_id(self): """Get kernel id""" if self.connection_file is not None: json_file = osp.basename(self.connection_file) return json_file.split('.json')[0] @property def stderr_file(self): """Filename to save kernel stderr output.""" stderr_file = None if self.connection_file is not None: stderr_file = self.kernel_id + '.stderr' if self.stderr_dir is not None: stderr_file = osp.join(self.stderr_dir, stderr_file) else: try: stderr_file = osp.join(get_temp_dir(), stderr_file) except (IOError, OSError): stderr_file = None return stderr_file @property def stderr_handle(self): """Get handle to stderr_file.""" if self.stderr_file is not None: # Needed to prevent any error that could appear. # See spyder-ide/spyder#6267. try: handle = codecs.open(self.stderr_file, 'w', encoding='utf-8') except Exception: handle = None else: handle = None return handle def remove_stderr_file(self): """Remove stderr_file associated with the client.""" try: # Defer closing the stderr_handle until the client # is closed because jupyter_client needs it open # while it tries to restart the kernel self.stderr_handle.close() os.remove(self.stderr_file) except Exception: pass def get_stderr_contents(self): """Get the contents of the stderr kernel file.""" try: stderr = self._read_stderr() except Exception: stderr = None return stderr @Slot() def poll_stderr_file_change(self): """Check if the stderr file just changed""" try: mtime = os.stat(self.stderr_file).st_mtime except Exception: return if mtime == self.stderr_mtime: return self.stderr_mtime = mtime stderr = self.get_stderr_contents() if stderr: self.shellwidget._append_plain_text('\n' + stderr, before_prompt=True) def configure_shellwidget(self, give_focus=True): """Configure shellwidget after kernel is connected.""" self.give_focus = give_focus # Make sure the kernel sends the comm config over self.shellwidget.call_kernel()._send_comm_config() # Set exit callback self.shellwidget.set_exit_callback() # To save history self.shellwidget.executing.connect(self.add_to_history) # For Mayavi to run correctly self.shellwidget.executing.connect( self.shellwidget.set_backend_for_mayavi) # To update history after execution self.shellwidget.executed.connect(self.update_history) # To update the Variable Explorer after execution self.shellwidget.executed.connect( self.shellwidget.refresh_namespacebrowser) # To enable the stop button when executing a process self.shellwidget.executing.connect(self.enable_stop_button) # To disable the stop button after execution stopped self.shellwidget.executed.connect(self.disable_stop_button) # To show kernel restarted/died messages self.shellwidget.sig_kernel_restarted_message.connect( self.kernel_restarted_message) self.shellwidget.sig_kernel_restarted.connect(self._finalise_restart) # To correctly change Matplotlib backend interactively self.shellwidget.executing.connect(self.shellwidget.change_mpl_backend) # To show env and sys.path contents self.shellwidget.sig_show_syspath.connect(self.show_syspath) self.shellwidget.sig_show_env.connect(self.show_env) # To sync with working directory toolbar self.shellwidget.executed.connect(self.shellwidget.update_cwd) # To apply style self.set_color_scheme(self.shellwidget.syntax_style, reset=False) def add_to_history(self, command): """Add command to history""" if self.shellwidget.is_debugging(): return return super(ClientWidget, self).add_to_history(command) def _before_prompt_is_ready(self): """Configure shellwidget before kernel is connected.""" self._show_loading_page() self.shellwidget.sig_prompt_ready.connect(self._when_prompt_is_ready) # If remote execution, the loading page should be hidden as well self.shellwidget.sig_remote_execute.connect(self._when_prompt_is_ready) def _when_prompt_is_ready(self): """Configuration after the prompt is shown.""" # To hide the loading page self._hide_loading_page() # Show possible errors when setting Matplotlib backend self._show_mpl_backend_errors() # To show if special console is valid self._check_special_console_error() self.shellwidget.sig_prompt_ready.disconnect( self._when_prompt_is_ready) self.shellwidget.sig_remote_execute.disconnect( self._when_prompt_is_ready) # It's necessary to do this at this point to avoid giving # focus to _control at startup. self._connect_control_signals() if self.give_focus: self.shellwidget._control.setFocus() def enable_stop_button(self): self.stop_button.setEnabled(True) def disable_stop_button(self): # This avoids disabling automatically the button when # re-running files on dedicated consoles. # See spyder-ide/spyder#5958. if not self.shellwidget._executing: # This avoids disabling the button while debugging # see spyder-ide/spyder#13283 if not self.shellwidget.is_waiting_pdb_input(): self.stop_button.setDisabled(True) else: self.stop_button.setEnabled(True) @Slot() def stop_button_click_handler(self): """Method to handle what to do when the stop button is pressed""" self.stop_button.setDisabled(True) # Interrupt computations or stop debugging if not self.shellwidget.is_waiting_pdb_input(): self.interrupt_kernel() else: self.shellwidget.pdb_execute_command('exit') def show_kernel_error(self, error): """Show kernel initialization errors in infowidget.""" # Replace end of line chars with <br> eol = sourcecode.get_eol_chars(error) if eol: error = error.replace(eol, '<br>') # Don't break lines in hyphens # From https://stackoverflow.com/q/7691569/438386 error = error.replace('-', '‑') # Create error page message = _("An error ocurred while starting the kernel") kernel_error_template = Template(KERNEL_ERROR) self.info_page = kernel_error_template.substitute( css_path=self.css_path, message=message, error=error) # Show error self.set_info_page() self.shellwidget.hide() self.infowidget.show() # Tell the client we're in error mode self.is_error_shown = True def get_name(self): """Return client name""" if self.given_name is None: # Name according to host if self.hostname is None: name = _("Console") else: name = self.hostname # Adding id to name client_id = self.id_['int_id'] + u'/' + self.id_['str_id'] name = name + u' ' + client_id elif self.given_name in ["Pylab", "SymPy", "Cython"]: client_id = self.id_['int_id'] + u'/' + self.id_['str_id'] name = self.given_name + u' ' + client_id else: name = self.given_name + u'/' + self.id_['str_id'] return name def get_control(self): """Return the text widget (or similar) to give focus to""" # page_control is the widget used for paging page_control = self.shellwidget._page_control if page_control and page_control.isVisible(): return page_control else: return self.shellwidget._control def get_kernel(self): """Get kernel associated with this client""" return self.shellwidget.kernel_manager def get_options_menu(self): """Return options menu""" env_action = create_action(self, _("Show environment variables"), icon=ima.icon('environ'), triggered=self.shellwidget.request_env) syspath_action = create_action( self, _("Show sys.path contents"), icon=ima.icon('syspath'), triggered=self.shellwidget.request_syspath) self.show_time_action.setChecked(self.show_elapsed_time) additional_actions = [ MENU_SEPARATOR, env_action, syspath_action, self.show_time_action ] if self.menu_actions is not None: console_menu = self.menu_actions + additional_actions return console_menu else: return additional_actions def get_toolbar_buttons(self): """Return toolbar buttons list.""" buttons = [] # Code to add the stop button if self.stop_button is None: self.stop_button = create_toolbutton( self, text=_("Stop"), icon=self.stop_icon, tip=_("Stop the current command")) self.disable_stop_button() # set click event handler self.stop_button.clicked.connect(self.stop_button_click_handler) if self.stop_button is not None: buttons.append(self.stop_button) self.stop_button.setStyleSheet(str(PANES_TABBAR_STYLESHEET)) # Reset namespace button if self.reset_button is None: self.reset_button = create_toolbutton( self, text=_("Remove"), icon=ima.icon('editdelete'), tip=_("Remove all variables"), triggered=self.reset_namespace) if self.reset_button is not None: buttons.append(self.reset_button) self.reset_button.setStyleSheet(str(PANES_TABBAR_STYLESHEET)) if self.options_button is None: options = self.get_options_menu() if options: self.options_button = create_toolbutton( self, text=_('Options'), icon=ima.icon('tooloptions')) self.options_button.setPopupMode(QToolButton.InstantPopup) menu = QMenu(self) add_actions(menu, options) self.options_button.setMenu(menu) if self.options_button is not None: buttons.append(self.options_button) return buttons def add_actions_to_context_menu(self, menu): """Add actions to IPython widget context menu""" inspect_action = create_action( self, _("Inspect current object"), QKeySequence(self.get_shortcut('inspect current object')), icon=ima.icon('MessageBoxInformation'), triggered=self.inspect_object) clear_line_action = create_action(self, _("Clear line or block"), QKeySequence( self.get_shortcut('clear line')), triggered=self.clear_line) reset_namespace_action = create_action( self, _("Remove all variables"), QKeySequence(self.get_shortcut('reset namespace')), icon=ima.icon('editdelete'), triggered=self.reset_namespace) clear_console_action = create_action( self, _("Clear console"), QKeySequence(self.get_shortcut('clear shell')), triggered=self.clear_console) quit_action = create_action(self, _("&Quit"), icon=ima.icon('exit'), triggered=self.exit_callback) add_actions( menu, (None, inspect_action, clear_line_action, clear_console_action, reset_namespace_action, None, quit_action)) return menu def set_font(self, font): """Set IPython widget's font""" self.shellwidget._control.setFont(font) self.shellwidget.font = font def set_color_scheme(self, color_scheme, reset=True): """Set IPython color scheme.""" # Needed to handle not initialized kernel_client # See spyder-ide/spyder#6996. try: self.shellwidget.set_color_scheme(color_scheme, reset) except AttributeError: pass def shutdown(self): """Shutdown kernel""" if self.get_kernel() is not None and not self.slave: self.shellwidget.shutdown() def close(self): """Close client""" self.shellwidget.will_close(self.get_kernel() is None or self.slave) super(ClientWidget, self).close() def interrupt_kernel(self): """Interrupt the associanted Spyder kernel if it's running""" # Needed to prevent a crash when a kernel is not running. # See spyder-ide/spyder#6299. try: self.shellwidget.request_interrupt_kernel() except RuntimeError: pass @Slot() def restart_kernel(self): """ Restart the associated kernel. Took this code from the qtconsole project Licensed under the BSD license """ sw = self.shellwidget if not running_under_pytest() and self.ask_before_restart: message = _('Are you sure you want to restart the kernel?') buttons = QMessageBox.Yes | QMessageBox.No result = QMessageBox.question(self, _('Restart kernel?'), message, buttons) else: result = None if (result == QMessageBox.Yes or running_under_pytest() or not self.ask_before_restart): if sw.kernel_manager: if self.infowidget.isVisible(): self.infowidget.hide() if self._abort_kernel_restart(): sw.spyder_kernel_comm.close() return self._show_loading_page() # Close comm sw.spyder_kernel_comm.close() # Stop autorestart mechanism sw.kernel_manager.stop_restarter() sw.kernel_manager.autorestart = False # Create and run restarting thread if (self.restart_thread is not None and self.restart_thread.isRunning()): self.restart_thread.finished.disconnect() self.restart_thread.terminate() self.restart_thread.wait() self.restart_thread = QThread() self.restart_thread.run = self._restart_thread_main self.restart_thread.error = None self.restart_thread.finished.connect( lambda: self._finalise_restart(True)) self.restart_thread.start() else: sw._append_plain_text( _('Cannot restart a kernel not started by Spyder\n'), before_prompt=True) self._hide_loading_page() def _restart_thread_main(self): """Restart the kernel in a thread.""" try: self.shellwidget.kernel_manager.restart_kernel( stderr=self.stderr_handle) except RuntimeError as e: self.restart_thread.error = e def _finalise_restart(self, reset=False): """Finishes the restarting of the kernel.""" sw = self.shellwidget if self._abort_kernel_restart(): sw.spyder_kernel_comm.close() return if self.restart_thread and self.restart_thread.error is not None: sw._append_plain_text(_('Error restarting kernel: %s\n') % self.restart_thread.error, before_prompt=True) else: # Reset Pdb state and reopen comm sw._pdb_in_loop = False sw.spyder_kernel_comm.remove() sw.spyder_kernel_comm.open_comm(sw.kernel_client) # Start autorestart mechanism sw.kernel_manager.autorestart = True sw.kernel_manager.start_restarter() # For spyder-ide/spyder#6235, IPython was changing the # setting of %colors on windows by assuming it was using a # dark background. This corrects it based on the scheme. self.set_color_scheme(sw.syntax_style, reset=reset) sw._append_html(_("<br>Restarting kernel...<br>"), before_prompt=True) sw.insert_horizontal_ruler() self._hide_loading_page() self.stop_button.setDisabled(True) self.restart_thread = None @Slot(str) def kernel_restarted_message(self, msg): """Show kernel restarted/died messages.""" if not self.is_error_shown: # If there are kernel creation errors, jupyter_client will # try to restart the kernel and qtconsole prints a # message about it. # So we read the kernel's stderr_file and display its # contents in the client instead of the usual message shown # by qtconsole. stderr = self.get_stderr_contents() if stderr: self.show_kernel_error('<tt>%s</tt>' % stderr) else: self.shellwidget._append_html("<br>%s<hr><br>" % msg, before_prompt=False) @Slot() def inspect_object(self): """Show how to inspect an object with our Help plugin""" self.shellwidget._control.inspect_current_object() @Slot() def clear_line(self): """Clear a console line""" self.shellwidget._keyboard_quit() @Slot() def clear_console(self): """Clear the whole console""" self.shellwidget.clear_console() @Slot() def reset_namespace(self): """Resets the namespace by removing all names defined by the user""" self.shellwidget.reset_namespace(warning=self.reset_warning, message=True) def update_history(self): self.history = self.shellwidget._history @Slot(object) def show_syspath(self, syspath): """Show sys.path contents.""" if syspath is not None: editor = CollectionsEditor(self) editor.setup(syspath, title="sys.path contents", readonly=True, icon=ima.icon('syspath')) self.dialog_manager.show(editor) else: return @Slot(object) def show_env(self, env): """Show environment variables.""" self.dialog_manager.show(RemoteEnvDialog(env, parent=self)) def create_time_label(self): """Create elapsed time label widget (if necessary) and return it""" if self.time_label is None: self.time_label = QLabel() return self.time_label def show_time(self, end=False): """Text to show in time_label.""" if self.time_label is None: return elapsed_time = time.monotonic() - self.t0 # System time changed to past date, so reset start. if elapsed_time < 0: self.t0 = time.monotonic() elapsed_time = 0 if elapsed_time > 24 * 3600: # More than a day...! fmt = "%d %H:%M:%S" else: fmt = "%H:%M:%S" if end: color = QStylePalette.COLOR_TEXT_3 else: color = QStylePalette.COLOR_ACCENT_4 text = "<span style=\'color: %s\'><b>%s" \ "</b></span>" % (color, time.strftime(fmt, time.gmtime(elapsed_time))) self.time_label.setText(text) def update_time_label_visibility(self): """Update elapsed time visibility.""" self.time_label.setVisible(self.show_elapsed_time) @Slot(bool) def set_elapsed_time_visible(self, state): """Slot to show/hide elapsed time label.""" self.show_elapsed_time = state if self.time_label is not None: self.time_label.setVisible(state) def set_info_page(self): """Set current info_page.""" if self.info_page is not None: self.infowidget.setHtml(self.info_page, QUrl.fromLocalFile(self.css_path)) #------ Private API ------------------------------------------------------- def _create_loading_page(self): """Create html page to show while the kernel is starting""" loading_template = Template(LOADING) loading_img = get_image_path('loading_sprites') if os.name == 'nt': loading_img = loading_img.replace('\\', '/') message = _("Connecting to kernel...") page = loading_template.substitute(css_path=self.css_path, loading_img=loading_img, message=message) return page def _create_blank_page(self): """Create html page to show while the kernel is starting""" loading_template = Template(BLANK) page = loading_template.substitute(css_path=self.css_path) return page def _show_loading_page(self): """Show animation while the kernel is loading.""" self.shellwidget.hide() self.infowidget.show() self.info_page = self.loading_page self.set_info_page() def _hide_loading_page(self): """Hide animation shown while the kernel is loading.""" self.infowidget.hide() self.info_page = self.blank_page self.set_info_page() self.shellwidget.show() def _read_stderr(self): """Read the stderr file of the kernel.""" # We need to read stderr_file as bytes to be able to # detect its encoding with chardet f = open(self.stderr_file, 'rb') try: stderr_text = f.read() # This is needed to avoid showing an empty error message # when the kernel takes too much time to start. # See spyder-ide/spyder#8581. if not stderr_text: return '' # This is needed since the stderr file could be encoded # in something different to utf-8. # See spyder-ide/spyder#4191. encoding = get_coding(stderr_text) stderr_text = to_text_string(stderr_text, encoding) return stderr_text finally: f.close() def _show_mpl_backend_errors(self): """ Show possible errors when setting the selected Matplotlib backend. """ if not self.external_kernel: self.shellwidget.call_kernel().show_mpl_backend_errors() def _check_special_console_error(self): """Check if the dependecies for special consoles are available.""" self.shellwidget.call_kernel(callback=self._show_special_console_error ).is_special_kernel_valid() def _show_special_console_error(self, missing_dependency): if missing_dependency is not None: error_message = _( "Your Python environment or installation doesn't have the " "<tt>{missing_dependency}</tt> module installed or it " "occurred a problem importing it. Due to that, it is not " "possible for Spyder to create this special console for " "you.").format(missing_dependency=missing_dependency) self.show_kernel_error(error_message) def _abort_kernel_restart(self): """ Abort kernel restart if there are errors while starting it. We also ignore errors about comms, which are irrelevant. """ stderr = self.get_stderr_contents() if stderr and 'No such comm' not in stderr: return True else: return False def _connect_control_signals(self): """Connect signals of control widgets.""" control = self.shellwidget._control page_control = self.shellwidget._page_control control.sig_focus_changed.connect( lambda: self.plugin.sig_focus_changed.emit()) page_control.sig_focus_changed.connect( lambda: self.plugin.sig_focus_changed.emit()) control.sig_visibility_changed.connect(self.plugin.refresh_plugin) page_control.sig_visibility_changed.connect(self.plugin.refresh_plugin) page_control.sig_show_find_widget_requested.connect( self.plugin.find_widget.show)