##// END OF EJS Templates
* Added undo/redo support to ConsoleWidget...
epatters -
Show More
@@ -1,650 +1,668 b''
1 1 # Standard library imports
2 2 import re
3 3
4 4 # System library imports
5 5 from PyQt4 import QtCore, QtGui
6 6
7 7 # Local imports
8 8 from completion_widget import CompletionWidget
9 9
10 10
11 11 class AnsiCodeProcessor(object):
12 12 """ Translates ANSI escape codes into readable attributes.
13 13 """
14 14
15 15 def __init__(self):
16 16 self.ansi_colors = ( # Normal, Bright/Light
17 17 ('#000000', '#7f7f7f'), # 0: black
18 18 ('#cd0000', '#ff0000'), # 1: red
19 19 ('#00cd00', '#00ff00'), # 2: green
20 20 ('#cdcd00', '#ffff00'), # 3: yellow
21 21 ('#0000ee', '#0000ff'), # 4: blue
22 22 ('#cd00cd', '#ff00ff'), # 5: magenta
23 23 ('#00cdcd', '#00ffff'), # 6: cyan
24 24 ('#e5e5e5', '#ffffff')) # 7: white
25 25 self.reset()
26 26
27 27 def set_code(self, code):
28 28 """ Set attributes based on code.
29 29 """
30 30 if code == 0:
31 31 self.reset()
32 32 elif code == 1:
33 33 self.intensity = 1
34 34 self.bold = True
35 35 elif code == 3:
36 36 self.italic = True
37 37 elif code == 4:
38 38 self.underline = True
39 39 elif code == 22:
40 40 self.intensity = 0
41 41 self.bold = False
42 42 elif code == 23:
43 43 self.italic = False
44 44 elif code == 24:
45 45 self.underline = False
46 46 elif code >= 30 and code <= 37:
47 47 self.foreground_color = code - 30
48 48 elif code == 39:
49 49 self.foreground_color = None
50 50 elif code >= 40 and code <= 47:
51 51 self.background_color = code - 40
52 52 elif code == 49:
53 53 self.background_color = None
54 54
55 55 def reset(self):
56 56 """ Reset attributs to their default values.
57 57 """
58 58 self.intensity = 0
59 59 self.italic = False
60 60 self.bold = False
61 61 self.underline = False
62 62 self.foreground_color = None
63 63 self.background_color = None
64 64
65 65
66 66 class QtAnsiCodeProcessor(AnsiCodeProcessor):
67 67 """ Translates ANSI escape codes into QTextCharFormats.
68 68 """
69 69
70 70 def get_format(self):
71 71 """ Returns a QTextCharFormat that encodes the current style attributes.
72 72 """
73 73 format = QtGui.QTextCharFormat()
74 74
75 75 # Set foreground color
76 76 if self.foreground_color is not None:
77 77 color = self.ansi_colors[self.foreground_color][self.intensity]
78 78 format.setForeground(QtGui.QColor(color))
79 79
80 80 # Set background color
81 81 if self.background_color is not None:
82 82 color = self.ansi_colors[self.background_color][self.intensity]
83 83 format.setBackground(QtGui.QColor(color))
84 84
85 85 # Set font weight/style options
86 86 if self.bold:
87 87 format.setFontWeight(QtGui.QFont.Bold)
88 88 else:
89 89 format.setFontWeight(QtGui.QFont.Normal)
90 90 format.setFontItalic(self.italic)
91 91 format.setFontUnderline(self.underline)
92 92
93 93 return format
94 94
95 95
96 96 class ConsoleWidget(QtGui.QPlainTextEdit):
97 97 """ Base class for console-type widgets. This class is mainly concerned with
98 98 dealing with the prompt, keeping the cursor inside the editing line, and
99 99 handling ANSI escape sequences.
100 100 """
101 101
102 102 # Regex to match ANSI escape sequences
103 103 _ansi_pattern = re.compile('\x01?\x1b\[(.*?)m\x02?')
104 104
105 105 # When ctrl is pressed, map certain keys to other keys (without the ctrl):
106 106 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
107 107 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
108 108 QtCore.Qt.Key_A : QtCore.Qt.Key_Home,
109 109 QtCore.Qt.Key_E : QtCore.Qt.Key_End,
110 110 QtCore.Qt.Key_P : QtCore.Qt.Key_Up,
111 111 QtCore.Qt.Key_N : QtCore.Qt.Key_Down,
112 112 QtCore.Qt.Key_D : QtCore.Qt.Key_Delete, }
113 113
114 114 #---------------------------------------------------------------------------
115 115 # 'QWidget' interface
116 116 #---------------------------------------------------------------------------
117 117
118 118 def __init__(self, parent=None):
119 119 QtGui.QPlainTextEdit.__init__(self, parent)
120 120
121 121 # Initialize public and protected variables
122 122 self.ansi_codes = True
123 self.buffer_size = 500
123 124 self.continuation_prompt = '> '
124 125 self.gui_completion = True
125 126 self._ansi_processor = QtAnsiCodeProcessor()
126 127 self._completion_widget = CompletionWidget(self)
127 128 self._executing = False
128 129 self._prompt = ''
129 130 self._prompt_pos = 0
130 131 self._reading = False
131
132 # Configure some basic QPlainTextEdit settings
133 self.setLineWrapMode(QtGui.QPlainTextEdit.WidgetWidth)
134 self.setMaximumBlockCount(500) # Limit text buffer size
135 self.setUndoRedoEnabled(False)
136 132
137 133 # Set a monospaced font
138 134 point_size = QtGui.QApplication.font().pointSize()
139 135 font = QtGui.QFont('Monospace', point_size)
140 136 font.setStyleHint(QtGui.QFont.TypeWriter)
141 137 self._completion_widget.setFont(font)
142 138 self.document().setDefaultFont(font)
143 139
144 140 # Define a custom context menu
145 141 self._context_menu = QtGui.QMenu(self)
146 142
147 143 copy_action = QtGui.QAction('Copy', self)
148 144 copy_action.triggered.connect(self.copy)
149 145 self.copyAvailable.connect(copy_action.setEnabled)
150 146 self._context_menu.addAction(copy_action)
151 147
152 148 self._paste_action = QtGui.QAction('Paste', self)
153 149 self._paste_action.triggered.connect(self.paste)
154 150 self._context_menu.addAction(self._paste_action)
155 151 self._context_menu.addSeparator()
156 152
157 153 select_all_action = QtGui.QAction('Select All', self)
158 154 select_all_action.triggered.connect(self.selectAll)
159 155 self._context_menu.addAction(select_all_action)
160 156
161 157 def contextMenuEvent(self, event):
162 158 """ Reimplemented to create a menu without destructive actions like
163 159 'Cut' and 'Delete'.
164 160 """
165 161 clipboard_empty = QtGui.QApplication.clipboard().text().isEmpty()
166 162 self._paste_action.setEnabled(not clipboard_empty)
167 163
168 164 self._context_menu.exec_(event.globalPos())
169 165
170 166 def keyPressEvent(self, event):
171 167 """ Reimplemented to create a console-like interface.
172 168 """
173 169 intercepted = False
174 170 cursor = self.textCursor()
175 171 position = cursor.position()
176 172 key = event.key()
177 173 ctrl_down = event.modifiers() & QtCore.Qt.ControlModifier
178 174 alt_down = event.modifiers() & QtCore.Qt.AltModifier
179 175 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
180 176
181 if ctrl_down:
177 # Even though we have reimplemented 'paste', the C++ level slot is still
178 # called by Qt. So we intercept the key press here.
179 if event.matches(QtGui.QKeySequence.Paste):
180 self.paste()
181 intercepted = True
182
183 elif ctrl_down:
182 184 if key in self._ctrl_down_remap:
183 185 ctrl_down = False
184 186 key = self._ctrl_down_remap[key]
185 187 event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, key,
186 188 QtCore.Qt.NoModifier)
187 189
188 190 elif key == QtCore.Qt.Key_K:
189 191 if self._in_buffer(position):
190 192 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
191 193 QtGui.QTextCursor.KeepAnchor)
192 194 cursor.removeSelectedText()
193 195 intercepted = True
194 196
195 197 elif key == QtCore.Qt.Key_Y:
196 198 self.paste()
197 199 intercepted = True
198 200
199 201 elif alt_down:
200 202 if key == QtCore.Qt.Key_B:
201 203 self.setTextCursor(self._get_word_start_cursor(position))
202 204 intercepted = True
203 205
204 206 elif key == QtCore.Qt.Key_F:
205 207 self.setTextCursor(self._get_word_end_cursor(position))
206 208 intercepted = True
207 209
208 210 elif key == QtCore.Qt.Key_Backspace:
209 211 cursor = self._get_word_start_cursor(position)
210 212 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
211 213 cursor.removeSelectedText()
212 214 intercepted = True
213 215
214 216 elif key == QtCore.Qt.Key_D:
215 217 cursor = self._get_word_end_cursor(position)
216 218 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
217 219 cursor.removeSelectedText()
218 220 intercepted = True
219 221
220 222 if self._completion_widget.isVisible():
221 223 self._completion_widget.keyPressEvent(event)
222 224 intercepted = event.isAccepted()
223 225
224 226 else:
225 227 if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
226 228 if self._reading:
227 229 self._reading = False
228 230 elif not self._executing:
229 self._executing = True
230 231 self.execute(interactive=True)
231 232 intercepted = True
232 233
233 234 elif key == QtCore.Qt.Key_Up:
234 235 if self._reading or not self._up_pressed():
235 236 intercepted = True
236 237 else:
237 238 prompt_line = self._get_prompt_cursor().blockNumber()
238 239 intercepted = cursor.blockNumber() <= prompt_line
239 240
240 241 elif key == QtCore.Qt.Key_Down:
241 242 if self._reading or not self._down_pressed():
242 243 intercepted = True
243 244 else:
244 245 end_line = self._get_end_cursor().blockNumber()
245 246 intercepted = cursor.blockNumber() == end_line
246 247
247 248 elif key == QtCore.Qt.Key_Tab:
248 249 if self._reading:
249 250 intercepted = False
250 251 else:
251 252 intercepted = not self._tab_pressed()
252 253
253 254 elif key == QtCore.Qt.Key_Left:
254 255 intercepted = not self._in_buffer(position - 1)
255 256
256 257 elif key == QtCore.Qt.Key_Home:
257 258 cursor.movePosition(QtGui.QTextCursor.StartOfLine)
258 259 start_pos = cursor.position()
259 260 start_line = cursor.blockNumber()
260 261 if start_line == self._get_prompt_cursor().blockNumber():
261 262 start_pos += len(self._prompt)
262 263 else:
263 264 start_pos += len(self.continuation_prompt)
264 265 if shift_down and self._in_buffer(position):
265 266 self._set_selection(position, start_pos)
266 267 else:
267 268 self._set_position(start_pos)
268 269 intercepted = True
269 270
270 271 elif key == QtCore.Qt.Key_Backspace and not alt_down:
271 272
272 273 # Line deletion (remove continuation prompt)
273 274 len_prompt = len(self.continuation_prompt)
274 275 if cursor.columnNumber() == len_prompt and \
275 276 position != self._prompt_pos:
276 277 cursor.setPosition(position - len_prompt,
277 278 QtGui.QTextCursor.KeepAnchor)
278 279 cursor.removeSelectedText()
279 280
280 281 # Regular backwards deletion
281 282 else:
282 283 anchor = cursor.anchor()
283 284 if anchor == position:
284 285 intercepted = not self._in_buffer(position - 1)
285 286 else:
286 287 intercepted = not self._in_buffer(min(anchor, position))
287 288
288 289 elif key == QtCore.Qt.Key_Delete:
289 290 anchor = cursor.anchor()
290 291 intercepted = not self._in_buffer(min(anchor, position))
291 292
292 293 # Don't move cursor if control is down to allow copy-paste using
293 # the keyboard in any part of the buffer
294 # the keyboard in any part of the buffer.
294 295 if not ctrl_down:
295 296 self._keep_cursor_in_buffer()
296 297
297 298 if not intercepted:
298 299 QtGui.QPlainTextEdit.keyPressEvent(self, event)
299 300
300 301 #--------------------------------------------------------------------------
301 302 # 'QPlainTextEdit' interface
302 303 #--------------------------------------------------------------------------
303 304
304 305 def appendPlainText(self, text):
305 306 """ Reimplemented to not append text as a new paragraph, which doesn't
306 307 make sense for a console widget. Also, if enabled, handle ANSI
307 308 codes.
308 309 """
309 310 cursor = self.textCursor()
310 311 cursor.movePosition(QtGui.QTextCursor.End)
311 312
312 313 if self.ansi_codes:
313 314 format = QtGui.QTextCharFormat()
314 315 previous_end = 0
315 316 for match in self._ansi_pattern.finditer(text):
316 317 cursor.insertText(text[previous_end:match.start()], format)
317 318 previous_end = match.end()
318 319 for code in match.group(1).split(';'):
319 320 self._ansi_processor.set_code(int(code))
320 321 format = self._ansi_processor.get_format()
321 322 cursor.insertText(text[previous_end:], format)
322 323 else:
323 324 cursor.insertText(text)
324 325
325 326 def paste(self):
326 327 """ Reimplemented to ensure that text is pasted in the editing region.
327 328 """
328 329 self._keep_cursor_in_buffer()
329 330 QtGui.QPlainTextEdit.paste(self)
330 331
331 332 #---------------------------------------------------------------------------
332 333 # 'ConsoleWidget' public interface
333 334 #---------------------------------------------------------------------------
334 335
335 336 def execute(self, interactive=False):
336 337 """ Execute the text in the input buffer. Returns whether the input
337 338 buffer was completely processed and a new prompt created.
338 339 """
339 340 self.appendPlainText('\n')
341 self._executing_input_buffer = self.input_buffer
342 self._executing = True
340 343 self._prompt_finished()
341 344 return self._execute(interactive=interactive)
342 345
343 346 def _get_input_buffer(self):
347 # If we're executing, the input buffer may not even exist anymore due
348 # the limit imposed by 'buffer_size'. Therefore, we store it.
349 if self._executing:
350 return self._executing_input_buffer
351
344 352 cursor = self._get_end_cursor()
345 353 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
346 354
347 355 # Use QTextDocumentFragment intermediate object because it strips
348 356 # out the Unicode line break characters that Qt insists on inserting.
349 357 input_buffer = str(cursor.selection().toPlainText())
350 358
351 359 # Strip out continuation prompts
352 360 return input_buffer.replace('\n' + self.continuation_prompt, '\n')
353 361
354 362 def _set_input_buffer(self, string):
355 363 # Add continuation prompts where necessary
356 364 lines = string.splitlines()
357 365 for i in xrange(1, len(lines)):
358 366 lines[i] = self.continuation_prompt + lines[i]
359 367 string = '\n'.join(lines)
360 368
361 369 # Replace buffer with new text
362 370 cursor = self._get_end_cursor()
363 371 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
364 372 cursor.insertText(string)
365 373 self.moveCursor(QtGui.QTextCursor.End)
366 374
367 375 input_buffer = property(_get_input_buffer, _set_input_buffer)
368 376
369 377 def _get_input_buffer_cursor_line(self):
378 if self._executing:
379 return None
370 380 cursor = self.textCursor()
371 381 if cursor.position() >= self._prompt_pos:
372 382 text = str(cursor.block().text())
373 383 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
374 384 return text[len(self._prompt):]
375 385 else:
376 386 return text[len(self.continuation_prompt):]
377 387 else:
378 388 return None
379 389
380 390 input_buffer_cursor_line = property(_get_input_buffer_cursor_line)
381 391
382 392 #---------------------------------------------------------------------------
383 393 # 'ConsoleWidget' abstract interface
384 394 #---------------------------------------------------------------------------
385 395
386 396 def _execute(self, interactive):
387 397 """ Called to execute the input buffer. When triggered by an the enter
388 398 key press, 'interactive' is True; otherwise, it is False. Returns
389 399 whether the input buffer was completely processed and a new prompt
390 400 created.
391 401 """
392 402 raise NotImplementedError
393 403
394 404 def _prompt_started_hook(self):
395 405 """ Called immediately after a new prompt is displayed.
396 406 """
397 407 pass
398 408
399 409 def _prompt_finished_hook(self):
400 410 """ Called immediately after a prompt is finished, i.e. when some input
401 411 will be processed and a new prompt displayed.
402 412 """
403 413 pass
404 414
405 415 def _up_pressed(self):
406 416 """ Called when the up key is pressed. Returns whether to continue
407 417 processing the event.
408 418 """
409 419 return True
410 420
411 421 def _down_pressed(self):
412 422 """ Called when the down key is pressed. Returns whether to continue
413 423 processing the event.
414 424 """
415 425 return True
416 426
417 427 def _tab_pressed(self):
418 428 """ Called when the tab key is pressed. Returns whether to continue
419 429 processing the event.
420 430 """
421 431 return False
422 432
423 433 #--------------------------------------------------------------------------
424 434 # 'ConsoleWidget' protected interface
425 435 #--------------------------------------------------------------------------
426 436
427 437 def _complete_with_items(self, cursor, items):
428 438 """ Performs completion with 'items' at the specified cursor location.
429 439 """
430 440 if len(items) == 1:
431 441 cursor.setPosition(self.textCursor().position(),
432 442 QtGui.QTextCursor.KeepAnchor)
433 443 cursor.insertText(items[0])
434 444 elif len(items) > 1:
435 445 if self.gui_completion:
436 446 self._completion_widget.show_items(cursor, items)
437 447 else:
438 448 text = '\n'.join(items) + '\n'
439 449 self._write_text_keeping_prompt(text)
440 450
441 451 def _get_end_cursor(self):
442 452 """ Convenience method that returns a cursor for the last character.
443 453 """
444 454 cursor = self.textCursor()
445 455 cursor.movePosition(QtGui.QTextCursor.End)
446 456 return cursor
447 457
448 458 def _get_prompt_cursor(self):
449 459 """ Convenience method that returns a cursor for the prompt position.
450 460 """
451 461 cursor = self.textCursor()
452 462 cursor.setPosition(self._prompt_pos)
453 463 return cursor
454 464
455 465 def _get_selection_cursor(self, start, end):
456 466 """ Convenience method that returns a cursor with text selected between
457 467 the positions 'start' and 'end'.
458 468 """
459 469 cursor = self.textCursor()
460 470 cursor.setPosition(start)
461 471 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
462 472 return cursor
463 473
464 474 def _get_word_start_cursor(self, position):
465 475 """ Find the start of the word to the left the given position. If a
466 476 sequence of non-word characters precedes the first word, skip over
467 477 them. (This emulates the behavior of bash, emacs, etc.)
468 478 """
469 479 document = self.document()
470 480 position -= 1
471 481 while self._in_buffer(position) and \
472 482 not document.characterAt(position).isLetterOrNumber():
473 483 position -= 1
474 484 while self._in_buffer(position) and \
475 485 document.characterAt(position).isLetterOrNumber():
476 486 position -= 1
477 487 cursor = self.textCursor()
478 488 cursor.setPosition(position + 1)
479 489 return cursor
480 490
481 491 def _get_word_end_cursor(self, position):
482 492 """ Find the end of the word to the right the given position. If a
483 493 sequence of non-word characters precedes the first word, skip over
484 494 them. (This emulates the behavior of bash, emacs, etc.)
485 495 """
486 496 document = self.document()
487 497 end = self._get_end_cursor().position()
488 498 while position < end and \
489 499 not document.characterAt(position).isLetterOrNumber():
490 500 position += 1
491 501 while position < end and \
492 502 document.characterAt(position).isLetterOrNumber():
493 503 position += 1
494 504 cursor = self.textCursor()
495 505 cursor.setPosition(position)
496 506 return cursor
497 507
498 508 def _prompt_started(self):
499 509 """ Called immediately after a new prompt is displayed.
500 510 """
511 # Temporarily disable the maximum block count to permit undo/redo.
512 self.setMaximumBlockCount(0)
513 self.setUndoRedoEnabled(True)
514
515 self.setReadOnly(False)
501 516 self.moveCursor(QtGui.QTextCursor.End)
502 517 self.centerCursor()
503 self.setReadOnly(False)
518
504 519 self._executing = False
505 520 self._prompt_started_hook()
506 521
507 522 def _prompt_finished(self):
508 523 """ Called immediately after a prompt is finished, i.e. when some input
509 524 will be processed and a new prompt displayed.
510 525 """
526 # This has the (desired) side effect of disabling the undo/redo history.
527 self.setMaximumBlockCount(self.buffer_size)
528
511 529 self.setReadOnly(True)
512 530 self._prompt_finished_hook()
513 531
514 532 def _set_position(self, position):
515 533 """ Convenience method to set the position of the cursor.
516 534 """
517 535 cursor = self.textCursor()
518 536 cursor.setPosition(position)
519 537 self.setTextCursor(cursor)
520 538
521 539 def _set_selection(self, start, end):
522 540 """ Convenience method to set the current selected text.
523 541 """
524 542 self.setTextCursor(self._get_selection_cursor(start, end))
525 543
526 544 def _show_prompt(self, prompt):
527 545 """ Writes a new prompt at the end of the buffer.
528 546 """
529 547 self.appendPlainText('\n' + prompt)
530 548 self._prompt = prompt
531 549 self._prompt_pos = self._get_end_cursor().position()
532 550 self._prompt_started()
533 551
534 552 def _show_continuation_prompt(self):
535 553 """ Writes a new continuation prompt at the end of the buffer.
536 554 """
537 555 self.appendPlainText(self.continuation_prompt)
538 556 self._prompt_started()
539 557
540 558 def _write_text_keeping_prompt(self, text):
541 559 """ Writes 'text' after the current prompt, then restores the old prompt
542 560 with its old input buffer.
543 561 """
544 562 input_buffer = self.input_buffer
545 563 self.appendPlainText('\n')
546 564 self._prompt_finished()
547 565
548 566 self.appendPlainText(text)
549 567 self._show_prompt(self._prompt)
550 568 self.input_buffer = input_buffer
551 569
552 570 def _in_buffer(self, position):
553 571 """ Returns whether the given position is inside the editing region.
554 572 """
555 573 return position >= self._prompt_pos
556 574
557 575 def _keep_cursor_in_buffer(self):
558 576 """ Ensures that the cursor is inside the editing region. Returns
559 577 whether the cursor was moved.
560 578 """
561 579 cursor = self.textCursor()
562 580 if cursor.position() < self._prompt_pos:
563 581 cursor.movePosition(QtGui.QTextCursor.End)
564 582 self.setTextCursor(cursor)
565 583 return True
566 584 else:
567 585 return False
568 586
569 587
570 588 class HistoryConsoleWidget(ConsoleWidget):
571 589 """ A ConsoleWidget that keeps a history of the commands that have been
572 590 executed.
573 591 """
574 592
575 593 #---------------------------------------------------------------------------
576 594 # 'QWidget' interface
577 595 #---------------------------------------------------------------------------
578 596
579 597 def __init__(self, parent=None):
580 598 super(HistoryConsoleWidget, self).__init__(parent)
581 599
582 600 self._history = []
583 601 self._history_index = 0
584 602
585 603 #---------------------------------------------------------------------------
586 604 # 'ConsoleWidget' public interface
587 605 #---------------------------------------------------------------------------
588 606
589 607 def execute(self, interactive=False):
590 608 """ Reimplemented to the store history.
591 609 """
592 610 stripped = self.input_buffer.rstrip()
593 611 executed = super(HistoryConsoleWidget, self).execute(interactive)
594 612 if executed:
595 613 self._history.append(stripped)
596 614 self._history_index = len(self._history)
597 615 return executed
598 616
599 617 #---------------------------------------------------------------------------
600 618 # 'ConsoleWidget' abstract interface
601 619 #---------------------------------------------------------------------------
602 620
603 621 def _up_pressed(self):
604 622 """ Called when the up key is pressed. Returns whether to continue
605 623 processing the event.
606 624 """
607 625 prompt_cursor = self._get_prompt_cursor()
608 626 if self.textCursor().blockNumber() == prompt_cursor.blockNumber():
609 627 self.history_previous()
610 628
611 629 # Go to the first line of prompt for seemless history scrolling.
612 630 cursor = self._get_prompt_cursor()
613 631 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
614 632 self.setTextCursor(cursor)
615 633
616 634 return False
617 635 return True
618 636
619 637 def _down_pressed(self):
620 638 """ Called when the down key is pressed. Returns whether to continue
621 639 processing the event.
622 640 """
623 641 end_cursor = self._get_end_cursor()
624 642 if self.textCursor().blockNumber() == end_cursor.blockNumber():
625 643 self.history_next()
626 644 return False
627 645 return True
628 646
629 647 #---------------------------------------------------------------------------
630 648 # 'HistoryConsoleWidget' interface
631 649 #---------------------------------------------------------------------------
632 650
633 651 def history_previous(self):
634 652 """ If possible, set the input buffer to the previous item in the
635 653 history.
636 654 """
637 655 if self._history_index > 0:
638 656 self._history_index -= 1
639 657 self.input_buffer = self._history[self._history_index]
640 658
641 659 def history_next(self):
642 660 """ Set the input buffer to the next item in the history, or a blank
643 661 line if there is no subsequent item.
644 662 """
645 663 if self._history_index < len(self._history):
646 664 self._history_index += 1
647 665 if self._history_index < len(self._history):
648 666 self.input_buffer = self._history[self._history_index]
649 667 else:
650 668 self.input_buffer = ''
General Comments 0
You need to be logged in to leave comments. Login now