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