##// END OF EJS Templates
Merge branch 'epatters-qtfrontend' into kernelmanager
Brian Granger -
r2741:e34a2ef9 merge
parent child Browse files
Show More
This diff has been collapsed as it changes many lines, (683 lines changed) Show them Hide them
@@ -9,7 +9,7 b' from ansi_code_processor import QtAnsiCodeProcessor'
9 from completion_widget import CompletionWidget
9 from completion_widget import CompletionWidget
10
10
11
11
12 class ConsoleWidget(QtGui.QPlainTextEdit):
12 class ConsoleWidget(QtGui.QWidget):
13 """ Base class for console-type widgets. This class is mainly concerned with
13 """ Base class for console-type widgets. This class is mainly concerned with
14 dealing with the prompt, keeping the cursor inside the editing line, and
14 dealing with the prompt, keeping the cursor inside the editing line, and
15 handling ANSI escape sequences.
15 handling ANSI escape sequences.
@@ -29,6 +29,11 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
29 # priority (when it has focus) over, e.g., window-level menu shortcuts.
29 # priority (when it has focus) over, e.g., window-level menu shortcuts.
30 override_shortcuts = False
30 override_shortcuts = False
31
31
32 # Signals that indicate ConsoleWidget state.
33 copy_available = QtCore.pyqtSignal(bool)
34 redo_available = QtCore.pyqtSignal(bool)
35 undo_available = QtCore.pyqtSignal(bool)
36
32 # Protected class variables.
37 # Protected class variables.
33 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
38 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
34 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
39 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
@@ -44,13 +49,28 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
44 # 'QObject' interface
49 # 'QObject' interface
45 #---------------------------------------------------------------------------
50 #---------------------------------------------------------------------------
46
51
47 def __init__(self, parent=None):
52 def __init__(self, kind='plain', parent=None):
48 QtGui.QPlainTextEdit.__init__(self, parent)
53 """ Create a ConsoleWidget.
54
55 Parameters
56 ----------
57 kind : str, optional [default 'plain']
58 The type of text widget to use. Valid values are 'plain', which
59 specifies a QPlainTextEdit, and 'rich', which specifies an
60 QTextEdit.
61
62 parent : QWidget, optional [default None]
63 The parent for this widget.
64 """
65 super(ConsoleWidget, self).__init__(parent)
66
67 # Create the underlying text widget.
68 self._control = self._create_control(kind)
49
69
50 # Initialize protected variables. Some variables contain useful state
70 # Initialize protected variables. Some variables contain useful state
51 # information for subclasses; they should be considered read-only.
71 # information for subclasses; they should be considered read-only.
52 self._ansi_processor = QtAnsiCodeProcessor()
72 self._ansi_processor = QtAnsiCodeProcessor()
53 self._completion_widget = CompletionWidget(self)
73 self._completion_widget = CompletionWidget(self._control)
54 self._continuation_prompt = '> '
74 self._continuation_prompt = '> '
55 self._continuation_prompt_html = None
75 self._continuation_prompt_html = None
56 self._executing = False
76 self._executing = False
@@ -64,249 +84,51 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
64 # Set a monospaced font.
84 # Set a monospaced font.
65 self.reset_font()
85 self.reset_font()
66
86
67 # Define a custom context menu.
87 def eventFilter(self, obj, event):
68 self._context_menu = QtGui.QMenu(self)
88 """ Reimplemented to ensure a console-like behavior in the underlying
69
89 text widget.
70 copy_action = QtGui.QAction('Copy', self)
71 copy_action.triggered.connect(self.copy)
72 self.copyAvailable.connect(copy_action.setEnabled)
73 self._context_menu.addAction(copy_action)
74
75 self._paste_action = QtGui.QAction('Paste', self)
76 self._paste_action.triggered.connect(self.paste)
77 self._context_menu.addAction(self._paste_action)
78 self._context_menu.addSeparator()
79
80 select_all_action = QtGui.QAction('Select All', self)
81 select_all_action.triggered.connect(self.selectAll)
82 self._context_menu.addAction(select_all_action)
83
84 def event(self, event):
85 """ Reimplemented to override shortcuts, if necessary.
86 """
87 # On Mac OS, it is always unnecessary to override shortcuts, hence the
88 # check below. Users should just use the Control key instead of the
89 # Command key.
90 if self.override_shortcuts and \
91 sys.platform != 'darwin' and \
92 event.type() == QtCore.QEvent.ShortcutOverride and \
93 self._control_down(event.modifiers()) and \
94 event.key() in self._shortcuts:
95 event.accept()
96 return True
97 else:
98 return QtGui.QPlainTextEdit.event(self, event)
99
100 #---------------------------------------------------------------------------
101 # 'QWidget' interface
102 #---------------------------------------------------------------------------
103
104 def contextMenuEvent(self, event):
105 """ Reimplemented to create a menu without destructive actions like
106 'Cut' and 'Delete'.
107 """
108 clipboard_empty = QtGui.QApplication.clipboard().text().isEmpty()
109 self._paste_action.setEnabled(not clipboard_empty)
110
111 self._context_menu.exec_(event.globalPos())
112
113 def dragMoveEvent(self, event):
114 """ Reimplemented to disable moving text by drag and drop.
115 """
116 event.ignore()
117
118 def keyPressEvent(self, event):
119 """ Reimplemented to create a console-like interface.
120 """
90 """
121 intercepted = False
91 if obj == self._control:
122 cursor = self.textCursor()
92 etype = event.type()
123 position = cursor.position()
124 key = event.key()
125 ctrl_down = self._control_down(event.modifiers())
126 alt_down = event.modifiers() & QtCore.Qt.AltModifier
127 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
128
129 # Even though we have reimplemented 'paste', the C++ level slot is still
130 # called by Qt. So we intercept the key press here.
131 if event.matches(QtGui.QKeySequence.Paste):
132 self.paste()
133 intercepted = True
134
135 elif ctrl_down:
136 if key in self._ctrl_down_remap:
137 ctrl_down = False
138 key = self._ctrl_down_remap[key]
139 event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, key,
140 QtCore.Qt.NoModifier)
141
142 elif key == QtCore.Qt.Key_K:
143 if self._in_buffer(position):
144 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
145 QtGui.QTextCursor.KeepAnchor)
146 cursor.removeSelectedText()
147 intercepted = True
148
149 elif key == QtCore.Qt.Key_X:
150 intercepted = True
151
152 elif key == QtCore.Qt.Key_Y:
153 self.paste()
154 intercepted = True
155
156 elif alt_down:
157 if key == QtCore.Qt.Key_B:
158 self.setTextCursor(self._get_word_start_cursor(position))
159 intercepted = True
160
161 elif key == QtCore.Qt.Key_F:
162 self.setTextCursor(self._get_word_end_cursor(position))
163 intercepted = True
164
165 elif key == QtCore.Qt.Key_Backspace:
166 cursor = self._get_word_start_cursor(position)
167 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
168 cursor.removeSelectedText()
169 intercepted = True
170
171 elif key == QtCore.Qt.Key_D:
172 cursor = self._get_word_end_cursor(position)
173 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
174 cursor.removeSelectedText()
175 intercepted = True
176
177 if self._completion_widget.isVisible():
178 self._completion_widget.keyPressEvent(event)
179 intercepted = event.isAccepted()
180
181 else:
182 if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
183 if self._reading:
184 self.appendPlainText('\n')
185 self._reading = False
186 if self._reading_callback:
187 self._reading_callback()
188 elif not self._executing:
189 self.execute(interactive=True)
190 intercepted = True
191
192 elif key == QtCore.Qt.Key_Up:
193 if self._reading or not self._up_pressed():
194 intercepted = True
195 else:
196 prompt_line = self._get_prompt_cursor().blockNumber()
197 intercepted = cursor.blockNumber() <= prompt_line
198
199 elif key == QtCore.Qt.Key_Down:
200 if self._reading or not self._down_pressed():
201 intercepted = True
202 else:
203 end_line = self._get_end_cursor().blockNumber()
204 intercepted = cursor.blockNumber() == end_line
205
206 elif key == QtCore.Qt.Key_Tab:
207 if self._reading:
208 intercepted = False
209 else:
210 intercepted = not self._tab_pressed()
211
212 elif key == QtCore.Qt.Key_Left:
213 intercepted = not self._in_buffer(position - 1)
214
93
215 elif key == QtCore.Qt.Key_Home:
94 # Disable moving text by drag and drop.
216 cursor.movePosition(QtGui.QTextCursor.StartOfLine)
95 if etype == QtCore.QEvent.DragMove:
217 start_line = cursor.blockNumber()
96 return True
218 if start_line == self._get_prompt_cursor().blockNumber():
219 start_pos = self._prompt_pos
220 else:
221 start_pos = cursor.position()
222 start_pos += len(self._continuation_prompt)
223 if shift_down and self._in_buffer(position):
224 self._set_selection(position, start_pos)
225 else:
226 self._set_position(start_pos)
227 intercepted = True
228
97
229 elif key == QtCore.Qt.Key_Backspace and not alt_down:
98 elif etype == QtCore.QEvent.KeyPress:
99 return self._event_filter_keypress(event)
230
100
231 # Line deletion (remove continuation prompt)
101 # On Mac OS, it is always unnecessary to override shortcuts, hence
232 len_prompt = len(self._continuation_prompt)
102 # the check below. Users should just use the Control key instead of
233 if not self._reading and \
103 # the Command key.
234 cursor.columnNumber() == len_prompt and \
104 elif etype == QtCore.QEvent.ShortcutOverride:
235 position != self._prompt_pos:
105 if sys.platform != 'darwin' and \
236 cursor.setPosition(position - len_prompt,
106 self._control_key_down(event.modifiers()) and \
237 QtGui.QTextCursor.KeepAnchor)
107 event.key() in self._shortcuts:
238 cursor.removeSelectedText()
108 event.accept()
109 return False
239
110
240 # Regular backwards deletion
111 return super(ConsoleWidget, self).eventFilter(obj, event)
241 else:
242 anchor = cursor.anchor()
243 if anchor == position:
244 intercepted = not self._in_buffer(position - 1)
245 else:
246 intercepted = not self._in_buffer(min(anchor, position))
247
248 elif key == QtCore.Qt.Key_Delete:
249 anchor = cursor.anchor()
250 intercepted = not self._in_buffer(min(anchor, position))
251
252 # Don't move cursor if control is down to allow copy-paste using
253 # the keyboard in any part of the buffer.
254 if not ctrl_down:
255 self._keep_cursor_in_buffer()
256
257 if not intercepted:
258 QtGui.QPlainTextEdit.keyPressEvent(self, event)
259
260 #--------------------------------------------------------------------------
261 # 'QPlainTextEdit' interface
262 #--------------------------------------------------------------------------
263
112
264 def appendHtml(self, html):
113 #---------------------------------------------------------------------------
265 """ Reimplemented to not append HTML as a new paragraph, which doesn't
114 # 'ConsoleWidget' public interface
266 make sense for a console widget.
115 #---------------------------------------------------------------------------
267 """
268 cursor = self._get_end_cursor()
269 self._insert_html(cursor, html)
270
271 def appendPlainText(self, text):
272 """ Reimplemented to not append text as a new paragraph, which doesn't
273 make sense for a console widget. Also, if enabled, handle ANSI
274 codes.
275 """
276 cursor = self._get_end_cursor()
277 if self.ansi_codes:
278 for substring in self._ansi_processor.split_string(text):
279 format = self._ansi_processor.get_format()
280 cursor.insertText(substring, format)
281 else:
282 cursor.insertText(text)
283
116
284 def clear(self, keep_input=False):
117 def clear(self, keep_input=False):
285 """ Reimplemented to write a new prompt. If 'keep_input' is set,
118 """ Clear the console, then write a new prompt. If 'keep_input' is set,
286 restores the old input buffer when the new prompt is written.
119 restores the old input buffer when the new prompt is written.
287 """
120 """
288 QtGui.QPlainTextEdit.clear(self)
121 self._control.clear()
289 if keep_input:
122 if keep_input:
290 input_buffer = self.input_buffer
123 input_buffer = self.input_buffer
291 self._show_prompt()
124 self._show_prompt()
292 if keep_input:
125 if keep_input:
293 self.input_buffer = input_buffer
126 self.input_buffer = input_buffer
294
127
295 def paste(self):
128 def copy(self):
296 """ Reimplemented to ensure that text is pasted in the editing region.
129 """ Copy the current selected text to the clipboard.
297 """
130 """
298 self._keep_cursor_in_buffer()
131 self._control.copy()
299 QtGui.QPlainTextEdit.paste(self)
300
301 def print_(self, printer):
302 """ Reimplemented to work around a bug in PyQt: the C++ level 'print_'
303 slot has the wrong signature.
304 """
305 QtGui.QPlainTextEdit.print_(self, printer)
306
307 #---------------------------------------------------------------------------
308 # 'ConsoleWidget' public interface
309 #---------------------------------------------------------------------------
310
132
311 def execute(self, source=None, hidden=False, interactive=False):
133 def execute(self, source=None, hidden=False, interactive=False):
312 """ Executes source or the input buffer, possibly prompting for more
134 """ Executes source or the input buffer, possibly prompting for more
@@ -346,7 +168,7 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
346 if source is not None:
168 if source is not None:
347 self.input_buffer = source
169 self.input_buffer = source
348
170
349 self.appendPlainText('\n')
171 self._append_plain_text('\n')
350 self._executing_input_buffer = self.input_buffer
172 self._executing_input_buffer = self.input_buffer
351 self._executing = True
173 self._executing = True
352 self._prompt_finished()
174 self._prompt_finished()
@@ -358,7 +180,7 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
358 # The maximum block count is only in effect during execution.
180 # The maximum block count is only in effect during execution.
359 # This ensures that _prompt_pos does not become invalid due to
181 # This ensures that _prompt_pos does not become invalid due to
360 # text truncation.
182 # text truncation.
361 self.setMaximumBlockCount(self.buffer_size)
183 self._control.document().setMaximumBlockCount(self.buffer_size)
362 self._execute(real_source, hidden)
184 self._execute(real_source, hidden)
363 elif hidden:
185 elif hidden:
364 raise RuntimeError('Incomplete noninteractive input: "%s"' % source)
186 raise RuntimeError('Incomplete noninteractive input: "%s"' % source)
@@ -393,14 +215,14 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
393 # Insert new text with continuation prompts.
215 # Insert new text with continuation prompts.
394 lines = string.splitlines(True)
216 lines = string.splitlines(True)
395 if lines:
217 if lines:
396 self.appendPlainText(lines[0])
218 self._append_plain_text(lines[0])
397 for i in xrange(1, len(lines)):
219 for i in xrange(1, len(lines)):
398 if self._continuation_prompt_html is None:
220 if self._continuation_prompt_html is None:
399 self.appendPlainText(self._continuation_prompt)
221 self._append_plain_text(self._continuation_prompt)
400 else:
222 else:
401 self.appendHtml(self._continuation_prompt_html)
223 self._append_html(self._continuation_prompt_html)
402 self.appendPlainText(lines[i])
224 self._append_plain_text(lines[i])
403 self.moveCursor(QtGui.QTextCursor.End)
225 self._control.moveCursor(QtGui.QTextCursor.End)
404
226
405 input_buffer = property(_get_input_buffer, _set_input_buffer)
227 input_buffer = property(_get_input_buffer, _set_input_buffer)
406
228
@@ -410,7 +232,7 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
410 """
232 """
411 if self._executing:
233 if self._executing:
412 return None
234 return None
413 cursor = self.textCursor()
235 cursor = self._control.textCursor()
414 if cursor.position() >= self._prompt_pos:
236 if cursor.position() >= self._prompt_pos:
415 text = self._get_block_plain_text(cursor.block())
237 text = self._get_block_plain_text(cursor.block())
416 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
238 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
@@ -425,19 +247,36 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
425 def _get_font(self):
247 def _get_font(self):
426 """ The base font being used by the ConsoleWidget.
248 """ The base font being used by the ConsoleWidget.
427 """
249 """
428 return self.document().defaultFont()
250 return self._control.document().defaultFont()
429
251
430 def _set_font(self, font):
252 def _set_font(self, font):
431 """ Sets the base font for the ConsoleWidget to the specified QFont.
253 """ Sets the base font for the ConsoleWidget to the specified QFont.
432 """
254 """
433 font_metrics = QtGui.QFontMetrics(font)
255 font_metrics = QtGui.QFontMetrics(font)
434 self.setTabStopWidth(self.tab_width * font_metrics.width(' '))
256 self._control.setTabStopWidth(self.tab_width * font_metrics.width(' '))
435
257
436 self._completion_widget.setFont(font)
258 self._completion_widget.setFont(font)
437 self.document().setDefaultFont(font)
259 self._control.document().setDefaultFont(font)
438
260
439 font = property(_get_font, _set_font)
261 font = property(_get_font, _set_font)
440
262
263 def paste(self):
264 """ Paste the contents of the clipboard into the input region.
265 """
266 self._keep_cursor_in_buffer()
267 self._control.paste()
268
269 def print_(self, printer):
270 """ Print the contents of the ConsoleWidget to the specified QPrinter.
271 """
272 self._control.print_(printer)
273
274 def redo(self):
275 """ Redo the last operation. If there is no operation to redo, nothing
276 happens.
277 """
278 self._control.redo()
279
441 def reset_font(self):
280 def reset_font(self):
442 """ Sets the font to the default fixed-width font for this platform.
281 """ Sets the font to the default fixed-width font for this platform.
443 """
282 """
@@ -451,6 +290,11 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
451 font.setStyleHint(QtGui.QFont.TypeWriter)
290 font.setStyleHint(QtGui.QFont.TypeWriter)
452 self._set_font(font)
291 self._set_font(font)
453
292
293 def select_all(self):
294 """ Selects all the text in the buffer.
295 """
296 self._control.selectAll()
297
454 def _get_tab_width(self):
298 def _get_tab_width(self):
455 """ The width (in terms of space characters) for tab characters.
299 """ The width (in terms of space characters) for tab characters.
456 """
300 """
@@ -460,12 +304,18 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
460 """ Sets the width (in terms of space characters) for tab characters.
304 """ Sets the width (in terms of space characters) for tab characters.
461 """
305 """
462 font_metrics = QtGui.QFontMetrics(self.font)
306 font_metrics = QtGui.QFontMetrics(self.font)
463 self.setTabStopWidth(tab_width * font_metrics.width(' '))
307 self._control.setTabStopWidth(tab_width * font_metrics.width(' '))
464
308
465 self._tab_width = tab_width
309 self._tab_width = tab_width
466
310
467 tab_width = property(_get_tab_width, _set_tab_width)
311 tab_width = property(_get_tab_width, _set_tab_width)
468
312
313 def undo(self):
314 """ Undo the last operation. If there is no operation to undo, nothing
315 happens.
316 """
317 self._control.undo()
318
469 #---------------------------------------------------------------------------
319 #---------------------------------------------------------------------------
470 # 'ConsoleWidget' abstract interface
320 # 'ConsoleWidget' abstract interface
471 #---------------------------------------------------------------------------
321 #---------------------------------------------------------------------------
@@ -515,28 +365,60 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
515 # 'ConsoleWidget' protected interface
365 # 'ConsoleWidget' protected interface
516 #--------------------------------------------------------------------------
366 #--------------------------------------------------------------------------
517
367
368 def _append_html(self, html):
369 """ Appends html at the end of the console buffer.
370 """
371 cursor = self._get_end_cursor()
372 self._insert_html(cursor, html)
373
518 def _append_html_fetching_plain_text(self, html):
374 def _append_html_fetching_plain_text(self, html):
519 """ Appends 'html', then returns the plain text version of it.
375 """ Appends 'html', then returns the plain text version of it.
520 """
376 """
521 anchor = self._get_end_cursor().position()
377 anchor = self._get_end_cursor().position()
522 self.appendHtml(html)
378 self._append_html(html)
523 cursor = self._get_end_cursor()
379 cursor = self._get_end_cursor()
524 cursor.setPosition(anchor, QtGui.QTextCursor.KeepAnchor)
380 cursor.setPosition(anchor, QtGui.QTextCursor.KeepAnchor)
525 return str(cursor.selection().toPlainText())
381 return str(cursor.selection().toPlainText())
526
382
383 def _append_plain_text(self, text):
384 """ Appends plain text at the end of the console buffer, processing
385 ANSI codes if enabled.
386 """
387 cursor = self._get_end_cursor()
388 if self.ansi_codes:
389 for substring in self._ansi_processor.split_string(text):
390 format = self._ansi_processor.get_format()
391 cursor.insertText(substring, format)
392 else:
393 cursor.insertText(text)
394
527 def _append_plain_text_keeping_prompt(self, text):
395 def _append_plain_text_keeping_prompt(self, text):
528 """ Writes 'text' after the current prompt, then restores the old prompt
396 """ Writes 'text' after the current prompt, then restores the old prompt
529 with its old input buffer.
397 with its old input buffer.
530 """
398 """
531 input_buffer = self.input_buffer
399 input_buffer = self.input_buffer
532 self.appendPlainText('\n')
400 self._append_plain_text('\n')
533 self._prompt_finished()
401 self._prompt_finished()
534
402
535 self.appendPlainText(text)
403 self._append_plain_text(text)
536 self._show_prompt()
404 self._show_prompt()
537 self.input_buffer = input_buffer
405 self.input_buffer = input_buffer
538
406
539 def _control_down(self, modifiers):
407 def _complete_with_items(self, cursor, items):
408 """ Performs completion with 'items' at the specified cursor location.
409 """
410 if len(items) == 1:
411 cursor.setPosition(self._control.textCursor().position(),
412 QtGui.QTextCursor.KeepAnchor)
413 cursor.insertText(items[0])
414 elif len(items) > 1:
415 if self.gui_completion:
416 self._completion_widget.show_items(cursor, items)
417 else:
418 text = self._format_as_columns(items)
419 self._append_plain_text_keeping_prompt(text)
420
421 def _control_key_down(self, modifiers):
540 """ Given a KeyboardModifiers flags object, return whether the Control
422 """ Given a KeyboardModifiers flags object, return whether the Control
541 key is down (on Mac OS, treat the Command key as a synonym for
423 key is down (on Mac OS, treat the Command key as a synonym for
542 Control).
424 Control).
@@ -549,20 +431,175 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
549 down = down ^ bool(modifiers & QtCore.Qt.MetaModifier)
431 down = down ^ bool(modifiers & QtCore.Qt.MetaModifier)
550
432
551 return down
433 return down
552
434
553 def _complete_with_items(self, cursor, items):
435 def _create_control(self, kind):
554 """ Performs completion with 'items' at the specified cursor location.
436 """ Creates and sets the underlying text widget.
555 """
437 """
556 if len(items) == 1:
438 layout = QtGui.QVBoxLayout(self)
557 cursor.setPosition(self.textCursor().position(),
439 layout.setMargin(0)
558 QtGui.QTextCursor.KeepAnchor)
440 if kind == 'plain':
559 cursor.insertText(items[0])
441 control = QtGui.QPlainTextEdit()
560 elif len(items) > 1:
442 elif kind == 'rich':
561 if self.gui_completion:
443 control = QtGui.QTextEdit()
562 self._completion_widget.show_items(cursor, items)
444 else:
563 else:
445 raise ValueError("Kind %s unknown." % repr(kind))
564 text = self._format_as_columns(items)
446 layout.addWidget(control)
565 self._append_plain_text_keeping_prompt(text)
447
448 control.installEventFilter(self)
449 control.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
450 control.customContextMenuRequested.connect(self._show_context_menu)
451 control.copyAvailable.connect(self.copy_available)
452 control.redoAvailable.connect(self.redo_available)
453 control.undoAvailable.connect(self.undo_available)
454
455 return control
456
457 def _event_filter_keypress(self, event):
458 """ Filter key events for the underlying text widget to create a
459 console-like interface.
460 """
461 key = event.key()
462 ctrl_down = self._control_key_down(event.modifiers())
463
464 # If the key is remapped, return immediately.
465 if ctrl_down and key in self._ctrl_down_remap:
466 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
467 self._ctrl_down_remap[key],
468 QtCore.Qt.NoModifier)
469 QtGui.qApp.sendEvent(self._control, new_event)
470 return True
471
472 # If the completion widget accepts the key press, return immediately.
473 if self._completion_widget.isVisible():
474 self._completion_widget.keyPressEvent(event)
475 if event.isAccepted():
476 return True
477
478 # Otherwise, proceed normally and do not return early.
479 intercepted = False
480 cursor = self._control.textCursor()
481 position = cursor.position()
482 alt_down = event.modifiers() & QtCore.Qt.AltModifier
483 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
484
485 if event.matches(QtGui.QKeySequence.Paste):
486 # Call our paste instead of the underlying text widget's.
487 self.paste()
488 intercepted = True
489
490 elif ctrl_down:
491 if key == QtCore.Qt.Key_K:
492 if self._in_buffer(position):
493 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
494 QtGui.QTextCursor.KeepAnchor)
495 cursor.removeSelectedText()
496 intercepted = True
497
498 elif key == QtCore.Qt.Key_X:
499 intercepted = True
500
501 elif key == QtCore.Qt.Key_Y:
502 self.paste()
503 intercepted = True
504
505 elif alt_down:
506 if key == QtCore.Qt.Key_B:
507 self._set_cursor(self._get_word_start_cursor(position))
508 intercepted = True
509
510 elif key == QtCore.Qt.Key_F:
511 self._set_cursor(self._get_word_end_cursor(position))
512 intercepted = True
513
514 elif key == QtCore.Qt.Key_Backspace:
515 cursor = self._get_word_start_cursor(position)
516 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
517 cursor.removeSelectedText()
518 intercepted = True
519
520 elif key == QtCore.Qt.Key_D:
521 cursor = self._get_word_end_cursor(position)
522 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
523 cursor.removeSelectedText()
524 intercepted = True
525
526 else:
527 if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
528 if self._reading:
529 self._append_plain_text('\n')
530 self._reading = False
531 if self._reading_callback:
532 self._reading_callback()
533 elif not self._executing:
534 self.execute(interactive=True)
535 intercepted = True
536
537 elif key == QtCore.Qt.Key_Up:
538 if self._reading or not self._up_pressed():
539 intercepted = True
540 else:
541 prompt_line = self._get_prompt_cursor().blockNumber()
542 intercepted = cursor.blockNumber() <= prompt_line
543
544 elif key == QtCore.Qt.Key_Down:
545 if self._reading or not self._down_pressed():
546 intercepted = True
547 else:
548 end_line = self._get_end_cursor().blockNumber()
549 intercepted = cursor.blockNumber() == end_line
550
551 elif key == QtCore.Qt.Key_Tab:
552 if self._reading:
553 intercepted = False
554 else:
555 intercepted = not self._tab_pressed()
556
557 elif key == QtCore.Qt.Key_Left:
558 intercepted = not self._in_buffer(position - 1)
559
560 elif key == QtCore.Qt.Key_Home:
561 cursor.movePosition(QtGui.QTextCursor.StartOfLine)
562 start_line = cursor.blockNumber()
563 if start_line == self._get_prompt_cursor().blockNumber():
564 start_pos = self._prompt_pos
565 else:
566 start_pos = cursor.position()
567 start_pos += len(self._continuation_prompt)
568 if shift_down and self._in_buffer(position):
569 self._set_selection(position, start_pos)
570 else:
571 self._set_position(start_pos)
572 intercepted = True
573
574 elif key == QtCore.Qt.Key_Backspace and not alt_down:
575
576 # Line deletion (remove continuation prompt)
577 len_prompt = len(self._continuation_prompt)
578 if not self._reading and \
579 cursor.columnNumber() == len_prompt and \
580 position != self._prompt_pos:
581 cursor.setPosition(position - len_prompt,
582 QtGui.QTextCursor.KeepAnchor)
583 cursor.removeSelectedText()
584
585 # Regular backwards deletion
586 else:
587 anchor = cursor.anchor()
588 if anchor == position:
589 intercepted = not self._in_buffer(position - 1)
590 else:
591 intercepted = not self._in_buffer(min(anchor, position))
592
593 elif key == QtCore.Qt.Key_Delete:
594 anchor = cursor.anchor()
595 intercepted = not self._in_buffer(min(anchor, position))
596
597 # Don't move the cursor if control is down to allow copy-paste using
598 # the keyboard in any part of the buffer.
599 if not ctrl_down:
600 self._keep_cursor_in_buffer()
601
602 return intercepted
566
603
567 def _format_as_columns(self, items, separator=' '):
604 def _format_as_columns(self, items, separator=' '):
568 """ Transform a list of strings into a single string with columns.
605 """ Transform a list of strings into a single string with columns.
@@ -639,18 +676,23 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
639 cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
676 cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
640 QtGui.QTextCursor.KeepAnchor)
677 QtGui.QTextCursor.KeepAnchor)
641 return str(cursor.selection().toPlainText())
678 return str(cursor.selection().toPlainText())
679
680 def _get_cursor(self):
681 """ Convenience method that returns a cursor for the current position.
682 """
683 return self._control.textCursor()
642
684
643 def _get_end_cursor(self):
685 def _get_end_cursor(self):
644 """ Convenience method that returns a cursor for the last character.
686 """ Convenience method that returns a cursor for the last character.
645 """
687 """
646 cursor = self.textCursor()
688 cursor = self._control.textCursor()
647 cursor.movePosition(QtGui.QTextCursor.End)
689 cursor.movePosition(QtGui.QTextCursor.End)
648 return cursor
690 return cursor
649
691
650 def _get_prompt_cursor(self):
692 def _get_prompt_cursor(self):
651 """ Convenience method that returns a cursor for the prompt position.
693 """ Convenience method that returns a cursor for the prompt position.
652 """
694 """
653 cursor = self.textCursor()
695 cursor = self._control.textCursor()
654 cursor.setPosition(self._prompt_pos)
696 cursor.setPosition(self._prompt_pos)
655 return cursor
697 return cursor
656
698
@@ -658,7 +700,7 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
658 """ Convenience method that returns a cursor with text selected between
700 """ Convenience method that returns a cursor with text selected between
659 the positions 'start' and 'end'.
701 the positions 'start' and 'end'.
660 """
702 """
661 cursor = self.textCursor()
703 cursor = self._control.textCursor()
662 cursor.setPosition(start)
704 cursor.setPosition(start)
663 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
705 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
664 return cursor
706 return cursor
@@ -668,7 +710,7 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
668 sequence of non-word characters precedes the first word, skip over
710 sequence of non-word characters precedes the first word, skip over
669 them. (This emulates the behavior of bash, emacs, etc.)
711 them. (This emulates the behavior of bash, emacs, etc.)
670 """
712 """
671 document = self.document()
713 document = self._control.document()
672 position -= 1
714 position -= 1
673 while self._in_buffer(position) and \
715 while self._in_buffer(position) and \
674 not document.characterAt(position).isLetterOrNumber():
716 not document.characterAt(position).isLetterOrNumber():
@@ -676,7 +718,7 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
676 while self._in_buffer(position) and \
718 while self._in_buffer(position) and \
677 document.characterAt(position).isLetterOrNumber():
719 document.characterAt(position).isLetterOrNumber():
678 position -= 1
720 position -= 1
679 cursor = self.textCursor()
721 cursor = self._control.textCursor()
680 cursor.setPosition(position + 1)
722 cursor.setPosition(position + 1)
681 return cursor
723 return cursor
682
724
@@ -685,7 +727,7 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
685 sequence of non-word characters precedes the first word, skip over
727 sequence of non-word characters precedes the first word, skip over
686 them. (This emulates the behavior of bash, emacs, etc.)
728 them. (This emulates the behavior of bash, emacs, etc.)
687 """
729 """
688 document = self.document()
730 document = self._control.document()
689 end = self._get_end_cursor().position()
731 end = self._get_end_cursor().position()
690 while position < end and \
732 while position < end and \
691 not document.characterAt(position).isLetterOrNumber():
733 not document.characterAt(position).isLetterOrNumber():
@@ -693,7 +735,7 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
693 while position < end and \
735 while position < end and \
694 document.characterAt(position).isLetterOrNumber():
736 document.characterAt(position).isLetterOrNumber():
695 position += 1
737 position += 1
696 cursor = self.textCursor()
738 cursor = self._control.textCursor()
697 cursor.setPosition(position)
739 cursor.setPosition(position)
698 return cursor
740 return cursor
699
741
@@ -718,17 +760,33 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
718 cursor.movePosition(QtGui.QTextCursor.Right)
760 cursor.movePosition(QtGui.QTextCursor.Right)
719 cursor.insertText(' ', QtGui.QTextCharFormat())
761 cursor.insertText(' ', QtGui.QTextCharFormat())
720
762
763 def _in_buffer(self, position):
764 """ Returns whether the given position is inside the editing region.
765 """
766 return position >= self._prompt_pos
767
768 def _keep_cursor_in_buffer(self):
769 """ Ensures that the cursor is inside the editing region. Returns
770 whether the cursor was moved.
771 """
772 cursor = self._control.textCursor()
773 if cursor.position() < self._prompt_pos:
774 cursor.movePosition(QtGui.QTextCursor.End)
775 self._control.setTextCursor(cursor)
776 return True
777 else:
778 return False
779
721 def _prompt_started(self):
780 def _prompt_started(self):
722 """ Called immediately after a new prompt is displayed.
781 """ Called immediately after a new prompt is displayed.
723 """
782 """
724 # Temporarily disable the maximum block count to permit undo/redo and
783 # Temporarily disable the maximum block count to permit undo/redo and
725 # to ensure that the prompt position does not change due to truncation.
784 # to ensure that the prompt position does not change due to truncation.
726 self.setMaximumBlockCount(0)
785 self._control.document().setMaximumBlockCount(0)
727 self.setUndoRedoEnabled(True)
786 self._control.setUndoRedoEnabled(True)
728
787
729 self.setReadOnly(False)
788 self._control.setReadOnly(False)
730 self.moveCursor(QtGui.QTextCursor.End)
789 self._control.moveCursor(QtGui.QTextCursor.End)
731 self.centerCursor()
732
790
733 self._executing = False
791 self._executing = False
734 self._prompt_started_hook()
792 self._prompt_started_hook()
@@ -737,8 +795,8 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
737 """ Called immediately after a prompt is finished, i.e. when some input
795 """ Called immediately after a prompt is finished, i.e. when some input
738 will be processed and a new prompt displayed.
796 will be processed and a new prompt displayed.
739 """
797 """
740 self.setUndoRedoEnabled(False)
798 self._control.setUndoRedoEnabled(False)
741 self.setReadOnly(True)
799 self._control.setReadOnly(True)
742 self._prompt_finished_hook()
800 self._prompt_finished_hook()
743
801
744 def _readline(self, prompt='', callback=None):
802 def _readline(self, prompt='', callback=None):
@@ -783,7 +841,7 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
783 def _reset(self):
841 def _reset(self):
784 """ Clears the console and resets internal state variables.
842 """ Clears the console and resets internal state variables.
785 """
843 """
786 QtGui.QPlainTextEdit.clear(self)
844 self._control.clear()
787 self._executing = self._reading = False
845 self._executing = self._reading = False
788
846
789 def _set_continuation_prompt(self, prompt, html=False):
847 def _set_continuation_prompt(self, prompt, html=False):
@@ -804,18 +862,47 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
804 else:
862 else:
805 self._continuation_prompt = prompt
863 self._continuation_prompt = prompt
806 self._continuation_prompt_html = None
864 self._continuation_prompt_html = None
865
866 def _set_cursor(self, cursor):
867 """ Convenience method to set the current cursor.
868 """
869 self._control.setTextCursor(cursor)
807
870
808 def _set_position(self, position):
871 def _set_position(self, position):
809 """ Convenience method to set the position of the cursor.
872 """ Convenience method to set the position of the cursor.
810 """
873 """
811 cursor = self.textCursor()
874 cursor = self._control.textCursor()
812 cursor.setPosition(position)
875 cursor.setPosition(position)
813 self.setTextCursor(cursor)
876 self._control.setTextCursor(cursor)
814
877
815 def _set_selection(self, start, end):
878 def _set_selection(self, start, end):
816 """ Convenience method to set the current selected text.
879 """ Convenience method to set the current selected text.
817 """
880 """
818 self.setTextCursor(self._get_selection_cursor(start, end))
881 self._control.setTextCursor(self._get_selection_cursor(start, end))
882
883 def _show_context_menu(self, pos):
884 """ Shows a context menu at the given QPoint (in widget coordinates).
885 """
886 menu = QtGui.QMenu()
887
888 copy_action = QtGui.QAction('Copy', menu)
889 copy_action.triggered.connect(self.copy)
890 copy_action.setEnabled(self._get_cursor().hasSelection())
891 copy_action.setShortcut(QtGui.QKeySequence.Copy)
892 menu.addAction(copy_action)
893
894 paste_action = QtGui.QAction('Paste', menu)
895 paste_action.triggered.connect(self.paste)
896 paste_action.setEnabled(self._control.canPaste())
897 paste_action.setShortcut(QtGui.QKeySequence.Paste)
898 menu.addAction(paste_action)
899 menu.addSeparator()
900
901 select_all_action = QtGui.QAction('Select All', menu)
902 select_all_action.triggered.connect(self.select_all)
903 menu.addAction(select_all_action)
904
905 menu.exec_(self._control.mapToGlobal(pos))
819
906
820 def _show_prompt(self, prompt=None, html=False, newline=True):
907 def _show_prompt(self, prompt=None, html=False, newline=True):
821 """ Writes a new prompt at the end of the buffer.
908 """ Writes a new prompt at the end of the buffer.
@@ -841,20 +928,20 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
841 cursor.movePosition(QtGui.QTextCursor.Left,
928 cursor.movePosition(QtGui.QTextCursor.Left,
842 QtGui.QTextCursor.KeepAnchor)
929 QtGui.QTextCursor.KeepAnchor)
843 if str(cursor.selection().toPlainText()) != '\n':
930 if str(cursor.selection().toPlainText()) != '\n':
844 self.appendPlainText('\n')
931 self._append_plain_text('\n')
845
932
846 # Write the prompt.
933 # Write the prompt.
847 if prompt is None:
934 if prompt is None:
848 if self._prompt_html is None:
935 if self._prompt_html is None:
849 self.appendPlainText(self._prompt)
936 self._append_plain_text(self._prompt)
850 else:
937 else:
851 self.appendHtml(self._prompt_html)
938 self._append_html(self._prompt_html)
852 else:
939 else:
853 if html:
940 if html:
854 self._prompt = self._append_html_fetching_plain_text(prompt)
941 self._prompt = self._append_html_fetching_plain_text(prompt)
855 self._prompt_html = prompt
942 self._prompt_html = prompt
856 else:
943 else:
857 self.appendPlainText(prompt)
944 self._append_plain_text(prompt)
858 self._prompt = prompt
945 self._prompt = prompt
859 self._prompt_html = None
946 self._prompt_html = None
860
947
@@ -865,30 +952,13 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
865 """ Writes a new continuation prompt at the end of the buffer.
952 """ Writes a new continuation prompt at the end of the buffer.
866 """
953 """
867 if self._continuation_prompt_html is None:
954 if self._continuation_prompt_html is None:
868 self.appendPlainText(self._continuation_prompt)
955 self._append_plain_text(self._continuation_prompt)
869 else:
956 else:
870 self._continuation_prompt = self._append_html_fetching_plain_text(
957 self._continuation_prompt = self._append_html_fetching_plain_text(
871 self._continuation_prompt_html)
958 self._continuation_prompt_html)
872
959
873 self._prompt_started()
960 self._prompt_started()
874
961
875 def _in_buffer(self, position):
876 """ Returns whether the given position is inside the editing region.
877 """
878 return position >= self._prompt_pos
879
880 def _keep_cursor_in_buffer(self):
881 """ Ensures that the cursor is inside the editing region. Returns
882 whether the cursor was moved.
883 """
884 cursor = self.textCursor()
885 if cursor.position() < self._prompt_pos:
886 cursor.movePosition(QtGui.QTextCursor.End)
887 self.setTextCursor(cursor)
888 return True
889 else:
890 return False
891
892
962
893 class HistoryConsoleWidget(ConsoleWidget):
963 class HistoryConsoleWidget(ConsoleWidget):
894 """ A ConsoleWidget that keeps a history of the commands that have been
964 """ A ConsoleWidget that keeps a history of the commands that have been
@@ -896,12 +966,11 b' class HistoryConsoleWidget(ConsoleWidget):'
896 """
966 """
897
967
898 #---------------------------------------------------------------------------
968 #---------------------------------------------------------------------------
899 # 'QObject' interface
969 # 'object' interface
900 #---------------------------------------------------------------------------
970 #---------------------------------------------------------------------------
901
971
902 def __init__(self, parent=None):
972 def __init__(self, *args, **kw):
903 super(HistoryConsoleWidget, self).__init__(parent)
973 super(HistoryConsoleWidget, self).__init__(*args, **kw)
904
905 self._history = []
974 self._history = []
906 self._history_index = 0
975 self._history_index = 0
907
976
@@ -933,13 +1002,13 b' class HistoryConsoleWidget(ConsoleWidget):'
933 processing the event.
1002 processing the event.
934 """
1003 """
935 prompt_cursor = self._get_prompt_cursor()
1004 prompt_cursor = self._get_prompt_cursor()
936 if self.textCursor().blockNumber() == prompt_cursor.blockNumber():
1005 if self._get_cursor().blockNumber() == prompt_cursor.blockNumber():
937 self.history_previous()
1006 self.history_previous()
938
1007
939 # Go to the first line of prompt for seemless history scrolling.
1008 # Go to the first line of prompt for seemless history scrolling.
940 cursor = self._get_prompt_cursor()
1009 cursor = self._get_prompt_cursor()
941 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
1010 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
942 self.setTextCursor(cursor)
1011 self._set_cursor(cursor)
943
1012
944 return False
1013 return False
945 return True
1014 return True
@@ -949,7 +1018,7 b' class HistoryConsoleWidget(ConsoleWidget):'
949 processing the event.
1018 processing the event.
950 """
1019 """
951 end_cursor = self._get_end_cursor()
1020 end_cursor = self._get_end_cursor()
952 if self.textCursor().blockNumber() == end_cursor.blockNumber():
1021 if self._get_cursor().blockNumber() == end_cursor.blockNumber():
953 self.history_next()
1022 self.history_next()
954 return False
1023 return False
955 return True
1024 return True
@@ -21,7 +21,7 b' class FrontendHighlighter(PygmentsHighlighter):'
21 """
21 """
22
22
23 def __init__(self, frontend):
23 def __init__(self, frontend):
24 super(FrontendHighlighter, self).__init__(frontend.document())
24 super(FrontendHighlighter, self).__init__(frontend._control.document())
25 self._current_offset = 0
25 self._current_offset = 0
26 self._frontend = frontend
26 self._frontend = frontend
27 self.highlighting_on = False
27 self.highlighting_on = False
@@ -68,14 +68,14 b' class FrontendWidget(HistoryConsoleWidget):'
68 executed = QtCore.pyqtSignal(object)
68 executed = QtCore.pyqtSignal(object)
69
69
70 #---------------------------------------------------------------------------
70 #---------------------------------------------------------------------------
71 # 'QObject' interface
71 # 'object' interface
72 #---------------------------------------------------------------------------
72 #---------------------------------------------------------------------------
73
73
74 def __init__(self, parent=None):
74 def __init__(self, *args, **kw):
75 super(FrontendWidget, self).__init__(parent)
75 super(FrontendWidget, self).__init__(*args, **kw)
76
76
77 # FrontendWidget protected variables.
77 # FrontendWidget protected variables.
78 self._call_tip_widget = CallTipWidget(self)
78 self._call_tip_widget = CallTipWidget(self._control)
79 self._completion_lexer = CompletionLexer(PythonLexer())
79 self._completion_lexer = CompletionLexer(PythonLexer())
80 self._hidden = True
80 self._hidden = True
81 self._highlighter = FrontendHighlighter(self)
81 self._highlighter = FrontendHighlighter(self)
@@ -86,7 +86,9 b' class FrontendWidget(HistoryConsoleWidget):'
86 self.tab_width = 4
86 self.tab_width = 4
87 self._set_continuation_prompt('... ')
87 self._set_continuation_prompt('... ')
88
88
89 self.document().contentsChange.connect(self._document_contents_change)
89 # Connect signal handlers.
90 document = self._control.document()
91 document.contentsChange.connect(self._document_contents_change)
90
92
91 #---------------------------------------------------------------------------
93 #---------------------------------------------------------------------------
92 # 'QWidget' interface
94 # 'QWidget' interface
@@ -140,8 +142,8 b' class FrontendWidget(HistoryConsoleWidget):'
140 if self._get_prompt_cursor().blockNumber() != \
142 if self._get_prompt_cursor().blockNumber() != \
141 self._get_end_cursor().blockNumber():
143 self._get_end_cursor().blockNumber():
142 spaces = self._input_splitter.indent_spaces
144 spaces = self._input_splitter.indent_spaces
143 self.appendPlainText('\t' * (spaces / self.tab_width))
145 self._append_plain_text('\t' * (spaces / self.tab_width))
144 self.appendPlainText(' ' * (spaces % self.tab_width))
146 self._append_plain_text(' ' * (spaces % self.tab_width))
145
147
146 def _prompt_finished_hook(self):
148 def _prompt_finished_hook(self):
147 """ Called immediately after a prompt is finished, i.e. when some input
149 """ Called immediately after a prompt is finished, i.e. when some input
@@ -155,7 +157,7 b' class FrontendWidget(HistoryConsoleWidget):'
155 processing the event.
157 processing the event.
156 """
158 """
157 self._keep_cursor_in_buffer()
159 self._keep_cursor_in_buffer()
158 cursor = self.textCursor()
160 cursor = self._get_cursor()
159 return not self._complete()
161 return not self._complete()
160
162
161 #---------------------------------------------------------------------------
163 #---------------------------------------------------------------------------
@@ -232,9 +234,9 b' class FrontendWidget(HistoryConsoleWidget):'
232 """ Shows a call tip, if appropriate, at the current cursor location.
234 """ Shows a call tip, if appropriate, at the current cursor location.
233 """
235 """
234 # Decide if it makes sense to show a call tip
236 # Decide if it makes sense to show a call tip
235 cursor = self.textCursor()
237 cursor = self._get_cursor()
236 cursor.movePosition(QtGui.QTextCursor.Left)
238 cursor.movePosition(QtGui.QTextCursor.Left)
237 document = self.document()
239 document = self._control.document()
238 if document.characterAt(cursor.position()).toAscii() != '(':
240 if document.characterAt(cursor.position()).toAscii() != '(':
239 return False
241 return False
240 context = self._get_context(cursor)
242 context = self._get_context(cursor)
@@ -244,7 +246,7 b' class FrontendWidget(HistoryConsoleWidget):'
244 # Send the metadata request to the kernel
246 # Send the metadata request to the kernel
245 name = '.'.join(context)
247 name = '.'.join(context)
246 self._calltip_id = self.kernel_manager.xreq_channel.object_info(name)
248 self._calltip_id = self.kernel_manager.xreq_channel.object_info(name)
247 self._calltip_pos = self.textCursor().position()
249 self._calltip_pos = self._get_cursor().position()
248 return True
250 return True
249
251
250 def _complete(self):
252 def _complete(self):
@@ -259,7 +261,7 b' class FrontendWidget(HistoryConsoleWidget):'
259 text = '.'.join(context)
261 text = '.'.join(context)
260 self._complete_id = self.kernel_manager.xreq_channel.complete(
262 self._complete_id = self.kernel_manager.xreq_channel.complete(
261 text, self.input_buffer_cursor_line, self.input_buffer)
263 text, self.input_buffer_cursor_line, self.input_buffer)
262 self._complete_pos = self.textCursor().position()
264 self._complete_pos = self._get_cursor().position()
263 return True
265 return True
264
266
265 def _get_banner(self):
267 def _get_banner(self):
@@ -273,7 +275,7 b' class FrontendWidget(HistoryConsoleWidget):'
273 """ Gets the context at the current cursor location.
275 """ Gets the context at the current cursor location.
274 """
276 """
275 if cursor is None:
277 if cursor is None:
276 cursor = self.textCursor()
278 cursor = self._get_cursor()
277 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
279 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
278 QtGui.QTextCursor.KeepAnchor)
280 QtGui.QTextCursor.KeepAnchor)
279 text = str(cursor.selection().toPlainText())
281 text = str(cursor.selection().toPlainText())
@@ -285,8 +287,8 b' class FrontendWidget(HistoryConsoleWidget):'
285 if self.kernel_manager.has_kernel:
287 if self.kernel_manager.has_kernel:
286 self.kernel_manager.signal_kernel(signal.SIGINT)
288 self.kernel_manager.signal_kernel(signal.SIGINT)
287 else:
289 else:
288 self.appendPlainText('Kernel process is either remote or '
290 self._append_plain_text('Kernel process is either remote or '
289 'unspecified. Cannot interrupt.\n')
291 'unspecified. Cannot interrupt.\n')
290
292
291 def _show_interpreter_prompt(self):
293 def _show_interpreter_prompt(self):
292 """ Shows a prompt for the interpreter.
294 """ Shows a prompt for the interpreter.
@@ -299,7 +301,7 b' class FrontendWidget(HistoryConsoleWidget):'
299 """ Called when the kernel manager has started listening.
301 """ Called when the kernel manager has started listening.
300 """
302 """
301 self._reset()
303 self._reset()
302 self.appendPlainText(self._get_banner())
304 self._append_plain_text(self._get_banner())
303 self._show_interpreter_prompt()
305 self._show_interpreter_prompt()
304
306
305 def _stopped_channels(self):
307 def _stopped_channels(self):
@@ -315,8 +317,8 b' class FrontendWidget(HistoryConsoleWidget):'
315 # Calculate where the cursor should be *after* the change:
317 # Calculate where the cursor should be *after* the change:
316 position += added
318 position += added
317
319
318 document = self.document()
320 document = self._control.document()
319 if position == self.textCursor().position():
321 if position == self._get_cursor().position():
320 self._call_tip()
322 self._call_tip()
321
323
322 def _handle_req(self, req):
324 def _handle_req(self, req):
@@ -336,11 +338,11 b' class FrontendWidget(HistoryConsoleWidget):'
336 handler(omsg)
338 handler(omsg)
337
339
338 def _handle_pyout(self, omsg):
340 def _handle_pyout(self, omsg):
339 self.appendPlainText(omsg['content']['data'] + '\n')
341 self._append_plain_text(omsg['content']['data'] + '\n')
340
342
341 def _handle_stream(self, omsg):
343 def _handle_stream(self, omsg):
342 self.appendPlainText(omsg['content']['data'])
344 self._append_plain_text(omsg['content']['data'])
343 self.moveCursor(QtGui.QTextCursor.End)
345 self._control.moveCursor(QtGui.QTextCursor.End)
344
346
345 def _handle_execute_reply(self, reply):
347 def _handle_execute_reply(self, reply):
346 if self._hidden:
348 if self._hidden:
@@ -355,7 +357,7 b' class FrontendWidget(HistoryConsoleWidget):'
355 self._handle_execute_error(reply)
357 self._handle_execute_error(reply)
356 elif status == 'aborted':
358 elif status == 'aborted':
357 text = "ERROR: ABORTED\n"
359 text = "ERROR: ABORTED\n"
358 self.appendPlainText(text)
360 self._append_plain_text(text)
359 self._hidden = True
361 self._hidden = True
360 self._show_interpreter_prompt()
362 self._show_interpreter_prompt()
361 self.executed.emit(reply)
363 self.executed.emit(reply)
@@ -363,10 +365,10 b' class FrontendWidget(HistoryConsoleWidget):'
363 def _handle_execute_error(self, reply):
365 def _handle_execute_error(self, reply):
364 content = reply['content']
366 content = reply['content']
365 traceback = ''.join(content['traceback'])
367 traceback = ''.join(content['traceback'])
366 self.appendPlainText(traceback)
368 self._append_plain_text(traceback)
367
369
368 def _handle_complete_reply(self, rep):
370 def _handle_complete_reply(self, rep):
369 cursor = self.textCursor()
371 cursor = self._get_cursor()
370 if rep['parent_header']['msg_id'] == self._complete_id and \
372 if rep['parent_header']['msg_id'] == self._complete_id and \
371 cursor.position() == self._complete_pos:
373 cursor.position() == self._complete_pos:
372 text = '.'.join(self._get_context())
374 text = '.'.join(self._get_context())
@@ -374,7 +376,7 b' class FrontendWidget(HistoryConsoleWidget):'
374 self._complete_with_items(cursor, rep['content']['matches'])
376 self._complete_with_items(cursor, rep['content']['matches'])
375
377
376 def _handle_object_info_reply(self, rep):
378 def _handle_object_info_reply(self, rep):
377 cursor = self.textCursor()
379 cursor = self._get_cursor()
378 if rep['parent_header']['msg_id'] == self._calltip_id and \
380 if rep['parent_header']['msg_id'] == self._calltip_id and \
379 cursor.position() == self._calltip_pos:
381 cursor.position() == self._calltip_pos:
380 doc = rep['content']['docstring']
382 doc = rep['content']['docstring']
@@ -35,11 +35,11 b' class IPythonWidget(FrontendWidget):'
35 out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
35 out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
36
36
37 #---------------------------------------------------------------------------
37 #---------------------------------------------------------------------------
38 # 'QObject' interface
38 # 'object' interface
39 #---------------------------------------------------------------------------
39 #---------------------------------------------------------------------------
40
40
41 def __init__(self, parent=None):
41 def __init__(self, *args, **kw):
42 super(IPythonWidget, self).__init__(parent)
42 super(IPythonWidget, self).__init__(*args, **kw)
43
43
44 # Initialize protected variables.
44 # Initialize protected variables.
45 self._previous_prompt_blocks = []
45 self._previous_prompt_blocks = []
@@ -108,15 +108,15 b' class IPythonWidget(FrontendWidget):'
108 ename_styled = '<span class="error">%s</span>' % ename
108 ename_styled = '<span class="error">%s</span>' % ename
109 traceback = traceback.replace(ename, ename_styled)
109 traceback = traceback.replace(ename, ename_styled)
110
110
111 self.appendHtml(traceback)
111 self._append_html(traceback)
112
112
113 def _handle_pyout(self, omsg):
113 def _handle_pyout(self, omsg):
114 """ Reimplemented for IPython-style "display hook".
114 """ Reimplemented for IPython-style "display hook".
115 """
115 """
116 self.appendHtml(self._make_out_prompt(self._prompt_count))
116 self._append_html(self._make_out_prompt(self._prompt_count))
117 self._save_prompt_block()
117 self._save_prompt_block()
118
118
119 self.appendPlainText(omsg['content']['data'] + '\n')
119 self._append_plain_text(omsg['content']['data'] + '\n')
120
120
121 #---------------------------------------------------------------------------
121 #---------------------------------------------------------------------------
122 # 'IPythonWidget' interface
122 # 'IPythonWidget' interface
@@ -144,7 +144,7 b' class IPythonWidget(FrontendWidget):'
144 the stylesheet is queried for Pygments style information.
144 the stylesheet is queried for Pygments style information.
145 """
145 """
146 self.setStyleSheet(stylesheet)
146 self.setStyleSheet(stylesheet)
147 self.document().setDefaultStyleSheet(stylesheet)
147 self._control.document().setDefaultStyleSheet(stylesheet)
148
148
149 if syntax_style is None:
149 if syntax_style is None:
150 self._highlighter.set_style_sheet(stylesheet)
150 self._highlighter.set_style_sheet(stylesheet)
@@ -180,7 +180,7 b' class IPythonWidget(FrontendWidget):'
180 """ Assuming a prompt has just been written at the end of the buffer,
180 """ Assuming a prompt has just been written at the end of the buffer,
181 store the QTextBlock that contains it and its length.
181 store the QTextBlock that contains it and its length.
182 """
182 """
183 block = self.document().lastBlock()
183 block = self._control.document().lastBlock()
184 self._previous_prompt_blocks.append((block, block.length()))
184 self._previous_prompt_blocks.append((block, block.length()))
185
185
186
186
General Comments 0
You need to be logged in to leave comments. Login now