Show More
@@ -0,0 +1,101 b'' | |||||
|
1 | """ Provides bracket matching for Q[Plain]TextEdit widgets. | |||
|
2 | """ | |||
|
3 | ||||
|
4 | # System library imports | |||
|
5 | from PyQt4 import QtCore, QtGui | |||
|
6 | ||||
|
7 | ||||
|
8 | class BracketMatcher(QtCore.QObject): | |||
|
9 | """ Matches square brackets, braces, and parentheses based on cursor | |||
|
10 | position. | |||
|
11 | """ | |||
|
12 | ||||
|
13 | # Protected class variables. | |||
|
14 | _opening_map = { '(':')', '{':'}', '[':']' } | |||
|
15 | _closing_map = { ')':'(', '}':'{', ']':'[' } | |||
|
16 | ||||
|
17 | #-------------------------------------------------------------------------- | |||
|
18 | # 'QObject' interface | |||
|
19 | #-------------------------------------------------------------------------- | |||
|
20 | ||||
|
21 | def __init__(self, parent): | |||
|
22 | """ Create a call tip manager that is attached to the specified Qt | |||
|
23 | text edit widget. | |||
|
24 | """ | |||
|
25 | assert isinstance(parent, (QtGui.QTextEdit, QtGui.QPlainTextEdit)) | |||
|
26 | QtCore.QObject.__init__(self, parent) | |||
|
27 | ||||
|
28 | # The format to apply to matching brackets. | |||
|
29 | self.format = QtGui.QTextCharFormat() | |||
|
30 | self.format.setBackground(QtGui.QColor('silver')) | |||
|
31 | ||||
|
32 | parent.cursorPositionChanged.connect(self._cursor_position_changed) | |||
|
33 | ||||
|
34 | #-------------------------------------------------------------------------- | |||
|
35 | # Protected interface | |||
|
36 | #-------------------------------------------------------------------------- | |||
|
37 | ||||
|
38 | def _find_match(self, position): | |||
|
39 | """ Given a valid position in the text document, try to find the | |||
|
40 | position of the matching bracket. Returns -1 if unsuccessful. | |||
|
41 | """ | |||
|
42 | # Decide what character to search for and what direction to search in. | |||
|
43 | document = self.parent().document() | |||
|
44 | qchar = document.characterAt(position) | |||
|
45 | start_char = qchar.toAscii() | |||
|
46 | search_char = self._opening_map.get(start_char) | |||
|
47 | if search_char: | |||
|
48 | increment = 1 | |||
|
49 | else: | |||
|
50 | search_char = self._closing_map.get(start_char) | |||
|
51 | if search_char: | |||
|
52 | increment = -1 | |||
|
53 | else: | |||
|
54 | return -1 | |||
|
55 | ||||
|
56 | # Search for the character. | |||
|
57 | depth = 0 | |||
|
58 | while position >= 0 and position < document.characterCount(): | |||
|
59 | char = qchar.toAscii() | |||
|
60 | if char == start_char: | |||
|
61 | depth += 1 | |||
|
62 | elif char == search_char: | |||
|
63 | depth -= 1 | |||
|
64 | if depth == 0: | |||
|
65 | break | |||
|
66 | position += increment | |||
|
67 | qchar = document.characterAt(position) | |||
|
68 | else: | |||
|
69 | position = -1 | |||
|
70 | return position | |||
|
71 | ||||
|
72 | def _selection_for_character(self, position): | |||
|
73 | """ Convenience method for selecting a character. | |||
|
74 | """ | |||
|
75 | selection = QtGui.QTextEdit.ExtraSelection() | |||
|
76 | cursor = self.parent().textCursor() | |||
|
77 | cursor.setPosition(position) | |||
|
78 | cursor.movePosition(QtGui.QTextCursor.NextCharacter, | |||
|
79 | QtGui.QTextCursor.KeepAnchor) | |||
|
80 | selection.cursor = cursor | |||
|
81 | selection.format = self.format | |||
|
82 | return selection | |||
|
83 | ||||
|
84 | #------ Signal handlers ---------------------------------------------------- | |||
|
85 | ||||
|
86 | def _cursor_position_changed(self): | |||
|
87 | """ Updates the document formatting based on the new cursor position. | |||
|
88 | """ | |||
|
89 | # Clear out the old formatting. | |||
|
90 | text_edit = self.parent() | |||
|
91 | text_edit.setExtraSelections([]) | |||
|
92 | ||||
|
93 | # Attempt to match a bracket for the new cursor position. | |||
|
94 | cursor = text_edit.textCursor() | |||
|
95 | if not cursor.hasSelection(): | |||
|
96 | position = cursor.position() - 1 | |||
|
97 | match_position = self._find_match(position) | |||
|
98 | if match_position != -1: | |||
|
99 | extra_selections = [ self._selection_for_character(pos) | |||
|
100 | for pos in (position, match_position) ] | |||
|
101 | text_edit.setExtraSelections(extra_selections) |
@@ -35,14 +35,14 b" def magic_history(self, parameter_s = ''):" | |||||
35 | for making documentation, and in conjunction with -o, for producing |
|
35 | for making documentation, and in conjunction with -o, for producing | |
36 | doctest-ready output. |
|
36 | doctest-ready output. | |
37 |
|
37 | |||
38 |
- |
|
38 | -r: (default) print the 'raw' history, i.e. the actual commands you typed. | |
39 | IPython filters your input and converts it all into valid Python source |
|
|||
40 | before executing it (things like magics or aliases are turned into |
|
|||
41 | function calls, for example). With this option, you'll see the native |
|
|||
42 | history instead of the user-entered version: '%cd /' will be seen as |
|
|||
43 | '_ip.magic("%cd /")' instead of '%cd /'. |
|
|||
44 |
|
39 | |||
45 |
- |
|
40 | -t: print the 'translated' history, as IPython understands it. IPython | |
|
41 | filters your input and converts it all into valid Python source before | |||
|
42 | executing it (things like magics or aliases are turned into function | |||
|
43 | calls, for example). With this option, you'll see the native history | |||
|
44 | instead of the user-entered version: '%cd /' will be seen as | |||
|
45 | 'get_ipython().magic("%cd /")' instead of '%cd /'. | |||
46 |
|
46 | |||
47 | -g: treat the arg as a pattern to grep for in (full) history. |
|
47 | -g: treat the arg as a pattern to grep for in (full) history. | |
48 | This includes the "shadow history" (almost all commands ever written). |
|
48 | This includes the "shadow history" (almost all commands ever written). | |
@@ -80,7 +80,8 b" def magic_history(self, parameter_s = ''):" | |||||
80 | elif 'r' in opts: |
|
80 | elif 'r' in opts: | |
81 | input_hist = self.input_hist_raw |
|
81 | input_hist = self.input_hist_raw | |
82 | else: |
|
82 | else: | |
83 | input_hist = self.input_hist |
|
83 | # Raw history is the default | |
|
84 | input_hist = self.input_hist_raw | |||
84 |
|
85 | |||
85 | default_length = 40 |
|
86 | default_length = 40 | |
86 | pattern = None |
|
87 | pattern = None |
@@ -1828,7 +1828,6 b' class InteractiveShell(Configurable, Magic):' | |||||
1828 | self.resetbuffer() |
|
1828 | self.resetbuffer() | |
1829 | lines = lines.splitlines() |
|
1829 | lines = lines.splitlines() | |
1830 | more = 0 |
|
1830 | more = 0 | |
1831 |
|
||||
1832 | with nested(self.builtin_trap, self.display_trap): |
|
1831 | with nested(self.builtin_trap, self.display_trap): | |
1833 | for line in lines: |
|
1832 | for line in lines: | |
1834 | # skip blank lines so we don't mess up the prompt counter, but do |
|
1833 | # skip blank lines so we don't mess up the prompt counter, but do | |
@@ -1837,8 +1836,9 b' class InteractiveShell(Configurable, Magic):' | |||||
1837 |
|
1836 | |||
1838 | if line or more: |
|
1837 | if line or more: | |
1839 | # push to raw history, so hist line numbers stay in sync |
|
1838 | # push to raw history, so hist line numbers stay in sync | |
1840 |
self.input_hist_raw.append( |
|
1839 | self.input_hist_raw.append(line + '\n') | |
1841 |
prefiltered = self.prefilter_manager.prefilter_lines(line, |
|
1840 | prefiltered = self.prefilter_manager.prefilter_lines(line, | |
|
1841 | more) | |||
1842 | more = self.push_line(prefiltered) |
|
1842 | more = self.push_line(prefiltered) | |
1843 | # IPython's runsource returns None if there was an error |
|
1843 | # IPython's runsource returns None if there was an error | |
1844 | # compiling the code. This allows us to stop processing right |
|
1844 | # compiling the code. This allows us to stop processing right |
@@ -101,7 +101,7 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||||
101 | QtCore.Qt.Key_N : QtCore.Qt.Key_Down, |
|
101 | QtCore.Qt.Key_N : QtCore.Qt.Key_Down, | |
102 | QtCore.Qt.Key_D : QtCore.Qt.Key_Delete, } |
|
102 | QtCore.Qt.Key_D : QtCore.Qt.Key_Delete, } | |
103 | _shortcuts = set(_ctrl_down_remap.keys() + |
|
103 | _shortcuts = set(_ctrl_down_remap.keys() + | |
104 | [ QtCore.Qt.Key_C, QtCore.Qt.Key_V ]) |
|
104 | [ QtCore.Qt.Key_C, QtCore.Qt.Key_V, QtCore.Qt.Key_O ]) | |
105 |
|
105 | |||
106 | #--------------------------------------------------------------------------- |
|
106 | #--------------------------------------------------------------------------- | |
107 | # 'QObject' interface |
|
107 | # 'QObject' interface | |
@@ -164,18 +164,26 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||||
164 |
|
164 | |||
165 | def eventFilter(self, obj, event): |
|
165 | def eventFilter(self, obj, event): | |
166 | """ Reimplemented to ensure a console-like behavior in the underlying |
|
166 | """ Reimplemented to ensure a console-like behavior in the underlying | |
167 | text widget. |
|
167 | text widgets. | |
168 | """ |
|
168 | """ | |
169 | # Re-map keys for all filtered widgets. |
|
|||
170 | etype = event.type() |
|
169 | etype = event.type() | |
171 |
if etype == QtCore.QEvent.KeyPress |
|
170 | if etype == QtCore.QEvent.KeyPress: | |
172 | self._control_key_down(event.modifiers()) and \ |
|
171 | ||
173 | event.key() in self._ctrl_down_remap: |
|
172 | # Re-map keys for all filtered widgets. | |
174 | new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, |
|
173 | key = event.key() | |
175 | self._ctrl_down_remap[event.key()], |
|
174 | if self._control_key_down(event.modifiers()) and \ | |
176 | QtCore.Qt.NoModifier) |
|
175 | key in self._ctrl_down_remap: | |
177 | QtGui.qApp.sendEvent(obj, new_event) |
|
176 | new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, | |
178 | return True |
|
177 | self._ctrl_down_remap[key], | |
|
178 | QtCore.Qt.NoModifier) | |||
|
179 | QtGui.qApp.sendEvent(obj, new_event) | |||
|
180 | return True | |||
|
181 | ||||
|
182 | elif obj == self._control: | |||
|
183 | return self._event_filter_console_keypress(event) | |||
|
184 | ||||
|
185 | elif obj == self._page_control: | |||
|
186 | return self._event_filter_page_keypress(event) | |||
179 |
|
187 | |||
180 | # Override shortucts for all filtered widgets. Note that on Mac OS it is |
|
188 | # Override shortucts for all filtered widgets. Note that on Mac OS it is | |
181 | # always unnecessary to override shortcuts, hence the check below (users |
|
189 | # always unnecessary to override shortcuts, hence the check below (users | |
@@ -187,12 +195,6 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||||
187 | event.accept() |
|
195 | event.accept() | |
188 | return False |
|
196 | return False | |
189 |
|
197 | |||
190 | elif etype == QtCore.QEvent.KeyPress: |
|
|||
191 | if obj == self._control: |
|
|||
192 | return self._event_filter_console_keypress(event) |
|
|||
193 | elif obj == self._page_control: |
|
|||
194 | return self._event_filter_page_keypress(event) |
|
|||
195 |
|
||||
196 | return super(ConsoleWidget, self).eventFilter(obj, event) |
|
198 | return super(ConsoleWidget, self).eventFilter(obj, event) | |
197 |
|
199 | |||
198 | #--------------------------------------------------------------------------- |
|
200 | #--------------------------------------------------------------------------- | |
@@ -334,12 +336,11 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||||
334 | else: |
|
336 | else: | |
335 | # Do this inside an edit block so continuation prompts are |
|
337 | # Do this inside an edit block so continuation prompts are | |
336 | # removed seamlessly via undo/redo. |
|
338 | # removed seamlessly via undo/redo. | |
337 |
cursor = self._ |
|
339 | cursor = self._get_end_cursor() | |
338 | cursor.beginEditBlock() |
|
340 | cursor.beginEditBlock() | |
339 |
|
341 | cursor.insertText('\n') | ||
340 | self._append_plain_text('\n') |
|
342 | self._insert_continuation_prompt(cursor) | |
341 | self._show_continuation_prompt() |
|
343 | self._control.moveCursor(QtGui.QTextCursor.End) | |
342 |
|
||||
343 | cursor.endEditBlock() |
|
344 | cursor.endEditBlock() | |
344 |
|
345 | |||
345 | return complete |
|
346 | return complete | |
@@ -431,9 +432,8 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||||
431 | """ Sets the font to the default fixed-width font for this platform. |
|
432 | """ Sets the font to the default fixed-width font for this platform. | |
432 | """ |
|
433 | """ | |
433 | # FIXME: font family and size should be configurable by the user. |
|
434 | # FIXME: font family and size should be configurable by the user. | |
434 |
|
||||
435 | if sys.platform == 'win32': |
|
435 | if sys.platform == 'win32': | |
436 |
# F |
|
436 | # FIXME: we should test whether Consolas is available and use it | |
437 | # first if it is. Consolas ships by default from Vista onwards, |
|
437 | # first if it is. Consolas ships by default from Vista onwards, | |
438 | # it's *vastly* more readable and prettier than Courier, and is |
|
438 | # it's *vastly* more readable and prettier than Courier, and is | |
439 | # often installed even on XP systems. So we should first check for |
|
439 | # often installed even on XP systems. So we should first check for | |
@@ -632,13 +632,32 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||||
632 | if self._in_buffer(position): |
|
632 | if self._in_buffer(position): | |
633 | cursor.movePosition(QtGui.QTextCursor.EndOfLine, |
|
633 | cursor.movePosition(QtGui.QTextCursor.EndOfLine, | |
634 | QtGui.QTextCursor.KeepAnchor) |
|
634 | QtGui.QTextCursor.KeepAnchor) | |
|
635 | if not cursor.hasSelection(): | |||
|
636 | # Line deletion (remove continuation prompt) | |||
|
637 | cursor.movePosition(QtGui.QTextCursor.NextBlock, | |||
|
638 | QtGui.QTextCursor.KeepAnchor) | |||
|
639 | cursor.movePosition(QtGui.QTextCursor.Right, | |||
|
640 | QtGui.QTextCursor.KeepAnchor, | |||
|
641 | len(self._continuation_prompt)) | |||
635 | cursor.removeSelectedText() |
|
642 | cursor.removeSelectedText() | |
636 | intercepted = True |
|
643 | intercepted = True | |
637 |
|
644 | |||
638 | elif key == QtCore.Qt.Key_L: |
|
645 | elif key == QtCore.Qt.Key_L: | |
|
646 | # It would be better to simply move the prompt block to the top | |||
|
647 | # of the control viewport. QPlainTextEdit has a private method | |||
|
648 | # to do this (setTopBlock), but it cannot be duplicated here | |||
|
649 | # because it requires access to the QTextControl that underlies | |||
|
650 | # both QPlainTextEdit and QTextEdit. In short, this can only be | |||
|
651 | # achieved by appending newlines after the prompt, which is a | |||
|
652 | # gigantic hack and likely to cause other problems. | |||
639 | self.clear() |
|
653 | self.clear() | |
640 | intercepted = True |
|
654 | intercepted = True | |
641 |
|
655 | |||
|
656 | elif key == QtCore.Qt.Key_O: | |||
|
657 | if self._page_control and self._page_control.isVisible(): | |||
|
658 | self._page_control.setFocus() | |||
|
659 | intercept = True | |||
|
660 | ||||
642 | elif key == QtCore.Qt.Key_X: |
|
661 | elif key == QtCore.Qt.Key_X: | |
643 | # FIXME: Instead of disabling cut completely, only allow it |
|
662 | # FIXME: Instead of disabling cut completely, only allow it | |
644 | # when safe. |
|
663 | # when safe. | |
@@ -669,16 +688,44 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||||
669 | cursor.removeSelectedText() |
|
688 | cursor.removeSelectedText() | |
670 | intercepted = True |
|
689 | intercepted = True | |
671 |
|
690 | |||
|
691 | elif key == QtCore.Qt.Key_Greater: | |||
|
692 | self._control.moveCursor(QtGui.QTextCursor.End) | |||
|
693 | intercepted = True | |||
|
694 | ||||
|
695 | elif key == QtCore.Qt.Key_Less: | |||
|
696 | self._control.setTextCursor(self._get_prompt_cursor()) | |||
|
697 | intercepted = True | |||
|
698 | ||||
672 | else: |
|
699 | else: | |
673 | if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter): |
|
700 | if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter): | |
674 | if self._reading: |
|
|||
675 | self._append_plain_text('\n') |
|
|||
676 | self._reading = False |
|
|||
677 | if self._reading_callback: |
|
|||
678 | self._reading_callback() |
|
|||
679 | elif not self._executing: |
|
|||
680 | self.execute(interactive=True) |
|
|||
681 | intercepted = True |
|
701 | intercepted = True | |
|
702 | if self._in_buffer(position): | |||
|
703 | if self._reading: | |||
|
704 | self._append_plain_text('\n') | |||
|
705 | self._reading = False | |||
|
706 | if self._reading_callback: | |||
|
707 | self._reading_callback() | |||
|
708 | ||||
|
709 | # If there is only whitespace after the cursor, execute. | |||
|
710 | # Otherwise, split the line with a continuation prompt. | |||
|
711 | elif not self._executing: | |||
|
712 | cursor.movePosition(QtGui.QTextCursor.End, | |||
|
713 | QtGui.QTextCursor.KeepAnchor) | |||
|
714 | if cursor.selectedText().trimmed().isEmpty(): | |||
|
715 | self.execute(interactive=True) | |||
|
716 | else: | |||
|
717 | cursor.beginEditBlock() | |||
|
718 | cursor.setPosition(position) | |||
|
719 | cursor.insertText('\n') | |||
|
720 | self._insert_continuation_prompt(cursor) | |||
|
721 | ||||
|
722 | # Ensure that the whole input buffer is visible. | |||
|
723 | # FIXME: This will not be usable if the input buffer | |||
|
724 | # is taller than the console widget. | |||
|
725 | self._control.moveCursor(QtGui.QTextCursor.End) | |||
|
726 | self._control.setTextCursor(cursor) | |||
|
727 | ||||
|
728 | cursor.endEditBlock() | |||
682 |
|
729 | |||
683 | elif key == QtCore.Qt.Key_Up: |
|
730 | elif key == QtCore.Qt.Key_Up: | |
684 | if self._reading or not self._up_pressed(): |
|
731 | if self._reading or not self._up_pressed(): | |
@@ -702,17 +749,20 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||||
702 | intercepted = not self._in_buffer(position - 1) |
|
749 | intercepted = not self._in_buffer(position - 1) | |
703 |
|
750 | |||
704 | elif key == QtCore.Qt.Key_Home: |
|
751 | elif key == QtCore.Qt.Key_Home: | |
705 | cursor.movePosition(QtGui.QTextCursor.StartOfBlock) |
|
|||
706 | start_line = cursor.blockNumber() |
|
752 | start_line = cursor.blockNumber() | |
707 | if start_line == self._get_prompt_cursor().blockNumber(): |
|
753 | if start_line == self._get_prompt_cursor().blockNumber(): | |
708 | start_pos = self._prompt_pos |
|
754 | start_pos = self._prompt_pos | |
709 | else: |
|
755 | else: | |
|
756 | cursor.movePosition(QtGui.QTextCursor.StartOfBlock, | |||
|
757 | QtGui.QTextCursor.KeepAnchor) | |||
710 | start_pos = cursor.position() |
|
758 | start_pos = cursor.position() | |
711 | start_pos += len(self._continuation_prompt) |
|
759 | start_pos += len(self._continuation_prompt) | |
|
760 | cursor.setPosition(position) | |||
712 | if shift_down and self._in_buffer(position): |
|
761 | if shift_down and self._in_buffer(position): | |
713 |
|
|
762 | cursor.setPosition(start_pos, QtGui.QTextCursor.KeepAnchor) | |
714 | else: |
|
763 | else: | |
715 |
|
|
764 | cursor.setPosition(start_pos) | |
|
765 | self._set_cursor(cursor) | |||
716 | intercepted = True |
|
766 | intercepted = True | |
717 |
|
767 | |||
718 | elif key == QtCore.Qt.Key_Backspace: |
|
768 | elif key == QtCore.Qt.Key_Backspace: | |
@@ -769,8 +819,24 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||||
769 | interface. |
|
819 | interface. | |
770 | """ |
|
820 | """ | |
771 | key = event.key() |
|
821 | key = event.key() | |
|
822 | ctrl_down = self._control_key_down(event.modifiers()) | |||
|
823 | alt_down = event.modifiers() & QtCore.Qt.AltModifier | |||
|
824 | ||||
|
825 | if ctrl_down: | |||
|
826 | if key == QtCore.Qt.Key_O: | |||
|
827 | self._control.setFocus() | |||
|
828 | intercept = True | |||
|
829 | ||||
|
830 | elif alt_down: | |||
|
831 | if key == QtCore.Qt.Key_Greater: | |||
|
832 | self._page_control.moveCursor(QtGui.QTextCursor.End) | |||
|
833 | intercepted = True | |||
|
834 | ||||
|
835 | elif key == QtCore.Qt.Key_Less: | |||
|
836 | self._page_control.moveCursor(QtGui.QTextCursor.Start) | |||
|
837 | intercepted = True | |||
772 |
|
838 | |||
773 | if key in (QtCore.Qt.Key_Q, QtCore.Qt.Key_Escape): |
|
839 | elif key in (QtCore.Qt.Key_Q, QtCore.Qt.Key_Escape): | |
774 | if self._splitter: |
|
840 | if self._splitter: | |
775 | self._page_control.hide() |
|
841 | self._page_control.hide() | |
776 | else: |
|
842 | else: | |
@@ -779,7 +845,14 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||||
779 |
|
845 | |||
780 | elif key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return): |
|
846 | elif key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return): | |
781 | new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, |
|
847 | new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, | |
782 | QtCore.Qt.Key_Down, |
|
848 | QtCore.Qt.Key_PageDown, | |
|
849 | QtCore.Qt.NoModifier) | |||
|
850 | QtGui.qApp.sendEvent(self._page_control, new_event) | |||
|
851 | return True | |||
|
852 | ||||
|
853 | elif key == QtCore.Qt.Key_Backspace: | |||
|
854 | new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, | |||
|
855 | QtCore.Qt.Key_PageUp, | |||
783 | QtCore.Qt.NoModifier) |
|
856 | QtCore.Qt.NoModifier) | |
784 | QtGui.qApp.sendEvent(self._page_control, new_event) |
|
857 | QtGui.qApp.sendEvent(self._page_control, new_event) | |
785 | return True |
|
858 | return True | |
@@ -964,6 +1037,15 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||||
964 | cursor.setPosition(position) |
|
1037 | cursor.setPosition(position) | |
965 | return cursor |
|
1038 | return cursor | |
966 |
|
1039 | |||
|
1040 | def _insert_continuation_prompt(self, cursor): | |||
|
1041 | """ Inserts new continuation prompt using the specified cursor. | |||
|
1042 | """ | |||
|
1043 | if self._continuation_prompt_html is None: | |||
|
1044 | self._insert_plain_text(cursor, self._continuation_prompt) | |||
|
1045 | else: | |||
|
1046 | self._continuation_prompt = self._insert_html_fetching_plain_text( | |||
|
1047 | cursor, self._continuation_prompt_html) | |||
|
1048 | ||||
967 | def _insert_html(self, cursor, html): |
|
1049 | def _insert_html(self, cursor, html): | |
968 | """ Inserts HTML using the specified cursor in such a way that future |
|
1050 | """ Inserts HTML using the specified cursor in such a way that future | |
969 | formatting is unaffected. |
|
1051 | formatting is unaffected. | |
@@ -1184,18 +1266,6 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||||
1184 | """ |
|
1266 | """ | |
1185 | self._control.setTextCursor(cursor) |
|
1267 | self._control.setTextCursor(cursor) | |
1186 |
|
1268 | |||
1187 | def _set_position(self, position): |
|
|||
1188 | """ Convenience method to set the position of the cursor. |
|
|||
1189 | """ |
|
|||
1190 | cursor = self._control.textCursor() |
|
|||
1191 | cursor.setPosition(position) |
|
|||
1192 | self._control.setTextCursor(cursor) |
|
|||
1193 |
|
||||
1194 | def _set_selection(self, start, end): |
|
|||
1195 | """ Convenience method to set the current selected text. |
|
|||
1196 | """ |
|
|||
1197 | self._control.setTextCursor(self._get_selection_cursor(start, end)) |
|
|||
1198 |
|
||||
1199 | def _show_context_menu(self, pos): |
|
1269 | def _show_context_menu(self, pos): | |
1200 | """ Shows a context menu at the given QPoint (in widget coordinates). |
|
1270 | """ Shows a context menu at the given QPoint (in widget coordinates). | |
1201 | """ |
|
1271 | """ | |
@@ -1259,15 +1329,6 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||||
1259 | self._prompt_pos = self._get_end_cursor().position() |
|
1329 | self._prompt_pos = self._get_end_cursor().position() | |
1260 | self._prompt_started() |
|
1330 | self._prompt_started() | |
1261 |
|
1331 | |||
1262 | def _show_continuation_prompt(self): |
|
|||
1263 | """ Writes a new continuation prompt at the end of the buffer. |
|
|||
1264 | """ |
|
|||
1265 | if self._continuation_prompt_html is None: |
|
|||
1266 | self._append_plain_text(self._continuation_prompt) |
|
|||
1267 | else: |
|
|||
1268 | self._continuation_prompt = self._append_html_fetching_plain_text( |
|
|||
1269 | self._continuation_prompt_html) |
|
|||
1270 |
|
||||
1271 |
|
1332 | |||
1272 | class HistoryConsoleWidget(ConsoleWidget): |
|
1333 | class HistoryConsoleWidget(ConsoleWidget): | |
1273 | """ A ConsoleWidget that keeps a history of the commands that have been |
|
1334 | """ A ConsoleWidget that keeps a history of the commands that have been |
@@ -9,7 +9,8 b' from PyQt4 import QtCore, QtGui' | |||||
9 | # Local imports |
|
9 | # Local imports | |
10 | from IPython.core.inputsplitter import InputSplitter |
|
10 | from IPython.core.inputsplitter import InputSplitter | |
11 | from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin |
|
11 | from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin | |
12 |
from IPython.utils.traitlets import Bool |
|
12 | from IPython.utils.traitlets import Bool | |
|
13 | from bracket_matcher import BracketMatcher | |||
13 | from call_tip_widget import CallTipWidget |
|
14 | from call_tip_widget import CallTipWidget | |
14 | from completion_lexer import CompletionLexer |
|
15 | from completion_lexer import CompletionLexer | |
15 | from console_widget import HistoryConsoleWidget |
|
16 | from console_widget import HistoryConsoleWidget | |
@@ -88,8 +89,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):' | |||||
88 | executed = QtCore.pyqtSignal(object) |
|
89 | executed = QtCore.pyqtSignal(object) | |
89 |
|
90 | |||
90 | # Protected class variables. |
|
91 | # Protected class variables. | |
91 | _highlighter_class = Type(FrontendHighlighter) |
|
92 | _input_splitter_class = InputSplitter | |
92 | _input_splitter_class = Type(InputSplitter) |
|
|||
93 |
|
93 | |||
94 | #--------------------------------------------------------------------------- |
|
94 | #--------------------------------------------------------------------------- | |
95 | # 'object' interface |
|
95 | # 'object' interface | |
@@ -99,10 +99,11 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):' | |||||
99 | super(FrontendWidget, self).__init__(*args, **kw) |
|
99 | super(FrontendWidget, self).__init__(*args, **kw) | |
100 |
|
100 | |||
101 | # FrontendWidget protected variables. |
|
101 | # FrontendWidget protected variables. | |
|
102 | self._bracket_matcher = BracketMatcher(self._control) | |||
102 | self._call_tip_widget = CallTipWidget(self._control) |
|
103 | self._call_tip_widget = CallTipWidget(self._control) | |
103 | self._completion_lexer = CompletionLexer(PythonLexer()) |
|
104 | self._completion_lexer = CompletionLexer(PythonLexer()) | |
104 | self._hidden = False |
|
105 | self._hidden = False | |
105 |
self._highlighter = |
|
106 | self._highlighter = FrontendHighlighter(self) | |
106 | self._input_splitter = self._input_splitter_class(input_mode='block') |
|
107 | self._input_splitter = self._input_splitter_class(input_mode='block') | |
107 | self._kernel_manager = None |
|
108 | self._kernel_manager = None | |
108 |
|
109 | |||
@@ -170,8 +171,8 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):' | |||||
170 | """ Reimplemented to allow execution interruption. |
|
171 | """ Reimplemented to allow execution interruption. | |
171 | """ |
|
172 | """ | |
172 | key = event.key() |
|
173 | key = event.key() | |
173 |
if |
|
174 | if self._control_key_down(event.modifiers()): | |
174 | if key == QtCore.Qt.Key_C: |
|
175 | if key == QtCore.Qt.Key_C and self._executing: | |
175 | self._kernel_interrupt() |
|
176 | self._kernel_interrupt() | |
176 | return True |
|
177 | return True | |
177 | elif key == QtCore.Qt.Key_Period: |
|
178 | elif key == QtCore.Qt.Key_Period: | |
@@ -179,13 +180,13 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):' | |||||
179 | return True |
|
180 | return True | |
180 | return super(FrontendWidget, self)._event_filter_console_keypress(event) |
|
181 | return super(FrontendWidget, self)._event_filter_console_keypress(event) | |
181 |
|
182 | |||
182 |
def _ |
|
183 | def _insert_continuation_prompt(self, cursor): | |
183 | """ Reimplemented for auto-indentation. |
|
184 | """ Reimplemented for auto-indentation. | |
184 | """ |
|
185 | """ | |
185 |
super(FrontendWidget, self)._ |
|
186 | super(FrontendWidget, self)._insert_continuation_prompt(cursor) | |
186 | spaces = self._input_splitter.indent_spaces |
|
187 | spaces = self._input_splitter.indent_spaces | |
187 |
|
|
188 | cursor.insertText('\t' * (spaces / self.tab_width)) | |
188 |
|
|
189 | cursor.insertText(' ' * (spaces % self.tab_width)) | |
189 |
|
190 | |||
190 | #--------------------------------------------------------------------------- |
|
191 | #--------------------------------------------------------------------------- | |
191 | # 'BaseFrontendMixin' abstract interface |
|
192 | # 'BaseFrontendMixin' abstract interface | |
@@ -353,15 +354,20 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):' | |||||
353 | if self.custom_restart: |
|
354 | if self.custom_restart: | |
354 | self.custom_restart_requested.emit() |
|
355 | self.custom_restart_requested.emit() | |
355 | elif self.kernel_manager.has_kernel: |
|
356 | elif self.kernel_manager.has_kernel: | |
356 | try: |
|
357 | message = 'Are you sure you want to restart the kernel?' | |
357 | self.kernel_manager.restart_kernel() |
|
358 | buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No | |
358 | except RuntimeError: |
|
359 | result = QtGui.QMessageBox.question(self, 'Restart kernel?', | |
359 | message = 'Kernel started externally. Cannot restart.\n' |
|
360 | message, buttons) | |
360 | self._append_plain_text(message) |
|
361 | if result == QtGui.QMessageBox.Yes: | |
361 |
|
|
362 | try: | |
362 | self._stopped_channels() |
|
363 | self.kernel_manager.restart_kernel() | |
363 | self._append_plain_text('Kernel restarting...\n') |
|
364 | except RuntimeError: | |
364 | self._show_interpreter_prompt() |
|
365 | message = 'Kernel started externally. Cannot restart.\n' | |
|
366 | self._append_plain_text(message) | |||
|
367 | else: | |||
|
368 | self._stopped_channels() | |||
|
369 | self._append_plain_text('Kernel restarting...\n') | |||
|
370 | self._show_interpreter_prompt() | |||
365 | else: |
|
371 | else: | |
366 | self._append_plain_text('Kernel process is either remote or ' |
|
372 | self._append_plain_text('Kernel process is either remote or ' | |
367 | 'unspecified. Cannot restart.\n') |
|
373 | 'unspecified. Cannot restart.\n') |
@@ -110,14 +110,12 b' class PygmentsHighlighter(QtGui.QSyntaxHighlighter):' | |||||
110 | elif hasattr(self._lexer, '_saved_state_stack'): |
|
110 | elif hasattr(self._lexer, '_saved_state_stack'): | |
111 | del self._lexer._saved_state_stack |
|
111 | del self._lexer._saved_state_stack | |
112 |
|
112 | |||
113 | index = 0 |
|
|||
114 | # Lex the text using Pygments |
|
113 | # Lex the text using Pygments | |
|
114 | index = 0 | |||
115 | for token, text in self._lexer.get_tokens(qstring): |
|
115 | for token, text in self._lexer.get_tokens(qstring): | |
116 | l = len(text) |
|
116 | length = len(text) | |
117 |
format |
|
117 | self.setFormat(index, length, self._get_format(token)) | |
118 | if format is not None: |
|
118 | index += length | |
119 | self.setFormat(index, l, format) |
|
|||
120 | index += l |
|
|||
121 |
|
119 | |||
122 | if hasattr(self._lexer, '_saved_state_stack'): |
|
120 | if hasattr(self._lexer, '_saved_state_stack'): | |
123 | data = PygmentsBlockUserData( |
|
121 | data = PygmentsBlockUserData( | |
@@ -185,11 +183,9 b' class PygmentsHighlighter(QtGui.QSyntaxHighlighter):' | |||||
185 | def _get_format_from_style(self, token, style): |
|
183 | def _get_format_from_style(self, token, style): | |
186 | """ Returns a QTextCharFormat for token by reading a Pygments style. |
|
184 | """ Returns a QTextCharFormat for token by reading a Pygments style. | |
187 | """ |
|
185 | """ | |
188 | result = None |
|
186 | result = QtGui.QTextCharFormat() | |
189 | for key, value in style.style_for_token(token).items(): |
|
187 | for key, value in style.style_for_token(token).items(): | |
190 | if value: |
|
188 | if value: | |
191 | if result is None: |
|
|||
192 | result = QtGui.QTextCharFormat() |
|
|||
193 | if key == 'color': |
|
189 | if key == 'color': | |
194 | result.setForeground(self._get_brush(value)) |
|
190 | result.setForeground(self._get_brush(value)) | |
195 | elif key == 'bgcolor': |
|
191 | elif key == 'bgcolor': |
@@ -64,10 +64,8 b' def make_kernel(namespace, kernel_factory,' | |||||
64 | """ Creates a kernel. |
|
64 | """ Creates a kernel. | |
65 | """ |
|
65 | """ | |
66 | # Install minimal exception handling |
|
66 | # Install minimal exception handling | |
67 | color_scheme = 'LightBG' if sys.platform == 'darwin' else 'Linux' |
|
67 | sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor', | |
68 | sys.excepthook = FormattedTB( |
|
68 | ostream=sys.__stdout__) | |
69 | mode='Verbose', color_scheme=color_scheme, ostream=sys.__stdout__ |
|
|||
70 | ) |
|
|||
71 |
|
69 | |||
72 | # Create a context, a session, and the kernel sockets. |
|
70 | # Create a context, a session, and the kernel sockets. | |
73 | io.raw_print("Starting the kernel...") |
|
71 | io.raw_print("Starting the kernel...") |
@@ -101,4 +101,4 b' gitwash-update:' | |||||
101 | cd source/development/gitwash && rename 's/.rst/.txt/' *.rst |
|
101 | cd source/development/gitwash && rename 's/.rst/.txt/' *.rst | |
102 |
|
102 | |||
103 | nightly: dist |
|
103 | nightly: dist | |
104 | rsync -avH --delete dist/ ipython:www/doc/nightly No newline at end of file |
|
104 | rsync -avH --delete dist/ ipython:www/doc/nightly |
@@ -130,20 +130,9 b' Messages on the XREP/XREQ socket' | |||||
130 | Execute |
|
130 | Execute | |
131 | ------- |
|
131 | ------- | |
132 |
|
132 | |||
133 | The execution request contains a single string, but this may be a multiline |
|
133 | This message type is used by frontends to ask the kernel to execute code on | |
134 | string. The kernel is responsible for splitting this into possibly more than |
|
134 | behalf of the user, in a namespace reserved to the user's variables (and thus | |
135 | one block and deciding whether to compile these in 'single' or 'exec' mode. |
|
135 | separate from the kernel's own internal code and variables). | |
136 | We're still sorting out this policy. The current inputsplitter is capable of |
|
|||
137 | splitting the input for blocks that can all be run as 'single', but in the long |
|
|||
138 | run it may prove cleaner to only use 'single' mode for truly single-line |
|
|||
139 | inputs, and run all multiline input in 'exec' mode. This would preserve the |
|
|||
140 | natural behavior of single-line inputs while allowing long cells to behave more |
|
|||
141 | likea a script. This design will be refined as we complete the implementation. |
|
|||
142 |
|
||||
143 | .. Note:: |
|
|||
144 |
|
||||
145 | What today we call 'prompt requests' will be encoded in the |
|
|||
146 | ``state_template`` field. |
|
|||
147 |
|
136 | |||
148 | Message type: ``execute_request``:: |
|
137 | Message type: ``execute_request``:: | |
149 |
|
138 | |||
@@ -162,25 +151,75 b' Message type: ``execute_request``::' | |||||
162 | # The default is False. |
|
151 | # The default is False. | |
163 | 'silent' : bool, |
|
152 | 'silent' : bool, | |
164 |
|
153 | |||
165 | # An optional string to request arbitrary state information from the |
|
154 | # A list of variable names from the user's namespace to be retrieved. What | |
166 | # kernel. This string is evaluated via the itpl module, and it can |
|
155 | # returns is a JSON string of the variable's repr(), not a python object. | |
167 | # therefore contain arbitrary code for execution. |
|
156 | 'user_variables' : list, | |
168 |
|
157 | |||
169 | 'state_template' : str, |
|
158 | # Similarly, a dict mapping names to expressions to be evaluated in the | |
|
159 | # user's dict. | |||
|
160 | 'user_expressions' : dict, | |||
170 | } |
|
161 | } | |
171 |
|
162 | |||
|
163 | The ``code`` field contains a single string, but this may be a multiline | |||
|
164 | string. The kernel is responsible for splitting this into possibly more than | |||
|
165 | one block and deciding whether to compile these in 'single' or 'exec' mode. | |||
|
166 | We're still sorting out this policy. The current inputsplitter is capable of | |||
|
167 | splitting the input for blocks that can all be run as 'single', but in the long | |||
|
168 | run it may prove cleaner to only use 'single' mode for truly single-line | |||
|
169 | inputs, and run all multiline input in 'exec' mode. This would preserve the | |||
|
170 | natural behavior of single-line inputs while allowing long cells to behave more | |||
|
171 | likea a script. This design will be refined as we complete the implementation. | |||
|
172 | ||||
|
173 | The ``user_`` fields deserve a detailed explanation. In the past, IPython had | |||
|
174 | the notion of a prompt string that allowed arbitrary code to be evaluated, and | |||
|
175 | this was put to good use by many in creating prompts that displayed system | |||
|
176 | status, path information, and even more esoteric uses like remote instrument | |||
|
177 | status aqcuired over the network. But now that IPython has a clean separation | |||
|
178 | between the kernel and the clients, the notion of embedding 'prompt' | |||
|
179 | maninpulations into the kernel itself feels awkward. Prompts should be a | |||
|
180 | frontend-side feature, and it should be even possible for different frontends | |||
|
181 | to display different prompts while interacting with the same kernel. | |||
|
182 | ||||
|
183 | We have therefore abandoned the idea of a 'prompt string' to be evaluated by | |||
|
184 | the kernel, and instead provide the ability to retrieve from the user's | |||
|
185 | namespace information after the execution of the main ``code``, with two fields | |||
|
186 | of the execution request: | |||
|
187 | ||||
|
188 | - ``user_variables``: If only variables from the user's namespace are needed, a | |||
|
189 | list of variable names can be passed and a dict with these names as keys and | |||
|
190 | their :func:`repr()` as values will be returned. | |||
|
191 | ||||
|
192 | - ``user_expressions``: For more complex expressions that require function | |||
|
193 | evaluations, a dict can be provided with string keys and arbitrary python | |||
|
194 | expressions as values. The return message will contain also a dict with the | |||
|
195 | same keys and the :func:`repr()` of the evaluated expressions as value. | |||
|
196 | ||||
|
197 | With this information, frontends can display any status information they wish | |||
|
198 | in the form that best suits each frontend (a status line, a popup, inline for a | |||
|
199 | terminal, etc). | |||
|
200 | ||||
|
201 | .. Note:: | |||
|
202 | ||||
|
203 | In order to obtain the current execution counter for the purposes of | |||
|
204 | displaying input prompts, frontends simply make an execution request with an | |||
|
205 | empty code string and ``silent=True``. | |||
|
206 | ||||
172 | Execution semantics |
|
207 | Execution semantics | |
173 |
Upon e |
|
208 | Upon completion of the execution request, the kernel *always* sends a | |
174 |
with a status code indicating what happened and additional data |
|
209 | reply, with a status code indicating what happened and additional data | |
175 | on the outcome. |
|
210 | depending on the outcome. | |
|
211 | ||||
|
212 | The ``code`` field is executed first, and then the ``user_variables`` and | |||
|
213 | ``user_expressions`` are computed. This ensures that any error in the | |||
|
214 | latter don't harm the main code execution. | |||
176 |
|
215 | |||
177 | Any code in the ``state_template`` string is evaluated, but full exceptions |
|
216 | Any error in retrieving the ``user_variables`` or evaluating the | |
178 | that may occur are *not* propagated back. If any error occurs during the |
|
217 | ``user_expressions`` will result in a simple error message in the return | |
179 | evaluation, the value of the string will simply be:: |
|
218 | fields of the form:: | |
180 |
|
219 | |||
181 |
[ERROR |
|
220 | [ERROR] ExceptionType: Exception message | |
182 |
|
221 | |||
183 |
The user can simply send the same |
|
222 | The user can simply send the same variable name or expression for | |
184 | evaluation to see a regular traceback. |
|
223 | evaluation to see a regular traceback. | |
185 |
|
224 | |||
186 | Execution counter (old prompt number) |
|
225 | Execution counter (old prompt number) | |
@@ -280,7 +319,7 b' Message type: ``getattr_request``::' | |||||
280 |
|
319 | |||
281 | content = { |
|
320 | content = { | |
282 | # The (possibly dotted) name of the attribute |
|
321 | # The (possibly dotted) name of the attribute | |
283 | 'name' : str |
|
322 | 'name' : str, | |
284 | } |
|
323 | } | |
285 |
|
324 | |||
286 | When a ``getattr_request`` fails, there are two possible error types: |
|
325 | When a ``getattr_request`` fails, there are two possible error types: | |
@@ -296,20 +335,20 b' Message type: ``getattr_reply``::' | |||||
296 |
|
335 | |||
297 | content = { |
|
336 | content = { | |
298 | # One of ['ok', 'AttributeError', 'AccessError']. |
|
337 | # One of ['ok', 'AttributeError', 'AccessError']. | |
299 | 'status' : str |
|
338 | 'status' : str, | |
300 | # If status is 'ok', a JSON object. |
|
339 | # If status is 'ok', a JSON object. | |
301 | 'value' : object |
|
340 | 'value' : object, | |
302 | } |
|
341 | } | |
303 |
|
342 | |||
304 | Message type: ``setattr_request``:: |
|
343 | Message type: ``setattr_request``:: | |
305 |
|
344 | |||
306 | content = { |
|
345 | content = { | |
307 | # The (possibly dotted) name of the attribute |
|
346 | # The (possibly dotted) name of the attribute | |
308 | 'name' : str |
|
347 | 'name' : str, | |
309 |
|
348 | |||
310 | # A JSON-encoded object, that will be validated by the Traits |
|
349 | # A JSON-encoded object, that will be validated by the Traits | |
311 | # information in the kernel |
|
350 | # information in the kernel | |
312 | 'value' : object |
|
351 | 'value' : object, | |
313 | } |
|
352 | } | |
314 |
|
353 | |||
315 | When a ``setattr_request`` fails, there are also two possible error types with |
|
354 | When a ``setattr_request`` fails, there are also two possible error types with | |
@@ -319,7 +358,7 b' Message type: ``setattr_reply``::' | |||||
319 |
|
358 | |||
320 | content = { |
|
359 | content = { | |
321 | # One of ['ok', 'AttributeError', 'AccessError']. |
|
360 | # One of ['ok', 'AttributeError', 'AccessError']. | |
322 | 'status' : str |
|
361 | 'status' : str, | |
323 | } |
|
362 | } | |
324 |
|
363 | |||
325 |
|
364 | |||
@@ -391,13 +430,13 b' Message type: ``object_info_reply``::' | |||||
391 | # The name of the varargs (*args), if any |
|
430 | # The name of the varargs (*args), if any | |
392 | varargs : str, |
|
431 | varargs : str, | |
393 | # The name of the varkw (**kw), if any |
|
432 | # The name of the varkw (**kw), if any | |
394 | varkw : str |
|
433 | varkw : str, | |
395 | # The values (as strings) of all default arguments. Note |
|
434 | # The values (as strings) of all default arguments. Note | |
396 | # that these must be matched *in reverse* with the 'args' |
|
435 | # that these must be matched *in reverse* with the 'args' | |
397 | # list above, since the first positional args have no default |
|
436 | # list above, since the first positional args have no default | |
398 | # value at all. |
|
437 | # value at all. | |
399 | func_defaults : list |
|
438 | func_defaults : list, | |
400 | } |
|
439 | }, | |
401 |
|
440 | |||
402 | # For instances, provide the constructor signature (the definition of |
|
441 | # For instances, provide the constructor signature (the definition of | |
403 | # the __init__ method): |
|
442 | # the __init__ method): | |
@@ -487,6 +526,7 b' Message type: ``history_reply``::' | |||||
487 | # respectively. |
|
526 | # respectively. | |
488 | 'history' : dict, |
|
527 | 'history' : dict, | |
489 | } |
|
528 | } | |
|
529 | ||||
490 | Messages on the PUB/SUB socket |
|
530 | Messages on the PUB/SUB socket | |
491 | ============================== |
|
531 | ============================== | |
492 |
|
532 | |||
@@ -539,10 +579,10 b' Message type: ``pyout``::' | |||||
539 | # The data is typically the repr() of the object. |
|
579 | # The data is typically the repr() of the object. | |
540 | 'data' : str, |
|
580 | 'data' : str, | |
541 |
|
581 | |||
542 |
# The |
|
582 | # The counter for this execution is also provided so that clients can | |
543 |
# |
|
583 | # display it, since IPython automatically creates variables called _N (for | |
544 |
# |
|
584 | # prompt N). | |
545 | 'prompt_number' : int, |
|
585 | 'execution_count' : int, | |
546 | } |
|
586 | } | |
547 |
|
587 | |||
548 | Python errors |
|
588 | Python errors |
General Comments 0
You need to be logged in to leave comments.
Login now