diff --git a/spyder/app/mainwindow.py b/spyder/app/mainwindow.py index ad1fccf550f..78df4523a5e 100644 --- a/spyder/app/mainwindow.py +++ b/spyder/app/mainwindow.py @@ -301,6 +301,10 @@ def signal_handler(signum, frame=None): mac_style = mac_style.replace('$IMAGE_PATH', img_path) self.setStyleSheet(mac_style) + # Create our TEMPDIR + if not osp.isdir(programs.TEMPDIR): + os.mkdir(programs.TEMPDIR) + # Shortcut management data self.shortcut_data = [] diff --git a/spyder/config/main.py b/spyder/config/main.py index d55f8269138..1cf7e2161a7 100755 --- a/spyder/config/main.py +++ b/spyder/config/main.py @@ -438,6 +438,7 @@ # ---- In widgets/ipythonconsole/shell.py ---- 'ipython_console/new tab': "Ctrl+T", 'ipython_console/reset namespace': "Ctrl+Alt+R", + 'ipython_console/restart kernel': "Ctrl+.", # ---- In widgets/arraybuider.py ---- 'array_builder/enter array inline': "Ctrl+Alt+M", 'array_builder/enter array table': "Ctrl+M", diff --git a/spyder/plugins/ipythonconsole.py b/spyder/plugins/ipythonconsole.py index b216404d049..01ed8a9a52c 100644 --- a/spyder/plugins/ipythonconsole.py +++ b/spyder/plugins/ipythonconsole.py @@ -15,6 +15,7 @@ # Standard library imports import atexit +import codecs import os import os.path as osp import uuid @@ -724,20 +725,22 @@ def refresh_plugin(self): self.sig_update_plugin_title.emit() def get_plugin_actions(self): - """Return a list of actions related to plugin""" - ctrl = "Cmd" if sys.platform == "darwin" else "Ctrl" - main_create_client_action = create_action(self, - _("Open an &IPython console"), - None, ima.icon('ipython_console'), - triggered=self.create_new_client, - tip=_("Use %s+T when the console is selected " - "to open a new one") % ctrl) - create_client_action = create_action(self, - _("Open a new console"), - QKeySequence("Ctrl+T"), - ima.icon('ipython_console'), - triggered=self.create_new_client, - context=Qt.WidgetWithChildrenShortcut) + """Return a list of actions related to plugin.""" + create_client_action = create_action( + self, + _("Open an &IPython console"), + icon=ima.icon('ipython_console'), + triggered=self.create_new_client, + context=Qt.WidgetWithChildrenShortcut) + self.register_shortcut(create_client_action, context="ipython_console", + name="New tab") + + restart_action = create_action(self, _("Restart kernel"), + icon=ima.icon('restart'), + triggered=self.restart_kernel, + context=Qt.WidgetWithChildrenShortcut) + self.register_shortcut(restart_action, context="ipython_console", + name="Restart kernel") connect_to_kernel_action = create_action(self, _("Connect to an existing kernel"), None, None, @@ -746,11 +749,13 @@ def get_plugin_actions(self): # Add the action to the 'Consoles' menu on the main window main_consoles_menu = self.main.consoles_menu_actions - main_consoles_menu.insert(0, main_create_client_action) - main_consoles_menu += [MENU_SEPARATOR, connect_to_kernel_action] + main_consoles_menu.insert(0, create_client_action) + main_consoles_menu += [MENU_SEPARATOR, restart_action, + connect_to_kernel_action] # Plugin actions - self.menu_actions = [create_client_action, connect_to_kernel_action] + self.menu_actions = [restart_action, MENU_SEPARATOR, + create_client_action, connect_to_kernel_action] return self.menu_actions @@ -911,7 +916,9 @@ def create_client_for_kernel(self): def connect_client_to_kernel(self, client): """Connect a client to its kernel""" connection_file = client.connection_file - km, kc = self.create_kernel_manager_and_kernel_client(connection_file) + stderr_file = client.stderr_file + km, kc = self.create_kernel_manager_and_kernel_client(connection_file, + stderr_file) kc.started_channels.connect(lambda c=client: self.process_started(c)) kc.stopped_channels.connect(lambda c=client: self.process_finished(c)) @@ -1290,13 +1297,17 @@ def create_kernel_spec(self): return KernelSpec(resource_dir='', **kernel_dict) - def create_kernel_manager_and_kernel_client(self, connection_file): - """Create kernel manager and client""" + def create_kernel_manager_and_kernel_client(self, connection_file, + stderr_file): + """Create kernel manager and client.""" # Kernel manager kernel_manager = QtKernelManager(connection_file=connection_file, config=None, autorestart=True) kernel_manager._kernel_spec = self.create_kernel_spec() - kernel_manager.start_kernel() + + # Save stderr in a file to read it later in case of errors + stderr = codecs.open(stderr_file, 'w', encoding='utf-8') + kernel_manager.start_kernel(stderr=stderr) # Kernel client kernel_client = kernel_manager.client() @@ -1307,6 +1318,12 @@ def create_kernel_manager_and_kernel_client(self, connection_file): return kernel_manager, kernel_client + def restart_kernel(self): + """Restart kernel of current client.""" + client = self.get_current_client() + if client is not None: + client.restart_kernel() + #------ Public API (for tabs) --------------------------------------------- def add_tab(self, widget, name): """Add tab""" diff --git a/spyder/utils/programs.py b/spyder/utils/programs.py index 41e3dd39ebd..cb758078a21 100644 --- a/spyder/utils/programs.py +++ b/spyder/utils/programs.py @@ -396,9 +396,6 @@ def is_module_installed(module_name, version=None, installed_version=None, in a determined interpreter """ if interpreter: - if not osp.isdir(TEMPDIR): - os.mkdir(TEMPDIR) - if osp.isfile(interpreter) and ('python' in interpreter): checkver = inspect.getsource(check_version) get_modver = inspect.getsource(get_module_version) diff --git a/spyder/widgets/ipythonconsole/client.py b/spyder/widgets/ipythonconsole/client.py index fbedfcbbfa5..39d6d06d662 100644 --- a/spyder/widgets/ipythonconsole/client.py +++ b/spyder/widgets/ipythonconsole/client.py @@ -13,6 +13,7 @@ # Standard library imports from __future__ import absolute_import # Fix for Issue 1356 +import codecs import os import os.path as osp from string import Template @@ -30,6 +31,8 @@ get_module_source_path) from spyder.config.gui import get_font, get_shortcut from spyder.utils import icon_manager as ima +from spyder.utils import sourcecode +from spyder.utils.programs import TEMPDIR from spyder.utils.qthelpers import (add_actions, create_action, create_toolbutton) from spyder.widgets.browser import WebView @@ -103,16 +106,15 @@ def __init__(self, plugin, name, history_filename, config_options, # --- 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.shellwidget.hide() self.infowidget = WebView(self) self.set_infowidget_font() self.loading_page = self._create_loading_page() - self.infowidget.setHtml(self.loading_page, - QUrl.fromLocalFile(CSS_PATH)) + self._show_loading_page() # --- Layout vlayout = QVBoxLayout() @@ -133,16 +135,24 @@ def __init__(self, plugin, name, history_filename, config_options, # As soon as some content is printed in the console, stop # our loading animation document = self.get_control().document() - document.contentsChange.connect(self._stop_loading_animation) + document.contentsChange.connect(self._hide_loading_page) #------ Public API -------------------------------------------------------- + @property + def stderr_file(self): + """Filename to save kernel stderr output.""" + json_file = osp.basename(self.connection_file) + stderr_file = json_file.split('json')[0] + 'stderr' + stderr_file = osp.join(TEMPDIR, stderr_file) + return stderr_file + def configure_shellwidget(self, give_focus=True): """Configure shellwidget after kernel is started""" if give_focus: self.get_control().setFocus() - # Connect shellwidget to the client - self.shellwidget.set_ipyclient(self) + # Set exit callback + self.shellwidget.set_exit_callback() # To save history self.shellwidget.executing.connect(self.add_to_history) @@ -163,6 +173,10 @@ def configure_shellwidget(self, give_focus=True): # 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) + def enable_stop_button(self): self.stop_button.setEnabled(True) @@ -180,7 +194,12 @@ def stop_button_click_handler(self): self.shellwidget.write_to_stdin('exit') def show_kernel_error(self, error): - """Show kernel initialization errors in infowidget""" + """Show kernel initialization errors in infowidget.""" + # Replace end of line chars with
+ eol = sourcecode.get_eol_chars(error) + if eol: + error = error.replace(eol, '
') + # Don't break lines in hyphens # From http://stackoverflow.com/q/7691569/438386 error = error.replace('-', '‑') @@ -217,27 +236,18 @@ def get_kernel(self): def get_options_menu(self): """Return options menu""" - restart_action = create_action(self, _("Restart kernel"), - shortcut=QKeySequence("Ctrl+."), - icon=ima.icon('restart'), - triggered=self.restart_kernel, - context=Qt.WidgetWithChildrenShortcut) - - # Main menu - if self.menu_actions is not None: - actions = [restart_action, None] + self.menu_actions - else: - actions = [restart_action] - return actions + return self.menu_actions def get_toolbar_buttons(self): - """Return toolbar buttons list""" + """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.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) @@ -320,6 +330,9 @@ def restart_kernel(self): if result == QMessageBox.Yes: sw = self.shellwidget if sw.kernel_manager: + if self.infowidget.isVisible(): + self.infowidget.hide() + sw.show() try: sw.kernel_manager.restart_kernel() except RuntimeError as e: @@ -328,8 +341,9 @@ def restart_kernel(self): before_prompt=True ) else: + sw.reset(clear=True) sw._append_html(_("
Restarting kernel...\n

"), - before_prompt=True, + before_prompt=False, ) else: sw._append_plain_text( @@ -337,6 +351,18 @@ def restart_kernel(self): before_prompt=True ) + @Slot(str) + def kernel_restarted_message(self, msg): + """Show kernel restarted/died messages.""" + stderr = codecs.open(self.stderr_file, 'r', encoding='utf-8').read() + + if stderr: + self.show_kernel_error('%s' % stderr) + else: + self.shellwidget._append_html("
%s

" % msg, + before_prompt=False) + + @Slot() def inspect_object(self): """Show how to inspect an object with our Help plugin""" @@ -390,11 +416,18 @@ def _create_loading_page(self): message=message) return page - def _stop_loading_animation(self): - """Stop animation shown while the kernel is starting""" + 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) document = self.get_control().document() - document.contentsChange.disconnect(self._stop_loading_animation) + document.contentsChange.disconnect(self._hide_loading_page) diff --git a/spyder/widgets/ipythonconsole/shell.py b/spyder/widgets/ipythonconsole/shell.py index 89973fdaccd..9ad89aff15c 100644 --- a/spyder/widgets/ipythonconsole/shell.py +++ b/spyder/widgets/ipythonconsole/shell.py @@ -45,22 +45,22 @@ class ShellWidget(NamepaceBrowserWidget, HelpWidget, DebuggingWidget): focus_changed = Signal() new_client = Signal() sig_got_reply = Signal() + sig_kernel_restarted = Signal(str) - def __init__(self, additional_options, interpreter_versions, + def __init__(self, ipyclient, additional_options, interpreter_versions, external_kernel, *args, **kw): # To override the Qt widget used by RichJupyterWidget self.custom_control = ControlWidget self.custom_page_control = PageControlWidget super(ShellWidget, self).__init__(*args, **kw) + + self.ipyclient = ipyclient self.additional_options = additional_options self.interpreter_versions = interpreter_versions + self.external_kernel = external_kernel self.set_background_color() - # Additional variables - self.ipyclient = None - self.external_kernel = external_kernel - # Keyboard shortcuts self.shortcuts = self.create_shortcuts() @@ -68,10 +68,9 @@ def __init__(self, additional_options, interpreter_versions, self._kernel_reply = None #---- Public API ---------------------------------------------------------- - def set_ipyclient(self, ipyclient): - """Bind this shell widget to an IPython client one""" - self.ipyclient = ipyclient - self.exit_requested.connect(ipyclient.exit_callback) + def set_exit_callback(self): + """Set exit callback for this shell.""" + self.exit_requested.connect(self.ipyclient.exit_callback) def is_running(self): if self.kernel_client is not None and \ @@ -165,6 +164,9 @@ def create_shortcuts(self): parent=self) clear_console = config_shortcut(self.clear_console, context='Console', name='Clear shell', parent=self) + restart_kernel = config_shortcut(self.ipyclient.restart_kernel, + context='ipython_console', + name='Restart kernel', parent=self) new_tab = config_shortcut(lambda: self.new_client.emit(), context='ipython_console', name='new tab', parent=self) reset_namespace = config_shortcut(lambda: self.reset_namespace(), @@ -177,8 +179,8 @@ def create_shortcuts(self): context='array_builder', name='enter array table', parent=self) - return [inspect, clear_console, new_tab, reset_namespace, - array_inline, array_table] + return [inspect, clear_console, restart_kernel, new_tab, + reset_namespace, array_inline, array_table] # --- To communicate with the kernel def silent_execute(self, code): @@ -274,6 +276,10 @@ def _banner_default(self): else: return self.short_banner() + def _kernel_restarted_message(self, died=True): + msg = _("Kernel died, restarting") if died else _("Kernel restarting") + self.sig_kernel_restarted.emit(msg) + #---- Qt methods ---------------------------------------------------------- def focusInEvent(self, event): """Reimplement Qt method to send focus change notification"""