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