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