##// END OF EJS Templates
IPythonWidget now supports 'input_sep'.
epatters -
Show More
@@ -1,271 +1,275 b''
1 # Standard library imports
1 # Standard library imports
2 from subprocess import Popen
2 from subprocess import Popen
3
3
4 # System library imports
4 # System library imports
5 from PyQt4 import QtCore, QtGui
5 from PyQt4 import QtCore, QtGui
6
6
7 # Local imports
7 # Local imports
8 from IPython.core.inputsplitter import IPythonInputSplitter
8 from IPython.core.inputsplitter import IPythonInputSplitter
9 from IPython.core.usage import default_banner
9 from IPython.core.usage import default_banner
10 from frontend_widget import FrontendWidget
10 from frontend_widget import FrontendWidget
11
11
12
12
13 class IPythonPromptBlock(object):
13 class IPythonPromptBlock(object):
14 """ An internal storage object for IPythonWidget.
14 """ An internal storage object for IPythonWidget.
15 """
15 """
16 def __init__(self, block, length, number):
16 def __init__(self, block, length, number):
17 self.block = block
17 self.block = block
18 self.length = length
18 self.length = length
19 self.number = number
19 self.number = number
20
20
21
21
22 class IPythonWidget(FrontendWidget):
22 class IPythonWidget(FrontendWidget):
23 """ A FrontendWidget for an IPython kernel.
23 """ A FrontendWidget for an IPython kernel.
24 """
24 """
25
25
26 # 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
27 # specified as 'custom'.
27 # specified as 'custom'.
28 custom_edit_requested = QtCore.pyqtSignal(object)
28 custom_edit_requested = QtCore.pyqtSignal(object)
29
29
30 # The default stylesheet: black text on a white background.
30 # The default stylesheet: black text on a white background.
31 default_stylesheet = """
31 default_stylesheet = """
32 .error { color: red; }
32 .error { color: red; }
33 .in-prompt { color: navy; }
33 .in-prompt { color: navy; }
34 .in-prompt-number { font-weight: bold; }
34 .in-prompt-number { font-weight: bold; }
35 .out-prompt { color: darkred; }
35 .out-prompt { color: darkred; }
36 .out-prompt-number { font-weight: bold; }
36 .out-prompt-number { font-weight: bold; }
37 """
37 """
38
38
39 # A dark stylesheet: white text on a black background.
39 # A dark stylesheet: white text on a black background.
40 dark_stylesheet = """
40 dark_stylesheet = """
41 QPlainTextEdit { background-color: black; color: white }
41 QPlainTextEdit { background-color: black; color: white }
42 QFrame { border: 1px solid grey; }
42 QFrame { border: 1px solid grey; }
43 .error { color: red; }
43 .error { color: red; }
44 .in-prompt { color: lime; }
44 .in-prompt { color: lime; }
45 .in-prompt-number { color: lime; font-weight: bold; }
45 .in-prompt-number { color: lime; font-weight: bold; }
46 .out-prompt { color: red; }
46 .out-prompt { color: red; }
47 .out-prompt-number { color: red; font-weight: bold; }
47 .out-prompt-number { color: red; font-weight: bold; }
48 """
48 """
49
49
50 # Default prompts.
50 # Default prompts.
51 in_prompt = '<br/>In [<span class="in-prompt-number">%i</span>]: '
51 in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
52 out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
52 out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
53
53
54 # FrontendWidget protected class attributes.
54 # FrontendWidget protected class attributes.
55 #_input_splitter_class = IPythonInputSplitter
55 #_input_splitter_class = IPythonInputSplitter
56
56
57 #---------------------------------------------------------------------------
57 #---------------------------------------------------------------------------
58 # 'object' interface
58 # 'object' interface
59 #---------------------------------------------------------------------------
59 #---------------------------------------------------------------------------
60
60
61 def __init__(self, *args, **kw):
61 def __init__(self, *args, **kw):
62 super(IPythonWidget, self).__init__(*args, **kw)
62 super(IPythonWidget, self).__init__(*args, **kw)
63
63
64 # IPythonWidget protected variables.
64 # IPythonWidget protected variables.
65 self._previous_prompt_obj = None
65 self._previous_prompt_obj = None
66
66
67 # Set a default editor and stylesheet.
67 # Set a default editor and stylesheet.
68 self.set_editor('default')
68 self.set_editor('default')
69 self.reset_styling()
69 self.reset_styling()
70
70
71 #---------------------------------------------------------------------------
71 #---------------------------------------------------------------------------
72 # 'BaseFrontendMixin' abstract interface
72 # 'BaseFrontendMixin' abstract interface
73 #---------------------------------------------------------------------------
73 #---------------------------------------------------------------------------
74
74
75 def _handle_pyout(self, omsg):
75 def _handle_pyout(self, msg):
76 """ Reimplemented for IPython-style "display hook".
76 """ Reimplemented for IPython-style "display hook".
77 """
77 """
78 prompt_number = omsg['content']['prompt_number']
78 content = msg['content']
79 data = omsg['content']['data']
79 prompt_number = content['prompt_number']
80 self._append_plain_text(content['output_sep'])
80 self._append_html(self._make_out_prompt(prompt_number))
81 self._append_html(self._make_out_prompt(prompt_number))
81 self._append_plain_text(data + '\n')
82 self._append_plain_text(content['data'] + '\n' + content['output_sep2'])
82
83
83 #---------------------------------------------------------------------------
84 #---------------------------------------------------------------------------
84 # 'FrontendWidget' interface
85 # 'FrontendWidget' interface
85 #---------------------------------------------------------------------------
86 #---------------------------------------------------------------------------
86
87
87 def execute_file(self, path, hidden=False):
88 def execute_file(self, path, hidden=False):
88 """ Reimplemented to use the 'run' magic.
89 """ Reimplemented to use the 'run' magic.
89 """
90 """
90 self.execute('run %s' % path, hidden=hidden)
91 self.execute('run %s' % path, hidden=hidden)
91
92
92 #---------------------------------------------------------------------------
93 #---------------------------------------------------------------------------
93 # 'FrontendWidget' protected interface
94 # 'FrontendWidget' protected interface
94 #---------------------------------------------------------------------------
95 #---------------------------------------------------------------------------
95
96
96 def _get_banner(self):
97 def _get_banner(self):
97 """ Reimplemented to return IPython's default banner.
98 """ Reimplemented to return IPython's default banner.
98 """
99 """
99 return default_banner
100 return default_banner + '\n'
100
101
101 def _process_execute_error(self, msg):
102 def _process_execute_error(self, msg):
102 """ Reimplemented for IPython-style traceback formatting.
103 """ Reimplemented for IPython-style traceback formatting.
103 """
104 """
104 content = msg['content']
105 content = msg['content']
105 traceback_lines = content['traceback'][:]
106 traceback_lines = content['traceback'][:]
106 traceback = ''.join(traceback_lines)
107 traceback = ''.join(traceback_lines)
107 traceback = traceback.replace(' ', '&nbsp;')
108 traceback = traceback.replace(' ', '&nbsp;')
108 traceback = traceback.replace('\n', '<br/>')
109 traceback = traceback.replace('\n', '<br/>')
109
110
110 ename = content['ename']
111 ename = content['ename']
111 ename_styled = '<span class="error">%s</span>' % ename
112 ename_styled = '<span class="error">%s</span>' % ename
112 traceback = traceback.replace(ename, ename_styled)
113 traceback = traceback.replace(ename, ename_styled)
113
114
114 self._append_html(traceback)
115 self._append_html(traceback)
115
116
116 def _show_interpreter_prompt(self, number=None):
117 def _show_interpreter_prompt(self, number=None, input_sep='\n'):
117 """ Reimplemented for IPython-style prompts.
118 """ Reimplemented for IPython-style prompts.
118 """
119 """
119 # TODO: If a number was not specified, make a prompt number request.
120 # TODO: If a number was not specified, make a prompt number request.
120 if number is None:
121 if number is None:
121 number = 0
122 number = 0
122
123
123 # Show a new prompt and save information about it so it can be updated
124 # Show a new prompt and save information about it so that it can be
124 # later if its number needs to be re-written.
125 # updated later if the prompt number turns out to be wrong.
125 block = self._control.document().lastBlock()
126 self._append_plain_text(input_sep)
126 self._show_prompt(self._make_in_prompt(number), html=True)
127 self._show_prompt(self._make_in_prompt(number), html=True)
127 self._previous_prompt_obj = IPythonPromptBlock(
128 block = self._control.document().lastBlock()
128 block.next(), len(self._prompt), number)
129 length = len(self._prompt)
130 self._previous_prompt_obj = IPythonPromptBlock(block, length, number)
129
131
130 # Update continuation prompt to reflect (possibly) new prompt length.
132 # Update continuation prompt to reflect (possibly) new prompt length.
131 self._set_continuation_prompt(
133 self._set_continuation_prompt(
132 self._make_continuation_prompt(self._prompt), html=True)
134 self._make_continuation_prompt(self._prompt), html=True)
133
135
134 def _show_interpreter_prompt_for_reply(self, msg):
136 def _show_interpreter_prompt_for_reply(self, msg):
135 """ Reimplemented for IPython-style prompts.
137 """ Reimplemented for IPython-style prompts.
136 """
138 """
137 # Update the old prompt number if necessary.
139 # Update the old prompt number if necessary.
138 content = msg['content']
140 content = msg['content']
139 previous_prompt_number = content['prompt_number']
141 previous_prompt_number = content['prompt_number']
140 if self._previous_prompt_obj and \
142 if self._previous_prompt_obj and \
141 self._previous_prompt_obj.number != previous_prompt_number:
143 self._previous_prompt_obj.number != previous_prompt_number:
142 block = self._previous_prompt_obj.block
144 block = self._previous_prompt_obj.block
143 if block.isValid():
145 if block.isValid():
144
146
145 # Remove the old prompt and insert a new prompt.
147 # Remove the old prompt and insert a new prompt.
146 cursor = QtGui.QTextCursor(block)
148 cursor = QtGui.QTextCursor(block)
147 cursor.movePosition(QtGui.QTextCursor.Right,
149 cursor.movePosition(QtGui.QTextCursor.Right,
148 QtGui.QTextCursor.KeepAnchor,
150 QtGui.QTextCursor.KeepAnchor,
149 self._previous_prompt_obj.length)
151 self._previous_prompt_obj.length)
150 prompt = self._make_in_prompt(previous_prompt_number)
152 prompt = self._make_in_prompt(previous_prompt_number)
151 self._prompt = self._insert_html_fetching_plain_text(
153 self._prompt = self._insert_html_fetching_plain_text(
152 cursor, prompt)
154 cursor, prompt)
153
155
154 # XXX: When the HTML is inserted, Qt blows away the syntax
156 # XXX: When the HTML is inserted, Qt blows away the syntax
155 # highlighting for the line. I cannot for the life of me
157 # highlighting for the line. I cannot for the life of me
156 # determine how to preserve the existing formatting.
158 # determine how to preserve the existing formatting.
157 self._highlighter.highlighting_on = True
159 self._highlighter.highlighting_on = True
158 self._highlighter.rehighlightBlock(cursor.block())
160 self._highlighter.rehighlightBlock(cursor.block())
159 self._highlighter.highlighting_on = False
161 self._highlighter.highlighting_on = False
160
162
161 self._previous_prompt_obj = None
163 self._previous_prompt_obj = None
162
164
163 # Show a new prompt with the kernel's estimated prompt number.
165 # Show a new prompt with the kernel's estimated prompt number.
164 self._show_interpreter_prompt(content['next_prompt']['prompt_number'])
166 next_prompt = content['next_prompt']
167 self._show_interpreter_prompt(next_prompt['prompt_number'],
168 next_prompt['input_sep'])
165
169
166 #---------------------------------------------------------------------------
170 #---------------------------------------------------------------------------
167 # 'IPythonWidget' interface
171 # 'IPythonWidget' interface
168 #---------------------------------------------------------------------------
172 #---------------------------------------------------------------------------
169
173
170 def edit(self, filename):
174 def edit(self, filename):
171 """ Opens a Python script for editing.
175 """ Opens a Python script for editing.
172
176
173 Parameters:
177 Parameters:
174 -----------
178 -----------
175 filename : str
179 filename : str
176 A path to a local system file.
180 A path to a local system file.
177
181
178 Raises:
182 Raises:
179 -------
183 -------
180 OSError
184 OSError
181 If the editor command cannot be executed.
185 If the editor command cannot be executed.
182 """
186 """
183 if self._editor == 'default':
187 if self._editor == 'default':
184 url = QtCore.QUrl.fromLocalFile(filename)
188 url = QtCore.QUrl.fromLocalFile(filename)
185 if not QtGui.QDesktopServices.openUrl(url):
189 if not QtGui.QDesktopServices.openUrl(url):
186 message = 'Failed to open %s with the default application'
190 message = 'Failed to open %s with the default application'
187 raise OSError(message % repr(filename))
191 raise OSError(message % repr(filename))
188 elif self._editor is None:
192 elif self._editor is None:
189 self.custom_edit_requested.emit(filename)
193 self.custom_edit_requested.emit(filename)
190 else:
194 else:
191 Popen(self._editor + [filename])
195 Popen(self._editor + [filename])
192
196
193 def reset_styling(self):
197 def reset_styling(self):
194 """ Restores the default IPythonWidget styling.
198 """ Restores the default IPythonWidget styling.
195 """
199 """
196 self.set_styling(self.default_stylesheet, syntax_style='default')
200 self.set_styling(self.default_stylesheet, syntax_style='default')
197 #self.set_styling(self.dark_stylesheet, syntax_style='monokai')
201 #self.set_styling(self.dark_stylesheet, syntax_style='monokai')
198
202
199 def set_editor(self, editor):
203 def set_editor(self, editor):
200 """ Sets the editor to use with the %edit magic.
204 """ Sets the editor to use with the %edit magic.
201
205
202 Parameters:
206 Parameters:
203 -----------
207 -----------
204 editor : str or sequence of str
208 editor : str or sequence of str
205 A command suitable for use with Popen. This command will be executed
209 A command suitable for use with Popen. This command will be executed
206 with a single argument--a filename--when editing is requested.
210 with a single argument--a filename--when editing is requested.
207
211
208 This parameter also takes two special values:
212 This parameter also takes two special values:
209 'default' : Files will be edited with the system default
213 'default' : Files will be edited with the system default
210 application for Python files.
214 application for Python files.
211 'custom' : Emit a 'custom_edit_requested(str)' signal instead
215 'custom' : Emit a 'custom_edit_requested(str)' signal instead
212 of opening an editor.
216 of opening an editor.
213 """
217 """
214 if editor == 'default':
218 if editor == 'default':
215 self._editor = 'default'
219 self._editor = 'default'
216 elif editor == 'custom':
220 elif editor == 'custom':
217 self._editor = None
221 self._editor = None
218 elif isinstance(editor, basestring):
222 elif isinstance(editor, basestring):
219 self._editor = [ editor ]
223 self._editor = [ editor ]
220 else:
224 else:
221 self._editor = list(editor)
225 self._editor = list(editor)
222
226
223 def set_styling(self, stylesheet, syntax_style=None):
227 def set_styling(self, stylesheet, syntax_style=None):
224 """ Sets the IPythonWidget styling.
228 """ Sets the IPythonWidget styling.
225
229
226 Parameters:
230 Parameters:
227 -----------
231 -----------
228 stylesheet : str
232 stylesheet : str
229 A CSS stylesheet. The stylesheet can contain classes for:
233 A CSS stylesheet. The stylesheet can contain classes for:
230 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
234 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
231 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
235 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
232 3. IPython: .error, .in-prompt, .out-prompt, etc.
236 3. IPython: .error, .in-prompt, .out-prompt, etc.
233
237
234 syntax_style : str or None [default None]
238 syntax_style : str or None [default None]
235 If specified, use the Pygments style with given name. Otherwise,
239 If specified, use the Pygments style with given name. Otherwise,
236 the stylesheet is queried for Pygments style information.
240 the stylesheet is queried for Pygments style information.
237 """
241 """
238 self.setStyleSheet(stylesheet)
242 self.setStyleSheet(stylesheet)
239 self._control.document().setDefaultStyleSheet(stylesheet)
243 self._control.document().setDefaultStyleSheet(stylesheet)
240 if self._page_control:
244 if self._page_control:
241 self._page_control.document().setDefaultStyleSheet(stylesheet)
245 self._page_control.document().setDefaultStyleSheet(stylesheet)
242
246
243 if syntax_style is None:
247 if syntax_style is None:
244 self._highlighter.set_style_sheet(stylesheet)
248 self._highlighter.set_style_sheet(stylesheet)
245 else:
249 else:
246 self._highlighter.set_style(syntax_style)
250 self._highlighter.set_style(syntax_style)
247
251
248 #---------------------------------------------------------------------------
252 #---------------------------------------------------------------------------
249 # 'IPythonWidget' protected interface
253 # 'IPythonWidget' protected interface
250 #---------------------------------------------------------------------------
254 #---------------------------------------------------------------------------
251
255
252 def _make_in_prompt(self, number):
256 def _make_in_prompt(self, number):
253 """ Given a prompt number, returns an HTML In prompt.
257 """ Given a prompt number, returns an HTML In prompt.
254 """
258 """
255 body = self.in_prompt % number
259 body = self.in_prompt % number
256 return '<span class="in-prompt">%s</span>' % body
260 return '<span class="in-prompt">%s</span>' % body
257
261
258 def _make_continuation_prompt(self, prompt):
262 def _make_continuation_prompt(self, prompt):
259 """ Given a plain text version of an In prompt, returns an HTML
263 """ Given a plain text version of an In prompt, returns an HTML
260 continuation prompt.
264 continuation prompt.
261 """
265 """
262 end_chars = '...: '
266 end_chars = '...: '
263 space_count = len(prompt.lstrip('\n')) - len(end_chars)
267 space_count = len(prompt.lstrip('\n')) - len(end_chars)
264 body = '&nbsp;' * space_count + end_chars
268 body = '&nbsp;' * space_count + end_chars
265 return '<span class="in-prompt">%s</span>' % body
269 return '<span class="in-prompt">%s</span>' % body
266
270
267 def _make_out_prompt(self, number):
271 def _make_out_prompt(self, number):
268 """ Given a prompt number, returns an HTML Out prompt.
272 """ Given a prompt number, returns an HTML Out prompt.
269 """
273 """
270 body = self.out_prompt % number
274 body = self.out_prompt % number
271 return '<span class="out-prompt">%s</span>' % body
275 return '<span class="out-prompt">%s</span>' % body
General Comments 0
You need to be logged in to leave comments. Login now