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