From c0d99cdc0990145562dfefbe23c69fc341911e7d 2010-08-24 19:11:57 From: epatters Date: 2010-08-24 19:11:57 Subject: [PATCH] First cut at allowing the kernel to be restarted from the frontend. --- diff --git a/IPython/frontend/qt/console/console_widget.py b/IPython/frontend/qt/console/console_widget.py index ec7961e..4c1dc98 100644 --- a/IPython/frontend/qt/console/console_widget.py +++ b/IPython/frontend/qt/console/console_widget.py @@ -430,12 +430,6 @@ class ConsoleWidget(QtGui.QWidget): """ raise NotImplementedError - def _execute_interrupt(self): - """ Attempts to stop execution. Returns whether this method has an - implementation. - """ - return False - def _prompt_started_hook(self): """ Called immediately after a new prompt is displayed. """ @@ -575,10 +569,7 @@ class ConsoleWidget(QtGui.QWidget): intercepted = True elif ctrl_down: - if key == QtCore.Qt.Key_C: - intercepted = self._executing and self._execute_interrupt() - - elif key == QtCore.Qt.Key_K: + if key == QtCore.Qt.Key_K: if self._in_buffer(position): cursor.movePosition(QtGui.QTextCursor.EndOfLine, QtGui.QTextCursor.KeepAnchor) diff --git a/IPython/frontend/qt/console/frontend_widget.py b/IPython/frontend/qt/console/frontend_widget.py index 5a5f0f6..1e99b0a 100644 --- a/IPython/frontend/qt/console/frontend_widget.py +++ b/IPython/frontend/qt/console/frontend_widget.py @@ -72,12 +72,22 @@ class FrontendHighlighter(PygmentsHighlighter): class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): """ A Qt frontend for a generic Python kernel. """ + + # An option and corresponding signal for overriding the default kernel + # interrupt behavior. + custom_interrupt = False + custom_interrupt_requested = QtCore.pyqtSignal() + + # An option and corresponding signal for overriding the default kernel + # restart behavior. + custom_restart = False + custom_restart_requested = QtCore.pyqtSignal() # Emitted when an 'execute_reply' has been received from the kernel and # processed by the FrontendWidget. executed = QtCore.pyqtSignal(object) - - # Protected class attributes. + + # Protected class variables. _highlighter_class = FrontendHighlighter _input_splitter_class = InputSplitter @@ -123,13 +133,6 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): """ self.kernel_manager.xreq_channel.execute(source, hidden) self._hidden = hidden - - def _execute_interrupt(self): - """ Attempts to stop execution. Returns whether this method has an - implementation. - """ - self._interrupt_kernel() - return True def _prompt_started_hook(self): """ Called immediately after a new prompt is displayed. @@ -163,6 +166,19 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): # 'ConsoleWidget' protected interface #--------------------------------------------------------------------------- + def _event_filter_console_keypress(self, event): + """ Reimplemented to allow execution interruption. + """ + key = event.key() + if self._executing and self._control_key_down(event.modifiers()): + if key == QtCore.Qt.Key_C: + self._kernel_interrupt() + return True + elif key == QtCore.Qt.Key_Period: + self._kernel_restart() + return True + return super(FrontendWidget, self)._event_filter_console_keypress(event) + def _show_continuation_prompt(self): """ Reimplemented for auto-indentation. """ @@ -324,15 +340,36 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): text = str(cursor.selection().toPlainText()) return self._completion_lexer.get_context(text) - def _interrupt_kernel(self): - """ Attempts to the interrupt the kernel. + def _kernel_interrupt(self): + """ Attempts to interrupt the running kernel. """ - if self.kernel_manager.has_kernel: + if self.custom_interrupt: + self.custom_interrupt_requested.emit() + elif self.kernel_manager.has_kernel: self.kernel_manager.signal_kernel(signal.SIGINT) else: self._append_plain_text('Kernel process is either remote or ' 'unspecified. Cannot interrupt.\n') + def _kernel_restart(self): + """ Attempts to restart the running kernel. + """ + if self.custom_restart: + self.custom_restart_requested.emit() + elif self.kernel_manager.has_kernel: + try: + self.kernel_manager.restart_kernel() + except RuntimeError: + message = 'Kernel started externally. Cannot restart.\n' + self._append_plain_text(message) + else: + self._stopped_channels() + self._append_plain_text('Kernel restarting...\n') + self._show_interpreter_prompt() + else: + self._append_plain_text('Kernel process is either remote or ' + 'unspecified. Cannot restart.\n') + def _process_execute_abort(self, msg): """ Process a reply for an aborted execution request. """ diff --git a/IPython/frontend/qt/console/rich_ipython_widget.py b/IPython/frontend/qt/console/rich_ipython_widget.py index de4d73b..a22f314 100644 --- a/IPython/frontend/qt/console/rich_ipython_widget.py +++ b/IPython/frontend/qt/console/rich_ipython_widget.py @@ -1,5 +1,3 @@ -import os - # System library imports from PyQt4 import QtCore, QtGui diff --git a/IPython/zmq/kernelmanager.py b/IPython/zmq/kernelmanager.py index 6b0030d..1aaf510 100644 --- a/IPython/zmq/kernelmanager.py +++ b/IPython/zmq/kernelmanager.py @@ -482,6 +482,7 @@ class KernelManager(HasTraits): rep_channel_class = Type(RepSocketChannel) # Protected traits. + _launch_args = Any _xreq_channel = Any _sub_channel = Any _rep_channel = Any @@ -523,7 +524,7 @@ class KernelManager(HasTraits): # Kernel process management methods: #-------------------------------------------------------------------------- - def start_kernel(self, ipython=True, **kw): + def start_kernel(self, **kw): """Starts a kernel process and configures the manager to use it. If random ports (port=0) are being used, this method must be called @@ -540,7 +541,8 @@ class KernelManager(HasTraits): "Make sure that the '*_address' attributes are " "configured properly.") - if ipython: + self._launch_args = kw.copy() + if kw.pop('ipython', True): from ipkernel import launch_kernel as launch else: from pykernel import launch_kernel as launch @@ -550,6 +552,19 @@ class KernelManager(HasTraits): self.sub_address = (LOCALHOST, pub) self.rep_address = (LOCALHOST, req) + def restart_kernel(self): + """Restarts a kernel with the same arguments that were used to launch + it. If the old kernel was launched with random ports, the same ports + will be used for the new kernel. + """ + if self._launch_args is None: + raise RuntimeError("Cannot restart the kernel. " + "No previous call to 'start_kernel'.") + else: + if self.has_kernel: + self.kill_kernel() + self.start_kernel(*self._launch_args) + @property def has_kernel(self): """Returns whether a kernel process has been specified for the kernel