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