##// END OF EJS Templates
Merge pull request #6729 from takluyver/qtconsole-empty-tooltip...
Min RK -
r18406:971330d9 merge
parent child Browse files
Show More
@@ -1,824 +1,824 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 was_newline = text[-1] == '\n'
218 was_newline = text[-1] == '\n'
219 text = self._prompt_transformer.transform_cell(text)
219 text = self._prompt_transformer.transform_cell(text)
220 if not was_newline: # user doesn't need newline
220 if not was_newline: # user doesn't need newline
221 text = text[:-1]
221 text = text[:-1]
222 QtGui.QApplication.clipboard().setText(text)
222 QtGui.QApplication.clipboard().setText(text)
223 else:
223 else:
224 self.log.debug("frontend widget : unknown copy target")
224 self.log.debug("frontend widget : unknown copy target")
225
225
226 #---------------------------------------------------------------------------
226 #---------------------------------------------------------------------------
227 # 'ConsoleWidget' abstract interface
227 # 'ConsoleWidget' abstract interface
228 #---------------------------------------------------------------------------
228 #---------------------------------------------------------------------------
229
229
230 def _is_complete(self, source, interactive):
230 def _is_complete(self, source, interactive):
231 """ Returns whether 'source' can be completely processed and a new
231 """ Returns whether 'source' can be completely processed and a new
232 prompt created. When triggered by an Enter/Return key press,
232 prompt created. When triggered by an Enter/Return key press,
233 'interactive' is True; otherwise, it is False.
233 'interactive' is True; otherwise, it is False.
234 """
234 """
235 self._input_splitter.reset()
235 self._input_splitter.reset()
236 try:
236 try:
237 complete = self._input_splitter.push(source)
237 complete = self._input_splitter.push(source)
238 except SyntaxError:
238 except SyntaxError:
239 return True
239 return True
240 if interactive:
240 if interactive:
241 complete = not self._input_splitter.push_accepts_more()
241 complete = not self._input_splitter.push_accepts_more()
242 return complete
242 return complete
243
243
244 def _execute(self, source, hidden):
244 def _execute(self, source, hidden):
245 """ Execute 'source'. If 'hidden', do not show any output.
245 """ Execute 'source'. If 'hidden', do not show any output.
246
246
247 See parent class :meth:`execute` docstring for full details.
247 See parent class :meth:`execute` docstring for full details.
248 """
248 """
249 msg_id = self.kernel_client.execute(source, hidden)
249 msg_id = self.kernel_client.execute(source, hidden)
250 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
250 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
251 self._hidden = hidden
251 self._hidden = hidden
252 if not hidden:
252 if not hidden:
253 self.executing.emit(source)
253 self.executing.emit(source)
254
254
255 def _prompt_started_hook(self):
255 def _prompt_started_hook(self):
256 """ Called immediately after a new prompt is displayed.
256 """ Called immediately after a new prompt is displayed.
257 """
257 """
258 if not self._reading:
258 if not self._reading:
259 self._highlighter.highlighting_on = True
259 self._highlighter.highlighting_on = True
260
260
261 def _prompt_finished_hook(self):
261 def _prompt_finished_hook(self):
262 """ Called immediately after a prompt is finished, i.e. when some input
262 """ Called immediately after a prompt is finished, i.e. when some input
263 will be processed and a new prompt displayed.
263 will be processed and a new prompt displayed.
264 """
264 """
265 # Flush all state from the input splitter so the next round of
265 # Flush all state from the input splitter so the next round of
266 # reading input starts with a clean buffer.
266 # reading input starts with a clean buffer.
267 self._input_splitter.reset()
267 self._input_splitter.reset()
268
268
269 if not self._reading:
269 if not self._reading:
270 self._highlighter.highlighting_on = False
270 self._highlighter.highlighting_on = False
271
271
272 def _tab_pressed(self):
272 def _tab_pressed(self):
273 """ Called when the tab key is pressed. Returns whether to continue
273 """ Called when the tab key is pressed. Returns whether to continue
274 processing the event.
274 processing the event.
275 """
275 """
276 # Perform tab completion if:
276 # Perform tab completion if:
277 # 1) The cursor is in the input buffer.
277 # 1) The cursor is in the input buffer.
278 # 2) There is a non-whitespace character before the cursor.
278 # 2) There is a non-whitespace character before the cursor.
279 text = self._get_input_buffer_cursor_line()
279 text = self._get_input_buffer_cursor_line()
280 if text is None:
280 if text is None:
281 return False
281 return False
282 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
282 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
283 if complete:
283 if complete:
284 self._complete()
284 self._complete()
285 return not complete
285 return not complete
286
286
287 #---------------------------------------------------------------------------
287 #---------------------------------------------------------------------------
288 # 'ConsoleWidget' protected interface
288 # 'ConsoleWidget' protected interface
289 #---------------------------------------------------------------------------
289 #---------------------------------------------------------------------------
290
290
291 def _context_menu_make(self, pos):
291 def _context_menu_make(self, pos):
292 """ Reimplemented to add an action for raw copy.
292 """ Reimplemented to add an action for raw copy.
293 """
293 """
294 menu = super(FrontendWidget, self)._context_menu_make(pos)
294 menu = super(FrontendWidget, self)._context_menu_make(pos)
295 for before_action in menu.actions():
295 for before_action in menu.actions():
296 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
296 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
297 QtGui.QKeySequence.ExactMatch:
297 QtGui.QKeySequence.ExactMatch:
298 menu.insertAction(before_action, self._copy_raw_action)
298 menu.insertAction(before_action, self._copy_raw_action)
299 break
299 break
300 return menu
300 return menu
301
301
302 def request_interrupt_kernel(self):
302 def request_interrupt_kernel(self):
303 if self._executing:
303 if self._executing:
304 self.interrupt_kernel()
304 self.interrupt_kernel()
305
305
306 def request_restart_kernel(self):
306 def request_restart_kernel(self):
307 message = 'Are you sure you want to restart the kernel?'
307 message = 'Are you sure you want to restart the kernel?'
308 self.restart_kernel(message, now=False)
308 self.restart_kernel(message, now=False)
309
309
310 def _event_filter_console_keypress(self, event):
310 def _event_filter_console_keypress(self, event):
311 """ Reimplemented for execution interruption and smart backspace.
311 """ Reimplemented for execution interruption and smart backspace.
312 """
312 """
313 key = event.key()
313 key = event.key()
314 if self._control_key_down(event.modifiers(), include_command=False):
314 if self._control_key_down(event.modifiers(), include_command=False):
315
315
316 if key == QtCore.Qt.Key_C and self._executing:
316 if key == QtCore.Qt.Key_C and self._executing:
317 self.request_interrupt_kernel()
317 self.request_interrupt_kernel()
318 return True
318 return True
319
319
320 elif key == QtCore.Qt.Key_Period:
320 elif key == QtCore.Qt.Key_Period:
321 self.request_restart_kernel()
321 self.request_restart_kernel()
322 return True
322 return True
323
323
324 elif not event.modifiers() & QtCore.Qt.AltModifier:
324 elif not event.modifiers() & QtCore.Qt.AltModifier:
325
325
326 # Smart backspace: remove four characters in one backspace if:
326 # Smart backspace: remove four characters in one backspace if:
327 # 1) everything left of the cursor is whitespace
327 # 1) everything left of the cursor is whitespace
328 # 2) the four characters immediately left of the cursor are spaces
328 # 2) the four characters immediately left of the cursor are spaces
329 if key == QtCore.Qt.Key_Backspace:
329 if key == QtCore.Qt.Key_Backspace:
330 col = self._get_input_buffer_cursor_column()
330 col = self._get_input_buffer_cursor_column()
331 cursor = self._control.textCursor()
331 cursor = self._control.textCursor()
332 if col > 3 and not cursor.hasSelection():
332 if col > 3 and not cursor.hasSelection():
333 text = self._get_input_buffer_cursor_line()[:col]
333 text = self._get_input_buffer_cursor_line()[:col]
334 if text.endswith(' ') and not text.strip():
334 if text.endswith(' ') and not text.strip():
335 cursor.movePosition(QtGui.QTextCursor.Left,
335 cursor.movePosition(QtGui.QTextCursor.Left,
336 QtGui.QTextCursor.KeepAnchor, 4)
336 QtGui.QTextCursor.KeepAnchor, 4)
337 cursor.removeSelectedText()
337 cursor.removeSelectedText()
338 return True
338 return True
339
339
340 return super(FrontendWidget, self)._event_filter_console_keypress(event)
340 return super(FrontendWidget, self)._event_filter_console_keypress(event)
341
341
342 def _insert_continuation_prompt(self, cursor):
342 def _insert_continuation_prompt(self, cursor):
343 """ Reimplemented for auto-indentation.
343 """ Reimplemented for auto-indentation.
344 """
344 """
345 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
345 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
346 cursor.insertText(' ' * self._input_splitter.indent_spaces)
346 cursor.insertText(' ' * self._input_splitter.indent_spaces)
347
347
348 #---------------------------------------------------------------------------
348 #---------------------------------------------------------------------------
349 # 'BaseFrontendMixin' abstract interface
349 # 'BaseFrontendMixin' abstract interface
350 #---------------------------------------------------------------------------
350 #---------------------------------------------------------------------------
351 def _handle_clear_output(self, msg):
351 def _handle_clear_output(self, msg):
352 """Handle clear output messages."""
352 """Handle clear output messages."""
353 if not self._hidden and self._is_from_this_session(msg):
353 if not self._hidden and self._is_from_this_session(msg):
354 wait = msg['content'].get('wait', True)
354 wait = msg['content'].get('wait', True)
355 if wait:
355 if wait:
356 self._pending_clearoutput = True
356 self._pending_clearoutput = True
357 else:
357 else:
358 self.clear_output()
358 self.clear_output()
359
359
360 def _handle_complete_reply(self, rep):
360 def _handle_complete_reply(self, rep):
361 """ Handle replies for tab completion.
361 """ Handle replies for tab completion.
362 """
362 """
363 self.log.debug("complete: %s", rep.get('content', ''))
363 self.log.debug("complete: %s", rep.get('content', ''))
364 cursor = self._get_cursor()
364 cursor = self._get_cursor()
365 info = self._request_info.get('complete')
365 info = self._request_info.get('complete')
366 if info and info.id == rep['parent_header']['msg_id'] and \
366 if info and info.id == rep['parent_header']['msg_id'] and \
367 info.pos == cursor.position():
367 info.pos == cursor.position():
368 text = '.'.join(self._get_context())
368 text = '.'.join(self._get_context())
369 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
369 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
370 self._complete_with_items(cursor, rep['content']['matches'])
370 self._complete_with_items(cursor, rep['content']['matches'])
371
371
372 def _silent_exec_callback(self, expr, callback):
372 def _silent_exec_callback(self, expr, callback):
373 """Silently execute `expr` in the kernel and call `callback` with reply
373 """Silently execute `expr` in the kernel and call `callback` with reply
374
374
375 the `expr` is evaluated silently in the kernel (without) output in
375 the `expr` is evaluated silently in the kernel (without) output in
376 the frontend. Call `callback` with the
376 the frontend. Call `callback` with the
377 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
377 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
378
378
379 Parameters
379 Parameters
380 ----------
380 ----------
381 expr : string
381 expr : string
382 valid string to be executed by the kernel.
382 valid string to be executed by the kernel.
383 callback : function
383 callback : function
384 function accepting one argument, as a string. The string will be
384 function accepting one argument, as a string. The string will be
385 the `repr` of the result of evaluating `expr`
385 the `repr` of the result of evaluating `expr`
386
386
387 The `callback` is called with the `repr()` of the result of `expr` as
387 The `callback` is called with the `repr()` of the result of `expr` as
388 first argument. To get the object, do `eval()` on the passed value.
388 first argument. To get the object, do `eval()` on the passed value.
389
389
390 See Also
390 See Also
391 --------
391 --------
392 _handle_exec_callback : private method, deal with calling callback with reply
392 _handle_exec_callback : private method, deal with calling callback with reply
393
393
394 """
394 """
395
395
396 # generate uuid, which would be used as an indication of whether or
396 # generate uuid, which would be used as an indication of whether or
397 # not the unique request originated from here (can use msg id ?)
397 # not the unique request originated from here (can use msg id ?)
398 local_uuid = str(uuid.uuid1())
398 local_uuid = str(uuid.uuid1())
399 msg_id = self.kernel_client.execute('',
399 msg_id = self.kernel_client.execute('',
400 silent=True, user_expressions={ local_uuid:expr })
400 silent=True, user_expressions={ local_uuid:expr })
401 self._callback_dict[local_uuid] = callback
401 self._callback_dict[local_uuid] = callback
402 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
402 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
403
403
404 def _handle_exec_callback(self, msg):
404 def _handle_exec_callback(self, msg):
405 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
405 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
406
406
407 Parameters
407 Parameters
408 ----------
408 ----------
409 msg : raw message send by the kernel containing an `user_expressions`
409 msg : raw message send by the kernel containing an `user_expressions`
410 and having a 'silent_exec_callback' kind.
410 and having a 'silent_exec_callback' kind.
411
411
412 Notes
412 Notes
413 -----
413 -----
414 This function will look for a `callback` associated with the
414 This function will look for a `callback` associated with the
415 corresponding message id. Association has been made by
415 corresponding message id. Association has been made by
416 `_silent_exec_callback`. `callback` is then called with the `repr()`
416 `_silent_exec_callback`. `callback` is then called with the `repr()`
417 of the value of corresponding `user_expressions` as argument.
417 of the value of corresponding `user_expressions` as argument.
418 `callback` is then removed from the known list so that any message
418 `callback` is then removed from the known list so that any message
419 coming again with the same id won't trigger it.
419 coming again with the same id won't trigger it.
420
420
421 """
421 """
422
422
423 user_exp = msg['content'].get('user_expressions')
423 user_exp = msg['content'].get('user_expressions')
424 if not user_exp:
424 if not user_exp:
425 return
425 return
426 for expression in user_exp:
426 for expression in user_exp:
427 if expression in self._callback_dict:
427 if expression in self._callback_dict:
428 self._callback_dict.pop(expression)(user_exp[expression])
428 self._callback_dict.pop(expression)(user_exp[expression])
429
429
430 def _handle_execute_reply(self, msg):
430 def _handle_execute_reply(self, msg):
431 """ Handles replies for code execution.
431 """ Handles replies for code execution.
432 """
432 """
433 self.log.debug("execute: %s", msg.get('content', ''))
433 self.log.debug("execute: %s", msg.get('content', ''))
434 msg_id = msg['parent_header']['msg_id']
434 msg_id = msg['parent_header']['msg_id']
435 info = self._request_info['execute'].get(msg_id)
435 info = self._request_info['execute'].get(msg_id)
436 # unset reading flag, because if execute finished, raw_input can't
436 # unset reading flag, because if execute finished, raw_input can't
437 # still be pending.
437 # still be pending.
438 self._reading = False
438 self._reading = False
439 if info and info.kind == 'user' and not self._hidden:
439 if info and info.kind == 'user' and not self._hidden:
440 # Make sure that all output from the SUB channel has been processed
440 # Make sure that all output from the SUB channel has been processed
441 # before writing a new prompt.
441 # before writing a new prompt.
442 self.kernel_client.iopub_channel.flush()
442 self.kernel_client.iopub_channel.flush()
443
443
444 # Reset the ANSI style information to prevent bad text in stdout
444 # Reset the ANSI style information to prevent bad text in stdout
445 # from messing up our colors. We're not a true terminal so we're
445 # from messing up our colors. We're not a true terminal so we're
446 # allowed to do this.
446 # allowed to do this.
447 if self.ansi_codes:
447 if self.ansi_codes:
448 self._ansi_processor.reset_sgr()
448 self._ansi_processor.reset_sgr()
449
449
450 content = msg['content']
450 content = msg['content']
451 status = content['status']
451 status = content['status']
452 if status == 'ok':
452 if status == 'ok':
453 self._process_execute_ok(msg)
453 self._process_execute_ok(msg)
454 elif status == 'error':
454 elif status == 'error':
455 self._process_execute_error(msg)
455 self._process_execute_error(msg)
456 elif status == 'aborted':
456 elif status == 'aborted':
457 self._process_execute_abort(msg)
457 self._process_execute_abort(msg)
458
458
459 self._show_interpreter_prompt_for_reply(msg)
459 self._show_interpreter_prompt_for_reply(msg)
460 self.executed.emit(msg)
460 self.executed.emit(msg)
461 self._request_info['execute'].pop(msg_id)
461 self._request_info['execute'].pop(msg_id)
462 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
462 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
463 self._handle_exec_callback(msg)
463 self._handle_exec_callback(msg)
464 self._request_info['execute'].pop(msg_id)
464 self._request_info['execute'].pop(msg_id)
465 else:
465 else:
466 super(FrontendWidget, self)._handle_execute_reply(msg)
466 super(FrontendWidget, self)._handle_execute_reply(msg)
467
467
468 def _handle_input_request(self, msg):
468 def _handle_input_request(self, msg):
469 """ Handle requests for raw_input.
469 """ Handle requests for raw_input.
470 """
470 """
471 self.log.debug("input: %s", msg.get('content', ''))
471 self.log.debug("input: %s", msg.get('content', ''))
472 if self._hidden:
472 if self._hidden:
473 raise RuntimeError('Request for raw input during hidden execution.')
473 raise RuntimeError('Request for raw input during hidden execution.')
474
474
475 # Make sure that all output from the SUB channel has been processed
475 # Make sure that all output from the SUB channel has been processed
476 # before entering readline mode.
476 # before entering readline mode.
477 self.kernel_client.iopub_channel.flush()
477 self.kernel_client.iopub_channel.flush()
478
478
479 def callback(line):
479 def callback(line):
480 self.kernel_client.stdin_channel.input(line)
480 self.kernel_client.stdin_channel.input(line)
481 if self._reading:
481 if self._reading:
482 self.log.debug("Got second input request, assuming first was interrupted.")
482 self.log.debug("Got second input request, assuming first was interrupted.")
483 self._reading = False
483 self._reading = False
484 self._readline(msg['content']['prompt'], callback=callback)
484 self._readline(msg['content']['prompt'], callback=callback)
485
485
486 def _kernel_restarted_message(self, died=True):
486 def _kernel_restarted_message(self, died=True):
487 msg = "Kernel died, restarting" if died else "Kernel restarting"
487 msg = "Kernel died, restarting" if died else "Kernel restarting"
488 self._append_html("<br>%s<hr><br>" % msg,
488 self._append_html("<br>%s<hr><br>" % msg,
489 before_prompt=False
489 before_prompt=False
490 )
490 )
491
491
492 def _handle_kernel_died(self, since_last_heartbeat):
492 def _handle_kernel_died(self, since_last_heartbeat):
493 """Handle the kernel's death (if we do not own the kernel).
493 """Handle the kernel's death (if we do not own the kernel).
494 """
494 """
495 self.log.warn("kernel died: %s", since_last_heartbeat)
495 self.log.warn("kernel died: %s", since_last_heartbeat)
496 if self.custom_restart:
496 if self.custom_restart:
497 self.custom_restart_kernel_died.emit(since_last_heartbeat)
497 self.custom_restart_kernel_died.emit(since_last_heartbeat)
498 else:
498 else:
499 self._kernel_restarted_message(died=True)
499 self._kernel_restarted_message(died=True)
500 self.reset()
500 self.reset()
501
501
502 def _handle_kernel_restarted(self, died=True):
502 def _handle_kernel_restarted(self, died=True):
503 """Notice that the autorestarter restarted the kernel.
503 """Notice that the autorestarter restarted the kernel.
504
504
505 There's nothing to do but show a message.
505 There's nothing to do but show a message.
506 """
506 """
507 self.log.warn("kernel restarted")
507 self.log.warn("kernel restarted")
508 self._kernel_restarted_message(died=died)
508 self._kernel_restarted_message(died=died)
509 self.reset()
509 self.reset()
510
510
511 def _handle_inspect_reply(self, rep):
511 def _handle_inspect_reply(self, rep):
512 """Handle replies for call tips."""
512 """Handle replies for call tips."""
513 self.log.debug("oinfo: %s", rep.get('content', ''))
513 self.log.debug("oinfo: %s", rep.get('content', ''))
514 cursor = self._get_cursor()
514 cursor = self._get_cursor()
515 info = self._request_info.get('call_tip')
515 info = self._request_info.get('call_tip')
516 if info and info.id == rep['parent_header']['msg_id'] and \
516 if info and info.id == rep['parent_header']['msg_id'] and \
517 info.pos == cursor.position():
517 info.pos == cursor.position():
518 content = rep['content']
518 content = rep['content']
519 if content.get('status') == 'ok':
519 if content.get('status') == 'ok' and content.get('found', False):
520 self._call_tip_widget.show_inspect_data(content)
520 self._call_tip_widget.show_inspect_data(content)
521
521
522 def _handle_execute_result(self, msg):
522 def _handle_execute_result(self, msg):
523 """ Handle display hook output.
523 """ Handle display hook output.
524 """
524 """
525 self.log.debug("execute_result: %s", msg.get('content', ''))
525 self.log.debug("execute_result: %s", msg.get('content', ''))
526 if not self._hidden and self._is_from_this_session(msg):
526 if not self._hidden and self._is_from_this_session(msg):
527 self.flush_clearoutput()
527 self.flush_clearoutput()
528 text = msg['content']['data']
528 text = msg['content']['data']
529 self._append_plain_text(text + '\n', before_prompt=True)
529 self._append_plain_text(text + '\n', before_prompt=True)
530
530
531 def _handle_stream(self, msg):
531 def _handle_stream(self, msg):
532 """ Handle stdout, stderr, and stdin.
532 """ Handle stdout, stderr, and stdin.
533 """
533 """
534 self.log.debug("stream: %s", msg.get('content', ''))
534 self.log.debug("stream: %s", msg.get('content', ''))
535 if not self._hidden and self._is_from_this_session(msg):
535 if not self._hidden and self._is_from_this_session(msg):
536 self.flush_clearoutput()
536 self.flush_clearoutput()
537 self.append_stream(msg['content']['text'])
537 self.append_stream(msg['content']['text'])
538
538
539 def _handle_shutdown_reply(self, msg):
539 def _handle_shutdown_reply(self, msg):
540 """ Handle shutdown signal, only if from other console.
540 """ Handle shutdown signal, only if from other console.
541 """
541 """
542 self.log.info("shutdown: %s", msg.get('content', ''))
542 self.log.info("shutdown: %s", msg.get('content', ''))
543 restart = msg.get('content', {}).get('restart', False)
543 restart = msg.get('content', {}).get('restart', False)
544 if not self._hidden and not self._is_from_this_session(msg):
544 if not self._hidden and not self._is_from_this_session(msg):
545 # got shutdown reply, request came from session other than ours
545 # got shutdown reply, request came from session other than ours
546 if restart:
546 if restart:
547 # someone restarted the kernel, handle it
547 # someone restarted the kernel, handle it
548 self._handle_kernel_restarted(died=False)
548 self._handle_kernel_restarted(died=False)
549 else:
549 else:
550 # kernel was shutdown permanently
550 # kernel was shutdown permanently
551 # this triggers exit_requested if the kernel was local,
551 # this triggers exit_requested if the kernel was local,
552 # and a dialog if the kernel was remote,
552 # and a dialog if the kernel was remote,
553 # so we don't suddenly clear the qtconsole without asking.
553 # so we don't suddenly clear the qtconsole without asking.
554 if self._local_kernel:
554 if self._local_kernel:
555 self.exit_requested.emit(self)
555 self.exit_requested.emit(self)
556 else:
556 else:
557 title = self.window().windowTitle()
557 title = self.window().windowTitle()
558 reply = QtGui.QMessageBox.question(self, title,
558 reply = QtGui.QMessageBox.question(self, title,
559 "Kernel has been shutdown permanently. "
559 "Kernel has been shutdown permanently. "
560 "Close the Console?",
560 "Close the Console?",
561 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
561 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
562 if reply == QtGui.QMessageBox.Yes:
562 if reply == QtGui.QMessageBox.Yes:
563 self.exit_requested.emit(self)
563 self.exit_requested.emit(self)
564
564
565 def _handle_status(self, msg):
565 def _handle_status(self, msg):
566 """Handle status message"""
566 """Handle status message"""
567 # This is where a busy/idle indicator would be triggered,
567 # This is where a busy/idle indicator would be triggered,
568 # when we make one.
568 # when we make one.
569 state = msg['content'].get('execution_state', '')
569 state = msg['content'].get('execution_state', '')
570 if state == 'starting':
570 if state == 'starting':
571 # kernel started while we were running
571 # kernel started while we were running
572 if self._executing:
572 if self._executing:
573 self._handle_kernel_restarted(died=True)
573 self._handle_kernel_restarted(died=True)
574 elif state == 'idle':
574 elif state == 'idle':
575 pass
575 pass
576 elif state == 'busy':
576 elif state == 'busy':
577 pass
577 pass
578
578
579 def _started_channels(self):
579 def _started_channels(self):
580 """ Called when the KernelManager channels have started listening or
580 """ Called when the KernelManager channels have started listening or
581 when the frontend is assigned an already listening KernelManager.
581 when the frontend is assigned an already listening KernelManager.
582 """
582 """
583 self.reset(clear=True)
583 self.reset(clear=True)
584
584
585 #---------------------------------------------------------------------------
585 #---------------------------------------------------------------------------
586 # 'FrontendWidget' public interface
586 # 'FrontendWidget' public interface
587 #---------------------------------------------------------------------------
587 #---------------------------------------------------------------------------
588
588
589 def copy_raw(self):
589 def copy_raw(self):
590 """ Copy the currently selected text to the clipboard without attempting
590 """ Copy the currently selected text to the clipboard without attempting
591 to remove prompts or otherwise alter the text.
591 to remove prompts or otherwise alter the text.
592 """
592 """
593 self._control.copy()
593 self._control.copy()
594
594
595 def execute_file(self, path, hidden=False):
595 def execute_file(self, path, hidden=False):
596 """ Attempts to execute file with 'path'. If 'hidden', no output is
596 """ Attempts to execute file with 'path'. If 'hidden', no output is
597 shown.
597 shown.
598 """
598 """
599 self.execute('execfile(%r)' % path, hidden=hidden)
599 self.execute('execfile(%r)' % path, hidden=hidden)
600
600
601 def interrupt_kernel(self):
601 def interrupt_kernel(self):
602 """ Attempts to interrupt the running kernel.
602 """ Attempts to interrupt the running kernel.
603
603
604 Also unsets _reading flag, to avoid runtime errors
604 Also unsets _reading flag, to avoid runtime errors
605 if raw_input is called again.
605 if raw_input is called again.
606 """
606 """
607 if self.custom_interrupt:
607 if self.custom_interrupt:
608 self._reading = False
608 self._reading = False
609 self.custom_interrupt_requested.emit()
609 self.custom_interrupt_requested.emit()
610 elif self.kernel_manager:
610 elif self.kernel_manager:
611 self._reading = False
611 self._reading = False
612 self.kernel_manager.interrupt_kernel()
612 self.kernel_manager.interrupt_kernel()
613 else:
613 else:
614 self._append_plain_text('Cannot interrupt a kernel I did not start.\n')
614 self._append_plain_text('Cannot interrupt a kernel I did not start.\n')
615
615
616 def reset(self, clear=False):
616 def reset(self, clear=False):
617 """ Resets the widget to its initial state if ``clear`` parameter
617 """ Resets the widget to its initial state if ``clear`` parameter
618 is True, otherwise
618 is True, otherwise
619 prints a visual indication of the fact that the kernel restarted, but
619 prints a visual indication of the fact that the kernel restarted, but
620 does not clear the traces from previous usage of the kernel before it
620 does not clear the traces from previous usage of the kernel before it
621 was restarted. With ``clear=True``, it is similar to ``%clear``, but
621 was restarted. With ``clear=True``, it is similar to ``%clear``, but
622 also re-writes the banner and aborts execution if necessary.
622 also re-writes the banner and aborts execution if necessary.
623 """
623 """
624 if self._executing:
624 if self._executing:
625 self._executing = False
625 self._executing = False
626 self._request_info['execute'] = {}
626 self._request_info['execute'] = {}
627 self._reading = False
627 self._reading = False
628 self._highlighter.highlighting_on = False
628 self._highlighter.highlighting_on = False
629
629
630 if clear:
630 if clear:
631 self._control.clear()
631 self._control.clear()
632 self._append_plain_text(self.banner)
632 self._append_plain_text(self.banner)
633 if self.kernel_banner:
633 if self.kernel_banner:
634 self._append_plain_text(self.kernel_banner)
634 self._append_plain_text(self.kernel_banner)
635
635
636 # update output marker for stdout/stderr, so that startup
636 # update output marker for stdout/stderr, so that startup
637 # messages appear after banner:
637 # messages appear after banner:
638 self._append_before_prompt_pos = self._get_cursor().position()
638 self._append_before_prompt_pos = self._get_cursor().position()
639 self._show_interpreter_prompt()
639 self._show_interpreter_prompt()
640
640
641 def restart_kernel(self, message, now=False):
641 def restart_kernel(self, message, now=False):
642 """ Attempts to restart the running kernel.
642 """ Attempts to restart the running kernel.
643 """
643 """
644 # FIXME: now should be configurable via a checkbox in the dialog. Right
644 # FIXME: now should be configurable via a checkbox in the dialog. Right
645 # now at least the heartbeat path sets it to True and the manual restart
645 # now at least the heartbeat path sets it to True and the manual restart
646 # to False. But those should just be the pre-selected states of a
646 # to False. But those should just be the pre-selected states of a
647 # checkbox that the user could override if so desired. But I don't know
647 # checkbox that the user could override if so desired. But I don't know
648 # enough Qt to go implementing the checkbox now.
648 # enough Qt to go implementing the checkbox now.
649
649
650 if self.custom_restart:
650 if self.custom_restart:
651 self.custom_restart_requested.emit()
651 self.custom_restart_requested.emit()
652 return
652 return
653
653
654 if self.kernel_manager:
654 if self.kernel_manager:
655 # Pause the heart beat channel to prevent further warnings.
655 # Pause the heart beat channel to prevent further warnings.
656 self.kernel_client.hb_channel.pause()
656 self.kernel_client.hb_channel.pause()
657
657
658 # Prompt the user to restart the kernel. Un-pause the heartbeat if
658 # Prompt the user to restart the kernel. Un-pause the heartbeat if
659 # they decline. (If they accept, the heartbeat will be un-paused
659 # they decline. (If they accept, the heartbeat will be un-paused
660 # automatically when the kernel is restarted.)
660 # automatically when the kernel is restarted.)
661 if self.confirm_restart:
661 if self.confirm_restart:
662 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
662 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
663 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
663 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
664 message, buttons)
664 message, buttons)
665 do_restart = result == QtGui.QMessageBox.Yes
665 do_restart = result == QtGui.QMessageBox.Yes
666 else:
666 else:
667 # confirm_restart is False, so we don't need to ask user
667 # confirm_restart is False, so we don't need to ask user
668 # anything, just do the restart
668 # anything, just do the restart
669 do_restart = True
669 do_restart = True
670 if do_restart:
670 if do_restart:
671 try:
671 try:
672 self.kernel_manager.restart_kernel(now=now)
672 self.kernel_manager.restart_kernel(now=now)
673 except RuntimeError as e:
673 except RuntimeError as e:
674 self._append_plain_text(
674 self._append_plain_text(
675 'Error restarting kernel: %s\n' % e,
675 'Error restarting kernel: %s\n' % e,
676 before_prompt=True
676 before_prompt=True
677 )
677 )
678 else:
678 else:
679 self._append_html("<br>Restarting kernel...\n<hr><br>",
679 self._append_html("<br>Restarting kernel...\n<hr><br>",
680 before_prompt=True,
680 before_prompt=True,
681 )
681 )
682 else:
682 else:
683 self.kernel_client.hb_channel.unpause()
683 self.kernel_client.hb_channel.unpause()
684
684
685 else:
685 else:
686 self._append_plain_text(
686 self._append_plain_text(
687 'Cannot restart a Kernel I did not start\n',
687 'Cannot restart a Kernel I did not start\n',
688 before_prompt=True
688 before_prompt=True
689 )
689 )
690
690
691 def append_stream(self, text):
691 def append_stream(self, text):
692 """Appends text to the output stream."""
692 """Appends text to the output stream."""
693 # Most consoles treat tabs as being 8 space characters. Convert tabs
693 # Most consoles treat tabs as being 8 space characters. Convert tabs
694 # to spaces so that output looks as expected regardless of this
694 # to spaces so that output looks as expected regardless of this
695 # widget's tab width.
695 # widget's tab width.
696 text = text.expandtabs(8)
696 text = text.expandtabs(8)
697 self._append_plain_text(text, before_prompt=True)
697 self._append_plain_text(text, before_prompt=True)
698 self._control.moveCursor(QtGui.QTextCursor.End)
698 self._control.moveCursor(QtGui.QTextCursor.End)
699
699
700 def flush_clearoutput(self):
700 def flush_clearoutput(self):
701 """If a clearoutput is pending, execute it."""
701 """If a clearoutput is pending, execute it."""
702 if self._pending_clearoutput:
702 if self._pending_clearoutput:
703 self._pending_clearoutput = False
703 self._pending_clearoutput = False
704 self.clear_output()
704 self.clear_output()
705
705
706 def clear_output(self):
706 def clear_output(self):
707 """Clears the current line of output."""
707 """Clears the current line of output."""
708 cursor = self._control.textCursor()
708 cursor = self._control.textCursor()
709 cursor.beginEditBlock()
709 cursor.beginEditBlock()
710 cursor.movePosition(cursor.StartOfLine, cursor.KeepAnchor)
710 cursor.movePosition(cursor.StartOfLine, cursor.KeepAnchor)
711 cursor.insertText('')
711 cursor.insertText('')
712 cursor.endEditBlock()
712 cursor.endEditBlock()
713
713
714 #---------------------------------------------------------------------------
714 #---------------------------------------------------------------------------
715 # 'FrontendWidget' protected interface
715 # 'FrontendWidget' protected interface
716 #---------------------------------------------------------------------------
716 #---------------------------------------------------------------------------
717
717
718 def _call_tip(self):
718 def _call_tip(self):
719 """ Shows a call tip, if appropriate, at the current cursor location.
719 """ Shows a call tip, if appropriate, at the current cursor location.
720 """
720 """
721 # Decide if it makes sense to show a call tip
721 # Decide if it makes sense to show a call tip
722 if not self.enable_calltips or not self.kernel_client.shell_channel.is_alive():
722 if not self.enable_calltips or not self.kernel_client.shell_channel.is_alive():
723 return False
723 return False
724 cursor_pos = self._get_input_buffer_cursor_pos()
724 cursor_pos = self._get_input_buffer_cursor_pos()
725 code = self.input_buffer
725 code = self.input_buffer
726 # Send the metadata request to the kernel
726 # Send the metadata request to the kernel
727 msg_id = self.kernel_client.inspect(code, cursor_pos)
727 msg_id = self.kernel_client.inspect(code, cursor_pos)
728 pos = self._get_cursor().position()
728 pos = self._get_cursor().position()
729 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
729 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
730 return True
730 return True
731
731
732 def _complete(self):
732 def _complete(self):
733 """ Performs completion at the current cursor location.
733 """ Performs completion at the current cursor location.
734 """
734 """
735 context = self._get_context()
735 context = self._get_context()
736 if context:
736 if context:
737 # Send the completion request to the kernel
737 # Send the completion request to the kernel
738 msg_id = self.kernel_client.complete(
738 msg_id = self.kernel_client.complete(
739 code=self.input_buffer,
739 code=self.input_buffer,
740 cursor_pos=self._get_input_buffer_cursor_pos(),
740 cursor_pos=self._get_input_buffer_cursor_pos(),
741 )
741 )
742 pos = self._get_cursor().position()
742 pos = self._get_cursor().position()
743 info = self._CompletionRequest(msg_id, pos)
743 info = self._CompletionRequest(msg_id, pos)
744 self._request_info['complete'] = info
744 self._request_info['complete'] = info
745
745
746 def _get_context(self, cursor=None):
746 def _get_context(self, cursor=None):
747 """ Gets the context for the specified cursor (or the current cursor
747 """ Gets the context for the specified cursor (or the current cursor
748 if none is specified).
748 if none is specified).
749 """
749 """
750 if cursor is None:
750 if cursor is None:
751 cursor = self._get_cursor()
751 cursor = self._get_cursor()
752 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
752 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
753 QtGui.QTextCursor.KeepAnchor)
753 QtGui.QTextCursor.KeepAnchor)
754 text = cursor.selection().toPlainText()
754 text = cursor.selection().toPlainText()
755 return self._completion_lexer.get_context(text)
755 return self._completion_lexer.get_context(text)
756
756
757 def _process_execute_abort(self, msg):
757 def _process_execute_abort(self, msg):
758 """ Process a reply for an aborted execution request.
758 """ Process a reply for an aborted execution request.
759 """
759 """
760 self._append_plain_text("ERROR: execution aborted\n")
760 self._append_plain_text("ERROR: execution aborted\n")
761
761
762 def _process_execute_error(self, msg):
762 def _process_execute_error(self, msg):
763 """ Process a reply for an execution request that resulted in an error.
763 """ Process a reply for an execution request that resulted in an error.
764 """
764 """
765 content = msg['content']
765 content = msg['content']
766 # If a SystemExit is passed along, this means exit() was called - also
766 # If a SystemExit is passed along, this means exit() was called - also
767 # all the ipython %exit magic syntax of '-k' to be used to keep
767 # all the ipython %exit magic syntax of '-k' to be used to keep
768 # the kernel running
768 # the kernel running
769 if content['ename']=='SystemExit':
769 if content['ename']=='SystemExit':
770 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
770 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
771 self._keep_kernel_on_exit = keepkernel
771 self._keep_kernel_on_exit = keepkernel
772 self.exit_requested.emit(self)
772 self.exit_requested.emit(self)
773 else:
773 else:
774 traceback = ''.join(content['traceback'])
774 traceback = ''.join(content['traceback'])
775 self._append_plain_text(traceback)
775 self._append_plain_text(traceback)
776
776
777 def _process_execute_ok(self, msg):
777 def _process_execute_ok(self, msg):
778 """ Process a reply for a successful execution request.
778 """ Process a reply for a successful execution request.
779 """
779 """
780 payload = msg['content']['payload']
780 payload = msg['content']['payload']
781 for item in payload:
781 for item in payload:
782 if not self._process_execute_payload(item):
782 if not self._process_execute_payload(item):
783 warning = 'Warning: received unknown payload of type %s'
783 warning = 'Warning: received unknown payload of type %s'
784 print(warning % repr(item['source']))
784 print(warning % repr(item['source']))
785
785
786 def _process_execute_payload(self, item):
786 def _process_execute_payload(self, item):
787 """ Process a single payload item from the list of payload items in an
787 """ Process a single payload item from the list of payload items in an
788 execution reply. Returns whether the payload was handled.
788 execution reply. Returns whether the payload was handled.
789 """
789 """
790 # The basic FrontendWidget doesn't handle payloads, as they are a
790 # The basic FrontendWidget doesn't handle payloads, as they are a
791 # mechanism for going beyond the standard Python interpreter model.
791 # mechanism for going beyond the standard Python interpreter model.
792 return False
792 return False
793
793
794 def _show_interpreter_prompt(self):
794 def _show_interpreter_prompt(self):
795 """ Shows a prompt for the interpreter.
795 """ Shows a prompt for the interpreter.
796 """
796 """
797 self._show_prompt('>>> ')
797 self._show_prompt('>>> ')
798
798
799 def _show_interpreter_prompt_for_reply(self, msg):
799 def _show_interpreter_prompt_for_reply(self, msg):
800 """ Shows a prompt for the interpreter given an 'execute_reply' message.
800 """ Shows a prompt for the interpreter given an 'execute_reply' message.
801 """
801 """
802 self._show_interpreter_prompt()
802 self._show_interpreter_prompt()
803
803
804 #------ Signal handlers ----------------------------------------------------
804 #------ Signal handlers ----------------------------------------------------
805
805
806 def _document_contents_change(self, position, removed, added):
806 def _document_contents_change(self, position, removed, added):
807 """ Called whenever the document's content changes. Display a call tip
807 """ Called whenever the document's content changes. Display a call tip
808 if appropriate.
808 if appropriate.
809 """
809 """
810 # Calculate where the cursor should be *after* the change:
810 # Calculate where the cursor should be *after* the change:
811 position += added
811 position += added
812
812
813 document = self._control.document()
813 document = self._control.document()
814 if position == self._get_cursor().position():
814 if position == self._get_cursor().position():
815 self._call_tip()
815 self._call_tip()
816
816
817 #------ Trait default initializers -----------------------------------------
817 #------ Trait default initializers -----------------------------------------
818
818
819 def _banner_default(self):
819 def _banner_default(self):
820 """ Returns the standard Python banner.
820 """ Returns the standard Python banner.
821 """
821 """
822 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
822 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
823 '"license" for more information.'
823 '"license" for more information.'
824 return banner % (sys.version, sys.platform)
824 return banner % (sys.version, sys.platform)
General Comments 0
You need to be logged in to leave comments. Login now