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