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