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