##// END OF EJS Templates
Don't log complete contents of history replies, even in debug
MinRK -
Show More
@@ -1,559 +1,559 b''
1 """ A FrontendWidget that emulates the interface of the console IPython and
1 """ A FrontendWidget that emulates the interface of the console IPython and
2 supports the additional functionality provided by the IPython kernel.
2 supports the additional functionality provided by the IPython kernel.
3 """
3 """
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Imports
6 # Imports
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8
8
9 # Standard library imports
9 # Standard library imports
10 from collections import namedtuple
10 from collections import namedtuple
11 import os.path
11 import os.path
12 import re
12 import re
13 from subprocess import Popen
13 from subprocess import Popen
14 import sys
14 import sys
15 import time
15 import time
16 from textwrap import dedent
16 from textwrap import dedent
17
17
18 # System library imports
18 # System library imports
19 from IPython.external.qt import QtCore, QtGui
19 from IPython.external.qt import QtCore, QtGui
20
20
21 # Local imports
21 # Local imports
22 from IPython.core.inputsplitter import IPythonInputSplitter, \
22 from IPython.core.inputsplitter import IPythonInputSplitter, \
23 transform_ipy_prompt
23 transform_ipy_prompt
24 from IPython.utils.traitlets import Bool, Unicode
24 from IPython.utils.traitlets import Bool, Unicode
25 from frontend_widget import FrontendWidget
25 from frontend_widget import FrontendWidget
26 import styles
26 import styles
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Constants
29 # Constants
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31
31
32 # Default strings to build and display input and output prompts (and separators
32 # Default strings to build and display input and output prompts (and separators
33 # in between)
33 # in between)
34 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
34 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
35 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
35 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
36 default_input_sep = '\n'
36 default_input_sep = '\n'
37 default_output_sep = ''
37 default_output_sep = ''
38 default_output_sep2 = ''
38 default_output_sep2 = ''
39
39
40 # Base path for most payload sources.
40 # Base path for most payload sources.
41 zmq_shell_source = 'IPython.zmq.zmqshell.ZMQInteractiveShell'
41 zmq_shell_source = 'IPython.zmq.zmqshell.ZMQInteractiveShell'
42
42
43 if sys.platform.startswith('win'):
43 if sys.platform.startswith('win'):
44 default_editor = 'notepad'
44 default_editor = 'notepad'
45 else:
45 else:
46 default_editor = ''
46 default_editor = ''
47
47
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49 # IPythonWidget class
49 # IPythonWidget class
50 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51
51
52 class IPythonWidget(FrontendWidget):
52 class IPythonWidget(FrontendWidget):
53 """ A FrontendWidget for an IPython kernel.
53 """ A FrontendWidget for an IPython kernel.
54 """
54 """
55
55
56 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
56 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
57 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
57 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
58 # settings.
58 # settings.
59 custom_edit = Bool(False)
59 custom_edit = Bool(False)
60 custom_edit_requested = QtCore.Signal(object, object)
60 custom_edit_requested = QtCore.Signal(object, object)
61
61
62 editor = Unicode(default_editor, config=True,
62 editor = Unicode(default_editor, config=True,
63 help="""
63 help="""
64 A command for invoking a system text editor. If the string contains a
64 A command for invoking a system text editor. If the string contains a
65 {filename} format specifier, it will be used. Otherwise, the filename
65 {filename} format specifier, it will be used. Otherwise, the filename
66 will be appended to the end the command.
66 will be appended to the end the command.
67 """)
67 """)
68
68
69 editor_line = Unicode(config=True,
69 editor_line = Unicode(config=True,
70 help="""
70 help="""
71 The editor command to use when a specific line number is requested. The
71 The editor command to use when a specific line number is requested. The
72 string should contain two format specifiers: {line} and {filename}. If
72 string should contain two format specifiers: {line} and {filename}. If
73 this parameter is not specified, the line number option to the %edit
73 this parameter is not specified, the line number option to the %edit
74 magic will be ignored.
74 magic will be ignored.
75 """)
75 """)
76
76
77 style_sheet = Unicode(config=True,
77 style_sheet = Unicode(config=True,
78 help="""
78 help="""
79 A CSS stylesheet. The stylesheet can contain classes for:
79 A CSS stylesheet. The stylesheet can contain classes for:
80 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
80 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
81 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
81 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
82 3. IPython: .error, .in-prompt, .out-prompt, etc
82 3. IPython: .error, .in-prompt, .out-prompt, etc
83 """)
83 """)
84
84
85 syntax_style = Unicode(config=True,
85 syntax_style = Unicode(config=True,
86 help="""
86 help="""
87 If not empty, use this Pygments style for syntax highlighting.
87 If not empty, use this Pygments style for syntax highlighting.
88 Otherwise, the style sheet is queried for Pygments style
88 Otherwise, the style sheet is queried for Pygments style
89 information.
89 information.
90 """)
90 """)
91
91
92 # Prompts.
92 # Prompts.
93 in_prompt = Unicode(default_in_prompt, config=True)
93 in_prompt = Unicode(default_in_prompt, config=True)
94 out_prompt = Unicode(default_out_prompt, config=True)
94 out_prompt = Unicode(default_out_prompt, config=True)
95 input_sep = Unicode(default_input_sep, config=True)
95 input_sep = Unicode(default_input_sep, config=True)
96 output_sep = Unicode(default_output_sep, config=True)
96 output_sep = Unicode(default_output_sep, config=True)
97 output_sep2 = Unicode(default_output_sep2, config=True)
97 output_sep2 = Unicode(default_output_sep2, config=True)
98
98
99 # FrontendWidget protected class variables.
99 # FrontendWidget protected class variables.
100 _input_splitter_class = IPythonInputSplitter
100 _input_splitter_class = IPythonInputSplitter
101 _transform_prompt = staticmethod(transform_ipy_prompt)
101 _transform_prompt = staticmethod(transform_ipy_prompt)
102
102
103 # IPythonWidget protected class variables.
103 # IPythonWidget protected class variables.
104 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
104 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
105 _payload_source_edit = zmq_shell_source + '.edit_magic'
105 _payload_source_edit = zmq_shell_source + '.edit_magic'
106 _payload_source_exit = zmq_shell_source + '.ask_exit'
106 _payload_source_exit = zmq_shell_source + '.ask_exit'
107 _payload_source_next_input = zmq_shell_source + '.set_next_input'
107 _payload_source_next_input = zmq_shell_source + '.set_next_input'
108 _payload_source_page = 'IPython.zmq.page.page'
108 _payload_source_page = 'IPython.zmq.page.page'
109 _retrying_history_request = False
109 _retrying_history_request = False
110
110
111 #---------------------------------------------------------------------------
111 #---------------------------------------------------------------------------
112 # 'object' interface
112 # 'object' interface
113 #---------------------------------------------------------------------------
113 #---------------------------------------------------------------------------
114
114
115 def __init__(self, *args, **kw):
115 def __init__(self, *args, **kw):
116 super(IPythonWidget, self).__init__(*args, **kw)
116 super(IPythonWidget, self).__init__(*args, **kw)
117
117
118 # IPythonWidget protected variables.
118 # IPythonWidget protected variables.
119 self._payload_handlers = {
119 self._payload_handlers = {
120 self._payload_source_edit : self._handle_payload_edit,
120 self._payload_source_edit : self._handle_payload_edit,
121 self._payload_source_exit : self._handle_payload_exit,
121 self._payload_source_exit : self._handle_payload_exit,
122 self._payload_source_page : self._handle_payload_page,
122 self._payload_source_page : self._handle_payload_page,
123 self._payload_source_next_input : self._handle_payload_next_input }
123 self._payload_source_next_input : self._handle_payload_next_input }
124 self._previous_prompt_obj = None
124 self._previous_prompt_obj = None
125 self._keep_kernel_on_exit = None
125 self._keep_kernel_on_exit = None
126
126
127 # Initialize widget styling.
127 # Initialize widget styling.
128 if self.style_sheet:
128 if self.style_sheet:
129 self._style_sheet_changed()
129 self._style_sheet_changed()
130 self._syntax_style_changed()
130 self._syntax_style_changed()
131 else:
131 else:
132 self.set_default_style()
132 self.set_default_style()
133
133
134 #---------------------------------------------------------------------------
134 #---------------------------------------------------------------------------
135 # 'BaseFrontendMixin' abstract interface
135 # 'BaseFrontendMixin' abstract interface
136 #---------------------------------------------------------------------------
136 #---------------------------------------------------------------------------
137
137
138 def _handle_complete_reply(self, rep):
138 def _handle_complete_reply(self, rep):
139 """ Reimplemented to support IPython's improved completion machinery.
139 """ Reimplemented to support IPython's improved completion machinery.
140 """
140 """
141 self.log.debug("complete: %s", rep.get('content', ''))
141 self.log.debug("complete: %s", rep.get('content', ''))
142 cursor = self._get_cursor()
142 cursor = self._get_cursor()
143 info = self._request_info.get('complete')
143 info = self._request_info.get('complete')
144 if info and info.id == rep['parent_header']['msg_id'] and \
144 if info and info.id == rep['parent_header']['msg_id'] and \
145 info.pos == cursor.position():
145 info.pos == cursor.position():
146 matches = rep['content']['matches']
146 matches = rep['content']['matches']
147 text = rep['content']['matched_text']
147 text = rep['content']['matched_text']
148 offset = len(text)
148 offset = len(text)
149
149
150 # Clean up matches with period and path separators if the matched
150 # Clean up matches with period and path separators if the matched
151 # text has not been transformed. This is done by truncating all
151 # text has not been transformed. This is done by truncating all
152 # but the last component and then suitably decreasing the offset
152 # but the last component and then suitably decreasing the offset
153 # between the current cursor position and the start of completion.
153 # between the current cursor position and the start of completion.
154 if len(matches) > 1 and matches[0][:offset] == text:
154 if len(matches) > 1 and matches[0][:offset] == text:
155 parts = re.split(r'[./\\]', text)
155 parts = re.split(r'[./\\]', text)
156 sep_count = len(parts) - 1
156 sep_count = len(parts) - 1
157 if sep_count:
157 if sep_count:
158 chop_length = sum(map(len, parts[:sep_count])) + sep_count
158 chop_length = sum(map(len, parts[:sep_count])) + sep_count
159 matches = [ match[chop_length:] for match in matches ]
159 matches = [ match[chop_length:] for match in matches ]
160 offset -= chop_length
160 offset -= chop_length
161
161
162 # Move the cursor to the start of the match and complete.
162 # Move the cursor to the start of the match and complete.
163 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
163 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
164 self._complete_with_items(cursor, matches)
164 self._complete_with_items(cursor, matches)
165
165
166 def _handle_execute_reply(self, msg):
166 def _handle_execute_reply(self, msg):
167 """ Reimplemented to support prompt requests.
167 """ Reimplemented to support prompt requests.
168 """
168 """
169 msg_id = msg['parent_header'].get('msg_id')
169 msg_id = msg['parent_header'].get('msg_id')
170 info = self._request_info['execute'].get(msg_id)
170 info = self._request_info['execute'].get(msg_id)
171 if info and info.kind == 'prompt':
171 if info and info.kind == 'prompt':
172 number = msg['content']['execution_count'] + 1
172 number = msg['content']['execution_count'] + 1
173 self._show_interpreter_prompt(number)
173 self._show_interpreter_prompt(number)
174 self._request_info['execute'].pop(msg_id)
174 self._request_info['execute'].pop(msg_id)
175 else:
175 else:
176 super(IPythonWidget, self)._handle_execute_reply(msg)
176 super(IPythonWidget, self)._handle_execute_reply(msg)
177
177
178 def _handle_history_reply(self, msg):
178 def _handle_history_reply(self, msg):
179 """ Implemented to handle history tail replies, which are only supported
179 """ Implemented to handle history tail replies, which are only supported
180 by the IPython kernel.
180 by the IPython kernel.
181 """
181 """
182 self.log.debug("history: %s", msg.get('content', ''))
183 content = msg['content']
182 content = msg['content']
184 if 'history' not in content:
183 if 'history' not in content:
185 self.log.error("History request failed: %r"%content)
184 self.log.error("History request failed: %r"%content)
186 if content.get('status', '') == 'aborted' and \
185 if content.get('status', '') == 'aborted' and \
187 not self._retrying_history_request:
186 not self._retrying_history_request:
188 # a *different* action caused this request to be aborted, so
187 # a *different* action caused this request to be aborted, so
189 # we should try again.
188 # we should try again.
190 self.log.error("Retrying aborted history request")
189 self.log.error("Retrying aborted history request")
191 # prevent multiple retries of aborted requests:
190 # prevent multiple retries of aborted requests:
192 self._retrying_history_request = True
191 self._retrying_history_request = True
193 # wait out the kernel's queue flush, which is currently timed at 0.1s
192 # wait out the kernel's queue flush, which is currently timed at 0.1s
194 time.sleep(0.25)
193 time.sleep(0.25)
195 self.kernel_manager.shell_channel.history(hist_access_type='tail',n=1000)
194 self.kernel_manager.shell_channel.history(hist_access_type='tail',n=1000)
196 else:
195 else:
197 self._retrying_history_request = False
196 self._retrying_history_request = False
198 return
197 return
199 # reset retry flag
198 # reset retry flag
200 self._retrying_history_request = False
199 self._retrying_history_request = False
201 history_items = content['history']
200 history_items = content['history']
201 self.log.debug("Received history reply with %i entries", len(history_items))
202 items = []
202 items = []
203 last_cell = u""
203 last_cell = u""
204 for _, _, cell in history_items:
204 for _, _, cell in history_items:
205 cell = cell.rstrip()
205 cell = cell.rstrip()
206 if cell != last_cell:
206 if cell != last_cell:
207 items.append(cell)
207 items.append(cell)
208 last_cell = cell
208 last_cell = cell
209 self._set_history(items)
209 self._set_history(items)
210
210
211 def _handle_pyout(self, msg):
211 def _handle_pyout(self, msg):
212 """ Reimplemented for IPython-style "display hook".
212 """ Reimplemented for IPython-style "display hook".
213 """
213 """
214 self.log.debug("pyout: %s", msg.get('content', ''))
214 self.log.debug("pyout: %s", msg.get('content', ''))
215 if not self._hidden and self._is_from_this_session(msg):
215 if not self._hidden and self._is_from_this_session(msg):
216 content = msg['content']
216 content = msg['content']
217 prompt_number = content['execution_count']
217 prompt_number = content['execution_count']
218 data = content['data']
218 data = content['data']
219 if data.has_key('text/html'):
219 if data.has_key('text/html'):
220 self._append_plain_text(self.output_sep, True)
220 self._append_plain_text(self.output_sep, True)
221 self._append_html(self._make_out_prompt(prompt_number), True)
221 self._append_html(self._make_out_prompt(prompt_number), True)
222 html = data['text/html']
222 html = data['text/html']
223 self._append_plain_text('\n', True)
223 self._append_plain_text('\n', True)
224 self._append_html(html + self.output_sep2, True)
224 self._append_html(html + self.output_sep2, True)
225 elif data.has_key('text/plain'):
225 elif data.has_key('text/plain'):
226 self._append_plain_text(self.output_sep, True)
226 self._append_plain_text(self.output_sep, True)
227 self._append_html(self._make_out_prompt(prompt_number), True)
227 self._append_html(self._make_out_prompt(prompt_number), True)
228 text = data['text/plain']
228 text = data['text/plain']
229 # If the repr is multiline, make sure we start on a new line,
229 # If the repr is multiline, make sure we start on a new line,
230 # so that its lines are aligned.
230 # so that its lines are aligned.
231 if "\n" in text and not self.output_sep.endswith("\n"):
231 if "\n" in text and not self.output_sep.endswith("\n"):
232 self._append_plain_text('\n', True)
232 self._append_plain_text('\n', True)
233 self._append_plain_text(text + self.output_sep2, True)
233 self._append_plain_text(text + self.output_sep2, True)
234
234
235 def _handle_display_data(self, msg):
235 def _handle_display_data(self, msg):
236 """ The base handler for the ``display_data`` message.
236 """ The base handler for the ``display_data`` message.
237 """
237 """
238 self.log.debug("display: %s", msg.get('content', ''))
238 self.log.debug("display: %s", msg.get('content', ''))
239 # For now, we don't display data from other frontends, but we
239 # For now, we don't display data from other frontends, but we
240 # eventually will as this allows all frontends to monitor the display
240 # 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.
241 # 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):
242 if not self._hidden and self._is_from_this_session(msg):
243 source = msg['content']['source']
243 source = msg['content']['source']
244 data = msg['content']['data']
244 data = msg['content']['data']
245 metadata = msg['content']['metadata']
245 metadata = msg['content']['metadata']
246 # In the regular IPythonWidget, we simply print the plain text
246 # In the regular IPythonWidget, we simply print the plain text
247 # representation.
247 # representation.
248 if data.has_key('text/html'):
248 if data.has_key('text/html'):
249 html = data['text/html']
249 html = data['text/html']
250 self._append_html(html, True)
250 self._append_html(html, True)
251 elif data.has_key('text/plain'):
251 elif data.has_key('text/plain'):
252 text = data['text/plain']
252 text = data['text/plain']
253 self._append_plain_text(text, True)
253 self._append_plain_text(text, True)
254 # This newline seems to be needed for text and html output.
254 # This newline seems to be needed for text and html output.
255 self._append_plain_text(u'\n', True)
255 self._append_plain_text(u'\n', True)
256
256
257 def _started_channels(self):
257 def _started_channels(self):
258 """ Reimplemented to make a history request.
258 """ Reimplemented to make a history request.
259 """
259 """
260 super(IPythonWidget, self)._started_channels()
260 super(IPythonWidget, self)._started_channels()
261 self.kernel_manager.shell_channel.history(hist_access_type='tail',
261 self.kernel_manager.shell_channel.history(hist_access_type='tail',
262 n=1000)
262 n=1000)
263 #---------------------------------------------------------------------------
263 #---------------------------------------------------------------------------
264 # 'ConsoleWidget' public interface
264 # 'ConsoleWidget' public interface
265 #---------------------------------------------------------------------------
265 #---------------------------------------------------------------------------
266
266
267 #---------------------------------------------------------------------------
267 #---------------------------------------------------------------------------
268 # 'FrontendWidget' public interface
268 # 'FrontendWidget' public interface
269 #---------------------------------------------------------------------------
269 #---------------------------------------------------------------------------
270
270
271 def execute_file(self, path, hidden=False):
271 def execute_file(self, path, hidden=False):
272 """ Reimplemented to use the 'run' magic.
272 """ Reimplemented to use the 'run' magic.
273 """
273 """
274 # Use forward slashes on Windows to avoid escaping each separator.
274 # Use forward slashes on Windows to avoid escaping each separator.
275 if sys.platform == 'win32':
275 if sys.platform == 'win32':
276 path = os.path.normpath(path).replace('\\', '/')
276 path = os.path.normpath(path).replace('\\', '/')
277
277
278 # Perhaps we should not be using %run directly, but while we
278 # Perhaps we should not be using %run directly, but while we
279 # are, it is necessary to quote or escape filenames containing spaces
279 # are, it is necessary to quote or escape filenames containing spaces
280 # or quotes.
280 # or quotes.
281
281
282 # In earlier code here, to minimize escaping, we sometimes quoted the
282 # In earlier code here, to minimize escaping, we sometimes quoted the
283 # filename with single quotes. But to do this, this code must be
283 # filename with single quotes. But to do this, this code must be
284 # platform-aware, because run uses shlex rather than python string
284 # platform-aware, because run uses shlex rather than python string
285 # parsing, so that:
285 # parsing, so that:
286 # * In Win: single quotes can be used in the filename without quoting,
286 # * In Win: single quotes can be used in the filename without quoting,
287 # and we cannot use single quotes to quote the filename.
287 # and we cannot use single quotes to quote the filename.
288 # * In *nix: we can escape double quotes in a double quoted filename,
288 # * In *nix: we can escape double quotes in a double quoted filename,
289 # but can't escape single quotes in a single quoted filename.
289 # but can't escape single quotes in a single quoted filename.
290
290
291 # So to keep this code non-platform-specific and simple, we now only
291 # So to keep this code non-platform-specific and simple, we now only
292 # use double quotes to quote filenames, and escape when needed:
292 # use double quotes to quote filenames, and escape when needed:
293 if ' ' in path or "'" in path or '"' in path:
293 if ' ' in path or "'" in path or '"' in path:
294 path = '"%s"' % path.replace('"', '\\"')
294 path = '"%s"' % path.replace('"', '\\"')
295 self.execute('%%run %s' % path, hidden=hidden)
295 self.execute('%%run %s' % path, hidden=hidden)
296
296
297 #---------------------------------------------------------------------------
297 #---------------------------------------------------------------------------
298 # 'FrontendWidget' protected interface
298 # 'FrontendWidget' protected interface
299 #---------------------------------------------------------------------------
299 #---------------------------------------------------------------------------
300
300
301 def _complete(self):
301 def _complete(self):
302 """ Reimplemented to support IPython's improved completion machinery.
302 """ Reimplemented to support IPython's improved completion machinery.
303 """
303 """
304 # We let the kernel split the input line, so we *always* send an empty
304 # We let the kernel split the input line, so we *always* send an empty
305 # text field. Readline-based frontends do get a real text field which
305 # text field. Readline-based frontends do get a real text field which
306 # they can use.
306 # they can use.
307 text = ''
307 text = ''
308
308
309 # Send the completion request to the kernel
309 # Send the completion request to the kernel
310 msg_id = self.kernel_manager.shell_channel.complete(
310 msg_id = self.kernel_manager.shell_channel.complete(
311 text, # text
311 text, # text
312 self._get_input_buffer_cursor_line(), # line
312 self._get_input_buffer_cursor_line(), # line
313 self._get_input_buffer_cursor_column(), # cursor_pos
313 self._get_input_buffer_cursor_column(), # cursor_pos
314 self.input_buffer) # block
314 self.input_buffer) # block
315 pos = self._get_cursor().position()
315 pos = self._get_cursor().position()
316 info = self._CompletionRequest(msg_id, pos)
316 info = self._CompletionRequest(msg_id, pos)
317 self._request_info['complete'] = info
317 self._request_info['complete'] = info
318
318
319 def _process_execute_error(self, msg):
319 def _process_execute_error(self, msg):
320 """ Reimplemented for IPython-style traceback formatting.
320 """ Reimplemented for IPython-style traceback formatting.
321 """
321 """
322 content = msg['content']
322 content = msg['content']
323 traceback = '\n'.join(content['traceback']) + '\n'
323 traceback = '\n'.join(content['traceback']) + '\n'
324 if False:
324 if False:
325 # FIXME: For now, tracebacks come as plain text, so we can't use
325 # FIXME: For now, tracebacks come as plain text, so we can't use
326 # the html renderer yet. Once we refactor ultratb to produce
326 # the html renderer yet. Once we refactor ultratb to produce
327 # properly styled tracebacks, this branch should be the default
327 # properly styled tracebacks, this branch should be the default
328 traceback = traceback.replace(' ', '&nbsp;')
328 traceback = traceback.replace(' ', '&nbsp;')
329 traceback = traceback.replace('\n', '<br/>')
329 traceback = traceback.replace('\n', '<br/>')
330
330
331 ename = content['ename']
331 ename = content['ename']
332 ename_styled = '<span class="error">%s</span>' % ename
332 ename_styled = '<span class="error">%s</span>' % ename
333 traceback = traceback.replace(ename, ename_styled)
333 traceback = traceback.replace(ename, ename_styled)
334
334
335 self._append_html(traceback)
335 self._append_html(traceback)
336 else:
336 else:
337 # This is the fallback for now, using plain text with ansi escapes
337 # This is the fallback for now, using plain text with ansi escapes
338 self._append_plain_text(traceback)
338 self._append_plain_text(traceback)
339
339
340 def _process_execute_payload(self, item):
340 def _process_execute_payload(self, item):
341 """ Reimplemented to dispatch payloads to handler methods.
341 """ Reimplemented to dispatch payloads to handler methods.
342 """
342 """
343 handler = self._payload_handlers.get(item['source'])
343 handler = self._payload_handlers.get(item['source'])
344 if handler is None:
344 if handler is None:
345 # We have no handler for this type of payload, simply ignore it
345 # We have no handler for this type of payload, simply ignore it
346 return False
346 return False
347 else:
347 else:
348 handler(item)
348 handler(item)
349 return True
349 return True
350
350
351 def _show_interpreter_prompt(self, number=None):
351 def _show_interpreter_prompt(self, number=None):
352 """ Reimplemented for IPython-style prompts.
352 """ Reimplemented for IPython-style prompts.
353 """
353 """
354 # If a number was not specified, make a prompt number request.
354 # If a number was not specified, make a prompt number request.
355 if number is None:
355 if number is None:
356 msg_id = self.kernel_manager.shell_channel.execute('', silent=True)
356 msg_id = self.kernel_manager.shell_channel.execute('', silent=True)
357 info = self._ExecutionRequest(msg_id, 'prompt')
357 info = self._ExecutionRequest(msg_id, 'prompt')
358 self._request_info['execute'][msg_id] = info
358 self._request_info['execute'][msg_id] = info
359 return
359 return
360
360
361 # Show a new prompt and save information about it so that it can be
361 # Show a new prompt and save information about it so that it can be
362 # updated later if the prompt number turns out to be wrong.
362 # updated later if the prompt number turns out to be wrong.
363 self._prompt_sep = self.input_sep
363 self._prompt_sep = self.input_sep
364 self._show_prompt(self._make_in_prompt(number), html=True)
364 self._show_prompt(self._make_in_prompt(number), html=True)
365 block = self._control.document().lastBlock()
365 block = self._control.document().lastBlock()
366 length = len(self._prompt)
366 length = len(self._prompt)
367 self._previous_prompt_obj = self._PromptBlock(block, length, number)
367 self._previous_prompt_obj = self._PromptBlock(block, length, number)
368
368
369 # Update continuation prompt to reflect (possibly) new prompt length.
369 # Update continuation prompt to reflect (possibly) new prompt length.
370 self._set_continuation_prompt(
370 self._set_continuation_prompt(
371 self._make_continuation_prompt(self._prompt), html=True)
371 self._make_continuation_prompt(self._prompt), html=True)
372
372
373 def _show_interpreter_prompt_for_reply(self, msg):
373 def _show_interpreter_prompt_for_reply(self, msg):
374 """ Reimplemented for IPython-style prompts.
374 """ Reimplemented for IPython-style prompts.
375 """
375 """
376 # Update the old prompt number if necessary.
376 # Update the old prompt number if necessary.
377 content = msg['content']
377 content = msg['content']
378 # abort replies do not have any keys:
378 # abort replies do not have any keys:
379 if content['status'] == 'aborted':
379 if content['status'] == 'aborted':
380 if self._previous_prompt_obj:
380 if self._previous_prompt_obj:
381 previous_prompt_number = self._previous_prompt_obj.number
381 previous_prompt_number = self._previous_prompt_obj.number
382 else:
382 else:
383 previous_prompt_number = 0
383 previous_prompt_number = 0
384 else:
384 else:
385 previous_prompt_number = content['execution_count']
385 previous_prompt_number = content['execution_count']
386 if self._previous_prompt_obj and \
386 if self._previous_prompt_obj and \
387 self._previous_prompt_obj.number != previous_prompt_number:
387 self._previous_prompt_obj.number != previous_prompt_number:
388 block = self._previous_prompt_obj.block
388 block = self._previous_prompt_obj.block
389
389
390 # Make sure the prompt block has not been erased.
390 # Make sure the prompt block has not been erased.
391 if block.isValid() and block.text():
391 if block.isValid() and block.text():
392
392
393 # Remove the old prompt and insert a new prompt.
393 # Remove the old prompt and insert a new prompt.
394 cursor = QtGui.QTextCursor(block)
394 cursor = QtGui.QTextCursor(block)
395 cursor.movePosition(QtGui.QTextCursor.Right,
395 cursor.movePosition(QtGui.QTextCursor.Right,
396 QtGui.QTextCursor.KeepAnchor,
396 QtGui.QTextCursor.KeepAnchor,
397 self._previous_prompt_obj.length)
397 self._previous_prompt_obj.length)
398 prompt = self._make_in_prompt(previous_prompt_number)
398 prompt = self._make_in_prompt(previous_prompt_number)
399 self._prompt = self._insert_html_fetching_plain_text(
399 self._prompt = self._insert_html_fetching_plain_text(
400 cursor, prompt)
400 cursor, prompt)
401
401
402 # When the HTML is inserted, Qt blows away the syntax
402 # When the HTML is inserted, Qt blows away the syntax
403 # highlighting for the line, so we need to rehighlight it.
403 # highlighting for the line, so we need to rehighlight it.
404 self._highlighter.rehighlightBlock(cursor.block())
404 self._highlighter.rehighlightBlock(cursor.block())
405
405
406 self._previous_prompt_obj = None
406 self._previous_prompt_obj = None
407
407
408 # Show a new prompt with the kernel's estimated prompt number.
408 # Show a new prompt with the kernel's estimated prompt number.
409 self._show_interpreter_prompt(previous_prompt_number + 1)
409 self._show_interpreter_prompt(previous_prompt_number + 1)
410
410
411 #---------------------------------------------------------------------------
411 #---------------------------------------------------------------------------
412 # 'IPythonWidget' interface
412 # 'IPythonWidget' interface
413 #---------------------------------------------------------------------------
413 #---------------------------------------------------------------------------
414
414
415 def set_default_style(self, colors='lightbg'):
415 def set_default_style(self, colors='lightbg'):
416 """ Sets the widget style to the class defaults.
416 """ Sets the widget style to the class defaults.
417
417
418 Parameters:
418 Parameters:
419 -----------
419 -----------
420 colors : str, optional (default lightbg)
420 colors : str, optional (default lightbg)
421 Whether to use the default IPython light background or dark
421 Whether to use the default IPython light background or dark
422 background or B&W style.
422 background or B&W style.
423 """
423 """
424 colors = colors.lower()
424 colors = colors.lower()
425 if colors=='lightbg':
425 if colors=='lightbg':
426 self.style_sheet = styles.default_light_style_sheet
426 self.style_sheet = styles.default_light_style_sheet
427 self.syntax_style = styles.default_light_syntax_style
427 self.syntax_style = styles.default_light_syntax_style
428 elif colors=='linux':
428 elif colors=='linux':
429 self.style_sheet = styles.default_dark_style_sheet
429 self.style_sheet = styles.default_dark_style_sheet
430 self.syntax_style = styles.default_dark_syntax_style
430 self.syntax_style = styles.default_dark_syntax_style
431 elif colors=='nocolor':
431 elif colors=='nocolor':
432 self.style_sheet = styles.default_bw_style_sheet
432 self.style_sheet = styles.default_bw_style_sheet
433 self.syntax_style = styles.default_bw_syntax_style
433 self.syntax_style = styles.default_bw_syntax_style
434 else:
434 else:
435 raise KeyError("No such color scheme: %s"%colors)
435 raise KeyError("No such color scheme: %s"%colors)
436
436
437 #---------------------------------------------------------------------------
437 #---------------------------------------------------------------------------
438 # 'IPythonWidget' protected interface
438 # 'IPythonWidget' protected interface
439 #---------------------------------------------------------------------------
439 #---------------------------------------------------------------------------
440
440
441 def _edit(self, filename, line=None):
441 def _edit(self, filename, line=None):
442 """ Opens a Python script for editing.
442 """ Opens a Python script for editing.
443
443
444 Parameters:
444 Parameters:
445 -----------
445 -----------
446 filename : str
446 filename : str
447 A path to a local system file.
447 A path to a local system file.
448
448
449 line : int, optional
449 line : int, optional
450 A line of interest in the file.
450 A line of interest in the file.
451 """
451 """
452 if self.custom_edit:
452 if self.custom_edit:
453 self.custom_edit_requested.emit(filename, line)
453 self.custom_edit_requested.emit(filename, line)
454 elif not self.editor:
454 elif not self.editor:
455 self._append_plain_text('No default editor available.\n'
455 self._append_plain_text('No default editor available.\n'
456 'Specify a GUI text editor in the `IPythonWidget.editor` '
456 'Specify a GUI text editor in the `IPythonWidget.editor` '
457 'configurable to enable the %edit magic')
457 'configurable to enable the %edit magic')
458 else:
458 else:
459 try:
459 try:
460 filename = '"%s"' % filename
460 filename = '"%s"' % filename
461 if line and self.editor_line:
461 if line and self.editor_line:
462 command = self.editor_line.format(filename=filename,
462 command = self.editor_line.format(filename=filename,
463 line=line)
463 line=line)
464 else:
464 else:
465 try:
465 try:
466 command = self.editor.format()
466 command = self.editor.format()
467 except KeyError:
467 except KeyError:
468 command = self.editor.format(filename=filename)
468 command = self.editor.format(filename=filename)
469 else:
469 else:
470 command += ' ' + filename
470 command += ' ' + filename
471 except KeyError:
471 except KeyError:
472 self._append_plain_text('Invalid editor command.\n')
472 self._append_plain_text('Invalid editor command.\n')
473 else:
473 else:
474 try:
474 try:
475 Popen(command, shell=True)
475 Popen(command, shell=True)
476 except OSError:
476 except OSError:
477 msg = 'Opening editor with command "%s" failed.\n'
477 msg = 'Opening editor with command "%s" failed.\n'
478 self._append_plain_text(msg % command)
478 self._append_plain_text(msg % command)
479
479
480 def _make_in_prompt(self, number):
480 def _make_in_prompt(self, number):
481 """ Given a prompt number, returns an HTML In prompt.
481 """ Given a prompt number, returns an HTML In prompt.
482 """
482 """
483 try:
483 try:
484 body = self.in_prompt % number
484 body = self.in_prompt % number
485 except TypeError:
485 except TypeError:
486 # allow in_prompt to leave out number, e.g. '>>> '
486 # allow in_prompt to leave out number, e.g. '>>> '
487 body = self.in_prompt
487 body = self.in_prompt
488 return '<span class="in-prompt">%s</span>' % body
488 return '<span class="in-prompt">%s</span>' % body
489
489
490 def _make_continuation_prompt(self, prompt):
490 def _make_continuation_prompt(self, prompt):
491 """ Given a plain text version of an In prompt, returns an HTML
491 """ Given a plain text version of an In prompt, returns an HTML
492 continuation prompt.
492 continuation prompt.
493 """
493 """
494 end_chars = '...: '
494 end_chars = '...: '
495 space_count = len(prompt.lstrip('\n')) - len(end_chars)
495 space_count = len(prompt.lstrip('\n')) - len(end_chars)
496 body = '&nbsp;' * space_count + end_chars
496 body = '&nbsp;' * space_count + end_chars
497 return '<span class="in-prompt">%s</span>' % body
497 return '<span class="in-prompt">%s</span>' % body
498
498
499 def _make_out_prompt(self, number):
499 def _make_out_prompt(self, number):
500 """ Given a prompt number, returns an HTML Out prompt.
500 """ Given a prompt number, returns an HTML Out prompt.
501 """
501 """
502 body = self.out_prompt % number
502 body = self.out_prompt % number
503 return '<span class="out-prompt">%s</span>' % body
503 return '<span class="out-prompt">%s</span>' % body
504
504
505 #------ Payload handlers --------------------------------------------------
505 #------ Payload handlers --------------------------------------------------
506
506
507 # Payload handlers with a generic interface: each takes the opaque payload
507 # Payload handlers with a generic interface: each takes the opaque payload
508 # dict, unpacks it and calls the underlying functions with the necessary
508 # dict, unpacks it and calls the underlying functions with the necessary
509 # arguments.
509 # arguments.
510
510
511 def _handle_payload_edit(self, item):
511 def _handle_payload_edit(self, item):
512 self._edit(item['filename'], item['line_number'])
512 self._edit(item['filename'], item['line_number'])
513
513
514 def _handle_payload_exit(self, item):
514 def _handle_payload_exit(self, item):
515 self._keep_kernel_on_exit = item['keepkernel']
515 self._keep_kernel_on_exit = item['keepkernel']
516 self.exit_requested.emit(self)
516 self.exit_requested.emit(self)
517
517
518 def _handle_payload_next_input(self, item):
518 def _handle_payload_next_input(self, item):
519 self.input_buffer = dedent(item['text'].rstrip())
519 self.input_buffer = dedent(item['text'].rstrip())
520
520
521 def _handle_payload_page(self, item):
521 def _handle_payload_page(self, item):
522 # Since the plain text widget supports only a very small subset of HTML
522 # Since the plain text widget supports only a very small subset of HTML
523 # and we have no control over the HTML source, we only page HTML
523 # and we have no control over the HTML source, we only page HTML
524 # payloads in the rich text widget.
524 # payloads in the rich text widget.
525 if item['html'] and self.kind == 'rich':
525 if item['html'] and self.kind == 'rich':
526 self._page(item['html'], html=True)
526 self._page(item['html'], html=True)
527 else:
527 else:
528 self._page(item['text'], html=False)
528 self._page(item['text'], html=False)
529
529
530 #------ Trait change handlers --------------------------------------------
530 #------ Trait change handlers --------------------------------------------
531
531
532 def _style_sheet_changed(self):
532 def _style_sheet_changed(self):
533 """ Set the style sheets of the underlying widgets.
533 """ Set the style sheets of the underlying widgets.
534 """
534 """
535 self.setStyleSheet(self.style_sheet)
535 self.setStyleSheet(self.style_sheet)
536 self._control.document().setDefaultStyleSheet(self.style_sheet)
536 self._control.document().setDefaultStyleSheet(self.style_sheet)
537 if self._page_control:
537 if self._page_control:
538 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
538 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
539
539
540 bg_color = self._control.palette().window().color()
540 bg_color = self._control.palette().window().color()
541 self._ansi_processor.set_background_color(bg_color)
541 self._ansi_processor.set_background_color(bg_color)
542
542
543
543
544 def _syntax_style_changed(self):
544 def _syntax_style_changed(self):
545 """ Set the style for the syntax highlighter.
545 """ Set the style for the syntax highlighter.
546 """
546 """
547 if self._highlighter is None:
547 if self._highlighter is None:
548 # ignore premature calls
548 # ignore premature calls
549 return
549 return
550 if self.syntax_style:
550 if self.syntax_style:
551 self._highlighter.set_style(self.syntax_style)
551 self._highlighter.set_style(self.syntax_style)
552 else:
552 else:
553 self._highlighter.set_style_sheet(self.style_sheet)
553 self._highlighter.set_style_sheet(self.style_sheet)
554
554
555 #------ Trait default initializers -----------------------------------------
555 #------ Trait default initializers -----------------------------------------
556
556
557 def _banner_default(self):
557 def _banner_default(self):
558 from IPython.core.usage import default_gui_banner
558 from IPython.core.usage import default_gui_banner
559 return default_gui_banner
559 return default_gui_banner
@@ -1,651 +1,652 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """A simple interactive kernel that talks to a frontend over 0MQ.
2 """A simple interactive kernel that talks to a frontend over 0MQ.
3
3
4 Things to do:
4 Things to do:
5
5
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 call set_parent on all the PUB objects with the message about to be executed.
7 call set_parent on all the PUB objects with the message about to be executed.
8 * Implement random port and security key logic.
8 * Implement random port and security key logic.
9 * Implement control messages.
9 * Implement control messages.
10 * Implement event loop and poll version.
10 * Implement event loop and poll version.
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Standard library imports.
18 # Standard library imports.
19 import __builtin__
19 import __builtin__
20 import atexit
20 import atexit
21 import sys
21 import sys
22 import time
22 import time
23 import traceback
23 import traceback
24 import logging
24 import logging
25 from signal import (
25 from signal import (
26 signal, default_int_handler, SIGINT, SIG_IGN
26 signal, default_int_handler, SIGINT, SIG_IGN
27 )
27 )
28 # System library imports.
28 # System library imports.
29 import zmq
29 import zmq
30
30
31 # Local imports.
31 # Local imports.
32 from IPython.core import pylabtools
32 from IPython.core import pylabtools
33 from IPython.config.configurable import Configurable
33 from IPython.config.configurable import Configurable
34 from IPython.config.application import boolean_flag, catch_config_error
34 from IPython.config.application import boolean_flag, catch_config_error
35 from IPython.core.application import ProfileDir
35 from IPython.core.application import ProfileDir
36 from IPython.core.error import StdinNotImplementedError
36 from IPython.core.error import StdinNotImplementedError
37 from IPython.core.shellapp import (
37 from IPython.core.shellapp import (
38 InteractiveShellApp, shell_flags, shell_aliases
38 InteractiveShellApp, shell_flags, shell_aliases
39 )
39 )
40 from IPython.utils import io
40 from IPython.utils import io
41 from IPython.utils import py3compat
41 from IPython.utils import py3compat
42 from IPython.utils.jsonutil import json_clean
42 from IPython.utils.jsonutil import json_clean
43 from IPython.utils.traitlets import (
43 from IPython.utils.traitlets import (
44 Any, Instance, Float, Dict, CaselessStrEnum
44 Any, Instance, Float, Dict, CaselessStrEnum
45 )
45 )
46
46
47 from entry_point import base_launch_kernel
47 from entry_point import base_launch_kernel
48 from kernelapp import KernelApp, kernel_flags, kernel_aliases
48 from kernelapp import KernelApp, kernel_flags, kernel_aliases
49 from session import Session, Message
49 from session import Session, Message
50 from zmqshell import ZMQInteractiveShell
50 from zmqshell import ZMQInteractiveShell
51
51
52
52
53 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
54 # Main kernel class
54 # Main kernel class
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56
56
57 class Kernel(Configurable):
57 class Kernel(Configurable):
58
58
59 #---------------------------------------------------------------------------
59 #---------------------------------------------------------------------------
60 # Kernel interface
60 # Kernel interface
61 #---------------------------------------------------------------------------
61 #---------------------------------------------------------------------------
62
62
63 # attribute to override with a GUI
63 # attribute to override with a GUI
64 eventloop = Any(None)
64 eventloop = Any(None)
65
65
66 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
66 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
67 session = Instance(Session)
67 session = Instance(Session)
68 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
68 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
69 shell_socket = Instance('zmq.Socket')
69 shell_socket = Instance('zmq.Socket')
70 iopub_socket = Instance('zmq.Socket')
70 iopub_socket = Instance('zmq.Socket')
71 stdin_socket = Instance('zmq.Socket')
71 stdin_socket = Instance('zmq.Socket')
72 log = Instance(logging.Logger)
72 log = Instance(logging.Logger)
73
73
74 # Private interface
74 # Private interface
75
75
76 # Time to sleep after flushing the stdout/err buffers in each execute
76 # Time to sleep after flushing the stdout/err buffers in each execute
77 # cycle. While this introduces a hard limit on the minimal latency of the
77 # cycle. While this introduces a hard limit on the minimal latency of the
78 # execute cycle, it helps prevent output synchronization problems for
78 # execute cycle, it helps prevent output synchronization problems for
79 # clients.
79 # clients.
80 # Units are in seconds. The minimum zmq latency on local host is probably
80 # Units are in seconds. The minimum zmq latency on local host is probably
81 # ~150 microseconds, set this to 500us for now. We may need to increase it
81 # ~150 microseconds, set this to 500us for now. We may need to increase it
82 # a little if it's not enough after more interactive testing.
82 # a little if it's not enough after more interactive testing.
83 _execute_sleep = Float(0.0005, config=True)
83 _execute_sleep = Float(0.0005, config=True)
84
84
85 # Frequency of the kernel's event loop.
85 # Frequency of the kernel's event loop.
86 # Units are in seconds, kernel subclasses for GUI toolkits may need to
86 # Units are in seconds, kernel subclasses for GUI toolkits may need to
87 # adapt to milliseconds.
87 # adapt to milliseconds.
88 _poll_interval = Float(0.05, config=True)
88 _poll_interval = Float(0.05, config=True)
89
89
90 # If the shutdown was requested over the network, we leave here the
90 # If the shutdown was requested over the network, we leave here the
91 # necessary reply message so it can be sent by our registered atexit
91 # necessary reply message so it can be sent by our registered atexit
92 # handler. This ensures that the reply is only sent to clients truly at
92 # handler. This ensures that the reply is only sent to clients truly at
93 # the end of our shutdown process (which happens after the underlying
93 # the end of our shutdown process (which happens after the underlying
94 # IPython shell's own shutdown).
94 # IPython shell's own shutdown).
95 _shutdown_message = None
95 _shutdown_message = None
96
96
97 # This is a dict of port number that the kernel is listening on. It is set
97 # This is a dict of port number that the kernel is listening on. It is set
98 # by record_ports and used by connect_request.
98 # by record_ports and used by connect_request.
99 _recorded_ports = Dict()
99 _recorded_ports = Dict()
100
100
101
101
102
102
103 def __init__(self, **kwargs):
103 def __init__(self, **kwargs):
104 super(Kernel, self).__init__(**kwargs)
104 super(Kernel, self).__init__(**kwargs)
105
105
106 # Before we even start up the shell, register *first* our exit handlers
106 # Before we even start up the shell, register *first* our exit handlers
107 # so they come before the shell's
107 # so they come before the shell's
108 atexit.register(self._at_shutdown)
108 atexit.register(self._at_shutdown)
109
109
110 # Initialize the InteractiveShell subclass
110 # Initialize the InteractiveShell subclass
111 self.shell = ZMQInteractiveShell.instance(config=self.config,
111 self.shell = ZMQInteractiveShell.instance(config=self.config,
112 profile_dir = self.profile_dir,
112 profile_dir = self.profile_dir,
113 )
113 )
114 self.shell.displayhook.session = self.session
114 self.shell.displayhook.session = self.session
115 self.shell.displayhook.pub_socket = self.iopub_socket
115 self.shell.displayhook.pub_socket = self.iopub_socket
116 self.shell.display_pub.session = self.session
116 self.shell.display_pub.session = self.session
117 self.shell.display_pub.pub_socket = self.iopub_socket
117 self.shell.display_pub.pub_socket = self.iopub_socket
118
118
119 # TMP - hack while developing
119 # TMP - hack while developing
120 self.shell._reply_content = None
120 self.shell._reply_content = None
121
121
122 # Build dict of handlers for message types
122 # Build dict of handlers for message types
123 msg_types = [ 'execute_request', 'complete_request',
123 msg_types = [ 'execute_request', 'complete_request',
124 'object_info_request', 'history_request',
124 'object_info_request', 'history_request',
125 'connect_request', 'shutdown_request']
125 'connect_request', 'shutdown_request']
126 self.handlers = {}
126 self.handlers = {}
127 for msg_type in msg_types:
127 for msg_type in msg_types:
128 self.handlers[msg_type] = getattr(self, msg_type)
128 self.handlers[msg_type] = getattr(self, msg_type)
129
129
130 def do_one_iteration(self):
130 def do_one_iteration(self):
131 """Do one iteration of the kernel's evaluation loop.
131 """Do one iteration of the kernel's evaluation loop.
132 """
132 """
133 try:
133 try:
134 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
134 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
135 except Exception:
135 except Exception:
136 self.log.warn("Invalid Message:", exc_info=True)
136 self.log.warn("Invalid Message:", exc_info=True)
137 return
137 return
138 if msg is None:
138 if msg is None:
139 return
139 return
140
140
141 msg_type = msg['header']['msg_type']
141 msg_type = msg['header']['msg_type']
142
142
143 # This assert will raise in versions of zeromq 2.0.7 and lesser.
143 # This assert will raise in versions of zeromq 2.0.7 and lesser.
144 # We now require 2.0.8 or above, so we can uncomment for safety.
144 # We now require 2.0.8 or above, so we can uncomment for safety.
145 # print(ident,msg, file=sys.__stdout__)
145 # print(ident,msg, file=sys.__stdout__)
146 assert ident is not None, "Missing message part."
146 assert ident is not None, "Missing message part."
147
147
148 # Print some info about this message and leave a '--->' marker, so it's
148 # Print some info about this message and leave a '--->' marker, so it's
149 # easier to trace visually the message chain when debugging. Each
149 # easier to trace visually the message chain when debugging. Each
150 # handler prints its message at the end.
150 # handler prints its message at the end.
151 self.log.debug('\n*** MESSAGE TYPE:'+str(msg_type)+'***')
151 self.log.debug('\n*** MESSAGE TYPE:'+str(msg_type)+'***')
152 self.log.debug(' Content: '+str(msg['content'])+'\n --->\n ')
152 self.log.debug(' Content: '+str(msg['content'])+'\n --->\n ')
153
153
154 # Find and call actual handler for message
154 # Find and call actual handler for message
155 handler = self.handlers.get(msg_type, None)
155 handler = self.handlers.get(msg_type, None)
156 if handler is None:
156 if handler is None:
157 self.log.error("UNKNOWN MESSAGE TYPE:" +str(msg))
157 self.log.error("UNKNOWN MESSAGE TYPE:" +str(msg))
158 else:
158 else:
159 handler(ident, msg)
159 handler(ident, msg)
160
160
161 # Check whether we should exit, in case the incoming message set the
161 # Check whether we should exit, in case the incoming message set the
162 # exit flag on
162 # exit flag on
163 if self.shell.exit_now:
163 if self.shell.exit_now:
164 self.log.debug('\nExiting IPython kernel...')
164 self.log.debug('\nExiting IPython kernel...')
165 # We do a normal, clean exit, which allows any actions registered
165 # We do a normal, clean exit, which allows any actions registered
166 # via atexit (such as history saving) to take place.
166 # via atexit (such as history saving) to take place.
167 sys.exit(0)
167 sys.exit(0)
168
168
169
169
170 def start(self):
170 def start(self):
171 """ Start the kernel main loop.
171 """ Start the kernel main loop.
172 """
172 """
173 # a KeyboardInterrupt (SIGINT) can occur on any python statement, so
173 # a KeyboardInterrupt (SIGINT) can occur on any python statement, so
174 # let's ignore (SIG_IGN) them until we're in a place to handle them properly
174 # let's ignore (SIG_IGN) them until we're in a place to handle them properly
175 signal(SIGINT,SIG_IGN)
175 signal(SIGINT,SIG_IGN)
176 poller = zmq.Poller()
176 poller = zmq.Poller()
177 poller.register(self.shell_socket, zmq.POLLIN)
177 poller.register(self.shell_socket, zmq.POLLIN)
178 # loop while self.eventloop has not been overridden
178 # loop while self.eventloop has not been overridden
179 while self.eventloop is None:
179 while self.eventloop is None:
180 try:
180 try:
181 # scale by extra factor of 10, because there is no
181 # scale by extra factor of 10, because there is no
182 # reason for this to be anything less than ~ 0.1s
182 # reason for this to be anything less than ~ 0.1s
183 # since it is a real poller and will respond
183 # since it is a real poller and will respond
184 # to events immediately
184 # to events immediately
185
185
186 # double nested try/except, to properly catch KeyboardInterrupt
186 # double nested try/except, to properly catch KeyboardInterrupt
187 # due to pyzmq Issue #130
187 # due to pyzmq Issue #130
188 try:
188 try:
189 poller.poll(10*1000*self._poll_interval)
189 poller.poll(10*1000*self._poll_interval)
190 # restore raising of KeyboardInterrupt
190 # restore raising of KeyboardInterrupt
191 signal(SIGINT, default_int_handler)
191 signal(SIGINT, default_int_handler)
192 self.do_one_iteration()
192 self.do_one_iteration()
193 except:
193 except:
194 raise
194 raise
195 finally:
195 finally:
196 # prevent raising of KeyboardInterrupt
196 # prevent raising of KeyboardInterrupt
197 signal(SIGINT,SIG_IGN)
197 signal(SIGINT,SIG_IGN)
198 except KeyboardInterrupt:
198 except KeyboardInterrupt:
199 # Ctrl-C shouldn't crash the kernel
199 # Ctrl-C shouldn't crash the kernel
200 io.raw_print("KeyboardInterrupt caught in kernel")
200 io.raw_print("KeyboardInterrupt caught in kernel")
201 # stop ignoring sigint, now that we are out of our own loop,
201 # stop ignoring sigint, now that we are out of our own loop,
202 # we don't want to prevent future code from handling it
202 # we don't want to prevent future code from handling it
203 signal(SIGINT, default_int_handler)
203 signal(SIGINT, default_int_handler)
204 if self.eventloop is not None:
204 if self.eventloop is not None:
205 try:
205 try:
206 self.eventloop(self)
206 self.eventloop(self)
207 except KeyboardInterrupt:
207 except KeyboardInterrupt:
208 # Ctrl-C shouldn't crash the kernel
208 # Ctrl-C shouldn't crash the kernel
209 io.raw_print("KeyboardInterrupt caught in kernel")
209 io.raw_print("KeyboardInterrupt caught in kernel")
210
210
211
211
212 def record_ports(self, ports):
212 def record_ports(self, ports):
213 """Record the ports that this kernel is using.
213 """Record the ports that this kernel is using.
214
214
215 The creator of the Kernel instance must call this methods if they
215 The creator of the Kernel instance must call this methods if they
216 want the :meth:`connect_request` method to return the port numbers.
216 want the :meth:`connect_request` method to return the port numbers.
217 """
217 """
218 self._recorded_ports = ports
218 self._recorded_ports = ports
219
219
220 #---------------------------------------------------------------------------
220 #---------------------------------------------------------------------------
221 # Kernel request handlers
221 # Kernel request handlers
222 #---------------------------------------------------------------------------
222 #---------------------------------------------------------------------------
223
223
224 def _publish_pyin(self, code, parent):
224 def _publish_pyin(self, code, parent):
225 """Publish the code request on the pyin stream."""
225 """Publish the code request on the pyin stream."""
226
226
227 self.session.send(self.iopub_socket, u'pyin', {u'code':code},
227 self.session.send(self.iopub_socket, u'pyin', {u'code':code},
228 parent=parent)
228 parent=parent)
229
229
230 def execute_request(self, ident, parent):
230 def execute_request(self, ident, parent):
231
231
232 self.session.send(self.iopub_socket,
232 self.session.send(self.iopub_socket,
233 u'status',
233 u'status',
234 {u'execution_state':u'busy'},
234 {u'execution_state':u'busy'},
235 parent=parent )
235 parent=parent )
236
236
237 try:
237 try:
238 content = parent[u'content']
238 content = parent[u'content']
239 code = content[u'code']
239 code = content[u'code']
240 silent = content[u'silent']
240 silent = content[u'silent']
241 except:
241 except:
242 self.log.error("Got bad msg: ")
242 self.log.error("Got bad msg: ")
243 self.log.error(str(Message(parent)))
243 self.log.error(str(Message(parent)))
244 return
244 return
245
245
246 shell = self.shell # we'll need this a lot here
246 shell = self.shell # we'll need this a lot here
247
247
248 # Replace raw_input. Note that is not sufficient to replace
248 # Replace raw_input. Note that is not sufficient to replace
249 # raw_input in the user namespace.
249 # raw_input in the user namespace.
250 if content.get('allow_stdin', False):
250 if content.get('allow_stdin', False):
251 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
251 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
252 else:
252 else:
253 raw_input = lambda prompt='' : self._no_raw_input()
253 raw_input = lambda prompt='' : self._no_raw_input()
254
254
255 if py3compat.PY3:
255 if py3compat.PY3:
256 __builtin__.input = raw_input
256 __builtin__.input = raw_input
257 else:
257 else:
258 __builtin__.raw_input = raw_input
258 __builtin__.raw_input = raw_input
259
259
260 # Set the parent message of the display hook and out streams.
260 # Set the parent message of the display hook and out streams.
261 shell.displayhook.set_parent(parent)
261 shell.displayhook.set_parent(parent)
262 shell.display_pub.set_parent(parent)
262 shell.display_pub.set_parent(parent)
263 sys.stdout.set_parent(parent)
263 sys.stdout.set_parent(parent)
264 sys.stderr.set_parent(parent)
264 sys.stderr.set_parent(parent)
265
265
266 # Re-broadcast our input for the benefit of listening clients, and
266 # Re-broadcast our input for the benefit of listening clients, and
267 # start computing output
267 # start computing output
268 if not silent:
268 if not silent:
269 self._publish_pyin(code, parent)
269 self._publish_pyin(code, parent)
270
270
271 reply_content = {}
271 reply_content = {}
272 try:
272 try:
273 if silent:
273 if silent:
274 # run_code uses 'exec' mode, so no displayhook will fire, and it
274 # run_code uses 'exec' mode, so no displayhook will fire, and it
275 # doesn't call logging or history manipulations. Print
275 # doesn't call logging or history manipulations. Print
276 # statements in that code will obviously still execute.
276 # statements in that code will obviously still execute.
277 shell.run_code(code)
277 shell.run_code(code)
278 else:
278 else:
279 # FIXME: the shell calls the exception handler itself.
279 # FIXME: the shell calls the exception handler itself.
280 shell.run_cell(code, store_history=True)
280 shell.run_cell(code, store_history=True)
281 except:
281 except:
282 status = u'error'
282 status = u'error'
283 # FIXME: this code right now isn't being used yet by default,
283 # FIXME: this code right now isn't being used yet by default,
284 # because the run_cell() call above directly fires off exception
284 # because the run_cell() call above directly fires off exception
285 # reporting. This code, therefore, is only active in the scenario
285 # reporting. This code, therefore, is only active in the scenario
286 # where runlines itself has an unhandled exception. We need to
286 # where runlines itself has an unhandled exception. We need to
287 # uniformize this, for all exception construction to come from a
287 # uniformize this, for all exception construction to come from a
288 # single location in the codbase.
288 # single location in the codbase.
289 etype, evalue, tb = sys.exc_info()
289 etype, evalue, tb = sys.exc_info()
290 tb_list = traceback.format_exception(etype, evalue, tb)
290 tb_list = traceback.format_exception(etype, evalue, tb)
291 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
291 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
292 else:
292 else:
293 status = u'ok'
293 status = u'ok'
294
294
295 reply_content[u'status'] = status
295 reply_content[u'status'] = status
296
296
297 # Return the execution counter so clients can display prompts
297 # Return the execution counter so clients can display prompts
298 reply_content['execution_count'] = shell.execution_count -1
298 reply_content['execution_count'] = shell.execution_count -1
299
299
300 # FIXME - fish exception info out of shell, possibly left there by
300 # FIXME - fish exception info out of shell, possibly left there by
301 # runlines. We'll need to clean up this logic later.
301 # runlines. We'll need to clean up this logic later.
302 if shell._reply_content is not None:
302 if shell._reply_content is not None:
303 reply_content.update(shell._reply_content)
303 reply_content.update(shell._reply_content)
304 # reset after use
304 # reset after use
305 shell._reply_content = None
305 shell._reply_content = None
306
306
307 # At this point, we can tell whether the main code execution succeeded
307 # At this point, we can tell whether the main code execution succeeded
308 # or not. If it did, we proceed to evaluate user_variables/expressions
308 # or not. If it did, we proceed to evaluate user_variables/expressions
309 if reply_content['status'] == 'ok':
309 if reply_content['status'] == 'ok':
310 reply_content[u'user_variables'] = \
310 reply_content[u'user_variables'] = \
311 shell.user_variables(content[u'user_variables'])
311 shell.user_variables(content[u'user_variables'])
312 reply_content[u'user_expressions'] = \
312 reply_content[u'user_expressions'] = \
313 shell.user_expressions(content[u'user_expressions'])
313 shell.user_expressions(content[u'user_expressions'])
314 else:
314 else:
315 # If there was an error, don't even try to compute variables or
315 # If there was an error, don't even try to compute variables or
316 # expressions
316 # expressions
317 reply_content[u'user_variables'] = {}
317 reply_content[u'user_variables'] = {}
318 reply_content[u'user_expressions'] = {}
318 reply_content[u'user_expressions'] = {}
319
319
320 # Payloads should be retrieved regardless of outcome, so we can both
320 # Payloads should be retrieved regardless of outcome, so we can both
321 # recover partial output (that could have been generated early in a
321 # recover partial output (that could have been generated early in a
322 # block, before an error) and clear the payload system always.
322 # block, before an error) and clear the payload system always.
323 reply_content[u'payload'] = shell.payload_manager.read_payload()
323 reply_content[u'payload'] = shell.payload_manager.read_payload()
324 # Be agressive about clearing the payload because we don't want
324 # Be agressive about clearing the payload because we don't want
325 # it to sit in memory until the next execute_request comes in.
325 # it to sit in memory until the next execute_request comes in.
326 shell.payload_manager.clear_payload()
326 shell.payload_manager.clear_payload()
327
327
328 # Flush output before sending the reply.
328 # Flush output before sending the reply.
329 sys.stdout.flush()
329 sys.stdout.flush()
330 sys.stderr.flush()
330 sys.stderr.flush()
331 # FIXME: on rare occasions, the flush doesn't seem to make it to the
331 # FIXME: on rare occasions, the flush doesn't seem to make it to the
332 # clients... This seems to mitigate the problem, but we definitely need
332 # clients... This seems to mitigate the problem, but we definitely need
333 # to better understand what's going on.
333 # to better understand what's going on.
334 if self._execute_sleep:
334 if self._execute_sleep:
335 time.sleep(self._execute_sleep)
335 time.sleep(self._execute_sleep)
336
336
337 # Send the reply.
337 # Send the reply.
338 reply_content = json_clean(reply_content)
338 reply_content = json_clean(reply_content)
339 reply_msg = self.session.send(self.shell_socket, u'execute_reply',
339 reply_msg = self.session.send(self.shell_socket, u'execute_reply',
340 reply_content, parent, ident=ident)
340 reply_content, parent, ident=ident)
341 self.log.debug(str(reply_msg))
341 self.log.debug(str(reply_msg))
342
342
343 if reply_msg['content']['status'] == u'error':
343 if reply_msg['content']['status'] == u'error':
344 self._abort_queue()
344 self._abort_queue()
345
345
346 self.session.send(self.iopub_socket,
346 self.session.send(self.iopub_socket,
347 u'status',
347 u'status',
348 {u'execution_state':u'idle'},
348 {u'execution_state':u'idle'},
349 parent=parent )
349 parent=parent )
350
350
351 def complete_request(self, ident, parent):
351 def complete_request(self, ident, parent):
352 txt, matches = self._complete(parent)
352 txt, matches = self._complete(parent)
353 matches = {'matches' : matches,
353 matches = {'matches' : matches,
354 'matched_text' : txt,
354 'matched_text' : txt,
355 'status' : 'ok'}
355 'status' : 'ok'}
356 matches = json_clean(matches)
356 matches = json_clean(matches)
357 completion_msg = self.session.send(self.shell_socket, 'complete_reply',
357 completion_msg = self.session.send(self.shell_socket, 'complete_reply',
358 matches, parent, ident)
358 matches, parent, ident)
359 self.log.debug(str(completion_msg))
359 self.log.debug(str(completion_msg))
360
360
361 def object_info_request(self, ident, parent):
361 def object_info_request(self, ident, parent):
362 object_info = self.shell.object_inspect(parent['content']['oname'])
362 object_info = self.shell.object_inspect(parent['content']['oname'])
363 # Before we send this object over, we scrub it for JSON usage
363 # Before we send this object over, we scrub it for JSON usage
364 oinfo = json_clean(object_info)
364 oinfo = json_clean(object_info)
365 msg = self.session.send(self.shell_socket, 'object_info_reply',
365 msg = self.session.send(self.shell_socket, 'object_info_reply',
366 oinfo, parent, ident)
366 oinfo, parent, ident)
367 self.log.debug(msg)
367 self.log.debug(msg)
368
368
369 def history_request(self, ident, parent):
369 def history_request(self, ident, parent):
370 # We need to pull these out, as passing **kwargs doesn't work with
370 # We need to pull these out, as passing **kwargs doesn't work with
371 # unicode keys before Python 2.6.5.
371 # unicode keys before Python 2.6.5.
372 hist_access_type = parent['content']['hist_access_type']
372 hist_access_type = parent['content']['hist_access_type']
373 raw = parent['content']['raw']
373 raw = parent['content']['raw']
374 output = parent['content']['output']
374 output = parent['content']['output']
375 if hist_access_type == 'tail':
375 if hist_access_type == 'tail':
376 n = parent['content']['n']
376 n = parent['content']['n']
377 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
377 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
378 include_latest=True)
378 include_latest=True)
379
379
380 elif hist_access_type == 'range':
380 elif hist_access_type == 'range':
381 session = parent['content']['session']
381 session = parent['content']['session']
382 start = parent['content']['start']
382 start = parent['content']['start']
383 stop = parent['content']['stop']
383 stop = parent['content']['stop']
384 hist = self.shell.history_manager.get_range(session, start, stop,
384 hist = self.shell.history_manager.get_range(session, start, stop,
385 raw=raw, output=output)
385 raw=raw, output=output)
386
386
387 elif hist_access_type == 'search':
387 elif hist_access_type == 'search':
388 pattern = parent['content']['pattern']
388 pattern = parent['content']['pattern']
389 hist = self.shell.history_manager.search(pattern, raw=raw,
389 hist = self.shell.history_manager.search(pattern, raw=raw,
390 output=output)
390 output=output)
391
391
392 else:
392 else:
393 hist = []
393 hist = []
394 content = {'history' : list(hist)}
394 hist = list(hist)
395 content = {'history' : hist}
395 content = json_clean(content)
396 content = json_clean(content)
396 msg = self.session.send(self.shell_socket, 'history_reply',
397 msg = self.session.send(self.shell_socket, 'history_reply',
397 content, parent, ident)
398 content, parent, ident)
398 self.log.debug(str(msg))
399 self.log.debug("Sending history reply with %i entries", len(hist))
399
400
400 def connect_request(self, ident, parent):
401 def connect_request(self, ident, parent):
401 if self._recorded_ports is not None:
402 if self._recorded_ports is not None:
402 content = self._recorded_ports.copy()
403 content = self._recorded_ports.copy()
403 else:
404 else:
404 content = {}
405 content = {}
405 msg = self.session.send(self.shell_socket, 'connect_reply',
406 msg = self.session.send(self.shell_socket, 'connect_reply',
406 content, parent, ident)
407 content, parent, ident)
407 self.log.debug(msg)
408 self.log.debug(msg)
408
409
409 def shutdown_request(self, ident, parent):
410 def shutdown_request(self, ident, parent):
410 self.shell.exit_now = True
411 self.shell.exit_now = True
411 self._shutdown_message = self.session.msg(u'shutdown_reply',
412 self._shutdown_message = self.session.msg(u'shutdown_reply',
412 parent['content'], parent)
413 parent['content'], parent)
413 sys.exit(0)
414 sys.exit(0)
414
415
415 #---------------------------------------------------------------------------
416 #---------------------------------------------------------------------------
416 # Protected interface
417 # Protected interface
417 #---------------------------------------------------------------------------
418 #---------------------------------------------------------------------------
418
419
419 def _abort_queue(self):
420 def _abort_queue(self):
420 while True:
421 while True:
421 try:
422 try:
422 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
423 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
423 except Exception:
424 except Exception:
424 self.log.warn("Invalid Message:", exc_info=True)
425 self.log.warn("Invalid Message:", exc_info=True)
425 continue
426 continue
426 if msg is None:
427 if msg is None:
427 break
428 break
428 else:
429 else:
429 assert ident is not None, \
430 assert ident is not None, \
430 "Unexpected missing message part."
431 "Unexpected missing message part."
431
432
432 self.log.debug("Aborting:\n"+str(Message(msg)))
433 self.log.debug("Aborting:\n"+str(Message(msg)))
433 msg_type = msg['header']['msg_type']
434 msg_type = msg['header']['msg_type']
434 reply_type = msg_type.split('_')[0] + '_reply'
435 reply_type = msg_type.split('_')[0] + '_reply'
435 reply_msg = self.session.send(self.shell_socket, reply_type,
436 reply_msg = self.session.send(self.shell_socket, reply_type,
436 {'status' : 'aborted'}, msg, ident=ident)
437 {'status' : 'aborted'}, msg, ident=ident)
437 self.log.debug(reply_msg)
438 self.log.debug(reply_msg)
438 # We need to wait a bit for requests to come in. This can probably
439 # We need to wait a bit for requests to come in. This can probably
439 # be set shorter for true asynchronous clients.
440 # be set shorter for true asynchronous clients.
440 time.sleep(0.1)
441 time.sleep(0.1)
441
442
442 def _no_raw_input(self):
443 def _no_raw_input(self):
443 """Raise StdinNotImplentedError if active frontend doesn't support
444 """Raise StdinNotImplentedError if active frontend doesn't support
444 stdin."""
445 stdin."""
445 raise StdinNotImplementedError("raw_input was called, but this "
446 raise StdinNotImplementedError("raw_input was called, but this "
446 "frontend does not support stdin.")
447 "frontend does not support stdin.")
447
448
448 def _raw_input(self, prompt, ident, parent):
449 def _raw_input(self, prompt, ident, parent):
449 # Flush output before making the request.
450 # Flush output before making the request.
450 sys.stderr.flush()
451 sys.stderr.flush()
451 sys.stdout.flush()
452 sys.stdout.flush()
452
453
453 # Send the input request.
454 # Send the input request.
454 content = json_clean(dict(prompt=prompt))
455 content = json_clean(dict(prompt=prompt))
455 self.session.send(self.stdin_socket, u'input_request', content, parent,
456 self.session.send(self.stdin_socket, u'input_request', content, parent,
456 ident=ident)
457 ident=ident)
457
458
458 # Await a response.
459 # Await a response.
459 while True:
460 while True:
460 try:
461 try:
461 ident, reply = self.session.recv(self.stdin_socket, 0)
462 ident, reply = self.session.recv(self.stdin_socket, 0)
462 except Exception:
463 except Exception:
463 self.log.warn("Invalid Message:", exc_info=True)
464 self.log.warn("Invalid Message:", exc_info=True)
464 else:
465 else:
465 break
466 break
466 try:
467 try:
467 value = reply['content']['value']
468 value = reply['content']['value']
468 except:
469 except:
469 self.log.error("Got bad raw_input reply: ")
470 self.log.error("Got bad raw_input reply: ")
470 self.log.error(str(Message(parent)))
471 self.log.error(str(Message(parent)))
471 value = ''
472 value = ''
472 if value == '\x04':
473 if value == '\x04':
473 # EOF
474 # EOF
474 raise EOFError
475 raise EOFError
475 return value
476 return value
476
477
477 def _complete(self, msg):
478 def _complete(self, msg):
478 c = msg['content']
479 c = msg['content']
479 try:
480 try:
480 cpos = int(c['cursor_pos'])
481 cpos = int(c['cursor_pos'])
481 except:
482 except:
482 # If we don't get something that we can convert to an integer, at
483 # If we don't get something that we can convert to an integer, at
483 # least attempt the completion guessing the cursor is at the end of
484 # least attempt the completion guessing the cursor is at the end of
484 # the text, if there's any, and otherwise of the line
485 # the text, if there's any, and otherwise of the line
485 cpos = len(c['text'])
486 cpos = len(c['text'])
486 if cpos==0:
487 if cpos==0:
487 cpos = len(c['line'])
488 cpos = len(c['line'])
488 return self.shell.complete(c['text'], c['line'], cpos)
489 return self.shell.complete(c['text'], c['line'], cpos)
489
490
490 def _object_info(self, context):
491 def _object_info(self, context):
491 symbol, leftover = self._symbol_from_context(context)
492 symbol, leftover = self._symbol_from_context(context)
492 if symbol is not None and not leftover:
493 if symbol is not None and not leftover:
493 doc = getattr(symbol, '__doc__', '')
494 doc = getattr(symbol, '__doc__', '')
494 else:
495 else:
495 doc = ''
496 doc = ''
496 object_info = dict(docstring = doc)
497 object_info = dict(docstring = doc)
497 return object_info
498 return object_info
498
499
499 def _symbol_from_context(self, context):
500 def _symbol_from_context(self, context):
500 if not context:
501 if not context:
501 return None, context
502 return None, context
502
503
503 base_symbol_string = context[0]
504 base_symbol_string = context[0]
504 symbol = self.shell.user_ns.get(base_symbol_string, None)
505 symbol = self.shell.user_ns.get(base_symbol_string, None)
505 if symbol is None:
506 if symbol is None:
506 symbol = __builtin__.__dict__.get(base_symbol_string, None)
507 symbol = __builtin__.__dict__.get(base_symbol_string, None)
507 if symbol is None:
508 if symbol is None:
508 return None, context
509 return None, context
509
510
510 context = context[1:]
511 context = context[1:]
511 for i, name in enumerate(context):
512 for i, name in enumerate(context):
512 new_symbol = getattr(symbol, name, None)
513 new_symbol = getattr(symbol, name, None)
513 if new_symbol is None:
514 if new_symbol is None:
514 return symbol, context[i:]
515 return symbol, context[i:]
515 else:
516 else:
516 symbol = new_symbol
517 symbol = new_symbol
517
518
518 return symbol, []
519 return symbol, []
519
520
520 def _at_shutdown(self):
521 def _at_shutdown(self):
521 """Actions taken at shutdown by the kernel, called by python's atexit.
522 """Actions taken at shutdown by the kernel, called by python's atexit.
522 """
523 """
523 # io.rprint("Kernel at_shutdown") # dbg
524 # io.rprint("Kernel at_shutdown") # dbg
524 if self._shutdown_message is not None:
525 if self._shutdown_message is not None:
525 self.session.send(self.shell_socket, self._shutdown_message)
526 self.session.send(self.shell_socket, self._shutdown_message)
526 self.session.send(self.iopub_socket, self._shutdown_message)
527 self.session.send(self.iopub_socket, self._shutdown_message)
527 self.log.debug(str(self._shutdown_message))
528 self.log.debug(str(self._shutdown_message))
528 # A very short sleep to give zmq time to flush its message buffers
529 # A very short sleep to give zmq time to flush its message buffers
529 # before Python truly shuts down.
530 # before Python truly shuts down.
530 time.sleep(0.01)
531 time.sleep(0.01)
531
532
532 #-----------------------------------------------------------------------------
533 #-----------------------------------------------------------------------------
533 # Aliases and Flags for the IPKernelApp
534 # Aliases and Flags for the IPKernelApp
534 #-----------------------------------------------------------------------------
535 #-----------------------------------------------------------------------------
535
536
536 flags = dict(kernel_flags)
537 flags = dict(kernel_flags)
537 flags.update(shell_flags)
538 flags.update(shell_flags)
538
539
539 addflag = lambda *args: flags.update(boolean_flag(*args))
540 addflag = lambda *args: flags.update(boolean_flag(*args))
540
541
541 flags['pylab'] = (
542 flags['pylab'] = (
542 {'IPKernelApp' : {'pylab' : 'auto'}},
543 {'IPKernelApp' : {'pylab' : 'auto'}},
543 """Pre-load matplotlib and numpy for interactive use with
544 """Pre-load matplotlib and numpy for interactive use with
544 the default matplotlib backend."""
545 the default matplotlib backend."""
545 )
546 )
546
547
547 aliases = dict(kernel_aliases)
548 aliases = dict(kernel_aliases)
548 aliases.update(shell_aliases)
549 aliases.update(shell_aliases)
549
550
550 # it's possible we don't want short aliases for *all* of these:
551 # it's possible we don't want short aliases for *all* of these:
551 aliases.update(dict(
552 aliases.update(dict(
552 pylab='IPKernelApp.pylab',
553 pylab='IPKernelApp.pylab',
553 ))
554 ))
554
555
555 #-----------------------------------------------------------------------------
556 #-----------------------------------------------------------------------------
556 # The IPKernelApp class
557 # The IPKernelApp class
557 #-----------------------------------------------------------------------------
558 #-----------------------------------------------------------------------------
558
559
559 class IPKernelApp(KernelApp, InteractiveShellApp):
560 class IPKernelApp(KernelApp, InteractiveShellApp):
560 name = 'ipkernel'
561 name = 'ipkernel'
561
562
562 aliases = Dict(aliases)
563 aliases = Dict(aliases)
563 flags = Dict(flags)
564 flags = Dict(flags)
564 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
565 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
565 # configurables
566 # configurables
566 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'inline', 'auto'],
567 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'inline', 'auto'],
567 config=True,
568 config=True,
568 help="""Pre-load matplotlib and numpy for interactive use,
569 help="""Pre-load matplotlib and numpy for interactive use,
569 selecting a particular matplotlib backend and loop integration.
570 selecting a particular matplotlib backend and loop integration.
570 """
571 """
571 )
572 )
572
573
573 @catch_config_error
574 @catch_config_error
574 def initialize(self, argv=None):
575 def initialize(self, argv=None):
575 super(IPKernelApp, self).initialize(argv)
576 super(IPKernelApp, self).initialize(argv)
576 self.init_shell()
577 self.init_shell()
577 self.init_extensions()
578 self.init_extensions()
578 self.init_code()
579 self.init_code()
579
580
580 def init_kernel(self):
581 def init_kernel(self):
581
582
582 kernel = Kernel(config=self.config, session=self.session,
583 kernel = Kernel(config=self.config, session=self.session,
583 shell_socket=self.shell_socket,
584 shell_socket=self.shell_socket,
584 iopub_socket=self.iopub_socket,
585 iopub_socket=self.iopub_socket,
585 stdin_socket=self.stdin_socket,
586 stdin_socket=self.stdin_socket,
586 log=self.log,
587 log=self.log,
587 profile_dir=self.profile_dir,
588 profile_dir=self.profile_dir,
588 )
589 )
589 self.kernel = kernel
590 self.kernel = kernel
590 kernel.record_ports(self.ports)
591 kernel.record_ports(self.ports)
591 shell = kernel.shell
592 shell = kernel.shell
592 if self.pylab:
593 if self.pylab:
593 try:
594 try:
594 gui, backend = pylabtools.find_gui_and_backend(self.pylab)
595 gui, backend = pylabtools.find_gui_and_backend(self.pylab)
595 shell.enable_pylab(gui, import_all=self.pylab_import_all)
596 shell.enable_pylab(gui, import_all=self.pylab_import_all)
596 except Exception:
597 except Exception:
597 self.log.error("Pylab initialization failed", exc_info=True)
598 self.log.error("Pylab initialization failed", exc_info=True)
598 # print exception straight to stdout, because normally
599 # print exception straight to stdout, because normally
599 # _showtraceback associates the reply with an execution,
600 # _showtraceback associates the reply with an execution,
600 # which means frontends will never draw it, as this exception
601 # which means frontends will never draw it, as this exception
601 # is not associated with any execute request.
602 # is not associated with any execute request.
602
603
603 # replace pyerr-sending traceback with stdout
604 # replace pyerr-sending traceback with stdout
604 _showtraceback = shell._showtraceback
605 _showtraceback = shell._showtraceback
605 def print_tb(etype, evalue, stb):
606 def print_tb(etype, evalue, stb):
606 print ("Error initializing pylab, pylab mode will not "
607 print ("Error initializing pylab, pylab mode will not "
607 "be active", file=io.stderr)
608 "be active", file=io.stderr)
608 print (shell.InteractiveTB.stb2text(stb), file=io.stdout)
609 print (shell.InteractiveTB.stb2text(stb), file=io.stdout)
609 shell._showtraceback = print_tb
610 shell._showtraceback = print_tb
610
611
611 # send the traceback over stdout
612 # send the traceback over stdout
612 shell.showtraceback(tb_offset=0)
613 shell.showtraceback(tb_offset=0)
613
614
614 # restore proper _showtraceback method
615 # restore proper _showtraceback method
615 shell._showtraceback = _showtraceback
616 shell._showtraceback = _showtraceback
616
617
617
618
618 def init_shell(self):
619 def init_shell(self):
619 self.shell = self.kernel.shell
620 self.shell = self.kernel.shell
620 self.shell.configurables.append(self)
621 self.shell.configurables.append(self)
621
622
622
623
623 #-----------------------------------------------------------------------------
624 #-----------------------------------------------------------------------------
624 # Kernel main and launch functions
625 # Kernel main and launch functions
625 #-----------------------------------------------------------------------------
626 #-----------------------------------------------------------------------------
626
627
627 def launch_kernel(*args, **kwargs):
628 def launch_kernel(*args, **kwargs):
628 """Launches a localhost IPython kernel, binding to the specified ports.
629 """Launches a localhost IPython kernel, binding to the specified ports.
629
630
630 This function simply calls entry_point.base_launch_kernel with the right
631 This function simply calls entry_point.base_launch_kernel with the right
631 first command to start an ipkernel. See base_launch_kernel for arguments.
632 first command to start an ipkernel. See base_launch_kernel for arguments.
632
633
633 Returns
634 Returns
634 -------
635 -------
635 A tuple of form:
636 A tuple of form:
636 (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
637 (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
637 where kernel_process is a Popen object and the ports are integers.
638 where kernel_process is a Popen object and the ports are integers.
638 """
639 """
639 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
640 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
640 *args, **kwargs)
641 *args, **kwargs)
641
642
642
643
643 def main():
644 def main():
644 """Run an IPKernel as an application"""
645 """Run an IPKernel as an application"""
645 app = IPKernelApp.instance()
646 app = IPKernelApp.instance()
646 app.initialize()
647 app.initialize()
647 app.start()
648 app.start()
648
649
649
650
650 if __name__ == '__main__':
651 if __name__ == '__main__':
651 main()
652 main()
General Comments 0
You need to be logged in to leave comments. Login now