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