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