##// END OF EJS Templates
Fix edit magic payload handling for Qt console...
Thomas Kluyver -
Show More
@@ -1,594 +1,594 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'
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)
155 end = max(end, start)
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.history(hist_access_type='tail',n=1000)
206 self.kernel_client.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):
223 def _insert_other_input(self, cursor, content):
224 """Insert function for input from other frontends"""
224 """Insert function for input from other frontends"""
225 cursor.beginEditBlock()
225 cursor.beginEditBlock()
226 start = cursor.position()
226 start = cursor.position()
227 n = content.get('execution_count', 0)
227 n = content.get('execution_count', 0)
228 cursor.insertText('\n')
228 cursor.insertText('\n')
229 self._insert_html(cursor, self._make_in_prompt(n))
229 self._insert_html(cursor, self._make_in_prompt(n))
230 cursor.insertText(content['code'])
230 cursor.insertText(content['code'])
231 self._highlighter.rehighlightBlock(cursor.block())
231 self._highlighter.rehighlightBlock(cursor.block())
232 cursor.endEditBlock()
232 cursor.endEditBlock()
233
233
234 def _handle_execute_input(self, msg):
234 def _handle_execute_input(self, msg):
235 """Handle an execute_input message"""
235 """Handle an execute_input message"""
236 self.log.debug("execute_input: %s", msg.get('content', ''))
236 self.log.debug("execute_input: %s", msg.get('content', ''))
237 if self.include_output(msg):
237 if self.include_output(msg):
238 self._append_custom(self._insert_other_input, msg['content'], before_prompt=True)
238 self._append_custom(self._insert_other_input, msg['content'], before_prompt=True)
239
239
240
240
241 def _handle_execute_result(self, msg):
241 def _handle_execute_result(self, msg):
242 """Reimplemented for IPython-style "display hook"."""
242 """Reimplemented for IPython-style "display hook"."""
243 if self.include_output(msg):
243 if self.include_output(msg):
244 self.flush_clearoutput()
244 self.flush_clearoutput()
245 content = msg['content']
245 content = msg['content']
246 prompt_number = content.get('execution_count', 0)
246 prompt_number = content.get('execution_count', 0)
247 data = content['data']
247 data = content['data']
248 if 'text/plain' in data:
248 if 'text/plain' in data:
249 self._append_plain_text(self.output_sep, True)
249 self._append_plain_text(self.output_sep, True)
250 self._append_html(self._make_out_prompt(prompt_number), True)
250 self._append_html(self._make_out_prompt(prompt_number), True)
251 text = data['text/plain']
251 text = data['text/plain']
252 # If the repr is multiline, make sure we start on a new line,
252 # If the repr is multiline, make sure we start on a new line,
253 # so that its lines are aligned.
253 # so that its lines are aligned.
254 if "\n" in text and not self.output_sep.endswith("\n"):
254 if "\n" in text and not self.output_sep.endswith("\n"):
255 self._append_plain_text('\n', True)
255 self._append_plain_text('\n', True)
256 self._append_plain_text(text + self.output_sep2, True)
256 self._append_plain_text(text + self.output_sep2, True)
257
257
258 def _handle_display_data(self, msg):
258 def _handle_display_data(self, msg):
259 """The base handler for the ``display_data`` message."""
259 """The base handler for the ``display_data`` message."""
260 # For now, we don't display data from other frontends, but we
260 # For now, we don't display data from other frontends, but we
261 # eventually will as this allows all frontends to monitor the display
261 # eventually will as this allows all frontends to monitor the display
262 # data. But we need to figure out how to handle this in the GUI.
262 # data. But we need to figure out how to handle this in the GUI.
263 if self.include_output(msg):
263 if self.include_output(msg):
264 self.flush_clearoutput()
264 self.flush_clearoutput()
265 data = msg['content']['data']
265 data = msg['content']['data']
266 metadata = msg['content']['metadata']
266 metadata = msg['content']['metadata']
267 # In the regular IPythonWidget, we simply print the plain text
267 # In the regular IPythonWidget, we simply print the plain text
268 # representation.
268 # representation.
269 if 'text/plain' in data:
269 if 'text/plain' in data:
270 text = data['text/plain']
270 text = data['text/plain']
271 self._append_plain_text(text, True)
271 self._append_plain_text(text, True)
272 # This newline seems to be needed for text and html output.
272 # This newline seems to be needed for text and html output.
273 self._append_plain_text(u'\n', True)
273 self._append_plain_text(u'\n', True)
274
274
275 def _handle_kernel_info_reply(self, rep):
275 def _handle_kernel_info_reply(self, rep):
276 """Handle kernel info replies."""
276 """Handle kernel info replies."""
277 content = rep['content']
277 content = rep['content']
278 if not self._guiref_loaded:
278 if not self._guiref_loaded:
279 if content.get('implementation') == 'ipython':
279 if content.get('implementation') == 'ipython':
280 self._load_guiref_magic()
280 self._load_guiref_magic()
281 self._guiref_loaded = True
281 self._guiref_loaded = True
282
282
283 self.kernel_banner = content.get('banner', '')
283 self.kernel_banner = content.get('banner', '')
284 if self._starting:
284 if self._starting:
285 # finish handling started channels
285 # finish handling started channels
286 self._starting = False
286 self._starting = False
287 super(IPythonWidget, self)._started_channels()
287 super(IPythonWidget, self)._started_channels()
288
288
289 def _started_channels(self):
289 def _started_channels(self):
290 """Reimplemented to make a history request and load %guiref."""
290 """Reimplemented to make a history request and load %guiref."""
291 self._starting = True
291 self._starting = True
292 # The reply will trigger %guiref load provided language=='python'
292 # The reply will trigger %guiref load provided language=='python'
293 self.kernel_client.kernel_info()
293 self.kernel_client.kernel_info()
294
294
295 self.kernel_client.history(hist_access_type='tail', n=1000)
295 self.kernel_client.history(hist_access_type='tail', n=1000)
296
296
297 def _load_guiref_magic(self):
297 def _load_guiref_magic(self):
298 """Load %guiref magic."""
298 """Load %guiref magic."""
299 self.kernel_client.execute('\n'.join([
299 self.kernel_client.execute('\n'.join([
300 "try:",
300 "try:",
301 " _usage",
301 " _usage",
302 "except:",
302 "except:",
303 " from IPython.core import usage as _usage",
303 " from IPython.core import usage as _usage",
304 " get_ipython().register_magic_function(_usage.page_guiref, 'line', 'guiref')",
304 " get_ipython().register_magic_function(_usage.page_guiref, 'line', 'guiref')",
305 " del _usage",
305 " del _usage",
306 ]), silent=True)
306 ]), silent=True)
307
307
308 #---------------------------------------------------------------------------
308 #---------------------------------------------------------------------------
309 # 'ConsoleWidget' public interface
309 # 'ConsoleWidget' public interface
310 #---------------------------------------------------------------------------
310 #---------------------------------------------------------------------------
311
311
312 #---------------------------------------------------------------------------
312 #---------------------------------------------------------------------------
313 # 'FrontendWidget' public interface
313 # 'FrontendWidget' public interface
314 #---------------------------------------------------------------------------
314 #---------------------------------------------------------------------------
315
315
316 def execute_file(self, path, hidden=False):
316 def execute_file(self, path, hidden=False):
317 """ Reimplemented to use the 'run' magic.
317 """ Reimplemented to use the 'run' magic.
318 """
318 """
319 # Use forward slashes on Windows to avoid escaping each separator.
319 # Use forward slashes on Windows to avoid escaping each separator.
320 if sys.platform == 'win32':
320 if sys.platform == 'win32':
321 path = os.path.normpath(path).replace('\\', '/')
321 path = os.path.normpath(path).replace('\\', '/')
322
322
323 # Perhaps we should not be using %run directly, but while we
323 # Perhaps we should not be using %run directly, but while we
324 # are, it is necessary to quote or escape filenames containing spaces
324 # are, it is necessary to quote or escape filenames containing spaces
325 # or quotes.
325 # or quotes.
326
326
327 # In earlier code here, to minimize escaping, we sometimes quoted the
327 # In earlier code here, to minimize escaping, we sometimes quoted the
328 # filename with single quotes. But to do this, this code must be
328 # filename with single quotes. But to do this, this code must be
329 # platform-aware, because run uses shlex rather than python string
329 # platform-aware, because run uses shlex rather than python string
330 # parsing, so that:
330 # parsing, so that:
331 # * In Win: single quotes can be used in the filename without quoting,
331 # * In Win: single quotes can be used in the filename without quoting,
332 # and we cannot use single quotes to quote the filename.
332 # and we cannot use single quotes to quote the filename.
333 # * In *nix: we can escape double quotes in a double quoted filename,
333 # * In *nix: we can escape double quotes in a double quoted filename,
334 # but can't escape single quotes in a single quoted filename.
334 # but can't escape single quotes in a single quoted filename.
335
335
336 # So to keep this code non-platform-specific and simple, we now only
336 # So to keep this code non-platform-specific and simple, we now only
337 # use double quotes to quote filenames, and escape when needed:
337 # use double quotes to quote filenames, and escape when needed:
338 if ' ' in path or "'" in path or '"' in path:
338 if ' ' in path or "'" in path or '"' in path:
339 path = '"%s"' % path.replace('"', '\\"')
339 path = '"%s"' % path.replace('"', '\\"')
340 self.execute('%%run %s' % path, hidden=hidden)
340 self.execute('%%run %s' % path, hidden=hidden)
341
341
342 #---------------------------------------------------------------------------
342 #---------------------------------------------------------------------------
343 # 'FrontendWidget' protected interface
343 # 'FrontendWidget' protected interface
344 #---------------------------------------------------------------------------
344 #---------------------------------------------------------------------------
345
345
346 def _process_execute_error(self, msg):
346 def _process_execute_error(self, msg):
347 """ Reimplemented for IPython-style traceback formatting.
347 """ Reimplemented for IPython-style traceback formatting.
348 """
348 """
349 content = msg['content']
349 content = msg['content']
350 traceback = '\n'.join(content['traceback']) + '\n'
350 traceback = '\n'.join(content['traceback']) + '\n'
351 if False:
351 if False:
352 # FIXME: For now, tracebacks come as plain text, so we can't use
352 # FIXME: For now, tracebacks come as plain text, so we can't use
353 # the html renderer yet. Once we refactor ultratb to produce
353 # the html renderer yet. Once we refactor ultratb to produce
354 # properly styled tracebacks, this branch should be the default
354 # properly styled tracebacks, this branch should be the default
355 traceback = traceback.replace(' ', '&nbsp;')
355 traceback = traceback.replace(' ', '&nbsp;')
356 traceback = traceback.replace('\n', '<br/>')
356 traceback = traceback.replace('\n', '<br/>')
357
357
358 ename = content['ename']
358 ename = content['ename']
359 ename_styled = '<span class="error">%s</span>' % ename
359 ename_styled = '<span class="error">%s</span>' % ename
360 traceback = traceback.replace(ename, ename_styled)
360 traceback = traceback.replace(ename, ename_styled)
361
361
362 self._append_html(traceback)
362 self._append_html(traceback)
363 else:
363 else:
364 # This is the fallback for now, using plain text with ansi escapes
364 # This is the fallback for now, using plain text with ansi escapes
365 self._append_plain_text(traceback)
365 self._append_plain_text(traceback)
366
366
367 def _process_execute_payload(self, item):
367 def _process_execute_payload(self, item):
368 """ Reimplemented to dispatch payloads to handler methods.
368 """ Reimplemented to dispatch payloads to handler methods.
369 """
369 """
370 handler = self._payload_handlers.get(item['source'])
370 handler = self._payload_handlers.get(item['source'])
371 if handler is None:
371 if handler is None:
372 # We have no handler for this type of payload, simply ignore it
372 # We have no handler for this type of payload, simply ignore it
373 return False
373 return False
374 else:
374 else:
375 handler(item)
375 handler(item)
376 return True
376 return True
377
377
378 def _show_interpreter_prompt(self, number=None):
378 def _show_interpreter_prompt(self, number=None):
379 """ Reimplemented for IPython-style prompts.
379 """ Reimplemented for IPython-style prompts.
380 """
380 """
381 # If a number was not specified, make a prompt number request.
381 # If a number was not specified, make a prompt number request.
382 if number is None:
382 if number is None:
383 msg_id = self.kernel_client.execute('', silent=True)
383 msg_id = self.kernel_client.execute('', silent=True)
384 info = self._ExecutionRequest(msg_id, 'prompt')
384 info = self._ExecutionRequest(msg_id, 'prompt')
385 self._request_info['execute'][msg_id] = info
385 self._request_info['execute'][msg_id] = info
386 return
386 return
387
387
388 # Show a new prompt and save information about it so that it can be
388 # Show a new prompt and save information about it so that it can be
389 # updated later if the prompt number turns out to be wrong.
389 # updated later if the prompt number turns out to be wrong.
390 self._prompt_sep = self.input_sep
390 self._prompt_sep = self.input_sep
391 self._show_prompt(self._make_in_prompt(number), html=True)
391 self._show_prompt(self._make_in_prompt(number), html=True)
392 block = self._control.document().lastBlock()
392 block = self._control.document().lastBlock()
393 length = len(self._prompt)
393 length = len(self._prompt)
394 self._previous_prompt_obj = self._PromptBlock(block, length, number)
394 self._previous_prompt_obj = self._PromptBlock(block, length, number)
395
395
396 # Update continuation prompt to reflect (possibly) new prompt length.
396 # Update continuation prompt to reflect (possibly) new prompt length.
397 self._set_continuation_prompt(
397 self._set_continuation_prompt(
398 self._make_continuation_prompt(self._prompt), html=True)
398 self._make_continuation_prompt(self._prompt), html=True)
399
399
400 def _show_interpreter_prompt_for_reply(self, msg):
400 def _show_interpreter_prompt_for_reply(self, msg):
401 """ Reimplemented for IPython-style prompts.
401 """ Reimplemented for IPython-style prompts.
402 """
402 """
403 # Update the old prompt number if necessary.
403 # Update the old prompt number if necessary.
404 content = msg['content']
404 content = msg['content']
405 # abort replies do not have any keys:
405 # abort replies do not have any keys:
406 if content['status'] == 'aborted':
406 if content['status'] == 'aborted':
407 if self._previous_prompt_obj:
407 if self._previous_prompt_obj:
408 previous_prompt_number = self._previous_prompt_obj.number
408 previous_prompt_number = self._previous_prompt_obj.number
409 else:
409 else:
410 previous_prompt_number = 0
410 previous_prompt_number = 0
411 else:
411 else:
412 previous_prompt_number = content['execution_count']
412 previous_prompt_number = content['execution_count']
413 if self._previous_prompt_obj and \
413 if self._previous_prompt_obj and \
414 self._previous_prompt_obj.number != previous_prompt_number:
414 self._previous_prompt_obj.number != previous_prompt_number:
415 block = self._previous_prompt_obj.block
415 block = self._previous_prompt_obj.block
416
416
417 # Make sure the prompt block has not been erased.
417 # Make sure the prompt block has not been erased.
418 if block.isValid() and block.text():
418 if block.isValid() and block.text():
419
419
420 # Remove the old prompt and insert a new prompt.
420 # Remove the old prompt and insert a new prompt.
421 cursor = QtGui.QTextCursor(block)
421 cursor = QtGui.QTextCursor(block)
422 cursor.movePosition(QtGui.QTextCursor.Right,
422 cursor.movePosition(QtGui.QTextCursor.Right,
423 QtGui.QTextCursor.KeepAnchor,
423 QtGui.QTextCursor.KeepAnchor,
424 self._previous_prompt_obj.length)
424 self._previous_prompt_obj.length)
425 prompt = self._make_in_prompt(previous_prompt_number)
425 prompt = self._make_in_prompt(previous_prompt_number)
426 self._prompt = self._insert_html_fetching_plain_text(
426 self._prompt = self._insert_html_fetching_plain_text(
427 cursor, prompt)
427 cursor, prompt)
428
428
429 # When the HTML is inserted, Qt blows away the syntax
429 # When the HTML is inserted, Qt blows away the syntax
430 # highlighting for the line, so we need to rehighlight it.
430 # highlighting for the line, so we need to rehighlight it.
431 self._highlighter.rehighlightBlock(cursor.block())
431 self._highlighter.rehighlightBlock(cursor.block())
432
432
433 self._previous_prompt_obj = None
433 self._previous_prompt_obj = None
434
434
435 # Show a new prompt with the kernel's estimated prompt number.
435 # Show a new prompt with the kernel's estimated prompt number.
436 self._show_interpreter_prompt(previous_prompt_number + 1)
436 self._show_interpreter_prompt(previous_prompt_number + 1)
437
437
438 #---------------------------------------------------------------------------
438 #---------------------------------------------------------------------------
439 # 'IPythonWidget' interface
439 # 'IPythonWidget' interface
440 #---------------------------------------------------------------------------
440 #---------------------------------------------------------------------------
441
441
442 def set_default_style(self, colors='lightbg'):
442 def set_default_style(self, colors='lightbg'):
443 """ Sets the widget style to the class defaults.
443 """ Sets the widget style to the class defaults.
444
444
445 Parameters
445 Parameters
446 ----------
446 ----------
447 colors : str, optional (default lightbg)
447 colors : str, optional (default lightbg)
448 Whether to use the default IPython light background or dark
448 Whether to use the default IPython light background or dark
449 background or B&W style.
449 background or B&W style.
450 """
450 """
451 colors = colors.lower()
451 colors = colors.lower()
452 if colors=='lightbg':
452 if colors=='lightbg':
453 self.style_sheet = styles.default_light_style_sheet
453 self.style_sheet = styles.default_light_style_sheet
454 self.syntax_style = styles.default_light_syntax_style
454 self.syntax_style = styles.default_light_syntax_style
455 elif colors=='linux':
455 elif colors=='linux':
456 self.style_sheet = styles.default_dark_style_sheet
456 self.style_sheet = styles.default_dark_style_sheet
457 self.syntax_style = styles.default_dark_syntax_style
457 self.syntax_style = styles.default_dark_syntax_style
458 elif colors=='nocolor':
458 elif colors=='nocolor':
459 self.style_sheet = styles.default_bw_style_sheet
459 self.style_sheet = styles.default_bw_style_sheet
460 self.syntax_style = styles.default_bw_syntax_style
460 self.syntax_style = styles.default_bw_syntax_style
461 else:
461 else:
462 raise KeyError("No such color scheme: %s"%colors)
462 raise KeyError("No such color scheme: %s"%colors)
463
463
464 #---------------------------------------------------------------------------
464 #---------------------------------------------------------------------------
465 # 'IPythonWidget' protected interface
465 # 'IPythonWidget' protected interface
466 #---------------------------------------------------------------------------
466 #---------------------------------------------------------------------------
467
467
468 def _edit(self, filename, line=None):
468 def _edit(self, filename, line=None):
469 """ Opens a Python script for editing.
469 """ Opens a Python script for editing.
470
470
471 Parameters
471 Parameters
472 ----------
472 ----------
473 filename : str
473 filename : str
474 A path to a local system file.
474 A path to a local system file.
475
475
476 line : int, optional
476 line : int, optional
477 A line of interest in the file.
477 A line of interest in the file.
478 """
478 """
479 if self.custom_edit:
479 if self.custom_edit:
480 self.custom_edit_requested.emit(filename, line)
480 self.custom_edit_requested.emit(filename, line)
481 elif not self.editor:
481 elif not self.editor:
482 self._append_plain_text('No default editor available.\n'
482 self._append_plain_text('No default editor available.\n'
483 'Specify a GUI text editor in the `IPythonWidget.editor` '
483 'Specify a GUI text editor in the `IPythonWidget.editor` '
484 'configurable to enable the %edit magic')
484 'configurable to enable the %edit magic')
485 else:
485 else:
486 try:
486 try:
487 filename = '"%s"' % filename
487 filename = '"%s"' % filename
488 if line and self.editor_line:
488 if line and self.editor_line:
489 command = self.editor_line.format(filename=filename,
489 command = self.editor_line.format(filename=filename,
490 line=line)
490 line=line)
491 else:
491 else:
492 try:
492 try:
493 command = self.editor.format()
493 command = self.editor.format()
494 except KeyError:
494 except KeyError:
495 command = self.editor.format(filename=filename)
495 command = self.editor.format(filename=filename)
496 else:
496 else:
497 command += ' ' + filename
497 command += ' ' + filename
498 except KeyError:
498 except KeyError:
499 self._append_plain_text('Invalid editor command.\n')
499 self._append_plain_text('Invalid editor command.\n')
500 else:
500 else:
501 try:
501 try:
502 Popen(command, shell=True)
502 Popen(command, shell=True)
503 except OSError:
503 except OSError:
504 msg = 'Opening editor with command "%s" failed.\n'
504 msg = 'Opening editor with command "%s" failed.\n'
505 self._append_plain_text(msg % command)
505 self._append_plain_text(msg % command)
506
506
507 def _make_in_prompt(self, number):
507 def _make_in_prompt(self, number):
508 """ Given a prompt number, returns an HTML In prompt.
508 """ Given a prompt number, returns an HTML In prompt.
509 """
509 """
510 try:
510 try:
511 body = self.in_prompt % number
511 body = self.in_prompt % number
512 except TypeError:
512 except TypeError:
513 # allow in_prompt to leave out number, e.g. '>>> '
513 # allow in_prompt to leave out number, e.g. '>>> '
514 from xml.sax.saxutils import escape
514 from xml.sax.saxutils import escape
515 body = escape(self.in_prompt)
515 body = escape(self.in_prompt)
516 return '<span class="in-prompt">%s</span>' % body
516 return '<span class="in-prompt">%s</span>' % body
517
517
518 def _make_continuation_prompt(self, prompt):
518 def _make_continuation_prompt(self, prompt):
519 """ Given a plain text version of an In prompt, returns an HTML
519 """ Given a plain text version of an In prompt, returns an HTML
520 continuation prompt.
520 continuation prompt.
521 """
521 """
522 end_chars = '...: '
522 end_chars = '...: '
523 space_count = len(prompt.lstrip('\n')) - len(end_chars)
523 space_count = len(prompt.lstrip('\n')) - len(end_chars)
524 body = '&nbsp;' * space_count + end_chars
524 body = '&nbsp;' * space_count + end_chars
525 return '<span class="in-prompt">%s</span>' % body
525 return '<span class="in-prompt">%s</span>' % body
526
526
527 def _make_out_prompt(self, number):
527 def _make_out_prompt(self, number):
528 """ Given a prompt number, returns an HTML Out prompt.
528 """ Given a prompt number, returns an HTML Out prompt.
529 """
529 """
530 try:
530 try:
531 body = self.out_prompt % number
531 body = self.out_prompt % number
532 except TypeError:
532 except TypeError:
533 # allow out_prompt to leave out number, e.g. '<<< '
533 # allow out_prompt to leave out number, e.g. '<<< '
534 from xml.sax.saxutils import escape
534 from xml.sax.saxutils import escape
535 body = escape(self.out_prompt)
535 body = escape(self.out_prompt)
536 return '<span class="out-prompt">%s</span>' % body
536 return '<span class="out-prompt">%s</span>' % body
537
537
538 #------ Payload handlers --------------------------------------------------
538 #------ Payload handlers --------------------------------------------------
539
539
540 # Payload handlers with a generic interface: each takes the opaque payload
540 # Payload handlers with a generic interface: each takes the opaque payload
541 # dict, unpacks it and calls the underlying functions with the necessary
541 # dict, unpacks it and calls the underlying functions with the necessary
542 # arguments.
542 # arguments.
543
543
544 def _handle_payload_edit(self, item):
544 def _handle_payload_edit(self, item):
545 self._edit(item['filename'], item['line_number'])
545 self._edit(item['filename'], item['line_number'])
546
546
547 def _handle_payload_exit(self, item):
547 def _handle_payload_exit(self, item):
548 self._keep_kernel_on_exit = item['keepkernel']
548 self._keep_kernel_on_exit = item['keepkernel']
549 self.exit_requested.emit(self)
549 self.exit_requested.emit(self)
550
550
551 def _handle_payload_next_input(self, item):
551 def _handle_payload_next_input(self, item):
552 self.input_buffer = item['text']
552 self.input_buffer = item['text']
553
553
554 def _handle_payload_page(self, item):
554 def _handle_payload_page(self, item):
555 # Since the plain text widget supports only a very small subset of HTML
555 # Since the plain text widget supports only a very small subset of HTML
556 # and we have no control over the HTML source, we only page HTML
556 # and we have no control over the HTML source, we only page HTML
557 # payloads in the rich text widget.
557 # payloads in the rich text widget.
558 data = item['data']
558 data = item['data']
559 if 'text/html' in data and self.kind == 'rich':
559 if 'text/html' in data and self.kind == 'rich':
560 self._page(data['text/html'], html=True)
560 self._page(data['text/html'], html=True)
561 else:
561 else:
562 self._page(data['text/plain'], html=False)
562 self._page(data['text/plain'], html=False)
563
563
564 #------ Trait change handlers --------------------------------------------
564 #------ Trait change handlers --------------------------------------------
565
565
566 def _style_sheet_changed(self):
566 def _style_sheet_changed(self):
567 """ Set the style sheets of the underlying widgets.
567 """ Set the style sheets of the underlying widgets.
568 """
568 """
569 self.setStyleSheet(self.style_sheet)
569 self.setStyleSheet(self.style_sheet)
570 if self._control is not None:
570 if self._control is not None:
571 self._control.document().setDefaultStyleSheet(self.style_sheet)
571 self._control.document().setDefaultStyleSheet(self.style_sheet)
572 bg_color = self._control.palette().window().color()
572 bg_color = self._control.palette().window().color()
573 self._ansi_processor.set_background_color(bg_color)
573 self._ansi_processor.set_background_color(bg_color)
574
574
575 if self._page_control is not None:
575 if self._page_control is not None:
576 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
576 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
577
577
578
578
579
579
580 def _syntax_style_changed(self):
580 def _syntax_style_changed(self):
581 """ Set the style for the syntax highlighter.
581 """ Set the style for the syntax highlighter.
582 """
582 """
583 if self._highlighter is None:
583 if self._highlighter is None:
584 # ignore premature calls
584 # ignore premature calls
585 return
585 return
586 if self.syntax_style:
586 if self.syntax_style:
587 self._highlighter.set_style(self.syntax_style)
587 self._highlighter.set_style(self.syntax_style)
588 else:
588 else:
589 self._highlighter.set_style_sheet(self.style_sheet)
589 self._highlighter.set_style_sheet(self.style_sheet)
590
590
591 #------ Trait default initializers -----------------------------------------
591 #------ Trait default initializers -----------------------------------------
592
592
593 def _banner_default(self):
593 def _banner_default(self):
594 return "IPython QtConsole {version}\n".format(version=version)
594 return "IPython QtConsole {version}\n".format(version=version)
General Comments 0
You need to be logged in to leave comments. Login now