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