##// END OF EJS Templates
* The Qt console frontend now ignores cross chatter from other frontends....
epatters -
Show More
@@ -1,85 +1,93 b''
1 """ Defines a convenient mix-in class for implementing Qt frontends.
1 """ Defines a convenient mix-in class for implementing Qt frontends.
2 """
2 """
3
3
4 class BaseFrontendMixin(object):
4 class BaseFrontendMixin(object):
5 """ A mix-in class for implementing Qt frontends.
5 """ A mix-in class for implementing Qt frontends.
6
6
7 To handle messages of a particular type, frontends need only define an
7 To handle messages of a particular type, frontends need only define an
8 appropriate handler method. For example, to handle 'stream' messaged, define
8 appropriate handler method. For example, to handle 'stream' messaged, define
9 a '_handle_stream(msg)' method.
9 a '_handle_stream(msg)' method.
10 """
10 """
11
11
12 #---------------------------------------------------------------------------
12 #---------------------------------------------------------------------------
13 # 'BaseFrontendMixin' concrete interface
13 # 'BaseFrontendMixin' concrete interface
14 #---------------------------------------------------------------------------
14 #---------------------------------------------------------------------------
15
15
16 def _get_kernel_manager(self):
16 def _get_kernel_manager(self):
17 """ Returns the current kernel manager.
17 """ Returns the current kernel manager.
18 """
18 """
19 return self._kernel_manager
19 return self._kernel_manager
20
20
21 def _set_kernel_manager(self, kernel_manager):
21 def _set_kernel_manager(self, kernel_manager):
22 """ Disconnect from the current kernel manager (if any) and set a new
22 """ Disconnect from the current kernel manager (if any) and set a new
23 kernel manager.
23 kernel manager.
24 """
24 """
25 # Disconnect the old kernel manager, if necessary.
25 # Disconnect the old kernel manager, if necessary.
26 old_manager = self._kernel_manager
26 old_manager = self._kernel_manager
27 if old_manager is not None:
27 if old_manager is not None:
28 old_manager.started_channels.disconnect(self._started_channels)
28 old_manager.started_channels.disconnect(self._started_channels)
29 old_manager.stopped_channels.disconnect(self._stopped_channels)
29 old_manager.stopped_channels.disconnect(self._stopped_channels)
30
30
31 # Disconnect the old kernel manager's channels.
31 # Disconnect the old kernel manager's channels.
32 old_manager.sub_channel.message_received.disconnect(self._dispatch)
32 old_manager.sub_channel.message_received.disconnect(self._dispatch)
33 old_manager.xreq_channel.message_received.disconnect(self._dispatch)
33 old_manager.xreq_channel.message_received.disconnect(self._dispatch)
34 old_manager.rep_channel.message_received.connect(self._dispatch)
34 old_manager.rep_channel.message_received.connect(self._dispatch)
35
35
36 # Handle the case where the old kernel manager is still listening.
36 # Handle the case where the old kernel manager is still listening.
37 if old_manager.channels_running:
37 if old_manager.channels_running:
38 self._stopped_channels()
38 self._stopped_channels()
39
39
40 # Set the new kernel manager.
40 # Set the new kernel manager.
41 self._kernel_manager = kernel_manager
41 self._kernel_manager = kernel_manager
42 if kernel_manager is None:
42 if kernel_manager is None:
43 return
43 return
44
44
45 # Connect the new kernel manager.
45 # Connect the new kernel manager.
46 kernel_manager.started_channels.connect(self._started_channels)
46 kernel_manager.started_channels.connect(self._started_channels)
47 kernel_manager.stopped_channels.connect(self._stopped_channels)
47 kernel_manager.stopped_channels.connect(self._stopped_channels)
48
48
49 # Connect the new kernel manager's channels.
49 # Connect the new kernel manager's channels.
50 kernel_manager.sub_channel.message_received.connect(self._dispatch)
50 kernel_manager.sub_channel.message_received.connect(self._dispatch)
51 kernel_manager.xreq_channel.message_received.connect(self._dispatch)
51 kernel_manager.xreq_channel.message_received.connect(self._dispatch)
52 kernel_manager.rep_channel.message_received.connect(self._dispatch)
52 kernel_manager.rep_channel.message_received.connect(self._dispatch)
53
53
54 # Handle the case where the kernel manager started channels before
54 # Handle the case where the kernel manager started channels before
55 # we connected.
55 # we connected.
56 if kernel_manager.channels_running:
56 if kernel_manager.channels_running:
57 self._started_channels()
57 self._started_channels()
58
58
59 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
59 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
60
60
61 #---------------------------------------------------------------------------
61 #---------------------------------------------------------------------------
62 # 'BaseFrontendMixin' abstract interface
62 # 'BaseFrontendMixin' abstract interface
63 #---------------------------------------------------------------------------
63 #---------------------------------------------------------------------------
64
64
65 def _started_channels(self):
65 def _started_channels(self):
66 """ Called when the KernelManager channels have started listening or
66 """ Called when the KernelManager channels have started listening or
67 when the frontend is assigned an already listening KernelManager.
67 when the frontend is assigned an already listening KernelManager.
68 """
68 """
69
69
70 def _stopped_channels(self):
70 def _stopped_channels(self):
71 """ Called when the KernelManager channels have stopped listening or
71 """ Called when the KernelManager channels have stopped listening or
72 when a listening KernelManager is removed from the frontend.
72 when a listening KernelManager is removed from the frontend.
73 """
73 """
74
74
75 #---------------------------------------------------------------------------
75 #---------------------------------------------------------------------------
76 # Private interface
76 # 'BaseFrontendMixin' protected interface
77 #---------------------------------------------------------------------------
77 #---------------------------------------------------------------------------
78
78
79 def _dispatch(self, msg):
79 def _dispatch(self, msg):
80 """ Call the frontend handler associated with
80 """ Calls the frontend handler associated with the message type of the
81 given message.
81 """
82 """
82 msg_type = msg['msg_type']
83 msg_type = msg['msg_type']
83 handler = getattr(self, '_handle_' + msg_type, None)
84 handler = getattr(self, '_handle_' + msg_type, None)
84 if handler:
85 if handler:
85 handler(msg)
86 handler(msg)
87
88 def _is_from_this_session(self, msg):
89 """ Returns whether a reply from the kernel originated from a request
90 from this frontend.
91 """
92 session = self._kernel_manager.session.session
93 return msg['parent_header']['session'] == session
@@ -1,358 +1,359 b''
1 # Standard library imports
1 # Standard library imports
2 import signal
2 import signal
3 import sys
3 import sys
4
4
5 # System library imports
5 # System library imports
6 from pygments.lexers import PythonLexer
6 from pygments.lexers import PythonLexer
7 from PyQt4 import QtCore, QtGui
7 from PyQt4 import QtCore, QtGui
8 import zmq
8 import zmq
9
9
10 # Local imports
10 # Local imports
11 from IPython.core.inputsplitter import InputSplitter
11 from IPython.core.inputsplitter import InputSplitter
12 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
12 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
13 from call_tip_widget import CallTipWidget
13 from call_tip_widget import CallTipWidget
14 from completion_lexer import CompletionLexer
14 from completion_lexer import CompletionLexer
15 from console_widget import HistoryConsoleWidget
15 from console_widget import HistoryConsoleWidget
16 from pygments_highlighter import PygmentsHighlighter
16 from pygments_highlighter import PygmentsHighlighter
17
17
18
18
19 class FrontendHighlighter(PygmentsHighlighter):
19 class FrontendHighlighter(PygmentsHighlighter):
20 """ A PygmentsHighlighter that can be turned on and off and that ignores
20 """ A PygmentsHighlighter that can be turned on and off and that ignores
21 prompts.
21 prompts.
22 """
22 """
23
23
24 def __init__(self, frontend):
24 def __init__(self, frontend):
25 super(FrontendHighlighter, self).__init__(frontend._control.document())
25 super(FrontendHighlighter, self).__init__(frontend._control.document())
26 self._current_offset = 0
26 self._current_offset = 0
27 self._frontend = frontend
27 self._frontend = frontend
28 self.highlighting_on = False
28 self.highlighting_on = False
29
29
30 def highlightBlock(self, qstring):
30 def highlightBlock(self, qstring):
31 """ Highlight a block of text. Reimplemented to highlight selectively.
31 """ Highlight a block of text. Reimplemented to highlight selectively.
32 """
32 """
33 if not self.highlighting_on:
33 if not self.highlighting_on:
34 return
34 return
35
35
36 # The input to this function is unicode string that may contain
36 # The input to this function is unicode string that may contain
37 # paragraph break characters, non-breaking spaces, etc. Here we acquire
37 # paragraph break characters, non-breaking spaces, etc. Here we acquire
38 # the string as plain text so we can compare it.
38 # the string as plain text so we can compare it.
39 current_block = self.currentBlock()
39 current_block = self.currentBlock()
40 string = self._frontend._get_block_plain_text(current_block)
40 string = self._frontend._get_block_plain_text(current_block)
41
41
42 # Decide whether to check for the regular or continuation prompt.
42 # Decide whether to check for the regular or continuation prompt.
43 if current_block.contains(self._frontend._prompt_pos):
43 if current_block.contains(self._frontend._prompt_pos):
44 prompt = self._frontend._prompt
44 prompt = self._frontend._prompt
45 else:
45 else:
46 prompt = self._frontend._continuation_prompt
46 prompt = self._frontend._continuation_prompt
47
47
48 # Don't highlight the part of the string that contains the prompt.
48 # Don't highlight the part of the string that contains the prompt.
49 if string.startswith(prompt):
49 if string.startswith(prompt):
50 self._current_offset = len(prompt)
50 self._current_offset = len(prompt)
51 qstring.remove(0, len(prompt))
51 qstring.remove(0, len(prompt))
52 else:
52 else:
53 self._current_offset = 0
53 self._current_offset = 0
54
54
55 PygmentsHighlighter.highlightBlock(self, qstring)
55 PygmentsHighlighter.highlightBlock(self, qstring)
56
56
57 def setFormat(self, start, count, format):
57 def setFormat(self, start, count, format):
58 """ Reimplemented to highlight selectively.
58 """ Reimplemented to highlight selectively.
59 """
59 """
60 start += self._current_offset
60 start += self._current_offset
61 PygmentsHighlighter.setFormat(self, start, count, format)
61 PygmentsHighlighter.setFormat(self, start, count, format)
62
62
63
63
64 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
64 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
65 """ A Qt frontend for a generic Python kernel.
65 """ A Qt frontend for a generic Python kernel.
66 """
66 """
67
67
68 # Emitted when an 'execute_reply' has been received from the kernel and
68 # Emitted when an 'execute_reply' has been received from the kernel and
69 # processed by the FrontendWidget.
69 # processed by the FrontendWidget.
70 executed = QtCore.pyqtSignal(object)
70 executed = QtCore.pyqtSignal(object)
71
71
72 # Protected class attributes.
72 # Protected class attributes.
73 _highlighter_class = FrontendHighlighter
73 _highlighter_class = FrontendHighlighter
74 _input_splitter_class = InputSplitter
74 _input_splitter_class = InputSplitter
75
75
76 #---------------------------------------------------------------------------
76 #---------------------------------------------------------------------------
77 # 'object' interface
77 # 'object' interface
78 #---------------------------------------------------------------------------
78 #---------------------------------------------------------------------------
79
79
80 def __init__(self, *args, **kw):
80 def __init__(self, *args, **kw):
81 super(FrontendWidget, self).__init__(*args, **kw)
81 super(FrontendWidget, self).__init__(*args, **kw)
82
82
83 # FrontendWidget protected variables.
83 # FrontendWidget protected variables.
84 self._call_tip_widget = CallTipWidget(self._control)
84 self._call_tip_widget = CallTipWidget(self._control)
85 self._completion_lexer = CompletionLexer(PythonLexer())
85 self._completion_lexer = CompletionLexer(PythonLexer())
86 self._hidden = True
86 self._hidden = False
87 self._highlighter = self._highlighter_class(self)
87 self._highlighter = self._highlighter_class(self)
88 self._input_splitter = self._input_splitter_class(input_mode='replace')
88 self._input_splitter = self._input_splitter_class(input_mode='replace')
89 self._kernel_manager = None
89 self._kernel_manager = None
90
90
91 # Configure the ConsoleWidget.
91 # Configure the ConsoleWidget.
92 self.tab_width = 4
92 self.tab_width = 4
93 self._set_continuation_prompt('... ')
93 self._set_continuation_prompt('... ')
94
94
95 # Connect signal handlers.
95 # Connect signal handlers.
96 document = self._control.document()
96 document = self._control.document()
97 document.contentsChange.connect(self._document_contents_change)
97 document.contentsChange.connect(self._document_contents_change)
98
98
99 #---------------------------------------------------------------------------
99 #---------------------------------------------------------------------------
100 # 'ConsoleWidget' abstract interface
100 # 'ConsoleWidget' abstract interface
101 #---------------------------------------------------------------------------
101 #---------------------------------------------------------------------------
102
102
103 def _is_complete(self, source, interactive):
103 def _is_complete(self, source, interactive):
104 """ Returns whether 'source' can be completely processed and a new
104 """ Returns whether 'source' can be completely processed and a new
105 prompt created. When triggered by an Enter/Return key press,
105 prompt created. When triggered by an Enter/Return key press,
106 'interactive' is True; otherwise, it is False.
106 'interactive' is True; otherwise, it is False.
107 """
107 """
108 complete = self._input_splitter.push(source.expandtabs(4))
108 complete = self._input_splitter.push(source.expandtabs(4))
109 if interactive:
109 if interactive:
110 complete = not self._input_splitter.push_accepts_more()
110 complete = not self._input_splitter.push_accepts_more()
111 return complete
111 return complete
112
112
113 def _execute(self, source, hidden):
113 def _execute(self, source, hidden):
114 """ Execute 'source'. If 'hidden', do not show any output.
114 """ Execute 'source'. If 'hidden', do not show any output.
115 """
115 """
116 self.kernel_manager.xreq_channel.execute(source)
116 self.kernel_manager.xreq_channel.execute(source)
117 self._hidden = hidden
117 self._hidden = hidden
118
118
119 def _execute_interrupt(self):
119 def _execute_interrupt(self):
120 """ Attempts to stop execution. Returns whether this method has an
120 """ Attempts to stop execution. Returns whether this method has an
121 implementation.
121 implementation.
122 """
122 """
123 self._interrupt_kernel()
123 self._interrupt_kernel()
124 return True
124 return True
125
125
126 def _prompt_started_hook(self):
126 def _prompt_started_hook(self):
127 """ Called immediately after a new prompt is displayed.
127 """ Called immediately after a new prompt is displayed.
128 """
128 """
129 if not self._reading:
129 if not self._reading:
130 self._highlighter.highlighting_on = True
130 self._highlighter.highlighting_on = True
131
131
132 def _prompt_finished_hook(self):
132 def _prompt_finished_hook(self):
133 """ Called immediately after a prompt is finished, i.e. when some input
133 """ Called immediately after a prompt is finished, i.e. when some input
134 will be processed and a new prompt displayed.
134 will be processed and a new prompt displayed.
135 """
135 """
136 if not self._reading:
136 if not self._reading:
137 self._highlighter.highlighting_on = False
137 self._highlighter.highlighting_on = False
138
138
139 def _tab_pressed(self):
139 def _tab_pressed(self):
140 """ Called when the tab key is pressed. Returns whether to continue
140 """ Called when the tab key is pressed. Returns whether to continue
141 processing the event.
141 processing the event.
142 """
142 """
143 self._keep_cursor_in_buffer()
143 self._keep_cursor_in_buffer()
144 cursor = self._get_cursor()
144 cursor = self._get_cursor()
145 return not self._complete()
145 return not self._complete()
146
146
147 #---------------------------------------------------------------------------
147 #---------------------------------------------------------------------------
148 # 'ConsoleWidget' protected interface
148 # 'ConsoleWidget' protected interface
149 #---------------------------------------------------------------------------
149 #---------------------------------------------------------------------------
150
150
151 def _show_continuation_prompt(self):
151 def _show_continuation_prompt(self):
152 """ Reimplemented for auto-indentation.
152 """ Reimplemented for auto-indentation.
153 """
153 """
154 super(FrontendWidget, self)._show_continuation_prompt()
154 super(FrontendWidget, self)._show_continuation_prompt()
155 spaces = self._input_splitter.indent_spaces
155 spaces = self._input_splitter.indent_spaces
156 self._append_plain_text('\t' * (spaces / self.tab_width))
156 self._append_plain_text('\t' * (spaces / self.tab_width))
157 self._append_plain_text(' ' * (spaces % self.tab_width))
157 self._append_plain_text(' ' * (spaces % self.tab_width))
158
158
159 #---------------------------------------------------------------------------
159 #---------------------------------------------------------------------------
160 # 'BaseFrontendMixin' abstract interface
160 # 'BaseFrontendMixin' abstract interface
161 #---------------------------------------------------------------------------
161 #---------------------------------------------------------------------------
162
162
163 def _handle_complete_reply(self, rep):
163 def _handle_complete_reply(self, rep):
164 """ Handle replies for tab completion.
164 """ Handle replies for tab completion.
165 """
165 """
166 cursor = self._get_cursor()
166 cursor = self._get_cursor()
167 if rep['parent_header']['msg_id'] == self._complete_id and \
167 if rep['parent_header']['msg_id'] == self._complete_id and \
168 cursor.position() == self._complete_pos:
168 cursor.position() == self._complete_pos:
169 text = '.'.join(self._get_context())
169 text = '.'.join(self._get_context())
170 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
170 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
171 self._complete_with_items(cursor, rep['content']['matches'])
171 self._complete_with_items(cursor, rep['content']['matches'])
172
172
173 def _handle_execute_reply(self, msg):
173 def _handle_execute_reply(self, msg):
174 """ Handles replies for code execution.
174 """ Handles replies for code execution.
175 """
175 """
176 if not self._hidden:
176 if not self._hidden:
177 # Make sure that all output from the SUB channel has been processed
177 # Make sure that all output from the SUB channel has been processed
178 # before writing a new prompt.
178 # before writing a new prompt.
179 self.kernel_manager.sub_channel.flush()
179 self.kernel_manager.sub_channel.flush()
180
180
181 content = msg['content']
181 content = msg['content']
182 status = content['status']
182 status = content['status']
183 if status == 'ok':
183 if status == 'ok':
184 self._process_execute_ok(msg)
184 self._process_execute_ok(msg)
185 elif status == 'error':
185 elif status == 'error':
186 self._process_execute_error(msg)
186 self._process_execute_error(msg)
187 elif status == 'abort':
187 elif status == 'abort':
188 self._process_execute_abort(msg)
188 self._process_execute_abort(msg)
189
189
190 self._hidden = True
191 self._show_interpreter_prompt_for_reply(msg)
190 self._show_interpreter_prompt_for_reply(msg)
192 self.executed.emit(msg)
191 self.executed.emit(msg)
193
192
194 def _handle_input_request(self, msg):
193 def _handle_input_request(self, msg):
195 """ Handle requests for raw_input.
194 """ Handle requests for raw_input.
196 """
195 """
197 if self._hidden:
196 if self._hidden:
198 raise RuntimeError('Request for raw input during hidden execution.')
197 raise RuntimeError('Request for raw input during hidden execution.')
199
198
200 # Make sure that all output from the SUB channel has been processed
199 # Make sure that all output from the SUB channel has been processed
201 # before entering readline mode.
200 # before entering readline mode.
202 self.kernel_manager.sub_channel.flush()
201 self.kernel_manager.sub_channel.flush()
203
202
204 def callback(line):
203 def callback(line):
205 self.kernel_manager.rep_channel.input(line)
204 self.kernel_manager.rep_channel.input(line)
206 self._readline(msg['content']['prompt'], callback=callback)
205 self._readline(msg['content']['prompt'], callback=callback)
207
206
208 def _handle_object_info_reply(self, rep):
207 def _handle_object_info_reply(self, rep):
209 """ Handle replies for call tips.
208 """ Handle replies for call tips.
210 """
209 """
211 cursor = self._get_cursor()
210 cursor = self._get_cursor()
212 if rep['parent_header']['msg_id'] == self._call_tip_id and \
211 if rep['parent_header']['msg_id'] == self._call_tip_id and \
213 cursor.position() == self._call_tip_pos:
212 cursor.position() == self._call_tip_pos:
214 doc = rep['content']['docstring']
213 doc = rep['content']['docstring']
215 if doc:
214 if doc:
216 self._call_tip_widget.show_docstring(doc)
215 self._call_tip_widget.show_docstring(doc)
217
216
218 def _handle_pyout(self, msg):
217 def _handle_pyout(self, msg):
219 """ Handle display hook output.
218 """ Handle display hook output.
220 """
219 """
221 self._append_plain_text(msg['content']['data'] + '\n')
220 if not self._hidden and self._is_from_this_session(msg):
221 self._append_plain_text(msg['content']['data'] + '\n')
222
222
223 def _handle_stream(self, msg):
223 def _handle_stream(self, msg):
224 """ Handle stdout, stderr, and stdin.
224 """ Handle stdout, stderr, and stdin.
225 """
225 """
226 self._append_plain_text(msg['content']['data'])
226 if not self._hidden and self._is_from_this_session(msg):
227 self._control.moveCursor(QtGui.QTextCursor.End)
227 self._append_plain_text(msg['content']['data'])
228 self._control.moveCursor(QtGui.QTextCursor.End)
228
229
229 def _started_channels(self):
230 def _started_channels(self):
230 """ Called when the KernelManager channels have started listening or
231 """ Called when the KernelManager channels have started listening or
231 when the frontend is assigned an already listening KernelManager.
232 when the frontend is assigned an already listening KernelManager.
232 """
233 """
233 self._reset()
234 self._reset()
234 self._append_plain_text(self._get_banner())
235 self._append_plain_text(self._get_banner())
235 self._show_interpreter_prompt()
236 self._show_interpreter_prompt()
236
237
237 def _stopped_channels(self):
238 def _stopped_channels(self):
238 """ Called when the KernelManager channels have stopped listening or
239 """ Called when the KernelManager channels have stopped listening or
239 when a listening KernelManager is removed from the frontend.
240 when a listening KernelManager is removed from the frontend.
240 """
241 """
241 # FIXME: Print a message here?
242 # FIXME: Print a message here?
242 pass
243 pass
243
244
244 #---------------------------------------------------------------------------
245 #---------------------------------------------------------------------------
245 # 'FrontendWidget' interface
246 # 'FrontendWidget' interface
246 #---------------------------------------------------------------------------
247 #---------------------------------------------------------------------------
247
248
248 def execute_file(self, path, hidden=False):
249 def execute_file(self, path, hidden=False):
249 """ Attempts to execute file with 'path'. If 'hidden', no output is
250 """ Attempts to execute file with 'path'. If 'hidden', no output is
250 shown.
251 shown.
251 """
252 """
252 self.execute('execfile("%s")' % path, hidden=hidden)
253 self.execute('execfile("%s")' % path, hidden=hidden)
253
254
254 #---------------------------------------------------------------------------
255 #---------------------------------------------------------------------------
255 # 'FrontendWidget' protected interface
256 # 'FrontendWidget' protected interface
256 #---------------------------------------------------------------------------
257 #---------------------------------------------------------------------------
257
258
258 def _call_tip(self):
259 def _call_tip(self):
259 """ Shows a call tip, if appropriate, at the current cursor location.
260 """ Shows a call tip, if appropriate, at the current cursor location.
260 """
261 """
261 # Decide if it makes sense to show a call tip
262 # Decide if it makes sense to show a call tip
262 cursor = self._get_cursor()
263 cursor = self._get_cursor()
263 cursor.movePosition(QtGui.QTextCursor.Left)
264 cursor.movePosition(QtGui.QTextCursor.Left)
264 document = self._control.document()
265 document = self._control.document()
265 if document.characterAt(cursor.position()).toAscii() != '(':
266 if document.characterAt(cursor.position()).toAscii() != '(':
266 return False
267 return False
267 context = self._get_context(cursor)
268 context = self._get_context(cursor)
268 if not context:
269 if not context:
269 return False
270 return False
270
271
271 # Send the metadata request to the kernel
272 # Send the metadata request to the kernel
272 name = '.'.join(context)
273 name = '.'.join(context)
273 self._call_tip_id = self.kernel_manager.xreq_channel.object_info(name)
274 self._call_tip_id = self.kernel_manager.xreq_channel.object_info(name)
274 self._call_tip_pos = self._get_cursor().position()
275 self._call_tip_pos = self._get_cursor().position()
275 return True
276 return True
276
277
277 def _complete(self):
278 def _complete(self):
278 """ Performs completion at the current cursor location.
279 """ Performs completion at the current cursor location.
279 """
280 """
280 # Decide if it makes sense to do completion
281 # Decide if it makes sense to do completion
281 context = self._get_context()
282 context = self._get_context()
282 if not context:
283 if not context:
283 return False
284 return False
284
285
285 # Send the completion request to the kernel
286 # Send the completion request to the kernel
286 text = '.'.join(context)
287 text = '.'.join(context)
287 self._complete_id = self.kernel_manager.xreq_channel.complete(
288 self._complete_id = self.kernel_manager.xreq_channel.complete(
288 text, self._get_input_buffer_cursor_line(), self.input_buffer)
289 text, self._get_input_buffer_cursor_line(), self.input_buffer)
289 self._complete_pos = self._get_cursor().position()
290 self._complete_pos = self._get_cursor().position()
290 return True
291 return True
291
292
292 def _get_banner(self):
293 def _get_banner(self):
293 """ Gets a banner to display at the beginning of a session.
294 """ Gets a banner to display at the beginning of a session.
294 """
295 """
295 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
296 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
296 '"license" for more information.'
297 '"license" for more information.'
297 return banner % (sys.version, sys.platform)
298 return banner % (sys.version, sys.platform)
298
299
299 def _get_context(self, cursor=None):
300 def _get_context(self, cursor=None):
300 """ Gets the context at the current cursor location.
301 """ Gets the context at the current cursor location.
301 """
302 """
302 if cursor is None:
303 if cursor is None:
303 cursor = self._get_cursor()
304 cursor = self._get_cursor()
304 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
305 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
305 QtGui.QTextCursor.KeepAnchor)
306 QtGui.QTextCursor.KeepAnchor)
306 text = str(cursor.selection().toPlainText())
307 text = str(cursor.selection().toPlainText())
307 return self._completion_lexer.get_context(text)
308 return self._completion_lexer.get_context(text)
308
309
309 def _interrupt_kernel(self):
310 def _interrupt_kernel(self):
310 """ Attempts to the interrupt the kernel.
311 """ Attempts to the interrupt the kernel.
311 """
312 """
312 if self.kernel_manager.has_kernel:
313 if self.kernel_manager.has_kernel:
313 self.kernel_manager.signal_kernel(signal.SIGINT)
314 self.kernel_manager.signal_kernel(signal.SIGINT)
314 else:
315 else:
315 self._append_plain_text('Kernel process is either remote or '
316 self._append_plain_text('Kernel process is either remote or '
316 'unspecified. Cannot interrupt.\n')
317 'unspecified. Cannot interrupt.\n')
317
318
318 def _process_execute_abort(self, msg):
319 def _process_execute_abort(self, msg):
319 """ Process a reply for an aborted execution request.
320 """ Process a reply for an aborted execution request.
320 """
321 """
321 self._append_plain_text("ERROR: execution aborted\n")
322 self._append_plain_text("ERROR: execution aborted\n")
322
323
323 def _process_execute_error(self, msg):
324 def _process_execute_error(self, msg):
324 """ Process a reply for an execution request that resulted in an error.
325 """ Process a reply for an execution request that resulted in an error.
325 """
326 """
326 content = msg['content']
327 content = msg['content']
327 traceback = ''.join(content['traceback'])
328 traceback = ''.join(content['traceback'])
328 self._append_plain_text(traceback)
329 self._append_plain_text(traceback)
329
330
330 def _process_execute_ok(self, msg):
331 def _process_execute_ok(self, msg):
331 """ Process a reply for a successful execution equest.
332 """ Process a reply for a successful execution equest.
332 """
333 """
333 # The basic FrontendWidget doesn't handle payloads, as they are a
334 # The basic FrontendWidget doesn't handle payloads, as they are a
334 # mechanism for going beyond the standard Python interpreter model.
335 # mechanism for going beyond the standard Python interpreter model.
335 pass
336 pass
336
337
337 def _show_interpreter_prompt(self):
338 def _show_interpreter_prompt(self):
338 """ Shows a prompt for the interpreter.
339 """ Shows a prompt for the interpreter.
339 """
340 """
340 self._show_prompt('>>> ')
341 self._show_prompt('>>> ')
341
342
342 def _show_interpreter_prompt_for_reply(self, msg):
343 def _show_interpreter_prompt_for_reply(self, msg):
343 """ Shows a prompt for the interpreter given an 'execute_reply' message.
344 """ Shows a prompt for the interpreter given an 'execute_reply' message.
344 """
345 """
345 self._show_interpreter_prompt()
346 self._show_interpreter_prompt()
346
347
347 #------ Signal handlers ----------------------------------------------------
348 #------ Signal handlers ----------------------------------------------------
348
349
349 def _document_contents_change(self, position, removed, added):
350 def _document_contents_change(self, position, removed, added):
350 """ Called whenever the document's content changes. Display a call tip
351 """ Called whenever the document's content changes. Display a call tip
351 if appropriate.
352 if appropriate.
352 """
353 """
353 # Calculate where the cursor should be *after* the change:
354 # Calculate where the cursor should be *after* the change:
354 position += added
355 position += added
355
356
356 document = self._control.document()
357 document = self._control.document()
357 if position == self._get_cursor().position():
358 if position == self._get_cursor().position():
358 self._call_tip()
359 self._call_tip()
@@ -1,275 +1,277 b''
1 # Standard library imports
1 # Standard library imports
2 from subprocess import Popen
2 from subprocess import Popen
3
3
4 # System library imports
4 # System library imports
5 from PyQt4 import QtCore, QtGui
5 from PyQt4 import QtCore, QtGui
6
6
7 # Local imports
7 # Local imports
8 from IPython.core.inputsplitter import IPythonInputSplitter
8 from IPython.core.inputsplitter import IPythonInputSplitter
9 from IPython.core.usage import default_banner
9 from IPython.core.usage import default_banner
10 from frontend_widget import FrontendWidget
10 from frontend_widget import FrontendWidget
11
11
12
12
13 class IPythonPromptBlock(object):
13 class IPythonPromptBlock(object):
14 """ An internal storage object for IPythonWidget.
14 """ An internal storage object for IPythonWidget.
15 """
15 """
16 def __init__(self, block, length, number):
16 def __init__(self, block, length, number):
17 self.block = block
17 self.block = block
18 self.length = length
18 self.length = length
19 self.number = number
19 self.number = number
20
20
21
21
22 class IPythonWidget(FrontendWidget):
22 class IPythonWidget(FrontendWidget):
23 """ A FrontendWidget for an IPython kernel.
23 """ A FrontendWidget for an IPython kernel.
24 """
24 """
25
25
26 # Signal emitted when an editor is needed for a file and the editor has been
26 # Signal emitted when an editor is needed for a file and the editor has been
27 # specified as 'custom'.
27 # specified as 'custom'.
28 custom_edit_requested = QtCore.pyqtSignal(object)
28 custom_edit_requested = QtCore.pyqtSignal(object)
29
29
30 # The default stylesheet: black text on a white background.
30 # The default stylesheet: black text on a white background.
31 default_stylesheet = """
31 default_stylesheet = """
32 .error { color: red; }
32 .error { color: red; }
33 .in-prompt { color: navy; }
33 .in-prompt { color: navy; }
34 .in-prompt-number { font-weight: bold; }
34 .in-prompt-number { font-weight: bold; }
35 .out-prompt { color: darkred; }
35 .out-prompt { color: darkred; }
36 .out-prompt-number { font-weight: bold; }
36 .out-prompt-number { font-weight: bold; }
37 """
37 """
38
38
39 # A dark stylesheet: white text on a black background.
39 # A dark stylesheet: white text on a black background.
40 dark_stylesheet = """
40 dark_stylesheet = """
41 QPlainTextEdit, QTextEdit { background-color: black; color: white }
41 QPlainTextEdit, QTextEdit { background-color: black; color: white }
42 QFrame { border: 1px solid grey; }
42 QFrame { border: 1px solid grey; }
43 .error { color: red; }
43 .error { color: red; }
44 .in-prompt { color: lime; }
44 .in-prompt { color: lime; }
45 .in-prompt-number { color: lime; font-weight: bold; }
45 .in-prompt-number { color: lime; font-weight: bold; }
46 .out-prompt { color: red; }
46 .out-prompt { color: red; }
47 .out-prompt-number { color: red; font-weight: bold; }
47 .out-prompt-number { color: red; font-weight: bold; }
48 """
48 """
49
49
50 # Default prompts.
50 # Default prompts.
51 in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
51 in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
52 out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
52 out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
53
53
54 # FrontendWidget protected class attributes.
54 # FrontendWidget protected class attributes.
55 #_input_splitter_class = IPythonInputSplitter
55 #_input_splitter_class = IPythonInputSplitter
56
56
57 #---------------------------------------------------------------------------
57 #---------------------------------------------------------------------------
58 # 'object' interface
58 # 'object' interface
59 #---------------------------------------------------------------------------
59 #---------------------------------------------------------------------------
60
60
61 def __init__(self, *args, **kw):
61 def __init__(self, *args, **kw):
62 super(IPythonWidget, self).__init__(*args, **kw)
62 super(IPythonWidget, self).__init__(*args, **kw)
63
63
64 # IPythonWidget protected variables.
64 # IPythonWidget protected variables.
65 self._previous_prompt_obj = None
65 self._previous_prompt_obj = None
66
66
67 # Set a default editor and stylesheet.
67 # Set a default editor and stylesheet.
68 self.set_editor('default')
68 self.set_editor('default')
69 self.reset_styling()
69 self.reset_styling()
70
70
71 #---------------------------------------------------------------------------
71 #---------------------------------------------------------------------------
72 # 'BaseFrontendMixin' abstract interface
72 # 'BaseFrontendMixin' abstract interface
73 #---------------------------------------------------------------------------
73 #---------------------------------------------------------------------------
74
74
75 def _handle_pyout(self, msg):
75 def _handle_pyout(self, msg):
76 """ Reimplemented for IPython-style "display hook".
76 """ Reimplemented for IPython-style "display hook".
77 """
77 """
78 content = msg['content']
78 if not self._hidden and self._is_from_this_session(msg):
79 prompt_number = content['prompt_number']
79 content = msg['content']
80 self._append_plain_text(content['output_sep'])
80 prompt_number = content['prompt_number']
81 self._append_html(self._make_out_prompt(prompt_number))
81 self._append_plain_text(content['output_sep'])
82 self._append_plain_text(content['data'] + '\n' + content['output_sep2'])
82 self._append_html(self._make_out_prompt(prompt_number))
83 self._append_plain_text(content['data'] + '\n' +
84 content['output_sep2'])
83
85
84 #---------------------------------------------------------------------------
86 #---------------------------------------------------------------------------
85 # 'FrontendWidget' interface
87 # 'FrontendWidget' interface
86 #---------------------------------------------------------------------------
88 #---------------------------------------------------------------------------
87
89
88 def execute_file(self, path, hidden=False):
90 def execute_file(self, path, hidden=False):
89 """ Reimplemented to use the 'run' magic.
91 """ Reimplemented to use the 'run' magic.
90 """
92 """
91 self.execute('run %s' % path, hidden=hidden)
93 self.execute('run %s' % path, hidden=hidden)
92
94
93 #---------------------------------------------------------------------------
95 #---------------------------------------------------------------------------
94 # 'FrontendWidget' protected interface
96 # 'FrontendWidget' protected interface
95 #---------------------------------------------------------------------------
97 #---------------------------------------------------------------------------
96
98
97 def _get_banner(self):
99 def _get_banner(self):
98 """ Reimplemented to return IPython's default banner.
100 """ Reimplemented to return IPython's default banner.
99 """
101 """
100 return default_banner + '\n'
102 return default_banner + '\n'
101
103
102 def _process_execute_error(self, msg):
104 def _process_execute_error(self, msg):
103 """ Reimplemented for IPython-style traceback formatting.
105 """ Reimplemented for IPython-style traceback formatting.
104 """
106 """
105 content = msg['content']
107 content = msg['content']
106 traceback_lines = content['traceback'][:]
108 traceback_lines = content['traceback'][:]
107 traceback = ''.join(traceback_lines)
109 traceback = ''.join(traceback_lines)
108 traceback = traceback.replace(' ', '&nbsp;')
110 traceback = traceback.replace(' ', '&nbsp;')
109 traceback = traceback.replace('\n', '<br/>')
111 traceback = traceback.replace('\n', '<br/>')
110
112
111 ename = content['ename']
113 ename = content['ename']
112 ename_styled = '<span class="error">%s</span>' % ename
114 ename_styled = '<span class="error">%s</span>' % ename
113 traceback = traceback.replace(ename, ename_styled)
115 traceback = traceback.replace(ename, ename_styled)
114
116
115 self._append_html(traceback)
117 self._append_html(traceback)
116
118
117 def _show_interpreter_prompt(self, number=None, input_sep='\n'):
119 def _show_interpreter_prompt(self, number=None, input_sep='\n'):
118 """ Reimplemented for IPython-style prompts.
120 """ Reimplemented for IPython-style prompts.
119 """
121 """
120 # TODO: If a number was not specified, make a prompt number request.
122 # TODO: If a number was not specified, make a prompt number request.
121 if number is None:
123 if number is None:
122 number = 0
124 number = 0
123
125
124 # Show a new prompt and save information about it so that it can be
126 # Show a new prompt and save information about it so that it can be
125 # updated later if the prompt number turns out to be wrong.
127 # updated later if the prompt number turns out to be wrong.
126 self._append_plain_text(input_sep)
128 self._append_plain_text(input_sep)
127 self._show_prompt(self._make_in_prompt(number), html=True)
129 self._show_prompt(self._make_in_prompt(number), html=True)
128 block = self._control.document().lastBlock()
130 block = self._control.document().lastBlock()
129 length = len(self._prompt)
131 length = len(self._prompt)
130 self._previous_prompt_obj = IPythonPromptBlock(block, length, number)
132 self._previous_prompt_obj = IPythonPromptBlock(block, length, number)
131
133
132 # Update continuation prompt to reflect (possibly) new prompt length.
134 # Update continuation prompt to reflect (possibly) new prompt length.
133 self._set_continuation_prompt(
135 self._set_continuation_prompt(
134 self._make_continuation_prompt(self._prompt), html=True)
136 self._make_continuation_prompt(self._prompt), html=True)
135
137
136 def _show_interpreter_prompt_for_reply(self, msg):
138 def _show_interpreter_prompt_for_reply(self, msg):
137 """ Reimplemented for IPython-style prompts.
139 """ Reimplemented for IPython-style prompts.
138 """
140 """
139 # Update the old prompt number if necessary.
141 # Update the old prompt number if necessary.
140 content = msg['content']
142 content = msg['content']
141 previous_prompt_number = content['prompt_number']
143 previous_prompt_number = content['prompt_number']
142 if self._previous_prompt_obj and \
144 if self._previous_prompt_obj and \
143 self._previous_prompt_obj.number != previous_prompt_number:
145 self._previous_prompt_obj.number != previous_prompt_number:
144 block = self._previous_prompt_obj.block
146 block = self._previous_prompt_obj.block
145 if block.isValid():
147 if block.isValid():
146
148
147 # Remove the old prompt and insert a new prompt.
149 # Remove the old prompt and insert a new prompt.
148 cursor = QtGui.QTextCursor(block)
150 cursor = QtGui.QTextCursor(block)
149 cursor.movePosition(QtGui.QTextCursor.Right,
151 cursor.movePosition(QtGui.QTextCursor.Right,
150 QtGui.QTextCursor.KeepAnchor,
152 QtGui.QTextCursor.KeepAnchor,
151 self._previous_prompt_obj.length)
153 self._previous_prompt_obj.length)
152 prompt = self._make_in_prompt(previous_prompt_number)
154 prompt = self._make_in_prompt(previous_prompt_number)
153 self._prompt = self._insert_html_fetching_plain_text(
155 self._prompt = self._insert_html_fetching_plain_text(
154 cursor, prompt)
156 cursor, prompt)
155
157
156 # XXX: When the HTML is inserted, Qt blows away the syntax
158 # XXX: When the HTML is inserted, Qt blows away the syntax
157 # highlighting for the line. I cannot for the life of me
159 # highlighting for the line. I cannot for the life of me
158 # determine how to preserve the existing formatting.
160 # determine how to preserve the existing formatting.
159 self._highlighter.highlighting_on = True
161 self._highlighter.highlighting_on = True
160 self._highlighter.rehighlightBlock(cursor.block())
162 self._highlighter.rehighlightBlock(cursor.block())
161 self._highlighter.highlighting_on = False
163 self._highlighter.highlighting_on = False
162
164
163 self._previous_prompt_obj = None
165 self._previous_prompt_obj = None
164
166
165 # Show a new prompt with the kernel's estimated prompt number.
167 # Show a new prompt with the kernel's estimated prompt number.
166 next_prompt = content['next_prompt']
168 next_prompt = content['next_prompt']
167 self._show_interpreter_prompt(next_prompt['prompt_number'],
169 self._show_interpreter_prompt(next_prompt['prompt_number'],
168 next_prompt['input_sep'])
170 next_prompt['input_sep'])
169
171
170 #---------------------------------------------------------------------------
172 #---------------------------------------------------------------------------
171 # 'IPythonWidget' interface
173 # 'IPythonWidget' interface
172 #---------------------------------------------------------------------------
174 #---------------------------------------------------------------------------
173
175
174 def edit(self, filename):
176 def edit(self, filename):
175 """ Opens a Python script for editing.
177 """ Opens a Python script for editing.
176
178
177 Parameters:
179 Parameters:
178 -----------
180 -----------
179 filename : str
181 filename : str
180 A path to a local system file.
182 A path to a local system file.
181
183
182 Raises:
184 Raises:
183 -------
185 -------
184 OSError
186 OSError
185 If the editor command cannot be executed.
187 If the editor command cannot be executed.
186 """
188 """
187 if self._editor == 'default':
189 if self._editor == 'default':
188 url = QtCore.QUrl.fromLocalFile(filename)
190 url = QtCore.QUrl.fromLocalFile(filename)
189 if not QtGui.QDesktopServices.openUrl(url):
191 if not QtGui.QDesktopServices.openUrl(url):
190 message = 'Failed to open %s with the default application'
192 message = 'Failed to open %s with the default application'
191 raise OSError(message % repr(filename))
193 raise OSError(message % repr(filename))
192 elif self._editor is None:
194 elif self._editor is None:
193 self.custom_edit_requested.emit(filename)
195 self.custom_edit_requested.emit(filename)
194 else:
196 else:
195 Popen(self._editor + [filename])
197 Popen(self._editor + [filename])
196
198
197 def reset_styling(self):
199 def reset_styling(self):
198 """ Restores the default IPythonWidget styling.
200 """ Restores the default IPythonWidget styling.
199 """
201 """
200 self.set_styling(self.default_stylesheet, syntax_style='default')
202 self.set_styling(self.default_stylesheet, syntax_style='default')
201 #self.set_styling(self.dark_stylesheet, syntax_style='monokai')
203 #self.set_styling(self.dark_stylesheet, syntax_style='monokai')
202
204
203 def set_editor(self, editor):
205 def set_editor(self, editor):
204 """ Sets the editor to use with the %edit magic.
206 """ Sets the editor to use with the %edit magic.
205
207
206 Parameters:
208 Parameters:
207 -----------
209 -----------
208 editor : str or sequence of str
210 editor : str or sequence of str
209 A command suitable for use with Popen. This command will be executed
211 A command suitable for use with Popen. This command will be executed
210 with a single argument--a filename--when editing is requested.
212 with a single argument--a filename--when editing is requested.
211
213
212 This parameter also takes two special values:
214 This parameter also takes two special values:
213 'default' : Files will be edited with the system default
215 'default' : Files will be edited with the system default
214 application for Python files.
216 application for Python files.
215 'custom' : Emit a 'custom_edit_requested(str)' signal instead
217 'custom' : Emit a 'custom_edit_requested(str)' signal instead
216 of opening an editor.
218 of opening an editor.
217 """
219 """
218 if editor == 'default':
220 if editor == 'default':
219 self._editor = 'default'
221 self._editor = 'default'
220 elif editor == 'custom':
222 elif editor == 'custom':
221 self._editor = None
223 self._editor = None
222 elif isinstance(editor, basestring):
224 elif isinstance(editor, basestring):
223 self._editor = [ editor ]
225 self._editor = [ editor ]
224 else:
226 else:
225 self._editor = list(editor)
227 self._editor = list(editor)
226
228
227 def set_styling(self, stylesheet, syntax_style=None):
229 def set_styling(self, stylesheet, syntax_style=None):
228 """ Sets the IPythonWidget styling.
230 """ Sets the IPythonWidget styling.
229
231
230 Parameters:
232 Parameters:
231 -----------
233 -----------
232 stylesheet : str
234 stylesheet : str
233 A CSS stylesheet. The stylesheet can contain classes for:
235 A CSS stylesheet. The stylesheet can contain classes for:
234 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
236 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
235 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
237 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
236 3. IPython: .error, .in-prompt, .out-prompt, etc.
238 3. IPython: .error, .in-prompt, .out-prompt, etc.
237
239
238 syntax_style : str or None [default None]
240 syntax_style : str or None [default None]
239 If specified, use the Pygments style with given name. Otherwise,
241 If specified, use the Pygments style with given name. Otherwise,
240 the stylesheet is queried for Pygments style information.
242 the stylesheet is queried for Pygments style information.
241 """
243 """
242 self.setStyleSheet(stylesheet)
244 self.setStyleSheet(stylesheet)
243 self._control.document().setDefaultStyleSheet(stylesheet)
245 self._control.document().setDefaultStyleSheet(stylesheet)
244 if self._page_control:
246 if self._page_control:
245 self._page_control.document().setDefaultStyleSheet(stylesheet)
247 self._page_control.document().setDefaultStyleSheet(stylesheet)
246
248
247 if syntax_style is None:
249 if syntax_style is None:
248 self._highlighter.set_style_sheet(stylesheet)
250 self._highlighter.set_style_sheet(stylesheet)
249 else:
251 else:
250 self._highlighter.set_style(syntax_style)
252 self._highlighter.set_style(syntax_style)
251
253
252 #---------------------------------------------------------------------------
254 #---------------------------------------------------------------------------
253 # 'IPythonWidget' protected interface
255 # 'IPythonWidget' protected interface
254 #---------------------------------------------------------------------------
256 #---------------------------------------------------------------------------
255
257
256 def _make_in_prompt(self, number):
258 def _make_in_prompt(self, number):
257 """ Given a prompt number, returns an HTML In prompt.
259 """ Given a prompt number, returns an HTML In prompt.
258 """
260 """
259 body = self.in_prompt % number
261 body = self.in_prompt % number
260 return '<span class="in-prompt">%s</span>' % body
262 return '<span class="in-prompt">%s</span>' % body
261
263
262 def _make_continuation_prompt(self, prompt):
264 def _make_continuation_prompt(self, prompt):
263 """ Given a plain text version of an In prompt, returns an HTML
265 """ Given a plain text version of an In prompt, returns an HTML
264 continuation prompt.
266 continuation prompt.
265 """
267 """
266 end_chars = '...: '
268 end_chars = '...: '
267 space_count = len(prompt.lstrip('\n')) - len(end_chars)
269 space_count = len(prompt.lstrip('\n')) - len(end_chars)
268 body = '&nbsp;' * space_count + end_chars
270 body = '&nbsp;' * space_count + end_chars
269 return '<span class="in-prompt">%s</span>' % body
271 return '<span class="in-prompt">%s</span>' % body
270
272
271 def _make_out_prompt(self, number):
273 def _make_out_prompt(self, number):
272 """ Given a prompt number, returns an HTML Out prompt.
274 """ Given a prompt number, returns an HTML Out prompt.
273 """
275 """
274 body = self.out_prompt % number
276 body = self.out_prompt % number
275 return '<span class="out-prompt">%s</span>' % body
277 return '<span class="out-prompt">%s</span>' % body
@@ -1,377 +1,379 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
16
17 # Standard library imports.
17 # Standard library imports.
18 import __builtin__
18 import __builtin__
19 import sys
19 import sys
20 import time
20 import time
21 import traceback
21 import traceback
22
22
23 # System library imports.
23 # System library imports.
24 import zmq
24 import zmq
25
25
26 # Local imports.
26 # Local imports.
27 from IPython.config.configurable import Configurable
27 from IPython.config.configurable import Configurable
28 from IPython.utils.traitlets import Instance
28 from IPython.utils.traitlets import Instance
29 from completer import KernelCompleter
29 from completer import KernelCompleter
30 from entry_point import base_launch_kernel, make_argument_parser, make_kernel, \
30 from entry_point import base_launch_kernel, make_argument_parser, make_kernel, \
31 start_kernel
31 start_kernel
32 from iostream import OutStream
32 from iostream import OutStream
33 from session import Session, Message
33 from session import Session, Message
34 from zmqshell import ZMQInteractiveShell
34 from zmqshell import ZMQInteractiveShell
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Main kernel class
37 # Main kernel class
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40 class Kernel(Configurable):
40 class Kernel(Configurable):
41
41
42 #---------------------------------------------------------------------------
42 #---------------------------------------------------------------------------
43 # Kernel interface
43 # Kernel interface
44 #---------------------------------------------------------------------------
44 #---------------------------------------------------------------------------
45
45
46 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
46 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
47 session = Instance(Session)
47 session = Instance(Session)
48 reply_socket = Instance('zmq.Socket')
48 reply_socket = Instance('zmq.Socket')
49 pub_socket = Instance('zmq.Socket')
49 pub_socket = Instance('zmq.Socket')
50 req_socket = Instance('zmq.Socket')
50 req_socket = Instance('zmq.Socket')
51
51
52 # Maps user-friendly backend names to matplotlib backend identifiers.
52 # Maps user-friendly backend names to matplotlib backend identifiers.
53 _pylab_map = { 'tk': 'TkAgg',
53 _pylab_map = { 'tk': 'TkAgg',
54 'gtk': 'GTKAgg',
54 'gtk': 'GTKAgg',
55 'wx': 'WXAgg',
55 'wx': 'WXAgg',
56 'qt': 'Qt4Agg', # qt3 not supported
56 'qt': 'Qt4Agg', # qt3 not supported
57 'qt4': 'Qt4Agg',
57 'qt4': 'Qt4Agg',
58 'payload-svg' : \
58 'payload-svg' : \
59 'module://IPython.zmq.pylab.backend_payload_svg' }
59 'module://IPython.zmq.pylab.backend_payload_svg' }
60
60
61 def __init__(self, **kwargs):
61 def __init__(self, **kwargs):
62 super(Kernel, self).__init__(**kwargs)
62 super(Kernel, self).__init__(**kwargs)
63
63
64 # Initialize the InteractiveShell subclass
64 # Initialize the InteractiveShell subclass
65 self.shell = ZMQInteractiveShell.instance()
65 self.shell = ZMQInteractiveShell.instance()
66 self.shell.displayhook.session = self.session
66 self.shell.displayhook.session = self.session
67 self.shell.displayhook.pub_socket = self.pub_socket
67 self.shell.displayhook.pub_socket = self.pub_socket
68
68
69 # Build dict of handlers for message types
69 # Build dict of handlers for message types
70 msg_types = [ 'execute_request', 'complete_request',
70 msg_types = [ 'execute_request', 'complete_request',
71 'object_info_request', 'prompt_request',
71 'object_info_request', 'prompt_request',
72 'history_request' ]
72 'history_request' ]
73 self.handlers = {}
73 self.handlers = {}
74 for msg_type in msg_types:
74 for msg_type in msg_types:
75 self.handlers[msg_type] = getattr(self, msg_type)
75 self.handlers[msg_type] = getattr(self, msg_type)
76
76
77 def activate_pylab(self, backend=None, import_all=True):
77 def activate_pylab(self, backend=None, import_all=True):
78 """ Activates pylab in this kernel's namespace.
78 """ Activates pylab in this kernel's namespace.
79
79
80 Parameters:
80 Parameters:
81 -----------
81 -----------
82 backend : str, optional
82 backend : str, optional
83 A valid backend name.
83 A valid backend name.
84
84
85 import_all : bool, optional
85 import_all : bool, optional
86 If true, an 'import *' is done from numpy and pylab.
86 If true, an 'import *' is done from numpy and pylab.
87 """
87 """
88 # FIXME: This is adapted from IPython.lib.pylabtools.pylab_activate.
88 # FIXME: This is adapted from IPython.lib.pylabtools.pylab_activate.
89 # Common funtionality should be refactored.
89 # Common funtionality should be refactored.
90
90
91 # We must set the desired backend before importing pylab.
91 # We must set the desired backend before importing pylab.
92 import matplotlib
92 import matplotlib
93 if backend:
93 if backend:
94 backend_id = self._pylab_map[backend]
94 backend_id = self._pylab_map[backend]
95 if backend_id.startswith('module://'):
95 if backend_id.startswith('module://'):
96 # Work around bug in matplotlib: matplotlib.use converts the
96 # Work around bug in matplotlib: matplotlib.use converts the
97 # backend_id to lowercase even if a module name is specified!
97 # backend_id to lowercase even if a module name is specified!
98 matplotlib.rcParams['backend'] = backend_id
98 matplotlib.rcParams['backend'] = backend_id
99 else:
99 else:
100 matplotlib.use(backend_id)
100 matplotlib.use(backend_id)
101
101
102 # Import numpy as np/pyplot as plt are conventions we're trying to
102 # Import numpy as np/pyplot as plt are conventions we're trying to
103 # somewhat standardize on. Making them available to users by default
103 # somewhat standardize on. Making them available to users by default
104 # will greatly help this.
104 # will greatly help this.
105 exec ("import numpy\n"
105 exec ("import numpy\n"
106 "import matplotlib\n"
106 "import matplotlib\n"
107 "from matplotlib import pylab, mlab, pyplot\n"
107 "from matplotlib import pylab, mlab, pyplot\n"
108 "np = numpy\n"
108 "np = numpy\n"
109 "plt = pyplot\n"
109 "plt = pyplot\n"
110 ) in self.shell.user_ns
110 ) in self.shell.user_ns
111
111
112 if import_all:
112 if import_all:
113 exec("from matplotlib.pylab import *\n"
113 exec("from matplotlib.pylab import *\n"
114 "from numpy import *\n") in self.shell.user_ns
114 "from numpy import *\n") in self.shell.user_ns
115
115
116 matplotlib.interactive(True)
116 matplotlib.interactive(True)
117
117
118 def start(self):
118 def start(self):
119 """ Start the kernel main loop.
119 """ Start the kernel main loop.
120 """
120 """
121
121
122 while True:
122 while True:
123 ident = self.reply_socket.recv()
123 ident = self.reply_socket.recv()
124 assert self.reply_socket.rcvmore(), "Missing message part."
124 assert self.reply_socket.rcvmore(), "Missing message part."
125 msg = self.reply_socket.recv_json()
125 msg = self.reply_socket.recv_json()
126 omsg = Message(msg)
126 omsg = Message(msg)
127 print>>sys.__stdout__
127 print>>sys.__stdout__
128 print>>sys.__stdout__, omsg
128 print>>sys.__stdout__, omsg
129 handler = self.handlers.get(omsg.msg_type, None)
129 handler = self.handlers.get(omsg.msg_type, None)
130 if handler is None:
130 if handler is None:
131 print >> sys.__stderr__, "UNKNOWN MESSAGE TYPE:", omsg
131 print >> sys.__stderr__, "UNKNOWN MESSAGE TYPE:", omsg
132 else:
132 else:
133 handler(ident, omsg)
133 handler(ident, omsg)
134
134
135 #---------------------------------------------------------------------------
135 #---------------------------------------------------------------------------
136 # Kernel request handlers
136 # Kernel request handlers
137 #---------------------------------------------------------------------------
137 #---------------------------------------------------------------------------
138
138
139 def execute_request(self, ident, parent):
139 def execute_request(self, ident, parent):
140 try:
140 try:
141 code = parent[u'content'][u'code']
141 code = parent[u'content'][u'code']
142 except:
142 except:
143 print>>sys.__stderr__, "Got bad msg: "
143 print>>sys.__stderr__, "Got bad msg: "
144 print>>sys.__stderr__, Message(parent)
144 print>>sys.__stderr__, Message(parent)
145 return
145 return
146 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
146 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
147 self.pub_socket.send_json(pyin_msg)
147 self.pub_socket.send_json(pyin_msg)
148
148
149 try:
149 try:
150 # Replace raw_input. Note that is not sufficient to replace
150 # Replace raw_input. Note that is not sufficient to replace
151 # raw_input in the user namespace.
151 # raw_input in the user namespace.
152 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
152 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
153 __builtin__.raw_input = raw_input
153 __builtin__.raw_input = raw_input
154
154
155 # Set the parent message of the display hook.
155 # Set the parent message of the display hook and out streams.
156 self.shell.displayhook.set_parent(parent)
156 self.shell.displayhook.set_parent(parent)
157 sys.stdout.set_parent(parent)
158 sys.stderr.set_parent(parent)
157
159
158 self.shell.runlines(code)
160 self.shell.runlines(code)
159 except:
161 except:
160 etype, evalue, tb = sys.exc_info()
162 etype, evalue, tb = sys.exc_info()
161 tb = traceback.format_exception(etype, evalue, tb)
163 tb = traceback.format_exception(etype, evalue, tb)
162 exc_content = {
164 exc_content = {
163 u'status' : u'error',
165 u'status' : u'error',
164 u'traceback' : tb,
166 u'traceback' : tb,
165 u'ename' : unicode(etype.__name__),
167 u'ename' : unicode(etype.__name__),
166 u'evalue' : unicode(evalue)
168 u'evalue' : unicode(evalue)
167 }
169 }
168 exc_msg = self.session.msg(u'pyerr', exc_content, parent)
170 exc_msg = self.session.msg(u'pyerr', exc_content, parent)
169 self.pub_socket.send_json(exc_msg)
171 self.pub_socket.send_json(exc_msg)
170 reply_content = exc_content
172 reply_content = exc_content
171 else:
173 else:
172 payload = self.shell.payload_manager.read_payload()
174 payload = self.shell.payload_manager.read_payload()
173 # Be agressive about clearing the payload because we don't want
175 # Be agressive about clearing the payload because we don't want
174 # it to sit in memory until the next execute_request comes in.
176 # it to sit in memory until the next execute_request comes in.
175 self.shell.payload_manager.clear_payload()
177 self.shell.payload_manager.clear_payload()
176 reply_content = { 'status' : 'ok', 'payload' : payload }
178 reply_content = { 'status' : 'ok', 'payload' : payload }
177
179
178 # Compute the prompt information
180 # Compute the prompt information
179 prompt_number = self.shell.displayhook.prompt_count
181 prompt_number = self.shell.displayhook.prompt_count
180 reply_content['prompt_number'] = prompt_number
182 reply_content['prompt_number'] = prompt_number
181 prompt_string = self.shell.displayhook.prompt1.peek_next_prompt()
183 prompt_string = self.shell.displayhook.prompt1.peek_next_prompt()
182 next_prompt = {'prompt_string' : prompt_string,
184 next_prompt = {'prompt_string' : prompt_string,
183 'prompt_number' : prompt_number+1,
185 'prompt_number' : prompt_number+1,
184 'input_sep' : self.shell.displayhook.input_sep}
186 'input_sep' : self.shell.displayhook.input_sep}
185 reply_content['next_prompt'] = next_prompt
187 reply_content['next_prompt'] = next_prompt
186
188
187 # Flush output before sending the reply.
189 # Flush output before sending the reply.
188 sys.stderr.flush()
190 sys.stderr.flush()
189 sys.stdout.flush()
191 sys.stdout.flush()
190
192
191 # Send the reply.
193 # Send the reply.
192 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
194 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
193 print>>sys.__stdout__, Message(reply_msg)
195 print>>sys.__stdout__, Message(reply_msg)
194 self.reply_socket.send(ident, zmq.SNDMORE)
196 self.reply_socket.send(ident, zmq.SNDMORE)
195 self.reply_socket.send_json(reply_msg)
197 self.reply_socket.send_json(reply_msg)
196 if reply_msg['content']['status'] == u'error':
198 if reply_msg['content']['status'] == u'error':
197 self._abort_queue()
199 self._abort_queue()
198
200
199 def complete_request(self, ident, parent):
201 def complete_request(self, ident, parent):
200 matches = {'matches' : self._complete(parent),
202 matches = {'matches' : self._complete(parent),
201 'status' : 'ok'}
203 'status' : 'ok'}
202 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
204 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
203 matches, parent, ident)
205 matches, parent, ident)
204 print >> sys.__stdout__, completion_msg
206 print >> sys.__stdout__, completion_msg
205
207
206 def object_info_request(self, ident, parent):
208 def object_info_request(self, ident, parent):
207 context = parent['content']['oname'].split('.')
209 context = parent['content']['oname'].split('.')
208 object_info = self._object_info(context)
210 object_info = self._object_info(context)
209 msg = self.session.send(self.reply_socket, 'object_info_reply',
211 msg = self.session.send(self.reply_socket, 'object_info_reply',
210 object_info, parent, ident)
212 object_info, parent, ident)
211 print >> sys.__stdout__, msg
213 print >> sys.__stdout__, msg
212
214
213 def prompt_request(self, ident, parent):
215 def prompt_request(self, ident, parent):
214 prompt_number = self.shell.displayhook.prompt_count
216 prompt_number = self.shell.displayhook.prompt_count
215 prompt_string = self.shell.displayhook.prompt1.peek_next_prompt()
217 prompt_string = self.shell.displayhook.prompt1.peek_next_prompt()
216 content = {'prompt_string' : prompt_string,
218 content = {'prompt_string' : prompt_string,
217 'prompt_number' : prompt_number+1}
219 'prompt_number' : prompt_number+1}
218 msg = self.session.send(self.reply_socket, 'prompt_reply',
220 msg = self.session.send(self.reply_socket, 'prompt_reply',
219 content, parent, ident)
221 content, parent, ident)
220 print >> sys.__stdout__, msg
222 print >> sys.__stdout__, msg
221
223
222 def history_request(self, ident, parent):
224 def history_request(self, ident, parent):
223 output = parent['content'].get('output', True)
225 output = parent['content'].get('output', True)
224 index = parent['content'].get('index')
226 index = parent['content'].get('index')
225 raw = parent['content'].get('raw', False)
227 raw = parent['content'].get('raw', False)
226 hist = self.shell.get_history(index=index, raw=raw, output=output)
228 hist = self.shell.get_history(index=index, raw=raw, output=output)
227 content = {'history' : hist}
229 content = {'history' : hist}
228 msg = self.session.send(self.reply_socket, 'history_reply',
230 msg = self.session.send(self.reply_socket, 'history_reply',
229 content, parent, ident)
231 content, parent, ident)
230 print >> sys.__stdout__, msg
232 print >> sys.__stdout__, msg
231
233
232 #---------------------------------------------------------------------------
234 #---------------------------------------------------------------------------
233 # Protected interface
235 # Protected interface
234 #---------------------------------------------------------------------------
236 #---------------------------------------------------------------------------
235
237
236 def _abort_queue(self):
238 def _abort_queue(self):
237 while True:
239 while True:
238 try:
240 try:
239 ident = self.reply_socket.recv(zmq.NOBLOCK)
241 ident = self.reply_socket.recv(zmq.NOBLOCK)
240 except zmq.ZMQError, e:
242 except zmq.ZMQError, e:
241 if e.errno == zmq.EAGAIN:
243 if e.errno == zmq.EAGAIN:
242 break
244 break
243 else:
245 else:
244 assert self.reply_socket.rcvmore(), "Unexpected missing message part."
246 assert self.reply_socket.rcvmore(), "Unexpected missing message part."
245 msg = self.reply_socket.recv_json()
247 msg = self.reply_socket.recv_json()
246 print>>sys.__stdout__, "Aborting:"
248 print>>sys.__stdout__, "Aborting:"
247 print>>sys.__stdout__, Message(msg)
249 print>>sys.__stdout__, Message(msg)
248 msg_type = msg['msg_type']
250 msg_type = msg['msg_type']
249 reply_type = msg_type.split('_')[0] + '_reply'
251 reply_type = msg_type.split('_')[0] + '_reply'
250 reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
252 reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
251 print>>sys.__stdout__, Message(reply_msg)
253 print>>sys.__stdout__, Message(reply_msg)
252 self.reply_socket.send(ident,zmq.SNDMORE)
254 self.reply_socket.send(ident,zmq.SNDMORE)
253 self.reply_socket.send_json(reply_msg)
255 self.reply_socket.send_json(reply_msg)
254 # We need to wait a bit for requests to come in. This can probably
256 # We need to wait a bit for requests to come in. This can probably
255 # be set shorter for true asynchronous clients.
257 # be set shorter for true asynchronous clients.
256 time.sleep(0.1)
258 time.sleep(0.1)
257
259
258 def _raw_input(self, prompt, ident, parent):
260 def _raw_input(self, prompt, ident, parent):
259 # Flush output before making the request.
261 # Flush output before making the request.
260 sys.stderr.flush()
262 sys.stderr.flush()
261 sys.stdout.flush()
263 sys.stdout.flush()
262
264
263 # Send the input request.
265 # Send the input request.
264 content = dict(prompt=prompt)
266 content = dict(prompt=prompt)
265 msg = self.session.msg(u'input_request', content, parent)
267 msg = self.session.msg(u'input_request', content, parent)
266 self.req_socket.send_json(msg)
268 self.req_socket.send_json(msg)
267
269
268 # Await a response.
270 # Await a response.
269 reply = self.req_socket.recv_json()
271 reply = self.req_socket.recv_json()
270 try:
272 try:
271 value = reply['content']['value']
273 value = reply['content']['value']
272 except:
274 except:
273 print>>sys.__stderr__, "Got bad raw_input reply: "
275 print>>sys.__stderr__, "Got bad raw_input reply: "
274 print>>sys.__stderr__, Message(parent)
276 print>>sys.__stderr__, Message(parent)
275 value = ''
277 value = ''
276 return value
278 return value
277
279
278 def _complete(self, msg):
280 def _complete(self, msg):
279 return self.shell.complete(msg.content.line)
281 return self.shell.complete(msg.content.line)
280
282
281 def _object_info(self, context):
283 def _object_info(self, context):
282 symbol, leftover = self._symbol_from_context(context)
284 symbol, leftover = self._symbol_from_context(context)
283 if symbol is not None and not leftover:
285 if symbol is not None and not leftover:
284 doc = getattr(symbol, '__doc__', '')
286 doc = getattr(symbol, '__doc__', '')
285 else:
287 else:
286 doc = ''
288 doc = ''
287 object_info = dict(docstring = doc)
289 object_info = dict(docstring = doc)
288 return object_info
290 return object_info
289
291
290 def _symbol_from_context(self, context):
292 def _symbol_from_context(self, context):
291 if not context:
293 if not context:
292 return None, context
294 return None, context
293
295
294 base_symbol_string = context[0]
296 base_symbol_string = context[0]
295 symbol = self.shell.user_ns.get(base_symbol_string, None)
297 symbol = self.shell.user_ns.get(base_symbol_string, None)
296 if symbol is None:
298 if symbol is None:
297 symbol = __builtin__.__dict__.get(base_symbol_string, None)
299 symbol = __builtin__.__dict__.get(base_symbol_string, None)
298 if symbol is None:
300 if symbol is None:
299 return None, context
301 return None, context
300
302
301 context = context[1:]
303 context = context[1:]
302 for i, name in enumerate(context):
304 for i, name in enumerate(context):
303 new_symbol = getattr(symbol, name, None)
305 new_symbol = getattr(symbol, name, None)
304 if new_symbol is None:
306 if new_symbol is None:
305 return symbol, context[i:]
307 return symbol, context[i:]
306 else:
308 else:
307 symbol = new_symbol
309 symbol = new_symbol
308
310
309 return symbol, []
311 return symbol, []
310
312
311 #-----------------------------------------------------------------------------
313 #-----------------------------------------------------------------------------
312 # Kernel main and launch functions
314 # Kernel main and launch functions
313 #-----------------------------------------------------------------------------
315 #-----------------------------------------------------------------------------
314
316
315 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False,
317 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False,
316 pylab=False):
318 pylab=False):
317 """ Launches a localhost kernel, binding to the specified ports.
319 """ Launches a localhost kernel, binding to the specified ports.
318
320
319 Parameters
321 Parameters
320 ----------
322 ----------
321 xrep_port : int, optional
323 xrep_port : int, optional
322 The port to use for XREP channel.
324 The port to use for XREP channel.
323
325
324 pub_port : int, optional
326 pub_port : int, optional
325 The port to use for the SUB channel.
327 The port to use for the SUB channel.
326
328
327 req_port : int, optional
329 req_port : int, optional
328 The port to use for the REQ (raw input) channel.
330 The port to use for the REQ (raw input) channel.
329
331
330 independent : bool, optional (default False)
332 independent : bool, optional (default False)
331 If set, the kernel process is guaranteed to survive if this process
333 If set, the kernel process is guaranteed to survive if this process
332 dies. If not set, an effort is made to ensure that the kernel is killed
334 dies. If not set, an effort is made to ensure that the kernel is killed
333 when this process dies. Note that in this case it is still good practice
335 when this process dies. Note that in this case it is still good practice
334 to kill kernels manually before exiting.
336 to kill kernels manually before exiting.
335
337
336 pylab : bool or string, optional (default False)
338 pylab : bool or string, optional (default False)
337 If not False, the kernel will be launched with pylab enabled. If a
339 If not False, the kernel will be launched with pylab enabled. If a
338 string is passed, matplotlib will use the specified backend. Otherwise,
340 string is passed, matplotlib will use the specified backend. Otherwise,
339 matplotlib's default backend will be used.
341 matplotlib's default backend will be used.
340
342
341 Returns
343 Returns
342 -------
344 -------
343 A tuple of form:
345 A tuple of form:
344 (kernel_process, xrep_port, pub_port, req_port)
346 (kernel_process, xrep_port, pub_port, req_port)
345 where kernel_process is a Popen object and the ports are integers.
347 where kernel_process is a Popen object and the ports are integers.
346 """
348 """
347 extra_arguments = []
349 extra_arguments = []
348 if pylab:
350 if pylab:
349 extra_arguments.append('--pylab')
351 extra_arguments.append('--pylab')
350 if isinstance(pylab, basestring):
352 if isinstance(pylab, basestring):
351 extra_arguments.append(pylab)
353 extra_arguments.append(pylab)
352 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
354 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
353 xrep_port, pub_port, req_port, independent,
355 xrep_port, pub_port, req_port, independent,
354 extra_arguments)
356 extra_arguments)
355
357
356 def main():
358 def main():
357 """ The IPython kernel main entry point.
359 """ The IPython kernel main entry point.
358 """
360 """
359 parser = make_argument_parser()
361 parser = make_argument_parser()
360 parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
362 parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
361 const='auto', help = \
363 const='auto', help = \
362 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
364 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
363 given, the GUI backend is matplotlib's, otherwise use one of: \
365 given, the GUI backend is matplotlib's, otherwise use one of: \
364 ['tk', 'gtk', 'qt', 'wx', 'payload-svg'].")
366 ['tk', 'gtk', 'qt', 'wx', 'payload-svg'].")
365 namespace = parser.parse_args()
367 namespace = parser.parse_args()
366
368
367 kernel = make_kernel(namespace, Kernel, OutStream)
369 kernel = make_kernel(namespace, Kernel, OutStream)
368 if namespace.pylab:
370 if namespace.pylab:
369 if namespace.pylab == 'auto':
371 if namespace.pylab == 'auto':
370 kernel.activate_pylab()
372 kernel.activate_pylab()
371 else:
373 else:
372 kernel.activate_pylab(namespace.pylab)
374 kernel.activate_pylab(namespace.pylab)
373
375
374 start_kernel(namespace, kernel)
376 start_kernel(namespace, kernel)
375
377
376 if __name__ == '__main__':
378 if __name__ == '__main__':
377 main()
379 main()
@@ -1,260 +1,262 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
16
17 # Standard library imports.
17 # Standard library imports.
18 import __builtin__
18 import __builtin__
19 from code import CommandCompiler
19 from code import CommandCompiler
20 import sys
20 import sys
21 import time
21 import time
22 import traceback
22 import traceback
23
23
24 # System library imports.
24 # System library imports.
25 import zmq
25 import zmq
26
26
27 # Local imports.
27 # Local imports.
28 from IPython.utils.traitlets import HasTraits, Instance
28 from IPython.utils.traitlets import HasTraits, Instance
29 from completer import KernelCompleter
29 from completer import KernelCompleter
30 from entry_point import base_launch_kernel, make_default_main
30 from entry_point import base_launch_kernel, make_default_main
31 from session import Session, Message
31 from session import Session, Message
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Main kernel class
34 # Main kernel class
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37 class Kernel(HasTraits):
37 class Kernel(HasTraits):
38
38
39 #---------------------------------------------------------------------------
39 #---------------------------------------------------------------------------
40 # Kernel interface
40 # Kernel interface
41 #---------------------------------------------------------------------------
41 #---------------------------------------------------------------------------
42
42
43 session = Instance(Session)
43 session = Instance(Session)
44 reply_socket = Instance('zmq.Socket')
44 reply_socket = Instance('zmq.Socket')
45 pub_socket = Instance('zmq.Socket')
45 pub_socket = Instance('zmq.Socket')
46 req_socket = Instance('zmq.Socket')
46 req_socket = Instance('zmq.Socket')
47
47
48 def __init__(self, **kwargs):
48 def __init__(self, **kwargs):
49 super(Kernel, self).__init__(**kwargs)
49 super(Kernel, self).__init__(**kwargs)
50 self.user_ns = {}
50 self.user_ns = {}
51 self.history = []
51 self.history = []
52 self.compiler = CommandCompiler()
52 self.compiler = CommandCompiler()
53 self.completer = KernelCompleter(self.user_ns)
53 self.completer = KernelCompleter(self.user_ns)
54
54
55 # Build dict of handlers for message types
55 # Build dict of handlers for message types
56 msg_types = [ 'execute_request', 'complete_request',
56 msg_types = [ 'execute_request', 'complete_request',
57 'object_info_request' ]
57 'object_info_request' ]
58 self.handlers = {}
58 self.handlers = {}
59 for msg_type in msg_types:
59 for msg_type in msg_types:
60 self.handlers[msg_type] = getattr(self, msg_type)
60 self.handlers[msg_type] = getattr(self, msg_type)
61
61
62 def start(self):
62 def start(self):
63 """ Start the kernel main loop.
63 """ Start the kernel main loop.
64 """
64 """
65 while True:
65 while True:
66 ident = self.reply_socket.recv()
66 ident = self.reply_socket.recv()
67 assert self.reply_socket.rcvmore(), "Missing message part."
67 assert self.reply_socket.rcvmore(), "Missing message part."
68 msg = self.reply_socket.recv_json()
68 msg = self.reply_socket.recv_json()
69 omsg = Message(msg)
69 omsg = Message(msg)
70 print>>sys.__stdout__
70 print>>sys.__stdout__
71 print>>sys.__stdout__, omsg
71 print>>sys.__stdout__, omsg
72 handler = self.handlers.get(omsg.msg_type, None)
72 handler = self.handlers.get(omsg.msg_type, None)
73 if handler is None:
73 if handler is None:
74 print >> sys.__stderr__, "UNKNOWN MESSAGE TYPE:", omsg
74 print >> sys.__stderr__, "UNKNOWN MESSAGE TYPE:", omsg
75 else:
75 else:
76 handler(ident, omsg)
76 handler(ident, omsg)
77
77
78 #---------------------------------------------------------------------------
78 #---------------------------------------------------------------------------
79 # Kernel request handlers
79 # Kernel request handlers
80 #---------------------------------------------------------------------------
80 #---------------------------------------------------------------------------
81
81
82 def execute_request(self, ident, parent):
82 def execute_request(self, ident, parent):
83 try:
83 try:
84 code = parent[u'content'][u'code']
84 code = parent[u'content'][u'code']
85 except:
85 except:
86 print>>sys.__stderr__, "Got bad msg: "
86 print>>sys.__stderr__, "Got bad msg: "
87 print>>sys.__stderr__, Message(parent)
87 print>>sys.__stderr__, Message(parent)
88 return
88 return
89 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
89 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
90 self.pub_socket.send_json(pyin_msg)
90 self.pub_socket.send_json(pyin_msg)
91
91
92 try:
92 try:
93 comp_code = self.compiler(code, '<zmq-kernel>')
93 comp_code = self.compiler(code, '<zmq-kernel>')
94
94
95 # Replace raw_input. Note that is not sufficient to replace
95 # Replace raw_input. Note that is not sufficient to replace
96 # raw_input in the user namespace.
96 # raw_input in the user namespace.
97 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
97 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
98 __builtin__.raw_input = raw_input
98 __builtin__.raw_input = raw_input
99
99
100 # Configure the display hook.
100 # Set the parent message of the display hook and out streams.
101 sys.displayhook.set_parent(parent)
101 sys.displayhook.set_parent(parent)
102 sys.stdout.set_parent(parent)
103 sys.stderr.set_parent(parent)
102
104
103 exec comp_code in self.user_ns, self.user_ns
105 exec comp_code in self.user_ns, self.user_ns
104 except:
106 except:
105 etype, evalue, tb = sys.exc_info()
107 etype, evalue, tb = sys.exc_info()
106 tb = traceback.format_exception(etype, evalue, tb)
108 tb = traceback.format_exception(etype, evalue, tb)
107 exc_content = {
109 exc_content = {
108 u'status' : u'error',
110 u'status' : u'error',
109 u'traceback' : tb,
111 u'traceback' : tb,
110 u'ename' : unicode(etype.__name__),
112 u'ename' : unicode(etype.__name__),
111 u'evalue' : unicode(evalue)
113 u'evalue' : unicode(evalue)
112 }
114 }
113 exc_msg = self.session.msg(u'pyerr', exc_content, parent)
115 exc_msg = self.session.msg(u'pyerr', exc_content, parent)
114 self.pub_socket.send_json(exc_msg)
116 self.pub_socket.send_json(exc_msg)
115 reply_content = exc_content
117 reply_content = exc_content
116 else:
118 else:
117 reply_content = { 'status' : 'ok', 'payload' : {} }
119 reply_content = { 'status' : 'ok', 'payload' : {} }
118
120
119 # Flush output before sending the reply.
121 # Flush output before sending the reply.
120 sys.stderr.flush()
122 sys.stderr.flush()
121 sys.stdout.flush()
123 sys.stdout.flush()
122
124
123 # Send the reply.
125 # Send the reply.
124 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
126 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
125 print>>sys.__stdout__, Message(reply_msg)
127 print>>sys.__stdout__, Message(reply_msg)
126 self.reply_socket.send(ident, zmq.SNDMORE)
128 self.reply_socket.send(ident, zmq.SNDMORE)
127 self.reply_socket.send_json(reply_msg)
129 self.reply_socket.send_json(reply_msg)
128 if reply_msg['content']['status'] == u'error':
130 if reply_msg['content']['status'] == u'error':
129 self._abort_queue()
131 self._abort_queue()
130
132
131 def complete_request(self, ident, parent):
133 def complete_request(self, ident, parent):
132 matches = {'matches' : self.complete(parent),
134 matches = {'matches' : self.complete(parent),
133 'status' : 'ok'}
135 'status' : 'ok'}
134 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
136 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
135 matches, parent, ident)
137 matches, parent, ident)
136 print >> sys.__stdout__, completion_msg
138 print >> sys.__stdout__, completion_msg
137
139
138 def object_info_request(self, ident, parent):
140 def object_info_request(self, ident, parent):
139 context = parent['content']['oname'].split('.')
141 context = parent['content']['oname'].split('.')
140 object_info = self._object_info(context)
142 object_info = self._object_info(context)
141 msg = self.session.send(self.reply_socket, 'object_info_reply',
143 msg = self.session.send(self.reply_socket, 'object_info_reply',
142 object_info, parent, ident)
144 object_info, parent, ident)
143 print >> sys.__stdout__, msg
145 print >> sys.__stdout__, msg
144
146
145 #---------------------------------------------------------------------------
147 #---------------------------------------------------------------------------
146 # Protected interface
148 # Protected interface
147 #---------------------------------------------------------------------------
149 #---------------------------------------------------------------------------
148
150
149 def _abort_queue(self):
151 def _abort_queue(self):
150 while True:
152 while True:
151 try:
153 try:
152 ident = self.reply_socket.recv(zmq.NOBLOCK)
154 ident = self.reply_socket.recv(zmq.NOBLOCK)
153 except zmq.ZMQError, e:
155 except zmq.ZMQError, e:
154 if e.errno == zmq.EAGAIN:
156 if e.errno == zmq.EAGAIN:
155 break
157 break
156 else:
158 else:
157 assert self.reply_socket.rcvmore(), "Missing message part."
159 assert self.reply_socket.rcvmore(), "Missing message part."
158 msg = self.reply_socket.recv_json()
160 msg = self.reply_socket.recv_json()
159 print>>sys.__stdout__, "Aborting:"
161 print>>sys.__stdout__, "Aborting:"
160 print>>sys.__stdout__, Message(msg)
162 print>>sys.__stdout__, Message(msg)
161 msg_type = msg['msg_type']
163 msg_type = msg['msg_type']
162 reply_type = msg_type.split('_')[0] + '_reply'
164 reply_type = msg_type.split('_')[0] + '_reply'
163 reply_msg = self.session.msg(reply_type, {'status':'aborted'}, msg)
165 reply_msg = self.session.msg(reply_type, {'status':'aborted'}, msg)
164 print>>sys.__stdout__, Message(reply_msg)
166 print>>sys.__stdout__, Message(reply_msg)
165 self.reply_socket.send(ident,zmq.SNDMORE)
167 self.reply_socket.send(ident,zmq.SNDMORE)
166 self.reply_socket.send_json(reply_msg)
168 self.reply_socket.send_json(reply_msg)
167 # We need to wait a bit for requests to come in. This can probably
169 # We need to wait a bit for requests to come in. This can probably
168 # be set shorter for true asynchronous clients.
170 # be set shorter for true asynchronous clients.
169 time.sleep(0.1)
171 time.sleep(0.1)
170
172
171 def _raw_input(self, prompt, ident, parent):
173 def _raw_input(self, prompt, ident, parent):
172 # Flush output before making the request.
174 # Flush output before making the request.
173 sys.stderr.flush()
175 sys.stderr.flush()
174 sys.stdout.flush()
176 sys.stdout.flush()
175
177
176 # Send the input request.
178 # Send the input request.
177 content = dict(prompt=prompt)
179 content = dict(prompt=prompt)
178 msg = self.session.msg(u'input_request', content, parent)
180 msg = self.session.msg(u'input_request', content, parent)
179 self.req_socket.send_json(msg)
181 self.req_socket.send_json(msg)
180
182
181 # Await a response.
183 # Await a response.
182 reply = self.req_socket.recv_json()
184 reply = self.req_socket.recv_json()
183 try:
185 try:
184 value = reply['content']['value']
186 value = reply['content']['value']
185 except:
187 except:
186 print>>sys.__stderr__, "Got bad raw_input reply: "
188 print>>sys.__stderr__, "Got bad raw_input reply: "
187 print>>sys.__stderr__, Message(parent)
189 print>>sys.__stderr__, Message(parent)
188 value = ''
190 value = ''
189 return value
191 return value
190
192
191 def _complete(self, msg):
193 def _complete(self, msg):
192 return self.completer.complete(msg.content.line, msg.content.text)
194 return self.completer.complete(msg.content.line, msg.content.text)
193
195
194 def _object_info(self, context):
196 def _object_info(self, context):
195 symbol, leftover = self._symbol_from_context(context)
197 symbol, leftover = self._symbol_from_context(context)
196 if symbol is not None and not leftover:
198 if symbol is not None and not leftover:
197 doc = getattr(symbol, '__doc__', '')
199 doc = getattr(symbol, '__doc__', '')
198 else:
200 else:
199 doc = ''
201 doc = ''
200 object_info = dict(docstring = doc)
202 object_info = dict(docstring = doc)
201 return object_info
203 return object_info
202
204
203 def _symbol_from_context(self, context):
205 def _symbol_from_context(self, context):
204 if not context:
206 if not context:
205 return None, context
207 return None, context
206
208
207 base_symbol_string = context[0]
209 base_symbol_string = context[0]
208 symbol = self.user_ns.get(base_symbol_string, None)
210 symbol = self.user_ns.get(base_symbol_string, None)
209 if symbol is None:
211 if symbol is None:
210 symbol = __builtin__.__dict__.get(base_symbol_string, None)
212 symbol = __builtin__.__dict__.get(base_symbol_string, None)
211 if symbol is None:
213 if symbol is None:
212 return None, context
214 return None, context
213
215
214 context = context[1:]
216 context = context[1:]
215 for i, name in enumerate(context):
217 for i, name in enumerate(context):
216 new_symbol = getattr(symbol, name, None)
218 new_symbol = getattr(symbol, name, None)
217 if new_symbol is None:
219 if new_symbol is None:
218 return symbol, context[i:]
220 return symbol, context[i:]
219 else:
221 else:
220 symbol = new_symbol
222 symbol = new_symbol
221
223
222 return symbol, []
224 return symbol, []
223
225
224 #-----------------------------------------------------------------------------
226 #-----------------------------------------------------------------------------
225 # Kernel main and launch functions
227 # Kernel main and launch functions
226 #-----------------------------------------------------------------------------
228 #-----------------------------------------------------------------------------
227
229
228 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False):
230 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False):
229 """ Launches a localhost kernel, binding to the specified ports.
231 """ Launches a localhost kernel, binding to the specified ports.
230
232
231 Parameters
233 Parameters
232 ----------
234 ----------
233 xrep_port : int, optional
235 xrep_port : int, optional
234 The port to use for XREP channel.
236 The port to use for XREP channel.
235
237
236 pub_port : int, optional
238 pub_port : int, optional
237 The port to use for the SUB channel.
239 The port to use for the SUB channel.
238
240
239 req_port : int, optional
241 req_port : int, optional
240 The port to use for the REQ (raw input) channel.
242 The port to use for the REQ (raw input) channel.
241
243
242 independent : bool, optional (default False)
244 independent : bool, optional (default False)
243 If set, the kernel process is guaranteed to survive if this process
245 If set, the kernel process is guaranteed to survive if this process
244 dies. If not set, an effort is made to ensure that the kernel is killed
246 dies. If not set, an effort is made to ensure that the kernel is killed
245 when this process dies. Note that in this case it is still good practice
247 when this process dies. Note that in this case it is still good practice
246 to kill kernels manually before exiting.
248 to kill kernels manually before exiting.
247
249
248 Returns
250 Returns
249 -------
251 -------
250 A tuple of form:
252 A tuple of form:
251 (kernel_process, xrep_port, pub_port, req_port)
253 (kernel_process, xrep_port, pub_port, req_port)
252 where kernel_process is a Popen object and the ports are integers.
254 where kernel_process is a Popen object and the ports are integers.
253 """
255 """
254 return base_launch_kernel('from IPython.zmq.pykernel import main; main()',
256 return base_launch_kernel('from IPython.zmq.pykernel import main; main()',
255 xrep_port, pub_port, req_port, independent)
257 xrep_port, pub_port, req_port, independent)
256
258
257 main = make_default_main(Kernel)
259 main = make_default_main(Kernel)
258
260
259 if __name__ == '__main__':
261 if __name__ == '__main__':
260 main()
262 main()
General Comments 0
You need to be logged in to leave comments. Login now