##// END OF EJS Templates
Cleaner fix for qt execute_file bug:...
Jonathan March -
Show More
@@ -1,556 +1,551 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 self.log.debug("history: %s", msg.get('content', ''))
182 self.log.debug("history: %s", msg.get('content', ''))
183 content = msg['content']
183 content = msg['content']
184 if 'history' not in content:
184 if 'history' not in content:
185 self.log.error("History request failed: %r"%content)
185 self.log.error("History request failed: %r"%content)
186 if content.get('status', '') == 'aborted' and \
186 if content.get('status', '') == 'aborted' and \
187 not self._retrying_history_request:
187 not self._retrying_history_request:
188 # a *different* action caused this request to be aborted, so
188 # a *different* action caused this request to be aborted, so
189 # we should try again.
189 # we should try again.
190 self.log.error("Retrying aborted history request")
190 self.log.error("Retrying aborted history request")
191 # prevent multiple retries of aborted requests:
191 # prevent multiple retries of aborted requests:
192 self._retrying_history_request = True
192 self._retrying_history_request = True
193 # wait out the kernel's queue flush, which is currently timed at 0.1s
193 # wait out the kernel's queue flush, which is currently timed at 0.1s
194 time.sleep(0.25)
194 time.sleep(0.25)
195 self.kernel_manager.shell_channel.history(hist_access_type='tail',n=1000)
195 self.kernel_manager.shell_channel.history(hist_access_type='tail',n=1000)
196 else:
196 else:
197 self._retrying_history_request = False
197 self._retrying_history_request = False
198 return
198 return
199 # reset retry flag
199 # reset retry flag
200 self._retrying_history_request = False
200 self._retrying_history_request = False
201 history_items = content['history']
201 history_items = content['history']
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['execution_count']
217 prompt_number = content['execution_count']
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.
259 """
259 """
260 super(IPythonWidget, self)._started_channels()
260 super(IPythonWidget, self)._started_channels()
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 #---------------------------------------------------------------------------
263 #---------------------------------------------------------------------------
264 # 'ConsoleWidget' public interface
264 # 'ConsoleWidget' public interface
265 #---------------------------------------------------------------------------
265 #---------------------------------------------------------------------------
266
266
267 #---------------------------------------------------------------------------
267 #---------------------------------------------------------------------------
268 # 'FrontendWidget' public interface
268 # 'FrontendWidget' public interface
269 #---------------------------------------------------------------------------
269 #---------------------------------------------------------------------------
270
270
271 def execute_file(self, path, hidden=False):
271 def execute_file(self, path, hidden=False):
272 """ Reimplemented to use the 'run' magic.
272 """ Reimplemented to use the 'run' magic.
273 """
273 """
274 # Use forward slashes on Windows to avoid escaping each separator.
274 # Use forward slashes on Windows to avoid escaping each separator.
275 if sys.platform == 'win32':
275 if sys.platform == 'win32':
276 path = os.path.normpath(path).replace('\\', '/')
276 path = os.path.normpath(path).replace('\\', '/')
277
277
278 # Perhaps we should not be using %run directly, but while we
278 # Perhaps we should not be using %run directly, but while we
279 # are, it is necessary to quote or escape filenames containing spaces
279 # are, it is necessary to quote or escape filenames containing spaces
280 # or quotes. As much as possible, we quote: more readable than escape.
280 # or quotes. Note that in this context, because run uses posix
281 if '"' in path:
281 # parsing, we can escape double quotes in a double quoted filename,
282 # but can't escape singe quotes in a single quoted filename.
282 if "'" in path:
283 if "'" in path:
283 # In this case, because %run 'a\'b"c.py' fails, we must escape
284 path = '"%s"' % path.replace('"', '\\"')
284 # all quotes and spaces.
285 elif ' ' in path or '"' in path:
285 for c in '" \'':
286 path = path.replace(c, '\\'+c)
287 else:
288 path = "'%s'" % path
286 path = "'%s'" % path
289 elif ' ' in path or "'" in path:
290 path = '"%s"' % path
291
292 self.execute('%%run %s' % path, hidden=hidden)
287 self.execute('%%run %s' % path, hidden=hidden)
293
288
294 #---------------------------------------------------------------------------
289 #---------------------------------------------------------------------------
295 # 'FrontendWidget' protected interface
290 # 'FrontendWidget' protected interface
296 #---------------------------------------------------------------------------
291 #---------------------------------------------------------------------------
297
292
298 def _complete(self):
293 def _complete(self):
299 """ Reimplemented to support IPython's improved completion machinery.
294 """ Reimplemented to support IPython's improved completion machinery.
300 """
295 """
301 # We let the kernel split the input line, so we *always* send an empty
296 # We let the kernel split the input line, so we *always* send an empty
302 # text field. Readline-based frontends do get a real text field which
297 # text field. Readline-based frontends do get a real text field which
303 # they can use.
298 # they can use.
304 text = ''
299 text = ''
305
300
306 # Send the completion request to the kernel
301 # Send the completion request to the kernel
307 msg_id = self.kernel_manager.shell_channel.complete(
302 msg_id = self.kernel_manager.shell_channel.complete(
308 text, # text
303 text, # text
309 self._get_input_buffer_cursor_line(), # line
304 self._get_input_buffer_cursor_line(), # line
310 self._get_input_buffer_cursor_column(), # cursor_pos
305 self._get_input_buffer_cursor_column(), # cursor_pos
311 self.input_buffer) # block
306 self.input_buffer) # block
312 pos = self._get_cursor().position()
307 pos = self._get_cursor().position()
313 info = self._CompletionRequest(msg_id, pos)
308 info = self._CompletionRequest(msg_id, pos)
314 self._request_info['complete'] = info
309 self._request_info['complete'] = info
315
310
316 def _process_execute_error(self, msg):
311 def _process_execute_error(self, msg):
317 """ Reimplemented for IPython-style traceback formatting.
312 """ Reimplemented for IPython-style traceback formatting.
318 """
313 """
319 content = msg['content']
314 content = msg['content']
320 traceback = '\n'.join(content['traceback']) + '\n'
315 traceback = '\n'.join(content['traceback']) + '\n'
321 if False:
316 if False:
322 # FIXME: For now, tracebacks come as plain text, so we can't use
317 # FIXME: For now, tracebacks come as plain text, so we can't use
323 # the html renderer yet. Once we refactor ultratb to produce
318 # the html renderer yet. Once we refactor ultratb to produce
324 # properly styled tracebacks, this branch should be the default
319 # properly styled tracebacks, this branch should be the default
325 traceback = traceback.replace(' ', '&nbsp;')
320 traceback = traceback.replace(' ', '&nbsp;')
326 traceback = traceback.replace('\n', '<br/>')
321 traceback = traceback.replace('\n', '<br/>')
327
322
328 ename = content['ename']
323 ename = content['ename']
329 ename_styled = '<span class="error">%s</span>' % ename
324 ename_styled = '<span class="error">%s</span>' % ename
330 traceback = traceback.replace(ename, ename_styled)
325 traceback = traceback.replace(ename, ename_styled)
331
326
332 self._append_html(traceback)
327 self._append_html(traceback)
333 else:
328 else:
334 # This is the fallback for now, using plain text with ansi escapes
329 # This is the fallback for now, using plain text with ansi escapes
335 self._append_plain_text(traceback)
330 self._append_plain_text(traceback)
336
331
337 def _process_execute_payload(self, item):
332 def _process_execute_payload(self, item):
338 """ Reimplemented to dispatch payloads to handler methods.
333 """ Reimplemented to dispatch payloads to handler methods.
339 """
334 """
340 handler = self._payload_handlers.get(item['source'])
335 handler = self._payload_handlers.get(item['source'])
341 if handler is None:
336 if handler is None:
342 # We have no handler for this type of payload, simply ignore it
337 # We have no handler for this type of payload, simply ignore it
343 return False
338 return False
344 else:
339 else:
345 handler(item)
340 handler(item)
346 return True
341 return True
347
342
348 def _show_interpreter_prompt(self, number=None):
343 def _show_interpreter_prompt(self, number=None):
349 """ Reimplemented for IPython-style prompts.
344 """ Reimplemented for IPython-style prompts.
350 """
345 """
351 # If a number was not specified, make a prompt number request.
346 # If a number was not specified, make a prompt number request.
352 if number is None:
347 if number is None:
353 msg_id = self.kernel_manager.shell_channel.execute('', silent=True)
348 msg_id = self.kernel_manager.shell_channel.execute('', silent=True)
354 info = self._ExecutionRequest(msg_id, 'prompt')
349 info = self._ExecutionRequest(msg_id, 'prompt')
355 self._request_info['execute'][msg_id] = info
350 self._request_info['execute'][msg_id] = info
356 return
351 return
357
352
358 # Show a new prompt and save information about it so that it can be
353 # Show a new prompt and save information about it so that it can be
359 # updated later if the prompt number turns out to be wrong.
354 # updated later if the prompt number turns out to be wrong.
360 self._prompt_sep = self.input_sep
355 self._prompt_sep = self.input_sep
361 self._show_prompt(self._make_in_prompt(number), html=True)
356 self._show_prompt(self._make_in_prompt(number), html=True)
362 block = self._control.document().lastBlock()
357 block = self._control.document().lastBlock()
363 length = len(self._prompt)
358 length = len(self._prompt)
364 self._previous_prompt_obj = self._PromptBlock(block, length, number)
359 self._previous_prompt_obj = self._PromptBlock(block, length, number)
365
360
366 # Update continuation prompt to reflect (possibly) new prompt length.
361 # Update continuation prompt to reflect (possibly) new prompt length.
367 self._set_continuation_prompt(
362 self._set_continuation_prompt(
368 self._make_continuation_prompt(self._prompt), html=True)
363 self._make_continuation_prompt(self._prompt), html=True)
369
364
370 def _show_interpreter_prompt_for_reply(self, msg):
365 def _show_interpreter_prompt_for_reply(self, msg):
371 """ Reimplemented for IPython-style prompts.
366 """ Reimplemented for IPython-style prompts.
372 """
367 """
373 # Update the old prompt number if necessary.
368 # Update the old prompt number if necessary.
374 content = msg['content']
369 content = msg['content']
375 # abort replies do not have any keys:
370 # abort replies do not have any keys:
376 if content['status'] == 'aborted':
371 if content['status'] == 'aborted':
377 if self._previous_prompt_obj:
372 if self._previous_prompt_obj:
378 previous_prompt_number = self._previous_prompt_obj.number
373 previous_prompt_number = self._previous_prompt_obj.number
379 else:
374 else:
380 previous_prompt_number = 0
375 previous_prompt_number = 0
381 else:
376 else:
382 previous_prompt_number = content['execution_count']
377 previous_prompt_number = content['execution_count']
383 if self._previous_prompt_obj and \
378 if self._previous_prompt_obj and \
384 self._previous_prompt_obj.number != previous_prompt_number:
379 self._previous_prompt_obj.number != previous_prompt_number:
385 block = self._previous_prompt_obj.block
380 block = self._previous_prompt_obj.block
386
381
387 # Make sure the prompt block has not been erased.
382 # Make sure the prompt block has not been erased.
388 if block.isValid() and block.text():
383 if block.isValid() and block.text():
389
384
390 # Remove the old prompt and insert a new prompt.
385 # Remove the old prompt and insert a new prompt.
391 cursor = QtGui.QTextCursor(block)
386 cursor = QtGui.QTextCursor(block)
392 cursor.movePosition(QtGui.QTextCursor.Right,
387 cursor.movePosition(QtGui.QTextCursor.Right,
393 QtGui.QTextCursor.KeepAnchor,
388 QtGui.QTextCursor.KeepAnchor,
394 self._previous_prompt_obj.length)
389 self._previous_prompt_obj.length)
395 prompt = self._make_in_prompt(previous_prompt_number)
390 prompt = self._make_in_prompt(previous_prompt_number)
396 self._prompt = self._insert_html_fetching_plain_text(
391 self._prompt = self._insert_html_fetching_plain_text(
397 cursor, prompt)
392 cursor, prompt)
398
393
399 # When the HTML is inserted, Qt blows away the syntax
394 # When the HTML is inserted, Qt blows away the syntax
400 # highlighting for the line, so we need to rehighlight it.
395 # highlighting for the line, so we need to rehighlight it.
401 self._highlighter.rehighlightBlock(cursor.block())
396 self._highlighter.rehighlightBlock(cursor.block())
402
397
403 self._previous_prompt_obj = None
398 self._previous_prompt_obj = None
404
399
405 # Show a new prompt with the kernel's estimated prompt number.
400 # Show a new prompt with the kernel's estimated prompt number.
406 self._show_interpreter_prompt(previous_prompt_number + 1)
401 self._show_interpreter_prompt(previous_prompt_number + 1)
407
402
408 #---------------------------------------------------------------------------
403 #---------------------------------------------------------------------------
409 # 'IPythonWidget' interface
404 # 'IPythonWidget' interface
410 #---------------------------------------------------------------------------
405 #---------------------------------------------------------------------------
411
406
412 def set_default_style(self, colors='lightbg'):
407 def set_default_style(self, colors='lightbg'):
413 """ Sets the widget style to the class defaults.
408 """ Sets the widget style to the class defaults.
414
409
415 Parameters:
410 Parameters:
416 -----------
411 -----------
417 colors : str, optional (default lightbg)
412 colors : str, optional (default lightbg)
418 Whether to use the default IPython light background or dark
413 Whether to use the default IPython light background or dark
419 background or B&W style.
414 background or B&W style.
420 """
415 """
421 colors = colors.lower()
416 colors = colors.lower()
422 if colors=='lightbg':
417 if colors=='lightbg':
423 self.style_sheet = styles.default_light_style_sheet
418 self.style_sheet = styles.default_light_style_sheet
424 self.syntax_style = styles.default_light_syntax_style
419 self.syntax_style = styles.default_light_syntax_style
425 elif colors=='linux':
420 elif colors=='linux':
426 self.style_sheet = styles.default_dark_style_sheet
421 self.style_sheet = styles.default_dark_style_sheet
427 self.syntax_style = styles.default_dark_syntax_style
422 self.syntax_style = styles.default_dark_syntax_style
428 elif colors=='nocolor':
423 elif colors=='nocolor':
429 self.style_sheet = styles.default_bw_style_sheet
424 self.style_sheet = styles.default_bw_style_sheet
430 self.syntax_style = styles.default_bw_syntax_style
425 self.syntax_style = styles.default_bw_syntax_style
431 else:
426 else:
432 raise KeyError("No such color scheme: %s"%colors)
427 raise KeyError("No such color scheme: %s"%colors)
433
428
434 #---------------------------------------------------------------------------
429 #---------------------------------------------------------------------------
435 # 'IPythonWidget' protected interface
430 # 'IPythonWidget' protected interface
436 #---------------------------------------------------------------------------
431 #---------------------------------------------------------------------------
437
432
438 def _edit(self, filename, line=None):
433 def _edit(self, filename, line=None):
439 """ Opens a Python script for editing.
434 """ Opens a Python script for editing.
440
435
441 Parameters:
436 Parameters:
442 -----------
437 -----------
443 filename : str
438 filename : str
444 A path to a local system file.
439 A path to a local system file.
445
440
446 line : int, optional
441 line : int, optional
447 A line of interest in the file.
442 A line of interest in the file.
448 """
443 """
449 if self.custom_edit:
444 if self.custom_edit:
450 self.custom_edit_requested.emit(filename, line)
445 self.custom_edit_requested.emit(filename, line)
451 elif not self.editor:
446 elif not self.editor:
452 self._append_plain_text('No default editor available.\n'
447 self._append_plain_text('No default editor available.\n'
453 'Specify a GUI text editor in the `IPythonWidget.editor` '
448 'Specify a GUI text editor in the `IPythonWidget.editor` '
454 'configurable to enable the %edit magic')
449 'configurable to enable the %edit magic')
455 else:
450 else:
456 try:
451 try:
457 filename = '"%s"' % filename
452 filename = '"%s"' % filename
458 if line and self.editor_line:
453 if line and self.editor_line:
459 command = self.editor_line.format(filename=filename,
454 command = self.editor_line.format(filename=filename,
460 line=line)
455 line=line)
461 else:
456 else:
462 try:
457 try:
463 command = self.editor.format()
458 command = self.editor.format()
464 except KeyError:
459 except KeyError:
465 command = self.editor.format(filename=filename)
460 command = self.editor.format(filename=filename)
466 else:
461 else:
467 command += ' ' + filename
462 command += ' ' + filename
468 except KeyError:
463 except KeyError:
469 self._append_plain_text('Invalid editor command.\n')
464 self._append_plain_text('Invalid editor command.\n')
470 else:
465 else:
471 try:
466 try:
472 Popen(command, shell=True)
467 Popen(command, shell=True)
473 except OSError:
468 except OSError:
474 msg = 'Opening editor with command "%s" failed.\n'
469 msg = 'Opening editor with command "%s" failed.\n'
475 self._append_plain_text(msg % command)
470 self._append_plain_text(msg % command)
476
471
477 def _make_in_prompt(self, number):
472 def _make_in_prompt(self, number):
478 """ Given a prompt number, returns an HTML In prompt.
473 """ Given a prompt number, returns an HTML In prompt.
479 """
474 """
480 try:
475 try:
481 body = self.in_prompt % number
476 body = self.in_prompt % number
482 except TypeError:
477 except TypeError:
483 # allow in_prompt to leave out number, e.g. '>>> '
478 # allow in_prompt to leave out number, e.g. '>>> '
484 body = self.in_prompt
479 body = self.in_prompt
485 return '<span class="in-prompt">%s</span>' % body
480 return '<span class="in-prompt">%s</span>' % body
486
481
487 def _make_continuation_prompt(self, prompt):
482 def _make_continuation_prompt(self, prompt):
488 """ Given a plain text version of an In prompt, returns an HTML
483 """ Given a plain text version of an In prompt, returns an HTML
489 continuation prompt.
484 continuation prompt.
490 """
485 """
491 end_chars = '...: '
486 end_chars = '...: '
492 space_count = len(prompt.lstrip('\n')) - len(end_chars)
487 space_count = len(prompt.lstrip('\n')) - len(end_chars)
493 body = '&nbsp;' * space_count + end_chars
488 body = '&nbsp;' * space_count + end_chars
494 return '<span class="in-prompt">%s</span>' % body
489 return '<span class="in-prompt">%s</span>' % body
495
490
496 def _make_out_prompt(self, number):
491 def _make_out_prompt(self, number):
497 """ Given a prompt number, returns an HTML Out prompt.
492 """ Given a prompt number, returns an HTML Out prompt.
498 """
493 """
499 body = self.out_prompt % number
494 body = self.out_prompt % number
500 return '<span class="out-prompt">%s</span>' % body
495 return '<span class="out-prompt">%s</span>' % body
501
496
502 #------ Payload handlers --------------------------------------------------
497 #------ Payload handlers --------------------------------------------------
503
498
504 # Payload handlers with a generic interface: each takes the opaque payload
499 # Payload handlers with a generic interface: each takes the opaque payload
505 # dict, unpacks it and calls the underlying functions with the necessary
500 # dict, unpacks it and calls the underlying functions with the necessary
506 # arguments.
501 # arguments.
507
502
508 def _handle_payload_edit(self, item):
503 def _handle_payload_edit(self, item):
509 self._edit(item['filename'], item['line_number'])
504 self._edit(item['filename'], item['line_number'])
510
505
511 def _handle_payload_exit(self, item):
506 def _handle_payload_exit(self, item):
512 self._keep_kernel_on_exit = item['keepkernel']
507 self._keep_kernel_on_exit = item['keepkernel']
513 self.exit_requested.emit(self)
508 self.exit_requested.emit(self)
514
509
515 def _handle_payload_next_input(self, item):
510 def _handle_payload_next_input(self, item):
516 self.input_buffer = dedent(item['text'].rstrip())
511 self.input_buffer = dedent(item['text'].rstrip())
517
512
518 def _handle_payload_page(self, item):
513 def _handle_payload_page(self, item):
519 # Since the plain text widget supports only a very small subset of HTML
514 # Since the plain text widget supports only a very small subset of HTML
520 # and we have no control over the HTML source, we only page HTML
515 # and we have no control over the HTML source, we only page HTML
521 # payloads in the rich text widget.
516 # payloads in the rich text widget.
522 if item['html'] and self.kind == 'rich':
517 if item['html'] and self.kind == 'rich':
523 self._page(item['html'], html=True)
518 self._page(item['html'], html=True)
524 else:
519 else:
525 self._page(item['text'], html=False)
520 self._page(item['text'], html=False)
526
521
527 #------ Trait change handlers --------------------------------------------
522 #------ Trait change handlers --------------------------------------------
528
523
529 def _style_sheet_changed(self):
524 def _style_sheet_changed(self):
530 """ Set the style sheets of the underlying widgets.
525 """ Set the style sheets of the underlying widgets.
531 """
526 """
532 self.setStyleSheet(self.style_sheet)
527 self.setStyleSheet(self.style_sheet)
533 self._control.document().setDefaultStyleSheet(self.style_sheet)
528 self._control.document().setDefaultStyleSheet(self.style_sheet)
534 if self._page_control:
529 if self._page_control:
535 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
530 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
536
531
537 bg_color = self._control.palette().window().color()
532 bg_color = self._control.palette().window().color()
538 self._ansi_processor.set_background_color(bg_color)
533 self._ansi_processor.set_background_color(bg_color)
539
534
540
535
541 def _syntax_style_changed(self):
536 def _syntax_style_changed(self):
542 """ Set the style for the syntax highlighter.
537 """ Set the style for the syntax highlighter.
543 """
538 """
544 if self._highlighter is None:
539 if self._highlighter is None:
545 # ignore premature calls
540 # ignore premature calls
546 return
541 return
547 if self.syntax_style:
542 if self.syntax_style:
548 self._highlighter.set_style(self.syntax_style)
543 self._highlighter.set_style(self.syntax_style)
549 else:
544 else:
550 self._highlighter.set_style_sheet(self.style_sheet)
545 self._highlighter.set_style_sheet(self.style_sheet)
551
546
552 #------ Trait default initializers -----------------------------------------
547 #------ Trait default initializers -----------------------------------------
553
548
554 def _banner_default(self):
549 def _banner_default(self):
555 from IPython.core.usage import default_gui_banner
550 from IPython.core.usage import default_gui_banner
556 return default_gui_banner
551 return default_gui_banner
General Comments 0
You need to be logged in to leave comments. Login now