##// END OF EJS Templates
class-attribute sets default _local_kernel in FrontendWidget
MinRK -
Show More
@@ -1,590 +1,590 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
7
8 # System library imports
8 # System library imports
9 from pygments.lexers import PythonLexer
9 from pygments.lexers import PythonLexer
10 from PyQt4 import QtCore, QtGui
10 from PyQt4 import QtCore, QtGui
11
11
12 # Local imports
12 # Local imports
13 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
13 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
14 from IPython.core.oinspect import call_tip
14 from IPython.core.oinspect import call_tip
15 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
15 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
16 from IPython.utils.traitlets import Bool
16 from IPython.utils.traitlets import Bool
17 from bracket_matcher import BracketMatcher
17 from bracket_matcher import BracketMatcher
18 from call_tip_widget import CallTipWidget
18 from call_tip_widget import CallTipWidget
19 from completion_lexer import CompletionLexer
19 from completion_lexer import CompletionLexer
20 from history_console_widget import HistoryConsoleWidget
20 from history_console_widget import HistoryConsoleWidget
21 from pygments_highlighter import PygmentsHighlighter
21 from pygments_highlighter import PygmentsHighlighter
22
22
23
23
24 class FrontendHighlighter(PygmentsHighlighter):
24 class FrontendHighlighter(PygmentsHighlighter):
25 """ A PygmentsHighlighter that can be turned on and off and that ignores
25 """ A PygmentsHighlighter that can be turned on and off and that ignores
26 prompts.
26 prompts.
27 """
27 """
28
28
29 def __init__(self, frontend):
29 def __init__(self, frontend):
30 super(FrontendHighlighter, self).__init__(frontend._control.document())
30 super(FrontendHighlighter, self).__init__(frontend._control.document())
31 self._current_offset = 0
31 self._current_offset = 0
32 self._frontend = frontend
32 self._frontend = frontend
33 self.highlighting_on = False
33 self.highlighting_on = False
34
34
35 def highlightBlock(self, qstring):
35 def highlightBlock(self, qstring):
36 """ Highlight a block of text. Reimplemented to highlight selectively.
36 """ Highlight a block of text. Reimplemented to highlight selectively.
37 """
37 """
38 if not self.highlighting_on:
38 if not self.highlighting_on:
39 return
39 return
40
40
41 # The input to this function is unicode string that may contain
41 # The input to this function is unicode string that may contain
42 # paragraph break characters, non-breaking spaces, etc. Here we acquire
42 # paragraph break characters, non-breaking spaces, etc. Here we acquire
43 # the string as plain text so we can compare it.
43 # the string as plain text so we can compare it.
44 current_block = self.currentBlock()
44 current_block = self.currentBlock()
45 string = self._frontend._get_block_plain_text(current_block)
45 string = self._frontend._get_block_plain_text(current_block)
46
46
47 # Decide whether to check for the regular or continuation prompt.
47 # Decide whether to check for the regular or continuation prompt.
48 if current_block.contains(self._frontend._prompt_pos):
48 if current_block.contains(self._frontend._prompt_pos):
49 prompt = self._frontend._prompt
49 prompt = self._frontend._prompt
50 else:
50 else:
51 prompt = self._frontend._continuation_prompt
51 prompt = self._frontend._continuation_prompt
52
52
53 # Don't highlight the part of the string that contains the prompt.
53 # Don't highlight the part of the string that contains the prompt.
54 if string.startswith(prompt):
54 if string.startswith(prompt):
55 self._current_offset = len(prompt)
55 self._current_offset = len(prompt)
56 qstring.remove(0, len(prompt))
56 qstring.remove(0, len(prompt))
57 else:
57 else:
58 self._current_offset = 0
58 self._current_offset = 0
59
59
60 PygmentsHighlighter.highlightBlock(self, qstring)
60 PygmentsHighlighter.highlightBlock(self, qstring)
61
61
62 def rehighlightBlock(self, block):
62 def rehighlightBlock(self, block):
63 """ Reimplemented to temporarily enable highlighting if disabled.
63 """ Reimplemented to temporarily enable highlighting if disabled.
64 """
64 """
65 old = self.highlighting_on
65 old = self.highlighting_on
66 self.highlighting_on = True
66 self.highlighting_on = True
67 super(FrontendHighlighter, self).rehighlightBlock(block)
67 super(FrontendHighlighter, self).rehighlightBlock(block)
68 self.highlighting_on = old
68 self.highlighting_on = old
69
69
70 def setFormat(self, start, count, format):
70 def setFormat(self, start, count, format):
71 """ Reimplemented to highlight selectively.
71 """ Reimplemented to highlight selectively.
72 """
72 """
73 start += self._current_offset
73 start += self._current_offset
74 PygmentsHighlighter.setFormat(self, start, count, format)
74 PygmentsHighlighter.setFormat(self, start, count, format)
75
75
76
76
77 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
77 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
78 """ A Qt frontend for a generic Python kernel.
78 """ A Qt frontend for a generic Python kernel.
79 """
79 """
80
80
81 # An option and corresponding signal for overriding the default kernel
81 # An option and corresponding signal for overriding the default kernel
82 # interrupt behavior.
82 # interrupt behavior.
83 custom_interrupt = Bool(False)
83 custom_interrupt = Bool(False)
84 custom_interrupt_requested = QtCore.pyqtSignal()
84 custom_interrupt_requested = QtCore.pyqtSignal()
85
85
86 # An option and corresponding signals for overriding the default kernel
86 # An option and corresponding signals for overriding the default kernel
87 # restart behavior.
87 # restart behavior.
88 custom_restart = Bool(False)
88 custom_restart = Bool(False)
89 custom_restart_kernel_died = QtCore.pyqtSignal(float)
89 custom_restart_kernel_died = QtCore.pyqtSignal(float)
90 custom_restart_requested = QtCore.pyqtSignal()
90 custom_restart_requested = QtCore.pyqtSignal()
91
91
92 # Emitted when an 'execute_reply' has been received from the kernel and
92 # Emitted when an 'execute_reply' has been received from the kernel and
93 # processed by the FrontendWidget.
93 # processed by the FrontendWidget.
94 executed = QtCore.pyqtSignal(object)
94 executed = QtCore.pyqtSignal(object)
95
95
96 # Emitted when an exit request has been received from the kernel.
96 # Emitted when an exit request has been received from the kernel.
97 exit_requested = QtCore.pyqtSignal()
97 exit_requested = QtCore.pyqtSignal()
98
98
99 # Protected class variables.
99 # Protected class variables.
100 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
100 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
101 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
101 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
102 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
102 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
103 _input_splitter_class = InputSplitter
103 _input_splitter_class = InputSplitter
104 _local_kernel = False
104 _local_kernel = False
105
105
106 #---------------------------------------------------------------------------
106 #---------------------------------------------------------------------------
107 # 'object' interface
107 # 'object' interface
108 #---------------------------------------------------------------------------
108 #---------------------------------------------------------------------------
109
109
110 def __init__(self, *args, **kw):
110 def __init__(self, *args, **kw):
111 super(FrontendWidget, self).__init__(*args, **kw)
111 super(FrontendWidget, self).__init__(*args, **kw)
112
112
113 # FrontendWidget protected variables.
113 # FrontendWidget protected variables.
114 self._bracket_matcher = BracketMatcher(self._control)
114 self._bracket_matcher = BracketMatcher(self._control)
115 self._call_tip_widget = CallTipWidget(self._control)
115 self._call_tip_widget = CallTipWidget(self._control)
116 self._completion_lexer = CompletionLexer(PythonLexer())
116 self._completion_lexer = CompletionLexer(PythonLexer())
117 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
117 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
118 self._hidden = False
118 self._hidden = False
119 self._highlighter = FrontendHighlighter(self)
119 self._highlighter = FrontendHighlighter(self)
120 self._input_splitter = self._input_splitter_class(input_mode='cell')
120 self._input_splitter = self._input_splitter_class(input_mode='cell')
121 self._kernel_manager = None
121 self._kernel_manager = None
122 self._request_info = {}
122 self._request_info = {}
123
123
124 # Configure the ConsoleWidget.
124 # Configure the ConsoleWidget.
125 self.tab_width = 4
125 self.tab_width = 4
126 self._set_continuation_prompt('... ')
126 self._set_continuation_prompt('... ')
127
127
128 # Configure the CallTipWidget.
128 # Configure the CallTipWidget.
129 self._call_tip_widget.setFont(self.font)
129 self._call_tip_widget.setFont(self.font)
130 self.font_changed.connect(self._call_tip_widget.setFont)
130 self.font_changed.connect(self._call_tip_widget.setFont)
131
131
132 # Configure actions.
132 # Configure actions.
133 action = self._copy_raw_action
133 action = self._copy_raw_action
134 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
134 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
135 action.setEnabled(False)
135 action.setEnabled(False)
136 action.setShortcut(QtGui.QKeySequence(key))
136 action.setShortcut(QtGui.QKeySequence(key))
137 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
137 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
138 action.triggered.connect(self.copy_raw)
138 action.triggered.connect(self.copy_raw)
139 self.copy_available.connect(action.setEnabled)
139 self.copy_available.connect(action.setEnabled)
140 self.addAction(action)
140 self.addAction(action)
141
141
142 # Connect signal handlers.
142 # Connect signal handlers.
143 document = self._control.document()
143 document = self._control.document()
144 document.contentsChange.connect(self._document_contents_change)
144 document.contentsChange.connect(self._document_contents_change)
145
145
146 # set flag for whether we are connected via localhost
146 # set flag for whether we are connected via localhost
147 self._local_kernel = kw.get('local_kernel', False)
147 self._local_kernel = kw.get('local_kernel', FrontendWidget._local_kernel)
148
148
149 #---------------------------------------------------------------------------
149 #---------------------------------------------------------------------------
150 # 'ConsoleWidget' public interface
150 # 'ConsoleWidget' public interface
151 #---------------------------------------------------------------------------
151 #---------------------------------------------------------------------------
152
152
153 def copy(self):
153 def copy(self):
154 """ Copy the currently selected text to the clipboard, removing prompts.
154 """ Copy the currently selected text to the clipboard, removing prompts.
155 """
155 """
156 text = unicode(self._control.textCursor().selection().toPlainText())
156 text = unicode(self._control.textCursor().selection().toPlainText())
157 if text:
157 if text:
158 lines = map(transform_classic_prompt, text.splitlines())
158 lines = map(transform_classic_prompt, text.splitlines())
159 text = '\n'.join(lines)
159 text = '\n'.join(lines)
160 QtGui.QApplication.clipboard().setText(text)
160 QtGui.QApplication.clipboard().setText(text)
161
161
162 #---------------------------------------------------------------------------
162 #---------------------------------------------------------------------------
163 # 'ConsoleWidget' abstract interface
163 # 'ConsoleWidget' abstract interface
164 #---------------------------------------------------------------------------
164 #---------------------------------------------------------------------------
165
165
166 def _is_complete(self, source, interactive):
166 def _is_complete(self, source, interactive):
167 """ Returns whether 'source' can be completely processed and a new
167 """ Returns whether 'source' can be completely processed and a new
168 prompt created. When triggered by an Enter/Return key press,
168 prompt created. When triggered by an Enter/Return key press,
169 'interactive' is True; otherwise, it is False.
169 'interactive' is True; otherwise, it is False.
170 """
170 """
171 complete = self._input_splitter.push(source)
171 complete = self._input_splitter.push(source)
172 if interactive:
172 if interactive:
173 complete = not self._input_splitter.push_accepts_more()
173 complete = not self._input_splitter.push_accepts_more()
174 return complete
174 return complete
175
175
176 def _execute(self, source, hidden):
176 def _execute(self, source, hidden):
177 """ Execute 'source'. If 'hidden', do not show any output.
177 """ Execute 'source'. If 'hidden', do not show any output.
178
178
179 See parent class :meth:`execute` docstring for full details.
179 See parent class :meth:`execute` docstring for full details.
180 """
180 """
181 msg_id = self.kernel_manager.xreq_channel.execute(source, hidden)
181 msg_id = self.kernel_manager.xreq_channel.execute(source, hidden)
182 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
182 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
183 self._hidden = hidden
183 self._hidden = hidden
184
184
185 def _prompt_started_hook(self):
185 def _prompt_started_hook(self):
186 """ Called immediately after a new prompt is displayed.
186 """ Called immediately after a new prompt is displayed.
187 """
187 """
188 if not self._reading:
188 if not self._reading:
189 self._highlighter.highlighting_on = True
189 self._highlighter.highlighting_on = True
190
190
191 def _prompt_finished_hook(self):
191 def _prompt_finished_hook(self):
192 """ Called immediately after a prompt is finished, i.e. when some input
192 """ Called immediately after a prompt is finished, i.e. when some input
193 will be processed and a new prompt displayed.
193 will be processed and a new prompt displayed.
194 """
194 """
195 if not self._reading:
195 if not self._reading:
196 self._highlighter.highlighting_on = False
196 self._highlighter.highlighting_on = False
197
197
198 def _tab_pressed(self):
198 def _tab_pressed(self):
199 """ Called when the tab key is pressed. Returns whether to continue
199 """ Called when the tab key is pressed. Returns whether to continue
200 processing the event.
200 processing the event.
201 """
201 """
202 # Perform tab completion if:
202 # Perform tab completion if:
203 # 1) The cursor is in the input buffer.
203 # 1) The cursor is in the input buffer.
204 # 2) There is a non-whitespace character before the cursor.
204 # 2) There is a non-whitespace character before the cursor.
205 text = self._get_input_buffer_cursor_line()
205 text = self._get_input_buffer_cursor_line()
206 if text is None:
206 if text is None:
207 return False
207 return False
208 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
208 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
209 if complete:
209 if complete:
210 self._complete()
210 self._complete()
211 return not complete
211 return not complete
212
212
213 #---------------------------------------------------------------------------
213 #---------------------------------------------------------------------------
214 # 'ConsoleWidget' protected interface
214 # 'ConsoleWidget' protected interface
215 #---------------------------------------------------------------------------
215 #---------------------------------------------------------------------------
216
216
217 def _context_menu_make(self, pos):
217 def _context_menu_make(self, pos):
218 """ Reimplemented to add an action for raw copy.
218 """ Reimplemented to add an action for raw copy.
219 """
219 """
220 menu = super(FrontendWidget, self)._context_menu_make(pos)
220 menu = super(FrontendWidget, self)._context_menu_make(pos)
221 for before_action in menu.actions():
221 for before_action in menu.actions():
222 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
222 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
223 QtGui.QKeySequence.ExactMatch:
223 QtGui.QKeySequence.ExactMatch:
224 menu.insertAction(before_action, self._copy_raw_action)
224 menu.insertAction(before_action, self._copy_raw_action)
225 break
225 break
226 return menu
226 return menu
227
227
228 def _event_filter_console_keypress(self, event):
228 def _event_filter_console_keypress(self, event):
229 """ Reimplemented for execution interruption and smart backspace.
229 """ Reimplemented for execution interruption and smart backspace.
230 """
230 """
231 key = event.key()
231 key = event.key()
232 if self._control_key_down(event.modifiers(), include_command=False):
232 if self._control_key_down(event.modifiers(), include_command=False):
233
233
234 if key == QtCore.Qt.Key_C and self._executing:
234 if key == QtCore.Qt.Key_C and self._executing:
235 self.interrupt_kernel()
235 self.interrupt_kernel()
236 return True
236 return True
237
237
238 elif key == QtCore.Qt.Key_Period:
238 elif key == QtCore.Qt.Key_Period:
239 message = 'Are you sure you want to restart the kernel?'
239 message = 'Are you sure you want to restart the kernel?'
240 self.restart_kernel(message, now=False)
240 self.restart_kernel(message, now=False)
241 return True
241 return True
242
242
243 elif not event.modifiers() & QtCore.Qt.AltModifier:
243 elif not event.modifiers() & QtCore.Qt.AltModifier:
244
244
245 # Smart backspace: remove four characters in one backspace if:
245 # Smart backspace: remove four characters in one backspace if:
246 # 1) everything left of the cursor is whitespace
246 # 1) everything left of the cursor is whitespace
247 # 2) the four characters immediately left of the cursor are spaces
247 # 2) the four characters immediately left of the cursor are spaces
248 if key == QtCore.Qt.Key_Backspace:
248 if key == QtCore.Qt.Key_Backspace:
249 col = self._get_input_buffer_cursor_column()
249 col = self._get_input_buffer_cursor_column()
250 cursor = self._control.textCursor()
250 cursor = self._control.textCursor()
251 if col > 3 and not cursor.hasSelection():
251 if col > 3 and not cursor.hasSelection():
252 text = self._get_input_buffer_cursor_line()[:col]
252 text = self._get_input_buffer_cursor_line()[:col]
253 if text.endswith(' ') and not text.strip():
253 if text.endswith(' ') and not text.strip():
254 cursor.movePosition(QtGui.QTextCursor.Left,
254 cursor.movePosition(QtGui.QTextCursor.Left,
255 QtGui.QTextCursor.KeepAnchor, 4)
255 QtGui.QTextCursor.KeepAnchor, 4)
256 cursor.removeSelectedText()
256 cursor.removeSelectedText()
257 return True
257 return True
258
258
259 return super(FrontendWidget, self)._event_filter_console_keypress(event)
259 return super(FrontendWidget, self)._event_filter_console_keypress(event)
260
260
261 def _insert_continuation_prompt(self, cursor):
261 def _insert_continuation_prompt(self, cursor):
262 """ Reimplemented for auto-indentation.
262 """ Reimplemented for auto-indentation.
263 """
263 """
264 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
264 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
265 cursor.insertText(' ' * self._input_splitter.indent_spaces)
265 cursor.insertText(' ' * self._input_splitter.indent_spaces)
266
266
267 #---------------------------------------------------------------------------
267 #---------------------------------------------------------------------------
268 # 'BaseFrontendMixin' abstract interface
268 # 'BaseFrontendMixin' abstract interface
269 #---------------------------------------------------------------------------
269 #---------------------------------------------------------------------------
270
270
271 def _handle_complete_reply(self, rep):
271 def _handle_complete_reply(self, rep):
272 """ Handle replies for tab completion.
272 """ Handle replies for tab completion.
273 """
273 """
274 cursor = self._get_cursor()
274 cursor = self._get_cursor()
275 info = self._request_info.get('complete')
275 info = self._request_info.get('complete')
276 if info and info.id == rep['parent_header']['msg_id'] and \
276 if info and info.id == rep['parent_header']['msg_id'] and \
277 info.pos == cursor.position():
277 info.pos == cursor.position():
278 text = '.'.join(self._get_context())
278 text = '.'.join(self._get_context())
279 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
279 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
280 self._complete_with_items(cursor, rep['content']['matches'])
280 self._complete_with_items(cursor, rep['content']['matches'])
281
281
282 def _handle_execute_reply(self, msg):
282 def _handle_execute_reply(self, msg):
283 """ Handles replies for code execution.
283 """ Handles replies for code execution.
284 """
284 """
285 info = self._request_info.get('execute')
285 info = self._request_info.get('execute')
286 if info and info.id == msg['parent_header']['msg_id'] and \
286 if info and info.id == msg['parent_header']['msg_id'] and \
287 info.kind == 'user' and not self._hidden:
287 info.kind == 'user' and not self._hidden:
288 # Make sure that all output from the SUB channel has been processed
288 # Make sure that all output from the SUB channel has been processed
289 # before writing a new prompt.
289 # before writing a new prompt.
290 self.kernel_manager.sub_channel.flush()
290 self.kernel_manager.sub_channel.flush()
291
291
292 # Reset the ANSI style information to prevent bad text in stdout
292 # Reset the ANSI style information to prevent bad text in stdout
293 # from messing up our colors. We're not a true terminal so we're
293 # from messing up our colors. We're not a true terminal so we're
294 # allowed to do this.
294 # allowed to do this.
295 if self.ansi_codes:
295 if self.ansi_codes:
296 self._ansi_processor.reset_sgr()
296 self._ansi_processor.reset_sgr()
297
297
298 content = msg['content']
298 content = msg['content']
299 status = content['status']
299 status = content['status']
300 if status == 'ok':
300 if status == 'ok':
301 self._process_execute_ok(msg)
301 self._process_execute_ok(msg)
302 elif status == 'error':
302 elif status == 'error':
303 self._process_execute_error(msg)
303 self._process_execute_error(msg)
304 elif status == 'abort':
304 elif status == 'abort':
305 self._process_execute_abort(msg)
305 self._process_execute_abort(msg)
306
306
307 self._show_interpreter_prompt_for_reply(msg)
307 self._show_interpreter_prompt_for_reply(msg)
308 self.executed.emit(msg)
308 self.executed.emit(msg)
309
309
310 def _handle_input_request(self, msg):
310 def _handle_input_request(self, msg):
311 """ Handle requests for raw_input.
311 """ Handle requests for raw_input.
312 """
312 """
313 if self._hidden:
313 if self._hidden:
314 raise RuntimeError('Request for raw input during hidden execution.')
314 raise RuntimeError('Request for raw input during hidden execution.')
315
315
316 # Make sure that all output from the SUB channel has been processed
316 # Make sure that all output from the SUB channel has been processed
317 # before entering readline mode.
317 # before entering readline mode.
318 self.kernel_manager.sub_channel.flush()
318 self.kernel_manager.sub_channel.flush()
319
319
320 def callback(line):
320 def callback(line):
321 self.kernel_manager.rep_channel.input(line)
321 self.kernel_manager.rep_channel.input(line)
322 self._readline(msg['content']['prompt'], callback=callback)
322 self._readline(msg['content']['prompt'], callback=callback)
323
323
324 def _handle_kernel_died(self, since_last_heartbeat):
324 def _handle_kernel_died(self, since_last_heartbeat):
325 """ Handle the kernel's death by asking if the user wants to restart.
325 """ Handle the kernel's death by asking if the user wants to restart.
326 """
326 """
327 if self.custom_restart:
327 if self.custom_restart:
328 self.custom_restart_kernel_died.emit(since_last_heartbeat)
328 self.custom_restart_kernel_died.emit(since_last_heartbeat)
329 else:
329 else:
330 message = 'The kernel heartbeat has been inactive for %.2f ' \
330 message = 'The kernel heartbeat has been inactive for %.2f ' \
331 'seconds. Do you want to restart the kernel? You may ' \
331 'seconds. Do you want to restart the kernel? You may ' \
332 'first want to check the network connection.' % \
332 'first want to check the network connection.' % \
333 since_last_heartbeat
333 since_last_heartbeat
334 self.restart_kernel(message, now=True)
334 self.restart_kernel(message, now=True)
335
335
336 def _handle_object_info_reply(self, rep):
336 def _handle_object_info_reply(self, rep):
337 """ Handle replies for call tips.
337 """ Handle replies for call tips.
338 """
338 """
339 cursor = self._get_cursor()
339 cursor = self._get_cursor()
340 info = self._request_info.get('call_tip')
340 info = self._request_info.get('call_tip')
341 if info and info.id == rep['parent_header']['msg_id'] and \
341 if info and info.id == rep['parent_header']['msg_id'] and \
342 info.pos == cursor.position():
342 info.pos == cursor.position():
343 # Get the information for a call tip. For now we format the call
343 # Get the information for a call tip. For now we format the call
344 # line as string, later we can pass False to format_call and
344 # line as string, later we can pass False to format_call and
345 # syntax-highlight it ourselves for nicer formatting in the
345 # syntax-highlight it ourselves for nicer formatting in the
346 # calltip.
346 # calltip.
347 call_info, doc = call_tip(rep['content'], format_call=True)
347 call_info, doc = call_tip(rep['content'], format_call=True)
348 if call_info or doc:
348 if call_info or doc:
349 self._call_tip_widget.show_call_info(call_info, doc)
349 self._call_tip_widget.show_call_info(call_info, doc)
350
350
351 def _handle_pyout(self, msg):
351 def _handle_pyout(self, msg):
352 """ Handle display hook output.
352 """ Handle display hook output.
353 """
353 """
354 if not self._hidden and self._is_from_this_session(msg):
354 if not self._hidden and self._is_from_this_session(msg):
355 self._append_plain_text(msg['content']['data'] + '\n')
355 self._append_plain_text(msg['content']['data'] + '\n')
356
356
357 def _handle_stream(self, msg):
357 def _handle_stream(self, msg):
358 """ Handle stdout, stderr, and stdin.
358 """ Handle stdout, stderr, and stdin.
359 """
359 """
360 if not self._hidden and self._is_from_this_session(msg):
360 if not self._hidden and self._is_from_this_session(msg):
361 # Most consoles treat tabs as being 8 space characters. Convert tabs
361 # Most consoles treat tabs as being 8 space characters. Convert tabs
362 # to spaces so that output looks as expected regardless of this
362 # to spaces so that output looks as expected regardless of this
363 # widget's tab width.
363 # widget's tab width.
364 text = msg['content']['data'].expandtabs(8)
364 text = msg['content']['data'].expandtabs(8)
365
365
366 self._append_plain_text(text)
366 self._append_plain_text(text)
367 self._control.moveCursor(QtGui.QTextCursor.End)
367 self._control.moveCursor(QtGui.QTextCursor.End)
368
368
369 def _handle_shutdown_reply(self, msg):
369 def _handle_shutdown_reply(self, msg):
370 """ Handle shutdown signal, only if from other console.
370 """ Handle shutdown signal, only if from other console.
371 """
371 """
372 if not self._hidden and not self._is_from_this_session(msg):
372 if not self._hidden and not self._is_from_this_session(msg):
373 if self._local_kernel:
373 if self._local_kernel:
374 if not msg['content']['restart']:
374 if not msg['content']['restart']:
375 sys.exit(0)
375 sys.exit(0)
376 else:
376 else:
377 # we just got notified of a restart!
377 # we just got notified of a restart!
378 time.sleep(0.25) # wait 1/4 sec to reset
378 time.sleep(0.25) # wait 1/4 sec to reset
379 # lest the request for a new prompt
379 # lest the request for a new prompt
380 # goes to the old kernel
380 # goes to the old kernel
381 self.reset()
381 self.reset()
382 else: # remote kernel, prompt on Kernel shutdown/reset
382 else: # remote kernel, prompt on Kernel shutdown/reset
383 title = self.window().windowTitle()
383 title = self.window().windowTitle()
384 if not msg['content']['restart']:
384 if not msg['content']['restart']:
385 reply = QtGui.QMessageBox.question(self, title,
385 reply = QtGui.QMessageBox.question(self, title,
386 "Kernel has been shutdown permanently. Close the Console?",
386 "Kernel has been shutdown permanently. Close the Console?",
387 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
387 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
388 if reply == QtGui.QMessageBox.Yes:
388 if reply == QtGui.QMessageBox.Yes:
389 sys.exit(0)
389 sys.exit(0)
390 else:
390 else:
391 reply = QtGui.QMessageBox.question(self, title,
391 reply = QtGui.QMessageBox.question(self, title,
392 "Kernel has been reset. Clear the Console?",
392 "Kernel has been reset. Clear the Console?",
393 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
393 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
394 if reply == QtGui.QMessageBox.Yes:
394 if reply == QtGui.QMessageBox.Yes:
395 time.sleep(0.25) # wait 1/4 sec to reset
395 time.sleep(0.25) # wait 1/4 sec to reset
396 # lest the request for a new prompt
396 # lest the request for a new prompt
397 # goes to the old kernel
397 # goes to the old kernel
398 self.reset()
398 self.reset()
399
399
400 def _started_channels(self):
400 def _started_channels(self):
401 """ Called when the KernelManager channels have started listening or
401 """ Called when the KernelManager channels have started listening or
402 when the frontend is assigned an already listening KernelManager.
402 when the frontend is assigned an already listening KernelManager.
403 """
403 """
404 self.reset()
404 self.reset()
405
405
406 #---------------------------------------------------------------------------
406 #---------------------------------------------------------------------------
407 # 'FrontendWidget' public interface
407 # 'FrontendWidget' public interface
408 #---------------------------------------------------------------------------
408 #---------------------------------------------------------------------------
409
409
410 def copy_raw(self):
410 def copy_raw(self):
411 """ Copy the currently selected text to the clipboard without attempting
411 """ Copy the currently selected text to the clipboard without attempting
412 to remove prompts or otherwise alter the text.
412 to remove prompts or otherwise alter the text.
413 """
413 """
414 self._control.copy()
414 self._control.copy()
415
415
416 def execute_file(self, path, hidden=False):
416 def execute_file(self, path, hidden=False):
417 """ Attempts to execute file with 'path'. If 'hidden', no output is
417 """ Attempts to execute file with 'path'. If 'hidden', no output is
418 shown.
418 shown.
419 """
419 """
420 self.execute('execfile("%s")' % path, hidden=hidden)
420 self.execute('execfile("%s")' % path, hidden=hidden)
421
421
422 def interrupt_kernel(self):
422 def interrupt_kernel(self):
423 """ Attempts to interrupt the running kernel.
423 """ Attempts to interrupt the running kernel.
424 """
424 """
425 if self.custom_interrupt:
425 if self.custom_interrupt:
426 self.custom_interrupt_requested.emit()
426 self.custom_interrupt_requested.emit()
427 elif self.kernel_manager.has_kernel:
427 elif self.kernel_manager.has_kernel:
428 self.kernel_manager.interrupt_kernel()
428 self.kernel_manager.interrupt_kernel()
429 else:
429 else:
430 self._append_plain_text('Kernel process is either remote or '
430 self._append_plain_text('Kernel process is either remote or '
431 'unspecified. Cannot interrupt.\n')
431 'unspecified. Cannot interrupt.\n')
432
432
433 def reset(self):
433 def reset(self):
434 """ Resets the widget to its initial state. Similar to ``clear``, but
434 """ Resets the widget to its initial state. Similar to ``clear``, but
435 also re-writes the banner and aborts execution if necessary.
435 also re-writes the banner and aborts execution if necessary.
436 """
436 """
437 if self._executing:
437 if self._executing:
438 self._executing = False
438 self._executing = False
439 self._request_info['execute'] = None
439 self._request_info['execute'] = None
440 self._reading = False
440 self._reading = False
441 self._highlighter.highlighting_on = False
441 self._highlighter.highlighting_on = False
442
442
443 self._control.clear()
443 self._control.clear()
444 self._append_plain_text(self._get_banner())
444 self._append_plain_text(self._get_banner())
445 self._show_interpreter_prompt()
445 self._show_interpreter_prompt()
446
446
447 def restart_kernel(self, message, now=False):
447 def restart_kernel(self, message, now=False):
448 """ Attempts to restart the running kernel.
448 """ Attempts to restart the running kernel.
449 """
449 """
450 # FIXME: now should be configurable via a checkbox in the dialog. Right
450 # FIXME: now should be configurable via a checkbox in the dialog. Right
451 # now at least the heartbeat path sets it to True and the manual restart
451 # now at least the heartbeat path sets it to True and the manual restart
452 # to False. But those should just be the pre-selected states of a
452 # to False. But those should just be the pre-selected states of a
453 # checkbox that the user could override if so desired. But I don't know
453 # checkbox that the user could override if so desired. But I don't know
454 # enough Qt to go implementing the checkbox now.
454 # enough Qt to go implementing the checkbox now.
455
455
456 if self.custom_restart:
456 if self.custom_restart:
457 self.custom_restart_requested.emit()
457 self.custom_restart_requested.emit()
458
458
459 elif self.kernel_manager.has_kernel:
459 elif self.kernel_manager.has_kernel:
460 # Pause the heart beat channel to prevent further warnings.
460 # Pause the heart beat channel to prevent further warnings.
461 self.kernel_manager.hb_channel.pause()
461 self.kernel_manager.hb_channel.pause()
462
462
463 # Prompt the user to restart the kernel. Un-pause the heartbeat if
463 # Prompt the user to restart the kernel. Un-pause the heartbeat if
464 # they decline. (If they accept, the heartbeat will be un-paused
464 # they decline. (If they accept, the heartbeat will be un-paused
465 # automatically when the kernel is restarted.)
465 # automatically when the kernel is restarted.)
466 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
466 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
467 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
467 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
468 message, buttons)
468 message, buttons)
469 if result == QtGui.QMessageBox.Yes:
469 if result == QtGui.QMessageBox.Yes:
470 try:
470 try:
471 self.kernel_manager.restart_kernel(now=now)
471 self.kernel_manager.restart_kernel(now=now)
472 except RuntimeError:
472 except RuntimeError:
473 self._append_plain_text('Kernel started externally. '
473 self._append_plain_text('Kernel started externally. '
474 'Cannot restart.\n')
474 'Cannot restart.\n')
475 else:
475 else:
476 self.reset()
476 self.reset()
477 else:
477 else:
478 self.kernel_manager.hb_channel.unpause()
478 self.kernel_manager.hb_channel.unpause()
479
479
480 else:
480 else:
481 self._append_plain_text('Kernel process is either remote or '
481 self._append_plain_text('Kernel process is either remote or '
482 'unspecified. Cannot restart.\n')
482 'unspecified. Cannot restart.\n')
483
483
484 #---------------------------------------------------------------------------
484 #---------------------------------------------------------------------------
485 # 'FrontendWidget' protected interface
485 # 'FrontendWidget' protected interface
486 #---------------------------------------------------------------------------
486 #---------------------------------------------------------------------------
487
487
488 def _call_tip(self):
488 def _call_tip(self):
489 """ Shows a call tip, if appropriate, at the current cursor location.
489 """ Shows a call tip, if appropriate, at the current cursor location.
490 """
490 """
491 # Decide if it makes sense to show a call tip
491 # Decide if it makes sense to show a call tip
492 cursor = self._get_cursor()
492 cursor = self._get_cursor()
493 cursor.movePosition(QtGui.QTextCursor.Left)
493 cursor.movePosition(QtGui.QTextCursor.Left)
494 if cursor.document().characterAt(cursor.position()).toAscii() != '(':
494 if cursor.document().characterAt(cursor.position()).toAscii() != '(':
495 return False
495 return False
496 context = self._get_context(cursor)
496 context = self._get_context(cursor)
497 if not context:
497 if not context:
498 return False
498 return False
499
499
500 # Send the metadata request to the kernel
500 # Send the metadata request to the kernel
501 name = '.'.join(context)
501 name = '.'.join(context)
502 msg_id = self.kernel_manager.xreq_channel.object_info(name)
502 msg_id = self.kernel_manager.xreq_channel.object_info(name)
503 pos = self._get_cursor().position()
503 pos = self._get_cursor().position()
504 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
504 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
505 return True
505 return True
506
506
507 def _complete(self):
507 def _complete(self):
508 """ Performs completion at the current cursor location.
508 """ Performs completion at the current cursor location.
509 """
509 """
510 context = self._get_context()
510 context = self._get_context()
511 if context:
511 if context:
512 # Send the completion request to the kernel
512 # Send the completion request to the kernel
513 msg_id = self.kernel_manager.xreq_channel.complete(
513 msg_id = self.kernel_manager.xreq_channel.complete(
514 '.'.join(context), # text
514 '.'.join(context), # text
515 self._get_input_buffer_cursor_line(), # line
515 self._get_input_buffer_cursor_line(), # line
516 self._get_input_buffer_cursor_column(), # cursor_pos
516 self._get_input_buffer_cursor_column(), # cursor_pos
517 self.input_buffer) # block
517 self.input_buffer) # block
518 pos = self._get_cursor().position()
518 pos = self._get_cursor().position()
519 info = self._CompletionRequest(msg_id, pos)
519 info = self._CompletionRequest(msg_id, pos)
520 self._request_info['complete'] = info
520 self._request_info['complete'] = info
521
521
522 def _get_banner(self):
522 def _get_banner(self):
523 """ Gets a banner to display at the beginning of a session.
523 """ Gets a banner to display at the beginning of a session.
524 """
524 """
525 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
525 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
526 '"license" for more information.'
526 '"license" for more information.'
527 return banner % (sys.version, sys.platform)
527 return banner % (sys.version, sys.platform)
528
528
529 def _get_context(self, cursor=None):
529 def _get_context(self, cursor=None):
530 """ Gets the context for the specified cursor (or the current cursor
530 """ Gets the context for the specified cursor (or the current cursor
531 if none is specified).
531 if none is specified).
532 """
532 """
533 if cursor is None:
533 if cursor is None:
534 cursor = self._get_cursor()
534 cursor = self._get_cursor()
535 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
535 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
536 QtGui.QTextCursor.KeepAnchor)
536 QtGui.QTextCursor.KeepAnchor)
537 text = unicode(cursor.selection().toPlainText())
537 text = unicode(cursor.selection().toPlainText())
538 return self._completion_lexer.get_context(text)
538 return self._completion_lexer.get_context(text)
539
539
540 def _process_execute_abort(self, msg):
540 def _process_execute_abort(self, msg):
541 """ Process a reply for an aborted execution request.
541 """ Process a reply for an aborted execution request.
542 """
542 """
543 self._append_plain_text("ERROR: execution aborted\n")
543 self._append_plain_text("ERROR: execution aborted\n")
544
544
545 def _process_execute_error(self, msg):
545 def _process_execute_error(self, msg):
546 """ Process a reply for an execution request that resulted in an error.
546 """ Process a reply for an execution request that resulted in an error.
547 """
547 """
548 content = msg['content']
548 content = msg['content']
549 traceback = ''.join(content['traceback'])
549 traceback = ''.join(content['traceback'])
550 self._append_plain_text(traceback)
550 self._append_plain_text(traceback)
551
551
552 def _process_execute_ok(self, msg):
552 def _process_execute_ok(self, msg):
553 """ Process a reply for a successful execution equest.
553 """ Process a reply for a successful execution equest.
554 """
554 """
555 payload = msg['content']['payload']
555 payload = msg['content']['payload']
556 for item in payload:
556 for item in payload:
557 if not self._process_execute_payload(item):
557 if not self._process_execute_payload(item):
558 warning = 'Warning: received unknown payload of type %s'
558 warning = 'Warning: received unknown payload of type %s'
559 print(warning % repr(item['source']))
559 print(warning % repr(item['source']))
560
560
561 def _process_execute_payload(self, item):
561 def _process_execute_payload(self, item):
562 """ Process a single payload item from the list of payload items in an
562 """ Process a single payload item from the list of payload items in an
563 execution reply. Returns whether the payload was handled.
563 execution reply. Returns whether the payload was handled.
564 """
564 """
565 # The basic FrontendWidget doesn't handle payloads, as they are a
565 # The basic FrontendWidget doesn't handle payloads, as they are a
566 # mechanism for going beyond the standard Python interpreter model.
566 # mechanism for going beyond the standard Python interpreter model.
567 return False
567 return False
568
568
569 def _show_interpreter_prompt(self):
569 def _show_interpreter_prompt(self):
570 """ Shows a prompt for the interpreter.
570 """ Shows a prompt for the interpreter.
571 """
571 """
572 self._show_prompt('>>> ')
572 self._show_prompt('>>> ')
573
573
574 def _show_interpreter_prompt_for_reply(self, msg):
574 def _show_interpreter_prompt_for_reply(self, msg):
575 """ Shows a prompt for the interpreter given an 'execute_reply' message.
575 """ Shows a prompt for the interpreter given an 'execute_reply' message.
576 """
576 """
577 self._show_interpreter_prompt()
577 self._show_interpreter_prompt()
578
578
579 #------ Signal handlers ----------------------------------------------------
579 #------ Signal handlers ----------------------------------------------------
580
580
581 def _document_contents_change(self, position, removed, added):
581 def _document_contents_change(self, position, removed, added):
582 """ Called whenever the document's content changes. Display a call tip
582 """ Called whenever the document's content changes. Display a call tip
583 if appropriate.
583 if appropriate.
584 """
584 """
585 # Calculate where the cursor should be *after* the change:
585 # Calculate where the cursor should be *after* the change:
586 position += added
586 position += added
587
587
588 document = self._control.document()
588 document = self._control.document()
589 if position == self._get_cursor().position():
589 if position == self._get_cursor().position():
590 self._call_tip()
590 self._call_tip()
General Comments 0
You need to be logged in to leave comments. Login now