##// END OF EJS Templates
cleanup channel names to match function not socket...
MinRK -
Show More
@@ -1,109 +1,109 b''
1 """ Defines a convenient mix-in class for implementing Qt frontends.
1 """ Defines a convenient mix-in class for implementing Qt frontends.
2 """
2 """
3
3
4 class BaseFrontendMixin(object):
4 class BaseFrontendMixin(object):
5 """ A mix-in class for implementing Qt frontends.
5 """ A mix-in class for implementing Qt frontends.
6
6
7 To handle messages of a particular type, frontends need only define an
7 To handle messages of a particular type, frontends need only define an
8 appropriate handler method. For example, to handle 'stream' messaged, define
8 appropriate handler method. For example, to handle 'stream' messaged, define
9 a '_handle_stream(msg)' method.
9 a '_handle_stream(msg)' method.
10 """
10 """
11
11
12 #---------------------------------------------------------------------------
12 #---------------------------------------------------------------------------
13 # 'BaseFrontendMixin' concrete interface
13 # 'BaseFrontendMixin' concrete interface
14 #---------------------------------------------------------------------------
14 #---------------------------------------------------------------------------
15
15
16 def _get_kernel_manager(self):
16 def _get_kernel_manager(self):
17 """ Returns the current kernel manager.
17 """ Returns the current kernel manager.
18 """
18 """
19 return self._kernel_manager
19 return self._kernel_manager
20
20
21 def _set_kernel_manager(self, kernel_manager):
21 def _set_kernel_manager(self, kernel_manager):
22 """ Disconnect from the current kernel manager (if any) and set a new
22 """ Disconnect from the current kernel manager (if any) and set a new
23 kernel manager.
23 kernel manager.
24 """
24 """
25 # Disconnect the old kernel manager, if necessary.
25 # Disconnect the old kernel manager, if necessary.
26 old_manager = self._kernel_manager
26 old_manager = self._kernel_manager
27 if old_manager is not None:
27 if old_manager is not None:
28 old_manager.started_channels.disconnect(self._started_channels)
28 old_manager.started_channels.disconnect(self._started_channels)
29 old_manager.stopped_channels.disconnect(self._stopped_channels)
29 old_manager.stopped_channels.disconnect(self._stopped_channels)
30
30
31 # Disconnect the old kernel manager's channels.
31 # Disconnect the old kernel manager's channels.
32 old_manager.sub_channel.message_received.disconnect(self._dispatch)
32 old_manager.sub_channel.message_received.disconnect(self._dispatch)
33 old_manager.xreq_channel.message_received.disconnect(self._dispatch)
33 old_manager.shell_channel.message_received.disconnect(self._dispatch)
34 old_manager.rep_channel.message_received.disconnect(self._dispatch)
34 old_manager.stdin_channel.message_received.disconnect(self._dispatch)
35 old_manager.hb_channel.kernel_died.disconnect(
35 old_manager.hb_channel.kernel_died.disconnect(
36 self._handle_kernel_died)
36 self._handle_kernel_died)
37
37
38 # Handle the case where the old kernel manager is still listening.
38 # Handle the case where the old kernel manager is still listening.
39 if old_manager.channels_running:
39 if old_manager.channels_running:
40 self._stopped_channels()
40 self._stopped_channels()
41
41
42 # Set the new kernel manager.
42 # Set the new kernel manager.
43 self._kernel_manager = kernel_manager
43 self._kernel_manager = kernel_manager
44 if kernel_manager is None:
44 if kernel_manager is None:
45 return
45 return
46
46
47 # Connect the new kernel manager.
47 # Connect the new kernel manager.
48 kernel_manager.started_channels.connect(self._started_channels)
48 kernel_manager.started_channels.connect(self._started_channels)
49 kernel_manager.stopped_channels.connect(self._stopped_channels)
49 kernel_manager.stopped_channels.connect(self._stopped_channels)
50
50
51 # Connect the new kernel manager's channels.
51 # Connect the new kernel manager's channels.
52 kernel_manager.sub_channel.message_received.connect(self._dispatch)
52 kernel_manager.sub_channel.message_received.connect(self._dispatch)
53 kernel_manager.xreq_channel.message_received.connect(self._dispatch)
53 kernel_manager.shell_channel.message_received.connect(self._dispatch)
54 kernel_manager.rep_channel.message_received.connect(self._dispatch)
54 kernel_manager.stdin_channel.message_received.connect(self._dispatch)
55 kernel_manager.hb_channel.kernel_died.connect(self._handle_kernel_died)
55 kernel_manager.hb_channel.kernel_died.connect(self._handle_kernel_died)
56
56
57 # Handle the case where the kernel manager started channels before
57 # Handle the case where the kernel manager started channels before
58 # we connected.
58 # we connected.
59 if kernel_manager.channels_running:
59 if kernel_manager.channels_running:
60 self._started_channels()
60 self._started_channels()
61
61
62 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
62 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
63
63
64 #---------------------------------------------------------------------------
64 #---------------------------------------------------------------------------
65 # 'BaseFrontendMixin' abstract interface
65 # 'BaseFrontendMixin' abstract interface
66 #---------------------------------------------------------------------------
66 #---------------------------------------------------------------------------
67
67
68 def _handle_kernel_died(self, since_last_heartbeat):
68 def _handle_kernel_died(self, since_last_heartbeat):
69 """ This is called when the ``kernel_died`` signal is emitted.
69 """ This is called when the ``kernel_died`` signal is emitted.
70
70
71 This method is called when the kernel heartbeat has not been
71 This method is called when the kernel heartbeat has not been
72 active for a certain amount of time. The typical action will be to
72 active for a certain amount of time. The typical action will be to
73 give the user the option of restarting the kernel.
73 give the user the option of restarting the kernel.
74
74
75 Parameters
75 Parameters
76 ----------
76 ----------
77 since_last_heartbeat : float
77 since_last_heartbeat : float
78 The time since the heartbeat was last received.
78 The time since the heartbeat was last received.
79 """
79 """
80
80
81 def _started_channels(self):
81 def _started_channels(self):
82 """ Called when the KernelManager channels have started listening or
82 """ Called when the KernelManager channels have started listening or
83 when the frontend is assigned an already listening KernelManager.
83 when the frontend is assigned an already listening KernelManager.
84 """
84 """
85
85
86 def _stopped_channels(self):
86 def _stopped_channels(self):
87 """ Called when the KernelManager channels have stopped listening or
87 """ Called when the KernelManager channels have stopped listening or
88 when a listening KernelManager is removed from the frontend.
88 when a listening KernelManager is removed from the frontend.
89 """
89 """
90
90
91 #---------------------------------------------------------------------------
91 #---------------------------------------------------------------------------
92 # 'BaseFrontendMixin' protected interface
92 # 'BaseFrontendMixin' protected interface
93 #---------------------------------------------------------------------------
93 #---------------------------------------------------------------------------
94
94
95 def _dispatch(self, msg):
95 def _dispatch(self, msg):
96 """ Calls the frontend handler associated with the message type of the
96 """ Calls the frontend handler associated with the message type of the
97 given message.
97 given message.
98 """
98 """
99 msg_type = msg['msg_type']
99 msg_type = msg['msg_type']
100 handler = getattr(self, '_handle_' + msg_type, None)
100 handler = getattr(self, '_handle_' + msg_type, None)
101 if handler:
101 if handler:
102 handler(msg)
102 handler(msg)
103
103
104 def _is_from_this_session(self, msg):
104 def _is_from_this_session(self, msg):
105 """ Returns whether a reply from the kernel originated from a request
105 """ Returns whether a reply from the kernel originated from a request
106 from this frontend.
106 from this frontend.
107 """
107 """
108 session = self._kernel_manager.session.session
108 session = self._kernel_manager.session.session
109 return msg['parent_header']['session'] == session
109 return msg['parent_header']['session'] == session
@@ -1,626 +1,626 b''
1 from __future__ import print_function
1 from __future__ import print_function
2
2
3 # Standard library imports
3 # Standard library imports
4 from collections import namedtuple
4 from collections import namedtuple
5 import sys
5 import sys
6 import time
6 import time
7
7
8 # System library imports
8 # System library imports
9 from pygments.lexers import PythonLexer
9 from pygments.lexers import PythonLexer
10 from IPython.external.qt import QtCore, QtGui
10 from IPython.external.qt import QtCore, QtGui
11
11
12 # Local imports
12 # Local imports
13 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
13 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
14 from IPython.core.oinspect import call_tip
14 from IPython.core.oinspect import call_tip
15 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
15 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
16 from IPython.utils.traitlets import Bool, Instance
16 from IPython.utils.traitlets import Bool, Instance
17 from bracket_matcher import BracketMatcher
17 from bracket_matcher import BracketMatcher
18 from call_tip_widget import CallTipWidget
18 from call_tip_widget import CallTipWidget
19 from completion_lexer import CompletionLexer
19 from completion_lexer import CompletionLexer
20 from history_console_widget import HistoryConsoleWidget
20 from history_console_widget import HistoryConsoleWidget
21 from pygments_highlighter import PygmentsHighlighter
21 from pygments_highlighter import PygmentsHighlighter
22
22
23
23
24 class FrontendHighlighter(PygmentsHighlighter):
24 class FrontendHighlighter(PygmentsHighlighter):
25 """ A PygmentsHighlighter that can be turned on and off and that ignores
25 """ A PygmentsHighlighter that can be turned on and off and that ignores
26 prompts.
26 prompts.
27 """
27 """
28
28
29 def __init__(self, frontend):
29 def __init__(self, frontend):
30 super(FrontendHighlighter, self).__init__(frontend._control.document())
30 super(FrontendHighlighter, self).__init__(frontend._control.document())
31 self._current_offset = 0
31 self._current_offset = 0
32 self._frontend = frontend
32 self._frontend = frontend
33 self.highlighting_on = False
33 self.highlighting_on = False
34
34
35 def highlightBlock(self, string):
35 def highlightBlock(self, string):
36 """ Highlight a block of text. Reimplemented to highlight selectively.
36 """ Highlight a block of text. Reimplemented to highlight selectively.
37 """
37 """
38 if not self.highlighting_on:
38 if not self.highlighting_on:
39 return
39 return
40
40
41 # The input to this function is a unicode string that may contain
41 # The input to this function is a unicode string that may contain
42 # paragraph break characters, non-breaking spaces, etc. Here we acquire
42 # paragraph break characters, non-breaking spaces, etc. Here we acquire
43 # the string as plain text so we can compare it.
43 # the string as plain text so we can compare it.
44 current_block = self.currentBlock()
44 current_block = self.currentBlock()
45 string = self._frontend._get_block_plain_text(current_block)
45 string = self._frontend._get_block_plain_text(current_block)
46
46
47 # Decide whether to check for the regular or continuation prompt.
47 # Decide whether to check for the regular or continuation prompt.
48 if current_block.contains(self._frontend._prompt_pos):
48 if current_block.contains(self._frontend._prompt_pos):
49 prompt = self._frontend._prompt
49 prompt = self._frontend._prompt
50 else:
50 else:
51 prompt = self._frontend._continuation_prompt
51 prompt = self._frontend._continuation_prompt
52
52
53 # Don't highlight the part of the string that contains the prompt.
53 # Don't highlight the part of the string that contains the prompt.
54 if string.startswith(prompt):
54 if string.startswith(prompt):
55 self._current_offset = len(prompt)
55 self._current_offset = len(prompt)
56 string = string[len(prompt):]
56 string = string[len(prompt):]
57 else:
57 else:
58 self._current_offset = 0
58 self._current_offset = 0
59
59
60 PygmentsHighlighter.highlightBlock(self, string)
60 PygmentsHighlighter.highlightBlock(self, string)
61
61
62 def rehighlightBlock(self, block):
62 def rehighlightBlock(self, block):
63 """ Reimplemented to temporarily enable highlighting if disabled.
63 """ Reimplemented to temporarily enable highlighting if disabled.
64 """
64 """
65 old = self.highlighting_on
65 old = self.highlighting_on
66 self.highlighting_on = True
66 self.highlighting_on = True
67 super(FrontendHighlighter, self).rehighlightBlock(block)
67 super(FrontendHighlighter, self).rehighlightBlock(block)
68 self.highlighting_on = old
68 self.highlighting_on = old
69
69
70 def setFormat(self, start, count, format):
70 def setFormat(self, start, count, format):
71 """ Reimplemented to highlight selectively.
71 """ Reimplemented to highlight selectively.
72 """
72 """
73 start += self._current_offset
73 start += self._current_offset
74 PygmentsHighlighter.setFormat(self, start, count, format)
74 PygmentsHighlighter.setFormat(self, start, count, format)
75
75
76
76
77 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
77 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
78 """ A Qt frontend for a generic Python kernel.
78 """ A Qt frontend for a generic Python kernel.
79 """
79 """
80
80
81 # An option and corresponding signal for overriding the default kernel
81 # An option and corresponding signal for overriding the default kernel
82 # interrupt behavior.
82 # interrupt behavior.
83 custom_interrupt = Bool(False)
83 custom_interrupt = Bool(False)
84 custom_interrupt_requested = QtCore.Signal()
84 custom_interrupt_requested = QtCore.Signal()
85
85
86 # An option and corresponding signals for overriding the default kernel
86 # An option and corresponding signals for overriding the default kernel
87 # restart behavior.
87 # restart behavior.
88 custom_restart = Bool(False)
88 custom_restart = Bool(False)
89 custom_restart_kernel_died = QtCore.Signal(float)
89 custom_restart_kernel_died = QtCore.Signal(float)
90 custom_restart_requested = QtCore.Signal()
90 custom_restart_requested = QtCore.Signal()
91
91
92 # Emitted when a user visible 'execute_request' has been submitted to the
92 # Emitted when a user visible 'execute_request' has been submitted to the
93 # kernel from the FrontendWidget. Contains the code to be executed.
93 # kernel from the FrontendWidget. Contains the code to be executed.
94 executing = QtCore.Signal(object)
94 executing = QtCore.Signal(object)
95
95
96 # Emitted when a user-visible 'execute_reply' has been received from the
96 # Emitted when a user-visible 'execute_reply' has been received from the
97 # kernel and processed by the FrontendWidget. Contains the response message.
97 # kernel and processed by the FrontendWidget. Contains the response message.
98 executed = QtCore.Signal(object)
98 executed = QtCore.Signal(object)
99
99
100 # Emitted when an exit request has been received from the kernel.
100 # Emitted when an exit request has been received from the kernel.
101 exit_requested = QtCore.Signal()
101 exit_requested = QtCore.Signal()
102
102
103 # Protected class variables.
103 # Protected class variables.
104 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
104 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
105 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
105 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
106 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
106 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
107 _input_splitter_class = InputSplitter
107 _input_splitter_class = InputSplitter
108 _local_kernel = False
108 _local_kernel = False
109 _highlighter = Instance(FrontendHighlighter)
109 _highlighter = Instance(FrontendHighlighter)
110
110
111 #---------------------------------------------------------------------------
111 #---------------------------------------------------------------------------
112 # 'object' interface
112 # 'object' interface
113 #---------------------------------------------------------------------------
113 #---------------------------------------------------------------------------
114
114
115 def __init__(self, *args, **kw):
115 def __init__(self, *args, **kw):
116 super(FrontendWidget, self).__init__(*args, **kw)
116 super(FrontendWidget, self).__init__(*args, **kw)
117
117
118 # FrontendWidget protected variables.
118 # FrontendWidget protected variables.
119 self._bracket_matcher = BracketMatcher(self._control)
119 self._bracket_matcher = BracketMatcher(self._control)
120 self._call_tip_widget = CallTipWidget(self._control)
120 self._call_tip_widget = CallTipWidget(self._control)
121 self._completion_lexer = CompletionLexer(PythonLexer())
121 self._completion_lexer = CompletionLexer(PythonLexer())
122 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
122 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
123 self._hidden = False
123 self._hidden = False
124 self._highlighter = FrontendHighlighter(self)
124 self._highlighter = FrontendHighlighter(self)
125 self._input_splitter = self._input_splitter_class(input_mode='cell')
125 self._input_splitter = self._input_splitter_class(input_mode='cell')
126 self._kernel_manager = None
126 self._kernel_manager = None
127 self._request_info = {}
127 self._request_info = {}
128
128
129 # Configure the ConsoleWidget.
129 # Configure the ConsoleWidget.
130 self.tab_width = 4
130 self.tab_width = 4
131 self._set_continuation_prompt('... ')
131 self._set_continuation_prompt('... ')
132
132
133 # Configure the CallTipWidget.
133 # Configure the CallTipWidget.
134 self._call_tip_widget.setFont(self.font)
134 self._call_tip_widget.setFont(self.font)
135 self.font_changed.connect(self._call_tip_widget.setFont)
135 self.font_changed.connect(self._call_tip_widget.setFont)
136
136
137 # Configure actions.
137 # Configure actions.
138 action = self._copy_raw_action
138 action = self._copy_raw_action
139 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
139 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
140 action.setEnabled(False)
140 action.setEnabled(False)
141 action.setShortcut(QtGui.QKeySequence(key))
141 action.setShortcut(QtGui.QKeySequence(key))
142 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
142 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
143 action.triggered.connect(self.copy_raw)
143 action.triggered.connect(self.copy_raw)
144 self.copy_available.connect(action.setEnabled)
144 self.copy_available.connect(action.setEnabled)
145 self.addAction(action)
145 self.addAction(action)
146
146
147 # Connect signal handlers.
147 # Connect signal handlers.
148 document = self._control.document()
148 document = self._control.document()
149 document.contentsChange.connect(self._document_contents_change)
149 document.contentsChange.connect(self._document_contents_change)
150
150
151 # Set flag for whether we are connected via localhost.
151 # Set flag for whether we are connected via localhost.
152 self._local_kernel = kw.get('local_kernel',
152 self._local_kernel = kw.get('local_kernel',
153 FrontendWidget._local_kernel)
153 FrontendWidget._local_kernel)
154
154
155 #---------------------------------------------------------------------------
155 #---------------------------------------------------------------------------
156 # 'ConsoleWidget' public interface
156 # 'ConsoleWidget' public interface
157 #---------------------------------------------------------------------------
157 #---------------------------------------------------------------------------
158
158
159 def copy(self):
159 def copy(self):
160 """ Copy the currently selected text to the clipboard, removing prompts.
160 """ Copy the currently selected text to the clipboard, removing prompts.
161 """
161 """
162 text = self._control.textCursor().selection().toPlainText()
162 text = self._control.textCursor().selection().toPlainText()
163 if text:
163 if text:
164 lines = map(transform_classic_prompt, text.splitlines())
164 lines = map(transform_classic_prompt, text.splitlines())
165 text = '\n'.join(lines)
165 text = '\n'.join(lines)
166 QtGui.QApplication.clipboard().setText(text)
166 QtGui.QApplication.clipboard().setText(text)
167
167
168 #---------------------------------------------------------------------------
168 #---------------------------------------------------------------------------
169 # 'ConsoleWidget' abstract interface
169 # 'ConsoleWidget' abstract interface
170 #---------------------------------------------------------------------------
170 #---------------------------------------------------------------------------
171
171
172 def _is_complete(self, source, interactive):
172 def _is_complete(self, source, interactive):
173 """ Returns whether 'source' can be completely processed and a new
173 """ Returns whether 'source' can be completely processed and a new
174 prompt created. When triggered by an Enter/Return key press,
174 prompt created. When triggered by an Enter/Return key press,
175 'interactive' is True; otherwise, it is False.
175 'interactive' is True; otherwise, it is False.
176 """
176 """
177 complete = self._input_splitter.push(source)
177 complete = self._input_splitter.push(source)
178 if interactive:
178 if interactive:
179 complete = not self._input_splitter.push_accepts_more()
179 complete = not self._input_splitter.push_accepts_more()
180 return complete
180 return complete
181
181
182 def _execute(self, source, hidden):
182 def _execute(self, source, hidden):
183 """ Execute 'source'. If 'hidden', do not show any output.
183 """ Execute 'source'. If 'hidden', do not show any output.
184
184
185 See parent class :meth:`execute` docstring for full details.
185 See parent class :meth:`execute` docstring for full details.
186 """
186 """
187 msg_id = self.kernel_manager.xreq_channel.execute(source, hidden)
187 msg_id = self.kernel_manager.shell_channel.execute(source, hidden)
188 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
188 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
189 self._hidden = hidden
189 self._hidden = hidden
190 if not hidden:
190 if not hidden:
191 self.executing.emit(source)
191 self.executing.emit(source)
192
192
193 def _prompt_started_hook(self):
193 def _prompt_started_hook(self):
194 """ Called immediately after a new prompt is displayed.
194 """ Called immediately after a new prompt is displayed.
195 """
195 """
196 if not self._reading:
196 if not self._reading:
197 self._highlighter.highlighting_on = True
197 self._highlighter.highlighting_on = True
198
198
199 def _prompt_finished_hook(self):
199 def _prompt_finished_hook(self):
200 """ Called immediately after a prompt is finished, i.e. when some input
200 """ Called immediately after a prompt is finished, i.e. when some input
201 will be processed and a new prompt displayed.
201 will be processed and a new prompt displayed.
202 """
202 """
203 # Flush all state from the input splitter so the next round of
203 # Flush all state from the input splitter so the next round of
204 # reading input starts with a clean buffer.
204 # reading input starts with a clean buffer.
205 self._input_splitter.reset()
205 self._input_splitter.reset()
206
206
207 if not self._reading:
207 if not self._reading:
208 self._highlighter.highlighting_on = False
208 self._highlighter.highlighting_on = False
209
209
210 def _tab_pressed(self):
210 def _tab_pressed(self):
211 """ Called when the tab key is pressed. Returns whether to continue
211 """ Called when the tab key is pressed. Returns whether to continue
212 processing the event.
212 processing the event.
213 """
213 """
214 # Perform tab completion if:
214 # Perform tab completion if:
215 # 1) The cursor is in the input buffer.
215 # 1) The cursor is in the input buffer.
216 # 2) There is a non-whitespace character before the cursor.
216 # 2) There is a non-whitespace character before the cursor.
217 text = self._get_input_buffer_cursor_line()
217 text = self._get_input_buffer_cursor_line()
218 if text is None:
218 if text is None:
219 return False
219 return False
220 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
220 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
221 if complete:
221 if complete:
222 self._complete()
222 self._complete()
223 return not complete
223 return not complete
224
224
225 #---------------------------------------------------------------------------
225 #---------------------------------------------------------------------------
226 # 'ConsoleWidget' protected interface
226 # 'ConsoleWidget' protected interface
227 #---------------------------------------------------------------------------
227 #---------------------------------------------------------------------------
228
228
229 def _context_menu_make(self, pos):
229 def _context_menu_make(self, pos):
230 """ Reimplemented to add an action for raw copy.
230 """ Reimplemented to add an action for raw copy.
231 """
231 """
232 menu = super(FrontendWidget, self)._context_menu_make(pos)
232 menu = super(FrontendWidget, self)._context_menu_make(pos)
233 for before_action in menu.actions():
233 for before_action in menu.actions():
234 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
234 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
235 QtGui.QKeySequence.ExactMatch:
235 QtGui.QKeySequence.ExactMatch:
236 menu.insertAction(before_action, self._copy_raw_action)
236 menu.insertAction(before_action, self._copy_raw_action)
237 break
237 break
238 return menu
238 return menu
239
239
240 def _event_filter_console_keypress(self, event):
240 def _event_filter_console_keypress(self, event):
241 """ Reimplemented for execution interruption and smart backspace.
241 """ Reimplemented for execution interruption and smart backspace.
242 """
242 """
243 key = event.key()
243 key = event.key()
244 if self._control_key_down(event.modifiers(), include_command=False):
244 if self._control_key_down(event.modifiers(), include_command=False):
245
245
246 if key == QtCore.Qt.Key_C and self._executing:
246 if key == QtCore.Qt.Key_C and self._executing:
247 self.interrupt_kernel()
247 self.interrupt_kernel()
248 return True
248 return True
249
249
250 elif key == QtCore.Qt.Key_Period:
250 elif key == QtCore.Qt.Key_Period:
251 message = 'Are you sure you want to restart the kernel?'
251 message = 'Are you sure you want to restart the kernel?'
252 self.restart_kernel(message, now=False)
252 self.restart_kernel(message, now=False)
253 return True
253 return True
254
254
255 elif not event.modifiers() & QtCore.Qt.AltModifier:
255 elif not event.modifiers() & QtCore.Qt.AltModifier:
256
256
257 # Smart backspace: remove four characters in one backspace if:
257 # Smart backspace: remove four characters in one backspace if:
258 # 1) everything left of the cursor is whitespace
258 # 1) everything left of the cursor is whitespace
259 # 2) the four characters immediately left of the cursor are spaces
259 # 2) the four characters immediately left of the cursor are spaces
260 if key == QtCore.Qt.Key_Backspace:
260 if key == QtCore.Qt.Key_Backspace:
261 col = self._get_input_buffer_cursor_column()
261 col = self._get_input_buffer_cursor_column()
262 cursor = self._control.textCursor()
262 cursor = self._control.textCursor()
263 if col > 3 and not cursor.hasSelection():
263 if col > 3 and not cursor.hasSelection():
264 text = self._get_input_buffer_cursor_line()[:col]
264 text = self._get_input_buffer_cursor_line()[:col]
265 if text.endswith(' ') and not text.strip():
265 if text.endswith(' ') and not text.strip():
266 cursor.movePosition(QtGui.QTextCursor.Left,
266 cursor.movePosition(QtGui.QTextCursor.Left,
267 QtGui.QTextCursor.KeepAnchor, 4)
267 QtGui.QTextCursor.KeepAnchor, 4)
268 cursor.removeSelectedText()
268 cursor.removeSelectedText()
269 return True
269 return True
270
270
271 return super(FrontendWidget, self)._event_filter_console_keypress(event)
271 return super(FrontendWidget, self)._event_filter_console_keypress(event)
272
272
273 def _insert_continuation_prompt(self, cursor):
273 def _insert_continuation_prompt(self, cursor):
274 """ Reimplemented for auto-indentation.
274 """ Reimplemented for auto-indentation.
275 """
275 """
276 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
276 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
277 cursor.insertText(' ' * self._input_splitter.indent_spaces)
277 cursor.insertText(' ' * self._input_splitter.indent_spaces)
278
278
279 #---------------------------------------------------------------------------
279 #---------------------------------------------------------------------------
280 # 'BaseFrontendMixin' abstract interface
280 # 'BaseFrontendMixin' abstract interface
281 #---------------------------------------------------------------------------
281 #---------------------------------------------------------------------------
282
282
283 def _handle_complete_reply(self, rep):
283 def _handle_complete_reply(self, rep):
284 """ Handle replies for tab completion.
284 """ Handle replies for tab completion.
285 """
285 """
286 cursor = self._get_cursor()
286 cursor = self._get_cursor()
287 info = self._request_info.get('complete')
287 info = self._request_info.get('complete')
288 if info and info.id == rep['parent_header']['msg_id'] and \
288 if info and info.id == rep['parent_header']['msg_id'] and \
289 info.pos == cursor.position():
289 info.pos == cursor.position():
290 text = '.'.join(self._get_context())
290 text = '.'.join(self._get_context())
291 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
291 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
292 self._complete_with_items(cursor, rep['content']['matches'])
292 self._complete_with_items(cursor, rep['content']['matches'])
293
293
294 def _handle_execute_reply(self, msg):
294 def _handle_execute_reply(self, msg):
295 """ Handles replies for code execution.
295 """ Handles replies for code execution.
296 """
296 """
297 info = self._request_info.get('execute')
297 info = self._request_info.get('execute')
298 if info and info.id == msg['parent_header']['msg_id'] and \
298 if info and info.id == msg['parent_header']['msg_id'] and \
299 info.kind == 'user' and not self._hidden:
299 info.kind == 'user' and not self._hidden:
300 # Make sure that all output from the SUB channel has been processed
300 # Make sure that all output from the SUB channel has been processed
301 # before writing a new prompt.
301 # before writing a new prompt.
302 self.kernel_manager.sub_channel.flush()
302 self.kernel_manager.sub_channel.flush()
303
303
304 # Reset the ANSI style information to prevent bad text in stdout
304 # Reset the ANSI style information to prevent bad text in stdout
305 # from messing up our colors. We're not a true terminal so we're
305 # from messing up our colors. We're not a true terminal so we're
306 # allowed to do this.
306 # allowed to do this.
307 if self.ansi_codes:
307 if self.ansi_codes:
308 self._ansi_processor.reset_sgr()
308 self._ansi_processor.reset_sgr()
309
309
310 content = msg['content']
310 content = msg['content']
311 status = content['status']
311 status = content['status']
312 if status == 'ok':
312 if status == 'ok':
313 self._process_execute_ok(msg)
313 self._process_execute_ok(msg)
314 elif status == 'error':
314 elif status == 'error':
315 self._process_execute_error(msg)
315 self._process_execute_error(msg)
316 elif status == 'abort':
316 elif status == 'abort':
317 self._process_execute_abort(msg)
317 self._process_execute_abort(msg)
318
318
319 self._show_interpreter_prompt_for_reply(msg)
319 self._show_interpreter_prompt_for_reply(msg)
320 self.executed.emit(msg)
320 self.executed.emit(msg)
321
321
322 def _handle_input_request(self, msg):
322 def _handle_input_request(self, msg):
323 """ Handle requests for raw_input.
323 """ Handle requests for raw_input.
324 """
324 """
325 if self._hidden:
325 if self._hidden:
326 raise RuntimeError('Request for raw input during hidden execution.')
326 raise RuntimeError('Request for raw input during hidden execution.')
327
327
328 # Make sure that all output from the SUB channel has been processed
328 # Make sure that all output from the SUB channel has been processed
329 # before entering readline mode.
329 # before entering readline mode.
330 self.kernel_manager.sub_channel.flush()
330 self.kernel_manager.sub_channel.flush()
331
331
332 def callback(line):
332 def callback(line):
333 self.kernel_manager.rep_channel.input(line)
333 self.kernel_manager.stdin_channel.input(line)
334 self._readline(msg['content']['prompt'], callback=callback)
334 self._readline(msg['content']['prompt'], callback=callback)
335
335
336 def _handle_kernel_died(self, since_last_heartbeat):
336 def _handle_kernel_died(self, since_last_heartbeat):
337 """ Handle the kernel's death by asking if the user wants to restart.
337 """ Handle the kernel's death by asking if the user wants to restart.
338 """
338 """
339 if self.custom_restart:
339 if self.custom_restart:
340 self.custom_restart_kernel_died.emit(since_last_heartbeat)
340 self.custom_restart_kernel_died.emit(since_last_heartbeat)
341 else:
341 else:
342 message = 'The kernel heartbeat has been inactive for %.2f ' \
342 message = 'The kernel heartbeat has been inactive for %.2f ' \
343 'seconds. Do you want to restart the kernel? You may ' \
343 'seconds. Do you want to restart the kernel? You may ' \
344 'first want to check the network connection.' % \
344 'first want to check the network connection.' % \
345 since_last_heartbeat
345 since_last_heartbeat
346 self.restart_kernel(message, now=True)
346 self.restart_kernel(message, now=True)
347
347
348 def _handle_object_info_reply(self, rep):
348 def _handle_object_info_reply(self, rep):
349 """ Handle replies for call tips.
349 """ Handle replies for call tips.
350 """
350 """
351 cursor = self._get_cursor()
351 cursor = self._get_cursor()
352 info = self._request_info.get('call_tip')
352 info = self._request_info.get('call_tip')
353 if info and info.id == rep['parent_header']['msg_id'] and \
353 if info and info.id == rep['parent_header']['msg_id'] and \
354 info.pos == cursor.position():
354 info.pos == cursor.position():
355 # Get the information for a call tip. For now we format the call
355 # Get the information for a call tip. For now we format the call
356 # line as string, later we can pass False to format_call and
356 # line as string, later we can pass False to format_call and
357 # syntax-highlight it ourselves for nicer formatting in the
357 # syntax-highlight it ourselves for nicer formatting in the
358 # calltip.
358 # calltip.
359 content = rep['content']
359 content = rep['content']
360 # if this is from pykernel, 'docstring' will be the only key
360 # if this is from pykernel, 'docstring' will be the only key
361 if content.get('ismagic', False):
361 if content.get('ismagic', False):
362 # Don't generate a call-tip for magics. Ideally, we should
362 # Don't generate a call-tip for magics. Ideally, we should
363 # generate a tooltip, but not on ( like we do for actual
363 # generate a tooltip, but not on ( like we do for actual
364 # callables.
364 # callables.
365 call_info, doc = None, None
365 call_info, doc = None, None
366 else:
366 else:
367 call_info, doc = call_tip(content, format_call=True)
367 call_info, doc = call_tip(content, format_call=True)
368 if call_info or doc:
368 if call_info or doc:
369 self._call_tip_widget.show_call_info(call_info, doc)
369 self._call_tip_widget.show_call_info(call_info, doc)
370
370
371 def _handle_pyout(self, msg):
371 def _handle_pyout(self, msg):
372 """ Handle display hook output.
372 """ Handle display hook output.
373 """
373 """
374 if not self._hidden and self._is_from_this_session(msg):
374 if not self._hidden and self._is_from_this_session(msg):
375 data = msg['content']['data']
375 data = msg['content']['data']
376 if isinstance(data, basestring):
376 if isinstance(data, basestring):
377 # plaintext data from pure Python kernel
377 # plaintext data from pure Python kernel
378 text = data
378 text = data
379 else:
379 else:
380 # formatted output from DisplayFormatter (IPython kernel)
380 # formatted output from DisplayFormatter (IPython kernel)
381 text = data.get('text/plain', '')
381 text = data.get('text/plain', '')
382 self._append_plain_text(text + '\n')
382 self._append_plain_text(text + '\n')
383
383
384 def _handle_stream(self, msg):
384 def _handle_stream(self, msg):
385 """ Handle stdout, stderr, and stdin.
385 """ Handle stdout, stderr, and stdin.
386 """
386 """
387 if not self._hidden and self._is_from_this_session(msg):
387 if not self._hidden and self._is_from_this_session(msg):
388 # Most consoles treat tabs as being 8 space characters. Convert tabs
388 # Most consoles treat tabs as being 8 space characters. Convert tabs
389 # to spaces so that output looks as expected regardless of this
389 # to spaces so that output looks as expected regardless of this
390 # widget's tab width.
390 # widget's tab width.
391 text = msg['content']['data'].expandtabs(8)
391 text = msg['content']['data'].expandtabs(8)
392
392
393 self._append_plain_text(text)
393 self._append_plain_text(text)
394 self._control.moveCursor(QtGui.QTextCursor.End)
394 self._control.moveCursor(QtGui.QTextCursor.End)
395
395
396 def _handle_shutdown_reply(self, msg):
396 def _handle_shutdown_reply(self, msg):
397 """ Handle shutdown signal, only if from other console.
397 """ Handle shutdown signal, only if from other console.
398 """
398 """
399 if not self._hidden and not self._is_from_this_session(msg):
399 if not self._hidden and not self._is_from_this_session(msg):
400 if self._local_kernel:
400 if self._local_kernel:
401 if not msg['content']['restart']:
401 if not msg['content']['restart']:
402 sys.exit(0)
402 sys.exit(0)
403 else:
403 else:
404 # we just got notified of a restart!
404 # we just got notified of a restart!
405 time.sleep(0.25) # wait 1/4 sec to reset
405 time.sleep(0.25) # wait 1/4 sec to reset
406 # lest the request for a new prompt
406 # lest the request for a new prompt
407 # goes to the old kernel
407 # goes to the old kernel
408 self.reset()
408 self.reset()
409 else: # remote kernel, prompt on Kernel shutdown/reset
409 else: # remote kernel, prompt on Kernel shutdown/reset
410 title = self.window().windowTitle()
410 title = self.window().windowTitle()
411 if not msg['content']['restart']:
411 if not msg['content']['restart']:
412 reply = QtGui.QMessageBox.question(self, title,
412 reply = QtGui.QMessageBox.question(self, title,
413 "Kernel has been shutdown permanently. "
413 "Kernel has been shutdown permanently. "
414 "Close the Console?",
414 "Close the Console?",
415 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
415 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
416 if reply == QtGui.QMessageBox.Yes:
416 if reply == QtGui.QMessageBox.Yes:
417 sys.exit(0)
417 sys.exit(0)
418 else:
418 else:
419 reply = QtGui.QMessageBox.question(self, title,
419 reply = QtGui.QMessageBox.question(self, title,
420 "Kernel has been reset. Clear the Console?",
420 "Kernel has been reset. Clear the Console?",
421 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
421 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
422 if reply == QtGui.QMessageBox.Yes:
422 if reply == QtGui.QMessageBox.Yes:
423 time.sleep(0.25) # wait 1/4 sec to reset
423 time.sleep(0.25) # wait 1/4 sec to reset
424 # lest the request for a new prompt
424 # lest the request for a new prompt
425 # goes to the old kernel
425 # goes to the old kernel
426 self.reset()
426 self.reset()
427
427
428 def _started_channels(self):
428 def _started_channels(self):
429 """ Called when the KernelManager channels have started listening or
429 """ Called when the KernelManager channels have started listening or
430 when the frontend is assigned an already listening KernelManager.
430 when the frontend is assigned an already listening KernelManager.
431 """
431 """
432 self.reset()
432 self.reset()
433
433
434 #---------------------------------------------------------------------------
434 #---------------------------------------------------------------------------
435 # 'FrontendWidget' public interface
435 # 'FrontendWidget' public interface
436 #---------------------------------------------------------------------------
436 #---------------------------------------------------------------------------
437
437
438 def copy_raw(self):
438 def copy_raw(self):
439 """ Copy the currently selected text to the clipboard without attempting
439 """ Copy the currently selected text to the clipboard without attempting
440 to remove prompts or otherwise alter the text.
440 to remove prompts or otherwise alter the text.
441 """
441 """
442 self._control.copy()
442 self._control.copy()
443
443
444 def execute_file(self, path, hidden=False):
444 def execute_file(self, path, hidden=False):
445 """ Attempts to execute file with 'path'. If 'hidden', no output is
445 """ Attempts to execute file with 'path'. If 'hidden', no output is
446 shown.
446 shown.
447 """
447 """
448 self.execute('execfile(%r)' % path, hidden=hidden)
448 self.execute('execfile(%r)' % path, hidden=hidden)
449
449
450 def interrupt_kernel(self):
450 def interrupt_kernel(self):
451 """ Attempts to interrupt the running kernel.
451 """ Attempts to interrupt the running kernel.
452 """
452 """
453 if self.custom_interrupt:
453 if self.custom_interrupt:
454 self.custom_interrupt_requested.emit()
454 self.custom_interrupt_requested.emit()
455 elif self.kernel_manager.has_kernel:
455 elif self.kernel_manager.has_kernel:
456 self.kernel_manager.interrupt_kernel()
456 self.kernel_manager.interrupt_kernel()
457 else:
457 else:
458 self._append_plain_text('Kernel process is either remote or '
458 self._append_plain_text('Kernel process is either remote or '
459 'unspecified. Cannot interrupt.\n')
459 'unspecified. Cannot interrupt.\n')
460
460
461 def reset(self):
461 def reset(self):
462 """ Resets the widget to its initial state. Similar to ``clear``, but
462 """ Resets the widget to its initial state. Similar to ``clear``, but
463 also re-writes the banner and aborts execution if necessary.
463 also re-writes the banner and aborts execution if necessary.
464 """
464 """
465 if self._executing:
465 if self._executing:
466 self._executing = False
466 self._executing = False
467 self._request_info['execute'] = None
467 self._request_info['execute'] = None
468 self._reading = False
468 self._reading = False
469 self._highlighter.highlighting_on = False
469 self._highlighter.highlighting_on = False
470
470
471 self._control.clear()
471 self._control.clear()
472 self._append_plain_text(self._get_banner())
472 self._append_plain_text(self._get_banner())
473 self._show_interpreter_prompt()
473 self._show_interpreter_prompt()
474
474
475 def restart_kernel(self, message, now=False):
475 def restart_kernel(self, message, now=False):
476 """ Attempts to restart the running kernel.
476 """ Attempts to restart the running kernel.
477 """
477 """
478 # FIXME: now should be configurable via a checkbox in the dialog. Right
478 # FIXME: now should be configurable via a checkbox in the dialog. Right
479 # now at least the heartbeat path sets it to True and the manual restart
479 # now at least the heartbeat path sets it to True and the manual restart
480 # to False. But those should just be the pre-selected states of a
480 # to False. But those should just be the pre-selected states of a
481 # checkbox that the user could override if so desired. But I don't know
481 # checkbox that the user could override if so desired. But I don't know
482 # enough Qt to go implementing the checkbox now.
482 # enough Qt to go implementing the checkbox now.
483
483
484 if self.custom_restart:
484 if self.custom_restart:
485 self.custom_restart_requested.emit()
485 self.custom_restart_requested.emit()
486
486
487 elif self.kernel_manager.has_kernel:
487 elif self.kernel_manager.has_kernel:
488 # Pause the heart beat channel to prevent further warnings.
488 # Pause the heart beat channel to prevent further warnings.
489 self.kernel_manager.hb_channel.pause()
489 self.kernel_manager.hb_channel.pause()
490
490
491 # Prompt the user to restart the kernel. Un-pause the heartbeat if
491 # Prompt the user to restart the kernel. Un-pause the heartbeat if
492 # they decline. (If they accept, the heartbeat will be un-paused
492 # they decline. (If they accept, the heartbeat will be un-paused
493 # automatically when the kernel is restarted.)
493 # automatically when the kernel is restarted.)
494 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
494 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
495 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
495 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
496 message, buttons)
496 message, buttons)
497 if result == QtGui.QMessageBox.Yes:
497 if result == QtGui.QMessageBox.Yes:
498 try:
498 try:
499 self.kernel_manager.restart_kernel(now=now)
499 self.kernel_manager.restart_kernel(now=now)
500 except RuntimeError:
500 except RuntimeError:
501 self._append_plain_text('Kernel started externally. '
501 self._append_plain_text('Kernel started externally. '
502 'Cannot restart.\n')
502 'Cannot restart.\n')
503 else:
503 else:
504 self.reset()
504 self.reset()
505 else:
505 else:
506 self.kernel_manager.hb_channel.unpause()
506 self.kernel_manager.hb_channel.unpause()
507
507
508 else:
508 else:
509 self._append_plain_text('Kernel process is either remote or '
509 self._append_plain_text('Kernel process is either remote or '
510 'unspecified. Cannot restart.\n')
510 'unspecified. Cannot restart.\n')
511
511
512 #---------------------------------------------------------------------------
512 #---------------------------------------------------------------------------
513 # 'FrontendWidget' protected interface
513 # 'FrontendWidget' protected interface
514 #---------------------------------------------------------------------------
514 #---------------------------------------------------------------------------
515
515
516 def _call_tip(self):
516 def _call_tip(self):
517 """ Shows a call tip, if appropriate, at the current cursor location.
517 """ Shows a call tip, if appropriate, at the current cursor location.
518 """
518 """
519 # Decide if it makes sense to show a call tip
519 # Decide if it makes sense to show a call tip
520 cursor = self._get_cursor()
520 cursor = self._get_cursor()
521 cursor.movePosition(QtGui.QTextCursor.Left)
521 cursor.movePosition(QtGui.QTextCursor.Left)
522 if cursor.document().characterAt(cursor.position()) != '(':
522 if cursor.document().characterAt(cursor.position()) != '(':
523 return False
523 return False
524 context = self._get_context(cursor)
524 context = self._get_context(cursor)
525 if not context:
525 if not context:
526 return False
526 return False
527
527
528 # Send the metadata request to the kernel
528 # Send the metadata request to the kernel
529 name = '.'.join(context)
529 name = '.'.join(context)
530 msg_id = self.kernel_manager.xreq_channel.object_info(name)
530 msg_id = self.kernel_manager.shell_channel.object_info(name)
531 pos = self._get_cursor().position()
531 pos = self._get_cursor().position()
532 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
532 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
533 return True
533 return True
534
534
535 def _complete(self):
535 def _complete(self):
536 """ Performs completion at the current cursor location.
536 """ Performs completion at the current cursor location.
537 """
537 """
538 context = self._get_context()
538 context = self._get_context()
539 if context:
539 if context:
540 # Send the completion request to the kernel
540 # Send the completion request to the kernel
541 msg_id = self.kernel_manager.xreq_channel.complete(
541 msg_id = self.kernel_manager.shell_channel.complete(
542 '.'.join(context), # text
542 '.'.join(context), # text
543 self._get_input_buffer_cursor_line(), # line
543 self._get_input_buffer_cursor_line(), # line
544 self._get_input_buffer_cursor_column(), # cursor_pos
544 self._get_input_buffer_cursor_column(), # cursor_pos
545 self.input_buffer) # block
545 self.input_buffer) # block
546 pos = self._get_cursor().position()
546 pos = self._get_cursor().position()
547 info = self._CompletionRequest(msg_id, pos)
547 info = self._CompletionRequest(msg_id, pos)
548 self._request_info['complete'] = info
548 self._request_info['complete'] = info
549
549
550 def _get_banner(self):
550 def _get_banner(self):
551 """ Gets a banner to display at the beginning of a session.
551 """ Gets a banner to display at the beginning of a session.
552 """
552 """
553 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
553 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
554 '"license" for more information.'
554 '"license" for more information.'
555 return banner % (sys.version, sys.platform)
555 return banner % (sys.version, sys.platform)
556
556
557 def _get_context(self, cursor=None):
557 def _get_context(self, cursor=None):
558 """ Gets the context for the specified cursor (or the current cursor
558 """ Gets the context for the specified cursor (or the current cursor
559 if none is specified).
559 if none is specified).
560 """
560 """
561 if cursor is None:
561 if cursor is None:
562 cursor = self._get_cursor()
562 cursor = self._get_cursor()
563 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
563 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
564 QtGui.QTextCursor.KeepAnchor)
564 QtGui.QTextCursor.KeepAnchor)
565 text = cursor.selection().toPlainText()
565 text = cursor.selection().toPlainText()
566 return self._completion_lexer.get_context(text)
566 return self._completion_lexer.get_context(text)
567
567
568 def _process_execute_abort(self, msg):
568 def _process_execute_abort(self, msg):
569 """ Process a reply for an aborted execution request.
569 """ Process a reply for an aborted execution request.
570 """
570 """
571 self._append_plain_text("ERROR: execution aborted\n")
571 self._append_plain_text("ERROR: execution aborted\n")
572
572
573 def _process_execute_error(self, msg):
573 def _process_execute_error(self, msg):
574 """ Process a reply for an execution request that resulted in an error.
574 """ Process a reply for an execution request that resulted in an error.
575 """
575 """
576 content = msg['content']
576 content = msg['content']
577 # If a SystemExit is passed along, this means exit() was called - also
577 # If a SystemExit is passed along, this means exit() was called - also
578 # all the ipython %exit magic syntax of '-k' to be used to keep
578 # all the ipython %exit magic syntax of '-k' to be used to keep
579 # the kernel running
579 # the kernel running
580 if content['ename']=='SystemExit':
580 if content['ename']=='SystemExit':
581 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
581 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
582 self._keep_kernel_on_exit = keepkernel
582 self._keep_kernel_on_exit = keepkernel
583 self.exit_requested.emit()
583 self.exit_requested.emit()
584 else:
584 else:
585 traceback = ''.join(content['traceback'])
585 traceback = ''.join(content['traceback'])
586 self._append_plain_text(traceback)
586 self._append_plain_text(traceback)
587
587
588 def _process_execute_ok(self, msg):
588 def _process_execute_ok(self, msg):
589 """ Process a reply for a successful execution equest.
589 """ Process a reply for a successful execution equest.
590 """
590 """
591 payload = msg['content']['payload']
591 payload = msg['content']['payload']
592 for item in payload:
592 for item in payload:
593 if not self._process_execute_payload(item):
593 if not self._process_execute_payload(item):
594 warning = 'Warning: received unknown payload of type %s'
594 warning = 'Warning: received unknown payload of type %s'
595 print(warning % repr(item['source']))
595 print(warning % repr(item['source']))
596
596
597 def _process_execute_payload(self, item):
597 def _process_execute_payload(self, item):
598 """ Process a single payload item from the list of payload items in an
598 """ Process a single payload item from the list of payload items in an
599 execution reply. Returns whether the payload was handled.
599 execution reply. Returns whether the payload was handled.
600 """
600 """
601 # The basic FrontendWidget doesn't handle payloads, as they are a
601 # The basic FrontendWidget doesn't handle payloads, as they are a
602 # mechanism for going beyond the standard Python interpreter model.
602 # mechanism for going beyond the standard Python interpreter model.
603 return False
603 return False
604
604
605 def _show_interpreter_prompt(self):
605 def _show_interpreter_prompt(self):
606 """ Shows a prompt for the interpreter.
606 """ Shows a prompt for the interpreter.
607 """
607 """
608 self._show_prompt('>>> ')
608 self._show_prompt('>>> ')
609
609
610 def _show_interpreter_prompt_for_reply(self, msg):
610 def _show_interpreter_prompt_for_reply(self, msg):
611 """ Shows a prompt for the interpreter given an 'execute_reply' message.
611 """ Shows a prompt for the interpreter given an 'execute_reply' message.
612 """
612 """
613 self._show_interpreter_prompt()
613 self._show_interpreter_prompt()
614
614
615 #------ Signal handlers ----------------------------------------------------
615 #------ Signal handlers ----------------------------------------------------
616
616
617 def _document_contents_change(self, position, removed, added):
617 def _document_contents_change(self, position, removed, added):
618 """ Called whenever the document's content changes. Display a call tip
618 """ Called whenever the document's content changes. Display a call tip
619 if appropriate.
619 if appropriate.
620 """
620 """
621 # Calculate where the cursor should be *after* the change:
621 # Calculate where the cursor should be *after* the change:
622 position += added
622 position += added
623
623
624 document = self._control.document()
624 document = self._control.document()
625 if position == self._get_cursor().position():
625 if position == self._get_cursor().position():
626 self._call_tip()
626 self._call_tip()
@@ -1,503 +1,503 b''
1 """ A FrontendWidget that emulates the interface of the console IPython and
1 """ A FrontendWidget that emulates the interface of the console IPython and
2 supports the additional functionality provided by the IPython kernel.
2 supports the additional functionality provided by the IPython kernel.
3 """
3 """
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Imports
6 # Imports
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8
8
9 # Standard library imports
9 # Standard library imports
10 from collections import namedtuple
10 from collections import namedtuple
11 import os.path
11 import os.path
12 import re
12 import re
13 from subprocess import Popen
13 from subprocess import Popen
14 import sys
14 import sys
15 from textwrap import dedent
15 from textwrap import dedent
16
16
17 # System library imports
17 # System library imports
18 from IPython.external.qt import QtCore, QtGui
18 from IPython.external.qt import QtCore, QtGui
19
19
20 # Local imports
20 # Local imports
21 from IPython.core.inputsplitter import IPythonInputSplitter, \
21 from IPython.core.inputsplitter import IPythonInputSplitter, \
22 transform_ipy_prompt
22 transform_ipy_prompt
23 from IPython.core.usage import default_gui_banner
23 from IPython.core.usage import default_gui_banner
24 from IPython.utils.traitlets import Bool, Str, Unicode
24 from IPython.utils.traitlets import Bool, Str, Unicode
25 from frontend_widget import FrontendWidget
25 from frontend_widget import FrontendWidget
26 import styles
26 import styles
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Constants
29 # Constants
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31
31
32 # Default strings to build and display input and output prompts (and separators
32 # Default strings to build and display input and output prompts (and separators
33 # in between)
33 # in between)
34 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
34 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
35 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
35 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
36 default_input_sep = '\n'
36 default_input_sep = '\n'
37 default_output_sep = ''
37 default_output_sep = ''
38 default_output_sep2 = ''
38 default_output_sep2 = ''
39
39
40 # Base path for most payload sources.
40 # Base path for most payload sources.
41 zmq_shell_source = 'IPython.zmq.zmqshell.ZMQInteractiveShell'
41 zmq_shell_source = 'IPython.zmq.zmqshell.ZMQInteractiveShell'
42
42
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44 # IPythonWidget class
44 # IPythonWidget class
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47 class IPythonWidget(FrontendWidget):
47 class IPythonWidget(FrontendWidget):
48 """ A FrontendWidget for an IPython kernel.
48 """ A FrontendWidget for an IPython kernel.
49 """
49 """
50
50
51 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
51 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
52 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
52 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
53 # settings.
53 # settings.
54 custom_edit = Bool(False)
54 custom_edit = Bool(False)
55 custom_edit_requested = QtCore.Signal(object, object)
55 custom_edit_requested = QtCore.Signal(object, object)
56
56
57 editor = Unicode('default', config=True,
57 editor = Unicode('default', config=True,
58 help="""
58 help="""
59 A command for invoking a system text editor. If the string contains a
59 A command for invoking a system text editor. If the string contains a
60 {filename} format specifier, it will be used. Otherwise, the filename will
60 {filename} format specifier, it will be used. Otherwise, the filename will
61 be appended to the end the command.
61 be appended to the end the command.
62 """)
62 """)
63
63
64 editor_line = Unicode(config=True,
64 editor_line = Unicode(config=True,
65 help="""
65 help="""
66 The editor command to use when a specific line number is requested. The
66 The editor command to use when a specific line number is requested. The
67 string should contain two format specifiers: {line} and {filename}. If
67 string should contain two format specifiers: {line} and {filename}. If
68 this parameter is not specified, the line number option to the %edit magic
68 this parameter is not specified, the line number option to the %edit magic
69 will be ignored.
69 will be ignored.
70 """)
70 """)
71
71
72 style_sheet = Unicode(config=True,
72 style_sheet = Unicode(config=True,
73 help="""
73 help="""
74 A CSS stylesheet. The stylesheet can contain classes for:
74 A CSS stylesheet. The stylesheet can contain classes for:
75 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
75 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
76 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
76 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
77 3. IPython: .error, .in-prompt, .out-prompt, etc
77 3. IPython: .error, .in-prompt, .out-prompt, etc
78 """)
78 """)
79
79
80
80
81 syntax_style = Str(config=True,
81 syntax_style = Str(config=True,
82 help="""
82 help="""
83 If not empty, use this Pygments style for syntax highlighting. Otherwise,
83 If not empty, use this Pygments style for syntax highlighting. Otherwise,
84 the style sheet is queried for Pygments style information.
84 the style sheet is queried for Pygments style information.
85 """)
85 """)
86
86
87 # Prompts.
87 # Prompts.
88 in_prompt = Str(default_in_prompt, config=True)
88 in_prompt = Str(default_in_prompt, config=True)
89 out_prompt = Str(default_out_prompt, config=True)
89 out_prompt = Str(default_out_prompt, config=True)
90 input_sep = Str(default_input_sep, config=True)
90 input_sep = Str(default_input_sep, config=True)
91 output_sep = Str(default_output_sep, config=True)
91 output_sep = Str(default_output_sep, config=True)
92 output_sep2 = Str(default_output_sep2, config=True)
92 output_sep2 = Str(default_output_sep2, config=True)
93
93
94 # FrontendWidget protected class variables.
94 # FrontendWidget protected class variables.
95 _input_splitter_class = IPythonInputSplitter
95 _input_splitter_class = IPythonInputSplitter
96
96
97 # IPythonWidget protected class variables.
97 # IPythonWidget protected class variables.
98 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
98 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
99 _payload_source_edit = zmq_shell_source + '.edit_magic'
99 _payload_source_edit = zmq_shell_source + '.edit_magic'
100 _payload_source_exit = zmq_shell_source + '.ask_exit'
100 _payload_source_exit = zmq_shell_source + '.ask_exit'
101 _payload_source_next_input = zmq_shell_source + '.set_next_input'
101 _payload_source_next_input = zmq_shell_source + '.set_next_input'
102 _payload_source_page = 'IPython.zmq.page.page'
102 _payload_source_page = 'IPython.zmq.page.page'
103
103
104 #---------------------------------------------------------------------------
104 #---------------------------------------------------------------------------
105 # 'object' interface
105 # 'object' interface
106 #---------------------------------------------------------------------------
106 #---------------------------------------------------------------------------
107
107
108 def __init__(self, *args, **kw):
108 def __init__(self, *args, **kw):
109 super(IPythonWidget, self).__init__(*args, **kw)
109 super(IPythonWidget, self).__init__(*args, **kw)
110
110
111 # IPythonWidget protected variables.
111 # IPythonWidget protected variables.
112 self._payload_handlers = {
112 self._payload_handlers = {
113 self._payload_source_edit : self._handle_payload_edit,
113 self._payload_source_edit : self._handle_payload_edit,
114 self._payload_source_exit : self._handle_payload_exit,
114 self._payload_source_exit : self._handle_payload_exit,
115 self._payload_source_page : self._handle_payload_page,
115 self._payload_source_page : self._handle_payload_page,
116 self._payload_source_next_input : self._handle_payload_next_input }
116 self._payload_source_next_input : self._handle_payload_next_input }
117 self._previous_prompt_obj = None
117 self._previous_prompt_obj = None
118 self._keep_kernel_on_exit = None
118 self._keep_kernel_on_exit = None
119
119
120 # Initialize widget styling.
120 # Initialize widget styling.
121 if self.style_sheet:
121 if self.style_sheet:
122 self._style_sheet_changed()
122 self._style_sheet_changed()
123 self._syntax_style_changed()
123 self._syntax_style_changed()
124 else:
124 else:
125 self.set_default_style()
125 self.set_default_style()
126
126
127 #---------------------------------------------------------------------------
127 #---------------------------------------------------------------------------
128 # 'BaseFrontendMixin' abstract interface
128 # 'BaseFrontendMixin' abstract interface
129 #---------------------------------------------------------------------------
129 #---------------------------------------------------------------------------
130
130
131 def _handle_complete_reply(self, rep):
131 def _handle_complete_reply(self, rep):
132 """ Reimplemented to support IPython's improved completion machinery.
132 """ Reimplemented to support IPython's improved completion machinery.
133 """
133 """
134 cursor = self._get_cursor()
134 cursor = self._get_cursor()
135 info = self._request_info.get('complete')
135 info = self._request_info.get('complete')
136 if info and info.id == rep['parent_header']['msg_id'] and \
136 if info and info.id == rep['parent_header']['msg_id'] and \
137 info.pos == cursor.position():
137 info.pos == cursor.position():
138 matches = rep['content']['matches']
138 matches = rep['content']['matches']
139 text = rep['content']['matched_text']
139 text = rep['content']['matched_text']
140 offset = len(text)
140 offset = len(text)
141
141
142 # Clean up matches with period and path separators if the matched
142 # Clean up matches with period and path separators if the matched
143 # text has not been transformed. This is done by truncating all
143 # text has not been transformed. This is done by truncating all
144 # but the last component and then suitably decreasing the offset
144 # but the last component and then suitably decreasing the offset
145 # between the current cursor position and the start of completion.
145 # between the current cursor position and the start of completion.
146 if len(matches) > 1 and matches[0][:offset] == text:
146 if len(matches) > 1 and matches[0][:offset] == text:
147 parts = re.split(r'[./\\]', text)
147 parts = re.split(r'[./\\]', text)
148 sep_count = len(parts) - 1
148 sep_count = len(parts) - 1
149 if sep_count:
149 if sep_count:
150 chop_length = sum(map(len, parts[:sep_count])) + sep_count
150 chop_length = sum(map(len, parts[:sep_count])) + sep_count
151 matches = [ match[chop_length:] for match in matches ]
151 matches = [ match[chop_length:] for match in matches ]
152 offset -= chop_length
152 offset -= chop_length
153
153
154 # Move the cursor to the start of the match and complete.
154 # Move the cursor to the start of the match and complete.
155 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
155 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
156 self._complete_with_items(cursor, matches)
156 self._complete_with_items(cursor, matches)
157
157
158 def _handle_execute_reply(self, msg):
158 def _handle_execute_reply(self, msg):
159 """ Reimplemented to support prompt requests.
159 """ Reimplemented to support prompt requests.
160 """
160 """
161 info = self._request_info.get('execute')
161 info = self._request_info.get('execute')
162 if info and info.id == msg['parent_header']['msg_id']:
162 if info and info.id == msg['parent_header']['msg_id']:
163 if info.kind == 'prompt':
163 if info.kind == 'prompt':
164 number = msg['content']['execution_count'] + 1
164 number = msg['content']['execution_count'] + 1
165 self._show_interpreter_prompt(number)
165 self._show_interpreter_prompt(number)
166 else:
166 else:
167 super(IPythonWidget, self)._handle_execute_reply(msg)
167 super(IPythonWidget, self)._handle_execute_reply(msg)
168
168
169 def _handle_history_reply(self, msg):
169 def _handle_history_reply(self, msg):
170 """ Implemented to handle history tail replies, which are only supported
170 """ Implemented to handle history tail replies, which are only supported
171 by the IPython kernel.
171 by the IPython kernel.
172 """
172 """
173 history_items = msg['content']['history']
173 history_items = msg['content']['history']
174 items = [ line.rstrip() for _, _, line in history_items ]
174 items = [ line.rstrip() for _, _, line in history_items ]
175 self._set_history(items)
175 self._set_history(items)
176
176
177 def _handle_pyout(self, msg):
177 def _handle_pyout(self, msg):
178 """ Reimplemented for IPython-style "display hook".
178 """ Reimplemented for IPython-style "display hook".
179 """
179 """
180 if not self._hidden and self._is_from_this_session(msg):
180 if not self._hidden and self._is_from_this_session(msg):
181 content = msg['content']
181 content = msg['content']
182 prompt_number = content['execution_count']
182 prompt_number = content['execution_count']
183 data = content['data']
183 data = content['data']
184 if data.has_key('text/html'):
184 if data.has_key('text/html'):
185 self._append_plain_text(self.output_sep)
185 self._append_plain_text(self.output_sep)
186 self._append_html(self._make_out_prompt(prompt_number))
186 self._append_html(self._make_out_prompt(prompt_number))
187 html = data['text/html']
187 html = data['text/html']
188 self._append_plain_text('\n')
188 self._append_plain_text('\n')
189 self._append_html(html + self.output_sep2)
189 self._append_html(html + self.output_sep2)
190 elif data.has_key('text/plain'):
190 elif data.has_key('text/plain'):
191 self._append_plain_text(self.output_sep)
191 self._append_plain_text(self.output_sep)
192 self._append_html(self._make_out_prompt(prompt_number))
192 self._append_html(self._make_out_prompt(prompt_number))
193 text = data['text/plain']
193 text = data['text/plain']
194 # If the repr is multiline, make sure we start on a new line,
194 # If the repr is multiline, make sure we start on a new line,
195 # so that its lines are aligned.
195 # so that its lines are aligned.
196 if "\n" in text and not self.output_sep.endswith("\n"):
196 if "\n" in text and not self.output_sep.endswith("\n"):
197 self._append_plain_text('\n')
197 self._append_plain_text('\n')
198 self._append_plain_text(text + self.output_sep2)
198 self._append_plain_text(text + self.output_sep2)
199
199
200 def _handle_display_data(self, msg):
200 def _handle_display_data(self, msg):
201 """ The base handler for the ``display_data`` message.
201 """ The base handler for the ``display_data`` message.
202 """
202 """
203 # For now, we don't display data from other frontends, but we
203 # For now, we don't display data from other frontends, but we
204 # eventually will as this allows all frontends to monitor the display
204 # eventually will as this allows all frontends to monitor the display
205 # data. But we need to figure out how to handle this in the GUI.
205 # data. But we need to figure out how to handle this in the GUI.
206 if not self._hidden and self._is_from_this_session(msg):
206 if not self._hidden and self._is_from_this_session(msg):
207 source = msg['content']['source']
207 source = msg['content']['source']
208 data = msg['content']['data']
208 data = msg['content']['data']
209 metadata = msg['content']['metadata']
209 metadata = msg['content']['metadata']
210 # In the regular IPythonWidget, we simply print the plain text
210 # In the regular IPythonWidget, we simply print the plain text
211 # representation.
211 # representation.
212 if data.has_key('text/html'):
212 if data.has_key('text/html'):
213 html = data['text/html']
213 html = data['text/html']
214 self._append_html(html)
214 self._append_html(html)
215 elif data.has_key('text/plain'):
215 elif data.has_key('text/plain'):
216 text = data['text/plain']
216 text = data['text/plain']
217 self._append_plain_text(text)
217 self._append_plain_text(text)
218 # This newline seems to be needed for text and html output.
218 # This newline seems to be needed for text and html output.
219 self._append_plain_text(u'\n')
219 self._append_plain_text(u'\n')
220
220
221 def _started_channels(self):
221 def _started_channels(self):
222 """ Reimplemented to make a history request.
222 """ Reimplemented to make a history request.
223 """
223 """
224 super(IPythonWidget, self)._started_channels()
224 super(IPythonWidget, self)._started_channels()
225 self.kernel_manager.xreq_channel.history(hist_access_type='tail', n=1000)
225 self.kernel_manager.shell_channel.history(hist_access_type='tail', n=1000)
226
226
227 #---------------------------------------------------------------------------
227 #---------------------------------------------------------------------------
228 # 'ConsoleWidget' public interface
228 # 'ConsoleWidget' public interface
229 #---------------------------------------------------------------------------
229 #---------------------------------------------------------------------------
230
230
231 def copy(self):
231 def copy(self):
232 """ Copy the currently selected text to the clipboard, removing prompts
232 """ Copy the currently selected text to the clipboard, removing prompts
233 if possible.
233 if possible.
234 """
234 """
235 text = self._control.textCursor().selection().toPlainText()
235 text = self._control.textCursor().selection().toPlainText()
236 if text:
236 if text:
237 lines = map(transform_ipy_prompt, text.splitlines())
237 lines = map(transform_ipy_prompt, text.splitlines())
238 text = '\n'.join(lines)
238 text = '\n'.join(lines)
239 QtGui.QApplication.clipboard().setText(text)
239 QtGui.QApplication.clipboard().setText(text)
240
240
241 #---------------------------------------------------------------------------
241 #---------------------------------------------------------------------------
242 # 'FrontendWidget' public interface
242 # 'FrontendWidget' public interface
243 #---------------------------------------------------------------------------
243 #---------------------------------------------------------------------------
244
244
245 def execute_file(self, path, hidden=False):
245 def execute_file(self, path, hidden=False):
246 """ Reimplemented to use the 'run' magic.
246 """ Reimplemented to use the 'run' magic.
247 """
247 """
248 # Use forward slashes on Windows to avoid escaping each separator.
248 # Use forward slashes on Windows to avoid escaping each separator.
249 if sys.platform == 'win32':
249 if sys.platform == 'win32':
250 path = os.path.normpath(path).replace('\\', '/')
250 path = os.path.normpath(path).replace('\\', '/')
251
251
252 self.execute('%%run %s' % path, hidden=hidden)
252 self.execute('%%run %s' % path, hidden=hidden)
253
253
254 #---------------------------------------------------------------------------
254 #---------------------------------------------------------------------------
255 # 'FrontendWidget' protected interface
255 # 'FrontendWidget' protected interface
256 #---------------------------------------------------------------------------
256 #---------------------------------------------------------------------------
257
257
258 def _complete(self):
258 def _complete(self):
259 """ Reimplemented to support IPython's improved completion machinery.
259 """ Reimplemented to support IPython's improved completion machinery.
260 """
260 """
261 # We let the kernel split the input line, so we *always* send an empty
261 # We let the kernel split the input line, so we *always* send an empty
262 # text field. Readline-based frontends do get a real text field which
262 # text field. Readline-based frontends do get a real text field which
263 # they can use.
263 # they can use.
264 text = ''
264 text = ''
265
265
266 # Send the completion request to the kernel
266 # Send the completion request to the kernel
267 msg_id = self.kernel_manager.xreq_channel.complete(
267 msg_id = self.kernel_manager.shell_channel.complete(
268 text, # text
268 text, # text
269 self._get_input_buffer_cursor_line(), # line
269 self._get_input_buffer_cursor_line(), # line
270 self._get_input_buffer_cursor_column(), # cursor_pos
270 self._get_input_buffer_cursor_column(), # cursor_pos
271 self.input_buffer) # block
271 self.input_buffer) # block
272 pos = self._get_cursor().position()
272 pos = self._get_cursor().position()
273 info = self._CompletionRequest(msg_id, pos)
273 info = self._CompletionRequest(msg_id, pos)
274 self._request_info['complete'] = info
274 self._request_info['complete'] = info
275
275
276 def _get_banner(self):
276 def _get_banner(self):
277 """ Reimplemented to return IPython's default banner.
277 """ Reimplemented to return IPython's default banner.
278 """
278 """
279 return default_gui_banner
279 return default_gui_banner
280
280
281 def _process_execute_error(self, msg):
281 def _process_execute_error(self, msg):
282 """ Reimplemented for IPython-style traceback formatting.
282 """ Reimplemented for IPython-style traceback formatting.
283 """
283 """
284 content = msg['content']
284 content = msg['content']
285 traceback = '\n'.join(content['traceback']) + '\n'
285 traceback = '\n'.join(content['traceback']) + '\n'
286 if False:
286 if False:
287 # FIXME: For now, tracebacks come as plain text, so we can't use
287 # FIXME: For now, tracebacks come as plain text, so we can't use
288 # the html renderer yet. Once we refactor ultratb to produce
288 # the html renderer yet. Once we refactor ultratb to produce
289 # properly styled tracebacks, this branch should be the default
289 # properly styled tracebacks, this branch should be the default
290 traceback = traceback.replace(' ', '&nbsp;')
290 traceback = traceback.replace(' ', '&nbsp;')
291 traceback = traceback.replace('\n', '<br/>')
291 traceback = traceback.replace('\n', '<br/>')
292
292
293 ename = content['ename']
293 ename = content['ename']
294 ename_styled = '<span class="error">%s</span>' % ename
294 ename_styled = '<span class="error">%s</span>' % ename
295 traceback = traceback.replace(ename, ename_styled)
295 traceback = traceback.replace(ename, ename_styled)
296
296
297 self._append_html(traceback)
297 self._append_html(traceback)
298 else:
298 else:
299 # This is the fallback for now, using plain text with ansi escapes
299 # This is the fallback for now, using plain text with ansi escapes
300 self._append_plain_text(traceback)
300 self._append_plain_text(traceback)
301
301
302 def _process_execute_payload(self, item):
302 def _process_execute_payload(self, item):
303 """ Reimplemented to dispatch payloads to handler methods.
303 """ Reimplemented to dispatch payloads to handler methods.
304 """
304 """
305 handler = self._payload_handlers.get(item['source'])
305 handler = self._payload_handlers.get(item['source'])
306 if handler is None:
306 if handler is None:
307 # We have no handler for this type of payload, simply ignore it
307 # We have no handler for this type of payload, simply ignore it
308 return False
308 return False
309 else:
309 else:
310 handler(item)
310 handler(item)
311 return True
311 return True
312
312
313 def _show_interpreter_prompt(self, number=None):
313 def _show_interpreter_prompt(self, number=None):
314 """ Reimplemented for IPython-style prompts.
314 """ Reimplemented for IPython-style prompts.
315 """
315 """
316 # If a number was not specified, make a prompt number request.
316 # If a number was not specified, make a prompt number request.
317 if number is None:
317 if number is None:
318 msg_id = self.kernel_manager.xreq_channel.execute('', silent=True)
318 msg_id = self.kernel_manager.shell_channel.execute('', silent=True)
319 info = self._ExecutionRequest(msg_id, 'prompt')
319 info = self._ExecutionRequest(msg_id, 'prompt')
320 self._request_info['execute'] = info
320 self._request_info['execute'] = info
321 return
321 return
322
322
323 # Show a new prompt and save information about it so that it can be
323 # Show a new prompt and save information about it so that it can be
324 # updated later if the prompt number turns out to be wrong.
324 # updated later if the prompt number turns out to be wrong.
325 self._prompt_sep = self.input_sep
325 self._prompt_sep = self.input_sep
326 self._show_prompt(self._make_in_prompt(number), html=True)
326 self._show_prompt(self._make_in_prompt(number), html=True)
327 block = self._control.document().lastBlock()
327 block = self._control.document().lastBlock()
328 length = len(self._prompt)
328 length = len(self._prompt)
329 self._previous_prompt_obj = self._PromptBlock(block, length, number)
329 self._previous_prompt_obj = self._PromptBlock(block, length, number)
330
330
331 # Update continuation prompt to reflect (possibly) new prompt length.
331 # Update continuation prompt to reflect (possibly) new prompt length.
332 self._set_continuation_prompt(
332 self._set_continuation_prompt(
333 self._make_continuation_prompt(self._prompt), html=True)
333 self._make_continuation_prompt(self._prompt), html=True)
334
334
335 def _show_interpreter_prompt_for_reply(self, msg):
335 def _show_interpreter_prompt_for_reply(self, msg):
336 """ Reimplemented for IPython-style prompts.
336 """ Reimplemented for IPython-style prompts.
337 """
337 """
338 # Update the old prompt number if necessary.
338 # Update the old prompt number if necessary.
339 content = msg['content']
339 content = msg['content']
340 previous_prompt_number = content['execution_count']
340 previous_prompt_number = content['execution_count']
341 if self._previous_prompt_obj and \
341 if self._previous_prompt_obj and \
342 self._previous_prompt_obj.number != previous_prompt_number:
342 self._previous_prompt_obj.number != previous_prompt_number:
343 block = self._previous_prompt_obj.block
343 block = self._previous_prompt_obj.block
344
344
345 # Make sure the prompt block has not been erased.
345 # Make sure the prompt block has not been erased.
346 if block.isValid() and block.text():
346 if block.isValid() and block.text():
347
347
348 # Remove the old prompt and insert a new prompt.
348 # Remove the old prompt and insert a new prompt.
349 cursor = QtGui.QTextCursor(block)
349 cursor = QtGui.QTextCursor(block)
350 cursor.movePosition(QtGui.QTextCursor.Right,
350 cursor.movePosition(QtGui.QTextCursor.Right,
351 QtGui.QTextCursor.KeepAnchor,
351 QtGui.QTextCursor.KeepAnchor,
352 self._previous_prompt_obj.length)
352 self._previous_prompt_obj.length)
353 prompt = self._make_in_prompt(previous_prompt_number)
353 prompt = self._make_in_prompt(previous_prompt_number)
354 self._prompt = self._insert_html_fetching_plain_text(
354 self._prompt = self._insert_html_fetching_plain_text(
355 cursor, prompt)
355 cursor, prompt)
356
356
357 # When the HTML is inserted, Qt blows away the syntax
357 # When the HTML is inserted, Qt blows away the syntax
358 # highlighting for the line, so we need to rehighlight it.
358 # highlighting for the line, so we need to rehighlight it.
359 self._highlighter.rehighlightBlock(cursor.block())
359 self._highlighter.rehighlightBlock(cursor.block())
360
360
361 self._previous_prompt_obj = None
361 self._previous_prompt_obj = None
362
362
363 # Show a new prompt with the kernel's estimated prompt number.
363 # Show a new prompt with the kernel's estimated prompt number.
364 self._show_interpreter_prompt(previous_prompt_number + 1)
364 self._show_interpreter_prompt(previous_prompt_number + 1)
365
365
366 #---------------------------------------------------------------------------
366 #---------------------------------------------------------------------------
367 # 'IPythonWidget' interface
367 # 'IPythonWidget' interface
368 #---------------------------------------------------------------------------
368 #---------------------------------------------------------------------------
369
369
370 def set_default_style(self, colors='lightbg'):
370 def set_default_style(self, colors='lightbg'):
371 """ Sets the widget style to the class defaults.
371 """ Sets the widget style to the class defaults.
372
372
373 Parameters:
373 Parameters:
374 -----------
374 -----------
375 colors : str, optional (default lightbg)
375 colors : str, optional (default lightbg)
376 Whether to use the default IPython light background or dark
376 Whether to use the default IPython light background or dark
377 background or B&W style.
377 background or B&W style.
378 """
378 """
379 colors = colors.lower()
379 colors = colors.lower()
380 if colors=='lightbg':
380 if colors=='lightbg':
381 self.style_sheet = styles.default_light_style_sheet
381 self.style_sheet = styles.default_light_style_sheet
382 self.syntax_style = styles.default_light_syntax_style
382 self.syntax_style = styles.default_light_syntax_style
383 elif colors=='linux':
383 elif colors=='linux':
384 self.style_sheet = styles.default_dark_style_sheet
384 self.style_sheet = styles.default_dark_style_sheet
385 self.syntax_style = styles.default_dark_syntax_style
385 self.syntax_style = styles.default_dark_syntax_style
386 elif colors=='nocolor':
386 elif colors=='nocolor':
387 self.style_sheet = styles.default_bw_style_sheet
387 self.style_sheet = styles.default_bw_style_sheet
388 self.syntax_style = styles.default_bw_syntax_style
388 self.syntax_style = styles.default_bw_syntax_style
389 else:
389 else:
390 raise KeyError("No such color scheme: %s"%colors)
390 raise KeyError("No such color scheme: %s"%colors)
391
391
392 #---------------------------------------------------------------------------
392 #---------------------------------------------------------------------------
393 # 'IPythonWidget' protected interface
393 # 'IPythonWidget' protected interface
394 #---------------------------------------------------------------------------
394 #---------------------------------------------------------------------------
395
395
396 def _edit(self, filename, line=None):
396 def _edit(self, filename, line=None):
397 """ Opens a Python script for editing.
397 """ Opens a Python script for editing.
398
398
399 Parameters:
399 Parameters:
400 -----------
400 -----------
401 filename : str
401 filename : str
402 A path to a local system file.
402 A path to a local system file.
403
403
404 line : int, optional
404 line : int, optional
405 A line of interest in the file.
405 A line of interest in the file.
406 """
406 """
407 if self.custom_edit:
407 if self.custom_edit:
408 self.custom_edit_requested.emit(filename, line)
408 self.custom_edit_requested.emit(filename, line)
409 elif self.editor == 'default':
409 elif self.editor == 'default':
410 self._append_plain_text('No default editor available.\n')
410 self._append_plain_text('No default editor available.\n')
411 else:
411 else:
412 try:
412 try:
413 filename = '"%s"' % filename
413 filename = '"%s"' % filename
414 if line and self.editor_line:
414 if line and self.editor_line:
415 command = self.editor_line.format(filename=filename,
415 command = self.editor_line.format(filename=filename,
416 line=line)
416 line=line)
417 else:
417 else:
418 try:
418 try:
419 command = self.editor.format()
419 command = self.editor.format()
420 except KeyError:
420 except KeyError:
421 command = self.editor.format(filename=filename)
421 command = self.editor.format(filename=filename)
422 else:
422 else:
423 command += ' ' + filename
423 command += ' ' + filename
424 except KeyError:
424 except KeyError:
425 self._append_plain_text('Invalid editor command.\n')
425 self._append_plain_text('Invalid editor command.\n')
426 else:
426 else:
427 try:
427 try:
428 Popen(command, shell=True)
428 Popen(command, shell=True)
429 except OSError:
429 except OSError:
430 msg = 'Opening editor with command "%s" failed.\n'
430 msg = 'Opening editor with command "%s" failed.\n'
431 self._append_plain_text(msg % command)
431 self._append_plain_text(msg % command)
432
432
433 def _make_in_prompt(self, number):
433 def _make_in_prompt(self, number):
434 """ Given a prompt number, returns an HTML In prompt.
434 """ Given a prompt number, returns an HTML In prompt.
435 """
435 """
436 body = self.in_prompt % number
436 body = self.in_prompt % number
437 return '<span class="in-prompt">%s</span>' % body
437 return '<span class="in-prompt">%s</span>' % body
438
438
439 def _make_continuation_prompt(self, prompt):
439 def _make_continuation_prompt(self, prompt):
440 """ Given a plain text version of an In prompt, returns an HTML
440 """ Given a plain text version of an In prompt, returns an HTML
441 continuation prompt.
441 continuation prompt.
442 """
442 """
443 end_chars = '...: '
443 end_chars = '...: '
444 space_count = len(prompt.lstrip('\n')) - len(end_chars)
444 space_count = len(prompt.lstrip('\n')) - len(end_chars)
445 body = '&nbsp;' * space_count + end_chars
445 body = '&nbsp;' * space_count + end_chars
446 return '<span class="in-prompt">%s</span>' % body
446 return '<span class="in-prompt">%s</span>' % body
447
447
448 def _make_out_prompt(self, number):
448 def _make_out_prompt(self, number):
449 """ Given a prompt number, returns an HTML Out prompt.
449 """ Given a prompt number, returns an HTML Out prompt.
450 """
450 """
451 body = self.out_prompt % number
451 body = self.out_prompt % number
452 return '<span class="out-prompt">%s</span>' % body
452 return '<span class="out-prompt">%s</span>' % body
453
453
454 #------ Payload handlers --------------------------------------------------
454 #------ Payload handlers --------------------------------------------------
455
455
456 # Payload handlers with a generic interface: each takes the opaque payload
456 # Payload handlers with a generic interface: each takes the opaque payload
457 # dict, unpacks it and calls the underlying functions with the necessary
457 # dict, unpacks it and calls the underlying functions with the necessary
458 # arguments.
458 # arguments.
459
459
460 def _handle_payload_edit(self, item):
460 def _handle_payload_edit(self, item):
461 self._edit(item['filename'], item['line_number'])
461 self._edit(item['filename'], item['line_number'])
462
462
463 def _handle_payload_exit(self, item):
463 def _handle_payload_exit(self, item):
464 self._keep_kernel_on_exit = item['keepkernel']
464 self._keep_kernel_on_exit = item['keepkernel']
465 self.exit_requested.emit()
465 self.exit_requested.emit()
466
466
467 def _handle_payload_next_input(self, item):
467 def _handle_payload_next_input(self, item):
468 self.input_buffer = dedent(item['text'].rstrip())
468 self.input_buffer = dedent(item['text'].rstrip())
469
469
470 def _handle_payload_page(self, item):
470 def _handle_payload_page(self, item):
471 # Since the plain text widget supports only a very small subset of HTML
471 # Since the plain text widget supports only a very small subset of HTML
472 # and we have no control over the HTML source, we only page HTML
472 # and we have no control over the HTML source, we only page HTML
473 # payloads in the rich text widget.
473 # payloads in the rich text widget.
474 if item['html'] and self.kind == 'rich':
474 if item['html'] and self.kind == 'rich':
475 self._page(item['html'], html=True)
475 self._page(item['html'], html=True)
476 else:
476 else:
477 self._page(item['text'], html=False)
477 self._page(item['text'], html=False)
478
478
479 #------ Trait change handlers --------------------------------------------
479 #------ Trait change handlers --------------------------------------------
480
480
481 def _style_sheet_changed(self):
481 def _style_sheet_changed(self):
482 """ Set the style sheets of the underlying widgets.
482 """ Set the style sheets of the underlying widgets.
483 """
483 """
484 self.setStyleSheet(self.style_sheet)
484 self.setStyleSheet(self.style_sheet)
485 self._control.document().setDefaultStyleSheet(self.style_sheet)
485 self._control.document().setDefaultStyleSheet(self.style_sheet)
486 if self._page_control:
486 if self._page_control:
487 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
487 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
488
488
489 bg_color = self._control.palette().window().color()
489 bg_color = self._control.palette().window().color()
490 self._ansi_processor.set_background_color(bg_color)
490 self._ansi_processor.set_background_color(bg_color)
491
491
492
492
493 def _syntax_style_changed(self):
493 def _syntax_style_changed(self):
494 """ Set the style for the syntax highlighter.
494 """ Set the style for the syntax highlighter.
495 """
495 """
496 if self._highlighter is None:
496 if self._highlighter is None:
497 # ignore premature calls
497 # ignore premature calls
498 return
498 return
499 if self.syntax_style:
499 if self.syntax_style:
500 self._highlighter.set_style(self.syntax_style)
500 self._highlighter.set_style(self.syntax_style)
501 else:
501 else:
502 self._highlighter.set_style_sheet(self.style_sheet)
502 self._highlighter.set_style_sheet(self.style_sheet)
503
503
@@ -1,372 +1,372 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 # stdlib imports
8 # stdlib imports
9 import os
9 import os
10 import signal
10 import signal
11 import sys
11 import sys
12
12
13 # System library imports
13 # System library imports
14 from IPython.external.qt import QtGui
14 from IPython.external.qt import QtGui
15 from pygments.styles import get_all_styles
15 from pygments.styles import get_all_styles
16
16
17 # Local imports
17 # Local imports
18 from IPython.core.newapplication import ProfileDir, BaseIPythonApplication
18 from IPython.core.newapplication import ProfileDir, BaseIPythonApplication
19 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
19 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
20 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
20 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
21 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
21 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
22 from IPython.frontend.qt.console import styles
22 from IPython.frontend.qt.console import styles
23 from IPython.frontend.qt.kernelmanager import QtKernelManager
23 from IPython.frontend.qt.kernelmanager import QtKernelManager
24 from IPython.utils.traitlets import (
24 from IPython.utils.traitlets import (
25 Dict, List, Unicode, Int, CaselessStrEnum, Bool, Any
25 Dict, List, Unicode, Int, CaselessStrEnum, Bool, Any
26 )
26 )
27 from IPython.zmq.ipkernel import (
27 from IPython.zmq.ipkernel import (
28 flags as ipkernel_flags,
28 flags as ipkernel_flags,
29 aliases as ipkernel_aliases,
29 aliases as ipkernel_aliases,
30 IPKernelApp
30 IPKernelApp
31 )
31 )
32 from IPython.zmq.zmqshell import ZMQInteractiveShell
32 from IPython.zmq.zmqshell import ZMQInteractiveShell
33
33
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Network Constants
36 # Network Constants
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
39 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
40
40
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42 # Classes
42 # Classes
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44
44
45 class MainWindow(QtGui.QMainWindow):
45 class MainWindow(QtGui.QMainWindow):
46
46
47 #---------------------------------------------------------------------------
47 #---------------------------------------------------------------------------
48 # 'object' interface
48 # 'object' interface
49 #---------------------------------------------------------------------------
49 #---------------------------------------------------------------------------
50
50
51 def __init__(self, app, frontend, existing=False, may_close=True):
51 def __init__(self, app, frontend, existing=False, may_close=True):
52 """ Create a MainWindow for the specified FrontendWidget.
52 """ Create a MainWindow for the specified FrontendWidget.
53
53
54 The app is passed as an argument to allow for different
54 The app is passed as an argument to allow for different
55 closing behavior depending on whether we are the Kernel's parent.
55 closing behavior depending on whether we are the Kernel's parent.
56
56
57 If existing is True, then this Console does not own the Kernel.
57 If existing is True, then this Console does not own the Kernel.
58
58
59 If may_close is True, then this Console is permitted to close the kernel
59 If may_close is True, then this Console is permitted to close the kernel
60 """
60 """
61 super(MainWindow, self).__init__()
61 super(MainWindow, self).__init__()
62 self._app = app
62 self._app = app
63 self._frontend = frontend
63 self._frontend = frontend
64 self._existing = existing
64 self._existing = existing
65 if existing:
65 if existing:
66 self._may_close = may_close
66 self._may_close = may_close
67 else:
67 else:
68 self._may_close = True
68 self._may_close = True
69 self._frontend.exit_requested.connect(self.close)
69 self._frontend.exit_requested.connect(self.close)
70 self.setCentralWidget(frontend)
70 self.setCentralWidget(frontend)
71
71
72 #---------------------------------------------------------------------------
72 #---------------------------------------------------------------------------
73 # QWidget interface
73 # QWidget interface
74 #---------------------------------------------------------------------------
74 #---------------------------------------------------------------------------
75
75
76 def closeEvent(self, event):
76 def closeEvent(self, event):
77 """ Close the window and the kernel (if necessary).
77 """ Close the window and the kernel (if necessary).
78
78
79 This will prompt the user if they are finished with the kernel, and if
79 This will prompt the user if they are finished with the kernel, and if
80 so, closes the kernel cleanly. Alternatively, if the exit magic is used,
80 so, closes the kernel cleanly. Alternatively, if the exit magic is used,
81 it closes without prompt.
81 it closes without prompt.
82 """
82 """
83 keepkernel = None #Use the prompt by default
83 keepkernel = None #Use the prompt by default
84 if hasattr(self._frontend,'_keep_kernel_on_exit'): #set by exit magic
84 if hasattr(self._frontend,'_keep_kernel_on_exit'): #set by exit magic
85 keepkernel = self._frontend._keep_kernel_on_exit
85 keepkernel = self._frontend._keep_kernel_on_exit
86
86
87 kernel_manager = self._frontend.kernel_manager
87 kernel_manager = self._frontend.kernel_manager
88
88
89 if keepkernel is None: #show prompt
89 if keepkernel is None: #show prompt
90 if kernel_manager and kernel_manager.channels_running:
90 if kernel_manager and kernel_manager.channels_running:
91 title = self.window().windowTitle()
91 title = self.window().windowTitle()
92 cancel = QtGui.QMessageBox.Cancel
92 cancel = QtGui.QMessageBox.Cancel
93 okay = QtGui.QMessageBox.Ok
93 okay = QtGui.QMessageBox.Ok
94 if self._may_close:
94 if self._may_close:
95 msg = "You are closing this Console window."
95 msg = "You are closing this Console window."
96 info = "Would you like to quit the Kernel and all attached Consoles as well?"
96 info = "Would you like to quit the Kernel and all attached Consoles as well?"
97 justthis = QtGui.QPushButton("&No, just this Console", self)
97 justthis = QtGui.QPushButton("&No, just this Console", self)
98 justthis.setShortcut('N')
98 justthis.setShortcut('N')
99 closeall = QtGui.QPushButton("&Yes, quit everything", self)
99 closeall = QtGui.QPushButton("&Yes, quit everything", self)
100 closeall.setShortcut('Y')
100 closeall.setShortcut('Y')
101 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
101 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
102 title, msg)
102 title, msg)
103 box.setInformativeText(info)
103 box.setInformativeText(info)
104 box.addButton(cancel)
104 box.addButton(cancel)
105 box.addButton(justthis, QtGui.QMessageBox.NoRole)
105 box.addButton(justthis, QtGui.QMessageBox.NoRole)
106 box.addButton(closeall, QtGui.QMessageBox.YesRole)
106 box.addButton(closeall, QtGui.QMessageBox.YesRole)
107 box.setDefaultButton(closeall)
107 box.setDefaultButton(closeall)
108 box.setEscapeButton(cancel)
108 box.setEscapeButton(cancel)
109 reply = box.exec_()
109 reply = box.exec_()
110 if reply == 1: # close All
110 if reply == 1: # close All
111 kernel_manager.shutdown_kernel()
111 kernel_manager.shutdown_kernel()
112 #kernel_manager.stop_channels()
112 #kernel_manager.stop_channels()
113 event.accept()
113 event.accept()
114 elif reply == 0: # close Console
114 elif reply == 0: # close Console
115 if not self._existing:
115 if not self._existing:
116 # Have kernel: don't quit, just close the window
116 # Have kernel: don't quit, just close the window
117 self._app.setQuitOnLastWindowClosed(False)
117 self._app.setQuitOnLastWindowClosed(False)
118 self.deleteLater()
118 self.deleteLater()
119 event.accept()
119 event.accept()
120 else:
120 else:
121 event.ignore()
121 event.ignore()
122 else:
122 else:
123 reply = QtGui.QMessageBox.question(self, title,
123 reply = QtGui.QMessageBox.question(self, title,
124 "Are you sure you want to close this Console?"+
124 "Are you sure you want to close this Console?"+
125 "\nThe Kernel and other Consoles will remain active.",
125 "\nThe Kernel and other Consoles will remain active.",
126 okay|cancel,
126 okay|cancel,
127 defaultButton=okay
127 defaultButton=okay
128 )
128 )
129 if reply == okay:
129 if reply == okay:
130 event.accept()
130 event.accept()
131 else:
131 else:
132 event.ignore()
132 event.ignore()
133 elif keepkernel: #close console but leave kernel running (no prompt)
133 elif keepkernel: #close console but leave kernel running (no prompt)
134 if kernel_manager and kernel_manager.channels_running:
134 if kernel_manager and kernel_manager.channels_running:
135 if not self._existing:
135 if not self._existing:
136 # I have the kernel: don't quit, just close the window
136 # I have the kernel: don't quit, just close the window
137 self._app.setQuitOnLastWindowClosed(False)
137 self._app.setQuitOnLastWindowClosed(False)
138 event.accept()
138 event.accept()
139 else: #close console and kernel (no prompt)
139 else: #close console and kernel (no prompt)
140 if kernel_manager and kernel_manager.channels_running:
140 if kernel_manager and kernel_manager.channels_running:
141 kernel_manager.shutdown_kernel()
141 kernel_manager.shutdown_kernel()
142 event.accept()
142 event.accept()
143
143
144 #-----------------------------------------------------------------------------
144 #-----------------------------------------------------------------------------
145 # Aliases and Flags
145 # Aliases and Flags
146 #-----------------------------------------------------------------------------
146 #-----------------------------------------------------------------------------
147
147
148 flags = dict(ipkernel_flags)
148 flags = dict(ipkernel_flags)
149
149
150 flags.update({
150 flags.update({
151 'existing' : ({'IPythonQtConsoleApp' : {'existing' : True}},
151 'existing' : ({'IPythonQtConsoleApp' : {'existing' : True}},
152 "Connect to an existing kernel."),
152 "Connect to an existing kernel."),
153 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
153 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
154 "Use a pure Python kernel instead of an IPython kernel."),
154 "Use a pure Python kernel instead of an IPython kernel."),
155 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
155 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
156 "Disable rich text support."),
156 "Disable rich text support."),
157 'gui-completion' : ({'FrontendWidget' : {'gui_completion' : True}},
157 'gui-completion' : ({'FrontendWidget' : {'gui_completion' : True}},
158 "use a GUI widget for tab completion"),
158 "use a GUI widget for tab completion"),
159 })
159 })
160
160
161 qt_flags = ['existing', 'pure', 'plain', 'gui-completion']
161 qt_flags = ['existing', 'pure', 'plain', 'gui-completion']
162
162
163 aliases = dict(ipkernel_aliases)
163 aliases = dict(ipkernel_aliases)
164
164
165 aliases.update(dict(
165 aliases.update(dict(
166 hb = 'IPythonQtConsoleApp.hb_port',
166 hb = 'IPythonQtConsoleApp.hb_port',
167 shell = 'IPythonQtConsoleApp.shell_port',
167 shell = 'IPythonQtConsoleApp.shell_port',
168 iopub = 'IPythonQtConsoleApp.iopub_port',
168 iopub = 'IPythonQtConsoleApp.iopub_port',
169 stdin = 'IPythonQtConsoleApp.stdin_port',
169 stdin = 'IPythonQtConsoleApp.stdin_port',
170 ip = 'IPythonQtConsoleApp.ip',
170 ip = 'IPythonQtConsoleApp.ip',
171
171
172 plain = 'IPythonQtConsoleApp.plain',
172 plain = 'IPythonQtConsoleApp.plain',
173 pure = 'IPythonQtConsoleApp.pure',
173 pure = 'IPythonQtConsoleApp.pure',
174 gui_completion = 'FrontendWidget.gui_completion',
174 gui_completion = 'FrontendWidget.gui_completion',
175 style = 'IPythonWidget.syntax_style',
175 style = 'IPythonWidget.syntax_style',
176 stylesheet = 'IPythonQtConsoleApp.stylesheet',
176 stylesheet = 'IPythonQtConsoleApp.stylesheet',
177 colors = 'ZMQInteractiveShell.colors',
177 colors = 'ZMQInteractiveShell.colors',
178
178
179 editor = 'IPythonWidget.editor',
179 editor = 'IPythonWidget.editor',
180 pi = 'IPythonWidget.in_prompt',
180 pi = 'IPythonWidget.in_prompt',
181 po = 'IPythonWidget.out_prompt',
181 po = 'IPythonWidget.out_prompt',
182 si = 'IPythonWidget.input_sep',
182 si = 'IPythonWidget.input_sep',
183 so = 'IPythonWidget.output_sep',
183 so = 'IPythonWidget.output_sep',
184 so2 = 'IPythonWidget.output_sep2',
184 so2 = 'IPythonWidget.output_sep2',
185 ))
185 ))
186
186
187 #-----------------------------------------------------------------------------
187 #-----------------------------------------------------------------------------
188 # IPythonQtConsole
188 # IPythonQtConsole
189 #-----------------------------------------------------------------------------
189 #-----------------------------------------------------------------------------
190
190
191 class IPythonQtConsoleApp(BaseIPythonApplication):
191 class IPythonQtConsoleApp(BaseIPythonApplication):
192 name = 'ipython-qtconsole'
192 name = 'ipython-qtconsole'
193 default_config_file_name='ipython_config.py'
193 default_config_file_name='ipython_config.py'
194 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir]
194 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir]
195 flags = Dict(flags)
195 flags = Dict(flags)
196 aliases = Dict(aliases)
196 aliases = Dict(aliases)
197
197
198 kernel_argv = List(Unicode)
198 kernel_argv = List(Unicode)
199
199
200 # connection info:
200 # connection info:
201 ip = Unicode(LOCALHOST, config=True,
201 ip = Unicode(LOCALHOST, config=True,
202 help="""Set the kernel\'s IP address [default localhost].
202 help="""Set the kernel\'s IP address [default localhost].
203 If the IP address is something other than localhost, then
203 If the IP address is something other than localhost, then
204 Consoles on other machines will be able to connect
204 Consoles on other machines will be able to connect
205 to the Kernel, so be careful!"""
205 to the Kernel, so be careful!"""
206 )
206 )
207 hb_port = Int(0, config=True,
207 hb_port = Int(0, config=True,
208 help="set the heartbeat port [default: random]")
208 help="set the heartbeat port [default: random]")
209 shell_port = Int(0, config=True,
209 shell_port = Int(0, config=True,
210 help="set the shell (XREP) port [default: random]")
210 help="set the shell (XREP) port [default: random]")
211 iopub_port = Int(0, config=True,
211 iopub_port = Int(0, config=True,
212 help="set the iopub (PUB) port [default: random]")
212 help="set the iopub (PUB) port [default: random]")
213 stdin_port = Int(0, config=True,
213 stdin_port = Int(0, config=True,
214 help="set the stdin (XREQ) port [default: random]")
214 help="set the stdin (XREQ) port [default: random]")
215
215
216 existing = Bool(False, config=True,
216 existing = Bool(False, config=True,
217 help="Whether to connect to an already running Kernel.")
217 help="Whether to connect to an already running Kernel.")
218
218
219 stylesheet = Unicode('', config=True,
219 stylesheet = Unicode('', config=True,
220 help="path to a custom CSS stylesheet")
220 help="path to a custom CSS stylesheet")
221
221
222 pure = Bool(False, config=True,
222 pure = Bool(False, config=True,
223 help="Use a pure Python kernel instead of an IPython kernel.")
223 help="Use a pure Python kernel instead of an IPython kernel.")
224 plain = Bool(False, config=True,
224 plain = Bool(False, config=True,
225 help="Use a pure Python kernel instead of an IPython kernel.")
225 help="Use a pure Python kernel instead of an IPython kernel.")
226
226
227 def _pure_changed(self, name, old, new):
227 def _pure_changed(self, name, old, new):
228 kind = 'plain' if self.plain else 'rich'
228 kind = 'plain' if self.plain else 'rich'
229 self.config.ConsoleWidget.kind = kind
229 self.config.ConsoleWidget.kind = kind
230 if self.pure:
230 if self.pure:
231 self.widget_factory = FrontendWidget
231 self.widget_factory = FrontendWidget
232 elif self.plain:
232 elif self.plain:
233 self.widget_factory = IPythonWidget
233 self.widget_factory = IPythonWidget
234 else:
234 else:
235 self.widget_factory = RichIPythonWidget
235 self.widget_factory = RichIPythonWidget
236
236
237 _plain_changed = _pure_changed
237 _plain_changed = _pure_changed
238
238
239 # the factory for creating a widget
239 # the factory for creating a widget
240 widget_factory = Any(RichIPythonWidget)
240 widget_factory = Any(RichIPythonWidget)
241
241
242 def parse_command_line(self, argv=None):
242 def parse_command_line(self, argv=None):
243 super(IPythonQtConsoleApp, self).parse_command_line(argv)
243 super(IPythonQtConsoleApp, self).parse_command_line(argv)
244 if argv is None:
244 if argv is None:
245 argv = sys.argv[1:]
245 argv = sys.argv[1:]
246
246
247 self.kernel_argv = list(argv) # copy
247 self.kernel_argv = list(argv) # copy
248
248
249 # scrub frontend-specific flags
249 # scrub frontend-specific flags
250 for a in argv:
250 for a in argv:
251 if a.startswith('--') and a[2:] in qt_flags:
251 if a.startswith('--') and a[2:] in qt_flags:
252 self.kernel_argv.remove(a)
252 self.kernel_argv.remove(a)
253
253
254 def init_kernel_manager(self):
254 def init_kernel_manager(self):
255 # Don't let Qt or ZMQ swallow KeyboardInterupts.
255 # Don't let Qt or ZMQ swallow KeyboardInterupts.
256 signal.signal(signal.SIGINT, signal.SIG_DFL)
256 signal.signal(signal.SIGINT, signal.SIG_DFL)
257
257
258 # Create a KernelManager and start a kernel.
258 # Create a KernelManager and start a kernel.
259 self.kernel_manager = QtKernelManager(
259 self.kernel_manager = QtKernelManager(
260 xreq_address=(self.ip, self.shell_port),
260 shell_address=(self.ip, self.shell_port),
261 sub_address=(self.ip, self.iopub_port),
261 sub_address=(self.ip, self.iopub_port),
262 rep_address=(self.ip, self.stdin_port),
262 stdin_address=(self.ip, self.stdin_port),
263 hb_address=(self.ip, self.hb_port)
263 hb_address=(self.ip, self.hb_port)
264 )
264 )
265 # start the kernel
265 # start the kernel
266 if not self.existing:
266 if not self.existing:
267 kwargs = dict(ip=self.ip, ipython=not self.pure)
267 kwargs = dict(ip=self.ip, ipython=not self.pure)
268 kwargs['extra_arguments'] = self.kernel_argv
268 kwargs['extra_arguments'] = self.kernel_argv
269 self.kernel_manager.start_kernel(**kwargs)
269 self.kernel_manager.start_kernel(**kwargs)
270 self.kernel_manager.start_channels()
270 self.kernel_manager.start_channels()
271
271
272
272
273 def init_qt_elements(self):
273 def init_qt_elements(self):
274 # Create the widget.
274 # Create the widget.
275 self.app = QtGui.QApplication([])
275 self.app = QtGui.QApplication([])
276 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
276 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
277 self.widget = self.widget_factory(config=self.config,
277 self.widget = self.widget_factory(config=self.config,
278 local_kernel=local_kernel)
278 local_kernel=local_kernel)
279 self.widget.kernel_manager = self.kernel_manager
279 self.widget.kernel_manager = self.kernel_manager
280 self.window = MainWindow(self.app, self.widget, self.existing,
280 self.window = MainWindow(self.app, self.widget, self.existing,
281 may_close=local_kernel)
281 may_close=local_kernel)
282 self.window.setWindowTitle('Python' if self.pure else 'IPython')
282 self.window.setWindowTitle('Python' if self.pure else 'IPython')
283
283
284 def init_colors(self):
284 def init_colors(self):
285 """Configure the coloring of the widget"""
285 """Configure the coloring of the widget"""
286 # Note: This will be dramatically simplified when colors
286 # Note: This will be dramatically simplified when colors
287 # are removed from the backend.
287 # are removed from the backend.
288
288
289 if self.pure:
289 if self.pure:
290 # only IPythonWidget supports styling
290 # only IPythonWidget supports styling
291 return
291 return
292
292
293 # parse the colors arg down to current known labels
293 # parse the colors arg down to current known labels
294 try:
294 try:
295 colors = self.config.ZMQInteractiveShell.colors
295 colors = self.config.ZMQInteractiveShell.colors
296 except AttributeError:
296 except AttributeError:
297 colors = None
297 colors = None
298 try:
298 try:
299 style = self.config.IPythonWidget.colors
299 style = self.config.IPythonWidget.colors
300 except AttributeError:
300 except AttributeError:
301 style = None
301 style = None
302
302
303 # find the value for colors:
303 # find the value for colors:
304 if colors:
304 if colors:
305 colors=colors.lower()
305 colors=colors.lower()
306 if colors in ('lightbg', 'light'):
306 if colors in ('lightbg', 'light'):
307 colors='lightbg'
307 colors='lightbg'
308 elif colors in ('dark', 'linux'):
308 elif colors in ('dark', 'linux'):
309 colors='linux'
309 colors='linux'
310 else:
310 else:
311 colors='nocolor'
311 colors='nocolor'
312 elif style:
312 elif style:
313 if style=='bw':
313 if style=='bw':
314 colors='nocolor'
314 colors='nocolor'
315 elif styles.dark_style(style):
315 elif styles.dark_style(style):
316 colors='linux'
316 colors='linux'
317 else:
317 else:
318 colors='lightbg'
318 colors='lightbg'
319 else:
319 else:
320 colors=None
320 colors=None
321
321
322 # Configure the style.
322 # Configure the style.
323 widget = self.widget
323 widget = self.widget
324 if style:
324 if style:
325 widget.style_sheet = styles.sheet_from_template(style, colors)
325 widget.style_sheet = styles.sheet_from_template(style, colors)
326 widget.syntax_style = style
326 widget.syntax_style = style
327 widget._syntax_style_changed()
327 widget._syntax_style_changed()
328 widget._style_sheet_changed()
328 widget._style_sheet_changed()
329 elif colors:
329 elif colors:
330 # use a default style
330 # use a default style
331 widget.set_default_style(colors=colors)
331 widget.set_default_style(colors=colors)
332 else:
332 else:
333 # this is redundant for now, but allows the widget's
333 # this is redundant for now, but allows the widget's
334 # defaults to change
334 # defaults to change
335 widget.set_default_style()
335 widget.set_default_style()
336
336
337 if self.stylesheet:
337 if self.stylesheet:
338 # we got an expicit stylesheet
338 # we got an expicit stylesheet
339 if os.path.isfile(self.stylesheet):
339 if os.path.isfile(self.stylesheet):
340 with open(self.stylesheet) as f:
340 with open(self.stylesheet) as f:
341 sheet = f.read()
341 sheet = f.read()
342 widget.style_sheet = sheet
342 widget.style_sheet = sheet
343 widget._style_sheet_changed()
343 widget._style_sheet_changed()
344 else:
344 else:
345 raise IOError("Stylesheet %r not found."%self.stylesheet)
345 raise IOError("Stylesheet %r not found."%self.stylesheet)
346
346
347 def initialize(self, argv=None):
347 def initialize(self, argv=None):
348 super(IPythonQtConsoleApp, self).initialize(argv)
348 super(IPythonQtConsoleApp, self).initialize(argv)
349 self.init_kernel_manager()
349 self.init_kernel_manager()
350 self.init_qt_elements()
350 self.init_qt_elements()
351 self.init_colors()
351 self.init_colors()
352
352
353 def start(self):
353 def start(self):
354
354
355 # draw the window
355 # draw the window
356 self.window.show()
356 self.window.show()
357
357
358 # Start the application main loop.
358 # Start the application main loop.
359 self.app.exec_()
359 self.app.exec_()
360
360
361 #-----------------------------------------------------------------------------
361 #-----------------------------------------------------------------------------
362 # Main entry point
362 # Main entry point
363 #-----------------------------------------------------------------------------
363 #-----------------------------------------------------------------------------
364
364
365 def main():
365 def main():
366 app = IPythonQtConsoleApp()
366 app = IPythonQtConsoleApp()
367 app.initialize()
367 app.initialize()
368 app.start()
368 app.start()
369
369
370
370
371 if __name__ == '__main__':
371 if __name__ == '__main__':
372 main()
372 main()
@@ -1,243 +1,243 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 IPython.external.qt import QtCore
5 from IPython.external.qt 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 ShellSocketChannel, StdInSocketChannel, 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.Signal()
17 started = QtCore.Signal()
18
18
19 # Emitted when the channel is stopped.
19 # Emitted when the channel is stopped.
20 stopped = QtCore.Signal()
20 stopped = QtCore.Signal()
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 QtShellSocketChannel(SocketChannelQObject, ShellSocketChannel):
40
40
41 # Emitted when any message is received.
41 # Emitted when any message is received.
42 message_received = QtCore.Signal(object)
42 message_received = QtCore.Signal(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.Signal(object)
46 execute_reply = QtCore.Signal(object)
47 complete_reply = QtCore.Signal(object)
47 complete_reply = QtCore.Signal(object)
48 object_info_reply = QtCore.Signal(object)
48 object_info_reply = QtCore.Signal(object)
49 history_reply = QtCore.Signal(object)
49 history_reply = QtCore.Signal(object)
50
50
51 # Emitted when the first reply comes back.
51 # Emitted when the first reply comes back.
52 first_reply = QtCore.Signal()
52 first_reply = QtCore.Signal()
53
53
54 # Used by the first_reply signal logic to determine if a reply is the
54 # Used by the first_reply signal logic to determine if a reply is the
55 # first.
55 # first.
56 _handlers_called = False
56 _handlers_called = False
57
57
58 #---------------------------------------------------------------------------
58 #---------------------------------------------------------------------------
59 # 'XReqSocketChannel' interface
59 # 'ShellSocketChannel' interface
60 #---------------------------------------------------------------------------
60 #---------------------------------------------------------------------------
61
61
62 def call_handlers(self, msg):
62 def call_handlers(self, msg):
63 """ Reimplemented to emit signals instead of making callbacks.
63 """ Reimplemented to emit signals instead of making callbacks.
64 """
64 """
65 # Emit the generic signal.
65 # Emit the generic signal.
66 self.message_received.emit(msg)
66 self.message_received.emit(msg)
67
67
68 # Emit signals for specialized message types.
68 # Emit signals for specialized message types.
69 msg_type = msg['msg_type']
69 msg_type = msg['msg_type']
70 signal = getattr(self, msg_type, None)
70 signal = getattr(self, msg_type, None)
71 if signal:
71 if signal:
72 signal.emit(msg)
72 signal.emit(msg)
73
73
74 if not self._handlers_called:
74 if not self._handlers_called:
75 self.first_reply.emit()
75 self.first_reply.emit()
76 self._handlers_called = True
76 self._handlers_called = True
77
77
78 #---------------------------------------------------------------------------
78 #---------------------------------------------------------------------------
79 # 'QtXReqSocketChannel' interface
79 # 'QtShellSocketChannel' interface
80 #---------------------------------------------------------------------------
80 #---------------------------------------------------------------------------
81
81
82 def reset_first_reply(self):
82 def reset_first_reply(self):
83 """ Reset the first_reply signal to fire again on the next reply.
83 """ Reset the first_reply signal to fire again on the next reply.
84 """
84 """
85 self._handlers_called = False
85 self._handlers_called = False
86
86
87
87
88 class QtSubSocketChannel(SocketChannelQObject, SubSocketChannel):
88 class QtSubSocketChannel(SocketChannelQObject, SubSocketChannel):
89
89
90 # Emitted when any message is received.
90 # Emitted when any message is received.
91 message_received = QtCore.Signal(object)
91 message_received = QtCore.Signal(object)
92
92
93 # Emitted when a message of type 'stream' is received.
93 # Emitted when a message of type 'stream' is received.
94 stream_received = QtCore.Signal(object)
94 stream_received = QtCore.Signal(object)
95
95
96 # Emitted when a message of type 'pyin' is received.
96 # Emitted when a message of type 'pyin' is received.
97 pyin_received = QtCore.Signal(object)
97 pyin_received = QtCore.Signal(object)
98
98
99 # Emitted when a message of type 'pyout' is received.
99 # Emitted when a message of type 'pyout' is received.
100 pyout_received = QtCore.Signal(object)
100 pyout_received = QtCore.Signal(object)
101
101
102 # Emitted when a message of type 'pyerr' is received.
102 # Emitted when a message of type 'pyerr' is received.
103 pyerr_received = QtCore.Signal(object)
103 pyerr_received = QtCore.Signal(object)
104
104
105 # Emitted when a message of type 'display_data' is received
105 # Emitted when a message of type 'display_data' is received
106 display_data_received = QtCore.Signal(object)
106 display_data_received = QtCore.Signal(object)
107
107
108 # Emitted when a crash report message is received from the kernel's
108 # Emitted when a crash report message is received from the kernel's
109 # last-resort sys.excepthook.
109 # last-resort sys.excepthook.
110 crash_received = QtCore.Signal(object)
110 crash_received = QtCore.Signal(object)
111
111
112 # Emitted when a shutdown is noticed.
112 # Emitted when a shutdown is noticed.
113 shutdown_reply_received = QtCore.Signal(object)
113 shutdown_reply_received = QtCore.Signal(object)
114
114
115 #---------------------------------------------------------------------------
115 #---------------------------------------------------------------------------
116 # 'SubSocketChannel' interface
116 # 'SubSocketChannel' interface
117 #---------------------------------------------------------------------------
117 #---------------------------------------------------------------------------
118
118
119 def call_handlers(self, msg):
119 def call_handlers(self, msg):
120 """ Reimplemented to emit signals instead of making callbacks.
120 """ Reimplemented to emit signals instead of making callbacks.
121 """
121 """
122 # Emit the generic signal.
122 # Emit the generic signal.
123 self.message_received.emit(msg)
123 self.message_received.emit(msg)
124 # Emit signals for specialized message types.
124 # Emit signals for specialized message types.
125 msg_type = msg['msg_type']
125 msg_type = msg['msg_type']
126 signal = getattr(self, msg_type + '_received', None)
126 signal = getattr(self, msg_type + '_received', None)
127 if signal:
127 if signal:
128 signal.emit(msg)
128 signal.emit(msg)
129 elif msg_type in ('stdout', 'stderr'):
129 elif msg_type in ('stdout', 'stderr'):
130 self.stream_received.emit(msg)
130 self.stream_received.emit(msg)
131
131
132 def flush(self):
132 def flush(self):
133 """ Reimplemented to ensure that signals are dispatched immediately.
133 """ Reimplemented to ensure that signals are dispatched immediately.
134 """
134 """
135 super(QtSubSocketChannel, self).flush()
135 super(QtSubSocketChannel, self).flush()
136 QtCore.QCoreApplication.instance().processEvents()
136 QtCore.QCoreApplication.instance().processEvents()
137
137
138
138
139 class QtRepSocketChannel(SocketChannelQObject, RepSocketChannel):
139 class QtStdInSocketChannel(SocketChannelQObject, StdInSocketChannel):
140
140
141 # Emitted when any message is received.
141 # Emitted when any message is received.
142 message_received = QtCore.Signal(object)
142 message_received = QtCore.Signal(object)
143
143
144 # Emitted when an input request is received.
144 # Emitted when an input request is received.
145 input_requested = QtCore.Signal(object)
145 input_requested = QtCore.Signal(object)
146
146
147 #---------------------------------------------------------------------------
147 #---------------------------------------------------------------------------
148 # 'RepSocketChannel' interface
148 # 'StdInSocketChannel' interface
149 #---------------------------------------------------------------------------
149 #---------------------------------------------------------------------------
150
150
151 def call_handlers(self, msg):
151 def call_handlers(self, msg):
152 """ Reimplemented to emit signals instead of making callbacks.
152 """ Reimplemented to emit signals instead of making callbacks.
153 """
153 """
154 # Emit the generic signal.
154 # Emit the generic signal.
155 self.message_received.emit(msg)
155 self.message_received.emit(msg)
156
156
157 # Emit signals for specialized message types.
157 # Emit signals for specialized message types.
158 msg_type = msg['msg_type']
158 msg_type = msg['msg_type']
159 if msg_type == 'input_request':
159 if msg_type == 'input_request':
160 self.input_requested.emit(msg)
160 self.input_requested.emit(msg)
161
161
162
162
163 class QtHBSocketChannel(SocketChannelQObject, HBSocketChannel):
163 class QtHBSocketChannel(SocketChannelQObject, HBSocketChannel):
164
164
165 # Emitted when the kernel has died.
165 # Emitted when the kernel has died.
166 kernel_died = QtCore.Signal(object)
166 kernel_died = QtCore.Signal(object)
167
167
168 #---------------------------------------------------------------------------
168 #---------------------------------------------------------------------------
169 # 'HBSocketChannel' interface
169 # 'HBSocketChannel' interface
170 #---------------------------------------------------------------------------
170 #---------------------------------------------------------------------------
171
171
172 def call_handlers(self, since_last_heartbeat):
172 def call_handlers(self, since_last_heartbeat):
173 """ Reimplemented to emit signals instead of making callbacks.
173 """ Reimplemented to emit signals instead of making callbacks.
174 """
174 """
175 # Emit the generic signal.
175 # Emit the generic signal.
176 self.kernel_died.emit(since_last_heartbeat)
176 self.kernel_died.emit(since_last_heartbeat)
177
177
178
178
179 class QtKernelManager(KernelManager, SuperQObject):
179 class QtKernelManager(KernelManager, SuperQObject):
180 """ A KernelManager that provides signals and slots.
180 """ A KernelManager that provides signals and slots.
181 """
181 """
182
182
183 __metaclass__ = MetaQObjectHasTraits
183 __metaclass__ = MetaQObjectHasTraits
184
184
185 # Emitted when the kernel manager has started listening.
185 # Emitted when the kernel manager has started listening.
186 started_channels = QtCore.Signal()
186 started_channels = QtCore.Signal()
187
187
188 # Emitted when the kernel manager has stopped listening.
188 # Emitted when the kernel manager has stopped listening.
189 stopped_channels = QtCore.Signal()
189 stopped_channels = QtCore.Signal()
190
190
191 # Use Qt-specific channel classes that emit signals.
191 # Use Qt-specific channel classes that emit signals.
192 sub_channel_class = Type(QtSubSocketChannel)
192 sub_channel_class = Type(QtSubSocketChannel)
193 xreq_channel_class = Type(QtXReqSocketChannel)
193 shell_channel_class = Type(QtShellSocketChannel)
194 rep_channel_class = Type(QtRepSocketChannel)
194 stdin_channel_class = Type(QtStdInSocketChannel)
195 hb_channel_class = Type(QtHBSocketChannel)
195 hb_channel_class = Type(QtHBSocketChannel)
196
196
197 #---------------------------------------------------------------------------
197 #---------------------------------------------------------------------------
198 # 'KernelManager' interface
198 # 'KernelManager' interface
199 #---------------------------------------------------------------------------
199 #---------------------------------------------------------------------------
200
200
201 #------ Kernel process management ------------------------------------------
201 #------ Kernel process management ------------------------------------------
202
202
203 def start_kernel(self, *args, **kw):
203 def start_kernel(self, *args, **kw):
204 """ Reimplemented for proper heartbeat management.
204 """ Reimplemented for proper heartbeat management.
205 """
205 """
206 if self._xreq_channel is not None:
206 if self._shell_channel is not None:
207 self._xreq_channel.reset_first_reply()
207 self._shell_channel.reset_first_reply()
208 super(QtKernelManager, self).start_kernel(*args, **kw)
208 super(QtKernelManager, self).start_kernel(*args, **kw)
209
209
210 #------ Channel management -------------------------------------------------
210 #------ Channel management -------------------------------------------------
211
211
212 def start_channels(self, *args, **kw):
212 def start_channels(self, *args, **kw):
213 """ Reimplemented to emit signal.
213 """ Reimplemented to emit signal.
214 """
214 """
215 super(QtKernelManager, self).start_channels(*args, **kw)
215 super(QtKernelManager, self).start_channels(*args, **kw)
216 self.started_channels.emit()
216 self.started_channels.emit()
217
217
218 def stop_channels(self):
218 def stop_channels(self):
219 """ Reimplemented to emit signal.
219 """ Reimplemented to emit signal.
220 """
220 """
221 super(QtKernelManager, self).stop_channels()
221 super(QtKernelManager, self).stop_channels()
222 self.stopped_channels.emit()
222 self.stopped_channels.emit()
223
223
224 @property
224 @property
225 def xreq_channel(self):
225 def shell_channel(self):
226 """ Reimplemented for proper heartbeat management.
226 """ Reimplemented for proper heartbeat management.
227 """
227 """
228 if self._xreq_channel is None:
228 if self._shell_channel is None:
229 self._xreq_channel = super(QtKernelManager, self).xreq_channel
229 self._shell_channel = super(QtKernelManager, self).shell_channel
230 self._xreq_channel.first_reply.connect(self._first_reply)
230 self._shell_channel.first_reply.connect(self._first_reply)
231 return self._xreq_channel
231 return self._shell_channel
232
232
233 #---------------------------------------------------------------------------
233 #---------------------------------------------------------------------------
234 # Protected interface
234 # Protected interface
235 #---------------------------------------------------------------------------
235 #---------------------------------------------------------------------------
236
236
237 def _first_reply(self):
237 def _first_reply(self):
238 """ Unpauses the heartbeat channel when the first reply is received on
238 """ Unpauses the heartbeat channel when the first reply is received on
239 the execute channel. Note that this will *not* start the heartbeat
239 the execute channel. Note that this will *not* start the heartbeat
240 channel if it is not already running!
240 channel if it is not already running!
241 """
241 """
242 if self._hb_channel is not None:
242 if self._hb_channel is not None:
243 self._hb_channel.unpause()
243 self._hb_channel.unpause()
@@ -1,121 +1,121 b''
1 """Implement a fully blocking kernel manager.
1 """Implement a fully blocking kernel manager.
2
2
3 Useful for test suites and blocking terminal interfaces.
3 Useful for test suites and blocking terminal interfaces.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (C) 2010 The IPython Development Team
6 # Copyright (C) 2010 The IPython Development Team
7 #
7 #
8 # Distributed under the terms of the BSD License. The full license is in
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING.txt, distributed as part of this software.
9 # the file COPYING.txt, distributed as part of this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from __future__ import print_function
15 from __future__ import print_function
16
16
17 # Stdlib
17 # Stdlib
18 from Queue import Queue, Empty
18 from Queue import Queue, Empty
19
19
20 # Our own
20 # Our own
21 from IPython.utils import io
21 from IPython.utils import io
22 from IPython.utils.traitlets import Type
22 from IPython.utils.traitlets import Type
23
23
24 from .kernelmanager import (KernelManager, SubSocketChannel,
24 from .kernelmanager import (KernelManager, SubSocketChannel, HBSocketChannel,
25 XReqSocketChannel, RepSocketChannel, HBSocketChannel)
25 ShellSocketChannel, StdInSocketChannel)
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Functions and classes
28 # Functions and classes
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31 class BlockingSubSocketChannel(SubSocketChannel):
31 class BlockingSubSocketChannel(SubSocketChannel):
32
32
33 def __init__(self, context, session, address=None):
33 def __init__(self, context, session, address=None):
34 super(BlockingSubSocketChannel, self).__init__(context, session,
34 super(BlockingSubSocketChannel, self).__init__(context, session,
35 address)
35 address)
36 self._in_queue = Queue()
36 self._in_queue = Queue()
37
37
38 def call_handlers(self, msg):
38 def call_handlers(self, msg):
39 #io.rprint('[[Sub]]', msg) # dbg
39 #io.rprint('[[Sub]]', msg) # dbg
40 self._in_queue.put(msg)
40 self._in_queue.put(msg)
41
41
42 def msg_ready(self):
42 def msg_ready(self):
43 """Is there a message that has been received?"""
43 """Is there a message that has been received?"""
44 if self._in_queue.qsize() == 0:
44 if self._in_queue.qsize() == 0:
45 return False
45 return False
46 else:
46 else:
47 return True
47 return True
48
48
49 def get_msg(self, block=True, timeout=None):
49 def get_msg(self, block=True, timeout=None):
50 """Get a message if there is one that is ready."""
50 """Get a message if there is one that is ready."""
51 return self._in_queue.get(block, timeout)
51 return self._in_queue.get(block, timeout)
52
52
53 def get_msgs(self):
53 def get_msgs(self):
54 """Get all messages that are currently ready."""
54 """Get all messages that are currently ready."""
55 msgs = []
55 msgs = []
56 while True:
56 while True:
57 try:
57 try:
58 msgs.append(self.get_msg(block=False))
58 msgs.append(self.get_msg(block=False))
59 except Empty:
59 except Empty:
60 break
60 break
61 return msgs
61 return msgs
62
62
63
63
64 class BlockingXReqSocketChannel(XReqSocketChannel):
64 class BlockingShellSocketChannel(ShellSocketChannel):
65
65
66 def __init__(self, context, session, address=None):
66 def __init__(self, context, session, address=None):
67 super(BlockingXReqSocketChannel, self).__init__(context, session,
67 super(BlockingShellSocketChannel, self).__init__(context, session,
68 address)
68 address)
69 self._in_queue = Queue()
69 self._in_queue = Queue()
70
70
71 def call_handlers(self, msg):
71 def call_handlers(self, msg):
72 #io.rprint('[[XReq]]', msg) # dbg
72 #io.rprint('[[Shell]]', msg) # dbg
73 self._in_queue.put(msg)
73 self._in_queue.put(msg)
74
74
75 def msg_ready(self):
75 def msg_ready(self):
76 """Is there a message that has been received?"""
76 """Is there a message that has been received?"""
77 if self._in_queue.qsize() == 0:
77 if self._in_queue.qsize() == 0:
78 return False
78 return False
79 else:
79 else:
80 return True
80 return True
81
81
82 def get_msg(self, block=True, timeout=None):
82 def get_msg(self, block=True, timeout=None):
83 """Get a message if there is one that is ready."""
83 """Get a message if there is one that is ready."""
84 return self._in_queue.get(block, timeout)
84 return self._in_queue.get(block, timeout)
85
85
86 def get_msgs(self):
86 def get_msgs(self):
87 """Get all messages that are currently ready."""
87 """Get all messages that are currently ready."""
88 msgs = []
88 msgs = []
89 while True:
89 while True:
90 try:
90 try:
91 msgs.append(self.get_msg(block=False))
91 msgs.append(self.get_msg(block=False))
92 except Empty:
92 except Empty:
93 break
93 break
94 return msgs
94 return msgs
95
95
96
96
97 class BlockingRepSocketChannel(RepSocketChannel):
97 class BlockingStdInSocketChannel(StdInSocketChannel):
98
98
99 def call_handlers(self, msg):
99 def call_handlers(self, msg):
100 #io.rprint('[[Rep]]', msg) # dbg
100 #io.rprint('[[Rep]]', msg) # dbg
101 pass
101 pass
102
102
103
103
104 class BlockingHBSocketChannel(HBSocketChannel):
104 class BlockingHBSocketChannel(HBSocketChannel):
105
105
106 # This kernel needs rapid monitoring capabilities
106 # This kernel needs rapid monitoring capabilities
107 time_to_dead = 0.2
107 time_to_dead = 0.2
108
108
109 def call_handlers(self, since_last_heartbeat):
109 def call_handlers(self, since_last_heartbeat):
110 #io.rprint('[[Heart]]', since_last_heartbeat) # dbg
110 #io.rprint('[[Heart]]', since_last_heartbeat) # dbg
111 pass
111 pass
112
112
113
113
114 class BlockingKernelManager(KernelManager):
114 class BlockingKernelManager(KernelManager):
115
115
116 # The classes to use for the various channels.
116 # The classes to use for the various channels.
117 xreq_channel_class = Type(BlockingXReqSocketChannel)
117 shell_channel_class = Type(BlockingShellSocketChannel)
118 sub_channel_class = Type(BlockingSubSocketChannel)
118 sub_channel_class = Type(BlockingSubSocketChannel)
119 rep_channel_class = Type(BlockingRepSocketChannel)
119 stdin_channel_class = Type(BlockingStdInSocketChannel)
120 hb_channel_class = Type(BlockingHBSocketChannel)
120 hb_channel_class = Type(BlockingHBSocketChannel)
121
121
@@ -1,968 +1,968 b''
1 """Base classes to manage the interaction with a running kernel.
1 """Base classes to manage the interaction with a running kernel.
2
2
3 TODO
3 TODO
4 * Create logger to handle debugging and console messages.
4 * Create logger to handle debugging and console messages.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2010 The IPython Development Team
8 # Copyright (C) 2008-2010 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # Standard library imports.
18 # Standard library imports.
19 import atexit
19 import atexit
20 import errno
20 import errno
21 from Queue import Queue, Empty
21 from Queue import Queue, Empty
22 from subprocess import Popen
22 from subprocess import Popen
23 import signal
23 import signal
24 import sys
24 import sys
25 from threading import Thread
25 from threading import Thread
26 import time
26 import time
27 import logging
27 import logging
28
28
29 # System library imports.
29 # System library imports.
30 import zmq
30 import zmq
31 from zmq import POLLIN, POLLOUT, POLLERR
31 from zmq import POLLIN, POLLOUT, POLLERR
32 from zmq.eventloop import ioloop
32 from zmq.eventloop import ioloop
33
33
34 # Local imports.
34 # Local imports.
35 from IPython.utils import io
35 from IPython.utils import io
36 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
36 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
37 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
37 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
38 from session import Session, Message
38 from session import Session, Message
39
39
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41 # Constants and exceptions
41 # Constants and exceptions
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43
43
44 class InvalidPortNumber(Exception):
44 class InvalidPortNumber(Exception):
45 pass
45 pass
46
46
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48 # Utility functions
48 # Utility functions
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50
50
51 # some utilities to validate message structure, these might get moved elsewhere
51 # some utilities to validate message structure, these might get moved elsewhere
52 # if they prove to have more generic utility
52 # if they prove to have more generic utility
53
53
54 def validate_string_list(lst):
54 def validate_string_list(lst):
55 """Validate that the input is a list of strings.
55 """Validate that the input is a list of strings.
56
56
57 Raises ValueError if not."""
57 Raises ValueError if not."""
58 if not isinstance(lst, list):
58 if not isinstance(lst, list):
59 raise ValueError('input %r must be a list' % lst)
59 raise ValueError('input %r must be a list' % lst)
60 for x in lst:
60 for x in lst:
61 if not isinstance(x, basestring):
61 if not isinstance(x, basestring):
62 raise ValueError('element %r in list must be a string' % x)
62 raise ValueError('element %r in list must be a string' % x)
63
63
64
64
65 def validate_string_dict(dct):
65 def validate_string_dict(dct):
66 """Validate that the input is a dict with string keys and values.
66 """Validate that the input is a dict with string keys and values.
67
67
68 Raises ValueError if not."""
68 Raises ValueError if not."""
69 for k,v in dct.iteritems():
69 for k,v in dct.iteritems():
70 if not isinstance(k, basestring):
70 if not isinstance(k, basestring):
71 raise ValueError('key %r in dict must be a string' % k)
71 raise ValueError('key %r in dict must be a string' % k)
72 if not isinstance(v, basestring):
72 if not isinstance(v, basestring):
73 raise ValueError('value %r in dict must be a string' % v)
73 raise ValueError('value %r in dict must be a string' % v)
74
74
75
75
76 #-----------------------------------------------------------------------------
76 #-----------------------------------------------------------------------------
77 # ZMQ Socket Channel classes
77 # ZMQ Socket Channel classes
78 #-----------------------------------------------------------------------------
78 #-----------------------------------------------------------------------------
79
79
80 class ZmqSocketChannel(Thread):
80 class ZMQSocketChannel(Thread):
81 """The base class for the channels that use ZMQ sockets.
81 """The base class for the channels that use ZMQ sockets.
82 """
82 """
83 context = None
83 context = None
84 session = None
84 session = None
85 socket = None
85 socket = None
86 ioloop = None
86 ioloop = None
87 iostate = None
87 iostate = None
88 _address = None
88 _address = None
89
89
90 def __init__(self, context, session, address):
90 def __init__(self, context, session, address):
91 """Create a channel
91 """Create a channel
92
92
93 Parameters
93 Parameters
94 ----------
94 ----------
95 context : :class:`zmq.Context`
95 context : :class:`zmq.Context`
96 The ZMQ context to use.
96 The ZMQ context to use.
97 session : :class:`session.Session`
97 session : :class:`session.Session`
98 The session to use.
98 The session to use.
99 address : tuple
99 address : tuple
100 Standard (ip, port) tuple that the kernel is listening on.
100 Standard (ip, port) tuple that the kernel is listening on.
101 """
101 """
102 super(ZmqSocketChannel, self).__init__()
102 super(ZMQSocketChannel, self).__init__()
103 self.daemon = True
103 self.daemon = True
104
104
105 self.context = context
105 self.context = context
106 self.session = session
106 self.session = session
107 if address[1] == 0:
107 if address[1] == 0:
108 message = 'The port number for a channel cannot be 0.'
108 message = 'The port number for a channel cannot be 0.'
109 raise InvalidPortNumber(message)
109 raise InvalidPortNumber(message)
110 self._address = address
110 self._address = address
111
111
112 def _run_loop(self):
112 def _run_loop(self):
113 """Run my loop, ignoring EINTR events in the poller"""
113 """Run my loop, ignoring EINTR events in the poller"""
114 while True:
114 while True:
115 try:
115 try:
116 self.ioloop.start()
116 self.ioloop.start()
117 except zmq.ZMQError as e:
117 except zmq.ZMQError as e:
118 if e.errno == errno.EINTR:
118 if e.errno == errno.EINTR:
119 continue
119 continue
120 else:
120 else:
121 raise
121 raise
122 else:
122 else:
123 break
123 break
124
124
125 def stop(self):
125 def stop(self):
126 """Stop the channel's activity.
126 """Stop the channel's activity.
127
127
128 This calls :method:`Thread.join` and returns when the thread
128 This calls :method:`Thread.join` and returns when the thread
129 terminates. :class:`RuntimeError` will be raised if
129 terminates. :class:`RuntimeError` will be raised if
130 :method:`self.start` is called again.
130 :method:`self.start` is called again.
131 """
131 """
132 self.join()
132 self.join()
133
133
134 @property
134 @property
135 def address(self):
135 def address(self):
136 """Get the channel's address as an (ip, port) tuple.
136 """Get the channel's address as an (ip, port) tuple.
137
137
138 By the default, the address is (localhost, 0), where 0 means a random
138 By the default, the address is (localhost, 0), where 0 means a random
139 port.
139 port.
140 """
140 """
141 return self._address
141 return self._address
142
142
143 def add_io_state(self, state):
143 def add_io_state(self, state):
144 """Add IO state to the eventloop.
144 """Add IO state to the eventloop.
145
145
146 Parameters
146 Parameters
147 ----------
147 ----------
148 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
148 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
149 The IO state flag to set.
149 The IO state flag to set.
150
150
151 This is thread safe as it uses the thread safe IOLoop.add_callback.
151 This is thread safe as it uses the thread safe IOLoop.add_callback.
152 """
152 """
153 def add_io_state_callback():
153 def add_io_state_callback():
154 if not self.iostate & state:
154 if not self.iostate & state:
155 self.iostate = self.iostate | state
155 self.iostate = self.iostate | state
156 self.ioloop.update_handler(self.socket, self.iostate)
156 self.ioloop.update_handler(self.socket, self.iostate)
157 self.ioloop.add_callback(add_io_state_callback)
157 self.ioloop.add_callback(add_io_state_callback)
158
158
159 def drop_io_state(self, state):
159 def drop_io_state(self, state):
160 """Drop IO state from the eventloop.
160 """Drop IO state from the eventloop.
161
161
162 Parameters
162 Parameters
163 ----------
163 ----------
164 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
164 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
165 The IO state flag to set.
165 The IO state flag to set.
166
166
167 This is thread safe as it uses the thread safe IOLoop.add_callback.
167 This is thread safe as it uses the thread safe IOLoop.add_callback.
168 """
168 """
169 def drop_io_state_callback():
169 def drop_io_state_callback():
170 if self.iostate & state:
170 if self.iostate & state:
171 self.iostate = self.iostate & (~state)
171 self.iostate = self.iostate & (~state)
172 self.ioloop.update_handler(self.socket, self.iostate)
172 self.ioloop.update_handler(self.socket, self.iostate)
173 self.ioloop.add_callback(drop_io_state_callback)
173 self.ioloop.add_callback(drop_io_state_callback)
174
174
175
175
176 class XReqSocketChannel(ZmqSocketChannel):
176 class ShellSocketChannel(ZMQSocketChannel):
177 """The XREQ channel for issues request/replies to the kernel.
177 """The XREQ channel for issues request/replies to the kernel.
178 """
178 """
179
179
180 command_queue = None
180 command_queue = None
181
181
182 def __init__(self, context, session, address):
182 def __init__(self, context, session, address):
183 super(XReqSocketChannel, self).__init__(context, session, address)
183 super(ShellSocketChannel, self).__init__(context, session, address)
184 self.command_queue = Queue()
184 self.command_queue = Queue()
185 self.ioloop = ioloop.IOLoop()
185 self.ioloop = ioloop.IOLoop()
186
186
187 def run(self):
187 def run(self):
188 """The thread's main activity. Call start() instead."""
188 """The thread's main activity. Call start() instead."""
189 self.socket = self.context.socket(zmq.XREQ)
189 self.socket = self.context.socket(zmq.XREQ)
190 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
190 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
191 self.socket.connect('tcp://%s:%i' % self.address)
191 self.socket.connect('tcp://%s:%i' % self.address)
192 self.iostate = POLLERR|POLLIN
192 self.iostate = POLLERR|POLLIN
193 self.ioloop.add_handler(self.socket, self._handle_events,
193 self.ioloop.add_handler(self.socket, self._handle_events,
194 self.iostate)
194 self.iostate)
195 self._run_loop()
195 self._run_loop()
196
196
197 def stop(self):
197 def stop(self):
198 self.ioloop.stop()
198 self.ioloop.stop()
199 super(XReqSocketChannel, self).stop()
199 super(ShellSocketChannel, self).stop()
200
200
201 def call_handlers(self, msg):
201 def call_handlers(self, msg):
202 """This method is called in the ioloop thread when a message arrives.
202 """This method is called in the ioloop thread when a message arrives.
203
203
204 Subclasses should override this method to handle incoming messages.
204 Subclasses should override this method to handle incoming messages.
205 It is important to remember that this method is called in the thread
205 It is important to remember that this method is called in the thread
206 so that some logic must be done to ensure that the application leve
206 so that some logic must be done to ensure that the application leve
207 handlers are called in the application thread.
207 handlers are called in the application thread.
208 """
208 """
209 raise NotImplementedError('call_handlers must be defined in a subclass.')
209 raise NotImplementedError('call_handlers must be defined in a subclass.')
210
210
211 def execute(self, code, silent=False,
211 def execute(self, code, silent=False,
212 user_variables=None, user_expressions=None):
212 user_variables=None, user_expressions=None):
213 """Execute code in the kernel.
213 """Execute code in the kernel.
214
214
215 Parameters
215 Parameters
216 ----------
216 ----------
217 code : str
217 code : str
218 A string of Python code.
218 A string of Python code.
219
219
220 silent : bool, optional (default False)
220 silent : bool, optional (default False)
221 If set, the kernel will execute the code as quietly possible.
221 If set, the kernel will execute the code as quietly possible.
222
222
223 user_variables : list, optional
223 user_variables : list, optional
224 A list of variable names to pull from the user's namespace. They
224 A list of variable names to pull from the user's namespace. They
225 will come back as a dict with these names as keys and their
225 will come back as a dict with these names as keys and their
226 :func:`repr` as values.
226 :func:`repr` as values.
227
227
228 user_expressions : dict, optional
228 user_expressions : dict, optional
229 A dict with string keys and to pull from the user's
229 A dict with string keys and to pull from the user's
230 namespace. They will come back as a dict with these names as keys
230 namespace. They will come back as a dict with these names as keys
231 and their :func:`repr` as values.
231 and their :func:`repr` as values.
232
232
233 Returns
233 Returns
234 -------
234 -------
235 The msg_id of the message sent.
235 The msg_id of the message sent.
236 """
236 """
237 if user_variables is None:
237 if user_variables is None:
238 user_variables = []
238 user_variables = []
239 if user_expressions is None:
239 if user_expressions is None:
240 user_expressions = {}
240 user_expressions = {}
241
241
242 # Don't waste network traffic if inputs are invalid
242 # Don't waste network traffic if inputs are invalid
243 if not isinstance(code, basestring):
243 if not isinstance(code, basestring):
244 raise ValueError('code %r must be a string' % code)
244 raise ValueError('code %r must be a string' % code)
245 validate_string_list(user_variables)
245 validate_string_list(user_variables)
246 validate_string_dict(user_expressions)
246 validate_string_dict(user_expressions)
247
247
248 # Create class for content/msg creation. Related to, but possibly
248 # Create class for content/msg creation. Related to, but possibly
249 # not in Session.
249 # not in Session.
250 content = dict(code=code, silent=silent,
250 content = dict(code=code, silent=silent,
251 user_variables=user_variables,
251 user_variables=user_variables,
252 user_expressions=user_expressions)
252 user_expressions=user_expressions)
253 msg = self.session.msg('execute_request', content)
253 msg = self.session.msg('execute_request', content)
254 self._queue_request(msg)
254 self._queue_request(msg)
255 return msg['header']['msg_id']
255 return msg['header']['msg_id']
256
256
257 def complete(self, text, line, cursor_pos, block=None):
257 def complete(self, text, line, cursor_pos, block=None):
258 """Tab complete text in the kernel's namespace.
258 """Tab complete text in the kernel's namespace.
259
259
260 Parameters
260 Parameters
261 ----------
261 ----------
262 text : str
262 text : str
263 The text to complete.
263 The text to complete.
264 line : str
264 line : str
265 The full line of text that is the surrounding context for the
265 The full line of text that is the surrounding context for the
266 text to complete.
266 text to complete.
267 cursor_pos : int
267 cursor_pos : int
268 The position of the cursor in the line where the completion was
268 The position of the cursor in the line where the completion was
269 requested.
269 requested.
270 block : str, optional
270 block : str, optional
271 The full block of code in which the completion is being requested.
271 The full block of code in which the completion is being requested.
272
272
273 Returns
273 Returns
274 -------
274 -------
275 The msg_id of the message sent.
275 The msg_id of the message sent.
276 """
276 """
277 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
277 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
278 msg = self.session.msg('complete_request', content)
278 msg = self.session.msg('complete_request', content)
279 self._queue_request(msg)
279 self._queue_request(msg)
280 return msg['header']['msg_id']
280 return msg['header']['msg_id']
281
281
282 def object_info(self, oname):
282 def object_info(self, oname):
283 """Get metadata information about an object.
283 """Get metadata information about an object.
284
284
285 Parameters
285 Parameters
286 ----------
286 ----------
287 oname : str
287 oname : str
288 A string specifying the object name.
288 A string specifying the object name.
289
289
290 Returns
290 Returns
291 -------
291 -------
292 The msg_id of the message sent.
292 The msg_id of the message sent.
293 """
293 """
294 content = dict(oname=oname)
294 content = dict(oname=oname)
295 msg = self.session.msg('object_info_request', content)
295 msg = self.session.msg('object_info_request', content)
296 self._queue_request(msg)
296 self._queue_request(msg)
297 return msg['header']['msg_id']
297 return msg['header']['msg_id']
298
298
299 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
299 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
300 """Get entries from the history list.
300 """Get entries from the history list.
301
301
302 Parameters
302 Parameters
303 ----------
303 ----------
304 raw : bool
304 raw : bool
305 If True, return the raw input.
305 If True, return the raw input.
306 output : bool
306 output : bool
307 If True, then return the output as well.
307 If True, then return the output as well.
308 hist_access_type : str
308 hist_access_type : str
309 'range' (fill in session, start and stop params), 'tail' (fill in n)
309 'range' (fill in session, start and stop params), 'tail' (fill in n)
310 or 'search' (fill in pattern param).
310 or 'search' (fill in pattern param).
311
311
312 session : int
312 session : int
313 For a range request, the session from which to get lines. Session
313 For a range request, the session from which to get lines. Session
314 numbers are positive integers; negative ones count back from the
314 numbers are positive integers; negative ones count back from the
315 current session.
315 current session.
316 start : int
316 start : int
317 The first line number of a history range.
317 The first line number of a history range.
318 stop : int
318 stop : int
319 The final (excluded) line number of a history range.
319 The final (excluded) line number of a history range.
320
320
321 n : int
321 n : int
322 The number of lines of history to get for a tail request.
322 The number of lines of history to get for a tail request.
323
323
324 pattern : str
324 pattern : str
325 The glob-syntax pattern for a search request.
325 The glob-syntax pattern for a search request.
326
326
327 Returns
327 Returns
328 -------
328 -------
329 The msg_id of the message sent.
329 The msg_id of the message sent.
330 """
330 """
331 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
331 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
332 **kwargs)
332 **kwargs)
333 msg = self.session.msg('history_request', content)
333 msg = self.session.msg('history_request', content)
334 self._queue_request(msg)
334 self._queue_request(msg)
335 return msg['header']['msg_id']
335 return msg['header']['msg_id']
336
336
337 def shutdown(self, restart=False):
337 def shutdown(self, restart=False):
338 """Request an immediate kernel shutdown.
338 """Request an immediate kernel shutdown.
339
339
340 Upon receipt of the (empty) reply, client code can safely assume that
340 Upon receipt of the (empty) reply, client code can safely assume that
341 the kernel has shut down and it's safe to forcefully terminate it if
341 the kernel has shut down and it's safe to forcefully terminate it if
342 it's still alive.
342 it's still alive.
343
343
344 The kernel will send the reply via a function registered with Python's
344 The kernel will send the reply via a function registered with Python's
345 atexit module, ensuring it's truly done as the kernel is done with all
345 atexit module, ensuring it's truly done as the kernel is done with all
346 normal operation.
346 normal operation.
347 """
347 """
348 # Send quit message to kernel. Once we implement kernel-side setattr,
348 # Send quit message to kernel. Once we implement kernel-side setattr,
349 # this should probably be done that way, but for now this will do.
349 # this should probably be done that way, but for now this will do.
350 msg = self.session.msg('shutdown_request', {'restart':restart})
350 msg = self.session.msg('shutdown_request', {'restart':restart})
351 self._queue_request(msg)
351 self._queue_request(msg)
352 return msg['header']['msg_id']
352 return msg['header']['msg_id']
353
353
354 def _handle_events(self, socket, events):
354 def _handle_events(self, socket, events):
355 if events & POLLERR:
355 if events & POLLERR:
356 self._handle_err()
356 self._handle_err()
357 if events & POLLOUT:
357 if events & POLLOUT:
358 self._handle_send()
358 self._handle_send()
359 if events & POLLIN:
359 if events & POLLIN:
360 self._handle_recv()
360 self._handle_recv()
361
361
362 def _handle_recv(self):
362 def _handle_recv(self):
363 ident,msg = self.session.recv(self.socket, 0)
363 ident,msg = self.session.recv(self.socket, 0)
364 self.call_handlers(msg)
364 self.call_handlers(msg)
365
365
366 def _handle_send(self):
366 def _handle_send(self):
367 try:
367 try:
368 msg = self.command_queue.get(False)
368 msg = self.command_queue.get(False)
369 except Empty:
369 except Empty:
370 pass
370 pass
371 else:
371 else:
372 self.session.send(self.socket,msg)
372 self.session.send(self.socket,msg)
373 if self.command_queue.empty():
373 if self.command_queue.empty():
374 self.drop_io_state(POLLOUT)
374 self.drop_io_state(POLLOUT)
375
375
376 def _handle_err(self):
376 def _handle_err(self):
377 # We don't want to let this go silently, so eventually we should log.
377 # We don't want to let this go silently, so eventually we should log.
378 raise zmq.ZMQError()
378 raise zmq.ZMQError()
379
379
380 def _queue_request(self, msg):
380 def _queue_request(self, msg):
381 self.command_queue.put(msg)
381 self.command_queue.put(msg)
382 self.add_io_state(POLLOUT)
382 self.add_io_state(POLLOUT)
383
383
384
384
385 class SubSocketChannel(ZmqSocketChannel):
385 class SubSocketChannel(ZMQSocketChannel):
386 """The SUB channel which listens for messages that the kernel publishes.
386 """The SUB channel which listens for messages that the kernel publishes.
387 """
387 """
388
388
389 def __init__(self, context, session, address):
389 def __init__(self, context, session, address):
390 super(SubSocketChannel, self).__init__(context, session, address)
390 super(SubSocketChannel, self).__init__(context, session, address)
391 self.ioloop = ioloop.IOLoop()
391 self.ioloop = ioloop.IOLoop()
392
392
393 def run(self):
393 def run(self):
394 """The thread's main activity. Call start() instead."""
394 """The thread's main activity. Call start() instead."""
395 self.socket = self.context.socket(zmq.SUB)
395 self.socket = self.context.socket(zmq.SUB)
396 self.socket.setsockopt(zmq.SUBSCRIBE,'')
396 self.socket.setsockopt(zmq.SUBSCRIBE,'')
397 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
397 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
398 self.socket.connect('tcp://%s:%i' % self.address)
398 self.socket.connect('tcp://%s:%i' % self.address)
399 self.iostate = POLLIN|POLLERR
399 self.iostate = POLLIN|POLLERR
400 self.ioloop.add_handler(self.socket, self._handle_events,
400 self.ioloop.add_handler(self.socket, self._handle_events,
401 self.iostate)
401 self.iostate)
402 self._run_loop()
402 self._run_loop()
403
403
404 def stop(self):
404 def stop(self):
405 self.ioloop.stop()
405 self.ioloop.stop()
406 super(SubSocketChannel, self).stop()
406 super(SubSocketChannel, self).stop()
407
407
408 def call_handlers(self, msg):
408 def call_handlers(self, msg):
409 """This method is called in the ioloop thread when a message arrives.
409 """This method is called in the ioloop thread when a message arrives.
410
410
411 Subclasses should override this method to handle incoming messages.
411 Subclasses should override this method to handle incoming messages.
412 It is important to remember that this method is called in the thread
412 It is important to remember that this method is called in the thread
413 so that some logic must be done to ensure that the application leve
413 so that some logic must be done to ensure that the application leve
414 handlers are called in the application thread.
414 handlers are called in the application thread.
415 """
415 """
416 raise NotImplementedError('call_handlers must be defined in a subclass.')
416 raise NotImplementedError('call_handlers must be defined in a subclass.')
417
417
418 def flush(self, timeout=1.0):
418 def flush(self, timeout=1.0):
419 """Immediately processes all pending messages on the SUB channel.
419 """Immediately processes all pending messages on the SUB channel.
420
420
421 Callers should use this method to ensure that :method:`call_handlers`
421 Callers should use this method to ensure that :method:`call_handlers`
422 has been called for all messages that have been received on the
422 has been called for all messages that have been received on the
423 0MQ SUB socket of this channel.
423 0MQ SUB socket of this channel.
424
424
425 This method is thread safe.
425 This method is thread safe.
426
426
427 Parameters
427 Parameters
428 ----------
428 ----------
429 timeout : float, optional
429 timeout : float, optional
430 The maximum amount of time to spend flushing, in seconds. The
430 The maximum amount of time to spend flushing, in seconds. The
431 default is one second.
431 default is one second.
432 """
432 """
433 # We do the IOLoop callback process twice to ensure that the IOLoop
433 # We do the IOLoop callback process twice to ensure that the IOLoop
434 # gets to perform at least one full poll.
434 # gets to perform at least one full poll.
435 stop_time = time.time() + timeout
435 stop_time = time.time() + timeout
436 for i in xrange(2):
436 for i in xrange(2):
437 self._flushed = False
437 self._flushed = False
438 self.ioloop.add_callback(self._flush)
438 self.ioloop.add_callback(self._flush)
439 while not self._flushed and time.time() < stop_time:
439 while not self._flushed and time.time() < stop_time:
440 time.sleep(0.01)
440 time.sleep(0.01)
441
441
442 def _handle_events(self, socket, events):
442 def _handle_events(self, socket, events):
443 # Turn on and off POLLOUT depending on if we have made a request
443 # Turn on and off POLLOUT depending on if we have made a request
444 if events & POLLERR:
444 if events & POLLERR:
445 self._handle_err()
445 self._handle_err()
446 if events & POLLIN:
446 if events & POLLIN:
447 self._handle_recv()
447 self._handle_recv()
448
448
449 def _handle_err(self):
449 def _handle_err(self):
450 # We don't want to let this go silently, so eventually we should log.
450 # We don't want to let this go silently, so eventually we should log.
451 raise zmq.ZMQError()
451 raise zmq.ZMQError()
452
452
453 def _handle_recv(self):
453 def _handle_recv(self):
454 # Get all of the messages we can
454 # Get all of the messages we can
455 while True:
455 while True:
456 try:
456 try:
457 ident,msg = self.session.recv(self.socket)
457 ident,msg = self.session.recv(self.socket)
458 except zmq.ZMQError:
458 except zmq.ZMQError:
459 # Check the errno?
459 # Check the errno?
460 # Will this trigger POLLERR?
460 # Will this trigger POLLERR?
461 break
461 break
462 else:
462 else:
463 if msg is None:
463 if msg is None:
464 break
464 break
465 self.call_handlers(msg)
465 self.call_handlers(msg)
466
466
467 def _flush(self):
467 def _flush(self):
468 """Callback for :method:`self.flush`."""
468 """Callback for :method:`self.flush`."""
469 self._flushed = True
469 self._flushed = True
470
470
471
471
472 class RepSocketChannel(ZmqSocketChannel):
472 class StdInSocketChannel(ZMQSocketChannel):
473 """A reply channel to handle raw_input requests that the kernel makes."""
473 """A reply channel to handle raw_input requests that the kernel makes."""
474
474
475 msg_queue = None
475 msg_queue = None
476
476
477 def __init__(self, context, session, address):
477 def __init__(self, context, session, address):
478 super(RepSocketChannel, self).__init__(context, session, address)
478 super(StdInSocketChannel, self).__init__(context, session, address)
479 self.ioloop = ioloop.IOLoop()
479 self.ioloop = ioloop.IOLoop()
480 self.msg_queue = Queue()
480 self.msg_queue = Queue()
481
481
482 def run(self):
482 def run(self):
483 """The thread's main activity. Call start() instead."""
483 """The thread's main activity. Call start() instead."""
484 self.socket = self.context.socket(zmq.XREQ)
484 self.socket = self.context.socket(zmq.XREQ)
485 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
485 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
486 self.socket.connect('tcp://%s:%i' % self.address)
486 self.socket.connect('tcp://%s:%i' % self.address)
487 self.iostate = POLLERR|POLLIN
487 self.iostate = POLLERR|POLLIN
488 self.ioloop.add_handler(self.socket, self._handle_events,
488 self.ioloop.add_handler(self.socket, self._handle_events,
489 self.iostate)
489 self.iostate)
490 self._run_loop()
490 self._run_loop()
491
491
492 def stop(self):
492 def stop(self):
493 self.ioloop.stop()
493 self.ioloop.stop()
494 super(RepSocketChannel, self).stop()
494 super(StdInSocketChannel, self).stop()
495
495
496 def call_handlers(self, msg):
496 def call_handlers(self, msg):
497 """This method is called in the ioloop thread when a message arrives.
497 """This method is called in the ioloop thread when a message arrives.
498
498
499 Subclasses should override this method to handle incoming messages.
499 Subclasses should override this method to handle incoming messages.
500 It is important to remember that this method is called in the thread
500 It is important to remember that this method is called in the thread
501 so that some logic must be done to ensure that the application leve
501 so that some logic must be done to ensure that the application leve
502 handlers are called in the application thread.
502 handlers are called in the application thread.
503 """
503 """
504 raise NotImplementedError('call_handlers must be defined in a subclass.')
504 raise NotImplementedError('call_handlers must be defined in a subclass.')
505
505
506 def input(self, string):
506 def input(self, string):
507 """Send a string of raw input to the kernel."""
507 """Send a string of raw input to the kernel."""
508 content = dict(value=string)
508 content = dict(value=string)
509 msg = self.session.msg('input_reply', content)
509 msg = self.session.msg('input_reply', content)
510 self._queue_reply(msg)
510 self._queue_reply(msg)
511
511
512 def _handle_events(self, socket, events):
512 def _handle_events(self, socket, events):
513 if events & POLLERR:
513 if events & POLLERR:
514 self._handle_err()
514 self._handle_err()
515 if events & POLLOUT:
515 if events & POLLOUT:
516 self._handle_send()
516 self._handle_send()
517 if events & POLLIN:
517 if events & POLLIN:
518 self._handle_recv()
518 self._handle_recv()
519
519
520 def _handle_recv(self):
520 def _handle_recv(self):
521 ident,msg = self.session.recv(self.socket, 0)
521 ident,msg = self.session.recv(self.socket, 0)
522 self.call_handlers(msg)
522 self.call_handlers(msg)
523
523
524 def _handle_send(self):
524 def _handle_send(self):
525 try:
525 try:
526 msg = self.msg_queue.get(False)
526 msg = self.msg_queue.get(False)
527 except Empty:
527 except Empty:
528 pass
528 pass
529 else:
529 else:
530 self.session.send(self.socket,msg)
530 self.session.send(self.socket,msg)
531 if self.msg_queue.empty():
531 if self.msg_queue.empty():
532 self.drop_io_state(POLLOUT)
532 self.drop_io_state(POLLOUT)
533
533
534 def _handle_err(self):
534 def _handle_err(self):
535 # We don't want to let this go silently, so eventually we should log.
535 # We don't want to let this go silently, so eventually we should log.
536 raise zmq.ZMQError()
536 raise zmq.ZMQError()
537
537
538 def _queue_reply(self, msg):
538 def _queue_reply(self, msg):
539 self.msg_queue.put(msg)
539 self.msg_queue.put(msg)
540 self.add_io_state(POLLOUT)
540 self.add_io_state(POLLOUT)
541
541
542
542
543 class HBSocketChannel(ZmqSocketChannel):
543 class HBSocketChannel(ZMQSocketChannel):
544 """The heartbeat channel which monitors the kernel heartbeat.
544 """The heartbeat channel which monitors the kernel heartbeat.
545
545
546 Note that the heartbeat channel is paused by default. As long as you start
546 Note that the heartbeat channel is paused by default. As long as you start
547 this channel, the kernel manager will ensure that it is paused and un-paused
547 this channel, the kernel manager will ensure that it is paused and un-paused
548 as appropriate.
548 as appropriate.
549 """
549 """
550
550
551 time_to_dead = 3.0
551 time_to_dead = 3.0
552 socket = None
552 socket = None
553 poller = None
553 poller = None
554 _running = None
554 _running = None
555 _pause = None
555 _pause = None
556
556
557 def __init__(self, context, session, address):
557 def __init__(self, context, session, address):
558 super(HBSocketChannel, self).__init__(context, session, address)
558 super(HBSocketChannel, self).__init__(context, session, address)
559 self._running = False
559 self._running = False
560 self._pause = True
560 self._pause = True
561
561
562 def _create_socket(self):
562 def _create_socket(self):
563 self.socket = self.context.socket(zmq.REQ)
563 self.socket = self.context.socket(zmq.REQ)
564 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
564 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
565 self.socket.connect('tcp://%s:%i' % self.address)
565 self.socket.connect('tcp://%s:%i' % self.address)
566 self.poller = zmq.Poller()
566 self.poller = zmq.Poller()
567 self.poller.register(self.socket, zmq.POLLIN)
567 self.poller.register(self.socket, zmq.POLLIN)
568
568
569 def run(self):
569 def run(self):
570 """The thread's main activity. Call start() instead."""
570 """The thread's main activity. Call start() instead."""
571 self._create_socket()
571 self._create_socket()
572 self._running = True
572 self._running = True
573 while self._running:
573 while self._running:
574 if self._pause:
574 if self._pause:
575 time.sleep(self.time_to_dead)
575 time.sleep(self.time_to_dead)
576 else:
576 else:
577 since_last_heartbeat = 0.0
577 since_last_heartbeat = 0.0
578 request_time = time.time()
578 request_time = time.time()
579 try:
579 try:
580 #io.rprint('Ping from HB channel') # dbg
580 #io.rprint('Ping from HB channel') # dbg
581 self.socket.send(b'ping')
581 self.socket.send(b'ping')
582 except zmq.ZMQError, e:
582 except zmq.ZMQError, e:
583 #io.rprint('*** HB Error:', e) # dbg
583 #io.rprint('*** HB Error:', e) # dbg
584 if e.errno == zmq.EFSM:
584 if e.errno == zmq.EFSM:
585 #io.rprint('sleep...', self.time_to_dead) # dbg
585 #io.rprint('sleep...', self.time_to_dead) # dbg
586 time.sleep(self.time_to_dead)
586 time.sleep(self.time_to_dead)
587 self._create_socket()
587 self._create_socket()
588 else:
588 else:
589 raise
589 raise
590 else:
590 else:
591 while True:
591 while True:
592 try:
592 try:
593 self.socket.recv(zmq.NOBLOCK)
593 self.socket.recv(zmq.NOBLOCK)
594 except zmq.ZMQError, e:
594 except zmq.ZMQError, e:
595 #io.rprint('*** HB Error 2:', e) # dbg
595 #io.rprint('*** HB Error 2:', e) # dbg
596 if e.errno == zmq.EAGAIN:
596 if e.errno == zmq.EAGAIN:
597 before_poll = time.time()
597 before_poll = time.time()
598 until_dead = self.time_to_dead - (before_poll -
598 until_dead = self.time_to_dead - (before_poll -
599 request_time)
599 request_time)
600
600
601 # When the return value of poll() is an empty
601 # When the return value of poll() is an empty
602 # list, that is when things have gone wrong
602 # list, that is when things have gone wrong
603 # (zeromq bug). As long as it is not an empty
603 # (zeromq bug). As long as it is not an empty
604 # list, poll is working correctly even if it
604 # list, poll is working correctly even if it
605 # returns quickly. Note: poll timeout is in
605 # returns quickly. Note: poll timeout is in
606 # milliseconds.
606 # milliseconds.
607 if until_dead > 0.0:
607 if until_dead > 0.0:
608 while True:
608 while True:
609 try:
609 try:
610 self.poller.poll(1000 * until_dead)
610 self.poller.poll(1000 * until_dead)
611 except zmq.ZMQError as e:
611 except zmq.ZMQError as e:
612 if e.errno == errno.EINTR:
612 if e.errno == errno.EINTR:
613 continue
613 continue
614 else:
614 else:
615 raise
615 raise
616 else:
616 else:
617 break
617 break
618
618
619 since_last_heartbeat = time.time()-request_time
619 since_last_heartbeat = time.time()-request_time
620 if since_last_heartbeat > self.time_to_dead:
620 if since_last_heartbeat > self.time_to_dead:
621 self.call_handlers(since_last_heartbeat)
621 self.call_handlers(since_last_heartbeat)
622 break
622 break
623 else:
623 else:
624 # FIXME: We should probably log this instead.
624 # FIXME: We should probably log this instead.
625 raise
625 raise
626 else:
626 else:
627 until_dead = self.time_to_dead - (time.time() -
627 until_dead = self.time_to_dead - (time.time() -
628 request_time)
628 request_time)
629 if until_dead > 0.0:
629 if until_dead > 0.0:
630 #io.rprint('sleep...', self.time_to_dead) # dbg
630 #io.rprint('sleep...', self.time_to_dead) # dbg
631 time.sleep(until_dead)
631 time.sleep(until_dead)
632 break
632 break
633
633
634 def pause(self):
634 def pause(self):
635 """Pause the heartbeat."""
635 """Pause the heartbeat."""
636 self._pause = True
636 self._pause = True
637
637
638 def unpause(self):
638 def unpause(self):
639 """Unpause the heartbeat."""
639 """Unpause the heartbeat."""
640 self._pause = False
640 self._pause = False
641
641
642 def is_beating(self):
642 def is_beating(self):
643 """Is the heartbeat running and not paused."""
643 """Is the heartbeat running and not paused."""
644 if self.is_alive() and not self._pause:
644 if self.is_alive() and not self._pause:
645 return True
645 return True
646 else:
646 else:
647 return False
647 return False
648
648
649 def stop(self):
649 def stop(self):
650 self._running = False
650 self._running = False
651 super(HBSocketChannel, self).stop()
651 super(HBSocketChannel, self).stop()
652
652
653 def call_handlers(self, since_last_heartbeat):
653 def call_handlers(self, since_last_heartbeat):
654 """This method is called in the ioloop thread when a message arrives.
654 """This method is called in the ioloop thread when a message arrives.
655
655
656 Subclasses should override this method to handle incoming messages.
656 Subclasses should override this method to handle incoming messages.
657 It is important to remember that this method is called in the thread
657 It is important to remember that this method is called in the thread
658 so that some logic must be done to ensure that the application leve
658 so that some logic must be done to ensure that the application leve
659 handlers are called in the application thread.
659 handlers are called in the application thread.
660 """
660 """
661 raise NotImplementedError('call_handlers must be defined in a subclass.')
661 raise NotImplementedError('call_handlers must be defined in a subclass.')
662
662
663
663
664 #-----------------------------------------------------------------------------
664 #-----------------------------------------------------------------------------
665 # Main kernel manager class
665 # Main kernel manager class
666 #-----------------------------------------------------------------------------
666 #-----------------------------------------------------------------------------
667
667
668 class KernelManager(HasTraits):
668 class KernelManager(HasTraits):
669 """ Manages a kernel for a frontend.
669 """ Manages a kernel for a frontend.
670
670
671 The SUB channel is for the frontend to receive messages published by the
671 The SUB channel is for the frontend to receive messages published by the
672 kernel.
672 kernel.
673
673
674 The REQ channel is for the frontend to make requests of the kernel.
674 The REQ channel is for the frontend to make requests of the kernel.
675
675
676 The REP channel is for the kernel to request stdin (raw_input) from the
676 The REP channel is for the kernel to request stdin (raw_input) from the
677 frontend.
677 frontend.
678 """
678 """
679 # The PyZMQ Context to use for communication with the kernel.
679 # The PyZMQ Context to use for communication with the kernel.
680 context = Instance(zmq.Context,(),{})
680 context = Instance(zmq.Context,(),{})
681
681
682 # The Session to use for communication with the kernel.
682 # The Session to use for communication with the kernel.
683 session = Instance(Session,(),{})
683 session = Instance(Session,(),{})
684
684
685 # The kernel process with which the KernelManager is communicating.
685 # The kernel process with which the KernelManager is communicating.
686 kernel = Instance(Popen)
686 kernel = Instance(Popen)
687
687
688 # The addresses for the communication channels.
688 # The addresses for the communication channels.
689 xreq_address = TCPAddress((LOCALHOST, 0))
689 shell_address = TCPAddress((LOCALHOST, 0))
690 sub_address = TCPAddress((LOCALHOST, 0))
690 sub_address = TCPAddress((LOCALHOST, 0))
691 rep_address = TCPAddress((LOCALHOST, 0))
691 stdin_address = TCPAddress((LOCALHOST, 0))
692 hb_address = TCPAddress((LOCALHOST, 0))
692 hb_address = TCPAddress((LOCALHOST, 0))
693
693
694 # The classes to use for the various channels.
694 # The classes to use for the various channels.
695 xreq_channel_class = Type(XReqSocketChannel)
695 shell_channel_class = Type(ShellSocketChannel)
696 sub_channel_class = Type(SubSocketChannel)
696 sub_channel_class = Type(SubSocketChannel)
697 rep_channel_class = Type(RepSocketChannel)
697 stdin_channel_class = Type(StdInSocketChannel)
698 hb_channel_class = Type(HBSocketChannel)
698 hb_channel_class = Type(HBSocketChannel)
699
699
700 # Protected traits.
700 # Protected traits.
701 _launch_args = Any
701 _launch_args = Any
702 _xreq_channel = Any
702 _shell_channel = Any
703 _sub_channel = Any
703 _sub_channel = Any
704 _rep_channel = Any
704 _stdin_channel = Any
705 _hb_channel = Any
705 _hb_channel = Any
706
706
707 def __init__(self, **kwargs):
707 def __init__(self, **kwargs):
708 super(KernelManager, self).__init__(**kwargs)
708 super(KernelManager, self).__init__(**kwargs)
709 # Uncomment this to try closing the context.
709 # Uncomment this to try closing the context.
710 # atexit.register(self.context.close)
710 # atexit.register(self.context.close)
711
711
712 #--------------------------------------------------------------------------
712 #--------------------------------------------------------------------------
713 # Channel management methods:
713 # Channel management methods:
714 #--------------------------------------------------------------------------
714 #--------------------------------------------------------------------------
715
715
716 def start_channels(self, xreq=True, sub=True, rep=True, hb=True):
716 def start_channels(self, shell=True, sub=True, stdin=True, hb=True):
717 """Starts the channels for this kernel.
717 """Starts the channels for this kernel.
718
718
719 This will create the channels if they do not exist and then start
719 This will create the channels if they do not exist and then start
720 them. If port numbers of 0 are being used (random ports) then you
720 them. If port numbers of 0 are being used (random ports) then you
721 must first call :method:`start_kernel`. If the channels have been
721 must first call :method:`start_kernel`. If the channels have been
722 stopped and you call this, :class:`RuntimeError` will be raised.
722 stopped and you call this, :class:`RuntimeError` will be raised.
723 """
723 """
724 if xreq:
724 if shell:
725 self.xreq_channel.start()
725 self.shell_channel.start()
726 if sub:
726 if sub:
727 self.sub_channel.start()
727 self.sub_channel.start()
728 if rep:
728 if stdin:
729 self.rep_channel.start()
729 self.stdin_channel.start()
730 if hb:
730 if hb:
731 self.hb_channel.start()
731 self.hb_channel.start()
732
732
733 def stop_channels(self):
733 def stop_channels(self):
734 """Stops all the running channels for this kernel.
734 """Stops all the running channels for this kernel.
735 """
735 """
736 if self.xreq_channel.is_alive():
736 if self.shell_channel.is_alive():
737 self.xreq_channel.stop()
737 self.shell_channel.stop()
738 if self.sub_channel.is_alive():
738 if self.sub_channel.is_alive():
739 self.sub_channel.stop()
739 self.sub_channel.stop()
740 if self.rep_channel.is_alive():
740 if self.stdin_channel.is_alive():
741 self.rep_channel.stop()
741 self.stdin_channel.stop()
742 if self.hb_channel.is_alive():
742 if self.hb_channel.is_alive():
743 self.hb_channel.stop()
743 self.hb_channel.stop()
744
744
745 @property
745 @property
746 def channels_running(self):
746 def channels_running(self):
747 """Are any of the channels created and running?"""
747 """Are any of the channels created and running?"""
748 return (self.xreq_channel.is_alive() or self.sub_channel.is_alive() or
748 return (self.shell_channel.is_alive() or self.sub_channel.is_alive() or
749 self.rep_channel.is_alive() or self.hb_channel.is_alive())
749 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
750
750
751 #--------------------------------------------------------------------------
751 #--------------------------------------------------------------------------
752 # Kernel process management methods:
752 # Kernel process management methods:
753 #--------------------------------------------------------------------------
753 #--------------------------------------------------------------------------
754
754
755 def start_kernel(self, **kw):
755 def start_kernel(self, **kw):
756 """Starts a kernel process and configures the manager to use it.
756 """Starts a kernel process and configures the manager to use it.
757
757
758 If random ports (port=0) are being used, this method must be called
758 If random ports (port=0) are being used, this method must be called
759 before the channels are created.
759 before the channels are created.
760
760
761 Parameters:
761 Parameters:
762 -----------
762 -----------
763 ipython : bool, optional (default True)
763 ipython : bool, optional (default True)
764 Whether to use an IPython kernel instead of a plain Python kernel.
764 Whether to use an IPython kernel instead of a plain Python kernel.
765
765
766 **kw : optional
766 **kw : optional
767 See respective options for IPython and Python kernels.
767 See respective options for IPython and Python kernels.
768 """
768 """
769 xreq, sub, rep, hb = self.xreq_address, self.sub_address, \
769 shell, sub, stdin, hb = self.shell_address, self.sub_address, \
770 self.rep_address, self.hb_address
770 self.stdin_address, self.hb_address
771 if xreq[0] not in LOCAL_IPS or sub[0] not in LOCAL_IPS or \
771 if shell[0] not in LOCAL_IPS or sub[0] not in LOCAL_IPS or \
772 rep[0] not in LOCAL_IPS or hb[0] not in LOCAL_IPS:
772 stdin[0] not in LOCAL_IPS or hb[0] not in LOCAL_IPS:
773 raise RuntimeError("Can only launch a kernel on a local interface. "
773 raise RuntimeError("Can only launch a kernel on a local interface. "
774 "Make sure that the '*_address' attributes are "
774 "Make sure that the '*_address' attributes are "
775 "configured properly. "
775 "configured properly. "
776 "Currently valid addresses are: %s"%LOCAL_IPS
776 "Currently valid addresses are: %s"%LOCAL_IPS
777 )
777 )
778
778
779 self._launch_args = kw.copy()
779 self._launch_args = kw.copy()
780 if kw.pop('ipython', True):
780 if kw.pop('ipython', True):
781 from ipkernel import launch_kernel
781 from ipkernel import launch_kernel
782 else:
782 else:
783 from pykernel import launch_kernel
783 from pykernel import launch_kernel
784 self.kernel, xrep, pub, req, _hb = launch_kernel(
784 self.kernel, xrep, pub, req, _hb = launch_kernel(
785 shell_port=xreq[1], iopub_port=sub[1],
785 shell_port=shell[1], iopub_port=sub[1],
786 stdin_port=rep[1], hb_port=hb[1], **kw)
786 stdin_port=stdin[1], hb_port=hb[1], **kw)
787 self.xreq_address = (xreq[0], xrep)
787 self.shell_address = (shell[0], xrep)
788 self.sub_address = (sub[0], pub)
788 self.sub_address = (sub[0], pub)
789 self.rep_address = (rep[0], req)
789 self.stdin_address = (stdin[0], req)
790 self.hb_address = (hb[0], _hb)
790 self.hb_address = (hb[0], _hb)
791
791
792 def shutdown_kernel(self, restart=False):
792 def shutdown_kernel(self, restart=False):
793 """ Attempts to the stop the kernel process cleanly. If the kernel
793 """ Attempts to the stop the kernel process cleanly. If the kernel
794 cannot be stopped, it is killed, if possible.
794 cannot be stopped, it is killed, if possible.
795 """
795 """
796 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
796 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
797 if sys.platform == 'win32':
797 if sys.platform == 'win32':
798 self.kill_kernel()
798 self.kill_kernel()
799 return
799 return
800
800
801 # Pause the heart beat channel if it exists.
801 # Pause the heart beat channel if it exists.
802 if self._hb_channel is not None:
802 if self._hb_channel is not None:
803 self._hb_channel.pause()
803 self._hb_channel.pause()
804
804
805 # Don't send any additional kernel kill messages immediately, to give
805 # Don't send any additional kernel kill messages immediately, to give
806 # the kernel a chance to properly execute shutdown actions. Wait for at
806 # the kernel a chance to properly execute shutdown actions. Wait for at
807 # most 1s, checking every 0.1s.
807 # most 1s, checking every 0.1s.
808 self.xreq_channel.shutdown(restart=restart)
808 self.shell_channel.shutdown(restart=restart)
809 for i in range(10):
809 for i in range(10):
810 if self.is_alive:
810 if self.is_alive:
811 time.sleep(0.1)
811 time.sleep(0.1)
812 else:
812 else:
813 break
813 break
814 else:
814 else:
815 # OK, we've waited long enough.
815 # OK, we've waited long enough.
816 if self.has_kernel:
816 if self.has_kernel:
817 self.kill_kernel()
817 self.kill_kernel()
818
818
819 def restart_kernel(self, now=False, **kw):
819 def restart_kernel(self, now=False, **kw):
820 """Restarts a kernel with the arguments that were used to launch it.
820 """Restarts a kernel with the arguments that were used to launch it.
821
821
822 If the old kernel was launched with random ports, the same ports will be
822 If the old kernel was launched with random ports, the same ports will be
823 used for the new kernel.
823 used for the new kernel.
824
824
825 Parameters
825 Parameters
826 ----------
826 ----------
827 now : bool, optional
827 now : bool, optional
828 If True, the kernel is forcefully restarted *immediately*, without
828 If True, the kernel is forcefully restarted *immediately*, without
829 having a chance to do any cleanup action. Otherwise the kernel is
829 having a chance to do any cleanup action. Otherwise the kernel is
830 given 1s to clean up before a forceful restart is issued.
830 given 1s to clean up before a forceful restart is issued.
831
831
832 In all cases the kernel is restarted, the only difference is whether
832 In all cases the kernel is restarted, the only difference is whether
833 it is given a chance to perform a clean shutdown or not.
833 it is given a chance to perform a clean shutdown or not.
834
834
835 **kw : optional
835 **kw : optional
836 Any options specified here will replace those used to launch the
836 Any options specified here will replace those used to launch the
837 kernel.
837 kernel.
838 """
838 """
839 if self._launch_args is None:
839 if self._launch_args is None:
840 raise RuntimeError("Cannot restart the kernel. "
840 raise RuntimeError("Cannot restart the kernel. "
841 "No previous call to 'start_kernel'.")
841 "No previous call to 'start_kernel'.")
842 else:
842 else:
843 # Stop currently running kernel.
843 # Stop currently running kernel.
844 if self.has_kernel:
844 if self.has_kernel:
845 if now:
845 if now:
846 self.kill_kernel()
846 self.kill_kernel()
847 else:
847 else:
848 self.shutdown_kernel(restart=True)
848 self.shutdown_kernel(restart=True)
849
849
850 # Start new kernel.
850 # Start new kernel.
851 self._launch_args.update(kw)
851 self._launch_args.update(kw)
852 self.start_kernel(**self._launch_args)
852 self.start_kernel(**self._launch_args)
853
853
854 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
854 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
855 # unless there is some delay here.
855 # unless there is some delay here.
856 if sys.platform == 'win32':
856 if sys.platform == 'win32':
857 time.sleep(0.2)
857 time.sleep(0.2)
858
858
859 @property
859 @property
860 def has_kernel(self):
860 def has_kernel(self):
861 """Returns whether a kernel process has been specified for the kernel
861 """Returns whether a kernel process has been specified for the kernel
862 manager.
862 manager.
863 """
863 """
864 return self.kernel is not None
864 return self.kernel is not None
865
865
866 def kill_kernel(self):
866 def kill_kernel(self):
867 """ Kill the running kernel. """
867 """ Kill the running kernel. """
868 if self.has_kernel:
868 if self.has_kernel:
869 # Pause the heart beat channel if it exists.
869 # Pause the heart beat channel if it exists.
870 if self._hb_channel is not None:
870 if self._hb_channel is not None:
871 self._hb_channel.pause()
871 self._hb_channel.pause()
872
872
873 # Attempt to kill the kernel.
873 # Attempt to kill the kernel.
874 try:
874 try:
875 self.kernel.kill()
875 self.kernel.kill()
876 except OSError, e:
876 except OSError, e:
877 # In Windows, we will get an Access Denied error if the process
877 # In Windows, we will get an Access Denied error if the process
878 # has already terminated. Ignore it.
878 # has already terminated. Ignore it.
879 if sys.platform == 'win32':
879 if sys.platform == 'win32':
880 if e.winerror != 5:
880 if e.winerror != 5:
881 raise
881 raise
882 # On Unix, we may get an ESRCH error if the process has already
882 # On Unix, we may get an ESRCH error if the process has already
883 # terminated. Ignore it.
883 # terminated. Ignore it.
884 else:
884 else:
885 from errno import ESRCH
885 from errno import ESRCH
886 if e.errno != ESRCH:
886 if e.errno != ESRCH:
887 raise
887 raise
888 self.kernel = None
888 self.kernel = None
889 else:
889 else:
890 raise RuntimeError("Cannot kill kernel. No kernel is running!")
890 raise RuntimeError("Cannot kill kernel. No kernel is running!")
891
891
892 def interrupt_kernel(self):
892 def interrupt_kernel(self):
893 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
893 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
894 well supported on all platforms.
894 well supported on all platforms.
895 """
895 """
896 if self.has_kernel:
896 if self.has_kernel:
897 if sys.platform == 'win32':
897 if sys.platform == 'win32':
898 from parentpoller import ParentPollerWindows as Poller
898 from parentpoller import ParentPollerWindows as Poller
899 Poller.send_interrupt(self.kernel.win32_interrupt_event)
899 Poller.send_interrupt(self.kernel.win32_interrupt_event)
900 else:
900 else:
901 self.kernel.send_signal(signal.SIGINT)
901 self.kernel.send_signal(signal.SIGINT)
902 else:
902 else:
903 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
903 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
904
904
905 def signal_kernel(self, signum):
905 def signal_kernel(self, signum):
906 """ Sends a signal to the kernel. Note that since only SIGTERM is
906 """ Sends a signal to the kernel. Note that since only SIGTERM is
907 supported on Windows, this function is only useful on Unix systems.
907 supported on Windows, this function is only useful on Unix systems.
908 """
908 """
909 if self.has_kernel:
909 if self.has_kernel:
910 self.kernel.send_signal(signum)
910 self.kernel.send_signal(signum)
911 else:
911 else:
912 raise RuntimeError("Cannot signal kernel. No kernel is running!")
912 raise RuntimeError("Cannot signal kernel. No kernel is running!")
913
913
914 @property
914 @property
915 def is_alive(self):
915 def is_alive(self):
916 """Is the kernel process still running?"""
916 """Is the kernel process still running?"""
917 # FIXME: not using a heartbeat means this method is broken for any
917 # FIXME: not using a heartbeat means this method is broken for any
918 # remote kernel, it's only capable of handling local kernels.
918 # remote kernel, it's only capable of handling local kernels.
919 if self.has_kernel:
919 if self.has_kernel:
920 if self.kernel.poll() is None:
920 if self.kernel.poll() is None:
921 return True
921 return True
922 else:
922 else:
923 return False
923 return False
924 else:
924 else:
925 # We didn't start the kernel with this KernelManager so we don't
925 # We didn't start the kernel with this KernelManager so we don't
926 # know if it is running. We should use a heartbeat for this case.
926 # know if it is running. We should use a heartbeat for this case.
927 return True
927 return True
928
928
929 #--------------------------------------------------------------------------
929 #--------------------------------------------------------------------------
930 # Channels used for communication with the kernel:
930 # Channels used for communication with the kernel:
931 #--------------------------------------------------------------------------
931 #--------------------------------------------------------------------------
932
932
933 @property
933 @property
934 def xreq_channel(self):
934 def shell_channel(self):
935 """Get the REQ socket channel object to make requests of the kernel."""
935 """Get the REQ socket channel object to make requests of the kernel."""
936 if self._xreq_channel is None:
936 if self._shell_channel is None:
937 self._xreq_channel = self.xreq_channel_class(self.context,
937 self._shell_channel = self.shell_channel_class(self.context,
938 self.session,
938 self.session,
939 self.xreq_address)
939 self.shell_address)
940 return self._xreq_channel
940 return self._shell_channel
941
941
942 @property
942 @property
943 def sub_channel(self):
943 def sub_channel(self):
944 """Get the SUB socket channel object."""
944 """Get the SUB socket channel object."""
945 if self._sub_channel is None:
945 if self._sub_channel is None:
946 self._sub_channel = self.sub_channel_class(self.context,
946 self._sub_channel = self.sub_channel_class(self.context,
947 self.session,
947 self.session,
948 self.sub_address)
948 self.sub_address)
949 return self._sub_channel
949 return self._sub_channel
950
950
951 @property
951 @property
952 def rep_channel(self):
952 def stdin_channel(self):
953 """Get the REP socket channel object to handle stdin (raw_input)."""
953 """Get the REP socket channel object to handle stdin (raw_input)."""
954 if self._rep_channel is None:
954 if self._stdin_channel is None:
955 self._rep_channel = self.rep_channel_class(self.context,
955 self._stdin_channel = self.stdin_channel_class(self.context,
956 self.session,
956 self.session,
957 self.rep_address)
957 self.stdin_address)
958 return self._rep_channel
958 return self._stdin_channel
959
959
960 @property
960 @property
961 def hb_channel(self):
961 def hb_channel(self):
962 """Get the heartbeat socket channel object to check that the
962 """Get the heartbeat socket channel object to check that the
963 kernel is alive."""
963 kernel is alive."""
964 if self._hb_channel is None:
964 if self._hb_channel is None:
965 self._hb_channel = self.hb_channel_class(self.context,
965 self._hb_channel = self.hb_channel_class(self.context,
966 self.session,
966 self.session,
967 self.hb_address)
967 self.hb_address)
968 return self._hb_channel
968 return self._hb_channel
@@ -1,40 +1,40 b''
1 """Test suite for our zeromq-based messaging specification.
1 """Test suite for our zeromq-based messaging specification.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2010 The IPython Development Team
4 # Copyright (C) 2010 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING.txt, distributed as part of this software.
7 # the file COPYING.txt, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 import sys
10 import sys
11 import time
11 import time
12
12
13 import nose.tools as nt
13 import nose.tools as nt
14
14
15 from ..blockingkernelmanager import BlockingKernelManager
15 from ..blockingkernelmanager import BlockingKernelManager
16
16
17 from IPython.utils import io
17 from IPython.utils import io
18
18
19 def setup():
19 def setup():
20 global KM
20 global KM
21 KM = BlockingKernelManager()
21 KM = BlockingKernelManager()
22
22
23 KM.start_kernel()
23 KM.start_kernel()
24 KM.start_channels()
24 KM.start_channels()
25 # Give the kernel a chance to come up.
25 # Give the kernel a chance to come up.
26 time.sleep(1)
26 time.sleep(1)
27
27
28 def teardown():
28 def teardown():
29 io.rprint('Entering teardown...') # dbg
29 io.rprint('Entering teardown...') # dbg
30 io.rprint('Stopping channels and kernel...') # dbg
30 io.rprint('Stopping channels and kernel...') # dbg
31 KM.stop_channels()
31 KM.stop_channels()
32 KM.kill_kernel()
32 KM.kill_kernel()
33
33
34
34
35 # Actual tests
35 # Actual tests
36
36
37 def test_execute():
37 def test_execute():
38 KM.xreq_channel.execute(code='x=1')
38 KM.shell_channel.execute(code='x=1')
39 KM.xreq_channel.execute(code='print 1')
39 KM.shell_channel.execute(code='print 1')
40
40
General Comments 0
You need to be logged in to leave comments. Login now