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