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