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