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