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