##// END OF EJS Templates
Merge pull request #8594 from minrk/missing-payload...
Thomas Kluyver -
r21512:03671d8b merge
parent child Browse files
Show More
@@ -1,811 +1,811 b''
1 """Frontend widget for the Qt Console"""
1 """Frontend widget for the Qt Console"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import print_function
6 from __future__ import print_function
7
7
8 from collections import namedtuple
8 from collections import namedtuple
9 import sys
9 import sys
10 import uuid
10 import uuid
11
11
12 from IPython.external import qt
12 from IPython.external import qt
13 from IPython.external.qt import QtCore, QtGui
13 from IPython.external.qt import QtCore, QtGui
14 from IPython.utils import py3compat
14 from IPython.utils import py3compat
15 from IPython.utils.importstring import import_item
15 from IPython.utils.importstring import import_item
16
16
17 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
17 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
18 from IPython.core.inputtransformer import classic_prompt
18 from IPython.core.inputtransformer import classic_prompt
19 from IPython.core.oinspect import call_tip
19 from IPython.core.oinspect import call_tip
20 from IPython.qt.base_frontend_mixin import BaseFrontendMixin
20 from IPython.qt.base_frontend_mixin import BaseFrontendMixin
21 from IPython.utils.traitlets import Any, Bool, Instance, Unicode, DottedObjectName
21 from IPython.utils.traitlets import Any, Bool, Instance, Unicode, DottedObjectName
22 from .bracket_matcher import BracketMatcher
22 from .bracket_matcher import BracketMatcher
23 from .call_tip_widget import CallTipWidget
23 from .call_tip_widget import CallTipWidget
24 from .history_console_widget import HistoryConsoleWidget
24 from .history_console_widget import HistoryConsoleWidget
25 from .pygments_highlighter import PygmentsHighlighter
25 from .pygments_highlighter import PygmentsHighlighter
26
26
27
27
28 class FrontendHighlighter(PygmentsHighlighter):
28 class FrontendHighlighter(PygmentsHighlighter):
29 """ A PygmentsHighlighter that understands and ignores prompts.
29 """ A PygmentsHighlighter that understands and ignores prompts.
30 """
30 """
31
31
32 def __init__(self, frontend, lexer=None):
32 def __init__(self, frontend, lexer=None):
33 super(FrontendHighlighter, self).__init__(frontend._control.document(), lexer=lexer)
33 super(FrontendHighlighter, self).__init__(frontend._control.document(), lexer=lexer)
34 self._current_offset = 0
34 self._current_offset = 0
35 self._frontend = frontend
35 self._frontend = frontend
36 self.highlighting_on = False
36 self.highlighting_on = False
37
37
38 def highlightBlock(self, string):
38 def highlightBlock(self, string):
39 """ Highlight a block of text. Reimplemented to highlight selectively.
39 """ Highlight a block of text. Reimplemented to highlight selectively.
40 """
40 """
41 if not self.highlighting_on:
41 if not self.highlighting_on:
42 return
42 return
43
43
44 # The input to this function is a unicode string that may contain
44 # The input to this function is a unicode string that may contain
45 # paragraph break characters, non-breaking spaces, etc. Here we acquire
45 # paragraph break characters, non-breaking spaces, etc. Here we acquire
46 # the string as plain text so we can compare it.
46 # the string as plain text so we can compare it.
47 current_block = self.currentBlock()
47 current_block = self.currentBlock()
48 string = self._frontend._get_block_plain_text(current_block)
48 string = self._frontend._get_block_plain_text(current_block)
49
49
50 # Decide whether to check for the regular or continuation prompt.
50 # Decide whether to check for the regular or continuation prompt.
51 if current_block.contains(self._frontend._prompt_pos):
51 if current_block.contains(self._frontend._prompt_pos):
52 prompt = self._frontend._prompt
52 prompt = self._frontend._prompt
53 else:
53 else:
54 prompt = self._frontend._continuation_prompt
54 prompt = self._frontend._continuation_prompt
55
55
56 # Only highlight if we can identify a prompt, but make sure not to
56 # Only highlight if we can identify a prompt, but make sure not to
57 # highlight the prompt.
57 # highlight the prompt.
58 if string.startswith(prompt):
58 if string.startswith(prompt):
59 self._current_offset = len(prompt)
59 self._current_offset = len(prompt)
60 string = string[len(prompt):]
60 string = string[len(prompt):]
61 super(FrontendHighlighter, self).highlightBlock(string)
61 super(FrontendHighlighter, self).highlightBlock(string)
62
62
63 def rehighlightBlock(self, block):
63 def rehighlightBlock(self, block):
64 """ Reimplemented to temporarily enable highlighting if disabled.
64 """ Reimplemented to temporarily enable highlighting if disabled.
65 """
65 """
66 old = self.highlighting_on
66 old = self.highlighting_on
67 self.highlighting_on = True
67 self.highlighting_on = True
68 super(FrontendHighlighter, self).rehighlightBlock(block)
68 super(FrontendHighlighter, self).rehighlightBlock(block)
69 self.highlighting_on = old
69 self.highlighting_on = old
70
70
71 def setFormat(self, start, count, format):
71 def setFormat(self, start, count, format):
72 """ Reimplemented to highlight selectively.
72 """ Reimplemented to highlight selectively.
73 """
73 """
74 start += self._current_offset
74 start += self._current_offset
75 super(FrontendHighlighter, self).setFormat(start, count, format)
75 super(FrontendHighlighter, self).setFormat(start, count, format)
76
76
77
77
78 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
78 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
79 """ A Qt frontend for a generic Python kernel.
79 """ A Qt frontend for a generic Python kernel.
80 """
80 """
81
81
82 # The text to show when the kernel is (re)started.
82 # The text to show when the kernel is (re)started.
83 banner = Unicode(config=True)
83 banner = Unicode(config=True)
84 kernel_banner = Unicode()
84 kernel_banner = Unicode()
85 # Whether to show the banner
85 # Whether to show the banner
86 _display_banner = Bool(False)
86 _display_banner = Bool(False)
87
87
88 # An option and corresponding signal for overriding the default kernel
88 # An option and corresponding signal for overriding the default kernel
89 # interrupt behavior.
89 # interrupt behavior.
90 custom_interrupt = Bool(False)
90 custom_interrupt = Bool(False)
91 custom_interrupt_requested = QtCore.Signal()
91 custom_interrupt_requested = QtCore.Signal()
92
92
93 # An option and corresponding signals for overriding the default kernel
93 # An option and corresponding signals for overriding the default kernel
94 # restart behavior.
94 # restart behavior.
95 custom_restart = Bool(False)
95 custom_restart = Bool(False)
96 custom_restart_kernel_died = QtCore.Signal(float)
96 custom_restart_kernel_died = QtCore.Signal(float)
97 custom_restart_requested = QtCore.Signal()
97 custom_restart_requested = QtCore.Signal()
98
98
99 # Whether to automatically show calltips on open-parentheses.
99 # Whether to automatically show calltips on open-parentheses.
100 enable_calltips = Bool(True, config=True,
100 enable_calltips = Bool(True, config=True,
101 help="Whether to draw information calltips on open-parentheses.")
101 help="Whether to draw information calltips on open-parentheses.")
102
102
103 clear_on_kernel_restart = Bool(True, config=True,
103 clear_on_kernel_restart = Bool(True, config=True,
104 help="Whether to clear the console when the kernel is restarted")
104 help="Whether to clear the console when the kernel is restarted")
105
105
106 confirm_restart = Bool(True, config=True,
106 confirm_restart = Bool(True, config=True,
107 help="Whether to ask for user confirmation when restarting kernel")
107 help="Whether to ask for user confirmation when restarting kernel")
108
108
109 lexer_class = DottedObjectName(config=True,
109 lexer_class = DottedObjectName(config=True,
110 help="The pygments lexer class to use."
110 help="The pygments lexer class to use."
111 )
111 )
112 def _lexer_class_changed(self, name, old, new):
112 def _lexer_class_changed(self, name, old, new):
113 lexer_class = import_item(new)
113 lexer_class = import_item(new)
114 self.lexer = lexer_class()
114 self.lexer = lexer_class()
115
115
116 def _lexer_class_default(self):
116 def _lexer_class_default(self):
117 if py3compat.PY3:
117 if py3compat.PY3:
118 return 'pygments.lexers.Python3Lexer'
118 return 'pygments.lexers.Python3Lexer'
119 else:
119 else:
120 return 'pygments.lexers.PythonLexer'
120 return 'pygments.lexers.PythonLexer'
121
121
122 lexer = Any()
122 lexer = Any()
123 def _lexer_default(self):
123 def _lexer_default(self):
124 lexer_class = import_item(self.lexer_class)
124 lexer_class = import_item(self.lexer_class)
125 return lexer_class()
125 return lexer_class()
126
126
127 # Emitted when a user visible 'execute_request' has been submitted to the
127 # Emitted when a user visible 'execute_request' has been submitted to the
128 # kernel from the FrontendWidget. Contains the code to be executed.
128 # kernel from the FrontendWidget. Contains the code to be executed.
129 executing = QtCore.Signal(object)
129 executing = QtCore.Signal(object)
130
130
131 # Emitted when a user-visible 'execute_reply' has been received from the
131 # Emitted when a user-visible 'execute_reply' has been received from the
132 # kernel and processed by the FrontendWidget. Contains the response message.
132 # kernel and processed by the FrontendWidget. Contains the response message.
133 executed = QtCore.Signal(object)
133 executed = QtCore.Signal(object)
134
134
135 # Emitted when an exit request has been received from the kernel.
135 # Emitted when an exit request has been received from the kernel.
136 exit_requested = QtCore.Signal(object)
136 exit_requested = QtCore.Signal(object)
137
137
138 # Protected class variables.
138 # Protected class variables.
139 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
139 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
140 logical_line_transforms=[],
140 logical_line_transforms=[],
141 python_line_transforms=[],
141 python_line_transforms=[],
142 )
142 )
143 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
143 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
144 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
144 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
145 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
145 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
146 _input_splitter_class = InputSplitter
146 _input_splitter_class = InputSplitter
147 _local_kernel = False
147 _local_kernel = False
148 _highlighter = Instance(FrontendHighlighter)
148 _highlighter = Instance(FrontendHighlighter)
149
149
150 #---------------------------------------------------------------------------
150 #---------------------------------------------------------------------------
151 # 'object' interface
151 # 'object' interface
152 #---------------------------------------------------------------------------
152 #---------------------------------------------------------------------------
153
153
154 def __init__(self, *args, **kw):
154 def __init__(self, *args, **kw):
155 super(FrontendWidget, self).__init__(*args, **kw)
155 super(FrontendWidget, self).__init__(*args, **kw)
156 # FIXME: remove this when PySide min version is updated past 1.0.7
156 # FIXME: remove this when PySide min version is updated past 1.0.7
157 # forcefully disable calltips if PySide is < 1.0.7, because they crash
157 # forcefully disable calltips if PySide is < 1.0.7, because they crash
158 if qt.QT_API == qt.QT_API_PYSIDE:
158 if qt.QT_API == qt.QT_API_PYSIDE:
159 import PySide
159 import PySide
160 if PySide.__version_info__ < (1,0,7):
160 if PySide.__version_info__ < (1,0,7):
161 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
161 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
162 self.enable_calltips = False
162 self.enable_calltips = False
163
163
164 # FrontendWidget protected variables.
164 # FrontendWidget protected variables.
165 self._bracket_matcher = BracketMatcher(self._control)
165 self._bracket_matcher = BracketMatcher(self._control)
166 self._call_tip_widget = CallTipWidget(self._control)
166 self._call_tip_widget = CallTipWidget(self._control)
167 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
167 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
168 self._hidden = False
168 self._hidden = False
169 self._highlighter = FrontendHighlighter(self, lexer=self.lexer)
169 self._highlighter = FrontendHighlighter(self, lexer=self.lexer)
170 self._input_splitter = self._input_splitter_class()
170 self._input_splitter = self._input_splitter_class()
171 self._kernel_manager = None
171 self._kernel_manager = None
172 self._kernel_client = None
172 self._kernel_client = None
173 self._request_info = {}
173 self._request_info = {}
174 self._request_info['execute'] = {};
174 self._request_info['execute'] = {};
175 self._callback_dict = {}
175 self._callback_dict = {}
176 self._display_banner = True
176 self._display_banner = True
177
177
178 # Configure the ConsoleWidget.
178 # Configure the ConsoleWidget.
179 self.tab_width = 4
179 self.tab_width = 4
180 self._set_continuation_prompt('... ')
180 self._set_continuation_prompt('... ')
181
181
182 # Configure the CallTipWidget.
182 # Configure the CallTipWidget.
183 self._call_tip_widget.setFont(self.font)
183 self._call_tip_widget.setFont(self.font)
184 self.font_changed.connect(self._call_tip_widget.setFont)
184 self.font_changed.connect(self._call_tip_widget.setFont)
185
185
186 # Configure actions.
186 # Configure actions.
187 action = self._copy_raw_action
187 action = self._copy_raw_action
188 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
188 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
189 action.setEnabled(False)
189 action.setEnabled(False)
190 action.setShortcut(QtGui.QKeySequence(key))
190 action.setShortcut(QtGui.QKeySequence(key))
191 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
191 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
192 action.triggered.connect(self.copy_raw)
192 action.triggered.connect(self.copy_raw)
193 self.copy_available.connect(action.setEnabled)
193 self.copy_available.connect(action.setEnabled)
194 self.addAction(action)
194 self.addAction(action)
195
195
196 # Connect signal handlers.
196 # Connect signal handlers.
197 document = self._control.document()
197 document = self._control.document()
198 document.contentsChange.connect(self._document_contents_change)
198 document.contentsChange.connect(self._document_contents_change)
199
199
200 # Set flag for whether we are connected via localhost.
200 # Set flag for whether we are connected via localhost.
201 self._local_kernel = kw.get('local_kernel',
201 self._local_kernel = kw.get('local_kernel',
202 FrontendWidget._local_kernel)
202 FrontendWidget._local_kernel)
203
203
204 # Whether or not a clear_output call is pending new output.
204 # Whether or not a clear_output call is pending new output.
205 self._pending_clearoutput = False
205 self._pending_clearoutput = False
206
206
207 #---------------------------------------------------------------------------
207 #---------------------------------------------------------------------------
208 # 'ConsoleWidget' public interface
208 # 'ConsoleWidget' public interface
209 #---------------------------------------------------------------------------
209 #---------------------------------------------------------------------------
210
210
211 def copy(self):
211 def copy(self):
212 """ Copy the currently selected text to the clipboard, removing prompts.
212 """ Copy the currently selected text to the clipboard, removing prompts.
213 """
213 """
214 if self._page_control is not None and self._page_control.hasFocus():
214 if self._page_control is not None and self._page_control.hasFocus():
215 self._page_control.copy()
215 self._page_control.copy()
216 elif self._control.hasFocus():
216 elif self._control.hasFocus():
217 text = self._control.textCursor().selection().toPlainText()
217 text = self._control.textCursor().selection().toPlainText()
218 if text:
218 if text:
219 was_newline = text[-1] == '\n'
219 was_newline = text[-1] == '\n'
220 text = self._prompt_transformer.transform_cell(text)
220 text = self._prompt_transformer.transform_cell(text)
221 if not was_newline: # user doesn't need newline
221 if not was_newline: # user doesn't need newline
222 text = text[:-1]
222 text = text[:-1]
223 QtGui.QApplication.clipboard().setText(text)
223 QtGui.QApplication.clipboard().setText(text)
224 else:
224 else:
225 self.log.debug("frontend widget : unknown copy target")
225 self.log.debug("frontend widget : unknown copy target")
226
226
227 #---------------------------------------------------------------------------
227 #---------------------------------------------------------------------------
228 # 'ConsoleWidget' abstract interface
228 # 'ConsoleWidget' abstract interface
229 #---------------------------------------------------------------------------
229 #---------------------------------------------------------------------------
230
230
231 def _is_complete(self, source, interactive):
231 def _is_complete(self, source, interactive):
232 """ Returns whether 'source' can be completely processed and a new
232 """ Returns whether 'source' can be completely processed and a new
233 prompt created. When triggered by an Enter/Return key press,
233 prompt created. When triggered by an Enter/Return key press,
234 'interactive' is True; otherwise, it is False.
234 'interactive' is True; otherwise, it is False.
235 """
235 """
236 self._input_splitter.reset()
236 self._input_splitter.reset()
237 try:
237 try:
238 complete = self._input_splitter.push(source)
238 complete = self._input_splitter.push(source)
239 except SyntaxError:
239 except SyntaxError:
240 return True
240 return True
241 if interactive:
241 if interactive:
242 complete = not self._input_splitter.push_accepts_more()
242 complete = not self._input_splitter.push_accepts_more()
243 return complete
243 return complete
244
244
245 def _execute(self, source, hidden):
245 def _execute(self, source, hidden):
246 """ Execute 'source'. If 'hidden', do not show any output.
246 """ Execute 'source'. If 'hidden', do not show any output.
247
247
248 See parent class :meth:`execute` docstring for full details.
248 See parent class :meth:`execute` docstring for full details.
249 """
249 """
250 msg_id = self.kernel_client.execute(source, hidden)
250 msg_id = self.kernel_client.execute(source, hidden)
251 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
251 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
252 self._hidden = hidden
252 self._hidden = hidden
253 if not hidden:
253 if not hidden:
254 self.executing.emit(source)
254 self.executing.emit(source)
255
255
256 def _prompt_started_hook(self):
256 def _prompt_started_hook(self):
257 """ Called immediately after a new prompt is displayed.
257 """ Called immediately after a new prompt is displayed.
258 """
258 """
259 if not self._reading:
259 if not self._reading:
260 self._highlighter.highlighting_on = True
260 self._highlighter.highlighting_on = True
261
261
262 def _prompt_finished_hook(self):
262 def _prompt_finished_hook(self):
263 """ Called immediately after a prompt is finished, i.e. when some input
263 """ Called immediately after a prompt is finished, i.e. when some input
264 will be processed and a new prompt displayed.
264 will be processed and a new prompt displayed.
265 """
265 """
266 # Flush all state from the input splitter so the next round of
266 # Flush all state from the input splitter so the next round of
267 # reading input starts with a clean buffer.
267 # reading input starts with a clean buffer.
268 self._input_splitter.reset()
268 self._input_splitter.reset()
269
269
270 if not self._reading:
270 if not self._reading:
271 self._highlighter.highlighting_on = False
271 self._highlighter.highlighting_on = False
272
272
273 def _tab_pressed(self):
273 def _tab_pressed(self):
274 """ Called when the tab key is pressed. Returns whether to continue
274 """ Called when the tab key is pressed. Returns whether to continue
275 processing the event.
275 processing the event.
276 """
276 """
277 # Perform tab completion if:
277 # Perform tab completion if:
278 # 1) The cursor is in the input buffer.
278 # 1) The cursor is in the input buffer.
279 # 2) There is a non-whitespace character before the cursor.
279 # 2) There is a non-whitespace character before the cursor.
280 text = self._get_input_buffer_cursor_line()
280 text = self._get_input_buffer_cursor_line()
281 if text is None:
281 if text is None:
282 return False
282 return False
283 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
283 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
284 if complete:
284 if complete:
285 self._complete()
285 self._complete()
286 return not complete
286 return not complete
287
287
288 #---------------------------------------------------------------------------
288 #---------------------------------------------------------------------------
289 # 'ConsoleWidget' protected interface
289 # 'ConsoleWidget' protected interface
290 #---------------------------------------------------------------------------
290 #---------------------------------------------------------------------------
291
291
292 def _context_menu_make(self, pos):
292 def _context_menu_make(self, pos):
293 """ Reimplemented to add an action for raw copy.
293 """ Reimplemented to add an action for raw copy.
294 """
294 """
295 menu = super(FrontendWidget, self)._context_menu_make(pos)
295 menu = super(FrontendWidget, self)._context_menu_make(pos)
296 for before_action in menu.actions():
296 for before_action in menu.actions():
297 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
297 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
298 QtGui.QKeySequence.ExactMatch:
298 QtGui.QKeySequence.ExactMatch:
299 menu.insertAction(before_action, self._copy_raw_action)
299 menu.insertAction(before_action, self._copy_raw_action)
300 break
300 break
301 return menu
301 return menu
302
302
303 def request_interrupt_kernel(self):
303 def request_interrupt_kernel(self):
304 if self._executing:
304 if self._executing:
305 self.interrupt_kernel()
305 self.interrupt_kernel()
306
306
307 def request_restart_kernel(self):
307 def request_restart_kernel(self):
308 message = 'Are you sure you want to restart the kernel?'
308 message = 'Are you sure you want to restart the kernel?'
309 self.restart_kernel(message, now=False)
309 self.restart_kernel(message, now=False)
310
310
311 def _event_filter_console_keypress(self, event):
311 def _event_filter_console_keypress(self, event):
312 """ Reimplemented for execution interruption and smart backspace.
312 """ Reimplemented for execution interruption and smart backspace.
313 """
313 """
314 key = event.key()
314 key = event.key()
315 if self._control_key_down(event.modifiers(), include_command=False):
315 if self._control_key_down(event.modifiers(), include_command=False):
316
316
317 if key == QtCore.Qt.Key_C and self._executing:
317 if key == QtCore.Qt.Key_C and self._executing:
318 self.request_interrupt_kernel()
318 self.request_interrupt_kernel()
319 return True
319 return True
320
320
321 elif key == QtCore.Qt.Key_Period:
321 elif key == QtCore.Qt.Key_Period:
322 self.request_restart_kernel()
322 self.request_restart_kernel()
323 return True
323 return True
324
324
325 elif not event.modifiers() & QtCore.Qt.AltModifier:
325 elif not event.modifiers() & QtCore.Qt.AltModifier:
326
326
327 # Smart backspace: remove four characters in one backspace if:
327 # Smart backspace: remove four characters in one backspace if:
328 # 1) everything left of the cursor is whitespace
328 # 1) everything left of the cursor is whitespace
329 # 2) the four characters immediately left of the cursor are spaces
329 # 2) the four characters immediately left of the cursor are spaces
330 if key == QtCore.Qt.Key_Backspace:
330 if key == QtCore.Qt.Key_Backspace:
331 col = self._get_input_buffer_cursor_column()
331 col = self._get_input_buffer_cursor_column()
332 cursor = self._control.textCursor()
332 cursor = self._control.textCursor()
333 if col > 3 and not cursor.hasSelection():
333 if col > 3 and not cursor.hasSelection():
334 text = self._get_input_buffer_cursor_line()[:col]
334 text = self._get_input_buffer_cursor_line()[:col]
335 if text.endswith(' ') and not text.strip():
335 if text.endswith(' ') and not text.strip():
336 cursor.movePosition(QtGui.QTextCursor.Left,
336 cursor.movePosition(QtGui.QTextCursor.Left,
337 QtGui.QTextCursor.KeepAnchor, 4)
337 QtGui.QTextCursor.KeepAnchor, 4)
338 cursor.removeSelectedText()
338 cursor.removeSelectedText()
339 return True
339 return True
340
340
341 return super(FrontendWidget, self)._event_filter_console_keypress(event)
341 return super(FrontendWidget, self)._event_filter_console_keypress(event)
342
342
343 def _insert_continuation_prompt(self, cursor):
343 def _insert_continuation_prompt(self, cursor):
344 """ Reimplemented for auto-indentation.
344 """ Reimplemented for auto-indentation.
345 """
345 """
346 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
346 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
347 cursor.insertText(' ' * self._input_splitter.indent_spaces)
347 cursor.insertText(' ' * self._input_splitter.indent_spaces)
348
348
349 #---------------------------------------------------------------------------
349 #---------------------------------------------------------------------------
350 # 'BaseFrontendMixin' abstract interface
350 # 'BaseFrontendMixin' abstract interface
351 #---------------------------------------------------------------------------
351 #---------------------------------------------------------------------------
352 def _handle_clear_output(self, msg):
352 def _handle_clear_output(self, msg):
353 """Handle clear output messages."""
353 """Handle clear output messages."""
354 if self.include_output(msg):
354 if self.include_output(msg):
355 wait = msg['content'].get('wait', True)
355 wait = msg['content'].get('wait', True)
356 if wait:
356 if wait:
357 self._pending_clearoutput = True
357 self._pending_clearoutput = True
358 else:
358 else:
359 self.clear_output()
359 self.clear_output()
360
360
361 def _silent_exec_callback(self, expr, callback):
361 def _silent_exec_callback(self, expr, callback):
362 """Silently execute `expr` in the kernel and call `callback` with reply
362 """Silently execute `expr` in the kernel and call `callback` with reply
363
363
364 the `expr` is evaluated silently in the kernel (without) output in
364 the `expr` is evaluated silently in the kernel (without) output in
365 the frontend. Call `callback` with the
365 the frontend. Call `callback` with the
366 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
366 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
367
367
368 Parameters
368 Parameters
369 ----------
369 ----------
370 expr : string
370 expr : string
371 valid string to be executed by the kernel.
371 valid string to be executed by the kernel.
372 callback : function
372 callback : function
373 function accepting one argument, as a string. The string will be
373 function accepting one argument, as a string. The string will be
374 the `repr` of the result of evaluating `expr`
374 the `repr` of the result of evaluating `expr`
375
375
376 The `callback` is called with the `repr()` of the result of `expr` as
376 The `callback` is called with the `repr()` of the result of `expr` as
377 first argument. To get the object, do `eval()` on the passed value.
377 first argument. To get the object, do `eval()` on the passed value.
378
378
379 See Also
379 See Also
380 --------
380 --------
381 _handle_exec_callback : private method, deal with calling callback with reply
381 _handle_exec_callback : private method, deal with calling callback with reply
382
382
383 """
383 """
384
384
385 # generate uuid, which would be used as an indication of whether or
385 # generate uuid, which would be used as an indication of whether or
386 # not the unique request originated from here (can use msg id ?)
386 # not the unique request originated from here (can use msg id ?)
387 local_uuid = str(uuid.uuid1())
387 local_uuid = str(uuid.uuid1())
388 msg_id = self.kernel_client.execute('',
388 msg_id = self.kernel_client.execute('',
389 silent=True, user_expressions={ local_uuid:expr })
389 silent=True, user_expressions={ local_uuid:expr })
390 self._callback_dict[local_uuid] = callback
390 self._callback_dict[local_uuid] = callback
391 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
391 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
392
392
393 def _handle_exec_callback(self, msg):
393 def _handle_exec_callback(self, msg):
394 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
394 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
395
395
396 Parameters
396 Parameters
397 ----------
397 ----------
398 msg : raw message send by the kernel containing an `user_expressions`
398 msg : raw message send by the kernel containing an `user_expressions`
399 and having a 'silent_exec_callback' kind.
399 and having a 'silent_exec_callback' kind.
400
400
401 Notes
401 Notes
402 -----
402 -----
403 This function will look for a `callback` associated with the
403 This function will look for a `callback` associated with the
404 corresponding message id. Association has been made by
404 corresponding message id. Association has been made by
405 `_silent_exec_callback`. `callback` is then called with the `repr()`
405 `_silent_exec_callback`. `callback` is then called with the `repr()`
406 of the value of corresponding `user_expressions` as argument.
406 of the value of corresponding `user_expressions` as argument.
407 `callback` is then removed from the known list so that any message
407 `callback` is then removed from the known list so that any message
408 coming again with the same id won't trigger it.
408 coming again with the same id won't trigger it.
409
409
410 """
410 """
411
411
412 user_exp = msg['content'].get('user_expressions')
412 user_exp = msg['content'].get('user_expressions')
413 if not user_exp:
413 if not user_exp:
414 return
414 return
415 for expression in user_exp:
415 for expression in user_exp:
416 if expression in self._callback_dict:
416 if expression in self._callback_dict:
417 self._callback_dict.pop(expression)(user_exp[expression])
417 self._callback_dict.pop(expression)(user_exp[expression])
418
418
419 def _handle_execute_reply(self, msg):
419 def _handle_execute_reply(self, msg):
420 """ Handles replies for code execution.
420 """ Handles replies for code execution.
421 """
421 """
422 self.log.debug("execute: %s", msg.get('content', ''))
422 self.log.debug("execute: %s", msg.get('content', ''))
423 msg_id = msg['parent_header']['msg_id']
423 msg_id = msg['parent_header']['msg_id']
424 info = self._request_info['execute'].get(msg_id)
424 info = self._request_info['execute'].get(msg_id)
425 # unset reading flag, because if execute finished, raw_input can't
425 # unset reading flag, because if execute finished, raw_input can't
426 # still be pending.
426 # still be pending.
427 self._reading = False
427 self._reading = False
428 if info and info.kind == 'user' and not self._hidden:
428 if info and info.kind == 'user' and not self._hidden:
429 # Make sure that all output from the SUB channel has been processed
429 # Make sure that all output from the SUB channel has been processed
430 # before writing a new prompt.
430 # before writing a new prompt.
431 self.kernel_client.iopub_channel.flush()
431 self.kernel_client.iopub_channel.flush()
432
432
433 # Reset the ANSI style information to prevent bad text in stdout
433 # Reset the ANSI style information to prevent bad text in stdout
434 # from messing up our colors. We're not a true terminal so we're
434 # from messing up our colors. We're not a true terminal so we're
435 # allowed to do this.
435 # allowed to do this.
436 if self.ansi_codes:
436 if self.ansi_codes:
437 self._ansi_processor.reset_sgr()
437 self._ansi_processor.reset_sgr()
438
438
439 content = msg['content']
439 content = msg['content']
440 status = content['status']
440 status = content['status']
441 if status == 'ok':
441 if status == 'ok':
442 self._process_execute_ok(msg)
442 self._process_execute_ok(msg)
443 elif status == 'error':
443 elif status == 'error':
444 self._process_execute_error(msg)
444 self._process_execute_error(msg)
445 elif status == 'aborted':
445 elif status == 'aborted':
446 self._process_execute_abort(msg)
446 self._process_execute_abort(msg)
447
447
448 self._show_interpreter_prompt_for_reply(msg)
448 self._show_interpreter_prompt_for_reply(msg)
449 self.executed.emit(msg)
449 self.executed.emit(msg)
450 self._request_info['execute'].pop(msg_id)
450 self._request_info['execute'].pop(msg_id)
451 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
451 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
452 self._handle_exec_callback(msg)
452 self._handle_exec_callback(msg)
453 self._request_info['execute'].pop(msg_id)
453 self._request_info['execute'].pop(msg_id)
454 else:
454 else:
455 super(FrontendWidget, self)._handle_execute_reply(msg)
455 super(FrontendWidget, self)._handle_execute_reply(msg)
456
456
457 def _handle_input_request(self, msg):
457 def _handle_input_request(self, msg):
458 """ Handle requests for raw_input.
458 """ Handle requests for raw_input.
459 """
459 """
460 self.log.debug("input: %s", msg.get('content', ''))
460 self.log.debug("input: %s", msg.get('content', ''))
461 if self._hidden:
461 if self._hidden:
462 raise RuntimeError('Request for raw input during hidden execution.')
462 raise RuntimeError('Request for raw input during hidden execution.')
463
463
464 # Make sure that all output from the SUB channel has been processed
464 # Make sure that all output from the SUB channel has been processed
465 # before entering readline mode.
465 # before entering readline mode.
466 self.kernel_client.iopub_channel.flush()
466 self.kernel_client.iopub_channel.flush()
467
467
468 def callback(line):
468 def callback(line):
469 self.kernel_client.input(line)
469 self.kernel_client.input(line)
470 if self._reading:
470 if self._reading:
471 self.log.debug("Got second input request, assuming first was interrupted.")
471 self.log.debug("Got second input request, assuming first was interrupted.")
472 self._reading = False
472 self._reading = False
473 self._readline(msg['content']['prompt'], callback=callback)
473 self._readline(msg['content']['prompt'], callback=callback)
474
474
475 def _kernel_restarted_message(self, died=True):
475 def _kernel_restarted_message(self, died=True):
476 msg = "Kernel died, restarting" if died else "Kernel restarting"
476 msg = "Kernel died, restarting" if died else "Kernel restarting"
477 self._append_html("<br>%s<hr><br>" % msg,
477 self._append_html("<br>%s<hr><br>" % msg,
478 before_prompt=False
478 before_prompt=False
479 )
479 )
480
480
481 def _handle_kernel_died(self, since_last_heartbeat):
481 def _handle_kernel_died(self, since_last_heartbeat):
482 """Handle the kernel's death (if we do not own the kernel).
482 """Handle the kernel's death (if we do not own the kernel).
483 """
483 """
484 self.log.warn("kernel died: %s", since_last_heartbeat)
484 self.log.warn("kernel died: %s", since_last_heartbeat)
485 if self.custom_restart:
485 if self.custom_restart:
486 self.custom_restart_kernel_died.emit(since_last_heartbeat)
486 self.custom_restart_kernel_died.emit(since_last_heartbeat)
487 else:
487 else:
488 self._kernel_restarted_message(died=True)
488 self._kernel_restarted_message(died=True)
489 self.reset()
489 self.reset()
490
490
491 def _handle_kernel_restarted(self, died=True):
491 def _handle_kernel_restarted(self, died=True):
492 """Notice that the autorestarter restarted the kernel.
492 """Notice that the autorestarter restarted the kernel.
493
493
494 There's nothing to do but show a message.
494 There's nothing to do but show a message.
495 """
495 """
496 self.log.warn("kernel restarted")
496 self.log.warn("kernel restarted")
497 self._kernel_restarted_message(died=died)
497 self._kernel_restarted_message(died=died)
498 self.reset()
498 self.reset()
499
499
500 def _handle_inspect_reply(self, rep):
500 def _handle_inspect_reply(self, rep):
501 """Handle replies for call tips."""
501 """Handle replies for call tips."""
502 self.log.debug("oinfo: %s", rep.get('content', ''))
502 self.log.debug("oinfo: %s", rep.get('content', ''))
503 cursor = self._get_cursor()
503 cursor = self._get_cursor()
504 info = self._request_info.get('call_tip')
504 info = self._request_info.get('call_tip')
505 if info and info.id == rep['parent_header']['msg_id'] and \
505 if info and info.id == rep['parent_header']['msg_id'] and \
506 info.pos == cursor.position():
506 info.pos == cursor.position():
507 content = rep['content']
507 content = rep['content']
508 if content.get('status') == 'ok' and content.get('found', False):
508 if content.get('status') == 'ok' and content.get('found', False):
509 self._call_tip_widget.show_inspect_data(content)
509 self._call_tip_widget.show_inspect_data(content)
510
510
511 def _handle_execute_result(self, msg):
511 def _handle_execute_result(self, msg):
512 """ Handle display hook output.
512 """ Handle display hook output.
513 """
513 """
514 self.log.debug("execute_result: %s", msg.get('content', ''))
514 self.log.debug("execute_result: %s", msg.get('content', ''))
515 if self.include_output(msg):
515 if self.include_output(msg):
516 self.flush_clearoutput()
516 self.flush_clearoutput()
517 text = msg['content']['data']
517 text = msg['content']['data']
518 self._append_plain_text(text + '\n', before_prompt=True)
518 self._append_plain_text(text + '\n', before_prompt=True)
519
519
520 def _handle_stream(self, msg):
520 def _handle_stream(self, msg):
521 """ Handle stdout, stderr, and stdin.
521 """ Handle stdout, stderr, and stdin.
522 """
522 """
523 self.log.debug("stream: %s", msg.get('content', ''))
523 self.log.debug("stream: %s", msg.get('content', ''))
524 if self.include_output(msg):
524 if self.include_output(msg):
525 self.flush_clearoutput()
525 self.flush_clearoutput()
526 self.append_stream(msg['content']['text'])
526 self.append_stream(msg['content']['text'])
527
527
528 def _handle_shutdown_reply(self, msg):
528 def _handle_shutdown_reply(self, msg):
529 """ Handle shutdown signal, only if from other console.
529 """ Handle shutdown signal, only if from other console.
530 """
530 """
531 self.log.info("shutdown: %s", msg.get('content', ''))
531 self.log.info("shutdown: %s", msg.get('content', ''))
532 restart = msg.get('content', {}).get('restart', False)
532 restart = msg.get('content', {}).get('restart', False)
533 if not self._hidden and not self.from_here(msg):
533 if not self._hidden and not self.from_here(msg):
534 # got shutdown reply, request came from session other than ours
534 # got shutdown reply, request came from session other than ours
535 if restart:
535 if restart:
536 # someone restarted the kernel, handle it
536 # someone restarted the kernel, handle it
537 self._handle_kernel_restarted(died=False)
537 self._handle_kernel_restarted(died=False)
538 else:
538 else:
539 # kernel was shutdown permanently
539 # kernel was shutdown permanently
540 # this triggers exit_requested if the kernel was local,
540 # this triggers exit_requested if the kernel was local,
541 # and a dialog if the kernel was remote,
541 # and a dialog if the kernel was remote,
542 # so we don't suddenly clear the qtconsole without asking.
542 # so we don't suddenly clear the qtconsole without asking.
543 if self._local_kernel:
543 if self._local_kernel:
544 self.exit_requested.emit(self)
544 self.exit_requested.emit(self)
545 else:
545 else:
546 title = self.window().windowTitle()
546 title = self.window().windowTitle()
547 reply = QtGui.QMessageBox.question(self, title,
547 reply = QtGui.QMessageBox.question(self, title,
548 "Kernel has been shutdown permanently. "
548 "Kernel has been shutdown permanently. "
549 "Close the Console?",
549 "Close the Console?",
550 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
550 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
551 if reply == QtGui.QMessageBox.Yes:
551 if reply == QtGui.QMessageBox.Yes:
552 self.exit_requested.emit(self)
552 self.exit_requested.emit(self)
553
553
554 def _handle_status(self, msg):
554 def _handle_status(self, msg):
555 """Handle status message"""
555 """Handle status message"""
556 # This is where a busy/idle indicator would be triggered,
556 # This is where a busy/idle indicator would be triggered,
557 # when we make one.
557 # when we make one.
558 state = msg['content'].get('execution_state', '')
558 state = msg['content'].get('execution_state', '')
559 if state == 'starting':
559 if state == 'starting':
560 # kernel started while we were running
560 # kernel started while we were running
561 if self._executing:
561 if self._executing:
562 self._handle_kernel_restarted(died=True)
562 self._handle_kernel_restarted(died=True)
563 elif state == 'idle':
563 elif state == 'idle':
564 pass
564 pass
565 elif state == 'busy':
565 elif state == 'busy':
566 pass
566 pass
567
567
568 def _started_channels(self):
568 def _started_channels(self):
569 """ Called when the KernelManager channels have started listening or
569 """ Called when the KernelManager channels have started listening or
570 when the frontend is assigned an already listening KernelManager.
570 when the frontend is assigned an already listening KernelManager.
571 """
571 """
572 self.reset(clear=True)
572 self.reset(clear=True)
573
573
574 #---------------------------------------------------------------------------
574 #---------------------------------------------------------------------------
575 # 'FrontendWidget' public interface
575 # 'FrontendWidget' public interface
576 #---------------------------------------------------------------------------
576 #---------------------------------------------------------------------------
577
577
578 def copy_raw(self):
578 def copy_raw(self):
579 """ Copy the currently selected text to the clipboard without attempting
579 """ Copy the currently selected text to the clipboard without attempting
580 to remove prompts or otherwise alter the text.
580 to remove prompts or otherwise alter the text.
581 """
581 """
582 self._control.copy()
582 self._control.copy()
583
583
584 def execute_file(self, path, hidden=False):
584 def execute_file(self, path, hidden=False):
585 """ Attempts to execute file with 'path'. If 'hidden', no output is
585 """ Attempts to execute file with 'path'. If 'hidden', no output is
586 shown.
586 shown.
587 """
587 """
588 self.execute('execfile(%r)' % path, hidden=hidden)
588 self.execute('execfile(%r)' % path, hidden=hidden)
589
589
590 def interrupt_kernel(self):
590 def interrupt_kernel(self):
591 """ Attempts to interrupt the running kernel.
591 """ Attempts to interrupt the running kernel.
592
592
593 Also unsets _reading flag, to avoid runtime errors
593 Also unsets _reading flag, to avoid runtime errors
594 if raw_input is called again.
594 if raw_input is called again.
595 """
595 """
596 if self.custom_interrupt:
596 if self.custom_interrupt:
597 self._reading = False
597 self._reading = False
598 self.custom_interrupt_requested.emit()
598 self.custom_interrupt_requested.emit()
599 elif self.kernel_manager:
599 elif self.kernel_manager:
600 self._reading = False
600 self._reading = False
601 self.kernel_manager.interrupt_kernel()
601 self.kernel_manager.interrupt_kernel()
602 else:
602 else:
603 self._append_plain_text('Cannot interrupt a kernel I did not start.\n')
603 self._append_plain_text('Cannot interrupt a kernel I did not start.\n')
604
604
605 def reset(self, clear=False):
605 def reset(self, clear=False):
606 """ Resets the widget to its initial state if ``clear`` parameter
606 """ Resets the widget to its initial state if ``clear`` parameter
607 is True, otherwise
607 is True, otherwise
608 prints a visual indication of the fact that the kernel restarted, but
608 prints a visual indication of the fact that the kernel restarted, but
609 does not clear the traces from previous usage of the kernel before it
609 does not clear the traces from previous usage of the kernel before it
610 was restarted. With ``clear=True``, it is similar to ``%clear``, but
610 was restarted. With ``clear=True``, it is similar to ``%clear``, but
611 also re-writes the banner and aborts execution if necessary.
611 also re-writes the banner and aborts execution if necessary.
612 """
612 """
613 if self._executing:
613 if self._executing:
614 self._executing = False
614 self._executing = False
615 self._request_info['execute'] = {}
615 self._request_info['execute'] = {}
616 self._reading = False
616 self._reading = False
617 self._highlighter.highlighting_on = False
617 self._highlighter.highlighting_on = False
618
618
619 if clear:
619 if clear:
620 self._control.clear()
620 self._control.clear()
621 if self._display_banner:
621 if self._display_banner:
622 self._append_plain_text(self.banner)
622 self._append_plain_text(self.banner)
623 if self.kernel_banner:
623 if self.kernel_banner:
624 self._append_plain_text(self.kernel_banner)
624 self._append_plain_text(self.kernel_banner)
625
625
626 # update output marker for stdout/stderr, so that startup
626 # update output marker for stdout/stderr, so that startup
627 # messages appear after banner:
627 # messages appear after banner:
628 self._append_before_prompt_pos = self._get_cursor().position()
628 self._append_before_prompt_pos = self._get_cursor().position()
629 self._show_interpreter_prompt()
629 self._show_interpreter_prompt()
630
630
631 def restart_kernel(self, message, now=False):
631 def restart_kernel(self, message, now=False):
632 """ Attempts to restart the running kernel.
632 """ Attempts to restart the running kernel.
633 """
633 """
634 # FIXME: now should be configurable via a checkbox in the dialog. Right
634 # FIXME: now should be configurable via a checkbox in the dialog. Right
635 # now at least the heartbeat path sets it to True and the manual restart
635 # now at least the heartbeat path sets it to True and the manual restart
636 # to False. But those should just be the pre-selected states of a
636 # to False. But those should just be the pre-selected states of a
637 # checkbox that the user could override if so desired. But I don't know
637 # checkbox that the user could override if so desired. But I don't know
638 # enough Qt to go implementing the checkbox now.
638 # enough Qt to go implementing the checkbox now.
639
639
640 if self.custom_restart:
640 if self.custom_restart:
641 self.custom_restart_requested.emit()
641 self.custom_restart_requested.emit()
642 return
642 return
643
643
644 if self.kernel_manager:
644 if self.kernel_manager:
645 # Pause the heart beat channel to prevent further warnings.
645 # Pause the heart beat channel to prevent further warnings.
646 self.kernel_client.hb_channel.pause()
646 self.kernel_client.hb_channel.pause()
647
647
648 # Prompt the user to restart the kernel. Un-pause the heartbeat if
648 # Prompt the user to restart the kernel. Un-pause the heartbeat if
649 # they decline. (If they accept, the heartbeat will be un-paused
649 # they decline. (If they accept, the heartbeat will be un-paused
650 # automatically when the kernel is restarted.)
650 # automatically when the kernel is restarted.)
651 if self.confirm_restart:
651 if self.confirm_restart:
652 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
652 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
653 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
653 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
654 message, buttons)
654 message, buttons)
655 do_restart = result == QtGui.QMessageBox.Yes
655 do_restart = result == QtGui.QMessageBox.Yes
656 else:
656 else:
657 # confirm_restart is False, so we don't need to ask user
657 # confirm_restart is False, so we don't need to ask user
658 # anything, just do the restart
658 # anything, just do the restart
659 do_restart = True
659 do_restart = True
660 if do_restart:
660 if do_restart:
661 try:
661 try:
662 self.kernel_manager.restart_kernel(now=now)
662 self.kernel_manager.restart_kernel(now=now)
663 except RuntimeError as e:
663 except RuntimeError as e:
664 self._append_plain_text(
664 self._append_plain_text(
665 'Error restarting kernel: %s\n' % e,
665 'Error restarting kernel: %s\n' % e,
666 before_prompt=True
666 before_prompt=True
667 )
667 )
668 else:
668 else:
669 self._append_html("<br>Restarting kernel...\n<hr><br>",
669 self._append_html("<br>Restarting kernel...\n<hr><br>",
670 before_prompt=True,
670 before_prompt=True,
671 )
671 )
672 else:
672 else:
673 self.kernel_client.hb_channel.unpause()
673 self.kernel_client.hb_channel.unpause()
674
674
675 else:
675 else:
676 self._append_plain_text(
676 self._append_plain_text(
677 'Cannot restart a Kernel I did not start\n',
677 'Cannot restart a Kernel I did not start\n',
678 before_prompt=True
678 before_prompt=True
679 )
679 )
680
680
681 def append_stream(self, text):
681 def append_stream(self, text):
682 """Appends text to the output stream."""
682 """Appends text to the output stream."""
683 # Most consoles treat tabs as being 8 space characters. Convert tabs
683 # Most consoles treat tabs as being 8 space characters. Convert tabs
684 # to spaces so that output looks as expected regardless of this
684 # to spaces so that output looks as expected regardless of this
685 # widget's tab width.
685 # widget's tab width.
686 text = text.expandtabs(8)
686 text = text.expandtabs(8)
687 self._append_plain_text(text, before_prompt=True)
687 self._append_plain_text(text, before_prompt=True)
688 self._control.moveCursor(QtGui.QTextCursor.End)
688 self._control.moveCursor(QtGui.QTextCursor.End)
689
689
690 def flush_clearoutput(self):
690 def flush_clearoutput(self):
691 """If a clearoutput is pending, execute it."""
691 """If a clearoutput is pending, execute it."""
692 if self._pending_clearoutput:
692 if self._pending_clearoutput:
693 self._pending_clearoutput = False
693 self._pending_clearoutput = False
694 self.clear_output()
694 self.clear_output()
695
695
696 def clear_output(self):
696 def clear_output(self):
697 """Clears the current line of output."""
697 """Clears the current line of output."""
698 cursor = self._control.textCursor()
698 cursor = self._control.textCursor()
699 cursor.beginEditBlock()
699 cursor.beginEditBlock()
700 cursor.movePosition(cursor.StartOfLine, cursor.KeepAnchor)
700 cursor.movePosition(cursor.StartOfLine, cursor.KeepAnchor)
701 cursor.insertText('')
701 cursor.insertText('')
702 cursor.endEditBlock()
702 cursor.endEditBlock()
703
703
704 #---------------------------------------------------------------------------
704 #---------------------------------------------------------------------------
705 # 'FrontendWidget' protected interface
705 # 'FrontendWidget' protected interface
706 #---------------------------------------------------------------------------
706 #---------------------------------------------------------------------------
707
707
708 def _auto_call_tip(self):
708 def _auto_call_tip(self):
709 """Trigger call tip automatically on open parenthesis
709 """Trigger call tip automatically on open parenthesis
710
710
711 Call tips can be requested explcitly with `_call_tip`.
711 Call tips can be requested explcitly with `_call_tip`.
712 """
712 """
713 cursor = self._get_cursor()
713 cursor = self._get_cursor()
714 cursor.movePosition(QtGui.QTextCursor.Left)
714 cursor.movePosition(QtGui.QTextCursor.Left)
715 if cursor.document().characterAt(cursor.position()) == '(':
715 if cursor.document().characterAt(cursor.position()) == '(':
716 # trigger auto call tip on open paren
716 # trigger auto call tip on open paren
717 self._call_tip()
717 self._call_tip()
718
718
719 def _call_tip(self):
719 def _call_tip(self):
720 """Shows a call tip, if appropriate, at the current cursor location."""
720 """Shows a call tip, if appropriate, at the current cursor location."""
721 # Decide if it makes sense to show a call tip
721 # Decide if it makes sense to show a call tip
722 if not self.enable_calltips or not self.kernel_client.shell_channel.is_alive():
722 if not self.enable_calltips or not self.kernel_client.shell_channel.is_alive():
723 return False
723 return False
724 cursor_pos = self._get_input_buffer_cursor_pos()
724 cursor_pos = self._get_input_buffer_cursor_pos()
725 code = self.input_buffer
725 code = self.input_buffer
726 # Send the metadata request to the kernel
726 # Send the metadata request to the kernel
727 msg_id = self.kernel_client.inspect(code, cursor_pos)
727 msg_id = self.kernel_client.inspect(code, cursor_pos)
728 pos = self._get_cursor().position()
728 pos = self._get_cursor().position()
729 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
729 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
730 return True
730 return True
731
731
732 def _complete(self):
732 def _complete(self):
733 """ Performs completion at the current cursor location.
733 """ Performs completion at the current cursor location.
734 """
734 """
735 # Send the completion request to the kernel
735 # Send the completion request to the kernel
736 msg_id = self.kernel_client.complete(
736 msg_id = self.kernel_client.complete(
737 code=self.input_buffer,
737 code=self.input_buffer,
738 cursor_pos=self._get_input_buffer_cursor_pos(),
738 cursor_pos=self._get_input_buffer_cursor_pos(),
739 )
739 )
740 pos = self._get_cursor().position()
740 pos = self._get_cursor().position()
741 info = self._CompletionRequest(msg_id, pos)
741 info = self._CompletionRequest(msg_id, pos)
742 self._request_info['complete'] = info
742 self._request_info['complete'] = info
743
743
744 def _process_execute_abort(self, msg):
744 def _process_execute_abort(self, msg):
745 """ Process a reply for an aborted execution request.
745 """ Process a reply for an aborted execution request.
746 """
746 """
747 self._append_plain_text("ERROR: execution aborted\n")
747 self._append_plain_text("ERROR: execution aborted\n")
748
748
749 def _process_execute_error(self, msg):
749 def _process_execute_error(self, msg):
750 """ Process a reply for an execution request that resulted in an error.
750 """ Process a reply for an execution request that resulted in an error.
751 """
751 """
752 content = msg['content']
752 content = msg['content']
753 # If a SystemExit is passed along, this means exit() was called - also
753 # If a SystemExit is passed along, this means exit() was called - also
754 # all the ipython %exit magic syntax of '-k' to be used to keep
754 # all the ipython %exit magic syntax of '-k' to be used to keep
755 # the kernel running
755 # the kernel running
756 if content['ename']=='SystemExit':
756 if content['ename']=='SystemExit':
757 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
757 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
758 self._keep_kernel_on_exit = keepkernel
758 self._keep_kernel_on_exit = keepkernel
759 self.exit_requested.emit(self)
759 self.exit_requested.emit(self)
760 else:
760 else:
761 traceback = ''.join(content['traceback'])
761 traceback = ''.join(content['traceback'])
762 self._append_plain_text(traceback)
762 self._append_plain_text(traceback)
763
763
764 def _process_execute_ok(self, msg):
764 def _process_execute_ok(self, msg):
765 """ Process a reply for a successful execution request.
765 """ Process a reply for a successful execution request.
766 """
766 """
767 payload = msg['content']['payload']
767 payload = msg['content'].get('payload', [])
768 for item in payload:
768 for item in payload:
769 if not self._process_execute_payload(item):
769 if not self._process_execute_payload(item):
770 warning = 'Warning: received unknown payload of type %s'
770 warning = 'Warning: received unknown payload of type %s'
771 print(warning % repr(item['source']))
771 print(warning % repr(item['source']))
772
772
773 def _process_execute_payload(self, item):
773 def _process_execute_payload(self, item):
774 """ Process a single payload item from the list of payload items in an
774 """ Process a single payload item from the list of payload items in an
775 execution reply. Returns whether the payload was handled.
775 execution reply. Returns whether the payload was handled.
776 """
776 """
777 # The basic FrontendWidget doesn't handle payloads, as they are a
777 # The basic FrontendWidget doesn't handle payloads, as they are a
778 # mechanism for going beyond the standard Python interpreter model.
778 # mechanism for going beyond the standard Python interpreter model.
779 return False
779 return False
780
780
781 def _show_interpreter_prompt(self):
781 def _show_interpreter_prompt(self):
782 """ Shows a prompt for the interpreter.
782 """ Shows a prompt for the interpreter.
783 """
783 """
784 self._show_prompt('>>> ')
784 self._show_prompt('>>> ')
785
785
786 def _show_interpreter_prompt_for_reply(self, msg):
786 def _show_interpreter_prompt_for_reply(self, msg):
787 """ Shows a prompt for the interpreter given an 'execute_reply' message.
787 """ Shows a prompt for the interpreter given an 'execute_reply' message.
788 """
788 """
789 self._show_interpreter_prompt()
789 self._show_interpreter_prompt()
790
790
791 #------ Signal handlers ----------------------------------------------------
791 #------ Signal handlers ----------------------------------------------------
792
792
793 def _document_contents_change(self, position, removed, added):
793 def _document_contents_change(self, position, removed, added):
794 """ Called whenever the document's content changes. Display a call tip
794 """ Called whenever the document's content changes. Display a call tip
795 if appropriate.
795 if appropriate.
796 """
796 """
797 # Calculate where the cursor should be *after* the change:
797 # Calculate where the cursor should be *after* the change:
798 position += added
798 position += added
799
799
800 document = self._control.document()
800 document = self._control.document()
801 if position == self._get_cursor().position():
801 if position == self._get_cursor().position():
802 self._auto_call_tip()
802 self._auto_call_tip()
803
803
804 #------ Trait default initializers -----------------------------------------
804 #------ Trait default initializers -----------------------------------------
805
805
806 def _banner_default(self):
806 def _banner_default(self):
807 """ Returns the standard Python banner.
807 """ Returns the standard Python banner.
808 """
808 """
809 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
809 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
810 '"license" for more information.'
810 '"license" for more information.'
811 return banner % (sys.version, sys.platform)
811 return banner % (sys.version, sys.platform)
@@ -1,595 +1,595 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """terminal client to the IPython kernel"""
2 """terminal client to the IPython kernel"""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 from __future__ import print_function
7 from __future__ import print_function
8
8
9 import base64
9 import base64
10 import bdb
10 import bdb
11 import errno
11 import errno
12 import signal
12 import signal
13 import os
13 import os
14 import sys
14 import sys
15 import time
15 import time
16 import subprocess
16 import subprocess
17 from getpass import getpass
17 from getpass import getpass
18 from io import BytesIO
18 from io import BytesIO
19
19
20 try:
20 try:
21 from queue import Empty # Py 3
21 from queue import Empty # Py 3
22 except ImportError:
22 except ImportError:
23 from Queue import Empty # Py 2
23 from Queue import Empty # Py 2
24
24
25 from zmq import ZMQError
25 from zmq import ZMQError
26
26
27 from IPython.core import page
27 from IPython.core import page
28 from IPython.core import release
28 from IPython.core import release
29 from IPython.terminal.console.zmqhistory import ZMQHistoryManager
29 from IPython.terminal.console.zmqhistory import ZMQHistoryManager
30 from IPython.utils.warn import warn, error
30 from IPython.utils.warn import warn, error
31 from IPython.utils import io
31 from IPython.utils import io
32 from IPython.utils.py3compat import string_types, input
32 from IPython.utils.py3compat import string_types, input
33 from IPython.utils.traitlets import List, Enum, Any, Instance, Unicode, Float, Bool
33 from IPython.utils.traitlets import List, Enum, Any, Instance, Unicode, Float, Bool
34 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
34 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
35
35
36 from IPython.terminal.interactiveshell import TerminalInteractiveShell
36 from IPython.terminal.interactiveshell import TerminalInteractiveShell
37 from IPython.terminal.console.completer import ZMQCompleter
37 from IPython.terminal.console.completer import ZMQCompleter
38
38
39 class ZMQTerminalInteractiveShell(TerminalInteractiveShell):
39 class ZMQTerminalInteractiveShell(TerminalInteractiveShell):
40 """A subclass of TerminalInteractiveShell that uses the 0MQ kernel"""
40 """A subclass of TerminalInteractiveShell that uses the 0MQ kernel"""
41 _executing = False
41 _executing = False
42 _execution_state = Unicode('')
42 _execution_state = Unicode('')
43 _pending_clearoutput = False
43 _pending_clearoutput = False
44 kernel_banner = Unicode('')
44 kernel_banner = Unicode('')
45 kernel_timeout = Float(60, config=True,
45 kernel_timeout = Float(60, config=True,
46 help="""Timeout for giving up on a kernel (in seconds).
46 help="""Timeout for giving up on a kernel (in seconds).
47
47
48 On first connect and restart, the console tests whether the
48 On first connect and restart, the console tests whether the
49 kernel is running and responsive by sending kernel_info_requests.
49 kernel is running and responsive by sending kernel_info_requests.
50 This sets the timeout in seconds for how long the kernel can take
50 This sets the timeout in seconds for how long the kernel can take
51 before being presumed dead.
51 before being presumed dead.
52 """
52 """
53 )
53 )
54
54
55 image_handler = Enum(('PIL', 'stream', 'tempfile', 'callable'),
55 image_handler = Enum(('PIL', 'stream', 'tempfile', 'callable'),
56 config=True, help=
56 config=True, help=
57 """
57 """
58 Handler for image type output. This is useful, for example,
58 Handler for image type output. This is useful, for example,
59 when connecting to the kernel in which pylab inline backend is
59 when connecting to the kernel in which pylab inline backend is
60 activated. There are four handlers defined. 'PIL': Use
60 activated. There are four handlers defined. 'PIL': Use
61 Python Imaging Library to popup image; 'stream': Use an
61 Python Imaging Library to popup image; 'stream': Use an
62 external program to show the image. Image will be fed into
62 external program to show the image. Image will be fed into
63 the STDIN of the program. You will need to configure
63 the STDIN of the program. You will need to configure
64 `stream_image_handler`; 'tempfile': Use an external program to
64 `stream_image_handler`; 'tempfile': Use an external program to
65 show the image. Image will be saved in a temporally file and
65 show the image. Image will be saved in a temporally file and
66 the program is called with the temporally file. You will need
66 the program is called with the temporally file. You will need
67 to configure `tempfile_image_handler`; 'callable': You can set
67 to configure `tempfile_image_handler`; 'callable': You can set
68 any Python callable which is called with the image data. You
68 any Python callable which is called with the image data. You
69 will need to configure `callable_image_handler`.
69 will need to configure `callable_image_handler`.
70 """
70 """
71 )
71 )
72
72
73 stream_image_handler = List(config=True, help=
73 stream_image_handler = List(config=True, help=
74 """
74 """
75 Command to invoke an image viewer program when you are using
75 Command to invoke an image viewer program when you are using
76 'stream' image handler. This option is a list of string where
76 'stream' image handler. This option is a list of string where
77 the first element is the command itself and reminders are the
77 the first element is the command itself and reminders are the
78 options for the command. Raw image data is given as STDIN to
78 options for the command. Raw image data is given as STDIN to
79 the program.
79 the program.
80 """
80 """
81 )
81 )
82
82
83 tempfile_image_handler = List(config=True, help=
83 tempfile_image_handler = List(config=True, help=
84 """
84 """
85 Command to invoke an image viewer program when you are using
85 Command to invoke an image viewer program when you are using
86 'tempfile' image handler. This option is a list of string
86 'tempfile' image handler. This option is a list of string
87 where the first element is the command itself and reminders
87 where the first element is the command itself and reminders
88 are the options for the command. You can use {file} and
88 are the options for the command. You can use {file} and
89 {format} in the string to represent the location of the
89 {format} in the string to represent the location of the
90 generated image file and image format.
90 generated image file and image format.
91 """
91 """
92 )
92 )
93
93
94 callable_image_handler = Any(config=True, help=
94 callable_image_handler = Any(config=True, help=
95 """
95 """
96 Callable object called via 'callable' image handler with one
96 Callable object called via 'callable' image handler with one
97 argument, `data`, which is `msg["content"]["data"]` where
97 argument, `data`, which is `msg["content"]["data"]` where
98 `msg` is the message from iopub channel. For exmaple, you can
98 `msg` is the message from iopub channel. For exmaple, you can
99 find base64 encoded PNG data as `data['image/png']`.
99 find base64 encoded PNG data as `data['image/png']`.
100 """
100 """
101 )
101 )
102
102
103 mime_preference = List(
103 mime_preference = List(
104 default_value=['image/png', 'image/jpeg', 'image/svg+xml'],
104 default_value=['image/png', 'image/jpeg', 'image/svg+xml'],
105 config=True, allow_none=False, help=
105 config=True, allow_none=False, help=
106 """
106 """
107 Preferred object representation MIME type in order. First
107 Preferred object representation MIME type in order. First
108 matched MIME type will be used.
108 matched MIME type will be used.
109 """
109 """
110 )
110 )
111
111
112 manager = Instance('IPython.kernel.KernelManager')
112 manager = Instance('IPython.kernel.KernelManager')
113 client = Instance('IPython.kernel.KernelClient')
113 client = Instance('IPython.kernel.KernelClient')
114 def _client_changed(self, name, old, new):
114 def _client_changed(self, name, old, new):
115 self.session_id = new.session.session
115 self.session_id = new.session.session
116 session_id = Unicode()
116 session_id = Unicode()
117
117
118 def init_completer(self):
118 def init_completer(self):
119 """Initialize the completion machinery.
119 """Initialize the completion machinery.
120
120
121 This creates completion machinery that can be used by client code,
121 This creates completion machinery that can be used by client code,
122 either interactively in-process (typically triggered by the readline
122 either interactively in-process (typically triggered by the readline
123 library), programmatically (such as in test suites) or out-of-process
123 library), programmatically (such as in test suites) or out-of-process
124 (typically over the network by remote frontends).
124 (typically over the network by remote frontends).
125 """
125 """
126 from IPython.core.completerlib import (module_completer,
126 from IPython.core.completerlib import (module_completer,
127 magic_run_completer, cd_completer)
127 magic_run_completer, cd_completer)
128
128
129 self.Completer = ZMQCompleter(self, self.client, config=self.config)
129 self.Completer = ZMQCompleter(self, self.client, config=self.config)
130
130
131
131
132 self.set_hook('complete_command', module_completer, str_key = 'import')
132 self.set_hook('complete_command', module_completer, str_key = 'import')
133 self.set_hook('complete_command', module_completer, str_key = 'from')
133 self.set_hook('complete_command', module_completer, str_key = 'from')
134 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
134 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
135 self.set_hook('complete_command', cd_completer, str_key = '%cd')
135 self.set_hook('complete_command', cd_completer, str_key = '%cd')
136
136
137 # Only configure readline if we truly are using readline. IPython can
137 # Only configure readline if we truly are using readline. IPython can
138 # do tab-completion over the network, in GUIs, etc, where readline
138 # do tab-completion over the network, in GUIs, etc, where readline
139 # itself may be absent
139 # itself may be absent
140 if self.has_readline:
140 if self.has_readline:
141 self.set_readline_completer()
141 self.set_readline_completer()
142
142
143 def run_cell(self, cell, store_history=True):
143 def run_cell(self, cell, store_history=True):
144 """Run a complete IPython cell.
144 """Run a complete IPython cell.
145
145
146 Parameters
146 Parameters
147 ----------
147 ----------
148 cell : str
148 cell : str
149 The code (including IPython code such as %magic functions) to run.
149 The code (including IPython code such as %magic functions) to run.
150 store_history : bool
150 store_history : bool
151 If True, the raw and translated cell will be stored in IPython's
151 If True, the raw and translated cell will be stored in IPython's
152 history. For user code calling back into IPython's machinery, this
152 history. For user code calling back into IPython's machinery, this
153 should be set to False.
153 should be set to False.
154 """
154 """
155 if (not cell) or cell.isspace():
155 if (not cell) or cell.isspace():
156 # pressing enter flushes any pending display
156 # pressing enter flushes any pending display
157 self.handle_iopub()
157 self.handle_iopub()
158 return
158 return
159
159
160 # flush stale replies, which could have been ignored, due to missed heartbeats
160 # flush stale replies, which could have been ignored, due to missed heartbeats
161 while self.client.shell_channel.msg_ready():
161 while self.client.shell_channel.msg_ready():
162 self.client.shell_channel.get_msg()
162 self.client.shell_channel.get_msg()
163 # execute takes 'hidden', which is the inverse of store_hist
163 # execute takes 'hidden', which is the inverse of store_hist
164 msg_id = self.client.execute(cell, not store_history)
164 msg_id = self.client.execute(cell, not store_history)
165
165
166 # first thing is wait for any side effects (output, stdin, etc.)
166 # first thing is wait for any side effects (output, stdin, etc.)
167 self._executing = True
167 self._executing = True
168 self._execution_state = "busy"
168 self._execution_state = "busy"
169 while self._execution_state != 'idle' and self.client.is_alive():
169 while self._execution_state != 'idle' and self.client.is_alive():
170 try:
170 try:
171 self.handle_input_request(msg_id, timeout=0.05)
171 self.handle_input_request(msg_id, timeout=0.05)
172 except Empty:
172 except Empty:
173 # display intermediate print statements, etc.
173 # display intermediate print statements, etc.
174 self.handle_iopub(msg_id)
174 self.handle_iopub(msg_id)
175 except ZMQError as e:
175 except ZMQError as e:
176 # Carry on if polling was interrupted by a signal
176 # Carry on if polling was interrupted by a signal
177 if e.errno != errno.EINTR:
177 if e.errno != errno.EINTR:
178 raise
178 raise
179
179
180 # after all of that is done, wait for the execute reply
180 # after all of that is done, wait for the execute reply
181 while self.client.is_alive():
181 while self.client.is_alive():
182 try:
182 try:
183 self.handle_execute_reply(msg_id, timeout=0.05)
183 self.handle_execute_reply(msg_id, timeout=0.05)
184 except Empty:
184 except Empty:
185 pass
185 pass
186 else:
186 else:
187 break
187 break
188 self._executing = False
188 self._executing = False
189
189
190 #-----------------
190 #-----------------
191 # message handlers
191 # message handlers
192 #-----------------
192 #-----------------
193
193
194 def handle_execute_reply(self, msg_id, timeout=None):
194 def handle_execute_reply(self, msg_id, timeout=None):
195 msg = self.client.shell_channel.get_msg(block=False, timeout=timeout)
195 msg = self.client.shell_channel.get_msg(block=False, timeout=timeout)
196 if msg["parent_header"].get("msg_id", None) == msg_id:
196 if msg["parent_header"].get("msg_id", None) == msg_id:
197
197
198 self.handle_iopub(msg_id)
198 self.handle_iopub(msg_id)
199
199
200 content = msg["content"]
200 content = msg["content"]
201 status = content['status']
201 status = content['status']
202
202
203 if status == 'aborted':
203 if status == 'aborted':
204 self.write('Aborted\n')
204 self.write('Aborted\n')
205 return
205 return
206 elif status == 'ok':
206 elif status == 'ok':
207 # handle payloads
207 # handle payloads
208 for item in content["payload"]:
208 for item in content.get('payload', []):
209 source = item['source']
209 source = item['source']
210 if source == 'page':
210 if source == 'page':
211 page.page(item['data']['text/plain'])
211 page.page(item['data']['text/plain'])
212 elif source == 'set_next_input':
212 elif source == 'set_next_input':
213 self.set_next_input(item['text'])
213 self.set_next_input(item['text'])
214 elif source == 'ask_exit':
214 elif source == 'ask_exit':
215 self.keepkernel=item.get('keepkernel', False)
215 self.keepkernel=item.get('keepkernel', False)
216 self.ask_exit()
216 self.ask_exit()
217
217
218 elif status == 'error':
218 elif status == 'error':
219 for frame in content["traceback"]:
219 for frame in content["traceback"]:
220 print(frame, file=io.stderr)
220 print(frame, file=io.stderr)
221
221
222 self.execution_count = int(content["execution_count"] + 1)
222 self.execution_count = int(content["execution_count"] + 1)
223
223
224 include_other_output = Bool(False, config=True,
224 include_other_output = Bool(False, config=True,
225 help="""Whether to include output from clients
225 help="""Whether to include output from clients
226 other than this one sharing the same kernel.
226 other than this one sharing the same kernel.
227
227
228 Outputs are not displayed until enter is pressed.
228 Outputs are not displayed until enter is pressed.
229 """
229 """
230 )
230 )
231 other_output_prefix = Unicode("[remote] ", config=True,
231 other_output_prefix = Unicode("[remote] ", config=True,
232 help="""Prefix to add to outputs coming from clients other than this one.
232 help="""Prefix to add to outputs coming from clients other than this one.
233
233
234 Only relevant if include_other_output is True.
234 Only relevant if include_other_output is True.
235 """
235 """
236 )
236 )
237
237
238 def from_here(self, msg):
238 def from_here(self, msg):
239 """Return whether a message is from this session"""
239 """Return whether a message is from this session"""
240 return msg['parent_header'].get("session", self.session_id) == self.session_id
240 return msg['parent_header'].get("session", self.session_id) == self.session_id
241
241
242 def include_output(self, msg):
242 def include_output(self, msg):
243 """Return whether we should include a given output message"""
243 """Return whether we should include a given output message"""
244 from_here = self.from_here(msg)
244 from_here = self.from_here(msg)
245 if msg['msg_type'] == 'execute_input':
245 if msg['msg_type'] == 'execute_input':
246 # only echo inputs not from here
246 # only echo inputs not from here
247 return self.include_other_output and not from_here
247 return self.include_other_output and not from_here
248
248
249 if self.include_other_output:
249 if self.include_other_output:
250 return True
250 return True
251 else:
251 else:
252 return from_here
252 return from_here
253
253
254 def handle_iopub(self, msg_id=''):
254 def handle_iopub(self, msg_id=''):
255 """Process messages on the IOPub channel
255 """Process messages on the IOPub channel
256
256
257 This method consumes and processes messages on the IOPub channel,
257 This method consumes and processes messages on the IOPub channel,
258 such as stdout, stderr, execute_result and status.
258 such as stdout, stderr, execute_result and status.
259
259
260 It only displays output that is caused by this session.
260 It only displays output that is caused by this session.
261 """
261 """
262 while self.client.iopub_channel.msg_ready():
262 while self.client.iopub_channel.msg_ready():
263 sub_msg = self.client.iopub_channel.get_msg()
263 sub_msg = self.client.iopub_channel.get_msg()
264 msg_type = sub_msg['header']['msg_type']
264 msg_type = sub_msg['header']['msg_type']
265 parent = sub_msg["parent_header"]
265 parent = sub_msg["parent_header"]
266
266
267 if self.include_output(sub_msg):
267 if self.include_output(sub_msg):
268 if msg_type == 'status':
268 if msg_type == 'status':
269 self._execution_state = sub_msg["content"]["execution_state"]
269 self._execution_state = sub_msg["content"]["execution_state"]
270 elif msg_type == 'stream':
270 elif msg_type == 'stream':
271 if sub_msg["content"]["name"] == "stdout":
271 if sub_msg["content"]["name"] == "stdout":
272 if self._pending_clearoutput:
272 if self._pending_clearoutput:
273 print("\r", file=io.stdout, end="")
273 print("\r", file=io.stdout, end="")
274 self._pending_clearoutput = False
274 self._pending_clearoutput = False
275 print(sub_msg["content"]["text"], file=io.stdout, end="")
275 print(sub_msg["content"]["text"], file=io.stdout, end="")
276 io.stdout.flush()
276 io.stdout.flush()
277 elif sub_msg["content"]["name"] == "stderr":
277 elif sub_msg["content"]["name"] == "stderr":
278 if self._pending_clearoutput:
278 if self._pending_clearoutput:
279 print("\r", file=io.stderr, end="")
279 print("\r", file=io.stderr, end="")
280 self._pending_clearoutput = False
280 self._pending_clearoutput = False
281 print(sub_msg["content"]["text"], file=io.stderr, end="")
281 print(sub_msg["content"]["text"], file=io.stderr, end="")
282 io.stderr.flush()
282 io.stderr.flush()
283
283
284 elif msg_type == 'execute_result':
284 elif msg_type == 'execute_result':
285 if self._pending_clearoutput:
285 if self._pending_clearoutput:
286 print("\r", file=io.stdout, end="")
286 print("\r", file=io.stdout, end="")
287 self._pending_clearoutput = False
287 self._pending_clearoutput = False
288 self.execution_count = int(sub_msg["content"]["execution_count"])
288 self.execution_count = int(sub_msg["content"]["execution_count"])
289 if not self.from_here(sub_msg):
289 if not self.from_here(sub_msg):
290 sys.stdout.write(self.other_output_prefix)
290 sys.stdout.write(self.other_output_prefix)
291 format_dict = sub_msg["content"]["data"]
291 format_dict = sub_msg["content"]["data"]
292 self.handle_rich_data(format_dict)
292 self.handle_rich_data(format_dict)
293
293
294 # taken from DisplayHook.__call__:
294 # taken from DisplayHook.__call__:
295 hook = self.displayhook
295 hook = self.displayhook
296 hook.start_displayhook()
296 hook.start_displayhook()
297 hook.write_output_prompt()
297 hook.write_output_prompt()
298 hook.write_format_data(format_dict)
298 hook.write_format_data(format_dict)
299 hook.log_output(format_dict)
299 hook.log_output(format_dict)
300 hook.finish_displayhook()
300 hook.finish_displayhook()
301
301
302 elif msg_type == 'display_data':
302 elif msg_type == 'display_data':
303 data = sub_msg["content"]["data"]
303 data = sub_msg["content"]["data"]
304 handled = self.handle_rich_data(data)
304 handled = self.handle_rich_data(data)
305 if not handled:
305 if not handled:
306 if not self.from_here(sub_msg):
306 if not self.from_here(sub_msg):
307 sys.stdout.write(self.other_output_prefix)
307 sys.stdout.write(self.other_output_prefix)
308 # if it was an image, we handled it by now
308 # if it was an image, we handled it by now
309 if 'text/plain' in data:
309 if 'text/plain' in data:
310 print(data['text/plain'])
310 print(data['text/plain'])
311
311
312 elif msg_type == 'execute_input':
312 elif msg_type == 'execute_input':
313 content = sub_msg['content']
313 content = sub_msg['content']
314 self.execution_count = content['execution_count']
314 self.execution_count = content['execution_count']
315 if not self.from_here(sub_msg):
315 if not self.from_here(sub_msg):
316 sys.stdout.write(self.other_output_prefix)
316 sys.stdout.write(self.other_output_prefix)
317 sys.stdout.write(self.prompt_manager.render('in'))
317 sys.stdout.write(self.prompt_manager.render('in'))
318 sys.stdout.write(content['code'])
318 sys.stdout.write(content['code'])
319
319
320 elif msg_type == 'clear_output':
320 elif msg_type == 'clear_output':
321 if sub_msg["content"]["wait"]:
321 if sub_msg["content"]["wait"]:
322 self._pending_clearoutput = True
322 self._pending_clearoutput = True
323 else:
323 else:
324 print("\r", file=io.stdout, end="")
324 print("\r", file=io.stdout, end="")
325
325
326 _imagemime = {
326 _imagemime = {
327 'image/png': 'png',
327 'image/png': 'png',
328 'image/jpeg': 'jpeg',
328 'image/jpeg': 'jpeg',
329 'image/svg+xml': 'svg',
329 'image/svg+xml': 'svg',
330 }
330 }
331
331
332 def handle_rich_data(self, data):
332 def handle_rich_data(self, data):
333 for mime in self.mime_preference:
333 for mime in self.mime_preference:
334 if mime in data and mime in self._imagemime:
334 if mime in data and mime in self._imagemime:
335 self.handle_image(data, mime)
335 self.handle_image(data, mime)
336 return True
336 return True
337
337
338 def handle_image(self, data, mime):
338 def handle_image(self, data, mime):
339 handler = getattr(
339 handler = getattr(
340 self, 'handle_image_{0}'.format(self.image_handler), None)
340 self, 'handle_image_{0}'.format(self.image_handler), None)
341 if handler:
341 if handler:
342 handler(data, mime)
342 handler(data, mime)
343
343
344 def handle_image_PIL(self, data, mime):
344 def handle_image_PIL(self, data, mime):
345 if mime not in ('image/png', 'image/jpeg'):
345 if mime not in ('image/png', 'image/jpeg'):
346 return
346 return
347 import PIL.Image
347 import PIL.Image
348 raw = base64.decodestring(data[mime].encode('ascii'))
348 raw = base64.decodestring(data[mime].encode('ascii'))
349 img = PIL.Image.open(BytesIO(raw))
349 img = PIL.Image.open(BytesIO(raw))
350 img.show()
350 img.show()
351
351
352 def handle_image_stream(self, data, mime):
352 def handle_image_stream(self, data, mime):
353 raw = base64.decodestring(data[mime].encode('ascii'))
353 raw = base64.decodestring(data[mime].encode('ascii'))
354 imageformat = self._imagemime[mime]
354 imageformat = self._imagemime[mime]
355 fmt = dict(format=imageformat)
355 fmt = dict(format=imageformat)
356 args = [s.format(**fmt) for s in self.stream_image_handler]
356 args = [s.format(**fmt) for s in self.stream_image_handler]
357 with open(os.devnull, 'w') as devnull:
357 with open(os.devnull, 'w') as devnull:
358 proc = subprocess.Popen(
358 proc = subprocess.Popen(
359 args, stdin=subprocess.PIPE,
359 args, stdin=subprocess.PIPE,
360 stdout=devnull, stderr=devnull)
360 stdout=devnull, stderr=devnull)
361 proc.communicate(raw)
361 proc.communicate(raw)
362
362
363 def handle_image_tempfile(self, data, mime):
363 def handle_image_tempfile(self, data, mime):
364 raw = base64.decodestring(data[mime].encode('ascii'))
364 raw = base64.decodestring(data[mime].encode('ascii'))
365 imageformat = self._imagemime[mime]
365 imageformat = self._imagemime[mime]
366 filename = 'tmp.{0}'.format(imageformat)
366 filename = 'tmp.{0}'.format(imageformat)
367 with NamedFileInTemporaryDirectory(filename) as f, \
367 with NamedFileInTemporaryDirectory(filename) as f, \
368 open(os.devnull, 'w') as devnull:
368 open(os.devnull, 'w') as devnull:
369 f.write(raw)
369 f.write(raw)
370 f.flush()
370 f.flush()
371 fmt = dict(file=f.name, format=imageformat)
371 fmt = dict(file=f.name, format=imageformat)
372 args = [s.format(**fmt) for s in self.tempfile_image_handler]
372 args = [s.format(**fmt) for s in self.tempfile_image_handler]
373 subprocess.call(args, stdout=devnull, stderr=devnull)
373 subprocess.call(args, stdout=devnull, stderr=devnull)
374
374
375 def handle_image_callable(self, data, mime):
375 def handle_image_callable(self, data, mime):
376 self.callable_image_handler(data)
376 self.callable_image_handler(data)
377
377
378 def handle_input_request(self, msg_id, timeout=0.1):
378 def handle_input_request(self, msg_id, timeout=0.1):
379 """ Method to capture raw_input
379 """ Method to capture raw_input
380 """
380 """
381 req = self.client.stdin_channel.get_msg(timeout=timeout)
381 req = self.client.stdin_channel.get_msg(timeout=timeout)
382 # in case any iopub came while we were waiting:
382 # in case any iopub came while we were waiting:
383 self.handle_iopub(msg_id)
383 self.handle_iopub(msg_id)
384 if msg_id == req["parent_header"].get("msg_id"):
384 if msg_id == req["parent_header"].get("msg_id"):
385 # wrap SIGINT handler
385 # wrap SIGINT handler
386 real_handler = signal.getsignal(signal.SIGINT)
386 real_handler = signal.getsignal(signal.SIGINT)
387 def double_int(sig,frame):
387 def double_int(sig,frame):
388 # call real handler (forwards sigint to kernel),
388 # call real handler (forwards sigint to kernel),
389 # then raise local interrupt, stopping local raw_input
389 # then raise local interrupt, stopping local raw_input
390 real_handler(sig,frame)
390 real_handler(sig,frame)
391 raise KeyboardInterrupt
391 raise KeyboardInterrupt
392 signal.signal(signal.SIGINT, double_int)
392 signal.signal(signal.SIGINT, double_int)
393 content = req['content']
393 content = req['content']
394 read = getpass if content.get('password', False) else input
394 read = getpass if content.get('password', False) else input
395 try:
395 try:
396 raw_data = read(content["prompt"])
396 raw_data = read(content["prompt"])
397 except EOFError:
397 except EOFError:
398 # turn EOFError into EOF character
398 # turn EOFError into EOF character
399 raw_data = '\x04'
399 raw_data = '\x04'
400 except KeyboardInterrupt:
400 except KeyboardInterrupt:
401 sys.stdout.write('\n')
401 sys.stdout.write('\n')
402 return
402 return
403 finally:
403 finally:
404 # restore SIGINT handler
404 # restore SIGINT handler
405 signal.signal(signal.SIGINT, real_handler)
405 signal.signal(signal.SIGINT, real_handler)
406
406
407 # only send stdin reply if there *was not* another request
407 # only send stdin reply if there *was not* another request
408 # or execution finished while we were reading.
408 # or execution finished while we were reading.
409 if not (self.client.stdin_channel.msg_ready() or self.client.shell_channel.msg_ready()):
409 if not (self.client.stdin_channel.msg_ready() or self.client.shell_channel.msg_ready()):
410 self.client.input(raw_data)
410 self.client.input(raw_data)
411
411
412 def mainloop(self, display_banner=False):
412 def mainloop(self, display_banner=False):
413 self.keepkernel = False
413 self.keepkernel = False
414 while True:
414 while True:
415 try:
415 try:
416 self.interact(display_banner=display_banner)
416 self.interact(display_banner=display_banner)
417 #self.interact_with_readline()
417 #self.interact_with_readline()
418 # XXX for testing of a readline-decoupled repl loop, call
418 # XXX for testing of a readline-decoupled repl loop, call
419 # interact_with_readline above
419 # interact_with_readline above
420 break
420 break
421 except KeyboardInterrupt:
421 except KeyboardInterrupt:
422 # this should not be necessary, but KeyboardInterrupt
422 # this should not be necessary, but KeyboardInterrupt
423 # handling seems rather unpredictable...
423 # handling seems rather unpredictable...
424 self.write("\nKeyboardInterrupt in interact()\n")
424 self.write("\nKeyboardInterrupt in interact()\n")
425
425
426
426
427 if self.keepkernel and not self.own_kernel:
427 if self.keepkernel and not self.own_kernel:
428 print('keeping kernel alive')
428 print('keeping kernel alive')
429 elif self.keepkernel and self.own_kernel :
429 elif self.keepkernel and self.own_kernel :
430 print("owning kernel, cannot keep it alive")
430 print("owning kernel, cannot keep it alive")
431 self.client.shutdown()
431 self.client.shutdown()
432 else :
432 else :
433 print("Shutting down kernel")
433 print("Shutting down kernel")
434 self.client.shutdown()
434 self.client.shutdown()
435
435
436 def _banner1_default(self):
436 def _banner1_default(self):
437 return "IPython Console {version}\n".format(version=release.version)
437 return "IPython Console {version}\n".format(version=release.version)
438
438
439 def compute_banner(self):
439 def compute_banner(self):
440 super(ZMQTerminalInteractiveShell, self).compute_banner()
440 super(ZMQTerminalInteractiveShell, self).compute_banner()
441 if self.client and not self.kernel_banner:
441 if self.client and not self.kernel_banner:
442 msg_id = self.client.kernel_info()
442 msg_id = self.client.kernel_info()
443 while True:
443 while True:
444 try:
444 try:
445 reply = self.client.get_shell_msg(timeout=1)
445 reply = self.client.get_shell_msg(timeout=1)
446 except Empty:
446 except Empty:
447 break
447 break
448 else:
448 else:
449 if reply['parent_header'].get('msg_id') == msg_id:
449 if reply['parent_header'].get('msg_id') == msg_id:
450 self.kernel_banner = reply['content'].get('banner', '')
450 self.kernel_banner = reply['content'].get('banner', '')
451 break
451 break
452 self.banner += self.kernel_banner
452 self.banner += self.kernel_banner
453
453
454 def wait_for_kernel(self, timeout=None):
454 def wait_for_kernel(self, timeout=None):
455 """method to wait for a kernel to be ready"""
455 """method to wait for a kernel to be ready"""
456 tic = time.time()
456 tic = time.time()
457 self.client.hb_channel.unpause()
457 self.client.hb_channel.unpause()
458 while True:
458 while True:
459 msg_id = self.client.kernel_info()
459 msg_id = self.client.kernel_info()
460 reply = None
460 reply = None
461 while True:
461 while True:
462 try:
462 try:
463 reply = self.client.get_shell_msg(timeout=1)
463 reply = self.client.get_shell_msg(timeout=1)
464 except Empty:
464 except Empty:
465 break
465 break
466 else:
466 else:
467 if reply['parent_header'].get('msg_id') == msg_id:
467 if reply['parent_header'].get('msg_id') == msg_id:
468 return True
468 return True
469 if timeout is not None \
469 if timeout is not None \
470 and (time.time() - tic) > timeout \
470 and (time.time() - tic) > timeout \
471 and not self.client.hb_channel.is_beating():
471 and not self.client.hb_channel.is_beating():
472 # heart failed
472 # heart failed
473 return False
473 return False
474 return True
474 return True
475
475
476 def interact(self, display_banner=None):
476 def interact(self, display_banner=None):
477 """Closely emulate the interactive Python console."""
477 """Closely emulate the interactive Python console."""
478
478
479 # batch run -> do not interact
479 # batch run -> do not interact
480 if self.exit_now:
480 if self.exit_now:
481 return
481 return
482
482
483 if display_banner is None:
483 if display_banner is None:
484 display_banner = self.display_banner
484 display_banner = self.display_banner
485
485
486 if isinstance(display_banner, string_types):
486 if isinstance(display_banner, string_types):
487 self.show_banner(display_banner)
487 self.show_banner(display_banner)
488 elif display_banner:
488 elif display_banner:
489 self.show_banner()
489 self.show_banner()
490
490
491 more = False
491 more = False
492
492
493 # run a non-empty no-op, so that we don't get a prompt until
493 # run a non-empty no-op, so that we don't get a prompt until
494 # we know the kernel is ready. This keeps the connection
494 # we know the kernel is ready. This keeps the connection
495 # message above the first prompt.
495 # message above the first prompt.
496 if not self.wait_for_kernel(self.kernel_timeout):
496 if not self.wait_for_kernel(self.kernel_timeout):
497 error("Kernel did not respond\n")
497 error("Kernel did not respond\n")
498 return
498 return
499
499
500 if self.has_readline:
500 if self.has_readline:
501 self.readline_startup_hook(self.pre_readline)
501 self.readline_startup_hook(self.pre_readline)
502 hlen_b4_cell = self.readline.get_current_history_length()
502 hlen_b4_cell = self.readline.get_current_history_length()
503 else:
503 else:
504 hlen_b4_cell = 0
504 hlen_b4_cell = 0
505 # exit_now is set by a call to %Exit or %Quit, through the
505 # exit_now is set by a call to %Exit or %Quit, through the
506 # ask_exit callback.
506 # ask_exit callback.
507
507
508 while not self.exit_now:
508 while not self.exit_now:
509 if not self.client.is_alive():
509 if not self.client.is_alive():
510 # kernel died, prompt for action or exit
510 # kernel died, prompt for action or exit
511
511
512 action = "restart" if self.manager else "wait for restart"
512 action = "restart" if self.manager else "wait for restart"
513 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
513 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
514 if ans:
514 if ans:
515 if self.manager:
515 if self.manager:
516 self.manager.restart_kernel(True)
516 self.manager.restart_kernel(True)
517 self.wait_for_kernel(self.kernel_timeout)
517 self.wait_for_kernel(self.kernel_timeout)
518 else:
518 else:
519 self.exit_now = True
519 self.exit_now = True
520 continue
520 continue
521 try:
521 try:
522 # protect prompt block from KeyboardInterrupt
522 # protect prompt block from KeyboardInterrupt
523 # when sitting on ctrl-C
523 # when sitting on ctrl-C
524 self.hooks.pre_prompt_hook()
524 self.hooks.pre_prompt_hook()
525 if more:
525 if more:
526 try:
526 try:
527 prompt = self.prompt_manager.render('in2')
527 prompt = self.prompt_manager.render('in2')
528 except Exception:
528 except Exception:
529 self.showtraceback()
529 self.showtraceback()
530 if self.autoindent:
530 if self.autoindent:
531 self.rl_do_indent = True
531 self.rl_do_indent = True
532
532
533 else:
533 else:
534 try:
534 try:
535 prompt = self.separate_in + self.prompt_manager.render('in')
535 prompt = self.separate_in + self.prompt_manager.render('in')
536 except Exception:
536 except Exception:
537 self.showtraceback()
537 self.showtraceback()
538
538
539 line = self.raw_input(prompt)
539 line = self.raw_input(prompt)
540 if self.exit_now:
540 if self.exit_now:
541 # quick exit on sys.std[in|out] close
541 # quick exit on sys.std[in|out] close
542 break
542 break
543 if self.autoindent:
543 if self.autoindent:
544 self.rl_do_indent = False
544 self.rl_do_indent = False
545
545
546 except KeyboardInterrupt:
546 except KeyboardInterrupt:
547 #double-guard against keyboardinterrupts during kbdint handling
547 #double-guard against keyboardinterrupts during kbdint handling
548 try:
548 try:
549 self.write('\n' + self.get_exception_only())
549 self.write('\n' + self.get_exception_only())
550 source_raw = self.input_splitter.raw_reset()
550 source_raw = self.input_splitter.raw_reset()
551 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
551 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
552 more = False
552 more = False
553 except KeyboardInterrupt:
553 except KeyboardInterrupt:
554 pass
554 pass
555 except EOFError:
555 except EOFError:
556 if self.autoindent:
556 if self.autoindent:
557 self.rl_do_indent = False
557 self.rl_do_indent = False
558 if self.has_readline:
558 if self.has_readline:
559 self.readline_startup_hook(None)
559 self.readline_startup_hook(None)
560 self.write('\n')
560 self.write('\n')
561 self.exit()
561 self.exit()
562 except bdb.BdbQuit:
562 except bdb.BdbQuit:
563 warn('The Python debugger has exited with a BdbQuit exception.\n'
563 warn('The Python debugger has exited with a BdbQuit exception.\n'
564 'Because of how pdb handles the stack, it is impossible\n'
564 'Because of how pdb handles the stack, it is impossible\n'
565 'for IPython to properly format this particular exception.\n'
565 'for IPython to properly format this particular exception.\n'
566 'IPython will resume normal operation.')
566 'IPython will resume normal operation.')
567 except:
567 except:
568 # exceptions here are VERY RARE, but they can be triggered
568 # exceptions here are VERY RARE, but they can be triggered
569 # asynchronously by signal handlers, for example.
569 # asynchronously by signal handlers, for example.
570 self.showtraceback()
570 self.showtraceback()
571 else:
571 else:
572 try:
572 try:
573 self.input_splitter.push(line)
573 self.input_splitter.push(line)
574 more = self.input_splitter.push_accepts_more()
574 more = self.input_splitter.push_accepts_more()
575 except SyntaxError:
575 except SyntaxError:
576 # Run the code directly - run_cell takes care of displaying
576 # Run the code directly - run_cell takes care of displaying
577 # the exception.
577 # the exception.
578 more = False
578 more = False
579 if (self.SyntaxTB.last_syntax_error and
579 if (self.SyntaxTB.last_syntax_error and
580 self.autoedit_syntax):
580 self.autoedit_syntax):
581 self.edit_syntax_error()
581 self.edit_syntax_error()
582 if not more:
582 if not more:
583 source_raw = self.input_splitter.raw_reset()
583 source_raw = self.input_splitter.raw_reset()
584 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
584 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
585 self.run_cell(source_raw)
585 self.run_cell(source_raw)
586
586
587
587
588 # Turn off the exit flag, so the mainloop can be restarted if desired
588 # Turn off the exit flag, so the mainloop can be restarted if desired
589 self.exit_now = False
589 self.exit_now = False
590
590
591 def init_history(self):
591 def init_history(self):
592 """Sets up the command history. """
592 """Sets up the command history. """
593 self.history_manager = ZMQHistoryManager(client=self.client)
593 self.history_manager = ZMQHistoryManager(client=self.client)
594 self.configurables.append(self.history_manager)
594 self.configurables.append(self.history_manager)
595
595
General Comments 0
You need to be logged in to leave comments. Login now