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