From 60bf09757856f7769eb79c2581a86656de4d275a 2010-09-12 09:50:34 From: Fernando Perez Date: 2010-09-12 09:50:34 Subject: [PATCH] Implement support for 'cell' mode with Ctrl-Enter. Now, once c-enter has been used, the widget will continue accepting more input just as if the input was indented: even complete expressions won't trigger execution, until an extra blank line (or Shift-Enter) is used. --- diff --git a/IPython/core/inputsplitter.py b/IPython/core/inputsplitter.py index ad1fc2b..79b8d20 100644 --- a/IPython/core/inputsplitter.py +++ b/IPython/core/inputsplitter.py @@ -60,6 +60,7 @@ Authors # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. #----------------------------------------------------------------------------- +from __future__ import print_function #----------------------------------------------------------------------------- # Imports @@ -71,6 +72,7 @@ import sys # IPython modules from IPython.utils.text import make_quoted_expr + #----------------------------------------------------------------------------- # Globals #----------------------------------------------------------------------------- @@ -81,14 +83,14 @@ from IPython.utils.text import make_quoted_expr # for all intents and purposes they constitute the 'IPython syntax', so they # should be considered fixed. -ESC_SHELL = '!' -ESC_SH_CAP = '!!' -ESC_HELP = '?' -ESC_HELP2 = '??' -ESC_MAGIC = '%' -ESC_QUOTE = ',' -ESC_QUOTE2 = ';' -ESC_PAREN = '/' +ESC_SHELL = '!' # Send line to underlying system shell +ESC_SH_CAP = '!!' # Send line to system shell and capture output +ESC_HELP = '?' # Find information about object +ESC_HELP2 = '??' # Find extra-detailed information about object +ESC_MAGIC = '%' # Call magic function +ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call +ESC_QUOTE2 = ';' # Quote all args as a single string, call +ESC_PAREN = '/' # Call first argument with rest of line as arguments #----------------------------------------------------------------------------- # Utilities @@ -308,7 +310,7 @@ class InputSplitter(object): ---------- input_mode : str - One of ['line', 'block']; default is 'line'. + One of ['line', 'cell']; default is 'line'. The input_mode parameter controls how new inputs are used when fed via the :meth:`push` method: @@ -316,10 +318,11 @@ class InputSplitter(object): - 'line': meant for line-oriented clients, inputs are appended one at a time to the internal buffer and the whole buffer is compiled. - - 'block': meant for clients that can edit multi-line blocks of text at - a time. Each new input new input completely replaces all prior - inputs. Block mode is thus equivalent to prepending a full reset() - to every push() call. + - 'cell': meant for clients that can edit multi-line 'cells' of text at + a time. A cell can contain one or more blocks that can be compile in + 'single' mode by Python. In this mode, each new input new input + completely replaces all prior inputs. Cell mode is thus equivalent + to prepending a full reset() to every push() call. """ self._buffer = [] self._compile = codeop.CommandCompiler() @@ -365,7 +368,7 @@ class InputSplitter(object): this value is also stored as a private attribute (_is_complete), so it can be queried at any time. """ - if self.input_mode == 'block': + if self.input_mode == 'cell': self.reset() # If the source code has leading blanks, add 'if 1:\n' to it @@ -433,13 +436,31 @@ class InputSplitter(object): backend which might convert the invalid syntax into valid Python via one of the dynamic IPython mechanisms. """ - + + # With incomplete input, unconditionally accept more if not self._is_complete: return True + # If we already have complete input and we're flush left, the answer + # depends. In line mode, we're done. But in cell mode, we need to + # check how many blocks the input so far compiles into, because if + # there's already more than one full independent block of input, then + # the client has entered full 'cell' mode and is feeding lines that + # each is complete. In this case we should then keep accepting. + # The Qt terminal-like console does precisely this, to provide the + # convenience of terminal-like input of single expressions, but + # allowing the user (with a separate keystroke) to switch to 'cell' + # mode and type multiple expressions in one shot. if self.indent_spaces==0: - return False - + if self.input_mode=='line': + return False + else: + nblocks = len(split_blocks(''.join(self._buffer))) + if nblocks==1: + return False + + # When input is complete, then termination is marked by an extra blank + # line at the end. last_line = self.source.splitlines()[-1] return bool(last_line and not last_line.isspace()) @@ -934,10 +955,10 @@ class IPythonInputSplitter(InputSplitter): # line. changed_input_mode = False - if len(lines_list)>1 and self.input_mode == 'block': + if len(lines_list)>1 and self.input_mode == 'cell': self.reset() changed_input_mode = True - saved_input_mode = 'block' + saved_input_mode = 'cell' self.input_mode = 'line' try: diff --git a/IPython/core/tests/test_inputsplitter.py b/IPython/core/tests/test_inputsplitter.py index a4848be..5e5fbd7 100644 --- a/IPython/core/tests/test_inputsplitter.py +++ b/IPython/core/tests/test_inputsplitter.py @@ -204,7 +204,7 @@ class InputSplitterTestCase(unittest.TestCase): def test_replace_mode(self): isp = self.isp - isp.input_mode = 'block' + isp.input_mode = 'cell' isp.push('x=1') self.assertEqual(isp.source, 'x=1\n') isp.push('x=2') @@ -591,7 +591,7 @@ class BlockIPythonInputTestCase(IPythonInputTestCase): test_push3 = test_split = lambda s: None def setUp(self): - self.isp = isp.IPythonInputSplitter(input_mode='block') + self.isp = isp.IPythonInputSplitter(input_mode='cell') def test_syntax_multiline(self): isp = self.isp diff --git a/IPython/frontend/qt/console/console_widget.py b/IPython/frontend/qt/console/console_widget.py index 124a94d..81beca1 100644 --- a/IPython/frontend/qt/console/console_widget.py +++ b/IPython/frontend/qt/console/console_widget.py @@ -363,6 +363,11 @@ class ConsoleWidget(Configurable, QtGui.QWidget): # disable the undo/redo history, but just to be safe: self._control.setUndoRedoEnabled(False) + # Flush all state from the input splitter so the next round of + # reading input starts with a clean buffer. + self._input_splitter.reset() + + # Call actual execution self._execute(source, hidden) else: diff --git a/IPython/frontend/qt/console/frontend_widget.py b/IPython/frontend/qt/console/frontend_widget.py index 0adf7ad..6816cac 100644 --- a/IPython/frontend/qt/console/frontend_widget.py +++ b/IPython/frontend/qt/console/frontend_widget.py @@ -1,3 +1,5 @@ +from __future__ import print_function + # Standard library imports from collections import namedtuple import signal @@ -114,7 +116,7 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None) self._hidden = False self._highlighter = FrontendHighlighter(self) - self._input_splitter = self._input_splitter_class(input_mode='block') + self._input_splitter = self._input_splitter_class(input_mode='cell') self._kernel_manager = None self._possible_kernel_restart = False self._request_info = {} @@ -236,6 +238,7 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): """ Reimplemented for auto-indentation. """ super(FrontendWidget, self)._insert_continuation_prompt(cursor) + #print('SPACES:', self._input_splitter.indent_spaces) # dbg spaces = self._input_splitter.indent_spaces cursor.insertText('\t' * (spaces / self.tab_width)) cursor.insertText(' ' * (spaces % self.tab_width))