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