##// END OF EJS Templates
Merge pull request #526 from epatters/append-before-prompt...
Evan Patterson -
r4065:889e008b merge
parent child Browse files
Show More
@@ -65,13 +65,16 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
65 65 """
66 66 )
67 67 gui_completion = Bool(False, config=True,
68 help="Use a list widget instead of plain text output for tab completion."
68 help="""
69 Use a list widget instead of plain text output for tab completion.
70 """
69 71 )
70 72 # NOTE: this value can only be specified during initialization.
71 73 kind = Enum(['plain', 'rich'], default_value='plain', config=True,
72 74 help="""
73 The type of underlying text widget to use. Valid values are 'plain', which
74 specifies a QPlainTextEdit, and 'rich', which specifies a QTextEdit.
75 The type of underlying text widget to use. Valid values are 'plain',
76 which specifies a QPlainTextEdit, and 'rich', which specifies a
77 QTextEdit.
75 78 """
76 79 )
77 80 # NOTE: this value can only be specified during initialization.
@@ -84,7 +87,8 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
84 87 'hsplit' : When paging is requested, the widget is split
85 88 horizontally. The top pane contains the console, and the
86 89 bottom pane contains the paged text.
87 'vsplit' : Similar to 'hsplit', except that a vertical splitter used.
90 'vsplit' : Similar to 'hsplit', except that a vertical splitter
91 used.
88 92 'custom' : No action is taken by the widget beyond emitting a
89 93 'custom_page_requested(str)' signal.
90 94 'none' : The text is written directly to the console.
@@ -195,6 +199,7 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
195 199
196 200 # Initialize protected variables. Some variables contain useful state
197 201 # information for subclasses; they should be considered read-only.
202 self._append_before_prompt_pos = 0
198 203 self._ansi_processor = QtAnsiCodeProcessor()
199 204 self._completion_widget = CompletionWidget(self._control)
200 205 self._continuation_prompt = '> '
@@ -507,7 +512,7 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
507 512 """
508 513 self._html_exporter.export()
509 514
510 def _get_input_buffer(self):
515 def _get_input_buffer(self, force=False):
511 516 """ The text that the user has entered entered at the current prompt.
512 517
513 518 If the console is currently executing, the text that is executing will
@@ -515,7 +520,7 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
515 520 """
516 521 # If we're executing, the input buffer may not even exist anymore due to
517 522 # the limit imposed by 'buffer_size'. Therefore, we store it.
518 if self._executing:
523 if self._executing and not force:
519 524 return self._input_buffer_executing
520 525
521 526 cursor = self._get_end_cursor()
@@ -718,36 +723,47 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
718 723 # 'ConsoleWidget' protected interface
719 724 #--------------------------------------------------------------------------
720 725
721 def _append_html(self, html):
722 """ Appends html at the end of the console buffer.
726 def _append_custom(self, insert, input, before_prompt=False):
727 """ A low-level method for appending content to the end of the buffer.
728
729 If 'before_prompt' is enabled, the content will be inserted before the
730 current prompt, if there is one.
723 731 """
724 cursor = self._get_end_cursor()
725 self._insert_html(cursor, html)
732 # Determine where to insert the content.
733 cursor = self._control.textCursor()
734 if before_prompt and not self._executing:
735 cursor.setPosition(self._append_before_prompt_pos)
736 else:
737 cursor.movePosition(QtGui.QTextCursor.End)
738 start_pos = cursor.position()
726 739
727 def _append_html_fetching_plain_text(self, html):
728 """ Appends 'html', then returns the plain text version of it.
729 """
730 cursor = self._get_end_cursor()
731 return self._insert_html_fetching_plain_text(cursor, html)
740 # Perform the insertion.
741 result = insert(cursor, input)
742
743 # Adjust the prompt position if we have inserted before it. This is safe
744 # because buffer truncation is disabled when not executing.
745 if before_prompt and not self._executing:
746 diff = cursor.position() - start_pos
747 self._append_before_prompt_pos += diff
748 self._prompt_pos += diff
749
750 return result
732 751
733 def _append_plain_text(self, text):
734 """ Appends plain text at the end of the console buffer, processing
735 ANSI codes if enabled.
752 def _append_html(self, html, before_prompt=False):
753 """ Appends HTML at the end of the console buffer.
736 754 """
737 cursor = self._get_end_cursor()
738 self._insert_plain_text(cursor, text)
755 self._append_custom(self._insert_html, html, before_prompt)
739 756
740 def _append_plain_text_keeping_prompt(self, text):
741 """ Writes 'text' after the current prompt, then restores the old prompt
742 with its old input buffer.
757 def _append_html_fetching_plain_text(self, html, before_prompt=False):
758 """ Appends HTML, then returns the plain text version of it.
743 759 """
744 input_buffer = self.input_buffer
745 self._append_plain_text('\n')
746 self._prompt_finished()
760 return self._append_custom(self._insert_html_fetching_plain_text,
761 html, before_prompt)
747 762
748 self._append_plain_text(text)
749 self._show_prompt()
750 self.input_buffer = input_buffer
763 def _append_plain_text(self, text, before_prompt=False):
764 """ Appends plain text, processing ANSI codes if enabled.
765 """
766 self._append_custom(self._insert_plain_text, text, before_prompt)
751 767
752 768 def _cancel_text_completion(self):
753 769 """ If text completion is progress, cancel it.
@@ -1616,7 +1632,8 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
1616 1632 self._control.setReadOnly(False)
1617 1633 self._control.setAttribute(QtCore.Qt.WA_InputMethodEnabled, True)
1618 1634
1619 self._executing = False
1635 if not self._reading:
1636 self._executing = False
1620 1637 self._prompt_started_hook()
1621 1638
1622 1639 # If the input buffer has changed while executing, load it.
@@ -1659,11 +1676,11 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
1659 1676 self._reading_callback = None
1660 1677 while self._reading:
1661 1678 QtCore.QCoreApplication.processEvents()
1662 return self.input_buffer.rstrip('\n')
1679 return self._get_input_buffer(force=True).rstrip('\n')
1663 1680
1664 1681 else:
1665 1682 self._reading_callback = lambda: \
1666 callback(self.input_buffer.rstrip('\n'))
1683 callback(self._get_input_buffer(force=True).rstrip('\n'))
1667 1684
1668 1685 def _set_continuation_prompt(self, prompt, html=False):
1669 1686 """ Sets the continuation prompt.
@@ -1716,14 +1733,16 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
1716 1733 If set, a new line will be written before showing the prompt if
1717 1734 there is not already a newline at the end of the buffer.
1718 1735 """
1736 # Save the current end position to support _append*(before_prompt=True).
1737 cursor = self._get_end_cursor()
1738 self._append_before_prompt_pos = cursor.position()
1739
1719 1740 # Insert a preliminary newline, if necessary.
1720 if newline:
1721 cursor = self._get_end_cursor()
1722 if cursor.position() > 0:
1723 cursor.movePosition(QtGui.QTextCursor.Left,
1724 QtGui.QTextCursor.KeepAnchor)
1725 if cursor.selection().toPlainText() != '\n':
1726 self._append_plain_text('\n')
1741 if newline and cursor.position() > 0:
1742 cursor.movePosition(QtGui.QTextCursor.Left,
1743 QtGui.QTextCursor.KeepAnchor)
1744 if cursor.selection().toPlainText() != '\n':
1745 self._append_plain_text('\n')
1727 1746
1728 1747 # Write the prompt.
1729 1748 self._append_plain_text(self._prompt_sep)
@@ -22,8 +22,7 b' from pygments_highlighter import PygmentsHighlighter'
22 22
23 23
24 24 class FrontendHighlighter(PygmentsHighlighter):
25 """ A PygmentsHighlighter that can be turned on and off and that ignores
26 prompts.
25 """ A PygmentsHighlighter that understands and ignores prompts.
27 26 """
28 27
29 28 def __init__(self, frontend):
@@ -50,14 +49,12 b' class FrontendHighlighter(PygmentsHighlighter):'
50 49 else:
51 50 prompt = self._frontend._continuation_prompt
52 51
53 # Don't highlight the part of the string that contains the prompt.
52 # Only highlight if we can identify a prompt, but make sure not to
53 # highlight the prompt.
54 54 if string.startswith(prompt):
55 55 self._current_offset = len(prompt)
56 56 string = string[len(prompt):]
57 else:
58 self._current_offset = 0
59
60 PygmentsHighlighter.highlightBlock(self, string)
57 super(FrontendHighlighter, self).highlightBlock(string)
61 58
62 59 def rehighlightBlock(self, block):
63 60 """ Reimplemented to temporarily enable highlighting if disabled.
@@ -71,7 +68,7 b' class FrontendHighlighter(PygmentsHighlighter):'
71 68 """ Reimplemented to highlight selectively.
72 69 """
73 70 start += self._current_offset
74 PygmentsHighlighter.setFormat(self, start, count, format)
71 super(FrontendHighlighter, self).setFormat(start, count, format)
75 72
76 73
77 74 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
@@ -372,14 +369,8 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
372 369 """ Handle display hook output.
373 370 """
374 371 if not self._hidden and self._is_from_this_session(msg):
375 data = msg['content']['data']
376 if isinstance(data, basestring):
377 # plaintext data from pure Python kernel
378 text = data
379 else:
380 # formatted output from DisplayFormatter (IPython kernel)
381 text = data.get('text/plain', '')
382 self._append_plain_text(text + '\n')
372 text = msg['content']['data']
373 self._append_plain_text(text + '\n', before_prompt=True)
383 374
384 375 def _handle_stream(self, msg):
385 376 """ Handle stdout, stderr, and stdin.
@@ -390,7 +381,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
390 381 # widget's tab width.
391 382 text = msg['content']['data'].expandtabs(8)
392 383
393 self._append_plain_text(text)
384 self._append_plain_text(text, before_prompt=True)
394 385 self._control.moveCursor(QtGui.QTextCursor.End)
395 386
396 387 def _handle_shutdown_reply(self, msg):
@@ -62,16 +62,16 b' class IPythonWidget(FrontendWidget):'
62 62 editor = Unicode(default_editor, config=True,
63 63 help="""
64 64 A command for invoking a system text editor. If the string contains a
65 {filename} format specifier, it will be used. Otherwise, the filename will
66 be appended to the end the command.
65 {filename} format specifier, it will be used. Otherwise, the filename
66 will be appended to the end the command.
67 67 """)
68 68
69 69 editor_line = Unicode(config=True,
70 70 help="""
71 71 The editor command to use when a specific line number is requested. The
72 72 string should contain two format specifiers: {line} and {filename}. If
73 this parameter is not specified, the line number option to the %edit magic
74 will be ignored.
73 this parameter is not specified, the line number option to the %edit
74 magic will be ignored.
75 75 """)
76 76
77 77 style_sheet = Unicode(config=True,
@@ -85,8 +85,9 b' class IPythonWidget(FrontendWidget):'
85 85
86 86 syntax_style = Str(config=True,
87 87 help="""
88 If not empty, use this Pygments style for syntax highlighting. Otherwise,
89 the style sheet is queried for Pygments style information.
88 If not empty, use this Pygments style for syntax highlighting.
89 Otherwise, the style sheet is queried for Pygments style
90 information.
90 91 """)
91 92
92 93 # Prompts.
@@ -187,20 +188,20 b' class IPythonWidget(FrontendWidget):'
187 188 prompt_number = content['execution_count']
188 189 data = content['data']
189 190 if data.has_key('text/html'):
190 self._append_plain_text(self.output_sep)
191 self._append_html(self._make_out_prompt(prompt_number))
191 self._append_plain_text(self.output_sep, True)
192 self._append_html(self._make_out_prompt(prompt_number), True)
192 193 html = data['text/html']
193 self._append_plain_text('\n')
194 self._append_html(html + self.output_sep2)
194 self._append_plain_text('\n', True)
195 self._append_html(html + self.output_sep2, True)
195 196 elif data.has_key('text/plain'):
196 self._append_plain_text(self.output_sep)
197 self._append_html(self._make_out_prompt(prompt_number))
197 self._append_plain_text(self.output_sep, True)
198 self._append_html(self._make_out_prompt(prompt_number), True)
198 199 text = data['text/plain']
199 200 # If the repr is multiline, make sure we start on a new line,
200 201 # so that its lines are aligned.
201 202 if "\n" in text and not self.output_sep.endswith("\n"):
202 self._append_plain_text('\n')
203 self._append_plain_text(text + self.output_sep2)
203 self._append_plain_text('\n', True)
204 self._append_plain_text(text + self.output_sep2, True)
204 205
205 206 def _handle_display_data(self, msg):
206 207 """ The base handler for the ``display_data`` message.
@@ -216,19 +217,19 b' class IPythonWidget(FrontendWidget):'
216 217 # representation.
217 218 if data.has_key('text/html'):
218 219 html = data['text/html']
219 self._append_html(html)
220 self._append_html(html, True)
220 221 elif data.has_key('text/plain'):
221 222 text = data['text/plain']
222 self._append_plain_text(text)
223 self._append_plain_text(text, True)
223 224 # This newline seems to be needed for text and html output.
224 self._append_plain_text(u'\n')
225 self._append_plain_text(u'\n', True)
225 226
226 227 def _started_channels(self):
227 228 """ Reimplemented to make a history request.
228 229 """
229 230 super(IPythonWidget, self)._started_channels()
230 self.kernel_manager.shell_channel.history(hist_access_type='tail', n=1000)
231
231 self.kernel_manager.shell_channel.history(hist_access_type='tail',
232 n=1000)
232 233 #---------------------------------------------------------------------------
233 234 # 'ConsoleWidget' public interface
234 235 #---------------------------------------------------------------------------
@@ -413,8 +414,8 b' class IPythonWidget(FrontendWidget):'
413 414 self.custom_edit_requested.emit(filename, line)
414 415 elif not self.editor:
415 416 self._append_plain_text('No default editor available.\n'
416 'Specify a GUI text editor in the `IPythonWidget.editor` configurable\n'
417 'to enable the %edit magic')
417 'Specify a GUI text editor in the `IPythonWidget.editor` '
418 'configurable to enable the %edit magic')
418 419 else:
419 420 try:
420 421 filename = '"%s"' % filename
@@ -74,20 +74,17 b' class RichIPythonWidget(IPythonWidget):'
74 74 prompt_number = content['execution_count']
75 75 data = content['data']
76 76 if data.has_key('image/svg+xml'):
77 self._append_plain_text(self.output_sep)
78 self._append_html(self._make_out_prompt(prompt_number))
79 # TODO: try/except this call.
80 self._append_svg(data['image/svg+xml'])
81 self._append_html(self.output_sep2)
77 self._append_plain_text(self.output_sep, True)
78 self._append_html(self._make_out_prompt(prompt_number), True)
79 self._append_svg(data['image/svg+xml'], True)
80 self._append_html(self.output_sep2, True)
82 81 elif data.has_key('image/png'):
83 self._append_plain_text(self.output_sep)
84 self._append_html(self._make_out_prompt(prompt_number))
82 self._append_plain_text(self.output_sep, True)
83 self._append_html(self._make_out_prompt(prompt_number), True)
85 84 # This helps the output to look nice.
86 self._append_plain_text('\n')
87 # TODO: try/except these calls
88 png = decodestring(data['image/png'])
89 self._append_png(png)
90 self._append_html(self.output_sep2)
85 self._append_plain_text('\n', True)
86 self._append_png(decodestring(data['image/png']), True)
87 self._append_html(self.output_sep2, True)
91 88 else:
92 89 # Default back to the plain text representation.
93 90 return super(RichIPythonWidget, self)._handle_pyout(msg)
@@ -103,14 +100,12 b' class RichIPythonWidget(IPythonWidget):'
103 100 # FIXME: Is this the right ordering of things to try?
104 101 if data.has_key('image/svg+xml'):
105 102 svg = data['image/svg+xml']
106 # TODO: try/except this call.
107 self._append_svg(svg)
103 self._append_svg(svg, True)
108 104 elif data.has_key('image/png'):
109 # TODO: try/except these calls
110 105 # PNG data is base64 encoded as it passes over the network
111 106 # in a JSON structure so we decode it.
112 107 png = decodestring(data['image/png'])
113 self._append_png(png)
108 self._append_png(png, True)
114 109 else:
115 110 # Default back to the plain text representation.
116 111 return super(RichIPythonWidget, self)._handle_display_data(msg)
@@ -119,35 +114,15 b' class RichIPythonWidget(IPythonWidget):'
119 114 # 'RichIPythonWidget' protected interface
120 115 #---------------------------------------------------------------------------
121 116
122 def _append_svg(self, svg):
123 """ Append raw svg data to the widget.
117 def _append_png(self, png, before_prompt=False):
118 """ Append raw PNG data to the widget.
124 119 """
125 try:
126 image = svg_to_image(svg)
127 except ValueError:
128 self._append_plain_text('Received invalid plot data.')
129 else:
130 format = self._add_image(image)
131 self._name_to_svg_map[format.name()] = svg
132 cursor = self._get_end_cursor()
133 cursor.insertBlock()
134 cursor.insertImage(format)
135 cursor.insertBlock()
120 self._append_custom(self._insert_png, png, before_prompt)
136 121
137 def _append_png(self, png):
138 """ Append raw svg data to the widget.
122 def _append_svg(self, svg, before_prompt=False):
123 """ Append raw SVG data to the widget.
139 124 """
140 try:
141 image = QtGui.QImage()
142 image.loadFromData(png, 'PNG')
143 except ValueError:
144 self._append_plain_text('Received invalid plot data.')
145 else:
146 format = self._add_image(image)
147 cursor = self._get_end_cursor()
148 cursor.insertBlock()
149 cursor.insertImage(format)
150 cursor.insertBlock()
125 self._append_custom(self._insert_svg, svg, before_prompt)
151 126
152 127 def _add_image(self, image):
153 128 """ Adds the specified QImage to the document and returns a
@@ -236,6 +211,34 b' class RichIPythonWidget(IPythonWidget):'
236 211 else:
237 212 return '<b>Unrecognized image format</b>'
238 213
214 def _insert_png(self, cursor, png):
215 """ Insert raw PNG data into the widget.
216 """
217 try:
218 image = QtGui.QImage()
219 image.loadFromData(png, 'PNG')
220 except ValueError:
221 self._insert_plain_text(cursor, 'Received invalid PNG data.')
222 else:
223 format = self._add_image(image)
224 cursor.insertBlock()
225 cursor.insertImage(format)
226 cursor.insertBlock()
227
228 def _insert_svg(self, cursor, svg):
229 """ Insert raw SVG data into the widet.
230 """
231 try:
232 image = svg_to_image(svg)
233 except ValueError:
234 self._insert_plain_text(cursor, 'Received invalid SVG data.')
235 else:
236 format = self._add_image(image)
237 self._name_to_svg_map[format.name()] = svg
238 cursor.insertBlock()
239 cursor.insertImage(format)
240 cursor.insertBlock()
241
239 242 def _save_image(self, name, format='PNG'):
240 243 """ Shows a save dialog for the ImageResource with 'name'.
241 244 """
General Comments 0
You need to be logged in to leave comments. Login now