##// END OF EJS Templates
Replaced internal storage object with namedtuple.
epatters -
Show More
@@ -1,375 +1,371 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 TODO: Add support for retrieving the system default editor. Requires code
4 TODO: Add support for retrieving the system default editor. Requires code
5 paths for Windows (use the registry), Mac OS (use LaunchServices), and
5 paths for Windows (use the registry), Mac OS (use LaunchServices), and
6 Linux (use the xdg system).
6 Linux (use the xdg system).
7 """
7 """
8
8
9 # Standard library imports
9 # Standard library imports
10 from collections import namedtuple
10 from subprocess import Popen
11 from subprocess import Popen
11
12
12 # System library imports
13 # System library imports
13 from PyQt4 import QtCore, QtGui
14 from PyQt4 import QtCore, QtGui
14
15
15 # Local imports
16 # Local imports
16 from IPython.core.inputsplitter import IPythonInputSplitter
17 from IPython.core.inputsplitter import IPythonInputSplitter
17 from IPython.core.usage import default_banner
18 from IPython.core.usage import default_banner
18 from frontend_widget import FrontendWidget
19 from frontend_widget import FrontendWidget
19
20
20
21
21 class IPythonPromptBlock(object):
22 """ An internal storage object for IPythonWidget.
23 """
24 def __init__(self, block, length, number):
25 self.block = block
26 self.length = length
27 self.number = number
28
29
30 class IPythonWidget(FrontendWidget):
22 class IPythonWidget(FrontendWidget):
31 """ A FrontendWidget for an IPython kernel.
23 """ A FrontendWidget for an IPython kernel.
32 """
24 """
33
25
34 # Signal emitted when an editor is needed for a file and the editor has been
26 # Signal emitted when an editor is needed for a file and the editor has been
35 # specified as 'custom'. See 'set_editor' for more information.
27 # specified as 'custom'. See 'set_editor' for more information.
36 custom_edit_requested = QtCore.pyqtSignal(object, object)
28 custom_edit_requested = QtCore.pyqtSignal(object, object)
37
29
38 # The default stylesheet: black text on a white background.
30 # The default stylesheet: black text on a white background.
39 default_stylesheet = """
31 default_stylesheet = """
40 .error { color: red; }
32 .error { color: red; }
41 .in-prompt { color: navy; }
33 .in-prompt { color: navy; }
42 .in-prompt-number { font-weight: bold; }
34 .in-prompt-number { font-weight: bold; }
43 .out-prompt { color: darkred; }
35 .out-prompt { color: darkred; }
44 .out-prompt-number { font-weight: bold; }
36 .out-prompt-number { font-weight: bold; }
45 """
37 """
46
38
47 # A dark stylesheet: white text on a black background.
39 # A dark stylesheet: white text on a black background.
48 dark_stylesheet = """
40 dark_stylesheet = """
49 QPlainTextEdit, QTextEdit { background-color: black; color: white }
41 QPlainTextEdit, QTextEdit { background-color: black; color: white }
50 QFrame { border: 1px solid grey; }
42 QFrame { border: 1px solid grey; }
51 .error { color: red; }
43 .error { color: red; }
52 .in-prompt { color: lime; }
44 .in-prompt { color: lime; }
53 .in-prompt-number { color: lime; font-weight: bold; }
45 .in-prompt-number { color: lime; font-weight: bold; }
54 .out-prompt { color: red; }
46 .out-prompt { color: red; }
55 .out-prompt-number { color: red; font-weight: bold; }
47 .out-prompt-number { color: red; font-weight: bold; }
56 """
48 """
57
49
58 # Default prompts.
50 # Default prompts.
59 in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
51 in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
60 out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
52 out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
61
53
54 # A type used internally by IPythonWidget for storing prompt information.
55 _PromptBlock = namedtuple('_PromptBlock',
56 ['block', 'length', 'number'])
57
62 # FrontendWidget protected class variables.
58 # FrontendWidget protected class variables.
63 _input_splitter_class = IPythonInputSplitter
59 _input_splitter_class = IPythonInputSplitter
64
60
65 # IPythonWidget protected class variables.
61 # IPythonWidget protected class variables.
66 _payload_source_edit = 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic'
62 _payload_source_edit = 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic'
67 _payload_source_page = 'IPython.zmq.page.page'
63 _payload_source_page = 'IPython.zmq.page.page'
68
64
69 #---------------------------------------------------------------------------
65 #---------------------------------------------------------------------------
70 # 'object' interface
66 # 'object' interface
71 #---------------------------------------------------------------------------
67 #---------------------------------------------------------------------------
72
68
73 def __init__(self, *args, **kw):
69 def __init__(self, *args, **kw):
74 super(IPythonWidget, self).__init__(*args, **kw)
70 super(IPythonWidget, self).__init__(*args, **kw)
75
71
76 # IPythonWidget protected variables.
72 # IPythonWidget protected variables.
77 self._previous_prompt_obj = None
73 self._previous_prompt_obj = None
78
74
79 # Set a default editor and stylesheet.
75 # Set a default editor and stylesheet.
80 self.set_editor('default')
76 self.set_editor('default')
81 self.reset_styling()
77 self.reset_styling()
82
78
83 #---------------------------------------------------------------------------
79 #---------------------------------------------------------------------------
84 # 'BaseFrontendMixin' abstract interface
80 # 'BaseFrontendMixin' abstract interface
85 #---------------------------------------------------------------------------
81 #---------------------------------------------------------------------------
86
82
87 def _handle_complete_reply(self, rep):
83 def _handle_complete_reply(self, rep):
88 """ Reimplemented to support IPython's improved completion machinery.
84 """ Reimplemented to support IPython's improved completion machinery.
89 """
85 """
90 cursor = self._get_cursor()
86 cursor = self._get_cursor()
91 if rep['parent_header']['msg_id'] == self._complete_id and \
87 if rep['parent_header']['msg_id'] == self._complete_id and \
92 cursor.position() == self._complete_pos:
88 cursor.position() == self._complete_pos:
93 # The completer tells us what text was actually used for the
89 # The completer tells us what text was actually used for the
94 # matching, so we must move that many characters left to apply the
90 # matching, so we must move that many characters left to apply the
95 # completions.
91 # completions.
96 text = rep['content']['matched_text']
92 text = rep['content']['matched_text']
97 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
93 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
98 self._complete_with_items(cursor, rep['content']['matches'])
94 self._complete_with_items(cursor, rep['content']['matches'])
99
95
100 def _handle_history_reply(self, msg):
96 def _handle_history_reply(self, msg):
101 """ Implemented to handle history replies, which are only supported by
97 """ Implemented to handle history replies, which are only supported by
102 the IPython kernel.
98 the IPython kernel.
103 """
99 """
104 history_dict = msg['content']['history']
100 history_dict = msg['content']['history']
105 items = [ history_dict[key] for key in sorted(history_dict.keys()) ]
101 items = [ history_dict[key] for key in sorted(history_dict.keys()) ]
106 self._set_history(items)
102 self._set_history(items)
107
103
108 def _handle_prompt_reply(self, msg):
104 def _handle_prompt_reply(self, msg):
109 """ Implemented to handle prompt number replies, which are only
105 """ Implemented to handle prompt number replies, which are only
110 supported by the IPython kernel.
106 supported by the IPython kernel.
111 """
107 """
112 content = msg['content']
108 content = msg['content']
113 self._show_interpreter_prompt(content['prompt_number'],
109 self._show_interpreter_prompt(content['prompt_number'],
114 content['input_sep'])
110 content['input_sep'])
115
111
116 def _handle_pyout(self, msg):
112 def _handle_pyout(self, msg):
117 """ Reimplemented for IPython-style "display hook".
113 """ Reimplemented for IPython-style "display hook".
118 """
114 """
119 if not self._hidden and self._is_from_this_session(msg):
115 if not self._hidden and self._is_from_this_session(msg):
120 content = msg['content']
116 content = msg['content']
121 prompt_number = content['prompt_number']
117 prompt_number = content['prompt_number']
122 self._append_plain_text(content['output_sep'])
118 self._append_plain_text(content['output_sep'])
123 self._append_html(self._make_out_prompt(prompt_number))
119 self._append_html(self._make_out_prompt(prompt_number))
124 self._append_plain_text(content['data'] + '\n' +
120 self._append_plain_text(content['data'] + '\n' +
125 content['output_sep2'])
121 content['output_sep2'])
126
122
127 def _started_channels(self):
123 def _started_channels(self):
128 """ Reimplemented to make a history request.
124 """ Reimplemented to make a history request.
129 """
125 """
130 super(IPythonWidget, self)._started_channels()
126 super(IPythonWidget, self)._started_channels()
131 # FIXME: Disabled until history requests are properly implemented.
127 # FIXME: Disabled until history requests are properly implemented.
132 #self.kernel_manager.xreq_channel.history(raw=True, output=False)
128 #self.kernel_manager.xreq_channel.history(raw=True, output=False)
133
129
134 #---------------------------------------------------------------------------
130 #---------------------------------------------------------------------------
135 # 'FrontendWidget' interface
131 # 'FrontendWidget' interface
136 #---------------------------------------------------------------------------
132 #---------------------------------------------------------------------------
137
133
138 def execute_file(self, path, hidden=False):
134 def execute_file(self, path, hidden=False):
139 """ Reimplemented to use the 'run' magic.
135 """ Reimplemented to use the 'run' magic.
140 """
136 """
141 self.execute('%%run %s' % path, hidden=hidden)
137 self.execute('%%run %s' % path, hidden=hidden)
142
138
143 #---------------------------------------------------------------------------
139 #---------------------------------------------------------------------------
144 # 'FrontendWidget' protected interface
140 # 'FrontendWidget' protected interface
145 #---------------------------------------------------------------------------
141 #---------------------------------------------------------------------------
146
142
147 def _complete(self):
143 def _complete(self):
148 """ Reimplemented to support IPython's improved completion machinery.
144 """ Reimplemented to support IPython's improved completion machinery.
149 """
145 """
150 # We let the kernel split the input line, so we *always* send an empty
146 # We let the kernel split the input line, so we *always* send an empty
151 # text field. Readline-based frontends do get a real text field which
147 # text field. Readline-based frontends do get a real text field which
152 # they can use.
148 # they can use.
153 text = ''
149 text = ''
154
150
155 # Send the completion request to the kernel
151 # Send the completion request to the kernel
156 self._complete_id = self.kernel_manager.xreq_channel.complete(
152 self._complete_id = self.kernel_manager.xreq_channel.complete(
157 text, # text
153 text, # text
158 self._get_input_buffer_cursor_line(), # line
154 self._get_input_buffer_cursor_line(), # line
159 self._get_input_buffer_cursor_column(), # cursor_pos
155 self._get_input_buffer_cursor_column(), # cursor_pos
160 self.input_buffer) # block
156 self.input_buffer) # block
161 self._complete_pos = self._get_cursor().position()
157 self._complete_pos = self._get_cursor().position()
162
158
163 def _get_banner(self):
159 def _get_banner(self):
164 """ Reimplemented to return IPython's default banner.
160 """ Reimplemented to return IPython's default banner.
165 """
161 """
166 return default_banner + '\n'
162 return default_banner + '\n'
167
163
168 def _process_execute_error(self, msg):
164 def _process_execute_error(self, msg):
169 """ Reimplemented for IPython-style traceback formatting.
165 """ Reimplemented for IPython-style traceback formatting.
170 """
166 """
171 content = msg['content']
167 content = msg['content']
172 traceback = '\n'.join(content['traceback']) + '\n'
168 traceback = '\n'.join(content['traceback']) + '\n'
173 if False:
169 if False:
174 # FIXME: For now, tracebacks come as plain text, so we can't use
170 # FIXME: For now, tracebacks come as plain text, so we can't use
175 # the html renderer yet. Once we refactor ultratb to produce
171 # the html renderer yet. Once we refactor ultratb to produce
176 # properly styled tracebacks, this branch should be the default
172 # properly styled tracebacks, this branch should be the default
177 traceback = traceback.replace(' ', '&nbsp;')
173 traceback = traceback.replace(' ', '&nbsp;')
178 traceback = traceback.replace('\n', '<br/>')
174 traceback = traceback.replace('\n', '<br/>')
179
175
180 ename = content['ename']
176 ename = content['ename']
181 ename_styled = '<span class="error">%s</span>' % ename
177 ename_styled = '<span class="error">%s</span>' % ename
182 traceback = traceback.replace(ename, ename_styled)
178 traceback = traceback.replace(ename, ename_styled)
183
179
184 self._append_html(traceback)
180 self._append_html(traceback)
185 else:
181 else:
186 # This is the fallback for now, using plain text with ansi escapes
182 # This is the fallback for now, using plain text with ansi escapes
187 self._append_plain_text(traceback)
183 self._append_plain_text(traceback)
188
184
189 def _process_execute_payload(self, item):
185 def _process_execute_payload(self, item):
190 """ Reimplemented to handle %edit and paging payloads.
186 """ Reimplemented to handle %edit and paging payloads.
191 """
187 """
192 if item['source'] == self._payload_source_edit:
188 if item['source'] == self._payload_source_edit:
193 self._edit(item['filename'], item['line_number'])
189 self._edit(item['filename'], item['line_number'])
194 return True
190 return True
195 elif item['source'] == self._payload_source_page:
191 elif item['source'] == self._payload_source_page:
196 self._page(item['data'])
192 self._page(item['data'])
197 return True
193 return True
198 else:
194 else:
199 return False
195 return False
200
196
201 def _show_interpreter_prompt(self, number=None, input_sep='\n'):
197 def _show_interpreter_prompt(self, number=None, input_sep='\n'):
202 """ Reimplemented for IPython-style prompts.
198 """ Reimplemented for IPython-style prompts.
203 """
199 """
204 # If a number was not specified, make a prompt number request.
200 # If a number was not specified, make a prompt number request.
205 if number is None:
201 if number is None:
206 self.kernel_manager.xreq_channel.prompt()
202 self.kernel_manager.xreq_channel.prompt()
207 return
203 return
208
204
209 # Show a new prompt and save information about it so that it can be
205 # Show a new prompt and save information about it so that it can be
210 # updated later if the prompt number turns out to be wrong.
206 # updated later if the prompt number turns out to be wrong.
211 self._prompt_sep = input_sep
207 self._prompt_sep = input_sep
212 self._show_prompt(self._make_in_prompt(number), html=True)
208 self._show_prompt(self._make_in_prompt(number), html=True)
213 block = self._control.document().lastBlock()
209 block = self._control.document().lastBlock()
214 length = len(self._prompt)
210 length = len(self._prompt)
215 self._previous_prompt_obj = IPythonPromptBlock(block, length, number)
211 self._previous_prompt_obj = self._PromptBlock(block, length, number)
216
212
217 # Update continuation prompt to reflect (possibly) new prompt length.
213 # Update continuation prompt to reflect (possibly) new prompt length.
218 self._set_continuation_prompt(
214 self._set_continuation_prompt(
219 self._make_continuation_prompt(self._prompt), html=True)
215 self._make_continuation_prompt(self._prompt), html=True)
220
216
221 def _show_interpreter_prompt_for_reply(self, msg):
217 def _show_interpreter_prompt_for_reply(self, msg):
222 """ Reimplemented for IPython-style prompts.
218 """ Reimplemented for IPython-style prompts.
223 """
219 """
224 # Update the old prompt number if necessary.
220 # Update the old prompt number if necessary.
225 content = msg['content']
221 content = msg['content']
226 previous_prompt_number = content['prompt_number']
222 previous_prompt_number = content['prompt_number']
227 if self._previous_prompt_obj and \
223 if self._previous_prompt_obj and \
228 self._previous_prompt_obj.number != previous_prompt_number:
224 self._previous_prompt_obj.number != previous_prompt_number:
229 block = self._previous_prompt_obj.block
225 block = self._previous_prompt_obj.block
230
226
231 # Make sure the prompt block has not been erased.
227 # Make sure the prompt block has not been erased.
232 if block.isValid() and not block.text().isEmpty():
228 if block.isValid() and not block.text().isEmpty():
233
229
234 # Remove the old prompt and insert a new prompt.
230 # Remove the old prompt and insert a new prompt.
235 cursor = QtGui.QTextCursor(block)
231 cursor = QtGui.QTextCursor(block)
236 cursor.movePosition(QtGui.QTextCursor.Right,
232 cursor.movePosition(QtGui.QTextCursor.Right,
237 QtGui.QTextCursor.KeepAnchor,
233 QtGui.QTextCursor.KeepAnchor,
238 self._previous_prompt_obj.length)
234 self._previous_prompt_obj.length)
239 prompt = self._make_in_prompt(previous_prompt_number)
235 prompt = self._make_in_prompt(previous_prompt_number)
240 self._prompt = self._insert_html_fetching_plain_text(
236 self._prompt = self._insert_html_fetching_plain_text(
241 cursor, prompt)
237 cursor, prompt)
242
238
243 # When the HTML is inserted, Qt blows away the syntax
239 # When the HTML is inserted, Qt blows away the syntax
244 # highlighting for the line, so we need to rehighlight it.
240 # highlighting for the line, so we need to rehighlight it.
245 self._highlighter.rehighlightBlock(cursor.block())
241 self._highlighter.rehighlightBlock(cursor.block())
246
242
247 self._previous_prompt_obj = None
243 self._previous_prompt_obj = None
248
244
249 # Show a new prompt with the kernel's estimated prompt number.
245 # Show a new prompt with the kernel's estimated prompt number.
250 next_prompt = content['next_prompt']
246 next_prompt = content['next_prompt']
251 self._show_interpreter_prompt(next_prompt['prompt_number'],
247 self._show_interpreter_prompt(next_prompt['prompt_number'],
252 next_prompt['input_sep'])
248 next_prompt['input_sep'])
253
249
254 #---------------------------------------------------------------------------
250 #---------------------------------------------------------------------------
255 # 'IPythonWidget' interface
251 # 'IPythonWidget' interface
256 #---------------------------------------------------------------------------
252 #---------------------------------------------------------------------------
257
253
258 def reset_styling(self):
254 def reset_styling(self):
259 """ Restores the default IPythonWidget styling.
255 """ Restores the default IPythonWidget styling.
260 """
256 """
261 self.set_styling(self.default_stylesheet, syntax_style='default')
257 self.set_styling(self.default_stylesheet, syntax_style='default')
262 #self.set_styling(self.dark_stylesheet, syntax_style='monokai')
258 #self.set_styling(self.dark_stylesheet, syntax_style='monokai')
263
259
264 def set_editor(self, editor, line_editor=None):
260 def set_editor(self, editor, line_editor=None):
265 """ Sets the editor to use with the %edit magic.
261 """ Sets the editor to use with the %edit magic.
266
262
267 Parameters:
263 Parameters:
268 -----------
264 -----------
269 editor : str
265 editor : str
270 A command for invoking a system text editor. If the string contains
266 A command for invoking a system text editor. If the string contains
271 a {filename} format specifier, it will be used. Otherwise, the
267 a {filename} format specifier, it will be used. Otherwise, the
272 filename will be appended to the end the command.
268 filename will be appended to the end the command.
273
269
274 This parameter also takes a special value:
270 This parameter also takes a special value:
275 'custom' : Emit a 'custom_edit_requested(str, int)' signal
271 'custom' : Emit a 'custom_edit_requested(str, int)' signal
276 instead of opening an editor.
272 instead of opening an editor.
277
273
278 line_editor : str, optional
274 line_editor : str, optional
279 The editor command to use when a specific line number is
275 The editor command to use when a specific line number is
280 requested. The string should contain two format specifiers: {line}
276 requested. The string should contain two format specifiers: {line}
281 and {filename}. If this parameter is not specified, the line number
277 and {filename}. If this parameter is not specified, the line number
282 option to the %edit magic will be ignored.
278 option to the %edit magic will be ignored.
283 """
279 """
284 self._editor = editor
280 self._editor = editor
285 self._editor_line = line_editor
281 self._editor_line = line_editor
286
282
287 def set_styling(self, stylesheet, syntax_style=None):
283 def set_styling(self, stylesheet, syntax_style=None):
288 """ Sets the IPythonWidget styling.
284 """ Sets the IPythonWidget styling.
289
285
290 Parameters:
286 Parameters:
291 -----------
287 -----------
292 stylesheet : str
288 stylesheet : str
293 A CSS stylesheet. The stylesheet can contain classes for:
289 A CSS stylesheet. The stylesheet can contain classes for:
294 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
290 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
295 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
291 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
296 3. IPython: .error, .in-prompt, .out-prompt, etc.
292 3. IPython: .error, .in-prompt, .out-prompt, etc.
297
293
298 syntax_style : str or None [default None]
294 syntax_style : str or None [default None]
299 If specified, use the Pygments style with given name. Otherwise,
295 If specified, use the Pygments style with given name. Otherwise,
300 the stylesheet is queried for Pygments style information.
296 the stylesheet is queried for Pygments style information.
301 """
297 """
302 self.setStyleSheet(stylesheet)
298 self.setStyleSheet(stylesheet)
303 self._control.document().setDefaultStyleSheet(stylesheet)
299 self._control.document().setDefaultStyleSheet(stylesheet)
304 if self._page_control:
300 if self._page_control:
305 self._page_control.document().setDefaultStyleSheet(stylesheet)
301 self._page_control.document().setDefaultStyleSheet(stylesheet)
306
302
307 if syntax_style is None:
303 if syntax_style is None:
308 self._highlighter.set_style_sheet(stylesheet)
304 self._highlighter.set_style_sheet(stylesheet)
309 else:
305 else:
310 self._highlighter.set_style(syntax_style)
306 self._highlighter.set_style(syntax_style)
311
307
312 bg_color = self._control.palette().background().color()
308 bg_color = self._control.palette().background().color()
313 self._ansi_processor.set_background_color(bg_color)
309 self._ansi_processor.set_background_color(bg_color)
314
310
315 #---------------------------------------------------------------------------
311 #---------------------------------------------------------------------------
316 # 'IPythonWidget' protected interface
312 # 'IPythonWidget' protected interface
317 #---------------------------------------------------------------------------
313 #---------------------------------------------------------------------------
318
314
319 def _edit(self, filename, line=None):
315 def _edit(self, filename, line=None):
320 """ Opens a Python script for editing.
316 """ Opens a Python script for editing.
321
317
322 Parameters:
318 Parameters:
323 -----------
319 -----------
324 filename : str
320 filename : str
325 A path to a local system file.
321 A path to a local system file.
326
322
327 line : int, optional
323 line : int, optional
328 A line of interest in the file.
324 A line of interest in the file.
329 """
325 """
330 if self._editor == 'custom':
326 if self._editor == 'custom':
331 self.custom_edit_requested.emit(filename, line)
327 self.custom_edit_requested.emit(filename, line)
332 elif self._editor == 'default':
328 elif self._editor == 'default':
333 self._append_plain_text('No default editor available.\n')
329 self._append_plain_text('No default editor available.\n')
334 else:
330 else:
335 try:
331 try:
336 filename = '"%s"' % filename
332 filename = '"%s"' % filename
337 if line and self._editor_line:
333 if line and self._editor_line:
338 command = self._editor_line.format(filename=filename,
334 command = self._editor_line.format(filename=filename,
339 line=line)
335 line=line)
340 else:
336 else:
341 try:
337 try:
342 command = self._editor.format()
338 command = self._editor.format()
343 except KeyError:
339 except KeyError:
344 command = self._editor.format(filename=filename)
340 command = self._editor.format(filename=filename)
345 else:
341 else:
346 command += ' ' + filename
342 command += ' ' + filename
347 except KeyError:
343 except KeyError:
348 self._append_plain_text('Invalid editor command.\n')
344 self._append_plain_text('Invalid editor command.\n')
349 else:
345 else:
350 try:
346 try:
351 Popen(command, shell=True)
347 Popen(command, shell=True)
352 except OSError:
348 except OSError:
353 msg = 'Opening editor with command "%s" failed.\n'
349 msg = 'Opening editor with command "%s" failed.\n'
354 self._append_plain_text(msg % command)
350 self._append_plain_text(msg % command)
355
351
356 def _make_in_prompt(self, number):
352 def _make_in_prompt(self, number):
357 """ Given a prompt number, returns an HTML In prompt.
353 """ Given a prompt number, returns an HTML In prompt.
358 """
354 """
359 body = self.in_prompt % number
355 body = self.in_prompt % number
360 return '<span class="in-prompt">%s</span>' % body
356 return '<span class="in-prompt">%s</span>' % body
361
357
362 def _make_continuation_prompt(self, prompt):
358 def _make_continuation_prompt(self, prompt):
363 """ Given a plain text version of an In prompt, returns an HTML
359 """ Given a plain text version of an In prompt, returns an HTML
364 continuation prompt.
360 continuation prompt.
365 """
361 """
366 end_chars = '...: '
362 end_chars = '...: '
367 space_count = len(prompt.lstrip('\n')) - len(end_chars)
363 space_count = len(prompt.lstrip('\n')) - len(end_chars)
368 body = '&nbsp;' * space_count + end_chars
364 body = '&nbsp;' * space_count + end_chars
369 return '<span class="in-prompt">%s</span>' % body
365 return '<span class="in-prompt">%s</span>' % body
370
366
371 def _make_out_prompt(self, number):
367 def _make_out_prompt(self, number):
372 """ Given a prompt number, returns an HTML Out prompt.
368 """ Given a prompt number, returns an HTML Out prompt.
373 """
369 """
374 body = self.out_prompt % number
370 body = self.out_prompt % number
375 return '<span class="out-prompt">%s</span>' % body
371 return '<span class="out-prompt">%s</span>' % body
General Comments 0
You need to be logged in to leave comments. Login now