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