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