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