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