##// END OF EJS Templates
* The SVG payload matplotlib backend now works....
epatters -
Show More
@@ -0,0 +1,54 b''
1 """ A demo of Qt console-style IPython frontend.
2 """
3
4 # Systemm library imports
5 from PyQt4 import QtCore, QtGui
6
7 # Local imports
8 from IPython.external.argparse import ArgumentParser
9 from IPython.frontend.qt.kernelmanager import QtKernelManager
10 from ipython_widget import IPythonWidget
11 from rich_ipython_widget import RichIPythonWidget
12
13
14 def main():
15 """ Entry point for demo.
16 """
17 # Parse command line arguments.
18 parser = ArgumentParser()
19 parser.add_argument('--pylab', action='store_true',
20 help='start kernel with pylab enabled')
21 parser.add_argument('--rich', action='store_true',
22 help='use rich text frontend')
23 namespace = parser.parse_args()
24
25 # Don't let Qt or ZMQ swallow KeyboardInterupts.
26 import signal
27 signal.signal(signal.SIGINT, signal.SIG_DFL)
28
29 # Create a KernelManager and start a kernel.
30 kernel_manager = QtKernelManager()
31 if namespace.pylab:
32 if namespace.rich:
33 kernel_manager.start_kernel(pylab='payload-svg')
34 else:
35 kernel_manager.start_kernel(pylab='qt4')
36 else:
37 kernel_manager.start_kernel()
38 kernel_manager.start_channels()
39
40 # Launch the application.
41 app = QtGui.QApplication([])
42 if namespace.rich:
43 widget = RichIPythonWidget()
44 else:
45 widget = IPythonWidget()
46 widget.kernel_manager = kernel_manager
47 widget.setWindowTitle('Python')
48 widget.resize(640, 480)
49 widget.show()
50 app.exec_()
51
52
53 if __name__ == '__main__':
54 main()
@@ -0,0 +1,43 b''
1 # System library imports
2 from PyQt4 import QtCore, QtGui
3
4 # Local imports
5 from IPython.frontend.qt.util import image_from_svg
6 from ipython_widget import IPythonWidget
7
8
9 class RichIPythonWidget(IPythonWidget):
10 """ An IPythonWidget that supports rich text, including lists, images, and
11 tables. Note that raw performance will be reduced compared to the plain
12 text version.
13 """
14
15 #---------------------------------------------------------------------------
16 # 'QObject' interface
17 #---------------------------------------------------------------------------
18
19 def __init__(self, parent=None):
20 """ Create a RichIPythonWidget.
21 """
22 super(RichIPythonWidget, self).__init__(kind='rich', parent=parent)
23
24 #---------------------------------------------------------------------------
25 # 'FrontendWidget' interface
26 #---------------------------------------------------------------------------
27
28 def _handle_execute_payload(self, payload):
29 """ Reimplemented to handle pylab plot payloads.
30 """
31 super(RichIPythonWidget, self)._handle_execute_payload(payload)
32
33 plot_payload = payload.get('plot', None)
34 if plot_payload and plot_payload['format'] == 'svg':
35 try:
36 image = image_from_svg(plot_payload['data'])
37 except ValueError:
38 self._append_plain_text('Received invalid plot data.')
39 else:
40 cursor = self._get_end_cursor()
41 cursor.insertBlock()
42 cursor.insertImage(image)
43 cursor.insertBlock()
@@ -1,165 +1,163 b''
1 # Standard library imports
1 # Standard library imports
2 import re
2 import re
3 from textwrap import dedent
3 from textwrap import dedent
4
4
5 # System library imports
5 # System library imports
6 from PyQt4 import QtCore, QtGui
6 from PyQt4 import QtCore, QtGui
7
7
8
8
9 class CallTipWidget(QtGui.QLabel):
9 class CallTipWidget(QtGui.QLabel):
10 """ Shows call tips by parsing the current text of Q[Plain]TextEdit.
10 """ Shows call tips by parsing the current text of Q[Plain]TextEdit.
11 """
11 """
12
12
13 #--------------------------------------------------------------------------
13 #--------------------------------------------------------------------------
14 # 'QObject' interface
14 # 'QObject' interface
15 #--------------------------------------------------------------------------
15 #--------------------------------------------------------------------------
16
16
17 def __init__(self, parent):
17 def __init__(self, parent):
18 """ Create a call tip manager that is attached to the specified Qt
18 """ Create a call tip manager that is attached to the specified Qt
19 text edit widget.
19 text edit widget.
20 """
20 """
21 assert isinstance(parent, (QtGui.QTextEdit, QtGui.QPlainTextEdit))
21 assert isinstance(parent, (QtGui.QTextEdit, QtGui.QPlainTextEdit))
22 QtGui.QLabel.__init__(self, parent, QtCore.Qt.ToolTip)
22 QtGui.QLabel.__init__(self, parent, QtCore.Qt.ToolTip)
23
23
24 self.setFont(parent.document().defaultFont())
24 self.setFont(parent.document().defaultFont())
25 self.setForegroundRole(QtGui.QPalette.ToolTipText)
25 self.setForegroundRole(QtGui.QPalette.ToolTipText)
26 self.setBackgroundRole(QtGui.QPalette.ToolTipBase)
26 self.setBackgroundRole(QtGui.QPalette.ToolTipBase)
27 self.setPalette(QtGui.QToolTip.palette())
27 self.setPalette(QtGui.QToolTip.palette())
28
28
29 self.setAlignment(QtCore.Qt.AlignLeft)
29 self.setAlignment(QtCore.Qt.AlignLeft)
30 self.setIndent(1)
30 self.setIndent(1)
31 self.setFrameStyle(QtGui.QFrame.NoFrame)
31 self.setFrameStyle(QtGui.QFrame.NoFrame)
32 self.setMargin(1 + self.style().pixelMetric(
32 self.setMargin(1 + self.style().pixelMetric(
33 QtGui.QStyle.PM_ToolTipLabelFrameWidth, None, self))
33 QtGui.QStyle.PM_ToolTipLabelFrameWidth, None, self))
34 self.setWindowOpacity(self.style().styleHint(
34 self.setWindowOpacity(self.style().styleHint(
35 QtGui.QStyle.SH_ToolTipLabel_Opacity, None, self) / 255.0)
35 QtGui.QStyle.SH_ToolTipLabel_Opacity, None, self) / 255.0)
36
36
37 def eventFilter(self, obj, event):
37 def eventFilter(self, obj, event):
38 """ Reimplemented to hide on certain key presses and on parent focus
38 """ Reimplemented to hide on certain key presses and on parent focus
39 changes.
39 changes.
40 """
40 """
41 if obj == self.parent():
41 if obj == self.parent():
42 etype = event.type()
42 etype = event.type()
43 if (etype == QtCore.QEvent.KeyPress and
43 if (etype == QtCore.QEvent.KeyPress and
44 event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return,
44 event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return,
45 QtCore.Qt.Key_Escape)):
45 QtCore.Qt.Key_Escape)):
46 self.hide()
46 self.hide()
47 elif etype == QtCore.QEvent.FocusOut:
47 elif etype == QtCore.QEvent.FocusOut:
48 self.hide()
48 self.hide()
49
49
50 return QtGui.QLabel.eventFilter(self, obj, event)
50 return QtGui.QLabel.eventFilter(self, obj, event)
51
51
52 #--------------------------------------------------------------------------
52 #--------------------------------------------------------------------------
53 # 'QWidget' interface
53 # 'QWidget' interface
54 #--------------------------------------------------------------------------
54 #--------------------------------------------------------------------------
55
55
56 def hideEvent(self, event):
56 def hideEvent(self, event):
57 """ Reimplemented to disconnect signal handlers and event filter.
57 """ Reimplemented to disconnect signal handlers and event filter.
58 """
58 """
59 QtGui.QLabel.hideEvent(self, event)
59 QtGui.QLabel.hideEvent(self, event)
60 parent = self.parent()
60 parent = self.parent()
61 parent.cursorPositionChanged.disconnect(self._cursor_position_changed)
61 parent.cursorPositionChanged.disconnect(self._cursor_position_changed)
62 parent.removeEventFilter(self)
62 parent.removeEventFilter(self)
63
63
64 def paintEvent(self, event):
64 def paintEvent(self, event):
65 """ Reimplemented to paint the background panel.
65 """ Reimplemented to paint the background panel.
66 """
66 """
67 painter = QtGui.QStylePainter(self)
67 painter = QtGui.QStylePainter(self)
68 option = QtGui.QStyleOptionFrame()
68 option = QtGui.QStyleOptionFrame()
69 option.init(self)
69 option.init(self)
70 painter.drawPrimitive(QtGui.QStyle.PE_PanelTipLabel, option)
70 painter.drawPrimitive(QtGui.QStyle.PE_PanelTipLabel, option)
71 painter.end()
71 painter.end()
72
72
73 QtGui.QLabel.paintEvent(self, event)
73 QtGui.QLabel.paintEvent(self, event)
74
74
75 def showEvent(self, event):
75 def showEvent(self, event):
76 """ Reimplemented to connect signal handlers and event filter.
76 """ Reimplemented to connect signal handlers and event filter.
77 """
77 """
78 QtGui.QLabel.showEvent(self, event)
78 QtGui.QLabel.showEvent(self, event)
79 parent = self.parent()
79 parent = self.parent()
80 parent.cursorPositionChanged.connect(self._cursor_position_changed)
80 parent.cursorPositionChanged.connect(self._cursor_position_changed)
81 parent.installEventFilter(self)
81 parent.installEventFilter(self)
82
82
83 #--------------------------------------------------------------------------
83 #--------------------------------------------------------------------------
84 # 'CallTipWidget' interface
84 # 'CallTipWidget' interface
85 #--------------------------------------------------------------------------
85 #--------------------------------------------------------------------------
86
86
87 def show_docstring(self, doc, maxlines=20):
87 def show_docstring(self, doc, maxlines=20):
88 """ Attempts to show the specified docstring at the current cursor
88 """ Attempts to show the specified docstring at the current cursor
89 location. The docstring is dedented and possibly truncated for
89 location. The docstring is dedented and possibly truncated for
90 length.
90 length.
91 """
91 """
92 doc = dedent(doc.rstrip()).lstrip()
92 doc = dedent(doc.rstrip()).lstrip()
93 match = re.match("(?:[^\n]*\n){%i}" % maxlines, doc)
93 match = re.match("(?:[^\n]*\n){%i}" % maxlines, doc)
94 if match:
94 if match:
95 doc = doc[:match.end()] + '\n[Documentation continues...]'
95 doc = doc[:match.end()] + '\n[Documentation continues...]'
96 return self.show_tip(doc)
96 return self.show_tip(doc)
97
97
98 def show_tip(self, tip):
98 def show_tip(self, tip):
99 """ Attempts to show the specified tip at the current cursor location.
99 """ Attempts to show the specified tip at the current cursor location.
100 """
100 """
101 text_edit = self.parent()
101 text_edit = self.parent()
102 document = text_edit.document()
102 document = text_edit.document()
103 cursor = text_edit.textCursor()
103 cursor = text_edit.textCursor()
104 search_pos = cursor.position() - 1
104 search_pos = cursor.position() - 1
105 self._start_position, _ = self._find_parenthesis(search_pos,
105 self._start_position, _ = self._find_parenthesis(search_pos,
106 forward=False)
106 forward=False)
107 if self._start_position == -1:
107 if self._start_position == -1:
108 return False
108 return False
109
109
110 point = text_edit.cursorRect(cursor).bottomRight()
110 point = text_edit.cursorRect(cursor).bottomRight()
111 point = text_edit.mapToGlobal(point)
111 point = text_edit.mapToGlobal(point)
112 self.move(point)
112 self.move(point)
113 self.setText(tip)
113 self.setText(tip)
114 if self.isVisible():
114 self.resize(self.sizeHint())
115 self.resize(self.sizeHint())
115 self.show()
116 else:
117 self.show()
118 return True
116 return True
119
117
120 #--------------------------------------------------------------------------
118 #--------------------------------------------------------------------------
121 # Protected interface
119 # Protected interface
122 #--------------------------------------------------------------------------
120 #--------------------------------------------------------------------------
123
121
124 def _find_parenthesis(self, position, forward=True):
122 def _find_parenthesis(self, position, forward=True):
125 """ If 'forward' is True (resp. False), proceed forwards
123 """ If 'forward' is True (resp. False), proceed forwards
126 (resp. backwards) through the line that contains 'position' until an
124 (resp. backwards) through the line that contains 'position' until an
127 unmatched closing (resp. opening) parenthesis is found. Returns a
125 unmatched closing (resp. opening) parenthesis is found. Returns a
128 tuple containing the position of this parenthesis (or -1 if it is
126 tuple containing the position of this parenthesis (or -1 if it is
129 not found) and the number commas (at depth 0) found along the way.
127 not found) and the number commas (at depth 0) found along the way.
130 """
128 """
131 commas = depth = 0
129 commas = depth = 0
132 document = self.parent().document()
130 document = self.parent().document()
133 qchar = document.characterAt(position)
131 qchar = document.characterAt(position)
134 while (position > 0 and qchar.isPrint() and
132 while (position > 0 and qchar.isPrint() and
135 # Need to check explicitly for line/paragraph separators:
133 # Need to check explicitly for line/paragraph separators:
136 qchar.unicode() not in (0x2028, 0x2029)):
134 qchar.unicode() not in (0x2028, 0x2029)):
137 char = qchar.toAscii()
135 char = qchar.toAscii()
138 if char == ',' and depth == 0:
136 if char == ',' and depth == 0:
139 commas += 1
137 commas += 1
140 elif char == ')':
138 elif char == ')':
141 if forward and depth == 0:
139 if forward and depth == 0:
142 break
140 break
143 depth += 1
141 depth += 1
144 elif char == '(':
142 elif char == '(':
145 if not forward and depth == 0:
143 if not forward and depth == 0:
146 break
144 break
147 depth -= 1
145 depth -= 1
148 position += 1 if forward else -1
146 position += 1 if forward else -1
149 qchar = document.characterAt(position)
147 qchar = document.characterAt(position)
150 else:
148 else:
151 position = -1
149 position = -1
152 return position, commas
150 return position, commas
153
151
154 #------ Signal handlers ----------------------------------------------------
152 #------ Signal handlers ----------------------------------------------------
155
153
156 def _cursor_position_changed(self):
154 def _cursor_position_changed(self):
157 """ Updates the tip based on user cursor movement.
155 """ Updates the tip based on user cursor movement.
158 """
156 """
159 cursor = self.parent().textCursor()
157 cursor = self.parent().textCursor()
160 if cursor.position() <= self._start_position:
158 if cursor.position() <= self._start_position:
161 self.hide()
159 self.hide()
162 else:
160 else:
163 position, commas = self._find_parenthesis(self._start_position + 1)
161 position, commas = self._find_parenthesis(self._start_position + 1)
164 if position != -1:
162 if position != -1:
165 self.hide()
163 self.hide()
@@ -1,369 +1,376 b''
1 # Standard library imports
1 # Standard library imports
2 import signal
2 import signal
3 import sys
3 import sys
4
4
5 # System library imports
5 # System library imports
6 from pygments.lexers import PythonLexer
6 from pygments.lexers import PythonLexer
7 from PyQt4 import QtCore, QtGui
7 from PyQt4 import QtCore, QtGui
8 import zmq
8 import zmq
9
9
10 # Local imports
10 # Local imports
11 from IPython.core.inputsplitter import InputSplitter
11 from IPython.core.inputsplitter import InputSplitter
12 from call_tip_widget import CallTipWidget
12 from call_tip_widget import CallTipWidget
13 from completion_lexer import CompletionLexer
13 from completion_lexer import CompletionLexer
14 from console_widget import HistoryConsoleWidget
14 from console_widget import HistoryConsoleWidget
15 from pygments_highlighter import PygmentsHighlighter
15 from pygments_highlighter import PygmentsHighlighter
16
16
17
17
18 class FrontendHighlighter(PygmentsHighlighter):
18 class FrontendHighlighter(PygmentsHighlighter):
19 """ A PygmentsHighlighter that can be turned on and off and that ignores
19 """ A PygmentsHighlighter that can be turned on and off and that ignores
20 prompts.
20 prompts.
21 """
21 """
22
22
23 def __init__(self, frontend):
23 def __init__(self, frontend):
24 super(FrontendHighlighter, self).__init__(frontend._control.document())
24 super(FrontendHighlighter, self).__init__(frontend._control.document())
25 self._current_offset = 0
25 self._current_offset = 0
26 self._frontend = frontend
26 self._frontend = frontend
27 self.highlighting_on = False
27 self.highlighting_on = False
28
28
29 def highlightBlock(self, qstring):
29 def highlightBlock(self, qstring):
30 """ Highlight a block of text. Reimplemented to highlight selectively.
30 """ Highlight a block of text. Reimplemented to highlight selectively.
31 """
31 """
32 if not self.highlighting_on:
32 if not self.highlighting_on:
33 return
33 return
34
34
35 # The input to this function is unicode string that may contain
35 # The input to this function is unicode string that may contain
36 # paragraph break characters, non-breaking spaces, etc. Here we acquire
36 # paragraph break characters, non-breaking spaces, etc. Here we acquire
37 # the string as plain text so we can compare it.
37 # the string as plain text so we can compare it.
38 current_block = self.currentBlock()
38 current_block = self.currentBlock()
39 string = self._frontend._get_block_plain_text(current_block)
39 string = self._frontend._get_block_plain_text(current_block)
40
40
41 # Decide whether to check for the regular or continuation prompt.
41 # Decide whether to check for the regular or continuation prompt.
42 if current_block.contains(self._frontend._prompt_pos):
42 if current_block.contains(self._frontend._prompt_pos):
43 prompt = self._frontend._prompt
43 prompt = self._frontend._prompt
44 else:
44 else:
45 prompt = self._frontend._continuation_prompt
45 prompt = self._frontend._continuation_prompt
46
46
47 # Don't highlight the part of the string that contains the prompt.
47 # Don't highlight the part of the string that contains the prompt.
48 if string.startswith(prompt):
48 if string.startswith(prompt):
49 self._current_offset = len(prompt)
49 self._current_offset = len(prompt)
50 qstring.remove(0, len(prompt))
50 qstring.remove(0, len(prompt))
51 else:
51 else:
52 self._current_offset = 0
52 self._current_offset = 0
53
53
54 PygmentsHighlighter.highlightBlock(self, qstring)
54 PygmentsHighlighter.highlightBlock(self, qstring)
55
55
56 def setFormat(self, start, count, format):
56 def setFormat(self, start, count, format):
57 """ Reimplemented to highlight selectively.
57 """ Reimplemented to highlight selectively.
58 """
58 """
59 start += self._current_offset
59 start += self._current_offset
60 PygmentsHighlighter.setFormat(self, start, count, format)
60 PygmentsHighlighter.setFormat(self, start, count, format)
61
61
62
62
63 class FrontendWidget(HistoryConsoleWidget):
63 class FrontendWidget(HistoryConsoleWidget):
64 """ A Qt frontend for a generic Python kernel.
64 """ A Qt frontend for a generic Python kernel.
65 """
65 """
66
66
67 # Emitted when an 'execute_reply' is received from the kernel.
67 # Emitted when an 'execute_reply' is received from the kernel.
68 executed = QtCore.pyqtSignal(object)
68 executed = QtCore.pyqtSignal(object)
69
69
70 #---------------------------------------------------------------------------
70 #---------------------------------------------------------------------------
71 # 'object' interface
71 # 'object' interface
72 #---------------------------------------------------------------------------
72 #---------------------------------------------------------------------------
73
73
74 def __init__(self, *args, **kw):
74 def __init__(self, *args, **kw):
75 super(FrontendWidget, self).__init__(*args, **kw)
75 super(FrontendWidget, self).__init__(*args, **kw)
76
76
77 # FrontendWidget protected variables.
77 # FrontendWidget protected variables.
78 self._call_tip_widget = CallTipWidget(self._control)
78 self._call_tip_widget = CallTipWidget(self._control)
79 self._completion_lexer = CompletionLexer(PythonLexer())
79 self._completion_lexer = CompletionLexer(PythonLexer())
80 self._hidden = True
80 self._hidden = True
81 self._highlighter = FrontendHighlighter(self)
81 self._highlighter = FrontendHighlighter(self)
82 self._input_splitter = InputSplitter(input_mode='replace')
82 self._input_splitter = InputSplitter(input_mode='replace')
83 self._kernel_manager = None
83 self._kernel_manager = None
84
84
85 # Configure the ConsoleWidget.
85 # Configure the ConsoleWidget.
86 self.tab_width = 4
86 self.tab_width = 4
87 self._set_continuation_prompt('... ')
87 self._set_continuation_prompt('... ')
88
88
89 # Connect signal handlers.
89 # Connect signal handlers.
90 document = self._control.document()
90 document = self._control.document()
91 document.contentsChange.connect(self._document_contents_change)
91 document.contentsChange.connect(self._document_contents_change)
92
92
93 #---------------------------------------------------------------------------
93 #---------------------------------------------------------------------------
94 # 'ConsoleWidget' abstract interface
94 # 'ConsoleWidget' abstract interface
95 #---------------------------------------------------------------------------
95 #---------------------------------------------------------------------------
96
96
97 def _is_complete(self, source, interactive):
97 def _is_complete(self, source, interactive):
98 """ Returns whether 'source' can be completely processed and a new
98 """ Returns whether 'source' can be completely processed and a new
99 prompt created. When triggered by an Enter/Return key press,
99 prompt created. When triggered by an Enter/Return key press,
100 'interactive' is True; otherwise, it is False.
100 'interactive' is True; otherwise, it is False.
101 """
101 """
102 complete = self._input_splitter.push(source.expandtabs(4))
102 complete = self._input_splitter.push(source.expandtabs(4))
103 if interactive:
103 if interactive:
104 complete = not self._input_splitter.push_accepts_more()
104 complete = not self._input_splitter.push_accepts_more()
105 return complete
105 return complete
106
106
107 def _execute(self, source, hidden):
107 def _execute(self, source, hidden):
108 """ Execute 'source'. If 'hidden', do not show any output.
108 """ Execute 'source'. If 'hidden', do not show any output.
109 """
109 """
110 self.kernel_manager.xreq_channel.execute(source)
110 self.kernel_manager.xreq_channel.execute(source)
111 self._hidden = hidden
111 self._hidden = hidden
112
112
113 def _execute_interrupt(self):
113 def _execute_interrupt(self):
114 """ Attempts to stop execution. Returns whether this method has an
114 """ Attempts to stop execution. Returns whether this method has an
115 implementation.
115 implementation.
116 """
116 """
117 self._interrupt_kernel()
117 self._interrupt_kernel()
118 return True
118 return True
119
119
120 def _prompt_started_hook(self):
120 def _prompt_started_hook(self):
121 """ Called immediately after a new prompt is displayed.
121 """ Called immediately after a new prompt is displayed.
122 """
122 """
123 if not self._reading:
123 if not self._reading:
124 self._highlighter.highlighting_on = True
124 self._highlighter.highlighting_on = True
125
125
126 # Auto-indent if this is a continuation prompt.
126 # Auto-indent if this is a continuation prompt.
127 if self._get_prompt_cursor().blockNumber() != \
127 if self._get_prompt_cursor().blockNumber() != \
128 self._get_end_cursor().blockNumber():
128 self._get_end_cursor().blockNumber():
129 spaces = self._input_splitter.indent_spaces
129 spaces = self._input_splitter.indent_spaces
130 self._append_plain_text('\t' * (spaces / self.tab_width))
130 self._append_plain_text('\t' * (spaces / self.tab_width))
131 self._append_plain_text(' ' * (spaces % self.tab_width))
131 self._append_plain_text(' ' * (spaces % self.tab_width))
132
132
133 def _prompt_finished_hook(self):
133 def _prompt_finished_hook(self):
134 """ Called immediately after a prompt is finished, i.e. when some input
134 """ Called immediately after a prompt is finished, i.e. when some input
135 will be processed and a new prompt displayed.
135 will be processed and a new prompt displayed.
136 """
136 """
137 if not self._reading:
137 if not self._reading:
138 self._highlighter.highlighting_on = False
138 self._highlighter.highlighting_on = False
139
139
140 def _tab_pressed(self):
140 def _tab_pressed(self):
141 """ Called when the tab key is pressed. Returns whether to continue
141 """ Called when the tab key is pressed. Returns whether to continue
142 processing the event.
142 processing the event.
143 """
143 """
144 self._keep_cursor_in_buffer()
144 self._keep_cursor_in_buffer()
145 cursor = self._get_cursor()
145 cursor = self._get_cursor()
146 return not self._complete()
146 return not self._complete()
147
147
148 #---------------------------------------------------------------------------
148 #---------------------------------------------------------------------------
149 # 'FrontendWidget' interface
149 # 'FrontendWidget' interface
150 #---------------------------------------------------------------------------
150 #---------------------------------------------------------------------------
151
151
152 def execute_file(self, path, hidden=False):
152 def execute_file(self, path, hidden=False):
153 """ Attempts to execute file with 'path'. If 'hidden', no output is
153 """ Attempts to execute file with 'path'. If 'hidden', no output is
154 shown.
154 shown.
155 """
155 """
156 self.execute('execfile("%s")' % path, hidden=hidden)
156 self.execute('execfile("%s")' % path, hidden=hidden)
157
157
158 def _get_kernel_manager(self):
158 def _get_kernel_manager(self):
159 """ Returns the current kernel manager.
159 """ Returns the current kernel manager.
160 """
160 """
161 return self._kernel_manager
161 return self._kernel_manager
162
162
163 def _set_kernel_manager(self, kernel_manager):
163 def _set_kernel_manager(self, kernel_manager):
164 """ Disconnect from the current kernel manager (if any) and set a new
164 """ Disconnect from the current kernel manager (if any) and set a new
165 kernel manager.
165 kernel manager.
166 """
166 """
167 # Disconnect the old kernel manager, if necessary.
167 # Disconnect the old kernel manager, if necessary.
168 if self._kernel_manager is not None:
168 if self._kernel_manager is not None:
169 self._kernel_manager.started_channels.disconnect(
169 self._kernel_manager.started_channels.disconnect(
170 self._started_channels)
170 self._started_channels)
171 self._kernel_manager.stopped_channels.disconnect(
171 self._kernel_manager.stopped_channels.disconnect(
172 self._stopped_channels)
172 self._stopped_channels)
173
173
174 # Disconnect the old kernel manager's channels.
174 # Disconnect the old kernel manager's channels.
175 sub = self._kernel_manager.sub_channel
175 sub = self._kernel_manager.sub_channel
176 xreq = self._kernel_manager.xreq_channel
176 xreq = self._kernel_manager.xreq_channel
177 rep = self._kernel_manager.rep_channel
177 rep = self._kernel_manager.rep_channel
178 sub.message_received.disconnect(self._handle_sub)
178 sub.message_received.disconnect(self._handle_sub)
179 xreq.execute_reply.disconnect(self._handle_execute_reply)
179 xreq.execute_reply.disconnect(self._handle_execute_reply)
180 xreq.complete_reply.disconnect(self._handle_complete_reply)
180 xreq.complete_reply.disconnect(self._handle_complete_reply)
181 xreq.object_info_reply.disconnect(self._handle_object_info_reply)
181 xreq.object_info_reply.disconnect(self._handle_object_info_reply)
182 rep.input_requested.disconnect(self._handle_req)
182 rep.input_requested.disconnect(self._handle_req)
183
183
184 # Handle the case where the old kernel manager is still listening.
184 # Handle the case where the old kernel manager is still listening.
185 if self._kernel_manager.channels_running:
185 if self._kernel_manager.channels_running:
186 self._stopped_channels()
186 self._stopped_channels()
187
187
188 # Set the new kernel manager.
188 # Set the new kernel manager.
189 self._kernel_manager = kernel_manager
189 self._kernel_manager = kernel_manager
190 if kernel_manager is None:
190 if kernel_manager is None:
191 return
191 return
192
192
193 # Connect the new kernel manager.
193 # Connect the new kernel manager.
194 kernel_manager.started_channels.connect(self._started_channels)
194 kernel_manager.started_channels.connect(self._started_channels)
195 kernel_manager.stopped_channels.connect(self._stopped_channels)
195 kernel_manager.stopped_channels.connect(self._stopped_channels)
196
196
197 # Connect the new kernel manager's channels.
197 # Connect the new kernel manager's channels.
198 sub = kernel_manager.sub_channel
198 sub = kernel_manager.sub_channel
199 xreq = kernel_manager.xreq_channel
199 xreq = kernel_manager.xreq_channel
200 rep = kernel_manager.rep_channel
200 rep = kernel_manager.rep_channel
201 sub.message_received.connect(self._handle_sub)
201 sub.message_received.connect(self._handle_sub)
202 xreq.execute_reply.connect(self._handle_execute_reply)
202 xreq.execute_reply.connect(self._handle_execute_reply)
203 xreq.complete_reply.connect(self._handle_complete_reply)
203 xreq.complete_reply.connect(self._handle_complete_reply)
204 xreq.object_info_reply.connect(self._handle_object_info_reply)
204 xreq.object_info_reply.connect(self._handle_object_info_reply)
205 rep.input_requested.connect(self._handle_req)
205 rep.input_requested.connect(self._handle_req)
206
206
207 # Handle the case where the kernel manager started channels before
207 # Handle the case where the kernel manager started channels before
208 # we connected.
208 # we connected.
209 if kernel_manager.channels_running:
209 if kernel_manager.channels_running:
210 self._started_channels()
210 self._started_channels()
211
211
212 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
212 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
213
213
214 #---------------------------------------------------------------------------
214 #---------------------------------------------------------------------------
215 # 'FrontendWidget' protected interface
215 # 'FrontendWidget' protected interface
216 #---------------------------------------------------------------------------
216 #---------------------------------------------------------------------------
217
217
218 def _call_tip(self):
218 def _call_tip(self):
219 """ Shows a call tip, if appropriate, at the current cursor location.
219 """ Shows a call tip, if appropriate, at the current cursor location.
220 """
220 """
221 # Decide if it makes sense to show a call tip
221 # Decide if it makes sense to show a call tip
222 cursor = self._get_cursor()
222 cursor = self._get_cursor()
223 cursor.movePosition(QtGui.QTextCursor.Left)
223 cursor.movePosition(QtGui.QTextCursor.Left)
224 document = self._control.document()
224 document = self._control.document()
225 if document.characterAt(cursor.position()).toAscii() != '(':
225 if document.characterAt(cursor.position()).toAscii() != '(':
226 return False
226 return False
227 context = self._get_context(cursor)
227 context = self._get_context(cursor)
228 if not context:
228 if not context:
229 return False
229 return False
230
230
231 # Send the metadata request to the kernel
231 # Send the metadata request to the kernel
232 name = '.'.join(context)
232 name = '.'.join(context)
233 self._call_tip_id = self.kernel_manager.xreq_channel.object_info(name)
233 self._call_tip_id = self.kernel_manager.xreq_channel.object_info(name)
234 self._call_tip_pos = self._get_cursor().position()
234 self._call_tip_pos = self._get_cursor().position()
235 return True
235 return True
236
236
237 def _complete(self):
237 def _complete(self):
238 """ Performs completion at the current cursor location.
238 """ Performs completion at the current cursor location.
239 """
239 """
240 # Decide if it makes sense to do completion
240 # Decide if it makes sense to do completion
241 context = self._get_context()
241 context = self._get_context()
242 if not context:
242 if not context:
243 return False
243 return False
244
244
245 # Send the completion request to the kernel
245 # Send the completion request to the kernel
246 text = '.'.join(context)
246 text = '.'.join(context)
247 self._complete_id = self.kernel_manager.xreq_channel.complete(
247 self._complete_id = self.kernel_manager.xreq_channel.complete(
248 text, self.input_buffer_cursor_line, self.input_buffer)
248 text, self.input_buffer_cursor_line, self.input_buffer)
249 self._complete_pos = self._get_cursor().position()
249 self._complete_pos = self._get_cursor().position()
250 return True
250 return True
251
251
252 def _get_banner(self):
252 def _get_banner(self):
253 """ Gets a banner to display at the beginning of a session.
253 """ Gets a banner to display at the beginning of a session.
254 """
254 """
255 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
255 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
256 '"license" for more information.'
256 '"license" for more information.'
257 return banner % (sys.version, sys.platform)
257 return banner % (sys.version, sys.platform)
258
258
259 def _get_context(self, cursor=None):
259 def _get_context(self, cursor=None):
260 """ Gets the context at the current cursor location.
260 """ Gets the context at the current cursor location.
261 """
261 """
262 if cursor is None:
262 if cursor is None:
263 cursor = self._get_cursor()
263 cursor = self._get_cursor()
264 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
264 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
265 QtGui.QTextCursor.KeepAnchor)
265 QtGui.QTextCursor.KeepAnchor)
266 text = str(cursor.selection().toPlainText())
266 text = str(cursor.selection().toPlainText())
267 return self._completion_lexer.get_context(text)
267 return self._completion_lexer.get_context(text)
268
268
269 def _interrupt_kernel(self):
269 def _interrupt_kernel(self):
270 """ Attempts to the interrupt the kernel.
270 """ Attempts to the interrupt the kernel.
271 """
271 """
272 if self.kernel_manager.has_kernel:
272 if self.kernel_manager.has_kernel:
273 self.kernel_manager.signal_kernel(signal.SIGINT)
273 self.kernel_manager.signal_kernel(signal.SIGINT)
274 else:
274 else:
275 self._append_plain_text('Kernel process is either remote or '
275 self._append_plain_text('Kernel process is either remote or '
276 'unspecified. Cannot interrupt.\n')
276 'unspecified. Cannot interrupt.\n')
277
277
278 def _show_interpreter_prompt(self):
278 def _show_interpreter_prompt(self):
279 """ Shows a prompt for the interpreter.
279 """ Shows a prompt for the interpreter.
280 """
280 """
281 self._show_prompt('>>> ')
281 self._show_prompt('>>> ')
282
282
283 #------ Signal handlers ----------------------------------------------------
283 #------ Signal handlers ----------------------------------------------------
284
284
285 def _started_channels(self):
285 def _started_channels(self):
286 """ Called when the kernel manager has started listening.
286 """ Called when the kernel manager has started listening.
287 """
287 """
288 self._reset()
288 self._reset()
289 self._append_plain_text(self._get_banner())
289 self._append_plain_text(self._get_banner())
290 self._show_interpreter_prompt()
290 self._show_interpreter_prompt()
291
291
292 def _stopped_channels(self):
292 def _stopped_channels(self):
293 """ Called when the kernel manager has stopped listening.
293 """ Called when the kernel manager has stopped listening.
294 """
294 """
295 # FIXME: Print a message here?
295 # FIXME: Print a message here?
296 pass
296 pass
297
297
298 def _document_contents_change(self, position, removed, added):
298 def _document_contents_change(self, position, removed, added):
299 """ Called whenever the document's content changes. Display a call tip
299 """ Called whenever the document's content changes. Display a call tip
300 if appropriate.
300 if appropriate.
301 """
301 """
302 # Calculate where the cursor should be *after* the change:
302 # Calculate where the cursor should be *after* the change:
303 position += added
303 position += added
304
304
305 document = self._control.document()
305 document = self._control.document()
306 if position == self._get_cursor().position():
306 if position == self._get_cursor().position():
307 self._call_tip()
307 self._call_tip()
308
308
309 def _handle_req(self, req):
309 def _handle_req(self, req):
310 # Make sure that all output from the SUB channel has been processed
310 # Make sure that all output from the SUB channel has been processed
311 # before entering readline mode.
311 # before entering readline mode.
312 self.kernel_manager.sub_channel.flush()
312 self.kernel_manager.sub_channel.flush()
313
313
314 def callback(line):
314 def callback(line):
315 self.kernel_manager.rep_channel.input(line)
315 self.kernel_manager.rep_channel.input(line)
316 self._readline(req['content']['prompt'], callback=callback)
316 self._readline(req['content']['prompt'], callback=callback)
317
317
318 def _handle_sub(self, omsg):
318 def _handle_sub(self, omsg):
319 if self._hidden:
319 if self._hidden:
320 return
320 return
321 handler = getattr(self, '_handle_%s' % omsg['msg_type'], None)
321 handler = getattr(self, '_handle_%s' % omsg['msg_type'], None)
322 if handler is not None:
322 if handler is not None:
323 handler(omsg)
323 handler(omsg)
324
324
325 def _handle_pyout(self, omsg):
325 def _handle_pyout(self, omsg):
326 self._append_plain_text(omsg['content']['data'] + '\n')
326 self._append_plain_text(omsg['content']['data'] + '\n')
327
327
328 def _handle_stream(self, omsg):
328 def _handle_stream(self, omsg):
329 self._append_plain_text(omsg['content']['data'])
329 self._append_plain_text(omsg['content']['data'])
330 self._control.moveCursor(QtGui.QTextCursor.End)
330 self._control.moveCursor(QtGui.QTextCursor.End)
331
331
332 def _handle_execute_reply(self, reply):
332 def _handle_execute_reply(self, reply):
333 if self._hidden:
333 if self._hidden:
334 return
334 return
335
335
336 # Make sure that all output from the SUB channel has been processed
336 # Make sure that all output from the SUB channel has been processed
337 # before writing a new prompt.
337 # before writing a new prompt.
338 self.kernel_manager.sub_channel.flush()
338 self.kernel_manager.sub_channel.flush()
339
339
340 status = reply['content']['status']
340 content = reply['content']
341 if status == 'error':
341 status = content['status']
342 if status == 'ok':
343 self._handle_execute_payload(content['payload'])
344 elif status == 'error':
342 self._handle_execute_error(reply)
345 self._handle_execute_error(reply)
343 elif status == 'aborted':
346 elif status == 'aborted':
344 text = "ERROR: ABORTED\n"
347 text = "ERROR: ABORTED\n"
345 self._append_plain_text(text)
348 self._append_plain_text(text)
349
346 self._hidden = True
350 self._hidden = True
347 self._show_interpreter_prompt()
351 self._show_interpreter_prompt()
348 self.executed.emit(reply)
352 self.executed.emit(reply)
349
353
350 def _handle_execute_error(self, reply):
354 def _handle_execute_error(self, reply):
351 content = reply['content']
355 content = reply['content']
352 traceback = ''.join(content['traceback'])
356 traceback = ''.join(content['traceback'])
353 self._append_plain_text(traceback)
357 self._append_plain_text(traceback)
354
358
359 def _handle_execute_payload(self, payload):
360 pass
361
355 def _handle_complete_reply(self, rep):
362 def _handle_complete_reply(self, rep):
356 cursor = self._get_cursor()
363 cursor = self._get_cursor()
357 if rep['parent_header']['msg_id'] == self._complete_id and \
364 if rep['parent_header']['msg_id'] == self._complete_id and \
358 cursor.position() == self._complete_pos:
365 cursor.position() == self._complete_pos:
359 text = '.'.join(self._get_context())
366 text = '.'.join(self._get_context())
360 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
367 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
361 self._complete_with_items(cursor, rep['content']['matches'])
368 self._complete_with_items(cursor, rep['content']['matches'])
362
369
363 def _handle_object_info_reply(self, rep):
370 def _handle_object_info_reply(self, rep):
364 cursor = self._get_cursor()
371 cursor = self._get_cursor()
365 if rep['parent_header']['msg_id'] == self._call_tip_id and \
372 if rep['parent_header']['msg_id'] == self._call_tip_id and \
366 cursor.position() == self._call_tip_pos:
373 cursor.position() == self._call_tip_pos:
367 doc = rep['content']['docstring']
374 doc = rep['content']['docstring']
368 if doc:
375 if doc:
369 self._call_tip_widget.show_docstring(doc)
376 self._call_tip_widget.show_docstring(doc)
@@ -1,206 +1,184 b''
1 # System library imports
1 # System library imports
2 from PyQt4 import QtCore, QtGui
2 from PyQt4 import QtCore, QtGui
3
3
4 # Local imports
4 # Local imports
5 from IPython.core.usage import default_banner
5 from IPython.core.usage import default_banner
6 from frontend_widget import FrontendWidget
6 from frontend_widget import FrontendWidget
7
7
8
8
9 class IPythonWidget(FrontendWidget):
9 class IPythonWidget(FrontendWidget):
10 """ A FrontendWidget for an IPython kernel.
10 """ A FrontendWidget for an IPython kernel.
11 """
11 """
12
12
13 # The default stylesheet: black text on a white background.
13 # The default stylesheet: black text on a white background.
14 default_stylesheet = """
14 default_stylesheet = """
15 .error { color: red; }
15 .error { color: red; }
16 .in-prompt { color: navy; }
16 .in-prompt { color: navy; }
17 .in-prompt-number { font-weight: bold; }
17 .in-prompt-number { font-weight: bold; }
18 .out-prompt { color: darkred; }
18 .out-prompt { color: darkred; }
19 .out-prompt-number { font-weight: bold; }
19 .out-prompt-number { font-weight: bold; }
20 """
20 """
21
21
22 # A dark stylesheet: white text on a black background.
22 # A dark stylesheet: white text on a black background.
23 dark_stylesheet = """
23 dark_stylesheet = """
24 QPlainTextEdit { background-color: black; color: white }
24 QPlainTextEdit { background-color: black; color: white }
25 QFrame { border: 1px solid grey; }
25 QFrame { border: 1px solid grey; }
26 .error { color: red; }
26 .error { color: red; }
27 .in-prompt { color: lime; }
27 .in-prompt { color: lime; }
28 .in-prompt-number { color: lime; font-weight: bold; }
28 .in-prompt-number { color: lime; font-weight: bold; }
29 .out-prompt { color: red; }
29 .out-prompt { color: red; }
30 .out-prompt-number { color: red; font-weight: bold; }
30 .out-prompt-number { color: red; font-weight: bold; }
31 """
31 """
32
32
33 # Default prompts.
33 # Default prompts.
34 in_prompt = '<br/>In [<span class="in-prompt-number">%i</span>]: '
34 in_prompt = '<br/>In [<span class="in-prompt-number">%i</span>]: '
35 out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
35 out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
36
36
37 #---------------------------------------------------------------------------
37 #---------------------------------------------------------------------------
38 # 'object' interface
38 # 'object' interface
39 #---------------------------------------------------------------------------
39 #---------------------------------------------------------------------------
40
40
41 def __init__(self, *args, **kw):
41 def __init__(self, *args, **kw):
42 super(IPythonWidget, self).__init__(*args, **kw)
42 super(IPythonWidget, self).__init__(*args, **kw)
43
43
44 # Initialize protected variables.
44 # Initialize protected variables.
45 self._previous_prompt_blocks = []
45 self._previous_prompt_blocks = []
46 self._prompt_count = 0
46 self._prompt_count = 0
47
47
48 # Set a default stylesheet.
48 # Set a default stylesheet.
49 self.reset_styling()
49 self.reset_styling()
50
50
51 #---------------------------------------------------------------------------
51 #---------------------------------------------------------------------------
52 # 'FrontendWidget' interface
52 # 'FrontendWidget' interface
53 #---------------------------------------------------------------------------
53 #---------------------------------------------------------------------------
54
54
55 def execute_file(self, path, hidden=False):
55 def execute_file(self, path, hidden=False):
56 """ Reimplemented to use the 'run' magic.
56 """ Reimplemented to use the 'run' magic.
57 """
57 """
58 self.execute('run %s' % path, hidden=hidden)
58 self.execute('run %s' % path, hidden=hidden)
59
59
60 #---------------------------------------------------------------------------
60 #---------------------------------------------------------------------------
61 # 'FrontendWidget' protected interface
61 # 'FrontendWidget' protected interface
62 #---------------------------------------------------------------------------
62 #---------------------------------------------------------------------------
63
63
64 def _get_banner(self):
64 def _get_banner(self):
65 """ Reimplemented to return IPython's default banner.
65 """ Reimplemented to return IPython's default banner.
66 """
66 """
67 return default_banner
67 return default_banner
68
68
69 def _show_interpreter_prompt(self):
69 def _show_interpreter_prompt(self):
70 """ Reimplemented for IPython-style prompts.
70 """ Reimplemented for IPython-style prompts.
71 """
71 """
72 # Update old prompt numbers if necessary.
72 # Update old prompt numbers if necessary.
73 previous_prompt_number = self._prompt_count
73 previous_prompt_number = self._prompt_count
74 if previous_prompt_number != self._prompt_count:
74 if previous_prompt_number != self._prompt_count:
75 for i, (block, length) in enumerate(self._previous_prompt_blocks):
75 for i, (block, length) in enumerate(self._previous_prompt_blocks):
76 if block.isValid():
76 if block.isValid():
77 cursor = QtGui.QTextCursor(block)
77 cursor = QtGui.QTextCursor(block)
78 cursor.movePosition(QtGui.QTextCursor.Right,
78 cursor.movePosition(QtGui.QTextCursor.Right,
79 QtGui.QTextCursor.KeepAnchor, length-1)
79 QtGui.QTextCursor.KeepAnchor, length-1)
80 if i == 0:
80 if i == 0:
81 prompt = self._make_in_prompt(previous_prompt_number)
81 prompt = self._make_in_prompt(previous_prompt_number)
82 else:
82 else:
83 prompt = self._make_out_prompt(previous_prompt_number)
83 prompt = self._make_out_prompt(previous_prompt_number)
84 self._insert_html(cursor, prompt)
84 self._insert_html(cursor, prompt)
85 self._previous_prompt_blocks = []
85 self._previous_prompt_blocks = []
86
86
87 # Show a new prompt.
87 # Show a new prompt.
88 self._prompt_count += 1
88 self._prompt_count += 1
89 self._show_prompt(self._make_in_prompt(self._prompt_count), html=True)
89 self._show_prompt(self._make_in_prompt(self._prompt_count), html=True)
90 self._save_prompt_block()
90 self._save_prompt_block()
91
91
92 # Update continuation prompt to reflect (possibly) new prompt length.
92 # Update continuation prompt to reflect (possibly) new prompt length.
93 self._set_continuation_prompt(
93 self._set_continuation_prompt(
94 self._make_continuation_prompt(self._prompt), html=True)
94 self._make_continuation_prompt(self._prompt), html=True)
95
95
96 #------ Signal handlers ----------------------------------------------------
96 #------ Signal handlers ----------------------------------------------------
97
97
98 def _handle_execute_error(self, reply):
98 def _handle_execute_error(self, reply):
99 """ Reimplemented for IPython-style traceback formatting.
99 """ Reimplemented for IPython-style traceback formatting.
100 """
100 """
101 content = reply['content']
101 content = reply['content']
102 traceback_lines = content['traceback'][:]
102 traceback_lines = content['traceback'][:]
103 traceback = ''.join(traceback_lines)
103 traceback = ''.join(traceback_lines)
104 traceback = traceback.replace(' ', '&nbsp;')
104 traceback = traceback.replace(' ', '&nbsp;')
105 traceback = traceback.replace('\n', '<br/>')
105 traceback = traceback.replace('\n', '<br/>')
106
106
107 ename = content['ename']
107 ename = content['ename']
108 ename_styled = '<span class="error">%s</span>' % ename
108 ename_styled = '<span class="error">%s</span>' % ename
109 traceback = traceback.replace(ename, ename_styled)
109 traceback = traceback.replace(ename, ename_styled)
110
110
111 self._append_html(traceback)
111 self._append_html(traceback)
112
112
113 def _handle_pyout(self, omsg):
113 def _handle_pyout(self, omsg):
114 """ Reimplemented for IPython-style "display hook".
114 """ Reimplemented for IPython-style "display hook".
115 """
115 """
116 self._append_html(self._make_out_prompt(self._prompt_count))
116 self._append_html(self._make_out_prompt(self._prompt_count))
117 self._save_prompt_block()
117 self._save_prompt_block()
118
118
119 self._append_plain_text(omsg['content']['data'] + '\n')
119 self._append_plain_text(omsg['content']['data'] + '\n')
120
120
121 #---------------------------------------------------------------------------
121 #---------------------------------------------------------------------------
122 # 'IPythonWidget' interface
122 # 'IPythonWidget' interface
123 #---------------------------------------------------------------------------
123 #---------------------------------------------------------------------------
124
124
125 def reset_styling(self):
125 def reset_styling(self):
126 """ Restores the default IPythonWidget styling.
126 """ Restores the default IPythonWidget styling.
127 """
127 """
128 self.set_styling(self.default_stylesheet, syntax_style='default')
128 self.set_styling(self.default_stylesheet, syntax_style='default')
129 #self.set_styling(self.dark_stylesheet, syntax_style='monokai')
129 #self.set_styling(self.dark_stylesheet, syntax_style='monokai')
130
130
131 def set_styling(self, stylesheet, syntax_style=None):
131 def set_styling(self, stylesheet, syntax_style=None):
132 """ Sets the IPythonWidget styling.
132 """ Sets the IPythonWidget styling.
133
133
134 Parameters:
134 Parameters:
135 -----------
135 -----------
136 stylesheet : str
136 stylesheet : str
137 A CSS stylesheet. The stylesheet can contain classes for:
137 A CSS stylesheet. The stylesheet can contain classes for:
138 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
138 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
139 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
139 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
140 3. IPython: .error, .in-prompt, .out-prompt, etc.
140 3. IPython: .error, .in-prompt, .out-prompt, etc.
141
141
142 syntax_style : str or None [default None]
142 syntax_style : str or None [default None]
143 If specified, use the Pygments style with given name. Otherwise,
143 If specified, use the Pygments style with given name. Otherwise,
144 the stylesheet is queried for Pygments style information.
144 the stylesheet is queried for Pygments style information.
145 """
145 """
146 self.setStyleSheet(stylesheet)
146 self.setStyleSheet(stylesheet)
147 self._control.document().setDefaultStyleSheet(stylesheet)
147 self._control.document().setDefaultStyleSheet(stylesheet)
148
148
149 if syntax_style is None:
149 if syntax_style is None:
150 self._highlighter.set_style_sheet(stylesheet)
150 self._highlighter.set_style_sheet(stylesheet)
151 else:
151 else:
152 self._highlighter.set_style(syntax_style)
152 self._highlighter.set_style(syntax_style)
153
153
154 #---------------------------------------------------------------------------
154 #---------------------------------------------------------------------------
155 # 'IPythonWidget' protected interface
155 # 'IPythonWidget' protected interface
156 #---------------------------------------------------------------------------
156 #---------------------------------------------------------------------------
157
157
158 def _make_in_prompt(self, number):
158 def _make_in_prompt(self, number):
159 """ Given a prompt number, returns an HTML In prompt.
159 """ Given a prompt number, returns an HTML In prompt.
160 """
160 """
161 body = self.in_prompt % number
161 body = self.in_prompt % number
162 return '<span class="in-prompt">%s</span>' % body
162 return '<span class="in-prompt">%s</span>' % body
163
163
164 def _make_continuation_prompt(self, prompt):
164 def _make_continuation_prompt(self, prompt):
165 """ Given a plain text version of an In prompt, returns an HTML
165 """ Given a plain text version of an In prompt, returns an HTML
166 continuation prompt.
166 continuation prompt.
167 """
167 """
168 end_chars = '...: '
168 end_chars = '...: '
169 space_count = len(prompt.lstrip('\n')) - len(end_chars)
169 space_count = len(prompt.lstrip('\n')) - len(end_chars)
170 body = '&nbsp;' * space_count + end_chars
170 body = '&nbsp;' * space_count + end_chars
171 return '<span class="in-prompt">%s</span>' % body
171 return '<span class="in-prompt">%s</span>' % body
172
172
173 def _make_out_prompt(self, number):
173 def _make_out_prompt(self, number):
174 """ Given a prompt number, returns an HTML Out prompt.
174 """ Given a prompt number, returns an HTML Out prompt.
175 """
175 """
176 body = self.out_prompt % number
176 body = self.out_prompt % number
177 return '<span class="out-prompt">%s</span>' % body
177 return '<span class="out-prompt">%s</span>' % body
178
178
179 def _save_prompt_block(self):
179 def _save_prompt_block(self):
180 """ Assuming a prompt has just been written at the end of the buffer,
180 """ Assuming a prompt has just been written at the end of the buffer,
181 store the QTextBlock that contains it and its length.
181 store the QTextBlock that contains it and its length.
182 """
182 """
183 block = self._control.document().lastBlock()
183 block = self._control.document().lastBlock()
184 self._previous_prompt_blocks.append((block, block.length()))
184 self._previous_prompt_blocks.append((block, block.length()))
185
186
187 if __name__ == '__main__':
188 from IPython.frontend.qt.kernelmanager import QtKernelManager
189
190 # Don't let Qt or ZMQ swallow KeyboardInterupts.
191 import signal
192 signal.signal(signal.SIGINT, signal.SIG_DFL)
193
194 # Create a KernelManager.
195 kernel_manager = QtKernelManager()
196 kernel_manager.start_kernel()
197 kernel_manager.start_channels()
198
199 # Launch the application.
200 app = QtGui.QApplication([])
201 widget = IPythonWidget()
202 widget.kernel_manager = kernel_manager
203 widget.setWindowTitle('Python')
204 widget.resize(640, 480)
205 widget.show()
206 app.exec_()
@@ -1,184 +1,180 b''
1 """ Defines a KernelManager that provides signals and slots.
1 """ Defines a KernelManager that provides signals and slots.
2 """
2 """
3
3
4 # System library imports.
4 # System library imports.
5 from PyQt4 import QtCore
5 from PyQt4 import QtCore
6 import zmq
6 import zmq
7
7
8 # IPython imports.
8 # IPython imports.
9 from IPython.zmq.kernelmanager import KernelManager, SubSocketChannel, \
9 from IPython.zmq.kernelmanager import KernelManager, SubSocketChannel, \
10 XReqSocketChannel, RepSocketChannel
10 XReqSocketChannel, RepSocketChannel
11 from util import MetaQObjectHasTraits
11 from util import MetaQObjectHasTraits
12
12
13 # When doing multiple inheritance from QtCore.QObject and other classes
13 # When doing multiple inheritance from QtCore.QObject and other classes
14 # the calling of the parent __init__'s is a subtle issue:
14 # the calling of the parent __init__'s is a subtle issue:
15 # * QtCore.QObject does not call super so you can't use super and put
15 # * QtCore.QObject does not call super so you can't use super and put
16 # QObject first in the inheritance list.
16 # QObject first in the inheritance list.
17 # * QtCore.QObject.__init__ takes 1 argument, the parent. So if you are going
17 # * QtCore.QObject.__init__ takes 1 argument, the parent. So if you are going
18 # to use super, any class that comes before QObject must pass it something
18 # to use super, any class that comes before QObject must pass it something
19 # reasonable.
19 # reasonable.
20 # In summary, I don't think using super in these situations will work.
20 # In summary, I don't think using super in these situations will work.
21 # Instead we will need to call the __init__ methods of both parents
21 # Instead we will need to call the __init__ methods of both parents
22 # by hand. Not pretty, but it works.
22 # by hand. Not pretty, but it works.
23
23
24 class QtSubSocketChannel(SubSocketChannel, QtCore.QObject):
24 class QtSubSocketChannel(SubSocketChannel, QtCore.QObject):
25
25
26 # Emitted when any message is received.
26 # Emitted when any message is received.
27 message_received = QtCore.pyqtSignal(object)
27 message_received = QtCore.pyqtSignal(object)
28
28
29 # Emitted when a message of type 'pyout' or 'stdout' is received.
29 # Emitted when a message of type 'pyout' or 'stdout' is received.
30 output_received = QtCore.pyqtSignal(object)
30 output_received = QtCore.pyqtSignal(object)
31
31
32 # Emitted when a message of type 'pyerr' or 'stderr' is received.
32 # Emitted when a message of type 'pyerr' or 'stderr' is received.
33 error_received = QtCore.pyqtSignal(object)
33 error_received = QtCore.pyqtSignal(object)
34
34
35 #---------------------------------------------------------------------------
35 #---------------------------------------------------------------------------
36 # 'object' interface
36 # 'object' interface
37 #---------------------------------------------------------------------------
37 #---------------------------------------------------------------------------
38
38
39 def __init__(self, *args, **kw):
39 def __init__(self, *args, **kw):
40 """ Reimplemented to ensure that QtCore.QObject is initialized first.
40 """ Reimplemented to ensure that QtCore.QObject is initialized first.
41 """
41 """
42 QtCore.QObject.__init__(self)
42 QtCore.QObject.__init__(self)
43 SubSocketChannel.__init__(self, *args, **kw)
43 SubSocketChannel.__init__(self, *args, **kw)
44
44
45 #---------------------------------------------------------------------------
45 #---------------------------------------------------------------------------
46 # 'SubSocketChannel' interface
46 # 'SubSocketChannel' interface
47 #---------------------------------------------------------------------------
47 #---------------------------------------------------------------------------
48
48
49 def call_handlers(self, msg):
49 def call_handlers(self, msg):
50 """ Reimplemented to emit signals instead of making callbacks.
50 """ Reimplemented to emit signals instead of making callbacks.
51 """
51 """
52 # Emit the generic signal.
52 # Emit the generic signal.
53 self.message_received.emit(msg)
53 self.message_received.emit(msg)
54
54
55 # Emit signals for specialized message types.
55 # Emit signals for specialized message types.
56 msg_type = msg['msg_type']
56 msg_type = msg['msg_type']
57 if msg_type in ('pyout', 'stdout'):
57 if msg_type in ('pyout', 'stdout'):
58 self.output_received.emit(msg)
58 self.output_received.emit(msg)
59 elif msg_type in ('pyerr', 'stderr'):
59 elif msg_type in ('pyerr', 'stderr'):
60 self.error_received.emit(msg)
60 self.error_received.emit(msg)
61
61
62 def flush(self):
62 def flush(self):
63 """ Reimplemented to ensure that signals are dispatched immediately.
63 """ Reimplemented to ensure that signals are dispatched immediately.
64 """
64 """
65 super(QtSubSocketChannel, self).flush()
65 super(QtSubSocketChannel, self).flush()
66 QtCore.QCoreApplication.instance().processEvents()
66 QtCore.QCoreApplication.instance().processEvents()
67
67
68
68
69 class QtXReqSocketChannel(XReqSocketChannel, QtCore.QObject):
69 class QtXReqSocketChannel(XReqSocketChannel, QtCore.QObject):
70
70
71 # Emitted when any message is received.
71 # Emitted when any message is received.
72 message_received = QtCore.pyqtSignal(object)
72 message_received = QtCore.pyqtSignal(object)
73
73
74 # Emitted when a reply has been received for the corresponding request type.
74 # Emitted when a reply has been received for the corresponding request type.
75 execute_reply = QtCore.pyqtSignal(object)
75 execute_reply = QtCore.pyqtSignal(object)
76 complete_reply = QtCore.pyqtSignal(object)
76 complete_reply = QtCore.pyqtSignal(object)
77 object_info_reply = QtCore.pyqtSignal(object)
77 object_info_reply = QtCore.pyqtSignal(object)
78
78
79 #---------------------------------------------------------------------------
79 #---------------------------------------------------------------------------
80 # 'object' interface
80 # 'object' interface
81 #---------------------------------------------------------------------------
81 #---------------------------------------------------------------------------
82
82
83 def __init__(self, *args, **kw):
83 def __init__(self, *args, **kw):
84 """ Reimplemented to ensure that QtCore.QObject is initialized first.
84 """ Reimplemented to ensure that QtCore.QObject is initialized first.
85 """
85 """
86 QtCore.QObject.__init__(self)
86 QtCore.QObject.__init__(self)
87 XReqSocketChannel.__init__(self, *args, **kw)
87 XReqSocketChannel.__init__(self, *args, **kw)
88
88
89 #---------------------------------------------------------------------------
89 #---------------------------------------------------------------------------
90 # 'XReqSocketChannel' interface
90 # 'XReqSocketChannel' interface
91 #---------------------------------------------------------------------------
91 #---------------------------------------------------------------------------
92
92
93 def call_handlers(self, msg):
93 def call_handlers(self, msg):
94 """ Reimplemented to emit signals instead of making callbacks.
94 """ Reimplemented to emit signals instead of making callbacks.
95 """
95 """
96 # Emit the generic signal.
96 # Emit the generic signal.
97 self.message_received.emit(msg)
97 self.message_received.emit(msg)
98
98
99 # Emit signals for specialized message types.
99 # Emit signals for specialized message types.
100 msg_type = msg['msg_type']
100 msg_type = msg['msg_type']
101 signal = getattr(self, msg_type, None)
101 signal = getattr(self, msg_type, None)
102 if signal:
102 if signal:
103 signal.emit(msg)
103 signal.emit(msg)
104
104
105
105
106 class QtRepSocketChannel(RepSocketChannel, QtCore.QObject):
106 class QtRepSocketChannel(RepSocketChannel, QtCore.QObject):
107
107
108 # Emitted when any message is received.
108 # Emitted when any message is received.
109 message_received = QtCore.pyqtSignal(object)
109 message_received = QtCore.pyqtSignal(object)
110
110
111 # Emitted when an input request is received.
111 # Emitted when an input request is received.
112 input_requested = QtCore.pyqtSignal(object)
112 input_requested = QtCore.pyqtSignal(object)
113
113
114 #---------------------------------------------------------------------------
114 #---------------------------------------------------------------------------
115 # 'object' interface
115 # 'object' interface
116 #---------------------------------------------------------------------------
116 #---------------------------------------------------------------------------
117
117
118 def __init__(self, *args, **kw):
118 def __init__(self, *args, **kw):
119 """ Reimplemented to ensure that QtCore.QObject is initialized first.
119 """ Reimplemented to ensure that QtCore.QObject is initialized first.
120 """
120 """
121 QtCore.QObject.__init__(self)
121 QtCore.QObject.__init__(self)
122 RepSocketChannel.__init__(self, *args, **kw)
122 RepSocketChannel.__init__(self, *args, **kw)
123
123
124 #---------------------------------------------------------------------------
124 #---------------------------------------------------------------------------
125 # 'RepSocketChannel' interface
125 # 'RepSocketChannel' interface
126 #---------------------------------------------------------------------------
126 #---------------------------------------------------------------------------
127
127
128 def call_handlers(self, msg):
128 def call_handlers(self, msg):
129 """ Reimplemented to emit signals instead of making callbacks.
129 """ Reimplemented to emit signals instead of making callbacks.
130 """
130 """
131 # Emit the generic signal.
131 # Emit the generic signal.
132 self.message_received.emit(msg)
132 self.message_received.emit(msg)
133
133
134 # Emit signals for specialized message types.
134 # Emit signals for specialized message types.
135 msg_type = msg['msg_type']
135 msg_type = msg['msg_type']
136 if msg_type == 'input_request':
136 if msg_type == 'input_request':
137 self.input_requested.emit(msg)
137 self.input_requested.emit(msg)
138
138
139 class QtKernelManager(KernelManager, QtCore.QObject):
139 class QtKernelManager(KernelManager, QtCore.QObject):
140 """ A KernelManager that provides signals and slots.
140 """ A KernelManager that provides signals and slots.
141 """
141 """
142
142
143 __metaclass__ = MetaQObjectHasTraits
143 __metaclass__ = MetaQObjectHasTraits
144
144
145 # Emitted when the kernel manager has started listening.
145 # Emitted when the kernel manager has started listening.
146 started_channels = QtCore.pyqtSignal()
146 started_channels = QtCore.pyqtSignal()
147
147
148 # Emitted when the kernel manager has stopped listening.
148 # Emitted when the kernel manager has stopped listening.
149 stopped_channels = QtCore.pyqtSignal()
149 stopped_channels = QtCore.pyqtSignal()
150
150
151 # Use Qt-specific channel classes that emit signals.
151 # Use Qt-specific channel classes that emit signals.
152 sub_channel_class = QtSubSocketChannel
152 sub_channel_class = QtSubSocketChannel
153 xreq_channel_class = QtXReqSocketChannel
153 xreq_channel_class = QtXReqSocketChannel
154 rep_channel_class = QtRepSocketChannel
154 rep_channel_class = QtRepSocketChannel
155
155
156 def __init__(self, *args, **kw):
157 QtCore.QObject.__init__(self)
158 KernelManager.__init__(self, *args, **kw)
159
160 #---------------------------------------------------------------------------
156 #---------------------------------------------------------------------------
161 # 'object' interface
157 # 'object' interface
162 #---------------------------------------------------------------------------
158 #---------------------------------------------------------------------------
163
159
164 def __init__(self, *args, **kw):
160 def __init__(self, *args, **kw):
165 """ Reimplemented to ensure that QtCore.QObject is initialized first.
161 """ Reimplemented to ensure that QtCore.QObject is initialized first.
166 """
162 """
167 QtCore.QObject.__init__(self)
163 QtCore.QObject.__init__(self)
168 KernelManager.__init__(self, *args, **kw)
164 KernelManager.__init__(self, *args, **kw)
169
165
170 #---------------------------------------------------------------------------
166 #---------------------------------------------------------------------------
171 # 'KernelManager' interface
167 # 'KernelManager' interface
172 #---------------------------------------------------------------------------
168 #---------------------------------------------------------------------------
173
169
174 def start_channels(self):
170 def start_channels(self):
175 """ Reimplemented to emit signal.
171 """ Reimplemented to emit signal.
176 """
172 """
177 super(QtKernelManager, self).start_channels()
173 super(QtKernelManager, self).start_channels()
178 self.started_channels.emit()
174 self.started_channels.emit()
179
175
180 def stop_channels(self):
176 def stop_channels(self):
181 """ Reimplemented to emit signal.
177 """ Reimplemented to emit signal.
182 """
178 """
183 super(QtKernelManager, self).stop_channels()
179 super(QtKernelManager, self).stop_channels()
184 self.stopped_channels.emit()
180 self.stopped_channels.emit()
@@ -1,586 +1,599 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """A simple interactive kernel that talks to a frontend over 0MQ.
2 """A simple interactive kernel that talks to a frontend over 0MQ.
3
3
4 Things to do:
4 Things to do:
5
5
6 * Finish implementing `raw_input`.
6 * Finish implementing `raw_input`.
7 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 * Implement `set_parent` logic. Right before doing exec, the Kernel should
8 call set_parent on all the PUB objects with the message about to be executed.
8 call set_parent on all the PUB objects with the message about to be executed.
9 * Implement random port and security key logic.
9 * Implement random port and security key logic.
10 * Implement control messages.
10 * Implement control messages.
11 * Implement event loop and poll version.
11 * Implement event loop and poll version.
12 """
12 """
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # Standard library imports.
18 # Standard library imports.
19 import __builtin__
19 import __builtin__
20 from code import CommandCompiler
20 from code import CommandCompiler
21 from cStringIO import StringIO
21 from cStringIO import StringIO
22 import os
22 import os
23 import sys
23 import sys
24 from threading import Thread
24 from threading import Thread
25 import time
25 import time
26 import traceback
26 import traceback
27
27
28 # System library imports.
28 # System library imports.
29 import zmq
29 import zmq
30
30
31 # Local imports.
31 # Local imports.
32 from IPython.external.argparse import ArgumentParser
32 from IPython.external.argparse import ArgumentParser
33 from session import Session, Message, extract_header
33 from session import Session, Message, extract_header
34 from completer import KernelCompleter
34 from completer import KernelCompleter
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Kernel and stream classes
37 # Kernel and stream classes
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40 class OutStream(object):
40 class OutStream(object):
41 """A file like object that publishes the stream to a 0MQ PUB socket."""
41 """A file like object that publishes the stream to a 0MQ PUB socket."""
42
42
43 # The time interval between automatic flushes, in seconds.
43 # The time interval between automatic flushes, in seconds.
44 flush_interval = 0.05
44 flush_interval = 0.05
45
45
46 def __init__(self, session, pub_socket, name):
46 def __init__(self, session, pub_socket, name):
47 self.session = session
47 self.session = session
48 self.pub_socket = pub_socket
48 self.pub_socket = pub_socket
49 self.name = name
49 self.name = name
50 self.parent_header = {}
50 self.parent_header = {}
51 self._new_buffer()
51 self._new_buffer()
52
52
53 def set_parent(self, parent):
53 def set_parent(self, parent):
54 self.parent_header = extract_header(parent)
54 self.parent_header = extract_header(parent)
55
55
56 def close(self):
56 def close(self):
57 self.pub_socket = None
57 self.pub_socket = None
58
58
59 def flush(self):
59 def flush(self):
60 if self.pub_socket is None:
60 if self.pub_socket is None:
61 raise ValueError(u'I/O operation on closed file')
61 raise ValueError(u'I/O operation on closed file')
62 else:
62 else:
63 data = self._buffer.getvalue()
63 data = self._buffer.getvalue()
64 if data:
64 if data:
65 content = {u'name':self.name, u'data':data}
65 content = {u'name':self.name, u'data':data}
66 msg = self.session.msg(u'stream', content=content,
66 msg = self.session.msg(u'stream', content=content,
67 parent=self.parent_header)
67 parent=self.parent_header)
68 print>>sys.__stdout__, Message(msg)
68 print>>sys.__stdout__, Message(msg)
69 self.pub_socket.send_json(msg)
69 self.pub_socket.send_json(msg)
70
70
71 self._buffer.close()
71 self._buffer.close()
72 self._new_buffer()
72 self._new_buffer()
73
73
74 def isatty(self):
74 def isatty(self):
75 return False
75 return False
76
76
77 def next(self):
77 def next(self):
78 raise IOError('Read not supported on a write only stream.')
78 raise IOError('Read not supported on a write only stream.')
79
79
80 def read(self, size=-1):
80 def read(self, size=-1):
81 raise IOError('Read not supported on a write only stream.')
81 raise IOError('Read not supported on a write only stream.')
82
82
83 def readline(self, size=-1):
83 def readline(self, size=-1):
84 raise IOError('Read not supported on a write only stream.')
84 raise IOError('Read not supported on a write only stream.')
85
85
86 def write(self, string):
86 def write(self, string):
87 if self.pub_socket is None:
87 if self.pub_socket is None:
88 raise ValueError('I/O operation on closed file')
88 raise ValueError('I/O operation on closed file')
89 else:
89 else:
90 self._buffer.write(string)
90 self._buffer.write(string)
91 current_time = time.time()
91 current_time = time.time()
92 if self._start <= 0:
92 if self._start <= 0:
93 self._start = current_time
93 self._start = current_time
94 elif current_time - self._start > self.flush_interval:
94 elif current_time - self._start > self.flush_interval:
95 self.flush()
95 self.flush()
96
96
97 def writelines(self, sequence):
97 def writelines(self, sequence):
98 if self.pub_socket is None:
98 if self.pub_socket is None:
99 raise ValueError('I/O operation on closed file')
99 raise ValueError('I/O operation on closed file')
100 else:
100 else:
101 for string in sequence:
101 for string in sequence:
102 self.write(string)
102 self.write(string)
103
103
104 def _new_buffer(self):
104 def _new_buffer(self):
105 self._buffer = StringIO()
105 self._buffer = StringIO()
106 self._start = -1
106 self._start = -1
107
107
108
108
109 class DisplayHook(object):
109 class DisplayHook(object):
110
110
111 def __init__(self, session, pub_socket):
111 def __init__(self, session, pub_socket):
112 self.session = session
112 self.session = session
113 self.pub_socket = pub_socket
113 self.pub_socket = pub_socket
114 self.parent_header = {}
114 self.parent_header = {}
115
115
116 def __call__(self, obj):
116 def __call__(self, obj):
117 if obj is not None:
117 if obj is not None:
118 __builtin__._ = obj
118 __builtin__._ = obj
119 msg = self.session.msg(u'pyout', {u'data':repr(obj)},
119 msg = self.session.msg(u'pyout', {u'data':repr(obj)},
120 parent=self.parent_header)
120 parent=self.parent_header)
121 self.pub_socket.send_json(msg)
121 self.pub_socket.send_json(msg)
122
122
123 def set_parent(self, parent):
123 def set_parent(self, parent):
124 self.parent_header = extract_header(parent)
124 self.parent_header = extract_header(parent)
125
125
126
126
127 class Kernel(object):
127 class Kernel(object):
128
128
129 # The global kernel instance.
129 # The global kernel instance.
130 _kernel = None
130 _kernel = None
131
131
132 # Maps user-friendly backend names to matplotlib backend identifiers.
132 # Maps user-friendly backend names to matplotlib backend identifiers.
133 _pylab_map = { 'tk': 'TkAgg',
133 _pylab_map = { 'tk': 'TkAgg',
134 'gtk': 'GTKAgg',
134 'gtk': 'GTKAgg',
135 'wx': 'WXAgg',
135 'wx': 'WXAgg',
136 'qt': 'Qt4Agg', # qt3 not supported
136 'qt': 'Qt4Agg', # qt3 not supported
137 'qt4': 'Qt4Agg',
137 'qt4': 'Qt4Agg',
138 'payload-svg' : \
138 'payload-svg' : \
139 'module://IPython.zmq.pylab.backend_payload_svg' }
139 'module://IPython.zmq.pylab.backend_payload_svg' }
140
140
141 #---------------------------------------------------------------------------
141 #---------------------------------------------------------------------------
142 # Kernel interface
142 # Kernel interface
143 #---------------------------------------------------------------------------
143 #---------------------------------------------------------------------------
144
144
145 def __init__(self, session, reply_socket, pub_socket, req_socket):
145 def __init__(self, session, reply_socket, pub_socket, req_socket):
146 self.session = session
146 self.session = session
147 self.reply_socket = reply_socket
147 self.reply_socket = reply_socket
148 self.pub_socket = pub_socket
148 self.pub_socket = pub_socket
149 self.req_socket = req_socket
149 self.req_socket = req_socket
150 self.user_ns = {}
150 self.user_ns = {}
151 self.history = []
151 self.history = []
152 self.compiler = CommandCompiler()
152 self.compiler = CommandCompiler()
153 self.completer = KernelCompleter(self.user_ns)
153 self.completer = KernelCompleter(self.user_ns)
154
154
155 # Protected variables.
155 # Protected variables.
156 self._exec_payload = {}
156 self._exec_payload = {}
157
157
158 # Build dict of handlers for message types
158 # Build dict of handlers for message types
159 msg_types = [ 'execute_request', 'complete_request',
159 msg_types = [ 'execute_request', 'complete_request',
160 'object_info_request' ]
160 'object_info_request' ]
161 self.handlers = {}
161 self.handlers = {}
162 for msg_type in msg_types:
162 for msg_type in msg_types:
163 self.handlers[msg_type] = getattr(self, msg_type)
163 self.handlers[msg_type] = getattr(self, msg_type)
164
164
165 def add_exec_payload(self, key, value):
165 def add_exec_payload(self, key, value):
166 """ Adds a key/value pair to the execute payload.
166 """ Adds a key/value pair to the execute payload.
167 """
167 """
168 self._exec_payload[key] = value
168 self._exec_payload[key] = value
169
169
170 def activate_pylab(self, backend=None, import_all=True):
170 def activate_pylab(self, backend=None, import_all=True):
171 """ Activates pylab in this kernel's namespace.
171 """ Activates pylab in this kernel's namespace.
172
172
173 Parameters:
173 Parameters:
174 -----------
174 -----------
175 backend : str, optional
175 backend : str, optional
176 A valid backend name.
176 A valid backend name.
177
177
178 import_all : bool, optional
178 import_all : bool, optional
179 If true, an 'import *' is done from numpy and pylab.
179 If true, an 'import *' is done from numpy and pylab.
180 """
180 """
181 # FIXME: This is adapted from IPython.lib.pylabtools.pylab_activate.
181 # FIXME: This is adapted from IPython.lib.pylabtools.pylab_activate.
182 # Common funtionality should be refactored.
182 # Common funtionality should be refactored.
183
183
184 import matplotlib
185
186 # We must set the desired backend before importing pylab.
184 # We must set the desired backend before importing pylab.
185 import matplotlib
187 if backend:
186 if backend:
188 matplotlib.use(self._pylab_map[backend])
187 backend_id = self._pylab_map[backend]
189
188 if backend_id.startswith('module://'):
190 # This must be imported last in the matplotlib series, after
189 # Work around bug in matplotlib: matplotlib.use converts the
191 # backend/interactivity choices have been made.
190 # backend_id to lowercase even if a module name is specified!
192 import matplotlib.pylab as pylab
191 matplotlib.rcParams['backend'] = backend_id
192 else:
193 matplotlib.use(backend_id)
193
194
194 # Import numpy as np/pyplot as plt are conventions we're trying to
195 # Import numpy as np/pyplot as plt are conventions we're trying to
195 # somewhat standardize on. Making them available to users by default
196 # somewhat standardize on. Making them available to users by default
196 # will greatly help this.
197 # will greatly help this.
197 exec ("import numpy\n"
198 exec ("import numpy\n"
198 "import matplotlib\n"
199 "import matplotlib\n"
199 "from matplotlib import pylab, mlab, pyplot\n"
200 "from matplotlib import pylab, mlab, pyplot\n"
200 "np = numpy\n"
201 "np = numpy\n"
201 "plt = pyplot\n"
202 "plt = pyplot\n"
202 ) in self.user_ns
203 ) in self.user_ns
203
204
204 if import_all:
205 if import_all:
205 exec("from matplotlib.pylab import *\n"
206 exec("from matplotlib.pylab import *\n"
206 "from numpy import *\n") in self.user_ns
207 "from numpy import *\n") in self.user_ns
207
208
208 matplotlib.interactive(True)
209 matplotlib.interactive(True)
209
210
210 @classmethod
211 @classmethod
211 def get_kernel(cls):
212 def get_kernel(cls):
212 """ Return the global kernel instance or raise a RuntimeError if it does
213 """ Return the global kernel instance or raise a RuntimeError if it does
213 not exist.
214 not exist.
214 """
215 """
215 if cls._kernel is None:
216 if cls._kernel is None:
216 raise RuntimeError("Kernel not started!")
217 raise RuntimeError("Kernel not started!")
217 else:
218 else:
218 return cls._kernel
219 return cls._kernel
219
220
220 def start(self):
221 def start(self):
221 """ Start the kernel main loop.
222 """ Start the kernel main loop.
222 """
223 """
223 # Set the global kernel instance.
224 # Set the global kernel instance.
224 Kernel._kernel = self
225 Kernel._kernel = self
225
226
226 while True:
227 while True:
227 ident = self.reply_socket.recv()
228 ident = self.reply_socket.recv()
228 assert self.reply_socket.rcvmore(), "Missing message part."
229 assert self.reply_socket.rcvmore(), "Missing message part."
229 msg = self.reply_socket.recv_json()
230 msg = self.reply_socket.recv_json()
230 omsg = Message(msg)
231 omsg = Message(msg)
231 print>>sys.__stdout__
232 print>>sys.__stdout__
232 print>>sys.__stdout__, omsg
233 print>>sys.__stdout__, omsg
233 handler = self.handlers.get(omsg.msg_type, None)
234 handler = self.handlers.get(omsg.msg_type, None)
234 if handler is None:
235 if handler is None:
235 print >> sys.__stderr__, "UNKNOWN MESSAGE TYPE:", omsg
236 print >> sys.__stderr__, "UNKNOWN MESSAGE TYPE:", omsg
236 else:
237 else:
237 handler(ident, omsg)
238 handler(ident, omsg)
238
239
239 #---------------------------------------------------------------------------
240 #---------------------------------------------------------------------------
240 # Kernel request handlers
241 # Kernel request handlers
241 #---------------------------------------------------------------------------
242 #---------------------------------------------------------------------------
242
243
243 def execute_request(self, ident, parent):
244 def execute_request(self, ident, parent):
244 try:
245 try:
245 code = parent[u'content'][u'code']
246 code = parent[u'content'][u'code']
246 except:
247 except:
247 print>>sys.__stderr__, "Got bad msg: "
248 print>>sys.__stderr__, "Got bad msg: "
248 print>>sys.__stderr__, Message(parent)
249 print>>sys.__stderr__, Message(parent)
249 return
250 return
250 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
251 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
251 self.pub_socket.send_json(pyin_msg)
252 self.pub_socket.send_json(pyin_msg)
252
253
253 # Clear the execute payload from the last request.
254 # Clear the execute payload from the last request.
254 self._exec_payload = {}
255 self._exec_payload = {}
255
256
256 try:
257 try:
257 comp_code = self.compiler(code, '<zmq-kernel>')
258 comp_code = self.compiler(code, '<zmq-kernel>')
258
259
259 # Replace raw_input. Note that is not sufficient to replace
260 # Replace raw_input. Note that is not sufficient to replace
260 # raw_input in the user namespace.
261 # raw_input in the user namespace.
261 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
262 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
262 __builtin__.raw_input = raw_input
263 __builtin__.raw_input = raw_input
263
264
264 # Configure the display hook.
265 # Configure the display hook.
265 sys.displayhook.set_parent(parent)
266 sys.displayhook.set_parent(parent)
266
267
267 exec comp_code in self.user_ns, self.user_ns
268 exec comp_code in self.user_ns, self.user_ns
268 except:
269 except:
269 result = u'error'
270 result = u'error'
270 etype, evalue, tb = sys.exc_info()
271 etype, evalue, tb = sys.exc_info()
271 tb = traceback.format_exception(etype, evalue, tb)
272 tb = traceback.format_exception(etype, evalue, tb)
272 exc_content = {
273 exc_content = {
273 u'status' : u'error',
274 u'status' : u'error',
274 u'traceback' : tb,
275 u'traceback' : tb,
275 u'ename' : unicode(etype.__name__),
276 u'ename' : unicode(etype.__name__),
276 u'evalue' : unicode(evalue)
277 u'evalue' : unicode(evalue)
277 }
278 }
278 exc_msg = self.session.msg(u'pyerr', exc_content, parent)
279 exc_msg = self.session.msg(u'pyerr', exc_content, parent)
279 self.pub_socket.send_json(exc_msg)
280 self.pub_socket.send_json(exc_msg)
280 reply_content = exc_content
281 reply_content = exc_content
281 else:
282 else:
282 reply_content = { 'status' : 'ok', 'payload' : self._exec_payload }
283 reply_content = { 'status' : 'ok', 'payload' : self._exec_payload }
283
284
284 # Flush output before sending the reply.
285 # Flush output before sending the reply.
285 sys.stderr.flush()
286 sys.stderr.flush()
286 sys.stdout.flush()
287 sys.stdout.flush()
287
288
288 # Send the reply.
289 # Send the reply.
289 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
290 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
290 print>>sys.__stdout__, Message(reply_msg)
291 print>>sys.__stdout__, Message(reply_msg)
291 self.reply_socket.send(ident, zmq.SNDMORE)
292 self.reply_socket.send(ident, zmq.SNDMORE)
292 self.reply_socket.send_json(reply_msg)
293 self.reply_socket.send_json(reply_msg)
293 if reply_msg['content']['status'] == u'error':
294 if reply_msg['content']['status'] == u'error':
294 self._abort_queue()
295 self._abort_queue()
295
296
296 def complete_request(self, ident, parent):
297 def complete_request(self, ident, parent):
297 comp = self.completer.complete(parent.content.line, parent.content.text)
298 comp = self.completer.complete(parent.content.line, parent.content.text)
298 matches = {'matches' : comp, 'status' : 'ok'}
299 matches = {'matches' : comp, 'status' : 'ok'}
299 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
300 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
300 matches, parent, ident)
301 matches, parent, ident)
301 print >> sys.__stdout__, completion_msg
302 print >> sys.__stdout__, completion_msg
302
303
303 def object_info_request(self, ident, parent):
304 def object_info_request(self, ident, parent):
304 context = parent['content']['oname'].split('.')
305 context = parent['content']['oname'].split('.')
305 object_info = self._object_info(context)
306 object_info = self._object_info(context)
306 msg = self.session.send(self.reply_socket, 'object_info_reply',
307 msg = self.session.send(self.reply_socket, 'object_info_reply',
307 object_info, parent, ident)
308 object_info, parent, ident)
308 print >> sys.__stdout__, msg
309 print >> sys.__stdout__, msg
309
310
310 #---------------------------------------------------------------------------
311 #---------------------------------------------------------------------------
311 # Protected interface
312 # Protected interface
312 #---------------------------------------------------------------------------
313 #---------------------------------------------------------------------------
313
314
314 def _abort_queue(self):
315 def _abort_queue(self):
315 while True:
316 while True:
316 try:
317 try:
317 ident = self.reply_socket.recv(zmq.NOBLOCK)
318 ident = self.reply_socket.recv(zmq.NOBLOCK)
318 except zmq.ZMQError, e:
319 except zmq.ZMQError, e:
319 if e.errno == zmq.EAGAIN:
320 if e.errno == zmq.EAGAIN:
320 break
321 break
321 else:
322 else:
322 assert self.reply_socket.rcvmore(), "Missing message part."
323 assert self.reply_socket.rcvmore(), "Missing message part."
323 msg = self.reply_socket.recv_json()
324 msg = self.reply_socket.recv_json()
324 print>>sys.__stdout__, "Aborting:"
325 print>>sys.__stdout__, "Aborting:"
325 print>>sys.__stdout__, Message(msg)
326 print>>sys.__stdout__, Message(msg)
326 msg_type = msg['msg_type']
327 msg_type = msg['msg_type']
327 reply_type = msg_type.split('_')[0] + '_reply'
328 reply_type = msg_type.split('_')[0] + '_reply'
328 reply_msg = self.session.msg(reply_type, {'status':'aborted'}, msg)
329 reply_msg = self.session.msg(reply_type, {'status':'aborted'}, msg)
329 print>>sys.__stdout__, Message(reply_msg)
330 print>>sys.__stdout__, Message(reply_msg)
330 self.reply_socket.send(ident,zmq.SNDMORE)
331 self.reply_socket.send(ident,zmq.SNDMORE)
331 self.reply_socket.send_json(reply_msg)
332 self.reply_socket.send_json(reply_msg)
332 # We need to wait a bit for requests to come in. This can probably
333 # We need to wait a bit for requests to come in. This can probably
333 # be set shorter for true asynchronous clients.
334 # be set shorter for true asynchronous clients.
334 time.sleep(0.1)
335 time.sleep(0.1)
335
336
336 def _raw_input(self, prompt, ident, parent):
337 def _raw_input(self, prompt, ident, parent):
337 # Flush output before making the request.
338 # Flush output before making the request.
338 sys.stderr.flush()
339 sys.stderr.flush()
339 sys.stdout.flush()
340 sys.stdout.flush()
340
341
341 # Send the input request.
342 # Send the input request.
342 content = dict(prompt=prompt)
343 content = dict(prompt=prompt)
343 msg = self.session.msg(u'input_request', content, parent)
344 msg = self.session.msg(u'input_request', content, parent)
344 self.req_socket.send_json(msg)
345 self.req_socket.send_json(msg)
345
346
346 # Await a response.
347 # Await a response.
347 reply = self.req_socket.recv_json()
348 reply = self.req_socket.recv_json()
348 try:
349 try:
349 value = reply['content']['value']
350 value = reply['content']['value']
350 except:
351 except:
351 print>>sys.__stderr__, "Got bad raw_input reply: "
352 print>>sys.__stderr__, "Got bad raw_input reply: "
352 print>>sys.__stderr__, Message(parent)
353 print>>sys.__stderr__, Message(parent)
353 value = ''
354 value = ''
354 return value
355 return value
355
356
356 def _object_info(self, context):
357 def _object_info(self, context):
357 symbol, leftover = self._symbol_from_context(context)
358 symbol, leftover = self._symbol_from_context(context)
358 if symbol is not None and not leftover:
359 if symbol is not None and not leftover:
359 doc = getattr(symbol, '__doc__', '')
360 doc = getattr(symbol, '__doc__', '')
360 else:
361 else:
361 doc = ''
362 doc = ''
362 object_info = dict(docstring = doc)
363 object_info = dict(docstring = doc)
363 return object_info
364 return object_info
364
365
365 def _symbol_from_context(self, context):
366 def _symbol_from_context(self, context):
366 if not context:
367 if not context:
367 return None, context
368 return None, context
368
369
369 base_symbol_string = context[0]
370 base_symbol_string = context[0]
370 symbol = self.user_ns.get(base_symbol_string, None)
371 symbol = self.user_ns.get(base_symbol_string, None)
371 if symbol is None:
372 if symbol is None:
372 symbol = __builtin__.__dict__.get(base_symbol_string, None)
373 symbol = __builtin__.__dict__.get(base_symbol_string, None)
373 if symbol is None:
374 if symbol is None:
374 return None, context
375 return None, context
375
376
376 context = context[1:]
377 context = context[1:]
377 for i, name in enumerate(context):
378 for i, name in enumerate(context):
378 new_symbol = getattr(symbol, name, None)
379 new_symbol = getattr(symbol, name, None)
379 if new_symbol is None:
380 if new_symbol is None:
380 return symbol, context[i:]
381 return symbol, context[i:]
381 else:
382 else:
382 symbol = new_symbol
383 symbol = new_symbol
383
384
384 return symbol, []
385 return symbol, []
385
386
386 #-----------------------------------------------------------------------------
387 #-----------------------------------------------------------------------------
387 # Kernel main and launch functions
388 # Kernel main and launch functions
388 #-----------------------------------------------------------------------------
389 #-----------------------------------------------------------------------------
389
390
390 class ExitPollerUnix(Thread):
391 class ExitPollerUnix(Thread):
391 """ A Unix-specific daemon thread that terminates the program immediately
392 """ A Unix-specific daemon thread that terminates the program immediately
392 when the parent process no longer exists.
393 when the parent process no longer exists.
393 """
394 """
394
395
395 def __init__(self):
396 def __init__(self):
396 super(ExitPollerUnix, self).__init__()
397 super(ExitPollerUnix, self).__init__()
397 self.daemon = True
398 self.daemon = True
398
399
399 def run(self):
400 def run(self):
400 # We cannot use os.waitpid because it works only for child processes.
401 # We cannot use os.waitpid because it works only for child processes.
401 from errno import EINTR
402 from errno import EINTR
402 while True:
403 while True:
403 try:
404 try:
404 if os.getppid() == 1:
405 if os.getppid() == 1:
405 os._exit(1)
406 os._exit(1)
406 time.sleep(1.0)
407 time.sleep(1.0)
407 except OSError, e:
408 except OSError, e:
408 if e.errno == EINTR:
409 if e.errno == EINTR:
409 continue
410 continue
410 raise
411 raise
411
412
412 class ExitPollerWindows(Thread):
413 class ExitPollerWindows(Thread):
413 """ A Windows-specific daemon thread that terminates the program immediately
414 """ A Windows-specific daemon thread that terminates the program immediately
414 when a Win32 handle is signaled.
415 when a Win32 handle is signaled.
415 """
416 """
416
417
417 def __init__(self, handle):
418 def __init__(self, handle):
418 super(ExitPollerWindows, self).__init__()
419 super(ExitPollerWindows, self).__init__()
419 self.daemon = True
420 self.daemon = True
420 self.handle = handle
421 self.handle = handle
421
422
422 def run(self):
423 def run(self):
423 from _subprocess import WaitForSingleObject, WAIT_OBJECT_0, INFINITE
424 from _subprocess import WaitForSingleObject, WAIT_OBJECT_0, INFINITE
424 result = WaitForSingleObject(self.handle, INFINITE)
425 result = WaitForSingleObject(self.handle, INFINITE)
425 if result == WAIT_OBJECT_0:
426 if result == WAIT_OBJECT_0:
426 os._exit(1)
427 os._exit(1)
427
428
428
429
429 def bind_port(socket, ip, port):
430 def bind_port(socket, ip, port):
430 """ Binds the specified ZMQ socket. If the port is zero, a random port is
431 """ Binds the specified ZMQ socket. If the port is zero, a random port is
431 chosen. Returns the port that was bound.
432 chosen. Returns the port that was bound.
432 """
433 """
433 connection = 'tcp://%s' % ip
434 connection = 'tcp://%s' % ip
434 if port <= 0:
435 if port <= 0:
435 port = socket.bind_to_random_port(connection)
436 port = socket.bind_to_random_port(connection)
436 else:
437 else:
437 connection += ':%i' % port
438 connection += ':%i' % port
438 socket.bind(connection)
439 socket.bind(connection)
439 return port
440 return port
440
441
441
442
442 def main():
443 def main():
443 """ Main entry point for launching a kernel.
444 """ Main entry point for launching a kernel.
444 """
445 """
445 # Parse command line arguments.
446 # Parse command line arguments.
446 parser = ArgumentParser()
447 parser = ArgumentParser()
447 parser.add_argument('--ip', type=str, default='127.0.0.1',
448 parser.add_argument('--ip', type=str, default='127.0.0.1',
448 help='set the kernel\'s IP address [default: local]')
449 help='set the kernel\'s IP address [default: local]')
449 parser.add_argument('--xrep', type=int, metavar='PORT', default=0,
450 parser.add_argument('--xrep', type=int, metavar='PORT', default=0,
450 help='set the XREP channel port [default: random]')
451 help='set the XREP channel port [default: random]')
451 parser.add_argument('--pub', type=int, metavar='PORT', default=0,
452 parser.add_argument('--pub', type=int, metavar='PORT', default=0,
452 help='set the PUB channel port [default: random]')
453 help='set the PUB channel port [default: random]')
453 parser.add_argument('--req', type=int, metavar='PORT', default=0,
454 parser.add_argument('--req', type=int, metavar='PORT', default=0,
454 help='set the REQ channel port [default: random]')
455 help='set the REQ channel port [default: random]')
455 if sys.platform == 'win32':
456 if sys.platform == 'win32':
456 parser.add_argument('--parent', type=int, metavar='HANDLE',
457 parser.add_argument('--parent', type=int, metavar='HANDLE',
457 default=0, help='kill this process if the process '
458 default=0, help='kill this process if the process '
458 'with HANDLE dies')
459 'with HANDLE dies')
459 else:
460 else:
460 parser.add_argument('--parent', action='store_true',
461 parser.add_argument('--parent', action='store_true',
461 help='kill this process if its parent dies')
462 help='kill this process if its parent dies')
462 parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
463 parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
463 const='auto', help = \
464 const='auto', help = \
464 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
465 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
465 given, the GUI backend is matplotlib's, otherwise use one of: \
466 given, the GUI backend is matplotlib's, otherwise use one of: \
466 ['tk', 'gtk', 'qt', 'wx', 'payload-svg'].")
467 ['tk', 'gtk', 'qt', 'wx', 'payload-svg'].")
467
468
468 namespace = parser.parse_args()
469 namespace = parser.parse_args()
469
470
470 # Create a context, a session, and the kernel sockets.
471 # Create a context, a session, and the kernel sockets.
471 print >>sys.__stdout__, "Starting the kernel..."
472 print >>sys.__stdout__, "Starting the kernel..."
472 context = zmq.Context()
473 context = zmq.Context()
473 session = Session(username=u'kernel')
474 session = Session(username=u'kernel')
474
475
475 reply_socket = context.socket(zmq.XREP)
476 reply_socket = context.socket(zmq.XREP)
476 xrep_port = bind_port(reply_socket, namespace.ip, namespace.xrep)
477 xrep_port = bind_port(reply_socket, namespace.ip, namespace.xrep)
477 print >>sys.__stdout__, "XREP Channel on port", xrep_port
478 print >>sys.__stdout__, "XREP Channel on port", xrep_port
478
479
479 pub_socket = context.socket(zmq.PUB)
480 pub_socket = context.socket(zmq.PUB)
480 pub_port = bind_port(pub_socket, namespace.ip, namespace.pub)
481 pub_port = bind_port(pub_socket, namespace.ip, namespace.pub)
481 print >>sys.__stdout__, "PUB Channel on port", pub_port
482 print >>sys.__stdout__, "PUB Channel on port", pub_port
482
483
483 req_socket = context.socket(zmq.XREQ)
484 req_socket = context.socket(zmq.XREQ)
484 req_port = bind_port(req_socket, namespace.ip, namespace.req)
485 req_port = bind_port(req_socket, namespace.ip, namespace.req)
485 print >>sys.__stdout__, "REQ Channel on port", req_port
486 print >>sys.__stdout__, "REQ Channel on port", req_port
486
487
487 # Create the kernel.
488 # Create the kernel.
488 kernel = Kernel(session, reply_socket, pub_socket, req_socket)
489 kernel = Kernel(session, reply_socket, pub_socket, req_socket)
489
490
490 # Set up pylab, if necessary.
491 # Set up pylab, if necessary.
491 if namespace.pylab:
492 if namespace.pylab:
492 if namespace.pylab == 'auto':
493 if namespace.pylab == 'auto':
493 kernel.activate_pylab()
494 kernel.activate_pylab()
494 else:
495 else:
495 kernel.activate_pylab(namespace.pylab)
496 kernel.activate_pylab(namespace.pylab)
496
497
497 # Redirect input streams and set a display hook.
498 # Redirect input streams and set a display hook.
498 sys.stdout = OutStream(session, pub_socket, u'stdout')
499 sys.stdout = OutStream(session, pub_socket, u'stdout')
499 sys.stderr = OutStream(session, pub_socket, u'stderr')
500 sys.stderr = OutStream(session, pub_socket, u'stderr')
500 sys.displayhook = DisplayHook(session, pub_socket)
501 sys.displayhook = DisplayHook(session, pub_socket)
501
502
502 # Configure this kernel/process to die on parent termination, if necessary.
503 # Configure this kernel/process to die on parent termination, if necessary.
503 if namespace.parent:
504 if namespace.parent:
504 if sys.platform == 'win32':
505 if sys.platform == 'win32':
505 poller = ExitPollerWindows(namespace.parent)
506 poller = ExitPollerWindows(namespace.parent)
506 else:
507 else:
507 poller = ExitPollerUnix()
508 poller = ExitPollerUnix()
508 poller.start()
509 poller.start()
509
510
510 # Start the kernel mainloop.
511 # Start the kernel mainloop.
511 kernel.start()
512 kernel.start()
512
513
513
514
514 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False):
515 def launch_kernel(xrep_port=0, pub_port=0, req_port=0,
516 pylab=False, independent=False):
515 """ Launches a localhost kernel, binding to the specified ports.
517 """ Launches a localhost kernel, binding to the specified ports.
516
518
517 Parameters
519 Parameters
518 ----------
520 ----------
519 xrep_port : int, optional
521 xrep_port : int, optional
520 The port to use for XREP channel.
522 The port to use for XREP channel.
521
523
522 pub_port : int, optional
524 pub_port : int, optional
523 The port to use for the SUB channel.
525 The port to use for the SUB channel.
524
526
525 req_port : int, optional
527 req_port : int, optional
526 The port to use for the REQ (raw input) channel.
528 The port to use for the REQ (raw input) channel.
527
529
530 pylab : bool or string, optional (default False)
531 If not False, the kernel will be launched with pylab enabled. If a
532 string is passed, matplotlib will use the specified backend. Otherwise,
533 matplotlib's default backend will be used.
534
528 independent : bool, optional (default False)
535 independent : bool, optional (default False)
529 If set, the kernel process is guaranteed to survive if this process
536 If set, the kernel process is guaranteed to survive if this process
530 dies. If not set, an effort is made to ensure that the kernel is killed
537 dies. If not set, an effort is made to ensure that the kernel is killed
531 when this process dies. Note that in this case it is still good practice
538 when this process dies. Note that in this case it is still good practice
532 to kill kernels manually before exiting.
539 to kill kernels manually before exiting.
533
540
534 Returns
541 Returns
535 -------
542 -------
536 A tuple of form:
543 A tuple of form:
537 (kernel_process, xrep_port, pub_port, req_port)
544 (kernel_process, xrep_port, pub_port, req_port)
538 where kernel_process is a Popen object and the ports are integers.
545 where kernel_process is a Popen object and the ports are integers.
539 """
546 """
540 import socket
547 import socket
541 from subprocess import Popen
548 from subprocess import Popen
542
549
543 # Find open ports as necessary.
550 # Find open ports as necessary.
544 ports = []
551 ports = []
545 ports_needed = int(xrep_port <= 0) + int(pub_port <= 0) + int(req_port <= 0)
552 ports_needed = int(xrep_port <= 0) + int(pub_port <= 0) + int(req_port <= 0)
546 for i in xrange(ports_needed):
553 for i in xrange(ports_needed):
547 sock = socket.socket()
554 sock = socket.socket()
548 sock.bind(('', 0))
555 sock.bind(('', 0))
549 ports.append(sock)
556 ports.append(sock)
550 for i, sock in enumerate(ports):
557 for i, sock in enumerate(ports):
551 port = sock.getsockname()[1]
558 port = sock.getsockname()[1]
552 sock.close()
559 sock.close()
553 ports[i] = port
560 ports[i] = port
554 if xrep_port <= 0:
561 if xrep_port <= 0:
555 xrep_port = ports.pop(0)
562 xrep_port = ports.pop(0)
556 if pub_port <= 0:
563 if pub_port <= 0:
557 pub_port = ports.pop(0)
564 pub_port = ports.pop(0)
558 if req_port <= 0:
565 if req_port <= 0:
559 req_port = ports.pop(0)
566 req_port = ports.pop(0)
560
567
561 # Spawn a kernel.
568 # Build the kernel launch command.
562 command = 'from IPython.zmq.kernel import main; main()'
569 command = 'from IPython.zmq.kernel import main; main()'
563 arguments = [ sys.executable, '-c', command, '--xrep', str(xrep_port),
570 arguments = [ sys.executable, '-c', command, '--xrep', str(xrep_port),
564 '--pub', str(pub_port), '--req', str(req_port) ]
571 '--pub', str(pub_port), '--req', str(req_port) ]
572 if pylab:
573 arguments.append('--pylab')
574 if isinstance(pylab, basestring):
575 arguments.append(pylab)
576
577 # Spawn a kernel.
565 if independent:
578 if independent:
566 if sys.platform == 'win32':
579 if sys.platform == 'win32':
567 proc = Popen(['start', '/b'] + arguments, shell=True)
580 proc = Popen(['start', '/b'] + arguments, shell=True)
568 else:
581 else:
569 proc = Popen(arguments, preexec_fn=lambda: os.setsid())
582 proc = Popen(arguments, preexec_fn=lambda: os.setsid())
570 else:
583 else:
571 if sys.platform == 'win32':
584 if sys.platform == 'win32':
572 from _subprocess import DuplicateHandle, GetCurrentProcess, \
585 from _subprocess import DuplicateHandle, GetCurrentProcess, \
573 DUPLICATE_SAME_ACCESS
586 DUPLICATE_SAME_ACCESS
574 pid = GetCurrentProcess()
587 pid = GetCurrentProcess()
575 handle = DuplicateHandle(pid, pid, pid, 0,
588 handle = DuplicateHandle(pid, pid, pid, 0,
576 True, # Inheritable by new processes.
589 True, # Inheritable by new processes.
577 DUPLICATE_SAME_ACCESS)
590 DUPLICATE_SAME_ACCESS)
578 proc = Popen(arguments + ['--parent', str(int(handle))])
591 proc = Popen(arguments + ['--parent', str(int(handle))])
579 else:
592 else:
580 proc = Popen(arguments + ['--parent'])
593 proc = Popen(arguments + ['--parent'])
581
594
582 return proc, xrep_port, pub_port, req_port
595 return proc, xrep_port, pub_port, req_port
583
596
584
597
585 if __name__ == '__main__':
598 if __name__ == '__main__':
586 main()
599 main()
@@ -1,571 +1,575 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 ====
4 ====
5
5
6 * Create logger to handle debugging and console messages.
6 * Create logger to handle debugging and console messages.
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2008-2010 The IPython Development Team
10 # Copyright (C) 2008-2010 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 # Standard library imports.
20 # Standard library imports.
21 from Queue import Queue, Empty
21 from Queue import Queue, Empty
22 from subprocess import Popen
22 from subprocess import Popen
23 from threading import Thread
23 from threading import Thread
24 import time
24 import time
25
25
26 # System library imports.
26 # System library imports.
27 import zmq
27 import zmq
28 from zmq import POLLIN, POLLOUT, POLLERR
28 from zmq import POLLIN, POLLOUT, POLLERR
29 from zmq.eventloop import ioloop
29 from zmq.eventloop import ioloop
30
30
31 # Local imports.
31 # Local imports.
32 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
32 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
33 from kernel import launch_kernel
33 from kernel import launch_kernel
34 from session import Session
34 from session import Session
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Constants and exceptions
37 # Constants and exceptions
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40 LOCALHOST = '127.0.0.1'
40 LOCALHOST = '127.0.0.1'
41
41
42 class InvalidPortNumber(Exception):
42 class InvalidPortNumber(Exception):
43 pass
43 pass
44
44
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46 # ZMQ Socket Channel classes
46 # ZMQ Socket Channel classes
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48
48
49 class ZmqSocketChannel(Thread):
49 class ZmqSocketChannel(Thread):
50 """The base class for the channels that use ZMQ sockets.
50 """The base class for the channels that use ZMQ sockets.
51 """
51 """
52 context = None
52 context = None
53 session = None
53 session = None
54 socket = None
54 socket = None
55 ioloop = None
55 ioloop = None
56 iostate = None
56 iostate = None
57 _address = None
57 _address = None
58
58
59 def __init__(self, context, session, address):
59 def __init__(self, context, session, address):
60 """Create a channel
60 """Create a channel
61
61
62 Parameters
62 Parameters
63 ----------
63 ----------
64 context : :class:`zmq.Context`
64 context : :class:`zmq.Context`
65 The ZMQ context to use.
65 The ZMQ context to use.
66 session : :class:`session.Session`
66 session : :class:`session.Session`
67 The session to use.
67 The session to use.
68 address : tuple
68 address : tuple
69 Standard (ip, port) tuple that the kernel is listening on.
69 Standard (ip, port) tuple that the kernel is listening on.
70 """
70 """
71 super(ZmqSocketChannel, self).__init__()
71 super(ZmqSocketChannel, self).__init__()
72 self.daemon = True
72 self.daemon = True
73
73
74 self.context = context
74 self.context = context
75 self.session = session
75 self.session = session
76 if address[1] == 0:
76 if address[1] == 0:
77 message = 'The port number for a channel cannot be 0.'
77 message = 'The port number for a channel cannot be 0.'
78 raise InvalidPortNumber(message)
78 raise InvalidPortNumber(message)
79 self._address = address
79 self._address = address
80
80
81 def stop(self):
81 def stop(self):
82 """Stop the channel's activity.
82 """Stop the channel's activity.
83
83
84 This calls :method:`Thread.join` and returns when the thread
84 This calls :method:`Thread.join` and returns when the thread
85 terminates. :class:`RuntimeError` will be raised if
85 terminates. :class:`RuntimeError` will be raised if
86 :method:`self.start` is called again.
86 :method:`self.start` is called again.
87 """
87 """
88 self.join()
88 self.join()
89
89
90 @property
90 @property
91 def address(self):
91 def address(self):
92 """Get the channel's address as an (ip, port) tuple.
92 """Get the channel's address as an (ip, port) tuple.
93
93
94 By the default, the address is (localhost, 0), where 0 means a random
94 By the default, the address is (localhost, 0), where 0 means a random
95 port.
95 port.
96 """
96 """
97 return self._address
97 return self._address
98
98
99 def add_io_state(self, state):
99 def add_io_state(self, state):
100 """Add IO state to the eventloop.
100 """Add IO state to the eventloop.
101
101
102 Parameters
102 Parameters
103 ----------
103 ----------
104 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
104 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
105 The IO state flag to set.
105 The IO state flag to set.
106
106
107 This is thread safe as it uses the thread safe IOLoop.add_callback.
107 This is thread safe as it uses the thread safe IOLoop.add_callback.
108 """
108 """
109 def add_io_state_callback():
109 def add_io_state_callback():
110 if not self.iostate & state:
110 if not self.iostate & state:
111 self.iostate = self.iostate | state
111 self.iostate = self.iostate | state
112 self.ioloop.update_handler(self.socket, self.iostate)
112 self.ioloop.update_handler(self.socket, self.iostate)
113 self.ioloop.add_callback(add_io_state_callback)
113 self.ioloop.add_callback(add_io_state_callback)
114
114
115 def drop_io_state(self, state):
115 def drop_io_state(self, state):
116 """Drop IO state from the eventloop.
116 """Drop IO state from the eventloop.
117
117
118 Parameters
118 Parameters
119 ----------
119 ----------
120 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
120 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
121 The IO state flag to set.
121 The IO state flag to set.
122
122
123 This is thread safe as it uses the thread safe IOLoop.add_callback.
123 This is thread safe as it uses the thread safe IOLoop.add_callback.
124 """
124 """
125 def drop_io_state_callback():
125 def drop_io_state_callback():
126 if self.iostate & state:
126 if self.iostate & state:
127 self.iostate = self.iostate & (~state)
127 self.iostate = self.iostate & (~state)
128 self.ioloop.update_handler(self.socket, self.iostate)
128 self.ioloop.update_handler(self.socket, self.iostate)
129 self.ioloop.add_callback(drop_io_state_callback)
129 self.ioloop.add_callback(drop_io_state_callback)
130
130
131
131
132 class XReqSocketChannel(ZmqSocketChannel):
132 class XReqSocketChannel(ZmqSocketChannel):
133 """The XREQ channel for issues request/replies to the kernel.
133 """The XREQ channel for issues request/replies to the kernel.
134 """
134 """
135
135
136 command_queue = None
136 command_queue = None
137
137
138 def __init__(self, context, session, address):
138 def __init__(self, context, session, address):
139 self.command_queue = Queue()
139 self.command_queue = Queue()
140 super(XReqSocketChannel, self).__init__(context, session, address)
140 super(XReqSocketChannel, self).__init__(context, session, address)
141
141
142 def run(self):
142 def run(self):
143 """The thread's main activity. Call start() instead."""
143 """The thread's main activity. Call start() instead."""
144 self.socket = self.context.socket(zmq.XREQ)
144 self.socket = self.context.socket(zmq.XREQ)
145 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
145 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
146 self.socket.connect('tcp://%s:%i' % self.address)
146 self.socket.connect('tcp://%s:%i' % self.address)
147 self.ioloop = ioloop.IOLoop()
147 self.ioloop = ioloop.IOLoop()
148 self.iostate = POLLERR|POLLIN
148 self.iostate = POLLERR|POLLIN
149 self.ioloop.add_handler(self.socket, self._handle_events,
149 self.ioloop.add_handler(self.socket, self._handle_events,
150 self.iostate)
150 self.iostate)
151 self.ioloop.start()
151 self.ioloop.start()
152
152
153 def stop(self):
153 def stop(self):
154 self.ioloop.stop()
154 self.ioloop.stop()
155 super(XReqSocketChannel, self).stop()
155 super(XReqSocketChannel, self).stop()
156
156
157 def call_handlers(self, msg):
157 def call_handlers(self, msg):
158 """This method is called in the ioloop thread when a message arrives.
158 """This method is called in the ioloop thread when a message arrives.
159
159
160 Subclasses should override this method to handle incoming messages.
160 Subclasses should override this method to handle incoming messages.
161 It is important to remember that this method is called in the thread
161 It is important to remember that this method is called in the thread
162 so that some logic must be done to ensure that the application leve
162 so that some logic must be done to ensure that the application leve
163 handlers are called in the application thread.
163 handlers are called in the application thread.
164 """
164 """
165 raise NotImplementedError('call_handlers must be defined in a subclass.')
165 raise NotImplementedError('call_handlers must be defined in a subclass.')
166
166
167 def execute(self, code):
167 def execute(self, code):
168 """Execute code in the kernel.
168 """Execute code in the kernel.
169
169
170 Parameters
170 Parameters
171 ----------
171 ----------
172 code : str
172 code : str
173 A string of Python code.
173 A string of Python code.
174
174
175 Returns
175 Returns
176 -------
176 -------
177 The msg_id of the message sent.
177 The msg_id of the message sent.
178 """
178 """
179 # Create class for content/msg creation. Related to, but possibly
179 # Create class for content/msg creation. Related to, but possibly
180 # not in Session.
180 # not in Session.
181 content = dict(code=code)
181 content = dict(code=code)
182 msg = self.session.msg('execute_request', content)
182 msg = self.session.msg('execute_request', content)
183 self._queue_request(msg)
183 self._queue_request(msg)
184 return msg['header']['msg_id']
184 return msg['header']['msg_id']
185
185
186 def complete(self, text, line, block=None):
186 def complete(self, text, line, block=None):
187 """Tab complete text, line, block in the kernel's namespace.
187 """Tab complete text, line, block in the kernel's namespace.
188
188
189 Parameters
189 Parameters
190 ----------
190 ----------
191 text : str
191 text : str
192 The text to complete.
192 The text to complete.
193 line : str
193 line : str
194 The full line of text that is the surrounding context for the
194 The full line of text that is the surrounding context for the
195 text to complete.
195 text to complete.
196 block : str
196 block : str
197 The full block of code in which the completion is being requested.
197 The full block of code in which the completion is being requested.
198
198
199 Returns
199 Returns
200 -------
200 -------
201 The msg_id of the message sent.
201 The msg_id of the message sent.
202 """
202 """
203 content = dict(text=text, line=line)
203 content = dict(text=text, line=line)
204 msg = self.session.msg('complete_request', content)
204 msg = self.session.msg('complete_request', content)
205 self._queue_request(msg)
205 self._queue_request(msg)
206 return msg['header']['msg_id']
206 return msg['header']['msg_id']
207
207
208 def object_info(self, oname):
208 def object_info(self, oname):
209 """Get metadata information about an object.
209 """Get metadata information about an object.
210
210
211 Parameters
211 Parameters
212 ----------
212 ----------
213 oname : str
213 oname : str
214 A string specifying the object name.
214 A string specifying the object name.
215
215
216 Returns
216 Returns
217 -------
217 -------
218 The msg_id of the message sent.
218 The msg_id of the message sent.
219 """
219 """
220 content = dict(oname=oname)
220 content = dict(oname=oname)
221 msg = self.session.msg('object_info_request', content)
221 msg = self.session.msg('object_info_request', content)
222 self._queue_request(msg)
222 self._queue_request(msg)
223 return msg['header']['msg_id']
223 return msg['header']['msg_id']
224
224
225 def _handle_events(self, socket, events):
225 def _handle_events(self, socket, events):
226 if events & POLLERR:
226 if events & POLLERR:
227 self._handle_err()
227 self._handle_err()
228 if events & POLLOUT:
228 if events & POLLOUT:
229 self._handle_send()
229 self._handle_send()
230 if events & POLLIN:
230 if events & POLLIN:
231 self._handle_recv()
231 self._handle_recv()
232
232
233 def _handle_recv(self):
233 def _handle_recv(self):
234 msg = self.socket.recv_json()
234 msg = self.socket.recv_json()
235 self.call_handlers(msg)
235 self.call_handlers(msg)
236
236
237 def _handle_send(self):
237 def _handle_send(self):
238 try:
238 try:
239 msg = self.command_queue.get(False)
239 msg = self.command_queue.get(False)
240 except Empty:
240 except Empty:
241 pass
241 pass
242 else:
242 else:
243 self.socket.send_json(msg)
243 self.socket.send_json(msg)
244 if self.command_queue.empty():
244 if self.command_queue.empty():
245 self.drop_io_state(POLLOUT)
245 self.drop_io_state(POLLOUT)
246
246
247 def _handle_err(self):
247 def _handle_err(self):
248 # We don't want to let this go silently, so eventually we should log.
248 # We don't want to let this go silently, so eventually we should log.
249 raise zmq.ZMQError()
249 raise zmq.ZMQError()
250
250
251 def _queue_request(self, msg):
251 def _queue_request(self, msg):
252 self.command_queue.put(msg)
252 self.command_queue.put(msg)
253 self.add_io_state(POLLOUT)
253 self.add_io_state(POLLOUT)
254
254
255
255
256 class SubSocketChannel(ZmqSocketChannel):
256 class SubSocketChannel(ZmqSocketChannel):
257 """The SUB channel which listens for messages that the kernel publishes.
257 """The SUB channel which listens for messages that the kernel publishes.
258 """
258 """
259
259
260 def __init__(self, context, session, address):
260 def __init__(self, context, session, address):
261 super(SubSocketChannel, self).__init__(context, session, address)
261 super(SubSocketChannel, self).__init__(context, session, address)
262
262
263 def run(self):
263 def run(self):
264 """The thread's main activity. Call start() instead."""
264 """The thread's main activity. Call start() instead."""
265 self.socket = self.context.socket(zmq.SUB)
265 self.socket = self.context.socket(zmq.SUB)
266 self.socket.setsockopt(zmq.SUBSCRIBE,'')
266 self.socket.setsockopt(zmq.SUBSCRIBE,'')
267 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
267 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
268 self.socket.connect('tcp://%s:%i' % self.address)
268 self.socket.connect('tcp://%s:%i' % self.address)
269 self.ioloop = ioloop.IOLoop()
269 self.ioloop = ioloop.IOLoop()
270 self.iostate = POLLIN|POLLERR
270 self.iostate = POLLIN|POLLERR
271 self.ioloop.add_handler(self.socket, self._handle_events,
271 self.ioloop.add_handler(self.socket, self._handle_events,
272 self.iostate)
272 self.iostate)
273 self.ioloop.start()
273 self.ioloop.start()
274
274
275 def stop(self):
275 def stop(self):
276 self.ioloop.stop()
276 self.ioloop.stop()
277 super(SubSocketChannel, self).stop()
277 super(SubSocketChannel, self).stop()
278
278
279 def call_handlers(self, msg):
279 def call_handlers(self, msg):
280 """This method is called in the ioloop thread when a message arrives.
280 """This method is called in the ioloop thread when a message arrives.
281
281
282 Subclasses should override this method to handle incoming messages.
282 Subclasses should override this method to handle incoming messages.
283 It is important to remember that this method is called in the thread
283 It is important to remember that this method is called in the thread
284 so that some logic must be done to ensure that the application leve
284 so that some logic must be done to ensure that the application leve
285 handlers are called in the application thread.
285 handlers are called in the application thread.
286 """
286 """
287 raise NotImplementedError('call_handlers must be defined in a subclass.')
287 raise NotImplementedError('call_handlers must be defined in a subclass.')
288
288
289 def flush(self, timeout=1.0):
289 def flush(self, timeout=1.0):
290 """Immediately processes all pending messages on the SUB channel.
290 """Immediately processes all pending messages on the SUB channel.
291
291
292 Callers should use this method to ensure that :method:`call_handlers`
292 Callers should use this method to ensure that :method:`call_handlers`
293 has been called for all messages that have been received on the
293 has been called for all messages that have been received on the
294 0MQ SUB socket of this channel.
294 0MQ SUB socket of this channel.
295
295
296 This method is thread safe.
296 This method is thread safe.
297
297
298 Parameters
298 Parameters
299 ----------
299 ----------
300 timeout : float, optional
300 timeout : float, optional
301 The maximum amount of time to spend flushing, in seconds. The
301 The maximum amount of time to spend flushing, in seconds. The
302 default is one second.
302 default is one second.
303 """
303 """
304 # We do the IOLoop callback process twice to ensure that the IOLoop
304 # We do the IOLoop callback process twice to ensure that the IOLoop
305 # gets to perform at least one full poll.
305 # gets to perform at least one full poll.
306 stop_time = time.time() + timeout
306 stop_time = time.time() + timeout
307 for i in xrange(2):
307 for i in xrange(2):
308 self._flushed = False
308 self._flushed = False
309 self.ioloop.add_callback(self._flush)
309 self.ioloop.add_callback(self._flush)
310 while not self._flushed and time.time() < stop_time:
310 while not self._flushed and time.time() < stop_time:
311 time.sleep(0.01)
311 time.sleep(0.01)
312
312
313 def _handle_events(self, socket, events):
313 def _handle_events(self, socket, events):
314 # Turn on and off POLLOUT depending on if we have made a request
314 # Turn on and off POLLOUT depending on if we have made a request
315 if events & POLLERR:
315 if events & POLLERR:
316 self._handle_err()
316 self._handle_err()
317 if events & POLLIN:
317 if events & POLLIN:
318 self._handle_recv()
318 self._handle_recv()
319
319
320 def _handle_err(self):
320 def _handle_err(self):
321 # We don't want to let this go silently, so eventually we should log.
321 # We don't want to let this go silently, so eventually we should log.
322 raise zmq.ZMQError()
322 raise zmq.ZMQError()
323
323
324 def _handle_recv(self):
324 def _handle_recv(self):
325 # Get all of the messages we can
325 # Get all of the messages we can
326 while True:
326 while True:
327 try:
327 try:
328 msg = self.socket.recv_json(zmq.NOBLOCK)
328 msg = self.socket.recv_json(zmq.NOBLOCK)
329 except zmq.ZMQError:
329 except zmq.ZMQError:
330 # Check the errno?
330 # Check the errno?
331 # Will this trigger POLLERR?
331 # Will this trigger POLLERR?
332 break
332 break
333 else:
333 else:
334 self.call_handlers(msg)
334 self.call_handlers(msg)
335
335
336 def _flush(self):
336 def _flush(self):
337 """Callback for :method:`self.flush`."""
337 """Callback for :method:`self.flush`."""
338 self._flushed = True
338 self._flushed = True
339
339
340
340
341 class RepSocketChannel(ZmqSocketChannel):
341 class RepSocketChannel(ZmqSocketChannel):
342 """A reply channel to handle raw_input requests that the kernel makes."""
342 """A reply channel to handle raw_input requests that the kernel makes."""
343
343
344 msg_queue = None
344 msg_queue = None
345
345
346 def __init__(self, context, session, address):
346 def __init__(self, context, session, address):
347 self.msg_queue = Queue()
347 self.msg_queue = Queue()
348 super(RepSocketChannel, self).__init__(context, session, address)
348 super(RepSocketChannel, self).__init__(context, session, address)
349
349
350 def run(self):
350 def run(self):
351 """The thread's main activity. Call start() instead."""
351 """The thread's main activity. Call start() instead."""
352 self.socket = self.context.socket(zmq.XREQ)
352 self.socket = self.context.socket(zmq.XREQ)
353 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
353 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
354 self.socket.connect('tcp://%s:%i' % self.address)
354 self.socket.connect('tcp://%s:%i' % self.address)
355 self.ioloop = ioloop.IOLoop()
355 self.ioloop = ioloop.IOLoop()
356 self.iostate = POLLERR|POLLIN
356 self.iostate = POLLERR|POLLIN
357 self.ioloop.add_handler(self.socket, self._handle_events,
357 self.ioloop.add_handler(self.socket, self._handle_events,
358 self.iostate)
358 self.iostate)
359 self.ioloop.start()
359 self.ioloop.start()
360
360
361 def stop(self):
361 def stop(self):
362 self.ioloop.stop()
362 self.ioloop.stop()
363 super(RepSocketChannel, self).stop()
363 super(RepSocketChannel, self).stop()
364
364
365 def call_handlers(self, msg):
365 def call_handlers(self, msg):
366 """This method is called in the ioloop thread when a message arrives.
366 """This method is called in the ioloop thread when a message arrives.
367
367
368 Subclasses should override this method to handle incoming messages.
368 Subclasses should override this method to handle incoming messages.
369 It is important to remember that this method is called in the thread
369 It is important to remember that this method is called in the thread
370 so that some logic must be done to ensure that the application leve
370 so that some logic must be done to ensure that the application leve
371 handlers are called in the application thread.
371 handlers are called in the application thread.
372 """
372 """
373 raise NotImplementedError('call_handlers must be defined in a subclass.')
373 raise NotImplementedError('call_handlers must be defined in a subclass.')
374
374
375 def input(self, string):
375 def input(self, string):
376 """Send a string of raw input to the kernel."""
376 """Send a string of raw input to the kernel."""
377 content = dict(value=string)
377 content = dict(value=string)
378 msg = self.session.msg('input_reply', content)
378 msg = self.session.msg('input_reply', content)
379 self._queue_reply(msg)
379 self._queue_reply(msg)
380
380
381 def _handle_events(self, socket, events):
381 def _handle_events(self, socket, events):
382 if events & POLLERR:
382 if events & POLLERR:
383 self._handle_err()
383 self._handle_err()
384 if events & POLLOUT:
384 if events & POLLOUT:
385 self._handle_send()
385 self._handle_send()
386 if events & POLLIN:
386 if events & POLLIN:
387 self._handle_recv()
387 self._handle_recv()
388
388
389 def _handle_recv(self):
389 def _handle_recv(self):
390 msg = self.socket.recv_json()
390 msg = self.socket.recv_json()
391 self.call_handlers(msg)
391 self.call_handlers(msg)
392
392
393 def _handle_send(self):
393 def _handle_send(self):
394 try:
394 try:
395 msg = self.msg_queue.get(False)
395 msg = self.msg_queue.get(False)
396 except Empty:
396 except Empty:
397 pass
397 pass
398 else:
398 else:
399 self.socket.send_json(msg)
399 self.socket.send_json(msg)
400 if self.msg_queue.empty():
400 if self.msg_queue.empty():
401 self.drop_io_state(POLLOUT)
401 self.drop_io_state(POLLOUT)
402
402
403 def _handle_err(self):
403 def _handle_err(self):
404 # We don't want to let this go silently, so eventually we should log.
404 # We don't want to let this go silently, so eventually we should log.
405 raise zmq.ZMQError()
405 raise zmq.ZMQError()
406
406
407 def _queue_reply(self, msg):
407 def _queue_reply(self, msg):
408 self.msg_queue.put(msg)
408 self.msg_queue.put(msg)
409 self.add_io_state(POLLOUT)
409 self.add_io_state(POLLOUT)
410
410
411
411
412 #-----------------------------------------------------------------------------
412 #-----------------------------------------------------------------------------
413 # Main kernel manager class
413 # Main kernel manager class
414 #-----------------------------------------------------------------------------
414 #-----------------------------------------------------------------------------
415
415
416 class KernelManager(HasTraits):
416 class KernelManager(HasTraits):
417 """ Manages a kernel for a frontend.
417 """ Manages a kernel for a frontend.
418
418
419 The SUB channel is for the frontend to receive messages published by the
419 The SUB channel is for the frontend to receive messages published by the
420 kernel.
420 kernel.
421
421
422 The REQ channel is for the frontend to make requests of the kernel.
422 The REQ channel is for the frontend to make requests of the kernel.
423
423
424 The REP channel is for the kernel to request stdin (raw_input) from the
424 The REP channel is for the kernel to request stdin (raw_input) from the
425 frontend.
425 frontend.
426 """
426 """
427 # The PyZMQ Context to use for communication with the kernel.
427 # The PyZMQ Context to use for communication with the kernel.
428 context = Instance(zmq.Context,(),{})
428 context = Instance(zmq.Context,(),{})
429
429
430 # The Session to use for communication with the kernel.
430 # The Session to use for communication with the kernel.
431 session = Instance(Session,(),{})
431 session = Instance(Session,(),{})
432
432
433 # The kernel process with which the KernelManager is communicating.
433 # The kernel process with which the KernelManager is communicating.
434 kernel = Instance(Popen)
434 kernel = Instance(Popen)
435
435
436 # The addresses for the communication channels.
437 xreq_address = TCPAddress((LOCALHOST, 0))
438 sub_address = TCPAddress((LOCALHOST, 0))
439 rep_address = TCPAddress((LOCALHOST, 0))
440
436 # The classes to use for the various channels.
441 # The classes to use for the various channels.
437 xreq_channel_class = Type(XReqSocketChannel)
442 xreq_channel_class = Type(XReqSocketChannel)
438 sub_channel_class = Type(SubSocketChannel)
443 sub_channel_class = Type(SubSocketChannel)
439 rep_channel_class = Type(RepSocketChannel)
444 rep_channel_class = Type(RepSocketChannel)
440
445
441 # Protected traits.
446 # Protected traits.
442 xreq_address = TCPAddress((LOCALHOST, 0))
443 sub_address = TCPAddress((LOCALHOST, 0))
444 rep_address = TCPAddress((LOCALHOST, 0))
445 _xreq_channel = Any
447 _xreq_channel = Any
446 _sub_channel = Any
448 _sub_channel = Any
447 _rep_channel = Any
449 _rep_channel = Any
448
450
449 def __init__(self, **kwargs):
451 #--------------------------------------------------------------------------
450 super(KernelManager, self).__init__(**kwargs)
451
452 #--------------------------------- -----------------------------------------
453 # Channel management methods:
452 # Channel management methods:
454 #--------------------------------------------------------------------------
453 #--------------------------------------------------------------------------
455
454
456 def start_channels(self):
455 def start_channels(self):
457 """Starts the channels for this kernel.
456 """Starts the channels for this kernel.
458
457
459 This will create the channels if they do not exist and then start
458 This will create the channels if they do not exist and then start
460 them. If port numbers of 0 are being used (random ports) then you
459 them. If port numbers of 0 are being used (random ports) then you
461 must first call :method:`start_kernel`. If the channels have been
460 must first call :method:`start_kernel`. If the channels have been
462 stopped and you call this, :class:`RuntimeError` will be raised.
461 stopped and you call this, :class:`RuntimeError` will be raised.
463 """
462 """
464 self.xreq_channel.start()
463 self.xreq_channel.start()
465 self.sub_channel.start()
464 self.sub_channel.start()
466 self.rep_channel.start()
465 self.rep_channel.start()
467
466
468 def stop_channels(self):
467 def stop_channels(self):
469 """Stops the channels for this kernel.
468 """Stops the channels for this kernel.
470
469
471 This stops the channels by joining their threads. If the channels
470 This stops the channels by joining their threads. If the channels
472 were not started, :class:`RuntimeError` will be raised.
471 were not started, :class:`RuntimeError` will be raised.
473 """
472 """
474 self.xreq_channel.stop()
473 self.xreq_channel.stop()
475 self.sub_channel.stop()
474 self.sub_channel.stop()
476 self.rep_channel.stop()
475 self.rep_channel.stop()
477
476
478 @property
477 @property
479 def channels_running(self):
478 def channels_running(self):
480 """Are all of the channels created and running?"""
479 """Are all of the channels created and running?"""
481 return self.xreq_channel.is_alive() \
480 return self.xreq_channel.is_alive() \
482 and self.sub_channel.is_alive() \
481 and self.sub_channel.is_alive() \
483 and self.rep_channel.is_alive()
482 and self.rep_channel.is_alive()
484
483
485 #--------------------------------------------------------------------------
484 #--------------------------------------------------------------------------
486 # Kernel process management methods:
485 # Kernel process management methods:
487 #--------------------------------------------------------------------------
486 #--------------------------------------------------------------------------
488
487
489 def start_kernel(self):
488 def start_kernel(self, pylab=False):
490 """Starts a kernel process and configures the manager to use it.
489 """Starts a kernel process and configures the manager to use it.
491
490
492 If random ports (port=0) are being used, this method must be called
491 If random ports (port=0) are being used, this method must be called
493 before the channels are created.
492 before the channels are created.
493
494 Parameters:
495 -----------
496 pylab : bool or string, optional (default False)
497 See IPython.zmq.kernel.launch_kernel for documentation.
494 """
498 """
495 xreq, sub, rep = self.xreq_address, self.sub_address, self.rep_address
499 xreq, sub, rep = self.xreq_address, self.sub_address, self.rep_address
496 if xreq[0] != LOCALHOST or sub[0] != LOCALHOST or rep[0] != LOCALHOST:
500 if xreq[0] != LOCALHOST or sub[0] != LOCALHOST or rep[0] != LOCALHOST:
497 raise RuntimeError("Can only launch a kernel on localhost."
501 raise RuntimeError("Can only launch a kernel on localhost."
498 "Make sure that the '*_address' attributes are "
502 "Make sure that the '*_address' attributes are "
499 "configured properly.")
503 "configured properly.")
500
504
501 self.kernel, xrep, pub, req = launch_kernel(
505 self.kernel, xrep, pub, req = launch_kernel(
502 xrep_port=xreq[1], pub_port=sub[1], req_port=rep[1])
506 xrep_port=xreq[1], pub_port=sub[1], req_port=rep[1], pylab=pylab)
503 self.xreq_address = (LOCALHOST, xrep)
507 self.xreq_address = (LOCALHOST, xrep)
504 self.sub_address = (LOCALHOST, pub)
508 self.sub_address = (LOCALHOST, pub)
505 self.rep_address = (LOCALHOST, req)
509 self.rep_address = (LOCALHOST, req)
506
510
507 @property
511 @property
508 def has_kernel(self):
512 def has_kernel(self):
509 """Returns whether a kernel process has been specified for the kernel
513 """Returns whether a kernel process has been specified for the kernel
510 manager.
514 manager.
511 """
515 """
512 return self.kernel is not None
516 return self.kernel is not None
513
517
514 def kill_kernel(self):
518 def kill_kernel(self):
515 """ Kill the running kernel. """
519 """ Kill the running kernel. """
516 if self.kernel is not None:
520 if self.kernel is not None:
517 self.kernel.kill()
521 self.kernel.kill()
518 self.kernel = None
522 self.kernel = None
519 else:
523 else:
520 raise RuntimeError("Cannot kill kernel. No kernel is running!")
524 raise RuntimeError("Cannot kill kernel. No kernel is running!")
521
525
522 def signal_kernel(self, signum):
526 def signal_kernel(self, signum):
523 """ Sends a signal to the kernel. """
527 """ Sends a signal to the kernel. """
524 if self.kernel is not None:
528 if self.kernel is not None:
525 self.kernel.send_signal(signum)
529 self.kernel.send_signal(signum)
526 else:
530 else:
527 raise RuntimeError("Cannot signal kernel. No kernel is running!")
531 raise RuntimeError("Cannot signal kernel. No kernel is running!")
528
532
529 @property
533 @property
530 def is_alive(self):
534 def is_alive(self):
531 """Is the kernel process still running?"""
535 """Is the kernel process still running?"""
532 if self.kernel is not None:
536 if self.kernel is not None:
533 if self.kernel.poll() is None:
537 if self.kernel.poll() is None:
534 return True
538 return True
535 else:
539 else:
536 return False
540 return False
537 else:
541 else:
538 # We didn't start the kernel with this KernelManager so we don't
542 # We didn't start the kernel with this KernelManager so we don't
539 # know if it is running. We should use a heartbeat for this case.
543 # know if it is running. We should use a heartbeat for this case.
540 return True
544 return True
541
545
542 #--------------------------------------------------------------------------
546 #--------------------------------------------------------------------------
543 # Channels used for communication with the kernel:
547 # Channels used for communication with the kernel:
544 #--------------------------------------------------------------------------
548 #--------------------------------------------------------------------------
545
549
546 @property
550 @property
547 def xreq_channel(self):
551 def xreq_channel(self):
548 """Get the REQ socket channel object to make requests of the kernel."""
552 """Get the REQ socket channel object to make requests of the kernel."""
549 if self._xreq_channel is None:
553 if self._xreq_channel is None:
550 self._xreq_channel = self.xreq_channel_class(self.context,
554 self._xreq_channel = self.xreq_channel_class(self.context,
551 self.session,
555 self.session,
552 self.xreq_address)
556 self.xreq_address)
553 return self._xreq_channel
557 return self._xreq_channel
554
558
555 @property
559 @property
556 def sub_channel(self):
560 def sub_channel(self):
557 """Get the SUB socket channel object."""
561 """Get the SUB socket channel object."""
558 if self._sub_channel is None:
562 if self._sub_channel is None:
559 self._sub_channel = self.sub_channel_class(self.context,
563 self._sub_channel = self.sub_channel_class(self.context,
560 self.session,
564 self.session,
561 self.sub_address)
565 self.sub_address)
562 return self._sub_channel
566 return self._sub_channel
563
567
564 @property
568 @property
565 def rep_channel(self):
569 def rep_channel(self):
566 """Get the REP socket channel object to handle stdin (raw_input)."""
570 """Get the REP socket channel object to handle stdin (raw_input)."""
567 if self._rep_channel is None:
571 if self._rep_channel is None:
568 self._rep_channel = self.rep_channel_class(self.context,
572 self._rep_channel = self.rep_channel_class(self.context,
569 self.session,
573 self.session,
570 self.rep_address)
574 self.rep_address)
571 return self._rep_channel
575 return self._rep_channel
@@ -1,25 +1,25 b''
1 # Standard library imports
1 # Standard library imports
2 from cStringIO import StringIO
2 from cStringIO import StringIO
3
3
4 # System library imports.
4 # System library imports.
5 from matplotlib.backends.backend_svg import new_figure_manager
5 from matplotlib.backends.backend_svg import new_figure_manager
6 from matplotlib._pylab_helpers import Gcf
6 from matplotlib._pylab_helpers import Gcf
7
7
8 # Local imports.
8 # Local imports.
9 from backend_payload import add_plot_payload
9 from backend_payload import add_plot_payload
10
10
11
11
12 def show():
12 def show():
13 """ Deliver a SVG payload.
13 """ Deliver a SVG payload.
14 """
14 """
15 figure_manager = Gcf.get_actve()
15 figure_manager = Gcf.get_active()
16 if figure_manager is not None:
16 if figure_manager is not None:
17 data = svg_from_canvas(figure_manager.canvas)
17 data = svg_from_canvas(figure_manager.canvas)
18 add_plot_payload('svg', data)
18 add_plot_payload('svg', data)
19
19
20 def svg_from_canvas(canvas):
20 def svg_from_canvas(canvas):
21 """ Return a string containing the SVG representation of a FigureCanvasSvg.
21 """ Return a string containing the SVG representation of a FigureCanvasSvg.
22 """
22 """
23 string_io = StringIO()
23 string_io = StringIO()
24 canvas.print_svg(string_io)
24 canvas.print_svg(string_io)
25 return string_io.getvalue()
25 return string_io.getvalue()
General Comments 0
You need to be logged in to leave comments. Login now