diff --git a/IPython/core/builtin_trap.py b/IPython/core/builtin_trap.py index 38a8097..6eccc29 100755 --- a/IPython/core/builtin_trap.py +++ b/IPython/core/builtin_trap.py @@ -21,7 +21,6 @@ Authors: import __builtin__ from IPython.config.configurable import Configurable -from IPython.core.quitter import Quitter from IPython.utils.traitlets import Instance @@ -32,6 +31,9 @@ from IPython.utils.traitlets import Instance class __BuiltinUndefined(object): pass BuiltinUndefined = __BuiltinUndefined() +class __HideBuiltin(object): pass +HideBuiltin = __HideBuiltin() + class BuiltinTrap(Configurable): @@ -44,9 +46,10 @@ class BuiltinTrap(Configurable): # Only turn off the trap when the outermost call to __exit__ is made. self._nested_level = 0 self.shell = shell - # builtins we always add - self.auto_builtins = {'exit': Quitter(self.shell, 'exit'), - 'quit': Quitter(self.shell, 'quit'), + # builtins we always add - if set to HideBuiltin, they will just + # be removed instead of being replaced by something else + self.auto_builtins = {'exit': HideBuiltin, + 'quit': HideBuiltin, 'get_ipython': self.shell.get_ipython, } # Recursive reload function @@ -77,8 +80,13 @@ class BuiltinTrap(Configurable): """Add a builtin and save the original.""" bdict = __builtin__.__dict__ orig = bdict.get(key, BuiltinUndefined) - self._orig_builtins[key] = orig - bdict[key] = value + if value is HideBuiltin: + if orig is not BuiltinUndefined: #same as 'key in bdict' + self._orig_builtins[key] = orig + del bdict[key] + else: + self._orig_builtins[key] = orig + bdict[key] = value def remove_builtin(self, key): """Remove an added builtin and re-set the original.""" diff --git a/IPython/core/quitter.py b/IPython/deathrow/quitter.py similarity index 100% rename from IPython/core/quitter.py rename to IPython/deathrow/quitter.py diff --git a/IPython/frontend/qt/console/frontend_widget.py b/IPython/frontend/qt/console/frontend_widget.py index 67ca1b5..f170639 100644 --- a/IPython/frontend/qt/console/frontend_widget.py +++ b/IPython/frontend/qt/console/frontend_widget.py @@ -546,8 +546,16 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): """ Process a reply for an execution request that resulted in an error. """ content = msg['content'] - traceback = ''.join(content['traceback']) - self._append_plain_text(traceback) + # If a SystemExit is passed along, this means exit() was called - also + # all the ipython %exit magic syntax of '-k' to be used to keep + # the kernel running + if content['ename']=='SystemExit': + keepkernel = content['evalue']=='-k' or content['evalue']=='True' + self._keep_kernel_on_exit = keepkernel + self.exit_requested.emit() + else: + traceback = ''.join(content['traceback']) + self._append_plain_text(traceback) def _process_execute_ok(self, msg): """ Process a reply for a successful execution equest. diff --git a/IPython/frontend/qt/console/ipython_widget.py b/IPython/frontend/qt/console/ipython_widget.py index dd2e594..b4741b8 100644 --- a/IPython/frontend/qt/console/ipython_widget.py +++ b/IPython/frontend/qt/console/ipython_widget.py @@ -111,6 +111,7 @@ class IPythonWidget(FrontendWidget): self._payload_source_page : self._handle_payload_page, self._payload_source_loadpy : self._handle_payload_loadpy } self._previous_prompt_obj = None + self._keep_kernel_on_exit = None # Initialize widget styling. if self.style_sheet: @@ -424,6 +425,7 @@ class IPythonWidget(FrontendWidget): self._edit(item['filename'], item['line_number']) def _handle_payload_exit(self, item): + self._keep_kernel_on_exit = item['keepkernel'] self.exit_requested.emit() def _handle_payload_loadpy(self, item): diff --git a/IPython/frontend/qt/console/ipythonqt.py b/IPython/frontend/qt/console/ipythonqt.py index 100404b..c350f2b 100644 --- a/IPython/frontend/qt/console/ipythonqt.py +++ b/IPython/frontend/qt/console/ipythonqt.py @@ -58,52 +58,71 @@ class MainWindow(QtGui.QMainWindow): #--------------------------------------------------------------------------- def closeEvent(self, event): - """ Reimplemented to prompt the user and close the kernel cleanly. + """ Close the window and the kernel (if necessary). + + This will prompt the user if they are finished with the kernel, and if + so, closes the kernel cleanly. Alternatively, if the exit magic is used, + it closes without prompt. """ + keepkernel = None #Use the prompt by default + if hasattr(self._frontend,'_keep_kernel_on_exit'): #set by exit magic + keepkernel = self._frontend._keep_kernel_on_exit + kernel_manager = self._frontend.kernel_manager - if kernel_manager and kernel_manager.channels_running: - title = self.window().windowTitle() - cancel = QtGui.QMessageBox.Cancel - okay = QtGui.QMessageBox.Ok - if self._may_close: - msg = "You are closing this Console window." - info = "Would you like to quit the Kernel and all attached Consoles as well?" - justthis = QtGui.QPushButton("&No, just this Console", self) - justthis.setShortcut('N') - closeall = QtGui.QPushButton("&Yes, quit everything", self) - closeall.setShortcut('Y') - box = QtGui.QMessageBox(QtGui.QMessageBox.Question, title, msg) - box.setInformativeText(info) - box.addButton(cancel) - box.addButton(justthis, QtGui.QMessageBox.NoRole) - box.addButton(closeall, QtGui.QMessageBox.YesRole) - box.setDefaultButton(closeall) - box.setEscapeButton(cancel) - reply = box.exec_() - if reply == 1: # close All - kernel_manager.shutdown_kernel() - #kernel_manager.stop_channels() - event.accept() - elif reply == 0: # close Console - if not self._existing: - # I have the kernel: don't quit, just close the window - self._app.setQuitOnLastWindowClosed(False) - self.deleteLater() - event.accept() - else: - event.ignore() - else: - reply = QtGui.QMessageBox.question(self, title, - "Are you sure you want to close this Console?"+ - "\nThe Kernel and other Consoles will remain active.", - okay|cancel, - defaultButton=okay - ) - if reply == okay: - event.accept() + + if keepkernel is None: #show prompt + if kernel_manager and kernel_manager.channels_running: + title = self.window().windowTitle() + cancel = QtGui.QMessageBox.Cancel + okay = QtGui.QMessageBox.Ok + if self._may_close: + msg = "You are closing this Console window." + info = "Would you like to quit the Kernel and all attached Consoles as well?" + justthis = QtGui.QPushButton("&No, just this Console", self) + justthis.setShortcut('N') + closeall = QtGui.QPushButton("&Yes, quit everything", self) + closeall.setShortcut('Y') + box = QtGui.QMessageBox(QtGui.QMessageBox.Question, title, msg) + box.setInformativeText(info) + box.addButton(cancel) + box.addButton(justthis, QtGui.QMessageBox.NoRole) + box.addButton(closeall, QtGui.QMessageBox.YesRole) + box.setDefaultButton(closeall) + box.setEscapeButton(cancel) + reply = box.exec_() + if reply == 1: # close All + kernel_manager.shutdown_kernel() + #kernel_manager.stop_channels() + event.accept() + elif reply == 0: # close Console + if not self._existing: + # Have kernel: don't quit, just close the window + self._app.setQuitOnLastWindowClosed(False) + self.deleteLater() + event.accept() + else: + event.ignore() else: - event.ignore() - + reply = QtGui.QMessageBox.question(self, title, + "Are you sure you want to close this Console?"+ + "\nThe Kernel and other Consoles will remain active.", + okay|cancel, + defaultButton=okay + ) + if reply == okay: + event.accept() + else: + event.ignore() + elif keepkernel: #close console but leave kernel running (no prompt) + if kernel_manager and kernel_manager.channels_running: + if not self._existing: + # I have the kernel: don't quit, just close the window + self._app.setQuitOnLastWindowClosed(False) + event.accept() + else: #close console and kernel (no prompt) + if kernel_manager and kernel_manager.channels_running: + kernel_manager.shutdown_kernel() + event.accept() #----------------------------------------------------------------------------- # Main entry point diff --git a/IPython/zmq/zmqshell.py b/IPython/zmq/zmqshell.py index c5785ac..b418ec3 100644 --- a/IPython/zmq/zmqshell.py +++ b/IPython/zmq/zmqshell.py @@ -78,6 +78,7 @@ class ZMQInteractiveShell(InteractiveShell): """A subclass of InteractiveShell for ZMQ.""" displayhook_class = Type(ZMQDisplayHook) + keepkernel_on_exit = None def init_environment(self): """Configure the user's environment. @@ -111,6 +112,7 @@ class ZMQInteractiveShell(InteractiveShell): payload = dict( source='IPython.zmq.zmqshell.ZMQInteractiveShell.ask_exit', exit=True, + keepkernel=self.keepkernel_on_exit, ) self.payload_manager.write_payload(payload) @@ -563,5 +565,16 @@ class ZMQInteractiveShell(InteractiveShell): text=content ) self.payload_manager.write_payload(payload) + + def magic_Exit(self, parameter_s=''): + """Exit IPython. If the -k option is provided, the kernel will be left + running. Otherwise, it will shutdown without prompting. + """ + opts,args = self.parse_options(parameter_s,'k') + self.shell.keepkernel_on_exit = opts.has_key('k') + self.shell.ask_exit() + + # Add aliases as magics so all common forms work: exit, quit, Exit, Quit. + magic_exit = magic_quit = magic_Quit = magic_Exit InteractiveShellABC.register(ZMQInteractiveShell)