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