##// END OF EJS Templates
Rename history_tail_reply back to history_reply.
Thomas Kluyver -
Show More
@@ -1,496 +1,496
1 1 """ A FrontendWidget that emulates the interface of the console IPython and
2 2 supports the additional functionality provided by the IPython kernel.
3 3 """
4 4
5 5 #-----------------------------------------------------------------------------
6 6 # Imports
7 7 #-----------------------------------------------------------------------------
8 8
9 9 # Standard library imports
10 10 from collections import namedtuple
11 11 import os.path
12 12 import re
13 13 from subprocess import Popen
14 14 import sys
15 15 from textwrap import dedent
16 16
17 17 # System library imports
18 18 from IPython.external.qt import QtCore, QtGui
19 19
20 20 # Local imports
21 21 from IPython.core.inputsplitter import IPythonInputSplitter, \
22 22 transform_ipy_prompt
23 23 from IPython.core.usage import default_gui_banner
24 24 from IPython.utils.traitlets import Bool, Str, Unicode
25 25 from frontend_widget import FrontendWidget
26 26 from styles import (default_light_style_sheet, default_light_syntax_style,
27 27 default_dark_style_sheet, default_dark_syntax_style,
28 28 default_bw_style_sheet, default_bw_syntax_style)
29 29
30 30 #-----------------------------------------------------------------------------
31 31 # Constants
32 32 #-----------------------------------------------------------------------------
33 33
34 34 # Default strings to build and display input and output prompts (and separators
35 35 # in between)
36 36 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
37 37 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
38 38 default_input_sep = '\n'
39 39 default_output_sep = ''
40 40 default_output_sep2 = ''
41 41
42 42 # Base path for most payload sources.
43 43 zmq_shell_source = 'IPython.zmq.zmqshell.ZMQInteractiveShell'
44 44
45 45 #-----------------------------------------------------------------------------
46 46 # IPythonWidget class
47 47 #-----------------------------------------------------------------------------
48 48
49 49 class IPythonWidget(FrontendWidget):
50 50 """ A FrontendWidget for an IPython kernel.
51 51 """
52 52
53 53 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
54 54 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
55 55 # settings.
56 56 custom_edit = Bool(False)
57 57 custom_edit_requested = QtCore.Signal(object, object)
58 58
59 59 # A command for invoking a system text editor. If the string contains a
60 60 # {filename} format specifier, it will be used. Otherwise, the filename will
61 61 # be appended to the end the command.
62 62 editor = Unicode('default', config=True)
63 63
64 64 # The editor command to use when a specific line number is requested. The
65 65 # string should contain two format specifiers: {line} and {filename}. If
66 66 # this parameter is not specified, the line number option to the %edit magic
67 67 # will be ignored.
68 68 editor_line = Unicode(config=True)
69 69
70 70 # A CSS stylesheet. The stylesheet can contain classes for:
71 71 # 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
72 72 # 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
73 73 # 3. IPython: .error, .in-prompt, .out-prompt, etc
74 74 style_sheet = Unicode(config=True)
75 75
76 76 # If not empty, use this Pygments style for syntax highlighting. Otherwise,
77 77 # the style sheet is queried for Pygments style information.
78 78 syntax_style = Str(config=True)
79 79
80 80 # Prompts.
81 81 in_prompt = Str(default_in_prompt, config=True)
82 82 out_prompt = Str(default_out_prompt, config=True)
83 83 input_sep = Str(default_input_sep, config=True)
84 84 output_sep = Str(default_output_sep, config=True)
85 85 output_sep2 = Str(default_output_sep2, config=True)
86 86
87 87 # FrontendWidget protected class variables.
88 88 _input_splitter_class = IPythonInputSplitter
89 89
90 90 # IPythonWidget protected class variables.
91 91 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
92 92 _payload_source_edit = zmq_shell_source + '.edit_magic'
93 93 _payload_source_exit = zmq_shell_source + '.ask_exit'
94 94 _payload_source_loadpy = zmq_shell_source + '.magic_loadpy'
95 95 _payload_source_page = 'IPython.zmq.page.page'
96 96
97 97 #---------------------------------------------------------------------------
98 98 # 'object' interface
99 99 #---------------------------------------------------------------------------
100 100
101 101 def __init__(self, *args, **kw):
102 102 super(IPythonWidget, self).__init__(*args, **kw)
103 103
104 104 # IPythonWidget protected variables.
105 105 self._code_to_load = None
106 106 self._payload_handlers = {
107 107 self._payload_source_edit : self._handle_payload_edit,
108 108 self._payload_source_exit : self._handle_payload_exit,
109 109 self._payload_source_page : self._handle_payload_page,
110 110 self._payload_source_loadpy : self._handle_payload_loadpy }
111 111 self._previous_prompt_obj = None
112 112 self._keep_kernel_on_exit = None
113 113
114 114 # Initialize widget styling.
115 115 if self.style_sheet:
116 116 self._style_sheet_changed()
117 117 self._syntax_style_changed()
118 118 else:
119 119 self.set_default_style()
120 120
121 121 #---------------------------------------------------------------------------
122 122 # 'BaseFrontendMixin' abstract interface
123 123 #---------------------------------------------------------------------------
124 124
125 125 def _handle_complete_reply(self, rep):
126 126 """ Reimplemented to support IPython's improved completion machinery.
127 127 """
128 128 cursor = self._get_cursor()
129 129 info = self._request_info.get('complete')
130 130 if info and info.id == rep['parent_header']['msg_id'] and \
131 131 info.pos == cursor.position():
132 132 matches = rep['content']['matches']
133 133 text = rep['content']['matched_text']
134 134 offset = len(text)
135 135
136 136 # Clean up matches with period and path separators if the matched
137 137 # text has not been transformed. This is done by truncating all
138 138 # but the last component and then suitably decreasing the offset
139 139 # between the current cursor position and the start of completion.
140 140 if len(matches) > 1 and matches[0][:offset] == text:
141 141 parts = re.split(r'[./\\]', text)
142 142 sep_count = len(parts) - 1
143 143 if sep_count:
144 144 chop_length = sum(map(len, parts[:sep_count])) + sep_count
145 145 matches = [ match[chop_length:] for match in matches ]
146 146 offset -= chop_length
147 147
148 148 # Move the cursor to the start of the match and complete.
149 149 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
150 150 self._complete_with_items(cursor, matches)
151 151
152 152 def _handle_execute_reply(self, msg):
153 153 """ Reimplemented to support prompt requests.
154 154 """
155 155 info = self._request_info.get('execute')
156 156 if info and info.id == msg['parent_header']['msg_id']:
157 157 if info.kind == 'prompt':
158 158 number = msg['content']['execution_count'] + 1
159 159 self._show_interpreter_prompt(number)
160 160 else:
161 161 super(IPythonWidget, self)._handle_execute_reply(msg)
162 162
163 def _handle_history_tail_reply(self, msg):
163 def _handle_history_reply(self, msg):
164 164 """ Implemented to handle history tail replies, which are only supported
165 165 by the IPython kernel.
166 166 """
167 167 history_items = msg['content']['history']
168 168 items = [ line.rstrip() for _, _, line in history_items ]
169 169 self._set_history(items)
170 170
171 171 def _handle_pyout(self, msg):
172 172 """ Reimplemented for IPython-style "display hook".
173 173 """
174 174 if not self._hidden and self._is_from_this_session(msg):
175 175 content = msg['content']
176 176 prompt_number = content['execution_count']
177 177 data = content['data']
178 178 if data.has_key('text/html'):
179 179 self._append_plain_text(self.output_sep)
180 180 self._append_html(self._make_out_prompt(prompt_number))
181 181 html = data['text/html']
182 182 self._append_plain_text('\n')
183 183 self._append_html(html + self.output_sep2)
184 184 elif data.has_key('text/plain'):
185 185 self._append_plain_text(self.output_sep)
186 186 self._append_html(self._make_out_prompt(prompt_number))
187 187 text = data['text/plain']
188 188 self._append_plain_text(text + self.output_sep2)
189 189
190 190 def _handle_display_data(self, msg):
191 191 """ The base handler for the ``display_data`` message.
192 192 """
193 193 # For now, we don't display data from other frontends, but we
194 194 # eventually will as this allows all frontends to monitor the display
195 195 # data. But we need to figure out how to handle this in the GUI.
196 196 if not self._hidden and self._is_from_this_session(msg):
197 197 source = msg['content']['source']
198 198 data = msg['content']['data']
199 199 metadata = msg['content']['metadata']
200 200 # In the regular IPythonWidget, we simply print the plain text
201 201 # representation.
202 202 if data.has_key('text/html'):
203 203 html = data['text/html']
204 204 self._append_html(html)
205 205 elif data.has_key('text/plain'):
206 206 text = data['text/plain']
207 207 self._append_plain_text(text)
208 208 # This newline seems to be needed for text and html output.
209 209 self._append_plain_text(u'\n')
210 210
211 211 def _started_channels(self):
212 212 """ Reimplemented to make a history request.
213 213 """
214 214 super(IPythonWidget, self)._started_channels()
215 215 self.kernel_manager.xreq_channel.history(hist_access_type='tail', n=1000)
216 216
217 217 #---------------------------------------------------------------------------
218 218 # 'ConsoleWidget' public interface
219 219 #---------------------------------------------------------------------------
220 220
221 221 def copy(self):
222 222 """ Copy the currently selected text to the clipboard, removing prompts
223 223 if possible.
224 224 """
225 225 text = self._control.textCursor().selection().toPlainText()
226 226 if text:
227 227 lines = map(transform_ipy_prompt, text.splitlines())
228 228 text = '\n'.join(lines)
229 229 QtGui.QApplication.clipboard().setText(text)
230 230
231 231 #---------------------------------------------------------------------------
232 232 # 'FrontendWidget' public interface
233 233 #---------------------------------------------------------------------------
234 234
235 235 def execute_file(self, path, hidden=False):
236 236 """ Reimplemented to use the 'run' magic.
237 237 """
238 238 # Use forward slashes on Windows to avoid escaping each separator.
239 239 if sys.platform == 'win32':
240 240 path = os.path.normpath(path).replace('\\', '/')
241 241
242 242 self.execute('%%run %s' % path, hidden=hidden)
243 243
244 244 #---------------------------------------------------------------------------
245 245 # 'FrontendWidget' protected interface
246 246 #---------------------------------------------------------------------------
247 247
248 248 def _complete(self):
249 249 """ Reimplemented to support IPython's improved completion machinery.
250 250 """
251 251 # We let the kernel split the input line, so we *always* send an empty
252 252 # text field. Readline-based frontends do get a real text field which
253 253 # they can use.
254 254 text = ''
255 255
256 256 # Send the completion request to the kernel
257 257 msg_id = self.kernel_manager.xreq_channel.complete(
258 258 text, # text
259 259 self._get_input_buffer_cursor_line(), # line
260 260 self._get_input_buffer_cursor_column(), # cursor_pos
261 261 self.input_buffer) # block
262 262 pos = self._get_cursor().position()
263 263 info = self._CompletionRequest(msg_id, pos)
264 264 self._request_info['complete'] = info
265 265
266 266 def _get_banner(self):
267 267 """ Reimplemented to return IPython's default banner.
268 268 """
269 269 return default_gui_banner
270 270
271 271 def _process_execute_error(self, msg):
272 272 """ Reimplemented for IPython-style traceback formatting.
273 273 """
274 274 content = msg['content']
275 275 traceback = '\n'.join(content['traceback']) + '\n'
276 276 if False:
277 277 # FIXME: For now, tracebacks come as plain text, so we can't use
278 278 # the html renderer yet. Once we refactor ultratb to produce
279 279 # properly styled tracebacks, this branch should be the default
280 280 traceback = traceback.replace(' ', '&nbsp;')
281 281 traceback = traceback.replace('\n', '<br/>')
282 282
283 283 ename = content['ename']
284 284 ename_styled = '<span class="error">%s</span>' % ename
285 285 traceback = traceback.replace(ename, ename_styled)
286 286
287 287 self._append_html(traceback)
288 288 else:
289 289 # This is the fallback for now, using plain text with ansi escapes
290 290 self._append_plain_text(traceback)
291 291
292 292 def _process_execute_payload(self, item):
293 293 """ Reimplemented to dispatch payloads to handler methods.
294 294 """
295 295 handler = self._payload_handlers.get(item['source'])
296 296 if handler is None:
297 297 # We have no handler for this type of payload, simply ignore it
298 298 return False
299 299 else:
300 300 handler(item)
301 301 return True
302 302
303 303 def _show_interpreter_prompt(self, number=None):
304 304 """ Reimplemented for IPython-style prompts.
305 305 """
306 306 # If a number was not specified, make a prompt number request.
307 307 if number is None:
308 308 msg_id = self.kernel_manager.xreq_channel.execute('', silent=True)
309 309 info = self._ExecutionRequest(msg_id, 'prompt')
310 310 self._request_info['execute'] = info
311 311 return
312 312
313 313 # Show a new prompt and save information about it so that it can be
314 314 # updated later if the prompt number turns out to be wrong.
315 315 self._prompt_sep = self.input_sep
316 316 self._show_prompt(self._make_in_prompt(number), html=True)
317 317 block = self._control.document().lastBlock()
318 318 length = len(self._prompt)
319 319 self._previous_prompt_obj = self._PromptBlock(block, length, number)
320 320
321 321 # Update continuation prompt to reflect (possibly) new prompt length.
322 322 self._set_continuation_prompt(
323 323 self._make_continuation_prompt(self._prompt), html=True)
324 324
325 325 # Load code from the %loadpy magic, if necessary.
326 326 if self._code_to_load is not None:
327 327 self.input_buffer = dedent(self._code_to_load.rstrip())
328 328 self._code_to_load = None
329 329
330 330 def _show_interpreter_prompt_for_reply(self, msg):
331 331 """ Reimplemented for IPython-style prompts.
332 332 """
333 333 # Update the old prompt number if necessary.
334 334 content = msg['content']
335 335 previous_prompt_number = content['execution_count']
336 336 if self._previous_prompt_obj and \
337 337 self._previous_prompt_obj.number != previous_prompt_number:
338 338 block = self._previous_prompt_obj.block
339 339
340 340 # Make sure the prompt block has not been erased.
341 341 if block.isValid() and block.text():
342 342
343 343 # Remove the old prompt and insert a new prompt.
344 344 cursor = QtGui.QTextCursor(block)
345 345 cursor.movePosition(QtGui.QTextCursor.Right,
346 346 QtGui.QTextCursor.KeepAnchor,
347 347 self._previous_prompt_obj.length)
348 348 prompt = self._make_in_prompt(previous_prompt_number)
349 349 self._prompt = self._insert_html_fetching_plain_text(
350 350 cursor, prompt)
351 351
352 352 # When the HTML is inserted, Qt blows away the syntax
353 353 # highlighting for the line, so we need to rehighlight it.
354 354 self._highlighter.rehighlightBlock(cursor.block())
355 355
356 356 self._previous_prompt_obj = None
357 357
358 358 # Show a new prompt with the kernel's estimated prompt number.
359 359 self._show_interpreter_prompt(previous_prompt_number + 1)
360 360
361 361 #---------------------------------------------------------------------------
362 362 # 'IPythonWidget' interface
363 363 #---------------------------------------------------------------------------
364 364
365 365 def set_default_style(self, colors='lightbg'):
366 366 """ Sets the widget style to the class defaults.
367 367
368 368 Parameters:
369 369 -----------
370 370 colors : str, optional (default lightbg)
371 371 Whether to use the default IPython light background or dark
372 372 background or B&W style.
373 373 """
374 374 colors = colors.lower()
375 375 if colors=='lightbg':
376 376 self.style_sheet = default_light_style_sheet
377 377 self.syntax_style = default_light_syntax_style
378 378 elif colors=='linux':
379 379 self.style_sheet = default_dark_style_sheet
380 380 self.syntax_style = default_dark_syntax_style
381 381 elif colors=='nocolor':
382 382 self.style_sheet = default_bw_style_sheet
383 383 self.syntax_style = default_bw_syntax_style
384 384 else:
385 385 raise KeyError("No such color scheme: %s"%colors)
386 386
387 387 #---------------------------------------------------------------------------
388 388 # 'IPythonWidget' protected interface
389 389 #---------------------------------------------------------------------------
390 390
391 391 def _edit(self, filename, line=None):
392 392 """ Opens a Python script for editing.
393 393
394 394 Parameters:
395 395 -----------
396 396 filename : str
397 397 A path to a local system file.
398 398
399 399 line : int, optional
400 400 A line of interest in the file.
401 401 """
402 402 if self.custom_edit:
403 403 self.custom_edit_requested.emit(filename, line)
404 404 elif self.editor == 'default':
405 405 self._append_plain_text('No default editor available.\n')
406 406 else:
407 407 try:
408 408 filename = '"%s"' % filename
409 409 if line and self.editor_line:
410 410 command = self.editor_line.format(filename=filename,
411 411 line=line)
412 412 else:
413 413 try:
414 414 command = self.editor.format()
415 415 except KeyError:
416 416 command = self.editor.format(filename=filename)
417 417 else:
418 418 command += ' ' + filename
419 419 except KeyError:
420 420 self._append_plain_text('Invalid editor command.\n')
421 421 else:
422 422 try:
423 423 Popen(command, shell=True)
424 424 except OSError:
425 425 msg = 'Opening editor with command "%s" failed.\n'
426 426 self._append_plain_text(msg % command)
427 427
428 428 def _make_in_prompt(self, number):
429 429 """ Given a prompt number, returns an HTML In prompt.
430 430 """
431 431 body = self.in_prompt % number
432 432 return '<span class="in-prompt">%s</span>' % body
433 433
434 434 def _make_continuation_prompt(self, prompt):
435 435 """ Given a plain text version of an In prompt, returns an HTML
436 436 continuation prompt.
437 437 """
438 438 end_chars = '...: '
439 439 space_count = len(prompt.lstrip('\n')) - len(end_chars)
440 440 body = '&nbsp;' * space_count + end_chars
441 441 return '<span class="in-prompt">%s</span>' % body
442 442
443 443 def _make_out_prompt(self, number):
444 444 """ Given a prompt number, returns an HTML Out prompt.
445 445 """
446 446 body = self.out_prompt % number
447 447 return '<span class="out-prompt">%s</span>' % body
448 448
449 449 #------ Payload handlers --------------------------------------------------
450 450
451 451 # Payload handlers with a generic interface: each takes the opaque payload
452 452 # dict, unpacks it and calls the underlying functions with the necessary
453 453 # arguments.
454 454
455 455 def _handle_payload_edit(self, item):
456 456 self._edit(item['filename'], item['line_number'])
457 457
458 458 def _handle_payload_exit(self, item):
459 459 self._keep_kernel_on_exit = item['keepkernel']
460 460 self.exit_requested.emit()
461 461
462 462 def _handle_payload_loadpy(self, item):
463 463 # Simple save the text of the .py file for later. The text is written
464 464 # to the buffer when _prompt_started_hook is called.
465 465 self._code_to_load = item['text']
466 466
467 467 def _handle_payload_page(self, item):
468 468 # Since the plain text widget supports only a very small subset of HTML
469 469 # and we have no control over the HTML source, we only page HTML
470 470 # payloads in the rich text widget.
471 471 if item['html'] and self.kind == 'rich':
472 472 self._page(item['html'], html=True)
473 473 else:
474 474 self._page(item['text'], html=False)
475 475
476 476 #------ Trait change handlers --------------------------------------------
477 477
478 478 def _style_sheet_changed(self):
479 479 """ Set the style sheets of the underlying widgets.
480 480 """
481 481 self.setStyleSheet(self.style_sheet)
482 482 self._control.document().setDefaultStyleSheet(self.style_sheet)
483 483 if self._page_control:
484 484 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
485 485
486 486 bg_color = self._control.palette().window().color()
487 487 self._ansi_processor.set_background_color(bg_color)
488 488
489 489 def _syntax_style_changed(self):
490 490 """ Set the style for the syntax highlighter.
491 491 """
492 492 if self.syntax_style:
493 493 self._highlighter.set_style(self.syntax_style)
494 494 else:
495 495 self._highlighter.set_style_sheet(self.style_sheet)
496 496
@@ -1,243 +1,243
1 1 """ Defines a KernelManager that provides signals and slots.
2 2 """
3 3
4 4 # System library imports.
5 5 from IPython.external.qt import QtCore
6 6
7 7 # IPython imports.
8 8 from IPython.utils.traitlets import Type
9 9 from IPython.zmq.kernelmanager import KernelManager, SubSocketChannel, \
10 10 XReqSocketChannel, RepSocketChannel, HBSocketChannel
11 11 from util import MetaQObjectHasTraits, SuperQObject
12 12
13 13
14 14 class SocketChannelQObject(SuperQObject):
15 15
16 16 # Emitted when the channel is started.
17 17 started = QtCore.Signal()
18 18
19 19 # Emitted when the channel is stopped.
20 20 stopped = QtCore.Signal()
21 21
22 22 #---------------------------------------------------------------------------
23 23 # 'ZmqSocketChannel' interface
24 24 #---------------------------------------------------------------------------
25 25
26 26 def start(self):
27 27 """ Reimplemented to emit signal.
28 28 """
29 29 super(SocketChannelQObject, self).start()
30 30 self.started.emit()
31 31
32 32 def stop(self):
33 33 """ Reimplemented to emit signal.
34 34 """
35 35 super(SocketChannelQObject, self).stop()
36 36 self.stopped.emit()
37 37
38 38
39 39 class QtXReqSocketChannel(SocketChannelQObject, XReqSocketChannel):
40 40
41 41 # Emitted when any message is received.
42 42 message_received = QtCore.Signal(object)
43 43
44 44 # Emitted when a reply has been received for the corresponding request
45 45 # type.
46 46 execute_reply = QtCore.Signal(object)
47 47 complete_reply = QtCore.Signal(object)
48 48 object_info_reply = QtCore.Signal(object)
49 history_tail_reply = QtCore.Signal(object)
49 history_reply = QtCore.Signal(object)
50 50
51 51 # Emitted when the first reply comes back.
52 52 first_reply = QtCore.Signal()
53 53
54 54 # Used by the first_reply signal logic to determine if a reply is the
55 55 # first.
56 56 _handlers_called = False
57 57
58 58 #---------------------------------------------------------------------------
59 59 # 'XReqSocketChannel' interface
60 60 #---------------------------------------------------------------------------
61 61
62 62 def call_handlers(self, msg):
63 63 """ Reimplemented to emit signals instead of making callbacks.
64 64 """
65 65 # Emit the generic signal.
66 66 self.message_received.emit(msg)
67 67
68 68 # Emit signals for specialized message types.
69 69 msg_type = msg['msg_type']
70 70 signal = getattr(self, msg_type, None)
71 71 if signal:
72 72 signal.emit(msg)
73 73
74 74 if not self._handlers_called:
75 75 self.first_reply.emit()
76 76 self._handlers_called = True
77 77
78 78 #---------------------------------------------------------------------------
79 79 # 'QtXReqSocketChannel' interface
80 80 #---------------------------------------------------------------------------
81 81
82 82 def reset_first_reply(self):
83 83 """ Reset the first_reply signal to fire again on the next reply.
84 84 """
85 85 self._handlers_called = False
86 86
87 87
88 88 class QtSubSocketChannel(SocketChannelQObject, SubSocketChannel):
89 89
90 90 # Emitted when any message is received.
91 91 message_received = QtCore.Signal(object)
92 92
93 93 # Emitted when a message of type 'stream' is received.
94 94 stream_received = QtCore.Signal(object)
95 95
96 96 # Emitted when a message of type 'pyin' is received.
97 97 pyin_received = QtCore.Signal(object)
98 98
99 99 # Emitted when a message of type 'pyout' is received.
100 100 pyout_received = QtCore.Signal(object)
101 101
102 102 # Emitted when a message of type 'pyerr' is received.
103 103 pyerr_received = QtCore.Signal(object)
104 104
105 105 # Emitted when a message of type 'display_data' is received
106 106 display_data_received = QtCore.Signal(object)
107 107
108 108 # Emitted when a crash report message is received from the kernel's
109 109 # last-resort sys.excepthook.
110 110 crash_received = QtCore.Signal(object)
111 111
112 112 # Emitted when a shutdown is noticed.
113 113 shutdown_reply_received = QtCore.Signal(object)
114 114
115 115 #---------------------------------------------------------------------------
116 116 # 'SubSocketChannel' interface
117 117 #---------------------------------------------------------------------------
118 118
119 119 def call_handlers(self, msg):
120 120 """ Reimplemented to emit signals instead of making callbacks.
121 121 """
122 122 # Emit the generic signal.
123 123 self.message_received.emit(msg)
124 124 # Emit signals for specialized message types.
125 125 msg_type = msg['msg_type']
126 126 signal = getattr(self, msg_type + '_received', None)
127 127 if signal:
128 128 signal.emit(msg)
129 129 elif msg_type in ('stdout', 'stderr'):
130 130 self.stream_received.emit(msg)
131 131
132 132 def flush(self):
133 133 """ Reimplemented to ensure that signals are dispatched immediately.
134 134 """
135 135 super(QtSubSocketChannel, self).flush()
136 136 QtCore.QCoreApplication.instance().processEvents()
137 137
138 138
139 139 class QtRepSocketChannel(SocketChannelQObject, RepSocketChannel):
140 140
141 141 # Emitted when any message is received.
142 142 message_received = QtCore.Signal(object)
143 143
144 144 # Emitted when an input request is received.
145 145 input_requested = QtCore.Signal(object)
146 146
147 147 #---------------------------------------------------------------------------
148 148 # 'RepSocketChannel' interface
149 149 #---------------------------------------------------------------------------
150 150
151 151 def call_handlers(self, msg):
152 152 """ Reimplemented to emit signals instead of making callbacks.
153 153 """
154 154 # Emit the generic signal.
155 155 self.message_received.emit(msg)
156 156
157 157 # Emit signals for specialized message types.
158 158 msg_type = msg['msg_type']
159 159 if msg_type == 'input_request':
160 160 self.input_requested.emit(msg)
161 161
162 162
163 163 class QtHBSocketChannel(SocketChannelQObject, HBSocketChannel):
164 164
165 165 # Emitted when the kernel has died.
166 166 kernel_died = QtCore.Signal(object)
167 167
168 168 #---------------------------------------------------------------------------
169 169 # 'HBSocketChannel' interface
170 170 #---------------------------------------------------------------------------
171 171
172 172 def call_handlers(self, since_last_heartbeat):
173 173 """ Reimplemented to emit signals instead of making callbacks.
174 174 """
175 175 # Emit the generic signal.
176 176 self.kernel_died.emit(since_last_heartbeat)
177 177
178 178
179 179 class QtKernelManager(KernelManager, SuperQObject):
180 180 """ A KernelManager that provides signals and slots.
181 181 """
182 182
183 183 __metaclass__ = MetaQObjectHasTraits
184 184
185 185 # Emitted when the kernel manager has started listening.
186 186 started_channels = QtCore.Signal()
187 187
188 188 # Emitted when the kernel manager has stopped listening.
189 189 stopped_channels = QtCore.Signal()
190 190
191 191 # Use Qt-specific channel classes that emit signals.
192 192 sub_channel_class = Type(QtSubSocketChannel)
193 193 xreq_channel_class = Type(QtXReqSocketChannel)
194 194 rep_channel_class = Type(QtRepSocketChannel)
195 195 hb_channel_class = Type(QtHBSocketChannel)
196 196
197 197 #---------------------------------------------------------------------------
198 198 # 'KernelManager' interface
199 199 #---------------------------------------------------------------------------
200 200
201 201 #------ Kernel process management ------------------------------------------
202 202
203 203 def start_kernel(self, *args, **kw):
204 204 """ Reimplemented for proper heartbeat management.
205 205 """
206 206 if self._xreq_channel is not None:
207 207 self._xreq_channel.reset_first_reply()
208 208 super(QtKernelManager, self).start_kernel(*args, **kw)
209 209
210 210 #------ Channel management -------------------------------------------------
211 211
212 212 def start_channels(self, *args, **kw):
213 213 """ Reimplemented to emit signal.
214 214 """
215 215 super(QtKernelManager, self).start_channels(*args, **kw)
216 216 self.started_channels.emit()
217 217
218 218 def stop_channels(self):
219 219 """ Reimplemented to emit signal.
220 220 """
221 221 super(QtKernelManager, self).stop_channels()
222 222 self.stopped_channels.emit()
223 223
224 224 @property
225 225 def xreq_channel(self):
226 226 """ Reimplemented for proper heartbeat management.
227 227 """
228 228 if self._xreq_channel is None:
229 229 self._xreq_channel = super(QtKernelManager, self).xreq_channel
230 230 self._xreq_channel.first_reply.connect(self._first_reply)
231 231 return self._xreq_channel
232 232
233 233 #---------------------------------------------------------------------------
234 234 # Protected interface
235 235 #---------------------------------------------------------------------------
236 236
237 237 def _first_reply(self):
238 238 """ Unpauses the heartbeat channel when the first reply is received on
239 239 the execute channel. Note that this will *not* start the heartbeat
240 240 channel if it is not already running!
241 241 """
242 242 if self._hb_channel is not None:
243 243 self._hb_channel.unpause()
@@ -1,679 +1,679
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 from __future__ import print_function
17 17
18 18 # Standard library imports.
19 19 import __builtin__
20 20 import atexit
21 21 import sys
22 22 import time
23 23 import traceback
24 24 import logging
25 25 # System library imports.
26 26 import zmq
27 27
28 28 # Local imports.
29 29 from IPython.config.configurable import Configurable
30 30 from IPython.utils import io
31 31 from IPython.utils.jsonutil import json_clean
32 32 from IPython.lib import pylabtools
33 33 from IPython.utils.traitlets import Instance, Float
34 34 from entry_point import (base_launch_kernel, make_argument_parser, make_kernel,
35 35 start_kernel)
36 36 from iostream import OutStream
37 37 from session import Session, Message
38 38 from zmqshell import ZMQInteractiveShell
39 39
40 40 #-----------------------------------------------------------------------------
41 41 # Globals
42 42 #-----------------------------------------------------------------------------
43 43
44 44 # Module-level logger
45 45 logger = logging.getLogger(__name__)
46 46
47 47 # FIXME: this needs to be done more cleanly later, once we have proper
48 48 # configuration support. This is a library, so it shouldn't set a stream
49 49 # handler, see:
50 50 # http://docs.python.org/library/logging.html#configuring-logging-for-a-library
51 51 # But this lets us at least do developer debugging for now by manually turning
52 52 # it on/off. And once we have full config support, the client entry points
53 53 # will select their logging handlers, as well as passing to this library the
54 54 # logging level.
55 55
56 56 if 0: # dbg - set to 1 to actually see the messages.
57 57 logger.addHandler(logging.StreamHandler())
58 58 logger.setLevel(logging.DEBUG)
59 59
60 60 # /FIXME
61 61
62 62 #-----------------------------------------------------------------------------
63 63 # Main kernel class
64 64 #-----------------------------------------------------------------------------
65 65
66 66 class Kernel(Configurable):
67 67
68 68 #---------------------------------------------------------------------------
69 69 # Kernel interface
70 70 #---------------------------------------------------------------------------
71 71
72 72 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
73 73 session = Instance(Session)
74 74 reply_socket = Instance('zmq.Socket')
75 75 pub_socket = Instance('zmq.Socket')
76 76 req_socket = Instance('zmq.Socket')
77 77
78 78 # Private interface
79 79
80 80 # Time to sleep after flushing the stdout/err buffers in each execute
81 81 # cycle. While this introduces a hard limit on the minimal latency of the
82 82 # execute cycle, it helps prevent output synchronization problems for
83 83 # clients.
84 84 # Units are in seconds. The minimum zmq latency on local host is probably
85 85 # ~150 microseconds, set this to 500us for now. We may need to increase it
86 86 # a little if it's not enough after more interactive testing.
87 87 _execute_sleep = Float(0.0005, config=True)
88 88
89 89 # Frequency of the kernel's event loop.
90 90 # Units are in seconds, kernel subclasses for GUI toolkits may need to
91 91 # adapt to milliseconds.
92 92 _poll_interval = Float(0.05, config=True)
93 93
94 94 # If the shutdown was requested over the network, we leave here the
95 95 # necessary reply message so it can be sent by our registered atexit
96 96 # handler. This ensures that the reply is only sent to clients truly at
97 97 # the end of our shutdown process (which happens after the underlying
98 98 # IPython shell's own shutdown).
99 99 _shutdown_message = None
100 100
101 101 # This is a dict of port number that the kernel is listening on. It is set
102 102 # by record_ports and used by connect_request.
103 103 _recorded_ports = None
104 104
105 105
106 106 def __init__(self, **kwargs):
107 107 super(Kernel, self).__init__(**kwargs)
108 108
109 109 # Before we even start up the shell, register *first* our exit handlers
110 110 # so they come before the shell's
111 111 atexit.register(self._at_shutdown)
112 112
113 113 # Initialize the InteractiveShell subclass
114 114 self.shell = ZMQInteractiveShell.instance()
115 115 self.shell.displayhook.session = self.session
116 116 self.shell.displayhook.pub_socket = self.pub_socket
117 117 self.shell.display_pub.session = self.session
118 118 self.shell.display_pub.pub_socket = self.pub_socket
119 119
120 120 # TMP - hack while developing
121 121 self.shell._reply_content = None
122 122
123 123 # Build dict of handlers for message types
124 124 msg_types = [ 'execute_request', 'complete_request',
125 125 'object_info_request', 'history_request',
126 126 'connect_request', 'shutdown_request']
127 127 self.handlers = {}
128 128 for msg_type in msg_types:
129 129 self.handlers[msg_type] = getattr(self, msg_type)
130 130
131 131 def do_one_iteration(self):
132 132 """Do one iteration of the kernel's evaluation loop.
133 133 """
134 134 ident,msg = self.session.recv(self.reply_socket, zmq.NOBLOCK)
135 135 if msg is None:
136 136 return
137 137
138 138 # This assert will raise in versions of zeromq 2.0.7 and lesser.
139 139 # We now require 2.0.8 or above, so we can uncomment for safety.
140 140 # print(ident,msg, file=sys.__stdout__)
141 141 assert ident is not None, "Missing message part."
142 142
143 143 # Print some info about this message and leave a '--->' marker, so it's
144 144 # easier to trace visually the message chain when debugging. Each
145 145 # handler prints its message at the end.
146 146 # Eventually we'll move these from stdout to a logger.
147 147 logger.debug('\n*** MESSAGE TYPE:'+str(msg['msg_type'])+'***')
148 148 logger.debug(' Content: '+str(msg['content'])+'\n --->\n ')
149 149
150 150 # Find and call actual handler for message
151 151 handler = self.handlers.get(msg['msg_type'], None)
152 152 if handler is None:
153 153 logger.error("UNKNOWN MESSAGE TYPE:" +str(msg))
154 154 else:
155 155 handler(ident, msg)
156 156
157 157 # Check whether we should exit, in case the incoming message set the
158 158 # exit flag on
159 159 if self.shell.exit_now:
160 160 logger.debug('\nExiting IPython kernel...')
161 161 # We do a normal, clean exit, which allows any actions registered
162 162 # via atexit (such as history saving) to take place.
163 163 sys.exit(0)
164 164
165 165
166 166 def start(self):
167 167 """ Start the kernel main loop.
168 168 """
169 169 while True:
170 170 time.sleep(self._poll_interval)
171 171 self.do_one_iteration()
172 172
173 173 def record_ports(self, xrep_port, pub_port, req_port, hb_port):
174 174 """Record the ports that this kernel is using.
175 175
176 176 The creator of the Kernel instance must call this methods if they
177 177 want the :meth:`connect_request` method to return the port numbers.
178 178 """
179 179 self._recorded_ports = {
180 180 'xrep_port' : xrep_port,
181 181 'pub_port' : pub_port,
182 182 'req_port' : req_port,
183 183 'hb_port' : hb_port
184 184 }
185 185
186 186 #---------------------------------------------------------------------------
187 187 # Kernel request handlers
188 188 #---------------------------------------------------------------------------
189 189
190 190 def _publish_pyin(self, code, parent):
191 191 """Publish the code request on the pyin stream."""
192 192
193 193 pyin_msg = self.session.send(self.pub_socket, u'pyin',{u'code':code}, parent=parent)
194 194
195 195 def execute_request(self, ident, parent):
196 196
197 197 status_msg = self.session.send(self.pub_socket,
198 198 u'status',
199 199 {u'execution_state':u'busy'},
200 200 parent=parent
201 201 )
202 202
203 203 try:
204 204 content = parent[u'content']
205 205 code = content[u'code']
206 206 silent = content[u'silent']
207 207 except:
208 208 logger.error("Got bad msg: ")
209 209 logger.error(str(Message(parent)))
210 210 return
211 211
212 212 shell = self.shell # we'll need this a lot here
213 213
214 214 # Replace raw_input. Note that is not sufficient to replace
215 215 # raw_input in the user namespace.
216 216 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
217 217 __builtin__.raw_input = raw_input
218 218
219 219 # Set the parent message of the display hook and out streams.
220 220 shell.displayhook.set_parent(parent)
221 221 shell.display_pub.set_parent(parent)
222 222 sys.stdout.set_parent(parent)
223 223 sys.stderr.set_parent(parent)
224 224
225 225 # Re-broadcast our input for the benefit of listening clients, and
226 226 # start computing output
227 227 if not silent:
228 228 self._publish_pyin(code, parent)
229 229
230 230 reply_content = {}
231 231 try:
232 232 if silent:
233 233 # run_code uses 'exec' mode, so no displayhook will fire, and it
234 234 # doesn't call logging or history manipulations. Print
235 235 # statements in that code will obviously still execute.
236 236 shell.run_code(code)
237 237 else:
238 238 # FIXME: the shell calls the exception handler itself.
239 239 shell._reply_content = None
240 240 shell.run_cell(code)
241 241 except:
242 242 status = u'error'
243 243 # FIXME: this code right now isn't being used yet by default,
244 244 # because the run_cell() call above directly fires off exception
245 245 # reporting. This code, therefore, is only active in the scenario
246 246 # where runlines itself has an unhandled exception. We need to
247 247 # uniformize this, for all exception construction to come from a
248 248 # single location in the codbase.
249 249 etype, evalue, tb = sys.exc_info()
250 250 tb_list = traceback.format_exception(etype, evalue, tb)
251 251 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
252 252 else:
253 253 status = u'ok'
254 254
255 255 reply_content[u'status'] = status
256 256
257 257 # Return the execution counter so clients can display prompts
258 258 reply_content['execution_count'] = shell.execution_count -1
259 259
260 260 # FIXME - fish exception info out of shell, possibly left there by
261 261 # runlines. We'll need to clean up this logic later.
262 262 if shell._reply_content is not None:
263 263 reply_content.update(shell._reply_content)
264 264
265 265 # At this point, we can tell whether the main code execution succeeded
266 266 # or not. If it did, we proceed to evaluate user_variables/expressions
267 267 if reply_content['status'] == 'ok':
268 268 reply_content[u'user_variables'] = \
269 269 shell.user_variables(content[u'user_variables'])
270 270 reply_content[u'user_expressions'] = \
271 271 shell.user_expressions(content[u'user_expressions'])
272 272 else:
273 273 # If there was an error, don't even try to compute variables or
274 274 # expressions
275 275 reply_content[u'user_variables'] = {}
276 276 reply_content[u'user_expressions'] = {}
277 277
278 278 # Payloads should be retrieved regardless of outcome, so we can both
279 279 # recover partial output (that could have been generated early in a
280 280 # block, before an error) and clear the payload system always.
281 281 reply_content[u'payload'] = shell.payload_manager.read_payload()
282 282 # Be agressive about clearing the payload because we don't want
283 283 # it to sit in memory until the next execute_request comes in.
284 284 shell.payload_manager.clear_payload()
285 285
286 286 # Flush output before sending the reply.
287 287 sys.stdout.flush()
288 288 sys.stderr.flush()
289 289 # FIXME: on rare occasions, the flush doesn't seem to make it to the
290 290 # clients... This seems to mitigate the problem, but we definitely need
291 291 # to better understand what's going on.
292 292 if self._execute_sleep:
293 293 time.sleep(self._execute_sleep)
294 294
295 295 # Send the reply.
296 296 reply_msg = self.session.send(self.reply_socket, u'execute_reply',
297 297 reply_content, parent, ident=ident)
298 298 logger.debug(str(reply_msg))
299 299
300 300 if reply_msg['content']['status'] == u'error':
301 301 self._abort_queue()
302 302
303 303 status_msg = self.session.send(self.pub_socket,
304 304 u'status',
305 305 {u'execution_state':u'idle'},
306 306 parent=parent
307 307 )
308 308
309 309 def complete_request(self, ident, parent):
310 310 txt, matches = self._complete(parent)
311 311 matches = {'matches' : matches,
312 312 'matched_text' : txt,
313 313 'status' : 'ok'}
314 314 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
315 315 matches, parent, ident)
316 316 logger.debug(str(completion_msg))
317 317
318 318 def object_info_request(self, ident, parent):
319 319 object_info = self.shell.object_inspect(parent['content']['oname'])
320 320 # Before we send this object over, we scrub it for JSON usage
321 321 oinfo = json_clean(object_info)
322 322 msg = self.session.send(self.reply_socket, 'object_info_reply',
323 323 oinfo, parent, ident)
324 324 logger.debug(msg)
325 325
326 326 def history_request(self, ident, parent):
327 327 # We need to pull these out, as passing **kwargs doesn't work with
328 328 # unicode keys before Python 2.6.5.
329 329 hist_access_type = parent['content']['hist_access_type']
330 330 raw = parent['content']['raw']
331 331 output = parent['content']['output']
332 332 if hist_access_type == 'tail':
333 333 n = parent['content']['n']
334 334 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
335 335 include_latest=True)
336 336
337 337 elif hist_access_type == 'range':
338 338 session = parent['content']['session']
339 339 start = parent['content']['start']
340 340 stop = parent['content']['stop']
341 341 hist = self.shell.history_manager.get_range(session, start, stop,
342 342 raw=raw, output=output)
343 343
344 344 elif hist_access_type == 'search':
345 345 pattern = parent['content']['pattern']
346 346 hist = self.shell.history_manager.search(pattern, raw=raw, output=output)
347 347
348 348 else:
349 349 hist = []
350 350 content = {'history' : list(hist)}
351 msg = self.session.send(self.reply_socket, 'history_tail_reply',
351 msg = self.session.send(self.reply_socket, 'history_reply',
352 352 content, parent, ident)
353 353 logger.debug(str(msg))
354 354
355 355 def connect_request(self, ident, parent):
356 356 if self._recorded_ports is not None:
357 357 content = self._recorded_ports.copy()
358 358 else:
359 359 content = {}
360 360 msg = self.session.send(self.reply_socket, 'connect_reply',
361 361 content, parent, ident)
362 362 logger.debug(msg)
363 363
364 364 def shutdown_request(self, ident, parent):
365 365 self.shell.exit_now = True
366 366 self._shutdown_message = self.session.msg(u'shutdown_reply', parent['content'], parent)
367 367 sys.exit(0)
368 368
369 369 #---------------------------------------------------------------------------
370 370 # Protected interface
371 371 #---------------------------------------------------------------------------
372 372
373 373 def _abort_queue(self):
374 374 while True:
375 375 ident,msg = self.session.recv(self.reply_socket, zmq.NOBLOCK)
376 376 if msg is None:
377 377 break
378 378 else:
379 379 assert ident is not None, \
380 380 "Unexpected missing message part."
381 381
382 382 logger.debug("Aborting:\n"+str(Message(msg)))
383 383 msg_type = msg['msg_type']
384 384 reply_type = msg_type.split('_')[0] + '_reply'
385 385 reply_msg = self.session.send(self.reply_socket, reply_type,
386 386 {'status' : 'aborted'}, msg, ident=ident)
387 387 logger.debug(reply_msg)
388 388 # We need to wait a bit for requests to come in. This can probably
389 389 # be set shorter for true asynchronous clients.
390 390 time.sleep(0.1)
391 391
392 392 def _raw_input(self, prompt, ident, parent):
393 393 # Flush output before making the request.
394 394 sys.stderr.flush()
395 395 sys.stdout.flush()
396 396
397 397 # Send the input request.
398 398 content = dict(prompt=prompt)
399 399 msg = self.session.send(self.req_socket, u'input_request', content, parent)
400 400
401 401 # Await a response.
402 402 ident, reply = self.session.recv(self.req_socket, 0)
403 403 try:
404 404 value = reply['content']['value']
405 405 except:
406 406 logger.error("Got bad raw_input reply: ")
407 407 logger.error(str(Message(parent)))
408 408 value = ''
409 409 return value
410 410
411 411 def _complete(self, msg):
412 412 c = msg['content']
413 413 try:
414 414 cpos = int(c['cursor_pos'])
415 415 except:
416 416 # If we don't get something that we can convert to an integer, at
417 417 # least attempt the completion guessing the cursor is at the end of
418 418 # the text, if there's any, and otherwise of the line
419 419 cpos = len(c['text'])
420 420 if cpos==0:
421 421 cpos = len(c['line'])
422 422 return self.shell.complete(c['text'], c['line'], cpos)
423 423
424 424 def _object_info(self, context):
425 425 symbol, leftover = self._symbol_from_context(context)
426 426 if symbol is not None and not leftover:
427 427 doc = getattr(symbol, '__doc__', '')
428 428 else:
429 429 doc = ''
430 430 object_info = dict(docstring = doc)
431 431 return object_info
432 432
433 433 def _symbol_from_context(self, context):
434 434 if not context:
435 435 return None, context
436 436
437 437 base_symbol_string = context[0]
438 438 symbol = self.shell.user_ns.get(base_symbol_string, None)
439 439 if symbol is None:
440 440 symbol = __builtin__.__dict__.get(base_symbol_string, None)
441 441 if symbol is None:
442 442 return None, context
443 443
444 444 context = context[1:]
445 445 for i, name in enumerate(context):
446 446 new_symbol = getattr(symbol, name, None)
447 447 if new_symbol is None:
448 448 return symbol, context[i:]
449 449 else:
450 450 symbol = new_symbol
451 451
452 452 return symbol, []
453 453
454 454 def _at_shutdown(self):
455 455 """Actions taken at shutdown by the kernel, called by python's atexit.
456 456 """
457 457 # io.rprint("Kernel at_shutdown") # dbg
458 458 if self._shutdown_message is not None:
459 459 self.session.send(self.reply_socket, self._shutdown_message)
460 460 self.session.send(self.pub_socket, self._shutdown_message)
461 461 logger.debug(str(self._shutdown_message))
462 462 # A very short sleep to give zmq time to flush its message buffers
463 463 # before Python truly shuts down.
464 464 time.sleep(0.01)
465 465
466 466
467 467 class QtKernel(Kernel):
468 468 """A Kernel subclass with Qt support."""
469 469
470 470 def start(self):
471 471 """Start a kernel with QtPy4 event loop integration."""
472 472
473 473 from PyQt4 import QtCore
474 474 from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
475 475
476 476 self.app = get_app_qt4([" "])
477 477 self.app.setQuitOnLastWindowClosed(False)
478 478 self.timer = QtCore.QTimer()
479 479 self.timer.timeout.connect(self.do_one_iteration)
480 480 # Units for the timer are in milliseconds
481 481 self.timer.start(1000*self._poll_interval)
482 482 start_event_loop_qt4(self.app)
483 483
484 484
485 485 class WxKernel(Kernel):
486 486 """A Kernel subclass with Wx support."""
487 487
488 488 def start(self):
489 489 """Start a kernel with wx event loop support."""
490 490
491 491 import wx
492 492 from IPython.lib.guisupport import start_event_loop_wx
493 493
494 494 doi = self.do_one_iteration
495 495 # Wx uses milliseconds
496 496 poll_interval = int(1000*self._poll_interval)
497 497
498 498 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
499 499 # We make the Frame hidden when we create it in the main app below.
500 500 class TimerFrame(wx.Frame):
501 501 def __init__(self, func):
502 502 wx.Frame.__init__(self, None, -1)
503 503 self.timer = wx.Timer(self)
504 504 # Units for the timer are in milliseconds
505 505 self.timer.Start(poll_interval)
506 506 self.Bind(wx.EVT_TIMER, self.on_timer)
507 507 self.func = func
508 508
509 509 def on_timer(self, event):
510 510 self.func()
511 511
512 512 # We need a custom wx.App to create our Frame subclass that has the
513 513 # wx.Timer to drive the ZMQ event loop.
514 514 class IPWxApp(wx.App):
515 515 def OnInit(self):
516 516 self.frame = TimerFrame(doi)
517 517 self.frame.Show(False)
518 518 return True
519 519
520 520 # The redirect=False here makes sure that wx doesn't replace
521 521 # sys.stdout/stderr with its own classes.
522 522 self.app = IPWxApp(redirect=False)
523 523 start_event_loop_wx(self.app)
524 524
525 525
526 526 class TkKernel(Kernel):
527 527 """A Kernel subclass with Tk support."""
528 528
529 529 def start(self):
530 530 """Start a Tk enabled event loop."""
531 531
532 532 import Tkinter
533 533 doi = self.do_one_iteration
534 534 # Tk uses milliseconds
535 535 poll_interval = int(1000*self._poll_interval)
536 536 # For Tkinter, we create a Tk object and call its withdraw method.
537 537 class Timer(object):
538 538 def __init__(self, func):
539 539 self.app = Tkinter.Tk()
540 540 self.app.withdraw()
541 541 self.func = func
542 542
543 543 def on_timer(self):
544 544 self.func()
545 545 self.app.after(poll_interval, self.on_timer)
546 546
547 547 def start(self):
548 548 self.on_timer() # Call it once to get things going.
549 549 self.app.mainloop()
550 550
551 551 self.timer = Timer(doi)
552 552 self.timer.start()
553 553
554 554
555 555 class GTKKernel(Kernel):
556 556 """A Kernel subclass with GTK support."""
557 557
558 558 def start(self):
559 559 """Start the kernel, coordinating with the GTK event loop"""
560 560 from .gui.gtkembed import GTKEmbed
561 561
562 562 gtk_kernel = GTKEmbed(self)
563 563 gtk_kernel.start()
564 564
565 565
566 566 #-----------------------------------------------------------------------------
567 567 # Kernel main and launch functions
568 568 #-----------------------------------------------------------------------------
569 569
570 570 def launch_kernel(ip=None, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
571 571 executable=None, independent=False, pylab=False, colors=None):
572 572 """Launches a localhost kernel, binding to the specified ports.
573 573
574 574 Parameters
575 575 ----------
576 576 ip : str, optional
577 577 The ip address the kernel will bind to.
578 578
579 579 xrep_port : int, optional
580 580 The port to use for XREP channel.
581 581
582 582 pub_port : int, optional
583 583 The port to use for the SUB channel.
584 584
585 585 req_port : int, optional
586 586 The port to use for the REQ (raw input) channel.
587 587
588 588 hb_port : int, optional
589 589 The port to use for the hearbeat REP channel.
590 590
591 591 executable : str, optional (default sys.executable)
592 592 The Python executable to use for the kernel process.
593 593
594 594 independent : bool, optional (default False)
595 595 If set, the kernel process is guaranteed to survive if this process
596 596 dies. If not set, an effort is made to ensure that the kernel is killed
597 597 when this process dies. Note that in this case it is still good practice
598 598 to kill kernels manually before exiting.
599 599
600 600 pylab : bool or string, optional (default False)
601 601 If not False, the kernel will be launched with pylab enabled. If a
602 602 string is passed, matplotlib will use the specified backend. Otherwise,
603 603 matplotlib's default backend will be used.
604 604
605 605 colors : None or string, optional (default None)
606 606 If not None, specify the color scheme. One of (NoColor, LightBG, Linux)
607 607
608 608 Returns
609 609 -------
610 610 A tuple of form:
611 611 (kernel_process, xrep_port, pub_port, req_port)
612 612 where kernel_process is a Popen object and the ports are integers.
613 613 """
614 614 extra_arguments = []
615 615 if pylab:
616 616 extra_arguments.append('--pylab')
617 617 if isinstance(pylab, basestring):
618 618 extra_arguments.append(pylab)
619 619 if ip is not None:
620 620 extra_arguments.append('--ip')
621 621 if isinstance(ip, basestring):
622 622 extra_arguments.append(ip)
623 623 if colors is not None:
624 624 extra_arguments.append('--colors')
625 625 extra_arguments.append(colors)
626 626 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
627 627 xrep_port, pub_port, req_port, hb_port,
628 628 executable, independent, extra_arguments)
629 629
630 630
631 631 def main():
632 632 """ The IPython kernel main entry point.
633 633 """
634 634 parser = make_argument_parser()
635 635 parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
636 636 const='auto', help = \
637 637 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
638 638 given, the GUI backend is matplotlib's, otherwise use one of: \
639 639 ['tk', 'gtk', 'qt', 'wx', 'osx', 'inline'].")
640 640 parser.add_argument('--colors',
641 641 type=str, dest='colors',
642 642 help="Set the color scheme (NoColor, Linux, and LightBG).",
643 643 metavar='ZMQInteractiveShell.colors')
644 644 namespace = parser.parse_args()
645 645
646 646 kernel_class = Kernel
647 647
648 648 kernel_classes = {
649 649 'qt' : QtKernel,
650 650 'qt4': QtKernel,
651 651 'inline': Kernel,
652 652 'osx': TkKernel,
653 653 'wx' : WxKernel,
654 654 'tk' : TkKernel,
655 655 'gtk': GTKKernel,
656 656 }
657 657 if namespace.pylab:
658 658 if namespace.pylab == 'auto':
659 659 gui, backend = pylabtools.find_gui_and_backend()
660 660 else:
661 661 gui, backend = pylabtools.find_gui_and_backend(namespace.pylab)
662 662 kernel_class = kernel_classes.get(gui)
663 663 if kernel_class is None:
664 664 raise ValueError('GUI is not supported: %r' % gui)
665 665 pylabtools.activate_matplotlib(backend)
666 666 if namespace.colors:
667 667 ZMQInteractiveShell.colors=namespace.colors
668 668
669 669 kernel = make_kernel(namespace, kernel_class, OutStream)
670 670
671 671 if namespace.pylab:
672 672 pylabtools.import_pylab(kernel.shell.user_ns, backend,
673 673 shell=kernel.shell)
674 674
675 675 start_kernel(namespace, kernel)
676 676
677 677
678 678 if __name__ == '__main__':
679 679 main()
General Comments 0
You need to be logged in to leave comments. Login now