##// END OF EJS Templates
Fixed wonky ConsoleWidget keyboard shortcuts on Mac OS.
epatters -
Show More
@@ -1,743 +1,761 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
126
127 #---------------------------------------------------------------------------
127 #---------------------------------------------------------------------------
128 # 'QObject' interface
128 # 'QObject' interface
129 #---------------------------------------------------------------------------
129 #---------------------------------------------------------------------------
130
130
131 def __init__(self, parent=None):
131 def __init__(self, parent=None):
132 QtGui.QPlainTextEdit.__init__(self, parent)
132 QtGui.QPlainTextEdit.__init__(self, parent)
133
133
134 # Initialize protected variables.
134 # Initialize protected variables.
135 self._ansi_processor = QtAnsiCodeProcessor()
135 self._ansi_processor = QtAnsiCodeProcessor()
136 self._completion_widget = CompletionWidget(self)
136 self._completion_widget = CompletionWidget(self)
137 self._continuation_prompt = '> '
137 self._continuation_prompt = '> '
138 self._executing = False
138 self._executing = False
139 self._prompt = ''
139 self._prompt = ''
140 self._prompt_pos = 0
140 self._prompt_pos = 0
141 self._reading = False
141 self._reading = False
142
142
143 # Set a monospaced font.
143 # Set a monospaced font.
144 self.reset_font()
144 self.reset_font()
145
145
146 # Define a custom context menu.
146 # Define a custom context menu.
147 self._context_menu = QtGui.QMenu(self)
147 self._context_menu = QtGui.QMenu(self)
148
148
149 copy_action = QtGui.QAction('Copy', self)
149 copy_action = QtGui.QAction('Copy', self)
150 copy_action.triggered.connect(self.copy)
150 copy_action.triggered.connect(self.copy)
151 self.copyAvailable.connect(copy_action.setEnabled)
151 self.copyAvailable.connect(copy_action.setEnabled)
152 self._context_menu.addAction(copy_action)
152 self._context_menu.addAction(copy_action)
153
153
154 self._paste_action = QtGui.QAction('Paste', self)
154 self._paste_action = QtGui.QAction('Paste', self)
155 self._paste_action.triggered.connect(self.paste)
155 self._paste_action.triggered.connect(self.paste)
156 self._context_menu.addAction(self._paste_action)
156 self._context_menu.addAction(self._paste_action)
157 self._context_menu.addSeparator()
157 self._context_menu.addSeparator()
158
158
159 select_all_action = QtGui.QAction('Select All', self)
159 select_all_action = QtGui.QAction('Select All', self)
160 select_all_action.triggered.connect(self.selectAll)
160 select_all_action.triggered.connect(self.selectAll)
161 self._context_menu.addAction(select_all_action)
161 self._context_menu.addAction(select_all_action)
162
162
163 def event(self, event):
163 def event(self, event):
164 """ Reimplemented to override shortcuts, if necessary.
164 """ Reimplemented to override shortcuts, if necessary.
165 """
165 """
166 # On Mac OS, it is always unnecessary to override shortcuts, hence the
167 # check below. Users should just use the Control key instead of the
168 # Command key.
166 if self.override_shortcuts and \
169 if self.override_shortcuts and \
170 sys.platform != 'darwin' and \
167 event.type() == QtCore.QEvent.ShortcutOverride and \
171 event.type() == QtCore.QEvent.ShortcutOverride and \
168 event.modifiers() & QtCore.Qt.ControlModifier and \
172 self._control_down(event.modifiers()) and \
169 event.key() in self._ctrl_down_remap:
173 event.key() in self._ctrl_down_remap:
170 event.accept()
174 event.accept()
171 return True
175 return True
172 else:
176 else:
173 return QtGui.QPlainTextEdit.event(self, event)
177 return QtGui.QPlainTextEdit.event(self, event)
174
178
175 #---------------------------------------------------------------------------
179 #---------------------------------------------------------------------------
176 # 'QWidget' interface
180 # 'QWidget' interface
177 #---------------------------------------------------------------------------
181 #---------------------------------------------------------------------------
178
182
179 def contextMenuEvent(self, event):
183 def contextMenuEvent(self, event):
180 """ Reimplemented to create a menu without destructive actions like
184 """ Reimplemented to create a menu without destructive actions like
181 'Cut' and 'Delete'.
185 'Cut' and 'Delete'.
182 """
186 """
183 clipboard_empty = QtGui.QApplication.clipboard().text().isEmpty()
187 clipboard_empty = QtGui.QApplication.clipboard().text().isEmpty()
184 self._paste_action.setEnabled(not clipboard_empty)
188 self._paste_action.setEnabled(not clipboard_empty)
185
189
186 self._context_menu.exec_(event.globalPos())
190 self._context_menu.exec_(event.globalPos())
187
191
188 def keyPressEvent(self, event):
192 def keyPressEvent(self, event):
189 """ Reimplemented to create a console-like interface.
193 """ Reimplemented to create a console-like interface.
190 """
194 """
191 intercepted = False
195 intercepted = False
192 cursor = self.textCursor()
196 cursor = self.textCursor()
193 position = cursor.position()
197 position = cursor.position()
194 key = event.key()
198 key = event.key()
195 ctrl_down = event.modifiers() & QtCore.Qt.ControlModifier
199 ctrl_down = self._control_down(event.modifiers())
196 alt_down = event.modifiers() & QtCore.Qt.AltModifier
200 alt_down = event.modifiers() & QtCore.Qt.AltModifier
197 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
201 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
198
202
199 # Even though we have reimplemented 'paste', the C++ level slot is still
203 # Even though we have reimplemented 'paste', the C++ level slot is still
200 # called by Qt. So we intercept the key press here.
204 # called by Qt. So we intercept the key press here.
201 if event.matches(QtGui.QKeySequence.Paste):
205 if event.matches(QtGui.QKeySequence.Paste):
202 self.paste()
206 self.paste()
203 intercepted = True
207 intercepted = True
204
208
205 elif ctrl_down:
209 elif ctrl_down:
206 if key in self._ctrl_down_remap:
210 if key in self._ctrl_down_remap:
207 ctrl_down = False
211 ctrl_down = False
208 key = self._ctrl_down_remap[key]
212 key = self._ctrl_down_remap[key]
209 event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, key,
213 event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, key,
210 QtCore.Qt.NoModifier)
214 QtCore.Qt.NoModifier)
211
215
212 elif key == QtCore.Qt.Key_K:
216 elif key == QtCore.Qt.Key_K:
213 if self._in_buffer(position):
217 if self._in_buffer(position):
214 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
218 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
215 QtGui.QTextCursor.KeepAnchor)
219 QtGui.QTextCursor.KeepAnchor)
216 cursor.removeSelectedText()
220 cursor.removeSelectedText()
217 intercepted = True
221 intercepted = True
218
222
219 elif key == QtCore.Qt.Key_Y:
223 elif key == QtCore.Qt.Key_Y:
220 self.paste()
224 self.paste()
221 intercepted = True
225 intercepted = True
222
226
223 elif alt_down:
227 elif alt_down:
224 if key == QtCore.Qt.Key_B:
228 if key == QtCore.Qt.Key_B:
225 self.setTextCursor(self._get_word_start_cursor(position))
229 self.setTextCursor(self._get_word_start_cursor(position))
226 intercepted = True
230 intercepted = True
227
231
228 elif key == QtCore.Qt.Key_F:
232 elif key == QtCore.Qt.Key_F:
229 self.setTextCursor(self._get_word_end_cursor(position))
233 self.setTextCursor(self._get_word_end_cursor(position))
230 intercepted = True
234 intercepted = True
231
235
232 elif key == QtCore.Qt.Key_Backspace:
236 elif key == QtCore.Qt.Key_Backspace:
233 cursor = self._get_word_start_cursor(position)
237 cursor = self._get_word_start_cursor(position)
234 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
238 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
235 cursor.removeSelectedText()
239 cursor.removeSelectedText()
236 intercepted = True
240 intercepted = True
237
241
238 elif key == QtCore.Qt.Key_D:
242 elif key == QtCore.Qt.Key_D:
239 cursor = self._get_word_end_cursor(position)
243 cursor = self._get_word_end_cursor(position)
240 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
244 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
241 cursor.removeSelectedText()
245 cursor.removeSelectedText()
242 intercepted = True
246 intercepted = True
243
247
244 if self._completion_widget.isVisible():
248 if self._completion_widget.isVisible():
245 self._completion_widget.keyPressEvent(event)
249 self._completion_widget.keyPressEvent(event)
246 intercepted = event.isAccepted()
250 intercepted = event.isAccepted()
247
251
248 else:
252 else:
249 if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
253 if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
250 if self._reading:
254 if self._reading:
251 self._reading = False
255 self._reading = False
252 elif not self._executing:
256 elif not self._executing:
253 self.execute(interactive=True)
257 self.execute(interactive=True)
254 intercepted = True
258 intercepted = True
255
259
256 elif key == QtCore.Qt.Key_Up:
260 elif key == QtCore.Qt.Key_Up:
257 if self._reading or not self._up_pressed():
261 if self._reading or not self._up_pressed():
258 intercepted = True
262 intercepted = True
259 else:
263 else:
260 prompt_line = self._get_prompt_cursor().blockNumber()
264 prompt_line = self._get_prompt_cursor().blockNumber()
261 intercepted = cursor.blockNumber() <= prompt_line
265 intercepted = cursor.blockNumber() <= prompt_line
262
266
263 elif key == QtCore.Qt.Key_Down:
267 elif key == QtCore.Qt.Key_Down:
264 if self._reading or not self._down_pressed():
268 if self._reading or not self._down_pressed():
265 intercepted = True
269 intercepted = True
266 else:
270 else:
267 end_line = self._get_end_cursor().blockNumber()
271 end_line = self._get_end_cursor().blockNumber()
268 intercepted = cursor.blockNumber() == end_line
272 intercepted = cursor.blockNumber() == end_line
269
273
270 elif key == QtCore.Qt.Key_Tab:
274 elif key == QtCore.Qt.Key_Tab:
271 if self._reading:
275 if self._reading:
272 intercepted = False
276 intercepted = False
273 else:
277 else:
274 intercepted = not self._tab_pressed()
278 intercepted = not self._tab_pressed()
275
279
276 elif key == QtCore.Qt.Key_Left:
280 elif key == QtCore.Qt.Key_Left:
277 intercepted = not self._in_buffer(position - 1)
281 intercepted = not self._in_buffer(position - 1)
278
282
279 elif key == QtCore.Qt.Key_Home:
283 elif key == QtCore.Qt.Key_Home:
280 cursor.movePosition(QtGui.QTextCursor.StartOfLine)
284 cursor.movePosition(QtGui.QTextCursor.StartOfLine)
281 start_pos = cursor.position()
285 start_pos = cursor.position()
282 start_line = cursor.blockNumber()
286 start_line = cursor.blockNumber()
283 if start_line == self._get_prompt_cursor().blockNumber():
287 if start_line == self._get_prompt_cursor().blockNumber():
284 start_pos += len(self._prompt)
288 start_pos += len(self._prompt)
285 else:
289 else:
286 start_pos += len(self._continuation_prompt)
290 start_pos += len(self._continuation_prompt)
287 if shift_down and self._in_buffer(position):
291 if shift_down and self._in_buffer(position):
288 self._set_selection(position, start_pos)
292 self._set_selection(position, start_pos)
289 else:
293 else:
290 self._set_position(start_pos)
294 self._set_position(start_pos)
291 intercepted = True
295 intercepted = True
292
296
293 elif key == QtCore.Qt.Key_Backspace and not alt_down:
297 elif key == QtCore.Qt.Key_Backspace and not alt_down:
294
298
295 # Line deletion (remove continuation prompt)
299 # Line deletion (remove continuation prompt)
296 len_prompt = len(self._continuation_prompt)
300 len_prompt = len(self._continuation_prompt)
297 if cursor.columnNumber() == len_prompt and \
301 if cursor.columnNumber() == len_prompt and \
298 position != self._prompt_pos:
302 position != self._prompt_pos:
299 cursor.setPosition(position - len_prompt,
303 cursor.setPosition(position - len_prompt,
300 QtGui.QTextCursor.KeepAnchor)
304 QtGui.QTextCursor.KeepAnchor)
301 cursor.removeSelectedText()
305 cursor.removeSelectedText()
302
306
303 # Regular backwards deletion
307 # Regular backwards deletion
304 else:
308 else:
305 anchor = cursor.anchor()
309 anchor = cursor.anchor()
306 if anchor == position:
310 if anchor == position:
307 intercepted = not self._in_buffer(position - 1)
311 intercepted = not self._in_buffer(position - 1)
308 else:
312 else:
309 intercepted = not self._in_buffer(min(anchor, position))
313 intercepted = not self._in_buffer(min(anchor, position))
310
314
311 elif key == QtCore.Qt.Key_Delete:
315 elif key == QtCore.Qt.Key_Delete:
312 anchor = cursor.anchor()
316 anchor = cursor.anchor()
313 intercepted = not self._in_buffer(min(anchor, position))
317 intercepted = not self._in_buffer(min(anchor, position))
314
318
315 # Don't move cursor if control is down to allow copy-paste using
319 # Don't move cursor if control is down to allow copy-paste using
316 # the keyboard in any part of the buffer.
320 # the keyboard in any part of the buffer.
317 if not ctrl_down:
321 if not ctrl_down:
318 self._keep_cursor_in_buffer()
322 self._keep_cursor_in_buffer()
319
323
320 if not intercepted:
324 if not intercepted:
321 QtGui.QPlainTextEdit.keyPressEvent(self, event)
325 QtGui.QPlainTextEdit.keyPressEvent(self, event)
322
326
323 #--------------------------------------------------------------------------
327 #--------------------------------------------------------------------------
324 # 'QPlainTextEdit' interface
328 # 'QPlainTextEdit' interface
325 #--------------------------------------------------------------------------
329 #--------------------------------------------------------------------------
326
330
327 def appendPlainText(self, text):
331 def appendPlainText(self, text):
328 """ Reimplemented to not append text as a new paragraph, which doesn't
332 """ Reimplemented to not append text as a new paragraph, which doesn't
329 make sense for a console widget. Also, if enabled, handle ANSI
333 make sense for a console widget. Also, if enabled, handle ANSI
330 codes.
334 codes.
331 """
335 """
332 cursor = self.textCursor()
336 cursor = self.textCursor()
333 cursor.movePosition(QtGui.QTextCursor.End)
337 cursor.movePosition(QtGui.QTextCursor.End)
334
338
335 if self.ansi_codes:
339 if self.ansi_codes:
336 format = QtGui.QTextCharFormat()
340 format = QtGui.QTextCharFormat()
337 previous_end = 0
341 previous_end = 0
338 for match in self._ansi_pattern.finditer(text):
342 for match in self._ansi_pattern.finditer(text):
339 cursor.insertText(text[previous_end:match.start()], format)
343 cursor.insertText(text[previous_end:match.start()], format)
340 previous_end = match.end()
344 previous_end = match.end()
341 for code in match.group(1).split(';'):
345 for code in match.group(1).split(';'):
342 self._ansi_processor.set_code(int(code))
346 self._ansi_processor.set_code(int(code))
343 format = self._ansi_processor.get_format()
347 format = self._ansi_processor.get_format()
344 cursor.insertText(text[previous_end:], format)
348 cursor.insertText(text[previous_end:], format)
345 else:
349 else:
346 cursor.insertText(text)
350 cursor.insertText(text)
347
351
348 def clear(self, keep_input=False):
352 def clear(self, keep_input=False):
349 """ Reimplemented to write a new prompt. If 'keep_input' is set,
353 """ Reimplemented to write a new prompt. If 'keep_input' is set,
350 restores the old input buffer when the new prompt is written.
354 restores the old input buffer when the new prompt is written.
351 """
355 """
352 super(ConsoleWidget, self).clear()
356 super(ConsoleWidget, self).clear()
353
357
354 if keep_input:
358 if keep_input:
355 input_buffer = self.input_buffer
359 input_buffer = self.input_buffer
356 self._show_prompt()
360 self._show_prompt()
357 if keep_input:
361 if keep_input:
358 self.input_buffer = input_buffer
362 self.input_buffer = input_buffer
359
363
360 def paste(self):
364 def paste(self):
361 """ Reimplemented to ensure that text is pasted in the editing region.
365 """ Reimplemented to ensure that text is pasted in the editing region.
362 """
366 """
363 self._keep_cursor_in_buffer()
367 self._keep_cursor_in_buffer()
364 QtGui.QPlainTextEdit.paste(self)
368 QtGui.QPlainTextEdit.paste(self)
365
369
366 def print_(self, printer):
370 def print_(self, printer):
367 """ Reimplemented to work around a bug in PyQt: the C++ level 'print_'
371 """ Reimplemented to work around a bug in PyQt: the C++ level 'print_'
368 slot has the wrong signature.
372 slot has the wrong signature.
369 """
373 """
370 QtGui.QPlainTextEdit.print_(self, printer)
374 QtGui.QPlainTextEdit.print_(self, printer)
371
375
372 #---------------------------------------------------------------------------
376 #---------------------------------------------------------------------------
373 # 'ConsoleWidget' public interface
377 # 'ConsoleWidget' public interface
374 #---------------------------------------------------------------------------
378 #---------------------------------------------------------------------------
375
379
376 def execute(self, interactive=False):
380 def execute(self, interactive=False):
377 """ Execute the text in the input buffer. Returns whether the input
381 """ Execute the text in the input buffer. Returns whether the input
378 buffer was completely processed and a new prompt created.
382 buffer was completely processed and a new prompt created.
379 """
383 """
380 self.appendPlainText('\n')
384 self.appendPlainText('\n')
381 self._executing_input_buffer = self.input_buffer
385 self._executing_input_buffer = self.input_buffer
382 self._executing = True
386 self._executing = True
383 self._prompt_finished()
387 self._prompt_finished()
384 return self._execute(interactive=interactive)
388 return self._execute(interactive=interactive)
385
389
386 def _get_input_buffer(self):
390 def _get_input_buffer(self):
387 """ The text that the user has entered entered at the current prompt.
391 """ The text that the user has entered entered at the current prompt.
388 """
392 """
389 # If we're executing, the input buffer may not even exist anymore due to
393 # 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.
394 # the limit imposed by 'buffer_size'. Therefore, we store it.
391 if self._executing:
395 if self._executing:
392 return self._executing_input_buffer
396 return self._executing_input_buffer
393
397
394 cursor = self._get_end_cursor()
398 cursor = self._get_end_cursor()
395 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
399 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
396
400
397 # Use QTextDocumentFragment intermediate object because it strips
401 # Use QTextDocumentFragment intermediate object because it strips
398 # out the Unicode line break characters that Qt insists on inserting.
402 # out the Unicode line break characters that Qt insists on inserting.
399 input_buffer = str(cursor.selection().toPlainText())
403 input_buffer = str(cursor.selection().toPlainText())
400
404
401 # Strip out continuation prompts.
405 # Strip out continuation prompts.
402 return input_buffer.replace('\n' + self._continuation_prompt, '\n')
406 return input_buffer.replace('\n' + self._continuation_prompt, '\n')
403
407
404 def _set_input_buffer(self, string):
408 def _set_input_buffer(self, string):
405 """ Replaces the text in the input buffer with 'string'.
409 """ Replaces the text in the input buffer with 'string'.
406 """
410 """
407 # Add continuation prompts where necessary.
411 # Add continuation prompts where necessary.
408 lines = string.splitlines()
412 lines = string.splitlines()
409 for i in xrange(1, len(lines)):
413 for i in xrange(1, len(lines)):
410 lines[i] = self._continuation_prompt + lines[i]
414 lines[i] = self._continuation_prompt + lines[i]
411 string = '\n'.join(lines)
415 string = '\n'.join(lines)
412
416
413 # Replace buffer with new text.
417 # Replace buffer with new text.
414 cursor = self._get_end_cursor()
418 cursor = self._get_end_cursor()
415 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
419 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
416 cursor.insertText(string)
420 cursor.insertText(string)
417 self.moveCursor(QtGui.QTextCursor.End)
421 self.moveCursor(QtGui.QTextCursor.End)
418
422
419 input_buffer = property(_get_input_buffer, _set_input_buffer)
423 input_buffer = property(_get_input_buffer, _set_input_buffer)
420
424
421 def _get_input_buffer_cursor_line(self):
425 def _get_input_buffer_cursor_line(self):
422 """ The text in the line of the input buffer in which the user's cursor
426 """ The text in the line of the input buffer in which the user's cursor
423 rests. Returns a string if there is such a line; otherwise, None.
427 rests. Returns a string if there is such a line; otherwise, None.
424 """
428 """
425 if self._executing:
429 if self._executing:
426 return None
430 return None
427 cursor = self.textCursor()
431 cursor = self.textCursor()
428 if cursor.position() >= self._prompt_pos:
432 if cursor.position() >= self._prompt_pos:
429 text = str(cursor.block().text())
433 text = str(cursor.block().text())
430 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
434 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
431 return text[len(self._prompt):]
435 return text[len(self._prompt):]
432 else:
436 else:
433 return text[len(self._continuation_prompt):]
437 return text[len(self._continuation_prompt):]
434 else:
438 else:
435 return None
439 return None
436
440
437 input_buffer_cursor_line = property(_get_input_buffer_cursor_line)
441 input_buffer_cursor_line = property(_get_input_buffer_cursor_line)
438
442
439 def _get_font(self):
443 def _get_font(self):
440 """ The base font being used by the ConsoleWidget.
444 """ The base font being used by the ConsoleWidget.
441 """
445 """
442 return self.document().defaultFont()
446 return self.document().defaultFont()
443
447
444 def _set_font(self, font):
448 def _set_font(self, font):
445 """ Sets the base font for the ConsoleWidget to the specified QFont.
449 """ Sets the base font for the ConsoleWidget to the specified QFont.
446 """
450 """
447 self._completion_widget.setFont(font)
451 self._completion_widget.setFont(font)
448 self.document().setDefaultFont(font)
452 self.document().setDefaultFont(font)
449
453
450 font = property(_get_font, _set_font)
454 font = property(_get_font, _set_font)
451
455
452 def reset_font(self):
456 def reset_font(self):
453 """ 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.
454 """
458 """
455 if sys.platform == 'win32':
459 if sys.platform == 'win32':
456 name = 'Courier'
460 name = 'Courier'
457 elif sys.platform == 'darwin':
461 elif sys.platform == 'darwin':
458 name = 'Monaco'
462 name = 'Monaco'
459 else:
463 else:
460 name = 'Monospace'
464 name = 'Monospace'
461 font = QtGui.QFont(name, QtGui.qApp.font().pointSize())
465 font = QtGui.QFont(name, QtGui.qApp.font().pointSize())
462 font.setStyleHint(QtGui.QFont.TypeWriter)
466 font.setStyleHint(QtGui.QFont.TypeWriter)
463 self._set_font(font)
467 self._set_font(font)
464
468
465 #---------------------------------------------------------------------------
469 #---------------------------------------------------------------------------
466 # 'ConsoleWidget' abstract interface
470 # 'ConsoleWidget' abstract interface
467 #---------------------------------------------------------------------------
471 #---------------------------------------------------------------------------
468
472
469 def _execute(self, interactive):
473 def _execute(self, interactive):
470 """ Called to execute the input buffer. When triggered by an the enter
474 """ Called to execute the input buffer. When triggered by an the enter
471 key press, 'interactive' is True; otherwise, it is False. Returns
475 key press, 'interactive' is True; otherwise, it is False. Returns
472 whether the input buffer was completely processed and a new prompt
476 whether the input buffer was completely processed and a new prompt
473 created.
477 created.
474 """
478 """
475 raise NotImplementedError
479 raise NotImplementedError
476
480
477 def _prompt_started_hook(self):
481 def _prompt_started_hook(self):
478 """ Called immediately after a new prompt is displayed.
482 """ Called immediately after a new prompt is displayed.
479 """
483 """
480 pass
484 pass
481
485
482 def _prompt_finished_hook(self):
486 def _prompt_finished_hook(self):
483 """ Called immediately after a prompt is finished, i.e. when some input
487 """ Called immediately after a prompt is finished, i.e. when some input
484 will be processed and a new prompt displayed.
488 will be processed and a new prompt displayed.
485 """
489 """
486 pass
490 pass
487
491
488 def _up_pressed(self):
492 def _up_pressed(self):
489 """ Called when the up key is pressed. Returns whether to continue
493 """ Called when the up key is pressed. Returns whether to continue
490 processing the event.
494 processing the event.
491 """
495 """
492 return True
496 return True
493
497
494 def _down_pressed(self):
498 def _down_pressed(self):
495 """ Called when the down key is pressed. Returns whether to continue
499 """ Called when the down key is pressed. Returns whether to continue
496 processing the event.
500 processing the event.
497 """
501 """
498 return True
502 return True
499
503
500 def _tab_pressed(self):
504 def _tab_pressed(self):
501 """ Called when the tab key is pressed. Returns whether to continue
505 """ Called when the tab key is pressed. Returns whether to continue
502 processing the event.
506 processing the event.
503 """
507 """
504 return False
508 return False
505
509
506 #--------------------------------------------------------------------------
510 #--------------------------------------------------------------------------
507 # 'ConsoleWidget' protected interface
511 # 'ConsoleWidget' protected interface
508 #--------------------------------------------------------------------------
512 #--------------------------------------------------------------------------
509
513
514 def _control_down(self, modifiers):
515 """ Given a KeyboardModifiers flags object, return whether the Control
516 key is down (on Mac OS, treat the Command key as a synonym for
517 Control).
518 """
519 down = bool(modifiers & QtCore.Qt.ControlModifier)
520
521 # Note: on Mac OS, ControlModifier corresponds to the Command key while
522 # MetaModifier corresponds to the Control key.
523 if sys.platform == 'darwin':
524 down = down ^ bool(modifiers & QtCore.Qt.MetaModifier)
525
526 return down
527
510 def _complete_with_items(self, cursor, items):
528 def _complete_with_items(self, cursor, items):
511 """ Performs completion with 'items' at the specified cursor location.
529 """ Performs completion with 'items' at the specified cursor location.
512 """
530 """
513 if len(items) == 1:
531 if len(items) == 1:
514 cursor.setPosition(self.textCursor().position(),
532 cursor.setPosition(self.textCursor().position(),
515 QtGui.QTextCursor.KeepAnchor)
533 QtGui.QTextCursor.KeepAnchor)
516 cursor.insertText(items[0])
534 cursor.insertText(items[0])
517 elif len(items) > 1:
535 elif len(items) > 1:
518 if self.gui_completion:
536 if self.gui_completion:
519 self._completion_widget.show_items(cursor, items)
537 self._completion_widget.show_items(cursor, items)
520 else:
538 else:
521 text = '\n'.join(items) + '\n'
539 text = '\n'.join(items) + '\n'
522 self._write_text_keeping_prompt(text)
540 self._write_text_keeping_prompt(text)
523
541
524 def _get_end_cursor(self):
542 def _get_end_cursor(self):
525 """ Convenience method that returns a cursor for the last character.
543 """ Convenience method that returns a cursor for the last character.
526 """
544 """
527 cursor = self.textCursor()
545 cursor = self.textCursor()
528 cursor.movePosition(QtGui.QTextCursor.End)
546 cursor.movePosition(QtGui.QTextCursor.End)
529 return cursor
547 return cursor
530
548
531 def _get_prompt_cursor(self):
549 def _get_prompt_cursor(self):
532 """ Convenience method that returns a cursor for the prompt position.
550 """ Convenience method that returns a cursor for the prompt position.
533 """
551 """
534 cursor = self.textCursor()
552 cursor = self.textCursor()
535 cursor.setPosition(self._prompt_pos)
553 cursor.setPosition(self._prompt_pos)
536 return cursor
554 return cursor
537
555
538 def _get_selection_cursor(self, start, end):
556 def _get_selection_cursor(self, start, end):
539 """ Convenience method that returns a cursor with text selected between
557 """ Convenience method that returns a cursor with text selected between
540 the positions 'start' and 'end'.
558 the positions 'start' and 'end'.
541 """
559 """
542 cursor = self.textCursor()
560 cursor = self.textCursor()
543 cursor.setPosition(start)
561 cursor.setPosition(start)
544 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
562 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
545 return cursor
563 return cursor
546
564
547 def _get_word_start_cursor(self, position):
565 def _get_word_start_cursor(self, position):
548 """ Find the start of the word to the left the given position. If a
566 """ Find the start of the word to the left the given position. If a
549 sequence of non-word characters precedes the first word, skip over
567 sequence of non-word characters precedes the first word, skip over
550 them. (This emulates the behavior of bash, emacs, etc.)
568 them. (This emulates the behavior of bash, emacs, etc.)
551 """
569 """
552 document = self.document()
570 document = self.document()
553 position -= 1
571 position -= 1
554 while self._in_buffer(position) and \
572 while self._in_buffer(position) and \
555 not document.characterAt(position).isLetterOrNumber():
573 not document.characterAt(position).isLetterOrNumber():
556 position -= 1
574 position -= 1
557 while self._in_buffer(position) and \
575 while self._in_buffer(position) and \
558 document.characterAt(position).isLetterOrNumber():
576 document.characterAt(position).isLetterOrNumber():
559 position -= 1
577 position -= 1
560 cursor = self.textCursor()
578 cursor = self.textCursor()
561 cursor.setPosition(position + 1)
579 cursor.setPosition(position + 1)
562 return cursor
580 return cursor
563
581
564 def _get_word_end_cursor(self, position):
582 def _get_word_end_cursor(self, position):
565 """ Find the end of the word to the right the given position. If a
583 """ Find the end of the word to the right the given position. If a
566 sequence of non-word characters precedes the first word, skip over
584 sequence of non-word characters precedes the first word, skip over
567 them. (This emulates the behavior of bash, emacs, etc.)
585 them. (This emulates the behavior of bash, emacs, etc.)
568 """
586 """
569 document = self.document()
587 document = self.document()
570 end = self._get_end_cursor().position()
588 end = self._get_end_cursor().position()
571 while position < end and \
589 while position < end and \
572 not document.characterAt(position).isLetterOrNumber():
590 not document.characterAt(position).isLetterOrNumber():
573 position += 1
591 position += 1
574 while position < end and \
592 while position < end and \
575 document.characterAt(position).isLetterOrNumber():
593 document.characterAt(position).isLetterOrNumber():
576 position += 1
594 position += 1
577 cursor = self.textCursor()
595 cursor = self.textCursor()
578 cursor.setPosition(position)
596 cursor.setPosition(position)
579 return cursor
597 return cursor
580
598
581 def _prompt_started(self):
599 def _prompt_started(self):
582 """ Called immediately after a new prompt is displayed.
600 """ Called immediately after a new prompt is displayed.
583 """
601 """
584 # Temporarily disable the maximum block count to permit undo/redo.
602 # Temporarily disable the maximum block count to permit undo/redo.
585 self.setMaximumBlockCount(0)
603 self.setMaximumBlockCount(0)
586 self.setUndoRedoEnabled(True)
604 self.setUndoRedoEnabled(True)
587
605
588 self.setReadOnly(False)
606 self.setReadOnly(False)
589 self.moveCursor(QtGui.QTextCursor.End)
607 self.moveCursor(QtGui.QTextCursor.End)
590 self.centerCursor()
608 self.centerCursor()
591
609
592 self._executing = False
610 self._executing = False
593 self._prompt_started_hook()
611 self._prompt_started_hook()
594
612
595 def _prompt_finished(self):
613 def _prompt_finished(self):
596 """ Called immediately after a prompt is finished, i.e. when some input
614 """ Called immediately after a prompt is finished, i.e. when some input
597 will be processed and a new prompt displayed.
615 will be processed and a new prompt displayed.
598 """
616 """
599 # This has the (desired) side effect of disabling the undo/redo history.
617 # This has the (desired) side effect of disabling the undo/redo history.
600 self.setMaximumBlockCount(self.buffer_size)
618 self.setMaximumBlockCount(self.buffer_size)
601
619
602 self.setReadOnly(True)
620 self.setReadOnly(True)
603 self._prompt_finished_hook()
621 self._prompt_finished_hook()
604
622
605 def _set_position(self, position):
623 def _set_position(self, position):
606 """ Convenience method to set the position of the cursor.
624 """ Convenience method to set the position of the cursor.
607 """
625 """
608 cursor = self.textCursor()
626 cursor = self.textCursor()
609 cursor.setPosition(position)
627 cursor.setPosition(position)
610 self.setTextCursor(cursor)
628 self.setTextCursor(cursor)
611
629
612 def _set_selection(self, start, end):
630 def _set_selection(self, start, end):
613 """ Convenience method to set the current selected text.
631 """ Convenience method to set the current selected text.
614 """
632 """
615 self.setTextCursor(self._get_selection_cursor(start, end))
633 self.setTextCursor(self._get_selection_cursor(start, end))
616
634
617 def _show_prompt(self, prompt=None):
635 def _show_prompt(self, prompt=None):
618 """ Writes a new prompt at the end of the buffer. If 'prompt' is not
636 """ Writes a new prompt at the end of the buffer. If 'prompt' is not
619 specified, uses the previous prompt.
637 specified, uses the previous prompt.
620 """
638 """
621 if prompt is not None:
639 if prompt is not None:
622 self._prompt = prompt
640 self._prompt = prompt
623 self.appendPlainText('\n' + self._prompt)
641 self.appendPlainText('\n' + self._prompt)
624 self._prompt_pos = self._get_end_cursor().position()
642 self._prompt_pos = self._get_end_cursor().position()
625 self._prompt_started()
643 self._prompt_started()
626
644
627 def _show_continuation_prompt(self):
645 def _show_continuation_prompt(self):
628 """ Writes a new continuation prompt at the end of the buffer.
646 """ Writes a new continuation prompt at the end of the buffer.
629 """
647 """
630 self.appendPlainText(self._continuation_prompt)
648 self.appendPlainText(self._continuation_prompt)
631 self._prompt_started()
649 self._prompt_started()
632
650
633 def _write_text_keeping_prompt(self, text):
651 def _write_text_keeping_prompt(self, text):
634 """ Writes 'text' after the current prompt, then restores the old prompt
652 """ Writes 'text' after the current prompt, then restores the old prompt
635 with its old input buffer.
653 with its old input buffer.
636 """
654 """
637 input_buffer = self.input_buffer
655 input_buffer = self.input_buffer
638 self.appendPlainText('\n')
656 self.appendPlainText('\n')
639 self._prompt_finished()
657 self._prompt_finished()
640
658
641 self.appendPlainText(text)
659 self.appendPlainText(text)
642 self._show_prompt()
660 self._show_prompt()
643 self.input_buffer = input_buffer
661 self.input_buffer = input_buffer
644
662
645 def _in_buffer(self, position):
663 def _in_buffer(self, position):
646 """ Returns whether the given position is inside the editing region.
664 """ Returns whether the given position is inside the editing region.
647 """
665 """
648 return position >= self._prompt_pos
666 return position >= self._prompt_pos
649
667
650 def _keep_cursor_in_buffer(self):
668 def _keep_cursor_in_buffer(self):
651 """ Ensures that the cursor is inside the editing region. Returns
669 """ Ensures that the cursor is inside the editing region. Returns
652 whether the cursor was moved.
670 whether the cursor was moved.
653 """
671 """
654 cursor = self.textCursor()
672 cursor = self.textCursor()
655 if cursor.position() < self._prompt_pos:
673 if cursor.position() < self._prompt_pos:
656 cursor.movePosition(QtGui.QTextCursor.End)
674 cursor.movePosition(QtGui.QTextCursor.End)
657 self.setTextCursor(cursor)
675 self.setTextCursor(cursor)
658 return True
676 return True
659 else:
677 else:
660 return False
678 return False
661
679
662
680
663 class HistoryConsoleWidget(ConsoleWidget):
681 class HistoryConsoleWidget(ConsoleWidget):
664 """ A ConsoleWidget that keeps a history of the commands that have been
682 """ A ConsoleWidget that keeps a history of the commands that have been
665 executed.
683 executed.
666 """
684 """
667
685
668 #---------------------------------------------------------------------------
686 #---------------------------------------------------------------------------
669 # 'QObject' interface
687 # 'QObject' interface
670 #---------------------------------------------------------------------------
688 #---------------------------------------------------------------------------
671
689
672 def __init__(self, parent=None):
690 def __init__(self, parent=None):
673 super(HistoryConsoleWidget, self).__init__(parent)
691 super(HistoryConsoleWidget, self).__init__(parent)
674
692
675 self._history = []
693 self._history = []
676 self._history_index = 0
694 self._history_index = 0
677
695
678 #---------------------------------------------------------------------------
696 #---------------------------------------------------------------------------
679 # 'ConsoleWidget' public interface
697 # 'ConsoleWidget' public interface
680 #---------------------------------------------------------------------------
698 #---------------------------------------------------------------------------
681
699
682 def execute(self, interactive=False):
700 def execute(self, interactive=False):
683 """ Reimplemented to the store history.
701 """ Reimplemented to the store history.
684 """
702 """
685 stripped = self.input_buffer.rstrip()
703 stripped = self.input_buffer.rstrip()
686 executed = super(HistoryConsoleWidget, self).execute(interactive)
704 executed = super(HistoryConsoleWidget, self).execute(interactive)
687 if executed:
705 if executed:
688 self._history.append(stripped)
706 self._history.append(stripped)
689 self._history_index = len(self._history)
707 self._history_index = len(self._history)
690 return executed
708 return executed
691
709
692 #---------------------------------------------------------------------------
710 #---------------------------------------------------------------------------
693 # 'ConsoleWidget' abstract interface
711 # 'ConsoleWidget' abstract interface
694 #---------------------------------------------------------------------------
712 #---------------------------------------------------------------------------
695
713
696 def _up_pressed(self):
714 def _up_pressed(self):
697 """ Called when the up key is pressed. Returns whether to continue
715 """ Called when the up key is pressed. Returns whether to continue
698 processing the event.
716 processing the event.
699 """
717 """
700 prompt_cursor = self._get_prompt_cursor()
718 prompt_cursor = self._get_prompt_cursor()
701 if self.textCursor().blockNumber() == prompt_cursor.blockNumber():
719 if self.textCursor().blockNumber() == prompt_cursor.blockNumber():
702 self.history_previous()
720 self.history_previous()
703
721
704 # Go to the first line of prompt for seemless history scrolling.
722 # Go to the first line of prompt for seemless history scrolling.
705 cursor = self._get_prompt_cursor()
723 cursor = self._get_prompt_cursor()
706 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
724 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
707 self.setTextCursor(cursor)
725 self.setTextCursor(cursor)
708
726
709 return False
727 return False
710 return True
728 return True
711
729
712 def _down_pressed(self):
730 def _down_pressed(self):
713 """ Called when the down key is pressed. Returns whether to continue
731 """ Called when the down key is pressed. Returns whether to continue
714 processing the event.
732 processing the event.
715 """
733 """
716 end_cursor = self._get_end_cursor()
734 end_cursor = self._get_end_cursor()
717 if self.textCursor().blockNumber() == end_cursor.blockNumber():
735 if self.textCursor().blockNumber() == end_cursor.blockNumber():
718 self.history_next()
736 self.history_next()
719 return False
737 return False
720 return True
738 return True
721
739
722 #---------------------------------------------------------------------------
740 #---------------------------------------------------------------------------
723 # 'HistoryConsoleWidget' interface
741 # 'HistoryConsoleWidget' interface
724 #---------------------------------------------------------------------------
742 #---------------------------------------------------------------------------
725
743
726 def history_previous(self):
744 def history_previous(self):
727 """ If possible, set the input buffer to the previous item in the
745 """ If possible, set the input buffer to the previous item in the
728 history.
746 history.
729 """
747 """
730 if self._history_index > 0:
748 if self._history_index > 0:
731 self._history_index -= 1
749 self._history_index -= 1
732 self.input_buffer = self._history[self._history_index]
750 self.input_buffer = self._history[self._history_index]
733
751
734 def history_next(self):
752 def history_next(self):
735 """ Set the input buffer to the next item in the history, or a blank
753 """ Set the input buffer to the next item in the history, or a blank
736 line if there is no subsequent item.
754 line if there is no subsequent item.
737 """
755 """
738 if self._history_index < len(self._history):
756 if self._history_index < len(self._history):
739 self._history_index += 1
757 self._history_index += 1
740 if self._history_index < len(self._history):
758 if self._history_index < len(self._history):
741 self.input_buffer = self._history[self._history_index]
759 self.input_buffer = self._history[self._history_index]
742 else:
760 else:
743 self.input_buffer = ''
761 self.input_buffer = ''
General Comments 0
You need to be logged in to leave comments. Login now