##// END OF EJS Templates
Merge branch 'epatters-qtfrontend' into kernelmanager
Brian Granger -
r2698:356d1034 merge
parent child Browse files
Show More
@@ -1,766 +1,823 b''
1 # Standard library imports
1 # Standard library imports
2 import re
2 import re
3 import sys
3 import sys
4
4
5 # System library imports
5 # System library imports
6 from PyQt4 import QtCore, QtGui
6 from PyQt4 import QtCore, QtGui
7
7
8 # Local imports
8 # Local imports
9 from completion_widget import CompletionWidget
9 from completion_widget import CompletionWidget
10
10
11
11
12 class AnsiCodeProcessor(object):
12 class AnsiCodeProcessor(object):
13 """ Translates ANSI escape codes into readable attributes.
13 """ Translates ANSI escape codes into readable attributes.
14 """
14 """
15
15
16 def __init__(self):
16 def __init__(self):
17 self.ansi_colors = ( # Normal, Bright/Light
17 self.ansi_colors = ( # Normal, Bright/Light
18 ('#000000', '#7f7f7f'), # 0: black
18 ('#000000', '#7f7f7f'), # 0: black
19 ('#cd0000', '#ff0000'), # 1: red
19 ('#cd0000', '#ff0000'), # 1: red
20 ('#00cd00', '#00ff00'), # 2: green
20 ('#00cd00', '#00ff00'), # 2: green
21 ('#cdcd00', '#ffff00'), # 3: yellow
21 ('#cdcd00', '#ffff00'), # 3: yellow
22 ('#0000ee', '#0000ff'), # 4: blue
22 ('#0000ee', '#0000ff'), # 4: blue
23 ('#cd00cd', '#ff00ff'), # 5: magenta
23 ('#cd00cd', '#ff00ff'), # 5: magenta
24 ('#00cdcd', '#00ffff'), # 6: cyan
24 ('#00cdcd', '#00ffff'), # 6: cyan
25 ('#e5e5e5', '#ffffff')) # 7: white
25 ('#e5e5e5', '#ffffff')) # 7: white
26 self.reset()
26 self.reset()
27
27
28 def set_code(self, code):
28 def set_code(self, code):
29 """ Set attributes based on code.
29 """ Set attributes based on code.
30 """
30 """
31 if code == 0:
31 if code == 0:
32 self.reset()
32 self.reset()
33 elif code == 1:
33 elif code == 1:
34 self.intensity = 1
34 self.intensity = 1
35 self.bold = True
35 self.bold = True
36 elif code == 3:
36 elif code == 3:
37 self.italic = True
37 self.italic = True
38 elif code == 4:
38 elif code == 4:
39 self.underline = True
39 self.underline = True
40 elif code == 22:
40 elif code == 22:
41 self.intensity = 0
41 self.intensity = 0
42 self.bold = False
42 self.bold = False
43 elif code == 23:
43 elif code == 23:
44 self.italic = False
44 self.italic = False
45 elif code == 24:
45 elif code == 24:
46 self.underline = False
46 self.underline = False
47 elif code >= 30 and code <= 37:
47 elif code >= 30 and code <= 37:
48 self.foreground_color = code - 30
48 self.foreground_color = code - 30
49 elif code == 39:
49 elif code == 39:
50 self.foreground_color = None
50 self.foreground_color = None
51 elif code >= 40 and code <= 47:
51 elif code >= 40 and code <= 47:
52 self.background_color = code - 40
52 self.background_color = code - 40
53 elif code == 49:
53 elif code == 49:
54 self.background_color = None
54 self.background_color = None
55
55
56 def reset(self):
56 def reset(self):
57 """ Reset attributs to their default values.
57 """ Reset attributs to their default values.
58 """
58 """
59 self.intensity = 0
59 self.intensity = 0
60 self.italic = False
60 self.italic = False
61 self.bold = False
61 self.bold = False
62 self.underline = False
62 self.underline = False
63 self.foreground_color = None
63 self.foreground_color = None
64 self.background_color = None
64 self.background_color = None
65
65
66
66
67 class QtAnsiCodeProcessor(AnsiCodeProcessor):
67 class QtAnsiCodeProcessor(AnsiCodeProcessor):
68 """ Translates ANSI escape codes into QTextCharFormats.
68 """ Translates ANSI escape codes into QTextCharFormats.
69 """
69 """
70
70
71 def get_format(self):
71 def get_format(self):
72 """ Returns a QTextCharFormat that encodes the current style attributes.
72 """ Returns a QTextCharFormat that encodes the current style attributes.
73 """
73 """
74 format = QtGui.QTextCharFormat()
74 format = QtGui.QTextCharFormat()
75
75
76 # Set foreground color
76 # Set foreground color
77 if self.foreground_color is not None:
77 if self.foreground_color is not None:
78 color = self.ansi_colors[self.foreground_color][self.intensity]
78 color = self.ansi_colors[self.foreground_color][self.intensity]
79 format.setForeground(QtGui.QColor(color))
79 format.setForeground(QtGui.QColor(color))
80
80
81 # Set background color
81 # Set background color
82 if self.background_color is not None:
82 if self.background_color is not None:
83 color = self.ansi_colors[self.background_color][self.intensity]
83 color = self.ansi_colors[self.background_color][self.intensity]
84 format.setBackground(QtGui.QColor(color))
84 format.setBackground(QtGui.QColor(color))
85
85
86 # Set font weight/style options
86 # Set font weight/style options
87 if self.bold:
87 if self.bold:
88 format.setFontWeight(QtGui.QFont.Bold)
88 format.setFontWeight(QtGui.QFont.Bold)
89 else:
89 else:
90 format.setFontWeight(QtGui.QFont.Normal)
90 format.setFontWeight(QtGui.QFont.Normal)
91 format.setFontItalic(self.italic)
91 format.setFontItalic(self.italic)
92 format.setFontUnderline(self.underline)
92 format.setFontUnderline(self.underline)
93
93
94 return format
94 return format
95
95
96
96
97 class ConsoleWidget(QtGui.QPlainTextEdit):
97 class ConsoleWidget(QtGui.QPlainTextEdit):
98 """ Base class for console-type widgets. This class is mainly concerned with
98 """ Base class for console-type widgets. This class is mainly concerned with
99 dealing with the prompt, keeping the cursor inside the editing line, and
99 dealing with the prompt, keeping the cursor inside the editing line, and
100 handling ANSI escape sequences.
100 handling ANSI escape sequences.
101 """
101 """
102
102
103 # Whether to process ANSI escape codes.
103 # Whether to process ANSI escape codes.
104 ansi_codes = True
104 ansi_codes = True
105
105
106 # The maximum number of lines of text before truncation.
106 # The maximum number of lines of text before truncation.
107 buffer_size = 500
107 buffer_size = 500
108
108
109 # Whether to use a CompletionWidget or plain text output for tab completion.
109 # Whether to use a CompletionWidget or plain text output for tab completion.
110 gui_completion = True
110 gui_completion = True
111
111
112 # Whether to override ShortcutEvents for the keybindings defined by this
112 # Whether to override ShortcutEvents for the keybindings defined by this
113 # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
113 # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
114 # priority (when it has focus) over, e.g., window-level menu shortcuts.
114 # priority (when it has focus) over, e.g., window-level menu shortcuts.
115 override_shortcuts = False
115 override_shortcuts = False
116
116
117 # Protected class variables.
117 # Protected class variables.
118 _ansi_pattern = re.compile('\x01?\x1b\[(.*?)m\x02?')
118 _ansi_pattern = re.compile('\x01?\x1b\[(.*?)m\x02?')
119 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
119 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
120 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
120 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
121 QtCore.Qt.Key_A : QtCore.Qt.Key_Home,
121 QtCore.Qt.Key_A : QtCore.Qt.Key_Home,
122 QtCore.Qt.Key_E : QtCore.Qt.Key_End,
122 QtCore.Qt.Key_E : QtCore.Qt.Key_End,
123 QtCore.Qt.Key_P : QtCore.Qt.Key_Up,
123 QtCore.Qt.Key_P : QtCore.Qt.Key_Up,
124 QtCore.Qt.Key_N : QtCore.Qt.Key_Down,
124 QtCore.Qt.Key_N : QtCore.Qt.Key_Down,
125 QtCore.Qt.Key_D : QtCore.Qt.Key_Delete, }
125 QtCore.Qt.Key_D : QtCore.Qt.Key_Delete, }
126 _shortcuts = set(_ctrl_down_remap.keys() +
126 _shortcuts = set(_ctrl_down_remap.keys() +
127 [ QtCore.Qt.Key_C, QtCore.Qt.Key_V ])
127 [ QtCore.Qt.Key_C, QtCore.Qt.Key_V ])
128
128
129 #---------------------------------------------------------------------------
129 #---------------------------------------------------------------------------
130 # 'QObject' interface
130 # 'QObject' interface
131 #---------------------------------------------------------------------------
131 #---------------------------------------------------------------------------
132
132
133 def __init__(self, parent=None):
133 def __init__(self, parent=None):
134 QtGui.QPlainTextEdit.__init__(self, parent)
134 QtGui.QPlainTextEdit.__init__(self, parent)
135
135
136 # Initialize protected variables.
136 # Initialize protected variables.
137 self._ansi_processor = QtAnsiCodeProcessor()
137 self._ansi_processor = QtAnsiCodeProcessor()
138 self._completion_widget = CompletionWidget(self)
138 self._completion_widget = CompletionWidget(self)
139 self._continuation_prompt = '> '
139 self._continuation_prompt = '> '
140 self._executing = False
140 self._executing = False
141 self._prompt = ''
141 self._prompt = ''
142 self._prompt_pos = 0
142 self._prompt_pos = 0
143 self._reading = False
143 self._reading = False
144
144
145 # Set a monospaced font.
145 # Set a monospaced font.
146 self.reset_font()
146 self.reset_font()
147
147
148 # Define a custom context menu.
148 # Define a custom context menu.
149 self._context_menu = QtGui.QMenu(self)
149 self._context_menu = QtGui.QMenu(self)
150
150
151 copy_action = QtGui.QAction('Copy', self)
151 copy_action = QtGui.QAction('Copy', self)
152 copy_action.triggered.connect(self.copy)
152 copy_action.triggered.connect(self.copy)
153 self.copyAvailable.connect(copy_action.setEnabled)
153 self.copyAvailable.connect(copy_action.setEnabled)
154 self._context_menu.addAction(copy_action)
154 self._context_menu.addAction(copy_action)
155
155
156 self._paste_action = QtGui.QAction('Paste', self)
156 self._paste_action = QtGui.QAction('Paste', self)
157 self._paste_action.triggered.connect(self.paste)
157 self._paste_action.triggered.connect(self.paste)
158 self._context_menu.addAction(self._paste_action)
158 self._context_menu.addAction(self._paste_action)
159 self._context_menu.addSeparator()
159 self._context_menu.addSeparator()
160
160
161 select_all_action = QtGui.QAction('Select All', self)
161 select_all_action = QtGui.QAction('Select All', self)
162 select_all_action.triggered.connect(self.selectAll)
162 select_all_action.triggered.connect(self.selectAll)
163 self._context_menu.addAction(select_all_action)
163 self._context_menu.addAction(select_all_action)
164
164
165 def event(self, event):
165 def event(self, event):
166 """ Reimplemented to override shortcuts, if necessary.
166 """ Reimplemented to override shortcuts, if necessary.
167 """
167 """
168 # On Mac OS, it is always unnecessary to override shortcuts, hence the
168 # On Mac OS, it is always unnecessary to override shortcuts, hence the
169 # check below. Users should just use the Control key instead of the
169 # check below. Users should just use the Control key instead of the
170 # Command key.
170 # Command key.
171 if self.override_shortcuts and \
171 if self.override_shortcuts and \
172 sys.platform != 'darwin' and \
172 sys.platform != 'darwin' and \
173 event.type() == QtCore.QEvent.ShortcutOverride and \
173 event.type() == QtCore.QEvent.ShortcutOverride and \
174 self._control_down(event.modifiers()) and \
174 self._control_down(event.modifiers()) and \
175 event.key() in self._shortcuts:
175 event.key() in self._shortcuts:
176 event.accept()
176 event.accept()
177 return True
177 return True
178 else:
178 else:
179 return QtGui.QPlainTextEdit.event(self, event)
179 return QtGui.QPlainTextEdit.event(self, event)
180
180
181 #---------------------------------------------------------------------------
181 #---------------------------------------------------------------------------
182 # 'QWidget' interface
182 # 'QWidget' interface
183 #---------------------------------------------------------------------------
183 #---------------------------------------------------------------------------
184
184
185 def contextMenuEvent(self, event):
185 def contextMenuEvent(self, event):
186 """ Reimplemented to create a menu without destructive actions like
186 """ Reimplemented to create a menu without destructive actions like
187 'Cut' and 'Delete'.
187 'Cut' and 'Delete'.
188 """
188 """
189 clipboard_empty = QtGui.QApplication.clipboard().text().isEmpty()
189 clipboard_empty = QtGui.QApplication.clipboard().text().isEmpty()
190 self._paste_action.setEnabled(not clipboard_empty)
190 self._paste_action.setEnabled(not clipboard_empty)
191
191
192 self._context_menu.exec_(event.globalPos())
192 self._context_menu.exec_(event.globalPos())
193
193
194 def keyPressEvent(self, event):
194 def keyPressEvent(self, event):
195 """ Reimplemented to create a console-like interface.
195 """ Reimplemented to create a console-like interface.
196 """
196 """
197 intercepted = False
197 intercepted = False
198 cursor = self.textCursor()
198 cursor = self.textCursor()
199 position = cursor.position()
199 position = cursor.position()
200 key = event.key()
200 key = event.key()
201 ctrl_down = self._control_down(event.modifiers())
201 ctrl_down = self._control_down(event.modifiers())
202 alt_down = event.modifiers() & QtCore.Qt.AltModifier
202 alt_down = event.modifiers() & QtCore.Qt.AltModifier
203 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
203 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
204
204
205 # Even though we have reimplemented 'paste', the C++ level slot is still
205 # Even though we have reimplemented 'paste', the C++ level slot is still
206 # called by Qt. So we intercept the key press here.
206 # called by Qt. So we intercept the key press here.
207 if event.matches(QtGui.QKeySequence.Paste):
207 if event.matches(QtGui.QKeySequence.Paste):
208 self.paste()
208 self.paste()
209 intercepted = True
209 intercepted = True
210
210
211 elif ctrl_down:
211 elif ctrl_down:
212 if key in self._ctrl_down_remap:
212 if key in self._ctrl_down_remap:
213 ctrl_down = False
213 ctrl_down = False
214 key = self._ctrl_down_remap[key]
214 key = self._ctrl_down_remap[key]
215 event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, key,
215 event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, key,
216 QtCore.Qt.NoModifier)
216 QtCore.Qt.NoModifier)
217
217
218 elif key == QtCore.Qt.Key_K:
218 elif key == QtCore.Qt.Key_K:
219 if self._in_buffer(position):
219 if self._in_buffer(position):
220 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
220 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
221 QtGui.QTextCursor.KeepAnchor)
221 QtGui.QTextCursor.KeepAnchor)
222 cursor.removeSelectedText()
222 cursor.removeSelectedText()
223 intercepted = True
223 intercepted = True
224
224
225 elif key == QtCore.Qt.Key_X:
225 elif key == QtCore.Qt.Key_X:
226 intercepted = True
226 intercepted = True
227
227
228 elif key == QtCore.Qt.Key_Y:
228 elif key == QtCore.Qt.Key_Y:
229 self.paste()
229 self.paste()
230 intercepted = True
230 intercepted = True
231
231
232 elif alt_down:
232 elif alt_down:
233 if key == QtCore.Qt.Key_B:
233 if key == QtCore.Qt.Key_B:
234 self.setTextCursor(self._get_word_start_cursor(position))
234 self.setTextCursor(self._get_word_start_cursor(position))
235 intercepted = True
235 intercepted = True
236
236
237 elif key == QtCore.Qt.Key_F:
237 elif key == QtCore.Qt.Key_F:
238 self.setTextCursor(self._get_word_end_cursor(position))
238 self.setTextCursor(self._get_word_end_cursor(position))
239 intercepted = True
239 intercepted = True
240
240
241 elif key == QtCore.Qt.Key_Backspace:
241 elif key == QtCore.Qt.Key_Backspace:
242 cursor = self._get_word_start_cursor(position)
242 cursor = self._get_word_start_cursor(position)
243 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
243 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
244 cursor.removeSelectedText()
244 cursor.removeSelectedText()
245 intercepted = True
245 intercepted = True
246
246
247 elif key == QtCore.Qt.Key_D:
247 elif key == QtCore.Qt.Key_D:
248 cursor = self._get_word_end_cursor(position)
248 cursor = self._get_word_end_cursor(position)
249 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
249 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
250 cursor.removeSelectedText()
250 cursor.removeSelectedText()
251 intercepted = True
251 intercepted = True
252
252
253 if self._completion_widget.isVisible():
253 if self._completion_widget.isVisible():
254 self._completion_widget.keyPressEvent(event)
254 self._completion_widget.keyPressEvent(event)
255 intercepted = event.isAccepted()
255 intercepted = event.isAccepted()
256
256
257 else:
257 else:
258 if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
258 if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
259 if self._reading:
259 if self._reading:
260 self._reading = False
260 self._reading = False
261 elif not self._executing:
261 elif not self._executing:
262 self.execute(interactive=True)
262 self.execute(interactive=True)
263 intercepted = True
263 intercepted = True
264
264
265 elif key == QtCore.Qt.Key_Up:
265 elif key == QtCore.Qt.Key_Up:
266 if self._reading or not self._up_pressed():
266 if self._reading or not self._up_pressed():
267 intercepted = True
267 intercepted = True
268 else:
268 else:
269 prompt_line = self._get_prompt_cursor().blockNumber()
269 prompt_line = self._get_prompt_cursor().blockNumber()
270 intercepted = cursor.blockNumber() <= prompt_line
270 intercepted = cursor.blockNumber() <= prompt_line
271
271
272 elif key == QtCore.Qt.Key_Down:
272 elif key == QtCore.Qt.Key_Down:
273 if self._reading or not self._down_pressed():
273 if self._reading or not self._down_pressed():
274 intercepted = True
274 intercepted = True
275 else:
275 else:
276 end_line = self._get_end_cursor().blockNumber()
276 end_line = self._get_end_cursor().blockNumber()
277 intercepted = cursor.blockNumber() == end_line
277 intercepted = cursor.blockNumber() == end_line
278
278
279 elif key == QtCore.Qt.Key_Tab:
279 elif key == QtCore.Qt.Key_Tab:
280 if self._reading:
280 if self._reading:
281 intercepted = False
281 intercepted = False
282 else:
282 else:
283 intercepted = not self._tab_pressed()
283 intercepted = not self._tab_pressed()
284
284
285 elif key == QtCore.Qt.Key_Left:
285 elif key == QtCore.Qt.Key_Left:
286 intercepted = not self._in_buffer(position - 1)
286 intercepted = not self._in_buffer(position - 1)
287
287
288 elif key == QtCore.Qt.Key_Home:
288 elif key == QtCore.Qt.Key_Home:
289 cursor.movePosition(QtGui.QTextCursor.StartOfLine)
289 cursor.movePosition(QtGui.QTextCursor.StartOfLine)
290 start_pos = cursor.position()
290 start_pos = cursor.position()
291 start_line = cursor.blockNumber()
291 start_line = cursor.blockNumber()
292 if start_line == self._get_prompt_cursor().blockNumber():
292 if start_line == self._get_prompt_cursor().blockNumber():
293 start_pos += len(self._prompt)
293 start_pos += len(self._prompt)
294 else:
294 else:
295 start_pos += len(self._continuation_prompt)
295 start_pos += len(self._continuation_prompt)
296 if shift_down and self._in_buffer(position):
296 if shift_down and self._in_buffer(position):
297 self._set_selection(position, start_pos)
297 self._set_selection(position, start_pos)
298 else:
298 else:
299 self._set_position(start_pos)
299 self._set_position(start_pos)
300 intercepted = True
300 intercepted = True
301
301
302 elif key == QtCore.Qt.Key_Backspace and not alt_down:
302 elif key == QtCore.Qt.Key_Backspace and not alt_down:
303
303
304 # Line deletion (remove continuation prompt)
304 # Line deletion (remove continuation prompt)
305 len_prompt = len(self._continuation_prompt)
305 len_prompt = len(self._continuation_prompt)
306 if cursor.columnNumber() == len_prompt and \
306 if cursor.columnNumber() == len_prompt and \
307 position != self._prompt_pos:
307 position != self._prompt_pos:
308 cursor.setPosition(position - len_prompt,
308 cursor.setPosition(position - len_prompt,
309 QtGui.QTextCursor.KeepAnchor)
309 QtGui.QTextCursor.KeepAnchor)
310 cursor.removeSelectedText()
310 cursor.removeSelectedText()
311
311
312 # Regular backwards deletion
312 # Regular backwards deletion
313 else:
313 else:
314 anchor = cursor.anchor()
314 anchor = cursor.anchor()
315 if anchor == position:
315 if anchor == position:
316 intercepted = not self._in_buffer(position - 1)
316 intercepted = not self._in_buffer(position - 1)
317 else:
317 else:
318 intercepted = not self._in_buffer(min(anchor, position))
318 intercepted = not self._in_buffer(min(anchor, position))
319
319
320 elif key == QtCore.Qt.Key_Delete:
320 elif key == QtCore.Qt.Key_Delete:
321 anchor = cursor.anchor()
321 anchor = cursor.anchor()
322 intercepted = not self._in_buffer(min(anchor, position))
322 intercepted = not self._in_buffer(min(anchor, position))
323
323
324 # Don't move cursor if control is down to allow copy-paste using
324 # Don't move cursor if control is down to allow copy-paste using
325 # the keyboard in any part of the buffer.
325 # the keyboard in any part of the buffer.
326 if not ctrl_down:
326 if not ctrl_down:
327 self._keep_cursor_in_buffer()
327 self._keep_cursor_in_buffer()
328
328
329 if not intercepted:
329 if not intercepted:
330 QtGui.QPlainTextEdit.keyPressEvent(self, event)
330 QtGui.QPlainTextEdit.keyPressEvent(self, event)
331
331
332 #--------------------------------------------------------------------------
332 #--------------------------------------------------------------------------
333 # 'QPlainTextEdit' interface
333 # 'QPlainTextEdit' interface
334 #--------------------------------------------------------------------------
334 #--------------------------------------------------------------------------
335
335
336 def appendPlainText(self, text):
336 def appendPlainText(self, text):
337 """ Reimplemented to not append text as a new paragraph, which doesn't
337 """ Reimplemented to not append text as a new paragraph, which doesn't
338 make sense for a console widget. Also, if enabled, handle ANSI
338 make sense for a console widget. Also, if enabled, handle ANSI
339 codes.
339 codes.
340 """
340 """
341 cursor = self.textCursor()
341 cursor = self.textCursor()
342 cursor.movePosition(QtGui.QTextCursor.End)
342 cursor.movePosition(QtGui.QTextCursor.End)
343
343
344 if self.ansi_codes:
344 if self.ansi_codes:
345 format = QtGui.QTextCharFormat()
345 format = QtGui.QTextCharFormat()
346 previous_end = 0
346 previous_end = 0
347 for match in self._ansi_pattern.finditer(text):
347 for match in self._ansi_pattern.finditer(text):
348 cursor.insertText(text[previous_end:match.start()], format)
348 cursor.insertText(text[previous_end:match.start()], format)
349 previous_end = match.end()
349 previous_end = match.end()
350 for code in match.group(1).split(';'):
350 for code in match.group(1).split(';'):
351 self._ansi_processor.set_code(int(code))
351 self._ansi_processor.set_code(int(code))
352 format = self._ansi_processor.get_format()
352 format = self._ansi_processor.get_format()
353 cursor.insertText(text[previous_end:], format)
353 cursor.insertText(text[previous_end:], format)
354 else:
354 else:
355 cursor.insertText(text)
355 cursor.insertText(text)
356
356
357 def clear(self, keep_input=False):
357 def clear(self, keep_input=False):
358 """ Reimplemented to write a new prompt. If 'keep_input' is set,
358 """ Reimplemented to write a new prompt. If 'keep_input' is set,
359 restores the old input buffer when the new prompt is written.
359 restores the old input buffer when the new prompt is written.
360 """
360 """
361 super(ConsoleWidget, self).clear()
361 super(ConsoleWidget, self).clear()
362
362
363 if keep_input:
363 if keep_input:
364 input_buffer = self.input_buffer
364 input_buffer = self.input_buffer
365 self._show_prompt()
365 self._show_prompt()
366 if keep_input:
366 if keep_input:
367 self.input_buffer = input_buffer
367 self.input_buffer = input_buffer
368
368
369 def paste(self):
369 def paste(self):
370 """ Reimplemented to ensure that text is pasted in the editing region.
370 """ Reimplemented to ensure that text is pasted in the editing region.
371 """
371 """
372 self._keep_cursor_in_buffer()
372 self._keep_cursor_in_buffer()
373 QtGui.QPlainTextEdit.paste(self)
373 QtGui.QPlainTextEdit.paste(self)
374
374
375 def print_(self, printer):
375 def print_(self, printer):
376 """ Reimplemented to work around a bug in PyQt: the C++ level 'print_'
376 """ Reimplemented to work around a bug in PyQt: the C++ level 'print_'
377 slot has the wrong signature.
377 slot has the wrong signature.
378 """
378 """
379 QtGui.QPlainTextEdit.print_(self, printer)
379 QtGui.QPlainTextEdit.print_(self, printer)
380
380
381 #---------------------------------------------------------------------------
381 #---------------------------------------------------------------------------
382 # 'ConsoleWidget' public interface
382 # 'ConsoleWidget' public interface
383 #---------------------------------------------------------------------------
383 #---------------------------------------------------------------------------
384
384
385 def execute(self, interactive=False):
385 def execute(self, source=None, hidden=False, interactive=False):
386 """ Execute the text in the input buffer. Returns whether the input
386 """ Executes source or the input buffer, possibly prompting for more
387 buffer was completely processed and a new prompt created.
387 input.
388 """
388
389 self.appendPlainText('\n')
389 Parameters:
390 self._executing_input_buffer = self.input_buffer
390 -----------
391 self._executing = True
391 source : str, optional
392 self._prompt_finished()
392
393 return self._execute(interactive=interactive)
393 The source to execute. If not specified, the input buffer will be
394 used. If specified and 'hidden' is False, the input buffer will be
395 replaced with the source before execution.
396
397 hidden : bool, optional (default False)
398
399 If set, no output will be shown and the prompt will not be modified.
400 In other words, it will be completely invisible to the user that
401 an execution has occurred.
402
403 interactive : bool, optional (default False)
404
405 Whether the console is to treat the source as having been manually
406 entered by the user. The effect of this parameter depends on the
407 subclass implementation.
408
409 Raises:
410 -------
411 RuntimeError
412 If incomplete input is given and 'hidden' is True. In this case,
413 it not possible to prompt for more input.
414
415 Returns:
416 --------
417 A boolean indicating whether the source was executed.
418 """
419 if not hidden:
420 if source is not None:
421 self.input_buffer = source
422
423 self.appendPlainText('\n')
424 self._executing_input_buffer = self.input_buffer
425 self._executing = True
426 self._prompt_finished()
427
428 real_source = self.input_buffer if source is None else source
429 complete = self._is_complete(real_source, interactive)
430 if complete:
431 if not hidden:
432 # The maximum block count is only in effect during execution.
433 # This ensures that _prompt_pos does not become invalid due to
434 # text truncation.
435 self.setMaximumBlockCount(self.buffer_size)
436 self._execute(real_source, hidden)
437 elif hidden:
438 raise RuntimeError('Incomplete noninteractive input: "%s"' % source)
439 else:
440 self._show_continuation_prompt()
441
442 return complete
394
443
395 def _get_input_buffer(self):
444 def _get_input_buffer(self):
396 """ The text that the user has entered entered at the current prompt.
445 """ The text that the user has entered entered at the current prompt.
397 """
446 """
398 # If we're executing, the input buffer may not even exist anymore due to
447 # If we're executing, the input buffer may not even exist anymore due to
399 # the limit imposed by 'buffer_size'. Therefore, we store it.
448 # the limit imposed by 'buffer_size'. Therefore, we store it.
400 if self._executing:
449 if self._executing:
401 return self._executing_input_buffer
450 return self._executing_input_buffer
402
451
403 cursor = self._get_end_cursor()
452 cursor = self._get_end_cursor()
404 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
453 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
405
454
406 # Use QTextDocumentFragment intermediate object because it strips
455 # Use QTextDocumentFragment intermediate object because it strips
407 # out the Unicode line break characters that Qt insists on inserting.
456 # out the Unicode line break characters that Qt insists on inserting.
408 input_buffer = str(cursor.selection().toPlainText())
457 input_buffer = str(cursor.selection().toPlainText())
409
458
410 # Strip out continuation prompts.
459 # Strip out continuation prompts.
411 return input_buffer.replace('\n' + self._continuation_prompt, '\n')
460 return input_buffer.replace('\n' + self._continuation_prompt, '\n')
412
461
413 def _set_input_buffer(self, string):
462 def _set_input_buffer(self, string):
414 """ Replaces the text in the input buffer with 'string'.
463 """ Replaces the text in the input buffer with 'string'.
415 """
464 """
416 # Add continuation prompts where necessary.
465 # Add continuation prompts where necessary.
417 lines = string.splitlines()
466 lines = string.splitlines()
418 for i in xrange(1, len(lines)):
467 for i in xrange(1, len(lines)):
419 lines[i] = self._continuation_prompt + lines[i]
468 lines[i] = self._continuation_prompt + lines[i]
420 string = '\n'.join(lines)
469 string = '\n'.join(lines)
421
470
422 # Replace buffer with new text.
471 # Replace buffer with new text.
423 cursor = self._get_end_cursor()
472 cursor = self._get_end_cursor()
424 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
473 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
425 cursor.insertText(string)
474 cursor.insertText(string)
426 self.moveCursor(QtGui.QTextCursor.End)
475 self.moveCursor(QtGui.QTextCursor.End)
427
476
428 input_buffer = property(_get_input_buffer, _set_input_buffer)
477 input_buffer = property(_get_input_buffer, _set_input_buffer)
429
478
430 def _get_input_buffer_cursor_line(self):
479 def _get_input_buffer_cursor_line(self):
431 """ The text in the line of the input buffer in which the user's cursor
480 """ The text in the line of the input buffer in which the user's cursor
432 rests. Returns a string if there is such a line; otherwise, None.
481 rests. Returns a string if there is such a line; otherwise, None.
433 """
482 """
434 if self._executing:
483 if self._executing:
435 return None
484 return None
436 cursor = self.textCursor()
485 cursor = self.textCursor()
437 if cursor.position() >= self._prompt_pos:
486 if cursor.position() >= self._prompt_pos:
438 text = str(cursor.block().text())
487 text = str(cursor.block().text())
439 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
488 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
440 return text[len(self._prompt):]
489 return text[len(self._prompt):]
441 else:
490 else:
442 return text[len(self._continuation_prompt):]
491 return text[len(self._continuation_prompt):]
443 else:
492 else:
444 return None
493 return None
445
494
446 input_buffer_cursor_line = property(_get_input_buffer_cursor_line)
495 input_buffer_cursor_line = property(_get_input_buffer_cursor_line)
447
496
448 def _get_font(self):
497 def _get_font(self):
449 """ The base font being used by the ConsoleWidget.
498 """ The base font being used by the ConsoleWidget.
450 """
499 """
451 return self.document().defaultFont()
500 return self.document().defaultFont()
452
501
453 def _set_font(self, font):
502 def _set_font(self, font):
454 """ Sets the base font for the ConsoleWidget to the specified QFont.
503 """ Sets the base font for the ConsoleWidget to the specified QFont.
455 """
504 """
456 self._completion_widget.setFont(font)
505 self._completion_widget.setFont(font)
457 self.document().setDefaultFont(font)
506 self.document().setDefaultFont(font)
458
507
459 font = property(_get_font, _set_font)
508 font = property(_get_font, _set_font)
460
509
461 def reset_font(self):
510 def reset_font(self):
462 """ Sets the font to the default fixed-width font for this platform.
511 """ Sets the font to the default fixed-width font for this platform.
463 """
512 """
464 if sys.platform == 'win32':
513 if sys.platform == 'win32':
465 name = 'Courier'
514 name = 'Courier'
466 elif sys.platform == 'darwin':
515 elif sys.platform == 'darwin':
467 name = 'Monaco'
516 name = 'Monaco'
468 else:
517 else:
469 name = 'Monospace'
518 name = 'Monospace'
470 font = QtGui.QFont(name, QtGui.qApp.font().pointSize())
519 font = QtGui.QFont(name, QtGui.qApp.font().pointSize())
471 font.setStyleHint(QtGui.QFont.TypeWriter)
520 font.setStyleHint(QtGui.QFont.TypeWriter)
472 self._set_font(font)
521 self._set_font(font)
473
522
474 #---------------------------------------------------------------------------
523 #---------------------------------------------------------------------------
475 # 'ConsoleWidget' abstract interface
524 # 'ConsoleWidget' abstract interface
476 #---------------------------------------------------------------------------
525 #---------------------------------------------------------------------------
477
526
478 def _execute(self, interactive):
527 def _is_complete(self, source, interactive):
479 """ Called to execute the input buffer. When triggered by an the enter
528 """ Returns whether 'source' can be executed. When triggered by an
480 key press, 'interactive' is True; otherwise, it is False. Returns
529 Enter/Return key press, 'interactive' is True; otherwise, it is
481 whether the input buffer was completely processed and a new prompt
530 False.
482 created.
531 """
532 raise NotImplementedError
533
534 def _execute(self, source, hidden):
535 """ Execute 'source'. If 'hidden', do not show any output.
483 """
536 """
484 raise NotImplementedError
537 raise NotImplementedError
485
538
486 def _prompt_started_hook(self):
539 def _prompt_started_hook(self):
487 """ Called immediately after a new prompt is displayed.
540 """ Called immediately after a new prompt is displayed.
488 """
541 """
489 pass
542 pass
490
543
491 def _prompt_finished_hook(self):
544 def _prompt_finished_hook(self):
492 """ Called immediately after a prompt is finished, i.e. when some input
545 """ Called immediately after a prompt is finished, i.e. when some input
493 will be processed and a new prompt displayed.
546 will be processed and a new prompt displayed.
494 """
547 """
495 pass
548 pass
496
549
497 def _up_pressed(self):
550 def _up_pressed(self):
498 """ Called when the up key is pressed. Returns whether to continue
551 """ Called when the up key is pressed. Returns whether to continue
499 processing the event.
552 processing the event.
500 """
553 """
501 return True
554 return True
502
555
503 def _down_pressed(self):
556 def _down_pressed(self):
504 """ Called when the down key is pressed. Returns whether to continue
557 """ Called when the down key is pressed. Returns whether to continue
505 processing the event.
558 processing the event.
506 """
559 """
507 return True
560 return True
508
561
509 def _tab_pressed(self):
562 def _tab_pressed(self):
510 """ Called when the tab key is pressed. Returns whether to continue
563 """ Called when the tab key is pressed. Returns whether to continue
511 processing the event.
564 processing the event.
512 """
565 """
513 return False
566 return False
514
567
515 #--------------------------------------------------------------------------
568 #--------------------------------------------------------------------------
516 # 'ConsoleWidget' protected interface
569 # 'ConsoleWidget' protected interface
517 #--------------------------------------------------------------------------
570 #--------------------------------------------------------------------------
518
571
519 def _control_down(self, modifiers):
572 def _control_down(self, modifiers):
520 """ Given a KeyboardModifiers flags object, return whether the Control
573 """ Given a KeyboardModifiers flags object, return whether the Control
521 key is down (on Mac OS, treat the Command key as a synonym for
574 key is down (on Mac OS, treat the Command key as a synonym for
522 Control).
575 Control).
523 """
576 """
524 down = bool(modifiers & QtCore.Qt.ControlModifier)
577 down = bool(modifiers & QtCore.Qt.ControlModifier)
525
578
526 # Note: on Mac OS, ControlModifier corresponds to the Command key while
579 # Note: on Mac OS, ControlModifier corresponds to the Command key while
527 # MetaModifier corresponds to the Control key.
580 # MetaModifier corresponds to the Control key.
528 if sys.platform == 'darwin':
581 if sys.platform == 'darwin':
529 down = down ^ bool(modifiers & QtCore.Qt.MetaModifier)
582 down = down ^ bool(modifiers & QtCore.Qt.MetaModifier)
530
583
531 return down
584 return down
532
585
533 def _complete_with_items(self, cursor, items):
586 def _complete_with_items(self, cursor, items):
534 """ Performs completion with 'items' at the specified cursor location.
587 """ Performs completion with 'items' at the specified cursor location.
535 """
588 """
536 if len(items) == 1:
589 if len(items) == 1:
537 cursor.setPosition(self.textCursor().position(),
590 cursor.setPosition(self.textCursor().position(),
538 QtGui.QTextCursor.KeepAnchor)
591 QtGui.QTextCursor.KeepAnchor)
539 cursor.insertText(items[0])
592 cursor.insertText(items[0])
540 elif len(items) > 1:
593 elif len(items) > 1:
541 if self.gui_completion:
594 if self.gui_completion:
542 self._completion_widget.show_items(cursor, items)
595 self._completion_widget.show_items(cursor, items)
543 else:
596 else:
544 text = '\n'.join(items) + '\n'
597 text = '\n'.join(items) + '\n'
545 self._write_text_keeping_prompt(text)
598 self._write_text_keeping_prompt(text)
546
599
547 def _get_end_cursor(self):
600 def _get_end_cursor(self):
548 """ Convenience method that returns a cursor for the last character.
601 """ Convenience method that returns a cursor for the last character.
549 """
602 """
550 cursor = self.textCursor()
603 cursor = self.textCursor()
551 cursor.movePosition(QtGui.QTextCursor.End)
604 cursor.movePosition(QtGui.QTextCursor.End)
552 return cursor
605 return cursor
553
606
554 def _get_prompt_cursor(self):
607 def _get_prompt_cursor(self):
555 """ Convenience method that returns a cursor for the prompt position.
608 """ Convenience method that returns a cursor for the prompt position.
556 """
609 """
557 cursor = self.textCursor()
610 cursor = self.textCursor()
558 cursor.setPosition(self._prompt_pos)
611 cursor.setPosition(self._prompt_pos)
559 return cursor
612 return cursor
560
613
561 def _get_selection_cursor(self, start, end):
614 def _get_selection_cursor(self, start, end):
562 """ Convenience method that returns a cursor with text selected between
615 """ Convenience method that returns a cursor with text selected between
563 the positions 'start' and 'end'.
616 the positions 'start' and 'end'.
564 """
617 """
565 cursor = self.textCursor()
618 cursor = self.textCursor()
566 cursor.setPosition(start)
619 cursor.setPosition(start)
567 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
620 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
568 return cursor
621 return cursor
569
622
570 def _get_word_start_cursor(self, position):
623 def _get_word_start_cursor(self, position):
571 """ Find the start of the word to the left the given position. If a
624 """ Find the start of the word to the left the given position. If a
572 sequence of non-word characters precedes the first word, skip over
625 sequence of non-word characters precedes the first word, skip over
573 them. (This emulates the behavior of bash, emacs, etc.)
626 them. (This emulates the behavior of bash, emacs, etc.)
574 """
627 """
575 document = self.document()
628 document = self.document()
576 position -= 1
629 position -= 1
577 while self._in_buffer(position) and \
630 while self._in_buffer(position) and \
578 not document.characterAt(position).isLetterOrNumber():
631 not document.characterAt(position).isLetterOrNumber():
579 position -= 1
632 position -= 1
580 while self._in_buffer(position) and \
633 while self._in_buffer(position) and \
581 document.characterAt(position).isLetterOrNumber():
634 document.characterAt(position).isLetterOrNumber():
582 position -= 1
635 position -= 1
583 cursor = self.textCursor()
636 cursor = self.textCursor()
584 cursor.setPosition(position + 1)
637 cursor.setPosition(position + 1)
585 return cursor
638 return cursor
586
639
587 def _get_word_end_cursor(self, position):
640 def _get_word_end_cursor(self, position):
588 """ Find the end of the word to the right the given position. If a
641 """ Find the end of the word to the right the given position. If a
589 sequence of non-word characters precedes the first word, skip over
642 sequence of non-word characters precedes the first word, skip over
590 them. (This emulates the behavior of bash, emacs, etc.)
643 them. (This emulates the behavior of bash, emacs, etc.)
591 """
644 """
592 document = self.document()
645 document = self.document()
593 end = self._get_end_cursor().position()
646 end = self._get_end_cursor().position()
594 while position < end and \
647 while position < end and \
595 not document.characterAt(position).isLetterOrNumber():
648 not document.characterAt(position).isLetterOrNumber():
596 position += 1
649 position += 1
597 while position < end and \
650 while position < end and \
598 document.characterAt(position).isLetterOrNumber():
651 document.characterAt(position).isLetterOrNumber():
599 position += 1
652 position += 1
600 cursor = self.textCursor()
653 cursor = self.textCursor()
601 cursor.setPosition(position)
654 cursor.setPosition(position)
602 return cursor
655 return cursor
603
656
604 def _prompt_started(self):
657 def _prompt_started(self):
605 """ Called immediately after a new prompt is displayed.
658 """ Called immediately after a new prompt is displayed.
606 """
659 """
607 # Temporarily disable the maximum block count to permit undo/redo.
660 # Temporarily disable the maximum block count to permit undo/redo and
661 # to ensure that the prompt position does not change due to truncation.
608 self.setMaximumBlockCount(0)
662 self.setMaximumBlockCount(0)
609 self.setUndoRedoEnabled(True)
663 self.setUndoRedoEnabled(True)
610
664
611 self.setReadOnly(False)
665 self.setReadOnly(False)
612 self.moveCursor(QtGui.QTextCursor.End)
666 self.moveCursor(QtGui.QTextCursor.End)
613 self.centerCursor()
667 self.centerCursor()
614
668
615 self._executing = False
669 self._executing = False
616 self._prompt_started_hook()
670 self._prompt_started_hook()
617
671
618 def _prompt_finished(self):
672 def _prompt_finished(self):
619 """ Called immediately after a prompt is finished, i.e. when some input
673 """ Called immediately after a prompt is finished, i.e. when some input
620 will be processed and a new prompt displayed.
674 will be processed and a new prompt displayed.
621 """
675 """
622 # This has the (desired) side effect of disabling the undo/redo history.
676 self.setUndoRedoEnabled(False)
623 self.setMaximumBlockCount(self.buffer_size)
624
625 self.setReadOnly(True)
677 self.setReadOnly(True)
626 self._prompt_finished_hook()
678 self._prompt_finished_hook()
627
679
628 def _set_position(self, position):
680 def _set_position(self, position):
629 """ Convenience method to set the position of the cursor.
681 """ Convenience method to set the position of the cursor.
630 """
682 """
631 cursor = self.textCursor()
683 cursor = self.textCursor()
632 cursor.setPosition(position)
684 cursor.setPosition(position)
633 self.setTextCursor(cursor)
685 self.setTextCursor(cursor)
634
686
635 def _set_selection(self, start, end):
687 def _set_selection(self, start, end):
636 """ Convenience method to set the current selected text.
688 """ Convenience method to set the current selected text.
637 """
689 """
638 self.setTextCursor(self._get_selection_cursor(start, end))
690 self.setTextCursor(self._get_selection_cursor(start, end))
639
691
640 def _show_prompt(self, prompt=None):
692 def _show_prompt(self, prompt=None):
641 """ Writes a new prompt at the end of the buffer. If 'prompt' is not
693 """ Writes a new prompt at the end of the buffer. If 'prompt' is not
642 specified, uses the previous prompt.
694 specified, uses the previous prompt.
643 """
695 """
644 if prompt is not None:
696 if prompt is not None:
645 self._prompt = prompt
697 self._prompt = prompt
646 self.appendPlainText('\n' + self._prompt)
698 self.appendPlainText('\n' + self._prompt)
647 self._prompt_pos = self._get_end_cursor().position()
699 self._prompt_pos = self._get_end_cursor().position()
648 self._prompt_started()
700 self._prompt_started()
649
701
650 def _show_continuation_prompt(self):
702 def _show_continuation_prompt(self):
651 """ Writes a new continuation prompt at the end of the buffer.
703 """ Writes a new continuation prompt at the end of the buffer.
652 """
704 """
653 self.appendPlainText(self._continuation_prompt)
705 self.appendPlainText(self._continuation_prompt)
654 self._prompt_started()
706 self._prompt_started()
655
707
656 def _write_text_keeping_prompt(self, text):
708 def _write_text_keeping_prompt(self, text):
657 """ Writes 'text' after the current prompt, then restores the old prompt
709 """ Writes 'text' after the current prompt, then restores the old prompt
658 with its old input buffer.
710 with its old input buffer.
659 """
711 """
660 input_buffer = self.input_buffer
712 input_buffer = self.input_buffer
661 self.appendPlainText('\n')
713 self.appendPlainText('\n')
662 self._prompt_finished()
714 self._prompt_finished()
663
715
664 self.appendPlainText(text)
716 self.appendPlainText(text)
665 self._show_prompt()
717 self._show_prompt()
666 self.input_buffer = input_buffer
718 self.input_buffer = input_buffer
667
719
668 def _in_buffer(self, position):
720 def _in_buffer(self, position):
669 """ Returns whether the given position is inside the editing region.
721 """ Returns whether the given position is inside the editing region.
670 """
722 """
671 return position >= self._prompt_pos
723 return position >= self._prompt_pos
672
724
673 def _keep_cursor_in_buffer(self):
725 def _keep_cursor_in_buffer(self):
674 """ Ensures that the cursor is inside the editing region. Returns
726 """ Ensures that the cursor is inside the editing region. Returns
675 whether the cursor was moved.
727 whether the cursor was moved.
676 """
728 """
677 cursor = self.textCursor()
729 cursor = self.textCursor()
678 if cursor.position() < self._prompt_pos:
730 if cursor.position() < self._prompt_pos:
679 cursor.movePosition(QtGui.QTextCursor.End)
731 cursor.movePosition(QtGui.QTextCursor.End)
680 self.setTextCursor(cursor)
732 self.setTextCursor(cursor)
681 return True
733 return True
682 else:
734 else:
683 return False
735 return False
684
736
685
737
686 class HistoryConsoleWidget(ConsoleWidget):
738 class HistoryConsoleWidget(ConsoleWidget):
687 """ A ConsoleWidget that keeps a history of the commands that have been
739 """ A ConsoleWidget that keeps a history of the commands that have been
688 executed.
740 executed.
689 """
741 """
690
742
691 #---------------------------------------------------------------------------
743 #---------------------------------------------------------------------------
692 # 'QObject' interface
744 # 'QObject' interface
693 #---------------------------------------------------------------------------
745 #---------------------------------------------------------------------------
694
746
695 def __init__(self, parent=None):
747 def __init__(self, parent=None):
696 super(HistoryConsoleWidget, self).__init__(parent)
748 super(HistoryConsoleWidget, self).__init__(parent)
697
749
698 self._history = []
750 self._history = []
699 self._history_index = 0
751 self._history_index = 0
700
752
701 #---------------------------------------------------------------------------
753 #---------------------------------------------------------------------------
702 # 'ConsoleWidget' public interface
754 # 'ConsoleWidget' public interface
703 #---------------------------------------------------------------------------
755 #---------------------------------------------------------------------------
704
756
705 def execute(self, interactive=False):
757 def execute(self, source=None, hidden=False, interactive=False):
706 """ Reimplemented to the store history.
758 """ Reimplemented to the store history.
707 """
759 """
708 stripped = self.input_buffer.rstrip()
760 if not hidden:
709 executed = super(HistoryConsoleWidget, self).execute(interactive)
761 history = self.input_buffer if source is None else source
710 if executed:
762
711 self._history.append(stripped)
763 executed = super(HistoryConsoleWidget, self).execute(
764 source, hidden, interactive)
765
766 if executed and not hidden:
767 self._history.append(history.rstrip())
712 self._history_index = len(self._history)
768 self._history_index = len(self._history)
769
713 return executed
770 return executed
714
771
715 #---------------------------------------------------------------------------
772 #---------------------------------------------------------------------------
716 # 'ConsoleWidget' abstract interface
773 # 'ConsoleWidget' abstract interface
717 #---------------------------------------------------------------------------
774 #---------------------------------------------------------------------------
718
775
719 def _up_pressed(self):
776 def _up_pressed(self):
720 """ Called when the up key is pressed. Returns whether to continue
777 """ Called when the up key is pressed. Returns whether to continue
721 processing the event.
778 processing the event.
722 """
779 """
723 prompt_cursor = self._get_prompt_cursor()
780 prompt_cursor = self._get_prompt_cursor()
724 if self.textCursor().blockNumber() == prompt_cursor.blockNumber():
781 if self.textCursor().blockNumber() == prompt_cursor.blockNumber():
725 self.history_previous()
782 self.history_previous()
726
783
727 # Go to the first line of prompt for seemless history scrolling.
784 # Go to the first line of prompt for seemless history scrolling.
728 cursor = self._get_prompt_cursor()
785 cursor = self._get_prompt_cursor()
729 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
786 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
730 self.setTextCursor(cursor)
787 self.setTextCursor(cursor)
731
788
732 return False
789 return False
733 return True
790 return True
734
791
735 def _down_pressed(self):
792 def _down_pressed(self):
736 """ Called when the down key is pressed. Returns whether to continue
793 """ Called when the down key is pressed. Returns whether to continue
737 processing the event.
794 processing the event.
738 """
795 """
739 end_cursor = self._get_end_cursor()
796 end_cursor = self._get_end_cursor()
740 if self.textCursor().blockNumber() == end_cursor.blockNumber():
797 if self.textCursor().blockNumber() == end_cursor.blockNumber():
741 self.history_next()
798 self.history_next()
742 return False
799 return False
743 return True
800 return True
744
801
745 #---------------------------------------------------------------------------
802 #---------------------------------------------------------------------------
746 # 'HistoryConsoleWidget' interface
803 # 'HistoryConsoleWidget' interface
747 #---------------------------------------------------------------------------
804 #---------------------------------------------------------------------------
748
805
749 def history_previous(self):
806 def history_previous(self):
750 """ If possible, set the input buffer to the previous item in the
807 """ If possible, set the input buffer to the previous item in the
751 history.
808 history.
752 """
809 """
753 if self._history_index > 0:
810 if self._history_index > 0:
754 self._history_index -= 1
811 self._history_index -= 1
755 self.input_buffer = self._history[self._history_index]
812 self.input_buffer = self._history[self._history_index]
756
813
757 def history_next(self):
814 def history_next(self):
758 """ Set the input buffer to the next item in the history, or a blank
815 """ Set the input buffer to the next item in the history, or a blank
759 line if there is no subsequent item.
816 line if there is no subsequent item.
760 """
817 """
761 if self._history_index < len(self._history):
818 if self._history_index < len(self._history):
762 self._history_index += 1
819 self._history_index += 1
763 if self._history_index < len(self._history):
820 if self._history_index < len(self._history):
764 self.input_buffer = self._history[self._history_index]
821 self.input_buffer = self._history[self._history_index]
765 else:
822 else:
766 self.input_buffer = ''
823 self.input_buffer = ''
@@ -1,326 +1,330 b''
1 # Standard library imports
1 # Standard library imports
2 import signal
2 import signal
3
3
4 # System library imports
4 # System library imports
5 from pygments.lexers import PythonLexer
5 from pygments.lexers import PythonLexer
6 from PyQt4 import QtCore, QtGui
6 from PyQt4 import QtCore, QtGui
7 import zmq
7 import zmq
8
8
9 # Local imports
9 # Local imports
10 from IPython.core.inputsplitter import InputSplitter
10 from IPython.core.inputsplitter import InputSplitter
11 from call_tip_widget import CallTipWidget
11 from call_tip_widget import CallTipWidget
12 from completion_lexer import CompletionLexer
12 from completion_lexer import CompletionLexer
13 from console_widget import HistoryConsoleWidget
13 from console_widget import HistoryConsoleWidget
14 from pygments_highlighter import PygmentsHighlighter
14 from pygments_highlighter import PygmentsHighlighter
15
15
16
16
17 class FrontendHighlighter(PygmentsHighlighter):
17 class FrontendHighlighter(PygmentsHighlighter):
18 """ A Python PygmentsHighlighter that can be turned on and off and which
18 """ A Python PygmentsHighlighter that can be turned on and off and which
19 knows about continuation prompts.
19 knows about continuation prompts.
20 """
20 """
21
21
22 def __init__(self, frontend):
22 def __init__(self, frontend):
23 PygmentsHighlighter.__init__(self, frontend.document(), PythonLexer())
23 PygmentsHighlighter.__init__(self, frontend.document(), PythonLexer())
24 self._current_offset = 0
24 self._current_offset = 0
25 self._frontend = frontend
25 self._frontend = frontend
26 self.highlighting_on = False
26 self.highlighting_on = False
27
27
28 def highlightBlock(self, qstring):
28 def highlightBlock(self, qstring):
29 """ Highlight a block of text. Reimplemented to highlight selectively.
29 """ Highlight a block of text. Reimplemented to highlight selectively.
30 """
30 """
31 if self.highlighting_on:
31 if self.highlighting_on:
32 for prompt in (self._frontend._continuation_prompt,
32 for prompt in (self._frontend._continuation_prompt,
33 self._frontend._prompt):
33 self._frontend._prompt):
34 if qstring.startsWith(prompt):
34 if qstring.startsWith(prompt):
35 qstring.remove(0, len(prompt))
35 qstring.remove(0, len(prompt))
36 self._current_offset = len(prompt)
36 self._current_offset = len(prompt)
37 break
37 break
38 PygmentsHighlighter.highlightBlock(self, qstring)
38 PygmentsHighlighter.highlightBlock(self, qstring)
39
39
40 def setFormat(self, start, count, format):
40 def setFormat(self, start, count, format):
41 """ Reimplemented to avoid highlighting continuation prompts.
41 """ Reimplemented to avoid highlighting continuation prompts.
42 """
42 """
43 start += self._current_offset
43 start += self._current_offset
44 PygmentsHighlighter.setFormat(self, start, count, format)
44 PygmentsHighlighter.setFormat(self, start, count, format)
45
45
46
46
47 class FrontendWidget(HistoryConsoleWidget):
47 class FrontendWidget(HistoryConsoleWidget):
48 """ A Qt frontend for a generic Python kernel.
48 """ A Qt frontend for a generic Python kernel.
49 """
49 """
50
50
51 # Emitted when an 'execute_reply' is received from the kernel.
51 # Emitted when an 'execute_reply' is received from the kernel.
52 executed = QtCore.pyqtSignal(object)
52 executed = QtCore.pyqtSignal(object)
53
53
54 #---------------------------------------------------------------------------
54 #---------------------------------------------------------------------------
55 # 'QObject' interface
55 # 'QObject' interface
56 #---------------------------------------------------------------------------
56 #---------------------------------------------------------------------------
57
57
58 def __init__(self, parent=None):
58 def __init__(self, parent=None):
59 super(FrontendWidget, self).__init__(parent)
59 super(FrontendWidget, self).__init__(parent)
60
60
61 # ConsoleWidget protected variables.
61 # ConsoleWidget protected variables.
62 self._continuation_prompt = '... '
62 self._continuation_prompt = '... '
63 self._prompt = '>>> '
63 self._prompt = '>>> '
64
64
65 # FrontendWidget protected variables.
65 # FrontendWidget protected variables.
66 self._call_tip_widget = CallTipWidget(self)
66 self._call_tip_widget = CallTipWidget(self)
67 self._completion_lexer = CompletionLexer(PythonLexer())
67 self._completion_lexer = CompletionLexer(PythonLexer())
68 self._hidden = True
68 self._hidden = True
69 self._highlighter = FrontendHighlighter(self)
69 self._highlighter = FrontendHighlighter(self)
70 self._input_splitter = InputSplitter(input_mode='replace')
70 self._input_splitter = InputSplitter(input_mode='replace')
71 self._kernel_manager = None
71 self._kernel_manager = None
72
72
73 self.document().contentsChange.connect(self._document_contents_change)
73 self.document().contentsChange.connect(self._document_contents_change)
74
74
75 #---------------------------------------------------------------------------
75 #---------------------------------------------------------------------------
76 # 'QWidget' interface
76 # 'QWidget' interface
77 #---------------------------------------------------------------------------
77 #---------------------------------------------------------------------------
78
78
79 def focusOutEvent(self, event):
79 def focusOutEvent(self, event):
80 """ Reimplemented to hide calltips.
80 """ Reimplemented to hide calltips.
81 """
81 """
82 self._call_tip_widget.hide()
82 self._call_tip_widget.hide()
83 super(FrontendWidget, self).focusOutEvent(event)
83 super(FrontendWidget, self).focusOutEvent(event)
84
84
85 def keyPressEvent(self, event):
85 def keyPressEvent(self, event):
86 """ Reimplemented to allow calltips to process events and to send
86 """ Reimplemented to allow calltips to process events and to send
87 signals to the kernel.
87 signals to the kernel.
88 """
88 """
89 if self._executing and event.key() == QtCore.Qt.Key_C and \
89 if self._executing and event.key() == QtCore.Qt.Key_C and \
90 self._control_down(event.modifiers()):
90 self._control_down(event.modifiers()):
91 self._interrupt_kernel()
91 self._interrupt_kernel()
92 else:
92 else:
93 self._call_tip_widget.keyPressEvent(event)
93 if self._call_tip_widget.isVisible():
94 self._call_tip_widget.keyPressEvent(event)
94 super(FrontendWidget, self).keyPressEvent(event)
95 super(FrontendWidget, self).keyPressEvent(event)
95
96
96 #---------------------------------------------------------------------------
97 #---------------------------------------------------------------------------
97 # 'ConsoleWidget' abstract interface
98 # 'ConsoleWidget' abstract interface
98 #---------------------------------------------------------------------------
99 #---------------------------------------------------------------------------
99
100
100 def _execute(self, interactive):
101 def _is_complete(self, source, interactive):
101 """ Called to execute the input buffer. When triggered by an the enter
102 """ Returns whether 'source' can be completely processed and a new
102 key press, 'interactive' is True; otherwise, it is False. Returns
103 prompt created. When triggered by an Enter/Return key press,
103 whether the input buffer was completely processed and a new prompt
104 'interactive' is True; otherwise, it is False.
104 created.
105 """
105 """
106 return self.execute_source(self.input_buffer, interactive=interactive)
106 complete = self._input_splitter.push(source)
107 if interactive:
108 complete = not self._input_splitter.push_accepts_more()
109 return complete
110
111 def _execute(self, source, hidden):
112 """ Execute 'source'. If 'hidden', do not show any output.
113 """
114 self.kernel_manager.xreq_channel.execute(source)
115 self._hidden = hidden
107
116
108 def _prompt_started_hook(self):
117 def _prompt_started_hook(self):
109 """ Called immediately after a new prompt is displayed.
118 """ Called immediately after a new prompt is displayed.
110 """
119 """
111 self._highlighter.highlighting_on = True
120 self._highlighter.highlighting_on = True
112
121
122 # Auto-indent if this is a continuation prompt.
123 if self._get_prompt_cursor().blockNumber() != \
124 self._get_end_cursor().blockNumber():
125 self.appendPlainText(' ' * self._input_splitter.indent_spaces)
126
113 def _prompt_finished_hook(self):
127 def _prompt_finished_hook(self):
114 """ Called immediately after a prompt is finished, i.e. when some input
128 """ Called immediately after a prompt is finished, i.e. when some input
115 will be processed and a new prompt displayed.
129 will be processed and a new prompt displayed.
116 """
130 """
117 self._highlighter.highlighting_on = False
131 self._highlighter.highlighting_on = False
118
132
119 def _tab_pressed(self):
133 def _tab_pressed(self):
120 """ Called when the tab key is pressed. Returns whether to continue
134 """ Called when the tab key is pressed. Returns whether to continue
121 processing the event.
135 processing the event.
122 """
136 """
123 self._keep_cursor_in_buffer()
137 self._keep_cursor_in_buffer()
124 cursor = self.textCursor()
138 cursor = self.textCursor()
125 if not self._complete():
139 if not self._complete():
126 cursor.insertText(' ')
140 cursor.insertText(' ')
127 return False
141 return False
128
142
129 #---------------------------------------------------------------------------
143 #---------------------------------------------------------------------------
130 # 'FrontendWidget' interface
144 # 'FrontendWidget' interface
131 #---------------------------------------------------------------------------
145 #---------------------------------------------------------------------------
132
146
133 def execute_source(self, source, hidden=False, interactive=False):
134 """ Execute a string containing Python code. If 'hidden', no output is
135 shown. Returns whether the source executed (i.e., returns True only
136 if no more input is necessary).
137 """
138 self._input_splitter.push(source)
139 executed = not self._input_splitter.push_accepts_more()
140 if executed:
141 self.kernel_manager.xreq_channel.execute(source)
142 self._hidden = hidden
143 else:
144 self._show_continuation_prompt()
145 self.appendPlainText(' ' * self._input_splitter.indent_spaces)
146 return executed
147
148 def execute_file(self, path, hidden=False):
147 def execute_file(self, path, hidden=False):
149 """ Attempts to execute file with 'path'. If 'hidden', no output is
148 """ Attempts to execute file with 'path'. If 'hidden', no output is
150 shown.
149 shown.
151 """
150 """
152 self.execute_source('run %s' % path, hidden=hidden)
151 self.execute('execfile("%s")' % path, hidden=hidden)
153
152
154 def _get_kernel_manager(self):
153 def _get_kernel_manager(self):
155 """ Returns the current kernel manager.
154 """ Returns the current kernel manager.
156 """
155 """
157 return self._kernel_manager
156 return self._kernel_manager
158
157
159 def _set_kernel_manager(self, kernel_manager):
158 def _set_kernel_manager(self, kernel_manager):
160 """ Disconnect from the current kernel manager (if any) and set a new
159 """ Disconnect from the current kernel manager (if any) and set a new
161 kernel manager.
160 kernel manager.
162 """
161 """
163 # Disconnect the old kernel manager, if necessary.
162 # Disconnect the old kernel manager, if necessary.
164 if self._kernel_manager is not None:
163 if self._kernel_manager is not None:
165 self._kernel_manager.started_listening.disconnect(
164 self._kernel_manager.started_listening.disconnect(
166 self._started_listening)
165 self._started_listening)
167 self._kernel_manager.stopped_listening.disconnect(
166 self._kernel_manager.stopped_listening.disconnect(
168 self._stopped_listening)
167 self._stopped_listening)
169
168
170 # Disconnect the old kernel manager's channels.
169 # Disconnect the old kernel manager's channels.
171 sub = self._kernel_manager.sub_channel
170 sub = self._kernel_manager.sub_channel
172 xreq = self._kernel_manager.xreq_channel
171 xreq = self._kernel_manager.xreq_channel
173 sub.message_received.disconnect(self._handle_sub)
172 sub.message_received.disconnect(self._handle_sub)
174 xreq.execute_reply.disconnect(self._handle_execute_reply)
173 xreq.execute_reply.disconnect(self._handle_execute_reply)
175 xreq.complete_reply.disconnect(self._handle_complete_reply)
174 xreq.complete_reply.disconnect(self._handle_complete_reply)
176 xreq.object_info_reply.disconnect(self._handle_object_info_reply)
175 xreq.object_info_reply.disconnect(self._handle_object_info_reply)
177
176
178 # Handle the case where the old kernel manager is still listening.
177 # Handle the case where the old kernel manager is still listening.
179 if self._kernel_manager.is_listening:
178 if self._kernel_manager.is_listening:
180 self._stopped_listening()
179 self._stopped_listening()
181
180
182 # Set the new kernel manager.
181 # Set the new kernel manager.
183 self._kernel_manager = kernel_manager
182 self._kernel_manager = kernel_manager
184 if kernel_manager is None:
183 if kernel_manager is None:
185 return
184 return
186
185
187 # Connect the new kernel manager.
186 # Connect the new kernel manager.
188 kernel_manager.started_listening.connect(self._started_listening)
187 kernel_manager.started_listening.connect(self._started_listening)
189 kernel_manager.stopped_listening.connect(self._stopped_listening)
188 kernel_manager.stopped_listening.connect(self._stopped_listening)
190
189
191 # Connect the new kernel manager's channels.
190 # Connect the new kernel manager's channels.
192 sub = kernel_manager.sub_channel
191 sub = kernel_manager.sub_channel
193 xreq = kernel_manager.xreq_channel
192 xreq = kernel_manager.xreq_channel
194 sub.message_received.connect(self._handle_sub)
193 sub.message_received.connect(self._handle_sub)
195 xreq.execute_reply.connect(self._handle_execute_reply)
194 xreq.execute_reply.connect(self._handle_execute_reply)
196 xreq.complete_reply.connect(self._handle_complete_reply)
195 xreq.complete_reply.connect(self._handle_complete_reply)
197 xreq.object_info_reply.connect(self._handle_object_info_reply)
196 xreq.object_info_reply.connect(self._handle_object_info_reply)
198
197
199 # Handle the case where the kernel manager started listening before
198 # Handle the case where the kernel manager started listening before
200 # we connected.
199 # we connected.
201 if kernel_manager.is_listening:
200 if kernel_manager.is_listening:
202 self._started_listening()
201 self._started_listening()
203
202
204 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
203 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
205
204
206 #---------------------------------------------------------------------------
205 #---------------------------------------------------------------------------
207 # 'FrontendWidget' protected interface
206 # 'FrontendWidget' protected interface
208 #---------------------------------------------------------------------------
207 #---------------------------------------------------------------------------
209
208
210 def _call_tip(self):
209 def _call_tip(self):
211 """ Shows a call tip, if appropriate, at the current cursor location.
210 """ Shows a call tip, if appropriate, at the current cursor location.
212 """
211 """
213 # Decide if it makes sense to show a call tip
212 # Decide if it makes sense to show a call tip
214 cursor = self.textCursor()
213 cursor = self.textCursor()
215 cursor.movePosition(QtGui.QTextCursor.Left)
214 cursor.movePosition(QtGui.QTextCursor.Left)
216 document = self.document()
215 document = self.document()
217 if document.characterAt(cursor.position()).toAscii() != '(':
216 if document.characterAt(cursor.position()).toAscii() != '(':
218 return False
217 return False
219 context = self._get_context(cursor)
218 context = self._get_context(cursor)
220 if not context:
219 if not context:
221 return False
220 return False
222
221
223 # Send the metadata request to the kernel
222 # Send the metadata request to the kernel
224 name = '.'.join(context)
223 name = '.'.join(context)
225 self._calltip_id = self.kernel_manager.xreq_channel.object_info(name)
224 self._calltip_id = self.kernel_manager.xreq_channel.object_info(name)
226 self._calltip_pos = self.textCursor().position()
225 self._calltip_pos = self.textCursor().position()
227 return True
226 return True
228
227
229 def _complete(self):
228 def _complete(self):
230 """ Performs completion at the current cursor location.
229 """ Performs completion at the current cursor location.
231 """
230 """
232 # Decide if it makes sense to do completion
231 # Decide if it makes sense to do completion
233 context = self._get_context()
232 context = self._get_context()
234 if not context:
233 if not context:
235 return False
234 return False
236
235
237 # Send the completion request to the kernel
236 # Send the completion request to the kernel
238 text = '.'.join(context)
237 text = '.'.join(context)
239 self._complete_id = self.kernel_manager.xreq_channel.complete(
238 self._complete_id = self.kernel_manager.xreq_channel.complete(
240 text, self.input_buffer_cursor_line, self.input_buffer)
239 text, self.input_buffer_cursor_line, self.input_buffer)
241 self._complete_pos = self.textCursor().position()
240 self._complete_pos = self.textCursor().position()
242 return True
241 return True
243
242
244 def _get_context(self, cursor=None):
243 def _get_context(self, cursor=None):
245 """ Gets the context at the current cursor location.
244 """ Gets the context at the current cursor location.
246 """
245 """
247 if cursor is None:
246 if cursor is None:
248 cursor = self.textCursor()
247 cursor = self.textCursor()
249 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
248 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
250 QtGui.QTextCursor.KeepAnchor)
249 QtGui.QTextCursor.KeepAnchor)
251 text = unicode(cursor.selectedText())
250 text = unicode(cursor.selectedText())
252 return self._completion_lexer.get_context(text)
251 return self._completion_lexer.get_context(text)
253
252
254 def _interrupt_kernel(self):
253 def _interrupt_kernel(self):
255 """ Attempts to the interrupt the kernel.
254 """ Attempts to the interrupt the kernel.
256 """
255 """
257 if self.kernel_manager.has_kernel:
256 if self.kernel_manager.has_kernel:
258 self.kernel_manager.signal_kernel(signal.SIGINT)
257 self.kernel_manager.signal_kernel(signal.SIGINT)
259 else:
258 else:
260 self.appendPlainText('Kernel process is either remote or '
259 self.appendPlainText('Kernel process is either remote or '
261 'unspecified. Cannot interrupt.\n')
260 'unspecified. Cannot interrupt.\n')
262
261
263 #------ Signal handlers ----------------------------------------------------
262 #------ Signal handlers ----------------------------------------------------
264
263
265 def _document_contents_change(self, position, removed, added):
264 def _document_contents_change(self, position, removed, added):
266 """ Called whenever the document's content changes. Display a calltip
265 """ Called whenever the document's content changes. Display a calltip
267 if appropriate.
266 if appropriate.
268 """
267 """
269 # Calculate where the cursor should be *after* the change:
268 # Calculate where the cursor should be *after* the change:
270 position += added
269 position += added
271
270
272 document = self.document()
271 document = self.document()
273 if position == self.textCursor().position():
272 if position == self.textCursor().position():
274 self._call_tip()
273 self._call_tip()
275
274
276 def _handle_sub(self, omsg):
275 def _handle_sub(self, omsg):
277 if not self._hidden:
276 if self._hidden:
278 handler = getattr(self, '_handle_%s' % omsg['msg_type'], None)
277 return
279 if handler is not None:
278 handler = getattr(self, '_handle_%s' % omsg['msg_type'], None)
280 handler(omsg)
279 if handler is not None:
280 handler(omsg)
281
281
282 def _handle_pyout(self, omsg):
282 def _handle_pyout(self, omsg):
283 session = omsg['parent_header']['session']
283 session = omsg['parent_header']['session']
284 if session == self.kernel_manager.session.session:
284 if session == self.kernel_manager.session.session:
285 self.appendPlainText(omsg['content']['data'] + '\n')
285 self.appendPlainText(omsg['content']['data'] + '\n')
286
286
287 def _handle_stream(self, omsg):
287 def _handle_stream(self, omsg):
288 self.appendPlainText(omsg['content']['data'])
288 self.appendPlainText(omsg['content']['data'])
289 self.moveCursor(QtGui.QTextCursor.End)
289
290
290 def _handle_execute_reply(self, rep):
291 def _handle_execute_reply(self, rep):
292 if self._hidden:
293 return
294
291 # Make sure that all output from the SUB channel has been processed
295 # Make sure that all output from the SUB channel has been processed
292 # before writing a new prompt.
296 # before writing a new prompt.
293 self.kernel_manager.sub_channel.flush()
297 self.kernel_manager.sub_channel.flush()
294
298
295 content = rep['content']
299 content = rep['content']
296 status = content['status']
300 status = content['status']
297 if status == 'error':
301 if status == 'error':
298 self.appendPlainText(content['traceback'][-1])
302 self.appendPlainText(content['traceback'][-1])
299 elif status == 'aborted':
303 elif status == 'aborted':
300 text = "ERROR: ABORTED\n"
304 text = "ERROR: ABORTED\n"
301 self.appendPlainText(text)
305 self.appendPlainText(text)
302 self._hidden = True
306 self._hidden = True
303 self._show_prompt()
307 self._show_prompt()
304 self.executed.emit(rep)
308 self.executed.emit(rep)
305
309
306 def _handle_complete_reply(self, rep):
310 def _handle_complete_reply(self, rep):
307 cursor = self.textCursor()
311 cursor = self.textCursor()
308 if rep['parent_header']['msg_id'] == self._complete_id and \
312 if rep['parent_header']['msg_id'] == self._complete_id and \
309 cursor.position() == self._complete_pos:
313 cursor.position() == self._complete_pos:
310 text = '.'.join(self._get_context())
314 text = '.'.join(self._get_context())
311 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
315 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
312 self._complete_with_items(cursor, rep['content']['matches'])
316 self._complete_with_items(cursor, rep['content']['matches'])
313
317
314 def _handle_object_info_reply(self, rep):
318 def _handle_object_info_reply(self, rep):
315 cursor = self.textCursor()
319 cursor = self.textCursor()
316 if rep['parent_header']['msg_id'] == self._calltip_id and \
320 if rep['parent_header']['msg_id'] == self._calltip_id and \
317 cursor.position() == self._calltip_pos:
321 cursor.position() == self._calltip_pos:
318 doc = rep['content']['docstring']
322 doc = rep['content']['docstring']
319 if doc:
323 if doc:
320 self._call_tip_widget.show_docstring(doc)
324 self._call_tip_widget.show_docstring(doc)
321
325
322 def _started_listening(self):
326 def _started_listening(self):
323 self.clear()
327 self.clear()
324
328
325 def _stopped_listening(self):
329 def _stopped_listening(self):
326 pass
330 pass
@@ -1,100 +1,105 b''
1 # System library imports
1 # System library imports
2 from PyQt4 import QtCore, QtGui
2 from PyQt4 import QtCore, QtGui
3
3
4 # Local imports
4 # Local imports
5 from frontend_widget import FrontendWidget
5 from frontend_widget import FrontendWidget
6
6
7
7
8 class IPythonWidget(FrontendWidget):
8 class IPythonWidget(FrontendWidget):
9 """ A FrontendWidget for an IPython kernel.
9 """ A FrontendWidget for an IPython kernel.
10 """
10 """
11
11
12 #---------------------------------------------------------------------------
12 #---------------------------------------------------------------------------
13 # 'QObject' interface
13 # 'QObject' interface
14 #---------------------------------------------------------------------------
14 #---------------------------------------------------------------------------
15
15
16 def __init__(self, parent=None):
16 def __init__(self, parent=None):
17 super(IPythonWidget, self).__init__(parent)
17 super(IPythonWidget, self).__init__(parent)
18
18
19 self._magic_overrides = {}
19 self._magic_overrides = {}
20
20
21 #---------------------------------------------------------------------------
21 #---------------------------------------------------------------------------
22 # 'FrontendWidget' interface
22 # 'ConsoleWidget' abstract interface
23 #---------------------------------------------------------------------------
23 #---------------------------------------------------------------------------
24
24
25 def execute_source(self, source, hidden=False, interactive=False):
25 def _execute(self, source, hidden):
26 """ Reimplemented to override magic commands.
26 """ Reimplemented to override magic commands.
27 """
27 """
28 magic_source = source.strip()
28 magic_source = source.strip()
29 if magic_source.startswith('%'):
29 if magic_source.startswith('%'):
30 magic_source = magic_source[1:]
30 magic_source = magic_source[1:]
31 magic, sep, arguments = magic_source.partition(' ')
31 magic, sep, arguments = magic_source.partition(' ')
32 if not magic:
32 if not magic:
33 magic = magic_source
33 magic = magic_source
34
34
35 callback = self._magic_overrides.get(magic)
35 callback = self._magic_overrides.get(magic)
36 if callback:
36 if callback:
37 output = callback(arguments)
37 output = callback(arguments)
38 if output:
38 if output:
39 self.appendPlainText(output)
39 self.appendPlainText(output)
40 self._show_prompt('>>> ')
40 self._show_prompt()
41 return True
42 else:
41 else:
43 return super(IPythonWidget, self).execute_source(source, hidden,
42 super(IPythonWidget, self)._execute(source, hidden)
44 interactive)
43
44 #---------------------------------------------------------------------------
45 # 'FrontendWidget' interface
46 #---------------------------------------------------------------------------
47
48 def execute_file(self, path, hidden=False):
49 """ Reimplemented to use the 'run' magic.
50 """
51 self.execute('run %s' % path, hidden=hidden)
45
52
46 #---------------------------------------------------------------------------
53 #---------------------------------------------------------------------------
47 # 'IPythonWidget' interface
54 # 'IPythonWidget' interface
48 #---------------------------------------------------------------------------
55 #---------------------------------------------------------------------------
49
56
50 def set_magic_override(self, magic, callback):
57 def set_magic_override(self, magic, callback):
51 """ Overrides an IPython magic command. This magic will be intercepted
58 """ Overrides an IPython magic command. This magic will be intercepted
52 by the frontend rather than passed on to the kernel and 'callback'
59 by the frontend rather than passed on to the kernel and 'callback'
53 will be called with a single argument: a string of argument(s) for
60 will be called with a single argument: a string of argument(s) for
54 the magic. The callback can (optionally) return text to print to the
61 the magic. The callback can (optionally) return text to print to the
55 console.
62 console.
56 """
63 """
57 self._magic_overrides[magic] = callback
64 self._magic_overrides[magic] = callback
58
65
59 def remove_magic_override(self, magic):
66 def remove_magic_override(self, magic):
60 """ Removes the override for the specified magic, if there is one.
67 """ Removes the override for the specified magic, if there is one.
61 """
68 """
62 try:
69 try:
63 del self._magic_overrides[magic]
70 del self._magic_overrides[magic]
64 except KeyError:
71 except KeyError:
65 pass
72 pass
66
73
67
74
68 if __name__ == '__main__':
75 if __name__ == '__main__':
69 import signal
76 import signal
70 from IPython.frontend.qt.kernelmanager import QtKernelManager
77 from IPython.frontend.qt.kernelmanager import QtKernelManager
71
78
72 # Create a KernelManager.
79 # Create a KernelManager.
73 kernel_manager = QtKernelManager()
80 kernel_manager = QtKernelManager()
74 kernel_manager.start_kernel()
81 kernel_manager.start_kernel()
75 kernel_manager.start_listening()
82 kernel_manager.start_listening()
76
83
77 # Don't let Qt or ZMQ swallow KeyboardInterupts.
84 # Don't let Qt or ZMQ swallow KeyboardInterupts.
78 # FIXME: Gah, ZMQ swallows even custom signal handlers. So for now we leave
85 # FIXME: Gah, ZMQ swallows even custom signal handlers. So for now we leave
79 # behind a kernel process when Ctrl-C is pressed.
86 # behind a kernel process when Ctrl-C is pressed.
80 #def sigint_hook(signum, frame):
87 #def sigint_hook(signum, frame):
81 # QtGui.qApp.quit()
88 # QtGui.qApp.quit()
82 #signal.signal(signal.SIGINT, sigint_hook)
89 #signal.signal(signal.SIGINT, sigint_hook)
83 signal.signal(signal.SIGINT, signal.SIG_DFL)
90 signal.signal(signal.SIGINT, signal.SIG_DFL)
84
91
85 # Create the application, making sure to clean up nicely when we exit.
92 # Create the application, making sure to clean up nicely when we exit.
86 app = QtGui.QApplication([])
93 app = QtGui.QApplication([])
87 def quit_hook():
94 def quit_hook():
88 kernel_manager.stop_listening()
95 kernel_manager.stop_listening()
89 kernel_manager.kill_kernel()
96 kernel_manager.kill_kernel()
90 app.aboutToQuit.connect(quit_hook)
97 app.aboutToQuit.connect(quit_hook)
91
98
92 # Launch the application.
99 # Launch the application.
93 widget = IPythonWidget()
100 widget = IPythonWidget()
94 widget.kernel_manager = kernel_manager
101 widget.kernel_manager = kernel_manager
95 widget.setWindowTitle('Python')
102 widget.setWindowTitle('Python')
96 widget.resize(640, 480)
103 widget.resize(640, 480)
97 widget.show()
104 widget.show()
98 app.exec_()
105 app.exec_()
99
100
General Comments 0
You need to be logged in to leave comments. Login now