##// END OF EJS Templates
fix to minrk suggestion
Matthias BUSSONNIER -
Show More
@@ -1,731 +1,729 b''
1 from __future__ import print_function
1 from __future__ import print_function
2
2
3 # Standard library imports
3 # Standard library imports
4 from collections import namedtuple
4 from collections import namedtuple
5 import sys
5 import sys
6 import time
6 import time
7 import uuid
7 import uuid
8
8
9 # System library imports
9 # System library imports
10 from pygments.lexers import PythonLexer
10 from pygments.lexers import PythonLexer
11 from IPython.external import qt
11 from IPython.external import qt
12 from IPython.external.qt import QtCore, QtGui
12 from IPython.external.qt import QtCore, QtGui
13
13
14 # Local imports
14 # Local imports
15 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
15 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
16 from IPython.core.oinspect import call_tip
16 from IPython.core.oinspect import call_tip
17 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
17 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
18 from IPython.utils.traitlets import Bool, Instance, Unicode
18 from IPython.utils.traitlets import Bool, Instance, Unicode
19 from bracket_matcher import BracketMatcher
19 from bracket_matcher import BracketMatcher
20 from call_tip_widget import CallTipWidget
20 from call_tip_widget import CallTipWidget
21 from completion_lexer import CompletionLexer
21 from completion_lexer import CompletionLexer
22 from history_console_widget import HistoryConsoleWidget
22 from history_console_widget import HistoryConsoleWidget
23 from pygments_highlighter import PygmentsHighlighter
23 from pygments_highlighter import PygmentsHighlighter
24
24
25
25
26 class FrontendHighlighter(PygmentsHighlighter):
26 class FrontendHighlighter(PygmentsHighlighter):
27 """ A PygmentsHighlighter that understands and ignores prompts.
27 """ A PygmentsHighlighter that understands and ignores prompts.
28 """
28 """
29
29
30 def __init__(self, frontend):
30 def __init__(self, frontend):
31 super(FrontendHighlighter, self).__init__(frontend._control.document())
31 super(FrontendHighlighter, self).__init__(frontend._control.document())
32 self._current_offset = 0
32 self._current_offset = 0
33 self._frontend = frontend
33 self._frontend = frontend
34 self.highlighting_on = False
34 self.highlighting_on = False
35
35
36 def highlightBlock(self, string):
36 def highlightBlock(self, string):
37 """ Highlight a block of text. Reimplemented to highlight selectively.
37 """ Highlight a block of text. Reimplemented to highlight selectively.
38 """
38 """
39 if not self.highlighting_on:
39 if not self.highlighting_on:
40 return
40 return
41
41
42 # The input to this function is a unicode string that may contain
42 # The input to this function is a unicode string that may contain
43 # paragraph break characters, non-breaking spaces, etc. Here we acquire
43 # paragraph break characters, non-breaking spaces, etc. Here we acquire
44 # the string as plain text so we can compare it.
44 # the string as plain text so we can compare it.
45 current_block = self.currentBlock()
45 current_block = self.currentBlock()
46 string = self._frontend._get_block_plain_text(current_block)
46 string = self._frontend._get_block_plain_text(current_block)
47
47
48 # Decide whether to check for the regular or continuation prompt.
48 # Decide whether to check for the regular or continuation prompt.
49 if current_block.contains(self._frontend._prompt_pos):
49 if current_block.contains(self._frontend._prompt_pos):
50 prompt = self._frontend._prompt
50 prompt = self._frontend._prompt
51 else:
51 else:
52 prompt = self._frontend._continuation_prompt
52 prompt = self._frontend._continuation_prompt
53
53
54 # Only highlight if we can identify a prompt, but make sure not to
54 # Only highlight if we can identify a prompt, but make sure not to
55 # highlight the prompt.
55 # highlight the prompt.
56 if string.startswith(prompt):
56 if string.startswith(prompt):
57 self._current_offset = len(prompt)
57 self._current_offset = len(prompt)
58 string = string[len(prompt):]
58 string = string[len(prompt):]
59 super(FrontendHighlighter, self).highlightBlock(string)
59 super(FrontendHighlighter, self).highlightBlock(string)
60
60
61 def rehighlightBlock(self, block):
61 def rehighlightBlock(self, block):
62 """ Reimplemented to temporarily enable highlighting if disabled.
62 """ Reimplemented to temporarily enable highlighting if disabled.
63 """
63 """
64 old = self.highlighting_on
64 old = self.highlighting_on
65 self.highlighting_on = True
65 self.highlighting_on = True
66 super(FrontendHighlighter, self).rehighlightBlock(block)
66 super(FrontendHighlighter, self).rehighlightBlock(block)
67 self.highlighting_on = old
67 self.highlighting_on = old
68
68
69 def setFormat(self, start, count, format):
69 def setFormat(self, start, count, format):
70 """ Reimplemented to highlight selectively.
70 """ Reimplemented to highlight selectively.
71 """
71 """
72 start += self._current_offset
72 start += self._current_offset
73 super(FrontendHighlighter, self).setFormat(start, count, format)
73 super(FrontendHighlighter, self).setFormat(start, count, format)
74
74
75
75
76 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
76 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
77 """ A Qt frontend for a generic Python kernel.
77 """ A Qt frontend for a generic Python kernel.
78 """
78 """
79
79
80 # The text to show when the kernel is (re)started.
80 # The text to show when the kernel is (re)started.
81 banner = Unicode()
81 banner = Unicode()
82
82
83 # An option and corresponding signal for overriding the default kernel
83 # An option and corresponding signal for overriding the default kernel
84 # interrupt behavior.
84 # interrupt behavior.
85 custom_interrupt = Bool(False)
85 custom_interrupt = Bool(False)
86 custom_interrupt_requested = QtCore.Signal()
86 custom_interrupt_requested = QtCore.Signal()
87
87
88 # An option and corresponding signals for overriding the default kernel
88 # An option and corresponding signals for overriding the default kernel
89 # restart behavior.
89 # restart behavior.
90 custom_restart = Bool(False)
90 custom_restart = Bool(False)
91 custom_restart_kernel_died = QtCore.Signal(float)
91 custom_restart_kernel_died = QtCore.Signal(float)
92 custom_restart_requested = QtCore.Signal()
92 custom_restart_requested = QtCore.Signal()
93
93
94 # Whether to automatically show calltips on open-parentheses.
94 # Whether to automatically show calltips on open-parentheses.
95 enable_calltips = Bool(True, config=True,
95 enable_calltips = Bool(True, config=True,
96 help="Whether to draw information calltips on open-parentheses.")
96 help="Whether to draw information calltips on open-parentheses.")
97
97
98 # Emitted when a user visible 'execute_request' has been submitted to the
98 # Emitted when a user visible 'execute_request' has been submitted to the
99 # kernel from the FrontendWidget. Contains the code to be executed.
99 # kernel from the FrontendWidget. Contains the code to be executed.
100 executing = QtCore.Signal(object)
100 executing = QtCore.Signal(object)
101
101
102 # Emitted when a user-visible 'execute_reply' has been received from the
102 # Emitted when a user-visible 'execute_reply' has been received from the
103 # kernel and processed by the FrontendWidget. Contains the response message.
103 # kernel and processed by the FrontendWidget. Contains the response message.
104 executed = QtCore.Signal(object)
104 executed = QtCore.Signal(object)
105
105
106 # Emitted when an exit request has been received from the kernel.
106 # Emitted when an exit request has been received from the kernel.
107 exit_requested = QtCore.Signal(object)
107 exit_requested = QtCore.Signal(object)
108
108
109 # Protected class variables.
109 # Protected class variables.
110 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
110 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
111 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
111 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
112 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
112 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
113 _input_splitter_class = InputSplitter
113 _input_splitter_class = InputSplitter
114 _local_kernel = False
114 _local_kernel = False
115 _highlighter = Instance(FrontendHighlighter)
115 _highlighter = Instance(FrontendHighlighter)
116
116
117 #---------------------------------------------------------------------------
117 #---------------------------------------------------------------------------
118 # 'object' interface
118 # 'object' interface
119 #---------------------------------------------------------------------------
119 #---------------------------------------------------------------------------
120
120
121 def __init__(self, *args, **kw):
121 def __init__(self, *args, **kw):
122 super(FrontendWidget, self).__init__(*args, **kw)
122 super(FrontendWidget, self).__init__(*args, **kw)
123 # FIXME: remove this when PySide min version is updated past 1.0.7
123 # FIXME: remove this when PySide min version is updated past 1.0.7
124 # forcefully disable calltips if PySide is < 1.0.7, because they crash
124 # forcefully disable calltips if PySide is < 1.0.7, because they crash
125 if qt.QT_API == qt.QT_API_PYSIDE:
125 if qt.QT_API == qt.QT_API_PYSIDE:
126 import PySide
126 import PySide
127 if PySide.__version_info__ < (1,0,7):
127 if PySide.__version_info__ < (1,0,7):
128 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
128 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
129 self.enable_calltips = False
129 self.enable_calltips = False
130
130
131 # FrontendWidget protected variables.
131 # FrontendWidget protected variables.
132 self._bracket_matcher = BracketMatcher(self._control)
132 self._bracket_matcher = BracketMatcher(self._control)
133 self._call_tip_widget = CallTipWidget(self._control)
133 self._call_tip_widget = CallTipWidget(self._control)
134 self._completion_lexer = CompletionLexer(PythonLexer())
134 self._completion_lexer = CompletionLexer(PythonLexer())
135 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
135 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
136 self._hidden = False
136 self._hidden = False
137 self._highlighter = FrontendHighlighter(self)
137 self._highlighter = FrontendHighlighter(self)
138 self._input_splitter = self._input_splitter_class(input_mode='cell')
138 self._input_splitter = self._input_splitter_class(input_mode='cell')
139 self._kernel_manager = None
139 self._kernel_manager = None
140 self._request_info = {}
140 self._request_info = {}
141 self._request_info['execute'] = {};
141 self._request_info['execute'] = {};
142 self._callback_dict = {}
142 self._callback_dict = {}
143
143
144 # Configure the ConsoleWidget.
144 # Configure the ConsoleWidget.
145 self.tab_width = 4
145 self.tab_width = 4
146 self._set_continuation_prompt('... ')
146 self._set_continuation_prompt('... ')
147
147
148 # Configure the CallTipWidget.
148 # Configure the CallTipWidget.
149 self._call_tip_widget.setFont(self.font)
149 self._call_tip_widget.setFont(self.font)
150 self.font_changed.connect(self._call_tip_widget.setFont)
150 self.font_changed.connect(self._call_tip_widget.setFont)
151
151
152 # Configure actions.
152 # Configure actions.
153 action = self._copy_raw_action
153 action = self._copy_raw_action
154 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
154 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
155 action.setEnabled(False)
155 action.setEnabled(False)
156 action.setShortcut(QtGui.QKeySequence(key))
156 action.setShortcut(QtGui.QKeySequence(key))
157 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
157 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
158 action.triggered.connect(self.copy_raw)
158 action.triggered.connect(self.copy_raw)
159 self.copy_available.connect(action.setEnabled)
159 self.copy_available.connect(action.setEnabled)
160 self.addAction(action)
160 self.addAction(action)
161
161
162 # Connect signal handlers.
162 # Connect signal handlers.
163 document = self._control.document()
163 document = self._control.document()
164 document.contentsChange.connect(self._document_contents_change)
164 document.contentsChange.connect(self._document_contents_change)
165
165
166 # Set flag for whether we are connected via localhost.
166 # Set flag for whether we are connected via localhost.
167 self._local_kernel = kw.get('local_kernel',
167 self._local_kernel = kw.get('local_kernel',
168 FrontendWidget._local_kernel)
168 FrontendWidget._local_kernel)
169
169
170 #---------------------------------------------------------------------------
170 #---------------------------------------------------------------------------
171 # 'ConsoleWidget' public interface
171 # 'ConsoleWidget' public interface
172 #---------------------------------------------------------------------------
172 #---------------------------------------------------------------------------
173
173
174 def copy(self):
174 def copy(self):
175 """ Copy the currently selected text to the clipboard, removing prompts.
175 """ Copy the currently selected text to the clipboard, removing prompts.
176 """
176 """
177 text = self._control.textCursor().selection().toPlainText()
177 text = self._control.textCursor().selection().toPlainText()
178 if text:
178 if text:
179 lines = map(transform_classic_prompt, text.splitlines())
179 lines = map(transform_classic_prompt, text.splitlines())
180 text = '\n'.join(lines)
180 text = '\n'.join(lines)
181 QtGui.QApplication.clipboard().setText(text)
181 QtGui.QApplication.clipboard().setText(text)
182
182
183 #---------------------------------------------------------------------------
183 #---------------------------------------------------------------------------
184 # 'ConsoleWidget' abstract interface
184 # 'ConsoleWidget' abstract interface
185 #---------------------------------------------------------------------------
185 #---------------------------------------------------------------------------
186
186
187 def _is_complete(self, source, interactive):
187 def _is_complete(self, source, interactive):
188 """ Returns whether 'source' can be completely processed and a new
188 """ Returns whether 'source' can be completely processed and a new
189 prompt created. When triggered by an Enter/Return key press,
189 prompt created. When triggered by an Enter/Return key press,
190 'interactive' is True; otherwise, it is False.
190 'interactive' is True; otherwise, it is False.
191 """
191 """
192 complete = self._input_splitter.push(source)
192 complete = self._input_splitter.push(source)
193 if interactive:
193 if interactive:
194 complete = not self._input_splitter.push_accepts_more()
194 complete = not self._input_splitter.push_accepts_more()
195 return complete
195 return complete
196
196
197 def _execute(self, source, hidden):
197 def _execute(self, source, hidden):
198 """ Execute 'source'. If 'hidden', do not show any output.
198 """ Execute 'source'. If 'hidden', do not show any output.
199
199
200 See parent class :meth:`execute` docstring for full details.
200 See parent class :meth:`execute` docstring for full details.
201 """
201 """
202 msg_id = self.kernel_manager.shell_channel.execute(source, hidden)
202 msg_id = self.kernel_manager.shell_channel.execute(source, hidden)
203 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
203 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
204 self._hidden = hidden
204 self._hidden = hidden
205 if not hidden:
205 if not hidden:
206 self.executing.emit(source)
206 self.executing.emit(source)
207
207
208 def _prompt_started_hook(self):
208 def _prompt_started_hook(self):
209 """ Called immediately after a new prompt is displayed.
209 """ Called immediately after a new prompt is displayed.
210 """
210 """
211 if not self._reading:
211 if not self._reading:
212 self._highlighter.highlighting_on = True
212 self._highlighter.highlighting_on = True
213
213
214 def _prompt_finished_hook(self):
214 def _prompt_finished_hook(self):
215 """ Called immediately after a prompt is finished, i.e. when some input
215 """ Called immediately after a prompt is finished, i.e. when some input
216 will be processed and a new prompt displayed.
216 will be processed and a new prompt displayed.
217 """
217 """
218 # Flush all state from the input splitter so the next round of
218 # Flush all state from the input splitter so the next round of
219 # reading input starts with a clean buffer.
219 # reading input starts with a clean buffer.
220 self._input_splitter.reset()
220 self._input_splitter.reset()
221
221
222 if not self._reading:
222 if not self._reading:
223 self._highlighter.highlighting_on = False
223 self._highlighter.highlighting_on = False
224
224
225 def _tab_pressed(self):
225 def _tab_pressed(self):
226 """ Called when the tab key is pressed. Returns whether to continue
226 """ Called when the tab key is pressed. Returns whether to continue
227 processing the event.
227 processing the event.
228 """
228 """
229 # Perform tab completion if:
229 # Perform tab completion if:
230 # 1) The cursor is in the input buffer.
230 # 1) The cursor is in the input buffer.
231 # 2) There is a non-whitespace character before the cursor.
231 # 2) There is a non-whitespace character before the cursor.
232 text = self._get_input_buffer_cursor_line()
232 text = self._get_input_buffer_cursor_line()
233 if text is None:
233 if text is None:
234 return False
234 return False
235 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
235 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
236 if complete:
236 if complete:
237 self._complete()
237 self._complete()
238 return not complete
238 return not complete
239
239
240 #---------------------------------------------------------------------------
240 #---------------------------------------------------------------------------
241 # 'ConsoleWidget' protected interface
241 # 'ConsoleWidget' protected interface
242 #---------------------------------------------------------------------------
242 #---------------------------------------------------------------------------
243
243
244 def _context_menu_make(self, pos):
244 def _context_menu_make(self, pos):
245 """ Reimplemented to add an action for raw copy.
245 """ Reimplemented to add an action for raw copy.
246 """
246 """
247 menu = super(FrontendWidget, self)._context_menu_make(pos)
247 menu = super(FrontendWidget, self)._context_menu_make(pos)
248 for before_action in menu.actions():
248 for before_action in menu.actions():
249 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
249 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
250 QtGui.QKeySequence.ExactMatch:
250 QtGui.QKeySequence.ExactMatch:
251 menu.insertAction(before_action, self._copy_raw_action)
251 menu.insertAction(before_action, self._copy_raw_action)
252 break
252 break
253 return menu
253 return menu
254
254
255 def request_interrupt_kernel(self):
255 def request_interrupt_kernel(self):
256 if self._executing:
256 if self._executing:
257 self.interrupt_kernel()
257 self.interrupt_kernel()
258
258
259 def request_restart_kernel(self):
259 def request_restart_kernel(self):
260 message = 'Are you sure you want to restart the kernel?'
260 message = 'Are you sure you want to restart the kernel?'
261 self.restart_kernel(message, now=False)
261 self.restart_kernel(message, now=False)
262
262
263 def _event_filter_console_keypress(self, event):
263 def _event_filter_console_keypress(self, event):
264 """ Reimplemented for execution interruption and smart backspace.
264 """ Reimplemented for execution interruption and smart backspace.
265 """
265 """
266 key = event.key()
266 key = event.key()
267 if self._control_key_down(event.modifiers(), include_command=False):
267 if self._control_key_down(event.modifiers(), include_command=False):
268
268
269 if key == QtCore.Qt.Key_C and self._executing:
269 if key == QtCore.Qt.Key_C and self._executing:
270 self.request_interrupt_kernel()
270 self.request_interrupt_kernel()
271 return True
271 return True
272
272
273 elif key == QtCore.Qt.Key_Period:
273 elif key == QtCore.Qt.Key_Period:
274 self.request_restart_kernel()
274 self.request_restart_kernel()
275 return True
275 return True
276
276
277 elif not event.modifiers() & QtCore.Qt.AltModifier:
277 elif not event.modifiers() & QtCore.Qt.AltModifier:
278
278
279 # Smart backspace: remove four characters in one backspace if:
279 # Smart backspace: remove four characters in one backspace if:
280 # 1) everything left of the cursor is whitespace
280 # 1) everything left of the cursor is whitespace
281 # 2) the four characters immediately left of the cursor are spaces
281 # 2) the four characters immediately left of the cursor are spaces
282 if key == QtCore.Qt.Key_Backspace:
282 if key == QtCore.Qt.Key_Backspace:
283 col = self._get_input_buffer_cursor_column()
283 col = self._get_input_buffer_cursor_column()
284 cursor = self._control.textCursor()
284 cursor = self._control.textCursor()
285 if col > 3 and not cursor.hasSelection():
285 if col > 3 and not cursor.hasSelection():
286 text = self._get_input_buffer_cursor_line()[:col]
286 text = self._get_input_buffer_cursor_line()[:col]
287 if text.endswith(' ') and not text.strip():
287 if text.endswith(' ') and not text.strip():
288 cursor.movePosition(QtGui.QTextCursor.Left,
288 cursor.movePosition(QtGui.QTextCursor.Left,
289 QtGui.QTextCursor.KeepAnchor, 4)
289 QtGui.QTextCursor.KeepAnchor, 4)
290 cursor.removeSelectedText()
290 cursor.removeSelectedText()
291 return True
291 return True
292
292
293 return super(FrontendWidget, self)._event_filter_console_keypress(event)
293 return super(FrontendWidget, self)._event_filter_console_keypress(event)
294
294
295 def _insert_continuation_prompt(self, cursor):
295 def _insert_continuation_prompt(self, cursor):
296 """ Reimplemented for auto-indentation.
296 """ Reimplemented for auto-indentation.
297 """
297 """
298 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
298 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
299 cursor.insertText(' ' * self._input_splitter.indent_spaces)
299 cursor.insertText(' ' * self._input_splitter.indent_spaces)
300
300
301 #---------------------------------------------------------------------------
301 #---------------------------------------------------------------------------
302 # 'BaseFrontendMixin' abstract interface
302 # 'BaseFrontendMixin' abstract interface
303 #---------------------------------------------------------------------------
303 #---------------------------------------------------------------------------
304
304
305 def _handle_complete_reply(self, rep):
305 def _handle_complete_reply(self, rep):
306 """ Handle replies for tab completion.
306 """ Handle replies for tab completion.
307 """
307 """
308 self.log.debug("complete: %s", rep.get('content', ''))
308 self.log.debug("complete: %s", rep.get('content', ''))
309 cursor = self._get_cursor()
309 cursor = self._get_cursor()
310 info = self._request_info.get('complete')
310 info = self._request_info.get('complete')
311 if info and info.id == rep['parent_header']['msg_id'] and \
311 if info and info.id == rep['parent_header']['msg_id'] and \
312 info.pos == cursor.position():
312 info.pos == cursor.position():
313 text = '.'.join(self._get_context())
313 text = '.'.join(self._get_context())
314 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
314 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
315 self._complete_with_items(cursor, rep['content']['matches'])
315 self._complete_with_items(cursor, rep['content']['matches'])
316
316
317 def _silent_exec_callback(self, expr, callback):
317 def _silent_exec_callback(self, expr, callback):
318 """Silently execute `expr` in the kernel and call `callback` with reply
318 """Silently execute `expr` in the kernel and call `callback` with reply
319
319
320 the `expr` is evaluated silently in the kernel (without) output in
320 the `expr` is evaluated silently in the kernel (without) output in
321 the frontend. Call `callback` with the
321 the frontend. Call `callback` with the
322 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
322 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
323
323
324 Parameters
324 Parameters
325 ----------
325 ----------
326 expr : string
326 expr : string
327 valid string to be executed by the kernel.
327 valid string to be executed by the kernel.
328 callback : function
328 callback : function
329 function accepting one arguement, as a string. The string will be
329 function accepting one arguement, as a string. The string will be
330 the `repr` of the result of evaluating `expr`
330 the `repr` of the result of evaluating `expr`
331
331
332 The `callback` is called with the 'repr()' of the result of `expr` as
332 The `callback` is called with the 'repr()' of the result of `expr` as
333 first argument. To get the object, do 'eval()' onthe passed value.
333 first argument. To get the object, do 'eval()' onthe passed value.
334
334
335 See Also
335 See Also
336 --------
336 --------
337 _handle_exec_callback : private method, deal with calling callback with reply
337 _handle_exec_callback : private method, deal with calling callback with reply
338
338
339 """
339 """
340
340
341 # generate uuid, which would be used as a indication of wether or not
341 # generate uuid, which would be used as a indication of wether or not
342 # the unique request originate from here (can use msg id ?)
342 # the unique request originate from here (can use msg id ?)
343 local_uuid = str(uuid.uuid1())
343 local_uuid = str(uuid.uuid1())
344 msg_id = self.kernel_manager.shell_channel.execute('',
344 msg_id = self.kernel_manager.shell_channel.execute('',
345 silent=True, user_expressions={ local_uuid:expr })
345 silent=True, user_expressions={ local_uuid:expr })
346 self._callback_dict[local_uuid] = callback
346 self._callback_dict[local_uuid] = callback
347 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
347 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
348
348
349 def _handle_exec_callback(self, msg):
349 def _handle_exec_callback(self, msg):
350 """Execute `callback` corresonding to `msg` reply, after ``_silent_exec_callback``
350 """Execute `callback` corresonding to `msg` reply, after ``_silent_exec_callback``
351
351
352 Parameters
352 Parameters
353 ----------
353 ----------
354 msg : raw message send by the kernel containing an `user_expressions`
354 msg : raw message send by the kernel containing an `user_expressions`
355 and having a 'silent_exec_callback' kind.
355 and having a 'silent_exec_callback' kind.
356
356
357 Notes
357 Notes
358 -----
358 -----
359 This fonction will look for a `callback` associated with the
359 This fonction will look for a `callback` associated with the
360 corresponding message id. Association has been made by
360 corresponding message id. Association has been made by
361 `_silent_exec_callback`. `callback` is then called with the `repr()`
361 `_silent_exec_callback`. `callback` is then called with the `repr()`
362 of the value of corresponding `user_expressions` as argument.
362 of the value of corresponding `user_expressions` as argument.
363 `callback` is then removed from the known list so that any message
363 `callback` is then removed from the known list so that any message
364 coming again with the same id won't trigger it.
364 coming again with the same id won't trigger it.
365
365
366 """
366 """
367
367
368 user_exp = msg['content']['user_expressions']
368 user_exp = msg['content']['user_expressions']
369 for expression in user_exp:
369 for expression in user_exp:
370 if expression in self._callback_dict:
370 if expression in self._callback_dict:
371 self._callback_dict.pop(expression)(user_exp[expression])
371 self._callback_dict.pop(expression)(user_exp[expression])
372
372
373 def _handle_execute_reply(self, msg):
373 def _handle_execute_reply(self, msg):
374 """ Handles replies for code execution.
374 """ Handles replies for code execution.
375 """
375 """
376 self.log.debug("execute: %s", msg.get('content', ''))
376 self.log.debug("execute: %s", msg.get('content', ''))
377 info_list = self._request_info.get('execute')
378 msg_id = msg['parent_header']['msg_id']
377 msg_id = msg['parent_header']['msg_id']
379 if msg_id in info_list:
378 info = self._request_info['execute'].get(msg_id)
380 info = info_list[msg_id]
381 # unset reading flag, because if execute finished, raw_input can't
379 # unset reading flag, because if execute finished, raw_input can't
382 # still be pending.
380 # still be pending.
383 self._reading = False
381 self._reading = False
384 if info and info.kind == 'user' and not self._hidden:
382 if info and info.kind == 'user' and not self._hidden:
385 # Make sure that all output from the SUB channel has been processed
383 # Make sure that all output from the SUB channel has been processed
386 # before writing a new prompt.
384 # before writing a new prompt.
387 self.kernel_manager.sub_channel.flush()
385 self.kernel_manager.sub_channel.flush()
388
386
389 # Reset the ANSI style information to prevent bad text in stdout
387 # Reset the ANSI style information to prevent bad text in stdout
390 # from messing up our colors. We're not a true terminal so we're
388 # from messing up our colors. We're not a true terminal so we're
391 # allowed to do this.
389 # allowed to do this.
392 if self.ansi_codes:
390 if self.ansi_codes:
393 self._ansi_processor.reset_sgr()
391 self._ansi_processor.reset_sgr()
394
392
395 content = msg['content']
393 content = msg['content']
396 status = content['status']
394 status = content['status']
397 if status == 'ok':
395 if status == 'ok':
398 self._process_execute_ok(msg)
396 self._process_execute_ok(msg)
399 elif status == 'error':
397 elif status == 'error':
400 self._process_execute_error(msg)
398 self._process_execute_error(msg)
401 elif status == 'aborted':
399 elif status == 'aborted':
402 self._process_execute_abort(msg)
400 self._process_execute_abort(msg)
403
401
404 self._show_interpreter_prompt_for_reply(msg)
402 self._show_interpreter_prompt_for_reply(msg)
405 self.executed.emit(msg)
403 self.executed.emit(msg)
406 info_list.pop(msg_id)
404 self._request_info['execute'].pop(msg_id)
407 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
405 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
408 self._handle_exec_callback(msg)
406 self._handle_exec_callback(msg)
409 else:
407 else:
410 super(FrontendWidget, self)._handle_execute_reply(msg)
408 super(FrontendWidget, self)._handle_execute_reply(msg)
411
409
412 def _handle_input_request(self, msg):
410 def _handle_input_request(self, msg):
413 """ Handle requests for raw_input.
411 """ Handle requests for raw_input.
414 """
412 """
415 self.log.debug("input: %s", msg.get('content', ''))
413 self.log.debug("input: %s", msg.get('content', ''))
416 if self._hidden:
414 if self._hidden:
417 raise RuntimeError('Request for raw input during hidden execution.')
415 raise RuntimeError('Request for raw input during hidden execution.')
418
416
419 # Make sure that all output from the SUB channel has been processed
417 # Make sure that all output from the SUB channel has been processed
420 # before entering readline mode.
418 # before entering readline mode.
421 self.kernel_manager.sub_channel.flush()
419 self.kernel_manager.sub_channel.flush()
422
420
423 def callback(line):
421 def callback(line):
424 self.kernel_manager.stdin_channel.input(line)
422 self.kernel_manager.stdin_channel.input(line)
425 if self._reading:
423 if self._reading:
426 self.log.debug("Got second input request, assuming first was interrupted.")
424 self.log.debug("Got second input request, assuming first was interrupted.")
427 self._reading = False
425 self._reading = False
428 self._readline(msg['content']['prompt'], callback=callback)
426 self._readline(msg['content']['prompt'], callback=callback)
429
427
430 def _handle_kernel_died(self, since_last_heartbeat):
428 def _handle_kernel_died(self, since_last_heartbeat):
431 """ Handle the kernel's death by asking if the user wants to restart.
429 """ Handle the kernel's death by asking if the user wants to restart.
432 """
430 """
433 self.log.debug("kernel died: %s", since_last_heartbeat)
431 self.log.debug("kernel died: %s", since_last_heartbeat)
434 if self.custom_restart:
432 if self.custom_restart:
435 self.custom_restart_kernel_died.emit(since_last_heartbeat)
433 self.custom_restart_kernel_died.emit(since_last_heartbeat)
436 else:
434 else:
437 message = 'The kernel heartbeat has been inactive for %.2f ' \
435 message = 'The kernel heartbeat has been inactive for %.2f ' \
438 'seconds. Do you want to restart the kernel? You may ' \
436 'seconds. Do you want to restart the kernel? You may ' \
439 'first want to check the network connection.' % \
437 'first want to check the network connection.' % \
440 since_last_heartbeat
438 since_last_heartbeat
441 self.restart_kernel(message, now=True)
439 self.restart_kernel(message, now=True)
442
440
443 def _handle_object_info_reply(self, rep):
441 def _handle_object_info_reply(self, rep):
444 """ Handle replies for call tips.
442 """ Handle replies for call tips.
445 """
443 """
446 self.log.debug("oinfo: %s", rep.get('content', ''))
444 self.log.debug("oinfo: %s", rep.get('content', ''))
447 cursor = self._get_cursor()
445 cursor = self._get_cursor()
448 info = self._request_info.get('call_tip')
446 info = self._request_info.get('call_tip')
449 if info and info.id == rep['parent_header']['msg_id'] and \
447 if info and info.id == rep['parent_header']['msg_id'] and \
450 info.pos == cursor.position():
448 info.pos == cursor.position():
451 # Get the information for a call tip. For now we format the call
449 # Get the information for a call tip. For now we format the call
452 # line as string, later we can pass False to format_call and
450 # line as string, later we can pass False to format_call and
453 # syntax-highlight it ourselves for nicer formatting in the
451 # syntax-highlight it ourselves for nicer formatting in the
454 # calltip.
452 # calltip.
455 content = rep['content']
453 content = rep['content']
456 # if this is from pykernel, 'docstring' will be the only key
454 # if this is from pykernel, 'docstring' will be the only key
457 if content.get('ismagic', False):
455 if content.get('ismagic', False):
458 # Don't generate a call-tip for magics. Ideally, we should
456 # Don't generate a call-tip for magics. Ideally, we should
459 # generate a tooltip, but not on ( like we do for actual
457 # generate a tooltip, but not on ( like we do for actual
460 # callables.
458 # callables.
461 call_info, doc = None, None
459 call_info, doc = None, None
462 else:
460 else:
463 call_info, doc = call_tip(content, format_call=True)
461 call_info, doc = call_tip(content, format_call=True)
464 if call_info or doc:
462 if call_info or doc:
465 self._call_tip_widget.show_call_info(call_info, doc)
463 self._call_tip_widget.show_call_info(call_info, doc)
466
464
467 def _handle_pyout(self, msg):
465 def _handle_pyout(self, msg):
468 """ Handle display hook output.
466 """ Handle display hook output.
469 """
467 """
470 self.log.debug("pyout: %s", msg.get('content', ''))
468 self.log.debug("pyout: %s", msg.get('content', ''))
471 if not self._hidden and self._is_from_this_session(msg):
469 if not self._hidden and self._is_from_this_session(msg):
472 text = msg['content']['data']
470 text = msg['content']['data']
473 self._append_plain_text(text + '\n', before_prompt=True)
471 self._append_plain_text(text + '\n', before_prompt=True)
474
472
475 def _handle_stream(self, msg):
473 def _handle_stream(self, msg):
476 """ Handle stdout, stderr, and stdin.
474 """ Handle stdout, stderr, and stdin.
477 """
475 """
478 self.log.debug("stream: %s", msg.get('content', ''))
476 self.log.debug("stream: %s", msg.get('content', ''))
479 if not self._hidden and self._is_from_this_session(msg):
477 if not self._hidden and self._is_from_this_session(msg):
480 # Most consoles treat tabs as being 8 space characters. Convert tabs
478 # Most consoles treat tabs as being 8 space characters. Convert tabs
481 # to spaces so that output looks as expected regardless of this
479 # to spaces so that output looks as expected regardless of this
482 # widget's tab width.
480 # widget's tab width.
483 text = msg['content']['data'].expandtabs(8)
481 text = msg['content']['data'].expandtabs(8)
484
482
485 self._append_plain_text(text, before_prompt=True)
483 self._append_plain_text(text, before_prompt=True)
486 self._control.moveCursor(QtGui.QTextCursor.End)
484 self._control.moveCursor(QtGui.QTextCursor.End)
487
485
488 def _handle_shutdown_reply(self, msg):
486 def _handle_shutdown_reply(self, msg):
489 """ Handle shutdown signal, only if from other console.
487 """ Handle shutdown signal, only if from other console.
490 """
488 """
491 self.log.debug("shutdown: %s", msg.get('content', ''))
489 self.log.debug("shutdown: %s", msg.get('content', ''))
492 if not self._hidden and not self._is_from_this_session(msg):
490 if not self._hidden and not self._is_from_this_session(msg):
493 if self._local_kernel:
491 if self._local_kernel:
494 if not msg['content']['restart']:
492 if not msg['content']['restart']:
495 self.exit_requested.emit(self)
493 self.exit_requested.emit(self)
496 else:
494 else:
497 # we just got notified of a restart!
495 # we just got notified of a restart!
498 time.sleep(0.25) # wait 1/4 sec to reset
496 time.sleep(0.25) # wait 1/4 sec to reset
499 # lest the request for a new prompt
497 # lest the request for a new prompt
500 # goes to the old kernel
498 # goes to the old kernel
501 self.reset()
499 self.reset()
502 else: # remote kernel, prompt on Kernel shutdown/reset
500 else: # remote kernel, prompt on Kernel shutdown/reset
503 title = self.window().windowTitle()
501 title = self.window().windowTitle()
504 if not msg['content']['restart']:
502 if not msg['content']['restart']:
505 reply = QtGui.QMessageBox.question(self, title,
503 reply = QtGui.QMessageBox.question(self, title,
506 "Kernel has been shutdown permanently. "
504 "Kernel has been shutdown permanently. "
507 "Close the Console?",
505 "Close the Console?",
508 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
506 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
509 if reply == QtGui.QMessageBox.Yes:
507 if reply == QtGui.QMessageBox.Yes:
510 self.exit_requested.emit(self)
508 self.exit_requested.emit(self)
511 else:
509 else:
512 reply = QtGui.QMessageBox.question(self, title,
510 reply = QtGui.QMessageBox.question(self, title,
513 "Kernel has been reset. Clear the Console?",
511 "Kernel has been reset. Clear the Console?",
514 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
512 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
515 if reply == QtGui.QMessageBox.Yes:
513 if reply == QtGui.QMessageBox.Yes:
516 time.sleep(0.25) # wait 1/4 sec to reset
514 time.sleep(0.25) # wait 1/4 sec to reset
517 # lest the request for a new prompt
515 # lest the request for a new prompt
518 # goes to the old kernel
516 # goes to the old kernel
519 self.reset()
517 self.reset()
520
518
521 def _started_channels(self):
519 def _started_channels(self):
522 """ Called when the KernelManager channels have started listening or
520 """ Called when the KernelManager channels have started listening or
523 when the frontend is assigned an already listening KernelManager.
521 when the frontend is assigned an already listening KernelManager.
524 """
522 """
525 self.reset()
523 self.reset()
526
524
527 #---------------------------------------------------------------------------
525 #---------------------------------------------------------------------------
528 # 'FrontendWidget' public interface
526 # 'FrontendWidget' public interface
529 #---------------------------------------------------------------------------
527 #---------------------------------------------------------------------------
530
528
531 def copy_raw(self):
529 def copy_raw(self):
532 """ Copy the currently selected text to the clipboard without attempting
530 """ Copy the currently selected text to the clipboard without attempting
533 to remove prompts or otherwise alter the text.
531 to remove prompts or otherwise alter the text.
534 """
532 """
535 self._control.copy()
533 self._control.copy()
536
534
537 def execute_file(self, path, hidden=False):
535 def execute_file(self, path, hidden=False):
538 """ Attempts to execute file with 'path'. If 'hidden', no output is
536 """ Attempts to execute file with 'path'. If 'hidden', no output is
539 shown.
537 shown.
540 """
538 """
541 self.execute('execfile(%r)' % path, hidden=hidden)
539 self.execute('execfile(%r)' % path, hidden=hidden)
542
540
543 def interrupt_kernel(self):
541 def interrupt_kernel(self):
544 """ Attempts to interrupt the running kernel.
542 """ Attempts to interrupt the running kernel.
545
543
546 Also unsets _reading flag, to avoid runtime errors
544 Also unsets _reading flag, to avoid runtime errors
547 if raw_input is called again.
545 if raw_input is called again.
548 """
546 """
549 if self.custom_interrupt:
547 if self.custom_interrupt:
550 self._reading = False
548 self._reading = False
551 self.custom_interrupt_requested.emit()
549 self.custom_interrupt_requested.emit()
552 elif self.kernel_manager.has_kernel:
550 elif self.kernel_manager.has_kernel:
553 self._reading = False
551 self._reading = False
554 self.kernel_manager.interrupt_kernel()
552 self.kernel_manager.interrupt_kernel()
555 else:
553 else:
556 self._append_plain_text('Kernel process is either remote or '
554 self._append_plain_text('Kernel process is either remote or '
557 'unspecified. Cannot interrupt.\n')
555 'unspecified. Cannot interrupt.\n')
558
556
559 def reset(self):
557 def reset(self):
560 """ Resets the widget to its initial state. Similar to ``clear``, but
558 """ Resets the widget to its initial state. Similar to ``clear``, but
561 also re-writes the banner and aborts execution if necessary.
559 also re-writes the banner and aborts execution if necessary.
562 """
560 """
563 if self._executing:
561 if self._executing:
564 self._executing = False
562 self._executing = False
565 self._request_info['execute'] = {}
563 self._request_info['execute'] = {}
566 self._reading = False
564 self._reading = False
567 self._highlighter.highlighting_on = False
565 self._highlighter.highlighting_on = False
568
566
569 self._control.clear()
567 self._control.clear()
570 self._append_plain_text(self.banner)
568 self._append_plain_text(self.banner)
571 # update output marker for stdout/stderr, so that startup
569 # update output marker for stdout/stderr, so that startup
572 # messages appear after banner:
570 # messages appear after banner:
573 self._append_before_prompt_pos = self._get_cursor().position()
571 self._append_before_prompt_pos = self._get_cursor().position()
574 self._show_interpreter_prompt()
572 self._show_interpreter_prompt()
575
573
576 def restart_kernel(self, message, now=False):
574 def restart_kernel(self, message, now=False):
577 """ Attempts to restart the running kernel.
575 """ Attempts to restart the running kernel.
578 """
576 """
579 # FIXME: now should be configurable via a checkbox in the dialog. Right
577 # FIXME: now should be configurable via a checkbox in the dialog. Right
580 # now at least the heartbeat path sets it to True and the manual restart
578 # now at least the heartbeat path sets it to True and the manual restart
581 # to False. But those should just be the pre-selected states of a
579 # to False. But those should just be the pre-selected states of a
582 # checkbox that the user could override if so desired. But I don't know
580 # checkbox that the user could override if so desired. But I don't know
583 # enough Qt to go implementing the checkbox now.
581 # enough Qt to go implementing the checkbox now.
584
582
585 if self.custom_restart:
583 if self.custom_restart:
586 self.custom_restart_requested.emit()
584 self.custom_restart_requested.emit()
587
585
588 elif self.kernel_manager.has_kernel:
586 elif self.kernel_manager.has_kernel:
589 # Pause the heart beat channel to prevent further warnings.
587 # Pause the heart beat channel to prevent further warnings.
590 self.kernel_manager.hb_channel.pause()
588 self.kernel_manager.hb_channel.pause()
591
589
592 # Prompt the user to restart the kernel. Un-pause the heartbeat if
590 # Prompt the user to restart the kernel. Un-pause the heartbeat if
593 # they decline. (If they accept, the heartbeat will be un-paused
591 # they decline. (If they accept, the heartbeat will be un-paused
594 # automatically when the kernel is restarted.)
592 # automatically when the kernel is restarted.)
595 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
593 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
596 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
594 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
597 message, buttons)
595 message, buttons)
598 if result == QtGui.QMessageBox.Yes:
596 if result == QtGui.QMessageBox.Yes:
599 try:
597 try:
600 self.kernel_manager.restart_kernel(now=now)
598 self.kernel_manager.restart_kernel(now=now)
601 except RuntimeError:
599 except RuntimeError:
602 self._append_plain_text('Kernel started externally. '
600 self._append_plain_text('Kernel started externally. '
603 'Cannot restart.\n')
601 'Cannot restart.\n')
604 else:
602 else:
605 self.reset()
603 self.reset()
606 else:
604 else:
607 self.kernel_manager.hb_channel.unpause()
605 self.kernel_manager.hb_channel.unpause()
608
606
609 else:
607 else:
610 self._append_plain_text('Kernel process is either remote or '
608 self._append_plain_text('Kernel process is either remote or '
611 'unspecified. Cannot restart.\n')
609 'unspecified. Cannot restart.\n')
612
610
613 #---------------------------------------------------------------------------
611 #---------------------------------------------------------------------------
614 # 'FrontendWidget' protected interface
612 # 'FrontendWidget' protected interface
615 #---------------------------------------------------------------------------
613 #---------------------------------------------------------------------------
616
614
617 def _call_tip(self):
615 def _call_tip(self):
618 """ Shows a call tip, if appropriate, at the current cursor location.
616 """ Shows a call tip, if appropriate, at the current cursor location.
619 """
617 """
620 # Decide if it makes sense to show a call tip
618 # Decide if it makes sense to show a call tip
621 if not self.enable_calltips:
619 if not self.enable_calltips:
622 return False
620 return False
623 cursor = self._get_cursor()
621 cursor = self._get_cursor()
624 cursor.movePosition(QtGui.QTextCursor.Left)
622 cursor.movePosition(QtGui.QTextCursor.Left)
625 if cursor.document().characterAt(cursor.position()) != '(':
623 if cursor.document().characterAt(cursor.position()) != '(':
626 return False
624 return False
627 context = self._get_context(cursor)
625 context = self._get_context(cursor)
628 if not context:
626 if not context:
629 return False
627 return False
630
628
631 # Send the metadata request to the kernel
629 # Send the metadata request to the kernel
632 name = '.'.join(context)
630 name = '.'.join(context)
633 msg_id = self.kernel_manager.shell_channel.object_info(name)
631 msg_id = self.kernel_manager.shell_channel.object_info(name)
634 pos = self._get_cursor().position()
632 pos = self._get_cursor().position()
635 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
633 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
636 return True
634 return True
637
635
638 def _complete(self):
636 def _complete(self):
639 """ Performs completion at the current cursor location.
637 """ Performs completion at the current cursor location.
640 """
638 """
641 context = self._get_context()
639 context = self._get_context()
642 if context:
640 if context:
643 # Send the completion request to the kernel
641 # Send the completion request to the kernel
644 msg_id = self.kernel_manager.shell_channel.complete(
642 msg_id = self.kernel_manager.shell_channel.complete(
645 '.'.join(context), # text
643 '.'.join(context), # text
646 self._get_input_buffer_cursor_line(), # line
644 self._get_input_buffer_cursor_line(), # line
647 self._get_input_buffer_cursor_column(), # cursor_pos
645 self._get_input_buffer_cursor_column(), # cursor_pos
648 self.input_buffer) # block
646 self.input_buffer) # block
649 pos = self._get_cursor().position()
647 pos = self._get_cursor().position()
650 info = self._CompletionRequest(msg_id, pos)
648 info = self._CompletionRequest(msg_id, pos)
651 self._request_info['complete'] = info
649 self._request_info['complete'] = info
652
650
653 def _get_context(self, cursor=None):
651 def _get_context(self, cursor=None):
654 """ Gets the context for the specified cursor (or the current cursor
652 """ Gets the context for the specified cursor (or the current cursor
655 if none is specified).
653 if none is specified).
656 """
654 """
657 if cursor is None:
655 if cursor is None:
658 cursor = self._get_cursor()
656 cursor = self._get_cursor()
659 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
657 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
660 QtGui.QTextCursor.KeepAnchor)
658 QtGui.QTextCursor.KeepAnchor)
661 text = cursor.selection().toPlainText()
659 text = cursor.selection().toPlainText()
662 return self._completion_lexer.get_context(text)
660 return self._completion_lexer.get_context(text)
663
661
664 def _process_execute_abort(self, msg):
662 def _process_execute_abort(self, msg):
665 """ Process a reply for an aborted execution request.
663 """ Process a reply for an aborted execution request.
666 """
664 """
667 self._append_plain_text("ERROR: execution aborted\n")
665 self._append_plain_text("ERROR: execution aborted\n")
668
666
669 def _process_execute_error(self, msg):
667 def _process_execute_error(self, msg):
670 """ Process a reply for an execution request that resulted in an error.
668 """ Process a reply for an execution request that resulted in an error.
671 """
669 """
672 content = msg['content']
670 content = msg['content']
673 # If a SystemExit is passed along, this means exit() was called - also
671 # If a SystemExit is passed along, this means exit() was called - also
674 # all the ipython %exit magic syntax of '-k' to be used to keep
672 # all the ipython %exit magic syntax of '-k' to be used to keep
675 # the kernel running
673 # the kernel running
676 if content['ename']=='SystemExit':
674 if content['ename']=='SystemExit':
677 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
675 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
678 self._keep_kernel_on_exit = keepkernel
676 self._keep_kernel_on_exit = keepkernel
679 self.exit_requested.emit(self)
677 self.exit_requested.emit(self)
680 else:
678 else:
681 traceback = ''.join(content['traceback'])
679 traceback = ''.join(content['traceback'])
682 self._append_plain_text(traceback)
680 self._append_plain_text(traceback)
683
681
684 def _process_execute_ok(self, msg):
682 def _process_execute_ok(self, msg):
685 """ Process a reply for a successful execution equest.
683 """ Process a reply for a successful execution equest.
686 """
684 """
687 payload = msg['content']['payload']
685 payload = msg['content']['payload']
688 for item in payload:
686 for item in payload:
689 if not self._process_execute_payload(item):
687 if not self._process_execute_payload(item):
690 warning = 'Warning: received unknown payload of type %s'
688 warning = 'Warning: received unknown payload of type %s'
691 print(warning % repr(item['source']))
689 print(warning % repr(item['source']))
692
690
693 def _process_execute_payload(self, item):
691 def _process_execute_payload(self, item):
694 """ Process a single payload item from the list of payload items in an
692 """ Process a single payload item from the list of payload items in an
695 execution reply. Returns whether the payload was handled.
693 execution reply. Returns whether the payload was handled.
696 """
694 """
697 # The basic FrontendWidget doesn't handle payloads, as they are a
695 # The basic FrontendWidget doesn't handle payloads, as they are a
698 # mechanism for going beyond the standard Python interpreter model.
696 # mechanism for going beyond the standard Python interpreter model.
699 return False
697 return False
700
698
701 def _show_interpreter_prompt(self):
699 def _show_interpreter_prompt(self):
702 """ Shows a prompt for the interpreter.
700 """ Shows a prompt for the interpreter.
703 """
701 """
704 self._show_prompt('>>> ')
702 self._show_prompt('>>> ')
705
703
706 def _show_interpreter_prompt_for_reply(self, msg):
704 def _show_interpreter_prompt_for_reply(self, msg):
707 """ Shows a prompt for the interpreter given an 'execute_reply' message.
705 """ Shows a prompt for the interpreter given an 'execute_reply' message.
708 """
706 """
709 self._show_interpreter_prompt()
707 self._show_interpreter_prompt()
710
708
711 #------ Signal handlers ----------------------------------------------------
709 #------ Signal handlers ----------------------------------------------------
712
710
713 def _document_contents_change(self, position, removed, added):
711 def _document_contents_change(self, position, removed, added):
714 """ Called whenever the document's content changes. Display a call tip
712 """ Called whenever the document's content changes. Display a call tip
715 if appropriate.
713 if appropriate.
716 """
714 """
717 # Calculate where the cursor should be *after* the change:
715 # Calculate where the cursor should be *after* the change:
718 position += added
716 position += added
719
717
720 document = self._control.document()
718 document = self._control.document()
721 if position == self._get_cursor().position():
719 if position == self._get_cursor().position():
722 self._call_tip()
720 self._call_tip()
723
721
724 #------ Trait default initializers -----------------------------------------
722 #------ Trait default initializers -----------------------------------------
725
723
726 def _banner_default(self):
724 def _banner_default(self):
727 """ Returns the standard Python banner.
725 """ Returns the standard Python banner.
728 """
726 """
729 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
727 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
730 '"license" for more information.'
728 '"license" for more information.'
731 return banner % (sys.version, sys.platform)
729 return banner % (sys.version, sys.platform)
@@ -1,285 +1,283 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.traitlets import Bool
5 from IPython.utils.traitlets import Bool
6 from console_widget import ConsoleWidget
6 from console_widget import ConsoleWidget
7
7
8
8
9 class HistoryConsoleWidget(ConsoleWidget):
9 class HistoryConsoleWidget(ConsoleWidget):
10 """ A ConsoleWidget that keeps a history of the commands that have been
10 """ A ConsoleWidget that keeps a history of the commands that have been
11 executed and provides a readline-esque interface to this history.
11 executed and provides a readline-esque interface to this history.
12 """
12 """
13
13
14 #------ Configuration ------------------------------------------------------
14 #------ Configuration ------------------------------------------------------
15
15
16 # If enabled, the input buffer will become "locked" to history movement when
16 # If enabled, the input buffer will become "locked" to history movement when
17 # an edit is made to a multi-line input buffer. To override the lock, use
17 # an edit is made to a multi-line input buffer. To override the lock, use
18 # Shift in conjunction with the standard history cycling keys.
18 # Shift in conjunction with the standard history cycling keys.
19 history_lock = Bool(False, config=True)
19 history_lock = Bool(False, config=True)
20
20
21 #---------------------------------------------------------------------------
21 #---------------------------------------------------------------------------
22 # 'object' interface
22 # 'object' interface
23 #---------------------------------------------------------------------------
23 #---------------------------------------------------------------------------
24
24
25 def __init__(self, *args, **kw):
25 def __init__(self, *args, **kw):
26 super(HistoryConsoleWidget, self).__init__(*args, **kw)
26 super(HistoryConsoleWidget, self).__init__(*args, **kw)
27
27
28 # HistoryConsoleWidget protected variables.
28 # HistoryConsoleWidget protected variables.
29 self._history = []
29 self._history = []
30 self._history_edits = {}
30 self._history_edits = {}
31 self._history_index = 0
31 self._history_index = 0
32 self._history_prefix = ''
32 self._history_prefix = ''
33
33
34 #---------------------------------------------------------------------------
34 #---------------------------------------------------------------------------
35 # 'ConsoleWidget' public interface
35 # 'ConsoleWidget' public interface
36 #---------------------------------------------------------------------------
36 #---------------------------------------------------------------------------
37
37
38 def execute(self, source=None, hidden=False, interactive=False):
38 def execute(self, source=None, hidden=False, interactive=False):
39 """ Reimplemented to the store history.
39 """ Reimplemented to the store history.
40 """
40 """
41 if not hidden:
41 if not hidden:
42 history = self.input_buffer if source is None else source
42 history = self.input_buffer if source is None else source
43
43
44 executed = super(HistoryConsoleWidget, self).execute(
44 executed = super(HistoryConsoleWidget, self).execute(
45 source, hidden, interactive)
45 source, hidden, interactive)
46
46
47 if executed and not hidden:
47 if executed and not hidden:
48 # Save the command unless it was an empty string or was identical
48 # Save the command unless it was an empty string or was identical
49 # to the previous command.
49 # to the previous command.
50 history = history.rstrip()
50 history = history.rstrip()
51 if history and (not self._history or self._history[-1] != history):
51 if history and (not self._history or self._history[-1] != history):
52 self._history.append(history)
52 self._history.append(history)
53
53
54 # Emulate readline: reset all history edits.
54 # Emulate readline: reset all history edits.
55 self._history_edits = {}
55 self._history_edits = {}
56
56
57 # Move the history index to the most recent item.
57 # Move the history index to the most recent item.
58 self._history_index = len(self._history)
58 self._history_index = len(self._history)
59
59
60 return executed
60 return executed
61
61
62 #---------------------------------------------------------------------------
62 #---------------------------------------------------------------------------
63 # 'ConsoleWidget' abstract interface
63 # 'ConsoleWidget' abstract interface
64 #---------------------------------------------------------------------------
64 #---------------------------------------------------------------------------
65
65
66 def _up_pressed(self, shift_modifier):
66 def _up_pressed(self, shift_modifier):
67 """ Called when the up key is pressed. Returns whether to continue
67 """ Called when the up key is pressed. Returns whether to continue
68 processing the event.
68 processing the event.
69 """
69 """
70 prompt_cursor = self._get_prompt_cursor()
70 prompt_cursor = self._get_prompt_cursor()
71 if self._get_cursor().blockNumber() == prompt_cursor.blockNumber():
71 if self._get_cursor().blockNumber() == prompt_cursor.blockNumber():
72 # Bail out if we're locked.
72 # Bail out if we're locked.
73 if self._history_locked() and not shift_modifier:
73 if self._history_locked() and not shift_modifier:
74 return False
74 return False
75
75
76 # Set a search prefix based on the cursor position.
76 # Set a search prefix based on the cursor position.
77 col = self._get_input_buffer_cursor_column()
77 col = self._get_input_buffer_cursor_column()
78 input_buffer = self.input_buffer
78 input_buffer = self.input_buffer
79 if self._history_index == len(self._history) or \
79 if self._history_index == len(self._history) or \
80 (self._history_prefix and col != len(self._history_prefix)):
80 (self._history_prefix and col != len(self._history_prefix)):
81 self._history_index = len(self._history)
81 self._history_index = len(self._history)
82 self._history_prefix = input_buffer[:col]
82 self._history_prefix = input_buffer[:col]
83
83
84 # Perform the search.
84 # Perform the search.
85 self.history_previous(self._history_prefix,
85 self.history_previous(self._history_prefix,
86 as_prefix=not shift_modifier)
86 as_prefix=not shift_modifier)
87
87
88 # Go to the first line of the prompt for seemless history scrolling.
88 # Go to the first line of the prompt for seemless history scrolling.
89 # Emulate readline: keep the cursor position fixed for a prefix
89 # Emulate readline: keep the cursor position fixed for a prefix
90 # search.
90 # search.
91 cursor = self._get_prompt_cursor()
91 cursor = self._get_prompt_cursor()
92 if self._history_prefix:
92 if self._history_prefix:
93 cursor.movePosition(QtGui.QTextCursor.Right,
93 cursor.movePosition(QtGui.QTextCursor.Right,
94 n=len(self._history_prefix))
94 n=len(self._history_prefix))
95 else:
95 else:
96 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
96 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
97 self._set_cursor(cursor)
97 self._set_cursor(cursor)
98
98
99 return False
99 return False
100
100
101 return True
101 return True
102
102
103 def _down_pressed(self, shift_modifier):
103 def _down_pressed(self, shift_modifier):
104 """ Called when the down key is pressed. Returns whether to continue
104 """ Called when the down key is pressed. Returns whether to continue
105 processing the event.
105 processing the event.
106 """
106 """
107 end_cursor = self._get_end_cursor()
107 end_cursor = self._get_end_cursor()
108 if self._get_cursor().blockNumber() == end_cursor.blockNumber():
108 if self._get_cursor().blockNumber() == end_cursor.blockNumber():
109 # Bail out if we're locked.
109 # Bail out if we're locked.
110 if self._history_locked() and not shift_modifier:
110 if self._history_locked() and not shift_modifier:
111 return False
111 return False
112
112
113 # Perform the search.
113 # Perform the search.
114 replaced = self.history_next(self._history_prefix,
114 replaced = self.history_next(self._history_prefix,
115 as_prefix=not shift_modifier)
115 as_prefix=not shift_modifier)
116
116
117 # Emulate readline: keep the cursor position fixed for a prefix
117 # Emulate readline: keep the cursor position fixed for a prefix
118 # search. (We don't need to move the cursor to the end of the buffer
118 # search. (We don't need to move the cursor to the end of the buffer
119 # in the other case because this happens automatically when the
119 # in the other case because this happens automatically when the
120 # input buffer is set.)
120 # input buffer is set.)
121 if self._history_prefix and replaced:
121 if self._history_prefix and replaced:
122 cursor = self._get_prompt_cursor()
122 cursor = self._get_prompt_cursor()
123 cursor.movePosition(QtGui.QTextCursor.Right,
123 cursor.movePosition(QtGui.QTextCursor.Right,
124 n=len(self._history_prefix))
124 n=len(self._history_prefix))
125 self._set_cursor(cursor)
125 self._set_cursor(cursor)
126
126
127 return False
127 return False
128
128
129 return True
129 return True
130
130
131 #---------------------------------------------------------------------------
131 #---------------------------------------------------------------------------
132 # 'HistoryConsoleWidget' public interface
132 # 'HistoryConsoleWidget' public interface
133 #---------------------------------------------------------------------------
133 #---------------------------------------------------------------------------
134
134
135 def history_previous(self, substring='', as_prefix=True):
135 def history_previous(self, substring='', as_prefix=True):
136 """ If possible, set the input buffer to a previous history item.
136 """ If possible, set the input buffer to a previous history item.
137
137
138 Parameters:
138 Parameters:
139 -----------
139 -----------
140 substring : str, optional
140 substring : str, optional
141 If specified, search for an item with this substring.
141 If specified, search for an item with this substring.
142 as_prefix : bool, optional
142 as_prefix : bool, optional
143 If True, the substring must match at the beginning (default).
143 If True, the substring must match at the beginning (default).
144
144
145 Returns:
145 Returns:
146 --------
146 --------
147 Whether the input buffer was changed.
147 Whether the input buffer was changed.
148 """
148 """
149 index = self._history_index
149 index = self._history_index
150 replace = False
150 replace = False
151 while index > 0:
151 while index > 0:
152 index -= 1
152 index -= 1
153 history = self._get_edited_history(index)
153 history = self._get_edited_history(index)
154 if (as_prefix and history.startswith(substring)) \
154 if (as_prefix and history.startswith(substring)) \
155 or (not as_prefix and substring in history):
155 or (not as_prefix and substring in history):
156 replace = True
156 replace = True
157 break
157 break
158
158
159 if replace:
159 if replace:
160 self._store_edits()
160 self._store_edits()
161 self._history_index = index
161 self._history_index = index
162 self.input_buffer = history
162 self.input_buffer = history
163
163
164 return replace
164 return replace
165
165
166 def history_next(self, substring='', as_prefix=True):
166 def history_next(self, substring='', as_prefix=True):
167 """ If possible, set the input buffer to a subsequent history item.
167 """ If possible, set the input buffer to a subsequent history item.
168
168
169 Parameters:
169 Parameters:
170 -----------
170 -----------
171 substring : str, optional
171 substring : str, optional
172 If specified, search for an item with this substring.
172 If specified, search for an item with this substring.
173 as_prefix : bool, optional
173 as_prefix : bool, optional
174 If True, the substring must match at the beginning (default).
174 If True, the substring must match at the beginning (default).
175
175
176 Returns:
176 Returns:
177 --------
177 --------
178 Whether the input buffer was changed.
178 Whether the input buffer was changed.
179 """
179 """
180 index = self._history_index
180 index = self._history_index
181 replace = False
181 replace = False
182 while self._history_index < len(self._history):
182 while self._history_index < len(self._history):
183 index += 1
183 index += 1
184 history = self._get_edited_history(index)
184 history = self._get_edited_history(index)
185 if (as_prefix and history.startswith(substring)) \
185 if (as_prefix and history.startswith(substring)) \
186 or (not as_prefix and substring in history):
186 or (not as_prefix and substring in history):
187 replace = True
187 replace = True
188 break
188 break
189
189
190 if replace:
190 if replace:
191 self._store_edits()
191 self._store_edits()
192 self._history_index = index
192 self._history_index = index
193 self.input_buffer = history
193 self.input_buffer = history
194
194
195 return replace
195 return replace
196
196
197 def history_tail(self, n=10):
197 def history_tail(self, n=10):
198 """ Get the local history list.
198 """ Get the local history list.
199
199
200 Parameters:
200 Parameters:
201 -----------
201 -----------
202 n : int
202 n : int
203 The (maximum) number of history items to get.
203 The (maximum) number of history items to get.
204 """
204 """
205 return self._history[-n:]
205 return self._history[-n:]
206
206
207 def _request_update_session_history_length(self):
207 def _request_update_session_history_length(self):
208 msg_id = self.kernel_manager.shell_channel.execute('',
208 msg_id = self.kernel_manager.shell_channel.execute('',
209 silent=True,
209 silent=True,
210 user_expressions={
210 user_expressions={
211 'hlen':'len(get_ipython().history_manager.input_hist_raw)',
211 'hlen':'len(get_ipython().history_manager.input_hist_raw)',
212 }
212 }
213 )
213 )
214 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'save_magic')
214 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'save_magic')
215
215
216 def _handle_execute_reply(self, msg):
216 def _handle_execute_reply(self, msg):
217 """ Handles replies for code execution, here only session history length
217 """ Handles replies for code execution, here only session history length
218 """
218 """
219 info_list = self._request_info.get('execute')
220 msg_id = msg['parent_header']['msg_id']
219 msg_id = msg['parent_header']['msg_id']
221 if msg_id in info_list:
220 info = self._request_info.get['execute'].pop(msg_id,None)
222 info = info_list.pop(msg_id)
221 if info and info.kind == 'save_magic' and not self._hidden:
223 if info.kind == 'save_magic' and not self._hidden:
222 content = msg['content']
224 content = msg['content']
223 status = content['status']
225 status = content['status']
224 if status == 'ok':
226 if status == 'ok':
225 self._max_session_history=(int(content['user_expressions']['hlen']))
227 self._max_session_history=(int(content['user_expressions']['hlen']))
228
226
229 def save_magic(self):
227 def save_magic(self):
230 # update the session history length
228 # update the session history length
231 self._request_update_session_history_length()
229 self._request_update_session_history_length()
232
230
233 file_name,extFilter = QtGui.QFileDialog.getSaveFileName(self,
231 file_name,extFilter = QtGui.QFileDialog.getSaveFileName(self,
234 "Enter A filename",
232 "Enter A filename",
235 filter='Python File (*.py);; All files (*.*)'
233 filter='Python File (*.py);; All files (*.*)'
236 )
234 )
237
235
238 # let's the user search/type for a file name, while the history length
236 # let's the user search/type for a file name, while the history length
239 # is fetched
237 # is fetched
240
238
241 if file_name:
239 if file_name:
242 hist_range, ok = QtGui.QInputDialog.getText(self,
240 hist_range, ok = QtGui.QInputDialog.getText(self,
243 'Please enter an interval of command to save',
241 'Please enter an interval of command to save',
244 'Saving commands:',
242 'Saving commands:',
245 text=str('1-'+str(self._max_session_history))
243 text=str('1-'+str(self._max_session_history))
246 )
244 )
247 if ok:
245 if ok:
248 self.execute("%save"+" "+file_name+" "+str(hist_range))
246 self.execute("%save"+" "+file_name+" "+str(hist_range))
249
247
250 #---------------------------------------------------------------------------
248 #---------------------------------------------------------------------------
251 # 'HistoryConsoleWidget' protected interface
249 # 'HistoryConsoleWidget' protected interface
252 #---------------------------------------------------------------------------
250 #---------------------------------------------------------------------------
253
251
254 def _history_locked(self):
252 def _history_locked(self):
255 """ Returns whether history movement is locked.
253 """ Returns whether history movement is locked.
256 """
254 """
257 return (self.history_lock and
255 return (self.history_lock and
258 (self._get_edited_history(self._history_index) !=
256 (self._get_edited_history(self._history_index) !=
259 self.input_buffer) and
257 self.input_buffer) and
260 (self._get_prompt_cursor().blockNumber() !=
258 (self._get_prompt_cursor().blockNumber() !=
261 self._get_end_cursor().blockNumber()))
259 self._get_end_cursor().blockNumber()))
262
260
263 def _get_edited_history(self, index):
261 def _get_edited_history(self, index):
264 """ Retrieves a history item, possibly with temporary edits.
262 """ Retrieves a history item, possibly with temporary edits.
265 """
263 """
266 if index in self._history_edits:
264 if index in self._history_edits:
267 return self._history_edits[index]
265 return self._history_edits[index]
268 elif index == len(self._history):
266 elif index == len(self._history):
269 return unicode()
267 return unicode()
270 return self._history[index]
268 return self._history[index]
271
269
272 def _set_history(self, history):
270 def _set_history(self, history):
273 """ Replace the current history with a sequence of history items.
271 """ Replace the current history with a sequence of history items.
274 """
272 """
275 self._history = list(history)
273 self._history = list(history)
276 self._history_edits = {}
274 self._history_edits = {}
277 self._history_index = len(self._history)
275 self._history_index = len(self._history)
278
276
279 def _store_edits(self):
277 def _store_edits(self):
280 """ If there are edits to the current input buffer, store them.
278 """ If there are edits to the current input buffer, store them.
281 """
279 """
282 current = self.input_buffer
280 current = self.input_buffer
283 if self._history_index == len(self._history) or \
281 if self._history_index == len(self._history) or \
284 self._history[self._history_index] != current:
282 self._history[self._history_index] != current:
285 self._history_edits[self._history_index] = current
283 self._history_edits[self._history_index] = current
@@ -1,565 +1,563 b''
1 """ A FrontendWidget that emulates the interface of the console IPython and
1 """ A FrontendWidget that emulates the interface of the console IPython and
2 supports the additional functionality provided by the IPython kernel.
2 supports the additional functionality provided by the IPython kernel.
3 """
3 """
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Imports
6 # Imports
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8
8
9 # Standard library imports
9 # Standard library imports
10 from collections import namedtuple
10 from collections import namedtuple
11 import os.path
11 import os.path
12 import re
12 import re
13 from subprocess import Popen
13 from subprocess import Popen
14 import sys
14 import sys
15 import time
15 import time
16 from textwrap import dedent
16 from textwrap import dedent
17
17
18 # System library imports
18 # System library imports
19 from IPython.external.qt import QtCore, QtGui
19 from IPython.external.qt import QtCore, QtGui
20
20
21 # Local imports
21 # Local imports
22 from IPython.core.inputsplitter import IPythonInputSplitter, \
22 from IPython.core.inputsplitter import IPythonInputSplitter, \
23 transform_ipy_prompt
23 transform_ipy_prompt
24 from IPython.utils.traitlets import Bool, Unicode
24 from IPython.utils.traitlets import Bool, Unicode
25 from frontend_widget import FrontendWidget
25 from frontend_widget import FrontendWidget
26 import styles
26 import styles
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Constants
29 # Constants
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31
31
32 # Default strings to build and display input and output prompts (and separators
32 # Default strings to build and display input and output prompts (and separators
33 # in between)
33 # in between)
34 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
34 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
35 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
35 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
36 default_input_sep = '\n'
36 default_input_sep = '\n'
37 default_output_sep = ''
37 default_output_sep = ''
38 default_output_sep2 = ''
38 default_output_sep2 = ''
39
39
40 # Base path for most payload sources.
40 # Base path for most payload sources.
41 zmq_shell_source = 'IPython.zmq.zmqshell.ZMQInteractiveShell'
41 zmq_shell_source = 'IPython.zmq.zmqshell.ZMQInteractiveShell'
42
42
43 if sys.platform.startswith('win'):
43 if sys.platform.startswith('win'):
44 default_editor = 'notepad'
44 default_editor = 'notepad'
45 else:
45 else:
46 default_editor = ''
46 default_editor = ''
47
47
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49 # IPythonWidget class
49 # IPythonWidget class
50 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51
51
52 class IPythonWidget(FrontendWidget):
52 class IPythonWidget(FrontendWidget):
53 """ A FrontendWidget for an IPython kernel.
53 """ A FrontendWidget for an IPython kernel.
54 """
54 """
55
55
56 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
56 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
57 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
57 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
58 # settings.
58 # settings.
59 custom_edit = Bool(False)
59 custom_edit = Bool(False)
60 custom_edit_requested = QtCore.Signal(object, object)
60 custom_edit_requested = QtCore.Signal(object, object)
61
61
62 editor = Unicode(default_editor, config=True,
62 editor = Unicode(default_editor, config=True,
63 help="""
63 help="""
64 A command for invoking a system text editor. If the string contains a
64 A command for invoking a system text editor. If the string contains a
65 {filename} format specifier, it will be used. Otherwise, the filename
65 {filename} format specifier, it will be used. Otherwise, the filename
66 will be appended to the end the command.
66 will be appended to the end the command.
67 """)
67 """)
68
68
69 editor_line = Unicode(config=True,
69 editor_line = Unicode(config=True,
70 help="""
70 help="""
71 The editor command to use when a specific line number is requested. The
71 The editor command to use when a specific line number is requested. The
72 string should contain two format specifiers: {line} and {filename}. If
72 string should contain two format specifiers: {line} and {filename}. If
73 this parameter is not specified, the line number option to the %edit
73 this parameter is not specified, the line number option to the %edit
74 magic will be ignored.
74 magic will be ignored.
75 """)
75 """)
76
76
77 style_sheet = Unicode(config=True,
77 style_sheet = Unicode(config=True,
78 help="""
78 help="""
79 A CSS stylesheet. The stylesheet can contain classes for:
79 A CSS stylesheet. The stylesheet can contain classes for:
80 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
80 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
81 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
81 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
82 3. IPython: .error, .in-prompt, .out-prompt, etc
82 3. IPython: .error, .in-prompt, .out-prompt, etc
83 """)
83 """)
84
84
85 syntax_style = Unicode(config=True,
85 syntax_style = Unicode(config=True,
86 help="""
86 help="""
87 If not empty, use this Pygments style for syntax highlighting.
87 If not empty, use this Pygments style for syntax highlighting.
88 Otherwise, the style sheet is queried for Pygments style
88 Otherwise, the style sheet is queried for Pygments style
89 information.
89 information.
90 """)
90 """)
91
91
92 # Prompts.
92 # Prompts.
93 in_prompt = Unicode(default_in_prompt, config=True)
93 in_prompt = Unicode(default_in_prompt, config=True)
94 out_prompt = Unicode(default_out_prompt, config=True)
94 out_prompt = Unicode(default_out_prompt, config=True)
95 input_sep = Unicode(default_input_sep, config=True)
95 input_sep = Unicode(default_input_sep, config=True)
96 output_sep = Unicode(default_output_sep, config=True)
96 output_sep = Unicode(default_output_sep, config=True)
97 output_sep2 = Unicode(default_output_sep2, config=True)
97 output_sep2 = Unicode(default_output_sep2, config=True)
98
98
99 # FrontendWidget protected class variables.
99 # FrontendWidget protected class variables.
100 _input_splitter_class = IPythonInputSplitter
100 _input_splitter_class = IPythonInputSplitter
101
101
102 # IPythonWidget protected class variables.
102 # IPythonWidget protected class variables.
103 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
103 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
104 _payload_source_edit = zmq_shell_source + '.edit_magic'
104 _payload_source_edit = zmq_shell_source + '.edit_magic'
105 _payload_source_exit = zmq_shell_source + '.ask_exit'
105 _payload_source_exit = zmq_shell_source + '.ask_exit'
106 _payload_source_next_input = zmq_shell_source + '.set_next_input'
106 _payload_source_next_input = zmq_shell_source + '.set_next_input'
107 _payload_source_page = 'IPython.zmq.page.page'
107 _payload_source_page = 'IPython.zmq.page.page'
108 _retrying_history_request = False
108 _retrying_history_request = False
109
109
110 #---------------------------------------------------------------------------
110 #---------------------------------------------------------------------------
111 # 'object' interface
111 # 'object' interface
112 #---------------------------------------------------------------------------
112 #---------------------------------------------------------------------------
113
113
114 def __init__(self, *args, **kw):
114 def __init__(self, *args, **kw):
115 super(IPythonWidget, self).__init__(*args, **kw)
115 super(IPythonWidget, self).__init__(*args, **kw)
116
116
117 # IPythonWidget protected variables.
117 # IPythonWidget protected variables.
118 self._payload_handlers = {
118 self._payload_handlers = {
119 self._payload_source_edit : self._handle_payload_edit,
119 self._payload_source_edit : self._handle_payload_edit,
120 self._payload_source_exit : self._handle_payload_exit,
120 self._payload_source_exit : self._handle_payload_exit,
121 self._payload_source_page : self._handle_payload_page,
121 self._payload_source_page : self._handle_payload_page,
122 self._payload_source_next_input : self._handle_payload_next_input }
122 self._payload_source_next_input : self._handle_payload_next_input }
123 self._previous_prompt_obj = None
123 self._previous_prompt_obj = None
124 self._keep_kernel_on_exit = None
124 self._keep_kernel_on_exit = None
125
125
126 # Initialize widget styling.
126 # Initialize widget styling.
127 if self.style_sheet:
127 if self.style_sheet:
128 self._style_sheet_changed()
128 self._style_sheet_changed()
129 self._syntax_style_changed()
129 self._syntax_style_changed()
130 else:
130 else:
131 self.set_default_style()
131 self.set_default_style()
132
132
133 #---------------------------------------------------------------------------
133 #---------------------------------------------------------------------------
134 # 'BaseFrontendMixin' abstract interface
134 # 'BaseFrontendMixin' abstract interface
135 #---------------------------------------------------------------------------
135 #---------------------------------------------------------------------------
136
136
137 def _handle_complete_reply(self, rep):
137 def _handle_complete_reply(self, rep):
138 """ Reimplemented to support IPython's improved completion machinery.
138 """ Reimplemented to support IPython's improved completion machinery.
139 """
139 """
140 self.log.debug("complete: %s", rep.get('content', ''))
140 self.log.debug("complete: %s", rep.get('content', ''))
141 cursor = self._get_cursor()
141 cursor = self._get_cursor()
142 info = self._request_info.get('complete')
142 info = self._request_info.get('complete')
143 if info and info.id == rep['parent_header']['msg_id'] and \
143 if info and info.id == rep['parent_header']['msg_id'] and \
144 info.pos == cursor.position():
144 info.pos == cursor.position():
145 matches = rep['content']['matches']
145 matches = rep['content']['matches']
146 text = rep['content']['matched_text']
146 text = rep['content']['matched_text']
147 offset = len(text)
147 offset = len(text)
148
148
149 # Clean up matches with period and path separators if the matched
149 # Clean up matches with period and path separators if the matched
150 # text has not been transformed. This is done by truncating all
150 # text has not been transformed. This is done by truncating all
151 # but the last component and then suitably decreasing the offset
151 # but the last component and then suitably decreasing the offset
152 # between the current cursor position and the start of completion.
152 # between the current cursor position and the start of completion.
153 if len(matches) > 1 and matches[0][:offset] == text:
153 if len(matches) > 1 and matches[0][:offset] == text:
154 parts = re.split(r'[./\\]', text)
154 parts = re.split(r'[./\\]', text)
155 sep_count = len(parts) - 1
155 sep_count = len(parts) - 1
156 if sep_count:
156 if sep_count:
157 chop_length = sum(map(len, parts[:sep_count])) + sep_count
157 chop_length = sum(map(len, parts[:sep_count])) + sep_count
158 matches = [ match[chop_length:] for match in matches ]
158 matches = [ match[chop_length:] for match in matches ]
159 offset -= chop_length
159 offset -= chop_length
160
160
161 # Move the cursor to the start of the match and complete.
161 # Move the cursor to the start of the match and complete.
162 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
162 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
163 self._complete_with_items(cursor, matches)
163 self._complete_with_items(cursor, matches)
164
164
165 def _handle_execute_reply(self, msg):
165 def _handle_execute_reply(self, msg):
166 """ Reimplemented to support prompt requests.
166 """ Reimplemented to support prompt requests.
167 """
167 """
168 info_list = self._request_info.get('execute')
168 msg_id = msg['parent_header'].get('msg_id')
169 msg_id = msg['parent_header']['msg_id']
169 info = self._request_info['execute'].get(msg_id)
170 if msg_id in info_list:
170 if info and info.kind == 'prompt':
171 info = info_list[msg_id]
171 number = msg['content']['execution_count'] + 1
172 if info.kind == 'prompt':
172 self._show_interpreter_prompt(number)
173 number = msg['content']['execution_count'] + 1
173 self._request_info['execute'].pop(msg_id)
174 self._show_interpreter_prompt(number)
174 else:
175 info_list.pop(msg_id)
175 super(IPythonWidget, self)._handle_execute_reply(msg)
176 else:
177 super(IPythonWidget, self)._handle_execute_reply(msg)
178
176
179 def _handle_history_reply(self, msg):
177 def _handle_history_reply(self, msg):
180 """ Implemented to handle history tail replies, which are only supported
178 """ Implemented to handle history tail replies, which are only supported
181 by the IPython kernel.
179 by the IPython kernel.
182 """
180 """
183 self.log.debug("history: %s", msg.get('content', ''))
181 self.log.debug("history: %s", msg.get('content', ''))
184 content = msg['content']
182 content = msg['content']
185 if 'history' not in content:
183 if 'history' not in content:
186 self.log.error("History request failed: %r"%content)
184 self.log.error("History request failed: %r"%content)
187 if content.get('status', '') == 'aborted' and \
185 if content.get('status', '') == 'aborted' and \
188 not self._retrying_history_request:
186 not self._retrying_history_request:
189 # a *different* action caused this request to be aborted, so
187 # a *different* action caused this request to be aborted, so
190 # we should try again.
188 # we should try again.
191 self.log.error("Retrying aborted history request")
189 self.log.error("Retrying aborted history request")
192 # prevent multiple retries of aborted requests:
190 # prevent multiple retries of aborted requests:
193 self._retrying_history_request = True
191 self._retrying_history_request = True
194 # wait out the kernel's queue flush, which is currently timed at 0.1s
192 # wait out the kernel's queue flush, which is currently timed at 0.1s
195 time.sleep(0.25)
193 time.sleep(0.25)
196 self.kernel_manager.shell_channel.history(hist_access_type='tail',n=1000)
194 self.kernel_manager.shell_channel.history(hist_access_type='tail',n=1000)
197 else:
195 else:
198 self._retrying_history_request = False
196 self._retrying_history_request = False
199 return
197 return
200 # reset retry flag
198 # reset retry flag
201 self._retrying_history_request = False
199 self._retrying_history_request = False
202 history_items = content['history']
200 history_items = content['history']
203 items = []
201 items = []
204 last_cell = u""
202 last_cell = u""
205 for _, _, cell in history_items:
203 for _, _, cell in history_items:
206 cell = cell.rstrip()
204 cell = cell.rstrip()
207 if cell != last_cell:
205 if cell != last_cell:
208 items.append(cell)
206 items.append(cell)
209 last_cell = cell
207 last_cell = cell
210 self._set_history(items)
208 self._set_history(items)
211
209
212 def _handle_pyout(self, msg):
210 def _handle_pyout(self, msg):
213 """ Reimplemented for IPython-style "display hook".
211 """ Reimplemented for IPython-style "display hook".
214 """
212 """
215 self.log.debug("pyout: %s", msg.get('content', ''))
213 self.log.debug("pyout: %s", msg.get('content', ''))
216 if not self._hidden and self._is_from_this_session(msg):
214 if not self._hidden and self._is_from_this_session(msg):
217 content = msg['content']
215 content = msg['content']
218 prompt_number = content['execution_count']
216 prompt_number = content['execution_count']
219 data = content['data']
217 data = content['data']
220 if data.has_key('text/html'):
218 if data.has_key('text/html'):
221 self._append_plain_text(self.output_sep, True)
219 self._append_plain_text(self.output_sep, True)
222 self._append_html(self._make_out_prompt(prompt_number), True)
220 self._append_html(self._make_out_prompt(prompt_number), True)
223 html = data['text/html']
221 html = data['text/html']
224 self._append_plain_text('\n', True)
222 self._append_plain_text('\n', True)
225 self._append_html(html + self.output_sep2, True)
223 self._append_html(html + self.output_sep2, True)
226 elif data.has_key('text/plain'):
224 elif data.has_key('text/plain'):
227 self._append_plain_text(self.output_sep, True)
225 self._append_plain_text(self.output_sep, True)
228 self._append_html(self._make_out_prompt(prompt_number), True)
226 self._append_html(self._make_out_prompt(prompt_number), True)
229 text = data['text/plain']
227 text = data['text/plain']
230 # If the repr is multiline, make sure we start on a new line,
228 # If the repr is multiline, make sure we start on a new line,
231 # so that its lines are aligned.
229 # so that its lines are aligned.
232 if "\n" in text and not self.output_sep.endswith("\n"):
230 if "\n" in text and not self.output_sep.endswith("\n"):
233 self._append_plain_text('\n', True)
231 self._append_plain_text('\n', True)
234 self._append_plain_text(text + self.output_sep2, True)
232 self._append_plain_text(text + self.output_sep2, True)
235
233
236 def _handle_display_data(self, msg):
234 def _handle_display_data(self, msg):
237 """ The base handler for the ``display_data`` message.
235 """ The base handler for the ``display_data`` message.
238 """
236 """
239 self.log.debug("display: %s", msg.get('content', ''))
237 self.log.debug("display: %s", msg.get('content', ''))
240 # For now, we don't display data from other frontends, but we
238 # For now, we don't display data from other frontends, but we
241 # eventually will as this allows all frontends to monitor the display
239 # eventually will as this allows all frontends to monitor the display
242 # data. But we need to figure out how to handle this in the GUI.
240 # data. But we need to figure out how to handle this in the GUI.
243 if not self._hidden and self._is_from_this_session(msg):
241 if not self._hidden and self._is_from_this_session(msg):
244 source = msg['content']['source']
242 source = msg['content']['source']
245 data = msg['content']['data']
243 data = msg['content']['data']
246 metadata = msg['content']['metadata']
244 metadata = msg['content']['metadata']
247 # In the regular IPythonWidget, we simply print the plain text
245 # In the regular IPythonWidget, we simply print the plain text
248 # representation.
246 # representation.
249 if data.has_key('text/html'):
247 if data.has_key('text/html'):
250 html = data['text/html']
248 html = data['text/html']
251 self._append_html(html, True)
249 self._append_html(html, True)
252 elif data.has_key('text/plain'):
250 elif data.has_key('text/plain'):
253 text = data['text/plain']
251 text = data['text/plain']
254 self._append_plain_text(text, True)
252 self._append_plain_text(text, True)
255 # This newline seems to be needed for text and html output.
253 # This newline seems to be needed for text and html output.
256 self._append_plain_text(u'\n', True)
254 self._append_plain_text(u'\n', True)
257
255
258 def _started_channels(self):
256 def _started_channels(self):
259 """ Reimplemented to make a history request.
257 """ Reimplemented to make a history request.
260 """
258 """
261 super(IPythonWidget, self)._started_channels()
259 super(IPythonWidget, self)._started_channels()
262 self.kernel_manager.shell_channel.history(hist_access_type='tail',
260 self.kernel_manager.shell_channel.history(hist_access_type='tail',
263 n=1000)
261 n=1000)
264 #---------------------------------------------------------------------------
262 #---------------------------------------------------------------------------
265 # 'ConsoleWidget' public interface
263 # 'ConsoleWidget' public interface
266 #---------------------------------------------------------------------------
264 #---------------------------------------------------------------------------
267
265
268 def copy(self):
266 def copy(self):
269 """ Copy the currently selected text to the clipboard, removing prompts
267 """ Copy the currently selected text to the clipboard, removing prompts
270 if possible.
268 if possible.
271 """
269 """
272 text = self._control.textCursor().selection().toPlainText()
270 text = self._control.textCursor().selection().toPlainText()
273 if text:
271 if text:
274 lines = map(transform_ipy_prompt, text.splitlines())
272 lines = map(transform_ipy_prompt, text.splitlines())
275 text = '\n'.join(lines)
273 text = '\n'.join(lines)
276 QtGui.QApplication.clipboard().setText(text)
274 QtGui.QApplication.clipboard().setText(text)
277
275
278 #---------------------------------------------------------------------------
276 #---------------------------------------------------------------------------
279 # 'FrontendWidget' public interface
277 # 'FrontendWidget' public interface
280 #---------------------------------------------------------------------------
278 #---------------------------------------------------------------------------
281
279
282 def execute_file(self, path, hidden=False):
280 def execute_file(self, path, hidden=False):
283 """ Reimplemented to use the 'run' magic.
281 """ Reimplemented to use the 'run' magic.
284 """
282 """
285 # Use forward slashes on Windows to avoid escaping each separator.
283 # Use forward slashes on Windows to avoid escaping each separator.
286 if sys.platform == 'win32':
284 if sys.platform == 'win32':
287 path = os.path.normpath(path).replace('\\', '/')
285 path = os.path.normpath(path).replace('\\', '/')
288
286
289 # Perhaps we should not be using %run directly, but while we
287 # Perhaps we should not be using %run directly, but while we
290 # are, it is necessary to quote filenames containing spaces or quotes.
288 # are, it is necessary to quote filenames containing spaces or quotes.
291 # Escaping quotes in filename in %run seems tricky and inconsistent,
289 # Escaping quotes in filename in %run seems tricky and inconsistent,
292 # so not trying it at present.
290 # so not trying it at present.
293 if '"' in path:
291 if '"' in path:
294 if "'" in path:
292 if "'" in path:
295 raise ValueError("Can't run filename containing both single "
293 raise ValueError("Can't run filename containing both single "
296 "and double quotes: %s" % path)
294 "and double quotes: %s" % path)
297 path = "'%s'" % path
295 path = "'%s'" % path
298 elif ' ' in path or "'" in path:
296 elif ' ' in path or "'" in path:
299 path = '"%s"' % path
297 path = '"%s"' % path
300
298
301 self.execute('%%run %s' % path, hidden=hidden)
299 self.execute('%%run %s' % path, hidden=hidden)
302
300
303 #---------------------------------------------------------------------------
301 #---------------------------------------------------------------------------
304 # 'FrontendWidget' protected interface
302 # 'FrontendWidget' protected interface
305 #---------------------------------------------------------------------------
303 #---------------------------------------------------------------------------
306
304
307 def _complete(self):
305 def _complete(self):
308 """ Reimplemented to support IPython's improved completion machinery.
306 """ Reimplemented to support IPython's improved completion machinery.
309 """
307 """
310 # We let the kernel split the input line, so we *always* send an empty
308 # We let the kernel split the input line, so we *always* send an empty
311 # text field. Readline-based frontends do get a real text field which
309 # text field. Readline-based frontends do get a real text field which
312 # they can use.
310 # they can use.
313 text = ''
311 text = ''
314
312
315 # Send the completion request to the kernel
313 # Send the completion request to the kernel
316 msg_id = self.kernel_manager.shell_channel.complete(
314 msg_id = self.kernel_manager.shell_channel.complete(
317 text, # text
315 text, # text
318 self._get_input_buffer_cursor_line(), # line
316 self._get_input_buffer_cursor_line(), # line
319 self._get_input_buffer_cursor_column(), # cursor_pos
317 self._get_input_buffer_cursor_column(), # cursor_pos
320 self.input_buffer) # block
318 self.input_buffer) # block
321 pos = self._get_cursor().position()
319 pos = self._get_cursor().position()
322 info = self._CompletionRequest(msg_id, pos)
320 info = self._CompletionRequest(msg_id, pos)
323 self._request_info['complete'] = info
321 self._request_info['complete'] = info
324
322
325 def _process_execute_error(self, msg):
323 def _process_execute_error(self, msg):
326 """ Reimplemented for IPython-style traceback formatting.
324 """ Reimplemented for IPython-style traceback formatting.
327 """
325 """
328 content = msg['content']
326 content = msg['content']
329 traceback = '\n'.join(content['traceback']) + '\n'
327 traceback = '\n'.join(content['traceback']) + '\n'
330 if False:
328 if False:
331 # FIXME: For now, tracebacks come as plain text, so we can't use
329 # FIXME: For now, tracebacks come as plain text, so we can't use
332 # the html renderer yet. Once we refactor ultratb to produce
330 # the html renderer yet. Once we refactor ultratb to produce
333 # properly styled tracebacks, this branch should be the default
331 # properly styled tracebacks, this branch should be the default
334 traceback = traceback.replace(' ', '&nbsp;')
332 traceback = traceback.replace(' ', '&nbsp;')
335 traceback = traceback.replace('\n', '<br/>')
333 traceback = traceback.replace('\n', '<br/>')
336
334
337 ename = content['ename']
335 ename = content['ename']
338 ename_styled = '<span class="error">%s</span>' % ename
336 ename_styled = '<span class="error">%s</span>' % ename
339 traceback = traceback.replace(ename, ename_styled)
337 traceback = traceback.replace(ename, ename_styled)
340
338
341 self._append_html(traceback)
339 self._append_html(traceback)
342 else:
340 else:
343 # This is the fallback for now, using plain text with ansi escapes
341 # This is the fallback for now, using plain text with ansi escapes
344 self._append_plain_text(traceback)
342 self._append_plain_text(traceback)
345
343
346 def _process_execute_payload(self, item):
344 def _process_execute_payload(self, item):
347 """ Reimplemented to dispatch payloads to handler methods.
345 """ Reimplemented to dispatch payloads to handler methods.
348 """
346 """
349 handler = self._payload_handlers.get(item['source'])
347 handler = self._payload_handlers.get(item['source'])
350 if handler is None:
348 if handler is None:
351 # We have no handler for this type of payload, simply ignore it
349 # We have no handler for this type of payload, simply ignore it
352 return False
350 return False
353 else:
351 else:
354 handler(item)
352 handler(item)
355 return True
353 return True
356
354
357 def _show_interpreter_prompt(self, number=None):
355 def _show_interpreter_prompt(self, number=None):
358 """ Reimplemented for IPython-style prompts.
356 """ Reimplemented for IPython-style prompts.
359 """
357 """
360 # If a number was not specified, make a prompt number request.
358 # If a number was not specified, make a prompt number request.
361 if number is None:
359 if number is None:
362 msg_id = self.kernel_manager.shell_channel.execute('', silent=True)
360 msg_id = self.kernel_manager.shell_channel.execute('', silent=True)
363 info = self._ExecutionRequest(msg_id, 'prompt')
361 info = self._ExecutionRequest(msg_id, 'prompt')
364 self._request_info['execute'][msg_id] = info
362 self._request_info['execute'][msg_id] = info
365 return
363 return
366
364
367 # Show a new prompt and save information about it so that it can be
365 # Show a new prompt and save information about it so that it can be
368 # updated later if the prompt number turns out to be wrong.
366 # updated later if the prompt number turns out to be wrong.
369 self._prompt_sep = self.input_sep
367 self._prompt_sep = self.input_sep
370 self._show_prompt(self._make_in_prompt(number), html=True)
368 self._show_prompt(self._make_in_prompt(number), html=True)
371 block = self._control.document().lastBlock()
369 block = self._control.document().lastBlock()
372 length = len(self._prompt)
370 length = len(self._prompt)
373 self._previous_prompt_obj = self._PromptBlock(block, length, number)
371 self._previous_prompt_obj = self._PromptBlock(block, length, number)
374
372
375 # Update continuation prompt to reflect (possibly) new prompt length.
373 # Update continuation prompt to reflect (possibly) new prompt length.
376 self._set_continuation_prompt(
374 self._set_continuation_prompt(
377 self._make_continuation_prompt(self._prompt), html=True)
375 self._make_continuation_prompt(self._prompt), html=True)
378
376
379 def _show_interpreter_prompt_for_reply(self, msg):
377 def _show_interpreter_prompt_for_reply(self, msg):
380 """ Reimplemented for IPython-style prompts.
378 """ Reimplemented for IPython-style prompts.
381 """
379 """
382 # Update the old prompt number if necessary.
380 # Update the old prompt number if necessary.
383 content = msg['content']
381 content = msg['content']
384 # abort replies do not have any keys:
382 # abort replies do not have any keys:
385 if content['status'] == 'aborted':
383 if content['status'] == 'aborted':
386 if self._previous_prompt_obj:
384 if self._previous_prompt_obj:
387 previous_prompt_number = self._previous_prompt_obj.number
385 previous_prompt_number = self._previous_prompt_obj.number
388 else:
386 else:
389 previous_prompt_number = 0
387 previous_prompt_number = 0
390 else:
388 else:
391 previous_prompt_number = content['execution_count']
389 previous_prompt_number = content['execution_count']
392 if self._previous_prompt_obj and \
390 if self._previous_prompt_obj and \
393 self._previous_prompt_obj.number != previous_prompt_number:
391 self._previous_prompt_obj.number != previous_prompt_number:
394 block = self._previous_prompt_obj.block
392 block = self._previous_prompt_obj.block
395
393
396 # Make sure the prompt block has not been erased.
394 # Make sure the prompt block has not been erased.
397 if block.isValid() and block.text():
395 if block.isValid() and block.text():
398
396
399 # Remove the old prompt and insert a new prompt.
397 # Remove the old prompt and insert a new prompt.
400 cursor = QtGui.QTextCursor(block)
398 cursor = QtGui.QTextCursor(block)
401 cursor.movePosition(QtGui.QTextCursor.Right,
399 cursor.movePosition(QtGui.QTextCursor.Right,
402 QtGui.QTextCursor.KeepAnchor,
400 QtGui.QTextCursor.KeepAnchor,
403 self._previous_prompt_obj.length)
401 self._previous_prompt_obj.length)
404 prompt = self._make_in_prompt(previous_prompt_number)
402 prompt = self._make_in_prompt(previous_prompt_number)
405 self._prompt = self._insert_html_fetching_plain_text(
403 self._prompt = self._insert_html_fetching_plain_text(
406 cursor, prompt)
404 cursor, prompt)
407
405
408 # When the HTML is inserted, Qt blows away the syntax
406 # When the HTML is inserted, Qt blows away the syntax
409 # highlighting for the line, so we need to rehighlight it.
407 # highlighting for the line, so we need to rehighlight it.
410 self._highlighter.rehighlightBlock(cursor.block())
408 self._highlighter.rehighlightBlock(cursor.block())
411
409
412 self._previous_prompt_obj = None
410 self._previous_prompt_obj = None
413
411
414 # Show a new prompt with the kernel's estimated prompt number.
412 # Show a new prompt with the kernel's estimated prompt number.
415 self._show_interpreter_prompt(previous_prompt_number + 1)
413 self._show_interpreter_prompt(previous_prompt_number + 1)
416
414
417 #---------------------------------------------------------------------------
415 #---------------------------------------------------------------------------
418 # 'IPythonWidget' interface
416 # 'IPythonWidget' interface
419 #---------------------------------------------------------------------------
417 #---------------------------------------------------------------------------
420
418
421 def set_default_style(self, colors='lightbg'):
419 def set_default_style(self, colors='lightbg'):
422 """ Sets the widget style to the class defaults.
420 """ Sets the widget style to the class defaults.
423
421
424 Parameters:
422 Parameters:
425 -----------
423 -----------
426 colors : str, optional (default lightbg)
424 colors : str, optional (default lightbg)
427 Whether to use the default IPython light background or dark
425 Whether to use the default IPython light background or dark
428 background or B&W style.
426 background or B&W style.
429 """
427 """
430 colors = colors.lower()
428 colors = colors.lower()
431 if colors=='lightbg':
429 if colors=='lightbg':
432 self.style_sheet = styles.default_light_style_sheet
430 self.style_sheet = styles.default_light_style_sheet
433 self.syntax_style = styles.default_light_syntax_style
431 self.syntax_style = styles.default_light_syntax_style
434 elif colors=='linux':
432 elif colors=='linux':
435 self.style_sheet = styles.default_dark_style_sheet
433 self.style_sheet = styles.default_dark_style_sheet
436 self.syntax_style = styles.default_dark_syntax_style
434 self.syntax_style = styles.default_dark_syntax_style
437 elif colors=='nocolor':
435 elif colors=='nocolor':
438 self.style_sheet = styles.default_bw_style_sheet
436 self.style_sheet = styles.default_bw_style_sheet
439 self.syntax_style = styles.default_bw_syntax_style
437 self.syntax_style = styles.default_bw_syntax_style
440 else:
438 else:
441 raise KeyError("No such color scheme: %s"%colors)
439 raise KeyError("No such color scheme: %s"%colors)
442
440
443 #---------------------------------------------------------------------------
441 #---------------------------------------------------------------------------
444 # 'IPythonWidget' protected interface
442 # 'IPythonWidget' protected interface
445 #---------------------------------------------------------------------------
443 #---------------------------------------------------------------------------
446
444
447 def _edit(self, filename, line=None):
445 def _edit(self, filename, line=None):
448 """ Opens a Python script for editing.
446 """ Opens a Python script for editing.
449
447
450 Parameters:
448 Parameters:
451 -----------
449 -----------
452 filename : str
450 filename : str
453 A path to a local system file.
451 A path to a local system file.
454
452
455 line : int, optional
453 line : int, optional
456 A line of interest in the file.
454 A line of interest in the file.
457 """
455 """
458 if self.custom_edit:
456 if self.custom_edit:
459 self.custom_edit_requested.emit(filename, line)
457 self.custom_edit_requested.emit(filename, line)
460 elif not self.editor:
458 elif not self.editor:
461 self._append_plain_text('No default editor available.\n'
459 self._append_plain_text('No default editor available.\n'
462 'Specify a GUI text editor in the `IPythonWidget.editor` '
460 'Specify a GUI text editor in the `IPythonWidget.editor` '
463 'configurable to enable the %edit magic')
461 'configurable to enable the %edit magic')
464 else:
462 else:
465 try:
463 try:
466 filename = '"%s"' % filename
464 filename = '"%s"' % filename
467 if line and self.editor_line:
465 if line and self.editor_line:
468 command = self.editor_line.format(filename=filename,
466 command = self.editor_line.format(filename=filename,
469 line=line)
467 line=line)
470 else:
468 else:
471 try:
469 try:
472 command = self.editor.format()
470 command = self.editor.format()
473 except KeyError:
471 except KeyError:
474 command = self.editor.format(filename=filename)
472 command = self.editor.format(filename=filename)
475 else:
473 else:
476 command += ' ' + filename
474 command += ' ' + filename
477 except KeyError:
475 except KeyError:
478 self._append_plain_text('Invalid editor command.\n')
476 self._append_plain_text('Invalid editor command.\n')
479 else:
477 else:
480 try:
478 try:
481 Popen(command, shell=True)
479 Popen(command, shell=True)
482 except OSError:
480 except OSError:
483 msg = 'Opening editor with command "%s" failed.\n'
481 msg = 'Opening editor with command "%s" failed.\n'
484 self._append_plain_text(msg % command)
482 self._append_plain_text(msg % command)
485
483
486 def _make_in_prompt(self, number):
484 def _make_in_prompt(self, number):
487 """ Given a prompt number, returns an HTML In prompt.
485 """ Given a prompt number, returns an HTML In prompt.
488 """
486 """
489 try:
487 try:
490 body = self.in_prompt % number
488 body = self.in_prompt % number
491 except TypeError:
489 except TypeError:
492 # allow in_prompt to leave out number, e.g. '>>> '
490 # allow in_prompt to leave out number, e.g. '>>> '
493 body = self.in_prompt
491 body = self.in_prompt
494 return '<span class="in-prompt">%s</span>' % body
492 return '<span class="in-prompt">%s</span>' % body
495
493
496 def _make_continuation_prompt(self, prompt):
494 def _make_continuation_prompt(self, prompt):
497 """ Given a plain text version of an In prompt, returns an HTML
495 """ Given a plain text version of an In prompt, returns an HTML
498 continuation prompt.
496 continuation prompt.
499 """
497 """
500 end_chars = '...: '
498 end_chars = '...: '
501 space_count = len(prompt.lstrip('\n')) - len(end_chars)
499 space_count = len(prompt.lstrip('\n')) - len(end_chars)
502 body = '&nbsp;' * space_count + end_chars
500 body = '&nbsp;' * space_count + end_chars
503 return '<span class="in-prompt">%s</span>' % body
501 return '<span class="in-prompt">%s</span>' % body
504
502
505 def _make_out_prompt(self, number):
503 def _make_out_prompt(self, number):
506 """ Given a prompt number, returns an HTML Out prompt.
504 """ Given a prompt number, returns an HTML Out prompt.
507 """
505 """
508 body = self.out_prompt % number
506 body = self.out_prompt % number
509 return '<span class="out-prompt">%s</span>' % body
507 return '<span class="out-prompt">%s</span>' % body
510
508
511 #------ Payload handlers --------------------------------------------------
509 #------ Payload handlers --------------------------------------------------
512
510
513 # Payload handlers with a generic interface: each takes the opaque payload
511 # Payload handlers with a generic interface: each takes the opaque payload
514 # dict, unpacks it and calls the underlying functions with the necessary
512 # dict, unpacks it and calls the underlying functions with the necessary
515 # arguments.
513 # arguments.
516
514
517 def _handle_payload_edit(self, item):
515 def _handle_payload_edit(self, item):
518 self._edit(item['filename'], item['line_number'])
516 self._edit(item['filename'], item['line_number'])
519
517
520 def _handle_payload_exit(self, item):
518 def _handle_payload_exit(self, item):
521 self._keep_kernel_on_exit = item['keepkernel']
519 self._keep_kernel_on_exit = item['keepkernel']
522 self.exit_requested.emit(self)
520 self.exit_requested.emit(self)
523
521
524 def _handle_payload_next_input(self, item):
522 def _handle_payload_next_input(self, item):
525 self.input_buffer = dedent(item['text'].rstrip())
523 self.input_buffer = dedent(item['text'].rstrip())
526
524
527 def _handle_payload_page(self, item):
525 def _handle_payload_page(self, item):
528 # Since the plain text widget supports only a very small subset of HTML
526 # Since the plain text widget supports only a very small subset of HTML
529 # and we have no control over the HTML source, we only page HTML
527 # and we have no control over the HTML source, we only page HTML
530 # payloads in the rich text widget.
528 # payloads in the rich text widget.
531 if item['html'] and self.kind == 'rich':
529 if item['html'] and self.kind == 'rich':
532 self._page(item['html'], html=True)
530 self._page(item['html'], html=True)
533 else:
531 else:
534 self._page(item['text'], html=False)
532 self._page(item['text'], html=False)
535
533
536 #------ Trait change handlers --------------------------------------------
534 #------ Trait change handlers --------------------------------------------
537
535
538 def _style_sheet_changed(self):
536 def _style_sheet_changed(self):
539 """ Set the style sheets of the underlying widgets.
537 """ Set the style sheets of the underlying widgets.
540 """
538 """
541 self.setStyleSheet(self.style_sheet)
539 self.setStyleSheet(self.style_sheet)
542 self._control.document().setDefaultStyleSheet(self.style_sheet)
540 self._control.document().setDefaultStyleSheet(self.style_sheet)
543 if self._page_control:
541 if self._page_control:
544 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
542 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
545
543
546 bg_color = self._control.palette().window().color()
544 bg_color = self._control.palette().window().color()
547 self._ansi_processor.set_background_color(bg_color)
545 self._ansi_processor.set_background_color(bg_color)
548
546
549
547
550 def _syntax_style_changed(self):
548 def _syntax_style_changed(self):
551 """ Set the style for the syntax highlighter.
549 """ Set the style for the syntax highlighter.
552 """
550 """
553 if self._highlighter is None:
551 if self._highlighter is None:
554 # ignore premature calls
552 # ignore premature calls
555 return
553 return
556 if self.syntax_style:
554 if self.syntax_style:
557 self._highlighter.set_style(self.syntax_style)
555 self._highlighter.set_style(self.syntax_style)
558 else:
556 else:
559 self._highlighter.set_style_sheet(self.style_sheet)
557 self._highlighter.set_style_sheet(self.style_sheet)
560
558
561 #------ Trait default initializers -----------------------------------------
559 #------ Trait default initializers -----------------------------------------
562
560
563 def _banner_default(self):
561 def _banner_default(self):
564 from IPython.core.usage import default_gui_banner
562 from IPython.core.usage import default_gui_banner
565 return default_gui_banner
563 return default_gui_banner
General Comments 0
You need to be logged in to leave comments. Login now