##// END OF EJS Templates
Don't assume history request succeeded...
MinRK -
Show More
@@ -1,513 +1,517 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 content = msg['content']
178 if 'history' not in content:
179 self.log.error("History request failed: %r"%content)
180 return
181 history_items = content['history']
178 items = [ line.rstrip() for _, _, line in history_items ]
182 items = [ line.rstrip() for _, _, line in history_items ]
179 self._set_history(items)
183 self._set_history(items)
180
184
181 def _handle_pyout(self, msg):
185 def _handle_pyout(self, msg):
182 """ Reimplemented for IPython-style "display hook".
186 """ Reimplemented for IPython-style "display hook".
183 """
187 """
184 if not self._hidden and self._is_from_this_session(msg):
188 if not self._hidden and self._is_from_this_session(msg):
185 content = msg['content']
189 content = msg['content']
186 prompt_number = content['execution_count']
190 prompt_number = content['execution_count']
187 data = content['data']
191 data = content['data']
188 if data.has_key('text/html'):
192 if data.has_key('text/html'):
189 self._append_plain_text(self.output_sep, True)
193 self._append_plain_text(self.output_sep, True)
190 self._append_html(self._make_out_prompt(prompt_number), True)
194 self._append_html(self._make_out_prompt(prompt_number), True)
191 html = data['text/html']
195 html = data['text/html']
192 self._append_plain_text('\n', True)
196 self._append_plain_text('\n', True)
193 self._append_html(html + self.output_sep2, True)
197 self._append_html(html + self.output_sep2, True)
194 elif data.has_key('text/plain'):
198 elif data.has_key('text/plain'):
195 self._append_plain_text(self.output_sep, True)
199 self._append_plain_text(self.output_sep, True)
196 self._append_html(self._make_out_prompt(prompt_number), True)
200 self._append_html(self._make_out_prompt(prompt_number), True)
197 text = data['text/plain']
201 text = data['text/plain']
198 # If the repr is multiline, make sure we start on a new line,
202 # If the repr is multiline, make sure we start on a new line,
199 # so that its lines are aligned.
203 # so that its lines are aligned.
200 if "\n" in text and not self.output_sep.endswith("\n"):
204 if "\n" in text and not self.output_sep.endswith("\n"):
201 self._append_plain_text('\n', True)
205 self._append_plain_text('\n', True)
202 self._append_plain_text(text + self.output_sep2, True)
206 self._append_plain_text(text + self.output_sep2, True)
203
207
204 def _handle_display_data(self, msg):
208 def _handle_display_data(self, msg):
205 """ The base handler for the ``display_data`` message.
209 """ The base handler for the ``display_data`` message.
206 """
210 """
207 # For now, we don't display data from other frontends, but we
211 # For now, we don't display data from other frontends, but we
208 # eventually will as this allows all frontends to monitor the display
212 # 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.
213 # 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):
214 if not self._hidden and self._is_from_this_session(msg):
211 source = msg['content']['source']
215 source = msg['content']['source']
212 data = msg['content']['data']
216 data = msg['content']['data']
213 metadata = msg['content']['metadata']
217 metadata = msg['content']['metadata']
214 # In the regular IPythonWidget, we simply print the plain text
218 # In the regular IPythonWidget, we simply print the plain text
215 # representation.
219 # representation.
216 if data.has_key('text/html'):
220 if data.has_key('text/html'):
217 html = data['text/html']
221 html = data['text/html']
218 self._append_html(html, True)
222 self._append_html(html, True)
219 elif data.has_key('text/plain'):
223 elif data.has_key('text/plain'):
220 text = data['text/plain']
224 text = data['text/plain']
221 self._append_plain_text(text, True)
225 self._append_plain_text(text, True)
222 # This newline seems to be needed for text and html output.
226 # This newline seems to be needed for text and html output.
223 self._append_plain_text(u'\n', True)
227 self._append_plain_text(u'\n', True)
224
228
225 def _started_channels(self):
229 def _started_channels(self):
226 """ Reimplemented to make a history request.
230 """ Reimplemented to make a history request.
227 """
231 """
228 super(IPythonWidget, self)._started_channels()
232 super(IPythonWidget, self)._started_channels()
229 self.kernel_manager.shell_channel.history(hist_access_type='tail',
233 self.kernel_manager.shell_channel.history(hist_access_type='tail',
230 n=1000)
234 n=1000)
231 #---------------------------------------------------------------------------
235 #---------------------------------------------------------------------------
232 # 'ConsoleWidget' public interface
236 # 'ConsoleWidget' public interface
233 #---------------------------------------------------------------------------
237 #---------------------------------------------------------------------------
234
238
235 def copy(self):
239 def copy(self):
236 """ Copy the currently selected text to the clipboard, removing prompts
240 """ Copy the currently selected text to the clipboard, removing prompts
237 if possible.
241 if possible.
238 """
242 """
239 text = self._control.textCursor().selection().toPlainText()
243 text = self._control.textCursor().selection().toPlainText()
240 if text:
244 if text:
241 lines = map(transform_ipy_prompt, text.splitlines())
245 lines = map(transform_ipy_prompt, text.splitlines())
242 text = '\n'.join(lines)
246 text = '\n'.join(lines)
243 QtGui.QApplication.clipboard().setText(text)
247 QtGui.QApplication.clipboard().setText(text)
244
248
245 #---------------------------------------------------------------------------
249 #---------------------------------------------------------------------------
246 # 'FrontendWidget' public interface
250 # 'FrontendWidget' public interface
247 #---------------------------------------------------------------------------
251 #---------------------------------------------------------------------------
248
252
249 def execute_file(self, path, hidden=False):
253 def execute_file(self, path, hidden=False):
250 """ Reimplemented to use the 'run' magic.
254 """ Reimplemented to use the 'run' magic.
251 """
255 """
252 # Use forward slashes on Windows to avoid escaping each separator.
256 # Use forward slashes on Windows to avoid escaping each separator.
253 if sys.platform == 'win32':
257 if sys.platform == 'win32':
254 path = os.path.normpath(path).replace('\\', '/')
258 path = os.path.normpath(path).replace('\\', '/')
255
259
256 self.execute('%%run %s' % path, hidden=hidden)
260 self.execute('%%run %s' % path, hidden=hidden)
257
261
258 #---------------------------------------------------------------------------
262 #---------------------------------------------------------------------------
259 # 'FrontendWidget' protected interface
263 # 'FrontendWidget' protected interface
260 #---------------------------------------------------------------------------
264 #---------------------------------------------------------------------------
261
265
262 def _complete(self):
266 def _complete(self):
263 """ Reimplemented to support IPython's improved completion machinery.
267 """ Reimplemented to support IPython's improved completion machinery.
264 """
268 """
265 # We let the kernel split the input line, so we *always* send an empty
269 # 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
270 # text field. Readline-based frontends do get a real text field which
267 # they can use.
271 # they can use.
268 text = ''
272 text = ''
269
273
270 # Send the completion request to the kernel
274 # Send the completion request to the kernel
271 msg_id = self.kernel_manager.shell_channel.complete(
275 msg_id = self.kernel_manager.shell_channel.complete(
272 text, # text
276 text, # text
273 self._get_input_buffer_cursor_line(), # line
277 self._get_input_buffer_cursor_line(), # line
274 self._get_input_buffer_cursor_column(), # cursor_pos
278 self._get_input_buffer_cursor_column(), # cursor_pos
275 self.input_buffer) # block
279 self.input_buffer) # block
276 pos = self._get_cursor().position()
280 pos = self._get_cursor().position()
277 info = self._CompletionRequest(msg_id, pos)
281 info = self._CompletionRequest(msg_id, pos)
278 self._request_info['complete'] = info
282 self._request_info['complete'] = info
279
283
280 def _process_execute_error(self, msg):
284 def _process_execute_error(self, msg):
281 """ Reimplemented for IPython-style traceback formatting.
285 """ Reimplemented for IPython-style traceback formatting.
282 """
286 """
283 content = msg['content']
287 content = msg['content']
284 traceback = '\n'.join(content['traceback']) + '\n'
288 traceback = '\n'.join(content['traceback']) + '\n'
285 if False:
289 if False:
286 # FIXME: For now, tracebacks come as plain text, so we can't use
290 # FIXME: For now, tracebacks come as plain text, so we can't use
287 # the html renderer yet. Once we refactor ultratb to produce
291 # the html renderer yet. Once we refactor ultratb to produce
288 # properly styled tracebacks, this branch should be the default
292 # properly styled tracebacks, this branch should be the default
289 traceback = traceback.replace(' ', '&nbsp;')
293 traceback = traceback.replace(' ', '&nbsp;')
290 traceback = traceback.replace('\n', '<br/>')
294 traceback = traceback.replace('\n', '<br/>')
291
295
292 ename = content['ename']
296 ename = content['ename']
293 ename_styled = '<span class="error">%s</span>' % ename
297 ename_styled = '<span class="error">%s</span>' % ename
294 traceback = traceback.replace(ename, ename_styled)
298 traceback = traceback.replace(ename, ename_styled)
295
299
296 self._append_html(traceback)
300 self._append_html(traceback)
297 else:
301 else:
298 # This is the fallback for now, using plain text with ansi escapes
302 # This is the fallback for now, using plain text with ansi escapes
299 self._append_plain_text(traceback)
303 self._append_plain_text(traceback)
300
304
301 def _process_execute_payload(self, item):
305 def _process_execute_payload(self, item):
302 """ Reimplemented to dispatch payloads to handler methods.
306 """ Reimplemented to dispatch payloads to handler methods.
303 """
307 """
304 handler = self._payload_handlers.get(item['source'])
308 handler = self._payload_handlers.get(item['source'])
305 if handler is None:
309 if handler is None:
306 # We have no handler for this type of payload, simply ignore it
310 # We have no handler for this type of payload, simply ignore it
307 return False
311 return False
308 else:
312 else:
309 handler(item)
313 handler(item)
310 return True
314 return True
311
315
312 def _show_interpreter_prompt(self, number=None):
316 def _show_interpreter_prompt(self, number=None):
313 """ Reimplemented for IPython-style prompts.
317 """ Reimplemented for IPython-style prompts.
314 """
318 """
315 # If a number was not specified, make a prompt number request.
319 # If a number was not specified, make a prompt number request.
316 if number is None:
320 if number is None:
317 msg_id = self.kernel_manager.shell_channel.execute('', silent=True)
321 msg_id = self.kernel_manager.shell_channel.execute('', silent=True)
318 info = self._ExecutionRequest(msg_id, 'prompt')
322 info = self._ExecutionRequest(msg_id, 'prompt')
319 self._request_info['execute'] = info
323 self._request_info['execute'] = info
320 return
324 return
321
325
322 # Show a new prompt and save information about it so that it can be
326 # 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.
327 # updated later if the prompt number turns out to be wrong.
324 self._prompt_sep = self.input_sep
328 self._prompt_sep = self.input_sep
325 self._show_prompt(self._make_in_prompt(number), html=True)
329 self._show_prompt(self._make_in_prompt(number), html=True)
326 block = self._control.document().lastBlock()
330 block = self._control.document().lastBlock()
327 length = len(self._prompt)
331 length = len(self._prompt)
328 self._previous_prompt_obj = self._PromptBlock(block, length, number)
332 self._previous_prompt_obj = self._PromptBlock(block, length, number)
329
333
330 # Update continuation prompt to reflect (possibly) new prompt length.
334 # Update continuation prompt to reflect (possibly) new prompt length.
331 self._set_continuation_prompt(
335 self._set_continuation_prompt(
332 self._make_continuation_prompt(self._prompt), html=True)
336 self._make_continuation_prompt(self._prompt), html=True)
333
337
334 def _show_interpreter_prompt_for_reply(self, msg):
338 def _show_interpreter_prompt_for_reply(self, msg):
335 """ Reimplemented for IPython-style prompts.
339 """ Reimplemented for IPython-style prompts.
336 """
340 """
337 # Update the old prompt number if necessary.
341 # Update the old prompt number if necessary.
338 content = msg['content']
342 content = msg['content']
339 previous_prompt_number = content['execution_count']
343 previous_prompt_number = content['execution_count']
340 if self._previous_prompt_obj and \
344 if self._previous_prompt_obj and \
341 self._previous_prompt_obj.number != previous_prompt_number:
345 self._previous_prompt_obj.number != previous_prompt_number:
342 block = self._previous_prompt_obj.block
346 block = self._previous_prompt_obj.block
343
347
344 # Make sure the prompt block has not been erased.
348 # Make sure the prompt block has not been erased.
345 if block.isValid() and block.text():
349 if block.isValid() and block.text():
346
350
347 # Remove the old prompt and insert a new prompt.
351 # Remove the old prompt and insert a new prompt.
348 cursor = QtGui.QTextCursor(block)
352 cursor = QtGui.QTextCursor(block)
349 cursor.movePosition(QtGui.QTextCursor.Right,
353 cursor.movePosition(QtGui.QTextCursor.Right,
350 QtGui.QTextCursor.KeepAnchor,
354 QtGui.QTextCursor.KeepAnchor,
351 self._previous_prompt_obj.length)
355 self._previous_prompt_obj.length)
352 prompt = self._make_in_prompt(previous_prompt_number)
356 prompt = self._make_in_prompt(previous_prompt_number)
353 self._prompt = self._insert_html_fetching_plain_text(
357 self._prompt = self._insert_html_fetching_plain_text(
354 cursor, prompt)
358 cursor, prompt)
355
359
356 # When the HTML is inserted, Qt blows away the syntax
360 # When the HTML is inserted, Qt blows away the syntax
357 # highlighting for the line, so we need to rehighlight it.
361 # highlighting for the line, so we need to rehighlight it.
358 self._highlighter.rehighlightBlock(cursor.block())
362 self._highlighter.rehighlightBlock(cursor.block())
359
363
360 self._previous_prompt_obj = None
364 self._previous_prompt_obj = None
361
365
362 # Show a new prompt with the kernel's estimated prompt number.
366 # Show a new prompt with the kernel's estimated prompt number.
363 self._show_interpreter_prompt(previous_prompt_number + 1)
367 self._show_interpreter_prompt(previous_prompt_number + 1)
364
368
365 #---------------------------------------------------------------------------
369 #---------------------------------------------------------------------------
366 # 'IPythonWidget' interface
370 # 'IPythonWidget' interface
367 #---------------------------------------------------------------------------
371 #---------------------------------------------------------------------------
368
372
369 def set_default_style(self, colors='lightbg'):
373 def set_default_style(self, colors='lightbg'):
370 """ Sets the widget style to the class defaults.
374 """ Sets the widget style to the class defaults.
371
375
372 Parameters:
376 Parameters:
373 -----------
377 -----------
374 colors : str, optional (default lightbg)
378 colors : str, optional (default lightbg)
375 Whether to use the default IPython light background or dark
379 Whether to use the default IPython light background or dark
376 background or B&W style.
380 background or B&W style.
377 """
381 """
378 colors = colors.lower()
382 colors = colors.lower()
379 if colors=='lightbg':
383 if colors=='lightbg':
380 self.style_sheet = styles.default_light_style_sheet
384 self.style_sheet = styles.default_light_style_sheet
381 self.syntax_style = styles.default_light_syntax_style
385 self.syntax_style = styles.default_light_syntax_style
382 elif colors=='linux':
386 elif colors=='linux':
383 self.style_sheet = styles.default_dark_style_sheet
387 self.style_sheet = styles.default_dark_style_sheet
384 self.syntax_style = styles.default_dark_syntax_style
388 self.syntax_style = styles.default_dark_syntax_style
385 elif colors=='nocolor':
389 elif colors=='nocolor':
386 self.style_sheet = styles.default_bw_style_sheet
390 self.style_sheet = styles.default_bw_style_sheet
387 self.syntax_style = styles.default_bw_syntax_style
391 self.syntax_style = styles.default_bw_syntax_style
388 else:
392 else:
389 raise KeyError("No such color scheme: %s"%colors)
393 raise KeyError("No such color scheme: %s"%colors)
390
394
391 #---------------------------------------------------------------------------
395 #---------------------------------------------------------------------------
392 # 'IPythonWidget' protected interface
396 # 'IPythonWidget' protected interface
393 #---------------------------------------------------------------------------
397 #---------------------------------------------------------------------------
394
398
395 def _edit(self, filename, line=None):
399 def _edit(self, filename, line=None):
396 """ Opens a Python script for editing.
400 """ Opens a Python script for editing.
397
401
398 Parameters:
402 Parameters:
399 -----------
403 -----------
400 filename : str
404 filename : str
401 A path to a local system file.
405 A path to a local system file.
402
406
403 line : int, optional
407 line : int, optional
404 A line of interest in the file.
408 A line of interest in the file.
405 """
409 """
406 if self.custom_edit:
410 if self.custom_edit:
407 self.custom_edit_requested.emit(filename, line)
411 self.custom_edit_requested.emit(filename, line)
408 elif not self.editor:
412 elif not self.editor:
409 self._append_plain_text('No default editor available.\n'
413 self._append_plain_text('No default editor available.\n'
410 'Specify a GUI text editor in the `IPythonWidget.editor` '
414 'Specify a GUI text editor in the `IPythonWidget.editor` '
411 'configurable to enable the %edit magic')
415 'configurable to enable the %edit magic')
412 else:
416 else:
413 try:
417 try:
414 filename = '"%s"' % filename
418 filename = '"%s"' % filename
415 if line and self.editor_line:
419 if line and self.editor_line:
416 command = self.editor_line.format(filename=filename,
420 command = self.editor_line.format(filename=filename,
417 line=line)
421 line=line)
418 else:
422 else:
419 try:
423 try:
420 command = self.editor.format()
424 command = self.editor.format()
421 except KeyError:
425 except KeyError:
422 command = self.editor.format(filename=filename)
426 command = self.editor.format(filename=filename)
423 else:
427 else:
424 command += ' ' + filename
428 command += ' ' + filename
425 except KeyError:
429 except KeyError:
426 self._append_plain_text('Invalid editor command.\n')
430 self._append_plain_text('Invalid editor command.\n')
427 else:
431 else:
428 try:
432 try:
429 Popen(command, shell=True)
433 Popen(command, shell=True)
430 except OSError:
434 except OSError:
431 msg = 'Opening editor with command "%s" failed.\n'
435 msg = 'Opening editor with command "%s" failed.\n'
432 self._append_plain_text(msg % command)
436 self._append_plain_text(msg % command)
433
437
434 def _make_in_prompt(self, number):
438 def _make_in_prompt(self, number):
435 """ Given a prompt number, returns an HTML In prompt.
439 """ Given a prompt number, returns an HTML In prompt.
436 """
440 """
437 try:
441 try:
438 body = self.in_prompt % number
442 body = self.in_prompt % number
439 except TypeError:
443 except TypeError:
440 # allow in_prompt to leave out number, e.g. '>>> '
444 # allow in_prompt to leave out number, e.g. '>>> '
441 body = self.in_prompt
445 body = self.in_prompt
442 return '<span class="in-prompt">%s</span>' % body
446 return '<span class="in-prompt">%s</span>' % body
443
447
444 def _make_continuation_prompt(self, prompt):
448 def _make_continuation_prompt(self, prompt):
445 """ Given a plain text version of an In prompt, returns an HTML
449 """ Given a plain text version of an In prompt, returns an HTML
446 continuation prompt.
450 continuation prompt.
447 """
451 """
448 end_chars = '...: '
452 end_chars = '...: '
449 space_count = len(prompt.lstrip('\n')) - len(end_chars)
453 space_count = len(prompt.lstrip('\n')) - len(end_chars)
450 body = '&nbsp;' * space_count + end_chars
454 body = '&nbsp;' * space_count + end_chars
451 return '<span class="in-prompt">%s</span>' % body
455 return '<span class="in-prompt">%s</span>' % body
452
456
453 def _make_out_prompt(self, number):
457 def _make_out_prompt(self, number):
454 """ Given a prompt number, returns an HTML Out prompt.
458 """ Given a prompt number, returns an HTML Out prompt.
455 """
459 """
456 body = self.out_prompt % number
460 body = self.out_prompt % number
457 return '<span class="out-prompt">%s</span>' % body
461 return '<span class="out-prompt">%s</span>' % body
458
462
459 #------ Payload handlers --------------------------------------------------
463 #------ Payload handlers --------------------------------------------------
460
464
461 # Payload handlers with a generic interface: each takes the opaque payload
465 # Payload handlers with a generic interface: each takes the opaque payload
462 # dict, unpacks it and calls the underlying functions with the necessary
466 # dict, unpacks it and calls the underlying functions with the necessary
463 # arguments.
467 # arguments.
464
468
465 def _handle_payload_edit(self, item):
469 def _handle_payload_edit(self, item):
466 self._edit(item['filename'], item['line_number'])
470 self._edit(item['filename'], item['line_number'])
467
471
468 def _handle_payload_exit(self, item):
472 def _handle_payload_exit(self, item):
469 self._keep_kernel_on_exit = item['keepkernel']
473 self._keep_kernel_on_exit = item['keepkernel']
470 self.exit_requested.emit()
474 self.exit_requested.emit()
471
475
472 def _handle_payload_next_input(self, item):
476 def _handle_payload_next_input(self, item):
473 self.input_buffer = dedent(item['text'].rstrip())
477 self.input_buffer = dedent(item['text'].rstrip())
474
478
475 def _handle_payload_page(self, item):
479 def _handle_payload_page(self, item):
476 # Since the plain text widget supports only a very small subset of HTML
480 # 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
481 # and we have no control over the HTML source, we only page HTML
478 # payloads in the rich text widget.
482 # payloads in the rich text widget.
479 if item['html'] and self.kind == 'rich':
483 if item['html'] and self.kind == 'rich':
480 self._page(item['html'], html=True)
484 self._page(item['html'], html=True)
481 else:
485 else:
482 self._page(item['text'], html=False)
486 self._page(item['text'], html=False)
483
487
484 #------ Trait change handlers --------------------------------------------
488 #------ Trait change handlers --------------------------------------------
485
489
486 def _style_sheet_changed(self):
490 def _style_sheet_changed(self):
487 """ Set the style sheets of the underlying widgets.
491 """ Set the style sheets of the underlying widgets.
488 """
492 """
489 self.setStyleSheet(self.style_sheet)
493 self.setStyleSheet(self.style_sheet)
490 self._control.document().setDefaultStyleSheet(self.style_sheet)
494 self._control.document().setDefaultStyleSheet(self.style_sheet)
491 if self._page_control:
495 if self._page_control:
492 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
496 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
493
497
494 bg_color = self._control.palette().window().color()
498 bg_color = self._control.palette().window().color()
495 self._ansi_processor.set_background_color(bg_color)
499 self._ansi_processor.set_background_color(bg_color)
496
500
497
501
498 def _syntax_style_changed(self):
502 def _syntax_style_changed(self):
499 """ Set the style for the syntax highlighter.
503 """ Set the style for the syntax highlighter.
500 """
504 """
501 if self._highlighter is None:
505 if self._highlighter is None:
502 # ignore premature calls
506 # ignore premature calls
503 return
507 return
504 if self.syntax_style:
508 if self.syntax_style:
505 self._highlighter.set_style(self.syntax_style)
509 self._highlighter.set_style(self.syntax_style)
506 else:
510 else:
507 self._highlighter.set_style_sheet(self.style_sheet)
511 self._highlighter.set_style_sheet(self.style_sheet)
508
512
509 #------ Trait default initializers -----------------------------------------
513 #------ Trait default initializers -----------------------------------------
510
514
511 def _banner_default(self):
515 def _banner_default(self):
512 from IPython.core.usage import default_gui_banner
516 from IPython.core.usage import default_gui_banner
513 return default_gui_banner
517 return default_gui_banner
General Comments 0
You need to be logged in to leave comments. Login now