##// END OF EJS Templates
use html <hr> instead of ascii one
Paul Ivanov -
Show More
@@ -1,763 +1,764 b''
1 1 from __future__ import print_function
2 2
3 3 # Standard library imports
4 4 from collections import namedtuple
5 5 import sys
6 6 import time
7 7 import uuid
8 8
9 9 # System library imports
10 10 from pygments.lexers import PythonLexer
11 11 from IPython.external import qt
12 12 from IPython.external.qt import QtCore, QtGui
13 13
14 14 # Local imports
15 15 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
16 16 from IPython.core.oinspect import call_tip
17 17 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
18 18 from IPython.utils.traitlets import Bool, Instance, Unicode
19 19 from bracket_matcher import BracketMatcher
20 20 from call_tip_widget import CallTipWidget
21 21 from completion_lexer import CompletionLexer
22 22 from history_console_widget import HistoryConsoleWidget
23 23 from pygments_highlighter import PygmentsHighlighter
24 24
25 25
26 26 class FrontendHighlighter(PygmentsHighlighter):
27 27 """ A PygmentsHighlighter that understands and ignores prompts.
28 28 """
29 29
30 30 def __init__(self, frontend):
31 31 super(FrontendHighlighter, self).__init__(frontend._control.document())
32 32 self._current_offset = 0
33 33 self._frontend = frontend
34 34 self.highlighting_on = False
35 35
36 36 def highlightBlock(self, string):
37 37 """ Highlight a block of text. Reimplemented to highlight selectively.
38 38 """
39 39 if not self.highlighting_on:
40 40 return
41 41
42 42 # The input to this function is a unicode string that may contain
43 43 # paragraph break characters, non-breaking spaces, etc. Here we acquire
44 44 # the string as plain text so we can compare it.
45 45 current_block = self.currentBlock()
46 46 string = self._frontend._get_block_plain_text(current_block)
47 47
48 48 # Decide whether to check for the regular or continuation prompt.
49 49 if current_block.contains(self._frontend._prompt_pos):
50 50 prompt = self._frontend._prompt
51 51 else:
52 52 prompt = self._frontend._continuation_prompt
53 53
54 54 # Only highlight if we can identify a prompt, but make sure not to
55 55 # highlight the prompt.
56 56 if string.startswith(prompt):
57 57 self._current_offset = len(prompt)
58 58 string = string[len(prompt):]
59 59 super(FrontendHighlighter, self).highlightBlock(string)
60 60
61 61 def rehighlightBlock(self, block):
62 62 """ Reimplemented to temporarily enable highlighting if disabled.
63 63 """
64 64 old = self.highlighting_on
65 65 self.highlighting_on = True
66 66 super(FrontendHighlighter, self).rehighlightBlock(block)
67 67 self.highlighting_on = old
68 68
69 69 def setFormat(self, start, count, format):
70 70 """ Reimplemented to highlight selectively.
71 71 """
72 72 start += self._current_offset
73 73 super(FrontendHighlighter, self).setFormat(start, count, format)
74 74
75 75
76 76 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
77 77 """ A Qt frontend for a generic Python kernel.
78 78 """
79 79
80 80 # The text to show when the kernel is (re)started.
81 81 banner = Unicode()
82 82
83 83 # An option and corresponding signal for overriding the default kernel
84 84 # interrupt behavior.
85 85 custom_interrupt = Bool(False)
86 86 custom_interrupt_requested = QtCore.Signal()
87 87
88 88 # An option and corresponding signals for overriding the default kernel
89 89 # restart behavior.
90 90 custom_restart = Bool(False)
91 91 custom_restart_kernel_died = QtCore.Signal(float)
92 92 custom_restart_requested = QtCore.Signal()
93 93
94 94 # Whether to automatically show calltips on open-parentheses.
95 95 enable_calltips = Bool(True, config=True,
96 96 help="Whether to draw information calltips on open-parentheses.")
97 97
98 98 clear_on_kernel_restart = Bool(True, config=True,
99 99 help="Whether to clear the console when the kernel is restarted")
100 100
101 101 # Emitted when a user visible 'execute_request' has been submitted to the
102 102 # kernel from the FrontendWidget. Contains the code to be executed.
103 103 executing = QtCore.Signal(object)
104 104
105 105 # Emitted when a user-visible 'execute_reply' has been received from the
106 106 # kernel and processed by the FrontendWidget. Contains the response message.
107 107 executed = QtCore.Signal(object)
108 108
109 109 # Emitted when an exit request has been received from the kernel.
110 110 exit_requested = QtCore.Signal(object)
111 111
112 112 # Protected class variables.
113 113 _transform_prompt = staticmethod(transform_classic_prompt)
114 114 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
115 115 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
116 116 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
117 117 _input_splitter_class = InputSplitter
118 118 _local_kernel = False
119 119 _highlighter = Instance(FrontendHighlighter)
120 120
121 121 #---------------------------------------------------------------------------
122 122 # 'object' interface
123 123 #---------------------------------------------------------------------------
124 124
125 125 def __init__(self, *args, **kw):
126 126 super(FrontendWidget, self).__init__(*args, **kw)
127 127 # FIXME: remove this when PySide min version is updated past 1.0.7
128 128 # forcefully disable calltips if PySide is < 1.0.7, because they crash
129 129 if qt.QT_API == qt.QT_API_PYSIDE:
130 130 import PySide
131 131 if PySide.__version_info__ < (1,0,7):
132 132 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
133 133 self.enable_calltips = False
134 134
135 135 # FrontendWidget protected variables.
136 136 self._bracket_matcher = BracketMatcher(self._control)
137 137 self._call_tip_widget = CallTipWidget(self._control)
138 138 self._completion_lexer = CompletionLexer(PythonLexer())
139 139 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
140 140 self._hidden = False
141 141 self._highlighter = FrontendHighlighter(self)
142 142 self._input_splitter = self._input_splitter_class(input_mode='cell')
143 143 self._kernel_manager = None
144 144 self._request_info = {}
145 145 self._request_info['execute'] = {};
146 146 self._callback_dict = {}
147 147
148 148 # Configure the ConsoleWidget.
149 149 self.tab_width = 4
150 150 self._set_continuation_prompt('... ')
151 151
152 152 # Configure the CallTipWidget.
153 153 self._call_tip_widget.setFont(self.font)
154 154 self.font_changed.connect(self._call_tip_widget.setFont)
155 155
156 156 # Configure actions.
157 157 action = self._copy_raw_action
158 158 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
159 159 action.setEnabled(False)
160 160 action.setShortcut(QtGui.QKeySequence(key))
161 161 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
162 162 action.triggered.connect(self.copy_raw)
163 163 self.copy_available.connect(action.setEnabled)
164 164 self.addAction(action)
165 165
166 166 # Connect signal handlers.
167 167 document = self._control.document()
168 168 document.contentsChange.connect(self._document_contents_change)
169 169
170 170 # Set flag for whether we are connected via localhost.
171 171 self._local_kernel = kw.get('local_kernel',
172 172 FrontendWidget._local_kernel)
173 173
174 174 #---------------------------------------------------------------------------
175 175 # 'ConsoleWidget' public interface
176 176 #---------------------------------------------------------------------------
177 177
178 178 def copy(self):
179 179 """ Copy the currently selected text to the clipboard, removing prompts.
180 180 """
181 181 if self._page_control.hasFocus():
182 182 self._page_control.copy()
183 183 elif self._control.hasFocus():
184 184 text = self._control.textCursor().selection().toPlainText()
185 185 if text:
186 186 lines = map(self._transform_prompt, text.splitlines())
187 187 text = '\n'.join(lines)
188 188 QtGui.QApplication.clipboard().setText(text)
189 189 else:
190 190 self.log.debug("frontend widget : unknown copy target")
191 191
192 192 #---------------------------------------------------------------------------
193 193 # 'ConsoleWidget' abstract interface
194 194 #---------------------------------------------------------------------------
195 195
196 196 def _is_complete(self, source, interactive):
197 197 """ Returns whether 'source' can be completely processed and a new
198 198 prompt created. When triggered by an Enter/Return key press,
199 199 'interactive' is True; otherwise, it is False.
200 200 """
201 201 complete = self._input_splitter.push(source)
202 202 if interactive:
203 203 complete = not self._input_splitter.push_accepts_more()
204 204 return complete
205 205
206 206 def _execute(self, source, hidden):
207 207 """ Execute 'source'. If 'hidden', do not show any output.
208 208
209 209 See parent class :meth:`execute` docstring for full details.
210 210 """
211 211 msg_id = self.kernel_manager.shell_channel.execute(source, hidden)
212 212 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
213 213 self._hidden = hidden
214 214 if not hidden:
215 215 self.executing.emit(source)
216 216
217 217 def _prompt_started_hook(self):
218 218 """ Called immediately after a new prompt is displayed.
219 219 """
220 220 if not self._reading:
221 221 self._highlighter.highlighting_on = True
222 222
223 223 def _prompt_finished_hook(self):
224 224 """ Called immediately after a prompt is finished, i.e. when some input
225 225 will be processed and a new prompt displayed.
226 226 """
227 227 # Flush all state from the input splitter so the next round of
228 228 # reading input starts with a clean buffer.
229 229 self._input_splitter.reset()
230 230
231 231 if not self._reading:
232 232 self._highlighter.highlighting_on = False
233 233
234 234 def _tab_pressed(self):
235 235 """ Called when the tab key is pressed. Returns whether to continue
236 236 processing the event.
237 237 """
238 238 # Perform tab completion if:
239 239 # 1) The cursor is in the input buffer.
240 240 # 2) There is a non-whitespace character before the cursor.
241 241 text = self._get_input_buffer_cursor_line()
242 242 if text is None:
243 243 return False
244 244 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
245 245 if complete:
246 246 self._complete()
247 247 return not complete
248 248
249 249 #---------------------------------------------------------------------------
250 250 # 'ConsoleWidget' protected interface
251 251 #---------------------------------------------------------------------------
252 252
253 253 def _context_menu_make(self, pos):
254 254 """ Reimplemented to add an action for raw copy.
255 255 """
256 256 menu = super(FrontendWidget, self)._context_menu_make(pos)
257 257 for before_action in menu.actions():
258 258 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
259 259 QtGui.QKeySequence.ExactMatch:
260 260 menu.insertAction(before_action, self._copy_raw_action)
261 261 break
262 262 return menu
263 263
264 264 def request_interrupt_kernel(self):
265 265 if self._executing:
266 266 self.interrupt_kernel()
267 267
268 268 def request_restart_kernel(self):
269 269 message = 'Are you sure you want to restart the kernel?'
270 270 self.restart_kernel(message, now=False)
271 271
272 272 def _event_filter_console_keypress(self, event):
273 273 """ Reimplemented for execution interruption and smart backspace.
274 274 """
275 275 key = event.key()
276 276 if self._control_key_down(event.modifiers(), include_command=False):
277 277
278 278 if key == QtCore.Qt.Key_C and self._executing:
279 279 self.request_interrupt_kernel()
280 280 return True
281 281
282 282 elif key == QtCore.Qt.Key_Period:
283 283 self.request_restart_kernel()
284 284 return True
285 285
286 286 elif not event.modifiers() & QtCore.Qt.AltModifier:
287 287
288 288 # Smart backspace: remove four characters in one backspace if:
289 289 # 1) everything left of the cursor is whitespace
290 290 # 2) the four characters immediately left of the cursor are spaces
291 291 if key == QtCore.Qt.Key_Backspace:
292 292 col = self._get_input_buffer_cursor_column()
293 293 cursor = self._control.textCursor()
294 294 if col > 3 and not cursor.hasSelection():
295 295 text = self._get_input_buffer_cursor_line()[:col]
296 296 if text.endswith(' ') and not text.strip():
297 297 cursor.movePosition(QtGui.QTextCursor.Left,
298 298 QtGui.QTextCursor.KeepAnchor, 4)
299 299 cursor.removeSelectedText()
300 300 return True
301 301
302 302 return super(FrontendWidget, self)._event_filter_console_keypress(event)
303 303
304 304 def _insert_continuation_prompt(self, cursor):
305 305 """ Reimplemented for auto-indentation.
306 306 """
307 307 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
308 308 cursor.insertText(' ' * self._input_splitter.indent_spaces)
309 309
310 310 #---------------------------------------------------------------------------
311 311 # 'BaseFrontendMixin' abstract interface
312 312 #---------------------------------------------------------------------------
313 313
314 314 def _handle_complete_reply(self, rep):
315 315 """ Handle replies for tab completion.
316 316 """
317 317 self.log.debug("complete: %s", rep.get('content', ''))
318 318 cursor = self._get_cursor()
319 319 info = self._request_info.get('complete')
320 320 if info and info.id == rep['parent_header']['msg_id'] and \
321 321 info.pos == cursor.position():
322 322 text = '.'.join(self._get_context())
323 323 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
324 324 self._complete_with_items(cursor, rep['content']['matches'])
325 325
326 326 def _silent_exec_callback(self, expr, callback):
327 327 """Silently execute `expr` in the kernel and call `callback` with reply
328 328
329 329 the `expr` is evaluated silently in the kernel (without) output in
330 330 the frontend. Call `callback` with the
331 331 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
332 332
333 333 Parameters
334 334 ----------
335 335 expr : string
336 336 valid string to be executed by the kernel.
337 337 callback : function
338 338 function accepting one arguement, as a string. The string will be
339 339 the `repr` of the result of evaluating `expr`
340 340
341 341 The `callback` is called with the 'repr()' of the result of `expr` as
342 342 first argument. To get the object, do 'eval()' onthe passed value.
343 343
344 344 See Also
345 345 --------
346 346 _handle_exec_callback : private method, deal with calling callback with reply
347 347
348 348 """
349 349
350 350 # generate uuid, which would be used as a indication of wether or not
351 351 # the unique request originate from here (can use msg id ?)
352 352 local_uuid = str(uuid.uuid1())
353 353 msg_id = self.kernel_manager.shell_channel.execute('',
354 354 silent=True, user_expressions={ local_uuid:expr })
355 355 self._callback_dict[local_uuid] = callback
356 356 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
357 357
358 358 def _handle_exec_callback(self, msg):
359 359 """Execute `callback` corresonding to `msg` reply, after ``_silent_exec_callback``
360 360
361 361 Parameters
362 362 ----------
363 363 msg : raw message send by the kernel containing an `user_expressions`
364 364 and having a 'silent_exec_callback' kind.
365 365
366 366 Notes
367 367 -----
368 368 This fonction will look for a `callback` associated with the
369 369 corresponding message id. Association has been made by
370 370 `_silent_exec_callback`. `callback` is then called with the `repr()`
371 371 of the value of corresponding `user_expressions` as argument.
372 372 `callback` is then removed from the known list so that any message
373 373 coming again with the same id won't trigger it.
374 374
375 375 """
376 376
377 377 user_exp = msg['content'].get('user_expressions')
378 378 if not user_exp:
379 379 return
380 380 for expression in user_exp:
381 381 if expression in self._callback_dict:
382 382 self._callback_dict.pop(expression)(user_exp[expression])
383 383
384 384 def _handle_execute_reply(self, msg):
385 385 """ Handles replies for code execution.
386 386 """
387 387 self.log.debug("execute: %s", msg.get('content', ''))
388 388 msg_id = msg['parent_header']['msg_id']
389 389 info = self._request_info['execute'].get(msg_id)
390 390 # unset reading flag, because if execute finished, raw_input can't
391 391 # still be pending.
392 392 self._reading = False
393 393 if info and info.kind == 'user' and not self._hidden:
394 394 # Make sure that all output from the SUB channel has been processed
395 395 # before writing a new prompt.
396 396 self.kernel_manager.sub_channel.flush()
397 397
398 398 # Reset the ANSI style information to prevent bad text in stdout
399 399 # from messing up our colors. We're not a true terminal so we're
400 400 # allowed to do this.
401 401 if self.ansi_codes:
402 402 self._ansi_processor.reset_sgr()
403 403
404 404 content = msg['content']
405 405 status = content['status']
406 406 if status == 'ok':
407 407 self._process_execute_ok(msg)
408 408 elif status == 'error':
409 409 self._process_execute_error(msg)
410 410 elif status == 'aborted':
411 411 self._process_execute_abort(msg)
412 412
413 413 self._show_interpreter_prompt_for_reply(msg)
414 414 self.executed.emit(msg)
415 415 self._request_info['execute'].pop(msg_id)
416 416 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
417 417 self._handle_exec_callback(msg)
418 418 self._request_info['execute'].pop(msg_id)
419 419 else:
420 420 super(FrontendWidget, self)._handle_execute_reply(msg)
421 421
422 422 def _handle_input_request(self, msg):
423 423 """ Handle requests for raw_input.
424 424 """
425 425 self.log.debug("input: %s", msg.get('content', ''))
426 426 if self._hidden:
427 427 raise RuntimeError('Request for raw input during hidden execution.')
428 428
429 429 # Make sure that all output from the SUB channel has been processed
430 430 # before entering readline mode.
431 431 self.kernel_manager.sub_channel.flush()
432 432
433 433 def callback(line):
434 434 self.kernel_manager.stdin_channel.input(line)
435 435 if self._reading:
436 436 self.log.debug("Got second input request, assuming first was interrupted.")
437 437 self._reading = False
438 438 self._readline(msg['content']['prompt'], callback=callback)
439 439
440 440 def _handle_kernel_died(self, since_last_heartbeat):
441 441 """ Handle the kernel's death by asking if the user wants to restart.
442 442 """
443 443 self.log.debug("kernel died: %s", since_last_heartbeat)
444 444 if self.custom_restart:
445 445 self.custom_restart_kernel_died.emit(since_last_heartbeat)
446 446 else:
447 447 message = 'The kernel heartbeat has been inactive for %.2f ' \
448 448 'seconds. Do you want to restart the kernel? You may ' \
449 449 'first want to check the network connection.' % \
450 450 since_last_heartbeat
451 451 self.restart_kernel(message, now=True)
452 452
453 453 def _handle_object_info_reply(self, rep):
454 454 """ Handle replies for call tips.
455 455 """
456 456 self.log.debug("oinfo: %s", rep.get('content', ''))
457 457 cursor = self._get_cursor()
458 458 info = self._request_info.get('call_tip')
459 459 if info and info.id == rep['parent_header']['msg_id'] and \
460 460 info.pos == cursor.position():
461 461 # Get the information for a call tip. For now we format the call
462 462 # line as string, later we can pass False to format_call and
463 463 # syntax-highlight it ourselves for nicer formatting in the
464 464 # calltip.
465 465 content = rep['content']
466 466 # if this is from pykernel, 'docstring' will be the only key
467 467 if content.get('ismagic', False):
468 468 # Don't generate a call-tip for magics. Ideally, we should
469 469 # generate a tooltip, but not on ( like we do for actual
470 470 # callables.
471 471 call_info, doc = None, None
472 472 else:
473 473 call_info, doc = call_tip(content, format_call=True)
474 474 if call_info or doc:
475 475 self._call_tip_widget.show_call_info(call_info, doc)
476 476
477 477 def _handle_pyout(self, msg):
478 478 """ Handle display hook output.
479 479 """
480 480 self.log.debug("pyout: %s", msg.get('content', ''))
481 481 if not self._hidden and self._is_from_this_session(msg):
482 482 text = msg['content']['data']
483 483 self._append_plain_text(text + '\n', before_prompt=True)
484 484
485 485 def _handle_stream(self, msg):
486 486 """ Handle stdout, stderr, and stdin.
487 487 """
488 488 self.log.debug("stream: %s", msg.get('content', ''))
489 489 if not self._hidden and self._is_from_this_session(msg):
490 490 # Most consoles treat tabs as being 8 space characters. Convert tabs
491 491 # to spaces so that output looks as expected regardless of this
492 492 # widget's tab width.
493 493 text = msg['content']['data'].expandtabs(8)
494 494
495 495 self._append_plain_text(text, before_prompt=True)
496 496 self._control.moveCursor(QtGui.QTextCursor.End)
497 497
498 498 def _handle_shutdown_reply(self, msg):
499 499 """ Handle shutdown signal, only if from other console.
500 500 """
501 501 self.log.debug("shutdown: %s", msg.get('content', ''))
502 502 if not self._hidden and not self._is_from_this_session(msg):
503 503 if self._local_kernel:
504 504 if not msg['content']['restart']:
505 505 self.exit_requested.emit(self)
506 506 else:
507 507 # we just got notified of a restart!
508 508 time.sleep(0.25) # wait 1/4 sec to reset
509 509 # lest the request for a new prompt
510 510 # goes to the old kernel
511 511 self.reset_on_restart()
512 512 else: # remote kernel, prompt on Kernel shutdown/reset
513 513 title = self.window().windowTitle()
514 514 if not msg['content']['restart']:
515 515 reply = QtGui.QMessageBox.question(self, title,
516 516 "Kernel has been shutdown permanently. "
517 517 "Close the Console?",
518 518 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
519 519 if reply == QtGui.QMessageBox.Yes:
520 520 self.exit_requested.emit(self)
521 521 else:
522 522 # XXX: remove message box in favor of using the
523 523 # clear_on_kernel_restart setting?
524 524 reply = QtGui.QMessageBox.question(self, title,
525 525 "Kernel has been reset. Clear the Console?",
526 526 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
527 527 if reply == QtGui.QMessageBox.Yes:
528 528 time.sleep(0.25) # wait 1/4 sec to reset
529 529 # lest the request for a new prompt
530 530 # goes to the old kernel
531 531 self.reset()
532 532
533 533 def _started_channels(self):
534 534 """ Called when the KernelManager channels have started listening or
535 535 when the frontend is assigned an already listening KernelManager.
536 536 """
537 537 self.reset()
538 538
539 539 #---------------------------------------------------------------------------
540 540 # 'FrontendWidget' public interface
541 541 #---------------------------------------------------------------------------
542 542
543 543 def copy_raw(self):
544 544 """ Copy the currently selected text to the clipboard without attempting
545 545 to remove prompts or otherwise alter the text.
546 546 """
547 547 self._control.copy()
548 548
549 549 def execute_file(self, path, hidden=False):
550 550 """ Attempts to execute file with 'path'. If 'hidden', no output is
551 551 shown.
552 552 """
553 553 self.execute('execfile(%r)' % path, hidden=hidden)
554 554
555 555 def interrupt_kernel(self):
556 556 """ Attempts to interrupt the running kernel.
557 557
558 558 Also unsets _reading flag, to avoid runtime errors
559 559 if raw_input is called again.
560 560 """
561 561 if self.custom_interrupt:
562 562 self._reading = False
563 563 self.custom_interrupt_requested.emit()
564 564 elif self.kernel_manager.has_kernel:
565 565 self._reading = False
566 566 self.kernel_manager.interrupt_kernel()
567 567 else:
568 568 self._append_plain_text('Kernel process is either remote or '
569 569 'unspecified. Cannot interrupt.\n')
570 570
571 571 def reset(self):
572 572 """ Resets the widget to its initial state. Similar to ``clear``, but
573 573 also re-writes the banner and aborts execution if necessary.
574 574 """
575 575 if self._executing:
576 576 self._executing = False
577 577 self._request_info['execute'] = {}
578 578 self._reading = False
579 579 self._highlighter.highlighting_on = False
580 580
581 581 self._control.clear()
582 582 self._append_plain_text(self.banner)
583 583 # update output marker for stdout/stderr, so that startup
584 584 # messages appear after banner:
585 585 self._append_before_prompt_pos = self._get_cursor().position()
586 586 self._show_interpreter_prompt()
587 587
588 588 def reset_on_restart(self):
589 589 """Resets the widget if ``clear_on_kernel_restart`` is True, otherwise
590 590 prints a visual indication of the fact that the kernel restarted, but
591 591 does not clear the traces from previous usage of the kernel before it
592 592 was restarted.
593 593 """
594 594 if self.clear_on_kernel_restart:
595 595 self.reset()
596 596 else:
597 597 self._append_before_prompt_pos = self._get_cursor().position()
598 self._append_plain_text("# restarting kernel...\n#" + "-"*42)
598 self._append_plain_text("# restarting kernel...")
599 self._append_html("<hr><br>")
599 600 # XXX: Reprinting the full banner may be too much, but once #1680 is
600 601 # addressed, that will mitigate it.
601 602 #self._append_plain_text(self.banner)
602 603 self._show_interpreter_prompt()
603 604
604 605 def restart_kernel(self, message, now=False):
605 606 """ Attempts to restart the running kernel.
606 607 """
607 608 # FIXME: now should be configurable via a checkbox in the dialog. Right
608 609 # now at least the heartbeat path sets it to True and the manual restart
609 610 # to False. But those should just be the pre-selected states of a
610 611 # checkbox that the user could override if so desired. But I don't know
611 612 # enough Qt to go implementing the checkbox now.
612 613
613 614 if self.custom_restart:
614 615 self.custom_restart_requested.emit()
615 616
616 617 elif self.kernel_manager.has_kernel:
617 618 # Pause the heart beat channel to prevent further warnings.
618 619 self.kernel_manager.hb_channel.pause()
619 620
620 621 # Prompt the user to restart the kernel. Un-pause the heartbeat if
621 622 # they decline. (If they accept, the heartbeat will be un-paused
622 623 # automatically when the kernel is restarted.)
623 624 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
624 625 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
625 626 message, buttons)
626 627 if result == QtGui.QMessageBox.Yes:
627 628 try:
628 629 self.kernel_manager.restart_kernel(now=now)
629 630 except RuntimeError:
630 631 self._append_plain_text('Kernel started externally. '
631 632 'Cannot restart.\n',
632 633 before_prompt=True
633 634 )
634 635 else:
635 636 self.reset_on_restart()
636 637 else:
637 638 self.kernel_manager.hb_channel.unpause()
638 639
639 640 else:
640 641 self._append_plain_text('Kernel process is either remote or '
641 642 'unspecified. Cannot restart.\n',
642 643 before_prompt=True
643 644 )
644 645
645 646 #---------------------------------------------------------------------------
646 647 # 'FrontendWidget' protected interface
647 648 #---------------------------------------------------------------------------
648 649
649 650 def _call_tip(self):
650 651 """ Shows a call tip, if appropriate, at the current cursor location.
651 652 """
652 653 # Decide if it makes sense to show a call tip
653 654 if not self.enable_calltips:
654 655 return False
655 656 cursor = self._get_cursor()
656 657 cursor.movePosition(QtGui.QTextCursor.Left)
657 658 if cursor.document().characterAt(cursor.position()) != '(':
658 659 return False
659 660 context = self._get_context(cursor)
660 661 if not context:
661 662 return False
662 663
663 664 # Send the metadata request to the kernel
664 665 name = '.'.join(context)
665 666 msg_id = self.kernel_manager.shell_channel.object_info(name)
666 667 pos = self._get_cursor().position()
667 668 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
668 669 return True
669 670
670 671 def _complete(self):
671 672 """ Performs completion at the current cursor location.
672 673 """
673 674 context = self._get_context()
674 675 if context:
675 676 # Send the completion request to the kernel
676 677 msg_id = self.kernel_manager.shell_channel.complete(
677 678 '.'.join(context), # text
678 679 self._get_input_buffer_cursor_line(), # line
679 680 self._get_input_buffer_cursor_column(), # cursor_pos
680 681 self.input_buffer) # block
681 682 pos = self._get_cursor().position()
682 683 info = self._CompletionRequest(msg_id, pos)
683 684 self._request_info['complete'] = info
684 685
685 686 def _get_context(self, cursor=None):
686 687 """ Gets the context for the specified cursor (or the current cursor
687 688 if none is specified).
688 689 """
689 690 if cursor is None:
690 691 cursor = self._get_cursor()
691 692 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
692 693 QtGui.QTextCursor.KeepAnchor)
693 694 text = cursor.selection().toPlainText()
694 695 return self._completion_lexer.get_context(text)
695 696
696 697 def _process_execute_abort(self, msg):
697 698 """ Process a reply for an aborted execution request.
698 699 """
699 700 self._append_plain_text("ERROR: execution aborted\n")
700 701
701 702 def _process_execute_error(self, msg):
702 703 """ Process a reply for an execution request that resulted in an error.
703 704 """
704 705 content = msg['content']
705 706 # If a SystemExit is passed along, this means exit() was called - also
706 707 # all the ipython %exit magic syntax of '-k' to be used to keep
707 708 # the kernel running
708 709 if content['ename']=='SystemExit':
709 710 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
710 711 self._keep_kernel_on_exit = keepkernel
711 712 self.exit_requested.emit(self)
712 713 else:
713 714 traceback = ''.join(content['traceback'])
714 715 self._append_plain_text(traceback)
715 716
716 717 def _process_execute_ok(self, msg):
717 718 """ Process a reply for a successful execution request.
718 719 """
719 720 payload = msg['content']['payload']
720 721 for item in payload:
721 722 if not self._process_execute_payload(item):
722 723 warning = 'Warning: received unknown payload of type %s'
723 724 print(warning % repr(item['source']))
724 725
725 726 def _process_execute_payload(self, item):
726 727 """ Process a single payload item from the list of payload items in an
727 728 execution reply. Returns whether the payload was handled.
728 729 """
729 730 # The basic FrontendWidget doesn't handle payloads, as they are a
730 731 # mechanism for going beyond the standard Python interpreter model.
731 732 return False
732 733
733 734 def _show_interpreter_prompt(self):
734 735 """ Shows a prompt for the interpreter.
735 736 """
736 737 self._show_prompt('>>> ')
737 738
738 739 def _show_interpreter_prompt_for_reply(self, msg):
739 740 """ Shows a prompt for the interpreter given an 'execute_reply' message.
740 741 """
741 742 self._show_interpreter_prompt()
742 743
743 744 #------ Signal handlers ----------------------------------------------------
744 745
745 746 def _document_contents_change(self, position, removed, added):
746 747 """ Called whenever the document's content changes. Display a call tip
747 748 if appropriate.
748 749 """
749 750 # Calculate where the cursor should be *after* the change:
750 751 position += added
751 752
752 753 document = self._control.document()
753 754 if position == self._get_cursor().position():
754 755 self._call_tip()
755 756
756 757 #------ Trait default initializers -----------------------------------------
757 758
758 759 def _banner_default(self):
759 760 """ Returns the standard Python banner.
760 761 """
761 762 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
762 763 '"license" for more information.'
763 764 return banner % (sys.version, sys.platform)
General Comments 0
You need to be logged in to leave comments. Login now