##// END OF EJS Templates
disable calltips in PySide < 1.0.7 to prevent segfault...
MinRK -
Show More
@@ -1,659 +1,665 b''
1 from __future__ import print_function
1 from __future__ import print_function
2
2
3 # Standard library imports
3 # Standard library imports
4 from collections import namedtuple
4 from collections import namedtuple
5 import sys
5 import sys
6 import time
6 import time
7
7
8 # System library imports
8 # System library imports
9 from pygments.lexers import PythonLexer
9 from pygments.lexers import PythonLexer
10 from IPython.external import qt
10 from IPython.external.qt import QtCore, QtGui
11 from IPython.external.qt import QtCore, QtGui
11
12
12 # Local imports
13 # Local imports
13 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
14 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
14 from IPython.core.oinspect import call_tip
15 from IPython.core.oinspect import call_tip
15 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
16 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
16 from IPython.utils.traitlets import Bool, Instance, Unicode
17 from IPython.utils.traitlets import Bool, Instance, Unicode
17 from bracket_matcher import BracketMatcher
18 from bracket_matcher import BracketMatcher
18 from call_tip_widget import CallTipWidget
19 from call_tip_widget import CallTipWidget
19 from completion_lexer import CompletionLexer
20 from completion_lexer import CompletionLexer
20 from history_console_widget import HistoryConsoleWidget
21 from history_console_widget import HistoryConsoleWidget
21 from pygments_highlighter import PygmentsHighlighter
22 from pygments_highlighter import PygmentsHighlighter
22
23
23
24
24 class FrontendHighlighter(PygmentsHighlighter):
25 class FrontendHighlighter(PygmentsHighlighter):
25 """ A PygmentsHighlighter that understands and ignores prompts.
26 """ A PygmentsHighlighter that understands and ignores prompts.
26 """
27 """
27
28
28 def __init__(self, frontend):
29 def __init__(self, frontend):
29 super(FrontendHighlighter, self).__init__(frontend._control.document())
30 super(FrontendHighlighter, self).__init__(frontend._control.document())
30 self._current_offset = 0
31 self._current_offset = 0
31 self._frontend = frontend
32 self._frontend = frontend
32 self.highlighting_on = False
33 self.highlighting_on = False
33
34
34 def highlightBlock(self, string):
35 def highlightBlock(self, string):
35 """ Highlight a block of text. Reimplemented to highlight selectively.
36 """ Highlight a block of text. Reimplemented to highlight selectively.
36 """
37 """
37 if not self.highlighting_on:
38 if not self.highlighting_on:
38 return
39 return
39
40
40 # The input to this function is a unicode string that may contain
41 # The input to this function is a unicode string that may contain
41 # paragraph break characters, non-breaking spaces, etc. Here we acquire
42 # paragraph break characters, non-breaking spaces, etc. Here we acquire
42 # the string as plain text so we can compare it.
43 # the string as plain text so we can compare it.
43 current_block = self.currentBlock()
44 current_block = self.currentBlock()
44 string = self._frontend._get_block_plain_text(current_block)
45 string = self._frontend._get_block_plain_text(current_block)
45
46
46 # Decide whether to check for the regular or continuation prompt.
47 # Decide whether to check for the regular or continuation prompt.
47 if current_block.contains(self._frontend._prompt_pos):
48 if current_block.contains(self._frontend._prompt_pos):
48 prompt = self._frontend._prompt
49 prompt = self._frontend._prompt
49 else:
50 else:
50 prompt = self._frontend._continuation_prompt
51 prompt = self._frontend._continuation_prompt
51
52
52 # Only highlight if we can identify a prompt, but make sure not to
53 # Only highlight if we can identify a prompt, but make sure not to
53 # highlight the prompt.
54 # highlight the prompt.
54 if string.startswith(prompt):
55 if string.startswith(prompt):
55 self._current_offset = len(prompt)
56 self._current_offset = len(prompt)
56 string = string[len(prompt):]
57 string = string[len(prompt):]
57 super(FrontendHighlighter, self).highlightBlock(string)
58 super(FrontendHighlighter, self).highlightBlock(string)
58
59
59 def rehighlightBlock(self, block):
60 def rehighlightBlock(self, block):
60 """ Reimplemented to temporarily enable highlighting if disabled.
61 """ Reimplemented to temporarily enable highlighting if disabled.
61 """
62 """
62 old = self.highlighting_on
63 old = self.highlighting_on
63 self.highlighting_on = True
64 self.highlighting_on = True
64 super(FrontendHighlighter, self).rehighlightBlock(block)
65 super(FrontendHighlighter, self).rehighlightBlock(block)
65 self.highlighting_on = old
66 self.highlighting_on = old
66
67
67 def setFormat(self, start, count, format):
68 def setFormat(self, start, count, format):
68 """ Reimplemented to highlight selectively.
69 """ Reimplemented to highlight selectively.
69 """
70 """
70 start += self._current_offset
71 start += self._current_offset
71 super(FrontendHighlighter, self).setFormat(start, count, format)
72 super(FrontendHighlighter, self).setFormat(start, count, format)
72
73
73
74
74 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
75 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
75 """ A Qt frontend for a generic Python kernel.
76 """ A Qt frontend for a generic Python kernel.
76 """
77 """
77
78
78 # The text to show when the kernel is (re)started.
79 # The text to show when the kernel is (re)started.
79 banner = Unicode()
80 banner = Unicode()
80
81
81 # An option and corresponding signal for overriding the default kernel
82 # An option and corresponding signal for overriding the default kernel
82 # interrupt behavior.
83 # interrupt behavior.
83 custom_interrupt = Bool(False)
84 custom_interrupt = Bool(False)
84 custom_interrupt_requested = QtCore.Signal()
85 custom_interrupt_requested = QtCore.Signal()
85
86
86 # An option and corresponding signals for overriding the default kernel
87 # An option and corresponding signals for overriding the default kernel
87 # restart behavior.
88 # restart behavior.
88 custom_restart = Bool(False)
89 custom_restart = Bool(False)
89 custom_restart_kernel_died = QtCore.Signal(float)
90 custom_restart_kernel_died = QtCore.Signal(float)
90 custom_restart_requested = QtCore.Signal()
91 custom_restart_requested = QtCore.Signal()
91
92
92 # Whether to automatically show calltips on open-parentheses.
93 # Whether to automatically show calltips on open-parentheses.
93 enable_calltips = Bool(True, config=True,
94 enable_calltips = Bool(True, config=True,
94 help="Whether to draw information calltips on open-parentheses.")
95 help="Whether to draw information calltips on open-parentheses.")
95
96
96 # Emitted when a user visible 'execute_request' has been submitted to the
97 # Emitted when a user visible 'execute_request' has been submitted to the
97 # kernel from the FrontendWidget. Contains the code to be executed.
98 # kernel from the FrontendWidget. Contains the code to be executed.
98 executing = QtCore.Signal(object)
99 executing = QtCore.Signal(object)
99
100
100 # Emitted when a user-visible 'execute_reply' has been received from the
101 # Emitted when a user-visible 'execute_reply' has been received from the
101 # kernel and processed by the FrontendWidget. Contains the response message.
102 # kernel and processed by the FrontendWidget. Contains the response message.
102 executed = QtCore.Signal(object)
103 executed = QtCore.Signal(object)
103
104
104 # Emitted when an exit request has been received from the kernel.
105 # Emitted when an exit request has been received from the kernel.
105 exit_requested = QtCore.Signal(object)
106 exit_requested = QtCore.Signal(object)
106
107
107 # Protected class variables.
108 # Protected class variables.
108 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
109 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
109 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
110 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
110 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
111 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
111 _input_splitter_class = InputSplitter
112 _input_splitter_class = InputSplitter
112 _local_kernel = False
113 _local_kernel = False
113 _highlighter = Instance(FrontendHighlighter)
114 _highlighter = Instance(FrontendHighlighter)
114
115
115 #---------------------------------------------------------------------------
116 #---------------------------------------------------------------------------
116 # 'object' interface
117 # 'object' interface
117 #---------------------------------------------------------------------------
118 #---------------------------------------------------------------------------
118
119
119 def __init__(self, *args, **kw):
120 def __init__(self, *args, **kw):
120 super(FrontendWidget, self).__init__(*args, **kw)
121 super(FrontendWidget, self).__init__(*args, **kw)
122 # forcefully disable calltips if PySide is < 1.0.7, because they crash
123 if qt.QT_API == qt.QT_API_PYSIDE:
124 import PySide
125 if PySide.__version_info__ < (1,0,7):
126 self.enable_calltips = False
121
127
122 # FrontendWidget protected variables.
128 # FrontendWidget protected variables.
123 self._bracket_matcher = BracketMatcher(self._control)
129 self._bracket_matcher = BracketMatcher(self._control)
124 self._call_tip_widget = CallTipWidget(self._control)
130 self._call_tip_widget = CallTipWidget(self._control)
125 self._completion_lexer = CompletionLexer(PythonLexer())
131 self._completion_lexer = CompletionLexer(PythonLexer())
126 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
132 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
127 self._hidden = False
133 self._hidden = False
128 self._highlighter = FrontendHighlighter(self)
134 self._highlighter = FrontendHighlighter(self)
129 self._input_splitter = self._input_splitter_class(input_mode='cell')
135 self._input_splitter = self._input_splitter_class(input_mode='cell')
130 self._kernel_manager = None
136 self._kernel_manager = None
131 self._request_info = {}
137 self._request_info = {}
132
138
133 # Configure the ConsoleWidget.
139 # Configure the ConsoleWidget.
134 self.tab_width = 4
140 self.tab_width = 4
135 self._set_continuation_prompt('... ')
141 self._set_continuation_prompt('... ')
136
142
137 # Configure the CallTipWidget.
143 # Configure the CallTipWidget.
138 self._call_tip_widget.setFont(self.font)
144 self._call_tip_widget.setFont(self.font)
139 self.font_changed.connect(self._call_tip_widget.setFont)
145 self.font_changed.connect(self._call_tip_widget.setFont)
140
146
141 # Configure actions.
147 # Configure actions.
142 action = self._copy_raw_action
148 action = self._copy_raw_action
143 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
149 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
144 action.setEnabled(False)
150 action.setEnabled(False)
145 action.setShortcut(QtGui.QKeySequence(key))
151 action.setShortcut(QtGui.QKeySequence(key))
146 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
152 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
147 action.triggered.connect(self.copy_raw)
153 action.triggered.connect(self.copy_raw)
148 self.copy_available.connect(action.setEnabled)
154 self.copy_available.connect(action.setEnabled)
149 self.addAction(action)
155 self.addAction(action)
150
156
151 # Connect signal handlers.
157 # Connect signal handlers.
152 document = self._control.document()
158 document = self._control.document()
153 document.contentsChange.connect(self._document_contents_change)
159 document.contentsChange.connect(self._document_contents_change)
154
160
155 # Set flag for whether we are connected via localhost.
161 # Set flag for whether we are connected via localhost.
156 self._local_kernel = kw.get('local_kernel',
162 self._local_kernel = kw.get('local_kernel',
157 FrontendWidget._local_kernel)
163 FrontendWidget._local_kernel)
158
164
159 #---------------------------------------------------------------------------
165 #---------------------------------------------------------------------------
160 # 'ConsoleWidget' public interface
166 # 'ConsoleWidget' public interface
161 #---------------------------------------------------------------------------
167 #---------------------------------------------------------------------------
162
168
163 def copy(self):
169 def copy(self):
164 """ Copy the currently selected text to the clipboard, removing prompts.
170 """ Copy the currently selected text to the clipboard, removing prompts.
165 """
171 """
166 text = self._control.textCursor().selection().toPlainText()
172 text = self._control.textCursor().selection().toPlainText()
167 if text:
173 if text:
168 lines = map(transform_classic_prompt, text.splitlines())
174 lines = map(transform_classic_prompt, text.splitlines())
169 text = '\n'.join(lines)
175 text = '\n'.join(lines)
170 QtGui.QApplication.clipboard().setText(text)
176 QtGui.QApplication.clipboard().setText(text)
171
177
172 #---------------------------------------------------------------------------
178 #---------------------------------------------------------------------------
173 # 'ConsoleWidget' abstract interface
179 # 'ConsoleWidget' abstract interface
174 #---------------------------------------------------------------------------
180 #---------------------------------------------------------------------------
175
181
176 def _is_complete(self, source, interactive):
182 def _is_complete(self, source, interactive):
177 """ Returns whether 'source' can be completely processed and a new
183 """ Returns whether 'source' can be completely processed and a new
178 prompt created. When triggered by an Enter/Return key press,
184 prompt created. When triggered by an Enter/Return key press,
179 'interactive' is True; otherwise, it is False.
185 'interactive' is True; otherwise, it is False.
180 """
186 """
181 complete = self._input_splitter.push(source)
187 complete = self._input_splitter.push(source)
182 if interactive:
188 if interactive:
183 complete = not self._input_splitter.push_accepts_more()
189 complete = not self._input_splitter.push_accepts_more()
184 return complete
190 return complete
185
191
186 def _execute(self, source, hidden):
192 def _execute(self, source, hidden):
187 """ Execute 'source'. If 'hidden', do not show any output.
193 """ Execute 'source'. If 'hidden', do not show any output.
188
194
189 See parent class :meth:`execute` docstring for full details.
195 See parent class :meth:`execute` docstring for full details.
190 """
196 """
191 msg_id = self.kernel_manager.shell_channel.execute(source, hidden)
197 msg_id = self.kernel_manager.shell_channel.execute(source, hidden)
192 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
198 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
193 self._hidden = hidden
199 self._hidden = hidden
194 if not hidden:
200 if not hidden:
195 self.executing.emit(source)
201 self.executing.emit(source)
196
202
197 def _prompt_started_hook(self):
203 def _prompt_started_hook(self):
198 """ Called immediately after a new prompt is displayed.
204 """ Called immediately after a new prompt is displayed.
199 """
205 """
200 if not self._reading:
206 if not self._reading:
201 self._highlighter.highlighting_on = True
207 self._highlighter.highlighting_on = True
202
208
203 def _prompt_finished_hook(self):
209 def _prompt_finished_hook(self):
204 """ Called immediately after a prompt is finished, i.e. when some input
210 """ Called immediately after a prompt is finished, i.e. when some input
205 will be processed and a new prompt displayed.
211 will be processed and a new prompt displayed.
206 """
212 """
207 # Flush all state from the input splitter so the next round of
213 # Flush all state from the input splitter so the next round of
208 # reading input starts with a clean buffer.
214 # reading input starts with a clean buffer.
209 self._input_splitter.reset()
215 self._input_splitter.reset()
210
216
211 if not self._reading:
217 if not self._reading:
212 self._highlighter.highlighting_on = False
218 self._highlighter.highlighting_on = False
213
219
214 def _tab_pressed(self):
220 def _tab_pressed(self):
215 """ Called when the tab key is pressed. Returns whether to continue
221 """ Called when the tab key is pressed. Returns whether to continue
216 processing the event.
222 processing the event.
217 """
223 """
218 # Perform tab completion if:
224 # Perform tab completion if:
219 # 1) The cursor is in the input buffer.
225 # 1) The cursor is in the input buffer.
220 # 2) There is a non-whitespace character before the cursor.
226 # 2) There is a non-whitespace character before the cursor.
221 text = self._get_input_buffer_cursor_line()
227 text = self._get_input_buffer_cursor_line()
222 if text is None:
228 if text is None:
223 return False
229 return False
224 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
230 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
225 if complete:
231 if complete:
226 self._complete()
232 self._complete()
227 return not complete
233 return not complete
228
234
229 #---------------------------------------------------------------------------
235 #---------------------------------------------------------------------------
230 # 'ConsoleWidget' protected interface
236 # 'ConsoleWidget' protected interface
231 #---------------------------------------------------------------------------
237 #---------------------------------------------------------------------------
232
238
233 def _context_menu_make(self, pos):
239 def _context_menu_make(self, pos):
234 """ Reimplemented to add an action for raw copy.
240 """ Reimplemented to add an action for raw copy.
235 """
241 """
236 menu = super(FrontendWidget, self)._context_menu_make(pos)
242 menu = super(FrontendWidget, self)._context_menu_make(pos)
237 for before_action in menu.actions():
243 for before_action in menu.actions():
238 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
244 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
239 QtGui.QKeySequence.ExactMatch:
245 QtGui.QKeySequence.ExactMatch:
240 menu.insertAction(before_action, self._copy_raw_action)
246 menu.insertAction(before_action, self._copy_raw_action)
241 break
247 break
242 return menu
248 return menu
243
249
244 def request_interrupt_kernel(self):
250 def request_interrupt_kernel(self):
245 if self._executing:
251 if self._executing:
246 self.interrupt_kernel()
252 self.interrupt_kernel()
247
253
248 def request_restart_kernel(self):
254 def request_restart_kernel(self):
249 message = 'Are you sure you want to restart the kernel?'
255 message = 'Are you sure you want to restart the kernel?'
250 self.restart_kernel(message, now=False)
256 self.restart_kernel(message, now=False)
251
257
252 def _event_filter_console_keypress(self, event):
258 def _event_filter_console_keypress(self, event):
253 """ Reimplemented for execution interruption and smart backspace.
259 """ Reimplemented for execution interruption and smart backspace.
254 """
260 """
255 key = event.key()
261 key = event.key()
256 if self._control_key_down(event.modifiers(), include_command=False):
262 if self._control_key_down(event.modifiers(), include_command=False):
257
263
258 if key == QtCore.Qt.Key_C and self._executing:
264 if key == QtCore.Qt.Key_C and self._executing:
259 self.request_interrupt_kernel()
265 self.request_interrupt_kernel()
260 return True
266 return True
261
267
262 elif key == QtCore.Qt.Key_Period:
268 elif key == QtCore.Qt.Key_Period:
263 self.request_restart_kernel()
269 self.request_restart_kernel()
264 return True
270 return True
265
271
266 elif not event.modifiers() & QtCore.Qt.AltModifier:
272 elif not event.modifiers() & QtCore.Qt.AltModifier:
267
273
268 # Smart backspace: remove four characters in one backspace if:
274 # Smart backspace: remove four characters in one backspace if:
269 # 1) everything left of the cursor is whitespace
275 # 1) everything left of the cursor is whitespace
270 # 2) the four characters immediately left of the cursor are spaces
276 # 2) the four characters immediately left of the cursor are spaces
271 if key == QtCore.Qt.Key_Backspace:
277 if key == QtCore.Qt.Key_Backspace:
272 col = self._get_input_buffer_cursor_column()
278 col = self._get_input_buffer_cursor_column()
273 cursor = self._control.textCursor()
279 cursor = self._control.textCursor()
274 if col > 3 and not cursor.hasSelection():
280 if col > 3 and not cursor.hasSelection():
275 text = self._get_input_buffer_cursor_line()[:col]
281 text = self._get_input_buffer_cursor_line()[:col]
276 if text.endswith(' ') and not text.strip():
282 if text.endswith(' ') and not text.strip():
277 cursor.movePosition(QtGui.QTextCursor.Left,
283 cursor.movePosition(QtGui.QTextCursor.Left,
278 QtGui.QTextCursor.KeepAnchor, 4)
284 QtGui.QTextCursor.KeepAnchor, 4)
279 cursor.removeSelectedText()
285 cursor.removeSelectedText()
280 return True
286 return True
281
287
282 return super(FrontendWidget, self)._event_filter_console_keypress(event)
288 return super(FrontendWidget, self)._event_filter_console_keypress(event)
283
289
284 def _insert_continuation_prompt(self, cursor):
290 def _insert_continuation_prompt(self, cursor):
285 """ Reimplemented for auto-indentation.
291 """ Reimplemented for auto-indentation.
286 """
292 """
287 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
293 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
288 cursor.insertText(' ' * self._input_splitter.indent_spaces)
294 cursor.insertText(' ' * self._input_splitter.indent_spaces)
289
295
290 #---------------------------------------------------------------------------
296 #---------------------------------------------------------------------------
291 # 'BaseFrontendMixin' abstract interface
297 # 'BaseFrontendMixin' abstract interface
292 #---------------------------------------------------------------------------
298 #---------------------------------------------------------------------------
293
299
294 def _handle_complete_reply(self, rep):
300 def _handle_complete_reply(self, rep):
295 """ Handle replies for tab completion.
301 """ Handle replies for tab completion.
296 """
302 """
297 self.log.debug("complete: %s", rep.get('content', ''))
303 self.log.debug("complete: %s", rep.get('content', ''))
298 cursor = self._get_cursor()
304 cursor = self._get_cursor()
299 info = self._request_info.get('complete')
305 info = self._request_info.get('complete')
300 if info and info.id == rep['parent_header']['msg_id'] and \
306 if info and info.id == rep['parent_header']['msg_id'] and \
301 info.pos == cursor.position():
307 info.pos == cursor.position():
302 text = '.'.join(self._get_context())
308 text = '.'.join(self._get_context())
303 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
309 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
304 self._complete_with_items(cursor, rep['content']['matches'])
310 self._complete_with_items(cursor, rep['content']['matches'])
305
311
306 def _handle_execute_reply(self, msg):
312 def _handle_execute_reply(self, msg):
307 """ Handles replies for code execution.
313 """ Handles replies for code execution.
308 """
314 """
309 self.log.debug("execute: %s", msg.get('content', ''))
315 self.log.debug("execute: %s", msg.get('content', ''))
310 info = self._request_info.get('execute')
316 info = self._request_info.get('execute')
311 # unset reading flag, because if execute finished, raw_input can't
317 # unset reading flag, because if execute finished, raw_input can't
312 # still be pending.
318 # still be pending.
313 self._reading = False
319 self._reading = False
314 if info and info.id == msg['parent_header']['msg_id'] and \
320 if info and info.id == msg['parent_header']['msg_id'] and \
315 info.kind == 'user' and not self._hidden:
321 info.kind == 'user' and not self._hidden:
316 # Make sure that all output from the SUB channel has been processed
322 # Make sure that all output from the SUB channel has been processed
317 # before writing a new prompt.
323 # before writing a new prompt.
318 self.kernel_manager.sub_channel.flush()
324 self.kernel_manager.sub_channel.flush()
319
325
320 # Reset the ANSI style information to prevent bad text in stdout
326 # Reset the ANSI style information to prevent bad text in stdout
321 # from messing up our colors. We're not a true terminal so we're
327 # from messing up our colors. We're not a true terminal so we're
322 # allowed to do this.
328 # allowed to do this.
323 if self.ansi_codes:
329 if self.ansi_codes:
324 self._ansi_processor.reset_sgr()
330 self._ansi_processor.reset_sgr()
325
331
326 content = msg['content']
332 content = msg['content']
327 status = content['status']
333 status = content['status']
328 if status == 'ok':
334 if status == 'ok':
329 self._process_execute_ok(msg)
335 self._process_execute_ok(msg)
330 elif status == 'error':
336 elif status == 'error':
331 self._process_execute_error(msg)
337 self._process_execute_error(msg)
332 elif status == 'aborted':
338 elif status == 'aborted':
333 self._process_execute_abort(msg)
339 self._process_execute_abort(msg)
334
340
335 self._show_interpreter_prompt_for_reply(msg)
341 self._show_interpreter_prompt_for_reply(msg)
336 self.executed.emit(msg)
342 self.executed.emit(msg)
337 else:
343 else:
338 super(FrontendWidget, self)._handle_execute_reply(msg)
344 super(FrontendWidget, self)._handle_execute_reply(msg)
339
345
340 def _handle_input_request(self, msg):
346 def _handle_input_request(self, msg):
341 """ Handle requests for raw_input.
347 """ Handle requests for raw_input.
342 """
348 """
343 self.log.debug("input: %s", msg.get('content', ''))
349 self.log.debug("input: %s", msg.get('content', ''))
344 if self._hidden:
350 if self._hidden:
345 raise RuntimeError('Request for raw input during hidden execution.')
351 raise RuntimeError('Request for raw input during hidden execution.')
346
352
347 # Make sure that all output from the SUB channel has been processed
353 # Make sure that all output from the SUB channel has been processed
348 # before entering readline mode.
354 # before entering readline mode.
349 self.kernel_manager.sub_channel.flush()
355 self.kernel_manager.sub_channel.flush()
350
356
351 def callback(line):
357 def callback(line):
352 self.kernel_manager.stdin_channel.input(line)
358 self.kernel_manager.stdin_channel.input(line)
353 if self._reading:
359 if self._reading:
354 self.log.debug("Got second input request, assuming first was interrupted.")
360 self.log.debug("Got second input request, assuming first was interrupted.")
355 self._reading = False
361 self._reading = False
356 self._readline(msg['content']['prompt'], callback=callback)
362 self._readline(msg['content']['prompt'], callback=callback)
357
363
358 def _handle_kernel_died(self, since_last_heartbeat):
364 def _handle_kernel_died(self, since_last_heartbeat):
359 """ Handle the kernel's death by asking if the user wants to restart.
365 """ Handle the kernel's death by asking if the user wants to restart.
360 """
366 """
361 self.log.debug("kernel died: %s", since_last_heartbeat)
367 self.log.debug("kernel died: %s", since_last_heartbeat)
362 if self.custom_restart:
368 if self.custom_restart:
363 self.custom_restart_kernel_died.emit(since_last_heartbeat)
369 self.custom_restart_kernel_died.emit(since_last_heartbeat)
364 else:
370 else:
365 message = 'The kernel heartbeat has been inactive for %.2f ' \
371 message = 'The kernel heartbeat has been inactive for %.2f ' \
366 'seconds. Do you want to restart the kernel? You may ' \
372 'seconds. Do you want to restart the kernel? You may ' \
367 'first want to check the network connection.' % \
373 'first want to check the network connection.' % \
368 since_last_heartbeat
374 since_last_heartbeat
369 self.restart_kernel(message, now=True)
375 self.restart_kernel(message, now=True)
370
376
371 def _handle_object_info_reply(self, rep):
377 def _handle_object_info_reply(self, rep):
372 """ Handle replies for call tips.
378 """ Handle replies for call tips.
373 """
379 """
374 self.log.debug("oinfo: %s", rep.get('content', ''))
380 self.log.debug("oinfo: %s", rep.get('content', ''))
375 cursor = self._get_cursor()
381 cursor = self._get_cursor()
376 info = self._request_info.get('call_tip')
382 info = self._request_info.get('call_tip')
377 if info and info.id == rep['parent_header']['msg_id'] and \
383 if info and info.id == rep['parent_header']['msg_id'] and \
378 info.pos == cursor.position():
384 info.pos == cursor.position():
379 # Get the information for a call tip. For now we format the call
385 # Get the information for a call tip. For now we format the call
380 # line as string, later we can pass False to format_call and
386 # line as string, later we can pass False to format_call and
381 # syntax-highlight it ourselves for nicer formatting in the
387 # syntax-highlight it ourselves for nicer formatting in the
382 # calltip.
388 # calltip.
383 content = rep['content']
389 content = rep['content']
384 # if this is from pykernel, 'docstring' will be the only key
390 # if this is from pykernel, 'docstring' will be the only key
385 if content.get('ismagic', False):
391 if content.get('ismagic', False):
386 # Don't generate a call-tip for magics. Ideally, we should
392 # Don't generate a call-tip for magics. Ideally, we should
387 # generate a tooltip, but not on ( like we do for actual
393 # generate a tooltip, but not on ( like we do for actual
388 # callables.
394 # callables.
389 call_info, doc = None, None
395 call_info, doc = None, None
390 else:
396 else:
391 call_info, doc = call_tip(content, format_call=True)
397 call_info, doc = call_tip(content, format_call=True)
392 if call_info or doc:
398 if call_info or doc:
393 self._call_tip_widget.show_call_info(call_info, doc)
399 self._call_tip_widget.show_call_info(call_info, doc)
394
400
395 def _handle_pyout(self, msg):
401 def _handle_pyout(self, msg):
396 """ Handle display hook output.
402 """ Handle display hook output.
397 """
403 """
398 self.log.debug("pyout: %s", msg.get('content', ''))
404 self.log.debug("pyout: %s", msg.get('content', ''))
399 if not self._hidden and self._is_from_this_session(msg):
405 if not self._hidden and self._is_from_this_session(msg):
400 text = msg['content']['data']
406 text = msg['content']['data']
401 self._append_plain_text(text + '\n', before_prompt=True)
407 self._append_plain_text(text + '\n', before_prompt=True)
402
408
403 def _handle_stream(self, msg):
409 def _handle_stream(self, msg):
404 """ Handle stdout, stderr, and stdin.
410 """ Handle stdout, stderr, and stdin.
405 """
411 """
406 self.log.debug("stream: %s", msg.get('content', ''))
412 self.log.debug("stream: %s", msg.get('content', ''))
407 if not self._hidden and self._is_from_this_session(msg):
413 if not self._hidden and self._is_from_this_session(msg):
408 # Most consoles treat tabs as being 8 space characters. Convert tabs
414 # Most consoles treat tabs as being 8 space characters. Convert tabs
409 # to spaces so that output looks as expected regardless of this
415 # to spaces so that output looks as expected regardless of this
410 # widget's tab width.
416 # widget's tab width.
411 text = msg['content']['data'].expandtabs(8)
417 text = msg['content']['data'].expandtabs(8)
412
418
413 self._append_plain_text(text, before_prompt=True)
419 self._append_plain_text(text, before_prompt=True)
414 self._control.moveCursor(QtGui.QTextCursor.End)
420 self._control.moveCursor(QtGui.QTextCursor.End)
415
421
416 def _handle_shutdown_reply(self, msg):
422 def _handle_shutdown_reply(self, msg):
417 """ Handle shutdown signal, only if from other console.
423 """ Handle shutdown signal, only if from other console.
418 """
424 """
419 self.log.debug("shutdown: %s", msg.get('content', ''))
425 self.log.debug("shutdown: %s", msg.get('content', ''))
420 if not self._hidden and not self._is_from_this_session(msg):
426 if not self._hidden and not self._is_from_this_session(msg):
421 if self._local_kernel:
427 if self._local_kernel:
422 if not msg['content']['restart']:
428 if not msg['content']['restart']:
423 self.exit_requested.emit(self)
429 self.exit_requested.emit(self)
424 else:
430 else:
425 # we just got notified of a restart!
431 # we just got notified of a restart!
426 time.sleep(0.25) # wait 1/4 sec to reset
432 time.sleep(0.25) # wait 1/4 sec to reset
427 # lest the request for a new prompt
433 # lest the request for a new prompt
428 # goes to the old kernel
434 # goes to the old kernel
429 self.reset()
435 self.reset()
430 else: # remote kernel, prompt on Kernel shutdown/reset
436 else: # remote kernel, prompt on Kernel shutdown/reset
431 title = self.window().windowTitle()
437 title = self.window().windowTitle()
432 if not msg['content']['restart']:
438 if not msg['content']['restart']:
433 reply = QtGui.QMessageBox.question(self, title,
439 reply = QtGui.QMessageBox.question(self, title,
434 "Kernel has been shutdown permanently. "
440 "Kernel has been shutdown permanently. "
435 "Close the Console?",
441 "Close the Console?",
436 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
442 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
437 if reply == QtGui.QMessageBox.Yes:
443 if reply == QtGui.QMessageBox.Yes:
438 self.exit_requested.emit(self)
444 self.exit_requested.emit(self)
439 else:
445 else:
440 reply = QtGui.QMessageBox.question(self, title,
446 reply = QtGui.QMessageBox.question(self, title,
441 "Kernel has been reset. Clear the Console?",
447 "Kernel has been reset. Clear the Console?",
442 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
448 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
443 if reply == QtGui.QMessageBox.Yes:
449 if reply == QtGui.QMessageBox.Yes:
444 time.sleep(0.25) # wait 1/4 sec to reset
450 time.sleep(0.25) # wait 1/4 sec to reset
445 # lest the request for a new prompt
451 # lest the request for a new prompt
446 # goes to the old kernel
452 # goes to the old kernel
447 self.reset()
453 self.reset()
448
454
449 def _started_channels(self):
455 def _started_channels(self):
450 """ Called when the KernelManager channels have started listening or
456 """ Called when the KernelManager channels have started listening or
451 when the frontend is assigned an already listening KernelManager.
457 when the frontend is assigned an already listening KernelManager.
452 """
458 """
453 self.reset()
459 self.reset()
454
460
455 #---------------------------------------------------------------------------
461 #---------------------------------------------------------------------------
456 # 'FrontendWidget' public interface
462 # 'FrontendWidget' public interface
457 #---------------------------------------------------------------------------
463 #---------------------------------------------------------------------------
458
464
459 def copy_raw(self):
465 def copy_raw(self):
460 """ Copy the currently selected text to the clipboard without attempting
466 """ Copy the currently selected text to the clipboard without attempting
461 to remove prompts or otherwise alter the text.
467 to remove prompts or otherwise alter the text.
462 """
468 """
463 self._control.copy()
469 self._control.copy()
464
470
465 def execute_file(self, path, hidden=False):
471 def execute_file(self, path, hidden=False):
466 """ Attempts to execute file with 'path'. If 'hidden', no output is
472 """ Attempts to execute file with 'path'. If 'hidden', no output is
467 shown.
473 shown.
468 """
474 """
469 self.execute('execfile(%r)' % path, hidden=hidden)
475 self.execute('execfile(%r)' % path, hidden=hidden)
470
476
471 def interrupt_kernel(self):
477 def interrupt_kernel(self):
472 """ Attempts to interrupt the running kernel.
478 """ Attempts to interrupt the running kernel.
473
479
474 Also unsets _reading flag, to avoid runtime errors
480 Also unsets _reading flag, to avoid runtime errors
475 if raw_input is called again.
481 if raw_input is called again.
476 """
482 """
477 if self.custom_interrupt:
483 if self.custom_interrupt:
478 self._reading = False
484 self._reading = False
479 self.custom_interrupt_requested.emit()
485 self.custom_interrupt_requested.emit()
480 elif self.kernel_manager.has_kernel:
486 elif self.kernel_manager.has_kernel:
481 self._reading = False
487 self._reading = False
482 self.kernel_manager.interrupt_kernel()
488 self.kernel_manager.interrupt_kernel()
483 else:
489 else:
484 self._append_plain_text('Kernel process is either remote or '
490 self._append_plain_text('Kernel process is either remote or '
485 'unspecified. Cannot interrupt.\n')
491 'unspecified. Cannot interrupt.\n')
486
492
487 def reset(self):
493 def reset(self):
488 """ Resets the widget to its initial state. Similar to ``clear``, but
494 """ Resets the widget to its initial state. Similar to ``clear``, but
489 also re-writes the banner and aborts execution if necessary.
495 also re-writes the banner and aborts execution if necessary.
490 """
496 """
491 if self._executing:
497 if self._executing:
492 self._executing = False
498 self._executing = False
493 self._request_info['execute'] = None
499 self._request_info['execute'] = None
494 self._reading = False
500 self._reading = False
495 self._highlighter.highlighting_on = False
501 self._highlighter.highlighting_on = False
496
502
497 self._control.clear()
503 self._control.clear()
498 self._append_plain_text(self.banner)
504 self._append_plain_text(self.banner)
499 # update output marker for stdout/stderr, so that startup
505 # update output marker for stdout/stderr, so that startup
500 # messages appear after banner:
506 # messages appear after banner:
501 self._append_before_prompt_pos = self._get_cursor().position()
507 self._append_before_prompt_pos = self._get_cursor().position()
502 self._show_interpreter_prompt()
508 self._show_interpreter_prompt()
503
509
504 def restart_kernel(self, message, now=False):
510 def restart_kernel(self, message, now=False):
505 """ Attempts to restart the running kernel.
511 """ Attempts to restart the running kernel.
506 """
512 """
507 # FIXME: now should be configurable via a checkbox in the dialog. Right
513 # FIXME: now should be configurable via a checkbox in the dialog. Right
508 # now at least the heartbeat path sets it to True and the manual restart
514 # now at least the heartbeat path sets it to True and the manual restart
509 # to False. But those should just be the pre-selected states of a
515 # to False. But those should just be the pre-selected states of a
510 # checkbox that the user could override if so desired. But I don't know
516 # checkbox that the user could override if so desired. But I don't know
511 # enough Qt to go implementing the checkbox now.
517 # enough Qt to go implementing the checkbox now.
512
518
513 if self.custom_restart:
519 if self.custom_restart:
514 self.custom_restart_requested.emit()
520 self.custom_restart_requested.emit()
515
521
516 elif self.kernel_manager.has_kernel:
522 elif self.kernel_manager.has_kernel:
517 # Pause the heart beat channel to prevent further warnings.
523 # Pause the heart beat channel to prevent further warnings.
518 self.kernel_manager.hb_channel.pause()
524 self.kernel_manager.hb_channel.pause()
519
525
520 # Prompt the user to restart the kernel. Un-pause the heartbeat if
526 # Prompt the user to restart the kernel. Un-pause the heartbeat if
521 # they decline. (If they accept, the heartbeat will be un-paused
527 # they decline. (If they accept, the heartbeat will be un-paused
522 # automatically when the kernel is restarted.)
528 # automatically when the kernel is restarted.)
523 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
529 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
524 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
530 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
525 message, buttons)
531 message, buttons)
526 if result == QtGui.QMessageBox.Yes:
532 if result == QtGui.QMessageBox.Yes:
527 try:
533 try:
528 self.kernel_manager.restart_kernel(now=now)
534 self.kernel_manager.restart_kernel(now=now)
529 except RuntimeError:
535 except RuntimeError:
530 self._append_plain_text('Kernel started externally. '
536 self._append_plain_text('Kernel started externally. '
531 'Cannot restart.\n')
537 'Cannot restart.\n')
532 else:
538 else:
533 self.reset()
539 self.reset()
534 else:
540 else:
535 self.kernel_manager.hb_channel.unpause()
541 self.kernel_manager.hb_channel.unpause()
536
542
537 else:
543 else:
538 self._append_plain_text('Kernel process is either remote or '
544 self._append_plain_text('Kernel process is either remote or '
539 'unspecified. Cannot restart.\n')
545 'unspecified. Cannot restart.\n')
540
546
541 #---------------------------------------------------------------------------
547 #---------------------------------------------------------------------------
542 # 'FrontendWidget' protected interface
548 # 'FrontendWidget' protected interface
543 #---------------------------------------------------------------------------
549 #---------------------------------------------------------------------------
544
550
545 def _call_tip(self):
551 def _call_tip(self):
546 """ Shows a call tip, if appropriate, at the current cursor location.
552 """ Shows a call tip, if appropriate, at the current cursor location.
547 """
553 """
548 # Decide if it makes sense to show a call tip
554 # Decide if it makes sense to show a call tip
549 if not self.enable_calltips:
555 if not self.enable_calltips:
550 return False
556 return False
551 cursor = self._get_cursor()
557 cursor = self._get_cursor()
552 cursor.movePosition(QtGui.QTextCursor.Left)
558 cursor.movePosition(QtGui.QTextCursor.Left)
553 if cursor.document().characterAt(cursor.position()) != '(':
559 if cursor.document().characterAt(cursor.position()) != '(':
554 return False
560 return False
555 context = self._get_context(cursor)
561 context = self._get_context(cursor)
556 if not context:
562 if not context:
557 return False
563 return False
558
564
559 # Send the metadata request to the kernel
565 # Send the metadata request to the kernel
560 name = '.'.join(context)
566 name = '.'.join(context)
561 msg_id = self.kernel_manager.shell_channel.object_info(name)
567 msg_id = self.kernel_manager.shell_channel.object_info(name)
562 pos = self._get_cursor().position()
568 pos = self._get_cursor().position()
563 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
569 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
564 return True
570 return True
565
571
566 def _complete(self):
572 def _complete(self):
567 """ Performs completion at the current cursor location.
573 """ Performs completion at the current cursor location.
568 """
574 """
569 context = self._get_context()
575 context = self._get_context()
570 if context:
576 if context:
571 # Send the completion request to the kernel
577 # Send the completion request to the kernel
572 msg_id = self.kernel_manager.shell_channel.complete(
578 msg_id = self.kernel_manager.shell_channel.complete(
573 '.'.join(context), # text
579 '.'.join(context), # text
574 self._get_input_buffer_cursor_line(), # line
580 self._get_input_buffer_cursor_line(), # line
575 self._get_input_buffer_cursor_column(), # cursor_pos
581 self._get_input_buffer_cursor_column(), # cursor_pos
576 self.input_buffer) # block
582 self.input_buffer) # block
577 pos = self._get_cursor().position()
583 pos = self._get_cursor().position()
578 info = self._CompletionRequest(msg_id, pos)
584 info = self._CompletionRequest(msg_id, pos)
579 self._request_info['complete'] = info
585 self._request_info['complete'] = info
580
586
581 def _get_context(self, cursor=None):
587 def _get_context(self, cursor=None):
582 """ Gets the context for the specified cursor (or the current cursor
588 """ Gets the context for the specified cursor (or the current cursor
583 if none is specified).
589 if none is specified).
584 """
590 """
585 if cursor is None:
591 if cursor is None:
586 cursor = self._get_cursor()
592 cursor = self._get_cursor()
587 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
593 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
588 QtGui.QTextCursor.KeepAnchor)
594 QtGui.QTextCursor.KeepAnchor)
589 text = cursor.selection().toPlainText()
595 text = cursor.selection().toPlainText()
590 return self._completion_lexer.get_context(text)
596 return self._completion_lexer.get_context(text)
591
597
592 def _process_execute_abort(self, msg):
598 def _process_execute_abort(self, msg):
593 """ Process a reply for an aborted execution request.
599 """ Process a reply for an aborted execution request.
594 """
600 """
595 self._append_plain_text("ERROR: execution aborted\n")
601 self._append_plain_text("ERROR: execution aborted\n")
596
602
597 def _process_execute_error(self, msg):
603 def _process_execute_error(self, msg):
598 """ Process a reply for an execution request that resulted in an error.
604 """ Process a reply for an execution request that resulted in an error.
599 """
605 """
600 content = msg['content']
606 content = msg['content']
601 # If a SystemExit is passed along, this means exit() was called - also
607 # If a SystemExit is passed along, this means exit() was called - also
602 # all the ipython %exit magic syntax of '-k' to be used to keep
608 # all the ipython %exit magic syntax of '-k' to be used to keep
603 # the kernel running
609 # the kernel running
604 if content['ename']=='SystemExit':
610 if content['ename']=='SystemExit':
605 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
611 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
606 self._keep_kernel_on_exit = keepkernel
612 self._keep_kernel_on_exit = keepkernel
607 self.exit_requested.emit(self)
613 self.exit_requested.emit(self)
608 else:
614 else:
609 traceback = ''.join(content['traceback'])
615 traceback = ''.join(content['traceback'])
610 self._append_plain_text(traceback)
616 self._append_plain_text(traceback)
611
617
612 def _process_execute_ok(self, msg):
618 def _process_execute_ok(self, msg):
613 """ Process a reply for a successful execution equest.
619 """ Process a reply for a successful execution equest.
614 """
620 """
615 payload = msg['content']['payload']
621 payload = msg['content']['payload']
616 for item in payload:
622 for item in payload:
617 if not self._process_execute_payload(item):
623 if not self._process_execute_payload(item):
618 warning = 'Warning: received unknown payload of type %s'
624 warning = 'Warning: received unknown payload of type %s'
619 print(warning % repr(item['source']))
625 print(warning % repr(item['source']))
620
626
621 def _process_execute_payload(self, item):
627 def _process_execute_payload(self, item):
622 """ Process a single payload item from the list of payload items in an
628 """ Process a single payload item from the list of payload items in an
623 execution reply. Returns whether the payload was handled.
629 execution reply. Returns whether the payload was handled.
624 """
630 """
625 # The basic FrontendWidget doesn't handle payloads, as they are a
631 # The basic FrontendWidget doesn't handle payloads, as they are a
626 # mechanism for going beyond the standard Python interpreter model.
632 # mechanism for going beyond the standard Python interpreter model.
627 return False
633 return False
628
634
629 def _show_interpreter_prompt(self):
635 def _show_interpreter_prompt(self):
630 """ Shows a prompt for the interpreter.
636 """ Shows a prompt for the interpreter.
631 """
637 """
632 self._show_prompt('>>> ')
638 self._show_prompt('>>> ')
633
639
634 def _show_interpreter_prompt_for_reply(self, msg):
640 def _show_interpreter_prompt_for_reply(self, msg):
635 """ Shows a prompt for the interpreter given an 'execute_reply' message.
641 """ Shows a prompt for the interpreter given an 'execute_reply' message.
636 """
642 """
637 self._show_interpreter_prompt()
643 self._show_interpreter_prompt()
638
644
639 #------ Signal handlers ----------------------------------------------------
645 #------ Signal handlers ----------------------------------------------------
640
646
641 def _document_contents_change(self, position, removed, added):
647 def _document_contents_change(self, position, removed, added):
642 """ Called whenever the document's content changes. Display a call tip
648 """ Called whenever the document's content changes. Display a call tip
643 if appropriate.
649 if appropriate.
644 """
650 """
645 # Calculate where the cursor should be *after* the change:
651 # Calculate where the cursor should be *after* the change:
646 position += added
652 position += added
647
653
648 document = self._control.document()
654 document = self._control.document()
649 if position == self._get_cursor().position():
655 if position == self._get_cursor().position():
650 self._call_tip()
656 self._call_tip()
651
657
652 #------ Trait default initializers -----------------------------------------
658 #------ Trait default initializers -----------------------------------------
653
659
654 def _banner_default(self):
660 def _banner_default(self):
655 """ Returns the standard Python banner.
661 """ Returns the standard Python banner.
656 """
662 """
657 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
663 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
658 '"license" for more information.'
664 '"license" for more information.'
659 return banner % (sys.version, sys.platform)
665 return banner % (sys.version, sys.platform)
General Comments 0
You need to be logged in to leave comments. Login now