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