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