Skip to content

Commit

Permalink
Merge from 3.x: PR #3861
Browse files Browse the repository at this point in the history
Fixes #3458
  • Loading branch information
ccordoba12 committed Dec 21, 2016
2 parents 9d18037 + 0956b35 commit de8fb8d
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 62 deletions.
4 changes: 4 additions & 0 deletions spyder/app/mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []

Expand Down
1 change: 1 addition & 0 deletions spyder/config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
59 changes: 38 additions & 21 deletions spyder/plugins/ipythonconsole.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

# Standard library imports
import atexit
import codecs
import os
import os.path as osp
import uuid
Expand Down Expand Up @@ -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,
Expand All @@ -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

Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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()
Expand All @@ -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"""
Expand Down
3 changes: 0 additions & 3 deletions spyder/utils/programs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
87 changes: 60 additions & 27 deletions spyder/widgets/ipythonconsole/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -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)
Expand All @@ -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)

Expand All @@ -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 <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('-', '&#8209')
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand All @@ -328,15 +341,28 @@ def restart_kernel(self):
before_prompt=True
)
else:
sw.reset(clear=True)
sw._append_html(_("<br>Restarting kernel...\n<hr><br>"),
before_prompt=True,
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."""
stderr = codecs.open(self.stderr_file, 'r', encoding='utf-8').read()

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"""
Expand Down Expand Up @@ -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)
28 changes: 17 additions & 11 deletions spyder/widgets/ipythonconsole/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,33 +45,32 @@ 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()

# To save kernel replies in silent execution
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 \
Expand Down Expand Up @@ -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(),
Expand All @@ -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):
Expand Down Expand Up @@ -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"""
Expand Down

0 comments on commit de8fb8d

Please sign in to comment.