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