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