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