##// END OF EJS Templates
Setting the input buffer now respects HTML continuation prompts.
epatters -
Show More
@@ -1,887 +1,892 b''
1 # Standard library imports
1 # Standard library imports
2 import sys
2 import sys
3
3
4 # System library imports
4 # System library imports
5 from PyQt4 import QtCore, QtGui
5 from PyQt4 import QtCore, QtGui
6
6
7 # Local imports
7 # Local imports
8 from ansi_code_processor import QtAnsiCodeProcessor
8 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.QPlainTextEdit):
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.
16 """
16 """
17
17
18 # Whether to process ANSI escape codes.
18 # Whether to process ANSI escape codes.
19 ansi_codes = True
19 ansi_codes = True
20
20
21 # The maximum number of lines of text before truncation.
21 # The maximum number of lines of text before truncation.
22 buffer_size = 500
22 buffer_size = 500
23
23
24 # Whether to use a CompletionWidget or plain text output for tab completion.
24 # Whether to use a CompletionWidget or plain text output for tab completion.
25 gui_completion = True
25 gui_completion = True
26
26
27 # Whether to override ShortcutEvents for the keybindings defined by this
27 # Whether to override ShortcutEvents for the keybindings defined by this
28 # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
28 # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
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 # The number of spaces to show for a tab character.
32 # The number of spaces to show for a tab character.
33 tab_width = 4
33 tab_width = 4
34
34
35 # Protected class variables.
35 # Protected class variables.
36 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
36 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
37 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
37 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
38 QtCore.Qt.Key_A : QtCore.Qt.Key_Home,
38 QtCore.Qt.Key_A : QtCore.Qt.Key_Home,
39 QtCore.Qt.Key_E : QtCore.Qt.Key_End,
39 QtCore.Qt.Key_E : QtCore.Qt.Key_End,
40 QtCore.Qt.Key_P : QtCore.Qt.Key_Up,
40 QtCore.Qt.Key_P : QtCore.Qt.Key_Up,
41 QtCore.Qt.Key_N : QtCore.Qt.Key_Down,
41 QtCore.Qt.Key_N : QtCore.Qt.Key_Down,
42 QtCore.Qt.Key_D : QtCore.Qt.Key_Delete, }
42 QtCore.Qt.Key_D : QtCore.Qt.Key_Delete, }
43 _shortcuts = set(_ctrl_down_remap.keys() +
43 _shortcuts = set(_ctrl_down_remap.keys() +
44 [ QtCore.Qt.Key_C, QtCore.Qt.Key_V ])
44 [ QtCore.Qt.Key_C, QtCore.Qt.Key_V ])
45
45
46 #---------------------------------------------------------------------------
46 #---------------------------------------------------------------------------
47 # 'QObject' interface
47 # 'QObject' interface
48 #---------------------------------------------------------------------------
48 #---------------------------------------------------------------------------
49
49
50 def __init__(self, parent=None):
50 def __init__(self, parent=None):
51 QtGui.QPlainTextEdit.__init__(self, parent)
51 QtGui.QPlainTextEdit.__init__(self, parent)
52
52
53 # Initialize protected variables. Some variables contain useful state
53 # Initialize protected variables. Some variables contain useful state
54 # information for subclasses; they should be considered read-only.
54 # information for subclasses; they should be considered read-only.
55 self._ansi_processor = QtAnsiCodeProcessor()
55 self._ansi_processor = QtAnsiCodeProcessor()
56 self._completion_widget = CompletionWidget(self)
56 self._completion_widget = CompletionWidget(self)
57 self._continuation_prompt = '> '
57 self._continuation_prompt = '> '
58 self._continuation_prompt_html = None
58 self._continuation_prompt_html = None
59 self._executing = False
59 self._executing = False
60 self._prompt = ''
60 self._prompt = ''
61 self._prompt_html = None
61 self._prompt_html = None
62 self._prompt_pos = 0
62 self._prompt_pos = 0
63 self._reading = False
63 self._reading = False
64 self._reading_callback = None
64 self._reading_callback = None
65
65
66 # Set a monospaced font.
66 # Set a monospaced font.
67 self.reset_font()
67 self.reset_font()
68
68
69 # Define a custom context menu.
69 # Define a custom context menu.
70 self._context_menu = QtGui.QMenu(self)
70 self._context_menu = QtGui.QMenu(self)
71
71
72 copy_action = QtGui.QAction('Copy', self)
72 copy_action = QtGui.QAction('Copy', self)
73 copy_action.triggered.connect(self.copy)
73 copy_action.triggered.connect(self.copy)
74 self.copyAvailable.connect(copy_action.setEnabled)
74 self.copyAvailable.connect(copy_action.setEnabled)
75 self._context_menu.addAction(copy_action)
75 self._context_menu.addAction(copy_action)
76
76
77 self._paste_action = QtGui.QAction('Paste', self)
77 self._paste_action = QtGui.QAction('Paste', self)
78 self._paste_action.triggered.connect(self.paste)
78 self._paste_action.triggered.connect(self.paste)
79 self._context_menu.addAction(self._paste_action)
79 self._context_menu.addAction(self._paste_action)
80 self._context_menu.addSeparator()
80 self._context_menu.addSeparator()
81
81
82 select_all_action = QtGui.QAction('Select All', self)
82 select_all_action = QtGui.QAction('Select All', self)
83 select_all_action.triggered.connect(self.selectAll)
83 select_all_action.triggered.connect(self.selectAll)
84 self._context_menu.addAction(select_all_action)
84 self._context_menu.addAction(select_all_action)
85
85
86 def event(self, event):
86 def event(self, event):
87 """ Reimplemented to override shortcuts, if necessary.
87 """ Reimplemented to override shortcuts, if necessary.
88 """
88 """
89 # On Mac OS, it is always unnecessary to override shortcuts, hence the
89 # On Mac OS, it is always unnecessary to override shortcuts, hence the
90 # check below. Users should just use the Control key instead of the
90 # check below. Users should just use the Control key instead of the
91 # Command key.
91 # Command key.
92 if self.override_shortcuts and \
92 if self.override_shortcuts and \
93 sys.platform != 'darwin' and \
93 sys.platform != 'darwin' and \
94 event.type() == QtCore.QEvent.ShortcutOverride and \
94 event.type() == QtCore.QEvent.ShortcutOverride and \
95 self._control_down(event.modifiers()) and \
95 self._control_down(event.modifiers()) and \
96 event.key() in self._shortcuts:
96 event.key() in self._shortcuts:
97 event.accept()
97 event.accept()
98 return True
98 return True
99 else:
99 else:
100 return QtGui.QPlainTextEdit.event(self, event)
100 return QtGui.QPlainTextEdit.event(self, event)
101
101
102 #---------------------------------------------------------------------------
102 #---------------------------------------------------------------------------
103 # 'QWidget' interface
103 # 'QWidget' interface
104 #---------------------------------------------------------------------------
104 #---------------------------------------------------------------------------
105
105
106 def contextMenuEvent(self, event):
106 def contextMenuEvent(self, event):
107 """ Reimplemented to create a menu without destructive actions like
107 """ Reimplemented to create a menu without destructive actions like
108 'Cut' and 'Delete'.
108 'Cut' and 'Delete'.
109 """
109 """
110 clipboard_empty = QtGui.QApplication.clipboard().text().isEmpty()
110 clipboard_empty = QtGui.QApplication.clipboard().text().isEmpty()
111 self._paste_action.setEnabled(not clipboard_empty)
111 self._paste_action.setEnabled(not clipboard_empty)
112
112
113 self._context_menu.exec_(event.globalPos())
113 self._context_menu.exec_(event.globalPos())
114
114
115 def dragMoveEvent(self, event):
115 def dragMoveEvent(self, event):
116 """ Reimplemented to disable dropping text.
116 """ Reimplemented to disable dropping text.
117 """
117 """
118 event.ignore()
118 event.ignore()
119
119
120 def keyPressEvent(self, event):
120 def keyPressEvent(self, event):
121 """ Reimplemented to create a console-like interface.
121 """ Reimplemented to create a console-like interface.
122 """
122 """
123 intercepted = False
123 intercepted = False
124 cursor = self.textCursor()
124 cursor = self.textCursor()
125 position = cursor.position()
125 position = cursor.position()
126 key = event.key()
126 key = event.key()
127 ctrl_down = self._control_down(event.modifiers())
127 ctrl_down = self._control_down(event.modifiers())
128 alt_down = event.modifiers() & QtCore.Qt.AltModifier
128 alt_down = event.modifiers() & QtCore.Qt.AltModifier
129 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
129 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
130
130
131 # Even though we have reimplemented 'paste', the C++ level slot is still
131 # Even though we have reimplemented 'paste', the C++ level slot is still
132 # called by Qt. So we intercept the key press here.
132 # called by Qt. So we intercept the key press here.
133 if event.matches(QtGui.QKeySequence.Paste):
133 if event.matches(QtGui.QKeySequence.Paste):
134 self.paste()
134 self.paste()
135 intercepted = True
135 intercepted = True
136
136
137 elif ctrl_down:
137 elif ctrl_down:
138 if key in self._ctrl_down_remap:
138 if key in self._ctrl_down_remap:
139 ctrl_down = False
139 ctrl_down = False
140 key = self._ctrl_down_remap[key]
140 key = self._ctrl_down_remap[key]
141 event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, key,
141 event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, key,
142 QtCore.Qt.NoModifier)
142 QtCore.Qt.NoModifier)
143
143
144 elif key == QtCore.Qt.Key_K:
144 elif key == QtCore.Qt.Key_K:
145 if self._in_buffer(position):
145 if self._in_buffer(position):
146 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
146 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
147 QtGui.QTextCursor.KeepAnchor)
147 QtGui.QTextCursor.KeepAnchor)
148 cursor.removeSelectedText()
148 cursor.removeSelectedText()
149 intercepted = True
149 intercepted = True
150
150
151 elif key == QtCore.Qt.Key_X:
151 elif key == QtCore.Qt.Key_X:
152 intercepted = True
152 intercepted = True
153
153
154 elif key == QtCore.Qt.Key_Y:
154 elif key == QtCore.Qt.Key_Y:
155 self.paste()
155 self.paste()
156 intercepted = True
156 intercepted = True
157
157
158 elif alt_down:
158 elif alt_down:
159 if key == QtCore.Qt.Key_B:
159 if key == QtCore.Qt.Key_B:
160 self.setTextCursor(self._get_word_start_cursor(position))
160 self.setTextCursor(self._get_word_start_cursor(position))
161 intercepted = True
161 intercepted = True
162
162
163 elif key == QtCore.Qt.Key_F:
163 elif key == QtCore.Qt.Key_F:
164 self.setTextCursor(self._get_word_end_cursor(position))
164 self.setTextCursor(self._get_word_end_cursor(position))
165 intercepted = True
165 intercepted = True
166
166
167 elif key == QtCore.Qt.Key_Backspace:
167 elif key == QtCore.Qt.Key_Backspace:
168 cursor = self._get_word_start_cursor(position)
168 cursor = self._get_word_start_cursor(position)
169 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
169 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
170 cursor.removeSelectedText()
170 cursor.removeSelectedText()
171 intercepted = True
171 intercepted = True
172
172
173 elif key == QtCore.Qt.Key_D:
173 elif key == QtCore.Qt.Key_D:
174 cursor = self._get_word_end_cursor(position)
174 cursor = self._get_word_end_cursor(position)
175 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
175 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
176 cursor.removeSelectedText()
176 cursor.removeSelectedText()
177 intercepted = True
177 intercepted = True
178
178
179 if self._completion_widget.isVisible():
179 if self._completion_widget.isVisible():
180 self._completion_widget.keyPressEvent(event)
180 self._completion_widget.keyPressEvent(event)
181 intercepted = event.isAccepted()
181 intercepted = event.isAccepted()
182
182
183 else:
183 else:
184 if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
184 if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
185 if self._reading:
185 if self._reading:
186 self.appendPlainText('\n')
186 self.appendPlainText('\n')
187 self._reading = False
187 self._reading = False
188 if self._reading_callback:
188 if self._reading_callback:
189 self._reading_callback()
189 self._reading_callback()
190 elif not self._executing:
190 elif not self._executing:
191 self.execute(interactive=True)
191 self.execute(interactive=True)
192 intercepted = True
192 intercepted = True
193
193
194 elif key == QtCore.Qt.Key_Up:
194 elif key == QtCore.Qt.Key_Up:
195 if self._reading or not self._up_pressed():
195 if self._reading or not self._up_pressed():
196 intercepted = True
196 intercepted = True
197 else:
197 else:
198 prompt_line = self._get_prompt_cursor().blockNumber()
198 prompt_line = self._get_prompt_cursor().blockNumber()
199 intercepted = cursor.blockNumber() <= prompt_line
199 intercepted = cursor.blockNumber() <= prompt_line
200
200
201 elif key == QtCore.Qt.Key_Down:
201 elif key == QtCore.Qt.Key_Down:
202 if self._reading or not self._down_pressed():
202 if self._reading or not self._down_pressed():
203 intercepted = True
203 intercepted = True
204 else:
204 else:
205 end_line = self._get_end_cursor().blockNumber()
205 end_line = self._get_end_cursor().blockNumber()
206 intercepted = cursor.blockNumber() == end_line
206 intercepted = cursor.blockNumber() == end_line
207
207
208 elif key == QtCore.Qt.Key_Tab:
208 elif key == QtCore.Qt.Key_Tab:
209 if self._reading:
209 if self._reading:
210 intercepted = False
210 intercepted = False
211 else:
211 else:
212 intercepted = not self._tab_pressed()
212 intercepted = not self._tab_pressed()
213
213
214 elif key == QtCore.Qt.Key_Left:
214 elif key == QtCore.Qt.Key_Left:
215 intercepted = not self._in_buffer(position - 1)
215 intercepted = not self._in_buffer(position - 1)
216
216
217 elif key == QtCore.Qt.Key_Home:
217 elif key == QtCore.Qt.Key_Home:
218 cursor.movePosition(QtGui.QTextCursor.StartOfLine)
218 cursor.movePosition(QtGui.QTextCursor.StartOfLine)
219 start_pos = cursor.position()
219 start_pos = cursor.position()
220 start_line = cursor.blockNumber()
220 start_line = cursor.blockNumber()
221 if start_line == self._get_prompt_cursor().blockNumber():
221 if start_line == self._get_prompt_cursor().blockNumber():
222 start_pos += len(self._prompt)
222 start_pos += len(self._prompt)
223 else:
223 else:
224 start_pos += len(self._continuation_prompt)
224 start_pos += len(self._continuation_prompt)
225 if shift_down and self._in_buffer(position):
225 if shift_down and self._in_buffer(position):
226 self._set_selection(position, start_pos)
226 self._set_selection(position, start_pos)
227 else:
227 else:
228 self._set_position(start_pos)
228 self._set_position(start_pos)
229 intercepted = True
229 intercepted = True
230
230
231 elif key == QtCore.Qt.Key_Backspace and not alt_down:
231 elif key == QtCore.Qt.Key_Backspace and not alt_down:
232
232
233 # Line deletion (remove continuation prompt)
233 # Line deletion (remove continuation prompt)
234 len_prompt = len(self._continuation_prompt)
234 len_prompt = len(self._continuation_prompt)
235 if not self._reading and \
235 if not self._reading and \
236 cursor.columnNumber() == len_prompt and \
236 cursor.columnNumber() == len_prompt and \
237 position != self._prompt_pos:
237 position != self._prompt_pos:
238 cursor.setPosition(position - len_prompt,
238 cursor.setPosition(position - len_prompt,
239 QtGui.QTextCursor.KeepAnchor)
239 QtGui.QTextCursor.KeepAnchor)
240 cursor.removeSelectedText()
240 cursor.removeSelectedText()
241
241
242 # Regular backwards deletion
242 # Regular backwards deletion
243 else:
243 else:
244 anchor = cursor.anchor()
244 anchor = cursor.anchor()
245 if anchor == position:
245 if anchor == position:
246 intercepted = not self._in_buffer(position - 1)
246 intercepted = not self._in_buffer(position - 1)
247 else:
247 else:
248 intercepted = not self._in_buffer(min(anchor, position))
248 intercepted = not self._in_buffer(min(anchor, position))
249
249
250 elif key == QtCore.Qt.Key_Delete:
250 elif key == QtCore.Qt.Key_Delete:
251 anchor = cursor.anchor()
251 anchor = cursor.anchor()
252 intercepted = not self._in_buffer(min(anchor, position))
252 intercepted = not self._in_buffer(min(anchor, position))
253
253
254 # Don't move cursor if control is down to allow copy-paste using
254 # Don't move cursor if control is down to allow copy-paste using
255 # the keyboard in any part of the buffer.
255 # the keyboard in any part of the buffer.
256 if not ctrl_down:
256 if not ctrl_down:
257 self._keep_cursor_in_buffer()
257 self._keep_cursor_in_buffer()
258
258
259 if not intercepted:
259 if not intercepted:
260 QtGui.QPlainTextEdit.keyPressEvent(self, event)
260 QtGui.QPlainTextEdit.keyPressEvent(self, event)
261
261
262 #--------------------------------------------------------------------------
262 #--------------------------------------------------------------------------
263 # 'QPlainTextEdit' interface
263 # 'QPlainTextEdit' interface
264 #--------------------------------------------------------------------------
264 #--------------------------------------------------------------------------
265
265
266 def appendHtml(self, html):
266 def appendHtml(self, html):
267 """ Reimplemented to not append HTML as a new paragraph, which doesn't
267 """ Reimplemented to not append HTML as a new paragraph, which doesn't
268 make sense for a console widget.
268 make sense for a console widget.
269 """
269 """
270 cursor = self._get_end_cursor()
270 cursor = self._get_end_cursor()
271 cursor.insertHtml(html)
271 cursor.insertHtml(html)
272
272
273 # After appending HTML, the text document "remembers" the current
273 # After appending HTML, the text document "remembers" the current
274 # formatting, which means that subsequent calls to 'appendPlainText'
274 # formatting, which means that subsequent calls to 'appendPlainText'
275 # will be formatted similarly, a behavior that we do not want. To
275 # will be formatted similarly, a behavior that we do not want. To
276 # prevent this, we make sure that the last character has no formatting.
276 # prevent this, we make sure that the last character has no formatting.
277 cursor.movePosition(QtGui.QTextCursor.Left,
277 cursor.movePosition(QtGui.QTextCursor.Left,
278 QtGui.QTextCursor.KeepAnchor)
278 QtGui.QTextCursor.KeepAnchor)
279 if cursor.selection().toPlainText().trimmed().isEmpty():
279 if cursor.selection().toPlainText().trimmed().isEmpty():
280 # If the last character is whitespace, it doesn't matter how it's
280 # If the last character is whitespace, it doesn't matter how it's
281 # formatted, so just clear the formatting.
281 # formatted, so just clear the formatting.
282 cursor.setCharFormat(QtGui.QTextCharFormat())
282 cursor.setCharFormat(QtGui.QTextCharFormat())
283 else:
283 else:
284 # Otherwise, add an unformatted space.
284 # Otherwise, add an unformatted space.
285 cursor.movePosition(QtGui.QTextCursor.Right)
285 cursor.movePosition(QtGui.QTextCursor.Right)
286 cursor.insertText(' ', QtGui.QTextCharFormat())
286 cursor.insertText(' ', QtGui.QTextCharFormat())
287
287
288 def appendPlainText(self, text):
288 def appendPlainText(self, text):
289 """ Reimplemented to not append text as a new paragraph, which doesn't
289 """ Reimplemented to not append text as a new paragraph, which doesn't
290 make sense for a console widget. Also, if enabled, handle ANSI
290 make sense for a console widget. Also, if enabled, handle ANSI
291 codes.
291 codes.
292 """
292 """
293 cursor = self._get_end_cursor()
293 cursor = self._get_end_cursor()
294 if self.ansi_codes:
294 if self.ansi_codes:
295 for substring in self._ansi_processor.split_string(text):
295 for substring in self._ansi_processor.split_string(text):
296 format = self._ansi_processor.get_format()
296 format = self._ansi_processor.get_format()
297 cursor.insertText(substring, format)
297 cursor.insertText(substring, format)
298 else:
298 else:
299 cursor.insertText(text)
299 cursor.insertText(text)
300
300
301 def clear(self, keep_input=False):
301 def clear(self, keep_input=False):
302 """ Reimplemented to write a new prompt. If 'keep_input' is set,
302 """ Reimplemented to write a new prompt. If 'keep_input' is set,
303 restores the old input buffer when the new prompt is written.
303 restores the old input buffer when the new prompt is written.
304 """
304 """
305 QtGui.QPlainTextEdit.clear(self)
305 QtGui.QPlainTextEdit.clear(self)
306 if keep_input:
306 if keep_input:
307 input_buffer = self.input_buffer
307 input_buffer = self.input_buffer
308 self._show_prompt()
308 self._show_prompt()
309 if keep_input:
309 if keep_input:
310 self.input_buffer = input_buffer
310 self.input_buffer = input_buffer
311
311
312 def paste(self):
312 def paste(self):
313 """ Reimplemented to ensure that text is pasted in the editing region.
313 """ Reimplemented to ensure that text is pasted in the editing region.
314 """
314 """
315 self._keep_cursor_in_buffer()
315 self._keep_cursor_in_buffer()
316 QtGui.QPlainTextEdit.paste(self)
316 QtGui.QPlainTextEdit.paste(self)
317
317
318 def print_(self, printer):
318 def print_(self, printer):
319 """ Reimplemented to work around a bug in PyQt: the C++ level 'print_'
319 """ Reimplemented to work around a bug in PyQt: the C++ level 'print_'
320 slot has the wrong signature.
320 slot has the wrong signature.
321 """
321 """
322 QtGui.QPlainTextEdit.print_(self, printer)
322 QtGui.QPlainTextEdit.print_(self, printer)
323
323
324 #---------------------------------------------------------------------------
324 #---------------------------------------------------------------------------
325 # 'ConsoleWidget' public interface
325 # 'ConsoleWidget' public interface
326 #---------------------------------------------------------------------------
326 #---------------------------------------------------------------------------
327
327
328 def execute(self, source=None, hidden=False, interactive=False):
328 def execute(self, source=None, hidden=False, interactive=False):
329 """ Executes source or the input buffer, possibly prompting for more
329 """ Executes source or the input buffer, possibly prompting for more
330 input.
330 input.
331
331
332 Parameters:
332 Parameters:
333 -----------
333 -----------
334 source : str, optional
334 source : str, optional
335
335
336 The source to execute. If not specified, the input buffer will be
336 The source to execute. If not specified, the input buffer will be
337 used. If specified and 'hidden' is False, the input buffer will be
337 used. If specified and 'hidden' is False, the input buffer will be
338 replaced with the source before execution.
338 replaced with the source before execution.
339
339
340 hidden : bool, optional (default False)
340 hidden : bool, optional (default False)
341
341
342 If set, no output will be shown and the prompt will not be modified.
342 If set, no output will be shown and the prompt will not be modified.
343 In other words, it will be completely invisible to the user that
343 In other words, it will be completely invisible to the user that
344 an execution has occurred.
344 an execution has occurred.
345
345
346 interactive : bool, optional (default False)
346 interactive : bool, optional (default False)
347
347
348 Whether the console is to treat the source as having been manually
348 Whether the console is to treat the source as having been manually
349 entered by the user. The effect of this parameter depends on the
349 entered by the user. The effect of this parameter depends on the
350 subclass implementation.
350 subclass implementation.
351
351
352 Raises:
352 Raises:
353 -------
353 -------
354 RuntimeError
354 RuntimeError
355 If incomplete input is given and 'hidden' is True. In this case,
355 If incomplete input is given and 'hidden' is True. In this case,
356 it not possible to prompt for more input.
356 it not possible to prompt for more input.
357
357
358 Returns:
358 Returns:
359 --------
359 --------
360 A boolean indicating whether the source was executed.
360 A boolean indicating whether the source was executed.
361 """
361 """
362 if not hidden:
362 if not hidden:
363 if source is not None:
363 if source is not None:
364 self.input_buffer = source
364 self.input_buffer = source
365
365
366 self.appendPlainText('\n')
366 self.appendPlainText('\n')
367 self._executing_input_buffer = self.input_buffer
367 self._executing_input_buffer = self.input_buffer
368 self._executing = True
368 self._executing = True
369 self._prompt_finished()
369 self._prompt_finished()
370
370
371 real_source = self.input_buffer if source is None else source
371 real_source = self.input_buffer if source is None else source
372 complete = self._is_complete(real_source, interactive)
372 complete = self._is_complete(real_source, interactive)
373 if complete:
373 if complete:
374 if not hidden:
374 if not hidden:
375 # The maximum block count is only in effect during execution.
375 # The maximum block count is only in effect during execution.
376 # This ensures that _prompt_pos does not become invalid due to
376 # This ensures that _prompt_pos does not become invalid due to
377 # text truncation.
377 # text truncation.
378 self.setMaximumBlockCount(self.buffer_size)
378 self.setMaximumBlockCount(self.buffer_size)
379 self._execute(real_source, hidden)
379 self._execute(real_source, hidden)
380 elif hidden:
380 elif hidden:
381 raise RuntimeError('Incomplete noninteractive input: "%s"' % source)
381 raise RuntimeError('Incomplete noninteractive input: "%s"' % source)
382 else:
382 else:
383 self._show_continuation_prompt()
383 self._show_continuation_prompt()
384
384
385 return complete
385 return complete
386
386
387 def _get_input_buffer(self):
387 def _get_input_buffer(self):
388 """ The text that the user has entered entered at the current prompt.
388 """ The text that the user has entered entered at the current prompt.
389 """
389 """
390 # If we're executing, the input buffer may not even exist anymore due to
390 # If we're executing, the input buffer may not even exist anymore due to
391 # the limit imposed by 'buffer_size'. Therefore, we store it.
391 # the limit imposed by 'buffer_size'. Therefore, we store it.
392 if self._executing:
392 if self._executing:
393 return self._executing_input_buffer
393 return self._executing_input_buffer
394
394
395 cursor = self._get_end_cursor()
395 cursor = self._get_end_cursor()
396 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
396 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
397 input_buffer = str(cursor.selection().toPlainText())
397 input_buffer = str(cursor.selection().toPlainText())
398
398
399 # Strip out continuation prompts.
399 # Strip out continuation prompts.
400 return input_buffer.replace('\n' + self._continuation_prompt, '\n')
400 return input_buffer.replace('\n' + self._continuation_prompt, '\n')
401
401
402 def _set_input_buffer(self, string):
402 def _set_input_buffer(self, string):
403 """ Replaces the text in the input buffer with 'string'.
403 """ Replaces the text in the input buffer with 'string'.
404 """
404 """
405 # Add continuation prompts where necessary.
405 # Remove old text.
406 lines = string.splitlines()
407 for i in xrange(1, len(lines)):
408 lines[i] = self._continuation_prompt + lines[i]
409 string = '\n'.join(lines)
410
411 # Replace buffer with new text.
412 cursor = self._get_end_cursor()
406 cursor = self._get_end_cursor()
413 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
407 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
414 cursor.insertText(string)
408 cursor.removeSelectedText()
409
410 # Insert new text with continuation prompts.
411 lines = string.splitlines(True)
412 if lines:
413 self.appendPlainText(lines[0])
414 for i in xrange(1, len(lines)):
415 if self._continuation_prompt_html is None:
416 self.appendPlainText(self._continuation_prompt)
417 else:
418 self.appendHtml(self._continuation_prompt_html)
419 self.appendPlainText(lines[i])
415 self.moveCursor(QtGui.QTextCursor.End)
420 self.moveCursor(QtGui.QTextCursor.End)
416
421
417 input_buffer = property(_get_input_buffer, _set_input_buffer)
422 input_buffer = property(_get_input_buffer, _set_input_buffer)
418
423
419 def _get_input_buffer_cursor_line(self):
424 def _get_input_buffer_cursor_line(self):
420 """ The text in the line of the input buffer in which the user's cursor
425 """ The text in the line of the input buffer in which the user's cursor
421 rests. Returns a string if there is such a line; otherwise, None.
426 rests. Returns a string if there is such a line; otherwise, None.
422 """
427 """
423 if self._executing:
428 if self._executing:
424 return None
429 return None
425 cursor = self.textCursor()
430 cursor = self.textCursor()
426 if cursor.position() >= self._prompt_pos:
431 if cursor.position() >= self._prompt_pos:
427 text = self._get_block_plain_text(cursor.block())
432 text = self._get_block_plain_text(cursor.block())
428 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
433 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
429 return text[len(self._prompt):]
434 return text[len(self._prompt):]
430 else:
435 else:
431 return text[len(self._continuation_prompt):]
436 return text[len(self._continuation_prompt):]
432 else:
437 else:
433 return None
438 return None
434
439
435 input_buffer_cursor_line = property(_get_input_buffer_cursor_line)
440 input_buffer_cursor_line = property(_get_input_buffer_cursor_line)
436
441
437 def _get_font(self):
442 def _get_font(self):
438 """ The base font being used by the ConsoleWidget.
443 """ The base font being used by the ConsoleWidget.
439 """
444 """
440 return self.document().defaultFont()
445 return self.document().defaultFont()
441
446
442 def _set_font(self, font):
447 def _set_font(self, font):
443 """ Sets the base font for the ConsoleWidget to the specified QFont.
448 """ Sets the base font for the ConsoleWidget to the specified QFont.
444 """
449 """
445 font_metrics = QtGui.QFontMetrics(font)
450 font_metrics = QtGui.QFontMetrics(font)
446 self.setTabStopWidth(self.tab_width * font_metrics.width(' '))
451 self.setTabStopWidth(self.tab_width * font_metrics.width(' '))
447
452
448 self._completion_widget.setFont(font)
453 self._completion_widget.setFont(font)
449 self.document().setDefaultFont(font)
454 self.document().setDefaultFont(font)
450
455
451 font = property(_get_font, _set_font)
456 font = property(_get_font, _set_font)
452
457
453 def reset_font(self):
458 def reset_font(self):
454 """ Sets the font to the default fixed-width font for this platform.
459 """ Sets the font to the default fixed-width font for this platform.
455 """
460 """
456 if sys.platform == 'win32':
461 if sys.platform == 'win32':
457 name = 'Courier'
462 name = 'Courier'
458 elif sys.platform == 'darwin':
463 elif sys.platform == 'darwin':
459 name = 'Monaco'
464 name = 'Monaco'
460 else:
465 else:
461 name = 'Monospace'
466 name = 'Monospace'
462 font = QtGui.QFont(name, QtGui.qApp.font().pointSize())
467 font = QtGui.QFont(name, QtGui.qApp.font().pointSize())
463 font.setStyleHint(QtGui.QFont.TypeWriter)
468 font.setStyleHint(QtGui.QFont.TypeWriter)
464 self._set_font(font)
469 self._set_font(font)
465
470
466 #---------------------------------------------------------------------------
471 #---------------------------------------------------------------------------
467 # 'ConsoleWidget' abstract interface
472 # 'ConsoleWidget' abstract interface
468 #---------------------------------------------------------------------------
473 #---------------------------------------------------------------------------
469
474
470 def _is_complete(self, source, interactive):
475 def _is_complete(self, source, interactive):
471 """ Returns whether 'source' can be executed. When triggered by an
476 """ Returns whether 'source' can be executed. When triggered by an
472 Enter/Return key press, 'interactive' is True; otherwise, it is
477 Enter/Return key press, 'interactive' is True; otherwise, it is
473 False.
478 False.
474 """
479 """
475 raise NotImplementedError
480 raise NotImplementedError
476
481
477 def _execute(self, source, hidden):
482 def _execute(self, source, hidden):
478 """ Execute 'source'. If 'hidden', do not show any output.
483 """ Execute 'source'. If 'hidden', do not show any output.
479 """
484 """
480 raise NotImplementedError
485 raise NotImplementedError
481
486
482 def _prompt_started_hook(self):
487 def _prompt_started_hook(self):
483 """ Called immediately after a new prompt is displayed.
488 """ Called immediately after a new prompt is displayed.
484 """
489 """
485 pass
490 pass
486
491
487 def _prompt_finished_hook(self):
492 def _prompt_finished_hook(self):
488 """ Called immediately after a prompt is finished, i.e. when some input
493 """ Called immediately after a prompt is finished, i.e. when some input
489 will be processed and a new prompt displayed.
494 will be processed and a new prompt displayed.
490 """
495 """
491 pass
496 pass
492
497
493 def _up_pressed(self):
498 def _up_pressed(self):
494 """ Called when the up key is pressed. Returns whether to continue
499 """ Called when the up key is pressed. Returns whether to continue
495 processing the event.
500 processing the event.
496 """
501 """
497 return True
502 return True
498
503
499 def _down_pressed(self):
504 def _down_pressed(self):
500 """ Called when the down key is pressed. Returns whether to continue
505 """ Called when the down key is pressed. Returns whether to continue
501 processing the event.
506 processing the event.
502 """
507 """
503 return True
508 return True
504
509
505 def _tab_pressed(self):
510 def _tab_pressed(self):
506 """ Called when the tab key is pressed. Returns whether to continue
511 """ Called when the tab key is pressed. Returns whether to continue
507 processing the event.
512 processing the event.
508 """
513 """
509 return False
514 return False
510
515
511 #--------------------------------------------------------------------------
516 #--------------------------------------------------------------------------
512 # 'ConsoleWidget' protected interface
517 # 'ConsoleWidget' protected interface
513 #--------------------------------------------------------------------------
518 #--------------------------------------------------------------------------
514
519
515 def _append_html_fetching_plain_text(self, html):
520 def _append_html_fetching_plain_text(self, html):
516 """ Appends 'html', then returns the plain text version of it.
521 """ Appends 'html', then returns the plain text version of it.
517 """
522 """
518 anchor = self._get_end_cursor().position()
523 anchor = self._get_end_cursor().position()
519 self.appendHtml(html)
524 self.appendHtml(html)
520 cursor = self._get_end_cursor()
525 cursor = self._get_end_cursor()
521 cursor.setPosition(anchor, QtGui.QTextCursor.KeepAnchor)
526 cursor.setPosition(anchor, QtGui.QTextCursor.KeepAnchor)
522 return str(cursor.selection().toPlainText())
527 return str(cursor.selection().toPlainText())
523
528
524 def _append_plain_text_keeping_prompt(self, text):
529 def _append_plain_text_keeping_prompt(self, text):
525 """ Writes 'text' after the current prompt, then restores the old prompt
530 """ Writes 'text' after the current prompt, then restores the old prompt
526 with its old input buffer.
531 with its old input buffer.
527 """
532 """
528 input_buffer = self.input_buffer
533 input_buffer = self.input_buffer
529 self.appendPlainText('\n')
534 self.appendPlainText('\n')
530 self._prompt_finished()
535 self._prompt_finished()
531
536
532 self.appendPlainText(text)
537 self.appendPlainText(text)
533 self._show_prompt()
538 self._show_prompt()
534 self.input_buffer = input_buffer
539 self.input_buffer = input_buffer
535
540
536 def _control_down(self, modifiers):
541 def _control_down(self, modifiers):
537 """ Given a KeyboardModifiers flags object, return whether the Control
542 """ Given a KeyboardModifiers flags object, return whether the Control
538 key is down (on Mac OS, treat the Command key as a synonym for
543 key is down (on Mac OS, treat the Command key as a synonym for
539 Control).
544 Control).
540 """
545 """
541 down = bool(modifiers & QtCore.Qt.ControlModifier)
546 down = bool(modifiers & QtCore.Qt.ControlModifier)
542
547
543 # Note: on Mac OS, ControlModifier corresponds to the Command key while
548 # Note: on Mac OS, ControlModifier corresponds to the Command key while
544 # MetaModifier corresponds to the Control key.
549 # MetaModifier corresponds to the Control key.
545 if sys.platform == 'darwin':
550 if sys.platform == 'darwin':
546 down = down ^ bool(modifiers & QtCore.Qt.MetaModifier)
551 down = down ^ bool(modifiers & QtCore.Qt.MetaModifier)
547
552
548 return down
553 return down
549
554
550 def _complete_with_items(self, cursor, items):
555 def _complete_with_items(self, cursor, items):
551 """ Performs completion with 'items' at the specified cursor location.
556 """ Performs completion with 'items' at the specified cursor location.
552 """
557 """
553 if len(items) == 1:
558 if len(items) == 1:
554 cursor.setPosition(self.textCursor().position(),
559 cursor.setPosition(self.textCursor().position(),
555 QtGui.QTextCursor.KeepAnchor)
560 QtGui.QTextCursor.KeepAnchor)
556 cursor.insertText(items[0])
561 cursor.insertText(items[0])
557 elif len(items) > 1:
562 elif len(items) > 1:
558 if self.gui_completion:
563 if self.gui_completion:
559 self._completion_widget.show_items(cursor, items)
564 self._completion_widget.show_items(cursor, items)
560 else:
565 else:
561 text = '\n'.join(items) + '\n'
566 text = '\n'.join(items) + '\n'
562 self._append_plain_text_keeping_prompt(text)
567 self._append_plain_text_keeping_prompt(text)
563
568
564 def _get_block_plain_text(self, block):
569 def _get_block_plain_text(self, block):
565 """ Given a QTextBlock, return its unformatted text.
570 """ Given a QTextBlock, return its unformatted text.
566 """
571 """
567 cursor = QtGui.QTextCursor(block)
572 cursor = QtGui.QTextCursor(block)
568 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
573 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
569 cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
574 cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
570 QtGui.QTextCursor.KeepAnchor)
575 QtGui.QTextCursor.KeepAnchor)
571 return str(cursor.selection().toPlainText())
576 return str(cursor.selection().toPlainText())
572
577
573 def _get_end_cursor(self):
578 def _get_end_cursor(self):
574 """ Convenience method that returns a cursor for the last character.
579 """ Convenience method that returns a cursor for the last character.
575 """
580 """
576 cursor = self.textCursor()
581 cursor = self.textCursor()
577 cursor.movePosition(QtGui.QTextCursor.End)
582 cursor.movePosition(QtGui.QTextCursor.End)
578 return cursor
583 return cursor
579
584
580 def _get_prompt_cursor(self):
585 def _get_prompt_cursor(self):
581 """ Convenience method that returns a cursor for the prompt position.
586 """ Convenience method that returns a cursor for the prompt position.
582 """
587 """
583 cursor = self.textCursor()
588 cursor = self.textCursor()
584 cursor.setPosition(self._prompt_pos)
589 cursor.setPosition(self._prompt_pos)
585 return cursor
590 return cursor
586
591
587 def _get_selection_cursor(self, start, end):
592 def _get_selection_cursor(self, start, end):
588 """ Convenience method that returns a cursor with text selected between
593 """ Convenience method that returns a cursor with text selected between
589 the positions 'start' and 'end'.
594 the positions 'start' and 'end'.
590 """
595 """
591 cursor = self.textCursor()
596 cursor = self.textCursor()
592 cursor.setPosition(start)
597 cursor.setPosition(start)
593 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
598 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
594 return cursor
599 return cursor
595
600
596 def _get_word_start_cursor(self, position):
601 def _get_word_start_cursor(self, position):
597 """ Find the start of the word to the left the given position. If a
602 """ Find the start of the word to the left the given position. If a
598 sequence of non-word characters precedes the first word, skip over
603 sequence of non-word characters precedes the first word, skip over
599 them. (This emulates the behavior of bash, emacs, etc.)
604 them. (This emulates the behavior of bash, emacs, etc.)
600 """
605 """
601 document = self.document()
606 document = self.document()
602 position -= 1
607 position -= 1
603 while self._in_buffer(position) and \
608 while self._in_buffer(position) and \
604 not document.characterAt(position).isLetterOrNumber():
609 not document.characterAt(position).isLetterOrNumber():
605 position -= 1
610 position -= 1
606 while self._in_buffer(position) and \
611 while self._in_buffer(position) and \
607 document.characterAt(position).isLetterOrNumber():
612 document.characterAt(position).isLetterOrNumber():
608 position -= 1
613 position -= 1
609 cursor = self.textCursor()
614 cursor = self.textCursor()
610 cursor.setPosition(position + 1)
615 cursor.setPosition(position + 1)
611 return cursor
616 return cursor
612
617
613 def _get_word_end_cursor(self, position):
618 def _get_word_end_cursor(self, position):
614 """ Find the end of the word to the right the given position. If a
619 """ Find the end of the word to the right the given position. If a
615 sequence of non-word characters precedes the first word, skip over
620 sequence of non-word characters precedes the first word, skip over
616 them. (This emulates the behavior of bash, emacs, etc.)
621 them. (This emulates the behavior of bash, emacs, etc.)
617 """
622 """
618 document = self.document()
623 document = self.document()
619 end = self._get_end_cursor().position()
624 end = self._get_end_cursor().position()
620 while position < end and \
625 while position < end and \
621 not document.characterAt(position).isLetterOrNumber():
626 not document.characterAt(position).isLetterOrNumber():
622 position += 1
627 position += 1
623 while position < end and \
628 while position < end and \
624 document.characterAt(position).isLetterOrNumber():
629 document.characterAt(position).isLetterOrNumber():
625 position += 1
630 position += 1
626 cursor = self.textCursor()
631 cursor = self.textCursor()
627 cursor.setPosition(position)
632 cursor.setPosition(position)
628 return cursor
633 return cursor
629
634
630 def _prompt_started(self):
635 def _prompt_started(self):
631 """ Called immediately after a new prompt is displayed.
636 """ Called immediately after a new prompt is displayed.
632 """
637 """
633 # Temporarily disable the maximum block count to permit undo/redo and
638 # Temporarily disable the maximum block count to permit undo/redo and
634 # to ensure that the prompt position does not change due to truncation.
639 # to ensure that the prompt position does not change due to truncation.
635 self.setMaximumBlockCount(0)
640 self.setMaximumBlockCount(0)
636 self.setUndoRedoEnabled(True)
641 self.setUndoRedoEnabled(True)
637
642
638 self.setReadOnly(False)
643 self.setReadOnly(False)
639 self.moveCursor(QtGui.QTextCursor.End)
644 self.moveCursor(QtGui.QTextCursor.End)
640 self.centerCursor()
645 self.centerCursor()
641
646
642 self._executing = False
647 self._executing = False
643 self._prompt_started_hook()
648 self._prompt_started_hook()
644
649
645 def _prompt_finished(self):
650 def _prompt_finished(self):
646 """ Called immediately after a prompt is finished, i.e. when some input
651 """ Called immediately after a prompt is finished, i.e. when some input
647 will be processed and a new prompt displayed.
652 will be processed and a new prompt displayed.
648 """
653 """
649 self.setUndoRedoEnabled(False)
654 self.setUndoRedoEnabled(False)
650 self.setReadOnly(True)
655 self.setReadOnly(True)
651 self._prompt_finished_hook()
656 self._prompt_finished_hook()
652
657
653 def _readline(self, prompt='', callback=None):
658 def _readline(self, prompt='', callback=None):
654 """ Reads one line of input from the user.
659 """ Reads one line of input from the user.
655
660
656 Parameters
661 Parameters
657 ----------
662 ----------
658 prompt : str, optional
663 prompt : str, optional
659 The prompt to print before reading the line.
664 The prompt to print before reading the line.
660
665
661 callback : callable, optional
666 callback : callable, optional
662 A callback to execute with the read line. If not specified, input is
667 A callback to execute with the read line. If not specified, input is
663 read *synchronously* and this method does not return until it has
668 read *synchronously* and this method does not return until it has
664 been read.
669 been read.
665
670
666 Returns
671 Returns
667 -------
672 -------
668 If a callback is specified, returns nothing. Otherwise, returns the
673 If a callback is specified, returns nothing. Otherwise, returns the
669 input string with the trailing newline stripped.
674 input string with the trailing newline stripped.
670 """
675 """
671 if self._reading:
676 if self._reading:
672 raise RuntimeError('Cannot read a line. Widget is already reading.')
677 raise RuntimeError('Cannot read a line. Widget is already reading.')
673
678
674 if not callback and not self.isVisible():
679 if not callback and not self.isVisible():
675 # If the user cannot see the widget, this function cannot return.
680 # If the user cannot see the widget, this function cannot return.
676 raise RuntimeError('Cannot synchronously read a line if the widget'
681 raise RuntimeError('Cannot synchronously read a line if the widget'
677 'is not visible!')
682 'is not visible!')
678
683
679 self._reading = True
684 self._reading = True
680 self._show_prompt(prompt, newline=False)
685 self._show_prompt(prompt, newline=False)
681
686
682 if callback is None:
687 if callback is None:
683 self._reading_callback = None
688 self._reading_callback = None
684 while self._reading:
689 while self._reading:
685 QtCore.QCoreApplication.processEvents()
690 QtCore.QCoreApplication.processEvents()
686 return self.input_buffer.rstrip('\n')
691 return self.input_buffer.rstrip('\n')
687
692
688 else:
693 else:
689 self._reading_callback = lambda: \
694 self._reading_callback = lambda: \
690 callback(self.input_buffer.rstrip('\n'))
695 callback(self.input_buffer.rstrip('\n'))
691
696
692 def _reset(self):
697 def _reset(self):
693 """ Clears the console and resets internal state variables.
698 """ Clears the console and resets internal state variables.
694 """
699 """
695 QtGui.QPlainTextEdit.clear(self)
700 QtGui.QPlainTextEdit.clear(self)
696 self._executing = self._reading = False
701 self._executing = self._reading = False
697
702
698 def _set_continuation_prompt(self, prompt, html=False):
703 def _set_continuation_prompt(self, prompt, html=False):
699 """ Sets the continuation prompt.
704 """ Sets the continuation prompt.
700
705
701 Parameters
706 Parameters
702 ----------
707 ----------
703 prompt : str
708 prompt : str
704 The prompt to show when more input is needed.
709 The prompt to show when more input is needed.
705
710
706 html : bool, optional (default False)
711 html : bool, optional (default False)
707 If set, the prompt will be inserted as formatted HTML. Otherwise,
712 If set, the prompt will be inserted as formatted HTML. Otherwise,
708 the prompt will be treated as plain text, though ANSI color codes
713 the prompt will be treated as plain text, though ANSI color codes
709 will be handled.
714 will be handled.
710 """
715 """
711 if html:
716 if html:
712 self._continuation_prompt_html = prompt
717 self._continuation_prompt_html = prompt
713 else:
718 else:
714 self._continuation_prompt = prompt
719 self._continuation_prompt = prompt
715 self._continuation_prompt_html = None
720 self._continuation_prompt_html = None
716
721
717 def _set_position(self, position):
722 def _set_position(self, position):
718 """ Convenience method to set the position of the cursor.
723 """ Convenience method to set the position of the cursor.
719 """
724 """
720 cursor = self.textCursor()
725 cursor = self.textCursor()
721 cursor.setPosition(position)
726 cursor.setPosition(position)
722 self.setTextCursor(cursor)
727 self.setTextCursor(cursor)
723
728
724 def _set_selection(self, start, end):
729 def _set_selection(self, start, end):
725 """ Convenience method to set the current selected text.
730 """ Convenience method to set the current selected text.
726 """
731 """
727 self.setTextCursor(self._get_selection_cursor(start, end))
732 self.setTextCursor(self._get_selection_cursor(start, end))
728
733
729 def _show_prompt(self, prompt=None, html=False, newline=True):
734 def _show_prompt(self, prompt=None, html=False, newline=True):
730 """ Writes a new prompt at the end of the buffer.
735 """ Writes a new prompt at the end of the buffer.
731
736
732 Parameters
737 Parameters
733 ----------
738 ----------
734 prompt : str, optional
739 prompt : str, optional
735 The prompt to show. If not specified, the previous prompt is used.
740 The prompt to show. If not specified, the previous prompt is used.
736
741
737 html : bool, optional (default False)
742 html : bool, optional (default False)
738 Only relevant when a prompt is specified. If set, the prompt will
743 Only relevant when a prompt is specified. If set, the prompt will
739 be inserted as formatted HTML. Otherwise, the prompt will be treated
744 be inserted as formatted HTML. Otherwise, the prompt will be treated
740 as plain text, though ANSI color codes will be handled.
745 as plain text, though ANSI color codes will be handled.
741
746
742 newline : bool, optional (default True)
747 newline : bool, optional (default True)
743 If set, a new line will be written before showing the prompt if
748 If set, a new line will be written before showing the prompt if
744 there is not already a newline at the end of the buffer.
749 there is not already a newline at the end of the buffer.
745 """
750 """
746 # Insert a preliminary newline, if necessary.
751 # Insert a preliminary newline, if necessary.
747 if newline:
752 if newline:
748 cursor = self._get_end_cursor()
753 cursor = self._get_end_cursor()
749 if cursor.position() > 0:
754 if cursor.position() > 0:
750 cursor.movePosition(QtGui.QTextCursor.Left,
755 cursor.movePosition(QtGui.QTextCursor.Left,
751 QtGui.QTextCursor.KeepAnchor)
756 QtGui.QTextCursor.KeepAnchor)
752 if str(cursor.selection().toPlainText()) != '\n':
757 if str(cursor.selection().toPlainText()) != '\n':
753 self.appendPlainText('\n')
758 self.appendPlainText('\n')
754
759
755 # Write the prompt.
760 # Write the prompt.
756 if prompt is None:
761 if prompt is None:
757 if self._prompt_html is None:
762 if self._prompt_html is None:
758 self.appendPlainText(self._prompt)
763 self.appendPlainText(self._prompt)
759 else:
764 else:
760 self.appendHtml(self._prompt_html)
765 self.appendHtml(self._prompt_html)
761 else:
766 else:
762 if html:
767 if html:
763 self._prompt = self._append_html_fetching_plain_text(prompt)
768 self._prompt = self._append_html_fetching_plain_text(prompt)
764 self._prompt_html = prompt
769 self._prompt_html = prompt
765 else:
770 else:
766 self.appendPlainText(prompt)
771 self.appendPlainText(prompt)
767 self._prompt = prompt
772 self._prompt = prompt
768 self._prompt_html = None
773 self._prompt_html = None
769
774
770 self._prompt_pos = self._get_end_cursor().position()
775 self._prompt_pos = self._get_end_cursor().position()
771 self._prompt_started()
776 self._prompt_started()
772
777
773 def _show_continuation_prompt(self):
778 def _show_continuation_prompt(self):
774 """ Writes a new continuation prompt at the end of the buffer.
779 """ Writes a new continuation prompt at the end of the buffer.
775 """
780 """
776 if self._continuation_prompt_html is None:
781 if self._continuation_prompt_html is None:
777 self.appendPlainText(self._continuation_prompt)
782 self.appendPlainText(self._continuation_prompt)
778 else:
783 else:
779 self._continuation_prompt = self._append_html_fetching_plain_text(
784 self._continuation_prompt = self._append_html_fetching_plain_text(
780 self._continuation_prompt_html)
785 self._continuation_prompt_html)
781
786
782 self._prompt_started()
787 self._prompt_started()
783
788
784 def _in_buffer(self, position):
789 def _in_buffer(self, position):
785 """ Returns whether the given position is inside the editing region.
790 """ Returns whether the given position is inside the editing region.
786 """
791 """
787 return position >= self._prompt_pos
792 return position >= self._prompt_pos
788
793
789 def _keep_cursor_in_buffer(self):
794 def _keep_cursor_in_buffer(self):
790 """ Ensures that the cursor is inside the editing region. Returns
795 """ Ensures that the cursor is inside the editing region. Returns
791 whether the cursor was moved.
796 whether the cursor was moved.
792 """
797 """
793 cursor = self.textCursor()
798 cursor = self.textCursor()
794 if cursor.position() < self._prompt_pos:
799 if cursor.position() < self._prompt_pos:
795 cursor.movePosition(QtGui.QTextCursor.End)
800 cursor.movePosition(QtGui.QTextCursor.End)
796 self.setTextCursor(cursor)
801 self.setTextCursor(cursor)
797 return True
802 return True
798 else:
803 else:
799 return False
804 return False
800
805
801
806
802 class HistoryConsoleWidget(ConsoleWidget):
807 class HistoryConsoleWidget(ConsoleWidget):
803 """ A ConsoleWidget that keeps a history of the commands that have been
808 """ A ConsoleWidget that keeps a history of the commands that have been
804 executed.
809 executed.
805 """
810 """
806
811
807 #---------------------------------------------------------------------------
812 #---------------------------------------------------------------------------
808 # 'QObject' interface
813 # 'QObject' interface
809 #---------------------------------------------------------------------------
814 #---------------------------------------------------------------------------
810
815
811 def __init__(self, parent=None):
816 def __init__(self, parent=None):
812 super(HistoryConsoleWidget, self).__init__(parent)
817 super(HistoryConsoleWidget, self).__init__(parent)
813
818
814 self._history = []
819 self._history = []
815 self._history_index = 0
820 self._history_index = 0
816
821
817 #---------------------------------------------------------------------------
822 #---------------------------------------------------------------------------
818 # 'ConsoleWidget' public interface
823 # 'ConsoleWidget' public interface
819 #---------------------------------------------------------------------------
824 #---------------------------------------------------------------------------
820
825
821 def execute(self, source=None, hidden=False, interactive=False):
826 def execute(self, source=None, hidden=False, interactive=False):
822 """ Reimplemented to the store history.
827 """ Reimplemented to the store history.
823 """
828 """
824 if not hidden:
829 if not hidden:
825 history = self.input_buffer if source is None else source
830 history = self.input_buffer if source is None else source
826
831
827 executed = super(HistoryConsoleWidget, self).execute(
832 executed = super(HistoryConsoleWidget, self).execute(
828 source, hidden, interactive)
833 source, hidden, interactive)
829
834
830 if executed and not hidden:
835 if executed and not hidden:
831 self._history.append(history.rstrip())
836 self._history.append(history.rstrip())
832 self._history_index = len(self._history)
837 self._history_index = len(self._history)
833
838
834 return executed
839 return executed
835
840
836 #---------------------------------------------------------------------------
841 #---------------------------------------------------------------------------
837 # 'ConsoleWidget' abstract interface
842 # 'ConsoleWidget' abstract interface
838 #---------------------------------------------------------------------------
843 #---------------------------------------------------------------------------
839
844
840 def _up_pressed(self):
845 def _up_pressed(self):
841 """ Called when the up key is pressed. Returns whether to continue
846 """ Called when the up key is pressed. Returns whether to continue
842 processing the event.
847 processing the event.
843 """
848 """
844 prompt_cursor = self._get_prompt_cursor()
849 prompt_cursor = self._get_prompt_cursor()
845 if self.textCursor().blockNumber() == prompt_cursor.blockNumber():
850 if self.textCursor().blockNumber() == prompt_cursor.blockNumber():
846 self.history_previous()
851 self.history_previous()
847
852
848 # Go to the first line of prompt for seemless history scrolling.
853 # Go to the first line of prompt for seemless history scrolling.
849 cursor = self._get_prompt_cursor()
854 cursor = self._get_prompt_cursor()
850 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
855 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
851 self.setTextCursor(cursor)
856 self.setTextCursor(cursor)
852
857
853 return False
858 return False
854 return True
859 return True
855
860
856 def _down_pressed(self):
861 def _down_pressed(self):
857 """ Called when the down key is pressed. Returns whether to continue
862 """ Called when the down key is pressed. Returns whether to continue
858 processing the event.
863 processing the event.
859 """
864 """
860 end_cursor = self._get_end_cursor()
865 end_cursor = self._get_end_cursor()
861 if self.textCursor().blockNumber() == end_cursor.blockNumber():
866 if self.textCursor().blockNumber() == end_cursor.blockNumber():
862 self.history_next()
867 self.history_next()
863 return False
868 return False
864 return True
869 return True
865
870
866 #---------------------------------------------------------------------------
871 #---------------------------------------------------------------------------
867 # 'HistoryConsoleWidget' interface
872 # 'HistoryConsoleWidget' interface
868 #---------------------------------------------------------------------------
873 #---------------------------------------------------------------------------
869
874
870 def history_previous(self):
875 def history_previous(self):
871 """ If possible, set the input buffer to the previous item in the
876 """ If possible, set the input buffer to the previous item in the
872 history.
877 history.
873 """
878 """
874 if self._history_index > 0:
879 if self._history_index > 0:
875 self._history_index -= 1
880 self._history_index -= 1
876 self.input_buffer = self._history[self._history_index]
881 self.input_buffer = self._history[self._history_index]
877
882
878 def history_next(self):
883 def history_next(self):
879 """ Set the input buffer to the next item in the history, or a blank
884 """ Set the input buffer to the next item in the history, or a blank
880 line if there is no subsequent item.
885 line if there is no subsequent item.
881 """
886 """
882 if self._history_index < len(self._history):
887 if self._history_index < len(self._history):
883 self._history_index += 1
888 self._history_index += 1
884 if self._history_index < len(self._history):
889 if self._history_index < len(self._history):
885 self.input_buffer = self._history[self._history_index]
890 self.input_buffer = self._history[self._history_index]
886 else:
891 else:
887 self.input_buffer = ''
892 self.input_buffer = ''
General Comments 0
You need to be logged in to leave comments. Login now