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