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