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