##// END OF EJS Templates
load %guiref magic on kernel start
MinRK -
Show More
@@ -1,561 +1,577 b''
1 """ A FrontendWidget that emulates the interface of the console IPython and
1 """ A FrontendWidget that emulates the interface of the console IPython and
2 supports the additional functionality provided by the IPython kernel.
2 supports the additional functionality provided by the IPython kernel.
3 """
3 """
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Imports
6 # Imports
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8
8
9 # Standard library imports
9 # Standard library imports
10 from collections import namedtuple
10 from collections import namedtuple
11 import os.path
11 import os.path
12 import re
12 import re
13 from subprocess import Popen
13 from subprocess import Popen
14 import sys
14 import sys
15 import time
15 import time
16 from textwrap import dedent
16 from textwrap import dedent
17
17
18 # System library imports
18 # System library imports
19 from IPython.external.qt import QtCore, QtGui
19 from IPython.external.qt import QtCore, QtGui
20
20
21 # Local imports
21 # Local imports
22 from IPython.core.inputsplitter import IPythonInputSplitter, \
22 from IPython.core.inputsplitter import IPythonInputSplitter, \
23 transform_ipy_prompt
23 transform_ipy_prompt
24 from IPython.utils.traitlets import Bool, Unicode
24 from IPython.utils.traitlets import Bool, Unicode
25 from frontend_widget import FrontendWidget
25 from frontend_widget import FrontendWidget
26 import styles
26 import styles
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Constants
29 # Constants
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31
31
32 # Default strings to build and display input and output prompts (and separators
32 # Default strings to build and display input and output prompts (and separators
33 # in between)
33 # in between)
34 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
34 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
35 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
35 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
36 default_input_sep = '\n'
36 default_input_sep = '\n'
37 default_output_sep = ''
37 default_output_sep = ''
38 default_output_sep2 = ''
38 default_output_sep2 = ''
39
39
40 # Base path for most payload sources.
40 # Base path for most payload sources.
41 zmq_shell_source = 'IPython.zmq.zmqshell.ZMQInteractiveShell'
41 zmq_shell_source = 'IPython.zmq.zmqshell.ZMQInteractiveShell'
42
42
43 if sys.platform.startswith('win'):
43 if sys.platform.startswith('win'):
44 default_editor = 'notepad'
44 default_editor = 'notepad'
45 else:
45 else:
46 default_editor = ''
46 default_editor = ''
47
47
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49 # IPythonWidget class
49 # IPythonWidget class
50 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51
51
52 class IPythonWidget(FrontendWidget):
52 class IPythonWidget(FrontendWidget):
53 """ A FrontendWidget for an IPython kernel.
53 """ A FrontendWidget for an IPython kernel.
54 """
54 """
55
55
56 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
56 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
57 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
57 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
58 # settings.
58 # settings.
59 custom_edit = Bool(False)
59 custom_edit = Bool(False)
60 custom_edit_requested = QtCore.Signal(object, object)
60 custom_edit_requested = QtCore.Signal(object, object)
61
61
62 editor = Unicode(default_editor, config=True,
62 editor = Unicode(default_editor, config=True,
63 help="""
63 help="""
64 A command for invoking a system text editor. If the string contains a
64 A command for invoking a system text editor. If the string contains a
65 {filename} format specifier, it will be used. Otherwise, the filename
65 {filename} format specifier, it will be used. Otherwise, the filename
66 will be appended to the end the command.
66 will be appended to the end the command.
67 """)
67 """)
68
68
69 editor_line = Unicode(config=True,
69 editor_line = Unicode(config=True,
70 help="""
70 help="""
71 The editor command to use when a specific line number is requested. The
71 The editor command to use when a specific line number is requested. The
72 string should contain two format specifiers: {line} and {filename}. If
72 string should contain two format specifiers: {line} and {filename}. If
73 this parameter is not specified, the line number option to the %edit
73 this parameter is not specified, the line number option to the %edit
74 magic will be ignored.
74 magic will be ignored.
75 """)
75 """)
76
76
77 style_sheet = Unicode(config=True,
77 style_sheet = Unicode(config=True,
78 help="""
78 help="""
79 A CSS stylesheet. The stylesheet can contain classes for:
79 A CSS stylesheet. The stylesheet can contain classes for:
80 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
80 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
81 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
81 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
82 3. IPython: .error, .in-prompt, .out-prompt, etc
82 3. IPython: .error, .in-prompt, .out-prompt, etc
83 """)
83 """)
84
84
85 syntax_style = Unicode(config=True,
85 syntax_style = Unicode(config=True,
86 help="""
86 help="""
87 If not empty, use this Pygments style for syntax highlighting.
87 If not empty, use this Pygments style for syntax highlighting.
88 Otherwise, the style sheet is queried for Pygments style
88 Otherwise, the style sheet is queried for Pygments style
89 information.
89 information.
90 """)
90 """)
91
91
92 # Prompts.
92 # Prompts.
93 in_prompt = Unicode(default_in_prompt, config=True)
93 in_prompt = Unicode(default_in_prompt, config=True)
94 out_prompt = Unicode(default_out_prompt, config=True)
94 out_prompt = Unicode(default_out_prompt, config=True)
95 input_sep = Unicode(default_input_sep, config=True)
95 input_sep = Unicode(default_input_sep, config=True)
96 output_sep = Unicode(default_output_sep, config=True)
96 output_sep = Unicode(default_output_sep, config=True)
97 output_sep2 = Unicode(default_output_sep2, config=True)
97 output_sep2 = Unicode(default_output_sep2, config=True)
98
98
99 # FrontendWidget protected class variables.
99 # FrontendWidget protected class variables.
100 _input_splitter_class = IPythonInputSplitter
100 _input_splitter_class = IPythonInputSplitter
101 _transform_prompt = staticmethod(transform_ipy_prompt)
101 _transform_prompt = staticmethod(transform_ipy_prompt)
102
102
103 # IPythonWidget protected class variables.
103 # IPythonWidget protected class variables.
104 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
104 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
105 _payload_source_edit = zmq_shell_source + '.edit_magic'
105 _payload_source_edit = zmq_shell_source + '.edit_magic'
106 _payload_source_exit = zmq_shell_source + '.ask_exit'
106 _payload_source_exit = zmq_shell_source + '.ask_exit'
107 _payload_source_next_input = zmq_shell_source + '.set_next_input'
107 _payload_source_next_input = zmq_shell_source + '.set_next_input'
108 _payload_source_page = 'IPython.zmq.page.page'
108 _payload_source_page = 'IPython.zmq.page.page'
109 _retrying_history_request = False
109 _retrying_history_request = False
110
110
111 #---------------------------------------------------------------------------
111 #---------------------------------------------------------------------------
112 # 'object' interface
112 # 'object' interface
113 #---------------------------------------------------------------------------
113 #---------------------------------------------------------------------------
114
114
115 def __init__(self, *args, **kw):
115 def __init__(self, *args, **kw):
116 super(IPythonWidget, self).__init__(*args, **kw)
116 super(IPythonWidget, self).__init__(*args, **kw)
117
117
118 # IPythonWidget protected variables.
118 # IPythonWidget protected variables.
119 self._payload_handlers = {
119 self._payload_handlers = {
120 self._payload_source_edit : self._handle_payload_edit,
120 self._payload_source_edit : self._handle_payload_edit,
121 self._payload_source_exit : self._handle_payload_exit,
121 self._payload_source_exit : self._handle_payload_exit,
122 self._payload_source_page : self._handle_payload_page,
122 self._payload_source_page : self._handle_payload_page,
123 self._payload_source_next_input : self._handle_payload_next_input }
123 self._payload_source_next_input : self._handle_payload_next_input }
124 self._previous_prompt_obj = None
124 self._previous_prompt_obj = None
125 self._keep_kernel_on_exit = None
125 self._keep_kernel_on_exit = None
126
126
127 # Initialize widget styling.
127 # Initialize widget styling.
128 if self.style_sheet:
128 if self.style_sheet:
129 self._style_sheet_changed()
129 self._style_sheet_changed()
130 self._syntax_style_changed()
130 self._syntax_style_changed()
131 else:
131 else:
132 self.set_default_style()
132 self.set_default_style()
133
133
134 #---------------------------------------------------------------------------
134 #---------------------------------------------------------------------------
135 # 'BaseFrontendMixin' abstract interface
135 # 'BaseFrontendMixin' abstract interface
136 #---------------------------------------------------------------------------
136 #---------------------------------------------------------------------------
137
137
138 def _handle_complete_reply(self, rep):
138 def _handle_complete_reply(self, rep):
139 """ Reimplemented to support IPython's improved completion machinery.
139 """ Reimplemented to support IPython's improved completion machinery.
140 """
140 """
141 self.log.debug("complete: %s", rep.get('content', ''))
141 self.log.debug("complete: %s", rep.get('content', ''))
142 cursor = self._get_cursor()
142 cursor = self._get_cursor()
143 info = self._request_info.get('complete')
143 info = self._request_info.get('complete')
144 if info and info.id == rep['parent_header']['msg_id'] and \
144 if info and info.id == rep['parent_header']['msg_id'] and \
145 info.pos == cursor.position():
145 info.pos == cursor.position():
146 matches = rep['content']['matches']
146 matches = rep['content']['matches']
147 text = rep['content']['matched_text']
147 text = rep['content']['matched_text']
148 offset = len(text)
148 offset = len(text)
149
149
150 # Clean up matches with period and path separators if the matched
150 # Clean up matches with period and path separators if the matched
151 # text has not been transformed. This is done by truncating all
151 # text has not been transformed. This is done by truncating all
152 # but the last component and then suitably decreasing the offset
152 # but the last component and then suitably decreasing the offset
153 # between the current cursor position and the start of completion.
153 # between the current cursor position and the start of completion.
154 if len(matches) > 1 and matches[0][:offset] == text:
154 if len(matches) > 1 and matches[0][:offset] == text:
155 parts = re.split(r'[./\\]', text)
155 parts = re.split(r'[./\\]', text)
156 sep_count = len(parts) - 1
156 sep_count = len(parts) - 1
157 if sep_count:
157 if sep_count:
158 chop_length = sum(map(len, parts[:sep_count])) + sep_count
158 chop_length = sum(map(len, parts[:sep_count])) + sep_count
159 matches = [ match[chop_length:] for match in matches ]
159 matches = [ match[chop_length:] for match in matches ]
160 offset -= chop_length
160 offset -= chop_length
161
161
162 # Move the cursor to the start of the match and complete.
162 # Move the cursor to the start of the match and complete.
163 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
163 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
164 self._complete_with_items(cursor, matches)
164 self._complete_with_items(cursor, matches)
165
165
166 def _handle_execute_reply(self, msg):
166 def _handle_execute_reply(self, msg):
167 """ Reimplemented to support prompt requests.
167 """ Reimplemented to support prompt requests.
168 """
168 """
169 msg_id = msg['parent_header'].get('msg_id')
169 msg_id = msg['parent_header'].get('msg_id')
170 info = self._request_info['execute'].get(msg_id)
170 info = self._request_info['execute'].get(msg_id)
171 if info and info.kind == 'prompt':
171 if info and info.kind == 'prompt':
172 number = msg['content']['execution_count'] + 1
172 number = msg['content']['execution_count'] + 1
173 self._show_interpreter_prompt(number)
173 self._show_interpreter_prompt(number)
174 self._request_info['execute'].pop(msg_id)
174 self._request_info['execute'].pop(msg_id)
175 else:
175 else:
176 super(IPythonWidget, self)._handle_execute_reply(msg)
176 super(IPythonWidget, self)._handle_execute_reply(msg)
177
177
178 def _handle_history_reply(self, msg):
178 def _handle_history_reply(self, msg):
179 """ Implemented to handle history tail replies, which are only supported
179 """ Implemented to handle history tail replies, which are only supported
180 by the IPython kernel.
180 by the IPython kernel.
181 """
181 """
182 content = msg['content']
182 content = msg['content']
183 if 'history' not in content:
183 if 'history' not in content:
184 self.log.error("History request failed: %r"%content)
184 self.log.error("History request failed: %r"%content)
185 if content.get('status', '') == 'aborted' and \
185 if content.get('status', '') == 'aborted' and \
186 not self._retrying_history_request:
186 not self._retrying_history_request:
187 # a *different* action caused this request to be aborted, so
187 # a *different* action caused this request to be aborted, so
188 # we should try again.
188 # we should try again.
189 self.log.error("Retrying aborted history request")
189 self.log.error("Retrying aborted history request")
190 # prevent multiple retries of aborted requests:
190 # prevent multiple retries of aborted requests:
191 self._retrying_history_request = True
191 self._retrying_history_request = True
192 # wait out the kernel's queue flush, which is currently timed at 0.1s
192 # wait out the kernel's queue flush, which is currently timed at 0.1s
193 time.sleep(0.25)
193 time.sleep(0.25)
194 self.kernel_manager.shell_channel.history(hist_access_type='tail',n=1000)
194 self.kernel_manager.shell_channel.history(hist_access_type='tail',n=1000)
195 else:
195 else:
196 self._retrying_history_request = False
196 self._retrying_history_request = False
197 return
197 return
198 # reset retry flag
198 # reset retry flag
199 self._retrying_history_request = False
199 self._retrying_history_request = False
200 history_items = content['history']
200 history_items = content['history']
201 self.log.debug("Received history reply with %i entries", len(history_items))
201 self.log.debug("Received history reply with %i entries", len(history_items))
202 items = []
202 items = []
203 last_cell = u""
203 last_cell = u""
204 for _, _, cell in history_items:
204 for _, _, cell in history_items:
205 cell = cell.rstrip()
205 cell = cell.rstrip()
206 if cell != last_cell:
206 if cell != last_cell:
207 items.append(cell)
207 items.append(cell)
208 last_cell = cell
208 last_cell = cell
209 self._set_history(items)
209 self._set_history(items)
210
210
211 def _handle_pyout(self, msg):
211 def _handle_pyout(self, msg):
212 """ Reimplemented for IPython-style "display hook".
212 """ Reimplemented for IPython-style "display hook".
213 """
213 """
214 self.log.debug("pyout: %s", msg.get('content', ''))
214 self.log.debug("pyout: %s", msg.get('content', ''))
215 if not self._hidden and self._is_from_this_session(msg):
215 if not self._hidden and self._is_from_this_session(msg):
216 content = msg['content']
216 content = msg['content']
217 prompt_number = content.get('execution_count', 0)
217 prompt_number = content.get('execution_count', 0)
218 data = content['data']
218 data = content['data']
219 if data.has_key('text/html'):
219 if data.has_key('text/html'):
220 self._append_plain_text(self.output_sep, True)
220 self._append_plain_text(self.output_sep, True)
221 self._append_html(self._make_out_prompt(prompt_number), True)
221 self._append_html(self._make_out_prompt(prompt_number), True)
222 html = data['text/html']
222 html = data['text/html']
223 self._append_plain_text('\n', True)
223 self._append_plain_text('\n', True)
224 self._append_html(html + self.output_sep2, True)
224 self._append_html(html + self.output_sep2, True)
225 elif data.has_key('text/plain'):
225 elif data.has_key('text/plain'):
226 self._append_plain_text(self.output_sep, True)
226 self._append_plain_text(self.output_sep, True)
227 self._append_html(self._make_out_prompt(prompt_number), True)
227 self._append_html(self._make_out_prompt(prompt_number), True)
228 text = data['text/plain']
228 text = data['text/plain']
229 # If the repr is multiline, make sure we start on a new line,
229 # If the repr is multiline, make sure we start on a new line,
230 # so that its lines are aligned.
230 # so that its lines are aligned.
231 if "\n" in text and not self.output_sep.endswith("\n"):
231 if "\n" in text and not self.output_sep.endswith("\n"):
232 self._append_plain_text('\n', True)
232 self._append_plain_text('\n', True)
233 self._append_plain_text(text + self.output_sep2, True)
233 self._append_plain_text(text + self.output_sep2, True)
234
234
235 def _handle_display_data(self, msg):
235 def _handle_display_data(self, msg):
236 """ The base handler for the ``display_data`` message.
236 """ The base handler for the ``display_data`` message.
237 """
237 """
238 self.log.debug("display: %s", msg.get('content', ''))
238 self.log.debug("display: %s", msg.get('content', ''))
239 # For now, we don't display data from other frontends, but we
239 # For now, we don't display data from other frontends, but we
240 # eventually will as this allows all frontends to monitor the display
240 # eventually will as this allows all frontends to monitor the display
241 # data. But we need to figure out how to handle this in the GUI.
241 # data. But we need to figure out how to handle this in the GUI.
242 if not self._hidden and self._is_from_this_session(msg):
242 if not self._hidden and self._is_from_this_session(msg):
243 source = msg['content']['source']
243 source = msg['content']['source']
244 data = msg['content']['data']
244 data = msg['content']['data']
245 metadata = msg['content']['metadata']
245 metadata = msg['content']['metadata']
246 # In the regular IPythonWidget, we simply print the plain text
246 # In the regular IPythonWidget, we simply print the plain text
247 # representation.
247 # representation.
248 if data.has_key('text/html'):
248 if data.has_key('text/html'):
249 html = data['text/html']
249 html = data['text/html']
250 self._append_html(html, True)
250 self._append_html(html, True)
251 elif data.has_key('text/plain'):
251 elif data.has_key('text/plain'):
252 text = data['text/plain']
252 text = data['text/plain']
253 self._append_plain_text(text, True)
253 self._append_plain_text(text, True)
254 # This newline seems to be needed for text and html output.
254 # This newline seems to be needed for text and html output.
255 self._append_plain_text(u'\n', True)
255 self._append_plain_text(u'\n', True)
256
256
257 def _started_channels(self):
257 def _started_channels(self):
258 """ Reimplemented to make a history request.
258 """Reimplemented to make a history request and load %guiref."""
259 """
260 super(IPythonWidget, self)._started_channels()
259 super(IPythonWidget, self)._started_channels()
260 self._load_guiref_magic()
261 self.kernel_manager.shell_channel.history(hist_access_type='tail',
261 self.kernel_manager.shell_channel.history(hist_access_type='tail',
262 n=1000)
262 n=1000)
263
264 def _started_kernel(self):
265 """Load %guiref when the kernel starts (if channels are also started).
266
267 Principally triggered by kernel restart.
268 """
269 if self.kernel_manager.shell_channel is not None:
270 self._load_guiref_magic()
271
272 def _load_guiref_magic(self):
273 """Load %guiref magic."""
274 self.kernel_manager.shell_channel.execute('\n'.join([
275 "from IPython.core import usage",
276 "get_ipython().register_magic_function(usage.page_guiref, 'line', 'guiref')",
277 ]), silent=True)
278
263 #---------------------------------------------------------------------------
279 #---------------------------------------------------------------------------
264 # 'ConsoleWidget' public interface
280 # 'ConsoleWidget' public interface
265 #---------------------------------------------------------------------------
281 #---------------------------------------------------------------------------
266
282
267 #---------------------------------------------------------------------------
283 #---------------------------------------------------------------------------
268 # 'FrontendWidget' public interface
284 # 'FrontendWidget' public interface
269 #---------------------------------------------------------------------------
285 #---------------------------------------------------------------------------
270
286
271 def execute_file(self, path, hidden=False):
287 def execute_file(self, path, hidden=False):
272 """ Reimplemented to use the 'run' magic.
288 """ Reimplemented to use the 'run' magic.
273 """
289 """
274 # Use forward slashes on Windows to avoid escaping each separator.
290 # Use forward slashes on Windows to avoid escaping each separator.
275 if sys.platform == 'win32':
291 if sys.platform == 'win32':
276 path = os.path.normpath(path).replace('\\', '/')
292 path = os.path.normpath(path).replace('\\', '/')
277
293
278 # Perhaps we should not be using %run directly, but while we
294 # Perhaps we should not be using %run directly, but while we
279 # are, it is necessary to quote or escape filenames containing spaces
295 # are, it is necessary to quote or escape filenames containing spaces
280 # or quotes.
296 # or quotes.
281
297
282 # In earlier code here, to minimize escaping, we sometimes quoted the
298 # In earlier code here, to minimize escaping, we sometimes quoted the
283 # filename with single quotes. But to do this, this code must be
299 # filename with single quotes. But to do this, this code must be
284 # platform-aware, because run uses shlex rather than python string
300 # platform-aware, because run uses shlex rather than python string
285 # parsing, so that:
301 # parsing, so that:
286 # * In Win: single quotes can be used in the filename without quoting,
302 # * In Win: single quotes can be used in the filename without quoting,
287 # and we cannot use single quotes to quote the filename.
303 # and we cannot use single quotes to quote the filename.
288 # * In *nix: we can escape double quotes in a double quoted filename,
304 # * In *nix: we can escape double quotes in a double quoted filename,
289 # but can't escape single quotes in a single quoted filename.
305 # but can't escape single quotes in a single quoted filename.
290
306
291 # So to keep this code non-platform-specific and simple, we now only
307 # So to keep this code non-platform-specific and simple, we now only
292 # use double quotes to quote filenames, and escape when needed:
308 # use double quotes to quote filenames, and escape when needed:
293 if ' ' in path or "'" in path or '"' in path:
309 if ' ' in path or "'" in path or '"' in path:
294 path = '"%s"' % path.replace('"', '\\"')
310 path = '"%s"' % path.replace('"', '\\"')
295 self.execute('%%run %s' % path, hidden=hidden)
311 self.execute('%%run %s' % path, hidden=hidden)
296
312
297 #---------------------------------------------------------------------------
313 #---------------------------------------------------------------------------
298 # 'FrontendWidget' protected interface
314 # 'FrontendWidget' protected interface
299 #---------------------------------------------------------------------------
315 #---------------------------------------------------------------------------
300
316
301 def _complete(self):
317 def _complete(self):
302 """ Reimplemented to support IPython's improved completion machinery.
318 """ Reimplemented to support IPython's improved completion machinery.
303 """
319 """
304 # We let the kernel split the input line, so we *always* send an empty
320 # We let the kernel split the input line, so we *always* send an empty
305 # text field. Readline-based frontends do get a real text field which
321 # text field. Readline-based frontends do get a real text field which
306 # they can use.
322 # they can use.
307 text = ''
323 text = ''
308
324
309 # Send the completion request to the kernel
325 # Send the completion request to the kernel
310 msg_id = self.kernel_manager.shell_channel.complete(
326 msg_id = self.kernel_manager.shell_channel.complete(
311 text, # text
327 text, # text
312 self._get_input_buffer_cursor_line(), # line
328 self._get_input_buffer_cursor_line(), # line
313 self._get_input_buffer_cursor_column(), # cursor_pos
329 self._get_input_buffer_cursor_column(), # cursor_pos
314 self.input_buffer) # block
330 self.input_buffer) # block
315 pos = self._get_cursor().position()
331 pos = self._get_cursor().position()
316 info = self._CompletionRequest(msg_id, pos)
332 info = self._CompletionRequest(msg_id, pos)
317 self._request_info['complete'] = info
333 self._request_info['complete'] = info
318
334
319 def _process_execute_error(self, msg):
335 def _process_execute_error(self, msg):
320 """ Reimplemented for IPython-style traceback formatting.
336 """ Reimplemented for IPython-style traceback formatting.
321 """
337 """
322 content = msg['content']
338 content = msg['content']
323 traceback = '\n'.join(content['traceback']) + '\n'
339 traceback = '\n'.join(content['traceback']) + '\n'
324 if False:
340 if False:
325 # FIXME: For now, tracebacks come as plain text, so we can't use
341 # FIXME: For now, tracebacks come as plain text, so we can't use
326 # the html renderer yet. Once we refactor ultratb to produce
342 # the html renderer yet. Once we refactor ultratb to produce
327 # properly styled tracebacks, this branch should be the default
343 # properly styled tracebacks, this branch should be the default
328 traceback = traceback.replace(' ', '&nbsp;')
344 traceback = traceback.replace(' ', '&nbsp;')
329 traceback = traceback.replace('\n', '<br/>')
345 traceback = traceback.replace('\n', '<br/>')
330
346
331 ename = content['ename']
347 ename = content['ename']
332 ename_styled = '<span class="error">%s</span>' % ename
348 ename_styled = '<span class="error">%s</span>' % ename
333 traceback = traceback.replace(ename, ename_styled)
349 traceback = traceback.replace(ename, ename_styled)
334
350
335 self._append_html(traceback)
351 self._append_html(traceback)
336 else:
352 else:
337 # This is the fallback for now, using plain text with ansi escapes
353 # This is the fallback for now, using plain text with ansi escapes
338 self._append_plain_text(traceback)
354 self._append_plain_text(traceback)
339
355
340 def _process_execute_payload(self, item):
356 def _process_execute_payload(self, item):
341 """ Reimplemented to dispatch payloads to handler methods.
357 """ Reimplemented to dispatch payloads to handler methods.
342 """
358 """
343 handler = self._payload_handlers.get(item['source'])
359 handler = self._payload_handlers.get(item['source'])
344 if handler is None:
360 if handler is None:
345 # We have no handler for this type of payload, simply ignore it
361 # We have no handler for this type of payload, simply ignore it
346 return False
362 return False
347 else:
363 else:
348 handler(item)
364 handler(item)
349 return True
365 return True
350
366
351 def _show_interpreter_prompt(self, number=None):
367 def _show_interpreter_prompt(self, number=None):
352 """ Reimplemented for IPython-style prompts.
368 """ Reimplemented for IPython-style prompts.
353 """
369 """
354 # If a number was not specified, make a prompt number request.
370 # If a number was not specified, make a prompt number request.
355 if number is None:
371 if number is None:
356 msg_id = self.kernel_manager.shell_channel.execute('', silent=True)
372 msg_id = self.kernel_manager.shell_channel.execute('', silent=True)
357 info = self._ExecutionRequest(msg_id, 'prompt')
373 info = self._ExecutionRequest(msg_id, 'prompt')
358 self._request_info['execute'][msg_id] = info
374 self._request_info['execute'][msg_id] = info
359 return
375 return
360
376
361 # Show a new prompt and save information about it so that it can be
377 # Show a new prompt and save information about it so that it can be
362 # updated later if the prompt number turns out to be wrong.
378 # updated later if the prompt number turns out to be wrong.
363 self._prompt_sep = self.input_sep
379 self._prompt_sep = self.input_sep
364 self._show_prompt(self._make_in_prompt(number), html=True)
380 self._show_prompt(self._make_in_prompt(number), html=True)
365 block = self._control.document().lastBlock()
381 block = self._control.document().lastBlock()
366 length = len(self._prompt)
382 length = len(self._prompt)
367 self._previous_prompt_obj = self._PromptBlock(block, length, number)
383 self._previous_prompt_obj = self._PromptBlock(block, length, number)
368
384
369 # Update continuation prompt to reflect (possibly) new prompt length.
385 # Update continuation prompt to reflect (possibly) new prompt length.
370 self._set_continuation_prompt(
386 self._set_continuation_prompt(
371 self._make_continuation_prompt(self._prompt), html=True)
387 self._make_continuation_prompt(self._prompt), html=True)
372
388
373 def _show_interpreter_prompt_for_reply(self, msg):
389 def _show_interpreter_prompt_for_reply(self, msg):
374 """ Reimplemented for IPython-style prompts.
390 """ Reimplemented for IPython-style prompts.
375 """
391 """
376 # Update the old prompt number if necessary.
392 # Update the old prompt number if necessary.
377 content = msg['content']
393 content = msg['content']
378 # abort replies do not have any keys:
394 # abort replies do not have any keys:
379 if content['status'] == 'aborted':
395 if content['status'] == 'aborted':
380 if self._previous_prompt_obj:
396 if self._previous_prompt_obj:
381 previous_prompt_number = self._previous_prompt_obj.number
397 previous_prompt_number = self._previous_prompt_obj.number
382 else:
398 else:
383 previous_prompt_number = 0
399 previous_prompt_number = 0
384 else:
400 else:
385 previous_prompt_number = content['execution_count']
401 previous_prompt_number = content['execution_count']
386 if self._previous_prompt_obj and \
402 if self._previous_prompt_obj and \
387 self._previous_prompt_obj.number != previous_prompt_number:
403 self._previous_prompt_obj.number != previous_prompt_number:
388 block = self._previous_prompt_obj.block
404 block = self._previous_prompt_obj.block
389
405
390 # Make sure the prompt block has not been erased.
406 # Make sure the prompt block has not been erased.
391 if block.isValid() and block.text():
407 if block.isValid() and block.text():
392
408
393 # Remove the old prompt and insert a new prompt.
409 # Remove the old prompt and insert a new prompt.
394 cursor = QtGui.QTextCursor(block)
410 cursor = QtGui.QTextCursor(block)
395 cursor.movePosition(QtGui.QTextCursor.Right,
411 cursor.movePosition(QtGui.QTextCursor.Right,
396 QtGui.QTextCursor.KeepAnchor,
412 QtGui.QTextCursor.KeepAnchor,
397 self._previous_prompt_obj.length)
413 self._previous_prompt_obj.length)
398 prompt = self._make_in_prompt(previous_prompt_number)
414 prompt = self._make_in_prompt(previous_prompt_number)
399 self._prompt = self._insert_html_fetching_plain_text(
415 self._prompt = self._insert_html_fetching_plain_text(
400 cursor, prompt)
416 cursor, prompt)
401
417
402 # When the HTML is inserted, Qt blows away the syntax
418 # When the HTML is inserted, Qt blows away the syntax
403 # highlighting for the line, so we need to rehighlight it.
419 # highlighting for the line, so we need to rehighlight it.
404 self._highlighter.rehighlightBlock(cursor.block())
420 self._highlighter.rehighlightBlock(cursor.block())
405
421
406 self._previous_prompt_obj = None
422 self._previous_prompt_obj = None
407
423
408 # Show a new prompt with the kernel's estimated prompt number.
424 # Show a new prompt with the kernel's estimated prompt number.
409 self._show_interpreter_prompt(previous_prompt_number + 1)
425 self._show_interpreter_prompt(previous_prompt_number + 1)
410
426
411 #---------------------------------------------------------------------------
427 #---------------------------------------------------------------------------
412 # 'IPythonWidget' interface
428 # 'IPythonWidget' interface
413 #---------------------------------------------------------------------------
429 #---------------------------------------------------------------------------
414
430
415 def set_default_style(self, colors='lightbg'):
431 def set_default_style(self, colors='lightbg'):
416 """ Sets the widget style to the class defaults.
432 """ Sets the widget style to the class defaults.
417
433
418 Parameters:
434 Parameters:
419 -----------
435 -----------
420 colors : str, optional (default lightbg)
436 colors : str, optional (default lightbg)
421 Whether to use the default IPython light background or dark
437 Whether to use the default IPython light background or dark
422 background or B&W style.
438 background or B&W style.
423 """
439 """
424 colors = colors.lower()
440 colors = colors.lower()
425 if colors=='lightbg':
441 if colors=='lightbg':
426 self.style_sheet = styles.default_light_style_sheet
442 self.style_sheet = styles.default_light_style_sheet
427 self.syntax_style = styles.default_light_syntax_style
443 self.syntax_style = styles.default_light_syntax_style
428 elif colors=='linux':
444 elif colors=='linux':
429 self.style_sheet = styles.default_dark_style_sheet
445 self.style_sheet = styles.default_dark_style_sheet
430 self.syntax_style = styles.default_dark_syntax_style
446 self.syntax_style = styles.default_dark_syntax_style
431 elif colors=='nocolor':
447 elif colors=='nocolor':
432 self.style_sheet = styles.default_bw_style_sheet
448 self.style_sheet = styles.default_bw_style_sheet
433 self.syntax_style = styles.default_bw_syntax_style
449 self.syntax_style = styles.default_bw_syntax_style
434 else:
450 else:
435 raise KeyError("No such color scheme: %s"%colors)
451 raise KeyError("No such color scheme: %s"%colors)
436
452
437 #---------------------------------------------------------------------------
453 #---------------------------------------------------------------------------
438 # 'IPythonWidget' protected interface
454 # 'IPythonWidget' protected interface
439 #---------------------------------------------------------------------------
455 #---------------------------------------------------------------------------
440
456
441 def _edit(self, filename, line=None):
457 def _edit(self, filename, line=None):
442 """ Opens a Python script for editing.
458 """ Opens a Python script for editing.
443
459
444 Parameters:
460 Parameters:
445 -----------
461 -----------
446 filename : str
462 filename : str
447 A path to a local system file.
463 A path to a local system file.
448
464
449 line : int, optional
465 line : int, optional
450 A line of interest in the file.
466 A line of interest in the file.
451 """
467 """
452 if self.custom_edit:
468 if self.custom_edit:
453 self.custom_edit_requested.emit(filename, line)
469 self.custom_edit_requested.emit(filename, line)
454 elif not self.editor:
470 elif not self.editor:
455 self._append_plain_text('No default editor available.\n'
471 self._append_plain_text('No default editor available.\n'
456 'Specify a GUI text editor in the `IPythonWidget.editor` '
472 'Specify a GUI text editor in the `IPythonWidget.editor` '
457 'configurable to enable the %edit magic')
473 'configurable to enable the %edit magic')
458 else:
474 else:
459 try:
475 try:
460 filename = '"%s"' % filename
476 filename = '"%s"' % filename
461 if line and self.editor_line:
477 if line and self.editor_line:
462 command = self.editor_line.format(filename=filename,
478 command = self.editor_line.format(filename=filename,
463 line=line)
479 line=line)
464 else:
480 else:
465 try:
481 try:
466 command = self.editor.format()
482 command = self.editor.format()
467 except KeyError:
483 except KeyError:
468 command = self.editor.format(filename=filename)
484 command = self.editor.format(filename=filename)
469 else:
485 else:
470 command += ' ' + filename
486 command += ' ' + filename
471 except KeyError:
487 except KeyError:
472 self._append_plain_text('Invalid editor command.\n')
488 self._append_plain_text('Invalid editor command.\n')
473 else:
489 else:
474 try:
490 try:
475 Popen(command, shell=True)
491 Popen(command, shell=True)
476 except OSError:
492 except OSError:
477 msg = 'Opening editor with command "%s" failed.\n'
493 msg = 'Opening editor with command "%s" failed.\n'
478 self._append_plain_text(msg % command)
494 self._append_plain_text(msg % command)
479
495
480 def _make_in_prompt(self, number):
496 def _make_in_prompt(self, number):
481 """ Given a prompt number, returns an HTML In prompt.
497 """ Given a prompt number, returns an HTML In prompt.
482 """
498 """
483 try:
499 try:
484 body = self.in_prompt % number
500 body = self.in_prompt % number
485 except TypeError:
501 except TypeError:
486 # allow in_prompt to leave out number, e.g. '>>> '
502 # allow in_prompt to leave out number, e.g. '>>> '
487 body = self.in_prompt
503 body = self.in_prompt
488 return '<span class="in-prompt">%s</span>' % body
504 return '<span class="in-prompt">%s</span>' % body
489
505
490 def _make_continuation_prompt(self, prompt):
506 def _make_continuation_prompt(self, prompt):
491 """ Given a plain text version of an In prompt, returns an HTML
507 """ Given a plain text version of an In prompt, returns an HTML
492 continuation prompt.
508 continuation prompt.
493 """
509 """
494 end_chars = '...: '
510 end_chars = '...: '
495 space_count = len(prompt.lstrip('\n')) - len(end_chars)
511 space_count = len(prompt.lstrip('\n')) - len(end_chars)
496 body = '&nbsp;' * space_count + end_chars
512 body = '&nbsp;' * space_count + end_chars
497 return '<span class="in-prompt">%s</span>' % body
513 return '<span class="in-prompt">%s</span>' % body
498
514
499 def _make_out_prompt(self, number):
515 def _make_out_prompt(self, number):
500 """ Given a prompt number, returns an HTML Out prompt.
516 """ Given a prompt number, returns an HTML Out prompt.
501 """
517 """
502 body = self.out_prompt % number
518 body = self.out_prompt % number
503 return '<span class="out-prompt">%s</span>' % body
519 return '<span class="out-prompt">%s</span>' % body
504
520
505 #------ Payload handlers --------------------------------------------------
521 #------ Payload handlers --------------------------------------------------
506
522
507 # Payload handlers with a generic interface: each takes the opaque payload
523 # Payload handlers with a generic interface: each takes the opaque payload
508 # dict, unpacks it and calls the underlying functions with the necessary
524 # dict, unpacks it and calls the underlying functions with the necessary
509 # arguments.
525 # arguments.
510
526
511 def _handle_payload_edit(self, item):
527 def _handle_payload_edit(self, item):
512 self._edit(item['filename'], item['line_number'])
528 self._edit(item['filename'], item['line_number'])
513
529
514 def _handle_payload_exit(self, item):
530 def _handle_payload_exit(self, item):
515 self._keep_kernel_on_exit = item['keepkernel']
531 self._keep_kernel_on_exit = item['keepkernel']
516 self.exit_requested.emit(self)
532 self.exit_requested.emit(self)
517
533
518 def _handle_payload_next_input(self, item):
534 def _handle_payload_next_input(self, item):
519 self.input_buffer = dedent(item['text'].rstrip())
535 self.input_buffer = dedent(item['text'].rstrip())
520
536
521 def _handle_payload_page(self, item):
537 def _handle_payload_page(self, item):
522 # Since the plain text widget supports only a very small subset of HTML
538 # Since the plain text widget supports only a very small subset of HTML
523 # and we have no control over the HTML source, we only page HTML
539 # and we have no control over the HTML source, we only page HTML
524 # payloads in the rich text widget.
540 # payloads in the rich text widget.
525 if item['html'] and self.kind == 'rich':
541 if item['html'] and self.kind == 'rich':
526 self._page(item['html'], html=True)
542 self._page(item['html'], html=True)
527 else:
543 else:
528 self._page(item['text'], html=False)
544 self._page(item['text'], html=False)
529
545
530 #------ Trait change handlers --------------------------------------------
546 #------ Trait change handlers --------------------------------------------
531
547
532 def _style_sheet_changed(self):
548 def _style_sheet_changed(self):
533 """ Set the style sheets of the underlying widgets.
549 """ Set the style sheets of the underlying widgets.
534 """
550 """
535 self.setStyleSheet(self.style_sheet)
551 self.setStyleSheet(self.style_sheet)
536 if self._control is not None:
552 if self._control is not None:
537 self._control.document().setDefaultStyleSheet(self.style_sheet)
553 self._control.document().setDefaultStyleSheet(self.style_sheet)
538 bg_color = self._control.palette().window().color()
554 bg_color = self._control.palette().window().color()
539 self._ansi_processor.set_background_color(bg_color)
555 self._ansi_processor.set_background_color(bg_color)
540
556
541 if self._page_control is not None:
557 if self._page_control is not None:
542 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
558 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
543
559
544
560
545
561
546 def _syntax_style_changed(self):
562 def _syntax_style_changed(self):
547 """ Set the style for the syntax highlighter.
563 """ Set the style for the syntax highlighter.
548 """
564 """
549 if self._highlighter is None:
565 if self._highlighter is None:
550 # ignore premature calls
566 # ignore premature calls
551 return
567 return
552 if self.syntax_style:
568 if self.syntax_style:
553 self._highlighter.set_style(self.syntax_style)
569 self._highlighter.set_style(self.syntax_style)
554 else:
570 else:
555 self._highlighter.set_style_sheet(self.style_sheet)
571 self._highlighter.set_style_sheet(self.style_sheet)
556
572
557 #------ Trait default initializers -----------------------------------------
573 #------ Trait default initializers -----------------------------------------
558
574
559 def _banner_default(self):
575 def _banner_default(self):
560 from IPython.core.usage import default_gui_banner
576 from IPython.core.usage import default_gui_banner
561 return default_gui_banner
577 return default_gui_banner
General Comments 0
You need to be logged in to leave comments. Login now