##// END OF EJS Templates
Use messaging methods on kernel client instead of channel objects
Thomas Kluyver -
Show More
@@ -1,82 +1,82 b''
1 # Copyright (c) IPython Development Team.
1 # Copyright (c) IPython Development Team.
2 # Distributed under the terms of the Modified BSD License.
2 # Distributed under the terms of the Modified BSD License.
3
3
4 from __future__ import print_function
4 from __future__ import print_function
5
5
6 import sys
6 import sys
7 import unittest
7 import unittest
8
8
9 from IPython.kernel.inprocess.blocking import BlockingInProcessKernelClient
9 from IPython.kernel.inprocess.blocking import BlockingInProcessKernelClient
10 from IPython.kernel.inprocess.manager import InProcessKernelManager
10 from IPython.kernel.inprocess.manager import InProcessKernelManager
11 from IPython.kernel.inprocess.ipkernel import InProcessKernel
11 from IPython.kernel.inprocess.ipkernel import InProcessKernel
12 from IPython.testing.decorators import skipif_not_matplotlib
12 from IPython.testing.decorators import skipif_not_matplotlib
13 from IPython.utils.io import capture_output
13 from IPython.utils.io import capture_output
14 from IPython.utils import py3compat
14 from IPython.utils import py3compat
15
15
16 if py3compat.PY3:
16 if py3compat.PY3:
17 from io import StringIO
17 from io import StringIO
18 else:
18 else:
19 from StringIO import StringIO
19 from StringIO import StringIO
20
20
21
21
22 class InProcessKernelTestCase(unittest.TestCase):
22 class InProcessKernelTestCase(unittest.TestCase):
23
23
24 def setUp(self):
24 def setUp(self):
25 self.km = InProcessKernelManager()
25 self.km = InProcessKernelManager()
26 self.km.start_kernel()
26 self.km.start_kernel()
27 self.kc = BlockingInProcessKernelClient(kernel=self.km.kernel)
27 self.kc = BlockingInProcessKernelClient(kernel=self.km.kernel)
28 self.kc.start_channels()
28 self.kc.start_channels()
29
29
30 @skipif_not_matplotlib
30 @skipif_not_matplotlib
31 def test_pylab(self):
31 def test_pylab(self):
32 """Does %pylab work in the in-process kernel?"""
32 """Does %pylab work in the in-process kernel?"""
33 kc = self.kc
33 kc = self.kc
34 kc.execute('%pylab')
34 kc.execute('%pylab')
35 msg = get_stream_message(kc)
35 msg = get_stream_message(kc)
36 self.assertIn('matplotlib', msg['content']['text'])
36 self.assertIn('matplotlib', msg['content']['text'])
37
37
38 def test_raw_input(self):
38 def test_raw_input(self):
39 """ Does the in-process kernel handle raw_input correctly?
39 """ Does the in-process kernel handle raw_input correctly?
40 """
40 """
41 io = StringIO('foobar\n')
41 io = StringIO('foobar\n')
42 sys_stdin = sys.stdin
42 sys_stdin = sys.stdin
43 sys.stdin = io
43 sys.stdin = io
44 try:
44 try:
45 if py3compat.PY3:
45 if py3compat.PY3:
46 self.kc.execute('x = input()')
46 self.kc.execute('x = input()')
47 else:
47 else:
48 self.kc.execute('x = raw_input()')
48 self.kc.execute('x = raw_input()')
49 finally:
49 finally:
50 sys.stdin = sys_stdin
50 sys.stdin = sys_stdin
51 self.assertEqual(self.km.kernel.shell.user_ns.get('x'), 'foobar')
51 self.assertEqual(self.km.kernel.shell.user_ns.get('x'), 'foobar')
52
52
53 def test_stdout(self):
53 def test_stdout(self):
54 """ Does the in-process kernel correctly capture IO?
54 """ Does the in-process kernel correctly capture IO?
55 """
55 """
56 kernel = InProcessKernel()
56 kernel = InProcessKernel()
57
57
58 with capture_output() as io:
58 with capture_output() as io:
59 kernel.shell.run_cell('print("foo")')
59 kernel.shell.run_cell('print("foo")')
60 self.assertEqual(io.stdout, 'foo\n')
60 self.assertEqual(io.stdout, 'foo\n')
61
61
62 kc = BlockingInProcessKernelClient(kernel=kernel)
62 kc = BlockingInProcessKernelClient(kernel=kernel)
63 kernel.frontends.append(kc)
63 kernel.frontends.append(kc)
64 kc.shell_channel.execute('print("bar")')
64 kc.execute('print("bar")')
65 msg = get_stream_message(kc)
65 msg = get_stream_message(kc)
66 self.assertEqual(msg['content']['text'], 'bar\n')
66 self.assertEqual(msg['content']['text'], 'bar\n')
67
67
68 #-----------------------------------------------------------------------------
68 #-----------------------------------------------------------------------------
69 # Utility functions
69 # Utility functions
70 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
71
71
72 def get_stream_message(kernel_client, timeout=5):
72 def get_stream_message(kernel_client, timeout=5):
73 """ Gets a single stream message synchronously from the sub channel.
73 """ Gets a single stream message synchronously from the sub channel.
74 """
74 """
75 while True:
75 while True:
76 msg = kernel_client.get_iopub_msg(timeout=timeout)
76 msg = kernel_client.get_iopub_msg(timeout=timeout)
77 if msg['header']['msg_type'] == 'stream':
77 if msg['header']['msg_type'] == 'stream':
78 return msg
78 return msg
79
79
80
80
81 if __name__ == '__main__':
81 if __name__ == '__main__':
82 unittest.main()
82 unittest.main()
@@ -1,808 +1,808 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
85
86 # An option and corresponding signal for overriding the default kernel
86 # An option and corresponding signal for overriding the default kernel
87 # interrupt behavior.
87 # interrupt behavior.
88 custom_interrupt = Bool(False)
88 custom_interrupt = Bool(False)
89 custom_interrupt_requested = QtCore.Signal()
89 custom_interrupt_requested = QtCore.Signal()
90
90
91 # An option and corresponding signals for overriding the default kernel
91 # An option and corresponding signals for overriding the default kernel
92 # restart behavior.
92 # restart behavior.
93 custom_restart = Bool(False)
93 custom_restart = Bool(False)
94 custom_restart_kernel_died = QtCore.Signal(float)
94 custom_restart_kernel_died = QtCore.Signal(float)
95 custom_restart_requested = QtCore.Signal()
95 custom_restart_requested = QtCore.Signal()
96
96
97 # Whether to automatically show calltips on open-parentheses.
97 # Whether to automatically show calltips on open-parentheses.
98 enable_calltips = Bool(True, config=True,
98 enable_calltips = Bool(True, config=True,
99 help="Whether to draw information calltips on open-parentheses.")
99 help="Whether to draw information calltips on open-parentheses.")
100
100
101 clear_on_kernel_restart = Bool(True, config=True,
101 clear_on_kernel_restart = Bool(True, config=True,
102 help="Whether to clear the console when the kernel is restarted")
102 help="Whether to clear the console when the kernel is restarted")
103
103
104 confirm_restart = Bool(True, config=True,
104 confirm_restart = Bool(True, config=True,
105 help="Whether to ask for user confirmation when restarting kernel")
105 help="Whether to ask for user confirmation when restarting kernel")
106
106
107 lexer_class = DottedObjectName(config=True,
107 lexer_class = DottedObjectName(config=True,
108 help="The pygments lexer class to use."
108 help="The pygments lexer class to use."
109 )
109 )
110 def _lexer_class_changed(self, name, old, new):
110 def _lexer_class_changed(self, name, old, new):
111 lexer_class = import_item(new)
111 lexer_class = import_item(new)
112 self.lexer = lexer_class()
112 self.lexer = lexer_class()
113
113
114 def _lexer_class_default(self):
114 def _lexer_class_default(self):
115 if py3compat.PY3:
115 if py3compat.PY3:
116 return 'pygments.lexers.Python3Lexer'
116 return 'pygments.lexers.Python3Lexer'
117 else:
117 else:
118 return 'pygments.lexers.PythonLexer'
118 return 'pygments.lexers.PythonLexer'
119
119
120 lexer = Any()
120 lexer = Any()
121 def _lexer_default(self):
121 def _lexer_default(self):
122 lexer_class = import_item(self.lexer_class)
122 lexer_class = import_item(self.lexer_class)
123 return lexer_class()
123 return lexer_class()
124
124
125 # Emitted when a user visible 'execute_request' has been submitted to the
125 # Emitted when a user visible 'execute_request' has been submitted to the
126 # kernel from the FrontendWidget. Contains the code to be executed.
126 # kernel from the FrontendWidget. Contains the code to be executed.
127 executing = QtCore.Signal(object)
127 executing = QtCore.Signal(object)
128
128
129 # Emitted when a user-visible 'execute_reply' has been received from the
129 # Emitted when a user-visible 'execute_reply' has been received from the
130 # kernel and processed by the FrontendWidget. Contains the response message.
130 # kernel and processed by the FrontendWidget. Contains the response message.
131 executed = QtCore.Signal(object)
131 executed = QtCore.Signal(object)
132
132
133 # Emitted when an exit request has been received from the kernel.
133 # Emitted when an exit request has been received from the kernel.
134 exit_requested = QtCore.Signal(object)
134 exit_requested = QtCore.Signal(object)
135
135
136 # Protected class variables.
136 # Protected class variables.
137 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
137 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
138 logical_line_transforms=[],
138 logical_line_transforms=[],
139 python_line_transforms=[],
139 python_line_transforms=[],
140 )
140 )
141 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
141 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
142 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
142 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
143 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
143 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
144 _input_splitter_class = InputSplitter
144 _input_splitter_class = InputSplitter
145 _local_kernel = False
145 _local_kernel = False
146 _highlighter = Instance(FrontendHighlighter)
146 _highlighter = Instance(FrontendHighlighter)
147
147
148 #---------------------------------------------------------------------------
148 #---------------------------------------------------------------------------
149 # 'object' interface
149 # 'object' interface
150 #---------------------------------------------------------------------------
150 #---------------------------------------------------------------------------
151
151
152 def __init__(self, *args, **kw):
152 def __init__(self, *args, **kw):
153 super(FrontendWidget, self).__init__(*args, **kw)
153 super(FrontendWidget, self).__init__(*args, **kw)
154 # FIXME: remove this when PySide min version is updated past 1.0.7
154 # FIXME: remove this when PySide min version is updated past 1.0.7
155 # forcefully disable calltips if PySide is < 1.0.7, because they crash
155 # forcefully disable calltips if PySide is < 1.0.7, because they crash
156 if qt.QT_API == qt.QT_API_PYSIDE:
156 if qt.QT_API == qt.QT_API_PYSIDE:
157 import PySide
157 import PySide
158 if PySide.__version_info__ < (1,0,7):
158 if PySide.__version_info__ < (1,0,7):
159 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
159 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
160 self.enable_calltips = False
160 self.enable_calltips = False
161
161
162 # FrontendWidget protected variables.
162 # FrontendWidget protected variables.
163 self._bracket_matcher = BracketMatcher(self._control)
163 self._bracket_matcher = BracketMatcher(self._control)
164 self._call_tip_widget = CallTipWidget(self._control)
164 self._call_tip_widget = CallTipWidget(self._control)
165 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
165 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
166 self._hidden = False
166 self._hidden = False
167 self._highlighter = FrontendHighlighter(self, lexer=self.lexer)
167 self._highlighter = FrontendHighlighter(self, lexer=self.lexer)
168 self._input_splitter = self._input_splitter_class()
168 self._input_splitter = self._input_splitter_class()
169 self._kernel_manager = None
169 self._kernel_manager = None
170 self._kernel_client = None
170 self._kernel_client = None
171 self._request_info = {}
171 self._request_info = {}
172 self._request_info['execute'] = {};
172 self._request_info['execute'] = {};
173 self._callback_dict = {}
173 self._callback_dict = {}
174
174
175 # Configure the ConsoleWidget.
175 # Configure the ConsoleWidget.
176 self.tab_width = 4
176 self.tab_width = 4
177 self._set_continuation_prompt('... ')
177 self._set_continuation_prompt('... ')
178
178
179 # Configure the CallTipWidget.
179 # Configure the CallTipWidget.
180 self._call_tip_widget.setFont(self.font)
180 self._call_tip_widget.setFont(self.font)
181 self.font_changed.connect(self._call_tip_widget.setFont)
181 self.font_changed.connect(self._call_tip_widget.setFont)
182
182
183 # Configure actions.
183 # Configure actions.
184 action = self._copy_raw_action
184 action = self._copy_raw_action
185 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
185 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
186 action.setEnabled(False)
186 action.setEnabled(False)
187 action.setShortcut(QtGui.QKeySequence(key))
187 action.setShortcut(QtGui.QKeySequence(key))
188 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
188 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
189 action.triggered.connect(self.copy_raw)
189 action.triggered.connect(self.copy_raw)
190 self.copy_available.connect(action.setEnabled)
190 self.copy_available.connect(action.setEnabled)
191 self.addAction(action)
191 self.addAction(action)
192
192
193 # Connect signal handlers.
193 # Connect signal handlers.
194 document = self._control.document()
194 document = self._control.document()
195 document.contentsChange.connect(self._document_contents_change)
195 document.contentsChange.connect(self._document_contents_change)
196
196
197 # Set flag for whether we are connected via localhost.
197 # Set flag for whether we are connected via localhost.
198 self._local_kernel = kw.get('local_kernel',
198 self._local_kernel = kw.get('local_kernel',
199 FrontendWidget._local_kernel)
199 FrontendWidget._local_kernel)
200
200
201 # Whether or not a clear_output call is pending new output.
201 # Whether or not a clear_output call is pending new output.
202 self._pending_clearoutput = False
202 self._pending_clearoutput = False
203
203
204 #---------------------------------------------------------------------------
204 #---------------------------------------------------------------------------
205 # 'ConsoleWidget' public interface
205 # 'ConsoleWidget' public interface
206 #---------------------------------------------------------------------------
206 #---------------------------------------------------------------------------
207
207
208 def copy(self):
208 def copy(self):
209 """ Copy the currently selected text to the clipboard, removing prompts.
209 """ Copy the currently selected text to the clipboard, removing prompts.
210 """
210 """
211 if self._page_control is not None and self._page_control.hasFocus():
211 if self._page_control is not None and self._page_control.hasFocus():
212 self._page_control.copy()
212 self._page_control.copy()
213 elif self._control.hasFocus():
213 elif self._control.hasFocus():
214 text = self._control.textCursor().selection().toPlainText()
214 text = self._control.textCursor().selection().toPlainText()
215 if text:
215 if text:
216 was_newline = text[-1] == '\n'
216 was_newline = text[-1] == '\n'
217 text = self._prompt_transformer.transform_cell(text)
217 text = self._prompt_transformer.transform_cell(text)
218 if not was_newline: # user doesn't need newline
218 if not was_newline: # user doesn't need newline
219 text = text[:-1]
219 text = text[:-1]
220 QtGui.QApplication.clipboard().setText(text)
220 QtGui.QApplication.clipboard().setText(text)
221 else:
221 else:
222 self.log.debug("frontend widget : unknown copy target")
222 self.log.debug("frontend widget : unknown copy target")
223
223
224 #---------------------------------------------------------------------------
224 #---------------------------------------------------------------------------
225 # 'ConsoleWidget' abstract interface
225 # 'ConsoleWidget' abstract interface
226 #---------------------------------------------------------------------------
226 #---------------------------------------------------------------------------
227
227
228 def _is_complete(self, source, interactive):
228 def _is_complete(self, source, interactive):
229 """ Returns whether 'source' can be completely processed and a new
229 """ Returns whether 'source' can be completely processed and a new
230 prompt created. When triggered by an Enter/Return key press,
230 prompt created. When triggered by an Enter/Return key press,
231 'interactive' is True; otherwise, it is False.
231 'interactive' is True; otherwise, it is False.
232 """
232 """
233 self._input_splitter.reset()
233 self._input_splitter.reset()
234 try:
234 try:
235 complete = self._input_splitter.push(source)
235 complete = self._input_splitter.push(source)
236 except SyntaxError:
236 except SyntaxError:
237 return True
237 return True
238 if interactive:
238 if interactive:
239 complete = not self._input_splitter.push_accepts_more()
239 complete = not self._input_splitter.push_accepts_more()
240 return complete
240 return complete
241
241
242 def _execute(self, source, hidden):
242 def _execute(self, source, hidden):
243 """ Execute 'source'. If 'hidden', do not show any output.
243 """ Execute 'source'. If 'hidden', do not show any output.
244
244
245 See parent class :meth:`execute` docstring for full details.
245 See parent class :meth:`execute` docstring for full details.
246 """
246 """
247 msg_id = self.kernel_client.execute(source, hidden)
247 msg_id = self.kernel_client.execute(source, hidden)
248 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
248 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
249 self._hidden = hidden
249 self._hidden = hidden
250 if not hidden:
250 if not hidden:
251 self.executing.emit(source)
251 self.executing.emit(source)
252
252
253 def _prompt_started_hook(self):
253 def _prompt_started_hook(self):
254 """ Called immediately after a new prompt is displayed.
254 """ Called immediately after a new prompt is displayed.
255 """
255 """
256 if not self._reading:
256 if not self._reading:
257 self._highlighter.highlighting_on = True
257 self._highlighter.highlighting_on = True
258
258
259 def _prompt_finished_hook(self):
259 def _prompt_finished_hook(self):
260 """ Called immediately after a prompt is finished, i.e. when some input
260 """ Called immediately after a prompt is finished, i.e. when some input
261 will be processed and a new prompt displayed.
261 will be processed and a new prompt displayed.
262 """
262 """
263 # Flush all state from the input splitter so the next round of
263 # Flush all state from the input splitter so the next round of
264 # reading input starts with a clean buffer.
264 # reading input starts with a clean buffer.
265 self._input_splitter.reset()
265 self._input_splitter.reset()
266
266
267 if not self._reading:
267 if not self._reading:
268 self._highlighter.highlighting_on = False
268 self._highlighter.highlighting_on = False
269
269
270 def _tab_pressed(self):
270 def _tab_pressed(self):
271 """ Called when the tab key is pressed. Returns whether to continue
271 """ Called when the tab key is pressed. Returns whether to continue
272 processing the event.
272 processing the event.
273 """
273 """
274 # Perform tab completion if:
274 # Perform tab completion if:
275 # 1) The cursor is in the input buffer.
275 # 1) The cursor is in the input buffer.
276 # 2) There is a non-whitespace character before the cursor.
276 # 2) There is a non-whitespace character before the cursor.
277 text = self._get_input_buffer_cursor_line()
277 text = self._get_input_buffer_cursor_line()
278 if text is None:
278 if text is None:
279 return False
279 return False
280 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
280 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
281 if complete:
281 if complete:
282 self._complete()
282 self._complete()
283 return not complete
283 return not complete
284
284
285 #---------------------------------------------------------------------------
285 #---------------------------------------------------------------------------
286 # 'ConsoleWidget' protected interface
286 # 'ConsoleWidget' protected interface
287 #---------------------------------------------------------------------------
287 #---------------------------------------------------------------------------
288
288
289 def _context_menu_make(self, pos):
289 def _context_menu_make(self, pos):
290 """ Reimplemented to add an action for raw copy.
290 """ Reimplemented to add an action for raw copy.
291 """
291 """
292 menu = super(FrontendWidget, self)._context_menu_make(pos)
292 menu = super(FrontendWidget, self)._context_menu_make(pos)
293 for before_action in menu.actions():
293 for before_action in menu.actions():
294 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
294 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
295 QtGui.QKeySequence.ExactMatch:
295 QtGui.QKeySequence.ExactMatch:
296 menu.insertAction(before_action, self._copy_raw_action)
296 menu.insertAction(before_action, self._copy_raw_action)
297 break
297 break
298 return menu
298 return menu
299
299
300 def request_interrupt_kernel(self):
300 def request_interrupt_kernel(self):
301 if self._executing:
301 if self._executing:
302 self.interrupt_kernel()
302 self.interrupt_kernel()
303
303
304 def request_restart_kernel(self):
304 def request_restart_kernel(self):
305 message = 'Are you sure you want to restart the kernel?'
305 message = 'Are you sure you want to restart the kernel?'
306 self.restart_kernel(message, now=False)
306 self.restart_kernel(message, now=False)
307
307
308 def _event_filter_console_keypress(self, event):
308 def _event_filter_console_keypress(self, event):
309 """ Reimplemented for execution interruption and smart backspace.
309 """ Reimplemented for execution interruption and smart backspace.
310 """
310 """
311 key = event.key()
311 key = event.key()
312 if self._control_key_down(event.modifiers(), include_command=False):
312 if self._control_key_down(event.modifiers(), include_command=False):
313
313
314 if key == QtCore.Qt.Key_C and self._executing:
314 if key == QtCore.Qt.Key_C and self._executing:
315 self.request_interrupt_kernel()
315 self.request_interrupt_kernel()
316 return True
316 return True
317
317
318 elif key == QtCore.Qt.Key_Period:
318 elif key == QtCore.Qt.Key_Period:
319 self.request_restart_kernel()
319 self.request_restart_kernel()
320 return True
320 return True
321
321
322 elif not event.modifiers() & QtCore.Qt.AltModifier:
322 elif not event.modifiers() & QtCore.Qt.AltModifier:
323
323
324 # Smart backspace: remove four characters in one backspace if:
324 # Smart backspace: remove four characters in one backspace if:
325 # 1) everything left of the cursor is whitespace
325 # 1) everything left of the cursor is whitespace
326 # 2) the four characters immediately left of the cursor are spaces
326 # 2) the four characters immediately left of the cursor are spaces
327 if key == QtCore.Qt.Key_Backspace:
327 if key == QtCore.Qt.Key_Backspace:
328 col = self._get_input_buffer_cursor_column()
328 col = self._get_input_buffer_cursor_column()
329 cursor = self._control.textCursor()
329 cursor = self._control.textCursor()
330 if col > 3 and not cursor.hasSelection():
330 if col > 3 and not cursor.hasSelection():
331 text = self._get_input_buffer_cursor_line()[:col]
331 text = self._get_input_buffer_cursor_line()[:col]
332 if text.endswith(' ') and not text.strip():
332 if text.endswith(' ') and not text.strip():
333 cursor.movePosition(QtGui.QTextCursor.Left,
333 cursor.movePosition(QtGui.QTextCursor.Left,
334 QtGui.QTextCursor.KeepAnchor, 4)
334 QtGui.QTextCursor.KeepAnchor, 4)
335 cursor.removeSelectedText()
335 cursor.removeSelectedText()
336 return True
336 return True
337
337
338 return super(FrontendWidget, self)._event_filter_console_keypress(event)
338 return super(FrontendWidget, self)._event_filter_console_keypress(event)
339
339
340 def _insert_continuation_prompt(self, cursor):
340 def _insert_continuation_prompt(self, cursor):
341 """ Reimplemented for auto-indentation.
341 """ Reimplemented for auto-indentation.
342 """
342 """
343 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
343 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
344 cursor.insertText(' ' * self._input_splitter.indent_spaces)
344 cursor.insertText(' ' * self._input_splitter.indent_spaces)
345
345
346 #---------------------------------------------------------------------------
346 #---------------------------------------------------------------------------
347 # 'BaseFrontendMixin' abstract interface
347 # 'BaseFrontendMixin' abstract interface
348 #---------------------------------------------------------------------------
348 #---------------------------------------------------------------------------
349 def _handle_clear_output(self, msg):
349 def _handle_clear_output(self, msg):
350 """Handle clear output messages."""
350 """Handle clear output messages."""
351 if include_output(msg):
351 if include_output(msg):
352 wait = msg['content'].get('wait', True)
352 wait = msg['content'].get('wait', True)
353 if wait:
353 if wait:
354 self._pending_clearoutput = True
354 self._pending_clearoutput = True
355 else:
355 else:
356 self.clear_output()
356 self.clear_output()
357
357
358 def _silent_exec_callback(self, expr, callback):
358 def _silent_exec_callback(self, expr, callback):
359 """Silently execute `expr` in the kernel and call `callback` with reply
359 """Silently execute `expr` in the kernel and call `callback` with reply
360
360
361 the `expr` is evaluated silently in the kernel (without) output in
361 the `expr` is evaluated silently in the kernel (without) output in
362 the frontend. Call `callback` with the
362 the frontend. Call `callback` with the
363 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
363 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
364
364
365 Parameters
365 Parameters
366 ----------
366 ----------
367 expr : string
367 expr : string
368 valid string to be executed by the kernel.
368 valid string to be executed by the kernel.
369 callback : function
369 callback : function
370 function accepting one argument, as a string. The string will be
370 function accepting one argument, as a string. The string will be
371 the `repr` of the result of evaluating `expr`
371 the `repr` of the result of evaluating `expr`
372
372
373 The `callback` is called with the `repr()` of the result of `expr` as
373 The `callback` is called with the `repr()` of the result of `expr` as
374 first argument. To get the object, do `eval()` on the passed value.
374 first argument. To get the object, do `eval()` on the passed value.
375
375
376 See Also
376 See Also
377 --------
377 --------
378 _handle_exec_callback : private method, deal with calling callback with reply
378 _handle_exec_callback : private method, deal with calling callback with reply
379
379
380 """
380 """
381
381
382 # generate uuid, which would be used as an indication of whether or
382 # generate uuid, which would be used as an indication of whether or
383 # not the unique request originated from here (can use msg id ?)
383 # not the unique request originated from here (can use msg id ?)
384 local_uuid = str(uuid.uuid1())
384 local_uuid = str(uuid.uuid1())
385 msg_id = self.kernel_client.execute('',
385 msg_id = self.kernel_client.execute('',
386 silent=True, user_expressions={ local_uuid:expr })
386 silent=True, user_expressions={ local_uuid:expr })
387 self._callback_dict[local_uuid] = callback
387 self._callback_dict[local_uuid] = callback
388 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
388 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
389
389
390 def _handle_exec_callback(self, msg):
390 def _handle_exec_callback(self, msg):
391 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
391 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
392
392
393 Parameters
393 Parameters
394 ----------
394 ----------
395 msg : raw message send by the kernel containing an `user_expressions`
395 msg : raw message send by the kernel containing an `user_expressions`
396 and having a 'silent_exec_callback' kind.
396 and having a 'silent_exec_callback' kind.
397
397
398 Notes
398 Notes
399 -----
399 -----
400 This function will look for a `callback` associated with the
400 This function will look for a `callback` associated with the
401 corresponding message id. Association has been made by
401 corresponding message id. Association has been made by
402 `_silent_exec_callback`. `callback` is then called with the `repr()`
402 `_silent_exec_callback`. `callback` is then called with the `repr()`
403 of the value of corresponding `user_expressions` as argument.
403 of the value of corresponding `user_expressions` as argument.
404 `callback` is then removed from the known list so that any message
404 `callback` is then removed from the known list so that any message
405 coming again with the same id won't trigger it.
405 coming again with the same id won't trigger it.
406
406
407 """
407 """
408
408
409 user_exp = msg['content'].get('user_expressions')
409 user_exp = msg['content'].get('user_expressions')
410 if not user_exp:
410 if not user_exp:
411 return
411 return
412 for expression in user_exp:
412 for expression in user_exp:
413 if expression in self._callback_dict:
413 if expression in self._callback_dict:
414 self._callback_dict.pop(expression)(user_exp[expression])
414 self._callback_dict.pop(expression)(user_exp[expression])
415
415
416 def _handle_execute_reply(self, msg):
416 def _handle_execute_reply(self, msg):
417 """ Handles replies for code execution.
417 """ Handles replies for code execution.
418 """
418 """
419 self.log.debug("execute: %s", msg.get('content', ''))
419 self.log.debug("execute: %s", msg.get('content', ''))
420 msg_id = msg['parent_header']['msg_id']
420 msg_id = msg['parent_header']['msg_id']
421 info = self._request_info['execute'].get(msg_id)
421 info = self._request_info['execute'].get(msg_id)
422 # unset reading flag, because if execute finished, raw_input can't
422 # unset reading flag, because if execute finished, raw_input can't
423 # still be pending.
423 # still be pending.
424 self._reading = False
424 self._reading = False
425 if info and info.kind == 'user' and not self._hidden:
425 if info and info.kind == 'user' and not self._hidden:
426 # Make sure that all output from the SUB channel has been processed
426 # Make sure that all output from the SUB channel has been processed
427 # before writing a new prompt.
427 # before writing a new prompt.
428 self.kernel_client.iopub_channel.flush()
428 self.kernel_client.iopub_channel.flush()
429
429
430 # Reset the ANSI style information to prevent bad text in stdout
430 # Reset the ANSI style information to prevent bad text in stdout
431 # from messing up our colors. We're not a true terminal so we're
431 # from messing up our colors. We're not a true terminal so we're
432 # allowed to do this.
432 # allowed to do this.
433 if self.ansi_codes:
433 if self.ansi_codes:
434 self._ansi_processor.reset_sgr()
434 self._ansi_processor.reset_sgr()
435
435
436 content = msg['content']
436 content = msg['content']
437 status = content['status']
437 status = content['status']
438 if status == 'ok':
438 if status == 'ok':
439 self._process_execute_ok(msg)
439 self._process_execute_ok(msg)
440 elif status == 'error':
440 elif status == 'error':
441 self._process_execute_error(msg)
441 self._process_execute_error(msg)
442 elif status == 'aborted':
442 elif status == 'aborted':
443 self._process_execute_abort(msg)
443 self._process_execute_abort(msg)
444
444
445 self._show_interpreter_prompt_for_reply(msg)
445 self._show_interpreter_prompt_for_reply(msg)
446 self.executed.emit(msg)
446 self.executed.emit(msg)
447 self._request_info['execute'].pop(msg_id)
447 self._request_info['execute'].pop(msg_id)
448 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
448 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
449 self._handle_exec_callback(msg)
449 self._handle_exec_callback(msg)
450 self._request_info['execute'].pop(msg_id)
450 self._request_info['execute'].pop(msg_id)
451 else:
451 else:
452 super(FrontendWidget, self)._handle_execute_reply(msg)
452 super(FrontendWidget, self)._handle_execute_reply(msg)
453
453
454 def _handle_input_request(self, msg):
454 def _handle_input_request(self, msg):
455 """ Handle requests for raw_input.
455 """ Handle requests for raw_input.
456 """
456 """
457 self.log.debug("input: %s", msg.get('content', ''))
457 self.log.debug("input: %s", msg.get('content', ''))
458 if self._hidden:
458 if self._hidden:
459 raise RuntimeError('Request for raw input during hidden execution.')
459 raise RuntimeError('Request for raw input during hidden execution.')
460
460
461 # Make sure that all output from the SUB channel has been processed
461 # Make sure that all output from the SUB channel has been processed
462 # before entering readline mode.
462 # before entering readline mode.
463 self.kernel_client.iopub_channel.flush()
463 self.kernel_client.iopub_channel.flush()
464
464
465 def callback(line):
465 def callback(line):
466 self.kernel_client.stdin_channel.input(line)
466 self.kernel_client.input(line)
467 if self._reading:
467 if self._reading:
468 self.log.debug("Got second input request, assuming first was interrupted.")
468 self.log.debug("Got second input request, assuming first was interrupted.")
469 self._reading = False
469 self._reading = False
470 self._readline(msg['content']['prompt'], callback=callback)
470 self._readline(msg['content']['prompt'], callback=callback)
471
471
472 def _kernel_restarted_message(self, died=True):
472 def _kernel_restarted_message(self, died=True):
473 msg = "Kernel died, restarting" if died else "Kernel restarting"
473 msg = "Kernel died, restarting" if died else "Kernel restarting"
474 self._append_html("<br>%s<hr><br>" % msg,
474 self._append_html("<br>%s<hr><br>" % msg,
475 before_prompt=False
475 before_prompt=False
476 )
476 )
477
477
478 def _handle_kernel_died(self, since_last_heartbeat):
478 def _handle_kernel_died(self, since_last_heartbeat):
479 """Handle the kernel's death (if we do not own the kernel).
479 """Handle the kernel's death (if we do not own the kernel).
480 """
480 """
481 self.log.warn("kernel died: %s", since_last_heartbeat)
481 self.log.warn("kernel died: %s", since_last_heartbeat)
482 if self.custom_restart:
482 if self.custom_restart:
483 self.custom_restart_kernel_died.emit(since_last_heartbeat)
483 self.custom_restart_kernel_died.emit(since_last_heartbeat)
484 else:
484 else:
485 self._kernel_restarted_message(died=True)
485 self._kernel_restarted_message(died=True)
486 self.reset()
486 self.reset()
487
487
488 def _handle_kernel_restarted(self, died=True):
488 def _handle_kernel_restarted(self, died=True):
489 """Notice that the autorestarter restarted the kernel.
489 """Notice that the autorestarter restarted the kernel.
490
490
491 There's nothing to do but show a message.
491 There's nothing to do but show a message.
492 """
492 """
493 self.log.warn("kernel restarted")
493 self.log.warn("kernel restarted")
494 self._kernel_restarted_message(died=died)
494 self._kernel_restarted_message(died=died)
495 self.reset()
495 self.reset()
496
496
497 def _handle_inspect_reply(self, rep):
497 def _handle_inspect_reply(self, rep):
498 """Handle replies for call tips."""
498 """Handle replies for call tips."""
499 self.log.debug("oinfo: %s", rep.get('content', ''))
499 self.log.debug("oinfo: %s", rep.get('content', ''))
500 cursor = self._get_cursor()
500 cursor = self._get_cursor()
501 info = self._request_info.get('call_tip')
501 info = self._request_info.get('call_tip')
502 if info and info.id == rep['parent_header']['msg_id'] and \
502 if info and info.id == rep['parent_header']['msg_id'] and \
503 info.pos == cursor.position():
503 info.pos == cursor.position():
504 content = rep['content']
504 content = rep['content']
505 if content.get('status') == 'ok' and content.get('found', False):
505 if content.get('status') == 'ok' and content.get('found', False):
506 self._call_tip_widget.show_inspect_data(content)
506 self._call_tip_widget.show_inspect_data(content)
507
507
508 def _handle_execute_result(self, msg):
508 def _handle_execute_result(self, msg):
509 """ Handle display hook output.
509 """ Handle display hook output.
510 """
510 """
511 self.log.debug("execute_result: %s", msg.get('content', ''))
511 self.log.debug("execute_result: %s", msg.get('content', ''))
512 if self.include_output(msg):
512 if self.include_output(msg):
513 self.flush_clearoutput()
513 self.flush_clearoutput()
514 text = msg['content']['data']
514 text = msg['content']['data']
515 self._append_plain_text(text + '\n', before_prompt=True)
515 self._append_plain_text(text + '\n', before_prompt=True)
516
516
517 def _handle_stream(self, msg):
517 def _handle_stream(self, msg):
518 """ Handle stdout, stderr, and stdin.
518 """ Handle stdout, stderr, and stdin.
519 """
519 """
520 self.log.debug("stream: %s", msg.get('content', ''))
520 self.log.debug("stream: %s", msg.get('content', ''))
521 if self.include_output(msg):
521 if self.include_output(msg):
522 self.flush_clearoutput()
522 self.flush_clearoutput()
523 self.append_stream(msg['content']['text'])
523 self.append_stream(msg['content']['text'])
524
524
525 def _handle_shutdown_reply(self, msg):
525 def _handle_shutdown_reply(self, msg):
526 """ Handle shutdown signal, only if from other console.
526 """ Handle shutdown signal, only if from other console.
527 """
527 """
528 self.log.info("shutdown: %s", msg.get('content', ''))
528 self.log.info("shutdown: %s", msg.get('content', ''))
529 restart = msg.get('content', {}).get('restart', False)
529 restart = msg.get('content', {}).get('restart', False)
530 if not self._hidden and not self.from_here(msg):
530 if not self._hidden and not self.from_here(msg):
531 # got shutdown reply, request came from session other than ours
531 # got shutdown reply, request came from session other than ours
532 if restart:
532 if restart:
533 # someone restarted the kernel, handle it
533 # someone restarted the kernel, handle it
534 self._handle_kernel_restarted(died=False)
534 self._handle_kernel_restarted(died=False)
535 else:
535 else:
536 # kernel was shutdown permanently
536 # kernel was shutdown permanently
537 # this triggers exit_requested if the kernel was local,
537 # this triggers exit_requested if the kernel was local,
538 # and a dialog if the kernel was remote,
538 # and a dialog if the kernel was remote,
539 # so we don't suddenly clear the qtconsole without asking.
539 # so we don't suddenly clear the qtconsole without asking.
540 if self._local_kernel:
540 if self._local_kernel:
541 self.exit_requested.emit(self)
541 self.exit_requested.emit(self)
542 else:
542 else:
543 title = self.window().windowTitle()
543 title = self.window().windowTitle()
544 reply = QtGui.QMessageBox.question(self, title,
544 reply = QtGui.QMessageBox.question(self, title,
545 "Kernel has been shutdown permanently. "
545 "Kernel has been shutdown permanently. "
546 "Close the Console?",
546 "Close the Console?",
547 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
547 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
548 if reply == QtGui.QMessageBox.Yes:
548 if reply == QtGui.QMessageBox.Yes:
549 self.exit_requested.emit(self)
549 self.exit_requested.emit(self)
550
550
551 def _handle_status(self, msg):
551 def _handle_status(self, msg):
552 """Handle status message"""
552 """Handle status message"""
553 # This is where a busy/idle indicator would be triggered,
553 # This is where a busy/idle indicator would be triggered,
554 # when we make one.
554 # when we make one.
555 state = msg['content'].get('execution_state', '')
555 state = msg['content'].get('execution_state', '')
556 if state == 'starting':
556 if state == 'starting':
557 # kernel started while we were running
557 # kernel started while we were running
558 if self._executing:
558 if self._executing:
559 self._handle_kernel_restarted(died=True)
559 self._handle_kernel_restarted(died=True)
560 elif state == 'idle':
560 elif state == 'idle':
561 pass
561 pass
562 elif state == 'busy':
562 elif state == 'busy':
563 pass
563 pass
564
564
565 def _started_channels(self):
565 def _started_channels(self):
566 """ Called when the KernelManager channels have started listening or
566 """ Called when the KernelManager channels have started listening or
567 when the frontend is assigned an already listening KernelManager.
567 when the frontend is assigned an already listening KernelManager.
568 """
568 """
569 self.reset(clear=True)
569 self.reset(clear=True)
570
570
571 #---------------------------------------------------------------------------
571 #---------------------------------------------------------------------------
572 # 'FrontendWidget' public interface
572 # 'FrontendWidget' public interface
573 #---------------------------------------------------------------------------
573 #---------------------------------------------------------------------------
574
574
575 def copy_raw(self):
575 def copy_raw(self):
576 """ Copy the currently selected text to the clipboard without attempting
576 """ Copy the currently selected text to the clipboard without attempting
577 to remove prompts or otherwise alter the text.
577 to remove prompts or otherwise alter the text.
578 """
578 """
579 self._control.copy()
579 self._control.copy()
580
580
581 def execute_file(self, path, hidden=False):
581 def execute_file(self, path, hidden=False):
582 """ Attempts to execute file with 'path'. If 'hidden', no output is
582 """ Attempts to execute file with 'path'. If 'hidden', no output is
583 shown.
583 shown.
584 """
584 """
585 self.execute('execfile(%r)' % path, hidden=hidden)
585 self.execute('execfile(%r)' % path, hidden=hidden)
586
586
587 def interrupt_kernel(self):
587 def interrupt_kernel(self):
588 """ Attempts to interrupt the running kernel.
588 """ Attempts to interrupt the running kernel.
589
589
590 Also unsets _reading flag, to avoid runtime errors
590 Also unsets _reading flag, to avoid runtime errors
591 if raw_input is called again.
591 if raw_input is called again.
592 """
592 """
593 if self.custom_interrupt:
593 if self.custom_interrupt:
594 self._reading = False
594 self._reading = False
595 self.custom_interrupt_requested.emit()
595 self.custom_interrupt_requested.emit()
596 elif self.kernel_manager:
596 elif self.kernel_manager:
597 self._reading = False
597 self._reading = False
598 self.kernel_manager.interrupt_kernel()
598 self.kernel_manager.interrupt_kernel()
599 else:
599 else:
600 self._append_plain_text('Cannot interrupt a kernel I did not start.\n')
600 self._append_plain_text('Cannot interrupt a kernel I did not start.\n')
601
601
602 def reset(self, clear=False):
602 def reset(self, clear=False):
603 """ Resets the widget to its initial state if ``clear`` parameter
603 """ Resets the widget to its initial state if ``clear`` parameter
604 is True, otherwise
604 is True, otherwise
605 prints a visual indication of the fact that the kernel restarted, but
605 prints a visual indication of the fact that the kernel restarted, but
606 does not clear the traces from previous usage of the kernel before it
606 does not clear the traces from previous usage of the kernel before it
607 was restarted. With ``clear=True``, it is similar to ``%clear``, but
607 was restarted. With ``clear=True``, it is similar to ``%clear``, but
608 also re-writes the banner and aborts execution if necessary.
608 also re-writes the banner and aborts execution if necessary.
609 """
609 """
610 if self._executing:
610 if self._executing:
611 self._executing = False
611 self._executing = False
612 self._request_info['execute'] = {}
612 self._request_info['execute'] = {}
613 self._reading = False
613 self._reading = False
614 self._highlighter.highlighting_on = False
614 self._highlighter.highlighting_on = False
615
615
616 if clear:
616 if clear:
617 self._control.clear()
617 self._control.clear()
618 if self._display_banner:
618 if self._display_banner:
619 self._append_plain_text(self.banner)
619 self._append_plain_text(self.banner)
620 if self.kernel_banner:
620 if self.kernel_banner:
621 self._append_plain_text(self.kernel_banner)
621 self._append_plain_text(self.kernel_banner)
622
622
623 # update output marker for stdout/stderr, so that startup
623 # update output marker for stdout/stderr, so that startup
624 # messages appear after banner:
624 # messages appear after banner:
625 self._append_before_prompt_pos = self._get_cursor().position()
625 self._append_before_prompt_pos = self._get_cursor().position()
626 self._show_interpreter_prompt()
626 self._show_interpreter_prompt()
627
627
628 def restart_kernel(self, message, now=False):
628 def restart_kernel(self, message, now=False):
629 """ Attempts to restart the running kernel.
629 """ Attempts to restart the running kernel.
630 """
630 """
631 # FIXME: now should be configurable via a checkbox in the dialog. Right
631 # FIXME: now should be configurable via a checkbox in the dialog. Right
632 # now at least the heartbeat path sets it to True and the manual restart
632 # now at least the heartbeat path sets it to True and the manual restart
633 # to False. But those should just be the pre-selected states of a
633 # to False. But those should just be the pre-selected states of a
634 # checkbox that the user could override if so desired. But I don't know
634 # checkbox that the user could override if so desired. But I don't know
635 # enough Qt to go implementing the checkbox now.
635 # enough Qt to go implementing the checkbox now.
636
636
637 if self.custom_restart:
637 if self.custom_restart:
638 self.custom_restart_requested.emit()
638 self.custom_restart_requested.emit()
639 return
639 return
640
640
641 if self.kernel_manager:
641 if self.kernel_manager:
642 # Pause the heart beat channel to prevent further warnings.
642 # Pause the heart beat channel to prevent further warnings.
643 self.kernel_client.hb_channel.pause()
643 self.kernel_client.hb_channel.pause()
644
644
645 # Prompt the user to restart the kernel. Un-pause the heartbeat if
645 # Prompt the user to restart the kernel. Un-pause the heartbeat if
646 # they decline. (If they accept, the heartbeat will be un-paused
646 # they decline. (If they accept, the heartbeat will be un-paused
647 # automatically when the kernel is restarted.)
647 # automatically when the kernel is restarted.)
648 if self.confirm_restart:
648 if self.confirm_restart:
649 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
649 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
650 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
650 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
651 message, buttons)
651 message, buttons)
652 do_restart = result == QtGui.QMessageBox.Yes
652 do_restart = result == QtGui.QMessageBox.Yes
653 else:
653 else:
654 # confirm_restart is False, so we don't need to ask user
654 # confirm_restart is False, so we don't need to ask user
655 # anything, just do the restart
655 # anything, just do the restart
656 do_restart = True
656 do_restart = True
657 if do_restart:
657 if do_restart:
658 try:
658 try:
659 self.kernel_manager.restart_kernel(now=now)
659 self.kernel_manager.restart_kernel(now=now)
660 except RuntimeError as e:
660 except RuntimeError as e:
661 self._append_plain_text(
661 self._append_plain_text(
662 'Error restarting kernel: %s\n' % e,
662 'Error restarting kernel: %s\n' % e,
663 before_prompt=True
663 before_prompt=True
664 )
664 )
665 else:
665 else:
666 self._append_html("<br>Restarting kernel...\n<hr><br>",
666 self._append_html("<br>Restarting kernel...\n<hr><br>",
667 before_prompt=True,
667 before_prompt=True,
668 )
668 )
669 else:
669 else:
670 self.kernel_client.hb_channel.unpause()
670 self.kernel_client.hb_channel.unpause()
671
671
672 else:
672 else:
673 self._append_plain_text(
673 self._append_plain_text(
674 'Cannot restart a Kernel I did not start\n',
674 'Cannot restart a Kernel I did not start\n',
675 before_prompt=True
675 before_prompt=True
676 )
676 )
677
677
678 def append_stream(self, text):
678 def append_stream(self, text):
679 """Appends text to the output stream."""
679 """Appends text to the output stream."""
680 # Most consoles treat tabs as being 8 space characters. Convert tabs
680 # Most consoles treat tabs as being 8 space characters. Convert tabs
681 # to spaces so that output looks as expected regardless of this
681 # to spaces so that output looks as expected regardless of this
682 # widget's tab width.
682 # widget's tab width.
683 text = text.expandtabs(8)
683 text = text.expandtabs(8)
684 self._append_plain_text(text, before_prompt=True)
684 self._append_plain_text(text, before_prompt=True)
685 self._control.moveCursor(QtGui.QTextCursor.End)
685 self._control.moveCursor(QtGui.QTextCursor.End)
686
686
687 def flush_clearoutput(self):
687 def flush_clearoutput(self):
688 """If a clearoutput is pending, execute it."""
688 """If a clearoutput is pending, execute it."""
689 if self._pending_clearoutput:
689 if self._pending_clearoutput:
690 self._pending_clearoutput = False
690 self._pending_clearoutput = False
691 self.clear_output()
691 self.clear_output()
692
692
693 def clear_output(self):
693 def clear_output(self):
694 """Clears the current line of output."""
694 """Clears the current line of output."""
695 cursor = self._control.textCursor()
695 cursor = self._control.textCursor()
696 cursor.beginEditBlock()
696 cursor.beginEditBlock()
697 cursor.movePosition(cursor.StartOfLine, cursor.KeepAnchor)
697 cursor.movePosition(cursor.StartOfLine, cursor.KeepAnchor)
698 cursor.insertText('')
698 cursor.insertText('')
699 cursor.endEditBlock()
699 cursor.endEditBlock()
700
700
701 #---------------------------------------------------------------------------
701 #---------------------------------------------------------------------------
702 # 'FrontendWidget' protected interface
702 # 'FrontendWidget' protected interface
703 #---------------------------------------------------------------------------
703 #---------------------------------------------------------------------------
704
704
705 def _auto_call_tip(self):
705 def _auto_call_tip(self):
706 """Trigger call tip automatically on open parenthesis
706 """Trigger call tip automatically on open parenthesis
707
707
708 Call tips can be requested explcitly with `_call_tip`.
708 Call tips can be requested explcitly with `_call_tip`.
709 """
709 """
710 cursor = self._get_cursor()
710 cursor = self._get_cursor()
711 cursor.movePosition(QtGui.QTextCursor.Left)
711 cursor.movePosition(QtGui.QTextCursor.Left)
712 if cursor.document().characterAt(cursor.position()) == '(':
712 if cursor.document().characterAt(cursor.position()) == '(':
713 # trigger auto call tip on open paren
713 # trigger auto call tip on open paren
714 self._call_tip()
714 self._call_tip()
715
715
716 def _call_tip(self):
716 def _call_tip(self):
717 """Shows a call tip, if appropriate, at the current cursor location."""
717 """Shows a call tip, if appropriate, at the current cursor location."""
718 # Decide if it makes sense to show a call tip
718 # Decide if it makes sense to show a call tip
719 if not self.enable_calltips or not self.kernel_client.shell_channel.is_alive():
719 if not self.enable_calltips or not self.kernel_client.shell_channel.is_alive():
720 return False
720 return False
721 cursor_pos = self._get_input_buffer_cursor_pos()
721 cursor_pos = self._get_input_buffer_cursor_pos()
722 code = self.input_buffer
722 code = self.input_buffer
723 # Send the metadata request to the kernel
723 # Send the metadata request to the kernel
724 msg_id = self.kernel_client.inspect(code, cursor_pos)
724 msg_id = self.kernel_client.inspect(code, cursor_pos)
725 pos = self._get_cursor().position()
725 pos = self._get_cursor().position()
726 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
726 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
727 return True
727 return True
728
728
729 def _complete(self):
729 def _complete(self):
730 """ Performs completion at the current cursor location.
730 """ Performs completion at the current cursor location.
731 """
731 """
732 # Send the completion request to the kernel
732 # Send the completion request to the kernel
733 msg_id = self.kernel_client.complete(
733 msg_id = self.kernel_client.complete(
734 code=self.input_buffer,
734 code=self.input_buffer,
735 cursor_pos=self._get_input_buffer_cursor_pos(),
735 cursor_pos=self._get_input_buffer_cursor_pos(),
736 )
736 )
737 pos = self._get_cursor().position()
737 pos = self._get_cursor().position()
738 info = self._CompletionRequest(msg_id, pos)
738 info = self._CompletionRequest(msg_id, pos)
739 self._request_info['complete'] = info
739 self._request_info['complete'] = info
740
740
741 def _process_execute_abort(self, msg):
741 def _process_execute_abort(self, msg):
742 """ Process a reply for an aborted execution request.
742 """ Process a reply for an aborted execution request.
743 """
743 """
744 self._append_plain_text("ERROR: execution aborted\n")
744 self._append_plain_text("ERROR: execution aborted\n")
745
745
746 def _process_execute_error(self, msg):
746 def _process_execute_error(self, msg):
747 """ Process a reply for an execution request that resulted in an error.
747 """ Process a reply for an execution request that resulted in an error.
748 """
748 """
749 content = msg['content']
749 content = msg['content']
750 # If a SystemExit is passed along, this means exit() was called - also
750 # If a SystemExit is passed along, this means exit() was called - also
751 # all the ipython %exit magic syntax of '-k' to be used to keep
751 # all the ipython %exit magic syntax of '-k' to be used to keep
752 # the kernel running
752 # the kernel running
753 if content['ename']=='SystemExit':
753 if content['ename']=='SystemExit':
754 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
754 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
755 self._keep_kernel_on_exit = keepkernel
755 self._keep_kernel_on_exit = keepkernel
756 self.exit_requested.emit(self)
756 self.exit_requested.emit(self)
757 else:
757 else:
758 traceback = ''.join(content['traceback'])
758 traceback = ''.join(content['traceback'])
759 self._append_plain_text(traceback)
759 self._append_plain_text(traceback)
760
760
761 def _process_execute_ok(self, msg):
761 def _process_execute_ok(self, msg):
762 """ Process a reply for a successful execution request.
762 """ Process a reply for a successful execution request.
763 """
763 """
764 payload = msg['content']['payload']
764 payload = msg['content']['payload']
765 for item in payload:
765 for item in payload:
766 if not self._process_execute_payload(item):
766 if not self._process_execute_payload(item):
767 warning = 'Warning: received unknown payload of type %s'
767 warning = 'Warning: received unknown payload of type %s'
768 print(warning % repr(item['source']))
768 print(warning % repr(item['source']))
769
769
770 def _process_execute_payload(self, item):
770 def _process_execute_payload(self, item):
771 """ Process a single payload item from the list of payload items in an
771 """ Process a single payload item from the list of payload items in an
772 execution reply. Returns whether the payload was handled.
772 execution reply. Returns whether the payload was handled.
773 """
773 """
774 # The basic FrontendWidget doesn't handle payloads, as they are a
774 # The basic FrontendWidget doesn't handle payloads, as they are a
775 # mechanism for going beyond the standard Python interpreter model.
775 # mechanism for going beyond the standard Python interpreter model.
776 return False
776 return False
777
777
778 def _show_interpreter_prompt(self):
778 def _show_interpreter_prompt(self):
779 """ Shows a prompt for the interpreter.
779 """ Shows a prompt for the interpreter.
780 """
780 """
781 self._show_prompt('>>> ')
781 self._show_prompt('>>> ')
782
782
783 def _show_interpreter_prompt_for_reply(self, msg):
783 def _show_interpreter_prompt_for_reply(self, msg):
784 """ Shows a prompt for the interpreter given an 'execute_reply' message.
784 """ Shows a prompt for the interpreter given an 'execute_reply' message.
785 """
785 """
786 self._show_interpreter_prompt()
786 self._show_interpreter_prompt()
787
787
788 #------ Signal handlers ----------------------------------------------------
788 #------ Signal handlers ----------------------------------------------------
789
789
790 def _document_contents_change(self, position, removed, added):
790 def _document_contents_change(self, position, removed, added):
791 """ Called whenever the document's content changes. Display a call tip
791 """ Called whenever the document's content changes. Display a call tip
792 if appropriate.
792 if appropriate.
793 """
793 """
794 # Calculate where the cursor should be *after* the change:
794 # Calculate where the cursor should be *after* the change:
795 position += added
795 position += added
796
796
797 document = self._control.document()
797 document = self._control.document()
798 if position == self._get_cursor().position():
798 if position == self._get_cursor().position():
799 self._auto_call_tip()
799 self._auto_call_tip()
800
800
801 #------ Trait default initializers -----------------------------------------
801 #------ Trait default initializers -----------------------------------------
802
802
803 def _banner_default(self):
803 def _banner_default(self):
804 """ Returns the standard Python banner.
804 """ Returns the standard Python banner.
805 """
805 """
806 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
806 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
807 '"license" for more information.'
807 '"license" for more information.'
808 return banner % (sys.version, sys.platform)
808 return banner % (sys.version, sys.platform)
@@ -1,305 +1,305 b''
1 # System library imports
1 # System library imports
2 from IPython.external.qt import QtGui
2 from IPython.external.qt import QtGui
3
3
4 # Local imports
4 # Local imports
5 from IPython.utils.py3compat import unicode_type
5 from IPython.utils.py3compat import unicode_type
6 from IPython.utils.traitlets import Bool
6 from IPython.utils.traitlets import Bool
7 from .console_widget import ConsoleWidget
7 from .console_widget import ConsoleWidget
8
8
9
9
10 class HistoryConsoleWidget(ConsoleWidget):
10 class HistoryConsoleWidget(ConsoleWidget):
11 """ A ConsoleWidget that keeps a history of the commands that have been
11 """ A ConsoleWidget that keeps a history of the commands that have been
12 executed and provides a readline-esque interface to this history.
12 executed and provides a readline-esque interface to this history.
13 """
13 """
14
14
15 #------ Configuration ------------------------------------------------------
15 #------ Configuration ------------------------------------------------------
16
16
17 # If enabled, the input buffer will become "locked" to history movement when
17 # If enabled, the input buffer will become "locked" to history movement when
18 # an edit is made to a multi-line input buffer. To override the lock, use
18 # an edit is made to a multi-line input buffer. To override the lock, use
19 # Shift in conjunction with the standard history cycling keys.
19 # Shift in conjunction with the standard history cycling keys.
20 history_lock = Bool(False, config=True)
20 history_lock = Bool(False, config=True)
21
21
22 #---------------------------------------------------------------------------
22 #---------------------------------------------------------------------------
23 # 'object' interface
23 # 'object' interface
24 #---------------------------------------------------------------------------
24 #---------------------------------------------------------------------------
25
25
26 def __init__(self, *args, **kw):
26 def __init__(self, *args, **kw):
27 super(HistoryConsoleWidget, self).__init__(*args, **kw)
27 super(HistoryConsoleWidget, self).__init__(*args, **kw)
28
28
29 # HistoryConsoleWidget protected variables.
29 # HistoryConsoleWidget protected variables.
30 self._history = []
30 self._history = []
31 self._history_edits = {}
31 self._history_edits = {}
32 self._history_index = 0
32 self._history_index = 0
33 self._history_prefix = ''
33 self._history_prefix = ''
34
34
35 #---------------------------------------------------------------------------
35 #---------------------------------------------------------------------------
36 # 'ConsoleWidget' public interface
36 # 'ConsoleWidget' public interface
37 #---------------------------------------------------------------------------
37 #---------------------------------------------------------------------------
38
38
39 def execute(self, source=None, hidden=False, interactive=False):
39 def execute(self, source=None, hidden=False, interactive=False):
40 """ Reimplemented to the store history.
40 """ Reimplemented to the store history.
41 """
41 """
42 if not hidden:
42 if not hidden:
43 history = self.input_buffer if source is None else source
43 history = self.input_buffer if source is None else source
44
44
45 executed = super(HistoryConsoleWidget, self).execute(
45 executed = super(HistoryConsoleWidget, self).execute(
46 source, hidden, interactive)
46 source, hidden, interactive)
47
47
48 if executed and not hidden:
48 if executed and not hidden:
49 # Save the command unless it was an empty string or was identical
49 # Save the command unless it was an empty string or was identical
50 # to the previous command.
50 # to the previous command.
51 history = history.rstrip()
51 history = history.rstrip()
52 if history and (not self._history or self._history[-1] != history):
52 if history and (not self._history or self._history[-1] != history):
53 self._history.append(history)
53 self._history.append(history)
54
54
55 # Emulate readline: reset all history edits.
55 # Emulate readline: reset all history edits.
56 self._history_edits = {}
56 self._history_edits = {}
57
57
58 # Move the history index to the most recent item.
58 # Move the history index to the most recent item.
59 self._history_index = len(self._history)
59 self._history_index = len(self._history)
60
60
61 return executed
61 return executed
62
62
63 #---------------------------------------------------------------------------
63 #---------------------------------------------------------------------------
64 # 'ConsoleWidget' abstract interface
64 # 'ConsoleWidget' abstract interface
65 #---------------------------------------------------------------------------
65 #---------------------------------------------------------------------------
66
66
67 def _up_pressed(self, shift_modifier):
67 def _up_pressed(self, shift_modifier):
68 """ Called when the up key is pressed. Returns whether to continue
68 """ Called when the up key is pressed. Returns whether to continue
69 processing the event.
69 processing the event.
70 """
70 """
71 prompt_cursor = self._get_prompt_cursor()
71 prompt_cursor = self._get_prompt_cursor()
72 if self._get_cursor().blockNumber() == prompt_cursor.blockNumber():
72 if self._get_cursor().blockNumber() == prompt_cursor.blockNumber():
73 # Bail out if we're locked.
73 # Bail out if we're locked.
74 if self._history_locked() and not shift_modifier:
74 if self._history_locked() and not shift_modifier:
75 return False
75 return False
76
76
77 # Set a search prefix based on the cursor position.
77 # Set a search prefix based on the cursor position.
78 col = self._get_input_buffer_cursor_column()
78 col = self._get_input_buffer_cursor_column()
79 input_buffer = self.input_buffer
79 input_buffer = self.input_buffer
80 # use the *shortest* of the cursor column and the history prefix
80 # use the *shortest* of the cursor column and the history prefix
81 # to determine if the prefix has changed
81 # to determine if the prefix has changed
82 n = min(col, len(self._history_prefix))
82 n = min(col, len(self._history_prefix))
83
83
84 # prefix changed, restart search from the beginning
84 # prefix changed, restart search from the beginning
85 if (self._history_prefix[:n] != input_buffer[:n]):
85 if (self._history_prefix[:n] != input_buffer[:n]):
86 self._history_index = len(self._history)
86 self._history_index = len(self._history)
87
87
88 # the only time we shouldn't set the history prefix
88 # the only time we shouldn't set the history prefix
89 # to the line up to the cursor is if we are already
89 # to the line up to the cursor is if we are already
90 # in a simple scroll (no prefix),
90 # in a simple scroll (no prefix),
91 # and the cursor is at the end of the first line
91 # and the cursor is at the end of the first line
92
92
93 # check if we are at the end of the first line
93 # check if we are at the end of the first line
94 c = self._get_cursor()
94 c = self._get_cursor()
95 current_pos = c.position()
95 current_pos = c.position()
96 c.movePosition(QtGui.QTextCursor.EndOfLine)
96 c.movePosition(QtGui.QTextCursor.EndOfLine)
97 at_eol = (c.position() == current_pos)
97 at_eol = (c.position() == current_pos)
98
98
99 if self._history_index == len(self._history) or \
99 if self._history_index == len(self._history) or \
100 not (self._history_prefix == '' and at_eol) or \
100 not (self._history_prefix == '' and at_eol) or \
101 not (self._get_edited_history(self._history_index)[:col] == input_buffer[:col]):
101 not (self._get_edited_history(self._history_index)[:col] == input_buffer[:col]):
102 self._history_prefix = input_buffer[:col]
102 self._history_prefix = input_buffer[:col]
103
103
104 # Perform the search.
104 # Perform the search.
105 self.history_previous(self._history_prefix,
105 self.history_previous(self._history_prefix,
106 as_prefix=not shift_modifier)
106 as_prefix=not shift_modifier)
107
107
108 # Go to the first line of the prompt for seemless history scrolling.
108 # Go to the first line of the prompt for seemless history scrolling.
109 # Emulate readline: keep the cursor position fixed for a prefix
109 # Emulate readline: keep the cursor position fixed for a prefix
110 # search.
110 # search.
111 cursor = self._get_prompt_cursor()
111 cursor = self._get_prompt_cursor()
112 if self._history_prefix:
112 if self._history_prefix:
113 cursor.movePosition(QtGui.QTextCursor.Right,
113 cursor.movePosition(QtGui.QTextCursor.Right,
114 n=len(self._history_prefix))
114 n=len(self._history_prefix))
115 else:
115 else:
116 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
116 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
117 self._set_cursor(cursor)
117 self._set_cursor(cursor)
118
118
119 return False
119 return False
120
120
121 return True
121 return True
122
122
123 def _down_pressed(self, shift_modifier):
123 def _down_pressed(self, shift_modifier):
124 """ Called when the down key is pressed. Returns whether to continue
124 """ Called when the down key is pressed. Returns whether to continue
125 processing the event.
125 processing the event.
126 """
126 """
127 end_cursor = self._get_end_cursor()
127 end_cursor = self._get_end_cursor()
128 if self._get_cursor().blockNumber() == end_cursor.blockNumber():
128 if self._get_cursor().blockNumber() == end_cursor.blockNumber():
129 # Bail out if we're locked.
129 # Bail out if we're locked.
130 if self._history_locked() and not shift_modifier:
130 if self._history_locked() and not shift_modifier:
131 return False
131 return False
132
132
133 # Perform the search.
133 # Perform the search.
134 replaced = self.history_next(self._history_prefix,
134 replaced = self.history_next(self._history_prefix,
135 as_prefix=not shift_modifier)
135 as_prefix=not shift_modifier)
136
136
137 # Emulate readline: keep the cursor position fixed for a prefix
137 # Emulate readline: keep the cursor position fixed for a prefix
138 # search. (We don't need to move the cursor to the end of the buffer
138 # search. (We don't need to move the cursor to the end of the buffer
139 # in the other case because this happens automatically when the
139 # in the other case because this happens automatically when the
140 # input buffer is set.)
140 # input buffer is set.)
141 if self._history_prefix and replaced:
141 if self._history_prefix and replaced:
142 cursor = self._get_prompt_cursor()
142 cursor = self._get_prompt_cursor()
143 cursor.movePosition(QtGui.QTextCursor.Right,
143 cursor.movePosition(QtGui.QTextCursor.Right,
144 n=len(self._history_prefix))
144 n=len(self._history_prefix))
145 self._set_cursor(cursor)
145 self._set_cursor(cursor)
146
146
147 return False
147 return False
148
148
149 return True
149 return True
150
150
151 #---------------------------------------------------------------------------
151 #---------------------------------------------------------------------------
152 # 'HistoryConsoleWidget' public interface
152 # 'HistoryConsoleWidget' public interface
153 #---------------------------------------------------------------------------
153 #---------------------------------------------------------------------------
154
154
155 def history_previous(self, substring='', as_prefix=True):
155 def history_previous(self, substring='', as_prefix=True):
156 """ If possible, set the input buffer to a previous history item.
156 """ If possible, set the input buffer to a previous history item.
157
157
158 Parameters
158 Parameters
159 ----------
159 ----------
160 substring : str, optional
160 substring : str, optional
161 If specified, search for an item with this substring.
161 If specified, search for an item with this substring.
162 as_prefix : bool, optional
162 as_prefix : bool, optional
163 If True, the substring must match at the beginning (default).
163 If True, the substring must match at the beginning (default).
164
164
165 Returns
165 Returns
166 -------
166 -------
167 Whether the input buffer was changed.
167 Whether the input buffer was changed.
168 """
168 """
169 index = self._history_index
169 index = self._history_index
170 replace = False
170 replace = False
171 while index > 0:
171 while index > 0:
172 index -= 1
172 index -= 1
173 history = self._get_edited_history(index)
173 history = self._get_edited_history(index)
174 if (as_prefix and history.startswith(substring)) \
174 if (as_prefix and history.startswith(substring)) \
175 or (not as_prefix and substring in history):
175 or (not as_prefix and substring in history):
176 replace = True
176 replace = True
177 break
177 break
178
178
179 if replace:
179 if replace:
180 self._store_edits()
180 self._store_edits()
181 self._history_index = index
181 self._history_index = index
182 self.input_buffer = history
182 self.input_buffer = history
183
183
184 return replace
184 return replace
185
185
186 def history_next(self, substring='', as_prefix=True):
186 def history_next(self, substring='', as_prefix=True):
187 """ If possible, set the input buffer to a subsequent history item.
187 """ If possible, set the input buffer to a subsequent history item.
188
188
189 Parameters
189 Parameters
190 ----------
190 ----------
191 substring : str, optional
191 substring : str, optional
192 If specified, search for an item with this substring.
192 If specified, search for an item with this substring.
193 as_prefix : bool, optional
193 as_prefix : bool, optional
194 If True, the substring must match at the beginning (default).
194 If True, the substring must match at the beginning (default).
195
195
196 Returns
196 Returns
197 -------
197 -------
198 Whether the input buffer was changed.
198 Whether the input buffer was changed.
199 """
199 """
200 index = self._history_index
200 index = self._history_index
201 replace = False
201 replace = False
202 while index < len(self._history):
202 while index < len(self._history):
203 index += 1
203 index += 1
204 history = self._get_edited_history(index)
204 history = self._get_edited_history(index)
205 if (as_prefix and history.startswith(substring)) \
205 if (as_prefix and history.startswith(substring)) \
206 or (not as_prefix and substring in history):
206 or (not as_prefix and substring in history):
207 replace = True
207 replace = True
208 break
208 break
209
209
210 if replace:
210 if replace:
211 self._store_edits()
211 self._store_edits()
212 self._history_index = index
212 self._history_index = index
213 self.input_buffer = history
213 self.input_buffer = history
214
214
215 return replace
215 return replace
216
216
217 def history_tail(self, n=10):
217 def history_tail(self, n=10):
218 """ Get the local history list.
218 """ Get the local history list.
219
219
220 Parameters
220 Parameters
221 ----------
221 ----------
222 n : int
222 n : int
223 The (maximum) number of history items to get.
223 The (maximum) number of history items to get.
224 """
224 """
225 return self._history[-n:]
225 return self._history[-n:]
226
226
227 def _request_update_session_history_length(self):
227 def _request_update_session_history_length(self):
228 msg_id = self.kernel_client.shell_channel.execute('',
228 msg_id = self.kernel_client.execute('',
229 silent=True,
229 silent=True,
230 user_expressions={
230 user_expressions={
231 'hlen':'len(get_ipython().history_manager.input_hist_raw)',
231 'hlen':'len(get_ipython().history_manager.input_hist_raw)',
232 }
232 }
233 )
233 )
234 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'save_magic')
234 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'save_magic')
235
235
236 def _handle_execute_reply(self, msg):
236 def _handle_execute_reply(self, msg):
237 """ Handles replies for code execution, here only session history length
237 """ Handles replies for code execution, here only session history length
238 """
238 """
239 msg_id = msg['parent_header']['msg_id']
239 msg_id = msg['parent_header']['msg_id']
240 info = self._request_info['execute'].pop(msg_id,None)
240 info = self._request_info['execute'].pop(msg_id,None)
241 if info and info.kind == 'save_magic' and not self._hidden:
241 if info and info.kind == 'save_magic' and not self._hidden:
242 content = msg['content']
242 content = msg['content']
243 status = content['status']
243 status = content['status']
244 if status == 'ok':
244 if status == 'ok':
245 self._max_session_history = int(
245 self._max_session_history = int(
246 content['user_expressions']['hlen']['data']['text/plain']
246 content['user_expressions']['hlen']['data']['text/plain']
247 )
247 )
248
248
249 def save_magic(self):
249 def save_magic(self):
250 # update the session history length
250 # update the session history length
251 self._request_update_session_history_length()
251 self._request_update_session_history_length()
252
252
253 file_name,extFilter = QtGui.QFileDialog.getSaveFileName(self,
253 file_name,extFilter = QtGui.QFileDialog.getSaveFileName(self,
254 "Enter A filename",
254 "Enter A filename",
255 filter='Python File (*.py);; All files (*.*)'
255 filter='Python File (*.py);; All files (*.*)'
256 )
256 )
257
257
258 # let's the user search/type for a file name, while the history length
258 # let's the user search/type for a file name, while the history length
259 # is fetched
259 # is fetched
260
260
261 if file_name:
261 if file_name:
262 hist_range, ok = QtGui.QInputDialog.getText(self,
262 hist_range, ok = QtGui.QInputDialog.getText(self,
263 'Please enter an interval of command to save',
263 'Please enter an interval of command to save',
264 'Saving commands:',
264 'Saving commands:',
265 text=str('1-'+str(self._max_session_history))
265 text=str('1-'+str(self._max_session_history))
266 )
266 )
267 if ok:
267 if ok:
268 self.execute("%save"+" "+file_name+" "+str(hist_range))
268 self.execute("%save"+" "+file_name+" "+str(hist_range))
269
269
270 #---------------------------------------------------------------------------
270 #---------------------------------------------------------------------------
271 # 'HistoryConsoleWidget' protected interface
271 # 'HistoryConsoleWidget' protected interface
272 #---------------------------------------------------------------------------
272 #---------------------------------------------------------------------------
273
273
274 def _history_locked(self):
274 def _history_locked(self):
275 """ Returns whether history movement is locked.
275 """ Returns whether history movement is locked.
276 """
276 """
277 return (self.history_lock and
277 return (self.history_lock and
278 (self._get_edited_history(self._history_index) !=
278 (self._get_edited_history(self._history_index) !=
279 self.input_buffer) and
279 self.input_buffer) and
280 (self._get_prompt_cursor().blockNumber() !=
280 (self._get_prompt_cursor().blockNumber() !=
281 self._get_end_cursor().blockNumber()))
281 self._get_end_cursor().blockNumber()))
282
282
283 def _get_edited_history(self, index):
283 def _get_edited_history(self, index):
284 """ Retrieves a history item, possibly with temporary edits.
284 """ Retrieves a history item, possibly with temporary edits.
285 """
285 """
286 if index in self._history_edits:
286 if index in self._history_edits:
287 return self._history_edits[index]
287 return self._history_edits[index]
288 elif index == len(self._history):
288 elif index == len(self._history):
289 return unicode_type()
289 return unicode_type()
290 return self._history[index]
290 return self._history[index]
291
291
292 def _set_history(self, history):
292 def _set_history(self, history):
293 """ Replace the current history with a sequence of history items.
293 """ Replace the current history with a sequence of history items.
294 """
294 """
295 self._history = list(history)
295 self._history = list(history)
296 self._history_edits = {}
296 self._history_edits = {}
297 self._history_index = len(self._history)
297 self._history_index = len(self._history)
298
298
299 def _store_edits(self):
299 def _store_edits(self):
300 """ If there are edits to the current input buffer, store them.
300 """ If there are edits to the current input buffer, store them.
301 """
301 """
302 current = self.input_buffer
302 current = self.input_buffer
303 if self._history_index == len(self._history) or \
303 if self._history_index == len(self._history) or \
304 self._history[self._history_index] != current:
304 self._history[self._history_index] != current:
305 self._history_edits[self._history_index] = current
305 self._history_edits[self._history_index] = current
@@ -1,599 +1,598 b''
1 """A FrontendWidget that emulates the interface of the console IPython.
1 """A FrontendWidget that emulates the interface of the console IPython.
2
2
3 This supports the additional functionality provided by the IPython kernel.
3 This supports the additional functionality provided by the IPython kernel.
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 from collections import namedtuple
9 from collections import namedtuple
10 import os.path
10 import os.path
11 import re
11 import re
12 from subprocess import Popen
12 from subprocess import Popen
13 import sys
13 import sys
14 import time
14 import time
15 from textwrap import dedent
15 from textwrap import dedent
16
16
17 from IPython.external.qt import QtCore, QtGui
17 from IPython.external.qt import QtCore, QtGui
18
18
19 from IPython.core.inputsplitter import IPythonInputSplitter
19 from IPython.core.inputsplitter import IPythonInputSplitter
20 from IPython.core.release import version
20 from IPython.core.release import version
21 from IPython.core.inputtransformer import ipy_prompt
21 from IPython.core.inputtransformer import ipy_prompt
22 from IPython.utils.traitlets import Bool, Unicode
22 from IPython.utils.traitlets import Bool, Unicode
23 from .frontend_widget import FrontendWidget
23 from .frontend_widget import FrontendWidget
24 from . import styles
24 from . import styles
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Constants
27 # Constants
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 # Default strings to build and display input and output prompts (and separators
30 # Default strings to build and display input and output prompts (and separators
31 # in between)
31 # in between)
32 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
32 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
33 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
33 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
34 default_input_sep = '\n'
34 default_input_sep = '\n'
35 default_output_sep = ''
35 default_output_sep = ''
36 default_output_sep2 = ''
36 default_output_sep2 = ''
37
37
38 # Base path for most payload sources.
38 # Base path for most payload sources.
39 zmq_shell_source = 'IPython.kernel.zmq.zmqshell.ZMQInteractiveShell'
39 zmq_shell_source = 'IPython.kernel.zmq.zmqshell.ZMQInteractiveShell'
40
40
41 if sys.platform.startswith('win'):
41 if sys.platform.startswith('win'):
42 default_editor = 'notepad'
42 default_editor = 'notepad'
43 else:
43 else:
44 default_editor = ''
44 default_editor = ''
45
45
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47 # IPythonWidget class
47 # IPythonWidget class
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49
49
50 class IPythonWidget(FrontendWidget):
50 class IPythonWidget(FrontendWidget):
51 """ A FrontendWidget for an IPython kernel.
51 """ A FrontendWidget for an IPython kernel.
52 """
52 """
53
53
54 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
54 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
55 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
55 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
56 # settings.
56 # settings.
57 custom_edit = Bool(False)
57 custom_edit = Bool(False)
58 custom_edit_requested = QtCore.Signal(object, object)
58 custom_edit_requested = QtCore.Signal(object, object)
59
59
60 editor = Unicode(default_editor, config=True,
60 editor = Unicode(default_editor, config=True,
61 help="""
61 help="""
62 A command for invoking a system text editor. If the string contains a
62 A command for invoking a system text editor. If the string contains a
63 {filename} format specifier, it will be used. Otherwise, the filename
63 {filename} format specifier, it will be used. Otherwise, the filename
64 will be appended to the end the command.
64 will be appended to the end the command.
65 """)
65 """)
66
66
67 editor_line = Unicode(config=True,
67 editor_line = Unicode(config=True,
68 help="""
68 help="""
69 The editor command to use when a specific line number is requested. The
69 The editor command to use when a specific line number is requested. The
70 string should contain two format specifiers: {line} and {filename}. If
70 string should contain two format specifiers: {line} and {filename}. If
71 this parameter is not specified, the line number option to the %edit
71 this parameter is not specified, the line number option to the %edit
72 magic will be ignored.
72 magic will be ignored.
73 """)
73 """)
74
74
75 style_sheet = Unicode(config=True,
75 style_sheet = Unicode(config=True,
76 help="""
76 help="""
77 A CSS stylesheet. The stylesheet can contain classes for:
77 A CSS stylesheet. The stylesheet can contain classes for:
78 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
78 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
79 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
79 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
80 3. IPython: .error, .in-prompt, .out-prompt, etc
80 3. IPython: .error, .in-prompt, .out-prompt, etc
81 """)
81 """)
82
82
83 syntax_style = Unicode(config=True,
83 syntax_style = Unicode(config=True,
84 help="""
84 help="""
85 If not empty, use this Pygments style for syntax highlighting.
85 If not empty, use this Pygments style for syntax highlighting.
86 Otherwise, the style sheet is queried for Pygments style
86 Otherwise, the style sheet is queried for Pygments style
87 information.
87 information.
88 """)
88 """)
89
89
90 # Prompts.
90 # Prompts.
91 in_prompt = Unicode(default_in_prompt, config=True)
91 in_prompt = Unicode(default_in_prompt, config=True)
92 out_prompt = Unicode(default_out_prompt, config=True)
92 out_prompt = Unicode(default_out_prompt, config=True)
93 input_sep = Unicode(default_input_sep, config=True)
93 input_sep = Unicode(default_input_sep, config=True)
94 output_sep = Unicode(default_output_sep, config=True)
94 output_sep = Unicode(default_output_sep, config=True)
95 output_sep2 = Unicode(default_output_sep2, config=True)
95 output_sep2 = Unicode(default_output_sep2, config=True)
96
96
97 # FrontendWidget protected class variables.
97 # FrontendWidget protected class variables.
98 _input_splitter_class = IPythonInputSplitter
98 _input_splitter_class = IPythonInputSplitter
99 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[ipy_prompt()],
99 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[ipy_prompt()],
100 logical_line_transforms=[],
100 logical_line_transforms=[],
101 python_line_transforms=[],
101 python_line_transforms=[],
102 )
102 )
103
103
104 # IPythonWidget protected class variables.
104 # IPythonWidget protected class variables.
105 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
105 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
106 _payload_source_edit = 'edit'
106 _payload_source_edit = 'edit'
107 _payload_source_exit = 'ask_exit'
107 _payload_source_exit = 'ask_exit'
108 _payload_source_next_input = 'set_next_input'
108 _payload_source_next_input = 'set_next_input'
109 _payload_source_page = 'page'
109 _payload_source_page = 'page'
110 _retrying_history_request = False
110 _retrying_history_request = False
111 _starting = False
111 _starting = False
112
112
113 #---------------------------------------------------------------------------
113 #---------------------------------------------------------------------------
114 # 'object' interface
114 # 'object' interface
115 #---------------------------------------------------------------------------
115 #---------------------------------------------------------------------------
116
116
117 def __init__(self, *args, **kw):
117 def __init__(self, *args, **kw):
118 super(IPythonWidget, self).__init__(*args, **kw)
118 super(IPythonWidget, self).__init__(*args, **kw)
119
119
120 # IPythonWidget protected variables.
120 # IPythonWidget protected variables.
121 self._payload_handlers = {
121 self._payload_handlers = {
122 self._payload_source_edit : self._handle_payload_edit,
122 self._payload_source_edit : self._handle_payload_edit,
123 self._payload_source_exit : self._handle_payload_exit,
123 self._payload_source_exit : self._handle_payload_exit,
124 self._payload_source_page : self._handle_payload_page,
124 self._payload_source_page : self._handle_payload_page,
125 self._payload_source_next_input : self._handle_payload_next_input }
125 self._payload_source_next_input : self._handle_payload_next_input }
126 self._previous_prompt_obj = None
126 self._previous_prompt_obj = None
127 self._keep_kernel_on_exit = None
127 self._keep_kernel_on_exit = None
128
128
129 # Initialize widget styling.
129 # Initialize widget styling.
130 if self.style_sheet:
130 if self.style_sheet:
131 self._style_sheet_changed()
131 self._style_sheet_changed()
132 self._syntax_style_changed()
132 self._syntax_style_changed()
133 else:
133 else:
134 self.set_default_style()
134 self.set_default_style()
135
135
136 self._guiref_loaded = False
136 self._guiref_loaded = False
137
137
138 #---------------------------------------------------------------------------
138 #---------------------------------------------------------------------------
139 # 'BaseFrontendMixin' abstract interface
139 # 'BaseFrontendMixin' abstract interface
140 #---------------------------------------------------------------------------
140 #---------------------------------------------------------------------------
141 def _handle_complete_reply(self, rep):
141 def _handle_complete_reply(self, rep):
142 """ Reimplemented to support IPython's improved completion machinery.
142 """ Reimplemented to support IPython's improved completion machinery.
143 """
143 """
144 self.log.debug("complete: %s", rep.get('content', ''))
144 self.log.debug("complete: %s", rep.get('content', ''))
145 cursor = self._get_cursor()
145 cursor = self._get_cursor()
146 info = self._request_info.get('complete')
146 info = self._request_info.get('complete')
147 if info and info.id == rep['parent_header']['msg_id'] and \
147 if info and info.id == rep['parent_header']['msg_id'] and \
148 info.pos == cursor.position():
148 info.pos == cursor.position():
149 content = rep['content']
149 content = rep['content']
150 matches = content['matches']
150 matches = content['matches']
151 start = content['cursor_start']
151 start = content['cursor_start']
152 end = content['cursor_end']
152 end = content['cursor_end']
153
153
154 start = max(start, 0)
154 start = max(start, 0)
155 end = max(end, start)
155 end = max(end, start)
156
156
157 # Move the control's cursor to the desired end point
157 # Move the control's cursor to the desired end point
158 cursor_pos = self._get_input_buffer_cursor_pos()
158 cursor_pos = self._get_input_buffer_cursor_pos()
159 if end < cursor_pos:
159 if end < cursor_pos:
160 cursor.movePosition(QtGui.QTextCursor.Left,
160 cursor.movePosition(QtGui.QTextCursor.Left,
161 n=(cursor_pos - end))
161 n=(cursor_pos - end))
162 elif end > cursor_pos:
162 elif end > cursor_pos:
163 cursor.movePosition(QtGui.QTextCursor.Right,
163 cursor.movePosition(QtGui.QTextCursor.Right,
164 n=(end - cursor_pos))
164 n=(end - cursor_pos))
165 # This line actually applies the move to control's cursor
165 # This line actually applies the move to control's cursor
166 self._control.setTextCursor(cursor)
166 self._control.setTextCursor(cursor)
167
167
168 offset = end - start
168 offset = end - start
169 # Move the local cursor object to the start of the match and
169 # Move the local cursor object to the start of the match and
170 # complete.
170 # complete.
171 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
171 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
172 self._complete_with_items(cursor, matches)
172 self._complete_with_items(cursor, matches)
173
173
174 def _handle_execute_reply(self, msg):
174 def _handle_execute_reply(self, msg):
175 """ Reimplemented to support prompt requests.
175 """ Reimplemented to support prompt requests.
176 """
176 """
177 msg_id = msg['parent_header'].get('msg_id')
177 msg_id = msg['parent_header'].get('msg_id')
178 info = self._request_info['execute'].get(msg_id)
178 info = self._request_info['execute'].get(msg_id)
179 if info and info.kind == 'prompt':
179 if info and info.kind == 'prompt':
180 content = msg['content']
180 content = msg['content']
181 if content['status'] == 'aborted':
181 if content['status'] == 'aborted':
182 self._show_interpreter_prompt()
182 self._show_interpreter_prompt()
183 else:
183 else:
184 number = content['execution_count'] + 1
184 number = content['execution_count'] + 1
185 self._show_interpreter_prompt(number)
185 self._show_interpreter_prompt(number)
186 self._request_info['execute'].pop(msg_id)
186 self._request_info['execute'].pop(msg_id)
187 else:
187 else:
188 super(IPythonWidget, self)._handle_execute_reply(msg)
188 super(IPythonWidget, self)._handle_execute_reply(msg)
189
189
190 def _handle_history_reply(self, msg):
190 def _handle_history_reply(self, msg):
191 """ Implemented to handle history tail replies, which are only supported
191 """ Implemented to handle history tail replies, which are only supported
192 by the IPython kernel.
192 by the IPython kernel.
193 """
193 """
194 content = msg['content']
194 content = msg['content']
195 if 'history' not in content:
195 if 'history' not in content:
196 self.log.error("History request failed: %r"%content)
196 self.log.error("History request failed: %r"%content)
197 if content.get('status', '') == 'aborted' and \
197 if content.get('status', '') == 'aborted' and \
198 not self._retrying_history_request:
198 not self._retrying_history_request:
199 # a *different* action caused this request to be aborted, so
199 # a *different* action caused this request to be aborted, so
200 # we should try again.
200 # we should try again.
201 self.log.error("Retrying aborted history request")
201 self.log.error("Retrying aborted history request")
202 # prevent multiple retries of aborted requests:
202 # prevent multiple retries of aborted requests:
203 self._retrying_history_request = True
203 self._retrying_history_request = True
204 # wait out the kernel's queue flush, which is currently timed at 0.1s
204 # wait out the kernel's queue flush, which is currently timed at 0.1s
205 time.sleep(0.25)
205 time.sleep(0.25)
206 self.kernel_client.shell_channel.history(hist_access_type='tail',n=1000)
206 self.kernel_client.history(hist_access_type='tail',n=1000)
207 else:
207 else:
208 self._retrying_history_request = False
208 self._retrying_history_request = False
209 return
209 return
210 # reset retry flag
210 # reset retry flag
211 self._retrying_history_request = False
211 self._retrying_history_request = False
212 history_items = content['history']
212 history_items = content['history']
213 self.log.debug("Received history reply with %i entries", len(history_items))
213 self.log.debug("Received history reply with %i entries", len(history_items))
214 items = []
214 items = []
215 last_cell = u""
215 last_cell = u""
216 for _, _, cell in history_items:
216 for _, _, cell in history_items:
217 cell = cell.rstrip()
217 cell = cell.rstrip()
218 if cell != last_cell:
218 if cell != last_cell:
219 items.append(cell)
219 items.append(cell)
220 last_cell = cell
220 last_cell = cell
221 self._set_history(items)
221 self._set_history(items)
222
222
223 def _insert_other_input(self, cursor, content):
223 def _insert_other_input(self, cursor, content):
224 """Insert function for input from other frontends"""
224 """Insert function for input from other frontends"""
225 cursor.beginEditBlock()
225 cursor.beginEditBlock()
226 start = cursor.position()
226 start = cursor.position()
227 n = content.get('execution_count', 0)
227 n = content.get('execution_count', 0)
228 cursor.insertText('\n')
228 cursor.insertText('\n')
229 self._insert_html(cursor, self._make_in_prompt(n))
229 self._insert_html(cursor, self._make_in_prompt(n))
230 cursor.insertText(content['code'])
230 cursor.insertText(content['code'])
231 self._highlighter.rehighlightBlock(cursor.block())
231 self._highlighter.rehighlightBlock(cursor.block())
232 cursor.endEditBlock()
232 cursor.endEditBlock()
233
233
234 def _handle_execute_input(self, msg):
234 def _handle_execute_input(self, msg):
235 """Handle an execute_input message"""
235 """Handle an execute_input message"""
236 self.log.debug("execute_input: %s", msg.get('content', ''))
236 self.log.debug("execute_input: %s", msg.get('content', ''))
237 if self.include_output(msg):
237 if self.include_output(msg):
238 self._append_custom(self._insert_other_input, msg['content'], before_prompt=True)
238 self._append_custom(self._insert_other_input, msg['content'], before_prompt=True)
239
239
240
240
241 def _handle_execute_result(self, msg):
241 def _handle_execute_result(self, msg):
242 """ Reimplemented for IPython-style "display hook".
242 """ Reimplemented for IPython-style "display hook".
243 """
243 """
244 self.log.debug("execute_result: %s", msg.get('content', ''))
244 self.log.debug("execute_result: %s", msg.get('content', ''))
245 if self.include_output(msg):
245 if self.include_output(msg):
246 self.flush_clearoutput()
246 self.flush_clearoutput()
247 content = msg['content']
247 content = msg['content']
248 prompt_number = content.get('execution_count', 0)
248 prompt_number = content.get('execution_count', 0)
249 data = content['data']
249 data = content['data']
250 if 'text/plain' in data:
250 if 'text/plain' in data:
251 self._append_plain_text(self.output_sep, True)
251 self._append_plain_text(self.output_sep, True)
252 self._append_html(self._make_out_prompt(prompt_number), True)
252 self._append_html(self._make_out_prompt(prompt_number), True)
253 text = data['text/plain']
253 text = data['text/plain']
254 # If the repr is multiline, make sure we start on a new line,
254 # If the repr is multiline, make sure we start on a new line,
255 # so that its lines are aligned.
255 # so that its lines are aligned.
256 if "\n" in text and not self.output_sep.endswith("\n"):
256 if "\n" in text and not self.output_sep.endswith("\n"):
257 self._append_plain_text('\n', True)
257 self._append_plain_text('\n', True)
258 self._append_plain_text(text + self.output_sep2, True)
258 self._append_plain_text(text + self.output_sep2, True)
259
259
260 def _handle_display_data(self, msg):
260 def _handle_display_data(self, msg):
261 """ The base handler for the ``display_data`` message.
261 """ The base handler for the ``display_data`` message.
262 """
262 """
263 self.log.debug("display: %s", msg.get('content', ''))
263 self.log.debug("display: %s", msg.get('content', ''))
264 # For now, we don't display data from other frontends, but we
264 # For now, we don't display data from other frontends, but we
265 # eventually will as this allows all frontends to monitor the display
265 # eventually will as this allows all frontends to monitor the display
266 # data. But we need to figure out how to handle this in the GUI.
266 # data. But we need to figure out how to handle this in the GUI.
267 if self.include_output(msg):
267 if self.include_output(msg):
268 self.flush_clearoutput()
268 self.flush_clearoutput()
269 data = msg['content']['data']
269 data = msg['content']['data']
270 metadata = msg['content']['metadata']
270 metadata = msg['content']['metadata']
271 # In the regular IPythonWidget, we simply print the plain text
271 # In the regular IPythonWidget, we simply print the plain text
272 # representation.
272 # representation.
273 if 'text/plain' in data:
273 if 'text/plain' in data:
274 text = data['text/plain']
274 text = data['text/plain']
275 self._append_plain_text(text, True)
275 self._append_plain_text(text, True)
276 # This newline seems to be needed for text and html output.
276 # This newline seems to be needed for text and html output.
277 self._append_plain_text(u'\n', True)
277 self._append_plain_text(u'\n', True)
278
278
279 def _handle_kernel_info_reply(self, rep):
279 def _handle_kernel_info_reply(self, rep):
280 """Handle kernel info replies."""
280 """Handle kernel info replies."""
281 content = rep['content']
281 content = rep['content']
282 if not self._guiref_loaded:
282 if not self._guiref_loaded:
283 if content.get('language') == 'python':
283 if content.get('language') == 'python':
284 self._load_guiref_magic()
284 self._load_guiref_magic()
285 self._guiref_loaded = True
285 self._guiref_loaded = True
286
286
287 self.kernel_banner = content.get('banner', '')
287 self.kernel_banner = content.get('banner', '')
288 if self._starting:
288 if self._starting:
289 # finish handling started channels
289 # finish handling started channels
290 self._starting = False
290 self._starting = False
291 super(IPythonWidget, self)._started_channels()
291 super(IPythonWidget, self)._started_channels()
292
292
293 def _started_channels(self):
293 def _started_channels(self):
294 """Reimplemented to make a history request and load %guiref."""
294 """Reimplemented to make a history request and load %guiref."""
295 self._starting = True
295 self._starting = True
296 # The reply will trigger %guiref load provided language=='python'
296 # The reply will trigger %guiref load provided language=='python'
297 self.kernel_client.kernel_info()
297 self.kernel_client.kernel_info()
298
298
299 self.kernel_client.shell_channel.history(hist_access_type='tail',
299 self.kernel_client.history(hist_access_type='tail', n=1000)
300 n=1000)
301
300
302 def _load_guiref_magic(self):
301 def _load_guiref_magic(self):
303 """Load %guiref magic."""
302 """Load %guiref magic."""
304 self.kernel_client.shell_channel.execute('\n'.join([
303 self.kernel_client.execute('\n'.join([
305 "try:",
304 "try:",
306 " _usage",
305 " _usage",
307 "except:",
306 "except:",
308 " from IPython.core import usage as _usage",
307 " from IPython.core import usage as _usage",
309 " get_ipython().register_magic_function(_usage.page_guiref, 'line', 'guiref')",
308 " get_ipython().register_magic_function(_usage.page_guiref, 'line', 'guiref')",
310 " del _usage",
309 " del _usage",
311 ]), silent=True)
310 ]), silent=True)
312
311
313 #---------------------------------------------------------------------------
312 #---------------------------------------------------------------------------
314 # 'ConsoleWidget' public interface
313 # 'ConsoleWidget' public interface
315 #---------------------------------------------------------------------------
314 #---------------------------------------------------------------------------
316
315
317 #---------------------------------------------------------------------------
316 #---------------------------------------------------------------------------
318 # 'FrontendWidget' public interface
317 # 'FrontendWidget' public interface
319 #---------------------------------------------------------------------------
318 #---------------------------------------------------------------------------
320
319
321 def execute_file(self, path, hidden=False):
320 def execute_file(self, path, hidden=False):
322 """ Reimplemented to use the 'run' magic.
321 """ Reimplemented to use the 'run' magic.
323 """
322 """
324 # Use forward slashes on Windows to avoid escaping each separator.
323 # Use forward slashes on Windows to avoid escaping each separator.
325 if sys.platform == 'win32':
324 if sys.platform == 'win32':
326 path = os.path.normpath(path).replace('\\', '/')
325 path = os.path.normpath(path).replace('\\', '/')
327
326
328 # Perhaps we should not be using %run directly, but while we
327 # Perhaps we should not be using %run directly, but while we
329 # are, it is necessary to quote or escape filenames containing spaces
328 # are, it is necessary to quote or escape filenames containing spaces
330 # or quotes.
329 # or quotes.
331
330
332 # In earlier code here, to minimize escaping, we sometimes quoted the
331 # In earlier code here, to minimize escaping, we sometimes quoted the
333 # filename with single quotes. But to do this, this code must be
332 # filename with single quotes. But to do this, this code must be
334 # platform-aware, because run uses shlex rather than python string
333 # platform-aware, because run uses shlex rather than python string
335 # parsing, so that:
334 # parsing, so that:
336 # * In Win: single quotes can be used in the filename without quoting,
335 # * In Win: single quotes can be used in the filename without quoting,
337 # and we cannot use single quotes to quote the filename.
336 # and we cannot use single quotes to quote the filename.
338 # * In *nix: we can escape double quotes in a double quoted filename,
337 # * In *nix: we can escape double quotes in a double quoted filename,
339 # but can't escape single quotes in a single quoted filename.
338 # but can't escape single quotes in a single quoted filename.
340
339
341 # So to keep this code non-platform-specific and simple, we now only
340 # So to keep this code non-platform-specific and simple, we now only
342 # use double quotes to quote filenames, and escape when needed:
341 # use double quotes to quote filenames, and escape when needed:
343 if ' ' in path or "'" in path or '"' in path:
342 if ' ' in path or "'" in path or '"' in path:
344 path = '"%s"' % path.replace('"', '\\"')
343 path = '"%s"' % path.replace('"', '\\"')
345 self.execute('%%run %s' % path, hidden=hidden)
344 self.execute('%%run %s' % path, hidden=hidden)
346
345
347 #---------------------------------------------------------------------------
346 #---------------------------------------------------------------------------
348 # 'FrontendWidget' protected interface
347 # 'FrontendWidget' protected interface
349 #---------------------------------------------------------------------------
348 #---------------------------------------------------------------------------
350
349
351 def _process_execute_error(self, msg):
350 def _process_execute_error(self, msg):
352 """ Reimplemented for IPython-style traceback formatting.
351 """ Reimplemented for IPython-style traceback formatting.
353 """
352 """
354 content = msg['content']
353 content = msg['content']
355 traceback = '\n'.join(content['traceback']) + '\n'
354 traceback = '\n'.join(content['traceback']) + '\n'
356 if False:
355 if False:
357 # FIXME: For now, tracebacks come as plain text, so we can't use
356 # FIXME: For now, tracebacks come as plain text, so we can't use
358 # the html renderer yet. Once we refactor ultratb to produce
357 # the html renderer yet. Once we refactor ultratb to produce
359 # properly styled tracebacks, this branch should be the default
358 # properly styled tracebacks, this branch should be the default
360 traceback = traceback.replace(' ', '&nbsp;')
359 traceback = traceback.replace(' ', '&nbsp;')
361 traceback = traceback.replace('\n', '<br/>')
360 traceback = traceback.replace('\n', '<br/>')
362
361
363 ename = content['ename']
362 ename = content['ename']
364 ename_styled = '<span class="error">%s</span>' % ename
363 ename_styled = '<span class="error">%s</span>' % ename
365 traceback = traceback.replace(ename, ename_styled)
364 traceback = traceback.replace(ename, ename_styled)
366
365
367 self._append_html(traceback)
366 self._append_html(traceback)
368 else:
367 else:
369 # This is the fallback for now, using plain text with ansi escapes
368 # This is the fallback for now, using plain text with ansi escapes
370 self._append_plain_text(traceback)
369 self._append_plain_text(traceback)
371
370
372 def _process_execute_payload(self, item):
371 def _process_execute_payload(self, item):
373 """ Reimplemented to dispatch payloads to handler methods.
372 """ Reimplemented to dispatch payloads to handler methods.
374 """
373 """
375 handler = self._payload_handlers.get(item['source'])
374 handler = self._payload_handlers.get(item['source'])
376 if handler is None:
375 if handler is None:
377 # We have no handler for this type of payload, simply ignore it
376 # We have no handler for this type of payload, simply ignore it
378 return False
377 return False
379 else:
378 else:
380 handler(item)
379 handler(item)
381 return True
380 return True
382
381
383 def _show_interpreter_prompt(self, number=None):
382 def _show_interpreter_prompt(self, number=None):
384 """ Reimplemented for IPython-style prompts.
383 """ Reimplemented for IPython-style prompts.
385 """
384 """
386 # If a number was not specified, make a prompt number request.
385 # If a number was not specified, make a prompt number request.
387 if number is None:
386 if number is None:
388 msg_id = self.kernel_client.shell_channel.execute('', silent=True)
387 msg_id = self.kernel_client.execute('', silent=True)
389 info = self._ExecutionRequest(msg_id, 'prompt')
388 info = self._ExecutionRequest(msg_id, 'prompt')
390 self._request_info['execute'][msg_id] = info
389 self._request_info['execute'][msg_id] = info
391 return
390 return
392
391
393 # Show a new prompt and save information about it so that it can be
392 # Show a new prompt and save information about it so that it can be
394 # updated later if the prompt number turns out to be wrong.
393 # updated later if the prompt number turns out to be wrong.
395 self._prompt_sep = self.input_sep
394 self._prompt_sep = self.input_sep
396 self._show_prompt(self._make_in_prompt(number), html=True)
395 self._show_prompt(self._make_in_prompt(number), html=True)
397 block = self._control.document().lastBlock()
396 block = self._control.document().lastBlock()
398 length = len(self._prompt)
397 length = len(self._prompt)
399 self._previous_prompt_obj = self._PromptBlock(block, length, number)
398 self._previous_prompt_obj = self._PromptBlock(block, length, number)
400
399
401 # Update continuation prompt to reflect (possibly) new prompt length.
400 # Update continuation prompt to reflect (possibly) new prompt length.
402 self._set_continuation_prompt(
401 self._set_continuation_prompt(
403 self._make_continuation_prompt(self._prompt), html=True)
402 self._make_continuation_prompt(self._prompt), html=True)
404
403
405 def _show_interpreter_prompt_for_reply(self, msg):
404 def _show_interpreter_prompt_for_reply(self, msg):
406 """ Reimplemented for IPython-style prompts.
405 """ Reimplemented for IPython-style prompts.
407 """
406 """
408 # Update the old prompt number if necessary.
407 # Update the old prompt number if necessary.
409 content = msg['content']
408 content = msg['content']
410 # abort replies do not have any keys:
409 # abort replies do not have any keys:
411 if content['status'] == 'aborted':
410 if content['status'] == 'aborted':
412 if self._previous_prompt_obj:
411 if self._previous_prompt_obj:
413 previous_prompt_number = self._previous_prompt_obj.number
412 previous_prompt_number = self._previous_prompt_obj.number
414 else:
413 else:
415 previous_prompt_number = 0
414 previous_prompt_number = 0
416 else:
415 else:
417 previous_prompt_number = content['execution_count']
416 previous_prompt_number = content['execution_count']
418 if self._previous_prompt_obj and \
417 if self._previous_prompt_obj and \
419 self._previous_prompt_obj.number != previous_prompt_number:
418 self._previous_prompt_obj.number != previous_prompt_number:
420 block = self._previous_prompt_obj.block
419 block = self._previous_prompt_obj.block
421
420
422 # Make sure the prompt block has not been erased.
421 # Make sure the prompt block has not been erased.
423 if block.isValid() and block.text():
422 if block.isValid() and block.text():
424
423
425 # Remove the old prompt and insert a new prompt.
424 # Remove the old prompt and insert a new prompt.
426 cursor = QtGui.QTextCursor(block)
425 cursor = QtGui.QTextCursor(block)
427 cursor.movePosition(QtGui.QTextCursor.Right,
426 cursor.movePosition(QtGui.QTextCursor.Right,
428 QtGui.QTextCursor.KeepAnchor,
427 QtGui.QTextCursor.KeepAnchor,
429 self._previous_prompt_obj.length)
428 self._previous_prompt_obj.length)
430 prompt = self._make_in_prompt(previous_prompt_number)
429 prompt = self._make_in_prompt(previous_prompt_number)
431 self._prompt = self._insert_html_fetching_plain_text(
430 self._prompt = self._insert_html_fetching_plain_text(
432 cursor, prompt)
431 cursor, prompt)
433
432
434 # When the HTML is inserted, Qt blows away the syntax
433 # When the HTML is inserted, Qt blows away the syntax
435 # highlighting for the line, so we need to rehighlight it.
434 # highlighting for the line, so we need to rehighlight it.
436 self._highlighter.rehighlightBlock(cursor.block())
435 self._highlighter.rehighlightBlock(cursor.block())
437
436
438 self._previous_prompt_obj = None
437 self._previous_prompt_obj = None
439
438
440 # Show a new prompt with the kernel's estimated prompt number.
439 # Show a new prompt with the kernel's estimated prompt number.
441 self._show_interpreter_prompt(previous_prompt_number + 1)
440 self._show_interpreter_prompt(previous_prompt_number + 1)
442
441
443 #---------------------------------------------------------------------------
442 #---------------------------------------------------------------------------
444 # 'IPythonWidget' interface
443 # 'IPythonWidget' interface
445 #---------------------------------------------------------------------------
444 #---------------------------------------------------------------------------
446
445
447 def set_default_style(self, colors='lightbg'):
446 def set_default_style(self, colors='lightbg'):
448 """ Sets the widget style to the class defaults.
447 """ Sets the widget style to the class defaults.
449
448
450 Parameters
449 Parameters
451 ----------
450 ----------
452 colors : str, optional (default lightbg)
451 colors : str, optional (default lightbg)
453 Whether to use the default IPython light background or dark
452 Whether to use the default IPython light background or dark
454 background or B&W style.
453 background or B&W style.
455 """
454 """
456 colors = colors.lower()
455 colors = colors.lower()
457 if colors=='lightbg':
456 if colors=='lightbg':
458 self.style_sheet = styles.default_light_style_sheet
457 self.style_sheet = styles.default_light_style_sheet
459 self.syntax_style = styles.default_light_syntax_style
458 self.syntax_style = styles.default_light_syntax_style
460 elif colors=='linux':
459 elif colors=='linux':
461 self.style_sheet = styles.default_dark_style_sheet
460 self.style_sheet = styles.default_dark_style_sheet
462 self.syntax_style = styles.default_dark_syntax_style
461 self.syntax_style = styles.default_dark_syntax_style
463 elif colors=='nocolor':
462 elif colors=='nocolor':
464 self.style_sheet = styles.default_bw_style_sheet
463 self.style_sheet = styles.default_bw_style_sheet
465 self.syntax_style = styles.default_bw_syntax_style
464 self.syntax_style = styles.default_bw_syntax_style
466 else:
465 else:
467 raise KeyError("No such color scheme: %s"%colors)
466 raise KeyError("No such color scheme: %s"%colors)
468
467
469 #---------------------------------------------------------------------------
468 #---------------------------------------------------------------------------
470 # 'IPythonWidget' protected interface
469 # 'IPythonWidget' protected interface
471 #---------------------------------------------------------------------------
470 #---------------------------------------------------------------------------
472
471
473 def _edit(self, filename, line=None):
472 def _edit(self, filename, line=None):
474 """ Opens a Python script for editing.
473 """ Opens a Python script for editing.
475
474
476 Parameters
475 Parameters
477 ----------
476 ----------
478 filename : str
477 filename : str
479 A path to a local system file.
478 A path to a local system file.
480
479
481 line : int, optional
480 line : int, optional
482 A line of interest in the file.
481 A line of interest in the file.
483 """
482 """
484 if self.custom_edit:
483 if self.custom_edit:
485 self.custom_edit_requested.emit(filename, line)
484 self.custom_edit_requested.emit(filename, line)
486 elif not self.editor:
485 elif not self.editor:
487 self._append_plain_text('No default editor available.\n'
486 self._append_plain_text('No default editor available.\n'
488 'Specify a GUI text editor in the `IPythonWidget.editor` '
487 'Specify a GUI text editor in the `IPythonWidget.editor` '
489 'configurable to enable the %edit magic')
488 'configurable to enable the %edit magic')
490 else:
489 else:
491 try:
490 try:
492 filename = '"%s"' % filename
491 filename = '"%s"' % filename
493 if line and self.editor_line:
492 if line and self.editor_line:
494 command = self.editor_line.format(filename=filename,
493 command = self.editor_line.format(filename=filename,
495 line=line)
494 line=line)
496 else:
495 else:
497 try:
496 try:
498 command = self.editor.format()
497 command = self.editor.format()
499 except KeyError:
498 except KeyError:
500 command = self.editor.format(filename=filename)
499 command = self.editor.format(filename=filename)
501 else:
500 else:
502 command += ' ' + filename
501 command += ' ' + filename
503 except KeyError:
502 except KeyError:
504 self._append_plain_text('Invalid editor command.\n')
503 self._append_plain_text('Invalid editor command.\n')
505 else:
504 else:
506 try:
505 try:
507 Popen(command, shell=True)
506 Popen(command, shell=True)
508 except OSError:
507 except OSError:
509 msg = 'Opening editor with command "%s" failed.\n'
508 msg = 'Opening editor with command "%s" failed.\n'
510 self._append_plain_text(msg % command)
509 self._append_plain_text(msg % command)
511
510
512 def _make_in_prompt(self, number):
511 def _make_in_prompt(self, number):
513 """ Given a prompt number, returns an HTML In prompt.
512 """ Given a prompt number, returns an HTML In prompt.
514 """
513 """
515 try:
514 try:
516 body = self.in_prompt % number
515 body = self.in_prompt % number
517 except TypeError:
516 except TypeError:
518 # allow in_prompt to leave out number, e.g. '>>> '
517 # allow in_prompt to leave out number, e.g. '>>> '
519 from xml.sax.saxutils import escape
518 from xml.sax.saxutils import escape
520 body = escape(self.in_prompt)
519 body = escape(self.in_prompt)
521 return '<span class="in-prompt">%s</span>' % body
520 return '<span class="in-prompt">%s</span>' % body
522
521
523 def _make_continuation_prompt(self, prompt):
522 def _make_continuation_prompt(self, prompt):
524 """ Given a plain text version of an In prompt, returns an HTML
523 """ Given a plain text version of an In prompt, returns an HTML
525 continuation prompt.
524 continuation prompt.
526 """
525 """
527 end_chars = '...: '
526 end_chars = '...: '
528 space_count = len(prompt.lstrip('\n')) - len(end_chars)
527 space_count = len(prompt.lstrip('\n')) - len(end_chars)
529 body = '&nbsp;' * space_count + end_chars
528 body = '&nbsp;' * space_count + end_chars
530 return '<span class="in-prompt">%s</span>' % body
529 return '<span class="in-prompt">%s</span>' % body
531
530
532 def _make_out_prompt(self, number):
531 def _make_out_prompt(self, number):
533 """ Given a prompt number, returns an HTML Out prompt.
532 """ Given a prompt number, returns an HTML Out prompt.
534 """
533 """
535 try:
534 try:
536 body = self.out_prompt % number
535 body = self.out_prompt % number
537 except TypeError:
536 except TypeError:
538 # allow out_prompt to leave out number, e.g. '<<< '
537 # allow out_prompt to leave out number, e.g. '<<< '
539 from xml.sax.saxutils import escape
538 from xml.sax.saxutils import escape
540 body = escape(self.out_prompt)
539 body = escape(self.out_prompt)
541 return '<span class="out-prompt">%s</span>' % body
540 return '<span class="out-prompt">%s</span>' % body
542
541
543 #------ Payload handlers --------------------------------------------------
542 #------ Payload handlers --------------------------------------------------
544
543
545 # Payload handlers with a generic interface: each takes the opaque payload
544 # Payload handlers with a generic interface: each takes the opaque payload
546 # dict, unpacks it and calls the underlying functions with the necessary
545 # dict, unpacks it and calls the underlying functions with the necessary
547 # arguments.
546 # arguments.
548
547
549 def _handle_payload_edit(self, item):
548 def _handle_payload_edit(self, item):
550 self._edit(item['filename'], item['line_number'])
549 self._edit(item['filename'], item['line_number'])
551
550
552 def _handle_payload_exit(self, item):
551 def _handle_payload_exit(self, item):
553 self._keep_kernel_on_exit = item['keepkernel']
552 self._keep_kernel_on_exit = item['keepkernel']
554 self.exit_requested.emit(self)
553 self.exit_requested.emit(self)
555
554
556 def _handle_payload_next_input(self, item):
555 def _handle_payload_next_input(self, item):
557 self.input_buffer = item['text']
556 self.input_buffer = item['text']
558
557
559 def _handle_payload_page(self, item):
558 def _handle_payload_page(self, item):
560 # Since the plain text widget supports only a very small subset of HTML
559 # Since the plain text widget supports only a very small subset of HTML
561 # and we have no control over the HTML source, we only page HTML
560 # and we have no control over the HTML source, we only page HTML
562 # payloads in the rich text widget.
561 # payloads in the rich text widget.
563 data = item['data']
562 data = item['data']
564 if 'text/html' in data and self.kind == 'rich':
563 if 'text/html' in data and self.kind == 'rich':
565 self._page(data['text/html'], html=True)
564 self._page(data['text/html'], html=True)
566 else:
565 else:
567 self._page(data['text/plain'], html=False)
566 self._page(data['text/plain'], html=False)
568
567
569 #------ Trait change handlers --------------------------------------------
568 #------ Trait change handlers --------------------------------------------
570
569
571 def _style_sheet_changed(self):
570 def _style_sheet_changed(self):
572 """ Set the style sheets of the underlying widgets.
571 """ Set the style sheets of the underlying widgets.
573 """
572 """
574 self.setStyleSheet(self.style_sheet)
573 self.setStyleSheet(self.style_sheet)
575 if self._control is not None:
574 if self._control is not None:
576 self._control.document().setDefaultStyleSheet(self.style_sheet)
575 self._control.document().setDefaultStyleSheet(self.style_sheet)
577 bg_color = self._control.palette().window().color()
576 bg_color = self._control.palette().window().color()
578 self._ansi_processor.set_background_color(bg_color)
577 self._ansi_processor.set_background_color(bg_color)
579
578
580 if self._page_control is not None:
579 if self._page_control is not None:
581 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
580 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
582
581
583
582
584
583
585 def _syntax_style_changed(self):
584 def _syntax_style_changed(self):
586 """ Set the style for the syntax highlighter.
585 """ Set the style for the syntax highlighter.
587 """
586 """
588 if self._highlighter is None:
587 if self._highlighter is None:
589 # ignore premature calls
588 # ignore premature calls
590 return
589 return
591 if self.syntax_style:
590 if self.syntax_style:
592 self._highlighter.set_style(self.syntax_style)
591 self._highlighter.set_style(self.syntax_style)
593 else:
592 else:
594 self._highlighter.set_style_sheet(self.style_sheet)
593 self._highlighter.set_style_sheet(self.style_sheet)
595
594
596 #------ Trait default initializers -----------------------------------------
595 #------ Trait default initializers -----------------------------------------
597
596
598 def _banner_default(self):
597 def _banner_default(self):
599 return "IPython QtConsole {version}\n".format(version=version)
598 return "IPython QtConsole {version}\n".format(version=version)
@@ -1,63 +1,63 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Adapt readline completer interface to make ZMQ request."""
2 """Adapt readline completer interface to make ZMQ request."""
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 try:
7 try:
8 from queue import Empty # Py 3
8 from queue import Empty # Py 3
9 except ImportError:
9 except ImportError:
10 from Queue import Empty # Py 2
10 from Queue import Empty # Py 2
11
11
12 from IPython.config import Configurable
12 from IPython.config import Configurable
13 from IPython.core.completer import IPCompleter
13 from IPython.core.completer import IPCompleter
14 from IPython.utils.traitlets import Float
14 from IPython.utils.traitlets import Float
15 import IPython.utils.rlineimpl as readline
15 import IPython.utils.rlineimpl as readline
16
16
17 class ZMQCompleter(IPCompleter):
17 class ZMQCompleter(IPCompleter):
18 """Client-side completion machinery.
18 """Client-side completion machinery.
19
19
20 How it works: self.complete will be called multiple times, with
20 How it works: self.complete will be called multiple times, with
21 state=0,1,2,... When state=0 it should compute ALL the completion matches,
21 state=0,1,2,... When state=0 it should compute ALL the completion matches,
22 and then return them for each value of state."""
22 and then return them for each value of state."""
23
23
24 timeout = Float(5.0, config=True, help='timeout before completion abort')
24 timeout = Float(5.0, config=True, help='timeout before completion abort')
25
25
26 def __init__(self, shell, client, config=None):
26 def __init__(self, shell, client, config=None):
27 super(ZMQCompleter,self).__init__(config=config)
27 super(ZMQCompleter,self).__init__(config=config)
28
28
29 self.shell = shell
29 self.shell = shell
30 self.client = client
30 self.client = client
31 self.matches = []
31 self.matches = []
32
32
33 def complete_request(self, text):
33 def complete_request(self, text):
34 line = readline.get_line_buffer()
34 line = readline.get_line_buffer()
35 cursor_pos = readline.get_endidx()
35 cursor_pos = readline.get_endidx()
36
36
37 # send completion request to kernel
37 # send completion request to kernel
38 # Give the kernel up to 0.5s to respond
38 # Give the kernel up to 0.5s to respond
39 msg_id = self.client.shell_channel.complete(
39 msg_id = self.client.complete(
40 code=line,
40 code=line,
41 cursor_pos=cursor_pos,
41 cursor_pos=cursor_pos,
42 )
42 )
43
43
44 msg = self.client.shell_channel.get_msg(timeout=self.timeout)
44 msg = self.client.shell_channel.get_msg(timeout=self.timeout)
45 if msg['parent_header']['msg_id'] == msg_id:
45 if msg['parent_header']['msg_id'] == msg_id:
46 return msg["content"]["matches"]
46 return msg["content"]["matches"]
47 return []
47 return []
48
48
49 def rlcomplete(self, text, state):
49 def rlcomplete(self, text, state):
50 if state == 0:
50 if state == 0:
51 try:
51 try:
52 self.matches = self.complete_request(text)
52 self.matches = self.complete_request(text)
53 except Empty:
53 except Empty:
54 #print('WARNING: Kernel timeout on tab completion.')
54 #print('WARNING: Kernel timeout on tab completion.')
55 pass
55 pass
56
56
57 try:
57 try:
58 return self.matches[state]
58 return self.matches[state]
59 except IndexError:
59 except IndexError:
60 return None
60 return None
61
61
62 def complete(self, text, line, cursor_pos=None):
62 def complete(self, text, line, cursor_pos=None):
63 return self.rlcomplete(text, 0)
63 return self.rlcomplete(text, 0)
@@ -1,578 +1,578 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 signal
11 import signal
12 import os
12 import os
13 import sys
13 import sys
14 import time
14 import time
15 import subprocess
15 import subprocess
16 from getpass import getpass
16 from getpass import getpass
17 from io import BytesIO
17 from io import BytesIO
18
18
19 try:
19 try:
20 from queue import Empty # Py 3
20 from queue import Empty # Py 3
21 except ImportError:
21 except ImportError:
22 from Queue import Empty # Py 2
22 from Queue import Empty # Py 2
23
23
24 from IPython.core import page
24 from IPython.core import page
25 from IPython.core import release
25 from IPython.core import release
26 from IPython.terminal.console.zmqhistory import ZMQHistoryManager
26 from IPython.terminal.console.zmqhistory import ZMQHistoryManager
27 from IPython.utils.warn import warn, error
27 from IPython.utils.warn import warn, error
28 from IPython.utils import io
28 from IPython.utils import io
29 from IPython.utils.py3compat import string_types, input
29 from IPython.utils.py3compat import string_types, input
30 from IPython.utils.traitlets import List, Enum, Any, Instance, Unicode, Float, Bool
30 from IPython.utils.traitlets import List, Enum, Any, Instance, Unicode, Float, Bool
31 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
31 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
32
32
33 from IPython.terminal.interactiveshell import TerminalInteractiveShell
33 from IPython.terminal.interactiveshell import TerminalInteractiveShell
34 from IPython.terminal.console.completer import ZMQCompleter
34 from IPython.terminal.console.completer import ZMQCompleter
35
35
36 class ZMQTerminalInteractiveShell(TerminalInteractiveShell):
36 class ZMQTerminalInteractiveShell(TerminalInteractiveShell):
37 """A subclass of TerminalInteractiveShell that uses the 0MQ kernel"""
37 """A subclass of TerminalInteractiveShell that uses the 0MQ kernel"""
38 _executing = False
38 _executing = False
39 _execution_state = Unicode('')
39 _execution_state = Unicode('')
40 _pending_clearoutput = False
40 _pending_clearoutput = False
41 kernel_banner = Unicode('')
41 kernel_banner = Unicode('')
42 kernel_timeout = Float(60, config=True,
42 kernel_timeout = Float(60, config=True,
43 help="""Timeout for giving up on a kernel (in seconds).
43 help="""Timeout for giving up on a kernel (in seconds).
44
44
45 On first connect and restart, the console tests whether the
45 On first connect and restart, the console tests whether the
46 kernel is running and responsive by sending kernel_info_requests.
46 kernel is running and responsive by sending kernel_info_requests.
47 This sets the timeout in seconds for how long the kernel can take
47 This sets the timeout in seconds for how long the kernel can take
48 before being presumed dead.
48 before being presumed dead.
49 """
49 """
50 )
50 )
51
51
52 image_handler = Enum(('PIL', 'stream', 'tempfile', 'callable'),
52 image_handler = Enum(('PIL', 'stream', 'tempfile', 'callable'),
53 config=True, help=
53 config=True, help=
54 """
54 """
55 Handler for image type output. This is useful, for example,
55 Handler for image type output. This is useful, for example,
56 when connecting to the kernel in which pylab inline backend is
56 when connecting to the kernel in which pylab inline backend is
57 activated. There are four handlers defined. 'PIL': Use
57 activated. There are four handlers defined. 'PIL': Use
58 Python Imaging Library to popup image; 'stream': Use an
58 Python Imaging Library to popup image; 'stream': Use an
59 external program to show the image. Image will be fed into
59 external program to show the image. Image will be fed into
60 the STDIN of the program. You will need to configure
60 the STDIN of the program. You will need to configure
61 `stream_image_handler`; 'tempfile': Use an external program to
61 `stream_image_handler`; 'tempfile': Use an external program to
62 show the image. Image will be saved in a temporally file and
62 show the image. Image will be saved in a temporally file and
63 the program is called with the temporally file. You will need
63 the program is called with the temporally file. You will need
64 to configure `tempfile_image_handler`; 'callable': You can set
64 to configure `tempfile_image_handler`; 'callable': You can set
65 any Python callable which is called with the image data. You
65 any Python callable which is called with the image data. You
66 will need to configure `callable_image_handler`.
66 will need to configure `callable_image_handler`.
67 """
67 """
68 )
68 )
69
69
70 stream_image_handler = List(config=True, help=
70 stream_image_handler = List(config=True, help=
71 """
71 """
72 Command to invoke an image viewer program when you are using
72 Command to invoke an image viewer program when you are using
73 'stream' image handler. This option is a list of string where
73 'stream' image handler. This option is a list of string where
74 the first element is the command itself and reminders are the
74 the first element is the command itself and reminders are the
75 options for the command. Raw image data is given as STDIN to
75 options for the command. Raw image data is given as STDIN to
76 the program.
76 the program.
77 """
77 """
78 )
78 )
79
79
80 tempfile_image_handler = List(config=True, help=
80 tempfile_image_handler = List(config=True, help=
81 """
81 """
82 Command to invoke an image viewer program when you are using
82 Command to invoke an image viewer program when you are using
83 'tempfile' image handler. This option is a list of string
83 'tempfile' image handler. This option is a list of string
84 where the first element is the command itself and reminders
84 where the first element is the command itself and reminders
85 are the options for the command. You can use {file} and
85 are the options for the command. You can use {file} and
86 {format} in the string to represent the location of the
86 {format} in the string to represent the location of the
87 generated image file and image format.
87 generated image file and image format.
88 """
88 """
89 )
89 )
90
90
91 callable_image_handler = Any(config=True, help=
91 callable_image_handler = Any(config=True, help=
92 """
92 """
93 Callable object called via 'callable' image handler with one
93 Callable object called via 'callable' image handler with one
94 argument, `data`, which is `msg["content"]["data"]` where
94 argument, `data`, which is `msg["content"]["data"]` where
95 `msg` is the message from iopub channel. For exmaple, you can
95 `msg` is the message from iopub channel. For exmaple, you can
96 find base64 encoded PNG data as `data['image/png']`.
96 find base64 encoded PNG data as `data['image/png']`.
97 """
97 """
98 )
98 )
99
99
100 mime_preference = List(
100 mime_preference = List(
101 default_value=['image/png', 'image/jpeg', 'image/svg+xml'],
101 default_value=['image/png', 'image/jpeg', 'image/svg+xml'],
102 config=True, allow_none=False, help=
102 config=True, allow_none=False, help=
103 """
103 """
104 Preferred object representation MIME type in order. First
104 Preferred object representation MIME type in order. First
105 matched MIME type will be used.
105 matched MIME type will be used.
106 """
106 """
107 )
107 )
108
108
109 manager = Instance('IPython.kernel.KernelManager')
109 manager = Instance('IPython.kernel.KernelManager')
110 client = Instance('IPython.kernel.KernelClient')
110 client = Instance('IPython.kernel.KernelClient')
111 def _client_changed(self, name, old, new):
111 def _client_changed(self, name, old, new):
112 self.session_id = new.session.session
112 self.session_id = new.session.session
113 session_id = Unicode()
113 session_id = Unicode()
114
114
115 def init_completer(self):
115 def init_completer(self):
116 """Initialize the completion machinery.
116 """Initialize the completion machinery.
117
117
118 This creates completion machinery that can be used by client code,
118 This creates completion machinery that can be used by client code,
119 either interactively in-process (typically triggered by the readline
119 either interactively in-process (typically triggered by the readline
120 library), programmatically (such as in test suites) or out-of-process
120 library), programmatically (such as in test suites) or out-of-process
121 (typically over the network by remote frontends).
121 (typically over the network by remote frontends).
122 """
122 """
123 from IPython.core.completerlib import (module_completer,
123 from IPython.core.completerlib import (module_completer,
124 magic_run_completer, cd_completer)
124 magic_run_completer, cd_completer)
125
125
126 self.Completer = ZMQCompleter(self, self.client, config=self.config)
126 self.Completer = ZMQCompleter(self, self.client, config=self.config)
127
127
128
128
129 self.set_hook('complete_command', module_completer, str_key = 'import')
129 self.set_hook('complete_command', module_completer, str_key = 'import')
130 self.set_hook('complete_command', module_completer, str_key = 'from')
130 self.set_hook('complete_command', module_completer, str_key = 'from')
131 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
131 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
132 self.set_hook('complete_command', cd_completer, str_key = '%cd')
132 self.set_hook('complete_command', cd_completer, str_key = '%cd')
133
133
134 # Only configure readline if we truly are using readline. IPython can
134 # Only configure readline if we truly are using readline. IPython can
135 # do tab-completion over the network, in GUIs, etc, where readline
135 # do tab-completion over the network, in GUIs, etc, where readline
136 # itself may be absent
136 # itself may be absent
137 if self.has_readline:
137 if self.has_readline:
138 self.set_readline_completer()
138 self.set_readline_completer()
139
139
140 def run_cell(self, cell, store_history=True):
140 def run_cell(self, cell, store_history=True):
141 """Run a complete IPython cell.
141 """Run a complete IPython cell.
142
142
143 Parameters
143 Parameters
144 ----------
144 ----------
145 cell : str
145 cell : str
146 The code (including IPython code such as %magic functions) to run.
146 The code (including IPython code such as %magic functions) to run.
147 store_history : bool
147 store_history : bool
148 If True, the raw and translated cell will be stored in IPython's
148 If True, the raw and translated cell will be stored in IPython's
149 history. For user code calling back into IPython's machinery, this
149 history. For user code calling back into IPython's machinery, this
150 should be set to False.
150 should be set to False.
151 """
151 """
152 if (not cell) or cell.isspace():
152 if (not cell) or cell.isspace():
153 # pressing enter flushes any pending display
153 # pressing enter flushes any pending display
154 self.handle_iopub()
154 self.handle_iopub()
155 return
155 return
156
156
157 # flush stale replies, which could have been ignored, due to missed heartbeats
157 # flush stale replies, which could have been ignored, due to missed heartbeats
158 while self.client.shell_channel.msg_ready():
158 while self.client.shell_channel.msg_ready():
159 self.client.shell_channel.get_msg()
159 self.client.shell_channel.get_msg()
160 # shell_channel.execute takes 'hidden', which is the inverse of store_hist
160 # execute takes 'hidden', which is the inverse of store_hist
161 msg_id = self.client.shell_channel.execute(cell, not store_history)
161 msg_id = self.client.execute(cell, not store_history)
162
162
163 # first thing is wait for any side effects (output, stdin, etc.)
163 # first thing is wait for any side effects (output, stdin, etc.)
164 self._executing = True
164 self._executing = True
165 self._execution_state = "busy"
165 self._execution_state = "busy"
166 while self._execution_state != 'idle' and self.client.is_alive():
166 while self._execution_state != 'idle' and self.client.is_alive():
167 try:
167 try:
168 self.handle_input_request(msg_id, timeout=0.05)
168 self.handle_input_request(msg_id, timeout=0.05)
169 except Empty:
169 except Empty:
170 # display intermediate print statements, etc.
170 # display intermediate print statements, etc.
171 self.handle_iopub(msg_id)
171 self.handle_iopub(msg_id)
172
172
173 # after all of that is done, wait for the execute reply
173 # after all of that is done, wait for the execute reply
174 while self.client.is_alive():
174 while self.client.is_alive():
175 try:
175 try:
176 self.handle_execute_reply(msg_id, timeout=0.05)
176 self.handle_execute_reply(msg_id, timeout=0.05)
177 except Empty:
177 except Empty:
178 pass
178 pass
179 else:
179 else:
180 break
180 break
181 self._executing = False
181 self._executing = False
182
182
183 #-----------------
183 #-----------------
184 # message handlers
184 # message handlers
185 #-----------------
185 #-----------------
186
186
187 def handle_execute_reply(self, msg_id, timeout=None):
187 def handle_execute_reply(self, msg_id, timeout=None):
188 msg = self.client.shell_channel.get_msg(block=False, timeout=timeout)
188 msg = self.client.shell_channel.get_msg(block=False, timeout=timeout)
189 if msg["parent_header"].get("msg_id", None) == msg_id:
189 if msg["parent_header"].get("msg_id", None) == msg_id:
190
190
191 self.handle_iopub(msg_id)
191 self.handle_iopub(msg_id)
192
192
193 content = msg["content"]
193 content = msg["content"]
194 status = content['status']
194 status = content['status']
195
195
196 if status == 'aborted':
196 if status == 'aborted':
197 self.write('Aborted\n')
197 self.write('Aborted\n')
198 return
198 return
199 elif status == 'ok':
199 elif status == 'ok':
200 # handle payloads
200 # handle payloads
201 for item in content["payload"]:
201 for item in content["payload"]:
202 source = item['source']
202 source = item['source']
203 if source == 'page':
203 if source == 'page':
204 page.page(item['data']['text/plain'])
204 page.page(item['data']['text/plain'])
205 elif source == 'set_next_input':
205 elif source == 'set_next_input':
206 self.set_next_input(item['text'])
206 self.set_next_input(item['text'])
207 elif source == 'ask_exit':
207 elif source == 'ask_exit':
208 self.ask_exit()
208 self.ask_exit()
209
209
210 elif status == 'error':
210 elif status == 'error':
211 for frame in content["traceback"]:
211 for frame in content["traceback"]:
212 print(frame, file=io.stderr)
212 print(frame, file=io.stderr)
213
213
214 self.execution_count = int(content["execution_count"] + 1)
214 self.execution_count = int(content["execution_count"] + 1)
215
215
216 include_other_output = Bool(False, config=True,
216 include_other_output = Bool(False, config=True,
217 help="""Whether to include output from clients
217 help="""Whether to include output from clients
218 other than this one sharing the same kernel.
218 other than this one sharing the same kernel.
219
219
220 Outputs are not displayed until enter is pressed.
220 Outputs are not displayed until enter is pressed.
221 """
221 """
222 )
222 )
223 other_output_prefix = Unicode("[remote] ", config=True,
223 other_output_prefix = Unicode("[remote] ", config=True,
224 help="""Prefix to add to outputs coming from clients other than this one.
224 help="""Prefix to add to outputs coming from clients other than this one.
225
225
226 Only relevant if include_other_output is True.
226 Only relevant if include_other_output is True.
227 """
227 """
228 )
228 )
229
229
230 def from_here(self, msg):
230 def from_here(self, msg):
231 """Return whether a message is from this session"""
231 """Return whether a message is from this session"""
232 return msg['parent_header'].get("session", self.session_id) == self.session_id
232 return msg['parent_header'].get("session", self.session_id) == self.session_id
233
233
234 def include_output(self, msg):
234 def include_output(self, msg):
235 """Return whether we should include a given output message"""
235 """Return whether we should include a given output message"""
236 from_here = self.from_here(msg)
236 from_here = self.from_here(msg)
237 if msg['msg_type'] == 'execute_input':
237 if msg['msg_type'] == 'execute_input':
238 # only echo inputs not from here
238 # only echo inputs not from here
239 return self.include_other_output and not from_here
239 return self.include_other_output and not from_here
240
240
241 if self.include_other_output:
241 if self.include_other_output:
242 return True
242 return True
243 else:
243 else:
244 return from_here
244 return from_here
245
245
246 def handle_iopub(self, msg_id=''):
246 def handle_iopub(self, msg_id=''):
247 """Process messages on the IOPub channel
247 """Process messages on the IOPub channel
248
248
249 This method consumes and processes messages on the IOPub channel,
249 This method consumes and processes messages on the IOPub channel,
250 such as stdout, stderr, execute_result and status.
250 such as stdout, stderr, execute_result and status.
251
251
252 It only displays output that is caused by this session.
252 It only displays output that is caused by this session.
253 """
253 """
254 while self.client.iopub_channel.msg_ready():
254 while self.client.iopub_channel.msg_ready():
255 sub_msg = self.client.iopub_channel.get_msg()
255 sub_msg = self.client.iopub_channel.get_msg()
256 msg_type = sub_msg['header']['msg_type']
256 msg_type = sub_msg['header']['msg_type']
257 parent = sub_msg["parent_header"]
257 parent = sub_msg["parent_header"]
258
258
259 if self.include_output(sub_msg):
259 if self.include_output(sub_msg):
260 if msg_type == 'status':
260 if msg_type == 'status':
261 self._execution_state = sub_msg["content"]["execution_state"]
261 self._execution_state = sub_msg["content"]["execution_state"]
262 elif msg_type == 'stream':
262 elif msg_type == 'stream':
263 if sub_msg["content"]["name"] == "stdout":
263 if sub_msg["content"]["name"] == "stdout":
264 if self._pending_clearoutput:
264 if self._pending_clearoutput:
265 print("\r", file=io.stdout, end="")
265 print("\r", file=io.stdout, end="")
266 self._pending_clearoutput = False
266 self._pending_clearoutput = False
267 print(sub_msg["content"]["text"], file=io.stdout, end="")
267 print(sub_msg["content"]["text"], file=io.stdout, end="")
268 io.stdout.flush()
268 io.stdout.flush()
269 elif sub_msg["content"]["name"] == "stderr":
269 elif sub_msg["content"]["name"] == "stderr":
270 if self._pending_clearoutput:
270 if self._pending_clearoutput:
271 print("\r", file=io.stderr, end="")
271 print("\r", file=io.stderr, end="")
272 self._pending_clearoutput = False
272 self._pending_clearoutput = False
273 print(sub_msg["content"]["text"], file=io.stderr, end="")
273 print(sub_msg["content"]["text"], file=io.stderr, end="")
274 io.stderr.flush()
274 io.stderr.flush()
275
275
276 elif msg_type == 'execute_result':
276 elif msg_type == 'execute_result':
277 if self._pending_clearoutput:
277 if self._pending_clearoutput:
278 print("\r", file=io.stdout, end="")
278 print("\r", file=io.stdout, end="")
279 self._pending_clearoutput = False
279 self._pending_clearoutput = False
280 self.execution_count = int(sub_msg["content"]["execution_count"])
280 self.execution_count = int(sub_msg["content"]["execution_count"])
281 if not self.from_here(sub_msg):
281 if not self.from_here(sub_msg):
282 sys.stdout.write(self.other_output_prefix)
282 sys.stdout.write(self.other_output_prefix)
283 format_dict = sub_msg["content"]["data"]
283 format_dict = sub_msg["content"]["data"]
284 self.handle_rich_data(format_dict)
284 self.handle_rich_data(format_dict)
285
285
286 # taken from DisplayHook.__call__:
286 # taken from DisplayHook.__call__:
287 hook = self.displayhook
287 hook = self.displayhook
288 hook.start_displayhook()
288 hook.start_displayhook()
289 hook.write_output_prompt()
289 hook.write_output_prompt()
290 hook.write_format_data(format_dict)
290 hook.write_format_data(format_dict)
291 hook.log_output(format_dict)
291 hook.log_output(format_dict)
292 hook.finish_displayhook()
292 hook.finish_displayhook()
293
293
294 elif msg_type == 'display_data':
294 elif msg_type == 'display_data':
295 data = sub_msg["content"]["data"]
295 data = sub_msg["content"]["data"]
296 handled = self.handle_rich_data(data)
296 handled = self.handle_rich_data(data)
297 if not handled:
297 if not handled:
298 if not self.from_here(sub_msg):
298 if not self.from_here(sub_msg):
299 sys.stdout.write(self.other_output_prefix)
299 sys.stdout.write(self.other_output_prefix)
300 # if it was an image, we handled it by now
300 # if it was an image, we handled it by now
301 if 'text/plain' in data:
301 if 'text/plain' in data:
302 print(data['text/plain'])
302 print(data['text/plain'])
303
303
304 elif msg_type == 'execute_input':
304 elif msg_type == 'execute_input':
305 content = sub_msg['content']
305 content = sub_msg['content']
306 self.execution_count = content['execution_count']
306 self.execution_count = content['execution_count']
307 if not self.from_here(sub_msg):
307 if not self.from_here(sub_msg):
308 sys.stdout.write(self.other_output_prefix)
308 sys.stdout.write(self.other_output_prefix)
309 sys.stdout.write(self.prompt_manager.render('in'))
309 sys.stdout.write(self.prompt_manager.render('in'))
310 sys.stdout.write(content['code'])
310 sys.stdout.write(content['code'])
311
311
312 elif msg_type == 'clear_output':
312 elif msg_type == 'clear_output':
313 if sub_msg["content"]["wait"]:
313 if sub_msg["content"]["wait"]:
314 self._pending_clearoutput = True
314 self._pending_clearoutput = True
315 else:
315 else:
316 print("\r", file=io.stdout, end="")
316 print("\r", file=io.stdout, end="")
317
317
318 _imagemime = {
318 _imagemime = {
319 'image/png': 'png',
319 'image/png': 'png',
320 'image/jpeg': 'jpeg',
320 'image/jpeg': 'jpeg',
321 'image/svg+xml': 'svg',
321 'image/svg+xml': 'svg',
322 }
322 }
323
323
324 def handle_rich_data(self, data):
324 def handle_rich_data(self, data):
325 for mime in self.mime_preference:
325 for mime in self.mime_preference:
326 if mime in data and mime in self._imagemime:
326 if mime in data and mime in self._imagemime:
327 self.handle_image(data, mime)
327 self.handle_image(data, mime)
328 return True
328 return True
329
329
330 def handle_image(self, data, mime):
330 def handle_image(self, data, mime):
331 handler = getattr(
331 handler = getattr(
332 self, 'handle_image_{0}'.format(self.image_handler), None)
332 self, 'handle_image_{0}'.format(self.image_handler), None)
333 if handler:
333 if handler:
334 handler(data, mime)
334 handler(data, mime)
335
335
336 def handle_image_PIL(self, data, mime):
336 def handle_image_PIL(self, data, mime):
337 if mime not in ('image/png', 'image/jpeg'):
337 if mime not in ('image/png', 'image/jpeg'):
338 return
338 return
339 import PIL.Image
339 import PIL.Image
340 raw = base64.decodestring(data[mime].encode('ascii'))
340 raw = base64.decodestring(data[mime].encode('ascii'))
341 img = PIL.Image.open(BytesIO(raw))
341 img = PIL.Image.open(BytesIO(raw))
342 img.show()
342 img.show()
343
343
344 def handle_image_stream(self, data, mime):
344 def handle_image_stream(self, data, mime):
345 raw = base64.decodestring(data[mime].encode('ascii'))
345 raw = base64.decodestring(data[mime].encode('ascii'))
346 imageformat = self._imagemime[mime]
346 imageformat = self._imagemime[mime]
347 fmt = dict(format=imageformat)
347 fmt = dict(format=imageformat)
348 args = [s.format(**fmt) for s in self.stream_image_handler]
348 args = [s.format(**fmt) for s in self.stream_image_handler]
349 with open(os.devnull, 'w') as devnull:
349 with open(os.devnull, 'w') as devnull:
350 proc = subprocess.Popen(
350 proc = subprocess.Popen(
351 args, stdin=subprocess.PIPE,
351 args, stdin=subprocess.PIPE,
352 stdout=devnull, stderr=devnull)
352 stdout=devnull, stderr=devnull)
353 proc.communicate(raw)
353 proc.communicate(raw)
354
354
355 def handle_image_tempfile(self, data, mime):
355 def handle_image_tempfile(self, data, mime):
356 raw = base64.decodestring(data[mime].encode('ascii'))
356 raw = base64.decodestring(data[mime].encode('ascii'))
357 imageformat = self._imagemime[mime]
357 imageformat = self._imagemime[mime]
358 filename = 'tmp.{0}'.format(imageformat)
358 filename = 'tmp.{0}'.format(imageformat)
359 with NamedFileInTemporaryDirectory(filename) as f, \
359 with NamedFileInTemporaryDirectory(filename) as f, \
360 open(os.devnull, 'w') as devnull:
360 open(os.devnull, 'w') as devnull:
361 f.write(raw)
361 f.write(raw)
362 f.flush()
362 f.flush()
363 fmt = dict(file=f.name, format=imageformat)
363 fmt = dict(file=f.name, format=imageformat)
364 args = [s.format(**fmt) for s in self.tempfile_image_handler]
364 args = [s.format(**fmt) for s in self.tempfile_image_handler]
365 subprocess.call(args, stdout=devnull, stderr=devnull)
365 subprocess.call(args, stdout=devnull, stderr=devnull)
366
366
367 def handle_image_callable(self, data, mime):
367 def handle_image_callable(self, data, mime):
368 self.callable_image_handler(data)
368 self.callable_image_handler(data)
369
369
370 def handle_input_request(self, msg_id, timeout=0.1):
370 def handle_input_request(self, msg_id, timeout=0.1):
371 """ Method to capture raw_input
371 """ Method to capture raw_input
372 """
372 """
373 req = self.client.stdin_channel.get_msg(timeout=timeout)
373 req = self.client.stdin_channel.get_msg(timeout=timeout)
374 # in case any iopub came while we were waiting:
374 # in case any iopub came while we were waiting:
375 self.handle_iopub(msg_id)
375 self.handle_iopub(msg_id)
376 if msg_id == req["parent_header"].get("msg_id"):
376 if msg_id == req["parent_header"].get("msg_id"):
377 # wrap SIGINT handler
377 # wrap SIGINT handler
378 real_handler = signal.getsignal(signal.SIGINT)
378 real_handler = signal.getsignal(signal.SIGINT)
379 def double_int(sig,frame):
379 def double_int(sig,frame):
380 # call real handler (forwards sigint to kernel),
380 # call real handler (forwards sigint to kernel),
381 # then raise local interrupt, stopping local raw_input
381 # then raise local interrupt, stopping local raw_input
382 real_handler(sig,frame)
382 real_handler(sig,frame)
383 raise KeyboardInterrupt
383 raise KeyboardInterrupt
384 signal.signal(signal.SIGINT, double_int)
384 signal.signal(signal.SIGINT, double_int)
385 content = req['content']
385 content = req['content']
386 read = getpass if content.get('password', False) else input
386 read = getpass if content.get('password', False) else input
387 try:
387 try:
388 raw_data = read(content["prompt"])
388 raw_data = read(content["prompt"])
389 except EOFError:
389 except EOFError:
390 # turn EOFError into EOF character
390 # turn EOFError into EOF character
391 raw_data = '\x04'
391 raw_data = '\x04'
392 except KeyboardInterrupt:
392 except KeyboardInterrupt:
393 sys.stdout.write('\n')
393 sys.stdout.write('\n')
394 return
394 return
395 finally:
395 finally:
396 # restore SIGINT handler
396 # restore SIGINT handler
397 signal.signal(signal.SIGINT, real_handler)
397 signal.signal(signal.SIGINT, real_handler)
398
398
399 # only send stdin reply if there *was not* another request
399 # only send stdin reply if there *was not* another request
400 # or execution finished while we were reading.
400 # or execution finished while we were reading.
401 if not (self.client.stdin_channel.msg_ready() or self.client.shell_channel.msg_ready()):
401 if not (self.client.stdin_channel.msg_ready() or self.client.shell_channel.msg_ready()):
402 self.client.stdin_channel.input(raw_data)
402 self.client.input(raw_data)
403
403
404 def mainloop(self, display_banner=False):
404 def mainloop(self, display_banner=False):
405 while True:
405 while True:
406 try:
406 try:
407 self.interact(display_banner=display_banner)
407 self.interact(display_banner=display_banner)
408 #self.interact_with_readline()
408 #self.interact_with_readline()
409 # XXX for testing of a readline-decoupled repl loop, call
409 # XXX for testing of a readline-decoupled repl loop, call
410 # interact_with_readline above
410 # interact_with_readline above
411 break
411 break
412 except KeyboardInterrupt:
412 except KeyboardInterrupt:
413 # this should not be necessary, but KeyboardInterrupt
413 # this should not be necessary, but KeyboardInterrupt
414 # handling seems rather unpredictable...
414 # handling seems rather unpredictable...
415 self.write("\nKeyboardInterrupt in interact()\n")
415 self.write("\nKeyboardInterrupt in interact()\n")
416
416
417 self.client.shell_channel.shutdown()
417 self.client.shutdown()
418
418
419 def _banner1_default(self):
419 def _banner1_default(self):
420 return "IPython Console {version}\n".format(version=release.version)
420 return "IPython Console {version}\n".format(version=release.version)
421
421
422 def compute_banner(self):
422 def compute_banner(self):
423 super(ZMQTerminalInteractiveShell, self).compute_banner()
423 super(ZMQTerminalInteractiveShell, self).compute_banner()
424 if self.client and not self.kernel_banner:
424 if self.client and not self.kernel_banner:
425 msg_id = self.client.kernel_info()
425 msg_id = self.client.kernel_info()
426 while True:
426 while True:
427 try:
427 try:
428 reply = self.client.get_shell_msg(timeout=1)
428 reply = self.client.get_shell_msg(timeout=1)
429 except Empty:
429 except Empty:
430 break
430 break
431 else:
431 else:
432 if reply['parent_header'].get('msg_id') == msg_id:
432 if reply['parent_header'].get('msg_id') == msg_id:
433 self.kernel_banner = reply['content'].get('banner', '')
433 self.kernel_banner = reply['content'].get('banner', '')
434 break
434 break
435 self.banner += self.kernel_banner
435 self.banner += self.kernel_banner
436
436
437 def wait_for_kernel(self, timeout=None):
437 def wait_for_kernel(self, timeout=None):
438 """method to wait for a kernel to be ready"""
438 """method to wait for a kernel to be ready"""
439 tic = time.time()
439 tic = time.time()
440 self.client.hb_channel.unpause()
440 self.client.hb_channel.unpause()
441 while True:
441 while True:
442 msg_id = self.client.kernel_info()
442 msg_id = self.client.kernel_info()
443 reply = None
443 reply = None
444 while True:
444 while True:
445 try:
445 try:
446 reply = self.client.get_shell_msg(timeout=1)
446 reply = self.client.get_shell_msg(timeout=1)
447 except Empty:
447 except Empty:
448 break
448 break
449 else:
449 else:
450 if reply['parent_header'].get('msg_id') == msg_id:
450 if reply['parent_header'].get('msg_id') == msg_id:
451 return True
451 return True
452 if timeout is not None \
452 if timeout is not None \
453 and (time.time() - tic) > timeout \
453 and (time.time() - tic) > timeout \
454 and not self.client.hb_channel.is_beating():
454 and not self.client.hb_channel.is_beating():
455 # heart failed
455 # heart failed
456 return False
456 return False
457 return True
457 return True
458
458
459 def interact(self, display_banner=None):
459 def interact(self, display_banner=None):
460 """Closely emulate the interactive Python console."""
460 """Closely emulate the interactive Python console."""
461
461
462 # batch run -> do not interact
462 # batch run -> do not interact
463 if self.exit_now:
463 if self.exit_now:
464 return
464 return
465
465
466 if display_banner is None:
466 if display_banner is None:
467 display_banner = self.display_banner
467 display_banner = self.display_banner
468
468
469 if isinstance(display_banner, string_types):
469 if isinstance(display_banner, string_types):
470 self.show_banner(display_banner)
470 self.show_banner(display_banner)
471 elif display_banner:
471 elif display_banner:
472 self.show_banner()
472 self.show_banner()
473
473
474 more = False
474 more = False
475
475
476 # run a non-empty no-op, so that we don't get a prompt until
476 # run a non-empty no-op, so that we don't get a prompt until
477 # we know the kernel is ready. This keeps the connection
477 # we know the kernel is ready. This keeps the connection
478 # message above the first prompt.
478 # message above the first prompt.
479 if not self.wait_for_kernel(self.kernel_timeout):
479 if not self.wait_for_kernel(self.kernel_timeout):
480 error("Kernel did not respond\n")
480 error("Kernel did not respond\n")
481 return
481 return
482
482
483 if self.has_readline:
483 if self.has_readline:
484 self.readline_startup_hook(self.pre_readline)
484 self.readline_startup_hook(self.pre_readline)
485 hlen_b4_cell = self.readline.get_current_history_length()
485 hlen_b4_cell = self.readline.get_current_history_length()
486 else:
486 else:
487 hlen_b4_cell = 0
487 hlen_b4_cell = 0
488 # exit_now is set by a call to %Exit or %Quit, through the
488 # exit_now is set by a call to %Exit or %Quit, through the
489 # ask_exit callback.
489 # ask_exit callback.
490
490
491 while not self.exit_now:
491 while not self.exit_now:
492 if not self.client.is_alive():
492 if not self.client.is_alive():
493 # kernel died, prompt for action or exit
493 # kernel died, prompt for action or exit
494
494
495 action = "restart" if self.manager else "wait for restart"
495 action = "restart" if self.manager else "wait for restart"
496 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
496 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
497 if ans:
497 if ans:
498 if self.manager:
498 if self.manager:
499 self.manager.restart_kernel(True)
499 self.manager.restart_kernel(True)
500 self.wait_for_kernel(self.kernel_timeout)
500 self.wait_for_kernel(self.kernel_timeout)
501 else:
501 else:
502 self.exit_now = True
502 self.exit_now = True
503 continue
503 continue
504 try:
504 try:
505 # protect prompt block from KeyboardInterrupt
505 # protect prompt block from KeyboardInterrupt
506 # when sitting on ctrl-C
506 # when sitting on ctrl-C
507 self.hooks.pre_prompt_hook()
507 self.hooks.pre_prompt_hook()
508 if more:
508 if more:
509 try:
509 try:
510 prompt = self.prompt_manager.render('in2')
510 prompt = self.prompt_manager.render('in2')
511 except Exception:
511 except Exception:
512 self.showtraceback()
512 self.showtraceback()
513 if self.autoindent:
513 if self.autoindent:
514 self.rl_do_indent = True
514 self.rl_do_indent = True
515
515
516 else:
516 else:
517 try:
517 try:
518 prompt = self.separate_in + self.prompt_manager.render('in')
518 prompt = self.separate_in + self.prompt_manager.render('in')
519 except Exception:
519 except Exception:
520 self.showtraceback()
520 self.showtraceback()
521
521
522 line = self.raw_input(prompt)
522 line = self.raw_input(prompt)
523 if self.exit_now:
523 if self.exit_now:
524 # quick exit on sys.std[in|out] close
524 # quick exit on sys.std[in|out] close
525 break
525 break
526 if self.autoindent:
526 if self.autoindent:
527 self.rl_do_indent = False
527 self.rl_do_indent = False
528
528
529 except KeyboardInterrupt:
529 except KeyboardInterrupt:
530 #double-guard against keyboardinterrupts during kbdint handling
530 #double-guard against keyboardinterrupts during kbdint handling
531 try:
531 try:
532 self.write('\n' + self.get_exception_only())
532 self.write('\n' + self.get_exception_only())
533 source_raw = self.input_splitter.raw_reset()
533 source_raw = self.input_splitter.raw_reset()
534 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
534 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
535 more = False
535 more = False
536 except KeyboardInterrupt:
536 except KeyboardInterrupt:
537 pass
537 pass
538 except EOFError:
538 except EOFError:
539 if self.autoindent:
539 if self.autoindent:
540 self.rl_do_indent = False
540 self.rl_do_indent = False
541 if self.has_readline:
541 if self.has_readline:
542 self.readline_startup_hook(None)
542 self.readline_startup_hook(None)
543 self.write('\n')
543 self.write('\n')
544 self.exit()
544 self.exit()
545 except bdb.BdbQuit:
545 except bdb.BdbQuit:
546 warn('The Python debugger has exited with a BdbQuit exception.\n'
546 warn('The Python debugger has exited with a BdbQuit exception.\n'
547 'Because of how pdb handles the stack, it is impossible\n'
547 'Because of how pdb handles the stack, it is impossible\n'
548 'for IPython to properly format this particular exception.\n'
548 'for IPython to properly format this particular exception.\n'
549 'IPython will resume normal operation.')
549 'IPython will resume normal operation.')
550 except:
550 except:
551 # exceptions here are VERY RARE, but they can be triggered
551 # exceptions here are VERY RARE, but they can be triggered
552 # asynchronously by signal handlers, for example.
552 # asynchronously by signal handlers, for example.
553 self.showtraceback()
553 self.showtraceback()
554 else:
554 else:
555 try:
555 try:
556 self.input_splitter.push(line)
556 self.input_splitter.push(line)
557 more = self.input_splitter.push_accepts_more()
557 more = self.input_splitter.push_accepts_more()
558 except SyntaxError:
558 except SyntaxError:
559 # Run the code directly - run_cell takes care of displaying
559 # Run the code directly - run_cell takes care of displaying
560 # the exception.
560 # the exception.
561 more = False
561 more = False
562 if (self.SyntaxTB.last_syntax_error and
562 if (self.SyntaxTB.last_syntax_error and
563 self.autoedit_syntax):
563 self.autoedit_syntax):
564 self.edit_syntax_error()
564 self.edit_syntax_error()
565 if not more:
565 if not more:
566 source_raw = self.input_splitter.raw_reset()
566 source_raw = self.input_splitter.raw_reset()
567 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
567 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
568 self.run_cell(source_raw)
568 self.run_cell(source_raw)
569
569
570
570
571 # Turn off the exit flag, so the mainloop can be restarted if desired
571 # Turn off the exit flag, so the mainloop can be restarted if desired
572 self.exit_now = False
572 self.exit_now = False
573
573
574 def init_history(self):
574 def init_history(self):
575 """Sets up the command history. """
575 """Sets up the command history. """
576 self.history_manager = ZMQHistoryManager(client=self.client)
576 self.history_manager = ZMQHistoryManager(client=self.client)
577 self.configurables.append(self.history_manager)
577 self.configurables.append(self.history_manager)
578
578
General Comments 0
You need to be logged in to leave comments. Login now