##// END OF EJS Templates
Merge pull request #762 from jdmarch/jdmarch-755-execute-file-hack...
Evan Patterson -
r4775:af8cee08 merge
parent child Browse files
Show More
@@ -1,513 +1,525
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 or quotes.
258 # Escaping quotes in filename in %run seems tricky and inconsistent,
259 # so not trying it at present.
260 if '"' in path:
261 if "'" in path:
262 raise ValueError("Can't run filename containing both single "
263 "and double quotes: %s" % path)
264 path = "'%s'" % path
265 elif ' ' in path or "'" in path:
266 path = '"%s"' % path
267
256 self.execute('%%run %s' % path, hidden=hidden)
268 self.execute('%%run %s' % path, hidden=hidden)
257
269
258 #---------------------------------------------------------------------------
270 #---------------------------------------------------------------------------
259 # 'FrontendWidget' protected interface
271 # 'FrontendWidget' protected interface
260 #---------------------------------------------------------------------------
272 #---------------------------------------------------------------------------
261
273
262 def _complete(self):
274 def _complete(self):
263 """ Reimplemented to support IPython's improved completion machinery.
275 """ Reimplemented to support IPython's improved completion machinery.
264 """
276 """
265 # We let the kernel split the input line, so we *always* send an empty
277 # 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
278 # text field. Readline-based frontends do get a real text field which
267 # they can use.
279 # they can use.
268 text = ''
280 text = ''
269
281
270 # Send the completion request to the kernel
282 # Send the completion request to the kernel
271 msg_id = self.kernel_manager.shell_channel.complete(
283 msg_id = self.kernel_manager.shell_channel.complete(
272 text, # text
284 text, # text
273 self._get_input_buffer_cursor_line(), # line
285 self._get_input_buffer_cursor_line(), # line
274 self._get_input_buffer_cursor_column(), # cursor_pos
286 self._get_input_buffer_cursor_column(), # cursor_pos
275 self.input_buffer) # block
287 self.input_buffer) # block
276 pos = self._get_cursor().position()
288 pos = self._get_cursor().position()
277 info = self._CompletionRequest(msg_id, pos)
289 info = self._CompletionRequest(msg_id, pos)
278 self._request_info['complete'] = info
290 self._request_info['complete'] = info
279
291
280 def _process_execute_error(self, msg):
292 def _process_execute_error(self, msg):
281 """ Reimplemented for IPython-style traceback formatting.
293 """ Reimplemented for IPython-style traceback formatting.
282 """
294 """
283 content = msg['content']
295 content = msg['content']
284 traceback = '\n'.join(content['traceback']) + '\n'
296 traceback = '\n'.join(content['traceback']) + '\n'
285 if False:
297 if False:
286 # FIXME: For now, tracebacks come as plain text, so we can't use
298 # FIXME: For now, tracebacks come as plain text, so we can't use
287 # the html renderer yet. Once we refactor ultratb to produce
299 # the html renderer yet. Once we refactor ultratb to produce
288 # properly styled tracebacks, this branch should be the default
300 # properly styled tracebacks, this branch should be the default
289 traceback = traceback.replace(' ', '&nbsp;')
301 traceback = traceback.replace(' ', '&nbsp;')
290 traceback = traceback.replace('\n', '<br/>')
302 traceback = traceback.replace('\n', '<br/>')
291
303
292 ename = content['ename']
304 ename = content['ename']
293 ename_styled = '<span class="error">%s</span>' % ename
305 ename_styled = '<span class="error">%s</span>' % ename
294 traceback = traceback.replace(ename, ename_styled)
306 traceback = traceback.replace(ename, ename_styled)
295
307
296 self._append_html(traceback)
308 self._append_html(traceback)
297 else:
309 else:
298 # This is the fallback for now, using plain text with ansi escapes
310 # This is the fallback for now, using plain text with ansi escapes
299 self._append_plain_text(traceback)
311 self._append_plain_text(traceback)
300
312
301 def _process_execute_payload(self, item):
313 def _process_execute_payload(self, item):
302 """ Reimplemented to dispatch payloads to handler methods.
314 """ Reimplemented to dispatch payloads to handler methods.
303 """
315 """
304 handler = self._payload_handlers.get(item['source'])
316 handler = self._payload_handlers.get(item['source'])
305 if handler is None:
317 if handler is None:
306 # We have no handler for this type of payload, simply ignore it
318 # We have no handler for this type of payload, simply ignore it
307 return False
319 return False
308 else:
320 else:
309 handler(item)
321 handler(item)
310 return True
322 return True
311
323
312 def _show_interpreter_prompt(self, number=None):
324 def _show_interpreter_prompt(self, number=None):
313 """ Reimplemented for IPython-style prompts.
325 """ Reimplemented for IPython-style prompts.
314 """
326 """
315 # If a number was not specified, make a prompt number request.
327 # If a number was not specified, make a prompt number request.
316 if number is None:
328 if number is None:
317 msg_id = self.kernel_manager.shell_channel.execute('', silent=True)
329 msg_id = self.kernel_manager.shell_channel.execute('', silent=True)
318 info = self._ExecutionRequest(msg_id, 'prompt')
330 info = self._ExecutionRequest(msg_id, 'prompt')
319 self._request_info['execute'] = info
331 self._request_info['execute'] = info
320 return
332 return
321
333
322 # Show a new prompt and save information about it so that it can be
334 # 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.
335 # updated later if the prompt number turns out to be wrong.
324 self._prompt_sep = self.input_sep
336 self._prompt_sep = self.input_sep
325 self._show_prompt(self._make_in_prompt(number), html=True)
337 self._show_prompt(self._make_in_prompt(number), html=True)
326 block = self._control.document().lastBlock()
338 block = self._control.document().lastBlock()
327 length = len(self._prompt)
339 length = len(self._prompt)
328 self._previous_prompt_obj = self._PromptBlock(block, length, number)
340 self._previous_prompt_obj = self._PromptBlock(block, length, number)
329
341
330 # Update continuation prompt to reflect (possibly) new prompt length.
342 # Update continuation prompt to reflect (possibly) new prompt length.
331 self._set_continuation_prompt(
343 self._set_continuation_prompt(
332 self._make_continuation_prompt(self._prompt), html=True)
344 self._make_continuation_prompt(self._prompt), html=True)
333
345
334 def _show_interpreter_prompt_for_reply(self, msg):
346 def _show_interpreter_prompt_for_reply(self, msg):
335 """ Reimplemented for IPython-style prompts.
347 """ Reimplemented for IPython-style prompts.
336 """
348 """
337 # Update the old prompt number if necessary.
349 # Update the old prompt number if necessary.
338 content = msg['content']
350 content = msg['content']
339 previous_prompt_number = content['execution_count']
351 previous_prompt_number = content['execution_count']
340 if self._previous_prompt_obj and \
352 if self._previous_prompt_obj and \
341 self._previous_prompt_obj.number != previous_prompt_number:
353 self._previous_prompt_obj.number != previous_prompt_number:
342 block = self._previous_prompt_obj.block
354 block = self._previous_prompt_obj.block
343
355
344 # Make sure the prompt block has not been erased.
356 # Make sure the prompt block has not been erased.
345 if block.isValid() and block.text():
357 if block.isValid() and block.text():
346
358
347 # Remove the old prompt and insert a new prompt.
359 # Remove the old prompt and insert a new prompt.
348 cursor = QtGui.QTextCursor(block)
360 cursor = QtGui.QTextCursor(block)
349 cursor.movePosition(QtGui.QTextCursor.Right,
361 cursor.movePosition(QtGui.QTextCursor.Right,
350 QtGui.QTextCursor.KeepAnchor,
362 QtGui.QTextCursor.KeepAnchor,
351 self._previous_prompt_obj.length)
363 self._previous_prompt_obj.length)
352 prompt = self._make_in_prompt(previous_prompt_number)
364 prompt = self._make_in_prompt(previous_prompt_number)
353 self._prompt = self._insert_html_fetching_plain_text(
365 self._prompt = self._insert_html_fetching_plain_text(
354 cursor, prompt)
366 cursor, prompt)
355
367
356 # When the HTML is inserted, Qt blows away the syntax
368 # When the HTML is inserted, Qt blows away the syntax
357 # highlighting for the line, so we need to rehighlight it.
369 # highlighting for the line, so we need to rehighlight it.
358 self._highlighter.rehighlightBlock(cursor.block())
370 self._highlighter.rehighlightBlock(cursor.block())
359
371
360 self._previous_prompt_obj = None
372 self._previous_prompt_obj = None
361
373
362 # Show a new prompt with the kernel's estimated prompt number.
374 # Show a new prompt with the kernel's estimated prompt number.
363 self._show_interpreter_prompt(previous_prompt_number + 1)
375 self._show_interpreter_prompt(previous_prompt_number + 1)
364
376
365 #---------------------------------------------------------------------------
377 #---------------------------------------------------------------------------
366 # 'IPythonWidget' interface
378 # 'IPythonWidget' interface
367 #---------------------------------------------------------------------------
379 #---------------------------------------------------------------------------
368
380
369 def set_default_style(self, colors='lightbg'):
381 def set_default_style(self, colors='lightbg'):
370 """ Sets the widget style to the class defaults.
382 """ Sets the widget style to the class defaults.
371
383
372 Parameters:
384 Parameters:
373 -----------
385 -----------
374 colors : str, optional (default lightbg)
386 colors : str, optional (default lightbg)
375 Whether to use the default IPython light background or dark
387 Whether to use the default IPython light background or dark
376 background or B&W style.
388 background or B&W style.
377 """
389 """
378 colors = colors.lower()
390 colors = colors.lower()
379 if colors=='lightbg':
391 if colors=='lightbg':
380 self.style_sheet = styles.default_light_style_sheet
392 self.style_sheet = styles.default_light_style_sheet
381 self.syntax_style = styles.default_light_syntax_style
393 self.syntax_style = styles.default_light_syntax_style
382 elif colors=='linux':
394 elif colors=='linux':
383 self.style_sheet = styles.default_dark_style_sheet
395 self.style_sheet = styles.default_dark_style_sheet
384 self.syntax_style = styles.default_dark_syntax_style
396 self.syntax_style = styles.default_dark_syntax_style
385 elif colors=='nocolor':
397 elif colors=='nocolor':
386 self.style_sheet = styles.default_bw_style_sheet
398 self.style_sheet = styles.default_bw_style_sheet
387 self.syntax_style = styles.default_bw_syntax_style
399 self.syntax_style = styles.default_bw_syntax_style
388 else:
400 else:
389 raise KeyError("No such color scheme: %s"%colors)
401 raise KeyError("No such color scheme: %s"%colors)
390
402
391 #---------------------------------------------------------------------------
403 #---------------------------------------------------------------------------
392 # 'IPythonWidget' protected interface
404 # 'IPythonWidget' protected interface
393 #---------------------------------------------------------------------------
405 #---------------------------------------------------------------------------
394
406
395 def _edit(self, filename, line=None):
407 def _edit(self, filename, line=None):
396 """ Opens a Python script for editing.
408 """ Opens a Python script for editing.
397
409
398 Parameters:
410 Parameters:
399 -----------
411 -----------
400 filename : str
412 filename : str
401 A path to a local system file.
413 A path to a local system file.
402
414
403 line : int, optional
415 line : int, optional
404 A line of interest in the file.
416 A line of interest in the file.
405 """
417 """
406 if self.custom_edit:
418 if self.custom_edit:
407 self.custom_edit_requested.emit(filename, line)
419 self.custom_edit_requested.emit(filename, line)
408 elif not self.editor:
420 elif not self.editor:
409 self._append_plain_text('No default editor available.\n'
421 self._append_plain_text('No default editor available.\n'
410 'Specify a GUI text editor in the `IPythonWidget.editor` '
422 'Specify a GUI text editor in the `IPythonWidget.editor` '
411 'configurable to enable the %edit magic')
423 'configurable to enable the %edit magic')
412 else:
424 else:
413 try:
425 try:
414 filename = '"%s"' % filename
426 filename = '"%s"' % filename
415 if line and self.editor_line:
427 if line and self.editor_line:
416 command = self.editor_line.format(filename=filename,
428 command = self.editor_line.format(filename=filename,
417 line=line)
429 line=line)
418 else:
430 else:
419 try:
431 try:
420 command = self.editor.format()
432 command = self.editor.format()
421 except KeyError:
433 except KeyError:
422 command = self.editor.format(filename=filename)
434 command = self.editor.format(filename=filename)
423 else:
435 else:
424 command += ' ' + filename
436 command += ' ' + filename
425 except KeyError:
437 except KeyError:
426 self._append_plain_text('Invalid editor command.\n')
438 self._append_plain_text('Invalid editor command.\n')
427 else:
439 else:
428 try:
440 try:
429 Popen(command, shell=True)
441 Popen(command, shell=True)
430 except OSError:
442 except OSError:
431 msg = 'Opening editor with command "%s" failed.\n'
443 msg = 'Opening editor with command "%s" failed.\n'
432 self._append_plain_text(msg % command)
444 self._append_plain_text(msg % command)
433
445
434 def _make_in_prompt(self, number):
446 def _make_in_prompt(self, number):
435 """ Given a prompt number, returns an HTML In prompt.
447 """ Given a prompt number, returns an HTML In prompt.
436 """
448 """
437 try:
449 try:
438 body = self.in_prompt % number
450 body = self.in_prompt % number
439 except TypeError:
451 except TypeError:
440 # allow in_prompt to leave out number, e.g. '>>> '
452 # allow in_prompt to leave out number, e.g. '>>> '
441 body = self.in_prompt
453 body = self.in_prompt
442 return '<span class="in-prompt">%s</span>' % body
454 return '<span class="in-prompt">%s</span>' % body
443
455
444 def _make_continuation_prompt(self, prompt):
456 def _make_continuation_prompt(self, prompt):
445 """ Given a plain text version of an In prompt, returns an HTML
457 """ Given a plain text version of an In prompt, returns an HTML
446 continuation prompt.
458 continuation prompt.
447 """
459 """
448 end_chars = '...: '
460 end_chars = '...: '
449 space_count = len(prompt.lstrip('\n')) - len(end_chars)
461 space_count = len(prompt.lstrip('\n')) - len(end_chars)
450 body = '&nbsp;' * space_count + end_chars
462 body = '&nbsp;' * space_count + end_chars
451 return '<span class="in-prompt">%s</span>' % body
463 return '<span class="in-prompt">%s</span>' % body
452
464
453 def _make_out_prompt(self, number):
465 def _make_out_prompt(self, number):
454 """ Given a prompt number, returns an HTML Out prompt.
466 """ Given a prompt number, returns an HTML Out prompt.
455 """
467 """
456 body = self.out_prompt % number
468 body = self.out_prompt % number
457 return '<span class="out-prompt">%s</span>' % body
469 return '<span class="out-prompt">%s</span>' % body
458
470
459 #------ Payload handlers --------------------------------------------------
471 #------ Payload handlers --------------------------------------------------
460
472
461 # Payload handlers with a generic interface: each takes the opaque payload
473 # Payload handlers with a generic interface: each takes the opaque payload
462 # dict, unpacks it and calls the underlying functions with the necessary
474 # dict, unpacks it and calls the underlying functions with the necessary
463 # arguments.
475 # arguments.
464
476
465 def _handle_payload_edit(self, item):
477 def _handle_payload_edit(self, item):
466 self._edit(item['filename'], item['line_number'])
478 self._edit(item['filename'], item['line_number'])
467
479
468 def _handle_payload_exit(self, item):
480 def _handle_payload_exit(self, item):
469 self._keep_kernel_on_exit = item['keepkernel']
481 self._keep_kernel_on_exit = item['keepkernel']
470 self.exit_requested.emit()
482 self.exit_requested.emit()
471
483
472 def _handle_payload_next_input(self, item):
484 def _handle_payload_next_input(self, item):
473 self.input_buffer = dedent(item['text'].rstrip())
485 self.input_buffer = dedent(item['text'].rstrip())
474
486
475 def _handle_payload_page(self, item):
487 def _handle_payload_page(self, item):
476 # Since the plain text widget supports only a very small subset of HTML
488 # 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
489 # and we have no control over the HTML source, we only page HTML
478 # payloads in the rich text widget.
490 # payloads in the rich text widget.
479 if item['html'] and self.kind == 'rich':
491 if item['html'] and self.kind == 'rich':
480 self._page(item['html'], html=True)
492 self._page(item['html'], html=True)
481 else:
493 else:
482 self._page(item['text'], html=False)
494 self._page(item['text'], html=False)
483
495
484 #------ Trait change handlers --------------------------------------------
496 #------ Trait change handlers --------------------------------------------
485
497
486 def _style_sheet_changed(self):
498 def _style_sheet_changed(self):
487 """ Set the style sheets of the underlying widgets.
499 """ Set the style sheets of the underlying widgets.
488 """
500 """
489 self.setStyleSheet(self.style_sheet)
501 self.setStyleSheet(self.style_sheet)
490 self._control.document().setDefaultStyleSheet(self.style_sheet)
502 self._control.document().setDefaultStyleSheet(self.style_sheet)
491 if self._page_control:
503 if self._page_control:
492 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
504 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
493
505
494 bg_color = self._control.palette().window().color()
506 bg_color = self._control.palette().window().color()
495 self._ansi_processor.set_background_color(bg_color)
507 self._ansi_processor.set_background_color(bg_color)
496
508
497
509
498 def _syntax_style_changed(self):
510 def _syntax_style_changed(self):
499 """ Set the style for the syntax highlighter.
511 """ Set the style for the syntax highlighter.
500 """
512 """
501 if self._highlighter is None:
513 if self._highlighter is None:
502 # ignore premature calls
514 # ignore premature calls
503 return
515 return
504 if self.syntax_style:
516 if self.syntax_style:
505 self._highlighter.set_style(self.syntax_style)
517 self._highlighter.set_style(self.syntax_style)
506 else:
518 else:
507 self._highlighter.set_style_sheet(self.style_sheet)
519 self._highlighter.set_style_sheet(self.style_sheet)
508
520
509 #------ Trait default initializers -----------------------------------------
521 #------ Trait default initializers -----------------------------------------
510
522
511 def _banner_default(self):
523 def _banner_default(self):
512 from IPython.core.usage import default_gui_banner
524 from IPython.core.usage import default_gui_banner
513 return default_gui_banner
525 return default_gui_banner
General Comments 0
You need to be logged in to leave comments. Login now