##// END OF EJS Templates
add kernel banner to terminal and qt frontends
MinRK -
Show More
@@ -1,569 +1,573 b''
1 1 """A ZMQ-based subclass of InteractiveShell.
2 2
3 3 This code is meant to ease the refactoring of the base InteractiveShell into
4 4 something with a cleaner architecture for 2-process use, without actually
5 5 breaking InteractiveShell itself. So we're doing something a bit ugly, where
6 6 we subclass and override what we want to fix. Once this is working well, we
7 7 can go back to the base class and refactor the code for a cleaner inheritance
8 8 implementation that doesn't rely on so much monkeypatching.
9 9
10 10 But this lets us maintain a fully working IPython as we develop the new
11 11 machinery. This should thus be thought of as scaffolding.
12 12 """
13 13
14 14 # Copyright (c) IPython Development Team.
15 15 # Distributed under the terms of the Modified BSD License.
16 16
17 17 from __future__ import print_function
18 18
19 19 import os
20 20 import sys
21 21 import time
22 22
23 23 from zmq.eventloop import ioloop
24 24
25 25 from IPython.core.interactiveshell import (
26 26 InteractiveShell, InteractiveShellABC
27 27 )
28 28 from IPython.core import page
29 29 from IPython.core.autocall import ZMQExitAutocall
30 30 from IPython.core.displaypub import DisplayPublisher
31 31 from IPython.core.error import UsageError
32 32 from IPython.core.magics import MacroToEdit, CodeMagics
33 33 from IPython.core.magic import magics_class, line_magic, Magics
34 34 from IPython.core.payloadpage import install_payload_page
35 from IPython.core.usage import default_gui_banner
35 36 from IPython.display import display, Javascript
36 37 from IPython.kernel.inprocess.socket import SocketABC
37 38 from IPython.kernel import (
38 39 get_connection_file, get_connection_info, connect_qtconsole
39 40 )
40 41 from IPython.testing.skipdoctest import skip_doctest
41 42 from IPython.utils import openpy
42 43 from IPython.utils.jsonutil import json_clean, encode_images
43 44 from IPython.utils.process import arg_split
44 45 from IPython.utils import py3compat
45 46 from IPython.utils.py3compat import unicode_type
46 47 from IPython.utils.traitlets import Instance, Type, Dict, CBool, CBytes, Any
47 48 from IPython.utils.warn import error
48 49 from IPython.kernel.zmq.displayhook import ZMQShellDisplayHook
49 50 from IPython.kernel.zmq.datapub import ZMQDataPublisher
50 51 from IPython.kernel.zmq.session import extract_header
51 52 from IPython.kernel.comm import CommManager
52 53 from .session import Session
53 54
54 55 #-----------------------------------------------------------------------------
55 56 # Functions and classes
56 57 #-----------------------------------------------------------------------------
57 58
58 59 class ZMQDisplayPublisher(DisplayPublisher):
59 60 """A display publisher that publishes data using a ZeroMQ PUB socket."""
60 61
61 62 session = Instance(Session)
62 63 pub_socket = Instance(SocketABC)
63 64 parent_header = Dict({})
64 65 topic = CBytes(b'display_data')
65 66
66 67 def set_parent(self, parent):
67 68 """Set the parent for outbound messages."""
68 69 self.parent_header = extract_header(parent)
69 70
70 71 def _flush_streams(self):
71 72 """flush IO Streams prior to display"""
72 73 sys.stdout.flush()
73 74 sys.stderr.flush()
74 75
75 76 def publish(self, source, data, metadata=None):
76 77 self._flush_streams()
77 78 if metadata is None:
78 79 metadata = {}
79 80 self._validate_data(source, data, metadata)
80 81 content = {}
81 82 content['source'] = source
82 83 content['data'] = encode_images(data)
83 84 content['metadata'] = metadata
84 85 self.session.send(
85 86 self.pub_socket, u'display_data', json_clean(content),
86 87 parent=self.parent_header, ident=self.topic,
87 88 )
88 89
89 90 def clear_output(self, wait=False):
90 91 content = dict(wait=wait)
91 92 self._flush_streams()
92 93 self.session.send(
93 94 self.pub_socket, u'clear_output', content,
94 95 parent=self.parent_header, ident=self.topic,
95 96 )
96 97
97 98 @magics_class
98 99 class KernelMagics(Magics):
99 100 #------------------------------------------------------------------------
100 101 # Magic overrides
101 102 #------------------------------------------------------------------------
102 103 # Once the base class stops inheriting from magic, this code needs to be
103 104 # moved into a separate machinery as well. For now, at least isolate here
104 105 # the magics which this class needs to implement differently from the base
105 106 # class, or that are unique to it.
106 107
107 108 @line_magic
108 109 def doctest_mode(self, parameter_s=''):
109 110 """Toggle doctest mode on and off.
110 111
111 112 This mode is intended to make IPython behave as much as possible like a
112 113 plain Python shell, from the perspective of how its prompts, exceptions
113 114 and output look. This makes it easy to copy and paste parts of a
114 115 session into doctests. It does so by:
115 116
116 117 - Changing the prompts to the classic ``>>>`` ones.
117 118 - Changing the exception reporting mode to 'Plain'.
118 119 - Disabling pretty-printing of output.
119 120
120 121 Note that IPython also supports the pasting of code snippets that have
121 122 leading '>>>' and '...' prompts in them. This means that you can paste
122 123 doctests from files or docstrings (even if they have leading
123 124 whitespace), and the code will execute correctly. You can then use
124 125 '%history -t' to see the translated history; this will give you the
125 126 input after removal of all the leading prompts and whitespace, which
126 127 can be pasted back into an editor.
127 128
128 129 With these features, you can switch into this mode easily whenever you
129 130 need to do testing and changes to doctests, without having to leave
130 131 your existing IPython session.
131 132 """
132 133
133 134 from IPython.utils.ipstruct import Struct
134 135
135 136 # Shorthands
136 137 shell = self.shell
137 138 disp_formatter = self.shell.display_formatter
138 139 ptformatter = disp_formatter.formatters['text/plain']
139 140 # dstore is a data store kept in the instance metadata bag to track any
140 141 # changes we make, so we can undo them later.
141 142 dstore = shell.meta.setdefault('doctest_mode', Struct())
142 143 save_dstore = dstore.setdefault
143 144
144 145 # save a few values we'll need to recover later
145 146 mode = save_dstore('mode', False)
146 147 save_dstore('rc_pprint', ptformatter.pprint)
147 148 save_dstore('rc_active_types',disp_formatter.active_types)
148 149 save_dstore('xmode', shell.InteractiveTB.mode)
149 150
150 151 if mode == False:
151 152 # turn on
152 153 ptformatter.pprint = False
153 154 disp_formatter.active_types = ['text/plain']
154 155 shell.magic('xmode Plain')
155 156 else:
156 157 # turn off
157 158 ptformatter.pprint = dstore.rc_pprint
158 159 disp_formatter.active_types = dstore.rc_active_types
159 160 shell.magic("xmode " + dstore.xmode)
160 161
161 162 # Store new mode and inform on console
162 163 dstore.mode = bool(1-int(mode))
163 164 mode_label = ['OFF','ON'][dstore.mode]
164 165 print('Doctest mode is:', mode_label)
165 166
166 167 # Send the payload back so that clients can modify their prompt display
167 168 payload = dict(
168 169 source='doctest_mode',
169 170 mode=dstore.mode)
170 171 shell.payload_manager.write_payload(payload)
171 172
172 173
173 174 _find_edit_target = CodeMagics._find_edit_target
174 175
175 176 @skip_doctest
176 177 @line_magic
177 178 def edit(self, parameter_s='', last_call=['','']):
178 179 """Bring up an editor and execute the resulting code.
179 180
180 181 Usage:
181 182 %edit [options] [args]
182 183
183 184 %edit runs an external text editor. You will need to set the command for
184 185 this editor via the ``TerminalInteractiveShell.editor`` option in your
185 186 configuration file before it will work.
186 187
187 188 This command allows you to conveniently edit multi-line code right in
188 189 your IPython session.
189 190
190 191 If called without arguments, %edit opens up an empty editor with a
191 192 temporary file and will execute the contents of this file when you
192 193 close it (don't forget to save it!).
193 194
194 195 Options:
195 196
196 197 -n <number>
197 198 Open the editor at a specified line number. By default, the IPython
198 199 editor hook uses the unix syntax 'editor +N filename', but you can
199 200 configure this by providing your own modified hook if your favorite
200 201 editor supports line-number specifications with a different syntax.
201 202
202 203 -p
203 204 Call the editor with the same data as the previous time it was used,
204 205 regardless of how long ago (in your current session) it was.
205 206
206 207 -r
207 208 Use 'raw' input. This option only applies to input taken from the
208 209 user's history. By default, the 'processed' history is used, so that
209 210 magics are loaded in their transformed version to valid Python. If
210 211 this option is given, the raw input as typed as the command line is
211 212 used instead. When you exit the editor, it will be executed by
212 213 IPython's own processor.
213 214
214 215 Arguments:
215 216
216 217 If arguments are given, the following possibilites exist:
217 218
218 219 - The arguments are numbers or pairs of colon-separated numbers (like
219 220 1 4:8 9). These are interpreted as lines of previous input to be
220 221 loaded into the editor. The syntax is the same of the %macro command.
221 222
222 223 - If the argument doesn't start with a number, it is evaluated as a
223 224 variable and its contents loaded into the editor. You can thus edit
224 225 any string which contains python code (including the result of
225 226 previous edits).
226 227
227 228 - If the argument is the name of an object (other than a string),
228 229 IPython will try to locate the file where it was defined and open the
229 230 editor at the point where it is defined. You can use ``%edit function``
230 231 to load an editor exactly at the point where 'function' is defined,
231 232 edit it and have the file be executed automatically.
232 233
233 234 If the object is a macro (see %macro for details), this opens up your
234 235 specified editor with a temporary file containing the macro's data.
235 236 Upon exit, the macro is reloaded with the contents of the file.
236 237
237 238 Note: opening at an exact line is only supported under Unix, and some
238 239 editors (like kedit and gedit up to Gnome 2.8) do not understand the
239 240 '+NUMBER' parameter necessary for this feature. Good editors like
240 241 (X)Emacs, vi, jed, pico and joe all do.
241 242
242 243 - If the argument is not found as a variable, IPython will look for a
243 244 file with that name (adding .py if necessary) and load it into the
244 245 editor. It will execute its contents with execfile() when you exit,
245 246 loading any code in the file into your interactive namespace.
246 247
247 248 Unlike in the terminal, this is designed to use a GUI editor, and we do
248 249 not know when it has closed. So the file you edit will not be
249 250 automatically executed or printed.
250 251
251 252 Note that %edit is also available through the alias %ed.
252 253 """
253 254
254 255 opts,args = self.parse_options(parameter_s,'prn:')
255 256
256 257 try:
257 258 filename, lineno, _ = CodeMagics._find_edit_target(self.shell, args, opts, last_call)
258 259 except MacroToEdit as e:
259 260 # TODO: Implement macro editing over 2 processes.
260 261 print("Macro editing not yet implemented in 2-process model.")
261 262 return
262 263
263 264 # Make sure we send to the client an absolute path, in case the working
264 265 # directory of client and kernel don't match
265 266 filename = os.path.abspath(filename)
266 267
267 268 payload = {
268 269 'source' : 'edit_magic',
269 270 'filename' : filename,
270 271 'line_number' : lineno
271 272 }
272 273 self.shell.payload_manager.write_payload(payload)
273 274
274 275 # A few magics that are adapted to the specifics of using pexpect and a
275 276 # remote terminal
276 277
277 278 @line_magic
278 279 def clear(self, arg_s):
279 280 """Clear the terminal."""
280 281 if os.name == 'posix':
281 282 self.shell.system("clear")
282 283 else:
283 284 self.shell.system("cls")
284 285
285 286 if os.name == 'nt':
286 287 # This is the usual name in windows
287 288 cls = line_magic('cls')(clear)
288 289
289 290 # Terminal pagers won't work over pexpect, but we do have our own pager
290 291
291 292 @line_magic
292 293 def less(self, arg_s):
293 294 """Show a file through the pager.
294 295
295 296 Files ending in .py are syntax-highlighted."""
296 297 if not arg_s:
297 298 raise UsageError('Missing filename.')
298 299
299 300 cont = open(arg_s).read()
300 301 if arg_s.endswith('.py'):
301 302 cont = self.shell.pycolorize(openpy.read_py_file(arg_s, skip_encoding_cookie=False))
302 303 else:
303 304 cont = open(arg_s).read()
304 305 page.page(cont)
305 306
306 307 more = line_magic('more')(less)
307 308
308 309 # Man calls a pager, so we also need to redefine it
309 310 if os.name == 'posix':
310 311 @line_magic
311 312 def man(self, arg_s):
312 313 """Find the man page for the given command and display in pager."""
313 314 page.page(self.shell.getoutput('man %s | col -b' % arg_s,
314 315 split=False))
315 316
316 317 @line_magic
317 318 def connect_info(self, arg_s):
318 319 """Print information for connecting other clients to this kernel
319 320
320 321 It will print the contents of this session's connection file, as well as
321 322 shortcuts for local clients.
322 323
323 324 In the simplest case, when called from the most recently launched kernel,
324 325 secondary clients can be connected, simply with:
325 326
326 327 $> ipython <app> --existing
327 328
328 329 """
329 330
330 331 from IPython.core.application import BaseIPythonApplication as BaseIPApp
331 332
332 333 if BaseIPApp.initialized():
333 334 app = BaseIPApp.instance()
334 335 security_dir = app.profile_dir.security_dir
335 336 profile = app.profile
336 337 else:
337 338 profile = 'default'
338 339 security_dir = ''
339 340
340 341 try:
341 342 connection_file = get_connection_file()
342 343 info = get_connection_info(unpack=False)
343 344 except Exception as e:
344 345 error("Could not get connection info: %r" % e)
345 346 return
346 347
347 348 # add profile flag for non-default profile
348 349 profile_flag = "--profile %s" % profile if profile != 'default' else ""
349 350
350 351 # if it's in the security dir, truncate to basename
351 352 if security_dir == os.path.dirname(connection_file):
352 353 connection_file = os.path.basename(connection_file)
353 354
354 355
355 356 print (info + '\n')
356 357 print ("Paste the above JSON into a file, and connect with:\n"
357 358 " $> ipython <app> --existing <file>\n"
358 359 "or, if you are local, you can connect with just:\n"
359 360 " $> ipython <app> --existing {0} {1}\n"
360 361 "or even just:\n"
361 362 " $> ipython <app> --existing {1}\n"
362 363 "if this is the most recent IPython session you have started.".format(
363 364 connection_file, profile_flag
364 365 )
365 366 )
366 367
367 368 @line_magic
368 369 def qtconsole(self, arg_s):
369 370 """Open a qtconsole connected to this kernel.
370 371
371 372 Useful for connecting a qtconsole to running notebooks, for better
372 373 debugging.
373 374 """
374 375
375 376 # %qtconsole should imply bind_kernel for engines:
376 377 try:
377 378 from IPython.parallel import bind_kernel
378 379 except ImportError:
379 380 # technically possible, because parallel has higher pyzmq min-version
380 381 pass
381 382 else:
382 383 bind_kernel()
383 384
384 385 try:
385 386 p = connect_qtconsole(argv=arg_split(arg_s, os.name=='posix'))
386 387 except Exception as e:
387 388 error("Could not start qtconsole: %r" % e)
388 389 return
389 390
390 391 @line_magic
391 392 def autosave(self, arg_s):
392 393 """Set the autosave interval in the notebook (in seconds).
393 394
394 395 The default value is 120, or two minutes.
395 396 ``%autosave 0`` will disable autosave.
396 397
397 398 This magic only has an effect when called from the notebook interface.
398 399 It has no effect when called in a startup file.
399 400 """
400 401
401 402 try:
402 403 interval = int(arg_s)
403 404 except ValueError:
404 405 raise UsageError("%%autosave requires an integer, got %r" % arg_s)
405 406
406 407 # javascript wants milliseconds
407 408 milliseconds = 1000 * interval
408 409 display(Javascript("IPython.notebook.set_autosave_interval(%i)" % milliseconds),
409 410 include=['application/javascript']
410 411 )
411 412 if interval:
412 413 print("Autosaving every %i seconds" % interval)
413 414 else:
414 415 print("Autosave disabled")
415 416
416 417
417 418 class ZMQInteractiveShell(InteractiveShell):
418 419 """A subclass of InteractiveShell for ZMQ."""
419 420
420 421 displayhook_class = Type(ZMQShellDisplayHook)
421 422 display_pub_class = Type(ZMQDisplayPublisher)
422 423 data_pub_class = Type(ZMQDataPublisher)
423 424 kernel = Any()
424 425 parent_header = Any()
426
427 def _banner1_default(self):
428 return default_gui_banner
425 429
426 430 # Override the traitlet in the parent class, because there's no point using
427 431 # readline for the kernel. Can be removed when the readline code is moved
428 432 # to the terminal frontend.
429 433 colors_force = CBool(True)
430 434 readline_use = CBool(False)
431 435 # autoindent has no meaning in a zmqshell, and attempting to enable it
432 436 # will print a warning in the absence of readline.
433 437 autoindent = CBool(False)
434 438
435 439 exiter = Instance(ZMQExitAutocall)
436 440 def _exiter_default(self):
437 441 return ZMQExitAutocall(self)
438 442
439 443 def _exit_now_changed(self, name, old, new):
440 444 """stop eventloop when exit_now fires"""
441 445 if new:
442 446 loop = ioloop.IOLoop.instance()
443 447 loop.add_timeout(time.time()+0.1, loop.stop)
444 448
445 449 keepkernel_on_exit = None
446 450
447 451 # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no
448 452 # interactive input being read; we provide event loop support in ipkernel
449 453 @staticmethod
450 454 def enable_gui(gui):
451 455 from .eventloops import enable_gui as real_enable_gui
452 456 try:
453 457 real_enable_gui(gui)
454 458 except ValueError as e:
455 459 raise UsageError("%s" % e)
456 460
457 461 def init_environment(self):
458 462 """Configure the user's environment.
459 463
460 464 """
461 465 env = os.environ
462 466 # These two ensure 'ls' produces nice coloring on BSD-derived systems
463 467 env['TERM'] = 'xterm-color'
464 468 env['CLICOLOR'] = '1'
465 469 # Since normal pagers don't work at all (over pexpect we don't have
466 470 # single-key control of the subprocess), try to disable paging in
467 471 # subprocesses as much as possible.
468 472 env['PAGER'] = 'cat'
469 473 env['GIT_PAGER'] = 'cat'
470 474
471 475 # And install the payload version of page.
472 476 install_payload_page()
473 477
474 478 def auto_rewrite_input(self, cmd):
475 479 """Called to show the auto-rewritten input for autocall and friends.
476 480
477 481 FIXME: this payload is currently not correctly processed by the
478 482 frontend.
479 483 """
480 484 new = self.prompt_manager.render('rewrite') + cmd
481 485 payload = dict(
482 486 source='auto_rewrite_input',
483 487 transformed_input=new,
484 488 )
485 489 self.payload_manager.write_payload(payload)
486 490
487 491 def ask_exit(self):
488 492 """Engage the exit actions."""
489 493 self.exit_now = True
490 494 payload = dict(
491 495 source='ask_exit',
492 496 exit=True,
493 497 keepkernel=self.keepkernel_on_exit,
494 498 )
495 499 self.payload_manager.write_payload(payload)
496 500
497 501 def _showtraceback(self, etype, evalue, stb):
498 502 # try to preserve ordering of tracebacks and print statements
499 503 sys.stdout.flush()
500 504 sys.stderr.flush()
501 505
502 506 exc_content = {
503 507 u'traceback' : stb,
504 508 u'ename' : unicode_type(etype.__name__),
505 509 u'evalue' : py3compat.safe_unicode(evalue),
506 510 }
507 511
508 512 dh = self.displayhook
509 513 # Send exception info over pub socket for other clients than the caller
510 514 # to pick up
511 515 topic = None
512 516 if dh.topic:
513 517 topic = dh.topic.replace(b'execute_result', b'error')
514 518
515 519 exc_msg = dh.session.send(dh.pub_socket, u'error', json_clean(exc_content), dh.parent_header, ident=topic)
516 520
517 521 # FIXME - Hack: store exception info in shell object. Right now, the
518 522 # caller is reading this info after the fact, we need to fix this logic
519 523 # to remove this hack. Even uglier, we need to store the error status
520 524 # here, because in the main loop, the logic that sets it is being
521 525 # skipped because runlines swallows the exceptions.
522 526 exc_content[u'status'] = u'error'
523 527 self._reply_content = exc_content
524 528 # /FIXME
525 529
526 530 return exc_content
527 531
528 532 def set_next_input(self, text):
529 533 """Send the specified text to the frontend to be presented at the next
530 534 input cell."""
531 535 payload = dict(
532 536 source='set_next_input',
533 537 text=text
534 538 )
535 539 self.payload_manager.write_payload(payload)
536 540
537 541 def set_parent(self, parent):
538 542 """Set the parent header for associating output with its triggering input"""
539 543 self.parent_header = parent
540 544 self.displayhook.set_parent(parent)
541 545 self.display_pub.set_parent(parent)
542 546 self.data_pub.set_parent(parent)
543 547 try:
544 548 sys.stdout.set_parent(parent)
545 549 except AttributeError:
546 550 pass
547 551 try:
548 552 sys.stderr.set_parent(parent)
549 553 except AttributeError:
550 554 pass
551 555
552 556 def get_parent(self):
553 557 return self.parent_header
554 558
555 559 #-------------------------------------------------------------------------
556 560 # Things related to magics
557 561 #-------------------------------------------------------------------------
558 562
559 563 def init_magics(self):
560 564 super(ZMQInteractiveShell, self).init_magics()
561 565 self.register_magics(KernelMagics)
562 566 self.magics_manager.register_alias('ed', 'edit')
563 567
564 568 def init_comms(self):
565 569 self.comm_manager = CommManager(shell=self, parent=self)
566 570 self.configurables.append(self.comm_manager)
567 571
568 572
569 573 InteractiveShellABC.register(ZMQInteractiveShell)
@@ -1,830 +1,834 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 kernel_banner = Unicode()
85 86
86 87 # An option and corresponding signal for overriding the default kernel
87 88 # interrupt behavior.
88 89 custom_interrupt = Bool(False)
89 90 custom_interrupt_requested = QtCore.Signal()
90 91
91 92 # An option and corresponding signals for overriding the default kernel
92 93 # restart behavior.
93 94 custom_restart = Bool(False)
94 95 custom_restart_kernel_died = QtCore.Signal(float)
95 96 custom_restart_requested = QtCore.Signal()
96 97
97 98 # Whether to automatically show calltips on open-parentheses.
98 99 enable_calltips = Bool(True, config=True,
99 100 help="Whether to draw information calltips on open-parentheses.")
100 101
101 102 clear_on_kernel_restart = Bool(True, config=True,
102 103 help="Whether to clear the console when the kernel is restarted")
103 104
104 105 confirm_restart = Bool(True, config=True,
105 106 help="Whether to ask for user confirmation when restarting kernel")
106 107
107 108 lexer_class = DottedObjectName(config=True,
108 109 help="The pygments lexer class to use."
109 110 )
110 111 def _lexer_class_changed(self, name, old, new):
111 112 lexer_class = import_item(new)
112 113 self.lexer = lexer_class()
113 114
114 115 def _lexer_class_default(self):
115 116 if py3compat.PY3:
116 117 return 'pygments.lexers.Python3Lexer'
117 118 else:
118 119 return 'pygments.lexers.PythonLexer'
119 120
120 121 lexer = Any()
121 122 def _lexer_default(self):
122 123 lexer_class = import_item(self.lexer_class)
123 124 return lexer_class()
124 125
125 126 # Emitted when a user visible 'execute_request' has been submitted to the
126 127 # kernel from the FrontendWidget. Contains the code to be executed.
127 128 executing = QtCore.Signal(object)
128 129
129 130 # Emitted when a user-visible 'execute_reply' has been received from the
130 131 # kernel and processed by the FrontendWidget. Contains the response message.
131 132 executed = QtCore.Signal(object)
132 133
133 134 # Emitted when an exit request has been received from the kernel.
134 135 exit_requested = QtCore.Signal(object)
135 136
136 137 # Protected class variables.
137 138 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
138 139 logical_line_transforms=[],
139 140 python_line_transforms=[],
140 141 )
141 142 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
142 143 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
143 144 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
144 145 _input_splitter_class = InputSplitter
145 146 _local_kernel = False
146 147 _highlighter = Instance(FrontendHighlighter)
147 148
148 149 #---------------------------------------------------------------------------
149 150 # 'object' interface
150 151 #---------------------------------------------------------------------------
151 152
152 153 def __init__(self, *args, **kw):
153 154 super(FrontendWidget, self).__init__(*args, **kw)
154 155 # FIXME: remove this when PySide min version is updated past 1.0.7
155 156 # forcefully disable calltips if PySide is < 1.0.7, because they crash
156 157 if qt.QT_API == qt.QT_API_PYSIDE:
157 158 import PySide
158 159 if PySide.__version_info__ < (1,0,7):
159 160 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
160 161 self.enable_calltips = False
161 162
162 163 # FrontendWidget protected variables.
163 164 self._bracket_matcher = BracketMatcher(self._control)
164 165 self._call_tip_widget = CallTipWidget(self._control)
165 166 self._completion_lexer = CompletionLexer(self.lexer)
166 167 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
167 168 self._hidden = False
168 169 self._highlighter = FrontendHighlighter(self, lexer=self.lexer)
169 170 self._input_splitter = self._input_splitter_class()
170 171 self._kernel_manager = None
171 172 self._kernel_client = None
172 173 self._request_info = {}
173 174 self._request_info['execute'] = {};
174 175 self._callback_dict = {}
175 176
176 177 # Configure the ConsoleWidget.
177 178 self.tab_width = 4
178 179 self._set_continuation_prompt('... ')
179 180
180 181 # Configure the CallTipWidget.
181 182 self._call_tip_widget.setFont(self.font)
182 183 self.font_changed.connect(self._call_tip_widget.setFont)
183 184
184 185 # Configure actions.
185 186 action = self._copy_raw_action
186 187 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
187 188 action.setEnabled(False)
188 189 action.setShortcut(QtGui.QKeySequence(key))
189 190 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
190 191 action.triggered.connect(self.copy_raw)
191 192 self.copy_available.connect(action.setEnabled)
192 193 self.addAction(action)
193 194
194 195 # Connect signal handlers.
195 196 document = self._control.document()
196 197 document.contentsChange.connect(self._document_contents_change)
197 198
198 199 # Set flag for whether we are connected via localhost.
199 200 self._local_kernel = kw.get('local_kernel',
200 201 FrontendWidget._local_kernel)
201 202
202 203 # Whether or not a clear_output call is pending new output.
203 204 self._pending_clearoutput = False
204 205
205 206 #---------------------------------------------------------------------------
206 207 # 'ConsoleWidget' public interface
207 208 #---------------------------------------------------------------------------
208 209
209 210 def copy(self):
210 211 """ Copy the currently selected text to the clipboard, removing prompts.
211 212 """
212 213 if self._page_control is not None and self._page_control.hasFocus():
213 214 self._page_control.copy()
214 215 elif self._control.hasFocus():
215 216 text = self._control.textCursor().selection().toPlainText()
216 217 if text:
217 218 text = self._prompt_transformer.transform_cell(text)
218 219 QtGui.QApplication.clipboard().setText(text)
219 220 else:
220 221 self.log.debug("frontend widget : unknown copy target")
221 222
222 223 #---------------------------------------------------------------------------
223 224 # 'ConsoleWidget' abstract interface
224 225 #---------------------------------------------------------------------------
225 226
226 227 def _is_complete(self, source, interactive):
227 228 """ Returns whether 'source' can be completely processed and a new
228 229 prompt created. When triggered by an Enter/Return key press,
229 230 'interactive' is True; otherwise, it is False.
230 231 """
231 232 self._input_splitter.reset()
232 233 try:
233 234 complete = self._input_splitter.push(source)
234 235 except SyntaxError:
235 236 return True
236 237 if interactive:
237 238 complete = not self._input_splitter.push_accepts_more()
238 239 return complete
239 240
240 241 def _execute(self, source, hidden):
241 242 """ Execute 'source'. If 'hidden', do not show any output.
242 243
243 244 See parent class :meth:`execute` docstring for full details.
244 245 """
245 246 msg_id = self.kernel_client.execute(source, hidden)
246 247 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
247 248 self._hidden = hidden
248 249 if not hidden:
249 250 self.executing.emit(source)
250 251
251 252 def _prompt_started_hook(self):
252 253 """ Called immediately after a new prompt is displayed.
253 254 """
254 255 if not self._reading:
255 256 self._highlighter.highlighting_on = True
256 257
257 258 def _prompt_finished_hook(self):
258 259 """ Called immediately after a prompt is finished, i.e. when some input
259 260 will be processed and a new prompt displayed.
260 261 """
261 262 # Flush all state from the input splitter so the next round of
262 263 # reading input starts with a clean buffer.
263 264 self._input_splitter.reset()
264 265
265 266 if not self._reading:
266 267 self._highlighter.highlighting_on = False
267 268
268 269 def _tab_pressed(self):
269 270 """ Called when the tab key is pressed. Returns whether to continue
270 271 processing the event.
271 272 """
272 273 # Perform tab completion if:
273 274 # 1) The cursor is in the input buffer.
274 275 # 2) There is a non-whitespace character before the cursor.
275 276 text = self._get_input_buffer_cursor_line()
276 277 if text is None:
277 278 return False
278 279 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
279 280 if complete:
280 281 self._complete()
281 282 return not complete
282 283
283 284 #---------------------------------------------------------------------------
284 285 # 'ConsoleWidget' protected interface
285 286 #---------------------------------------------------------------------------
286 287
287 288 def _context_menu_make(self, pos):
288 289 """ Reimplemented to add an action for raw copy.
289 290 """
290 291 menu = super(FrontendWidget, self)._context_menu_make(pos)
291 292 for before_action in menu.actions():
292 293 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
293 294 QtGui.QKeySequence.ExactMatch:
294 295 menu.insertAction(before_action, self._copy_raw_action)
295 296 break
296 297 return menu
297 298
298 299 def request_interrupt_kernel(self):
299 300 if self._executing:
300 301 self.interrupt_kernel()
301 302
302 303 def request_restart_kernel(self):
303 304 message = 'Are you sure you want to restart the kernel?'
304 305 self.restart_kernel(message, now=False)
305 306
306 307 def _event_filter_console_keypress(self, event):
307 308 """ Reimplemented for execution interruption and smart backspace.
308 309 """
309 310 key = event.key()
310 311 if self._control_key_down(event.modifiers(), include_command=False):
311 312
312 313 if key == QtCore.Qt.Key_C and self._executing:
313 314 self.request_interrupt_kernel()
314 315 return True
315 316
316 317 elif key == QtCore.Qt.Key_Period:
317 318 self.request_restart_kernel()
318 319 return True
319 320
320 321 elif not event.modifiers() & QtCore.Qt.AltModifier:
321 322
322 323 # Smart backspace: remove four characters in one backspace if:
323 324 # 1) everything left of the cursor is whitespace
324 325 # 2) the four characters immediately left of the cursor are spaces
325 326 if key == QtCore.Qt.Key_Backspace:
326 327 col = self._get_input_buffer_cursor_column()
327 328 cursor = self._control.textCursor()
328 329 if col > 3 and not cursor.hasSelection():
329 330 text = self._get_input_buffer_cursor_line()[:col]
330 331 if text.endswith(' ') and not text.strip():
331 332 cursor.movePosition(QtGui.QTextCursor.Left,
332 333 QtGui.QTextCursor.KeepAnchor, 4)
333 334 cursor.removeSelectedText()
334 335 return True
335 336
336 337 return super(FrontendWidget, self)._event_filter_console_keypress(event)
337 338
338 339 def _insert_continuation_prompt(self, cursor):
339 340 """ Reimplemented for auto-indentation.
340 341 """
341 342 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
342 343 cursor.insertText(' ' * self._input_splitter.indent_spaces)
343 344
344 345 #---------------------------------------------------------------------------
345 346 # 'BaseFrontendMixin' abstract interface
346 347 #---------------------------------------------------------------------------
347 348 def _handle_clear_output(self, msg):
348 349 """Handle clear output messages."""
349 350 if not self._hidden and self._is_from_this_session(msg):
350 351 wait = msg['content'].get('wait', True)
351 352 if wait:
352 353 self._pending_clearoutput = True
353 354 else:
354 355 self.clear_output()
355 356
356 357 def _handle_complete_reply(self, rep):
357 358 """ Handle replies for tab completion.
358 359 """
359 360 self.log.debug("complete: %s", rep.get('content', ''))
360 361 cursor = self._get_cursor()
361 362 info = self._request_info.get('complete')
362 363 if info and info.id == rep['parent_header']['msg_id'] and \
363 364 info.pos == cursor.position():
364 365 text = '.'.join(self._get_context())
365 366 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
366 367 self._complete_with_items(cursor, rep['content']['matches'])
367 368
368 369 def _silent_exec_callback(self, expr, callback):
369 370 """Silently execute `expr` in the kernel and call `callback` with reply
370 371
371 372 the `expr` is evaluated silently in the kernel (without) output in
372 373 the frontend. Call `callback` with the
373 374 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
374 375
375 376 Parameters
376 377 ----------
377 378 expr : string
378 379 valid string to be executed by the kernel.
379 380 callback : function
380 381 function accepting one argument, as a string. The string will be
381 382 the `repr` of the result of evaluating `expr`
382 383
383 384 The `callback` is called with the `repr()` of the result of `expr` as
384 385 first argument. To get the object, do `eval()` on the passed value.
385 386
386 387 See Also
387 388 --------
388 389 _handle_exec_callback : private method, deal with calling callback with reply
389 390
390 391 """
391 392
392 393 # generate uuid, which would be used as an indication of whether or
393 394 # not the unique request originated from here (can use msg id ?)
394 395 local_uuid = str(uuid.uuid1())
395 396 msg_id = self.kernel_client.execute('',
396 397 silent=True, user_expressions={ local_uuid:expr })
397 398 self._callback_dict[local_uuid] = callback
398 399 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
399 400
400 401 def _handle_exec_callback(self, msg):
401 402 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
402 403
403 404 Parameters
404 405 ----------
405 406 msg : raw message send by the kernel containing an `user_expressions`
406 407 and having a 'silent_exec_callback' kind.
407 408
408 409 Notes
409 410 -----
410 411 This function will look for a `callback` associated with the
411 412 corresponding message id. Association has been made by
412 413 `_silent_exec_callback`. `callback` is then called with the `repr()`
413 414 of the value of corresponding `user_expressions` as argument.
414 415 `callback` is then removed from the known list so that any message
415 416 coming again with the same id won't trigger it.
416 417
417 418 """
418 419
419 420 user_exp = msg['content'].get('user_expressions')
420 421 if not user_exp:
421 422 return
422 423 for expression in user_exp:
423 424 if expression in self._callback_dict:
424 425 self._callback_dict.pop(expression)(user_exp[expression])
425 426
426 427 def _handle_execute_reply(self, msg):
427 428 """ Handles replies for code execution.
428 429 """
429 430 self.log.debug("execute: %s", msg.get('content', ''))
430 431 msg_id = msg['parent_header']['msg_id']
431 432 info = self._request_info['execute'].get(msg_id)
432 433 # unset reading flag, because if execute finished, raw_input can't
433 434 # still be pending.
434 435 self._reading = False
435 436 if info and info.kind == 'user' and not self._hidden:
436 437 # Make sure that all output from the SUB channel has been processed
437 438 # before writing a new prompt.
438 439 self.kernel_client.iopub_channel.flush()
439 440
440 441 # Reset the ANSI style information to prevent bad text in stdout
441 442 # from messing up our colors. We're not a true terminal so we're
442 443 # allowed to do this.
443 444 if self.ansi_codes:
444 445 self._ansi_processor.reset_sgr()
445 446
446 447 content = msg['content']
447 448 status = content['status']
448 449 if status == 'ok':
449 450 self._process_execute_ok(msg)
450 451 elif status == 'error':
451 452 self._process_execute_error(msg)
452 453 elif status == 'aborted':
453 454 self._process_execute_abort(msg)
454 455
455 456 self._show_interpreter_prompt_for_reply(msg)
456 457 self.executed.emit(msg)
457 458 self._request_info['execute'].pop(msg_id)
458 459 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
459 460 self._handle_exec_callback(msg)
460 461 self._request_info['execute'].pop(msg_id)
461 462 else:
462 463 super(FrontendWidget, self)._handle_execute_reply(msg)
463 464
464 465 def _handle_input_request(self, msg):
465 466 """ Handle requests for raw_input.
466 467 """
467 468 self.log.debug("input: %s", msg.get('content', ''))
468 469 if self._hidden:
469 470 raise RuntimeError('Request for raw input during hidden execution.')
470 471
471 472 # Make sure that all output from the SUB channel has been processed
472 473 # before entering readline mode.
473 474 self.kernel_client.iopub_channel.flush()
474 475
475 476 def callback(line):
476 477 self.kernel_client.stdin_channel.input(line)
477 478 if self._reading:
478 479 self.log.debug("Got second input request, assuming first was interrupted.")
479 480 self._reading = False
480 481 self._readline(msg['content']['prompt'], callback=callback)
481 482
482 483 def _kernel_restarted_message(self, died=True):
483 484 msg = "Kernel died, restarting" if died else "Kernel restarting"
484 485 self._append_html("<br>%s<hr><br>" % msg,
485 486 before_prompt=False
486 487 )
487 488
488 489 def _handle_kernel_died(self, since_last_heartbeat):
489 490 """Handle the kernel's death (if we do not own the kernel).
490 491 """
491 492 self.log.warn("kernel died: %s", since_last_heartbeat)
492 493 if self.custom_restart:
493 494 self.custom_restart_kernel_died.emit(since_last_heartbeat)
494 495 else:
495 496 self._kernel_restarted_message(died=True)
496 497 self.reset()
497 498
498 499 def _handle_kernel_restarted(self, died=True):
499 500 """Notice that the autorestarter restarted the kernel.
500 501
501 502 There's nothing to do but show a message.
502 503 """
503 504 self.log.warn("kernel restarted")
504 505 self._kernel_restarted_message(died=died)
505 506 self.reset()
506 507
507 508 def _handle_object_info_reply(self, rep):
508 509 """ Handle replies for call tips.
509 510 """
510 511 self.log.debug("oinfo: %s", rep.get('content', ''))
511 512 cursor = self._get_cursor()
512 513 info = self._request_info.get('call_tip')
513 514 if info and info.id == rep['parent_header']['msg_id'] and \
514 515 info.pos == cursor.position():
515 516 # Get the information for a call tip. For now we format the call
516 517 # line as string, later we can pass False to format_call and
517 518 # syntax-highlight it ourselves for nicer formatting in the
518 519 # calltip.
519 520 content = rep['content']
520 521 # if this is from pykernel, 'docstring' will be the only key
521 522 if content.get('ismagic', False):
522 523 # Don't generate a call-tip for magics. Ideally, we should
523 524 # generate a tooltip, but not on ( like we do for actual
524 525 # callables.
525 526 call_info, doc = None, None
526 527 else:
527 528 call_info, doc = call_tip(content, format_call=True)
528 529 if call_info or doc:
529 530 self._call_tip_widget.show_call_info(call_info, doc)
530 531
531 532 def _handle_execute_result(self, msg):
532 533 """ Handle display hook output.
533 534 """
534 535 self.log.debug("execute_result: %s", msg.get('content', ''))
535 536 if not self._hidden and self._is_from_this_session(msg):
536 537 self.flush_clearoutput()
537 538 text = msg['content']['data']
538 539 self._append_plain_text(text + '\n', before_prompt=True)
539 540
540 541 def _handle_stream(self, msg):
541 542 """ Handle stdout, stderr, and stdin.
542 543 """
543 544 self.log.debug("stream: %s", msg.get('content', ''))
544 545 if not self._hidden and self._is_from_this_session(msg):
545 546 self.flush_clearoutput()
546 547 self.append_stream(msg['content']['data'])
547 548
548 549 def _handle_shutdown_reply(self, msg):
549 550 """ Handle shutdown signal, only if from other console.
550 551 """
551 552 self.log.warn("shutdown: %s", msg.get('content', ''))
552 553 restart = msg.get('content', {}).get('restart', False)
553 554 if not self._hidden and not self._is_from_this_session(msg):
554 555 # got shutdown reply, request came from session other than ours
555 556 if restart:
556 557 # someone restarted the kernel, handle it
557 558 self._handle_kernel_restarted(died=False)
558 559 else:
559 560 # kernel was shutdown permanently
560 561 # this triggers exit_requested if the kernel was local,
561 562 # and a dialog if the kernel was remote,
562 563 # so we don't suddenly clear the qtconsole without asking.
563 564 if self._local_kernel:
564 565 self.exit_requested.emit(self)
565 566 else:
566 567 title = self.window().windowTitle()
567 568 reply = QtGui.QMessageBox.question(self, title,
568 569 "Kernel has been shutdown permanently. "
569 570 "Close the Console?",
570 571 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
571 572 if reply == QtGui.QMessageBox.Yes:
572 573 self.exit_requested.emit(self)
573 574
574 575 def _handle_status(self, msg):
575 576 """Handle status message"""
576 577 # This is where a busy/idle indicator would be triggered,
577 578 # when we make one.
578 579 state = msg['content'].get('execution_state', '')
579 580 if state == 'starting':
580 581 # kernel started while we were running
581 582 if self._executing:
582 583 self._handle_kernel_restarted(died=True)
583 584 elif state == 'idle':
584 585 pass
585 586 elif state == 'busy':
586 587 pass
587 588
588 589 def _started_channels(self):
589 590 """ Called when the KernelManager channels have started listening or
590 591 when the frontend is assigned an already listening KernelManager.
591 592 """
592 593 self.reset(clear=True)
593 594
594 595 #---------------------------------------------------------------------------
595 596 # 'FrontendWidget' public interface
596 597 #---------------------------------------------------------------------------
597 598
598 599 def copy_raw(self):
599 600 """ Copy the currently selected text to the clipboard without attempting
600 601 to remove prompts or otherwise alter the text.
601 602 """
602 603 self._control.copy()
603 604
604 605 def execute_file(self, path, hidden=False):
605 606 """ Attempts to execute file with 'path'. If 'hidden', no output is
606 607 shown.
607 608 """
608 609 self.execute('execfile(%r)' % path, hidden=hidden)
609 610
610 611 def interrupt_kernel(self):
611 612 """ Attempts to interrupt the running kernel.
612 613
613 614 Also unsets _reading flag, to avoid runtime errors
614 615 if raw_input is called again.
615 616 """
616 617 if self.custom_interrupt:
617 618 self._reading = False
618 619 self.custom_interrupt_requested.emit()
619 620 elif self.kernel_manager:
620 621 self._reading = False
621 622 self.kernel_manager.interrupt_kernel()
622 623 else:
623 624 self._append_plain_text('Cannot interrupt a kernel I did not start.\n')
624 625
625 626 def reset(self, clear=False):
626 627 """ Resets the widget to its initial state if ``clear`` parameter
627 628 is True, otherwise
628 629 prints a visual indication of the fact that the kernel restarted, but
629 630 does not clear the traces from previous usage of the kernel before it
630 631 was restarted. With ``clear=True``, it is similar to ``%clear``, but
631 632 also re-writes the banner and aborts execution if necessary.
632 633 """
633 634 if self._executing:
634 635 self._executing = False
635 636 self._request_info['execute'] = {}
636 637 self._reading = False
637 638 self._highlighter.highlighting_on = False
638 639
639 640 if clear:
640 641 self._control.clear()
641 642 self._append_plain_text(self.banner)
643 if self.kernel_banner:
644 self._append_plain_text(self.kernel_banner)
645
642 646 # update output marker for stdout/stderr, so that startup
643 647 # messages appear after banner:
644 648 self._append_before_prompt_pos = self._get_cursor().position()
645 649 self._show_interpreter_prompt()
646 650
647 651 def restart_kernel(self, message, now=False):
648 652 """ Attempts to restart the running kernel.
649 653 """
650 654 # FIXME: now should be configurable via a checkbox in the dialog. Right
651 655 # now at least the heartbeat path sets it to True and the manual restart
652 656 # to False. But those should just be the pre-selected states of a
653 657 # checkbox that the user could override if so desired. But I don't know
654 658 # enough Qt to go implementing the checkbox now.
655 659
656 660 if self.custom_restart:
657 661 self.custom_restart_requested.emit()
658 662 return
659 663
660 664 if self.kernel_manager:
661 665 # Pause the heart beat channel to prevent further warnings.
662 666 self.kernel_client.hb_channel.pause()
663 667
664 668 # Prompt the user to restart the kernel. Un-pause the heartbeat if
665 669 # they decline. (If they accept, the heartbeat will be un-paused
666 670 # automatically when the kernel is restarted.)
667 671 if self.confirm_restart:
668 672 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
669 673 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
670 674 message, buttons)
671 675 do_restart = result == QtGui.QMessageBox.Yes
672 676 else:
673 677 # confirm_restart is False, so we don't need to ask user
674 678 # anything, just do the restart
675 679 do_restart = True
676 680 if do_restart:
677 681 try:
678 682 self.kernel_manager.restart_kernel(now=now)
679 683 except RuntimeError as e:
680 684 self._append_plain_text(
681 685 'Error restarting kernel: %s\n' % e,
682 686 before_prompt=True
683 687 )
684 688 else:
685 689 self._append_html("<br>Restarting kernel...\n<hr><br>",
686 690 before_prompt=True,
687 691 )
688 692 else:
689 693 self.kernel_client.hb_channel.unpause()
690 694
691 695 else:
692 696 self._append_plain_text(
693 697 'Cannot restart a Kernel I did not start\n',
694 698 before_prompt=True
695 699 )
696 700
697 701 def append_stream(self, text):
698 702 """Appends text to the output stream."""
699 703 # Most consoles treat tabs as being 8 space characters. Convert tabs
700 704 # to spaces so that output looks as expected regardless of this
701 705 # widget's tab width.
702 706 text = text.expandtabs(8)
703 707 self._append_plain_text(text, before_prompt=True)
704 708 self._control.moveCursor(QtGui.QTextCursor.End)
705 709
706 710 def flush_clearoutput(self):
707 711 """If a clearoutput is pending, execute it."""
708 712 if self._pending_clearoutput:
709 713 self._pending_clearoutput = False
710 714 self.clear_output()
711 715
712 716 def clear_output(self):
713 717 """Clears the current line of output."""
714 718 cursor = self._control.textCursor()
715 719 cursor.beginEditBlock()
716 720 cursor.movePosition(cursor.StartOfLine, cursor.KeepAnchor)
717 721 cursor.insertText('')
718 722 cursor.endEditBlock()
719 723
720 724 #---------------------------------------------------------------------------
721 725 # 'FrontendWidget' protected interface
722 726 #---------------------------------------------------------------------------
723 727
724 728 def _call_tip(self):
725 729 """ Shows a call tip, if appropriate, at the current cursor location.
726 730 """
727 731 # Decide if it makes sense to show a call tip
728 732 if not self.enable_calltips:
729 733 return False
730 734 cursor_pos = self._get_input_buffer_cursor_pos()
731 735 code = self.input_buffer
732 736 # Send the metadata request to the kernel
733 737 msg_id = self.kernel_client.object_info(code, cursor_pos)
734 738 pos = self._get_cursor().position()
735 739 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
736 740 return True
737 741
738 742 def _complete(self):
739 743 """ Performs completion at the current cursor location.
740 744 """
741 745 context = self._get_context()
742 746 if context:
743 747 # Send the completion request to the kernel
744 748 msg_id = self.kernel_client.complete(
745 749 code=self.input_buffer,
746 750 cursor_pos=self._get_input_buffer_cursor_pos(),
747 751 )
748 752 pos = self._get_cursor().position()
749 753 info = self._CompletionRequest(msg_id, pos)
750 754 self._request_info['complete'] = info
751 755
752 756 def _get_context(self, cursor=None):
753 757 """ Gets the context for the specified cursor (or the current cursor
754 758 if none is specified).
755 759 """
756 760 if cursor is None:
757 761 cursor = self._get_cursor()
758 762 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
759 763 QtGui.QTextCursor.KeepAnchor)
760 764 text = cursor.selection().toPlainText()
761 765 return self._completion_lexer.get_context(text)
762 766
763 767 def _process_execute_abort(self, msg):
764 768 """ Process a reply for an aborted execution request.
765 769 """
766 770 self._append_plain_text("ERROR: execution aborted\n")
767 771
768 772 def _process_execute_error(self, msg):
769 773 """ Process a reply for an execution request that resulted in an error.
770 774 """
771 775 content = msg['content']
772 776 # If a SystemExit is passed along, this means exit() was called - also
773 777 # all the ipython %exit magic syntax of '-k' to be used to keep
774 778 # the kernel running
775 779 if content['ename']=='SystemExit':
776 780 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
777 781 self._keep_kernel_on_exit = keepkernel
778 782 self.exit_requested.emit(self)
779 783 else:
780 784 traceback = ''.join(content['traceback'])
781 785 self._append_plain_text(traceback)
782 786
783 787 def _process_execute_ok(self, msg):
784 788 """ Process a reply for a successful execution request.
785 789 """
786 790 payload = msg['content']['payload']
787 791 for item in payload:
788 792 if not self._process_execute_payload(item):
789 793 warning = 'Warning: received unknown payload of type %s'
790 794 print(warning % repr(item['source']))
791 795
792 796 def _process_execute_payload(self, item):
793 797 """ Process a single payload item from the list of payload items in an
794 798 execution reply. Returns whether the payload was handled.
795 799 """
796 800 # The basic FrontendWidget doesn't handle payloads, as they are a
797 801 # mechanism for going beyond the standard Python interpreter model.
798 802 return False
799 803
800 804 def _show_interpreter_prompt(self):
801 805 """ Shows a prompt for the interpreter.
802 806 """
803 807 self._show_prompt('>>> ')
804 808
805 809 def _show_interpreter_prompt_for_reply(self, msg):
806 810 """ Shows a prompt for the interpreter given an 'execute_reply' message.
807 811 """
808 812 self._show_interpreter_prompt()
809 813
810 814 #------ Signal handlers ----------------------------------------------------
811 815
812 816 def _document_contents_change(self, position, removed, added):
813 817 """ Called whenever the document's content changes. Display a call tip
814 818 if appropriate.
815 819 """
816 820 # Calculate where the cursor should be *after* the change:
817 821 position += added
818 822
819 823 document = self._control.document()
820 824 if position == self._get_cursor().position():
821 825 self._call_tip()
822 826
823 827 #------ Trait default initializers -----------------------------------------
824 828
825 829 def _banner_default(self):
826 830 """ Returns the standard Python banner.
827 831 """
828 832 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
829 833 '"license" for more information.'
830 834 return banner % (sys.version, sys.platform)
@@ -1,572 +1,570 b''
1 1 """A FrontendWidget that emulates the interface of the console IPython.
2 2
3 3 This supports the additional functionality provided by the IPython kernel.
4 4 """
5 5
6 6 # Copyright (c) IPython Development Team.
7 7 # Distributed under the terms of the Modified BSD License.
8 8
9 9 from collections import namedtuple
10 10 import os.path
11 11 import re
12 12 from subprocess import Popen
13 13 import sys
14 14 import time
15 15 from textwrap import dedent
16 16
17 17 from IPython.external.qt import QtCore, QtGui
18 18
19 19 from IPython.core.inputsplitter import IPythonInputSplitter
20 from IPython.core.release import version
20 21 from IPython.core.inputtransformer import ipy_prompt
21 22 from IPython.utils.traitlets import Bool, Unicode
22 23 from .frontend_widget import FrontendWidget
23 24 from . import styles
24 25
25 26 #-----------------------------------------------------------------------------
26 27 # Constants
27 28 #-----------------------------------------------------------------------------
28 29
29 30 # Default strings to build and display input and output prompts (and separators
30 31 # in between)
31 32 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
32 33 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
33 34 default_input_sep = '\n'
34 35 default_output_sep = ''
35 36 default_output_sep2 = ''
36 37
37 38 # Base path for most payload sources.
38 39 zmq_shell_source = 'IPython.kernel.zmq.zmqshell.ZMQInteractiveShell'
39 40
40 41 if sys.platform.startswith('win'):
41 42 default_editor = 'notepad'
42 43 else:
43 44 default_editor = ''
44 45
45 46 #-----------------------------------------------------------------------------
46 47 # IPythonWidget class
47 48 #-----------------------------------------------------------------------------
48 49
49 50 class IPythonWidget(FrontendWidget):
50 51 """ A FrontendWidget for an IPython kernel.
51 52 """
52 53
53 54 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
54 55 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
55 56 # settings.
56 57 custom_edit = Bool(False)
57 58 custom_edit_requested = QtCore.Signal(object, object)
58 59
59 60 editor = Unicode(default_editor, config=True,
60 61 help="""
61 62 A command for invoking a system text editor. If the string contains a
62 63 {filename} format specifier, it will be used. Otherwise, the filename
63 64 will be appended to the end the command.
64 65 """)
65 66
66 67 editor_line = Unicode(config=True,
67 68 help="""
68 69 The editor command to use when a specific line number is requested. The
69 70 string should contain two format specifiers: {line} and {filename}. If
70 71 this parameter is not specified, the line number option to the %edit
71 72 magic will be ignored.
72 73 """)
73 74
74 75 style_sheet = Unicode(config=True,
75 76 help="""
76 77 A CSS stylesheet. The stylesheet can contain classes for:
77 78 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
78 79 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
79 80 3. IPython: .error, .in-prompt, .out-prompt, etc
80 81 """)
81 82
82 83 syntax_style = Unicode(config=True,
83 84 help="""
84 85 If not empty, use this Pygments style for syntax highlighting.
85 86 Otherwise, the style sheet is queried for Pygments style
86 87 information.
87 88 """)
88 89
89 90 # Prompts.
90 91 in_prompt = Unicode(default_in_prompt, config=True)
91 92 out_prompt = Unicode(default_out_prompt, config=True)
92 93 input_sep = Unicode(default_input_sep, config=True)
93 94 output_sep = Unicode(default_output_sep, config=True)
94 95 output_sep2 = Unicode(default_output_sep2, config=True)
95 96
96 97 # FrontendWidget protected class variables.
97 98 _input_splitter_class = IPythonInputSplitter
98 99 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[ipy_prompt()],
99 100 logical_line_transforms=[],
100 101 python_line_transforms=[],
101 102 )
102 103
103 104 # IPythonWidget protected class variables.
104 105 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
105 106 _payload_source_edit = 'edit_magic'
106 107 _payload_source_exit = 'ask_exit'
107 108 _payload_source_next_input = 'set_next_input'
108 109 _payload_source_page = 'page'
109 110 _retrying_history_request = False
111 _starting = False
110 112
111 113 #---------------------------------------------------------------------------
112 114 # 'object' interface
113 115 #---------------------------------------------------------------------------
114 116
115 117 def __init__(self, *args, **kw):
116 118 super(IPythonWidget, self).__init__(*args, **kw)
117 119
118 120 # IPythonWidget protected variables.
119 121 self._payload_handlers = {
120 122 self._payload_source_edit : self._handle_payload_edit,
121 123 self._payload_source_exit : self._handle_payload_exit,
122 124 self._payload_source_page : self._handle_payload_page,
123 125 self._payload_source_next_input : self._handle_payload_next_input }
124 126 self._previous_prompt_obj = None
125 127 self._keep_kernel_on_exit = None
126 128
127 129 # Initialize widget styling.
128 130 if self.style_sheet:
129 131 self._style_sheet_changed()
130 132 self._syntax_style_changed()
131 133 else:
132 134 self.set_default_style()
133 135
134 136 self._guiref_loaded = False
135 137
136 138 #---------------------------------------------------------------------------
137 139 # 'BaseFrontendMixin' abstract interface
138 140 #---------------------------------------------------------------------------
139 141 def _handle_complete_reply(self, rep):
140 142 """ Reimplemented to support IPython's improved completion machinery.
141 143 """
142 144 self.log.debug("complete: %s", rep.get('content', ''))
143 145 cursor = self._get_cursor()
144 146 info = self._request_info.get('complete')
145 147 if info and info.id == rep['parent_header']['msg_id'] and \
146 148 info.pos == cursor.position():
147 149 matches = rep['content']['matches']
148 150 text = rep['content']['matched_text']
149 151 offset = len(text)
150 152
151 153 # Clean up matches with period and path separators if the matched
152 154 # text has not been transformed. This is done by truncating all
153 155 # but the last component and then suitably decreasing the offset
154 156 # between the current cursor position and the start of completion.
155 157 if len(matches) > 1 and matches[0][:offset] == text:
156 158 parts = re.split(r'[./\\]', text)
157 159 sep_count = len(parts) - 1
158 160 if sep_count:
159 161 chop_length = sum(map(len, parts[:sep_count])) + sep_count
160 162 matches = [ match[chop_length:] for match in matches ]
161 163 offset -= chop_length
162 164
163 165 # Move the cursor to the start of the match and complete.
164 166 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
165 167 self._complete_with_items(cursor, matches)
166 168
167 169 def _handle_execute_reply(self, msg):
168 170 """ Reimplemented to support prompt requests.
169 171 """
170 172 msg_id = msg['parent_header'].get('msg_id')
171 173 info = self._request_info['execute'].get(msg_id)
172 174 if info and info.kind == 'prompt':
173 175 content = msg['content']
174 176 if content['status'] == 'aborted':
175 177 self._show_interpreter_prompt()
176 178 else:
177 179 number = content['execution_count'] + 1
178 180 self._show_interpreter_prompt(number)
179 181 self._request_info['execute'].pop(msg_id)
180 182 else:
181 183 super(IPythonWidget, self)._handle_execute_reply(msg)
182 184
183 185 def _handle_history_reply(self, msg):
184 186 """ Implemented to handle history tail replies, which are only supported
185 187 by the IPython kernel.
186 188 """
187 189 content = msg['content']
188 190 if 'history' not in content:
189 191 self.log.error("History request failed: %r"%content)
190 192 if content.get('status', '') == 'aborted' and \
191 193 not self._retrying_history_request:
192 194 # a *different* action caused this request to be aborted, so
193 195 # we should try again.
194 196 self.log.error("Retrying aborted history request")
195 197 # prevent multiple retries of aborted requests:
196 198 self._retrying_history_request = True
197 199 # wait out the kernel's queue flush, which is currently timed at 0.1s
198 200 time.sleep(0.25)
199 201 self.kernel_client.shell_channel.history(hist_access_type='tail',n=1000)
200 202 else:
201 203 self._retrying_history_request = False
202 204 return
203 205 # reset retry flag
204 206 self._retrying_history_request = False
205 207 history_items = content['history']
206 208 self.log.debug("Received history reply with %i entries", len(history_items))
207 209 items = []
208 210 last_cell = u""
209 211 for _, _, cell in history_items:
210 212 cell = cell.rstrip()
211 213 if cell != last_cell:
212 214 items.append(cell)
213 215 last_cell = cell
214 216 self._set_history(items)
215 217
216 218 def _handle_execute_result(self, msg):
217 219 """ Reimplemented for IPython-style "display hook".
218 220 """
219 221 self.log.debug("execute_result: %s", msg.get('content', ''))
220 222 if not self._hidden and self._is_from_this_session(msg):
221 223 self.flush_clearoutput()
222 224 content = msg['content']
223 225 prompt_number = content.get('execution_count', 0)
224 226 data = content['data']
225 227 if 'text/plain' in data:
226 228 self._append_plain_text(self.output_sep, True)
227 229 self._append_html(self._make_out_prompt(prompt_number), True)
228 230 text = data['text/plain']
229 231 # If the repr is multiline, make sure we start on a new line,
230 232 # so that its lines are aligned.
231 233 if "\n" in text and not self.output_sep.endswith("\n"):
232 234 self._append_plain_text('\n', True)
233 235 self._append_plain_text(text + self.output_sep2, True)
234 236
235 237 def _handle_display_data(self, msg):
236 238 """ The base handler for the ``display_data`` message.
237 239 """
238 240 self.log.debug("display: %s", msg.get('content', ''))
239 241 # For now, we don't display data from other frontends, but we
240 242 # eventually will as this allows all frontends to monitor the display
241 243 # data. But we need to figure out how to handle this in the GUI.
242 244 if not self._hidden and self._is_from_this_session(msg):
243 245 self.flush_clearoutput()
244 246 source = msg['content']['source']
245 247 data = msg['content']['data']
246 248 metadata = msg['content']['metadata']
247 249 # In the regular IPythonWidget, we simply print the plain text
248 250 # representation.
249 251 if 'text/plain' in data:
250 252 text = data['text/plain']
251 253 self._append_plain_text(text, True)
252 254 # This newline seems to be needed for text and html output.
253 255 self._append_plain_text(u'\n', True)
254 256
255 257 def _handle_kernel_info_reply(self, rep):
256 """ Handle kernel info replies.
257 """
258 """Handle kernel info replies."""
259 content = rep['content']
258 260 if not self._guiref_loaded:
259 if rep['content'].get('language') == 'python':
261 if content.get('language') == 'python':
260 262 self._load_guiref_magic()
261 263 self._guiref_loaded = True
264
265 self.kernel_banner = content.get('banner', '')
266 if self._starting:
267 # finish handling started channels
268 self._starting = False
269 super(IPythonWidget, self)._started_channels()
262 270
263 271 def _started_channels(self):
264 272 """Reimplemented to make a history request and load %guiref."""
265 super(IPythonWidget, self)._started_channels()
266
273 self._starting = True
267 274 # The reply will trigger %guiref load provided language=='python'
268 275 self.kernel_client.kernel_info()
269 276
270 277 self.kernel_client.shell_channel.history(hist_access_type='tail',
271 278 n=1000)
272 279
273 def _started_kernel(self):
274 """Load %guiref when the kernel starts (if channels are also started).
275
276 Principally triggered by kernel restart.
277 """
278 if self.kernel_client.shell_channel is not None:
279 self._load_guiref_magic()
280
281 280 def _load_guiref_magic(self):
282 281 """Load %guiref magic."""
283 282 self.kernel_client.shell_channel.execute('\n'.join([
284 283 "try:",
285 284 " _usage",
286 285 "except:",
287 286 " from IPython.core import usage as _usage",
288 287 " get_ipython().register_magic_function(_usage.page_guiref, 'line', 'guiref')",
289 288 " del _usage",
290 289 ]), silent=True)
291 290
292 291 #---------------------------------------------------------------------------
293 292 # 'ConsoleWidget' public interface
294 293 #---------------------------------------------------------------------------
295 294
296 295 #---------------------------------------------------------------------------
297 296 # 'FrontendWidget' public interface
298 297 #---------------------------------------------------------------------------
299 298
300 299 def execute_file(self, path, hidden=False):
301 300 """ Reimplemented to use the 'run' magic.
302 301 """
303 302 # Use forward slashes on Windows to avoid escaping each separator.
304 303 if sys.platform == 'win32':
305 304 path = os.path.normpath(path).replace('\\', '/')
306 305
307 306 # Perhaps we should not be using %run directly, but while we
308 307 # are, it is necessary to quote or escape filenames containing spaces
309 308 # or quotes.
310 309
311 310 # In earlier code here, to minimize escaping, we sometimes quoted the
312 311 # filename with single quotes. But to do this, this code must be
313 312 # platform-aware, because run uses shlex rather than python string
314 313 # parsing, so that:
315 314 # * In Win: single quotes can be used in the filename without quoting,
316 315 # and we cannot use single quotes to quote the filename.
317 316 # * In *nix: we can escape double quotes in a double quoted filename,
318 317 # but can't escape single quotes in a single quoted filename.
319 318
320 319 # So to keep this code non-platform-specific and simple, we now only
321 320 # use double quotes to quote filenames, and escape when needed:
322 321 if ' ' in path or "'" in path or '"' in path:
323 322 path = '"%s"' % path.replace('"', '\\"')
324 323 self.execute('%%run %s' % path, hidden=hidden)
325 324
326 325 #---------------------------------------------------------------------------
327 326 # 'FrontendWidget' protected interface
328 327 #---------------------------------------------------------------------------
329 328
330 329 def _process_execute_error(self, msg):
331 330 """ Reimplemented for IPython-style traceback formatting.
332 331 """
333 332 content = msg['content']
334 333 traceback = '\n'.join(content['traceback']) + '\n'
335 334 if False:
336 335 # FIXME: For now, tracebacks come as plain text, so we can't use
337 336 # the html renderer yet. Once we refactor ultratb to produce
338 337 # properly styled tracebacks, this branch should be the default
339 338 traceback = traceback.replace(' ', '&nbsp;')
340 339 traceback = traceback.replace('\n', '<br/>')
341 340
342 341 ename = content['ename']
343 342 ename_styled = '<span class="error">%s</span>' % ename
344 343 traceback = traceback.replace(ename, ename_styled)
345 344
346 345 self._append_html(traceback)
347 346 else:
348 347 # This is the fallback for now, using plain text with ansi escapes
349 348 self._append_plain_text(traceback)
350 349
351 350 def _process_execute_payload(self, item):
352 351 """ Reimplemented to dispatch payloads to handler methods.
353 352 """
354 353 handler = self._payload_handlers.get(item['source'])
355 354 if handler is None:
356 355 # We have no handler for this type of payload, simply ignore it
357 356 return False
358 357 else:
359 358 handler(item)
360 359 return True
361 360
362 361 def _show_interpreter_prompt(self, number=None):
363 362 """ Reimplemented for IPython-style prompts.
364 363 """
365 364 # If a number was not specified, make a prompt number request.
366 365 if number is None:
367 366 msg_id = self.kernel_client.shell_channel.execute('', silent=True)
368 367 info = self._ExecutionRequest(msg_id, 'prompt')
369 368 self._request_info['execute'][msg_id] = info
370 369 return
371 370
372 371 # Show a new prompt and save information about it so that it can be
373 372 # updated later if the prompt number turns out to be wrong.
374 373 self._prompt_sep = self.input_sep
375 374 self._show_prompt(self._make_in_prompt(number), html=True)
376 375 block = self._control.document().lastBlock()
377 376 length = len(self._prompt)
378 377 self._previous_prompt_obj = self._PromptBlock(block, length, number)
379 378
380 379 # Update continuation prompt to reflect (possibly) new prompt length.
381 380 self._set_continuation_prompt(
382 381 self._make_continuation_prompt(self._prompt), html=True)
383 382
384 383 def _show_interpreter_prompt_for_reply(self, msg):
385 384 """ Reimplemented for IPython-style prompts.
386 385 """
387 386 # Update the old prompt number if necessary.
388 387 content = msg['content']
389 388 # abort replies do not have any keys:
390 389 if content['status'] == 'aborted':
391 390 if self._previous_prompt_obj:
392 391 previous_prompt_number = self._previous_prompt_obj.number
393 392 else:
394 393 previous_prompt_number = 0
395 394 else:
396 395 previous_prompt_number = content['execution_count']
397 396 if self._previous_prompt_obj and \
398 397 self._previous_prompt_obj.number != previous_prompt_number:
399 398 block = self._previous_prompt_obj.block
400 399
401 400 # Make sure the prompt block has not been erased.
402 401 if block.isValid() and block.text():
403 402
404 403 # Remove the old prompt and insert a new prompt.
405 404 cursor = QtGui.QTextCursor(block)
406 405 cursor.movePosition(QtGui.QTextCursor.Right,
407 406 QtGui.QTextCursor.KeepAnchor,
408 407 self._previous_prompt_obj.length)
409 408 prompt = self._make_in_prompt(previous_prompt_number)
410 409 self._prompt = self._insert_html_fetching_plain_text(
411 410 cursor, prompt)
412 411
413 412 # When the HTML is inserted, Qt blows away the syntax
414 413 # highlighting for the line, so we need to rehighlight it.
415 414 self._highlighter.rehighlightBlock(cursor.block())
416 415
417 416 self._previous_prompt_obj = None
418 417
419 418 # Show a new prompt with the kernel's estimated prompt number.
420 419 self._show_interpreter_prompt(previous_prompt_number + 1)
421 420
422 421 #---------------------------------------------------------------------------
423 422 # 'IPythonWidget' interface
424 423 #---------------------------------------------------------------------------
425 424
426 425 def set_default_style(self, colors='lightbg'):
427 426 """ Sets the widget style to the class defaults.
428 427
429 428 Parameters
430 429 ----------
431 430 colors : str, optional (default lightbg)
432 431 Whether to use the default IPython light background or dark
433 432 background or B&W style.
434 433 """
435 434 colors = colors.lower()
436 435 if colors=='lightbg':
437 436 self.style_sheet = styles.default_light_style_sheet
438 437 self.syntax_style = styles.default_light_syntax_style
439 438 elif colors=='linux':
440 439 self.style_sheet = styles.default_dark_style_sheet
441 440 self.syntax_style = styles.default_dark_syntax_style
442 441 elif colors=='nocolor':
443 442 self.style_sheet = styles.default_bw_style_sheet
444 443 self.syntax_style = styles.default_bw_syntax_style
445 444 else:
446 445 raise KeyError("No such color scheme: %s"%colors)
447 446
448 447 #---------------------------------------------------------------------------
449 448 # 'IPythonWidget' protected interface
450 449 #---------------------------------------------------------------------------
451 450
452 451 def _edit(self, filename, line=None):
453 452 """ Opens a Python script for editing.
454 453
455 454 Parameters
456 455 ----------
457 456 filename : str
458 457 A path to a local system file.
459 458
460 459 line : int, optional
461 460 A line of interest in the file.
462 461 """
463 462 if self.custom_edit:
464 463 self.custom_edit_requested.emit(filename, line)
465 464 elif not self.editor:
466 465 self._append_plain_text('No default editor available.\n'
467 466 'Specify a GUI text editor in the `IPythonWidget.editor` '
468 467 'configurable to enable the %edit magic')
469 468 else:
470 469 try:
471 470 filename = '"%s"' % filename
472 471 if line and self.editor_line:
473 472 command = self.editor_line.format(filename=filename,
474 473 line=line)
475 474 else:
476 475 try:
477 476 command = self.editor.format()
478 477 except KeyError:
479 478 command = self.editor.format(filename=filename)
480 479 else:
481 480 command += ' ' + filename
482 481 except KeyError:
483 482 self._append_plain_text('Invalid editor command.\n')
484 483 else:
485 484 try:
486 485 Popen(command, shell=True)
487 486 except OSError:
488 487 msg = 'Opening editor with command "%s" failed.\n'
489 488 self._append_plain_text(msg % command)
490 489
491 490 def _make_in_prompt(self, number):
492 491 """ Given a prompt number, returns an HTML In prompt.
493 492 """
494 493 try:
495 494 body = self.in_prompt % number
496 495 except TypeError:
497 496 # allow in_prompt to leave out number, e.g. '>>> '
498 497 body = self.in_prompt
499 498 return '<span class="in-prompt">%s</span>' % body
500 499
501 500 def _make_continuation_prompt(self, prompt):
502 501 """ Given a plain text version of an In prompt, returns an HTML
503 502 continuation prompt.
504 503 """
505 504 end_chars = '...: '
506 505 space_count = len(prompt.lstrip('\n')) - len(end_chars)
507 506 body = '&nbsp;' * space_count + end_chars
508 507 return '<span class="in-prompt">%s</span>' % body
509 508
510 509 def _make_out_prompt(self, number):
511 510 """ Given a prompt number, returns an HTML Out prompt.
512 511 """
513 512 body = self.out_prompt % number
514 513 return '<span class="out-prompt">%s</span>' % body
515 514
516 515 #------ Payload handlers --------------------------------------------------
517 516
518 517 # Payload handlers with a generic interface: each takes the opaque payload
519 518 # dict, unpacks it and calls the underlying functions with the necessary
520 519 # arguments.
521 520
522 521 def _handle_payload_edit(self, item):
523 522 self._edit(item['filename'], item['line_number'])
524 523
525 524 def _handle_payload_exit(self, item):
526 525 self._keep_kernel_on_exit = item['keepkernel']
527 526 self.exit_requested.emit(self)
528 527
529 528 def _handle_payload_next_input(self, item):
530 529 self.input_buffer = item['text']
531 530
532 531 def _handle_payload_page(self, item):
533 532 # Since the plain text widget supports only a very small subset of HTML
534 533 # and we have no control over the HTML source, we only page HTML
535 534 # payloads in the rich text widget.
536 535 if item['html'] and self.kind == 'rich':
537 536 self._page(item['html'], html=True)
538 537 else:
539 538 self._page(item['text'], html=False)
540 539
541 540 #------ Trait change handlers --------------------------------------------
542 541
543 542 def _style_sheet_changed(self):
544 543 """ Set the style sheets of the underlying widgets.
545 544 """
546 545 self.setStyleSheet(self.style_sheet)
547 546 if self._control is not None:
548 547 self._control.document().setDefaultStyleSheet(self.style_sheet)
549 548 bg_color = self._control.palette().window().color()
550 549 self._ansi_processor.set_background_color(bg_color)
551 550
552 551 if self._page_control is not None:
553 552 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
554 553
555 554
556 555
557 556 def _syntax_style_changed(self):
558 557 """ Set the style for the syntax highlighter.
559 558 """
560 559 if self._highlighter is None:
561 560 # ignore premature calls
562 561 return
563 562 if self.syntax_style:
564 563 self._highlighter.set_style(self.syntax_style)
565 564 else:
566 565 self._highlighter.set_style_sheet(self.style_sheet)
567 566
568 567 #------ Trait default initializers -----------------------------------------
569 568
570 569 def _banner_default(self):
571 from IPython.core.usage import default_gui_banner
572 return default_gui_banner
570 return "IPython QtConsole {version}\n".format(version=version)
@@ -1,513 +1,533 b''
1 1 # -*- coding: utf-8 -*-
2 2 """terminal client to the IPython kernel"""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 from __future__ import print_function
8 8
9 9 import base64
10 10 import bdb
11 11 import signal
12 12 import os
13 13 import sys
14 14 import time
15 15 import subprocess
16 16 from getpass import getpass
17 17 from io import BytesIO
18 18
19 19 try:
20 20 from queue import Empty # Py 3
21 21 except ImportError:
22 22 from Queue import Empty # Py 2
23 23
24 24 from IPython.core import page
25 from IPython.core import release
25 26 from IPython.utils.warn import warn, error
26 27 from IPython.utils import io
27 28 from IPython.utils.py3compat import string_types, input
28 29 from IPython.utils.traitlets import List, Enum, Any, Instance, Unicode, Float
29 30 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
30 31
31 32 from IPython.terminal.interactiveshell import TerminalInteractiveShell
32 33 from IPython.terminal.console.completer import ZMQCompleter
33 34
34 35
35 36 class ZMQTerminalInteractiveShell(TerminalInteractiveShell):
36 37 """A subclass of TerminalInteractiveShell that uses the 0MQ kernel"""
37 38 _executing = False
38 39 _execution_state = Unicode('')
39 40 _pending_clearoutput = False
41 kernel_banner = Unicode('')
40 42 kernel_timeout = Float(60, config=True,
41 43 help="""Timeout for giving up on a kernel (in seconds).
42 44
43 45 On first connect and restart, the console tests whether the
44 46 kernel is running and responsive by sending kernel_info_requests.
45 47 This sets the timeout in seconds for how long the kernel can take
46 48 before being presumed dead.
47 49 """
48 50 )
49 51
50 52 image_handler = Enum(('PIL', 'stream', 'tempfile', 'callable'),
51 53 config=True, help=
52 54 """
53 55 Handler for image type output. This is useful, for example,
54 56 when connecting to the kernel in which pylab inline backend is
55 57 activated. There are four handlers defined. 'PIL': Use
56 58 Python Imaging Library to popup image; 'stream': Use an
57 59 external program to show the image. Image will be fed into
58 60 the STDIN of the program. You will need to configure
59 61 `stream_image_handler`; 'tempfile': Use an external program to
60 62 show the image. Image will be saved in a temporally file and
61 63 the program is called with the temporally file. You will need
62 64 to configure `tempfile_image_handler`; 'callable': You can set
63 65 any Python callable which is called with the image data. You
64 66 will need to configure `callable_image_handler`.
65 67 """
66 68 )
67 69
68 70 stream_image_handler = List(config=True, help=
69 71 """
70 72 Command to invoke an image viewer program when you are using
71 73 'stream' image handler. This option is a list of string where
72 74 the first element is the command itself and reminders are the
73 75 options for the command. Raw image data is given as STDIN to
74 76 the program.
75 77 """
76 78 )
77 79
78 80 tempfile_image_handler = List(config=True, help=
79 81 """
80 82 Command to invoke an image viewer program when you are using
81 83 'tempfile' image handler. This option is a list of string
82 84 where the first element is the command itself and reminders
83 85 are the options for the command. You can use {file} and
84 86 {format} in the string to represent the location of the
85 87 generated image file and image format.
86 88 """
87 89 )
88 90
89 91 callable_image_handler = Any(config=True, help=
90 92 """
91 93 Callable object called via 'callable' image handler with one
92 94 argument, `data`, which is `msg["content"]["data"]` where
93 95 `msg` is the message from iopub channel. For exmaple, you can
94 96 find base64 encoded PNG data as `data['image/png']`.
95 97 """
96 98 )
97 99
98 100 mime_preference = List(
99 101 default_value=['image/png', 'image/jpeg', 'image/svg+xml'],
100 102 config=True, allow_none=False, help=
101 103 """
102 104 Preferred object representation MIME type in order. First
103 105 matched MIME type will be used.
104 106 """
105 107 )
106 108
107 109 manager = Instance('IPython.kernel.KernelManager')
108 110 client = Instance('IPython.kernel.KernelClient')
109 111 def _client_changed(self, name, old, new):
110 112 self.session_id = new.session.session
111 113 session_id = Unicode()
112 114
113 115 def init_completer(self):
114 116 """Initialize the completion machinery.
115 117
116 118 This creates completion machinery that can be used by client code,
117 119 either interactively in-process (typically triggered by the readline
118 120 library), programmatically (such as in test suites) or out-of-process
119 121 (typically over the network by remote frontends).
120 122 """
121 123 from IPython.core.completerlib import (module_completer,
122 124 magic_run_completer, cd_completer)
123 125
124 126 self.Completer = ZMQCompleter(self, self.client, config=self.config)
125 127
126 128
127 129 self.set_hook('complete_command', module_completer, str_key = 'import')
128 130 self.set_hook('complete_command', module_completer, str_key = 'from')
129 131 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
130 132 self.set_hook('complete_command', cd_completer, str_key = '%cd')
131 133
132 134 # Only configure readline if we truly are using readline. IPython can
133 135 # do tab-completion over the network, in GUIs, etc, where readline
134 136 # itself may be absent
135 137 if self.has_readline:
136 138 self.set_readline_completer()
137 139
138 140 def ask_exit(self):
139 141 super(ZMQTerminalInteractiveShell, self).ask_exit()
140 142 if self.exit_now and self.manager:
141 143 self.client.shutdown()
142 144
143 145 def run_cell(self, cell, store_history=True):
144 146 """Run a complete IPython cell.
145 147
146 148 Parameters
147 149 ----------
148 150 cell : str
149 151 The code (including IPython code such as %magic functions) to run.
150 152 store_history : bool
151 153 If True, the raw and translated cell will be stored in IPython's
152 154 history. For user code calling back into IPython's machinery, this
153 155 should be set to False.
154 156 """
155 157 if (not cell) or cell.isspace():
156 158 # pressing enter flushes any pending display
157 159 self.handle_iopub()
158 160 return
159 161
160 162 if cell.strip() == 'exit':
161 163 # explicitly handle 'exit' command
162 164 return self.ask_exit()
163 165
164 166 # flush stale replies, which could have been ignored, due to missed heartbeats
165 167 while self.client.shell_channel.msg_ready():
166 168 self.client.shell_channel.get_msg()
167 169 # shell_channel.execute takes 'hidden', which is the inverse of store_hist
168 170 msg_id = self.client.shell_channel.execute(cell, not store_history)
169 171
170 172 # first thing is wait for any side effects (output, stdin, etc.)
171 173 self._executing = True
172 174 self._execution_state = "busy"
173 175 while self._execution_state != 'idle' and self.client.is_alive():
174 176 try:
175 177 self.handle_input_request(msg_id, timeout=0.05)
176 178 except Empty:
177 179 # display intermediate print statements, etc.
178 180 self.handle_iopub(msg_id)
179 181
180 182 # after all of that is done, wait for the execute reply
181 183 while self.client.is_alive():
182 184 try:
183 185 self.handle_execute_reply(msg_id, timeout=0.05)
184 186 except Empty:
185 187 pass
186 188 else:
187 189 break
188 190 self._executing = False
189 191
190 192 #-----------------
191 193 # message handlers
192 194 #-----------------
193 195
194 196 def handle_execute_reply(self, msg_id, timeout=None):
195 197 msg = self.client.shell_channel.get_msg(block=False, timeout=timeout)
196 198 if msg["parent_header"].get("msg_id", None) == msg_id:
197 199
198 200 self.handle_iopub(msg_id)
199 201
200 202 content = msg["content"]
201 203 status = content['status']
202 204
203 205 if status == 'aborted':
204 206 self.write('Aborted\n')
205 207 return
206 208 elif status == 'ok':
207 209 # print execution payloads as well:
208 210 for item in content["payload"]:
209 211 text = item.get('text', None)
210 212 if text:
211 213 page.page(text)
212 214
213 215 elif status == 'error':
214 216 for frame in content["traceback"]:
215 217 print(frame, file=io.stderr)
216 218
217 219 self.execution_count = int(content["execution_count"] + 1)
218 220
219 221
220 222 def handle_iopub(self, msg_id=''):
221 223 """Process messages on the IOPub channel
222 224
223 225 This method consumes and processes messages on the IOPub channel,
224 226 such as stdout, stderr, execute_result and status.
225 227
226 228 It only displays output that is caused by this session.
227 229 """
228 230 while self.client.iopub_channel.msg_ready():
229 231 sub_msg = self.client.iopub_channel.get_msg()
230 232 msg_type = sub_msg['header']['msg_type']
231 233 parent = sub_msg["parent_header"]
232 234
233 235 if parent.get("session", self.session_id) == self.session_id:
234 236 if msg_type == 'status':
235 237 self._execution_state = sub_msg["content"]["execution_state"]
236 238 elif msg_type == 'stream':
237 239 if sub_msg["content"]["name"] == "stdout":
238 240 if self._pending_clearoutput:
239 241 print("\r", file=io.stdout, end="")
240 242 self._pending_clearoutput = False
241 243 print(sub_msg["content"]["data"], file=io.stdout, end="")
242 244 io.stdout.flush()
243 245 elif sub_msg["content"]["name"] == "stderr" :
244 246 if self._pending_clearoutput:
245 247 print("\r", file=io.stderr, end="")
246 248 self._pending_clearoutput = False
247 249 print(sub_msg["content"]["data"], file=io.stderr, end="")
248 250 io.stderr.flush()
249 251
250 252 elif msg_type == 'execute_result':
251 253 if self._pending_clearoutput:
252 254 print("\r", file=io.stdout, end="")
253 255 self._pending_clearoutput = False
254 256 self.execution_count = int(sub_msg["content"]["execution_count"])
255 257 format_dict = sub_msg["content"]["data"]
256 258 self.handle_rich_data(format_dict)
257 259 # taken from DisplayHook.__call__:
258 260 hook = self.displayhook
259 261 hook.start_displayhook()
260 262 hook.write_output_prompt()
261 263 hook.write_format_data(format_dict)
262 264 hook.log_output(format_dict)
263 265 hook.finish_displayhook()
264 266
265 267 elif msg_type == 'display_data':
266 268 data = sub_msg["content"]["data"]
267 269 handled = self.handle_rich_data(data)
268 270 if not handled:
269 271 # if it was an image, we handled it by now
270 272 if 'text/plain' in data:
271 273 print(data['text/plain'])
272 274
273 275 elif msg_type == 'clear_output':
274 276 if sub_msg["content"]["wait"]:
275 277 self._pending_clearoutput = True
276 278 else:
277 279 print("\r", file=io.stdout, end="")
278 280
279 281 _imagemime = {
280 282 'image/png': 'png',
281 283 'image/jpeg': 'jpeg',
282 284 'image/svg+xml': 'svg',
283 285 }
284 286
285 287 def handle_rich_data(self, data):
286 288 for mime in self.mime_preference:
287 289 if mime in data and mime in self._imagemime:
288 290 self.handle_image(data, mime)
289 291 return True
290 292
291 293 def handle_image(self, data, mime):
292 294 handler = getattr(
293 295 self, 'handle_image_{0}'.format(self.image_handler), None)
294 296 if handler:
295 297 handler(data, mime)
296 298
297 299 def handle_image_PIL(self, data, mime):
298 300 if mime not in ('image/png', 'image/jpeg'):
299 301 return
300 302 import PIL.Image
301 303 raw = base64.decodestring(data[mime].encode('ascii'))
302 304 img = PIL.Image.open(BytesIO(raw))
303 305 img.show()
304 306
305 307 def handle_image_stream(self, data, mime):
306 308 raw = base64.decodestring(data[mime].encode('ascii'))
307 309 imageformat = self._imagemime[mime]
308 310 fmt = dict(format=imageformat)
309 311 args = [s.format(**fmt) for s in self.stream_image_handler]
310 312 with open(os.devnull, 'w') as devnull:
311 313 proc = subprocess.Popen(
312 314 args, stdin=subprocess.PIPE,
313 315 stdout=devnull, stderr=devnull)
314 316 proc.communicate(raw)
315 317
316 318 def handle_image_tempfile(self, data, mime):
317 319 raw = base64.decodestring(data[mime].encode('ascii'))
318 320 imageformat = self._imagemime[mime]
319 321 filename = 'tmp.{0}'.format(imageformat)
320 322 with NamedFileInTemporaryDirectory(filename) as f, \
321 323 open(os.devnull, 'w') as devnull:
322 324 f.write(raw)
323 325 f.flush()
324 326 fmt = dict(file=f.name, format=imageformat)
325 327 args = [s.format(**fmt) for s in self.tempfile_image_handler]
326 328 subprocess.call(args, stdout=devnull, stderr=devnull)
327 329
328 330 def handle_image_callable(self, data, mime):
329 331 self.callable_image_handler(data)
330 332
331 333 def handle_input_request(self, msg_id, timeout=0.1):
332 334 """ Method to capture raw_input
333 335 """
334 336 req = self.client.stdin_channel.get_msg(timeout=timeout)
335 337 # in case any iopub came while we were waiting:
336 338 self.handle_iopub(msg_id)
337 339 if msg_id == req["parent_header"].get("msg_id"):
338 340 # wrap SIGINT handler
339 341 real_handler = signal.getsignal(signal.SIGINT)
340 342 def double_int(sig,frame):
341 343 # call real handler (forwards sigint to kernel),
342 344 # then raise local interrupt, stopping local raw_input
343 345 real_handler(sig,frame)
344 346 raise KeyboardInterrupt
345 347 signal.signal(signal.SIGINT, double_int)
346 348 content = req['content']
347 349 read = getpass if content.get('password', False) else input
348 350 try:
349 351 raw_data = read(content["prompt"])
350 352 except EOFError:
351 353 # turn EOFError into EOF character
352 354 raw_data = '\x04'
353 355 except KeyboardInterrupt:
354 356 sys.stdout.write('\n')
355 357 return
356 358 finally:
357 359 # restore SIGINT handler
358 360 signal.signal(signal.SIGINT, real_handler)
359 361
360 362 # only send stdin reply if there *was not* another request
361 363 # or execution finished while we were reading.
362 364 if not (self.client.stdin_channel.msg_ready() or self.client.shell_channel.msg_ready()):
363 365 self.client.stdin_channel.input(raw_data)
364 366
365 367 def mainloop(self, display_banner=False):
366 368 while True:
367 369 try:
368 370 self.interact(display_banner=display_banner)
369 #self.interact_with_readline()
371 #self.interact_with_readline()
370 372 # XXX for testing of a readline-decoupled repl loop, call
371 373 # interact_with_readline above
372 374 break
373 375 except KeyboardInterrupt:
374 376 # this should not be necessary, but KeyboardInterrupt
375 377 # handling seems rather unpredictable...
376 378 self.write("\nKeyboardInterrupt in interact()\n")
377 379
380 def _banner1_default(self):
381 return "IPython Console {version}\n".format(version=release.version)
382
383 def compute_banner(self):
384 super(ZMQTerminalInteractiveShell, self).compute_banner()
385 if self.client and not self.kernel_banner:
386 msg_id = self.client.kernel_info()
387 while True:
388 try:
389 reply = self.client.get_shell_msg(timeout=1)
390 except Empty:
391 break
392 else:
393 if reply['parent_header'].get('msg_id') == msg_id:
394 self.kernel_banner = reply['content'].get('banner', '')
395 break
396 self.banner += self.kernel_banner
397
378 398 def wait_for_kernel(self, timeout=None):
379 399 """method to wait for a kernel to be ready"""
380 400 tic = time.time()
381 401 self.client.hb_channel.unpause()
382 402 while True:
383 403 msg_id = self.client.kernel_info()
384 404 reply = None
385 405 while True:
386 406 try:
387 407 reply = self.client.get_shell_msg(timeout=1)
388 408 except Empty:
389 409 break
390 410 else:
391 411 if reply['parent_header'].get('msg_id') == msg_id:
392 412 return True
393 413 if timeout is not None \
394 414 and (time.time() - tic) > timeout \
395 415 and not self.client.hb_channel.is_beating():
396 416 # heart failed
397 417 return False
398 418 return True
399 419
400 420 def interact(self, display_banner=None):
401 421 """Closely emulate the interactive Python console."""
402 422
403 423 # batch run -> do not interact
404 424 if self.exit_now:
405 425 return
406 426
407 427 if display_banner is None:
408 428 display_banner = self.display_banner
409 429
410 430 if isinstance(display_banner, string_types):
411 431 self.show_banner(display_banner)
412 432 elif display_banner:
413 433 self.show_banner()
414 434
415 435 more = False
416 436
417 437 # run a non-empty no-op, so that we don't get a prompt until
418 438 # we know the kernel is ready. This keeps the connection
419 439 # message above the first prompt.
420 440 if not self.wait_for_kernel(self.kernel_timeout):
421 441 error("Kernel did not respond\n")
422 442 return
423 443
424 444 if self.has_readline:
425 445 self.readline_startup_hook(self.pre_readline)
426 446 hlen_b4_cell = self.readline.get_current_history_length()
427 447 else:
428 448 hlen_b4_cell = 0
429 449 # exit_now is set by a call to %Exit or %Quit, through the
430 450 # ask_exit callback.
431 451
432 452 while not self.exit_now:
433 453 if not self.client.is_alive():
434 454 # kernel died, prompt for action or exit
435 455
436 456 action = "restart" if self.manager else "wait for restart"
437 457 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
438 458 if ans:
439 459 if self.manager:
440 460 self.manager.restart_kernel(True)
441 461 self.wait_for_kernel(self.kernel_timeout)
442 462 else:
443 463 self.exit_now = True
444 464 continue
445 465 try:
446 466 # protect prompt block from KeyboardInterrupt
447 467 # when sitting on ctrl-C
448 468 self.hooks.pre_prompt_hook()
449 469 if more:
450 470 try:
451 471 prompt = self.prompt_manager.render('in2')
452 472 except Exception:
453 473 self.showtraceback()
454 474 if self.autoindent:
455 475 self.rl_do_indent = True
456 476
457 477 else:
458 478 try:
459 479 prompt = self.separate_in + self.prompt_manager.render('in')
460 480 except Exception:
461 481 self.showtraceback()
462 482
463 483 line = self.raw_input(prompt)
464 484 if self.exit_now:
465 485 # quick exit on sys.std[in|out] close
466 486 break
467 487 if self.autoindent:
468 488 self.rl_do_indent = False
469 489
470 490 except KeyboardInterrupt:
471 491 #double-guard against keyboardinterrupts during kbdint handling
472 492 try:
473 493 self.write('\nKeyboardInterrupt\n')
474 494 source_raw = self.input_splitter.raw_reset()
475 495 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
476 496 more = False
477 497 except KeyboardInterrupt:
478 498 pass
479 499 except EOFError:
480 500 if self.autoindent:
481 501 self.rl_do_indent = False
482 502 if self.has_readline:
483 503 self.readline_startup_hook(None)
484 504 self.write('\n')
485 505 self.exit()
486 506 except bdb.BdbQuit:
487 507 warn('The Python debugger has exited with a BdbQuit exception.\n'
488 508 'Because of how pdb handles the stack, it is impossible\n'
489 509 'for IPython to properly format this particular exception.\n'
490 510 'IPython will resume normal operation.')
491 511 except:
492 512 # exceptions here are VERY RARE, but they can be triggered
493 513 # asynchronously by signal handlers, for example.
494 514 self.showtraceback()
495 515 else:
496 516 try:
497 517 self.input_splitter.push(line)
498 518 more = self.input_splitter.push_accepts_more()
499 519 except SyntaxError:
500 520 # Run the code directly - run_cell takes care of displaying
501 521 # the exception.
502 522 more = False
503 523 if (self.SyntaxTB.last_syntax_error and
504 524 self.autoedit_syntax):
505 525 self.edit_syntax_error()
506 526 if not more:
507 527 source_raw = self.input_splitter.raw_reset()
508 528 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
509 529 self.run_cell(source_raw)
510 530
511 531
512 532 # Turn off the exit flag, so the mainloop can be restarted if desired
513 533 self.exit_now = False
General Comments 0
You need to be logged in to leave comments. Login now