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 | 35 | for making documentation, and in conjunction with -o, for producing |
|
36 | 36 | doctest-ready output. |
|
37 | 37 | |
|
38 |
- |
|
|
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 /'. | |
|
38 | -r: (default) print the 'raw' history, i.e. the actual commands you typed. | |
|
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 | 47 | -g: treat the arg as a pattern to grep for in (full) history. |
|
48 | 48 | This includes the "shadow history" (almost all commands ever written). |
@@ -80,7 +80,8 b" def magic_history(self, parameter_s = ''):" | |||
|
80 | 80 | elif 'r' in opts: |
|
81 | 81 | input_hist = self.input_hist_raw |
|
82 | 82 | else: |
|
83 | input_hist = self.input_hist | |
|
83 | # Raw history is the default | |
|
84 | input_hist = self.input_hist_raw | |
|
84 | 85 | |
|
85 | 86 | default_length = 40 |
|
86 | 87 | pattern = None |
@@ -1828,7 +1828,6 b' class InteractiveShell(Configurable, Magic):' | |||
|
1828 | 1828 | self.resetbuffer() |
|
1829 | 1829 | lines = lines.splitlines() |
|
1830 | 1830 | more = 0 |
|
1831 | ||
|
1832 | 1831 | with nested(self.builtin_trap, self.display_trap): |
|
1833 | 1832 | for line in lines: |
|
1834 | 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 | 1837 | if line or more: |
|
1839 | 1838 | # push to raw history, so hist line numbers stay in sync |
|
1840 |
self.input_hist_raw.append( |
|
|
1841 |
prefiltered = self.prefilter_manager.prefilter_lines(line, |
|
|
1839 | self.input_hist_raw.append(line + '\n') | |
|
1840 | prefiltered = self.prefilter_manager.prefilter_lines(line, | |
|
1841 | more) | |
|
1842 | 1842 | more = self.push_line(prefiltered) |
|
1843 | 1843 | # IPython's runsource returns None if there was an error |
|
1844 | 1844 | # compiling the code. This allows us to stop processing right |
@@ -101,7 +101,7 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||
|
101 | 101 | QtCore.Qt.Key_N : QtCore.Qt.Key_Down, |
|
102 | 102 | QtCore.Qt.Key_D : QtCore.Qt.Key_Delete, } |
|
103 | 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 | 107 | # 'QObject' interface |
@@ -164,18 +164,26 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||
|
164 | 164 | |
|
165 | 165 | def eventFilter(self, obj, event): |
|
166 | 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 | 169 | etype = event.type() |
|
171 |
if etype == QtCore.QEvent.KeyPress |
|
|
172 | self._control_key_down(event.modifiers()) and \ | |
|
173 | event.key() in self._ctrl_down_remap: | |
|
174 | new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, | |
|
175 | self._ctrl_down_remap[event.key()], | |
|
176 | QtCore.Qt.NoModifier) | |
|
177 | QtGui.qApp.sendEvent(obj, new_event) | |
|
178 | return True | |
|
170 | if etype == QtCore.QEvent.KeyPress: | |
|
171 | ||
|
172 | # Re-map keys for all filtered widgets. | |
|
173 | key = event.key() | |
|
174 | if self._control_key_down(event.modifiers()) and \ | |
|
175 | key in self._ctrl_down_remap: | |
|
176 | new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, | |
|
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 | 188 | # Override shortucts for all filtered widgets. Note that on Mac OS it is |
|
181 | 189 | # always unnecessary to override shortcuts, hence the check below (users |
@@ -187,12 +195,6 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||
|
187 | 195 | event.accept() |
|
188 | 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 | 198 | return super(ConsoleWidget, self).eventFilter(obj, event) |
|
197 | 199 | |
|
198 | 200 | #--------------------------------------------------------------------------- |
@@ -334,12 +336,11 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||
|
334 | 336 | else: |
|
335 | 337 | # Do this inside an edit block so continuation prompts are |
|
336 | 338 | # removed seamlessly via undo/redo. |
|
337 |
cursor = self._ |
|
|
339 | cursor = self._get_end_cursor() | |
|
338 | 340 | cursor.beginEditBlock() |
|
339 | ||
|
340 | self._append_plain_text('\n') | |
|
341 | self._show_continuation_prompt() | |
|
342 | ||
|
341 | cursor.insertText('\n') | |
|
342 | self._insert_continuation_prompt(cursor) | |
|
343 | self._control.moveCursor(QtGui.QTextCursor.End) | |
|
343 | 344 | cursor.endEditBlock() |
|
344 | 345 | |
|
345 | 346 | return complete |
@@ -431,9 +432,8 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||
|
431 | 432 | """ Sets the font to the default fixed-width font for this platform. |
|
432 | 433 | """ |
|
433 | 434 | # FIXME: font family and size should be configurable by the user. |
|
434 | ||
|
435 | 435 | if sys.platform == 'win32': |
|
436 |
# F |
|
|
436 | # FIXME: we should test whether Consolas is available and use it | |
|
437 | 437 | # first if it is. Consolas ships by default from Vista onwards, |
|
438 | 438 | # it's *vastly* more readable and prettier than Courier, and is |
|
439 | 439 | # often installed even on XP systems. So we should first check for |
@@ -632,13 +632,32 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||
|
632 | 632 | if self._in_buffer(position): |
|
633 | 633 | cursor.movePosition(QtGui.QTextCursor.EndOfLine, |
|
634 | 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 | 642 | cursor.removeSelectedText() |
|
636 | 643 | intercepted = True |
|
637 | 644 | |
|
638 | 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 | 653 | self.clear() |
|
640 | 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 | 661 | elif key == QtCore.Qt.Key_X: |
|
643 | 662 | # FIXME: Instead of disabling cut completely, only allow it |
|
644 | 663 | # when safe. |
@@ -669,16 +688,44 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||
|
669 | 688 | cursor.removeSelectedText() |
|
670 | 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 | 699 | else: |
|
673 | 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 | 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 | 730 | elif key == QtCore.Qt.Key_Up: |
|
684 | 731 | if self._reading or not self._up_pressed(): |
@@ -702,17 +749,20 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||
|
702 | 749 | intercepted = not self._in_buffer(position - 1) |
|
703 | 750 | |
|
704 | 751 | elif key == QtCore.Qt.Key_Home: |
|
705 | cursor.movePosition(QtGui.QTextCursor.StartOfBlock) | |
|
706 | 752 | start_line = cursor.blockNumber() |
|
707 | 753 | if start_line == self._get_prompt_cursor().blockNumber(): |
|
708 | 754 | start_pos = self._prompt_pos |
|
709 | 755 | else: |
|
756 | cursor.movePosition(QtGui.QTextCursor.StartOfBlock, | |
|
757 | QtGui.QTextCursor.KeepAnchor) | |
|
710 | 758 | start_pos = cursor.position() |
|
711 | 759 | start_pos += len(self._continuation_prompt) |
|
760 | cursor.setPosition(position) | |
|
712 | 761 | if shift_down and self._in_buffer(position): |
|
713 |
|
|
|
762 | cursor.setPosition(start_pos, QtGui.QTextCursor.KeepAnchor) | |
|
714 | 763 | else: |
|
715 |
|
|
|
764 | cursor.setPosition(start_pos) | |
|
765 | self._set_cursor(cursor) | |
|
716 | 766 | intercepted = True |
|
717 | 767 | |
|
718 | 768 | elif key == QtCore.Qt.Key_Backspace: |
@@ -769,8 +819,24 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||
|
769 | 819 | interface. |
|
770 | 820 | """ |
|
771 | 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 | 840 | if self._splitter: |
|
775 | 841 | self._page_control.hide() |
|
776 | 842 | else: |
@@ -779,7 +845,14 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||
|
779 | 845 | |
|
780 | 846 | elif key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return): |
|
781 | 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 | 856 | QtCore.Qt.NoModifier) |
|
784 | 857 | QtGui.qApp.sendEvent(self._page_control, new_event) |
|
785 | 858 | return True |
@@ -964,6 +1037,15 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||
|
964 | 1037 | cursor.setPosition(position) |
|
965 | 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 | 1049 | def _insert_html(self, cursor, html): |
|
968 | 1050 | """ Inserts HTML using the specified cursor in such a way that future |
|
969 | 1051 | formatting is unaffected. |
@@ -1184,18 +1266,6 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||
|
1184 | 1266 | """ |
|
1185 | 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 | 1269 | def _show_context_menu(self, pos): |
|
1200 | 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 | 1329 | self._prompt_pos = self._get_end_cursor().position() |
|
1260 | 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 | 1333 | class HistoryConsoleWidget(ConsoleWidget): |
|
1273 | 1334 | """ A ConsoleWidget that keeps a history of the commands that have been |
@@ -9,7 +9,8 b' from PyQt4 import QtCore, QtGui' | |||
|
9 | 9 | # Local imports |
|
10 | 10 | from IPython.core.inputsplitter import InputSplitter |
|
11 | 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 | 14 | from call_tip_widget import CallTipWidget |
|
14 | 15 | from completion_lexer import CompletionLexer |
|
15 | 16 | from console_widget import HistoryConsoleWidget |
@@ -88,8 +89,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):' | |||
|
88 | 89 | executed = QtCore.pyqtSignal(object) |
|
89 | 90 | |
|
90 | 91 | # Protected class variables. |
|
91 | _highlighter_class = Type(FrontendHighlighter) | |
|
92 | _input_splitter_class = Type(InputSplitter) | |
|
92 | _input_splitter_class = InputSplitter | |
|
93 | 93 | |
|
94 | 94 | #--------------------------------------------------------------------------- |
|
95 | 95 | # 'object' interface |
@@ -99,10 +99,11 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):' | |||
|
99 | 99 | super(FrontendWidget, self).__init__(*args, **kw) |
|
100 | 100 | |
|
101 | 101 | # FrontendWidget protected variables. |
|
102 | self._bracket_matcher = BracketMatcher(self._control) | |
|
102 | 103 | self._call_tip_widget = CallTipWidget(self._control) |
|
103 | 104 | self._completion_lexer = CompletionLexer(PythonLexer()) |
|
104 | 105 | self._hidden = False |
|
105 |
self._highlighter = |
|
|
106 | self._highlighter = FrontendHighlighter(self) | |
|
106 | 107 | self._input_splitter = self._input_splitter_class(input_mode='block') |
|
107 | 108 | self._kernel_manager = None |
|
108 | 109 | |
@@ -170,8 +171,8 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):' | |||
|
170 | 171 | """ Reimplemented to allow execution interruption. |
|
171 | 172 | """ |
|
172 | 173 | key = event.key() |
|
173 |
if |
|
|
174 | if key == QtCore.Qt.Key_C: | |
|
174 | if self._control_key_down(event.modifiers()): | |
|
175 | if key == QtCore.Qt.Key_C and self._executing: | |
|
175 | 176 | self._kernel_interrupt() |
|
176 | 177 | return True |
|
177 | 178 | elif key == QtCore.Qt.Key_Period: |
@@ -179,13 +180,13 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):' | |||
|
179 | 180 | return True |
|
180 | 181 | return super(FrontendWidget, self)._event_filter_console_keypress(event) |
|
181 | 182 | |
|
182 |
def _ |
|
|
183 | def _insert_continuation_prompt(self, cursor): | |
|
183 | 184 | """ Reimplemented for auto-indentation. |
|
184 | 185 | """ |
|
185 |
super(FrontendWidget, self)._ |
|
|
186 | super(FrontendWidget, self)._insert_continuation_prompt(cursor) | |
|
186 | 187 | spaces = self._input_splitter.indent_spaces |
|
187 |
|
|
|
188 |
|
|
|
188 | cursor.insertText('\t' * (spaces / self.tab_width)) | |
|
189 | cursor.insertText(' ' * (spaces % self.tab_width)) | |
|
189 | 190 | |
|
190 | 191 | #--------------------------------------------------------------------------- |
|
191 | 192 | # 'BaseFrontendMixin' abstract interface |
@@ -353,15 +354,20 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):' | |||
|
353 | 354 | if self.custom_restart: |
|
354 | 355 | self.custom_restart_requested.emit() |
|
355 | 356 | elif self.kernel_manager.has_kernel: |
|
356 | try: | |
|
357 | self.kernel_manager.restart_kernel() | |
|
358 | except RuntimeError: | |
|
359 | message = 'Kernel started externally. Cannot restart.\n' | |
|
360 | self._append_plain_text(message) | |
|
361 |
|
|
|
362 | self._stopped_channels() | |
|
363 | self._append_plain_text('Kernel restarting...\n') | |
|
364 | self._show_interpreter_prompt() | |
|
357 | message = 'Are you sure you want to restart the kernel?' | |
|
358 | buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No | |
|
359 | result = QtGui.QMessageBox.question(self, 'Restart kernel?', | |
|
360 | message, buttons) | |
|
361 | if result == QtGui.QMessageBox.Yes: | |
|
362 | try: | |
|
363 | self.kernel_manager.restart_kernel() | |
|
364 | except RuntimeError: | |
|
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 | 371 | else: |
|
366 | 372 | self._append_plain_text('Kernel process is either remote or ' |
|
367 | 373 | 'unspecified. Cannot restart.\n') |
@@ -110,14 +110,12 b' class PygmentsHighlighter(QtGui.QSyntaxHighlighter):' | |||
|
110 | 110 | elif hasattr(self._lexer, '_saved_state_stack'): |
|
111 | 111 | del self._lexer._saved_state_stack |
|
112 | 112 | |
|
113 | index = 0 | |
|
114 | 113 | # Lex the text using Pygments |
|
114 | index = 0 | |
|
115 | 115 | for token, text in self._lexer.get_tokens(qstring): |
|
116 | l = len(text) | |
|
117 |
format |
|
|
118 | if format is not None: | |
|
119 | self.setFormat(index, l, format) | |
|
120 | index += l | |
|
116 | length = len(text) | |
|
117 | self.setFormat(index, length, self._get_format(token)) | |
|
118 | index += length | |
|
121 | 119 | |
|
122 | 120 | if hasattr(self._lexer, '_saved_state_stack'): |
|
123 | 121 | data = PygmentsBlockUserData( |
@@ -185,11 +183,9 b' class PygmentsHighlighter(QtGui.QSyntaxHighlighter):' | |||
|
185 | 183 | def _get_format_from_style(self, token, style): |
|
186 | 184 | """ Returns a QTextCharFormat for token by reading a Pygments style. |
|
187 | 185 | """ |
|
188 | result = None | |
|
186 | result = QtGui.QTextCharFormat() | |
|
189 | 187 | for key, value in style.style_for_token(token).items(): |
|
190 | 188 | if value: |
|
191 | if result is None: | |
|
192 | result = QtGui.QTextCharFormat() | |
|
193 | 189 | if key == 'color': |
|
194 | 190 | result.setForeground(self._get_brush(value)) |
|
195 | 191 | elif key == 'bgcolor': |
@@ -64,10 +64,8 b' def make_kernel(namespace, kernel_factory,' | |||
|
64 | 64 | """ Creates a kernel. |
|
65 | 65 | """ |
|
66 | 66 | # Install minimal exception handling |
|
67 | color_scheme = 'LightBG' if sys.platform == 'darwin' else 'Linux' | |
|
68 | sys.excepthook = FormattedTB( | |
|
69 | mode='Verbose', color_scheme=color_scheme, ostream=sys.__stdout__ | |
|
70 | ) | |
|
67 | sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor', | |
|
68 | ostream=sys.__stdout__) | |
|
71 | 69 | |
|
72 | 70 | # Create a context, a session, and the kernel sockets. |
|
73 | 71 | io.raw_print("Starting the kernel...") |
@@ -101,4 +101,4 b' gitwash-update:' | |||
|
101 | 101 | cd source/development/gitwash && rename 's/.rst/.txt/' *.rst |
|
102 | 102 | |
|
103 | 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 | 130 | Execute |
|
131 | 131 | ------- |
|
132 | 132 | |
|
133 | The execution request contains a single string, but this may be a multiline | |
|
134 | string. The kernel is responsible for splitting this into possibly more than | |
|
135 | one block and deciding whether to compile these in 'single' or 'exec' mode. | |
|
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. | |
|
133 | This message type is used by frontends to ask the kernel to execute code on | |
|
134 | behalf of the user, in a namespace reserved to the user's variables (and thus | |
|
135 | separate from the kernel's own internal code and variables). | |
|
147 | 136 | |
|
148 | 137 | Message type: ``execute_request``:: |
|
149 | 138 | |
@@ -162,25 +151,75 b' Message type: ``execute_request``::' | |||
|
162 | 151 | # The default is False. |
|
163 | 152 | 'silent' : bool, |
|
164 | 153 | |
|
165 | # An optional string to request arbitrary state information from the | |
|
166 | # kernel. This string is evaluated via the itpl module, and it can | |
|
167 | # therefore contain arbitrary code for execution. | |
|
168 | ||
|
169 | 'state_template' : str, | |
|
154 | # A list of variable names from the user's namespace to be retrieved. What | |
|
155 | # returns is a JSON string of the variable's repr(), not a python object. | |
|
156 | 'user_variables' : list, | |
|
157 | ||
|
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 | 207 | Execution semantics |
|
173 |
Upon e |
|
|
174 |
with a status code indicating what happened and additional data |
|
|
175 | on the outcome. | |
|
208 | Upon completion of the execution request, the kernel *always* sends a | |
|
209 | reply, with a status code indicating what happened and additional data | |
|
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 | |
|
178 | that may occur are *not* propagated back. If any error occurs during the | |
|
179 | evaluation, the value of the string will simply be:: | |
|
216 | Any error in retrieving the ``user_variables`` or evaluating the | |
|
217 | ``user_expressions`` will result in a simple error message in the return | |
|
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 | 223 | evaluation to see a regular traceback. |
|
185 | 224 | |
|
186 | 225 | Execution counter (old prompt number) |
@@ -280,7 +319,7 b' Message type: ``getattr_request``::' | |||
|
280 | 319 | |
|
281 | 320 | content = { |
|
282 | 321 | # The (possibly dotted) name of the attribute |
|
283 | 'name' : str | |
|
322 | 'name' : str, | |
|
284 | 323 | } |
|
285 | 324 | |
|
286 | 325 | When a ``getattr_request`` fails, there are two possible error types: |
@@ -296,20 +335,20 b' Message type: ``getattr_reply``::' | |||
|
296 | 335 | |
|
297 | 336 | content = { |
|
298 | 337 | # One of ['ok', 'AttributeError', 'AccessError']. |
|
299 | 'status' : str | |
|
338 | 'status' : str, | |
|
300 | 339 | # If status is 'ok', a JSON object. |
|
301 | 'value' : object | |
|
340 | 'value' : object, | |
|
302 | 341 | } |
|
303 | 342 | |
|
304 | 343 | Message type: ``setattr_request``:: |
|
305 | 344 | |
|
306 | 345 | content = { |
|
307 | 346 | # The (possibly dotted) name of the attribute |
|
308 | 'name' : str | |
|
347 | 'name' : str, | |
|
309 | 348 | |
|
310 | 349 | # A JSON-encoded object, that will be validated by the Traits |
|
311 | 350 | # information in the kernel |
|
312 | 'value' : object | |
|
351 | 'value' : object, | |
|
313 | 352 | } |
|
314 | 353 | |
|
315 | 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 | 359 | content = { |
|
321 | 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 | 430 | # The name of the varargs (*args), if any |
|
392 | 431 | varargs : str, |
|
393 | 432 | # The name of the varkw (**kw), if any |
|
394 | varkw : str | |
|
433 | varkw : str, | |
|
395 | 434 | # The values (as strings) of all default arguments. Note |
|
396 | 435 | # that these must be matched *in reverse* with the 'args' |
|
397 | 436 | # list above, since the first positional args have no default |
|
398 | 437 | # value at all. |
|
399 | func_defaults : list | |
|
400 | } | |
|
438 | func_defaults : list, | |
|
439 | }, | |
|
401 | 440 | |
|
402 | 441 | # For instances, provide the constructor signature (the definition of |
|
403 | 442 | # the __init__ method): |
@@ -487,6 +526,7 b' Message type: ``history_reply``::' | |||
|
487 | 526 | # respectively. |
|
488 | 527 | 'history' : dict, |
|
489 | 528 | } |
|
529 | ||
|
490 | 530 | Messages on the PUB/SUB socket |
|
491 | 531 | ============================== |
|
492 | 532 | |
@@ -539,10 +579,10 b' Message type: ``pyout``::' | |||
|
539 | 579 | # The data is typically the repr() of the object. |
|
540 | 580 | 'data' : str, |
|
541 | 581 | |
|
542 |
# The |
|
|
543 |
# |
|
|
544 |
# |
|
|
545 | 'prompt_number' : int, | |
|
582 | # The counter for this execution is also provided so that clients can | |
|
583 | # display it, since IPython automatically creates variables called _N (for | |
|
584 | # prompt N). | |
|
585 | 'execution_count' : int, | |
|
546 | 586 | } |
|
547 | 587 | |
|
548 | 588 | Python errors |
General Comments 0
You need to be logged in to leave comments.
Login now