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