diff --git a/IPython/frontend/qt/console/console_widget.py b/IPython/frontend/qt/console/console_widget.py index 93ccdf5..7308116 100644 --- a/IPython/frontend/qt/console/console_widget.py +++ b/IPython/frontend/qt/console/console_widget.py @@ -214,11 +214,11 @@ class ConsoleWidget(QtGui.QPlainTextEdit): elif key == QtCore.Qt.Key_Home: cursor.movePosition(QtGui.QTextCursor.StartOfLine) - start_pos = cursor.position() start_line = cursor.blockNumber() if start_line == self._get_prompt_cursor().blockNumber(): - start_pos += len(self._prompt) + start_pos = self._prompt_pos else: + start_pos = cursor.position() start_pos += len(self._continuation_prompt) if shift_down and self._in_buffer(position): self._set_selection(position, start_pos) @@ -266,22 +266,7 @@ class ConsoleWidget(QtGui.QPlainTextEdit): make sense for a console widget. """ cursor = self._get_end_cursor() - cursor.insertHtml(html) - - # After appending HTML, the text document "remembers" the current - # formatting, which means that subsequent calls to 'appendPlainText' - # will be formatted similarly, a behavior that we do not want. To - # prevent this, we make sure that the last character has no formatting. - cursor.movePosition(QtGui.QTextCursor.Left, - QtGui.QTextCursor.KeepAnchor) - if cursor.selection().toPlainText().trimmed().isEmpty(): - # If the last character is whitespace, it doesn't matter how it's - # formatted, so just clear the formatting. - cursor.setCharFormat(QtGui.QTextCharFormat()) - else: - # Otherwise, add an unformatted space. - cursor.movePosition(QtGui.QTextCursor.Right) - cursor.insertText(' ', QtGui.QTextCharFormat()) + self._insert_html(cursor, html) def appendPlainText(self, text): """ Reimplemented to not append text as a new paragraph, which doesn't @@ -712,6 +697,27 @@ class ConsoleWidget(QtGui.QPlainTextEdit): cursor.setPosition(position) return cursor + def _insert_html(self, cursor, html): + """ Insert HTML using the specified cursor in such a way that future + formatting is unaffected. + """ + cursor.insertHtml(html) + + # After inserting HTML, the text document "remembers" the current + # formatting, which means that subsequent calls adding plain text + # will result in similar formatting, a behavior that we do not want. To + # prevent this, we make sure that the last character has no formatting. + cursor.movePosition(QtGui.QTextCursor.Left, + QtGui.QTextCursor.KeepAnchor) + if cursor.selection().toPlainText().trimmed().isEmpty(): + # If the last character is whitespace, it doesn't matter how it's + # formatted, so just clear the formatting. + cursor.setCharFormat(QtGui.QTextCharFormat()) + else: + # Otherwise, add an unformatted space. + cursor.movePosition(QtGui.QTextCursor.Right) + cursor.insertText(' ', QtGui.QTextCharFormat()) + def _prompt_started(self): """ Called immediately after a new prompt is displayed. """ diff --git a/IPython/frontend/qt/console/ipython_widget.py b/IPython/frontend/qt/console/ipython_widget.py index 0fa6e85..5db2a3d 100644 --- a/IPython/frontend/qt/console/ipython_widget.py +++ b/IPython/frontend/qt/console/ipython_widget.py @@ -30,6 +30,10 @@ class IPythonWidget(FrontendWidget): .out-prompt-number { color: red; font-weight: bold; } """ + # Default prompts. + in_prompt = '
In [%i]: ' + out_prompt = 'Out[%i]: ' + #--------------------------------------------------------------------------- # 'QObject' interface #--------------------------------------------------------------------------- @@ -38,6 +42,7 @@ class IPythonWidget(FrontendWidget): super(IPythonWidget, self).__init__(parent) # Initialize protected variables. + self._previous_prompt_blocks = [] self._prompt_count = 0 # Set a default stylesheet. @@ -64,17 +69,29 @@ class IPythonWidget(FrontendWidget): def _show_interpreter_prompt(self): """ Reimplemented for IPython-style prompts. """ + # Update old prompt numbers if necessary. + previous_prompt_number = self._prompt_count + if previous_prompt_number != self._prompt_count: + for i, (block, length) in enumerate(self._previous_prompt_blocks): + if block.isValid(): + cursor = QtGui.QTextCursor(block) + cursor.movePosition(QtGui.QTextCursor.Right, + QtGui.QTextCursor.KeepAnchor, length-1) + if i == 0: + prompt = self._make_in_prompt(previous_prompt_number) + else: + prompt = self._make_out_prompt(previous_prompt_number) + self._insert_html(cursor, prompt) + self._previous_prompt_blocks = [] + + # Show a new prompt. self._prompt_count += 1 - prompt_template = '%s' - prompt_body = '
In [%i]: ' - prompt = (prompt_template % prompt_body) % self._prompt_count - self._show_prompt(prompt, html=True) + self._show_prompt(self._make_in_prompt(self._prompt_count), html=True) + self._save_prompt_block() # Update continuation prompt to reflect (possibly) new prompt length. - cont_prompt_chars = '...: ' - space_count = len(self._prompt.lstrip()) - len(cont_prompt_chars) - cont_prompt_body = ' ' * space_count + cont_prompt_chars - self._continuation_prompt_html = prompt_template % cont_prompt_body + self._set_continuation_prompt( + self._make_continuation_prompt(self._prompt), html=True) #------ Signal handlers ---------------------------------------------------- @@ -96,10 +113,9 @@ class IPythonWidget(FrontendWidget): def _handle_pyout(self, omsg): """ Reimplemented for IPython-style "display hook". """ - prompt_template = '%s' - prompt_body = 'Out[%i]: ' - prompt = (prompt_template % prompt_body) % self._prompt_count - self.appendHtml(prompt) + self.appendHtml(self._make_out_prompt(self._prompt_count)) + self._save_prompt_block() + self.appendPlainText(omsg['content']['data'] + '\n') #--------------------------------------------------------------------------- @@ -135,6 +151,38 @@ class IPythonWidget(FrontendWidget): else: self._highlighter.set_style(syntax_style) + #--------------------------------------------------------------------------- + # 'IPythonWidget' protected interface + #--------------------------------------------------------------------------- + + def _make_in_prompt(self, number): + """ Given a prompt number, returns an HTML In prompt. + """ + body = self.in_prompt % number + return '%s' % body + + def _make_continuation_prompt(self, prompt): + """ Given a plain text version of an In prompt, returns an HTML + continuation prompt. + """ + end_chars = '...: ' + space_count = len(prompt.lstrip('\n')) - len(end_chars) + body = ' ' * space_count + end_chars + return '%s' % body + + def _make_out_prompt(self, number): + """ Given a prompt number, returns an HTML Out prompt. + """ + body = self.out_prompt % number + return '%s' % body + + def _save_prompt_block(self): + """ Assuming a prompt has just been written at the end of the buffer, + store the QTextBlock that contains it and its length. + """ + block = self.document().lastBlock() + self._previous_prompt_blocks.append((block, block.length())) + if __name__ == '__main__': from IPython.frontend.qt.kernelmanager import QtKernelManager