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