##// END OF EJS Templates
Merge pull request #5965 from minrk/qt-shutdown...
Thomas Kluyver -
r16976:06b3624f merge
parent child Browse files
Show More
@@ -1,821 +1,821 b''
1 """Frontend widget for the Qt Console"""
1 """Frontend widget for the Qt Console"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import print_function
6 from __future__ import print_function
7
7
8 from collections import namedtuple
8 from collections import namedtuple
9 import sys
9 import sys
10 import uuid
10 import uuid
11
11
12 from IPython.external import qt
12 from IPython.external import qt
13 from IPython.external.qt import QtCore, QtGui
13 from IPython.external.qt import QtCore, QtGui
14 from IPython.utils import py3compat
14 from IPython.utils import py3compat
15 from IPython.utils.importstring import import_item
15 from IPython.utils.importstring import import_item
16
16
17 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
17 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
18 from IPython.core.inputtransformer import classic_prompt
18 from IPython.core.inputtransformer import classic_prompt
19 from IPython.core.oinspect import call_tip
19 from IPython.core.oinspect import call_tip
20 from IPython.qt.base_frontend_mixin import BaseFrontendMixin
20 from IPython.qt.base_frontend_mixin import BaseFrontendMixin
21 from IPython.utils.traitlets import Any, Bool, Instance, Unicode, DottedObjectName
21 from IPython.utils.traitlets import Any, Bool, Instance, Unicode, DottedObjectName
22 from .bracket_matcher import BracketMatcher
22 from .bracket_matcher import BracketMatcher
23 from .call_tip_widget import CallTipWidget
23 from .call_tip_widget import CallTipWidget
24 from .completion_lexer import CompletionLexer
24 from .completion_lexer import CompletionLexer
25 from .history_console_widget import HistoryConsoleWidget
25 from .history_console_widget import HistoryConsoleWidget
26 from .pygments_highlighter import PygmentsHighlighter
26 from .pygments_highlighter import PygmentsHighlighter
27
27
28
28
29 class FrontendHighlighter(PygmentsHighlighter):
29 class FrontendHighlighter(PygmentsHighlighter):
30 """ A PygmentsHighlighter that understands and ignores prompts.
30 """ A PygmentsHighlighter that understands and ignores prompts.
31 """
31 """
32
32
33 def __init__(self, frontend, lexer=None):
33 def __init__(self, frontend, lexer=None):
34 super(FrontendHighlighter, self).__init__(frontend._control.document(), lexer=lexer)
34 super(FrontendHighlighter, self).__init__(frontend._control.document(), lexer=lexer)
35 self._current_offset = 0
35 self._current_offset = 0
36 self._frontend = frontend
36 self._frontend = frontend
37 self.highlighting_on = False
37 self.highlighting_on = False
38
38
39 def highlightBlock(self, string):
39 def highlightBlock(self, string):
40 """ Highlight a block of text. Reimplemented to highlight selectively.
40 """ Highlight a block of text. Reimplemented to highlight selectively.
41 """
41 """
42 if not self.highlighting_on:
42 if not self.highlighting_on:
43 return
43 return
44
44
45 # The input to this function is a unicode string that may contain
45 # The input to this function is a unicode string that may contain
46 # paragraph break characters, non-breaking spaces, etc. Here we acquire
46 # paragraph break characters, non-breaking spaces, etc. Here we acquire
47 # the string as plain text so we can compare it.
47 # the string as plain text so we can compare it.
48 current_block = self.currentBlock()
48 current_block = self.currentBlock()
49 string = self._frontend._get_block_plain_text(current_block)
49 string = self._frontend._get_block_plain_text(current_block)
50
50
51 # Decide whether to check for the regular or continuation prompt.
51 # Decide whether to check for the regular or continuation prompt.
52 if current_block.contains(self._frontend._prompt_pos):
52 if current_block.contains(self._frontend._prompt_pos):
53 prompt = self._frontend._prompt
53 prompt = self._frontend._prompt
54 else:
54 else:
55 prompt = self._frontend._continuation_prompt
55 prompt = self._frontend._continuation_prompt
56
56
57 # Only highlight if we can identify a prompt, but make sure not to
57 # Only highlight if we can identify a prompt, but make sure not to
58 # highlight the prompt.
58 # highlight the prompt.
59 if string.startswith(prompt):
59 if string.startswith(prompt):
60 self._current_offset = len(prompt)
60 self._current_offset = len(prompt)
61 string = string[len(prompt):]
61 string = string[len(prompt):]
62 super(FrontendHighlighter, self).highlightBlock(string)
62 super(FrontendHighlighter, self).highlightBlock(string)
63
63
64 def rehighlightBlock(self, block):
64 def rehighlightBlock(self, block):
65 """ Reimplemented to temporarily enable highlighting if disabled.
65 """ Reimplemented to temporarily enable highlighting if disabled.
66 """
66 """
67 old = self.highlighting_on
67 old = self.highlighting_on
68 self.highlighting_on = True
68 self.highlighting_on = True
69 super(FrontendHighlighter, self).rehighlightBlock(block)
69 super(FrontendHighlighter, self).rehighlightBlock(block)
70 self.highlighting_on = old
70 self.highlighting_on = old
71
71
72 def setFormat(self, start, count, format):
72 def setFormat(self, start, count, format):
73 """ Reimplemented to highlight selectively.
73 """ Reimplemented to highlight selectively.
74 """
74 """
75 start += self._current_offset
75 start += self._current_offset
76 super(FrontendHighlighter, self).setFormat(start, count, format)
76 super(FrontendHighlighter, self).setFormat(start, count, format)
77
77
78
78
79 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
79 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
80 """ A Qt frontend for a generic Python kernel.
80 """ A Qt frontend for a generic Python kernel.
81 """
81 """
82
82
83 # The text to show when the kernel is (re)started.
83 # The text to show when the kernel is (re)started.
84 banner = Unicode(config=True)
84 banner = Unicode(config=True)
85 kernel_banner = Unicode()
85 kernel_banner = Unicode()
86
86
87 # An option and corresponding signal for overriding the default kernel
87 # An option and corresponding signal for overriding the default kernel
88 # interrupt behavior.
88 # interrupt behavior.
89 custom_interrupt = Bool(False)
89 custom_interrupt = Bool(False)
90 custom_interrupt_requested = QtCore.Signal()
90 custom_interrupt_requested = QtCore.Signal()
91
91
92 # An option and corresponding signals for overriding the default kernel
92 # An option and corresponding signals for overriding the default kernel
93 # restart behavior.
93 # restart behavior.
94 custom_restart = Bool(False)
94 custom_restart = Bool(False)
95 custom_restart_kernel_died = QtCore.Signal(float)
95 custom_restart_kernel_died = QtCore.Signal(float)
96 custom_restart_requested = QtCore.Signal()
96 custom_restart_requested = QtCore.Signal()
97
97
98 # Whether to automatically show calltips on open-parentheses.
98 # Whether to automatically show calltips on open-parentheses.
99 enable_calltips = Bool(True, config=True,
99 enable_calltips = Bool(True, config=True,
100 help="Whether to draw information calltips on open-parentheses.")
100 help="Whether to draw information calltips on open-parentheses.")
101
101
102 clear_on_kernel_restart = Bool(True, config=True,
102 clear_on_kernel_restart = Bool(True, config=True,
103 help="Whether to clear the console when the kernel is restarted")
103 help="Whether to clear the console when the kernel is restarted")
104
104
105 confirm_restart = Bool(True, config=True,
105 confirm_restart = Bool(True, config=True,
106 help="Whether to ask for user confirmation when restarting kernel")
106 help="Whether to ask for user confirmation when restarting kernel")
107
107
108 lexer_class = DottedObjectName(config=True,
108 lexer_class = DottedObjectName(config=True,
109 help="The pygments lexer class to use."
109 help="The pygments lexer class to use."
110 )
110 )
111 def _lexer_class_changed(self, name, old, new):
111 def _lexer_class_changed(self, name, old, new):
112 lexer_class = import_item(new)
112 lexer_class = import_item(new)
113 self.lexer = lexer_class()
113 self.lexer = lexer_class()
114
114
115 def _lexer_class_default(self):
115 def _lexer_class_default(self):
116 if py3compat.PY3:
116 if py3compat.PY3:
117 return 'pygments.lexers.Python3Lexer'
117 return 'pygments.lexers.Python3Lexer'
118 else:
118 else:
119 return 'pygments.lexers.PythonLexer'
119 return 'pygments.lexers.PythonLexer'
120
120
121 lexer = Any()
121 lexer = Any()
122 def _lexer_default(self):
122 def _lexer_default(self):
123 lexer_class = import_item(self.lexer_class)
123 lexer_class = import_item(self.lexer_class)
124 return lexer_class()
124 return lexer_class()
125
125
126 # Emitted when a user visible 'execute_request' has been submitted to the
126 # Emitted when a user visible 'execute_request' has been submitted to the
127 # kernel from the FrontendWidget. Contains the code to be executed.
127 # kernel from the FrontendWidget. Contains the code to be executed.
128 executing = QtCore.Signal(object)
128 executing = QtCore.Signal(object)
129
129
130 # Emitted when a user-visible 'execute_reply' has been received from the
130 # Emitted when a user-visible 'execute_reply' has been received from the
131 # kernel and processed by the FrontendWidget. Contains the response message.
131 # kernel and processed by the FrontendWidget. Contains the response message.
132 executed = QtCore.Signal(object)
132 executed = QtCore.Signal(object)
133
133
134 # Emitted when an exit request has been received from the kernel.
134 # Emitted when an exit request has been received from the kernel.
135 exit_requested = QtCore.Signal(object)
135 exit_requested = QtCore.Signal(object)
136
136
137 # Protected class variables.
137 # Protected class variables.
138 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
138 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
139 logical_line_transforms=[],
139 logical_line_transforms=[],
140 python_line_transforms=[],
140 python_line_transforms=[],
141 )
141 )
142 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
142 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
143 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
143 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
144 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
144 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
145 _input_splitter_class = InputSplitter
145 _input_splitter_class = InputSplitter
146 _local_kernel = False
146 _local_kernel = False
147 _highlighter = Instance(FrontendHighlighter)
147 _highlighter = Instance(FrontendHighlighter)
148
148
149 #---------------------------------------------------------------------------
149 #---------------------------------------------------------------------------
150 # 'object' interface
150 # 'object' interface
151 #---------------------------------------------------------------------------
151 #---------------------------------------------------------------------------
152
152
153 def __init__(self, *args, **kw):
153 def __init__(self, *args, **kw):
154 super(FrontendWidget, self).__init__(*args, **kw)
154 super(FrontendWidget, self).__init__(*args, **kw)
155 # FIXME: remove this when PySide min version is updated past 1.0.7
155 # FIXME: remove this when PySide min version is updated past 1.0.7
156 # forcefully disable calltips if PySide is < 1.0.7, because they crash
156 # forcefully disable calltips if PySide is < 1.0.7, because they crash
157 if qt.QT_API == qt.QT_API_PYSIDE:
157 if qt.QT_API == qt.QT_API_PYSIDE:
158 import PySide
158 import PySide
159 if PySide.__version_info__ < (1,0,7):
159 if PySide.__version_info__ < (1,0,7):
160 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
160 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
161 self.enable_calltips = False
161 self.enable_calltips = False
162
162
163 # FrontendWidget protected variables.
163 # FrontendWidget protected variables.
164 self._bracket_matcher = BracketMatcher(self._control)
164 self._bracket_matcher = BracketMatcher(self._control)
165 self._call_tip_widget = CallTipWidget(self._control)
165 self._call_tip_widget = CallTipWidget(self._control)
166 self._completion_lexer = CompletionLexer(self.lexer)
166 self._completion_lexer = CompletionLexer(self.lexer)
167 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
167 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
168 self._hidden = False
168 self._hidden = False
169 self._highlighter = FrontendHighlighter(self, lexer=self.lexer)
169 self._highlighter = FrontendHighlighter(self, lexer=self.lexer)
170 self._input_splitter = self._input_splitter_class()
170 self._input_splitter = self._input_splitter_class()
171 self._kernel_manager = None
171 self._kernel_manager = None
172 self._kernel_client = None
172 self._kernel_client = None
173 self._request_info = {}
173 self._request_info = {}
174 self._request_info['execute'] = {};
174 self._request_info['execute'] = {};
175 self._callback_dict = {}
175 self._callback_dict = {}
176
176
177 # Configure the ConsoleWidget.
177 # Configure the ConsoleWidget.
178 self.tab_width = 4
178 self.tab_width = 4
179 self._set_continuation_prompt('... ')
179 self._set_continuation_prompt('... ')
180
180
181 # Configure the CallTipWidget.
181 # Configure the CallTipWidget.
182 self._call_tip_widget.setFont(self.font)
182 self._call_tip_widget.setFont(self.font)
183 self.font_changed.connect(self._call_tip_widget.setFont)
183 self.font_changed.connect(self._call_tip_widget.setFont)
184
184
185 # Configure actions.
185 # Configure actions.
186 action = self._copy_raw_action
186 action = self._copy_raw_action
187 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
187 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
188 action.setEnabled(False)
188 action.setEnabled(False)
189 action.setShortcut(QtGui.QKeySequence(key))
189 action.setShortcut(QtGui.QKeySequence(key))
190 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
190 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
191 action.triggered.connect(self.copy_raw)
191 action.triggered.connect(self.copy_raw)
192 self.copy_available.connect(action.setEnabled)
192 self.copy_available.connect(action.setEnabled)
193 self.addAction(action)
193 self.addAction(action)
194
194
195 # Connect signal handlers.
195 # Connect signal handlers.
196 document = self._control.document()
196 document = self._control.document()
197 document.contentsChange.connect(self._document_contents_change)
197 document.contentsChange.connect(self._document_contents_change)
198
198
199 # Set flag for whether we are connected via localhost.
199 # Set flag for whether we are connected via localhost.
200 self._local_kernel = kw.get('local_kernel',
200 self._local_kernel = kw.get('local_kernel',
201 FrontendWidget._local_kernel)
201 FrontendWidget._local_kernel)
202
202
203 # Whether or not a clear_output call is pending new output.
203 # Whether or not a clear_output call is pending new output.
204 self._pending_clearoutput = False
204 self._pending_clearoutput = False
205
205
206 #---------------------------------------------------------------------------
206 #---------------------------------------------------------------------------
207 # 'ConsoleWidget' public interface
207 # 'ConsoleWidget' public interface
208 #---------------------------------------------------------------------------
208 #---------------------------------------------------------------------------
209
209
210 def copy(self):
210 def copy(self):
211 """ Copy the currently selected text to the clipboard, removing prompts.
211 """ Copy the currently selected text to the clipboard, removing prompts.
212 """
212 """
213 if self._page_control is not None and self._page_control.hasFocus():
213 if self._page_control is not None and self._page_control.hasFocus():
214 self._page_control.copy()
214 self._page_control.copy()
215 elif self._control.hasFocus():
215 elif self._control.hasFocus():
216 text = self._control.textCursor().selection().toPlainText()
216 text = self._control.textCursor().selection().toPlainText()
217 if text:
217 if text:
218 text = self._prompt_transformer.transform_cell(text)
218 text = self._prompt_transformer.transform_cell(text)
219 QtGui.QApplication.clipboard().setText(text)
219 QtGui.QApplication.clipboard().setText(text)
220 else:
220 else:
221 self.log.debug("frontend widget : unknown copy target")
221 self.log.debug("frontend widget : unknown copy target")
222
222
223 #---------------------------------------------------------------------------
223 #---------------------------------------------------------------------------
224 # 'ConsoleWidget' abstract interface
224 # 'ConsoleWidget' abstract interface
225 #---------------------------------------------------------------------------
225 #---------------------------------------------------------------------------
226
226
227 def _is_complete(self, source, interactive):
227 def _is_complete(self, source, interactive):
228 """ Returns whether 'source' can be completely processed and a new
228 """ Returns whether 'source' can be completely processed and a new
229 prompt created. When triggered by an Enter/Return key press,
229 prompt created. When triggered by an Enter/Return key press,
230 'interactive' is True; otherwise, it is False.
230 'interactive' is True; otherwise, it is False.
231 """
231 """
232 self._input_splitter.reset()
232 self._input_splitter.reset()
233 try:
233 try:
234 complete = self._input_splitter.push(source)
234 complete = self._input_splitter.push(source)
235 except SyntaxError:
235 except SyntaxError:
236 return True
236 return True
237 if interactive:
237 if interactive:
238 complete = not self._input_splitter.push_accepts_more()
238 complete = not self._input_splitter.push_accepts_more()
239 return complete
239 return complete
240
240
241 def _execute(self, source, hidden):
241 def _execute(self, source, hidden):
242 """ Execute 'source'. If 'hidden', do not show any output.
242 """ Execute 'source'. If 'hidden', do not show any output.
243
243
244 See parent class :meth:`execute` docstring for full details.
244 See parent class :meth:`execute` docstring for full details.
245 """
245 """
246 msg_id = self.kernel_client.execute(source, hidden)
246 msg_id = self.kernel_client.execute(source, hidden)
247 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
247 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
248 self._hidden = hidden
248 self._hidden = hidden
249 if not hidden:
249 if not hidden:
250 self.executing.emit(source)
250 self.executing.emit(source)
251
251
252 def _prompt_started_hook(self):
252 def _prompt_started_hook(self):
253 """ Called immediately after a new prompt is displayed.
253 """ Called immediately after a new prompt is displayed.
254 """
254 """
255 if not self._reading:
255 if not self._reading:
256 self._highlighter.highlighting_on = True
256 self._highlighter.highlighting_on = True
257
257
258 def _prompt_finished_hook(self):
258 def _prompt_finished_hook(self):
259 """ Called immediately after a prompt is finished, i.e. when some input
259 """ Called immediately after a prompt is finished, i.e. when some input
260 will be processed and a new prompt displayed.
260 will be processed and a new prompt displayed.
261 """
261 """
262 # Flush all state from the input splitter so the next round of
262 # Flush all state from the input splitter so the next round of
263 # reading input starts with a clean buffer.
263 # reading input starts with a clean buffer.
264 self._input_splitter.reset()
264 self._input_splitter.reset()
265
265
266 if not self._reading:
266 if not self._reading:
267 self._highlighter.highlighting_on = False
267 self._highlighter.highlighting_on = False
268
268
269 def _tab_pressed(self):
269 def _tab_pressed(self):
270 """ Called when the tab key is pressed. Returns whether to continue
270 """ Called when the tab key is pressed. Returns whether to continue
271 processing the event.
271 processing the event.
272 """
272 """
273 # Perform tab completion if:
273 # Perform tab completion if:
274 # 1) The cursor is in the input buffer.
274 # 1) The cursor is in the input buffer.
275 # 2) There is a non-whitespace character before the cursor.
275 # 2) There is a non-whitespace character before the cursor.
276 text = self._get_input_buffer_cursor_line()
276 text = self._get_input_buffer_cursor_line()
277 if text is None:
277 if text is None:
278 return False
278 return False
279 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
279 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
280 if complete:
280 if complete:
281 self._complete()
281 self._complete()
282 return not complete
282 return not complete
283
283
284 #---------------------------------------------------------------------------
284 #---------------------------------------------------------------------------
285 # 'ConsoleWidget' protected interface
285 # 'ConsoleWidget' protected interface
286 #---------------------------------------------------------------------------
286 #---------------------------------------------------------------------------
287
287
288 def _context_menu_make(self, pos):
288 def _context_menu_make(self, pos):
289 """ Reimplemented to add an action for raw copy.
289 """ Reimplemented to add an action for raw copy.
290 """
290 """
291 menu = super(FrontendWidget, self)._context_menu_make(pos)
291 menu = super(FrontendWidget, self)._context_menu_make(pos)
292 for before_action in menu.actions():
292 for before_action in menu.actions():
293 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
293 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
294 QtGui.QKeySequence.ExactMatch:
294 QtGui.QKeySequence.ExactMatch:
295 menu.insertAction(before_action, self._copy_raw_action)
295 menu.insertAction(before_action, self._copy_raw_action)
296 break
296 break
297 return menu
297 return menu
298
298
299 def request_interrupt_kernel(self):
299 def request_interrupt_kernel(self):
300 if self._executing:
300 if self._executing:
301 self.interrupt_kernel()
301 self.interrupt_kernel()
302
302
303 def request_restart_kernel(self):
303 def request_restart_kernel(self):
304 message = 'Are you sure you want to restart the kernel?'
304 message = 'Are you sure you want to restart the kernel?'
305 self.restart_kernel(message, now=False)
305 self.restart_kernel(message, now=False)
306
306
307 def _event_filter_console_keypress(self, event):
307 def _event_filter_console_keypress(self, event):
308 """ Reimplemented for execution interruption and smart backspace.
308 """ Reimplemented for execution interruption and smart backspace.
309 """
309 """
310 key = event.key()
310 key = event.key()
311 if self._control_key_down(event.modifiers(), include_command=False):
311 if self._control_key_down(event.modifiers(), include_command=False):
312
312
313 if key == QtCore.Qt.Key_C and self._executing:
313 if key == QtCore.Qt.Key_C and self._executing:
314 self.request_interrupt_kernel()
314 self.request_interrupt_kernel()
315 return True
315 return True
316
316
317 elif key == QtCore.Qt.Key_Period:
317 elif key == QtCore.Qt.Key_Period:
318 self.request_restart_kernel()
318 self.request_restart_kernel()
319 return True
319 return True
320
320
321 elif not event.modifiers() & QtCore.Qt.AltModifier:
321 elif not event.modifiers() & QtCore.Qt.AltModifier:
322
322
323 # Smart backspace: remove four characters in one backspace if:
323 # Smart backspace: remove four characters in one backspace if:
324 # 1) everything left of the cursor is whitespace
324 # 1) everything left of the cursor is whitespace
325 # 2) the four characters immediately left of the cursor are spaces
325 # 2) the four characters immediately left of the cursor are spaces
326 if key == QtCore.Qt.Key_Backspace:
326 if key == QtCore.Qt.Key_Backspace:
327 col = self._get_input_buffer_cursor_column()
327 col = self._get_input_buffer_cursor_column()
328 cursor = self._control.textCursor()
328 cursor = self._control.textCursor()
329 if col > 3 and not cursor.hasSelection():
329 if col > 3 and not cursor.hasSelection():
330 text = self._get_input_buffer_cursor_line()[:col]
330 text = self._get_input_buffer_cursor_line()[:col]
331 if text.endswith(' ') and not text.strip():
331 if text.endswith(' ') and not text.strip():
332 cursor.movePosition(QtGui.QTextCursor.Left,
332 cursor.movePosition(QtGui.QTextCursor.Left,
333 QtGui.QTextCursor.KeepAnchor, 4)
333 QtGui.QTextCursor.KeepAnchor, 4)
334 cursor.removeSelectedText()
334 cursor.removeSelectedText()
335 return True
335 return True
336
336
337 return super(FrontendWidget, self)._event_filter_console_keypress(event)
337 return super(FrontendWidget, self)._event_filter_console_keypress(event)
338
338
339 def _insert_continuation_prompt(self, cursor):
339 def _insert_continuation_prompt(self, cursor):
340 """ Reimplemented for auto-indentation.
340 """ Reimplemented for auto-indentation.
341 """
341 """
342 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
342 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
343 cursor.insertText(' ' * self._input_splitter.indent_spaces)
343 cursor.insertText(' ' * self._input_splitter.indent_spaces)
344
344
345 #---------------------------------------------------------------------------
345 #---------------------------------------------------------------------------
346 # 'BaseFrontendMixin' abstract interface
346 # 'BaseFrontendMixin' abstract interface
347 #---------------------------------------------------------------------------
347 #---------------------------------------------------------------------------
348 def _handle_clear_output(self, msg):
348 def _handle_clear_output(self, msg):
349 """Handle clear output messages."""
349 """Handle clear output messages."""
350 if not self._hidden and self._is_from_this_session(msg):
350 if not self._hidden and self._is_from_this_session(msg):
351 wait = msg['content'].get('wait', True)
351 wait = msg['content'].get('wait', True)
352 if wait:
352 if wait:
353 self._pending_clearoutput = True
353 self._pending_clearoutput = True
354 else:
354 else:
355 self.clear_output()
355 self.clear_output()
356
356
357 def _handle_complete_reply(self, rep):
357 def _handle_complete_reply(self, rep):
358 """ Handle replies for tab completion.
358 """ Handle replies for tab completion.
359 """
359 """
360 self.log.debug("complete: %s", rep.get('content', ''))
360 self.log.debug("complete: %s", rep.get('content', ''))
361 cursor = self._get_cursor()
361 cursor = self._get_cursor()
362 info = self._request_info.get('complete')
362 info = self._request_info.get('complete')
363 if info and info.id == rep['parent_header']['msg_id'] and \
363 if info and info.id == rep['parent_header']['msg_id'] and \
364 info.pos == cursor.position():
364 info.pos == cursor.position():
365 text = '.'.join(self._get_context())
365 text = '.'.join(self._get_context())
366 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
366 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
367 self._complete_with_items(cursor, rep['content']['matches'])
367 self._complete_with_items(cursor, rep['content']['matches'])
368
368
369 def _silent_exec_callback(self, expr, callback):
369 def _silent_exec_callback(self, expr, callback):
370 """Silently execute `expr` in the kernel and call `callback` with reply
370 """Silently execute `expr` in the kernel and call `callback` with reply
371
371
372 the `expr` is evaluated silently in the kernel (without) output in
372 the `expr` is evaluated silently in the kernel (without) output in
373 the frontend. Call `callback` with the
373 the frontend. Call `callback` with the
374 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
374 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
375
375
376 Parameters
376 Parameters
377 ----------
377 ----------
378 expr : string
378 expr : string
379 valid string to be executed by the kernel.
379 valid string to be executed by the kernel.
380 callback : function
380 callback : function
381 function accepting one argument, as a string. The string will be
381 function accepting one argument, as a string. The string will be
382 the `repr` of the result of evaluating `expr`
382 the `repr` of the result of evaluating `expr`
383
383
384 The `callback` is called with the `repr()` of the result of `expr` as
384 The `callback` is called with the `repr()` of the result of `expr` as
385 first argument. To get the object, do `eval()` on the passed value.
385 first argument. To get the object, do `eval()` on the passed value.
386
386
387 See Also
387 See Also
388 --------
388 --------
389 _handle_exec_callback : private method, deal with calling callback with reply
389 _handle_exec_callback : private method, deal with calling callback with reply
390
390
391 """
391 """
392
392
393 # generate uuid, which would be used as an indication of whether or
393 # generate uuid, which would be used as an indication of whether or
394 # not the unique request originated from here (can use msg id ?)
394 # not the unique request originated from here (can use msg id ?)
395 local_uuid = str(uuid.uuid1())
395 local_uuid = str(uuid.uuid1())
396 msg_id = self.kernel_client.execute('',
396 msg_id = self.kernel_client.execute('',
397 silent=True, user_expressions={ local_uuid:expr })
397 silent=True, user_expressions={ local_uuid:expr })
398 self._callback_dict[local_uuid] = callback
398 self._callback_dict[local_uuid] = callback
399 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
399 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
400
400
401 def _handle_exec_callback(self, msg):
401 def _handle_exec_callback(self, msg):
402 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
402 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
403
403
404 Parameters
404 Parameters
405 ----------
405 ----------
406 msg : raw message send by the kernel containing an `user_expressions`
406 msg : raw message send by the kernel containing an `user_expressions`
407 and having a 'silent_exec_callback' kind.
407 and having a 'silent_exec_callback' kind.
408
408
409 Notes
409 Notes
410 -----
410 -----
411 This function will look for a `callback` associated with the
411 This function will look for a `callback` associated with the
412 corresponding message id. Association has been made by
412 corresponding message id. Association has been made by
413 `_silent_exec_callback`. `callback` is then called with the `repr()`
413 `_silent_exec_callback`. `callback` is then called with the `repr()`
414 of the value of corresponding `user_expressions` as argument.
414 of the value of corresponding `user_expressions` as argument.
415 `callback` is then removed from the known list so that any message
415 `callback` is then removed from the known list so that any message
416 coming again with the same id won't trigger it.
416 coming again with the same id won't trigger it.
417
417
418 """
418 """
419
419
420 user_exp = msg['content'].get('user_expressions')
420 user_exp = msg['content'].get('user_expressions')
421 if not user_exp:
421 if not user_exp:
422 return
422 return
423 for expression in user_exp:
423 for expression in user_exp:
424 if expression in self._callback_dict:
424 if expression in self._callback_dict:
425 self._callback_dict.pop(expression)(user_exp[expression])
425 self._callback_dict.pop(expression)(user_exp[expression])
426
426
427 def _handle_execute_reply(self, msg):
427 def _handle_execute_reply(self, msg):
428 """ Handles replies for code execution.
428 """ Handles replies for code execution.
429 """
429 """
430 self.log.debug("execute: %s", msg.get('content', ''))
430 self.log.debug("execute: %s", msg.get('content', ''))
431 msg_id = msg['parent_header']['msg_id']
431 msg_id = msg['parent_header']['msg_id']
432 info = self._request_info['execute'].get(msg_id)
432 info = self._request_info['execute'].get(msg_id)
433 # unset reading flag, because if execute finished, raw_input can't
433 # unset reading flag, because if execute finished, raw_input can't
434 # still be pending.
434 # still be pending.
435 self._reading = False
435 self._reading = False
436 if info and info.kind == 'user' and not self._hidden:
436 if info and info.kind == 'user' and not self._hidden:
437 # Make sure that all output from the SUB channel has been processed
437 # Make sure that all output from the SUB channel has been processed
438 # before writing a new prompt.
438 # before writing a new prompt.
439 self.kernel_client.iopub_channel.flush()
439 self.kernel_client.iopub_channel.flush()
440
440
441 # Reset the ANSI style information to prevent bad text in stdout
441 # Reset the ANSI style information to prevent bad text in stdout
442 # from messing up our colors. We're not a true terminal so we're
442 # from messing up our colors. We're not a true terminal so we're
443 # allowed to do this.
443 # allowed to do this.
444 if self.ansi_codes:
444 if self.ansi_codes:
445 self._ansi_processor.reset_sgr()
445 self._ansi_processor.reset_sgr()
446
446
447 content = msg['content']
447 content = msg['content']
448 status = content['status']
448 status = content['status']
449 if status == 'ok':
449 if status == 'ok':
450 self._process_execute_ok(msg)
450 self._process_execute_ok(msg)
451 elif status == 'error':
451 elif status == 'error':
452 self._process_execute_error(msg)
452 self._process_execute_error(msg)
453 elif status == 'aborted':
453 elif status == 'aborted':
454 self._process_execute_abort(msg)
454 self._process_execute_abort(msg)
455
455
456 self._show_interpreter_prompt_for_reply(msg)
456 self._show_interpreter_prompt_for_reply(msg)
457 self.executed.emit(msg)
457 self.executed.emit(msg)
458 self._request_info['execute'].pop(msg_id)
458 self._request_info['execute'].pop(msg_id)
459 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
459 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
460 self._handle_exec_callback(msg)
460 self._handle_exec_callback(msg)
461 self._request_info['execute'].pop(msg_id)
461 self._request_info['execute'].pop(msg_id)
462 else:
462 else:
463 super(FrontendWidget, self)._handle_execute_reply(msg)
463 super(FrontendWidget, self)._handle_execute_reply(msg)
464
464
465 def _handle_input_request(self, msg):
465 def _handle_input_request(self, msg):
466 """ Handle requests for raw_input.
466 """ Handle requests for raw_input.
467 """
467 """
468 self.log.debug("input: %s", msg.get('content', ''))
468 self.log.debug("input: %s", msg.get('content', ''))
469 if self._hidden:
469 if self._hidden:
470 raise RuntimeError('Request for raw input during hidden execution.')
470 raise RuntimeError('Request for raw input during hidden execution.')
471
471
472 # Make sure that all output from the SUB channel has been processed
472 # Make sure that all output from the SUB channel has been processed
473 # before entering readline mode.
473 # before entering readline mode.
474 self.kernel_client.iopub_channel.flush()
474 self.kernel_client.iopub_channel.flush()
475
475
476 def callback(line):
476 def callback(line):
477 self.kernel_client.stdin_channel.input(line)
477 self.kernel_client.stdin_channel.input(line)
478 if self._reading:
478 if self._reading:
479 self.log.debug("Got second input request, assuming first was interrupted.")
479 self.log.debug("Got second input request, assuming first was interrupted.")
480 self._reading = False
480 self._reading = False
481 self._readline(msg['content']['prompt'], callback=callback)
481 self._readline(msg['content']['prompt'], callback=callback)
482
482
483 def _kernel_restarted_message(self, died=True):
483 def _kernel_restarted_message(self, died=True):
484 msg = "Kernel died, restarting" if died else "Kernel restarting"
484 msg = "Kernel died, restarting" if died else "Kernel restarting"
485 self._append_html("<br>%s<hr><br>" % msg,
485 self._append_html("<br>%s<hr><br>" % msg,
486 before_prompt=False
486 before_prompt=False
487 )
487 )
488
488
489 def _handle_kernel_died(self, since_last_heartbeat):
489 def _handle_kernel_died(self, since_last_heartbeat):
490 """Handle the kernel's death (if we do not own the kernel).
490 """Handle the kernel's death (if we do not own the kernel).
491 """
491 """
492 self.log.warn("kernel died: %s", since_last_heartbeat)
492 self.log.warn("kernel died: %s", since_last_heartbeat)
493 if self.custom_restart:
493 if self.custom_restart:
494 self.custom_restart_kernel_died.emit(since_last_heartbeat)
494 self.custom_restart_kernel_died.emit(since_last_heartbeat)
495 else:
495 else:
496 self._kernel_restarted_message(died=True)
496 self._kernel_restarted_message(died=True)
497 self.reset()
497 self.reset()
498
498
499 def _handle_kernel_restarted(self, died=True):
499 def _handle_kernel_restarted(self, died=True):
500 """Notice that the autorestarter restarted the kernel.
500 """Notice that the autorestarter restarted the kernel.
501
501
502 There's nothing to do but show a message.
502 There's nothing to do but show a message.
503 """
503 """
504 self.log.warn("kernel restarted")
504 self.log.warn("kernel restarted")
505 self._kernel_restarted_message(died=died)
505 self._kernel_restarted_message(died=died)
506 self.reset()
506 self.reset()
507
507
508 def _handle_inspect_reply(self, rep):
508 def _handle_inspect_reply(self, rep):
509 """Handle replies for call tips."""
509 """Handle replies for call tips."""
510 self.log.debug("oinfo: %s", rep.get('content', ''))
510 self.log.debug("oinfo: %s", rep.get('content', ''))
511 cursor = self._get_cursor()
511 cursor = self._get_cursor()
512 info = self._request_info.get('call_tip')
512 info = self._request_info.get('call_tip')
513 if info and info.id == rep['parent_header']['msg_id'] and \
513 if info and info.id == rep['parent_header']['msg_id'] and \
514 info.pos == cursor.position():
514 info.pos == cursor.position():
515 content = rep['content']
515 content = rep['content']
516 if content.get('status') == 'ok':
516 if content.get('status') == 'ok':
517 self._call_tip_widget.show_inspect_data(content)
517 self._call_tip_widget.show_inspect_data(content)
518
518
519 def _handle_execute_result(self, msg):
519 def _handle_execute_result(self, msg):
520 """ Handle display hook output.
520 """ Handle display hook output.
521 """
521 """
522 self.log.debug("execute_result: %s", msg.get('content', ''))
522 self.log.debug("execute_result: %s", msg.get('content', ''))
523 if not self._hidden and self._is_from_this_session(msg):
523 if not self._hidden and self._is_from_this_session(msg):
524 self.flush_clearoutput()
524 self.flush_clearoutput()
525 text = msg['content']['data']
525 text = msg['content']['data']
526 self._append_plain_text(text + '\n', before_prompt=True)
526 self._append_plain_text(text + '\n', before_prompt=True)
527
527
528 def _handle_stream(self, msg):
528 def _handle_stream(self, msg):
529 """ Handle stdout, stderr, and stdin.
529 """ Handle stdout, stderr, and stdin.
530 """
530 """
531 self.log.debug("stream: %s", msg.get('content', ''))
531 self.log.debug("stream: %s", msg.get('content', ''))
532 if not self._hidden and self._is_from_this_session(msg):
532 if not self._hidden and self._is_from_this_session(msg):
533 self.flush_clearoutput()
533 self.flush_clearoutput()
534 self.append_stream(msg['content']['data'])
534 self.append_stream(msg['content']['data'])
535
535
536 def _handle_shutdown_reply(self, msg):
536 def _handle_shutdown_reply(self, msg):
537 """ Handle shutdown signal, only if from other console.
537 """ Handle shutdown signal, only if from other console.
538 """
538 """
539 self.log.info("shutdown: %s", msg.get('content', ''))
539 self.log.info("shutdown: %s", msg.get('content', ''))
540 restart = msg.get('content', {}).get('restart', False)
540 restart = msg.get('content', {}).get('restart', False)
541 if not self._hidden and not self._is_from_this_session(msg):
541 if not self._hidden and not self._is_from_this_session(msg):
542 # got shutdown reply, request came from session other than ours
542 # got shutdown reply, request came from session other than ours
543 if restart:
543 if restart:
544 # someone restarted the kernel, handle it
544 # someone restarted the kernel, handle it
545 self._handle_kernel_restarted(died=False)
545 self._handle_kernel_restarted(died=False)
546 else:
546 else:
547 # kernel was shutdown permanently
547 # kernel was shutdown permanently
548 # this triggers exit_requested if the kernel was local,
548 # this triggers exit_requested if the kernel was local,
549 # and a dialog if the kernel was remote,
549 # and a dialog if the kernel was remote,
550 # so we don't suddenly clear the qtconsole without asking.
550 # so we don't suddenly clear the qtconsole without asking.
551 if self._local_kernel:
551 if self._local_kernel:
552 self.exit_requested.emit(self)
552 self.exit_requested.emit(self)
553 else:
553 else:
554 title = self.window().windowTitle()
554 title = self.window().windowTitle()
555 reply = QtGui.QMessageBox.question(self, title,
555 reply = QtGui.QMessageBox.question(self, title,
556 "Kernel has been shutdown permanently. "
556 "Kernel has been shutdown permanently. "
557 "Close the Console?",
557 "Close the Console?",
558 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
558 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
559 if reply == QtGui.QMessageBox.Yes:
559 if reply == QtGui.QMessageBox.Yes:
560 self.exit_requested.emit(self)
560 self.exit_requested.emit(self)
561
561
562 def _handle_status(self, msg):
562 def _handle_status(self, msg):
563 """Handle status message"""
563 """Handle status message"""
564 # This is where a busy/idle indicator would be triggered,
564 # This is where a busy/idle indicator would be triggered,
565 # when we make one.
565 # when we make one.
566 state = msg['content'].get('execution_state', '')
566 state = msg['content'].get('execution_state', '')
567 if state == 'starting':
567 if state == 'starting':
568 # kernel started while we were running
568 # kernel started while we were running
569 if self._executing:
569 if self._executing:
570 self._handle_kernel_restarted(died=True)
570 self._handle_kernel_restarted(died=True)
571 elif state == 'idle':
571 elif state == 'idle':
572 pass
572 pass
573 elif state == 'busy':
573 elif state == 'busy':
574 pass
574 pass
575
575
576 def _started_channels(self):
576 def _started_channels(self):
577 """ Called when the KernelManager channels have started listening or
577 """ Called when the KernelManager channels have started listening or
578 when the frontend is assigned an already listening KernelManager.
578 when the frontend is assigned an already listening KernelManager.
579 """
579 """
580 self.reset(clear=True)
580 self.reset(clear=True)
581
581
582 #---------------------------------------------------------------------------
582 #---------------------------------------------------------------------------
583 # 'FrontendWidget' public interface
583 # 'FrontendWidget' public interface
584 #---------------------------------------------------------------------------
584 #---------------------------------------------------------------------------
585
585
586 def copy_raw(self):
586 def copy_raw(self):
587 """ Copy the currently selected text to the clipboard without attempting
587 """ Copy the currently selected text to the clipboard without attempting
588 to remove prompts or otherwise alter the text.
588 to remove prompts or otherwise alter the text.
589 """
589 """
590 self._control.copy()
590 self._control.copy()
591
591
592 def execute_file(self, path, hidden=False):
592 def execute_file(self, path, hidden=False):
593 """ Attempts to execute file with 'path'. If 'hidden', no output is
593 """ Attempts to execute file with 'path'. If 'hidden', no output is
594 shown.
594 shown.
595 """
595 """
596 self.execute('execfile(%r)' % path, hidden=hidden)
596 self.execute('execfile(%r)' % path, hidden=hidden)
597
597
598 def interrupt_kernel(self):
598 def interrupt_kernel(self):
599 """ Attempts to interrupt the running kernel.
599 """ Attempts to interrupt the running kernel.
600
600
601 Also unsets _reading flag, to avoid runtime errors
601 Also unsets _reading flag, to avoid runtime errors
602 if raw_input is called again.
602 if raw_input is called again.
603 """
603 """
604 if self.custom_interrupt:
604 if self.custom_interrupt:
605 self._reading = False
605 self._reading = False
606 self.custom_interrupt_requested.emit()
606 self.custom_interrupt_requested.emit()
607 elif self.kernel_manager:
607 elif self.kernel_manager:
608 self._reading = False
608 self._reading = False
609 self.kernel_manager.interrupt_kernel()
609 self.kernel_manager.interrupt_kernel()
610 else:
610 else:
611 self._append_plain_text('Cannot interrupt a kernel I did not start.\n')
611 self._append_plain_text('Cannot interrupt a kernel I did not start.\n')
612
612
613 def reset(self, clear=False):
613 def reset(self, clear=False):
614 """ Resets the widget to its initial state if ``clear`` parameter
614 """ Resets the widget to its initial state if ``clear`` parameter
615 is True, otherwise
615 is True, otherwise
616 prints a visual indication of the fact that the kernel restarted, but
616 prints a visual indication of the fact that the kernel restarted, but
617 does not clear the traces from previous usage of the kernel before it
617 does not clear the traces from previous usage of the kernel before it
618 was restarted. With ``clear=True``, it is similar to ``%clear``, but
618 was restarted. With ``clear=True``, it is similar to ``%clear``, but
619 also re-writes the banner and aborts execution if necessary.
619 also re-writes the banner and aborts execution if necessary.
620 """
620 """
621 if self._executing:
621 if self._executing:
622 self._executing = False
622 self._executing = False
623 self._request_info['execute'] = {}
623 self._request_info['execute'] = {}
624 self._reading = False
624 self._reading = False
625 self._highlighter.highlighting_on = False
625 self._highlighter.highlighting_on = False
626
626
627 if clear:
627 if clear:
628 self._control.clear()
628 self._control.clear()
629 self._append_plain_text(self.banner)
629 self._append_plain_text(self.banner)
630 if self.kernel_banner:
630 if self.kernel_banner:
631 self._append_plain_text(self.kernel_banner)
631 self._append_plain_text(self.kernel_banner)
632
632
633 # update output marker for stdout/stderr, so that startup
633 # update output marker for stdout/stderr, so that startup
634 # messages appear after banner:
634 # messages appear after banner:
635 self._append_before_prompt_pos = self._get_cursor().position()
635 self._append_before_prompt_pos = self._get_cursor().position()
636 self._show_interpreter_prompt()
636 self._show_interpreter_prompt()
637
637
638 def restart_kernel(self, message, now=False):
638 def restart_kernel(self, message, now=False):
639 """ Attempts to restart the running kernel.
639 """ Attempts to restart the running kernel.
640 """
640 """
641 # FIXME: now should be configurable via a checkbox in the dialog. Right
641 # FIXME: now should be configurable via a checkbox in the dialog. Right
642 # now at least the heartbeat path sets it to True and the manual restart
642 # now at least the heartbeat path sets it to True and the manual restart
643 # to False. But those should just be the pre-selected states of a
643 # to False. But those should just be the pre-selected states of a
644 # checkbox that the user could override if so desired. But I don't know
644 # checkbox that the user could override if so desired. But I don't know
645 # enough Qt to go implementing the checkbox now.
645 # enough Qt to go implementing the checkbox now.
646
646
647 if self.custom_restart:
647 if self.custom_restart:
648 self.custom_restart_requested.emit()
648 self.custom_restart_requested.emit()
649 return
649 return
650
650
651 if self.kernel_manager:
651 if self.kernel_manager:
652 # Pause the heart beat channel to prevent further warnings.
652 # Pause the heart beat channel to prevent further warnings.
653 self.kernel_client.hb_channel.pause()
653 self.kernel_client.hb_channel.pause()
654
654
655 # Prompt the user to restart the kernel. Un-pause the heartbeat if
655 # Prompt the user to restart the kernel. Un-pause the heartbeat if
656 # they decline. (If they accept, the heartbeat will be un-paused
656 # they decline. (If they accept, the heartbeat will be un-paused
657 # automatically when the kernel is restarted.)
657 # automatically when the kernel is restarted.)
658 if self.confirm_restart:
658 if self.confirm_restart:
659 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
659 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
660 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
660 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
661 message, buttons)
661 message, buttons)
662 do_restart = result == QtGui.QMessageBox.Yes
662 do_restart = result == QtGui.QMessageBox.Yes
663 else:
663 else:
664 # confirm_restart is False, so we don't need to ask user
664 # confirm_restart is False, so we don't need to ask user
665 # anything, just do the restart
665 # anything, just do the restart
666 do_restart = True
666 do_restart = True
667 if do_restart:
667 if do_restart:
668 try:
668 try:
669 self.kernel_manager.restart_kernel(now=now)
669 self.kernel_manager.restart_kernel(now=now)
670 except RuntimeError as e:
670 except RuntimeError as e:
671 self._append_plain_text(
671 self._append_plain_text(
672 'Error restarting kernel: %s\n' % e,
672 'Error restarting kernel: %s\n' % e,
673 before_prompt=True
673 before_prompt=True
674 )
674 )
675 else:
675 else:
676 self._append_html("<br>Restarting kernel...\n<hr><br>",
676 self._append_html("<br>Restarting kernel...\n<hr><br>",
677 before_prompt=True,
677 before_prompt=True,
678 )
678 )
679 else:
679 else:
680 self.kernel_client.hb_channel.unpause()
680 self.kernel_client.hb_channel.unpause()
681
681
682 else:
682 else:
683 self._append_plain_text(
683 self._append_plain_text(
684 'Cannot restart a Kernel I did not start\n',
684 'Cannot restart a Kernel I did not start\n',
685 before_prompt=True
685 before_prompt=True
686 )
686 )
687
687
688 def append_stream(self, text):
688 def append_stream(self, text):
689 """Appends text to the output stream."""
689 """Appends text to the output stream."""
690 # Most consoles treat tabs as being 8 space characters. Convert tabs
690 # Most consoles treat tabs as being 8 space characters. Convert tabs
691 # to spaces so that output looks as expected regardless of this
691 # to spaces so that output looks as expected regardless of this
692 # widget's tab width.
692 # widget's tab width.
693 text = text.expandtabs(8)
693 text = text.expandtabs(8)
694 self._append_plain_text(text, before_prompt=True)
694 self._append_plain_text(text, before_prompt=True)
695 self._control.moveCursor(QtGui.QTextCursor.End)
695 self._control.moveCursor(QtGui.QTextCursor.End)
696
696
697 def flush_clearoutput(self):
697 def flush_clearoutput(self):
698 """If a clearoutput is pending, execute it."""
698 """If a clearoutput is pending, execute it."""
699 if self._pending_clearoutput:
699 if self._pending_clearoutput:
700 self._pending_clearoutput = False
700 self._pending_clearoutput = False
701 self.clear_output()
701 self.clear_output()
702
702
703 def clear_output(self):
703 def clear_output(self):
704 """Clears the current line of output."""
704 """Clears the current line of output."""
705 cursor = self._control.textCursor()
705 cursor = self._control.textCursor()
706 cursor.beginEditBlock()
706 cursor.beginEditBlock()
707 cursor.movePosition(cursor.StartOfLine, cursor.KeepAnchor)
707 cursor.movePosition(cursor.StartOfLine, cursor.KeepAnchor)
708 cursor.insertText('')
708 cursor.insertText('')
709 cursor.endEditBlock()
709 cursor.endEditBlock()
710
710
711 #---------------------------------------------------------------------------
711 #---------------------------------------------------------------------------
712 # 'FrontendWidget' protected interface
712 # 'FrontendWidget' protected interface
713 #---------------------------------------------------------------------------
713 #---------------------------------------------------------------------------
714
714
715 def _call_tip(self):
715 def _call_tip(self):
716 """ Shows a call tip, if appropriate, at the current cursor location.
716 """ Shows a call tip, if appropriate, at the current cursor location.
717 """
717 """
718 # Decide if it makes sense to show a call tip
718 # Decide if it makes sense to show a call tip
719 if not self.enable_calltips:
719 if not self.enable_calltips or not self.kernel_client.shell_channel.is_alive():
720 return False
720 return False
721 cursor_pos = self._get_input_buffer_cursor_pos()
721 cursor_pos = self._get_input_buffer_cursor_pos()
722 code = self.input_buffer
722 code = self.input_buffer
723 # Send the metadata request to the kernel
723 # Send the metadata request to the kernel
724 msg_id = self.kernel_client.inspect(code, cursor_pos)
724 msg_id = self.kernel_client.inspect(code, cursor_pos)
725 pos = self._get_cursor().position()
725 pos = self._get_cursor().position()
726 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
726 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
727 return True
727 return True
728
728
729 def _complete(self):
729 def _complete(self):
730 """ Performs completion at the current cursor location.
730 """ Performs completion at the current cursor location.
731 """
731 """
732 context = self._get_context()
732 context = self._get_context()
733 if context:
733 if context:
734 # Send the completion request to the kernel
734 # Send the completion request to the kernel
735 msg_id = self.kernel_client.complete(
735 msg_id = self.kernel_client.complete(
736 code=self.input_buffer,
736 code=self.input_buffer,
737 cursor_pos=self._get_input_buffer_cursor_pos(),
737 cursor_pos=self._get_input_buffer_cursor_pos(),
738 )
738 )
739 pos = self._get_cursor().position()
739 pos = self._get_cursor().position()
740 info = self._CompletionRequest(msg_id, pos)
740 info = self._CompletionRequest(msg_id, pos)
741 self._request_info['complete'] = info
741 self._request_info['complete'] = info
742
742
743 def _get_context(self, cursor=None):
743 def _get_context(self, cursor=None):
744 """ Gets the context for the specified cursor (or the current cursor
744 """ Gets the context for the specified cursor (or the current cursor
745 if none is specified).
745 if none is specified).
746 """
746 """
747 if cursor is None:
747 if cursor is None:
748 cursor = self._get_cursor()
748 cursor = self._get_cursor()
749 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
749 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
750 QtGui.QTextCursor.KeepAnchor)
750 QtGui.QTextCursor.KeepAnchor)
751 text = cursor.selection().toPlainText()
751 text = cursor.selection().toPlainText()
752 return self._completion_lexer.get_context(text)
752 return self._completion_lexer.get_context(text)
753
753
754 def _process_execute_abort(self, msg):
754 def _process_execute_abort(self, msg):
755 """ Process a reply for an aborted execution request.
755 """ Process a reply for an aborted execution request.
756 """
756 """
757 self._append_plain_text("ERROR: execution aborted\n")
757 self._append_plain_text("ERROR: execution aborted\n")
758
758
759 def _process_execute_error(self, msg):
759 def _process_execute_error(self, msg):
760 """ Process a reply for an execution request that resulted in an error.
760 """ Process a reply for an execution request that resulted in an error.
761 """
761 """
762 content = msg['content']
762 content = msg['content']
763 # If a SystemExit is passed along, this means exit() was called - also
763 # If a SystemExit is passed along, this means exit() was called - also
764 # all the ipython %exit magic syntax of '-k' to be used to keep
764 # all the ipython %exit magic syntax of '-k' to be used to keep
765 # the kernel running
765 # the kernel running
766 if content['ename']=='SystemExit':
766 if content['ename']=='SystemExit':
767 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
767 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
768 self._keep_kernel_on_exit = keepkernel
768 self._keep_kernel_on_exit = keepkernel
769 self.exit_requested.emit(self)
769 self.exit_requested.emit(self)
770 else:
770 else:
771 traceback = ''.join(content['traceback'])
771 traceback = ''.join(content['traceback'])
772 self._append_plain_text(traceback)
772 self._append_plain_text(traceback)
773
773
774 def _process_execute_ok(self, msg):
774 def _process_execute_ok(self, msg):
775 """ Process a reply for a successful execution request.
775 """ Process a reply for a successful execution request.
776 """
776 """
777 payload = msg['content']['payload']
777 payload = msg['content']['payload']
778 for item in payload:
778 for item in payload:
779 if not self._process_execute_payload(item):
779 if not self._process_execute_payload(item):
780 warning = 'Warning: received unknown payload of type %s'
780 warning = 'Warning: received unknown payload of type %s'
781 print(warning % repr(item['source']))
781 print(warning % repr(item['source']))
782
782
783 def _process_execute_payload(self, item):
783 def _process_execute_payload(self, item):
784 """ Process a single payload item from the list of payload items in an
784 """ Process a single payload item from the list of payload items in an
785 execution reply. Returns whether the payload was handled.
785 execution reply. Returns whether the payload was handled.
786 """
786 """
787 # The basic FrontendWidget doesn't handle payloads, as they are a
787 # The basic FrontendWidget doesn't handle payloads, as they are a
788 # mechanism for going beyond the standard Python interpreter model.
788 # mechanism for going beyond the standard Python interpreter model.
789 return False
789 return False
790
790
791 def _show_interpreter_prompt(self):
791 def _show_interpreter_prompt(self):
792 """ Shows a prompt for the interpreter.
792 """ Shows a prompt for the interpreter.
793 """
793 """
794 self._show_prompt('>>> ')
794 self._show_prompt('>>> ')
795
795
796 def _show_interpreter_prompt_for_reply(self, msg):
796 def _show_interpreter_prompt_for_reply(self, msg):
797 """ Shows a prompt for the interpreter given an 'execute_reply' message.
797 """ Shows a prompt for the interpreter given an 'execute_reply' message.
798 """
798 """
799 self._show_interpreter_prompt()
799 self._show_interpreter_prompt()
800
800
801 #------ Signal handlers ----------------------------------------------------
801 #------ Signal handlers ----------------------------------------------------
802
802
803 def _document_contents_change(self, position, removed, added):
803 def _document_contents_change(self, position, removed, added):
804 """ Called whenever the document's content changes. Display a call tip
804 """ Called whenever the document's content changes. Display a call tip
805 if appropriate.
805 if appropriate.
806 """
806 """
807 # Calculate where the cursor should be *after* the change:
807 # Calculate where the cursor should be *after* the change:
808 position += added
808 position += added
809
809
810 document = self._control.document()
810 document = self._control.document()
811 if position == self._get_cursor().position():
811 if position == self._get_cursor().position():
812 self._call_tip()
812 self._call_tip()
813
813
814 #------ Trait default initializers -----------------------------------------
814 #------ Trait default initializers -----------------------------------------
815
815
816 def _banner_default(self):
816 def _banner_default(self):
817 """ Returns the standard Python banner.
817 """ Returns the standard Python banner.
818 """
818 """
819 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
819 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
820 '"license" for more information.'
820 '"license" for more information.'
821 return banner % (sys.version, sys.platform)
821 return banner % (sys.version, sys.platform)
General Comments 0
You need to be logged in to leave comments. Login now