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