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