From 42928a5e113797ca61c7a4971fb90ec8ec4c165f 2010-08-02 21:22:20 From: epatters Date: 2010-08-02 21:22:20 Subject: [PATCH] * Refactored ConsoleWidget execution API for greater flexibility and clarity. * Fixed bug where multi-line input was broken when the buffer size was exceeded * Properly implemented execute_file --- diff --git a/IPython/frontend/qt/console/console_widget.py b/IPython/frontend/qt/console/console_widget.py index e9b9354..2755c70 100644 --- a/IPython/frontend/qt/console/console_widget.py +++ b/IPython/frontend/qt/console/console_widget.py @@ -382,15 +382,58 @@ class ConsoleWidget(QtGui.QPlainTextEdit): # 'ConsoleWidget' public interface #--------------------------------------------------------------------------- - def execute(self, interactive=False): - """ Execute the text in the input buffer. Returns whether the input - buffer was completely processed and a new prompt created. - """ - self.appendPlainText('\n') - self._executing_input_buffer = self.input_buffer - self._executing = True - self._prompt_finished() - return self._execute(interactive=interactive) + def execute(self, source=None, hidden=False, interactive=False): + """ Executes source or the input buffer, possibly prompting for more + input. + + Parameters: + ----------- + source : str, optional + + The source to execute. If not specified, the input buffer will be + used. If specified and 'hidden' is False, the input buffer will be + replaced with the source before execution. + + hidden : bool, optional (default False) + + If set, no output will be shown and the prompt will not be modified. + In other words, it will be completely invisible to the user that + an execution has occurred. + + interactive : bool, optional (default False) + + Whether the console is to treat the source as having been manually + entered by the user. The effect of this parameter depends on the + subclass implementation. + + Returns: + -------- + A boolean indicating whether the source was executed. + """ + if not hidden: + if source is not None: + self.input_buffer = source + + self.appendPlainText('\n') + self._executing_input_buffer = self.input_buffer + self._executing = True + self._prompt_finished() + + real_source = self.input_buffer if source is None else source + complete = self._is_complete(real_source, interactive) + if complete: + if not hidden: + # The maximum block count is only in effect during execution. + # This ensures that _prompt_pos does not become invalid due to + # text truncation. + self.setMaximumBlockCount(self.buffer_size) + self._execute(real_source, hidden) + elif hidden: + raise RuntimeError('Incomplete noninteractive input: "%s"' % source) + else: + self._show_continuation_prompt() + + return complete def _get_input_buffer(self): """ The text that the user has entered entered at the current prompt. @@ -475,11 +518,15 @@ class ConsoleWidget(QtGui.QPlainTextEdit): # 'ConsoleWidget' abstract interface #--------------------------------------------------------------------------- - def _execute(self, interactive): - """ Called to execute the input buffer. When triggered by an the enter - key press, 'interactive' is True; otherwise, it is False. Returns - whether the input buffer was completely processed and a new prompt - created. + def _is_complete(self, source, interactive): + """ Returns whether 'source' can be executed. When triggered by an + Enter/Return key press, 'interactive' is True; otherwise, it is + False. + """ + raise NotImplementedError + + def _execute(self, source, hidden): + """ Execute 'source'. If 'hidden', do not show any output. """ raise NotImplementedError @@ -604,7 +651,8 @@ class ConsoleWidget(QtGui.QPlainTextEdit): def _prompt_started(self): """ Called immediately after a new prompt is displayed. """ - # Temporarily disable the maximum block count to permit undo/redo. + # Temporarily disable the maximum block count to permit undo/redo and + # to ensure that the prompt position does not change due to truncation. self.setMaximumBlockCount(0) self.setUndoRedoEnabled(True) @@ -619,9 +667,7 @@ class ConsoleWidget(QtGui.QPlainTextEdit): """ Called immediately after a prompt is finished, i.e. when some input will be processed and a new prompt displayed. """ - # This has the (desired) side effect of disabling the undo/redo history. - self.setMaximumBlockCount(self.buffer_size) - + self.setUndoRedoEnabled(False) self.setReadOnly(True) self._prompt_finished_hook() @@ -702,14 +748,19 @@ class HistoryConsoleWidget(ConsoleWidget): # 'ConsoleWidget' public interface #--------------------------------------------------------------------------- - def execute(self, interactive=False): + def execute(self, source=None, hidden=False, interactive=False): """ Reimplemented to the store history. """ - stripped = self.input_buffer.rstrip() - executed = super(HistoryConsoleWidget, self).execute(interactive) - if executed: - self._history.append(stripped) + if source is None and not hidden: + history = self.input_buffer.rstrip() + + executed = super(HistoryConsoleWidget, self).execute( + source, hidden, interactive) + + if executed and not hidden: + self._history.append(history) self._history_index = len(self._history) + return executed #--------------------------------------------------------------------------- diff --git a/IPython/frontend/qt/console/frontend_widget.py b/IPython/frontend/qt/console/frontend_widget.py index 86119f0..f5ab175 100644 --- a/IPython/frontend/qt/console/frontend_widget.py +++ b/IPython/frontend/qt/console/frontend_widget.py @@ -90,26 +90,40 @@ class FrontendWidget(HistoryConsoleWidget): self._control_down(event.modifiers()): self._interrupt_kernel() else: - self._call_tip_widget.keyPressEvent(event) + if self._call_tip_widget.isVisible(): + self._call_tip_widget.keyPressEvent(event) super(FrontendWidget, self).keyPressEvent(event) #--------------------------------------------------------------------------- # 'ConsoleWidget' abstract interface #--------------------------------------------------------------------------- - def _execute(self, interactive): - """ Called to execute the input buffer. When triggered by an the enter - key press, 'interactive' is True; otherwise, it is False. Returns - whether the input buffer was completely processed and a new prompt - created. + def _is_complete(self, source, interactive): + """ Returns whether 'source' can be completely processed and a new + prompt created. When triggered by an Enter/Return key press, + 'interactive' is True; otherwise, it is False. """ - return self.execute_source(self.input_buffer, interactive=interactive) + complete = self._input_splitter.push(source) + if interactive: + complete = not self._input_splitter.push_accepts_more() + return complete + + def _execute(self, source, hidden): + """ Execute 'source'. If 'hidden', do not show any output. + """ + self.kernel_manager.xreq_channel.execute(source) + self._hidden = hidden def _prompt_started_hook(self): """ Called immediately after a new prompt is displayed. """ self._highlighter.highlighting_on = True + # Auto-indent if this is a continuation prompt. + if self._get_prompt_cursor().blockNumber() != \ + self._get_end_cursor().blockNumber(): + self.appendPlainText(' ' * self._input_splitter.indent_spaces) + def _prompt_finished_hook(self): """ Called immediately after a prompt is finished, i.e. when some input will be processed and a new prompt displayed. @@ -130,26 +144,11 @@ class FrontendWidget(HistoryConsoleWidget): # 'FrontendWidget' interface #--------------------------------------------------------------------------- - def execute_source(self, source, hidden=False, interactive=False): - """ Execute a string containing Python code. If 'hidden', no output is - shown. Returns whether the source executed (i.e., returns True only - if no more input is necessary). - """ - self._input_splitter.push(source) - executed = not self._input_splitter.push_accepts_more() - if executed: - self.kernel_manager.xreq_channel.execute(source) - self._hidden = hidden - else: - self._show_continuation_prompt() - self.appendPlainText(' ' * self._input_splitter.indent_spaces) - return executed - def execute_file(self, path, hidden=False): """ Attempts to execute file with 'path'. If 'hidden', no output is shown. """ - self.execute_source('run %s' % path, hidden=hidden) + self.execute('execfile("%s")' % path, hidden=hidden) def _get_kernel_manager(self): """ Returns the current kernel manager. @@ -274,10 +273,11 @@ class FrontendWidget(HistoryConsoleWidget): self._call_tip() def _handle_sub(self, omsg): - if not self._hidden: - handler = getattr(self, '_handle_%s' % omsg['msg_type'], None) - if handler is not None: - handler(omsg) + if self._hidden: + return + handler = getattr(self, '_handle_%s' % omsg['msg_type'], None) + if handler is not None: + handler(omsg) def _handle_pyout(self, omsg): session = omsg['parent_header']['session'] @@ -286,8 +286,12 @@ class FrontendWidget(HistoryConsoleWidget): def _handle_stream(self, omsg): self.appendPlainText(omsg['content']['data']) + self.moveCursor(QtGui.QTextCursor.End) def _handle_execute_reply(self, rep): + if self._hidden: + return + # Make sure that all output from the SUB channel has been processed # before writing a new prompt. self.kernel_manager.sub_channel.flush() diff --git a/IPython/frontend/qt/console/ipython_widget.py b/IPython/frontend/qt/console/ipython_widget.py index b8a4ccd..38d85a6 100644 --- a/IPython/frontend/qt/console/ipython_widget.py +++ b/IPython/frontend/qt/console/ipython_widget.py @@ -19,10 +19,10 @@ class IPythonWidget(FrontendWidget): self._magic_overrides = {} #--------------------------------------------------------------------------- - # 'FrontendWidget' interface + # 'ConsoleWidget' abstract interface #--------------------------------------------------------------------------- - def execute_source(self, source, hidden=False, interactive=False): + def _execute(self, source, hidden): """ Reimplemented to override magic commands. """ magic_source = source.strip() @@ -37,11 +37,18 @@ class IPythonWidget(FrontendWidget): output = callback(arguments) if output: self.appendPlainText(output) - self._show_prompt('>>> ') - return True + self._show_prompt() else: - return super(IPythonWidget, self).execute_source(source, hidden, - interactive) + super(IPythonWidget, self)._execute(source, hidden) + + #--------------------------------------------------------------------------- + # 'FrontendWidget' interface + #--------------------------------------------------------------------------- + + def execute_file(self, path, hidden=False): + """ Reimplemented to use the 'run' magic. + """ + self.execute('run %s' % path, hidden=hidden) #--------------------------------------------------------------------------- # 'IPythonWidget' interface