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