##// END OF EJS Templates
Fixed bugs with raw_input and finished and implementing InStream.
epatters -
Show More
@@ -1,883 +1,890 b''
1 # Standard library imports
1 # Standard library imports
2 import re
2 import re
3 import sys
3 import sys
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 # Local imports
8 # Local imports
9 from completion_widget import CompletionWidget
9 from completion_widget import CompletionWidget
10
10
11
11
12 class AnsiCodeProcessor(object):
12 class AnsiCodeProcessor(object):
13 """ Translates ANSI escape codes into readable attributes.
13 """ Translates ANSI escape codes into readable attributes.
14 """
14 """
15
15
16 def __init__(self):
16 def __init__(self):
17 self.ansi_colors = ( # Normal, Bright/Light
17 self.ansi_colors = ( # Normal, Bright/Light
18 ('#000000', '#7f7f7f'), # 0: black
18 ('#000000', '#7f7f7f'), # 0: black
19 ('#cd0000', '#ff0000'), # 1: red
19 ('#cd0000', '#ff0000'), # 1: red
20 ('#00cd00', '#00ff00'), # 2: green
20 ('#00cd00', '#00ff00'), # 2: green
21 ('#cdcd00', '#ffff00'), # 3: yellow
21 ('#cdcd00', '#ffff00'), # 3: yellow
22 ('#0000ee', '#0000ff'), # 4: blue
22 ('#0000ee', '#0000ff'), # 4: blue
23 ('#cd00cd', '#ff00ff'), # 5: magenta
23 ('#cd00cd', '#ff00ff'), # 5: magenta
24 ('#00cdcd', '#00ffff'), # 6: cyan
24 ('#00cdcd', '#00ffff'), # 6: cyan
25 ('#e5e5e5', '#ffffff')) # 7: white
25 ('#e5e5e5', '#ffffff')) # 7: white
26 self.reset()
26 self.reset()
27
27
28 def set_code(self, code):
28 def set_code(self, code):
29 """ Set attributes based on code.
29 """ Set attributes based on code.
30 """
30 """
31 if code == 0:
31 if code == 0:
32 self.reset()
32 self.reset()
33 elif code == 1:
33 elif code == 1:
34 self.intensity = 1
34 self.intensity = 1
35 self.bold = True
35 self.bold = True
36 elif code == 3:
36 elif code == 3:
37 self.italic = True
37 self.italic = True
38 elif code == 4:
38 elif code == 4:
39 self.underline = True
39 self.underline = True
40 elif code == 22:
40 elif code == 22:
41 self.intensity = 0
41 self.intensity = 0
42 self.bold = False
42 self.bold = False
43 elif code == 23:
43 elif code == 23:
44 self.italic = False
44 self.italic = False
45 elif code == 24:
45 elif code == 24:
46 self.underline = False
46 self.underline = False
47 elif code >= 30 and code <= 37:
47 elif code >= 30 and code <= 37:
48 self.foreground_color = code - 30
48 self.foreground_color = code - 30
49 elif code == 39:
49 elif code == 39:
50 self.foreground_color = None
50 self.foreground_color = None
51 elif code >= 40 and code <= 47:
51 elif code >= 40 and code <= 47:
52 self.background_color = code - 40
52 self.background_color = code - 40
53 elif code == 49:
53 elif code == 49:
54 self.background_color = None
54 self.background_color = None
55
55
56 def reset(self):
56 def reset(self):
57 """ Reset attributs to their default values.
57 """ Reset attributs to their default values.
58 """
58 """
59 self.intensity = 0
59 self.intensity = 0
60 self.italic = False
60 self.italic = False
61 self.bold = False
61 self.bold = False
62 self.underline = False
62 self.underline = False
63 self.foreground_color = None
63 self.foreground_color = None
64 self.background_color = None
64 self.background_color = None
65
65
66
66
67 class QtAnsiCodeProcessor(AnsiCodeProcessor):
67 class QtAnsiCodeProcessor(AnsiCodeProcessor):
68 """ Translates ANSI escape codes into QTextCharFormats.
68 """ Translates ANSI escape codes into QTextCharFormats.
69 """
69 """
70
70
71 def get_format(self):
71 def get_format(self):
72 """ Returns a QTextCharFormat that encodes the current style attributes.
72 """ Returns a QTextCharFormat that encodes the current style attributes.
73 """
73 """
74 format = QtGui.QTextCharFormat()
74 format = QtGui.QTextCharFormat()
75
75
76 # Set foreground color
76 # Set foreground color
77 if self.foreground_color is not None:
77 if self.foreground_color is not None:
78 color = self.ansi_colors[self.foreground_color][self.intensity]
78 color = self.ansi_colors[self.foreground_color][self.intensity]
79 format.setForeground(QtGui.QColor(color))
79 format.setForeground(QtGui.QColor(color))
80
80
81 # Set background color
81 # Set background color
82 if self.background_color is not None:
82 if self.background_color is not None:
83 color = self.ansi_colors[self.background_color][self.intensity]
83 color = self.ansi_colors[self.background_color][self.intensity]
84 format.setBackground(QtGui.QColor(color))
84 format.setBackground(QtGui.QColor(color))
85
85
86 # Set font weight/style options
86 # Set font weight/style options
87 if self.bold:
87 if self.bold:
88 format.setFontWeight(QtGui.QFont.Bold)
88 format.setFontWeight(QtGui.QFont.Bold)
89 else:
89 else:
90 format.setFontWeight(QtGui.QFont.Normal)
90 format.setFontWeight(QtGui.QFont.Normal)
91 format.setFontItalic(self.italic)
91 format.setFontItalic(self.italic)
92 format.setFontUnderline(self.underline)
92 format.setFontUnderline(self.underline)
93
93
94 return format
94 return format
95
95
96
96
97 class ConsoleWidget(QtGui.QPlainTextEdit):
97 class ConsoleWidget(QtGui.QPlainTextEdit):
98 """ Base class for console-type widgets. This class is mainly concerned with
98 """ Base class for console-type widgets. This class is mainly concerned with
99 dealing with the prompt, keeping the cursor inside the editing line, and
99 dealing with the prompt, keeping the cursor inside the editing line, and
100 handling ANSI escape sequences.
100 handling ANSI escape sequences.
101 """
101 """
102
102
103 # Whether to process ANSI escape codes.
103 # Whether to process ANSI escape codes.
104 ansi_codes = True
104 ansi_codes = True
105
105
106 # The maximum number of lines of text before truncation.
106 # The maximum number of lines of text before truncation.
107 buffer_size = 500
107 buffer_size = 500
108
108
109 # Whether to use a CompletionWidget or plain text output for tab completion.
109 # Whether to use a CompletionWidget or plain text output for tab completion.
110 gui_completion = True
110 gui_completion = True
111
111
112 # Whether to override ShortcutEvents for the keybindings defined by this
112 # Whether to override ShortcutEvents for the keybindings defined by this
113 # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
113 # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
114 # priority (when it has focus) over, e.g., window-level menu shortcuts.
114 # priority (when it has focus) over, e.g., window-level menu shortcuts.
115 override_shortcuts = False
115 override_shortcuts = False
116
116
117 # Protected class variables.
117 # Protected class variables.
118 _ansi_pattern = re.compile('\x01?\x1b\[(.*?)m\x02?')
118 _ansi_pattern = re.compile('\x01?\x1b\[(.*?)m\x02?')
119 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
119 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
120 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
120 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
121 QtCore.Qt.Key_A : QtCore.Qt.Key_Home,
121 QtCore.Qt.Key_A : QtCore.Qt.Key_Home,
122 QtCore.Qt.Key_E : QtCore.Qt.Key_End,
122 QtCore.Qt.Key_E : QtCore.Qt.Key_End,
123 QtCore.Qt.Key_P : QtCore.Qt.Key_Up,
123 QtCore.Qt.Key_P : QtCore.Qt.Key_Up,
124 QtCore.Qt.Key_N : QtCore.Qt.Key_Down,
124 QtCore.Qt.Key_N : QtCore.Qt.Key_Down,
125 QtCore.Qt.Key_D : QtCore.Qt.Key_Delete, }
125 QtCore.Qt.Key_D : QtCore.Qt.Key_Delete, }
126 _shortcuts = set(_ctrl_down_remap.keys() +
126 _shortcuts = set(_ctrl_down_remap.keys() +
127 [ QtCore.Qt.Key_C, QtCore.Qt.Key_V ])
127 [ QtCore.Qt.Key_C, QtCore.Qt.Key_V ])
128
128
129 #---------------------------------------------------------------------------
129 #---------------------------------------------------------------------------
130 # 'QObject' interface
130 # 'QObject' interface
131 #---------------------------------------------------------------------------
131 #---------------------------------------------------------------------------
132
132
133 def __init__(self, parent=None):
133 def __init__(self, parent=None):
134 QtGui.QPlainTextEdit.__init__(self, parent)
134 QtGui.QPlainTextEdit.__init__(self, parent)
135
135
136 # Initialize protected variables.
136 # Initialize protected variables.
137 self._ansi_processor = QtAnsiCodeProcessor()
137 self._ansi_processor = QtAnsiCodeProcessor()
138 self._completion_widget = CompletionWidget(self)
138 self._completion_widget = CompletionWidget(self)
139 self._continuation_prompt = '> '
139 self._continuation_prompt = '> '
140 self._executing = False
140 self._executing = False
141 self._prompt = ''
141 self._prompt = ''
142 self._prompt_pos = 0
142 self._prompt_pos = 0
143 self._reading = False
143 self._reading = False
144 self._reading_callback = None
144 self._reading_callback = None
145
145
146 # Set a monospaced font.
146 # Set a monospaced font.
147 self.reset_font()
147 self.reset_font()
148
148
149 # Define a custom context menu.
149 # Define a custom context menu.
150 self._context_menu = QtGui.QMenu(self)
150 self._context_menu = QtGui.QMenu(self)
151
151
152 copy_action = QtGui.QAction('Copy', self)
152 copy_action = QtGui.QAction('Copy', self)
153 copy_action.triggered.connect(self.copy)
153 copy_action.triggered.connect(self.copy)
154 self.copyAvailable.connect(copy_action.setEnabled)
154 self.copyAvailable.connect(copy_action.setEnabled)
155 self._context_menu.addAction(copy_action)
155 self._context_menu.addAction(copy_action)
156
156
157 self._paste_action = QtGui.QAction('Paste', self)
157 self._paste_action = QtGui.QAction('Paste', self)
158 self._paste_action.triggered.connect(self.paste)
158 self._paste_action.triggered.connect(self.paste)
159 self._context_menu.addAction(self._paste_action)
159 self._context_menu.addAction(self._paste_action)
160 self._context_menu.addSeparator()
160 self._context_menu.addSeparator()
161
161
162 select_all_action = QtGui.QAction('Select All', self)
162 select_all_action = QtGui.QAction('Select All', self)
163 select_all_action.triggered.connect(self.selectAll)
163 select_all_action.triggered.connect(self.selectAll)
164 self._context_menu.addAction(select_all_action)
164 self._context_menu.addAction(select_all_action)
165
165
166 def event(self, event):
166 def event(self, event):
167 """ Reimplemented to override shortcuts, if necessary.
167 """ Reimplemented to override shortcuts, if necessary.
168 """
168 """
169 # On Mac OS, it is always unnecessary to override shortcuts, hence the
169 # On Mac OS, it is always unnecessary to override shortcuts, hence the
170 # check below. Users should just use the Control key instead of the
170 # check below. Users should just use the Control key instead of the
171 # Command key.
171 # Command key.
172 if self.override_shortcuts and \
172 if self.override_shortcuts and \
173 sys.platform != 'darwin' and \
173 sys.platform != 'darwin' and \
174 event.type() == QtCore.QEvent.ShortcutOverride and \
174 event.type() == QtCore.QEvent.ShortcutOverride and \
175 self._control_down(event.modifiers()) and \
175 self._control_down(event.modifiers()) and \
176 event.key() in self._shortcuts:
176 event.key() in self._shortcuts:
177 event.accept()
177 event.accept()
178 return True
178 return True
179 else:
179 else:
180 return QtGui.QPlainTextEdit.event(self, event)
180 return QtGui.QPlainTextEdit.event(self, event)
181
181
182 #---------------------------------------------------------------------------
182 #---------------------------------------------------------------------------
183 # 'QWidget' interface
183 # 'QWidget' interface
184 #---------------------------------------------------------------------------
184 #---------------------------------------------------------------------------
185
185
186 def contextMenuEvent(self, event):
186 def contextMenuEvent(self, event):
187 """ Reimplemented to create a menu without destructive actions like
187 """ Reimplemented to create a menu without destructive actions like
188 'Cut' and 'Delete'.
188 'Cut' and 'Delete'.
189 """
189 """
190 clipboard_empty = QtGui.QApplication.clipboard().text().isEmpty()
190 clipboard_empty = QtGui.QApplication.clipboard().text().isEmpty()
191 self._paste_action.setEnabled(not clipboard_empty)
191 self._paste_action.setEnabled(not clipboard_empty)
192
192
193 self._context_menu.exec_(event.globalPos())
193 self._context_menu.exec_(event.globalPos())
194
194
195 def dragMoveEvent(self, event):
195 def dragMoveEvent(self, event):
196 """ Reimplemented to disable dropping text.
196 """ Reimplemented to disable dropping text.
197 """
197 """
198 event.ignore()
198 event.ignore()
199
199
200 def keyPressEvent(self, event):
200 def keyPressEvent(self, event):
201 """ Reimplemented to create a console-like interface.
201 """ Reimplemented to create a console-like interface.
202 """
202 """
203 intercepted = False
203 intercepted = False
204 cursor = self.textCursor()
204 cursor = self.textCursor()
205 position = cursor.position()
205 position = cursor.position()
206 key = event.key()
206 key = event.key()
207 ctrl_down = self._control_down(event.modifiers())
207 ctrl_down = self._control_down(event.modifiers())
208 alt_down = event.modifiers() & QtCore.Qt.AltModifier
208 alt_down = event.modifiers() & QtCore.Qt.AltModifier
209 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
209 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
210
210
211 # Even though we have reimplemented 'paste', the C++ level slot is still
211 # Even though we have reimplemented 'paste', the C++ level slot is still
212 # called by Qt. So we intercept the key press here.
212 # called by Qt. So we intercept the key press here.
213 if event.matches(QtGui.QKeySequence.Paste):
213 if event.matches(QtGui.QKeySequence.Paste):
214 self.paste()
214 self.paste()
215 intercepted = True
215 intercepted = True
216
216
217 elif ctrl_down:
217 elif ctrl_down:
218 if key in self._ctrl_down_remap:
218 if key in self._ctrl_down_remap:
219 ctrl_down = False
219 ctrl_down = False
220 key = self._ctrl_down_remap[key]
220 key = self._ctrl_down_remap[key]
221 event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, key,
221 event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, key,
222 QtCore.Qt.NoModifier)
222 QtCore.Qt.NoModifier)
223
223
224 elif key == QtCore.Qt.Key_K:
224 elif key == QtCore.Qt.Key_K:
225 if self._in_buffer(position):
225 if self._in_buffer(position):
226 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
226 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
227 QtGui.QTextCursor.KeepAnchor)
227 QtGui.QTextCursor.KeepAnchor)
228 cursor.removeSelectedText()
228 cursor.removeSelectedText()
229 intercepted = True
229 intercepted = True
230
230
231 elif key == QtCore.Qt.Key_X:
231 elif key == QtCore.Qt.Key_X:
232 intercepted = True
232 intercepted = True
233
233
234 elif key == QtCore.Qt.Key_Y:
234 elif key == QtCore.Qt.Key_Y:
235 self.paste()
235 self.paste()
236 intercepted = True
236 intercepted = True
237
237
238 elif alt_down:
238 elif alt_down:
239 if key == QtCore.Qt.Key_B:
239 if key == QtCore.Qt.Key_B:
240 self.setTextCursor(self._get_word_start_cursor(position))
240 self.setTextCursor(self._get_word_start_cursor(position))
241 intercepted = True
241 intercepted = True
242
242
243 elif key == QtCore.Qt.Key_F:
243 elif key == QtCore.Qt.Key_F:
244 self.setTextCursor(self._get_word_end_cursor(position))
244 self.setTextCursor(self._get_word_end_cursor(position))
245 intercepted = True
245 intercepted = True
246
246
247 elif key == QtCore.Qt.Key_Backspace:
247 elif key == QtCore.Qt.Key_Backspace:
248 cursor = self._get_word_start_cursor(position)
248 cursor = self._get_word_start_cursor(position)
249 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
249 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
250 cursor.removeSelectedText()
250 cursor.removeSelectedText()
251 intercepted = True
251 intercepted = True
252
252
253 elif key == QtCore.Qt.Key_D:
253 elif key == QtCore.Qt.Key_D:
254 cursor = self._get_word_end_cursor(position)
254 cursor = self._get_word_end_cursor(position)
255 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
255 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
256 cursor.removeSelectedText()
256 cursor.removeSelectedText()
257 intercepted = True
257 intercepted = True
258
258
259 if self._completion_widget.isVisible():
259 if self._completion_widget.isVisible():
260 self._completion_widget.keyPressEvent(event)
260 self._completion_widget.keyPressEvent(event)
261 intercepted = event.isAccepted()
261 intercepted = event.isAccepted()
262
262
263 else:
263 else:
264 if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
264 if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
265 if self._reading:
265 if self._reading:
266 self.appendPlainText('\n')
266 self.appendPlainText('\n')
267 self._reading = False
267 self._reading = False
268 if self._reading_callback:
268 if self._reading_callback:
269 self._reading_callback()
269 self._reading_callback()
270 self._reading_callback = None
270 self._reading_callback = None
271 elif not self._executing:
271 elif not self._executing:
272 self.execute(interactive=True)
272 self.execute(interactive=True)
273 intercepted = True
273 intercepted = True
274
274
275 elif key == QtCore.Qt.Key_Up:
275 elif key == QtCore.Qt.Key_Up:
276 if self._reading or not self._up_pressed():
276 if self._reading or not self._up_pressed():
277 intercepted = True
277 intercepted = True
278 else:
278 else:
279 prompt_line = self._get_prompt_cursor().blockNumber()
279 prompt_line = self._get_prompt_cursor().blockNumber()
280 intercepted = cursor.blockNumber() <= prompt_line
280 intercepted = cursor.blockNumber() <= prompt_line
281
281
282 elif key == QtCore.Qt.Key_Down:
282 elif key == QtCore.Qt.Key_Down:
283 if self._reading or not self._down_pressed():
283 if self._reading or not self._down_pressed():
284 intercepted = True
284 intercepted = True
285 else:
285 else:
286 end_line = self._get_end_cursor().blockNumber()
286 end_line = self._get_end_cursor().blockNumber()
287 intercepted = cursor.blockNumber() == end_line
287 intercepted = cursor.blockNumber() == end_line
288
288
289 elif key == QtCore.Qt.Key_Tab:
289 elif key == QtCore.Qt.Key_Tab:
290 if self._reading:
290 if self._reading:
291 intercepted = False
291 intercepted = False
292 else:
292 else:
293 intercepted = not self._tab_pressed()
293 intercepted = not self._tab_pressed()
294
294
295 elif key == QtCore.Qt.Key_Left:
295 elif key == QtCore.Qt.Key_Left:
296 intercepted = not self._in_buffer(position - 1)
296 intercepted = not self._in_buffer(position - 1)
297
297
298 elif key == QtCore.Qt.Key_Home:
298 elif key == QtCore.Qt.Key_Home:
299 cursor.movePosition(QtGui.QTextCursor.StartOfLine)
299 cursor.movePosition(QtGui.QTextCursor.StartOfLine)
300 start_pos = cursor.position()
300 start_pos = cursor.position()
301 start_line = cursor.blockNumber()
301 start_line = cursor.blockNumber()
302 if start_line == self._get_prompt_cursor().blockNumber():
302 if start_line == self._get_prompt_cursor().blockNumber():
303 start_pos += len(self._prompt)
303 start_pos += len(self._prompt)
304 else:
304 else:
305 start_pos += len(self._continuation_prompt)
305 start_pos += len(self._continuation_prompt)
306 if shift_down and self._in_buffer(position):
306 if shift_down and self._in_buffer(position):
307 self._set_selection(position, start_pos)
307 self._set_selection(position, start_pos)
308 else:
308 else:
309 self._set_position(start_pos)
309 self._set_position(start_pos)
310 intercepted = True
310 intercepted = True
311
311
312 elif key == QtCore.Qt.Key_Backspace and not alt_down:
312 elif key == QtCore.Qt.Key_Backspace and not alt_down:
313
313
314 # Line deletion (remove continuation prompt)
314 # Line deletion (remove continuation prompt)
315 len_prompt = len(self._continuation_prompt)
315 len_prompt = len(self._continuation_prompt)
316 if not self._reading and \
316 if not self._reading and \
317 cursor.columnNumber() == len_prompt and \
317 cursor.columnNumber() == len_prompt and \
318 position != self._prompt_pos:
318 position != self._prompt_pos:
319 cursor.setPosition(position - len_prompt,
319 cursor.setPosition(position - len_prompt,
320 QtGui.QTextCursor.KeepAnchor)
320 QtGui.QTextCursor.KeepAnchor)
321 cursor.removeSelectedText()
321 cursor.removeSelectedText()
322
322
323 # Regular backwards deletion
323 # Regular backwards deletion
324 else:
324 else:
325 anchor = cursor.anchor()
325 anchor = cursor.anchor()
326 if anchor == position:
326 if anchor == position:
327 intercepted = not self._in_buffer(position - 1)
327 intercepted = not self._in_buffer(position - 1)
328 else:
328 else:
329 intercepted = not self._in_buffer(min(anchor, position))
329 intercepted = not self._in_buffer(min(anchor, position))
330
330
331 elif key == QtCore.Qt.Key_Delete:
331 elif key == QtCore.Qt.Key_Delete:
332 anchor = cursor.anchor()
332 anchor = cursor.anchor()
333 intercepted = not self._in_buffer(min(anchor, position))
333 intercepted = not self._in_buffer(min(anchor, position))
334
334
335 # Don't move cursor if control is down to allow copy-paste using
335 # Don't move cursor if control is down to allow copy-paste using
336 # the keyboard in any part of the buffer.
336 # the keyboard in any part of the buffer.
337 if not ctrl_down:
337 if not ctrl_down:
338 self._keep_cursor_in_buffer()
338 self._keep_cursor_in_buffer()
339
339
340 if not intercepted:
340 if not intercepted:
341 QtGui.QPlainTextEdit.keyPressEvent(self, event)
341 QtGui.QPlainTextEdit.keyPressEvent(self, event)
342
342
343 #--------------------------------------------------------------------------
343 #--------------------------------------------------------------------------
344 # 'QPlainTextEdit' interface
344 # 'QPlainTextEdit' interface
345 #--------------------------------------------------------------------------
345 #--------------------------------------------------------------------------
346
346
347 def appendPlainText(self, text):
347 def appendPlainText(self, text):
348 """ Reimplemented to not append text as a new paragraph, which doesn't
348 """ Reimplemented to not append text as a new paragraph, which doesn't
349 make sense for a console widget. Also, if enabled, handle ANSI
349 make sense for a console widget. Also, if enabled, handle ANSI
350 codes.
350 codes.
351 """
351 """
352 cursor = self.textCursor()
352 cursor = self.textCursor()
353 cursor.movePosition(QtGui.QTextCursor.End)
353 cursor.movePosition(QtGui.QTextCursor.End)
354
354
355 if self.ansi_codes:
355 if self.ansi_codes:
356 format = QtGui.QTextCharFormat()
356 format = QtGui.QTextCharFormat()
357 previous_end = 0
357 previous_end = 0
358 for match in self._ansi_pattern.finditer(text):
358 for match in self._ansi_pattern.finditer(text):
359 cursor.insertText(text[previous_end:match.start()], format)
359 cursor.insertText(text[previous_end:match.start()], format)
360 previous_end = match.end()
360 previous_end = match.end()
361 for code in match.group(1).split(';'):
361 for code in match.group(1).split(';'):
362 self._ansi_processor.set_code(int(code))
362 self._ansi_processor.set_code(int(code))
363 format = self._ansi_processor.get_format()
363 format = self._ansi_processor.get_format()
364 cursor.insertText(text[previous_end:], format)
364 cursor.insertText(text[previous_end:], format)
365 else:
365 else:
366 cursor.insertText(text)
366 cursor.insertText(text)
367
367
368 def clear(self, keep_input=False):
368 def clear(self, keep_input=False):
369 """ Reimplemented to write a new prompt. If 'keep_input' is set,
369 """ Reimplemented to write a new prompt. If 'keep_input' is set,
370 restores the old input buffer when the new prompt is written.
370 restores the old input buffer when the new prompt is written.
371 """
371 """
372 super(ConsoleWidget, self).clear()
372 super(ConsoleWidget, self).clear()
373
373
374 if keep_input:
374 if keep_input:
375 input_buffer = self.input_buffer
375 input_buffer = self.input_buffer
376 self._show_prompt()
376 self._show_prompt()
377 if keep_input:
377 if keep_input:
378 self.input_buffer = input_buffer
378 self.input_buffer = input_buffer
379
379
380 def paste(self):
380 def paste(self):
381 """ Reimplemented to ensure that text is pasted in the editing region.
381 """ Reimplemented to ensure that text is pasted in the editing region.
382 """
382 """
383 self._keep_cursor_in_buffer()
383 self._keep_cursor_in_buffer()
384 QtGui.QPlainTextEdit.paste(self)
384 QtGui.QPlainTextEdit.paste(self)
385
385
386 def print_(self, printer):
386 def print_(self, printer):
387 """ Reimplemented to work around a bug in PyQt: the C++ level 'print_'
387 """ Reimplemented to work around a bug in PyQt: the C++ level 'print_'
388 slot has the wrong signature.
388 slot has the wrong signature.
389 """
389 """
390 QtGui.QPlainTextEdit.print_(self, printer)
390 QtGui.QPlainTextEdit.print_(self, printer)
391
391
392 #---------------------------------------------------------------------------
392 #---------------------------------------------------------------------------
393 # 'ConsoleWidget' public interface
393 # 'ConsoleWidget' public interface
394 #---------------------------------------------------------------------------
394 #---------------------------------------------------------------------------
395
395
396 def execute(self, source=None, hidden=False, interactive=False):
396 def execute(self, source=None, hidden=False, interactive=False):
397 """ Executes source or the input buffer, possibly prompting for more
397 """ Executes source or the input buffer, possibly prompting for more
398 input.
398 input.
399
399
400 Parameters:
400 Parameters:
401 -----------
401 -----------
402 source : str, optional
402 source : str, optional
403
403
404 The source to execute. If not specified, the input buffer will be
404 The source to execute. If not specified, the input buffer will be
405 used. If specified and 'hidden' is False, the input buffer will be
405 used. If specified and 'hidden' is False, the input buffer will be
406 replaced with the source before execution.
406 replaced with the source before execution.
407
407
408 hidden : bool, optional (default False)
408 hidden : bool, optional (default False)
409
409
410 If set, no output will be shown and the prompt will not be modified.
410 If set, no output will be shown and the prompt will not be modified.
411 In other words, it will be completely invisible to the user that
411 In other words, it will be completely invisible to the user that
412 an execution has occurred.
412 an execution has occurred.
413
413
414 interactive : bool, optional (default False)
414 interactive : bool, optional (default False)
415
415
416 Whether the console is to treat the source as having been manually
416 Whether the console is to treat the source as having been manually
417 entered by the user. The effect of this parameter depends on the
417 entered by the user. The effect of this parameter depends on the
418 subclass implementation.
418 subclass implementation.
419
419
420 Raises:
420 Raises:
421 -------
421 -------
422 RuntimeError
422 RuntimeError
423 If incomplete input is given and 'hidden' is True. In this case,
423 If incomplete input is given and 'hidden' is True. In this case,
424 it not possible to prompt for more input.
424 it not possible to prompt for more input.
425
425
426 Returns:
426 Returns:
427 --------
427 --------
428 A boolean indicating whether the source was executed.
428 A boolean indicating whether the source was executed.
429 """
429 """
430 if not hidden:
430 if not hidden:
431 if source is not None:
431 if source is not None:
432 self.input_buffer = source
432 self.input_buffer = source
433
433
434 self.appendPlainText('\n')
434 self.appendPlainText('\n')
435 self._executing_input_buffer = self.input_buffer
435 self._executing_input_buffer = self.input_buffer
436 self._executing = True
436 self._executing = True
437 self._prompt_finished()
437 self._prompt_finished()
438
438
439 real_source = self.input_buffer if source is None else source
439 real_source = self.input_buffer if source is None else source
440 complete = self._is_complete(real_source, interactive)
440 complete = self._is_complete(real_source, interactive)
441 if complete:
441 if complete:
442 if not hidden:
442 if not hidden:
443 # The maximum block count is only in effect during execution.
443 # The maximum block count is only in effect during execution.
444 # This ensures that _prompt_pos does not become invalid due to
444 # This ensures that _prompt_pos does not become invalid due to
445 # text truncation.
445 # text truncation.
446 self.setMaximumBlockCount(self.buffer_size)
446 self.setMaximumBlockCount(self.buffer_size)
447 self._execute(real_source, hidden)
447 self._execute(real_source, hidden)
448 elif hidden:
448 elif hidden:
449 raise RuntimeError('Incomplete noninteractive input: "%s"' % source)
449 raise RuntimeError('Incomplete noninteractive input: "%s"' % source)
450 else:
450 else:
451 self._show_continuation_prompt()
451 self._show_continuation_prompt()
452
452
453 return complete
453 return complete
454
454
455 def _get_input_buffer(self):
455 def _get_input_buffer(self):
456 """ The text that the user has entered entered at the current prompt.
456 """ The text that the user has entered entered at the current prompt.
457 """
457 """
458 # If we're executing, the input buffer may not even exist anymore due to
458 # If we're executing, the input buffer may not even exist anymore due to
459 # the limit imposed by 'buffer_size'. Therefore, we store it.
459 # the limit imposed by 'buffer_size'. Therefore, we store it.
460 if self._executing:
460 if self._executing:
461 return self._executing_input_buffer
461 return self._executing_input_buffer
462
462
463 cursor = self._get_end_cursor()
463 cursor = self._get_end_cursor()
464 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
464 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
465
465
466 # Use QTextDocumentFragment intermediate object because it strips
466 # Use QTextDocumentFragment intermediate object because it strips
467 # out the Unicode line break characters that Qt insists on inserting.
467 # out the Unicode line break characters that Qt insists on inserting.
468 input_buffer = str(cursor.selection().toPlainText())
468 input_buffer = str(cursor.selection().toPlainText())
469
469
470 # Strip out continuation prompts.
470 # Strip out continuation prompts.
471 return input_buffer.replace('\n' + self._continuation_prompt, '\n')
471 return input_buffer.replace('\n' + self._continuation_prompt, '\n')
472
472
473 def _set_input_buffer(self, string):
473 def _set_input_buffer(self, string):
474 """ Replaces the text in the input buffer with 'string'.
474 """ Replaces the text in the input buffer with 'string'.
475 """
475 """
476 # Add continuation prompts where necessary.
476 # Add continuation prompts where necessary.
477 lines = string.splitlines()
477 lines = string.splitlines()
478 for i in xrange(1, len(lines)):
478 for i in xrange(1, len(lines)):
479 lines[i] = self._continuation_prompt + lines[i]
479 lines[i] = self._continuation_prompt + lines[i]
480 string = '\n'.join(lines)
480 string = '\n'.join(lines)
481
481
482 # Replace buffer with new text.
482 # Replace buffer with new text.
483 cursor = self._get_end_cursor()
483 cursor = self._get_end_cursor()
484 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
484 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
485 cursor.insertText(string)
485 cursor.insertText(string)
486 self.moveCursor(QtGui.QTextCursor.End)
486 self.moveCursor(QtGui.QTextCursor.End)
487
487
488 input_buffer = property(_get_input_buffer, _set_input_buffer)
488 input_buffer = property(_get_input_buffer, _set_input_buffer)
489
489
490 def _get_input_buffer_cursor_line(self):
490 def _get_input_buffer_cursor_line(self):
491 """ The text in the line of the input buffer in which the user's cursor
491 """ The text in the line of the input buffer in which the user's cursor
492 rests. Returns a string if there is such a line; otherwise, None.
492 rests. Returns a string if there is such a line; otherwise, None.
493 """
493 """
494 if self._executing:
494 if self._executing:
495 return None
495 return None
496 cursor = self.textCursor()
496 cursor = self.textCursor()
497 if cursor.position() >= self._prompt_pos:
497 if cursor.position() >= self._prompt_pos:
498 text = str(cursor.block().text())
498 text = str(cursor.block().text())
499 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
499 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
500 return text[len(self._prompt):]
500 return text[len(self._prompt):]
501 else:
501 else:
502 return text[len(self._continuation_prompt):]
502 return text[len(self._continuation_prompt):]
503 else:
503 else:
504 return None
504 return None
505
505
506 input_buffer_cursor_line = property(_get_input_buffer_cursor_line)
506 input_buffer_cursor_line = property(_get_input_buffer_cursor_line)
507
507
508 def _get_font(self):
508 def _get_font(self):
509 """ The base font being used by the ConsoleWidget.
509 """ The base font being used by the ConsoleWidget.
510 """
510 """
511 return self.document().defaultFont()
511 return self.document().defaultFont()
512
512
513 def _set_font(self, font):
513 def _set_font(self, font):
514 """ Sets the base font for the ConsoleWidget to the specified QFont.
514 """ Sets the base font for the ConsoleWidget to the specified QFont.
515 """
515 """
516 self._completion_widget.setFont(font)
516 self._completion_widget.setFont(font)
517 self.document().setDefaultFont(font)
517 self.document().setDefaultFont(font)
518
518
519 font = property(_get_font, _set_font)
519 font = property(_get_font, _set_font)
520
520
521 def reset_font(self):
521 def reset_font(self):
522 """ Sets the font to the default fixed-width font for this platform.
522 """ Sets the font to the default fixed-width font for this platform.
523 """
523 """
524 if sys.platform == 'win32':
524 if sys.platform == 'win32':
525 name = 'Courier'
525 name = 'Courier'
526 elif sys.platform == 'darwin':
526 elif sys.platform == 'darwin':
527 name = 'Monaco'
527 name = 'Monaco'
528 else:
528 else:
529 name = 'Monospace'
529 name = 'Monospace'
530 font = QtGui.QFont(name, QtGui.qApp.font().pointSize())
530 font = QtGui.QFont(name, QtGui.qApp.font().pointSize())
531 font.setStyleHint(QtGui.QFont.TypeWriter)
531 font.setStyleHint(QtGui.QFont.TypeWriter)
532 self._set_font(font)
532 self._set_font(font)
533
533
534 #---------------------------------------------------------------------------
534 #---------------------------------------------------------------------------
535 # 'ConsoleWidget' abstract interface
535 # 'ConsoleWidget' abstract interface
536 #---------------------------------------------------------------------------
536 #---------------------------------------------------------------------------
537
537
538 def _is_complete(self, source, interactive):
538 def _is_complete(self, source, interactive):
539 """ Returns whether 'source' can be executed. When triggered by an
539 """ Returns whether 'source' can be executed. When triggered by an
540 Enter/Return key press, 'interactive' is True; otherwise, it is
540 Enter/Return key press, 'interactive' is True; otherwise, it is
541 False.
541 False.
542 """
542 """
543 raise NotImplementedError
543 raise NotImplementedError
544
544
545 def _execute(self, source, hidden):
545 def _execute(self, source, hidden):
546 """ Execute 'source'. If 'hidden', do not show any output.
546 """ Execute 'source'. If 'hidden', do not show any output.
547 """
547 """
548 raise NotImplementedError
548 raise NotImplementedError
549
549
550 def _prompt_started_hook(self):
550 def _prompt_started_hook(self):
551 """ Called immediately after a new prompt is displayed.
551 """ Called immediately after a new prompt is displayed.
552 """
552 """
553 pass
553 pass
554
554
555 def _prompt_finished_hook(self):
555 def _prompt_finished_hook(self):
556 """ Called immediately after a prompt is finished, i.e. when some input
556 """ Called immediately after a prompt is finished, i.e. when some input
557 will be processed and a new prompt displayed.
557 will be processed and a new prompt displayed.
558 """
558 """
559 pass
559 pass
560
560
561 def _up_pressed(self):
561 def _up_pressed(self):
562 """ Called when the up key is pressed. Returns whether to continue
562 """ Called when the up key is pressed. Returns whether to continue
563 processing the event.
563 processing the event.
564 """
564 """
565 return True
565 return True
566
566
567 def _down_pressed(self):
567 def _down_pressed(self):
568 """ Called when the down key is pressed. Returns whether to continue
568 """ Called when the down key is pressed. Returns whether to continue
569 processing the event.
569 processing the event.
570 """
570 """
571 return True
571 return True
572
572
573 def _tab_pressed(self):
573 def _tab_pressed(self):
574 """ Called when the tab key is pressed. Returns whether to continue
574 """ Called when the tab key is pressed. Returns whether to continue
575 processing the event.
575 processing the event.
576 """
576 """
577 return False
577 return False
578
578
579 #--------------------------------------------------------------------------
579 #--------------------------------------------------------------------------
580 # 'ConsoleWidget' protected interface
580 # 'ConsoleWidget' protected interface
581 #--------------------------------------------------------------------------
581 #--------------------------------------------------------------------------
582
582
583 def _control_down(self, modifiers):
583 def _control_down(self, modifiers):
584 """ Given a KeyboardModifiers flags object, return whether the Control
584 """ Given a KeyboardModifiers flags object, return whether the Control
585 key is down (on Mac OS, treat the Command key as a synonym for
585 key is down (on Mac OS, treat the Command key as a synonym for
586 Control).
586 Control).
587 """
587 """
588 down = bool(modifiers & QtCore.Qt.ControlModifier)
588 down = bool(modifiers & QtCore.Qt.ControlModifier)
589
589
590 # Note: on Mac OS, ControlModifier corresponds to the Command key while
590 # Note: on Mac OS, ControlModifier corresponds to the Command key while
591 # MetaModifier corresponds to the Control key.
591 # MetaModifier corresponds to the Control key.
592 if sys.platform == 'darwin':
592 if sys.platform == 'darwin':
593 down = down ^ bool(modifiers & QtCore.Qt.MetaModifier)
593 down = down ^ bool(modifiers & QtCore.Qt.MetaModifier)
594
594
595 return down
595 return down
596
596
597 def _complete_with_items(self, cursor, items):
597 def _complete_with_items(self, cursor, items):
598 """ Performs completion with 'items' at the specified cursor location.
598 """ Performs completion with 'items' at the specified cursor location.
599 """
599 """
600 if len(items) == 1:
600 if len(items) == 1:
601 cursor.setPosition(self.textCursor().position(),
601 cursor.setPosition(self.textCursor().position(),
602 QtGui.QTextCursor.KeepAnchor)
602 QtGui.QTextCursor.KeepAnchor)
603 cursor.insertText(items[0])
603 cursor.insertText(items[0])
604 elif len(items) > 1:
604 elif len(items) > 1:
605 if self.gui_completion:
605 if self.gui_completion:
606 self._completion_widget.show_items(cursor, items)
606 self._completion_widget.show_items(cursor, items)
607 else:
607 else:
608 text = '\n'.join(items) + '\n'
608 text = '\n'.join(items) + '\n'
609 self._write_text_keeping_prompt(text)
609 self._write_text_keeping_prompt(text)
610
610
611 def _get_end_cursor(self):
611 def _get_end_cursor(self):
612 """ Convenience method that returns a cursor for the last character.
612 """ Convenience method that returns a cursor for the last character.
613 """
613 """
614 cursor = self.textCursor()
614 cursor = self.textCursor()
615 cursor.movePosition(QtGui.QTextCursor.End)
615 cursor.movePosition(QtGui.QTextCursor.End)
616 return cursor
616 return cursor
617
617
618 def _get_prompt_cursor(self):
618 def _get_prompt_cursor(self):
619 """ Convenience method that returns a cursor for the prompt position.
619 """ Convenience method that returns a cursor for the prompt position.
620 """
620 """
621 cursor = self.textCursor()
621 cursor = self.textCursor()
622 cursor.setPosition(self._prompt_pos)
622 cursor.setPosition(self._prompt_pos)
623 return cursor
623 return cursor
624
624
625 def _get_selection_cursor(self, start, end):
625 def _get_selection_cursor(self, start, end):
626 """ Convenience method that returns a cursor with text selected between
626 """ Convenience method that returns a cursor with text selected between
627 the positions 'start' and 'end'.
627 the positions 'start' and 'end'.
628 """
628 """
629 cursor = self.textCursor()
629 cursor = self.textCursor()
630 cursor.setPosition(start)
630 cursor.setPosition(start)
631 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
631 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
632 return cursor
632 return cursor
633
633
634 def _get_word_start_cursor(self, position):
634 def _get_word_start_cursor(self, position):
635 """ Find the start of the word to the left the given position. If a
635 """ Find the start of the word to the left the given position. If a
636 sequence of non-word characters precedes the first word, skip over
636 sequence of non-word characters precedes the first word, skip over
637 them. (This emulates the behavior of bash, emacs, etc.)
637 them. (This emulates the behavior of bash, emacs, etc.)
638 """
638 """
639 document = self.document()
639 document = self.document()
640 position -= 1
640 position -= 1
641 while self._in_buffer(position) and \
641 while self._in_buffer(position) and \
642 not document.characterAt(position).isLetterOrNumber():
642 not document.characterAt(position).isLetterOrNumber():
643 position -= 1
643 position -= 1
644 while self._in_buffer(position) and \
644 while self._in_buffer(position) and \
645 document.characterAt(position).isLetterOrNumber():
645 document.characterAt(position).isLetterOrNumber():
646 position -= 1
646 position -= 1
647 cursor = self.textCursor()
647 cursor = self.textCursor()
648 cursor.setPosition(position + 1)
648 cursor.setPosition(position + 1)
649 return cursor
649 return cursor
650
650
651 def _get_word_end_cursor(self, position):
651 def _get_word_end_cursor(self, position):
652 """ Find the end of the word to the right the given position. If a
652 """ Find the end of the word to the right the given position. If a
653 sequence of non-word characters precedes the first word, skip over
653 sequence of non-word characters precedes the first word, skip over
654 them. (This emulates the behavior of bash, emacs, etc.)
654 them. (This emulates the behavior of bash, emacs, etc.)
655 """
655 """
656 document = self.document()
656 document = self.document()
657 end = self._get_end_cursor().position()
657 end = self._get_end_cursor().position()
658 while position < end and \
658 while position < end and \
659 not document.characterAt(position).isLetterOrNumber():
659 not document.characterAt(position).isLetterOrNumber():
660 position += 1
660 position += 1
661 while position < end and \
661 while position < end and \
662 document.characterAt(position).isLetterOrNumber():
662 document.characterAt(position).isLetterOrNumber():
663 position += 1
663 position += 1
664 cursor = self.textCursor()
664 cursor = self.textCursor()
665 cursor.setPosition(position)
665 cursor.setPosition(position)
666 return cursor
666 return cursor
667
667
668 def _prompt_started(self):
668 def _prompt_started(self):
669 """ Called immediately after a new prompt is displayed.
669 """ Called immediately after a new prompt is displayed.
670 """
670 """
671 # Temporarily disable the maximum block count to permit undo/redo and
671 # Temporarily disable the maximum block count to permit undo/redo and
672 # to ensure that the prompt position does not change due to truncation.
672 # to ensure that the prompt position does not change due to truncation.
673 self.setMaximumBlockCount(0)
673 self.setMaximumBlockCount(0)
674 self.setUndoRedoEnabled(True)
674 self.setUndoRedoEnabled(True)
675
675
676 self.setReadOnly(False)
676 self.setReadOnly(False)
677 self.moveCursor(QtGui.QTextCursor.End)
677 self.moveCursor(QtGui.QTextCursor.End)
678 self.centerCursor()
678 self.centerCursor()
679
679
680 self._executing = False
680 self._executing = False
681 self._prompt_started_hook()
681 self._prompt_started_hook()
682
682
683 def _prompt_finished(self):
683 def _prompt_finished(self):
684 """ Called immediately after a prompt is finished, i.e. when some input
684 """ Called immediately after a prompt is finished, i.e. when some input
685 will be processed and a new prompt displayed.
685 will be processed and a new prompt displayed.
686 """
686 """
687 self.setUndoRedoEnabled(False)
687 self.setUndoRedoEnabled(False)
688 self.setReadOnly(True)
688 self.setReadOnly(True)
689 self._prompt_finished_hook()
689 self._prompt_finished_hook()
690
690
691 def _readline(self, prompt='', callback=None):
691 def _readline(self, prompt='', callback=None):
692 """ Reads one line of input from the user.
692 """ Reads one line of input from the user.
693
693
694 Parameters
694 Parameters
695 ----------
695 ----------
696 prompt : str, optional
696 prompt : str, optional
697 The prompt to print before reading the line.
697 The prompt to print before reading the line.
698
698
699 callback : callable, optional
699 callback : callable, optional
700 A callback to execute with the read line. If not specified, input is
700 A callback to execute with the read line. If not specified, input is
701 read *synchronously* and this method does not return until it has
701 read *synchronously* and this method does not return until it has
702 been read.
702 been read.
703
703
704 Returns
704 Returns
705 -------
705 -------
706 If a callback is specified, returns nothing. Otherwise, returns the
706 If a callback is specified, returns nothing. Otherwise, returns the
707 input string with the trailing newline stripped.
707 input string with the trailing newline stripped.
708 """
708 """
709 if self._reading:
709 if self._reading:
710 raise RuntimeError('Cannot read a line. Widget is already reading.')
710 raise RuntimeError('Cannot read a line. Widget is already reading.')
711
711
712 if not callback and not self.isVisible():
712 if not callback and not self.isVisible():
713 # If the user cannot see the widget, this function cannot return.
713 # If the user cannot see the widget, this function cannot return.
714 raise RuntimeError('Cannot synchronously read a line if the widget'
714 raise RuntimeError('Cannot synchronously read a line if the widget'
715 'is not visible!')
715 'is not visible!')
716
716
717 self._reading = True
717 self._reading = True
718 self._show_prompt(prompt)
718 self._show_prompt(prompt, newline=False)
719
719
720 if callback is None:
720 if callback is None:
721 self._reading_callback = None
721 self._reading_callback = None
722 while self._reading:
722 while self._reading:
723 QtCore.QCoreApplication.processEvents()
723 QtCore.QCoreApplication.processEvents()
724 return self.input_buffer.rstrip('\n')
724 return self.input_buffer.rstrip('\n')
725
725
726 else:
726 else:
727 self._reading_callback = lambda: \
727 self._reading_callback = lambda: \
728 callback(self.input_buffer.rstrip('\n'))
728 callback(self.input_buffer.rstrip('\n'))
729
729
730 def _set_position(self, position):
730 def _set_position(self, position):
731 """ Convenience method to set the position of the cursor.
731 """ Convenience method to set the position of the cursor.
732 """
732 """
733 cursor = self.textCursor()
733 cursor = self.textCursor()
734 cursor.setPosition(position)
734 cursor.setPosition(position)
735 self.setTextCursor(cursor)
735 self.setTextCursor(cursor)
736
736
737 def _set_selection(self, start, end):
737 def _set_selection(self, start, end):
738 """ Convenience method to set the current selected text.
738 """ Convenience method to set the current selected text.
739 """
739 """
740 self.setTextCursor(self._get_selection_cursor(start, end))
740 self.setTextCursor(self._get_selection_cursor(start, end))
741
741
742 def _show_prompt(self, prompt=None):
742 def _show_prompt(self, prompt=None, newline=True):
743 """ Writes a new prompt at the end of the buffer. If 'prompt' is not
743 """ Writes a new prompt at the end of the buffer.
744 specified, the previous prompt is used.
744
745 """
745 Parameters
746 # Use QTextDocumentFragment intermediate object because it strips
746 ----------
747 # out the Unicode line break characters that Qt insists on inserting.
747 prompt : str, optional
748 cursor = self._get_end_cursor()
748 The prompt to show. If not specified, the previous prompt is used.
749 if cursor.position() > 0:
749
750 cursor.movePosition(QtGui.QTextCursor.Left,
750 newline : bool, optional (default True)
751 QtGui.QTextCursor.KeepAnchor)
751 If set, a new line will be written before showing the prompt if
752 if str(cursor.selection().toPlainText()) != '\n':
752 there is not already a newline at the end of the buffer.
753 self.appendPlainText('\n')
753 """
754 if newline:
755 cursor = self._get_end_cursor()
756 if cursor.position() > 0:
757 cursor.movePosition(QtGui.QTextCursor.Left,
758 QtGui.QTextCursor.KeepAnchor)
759 if str(cursor.selection().toPlainText()) != '\n':
760 self.appendPlainText('\n')
754
761
755 if prompt is not None:
762 if prompt is not None:
756 self._prompt = prompt
763 self._prompt = prompt
757 self.appendPlainText(self._prompt)
764 self.appendPlainText(self._prompt)
758
765
759 self._prompt_pos = self._get_end_cursor().position()
766 self._prompt_pos = self._get_end_cursor().position()
760 self._prompt_started()
767 self._prompt_started()
761
768
762 def _show_continuation_prompt(self):
769 def _show_continuation_prompt(self):
763 """ Writes a new continuation prompt at the end of the buffer.
770 """ Writes a new continuation prompt at the end of the buffer.
764 """
771 """
765 self.appendPlainText(self._continuation_prompt)
772 self.appendPlainText(self._continuation_prompt)
766 self._prompt_started()
773 self._prompt_started()
767
774
768 def _write_text_keeping_prompt(self, text):
775 def _write_text_keeping_prompt(self, text):
769 """ Writes 'text' after the current prompt, then restores the old prompt
776 """ Writes 'text' after the current prompt, then restores the old prompt
770 with its old input buffer.
777 with its old input buffer.
771 """
778 """
772 input_buffer = self.input_buffer
779 input_buffer = self.input_buffer
773 self.appendPlainText('\n')
780 self.appendPlainText('\n')
774 self._prompt_finished()
781 self._prompt_finished()
775
782
776 self.appendPlainText(text)
783 self.appendPlainText(text)
777 self._show_prompt()
784 self._show_prompt()
778 self.input_buffer = input_buffer
785 self.input_buffer = input_buffer
779
786
780 def _in_buffer(self, position):
787 def _in_buffer(self, position):
781 """ Returns whether the given position is inside the editing region.
788 """ Returns whether the given position is inside the editing region.
782 """
789 """
783 return position >= self._prompt_pos
790 return position >= self._prompt_pos
784
791
785 def _keep_cursor_in_buffer(self):
792 def _keep_cursor_in_buffer(self):
786 """ Ensures that the cursor is inside the editing region. Returns
793 """ Ensures that the cursor is inside the editing region. Returns
787 whether the cursor was moved.
794 whether the cursor was moved.
788 """
795 """
789 cursor = self.textCursor()
796 cursor = self.textCursor()
790 if cursor.position() < self._prompt_pos:
797 if cursor.position() < self._prompt_pos:
791 cursor.movePosition(QtGui.QTextCursor.End)
798 cursor.movePosition(QtGui.QTextCursor.End)
792 self.setTextCursor(cursor)
799 self.setTextCursor(cursor)
793 return True
800 return True
794 else:
801 else:
795 return False
802 return False
796
803
797
804
798 class HistoryConsoleWidget(ConsoleWidget):
805 class HistoryConsoleWidget(ConsoleWidget):
799 """ A ConsoleWidget that keeps a history of the commands that have been
806 """ A ConsoleWidget that keeps a history of the commands that have been
800 executed.
807 executed.
801 """
808 """
802
809
803 #---------------------------------------------------------------------------
810 #---------------------------------------------------------------------------
804 # 'QObject' interface
811 # 'QObject' interface
805 #---------------------------------------------------------------------------
812 #---------------------------------------------------------------------------
806
813
807 def __init__(self, parent=None):
814 def __init__(self, parent=None):
808 super(HistoryConsoleWidget, self).__init__(parent)
815 super(HistoryConsoleWidget, self).__init__(parent)
809
816
810 self._history = []
817 self._history = []
811 self._history_index = 0
818 self._history_index = 0
812
819
813 #---------------------------------------------------------------------------
820 #---------------------------------------------------------------------------
814 # 'ConsoleWidget' public interface
821 # 'ConsoleWidget' public interface
815 #---------------------------------------------------------------------------
822 #---------------------------------------------------------------------------
816
823
817 def execute(self, source=None, hidden=False, interactive=False):
824 def execute(self, source=None, hidden=False, interactive=False):
818 """ Reimplemented to the store history.
825 """ Reimplemented to the store history.
819 """
826 """
820 if not hidden:
827 if not hidden:
821 history = self.input_buffer if source is None else source
828 history = self.input_buffer if source is None else source
822
829
823 executed = super(HistoryConsoleWidget, self).execute(
830 executed = super(HistoryConsoleWidget, self).execute(
824 source, hidden, interactive)
831 source, hidden, interactive)
825
832
826 if executed and not hidden:
833 if executed and not hidden:
827 self._history.append(history.rstrip())
834 self._history.append(history.rstrip())
828 self._history_index = len(self._history)
835 self._history_index = len(self._history)
829
836
830 return executed
837 return executed
831
838
832 #---------------------------------------------------------------------------
839 #---------------------------------------------------------------------------
833 # 'ConsoleWidget' abstract interface
840 # 'ConsoleWidget' abstract interface
834 #---------------------------------------------------------------------------
841 #---------------------------------------------------------------------------
835
842
836 def _up_pressed(self):
843 def _up_pressed(self):
837 """ Called when the up key is pressed. Returns whether to continue
844 """ Called when the up key is pressed. Returns whether to continue
838 processing the event.
845 processing the event.
839 """
846 """
840 prompt_cursor = self._get_prompt_cursor()
847 prompt_cursor = self._get_prompt_cursor()
841 if self.textCursor().blockNumber() == prompt_cursor.blockNumber():
848 if self.textCursor().blockNumber() == prompt_cursor.blockNumber():
842 self.history_previous()
849 self.history_previous()
843
850
844 # Go to the first line of prompt for seemless history scrolling.
851 # Go to the first line of prompt for seemless history scrolling.
845 cursor = self._get_prompt_cursor()
852 cursor = self._get_prompt_cursor()
846 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
853 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
847 self.setTextCursor(cursor)
854 self.setTextCursor(cursor)
848
855
849 return False
856 return False
850 return True
857 return True
851
858
852 def _down_pressed(self):
859 def _down_pressed(self):
853 """ Called when the down key is pressed. Returns whether to continue
860 """ Called when the down key is pressed. Returns whether to continue
854 processing the event.
861 processing the event.
855 """
862 """
856 end_cursor = self._get_end_cursor()
863 end_cursor = self._get_end_cursor()
857 if self.textCursor().blockNumber() == end_cursor.blockNumber():
864 if self.textCursor().blockNumber() == end_cursor.blockNumber():
858 self.history_next()
865 self.history_next()
859 return False
866 return False
860 return True
867 return True
861
868
862 #---------------------------------------------------------------------------
869 #---------------------------------------------------------------------------
863 # 'HistoryConsoleWidget' interface
870 # 'HistoryConsoleWidget' interface
864 #---------------------------------------------------------------------------
871 #---------------------------------------------------------------------------
865
872
866 def history_previous(self):
873 def history_previous(self):
867 """ If possible, set the input buffer to the previous item in the
874 """ If possible, set the input buffer to the previous item in the
868 history.
875 history.
869 """
876 """
870 if self._history_index > 0:
877 if self._history_index > 0:
871 self._history_index -= 1
878 self._history_index -= 1
872 self.input_buffer = self._history[self._history_index]
879 self.input_buffer = self._history[self._history_index]
873
880
874 def history_next(self):
881 def history_next(self):
875 """ Set the input buffer to the next item in the history, or a blank
882 """ Set the input buffer to the next item in the history, or a blank
876 line if there is no subsequent item.
883 line if there is no subsequent item.
877 """
884 """
878 if self._history_index < len(self._history):
885 if self._history_index < len(self._history):
879 self._history_index += 1
886 self._history_index += 1
880 if self._history_index < len(self._history):
887 if self._history_index < len(self._history):
881 self.input_buffer = self._history[self._history_index]
888 self.input_buffer = self._history[self._history_index]
882 else:
889 else:
883 self.input_buffer = ''
890 self.input_buffer = ''
@@ -1,353 +1,357 b''
1 # Standard library imports
1 # Standard library imports
2 import signal
2 import signal
3
3
4 # System library imports
4 # System library imports
5 from pygments.lexers import PythonLexer
5 from pygments.lexers import PythonLexer
6 from PyQt4 import QtCore, QtGui
6 from PyQt4 import QtCore, QtGui
7 import zmq
7 import zmq
8
8
9 # Local imports
9 # Local imports
10 from IPython.core.inputsplitter import InputSplitter
10 from IPython.core.inputsplitter import InputSplitter
11 from call_tip_widget import CallTipWidget
11 from call_tip_widget import CallTipWidget
12 from completion_lexer import CompletionLexer
12 from completion_lexer import CompletionLexer
13 from console_widget import HistoryConsoleWidget
13 from console_widget import HistoryConsoleWidget
14 from pygments_highlighter import PygmentsHighlighter
14 from pygments_highlighter import PygmentsHighlighter
15
15
16
16
17 class FrontendHighlighter(PygmentsHighlighter):
17 class FrontendHighlighter(PygmentsHighlighter):
18 """ A Python PygmentsHighlighter that can be turned on and off and which
18 """ A Python PygmentsHighlighter that can be turned on and off and which
19 knows about continuation prompts.
19 knows about continuation prompts.
20 """
20 """
21
21
22 def __init__(self, frontend):
22 def __init__(self, frontend):
23 PygmentsHighlighter.__init__(self, frontend.document(), PythonLexer())
23 PygmentsHighlighter.__init__(self, frontend.document(), PythonLexer())
24 self._current_offset = 0
24 self._current_offset = 0
25 self._frontend = frontend
25 self._frontend = frontend
26 self.highlighting_on = False
26 self.highlighting_on = False
27
27
28 def highlightBlock(self, qstring):
28 def highlightBlock(self, qstring):
29 """ Highlight a block of text. Reimplemented to highlight selectively.
29 """ Highlight a block of text. Reimplemented to highlight selectively.
30 """
30 """
31 if self.highlighting_on:
31 if self.highlighting_on:
32 for prompt in (self._frontend._continuation_prompt,
32 for prompt in (self._frontend._continuation_prompt,
33 self._frontend._prompt):
33 self._frontend._prompt):
34 if qstring.startsWith(prompt):
34 if qstring.startsWith(prompt):
35 qstring.remove(0, len(prompt))
35 qstring.remove(0, len(prompt))
36 self._current_offset = len(prompt)
36 self._current_offset = len(prompt)
37 break
37 break
38 PygmentsHighlighter.highlightBlock(self, qstring)
38 PygmentsHighlighter.highlightBlock(self, qstring)
39
39
40 def setFormat(self, start, count, format):
40 def setFormat(self, start, count, format):
41 """ Reimplemented to avoid highlighting continuation prompts.
41 """ Reimplemented to avoid highlighting continuation prompts.
42 """
42 """
43 start += self._current_offset
43 start += self._current_offset
44 PygmentsHighlighter.setFormat(self, start, count, format)
44 PygmentsHighlighter.setFormat(self, start, count, format)
45
45
46
46
47 class FrontendWidget(HistoryConsoleWidget):
47 class FrontendWidget(HistoryConsoleWidget):
48 """ A Qt frontend for a generic Python kernel.
48 """ A Qt frontend for a generic Python kernel.
49 """
49 """
50
50
51 # Emitted when an 'execute_reply' is received from the kernel.
51 # Emitted when an 'execute_reply' is received from the kernel.
52 executed = QtCore.pyqtSignal(object)
52 executed = QtCore.pyqtSignal(object)
53
53
54 #---------------------------------------------------------------------------
54 #---------------------------------------------------------------------------
55 # 'QObject' interface
55 # 'QObject' interface
56 #---------------------------------------------------------------------------
56 #---------------------------------------------------------------------------
57
57
58 def __init__(self, parent=None):
58 def __init__(self, parent=None):
59 super(FrontendWidget, self).__init__(parent)
59 super(FrontendWidget, self).__init__(parent)
60
60
61 # ConsoleWidget protected variables.
61 # ConsoleWidget protected variables.
62 self._continuation_prompt = '... '
62 self._continuation_prompt = '... '
63
63
64 # FrontendWidget protected variables.
64 # FrontendWidget protected variables.
65 self._call_tip_widget = CallTipWidget(self)
65 self._call_tip_widget = CallTipWidget(self)
66 self._completion_lexer = CompletionLexer(PythonLexer())
66 self._completion_lexer = CompletionLexer(PythonLexer())
67 self._hidden = True
67 self._hidden = True
68 self._highlighter = FrontendHighlighter(self)
68 self._highlighter = FrontendHighlighter(self)
69 self._input_splitter = InputSplitter(input_mode='replace')
69 self._input_splitter = InputSplitter(input_mode='replace')
70 self._kernel_manager = None
70 self._kernel_manager = None
71
71
72 self.document().contentsChange.connect(self._document_contents_change)
72 self.document().contentsChange.connect(self._document_contents_change)
73
73
74 #---------------------------------------------------------------------------
74 #---------------------------------------------------------------------------
75 # 'QWidget' interface
75 # 'QWidget' interface
76 #---------------------------------------------------------------------------
76 #---------------------------------------------------------------------------
77
77
78 def focusOutEvent(self, event):
78 def focusOutEvent(self, event):
79 """ Reimplemented to hide calltips.
79 """ Reimplemented to hide calltips.
80 """
80 """
81 self._call_tip_widget.hide()
81 self._call_tip_widget.hide()
82 super(FrontendWidget, self).focusOutEvent(event)
82 super(FrontendWidget, self).focusOutEvent(event)
83
83
84 def keyPressEvent(self, event):
84 def keyPressEvent(self, event):
85 """ Reimplemented to allow calltips to process events and to send
85 """ Reimplemented to allow calltips to process events and to send
86 signals to the kernel.
86 signals to the kernel.
87 """
87 """
88 if self._executing and event.key() == QtCore.Qt.Key_C and \
88 if self._executing and event.key() == QtCore.Qt.Key_C and \
89 self._control_down(event.modifiers()):
89 self._control_down(event.modifiers()):
90 self._interrupt_kernel()
90 self._interrupt_kernel()
91 else:
91 else:
92 if self._call_tip_widget.isVisible():
92 if self._call_tip_widget.isVisible():
93 self._call_tip_widget.keyPressEvent(event)
93 self._call_tip_widget.keyPressEvent(event)
94 super(FrontendWidget, self).keyPressEvent(event)
94 super(FrontendWidget, self).keyPressEvent(event)
95
95
96 #---------------------------------------------------------------------------
96 #---------------------------------------------------------------------------
97 # 'ConsoleWidget' abstract interface
97 # 'ConsoleWidget' abstract interface
98 #---------------------------------------------------------------------------
98 #---------------------------------------------------------------------------
99
99
100 def _is_complete(self, source, interactive):
100 def _is_complete(self, source, interactive):
101 """ Returns whether 'source' can be completely processed and a new
101 """ Returns whether 'source' can be completely processed and a new
102 prompt created. When triggered by an Enter/Return key press,
102 prompt created. When triggered by an Enter/Return key press,
103 'interactive' is True; otherwise, it is False.
103 'interactive' is True; otherwise, it is False.
104 """
104 """
105 complete = self._input_splitter.push(source)
105 complete = self._input_splitter.push(source)
106 if interactive:
106 if interactive:
107 complete = not self._input_splitter.push_accepts_more()
107 complete = not self._input_splitter.push_accepts_more()
108 return complete
108 return complete
109
109
110 def _execute(self, source, hidden):
110 def _execute(self, source, hidden):
111 """ Execute 'source'. If 'hidden', do not show any output.
111 """ Execute 'source'. If 'hidden', do not show any output.
112 """
112 """
113 self.kernel_manager.xreq_channel.execute(source)
113 self.kernel_manager.xreq_channel.execute(source)
114 self._hidden = hidden
114 self._hidden = hidden
115
115
116 def _prompt_started_hook(self):
116 def _prompt_started_hook(self):
117 """ Called immediately after a new prompt is displayed.
117 """ Called immediately after a new prompt is displayed.
118 """
118 """
119 self._highlighter.highlighting_on = True
119 self._highlighter.highlighting_on = True
120
120
121 # Auto-indent if this is a continuation prompt.
121 # Auto-indent if this is a continuation prompt.
122 if self._get_prompt_cursor().blockNumber() != \
122 if self._get_prompt_cursor().blockNumber() != \
123 self._get_end_cursor().blockNumber():
123 self._get_end_cursor().blockNumber():
124 self.appendPlainText(' ' * self._input_splitter.indent_spaces)
124 self.appendPlainText(' ' * self._input_splitter.indent_spaces)
125
125
126 def _prompt_finished_hook(self):
126 def _prompt_finished_hook(self):
127 """ Called immediately after a prompt is finished, i.e. when some input
127 """ Called immediately after a prompt is finished, i.e. when some input
128 will be processed and a new prompt displayed.
128 will be processed and a new prompt displayed.
129 """
129 """
130 self._highlighter.highlighting_on = False
130 self._highlighter.highlighting_on = False
131
131
132 def _tab_pressed(self):
132 def _tab_pressed(self):
133 """ Called when the tab key is pressed. Returns whether to continue
133 """ Called when the tab key is pressed. Returns whether to continue
134 processing the event.
134 processing the event.
135 """
135 """
136 self._keep_cursor_in_buffer()
136 self._keep_cursor_in_buffer()
137 cursor = self.textCursor()
137 cursor = self.textCursor()
138 if not self._complete():
138 if not self._complete():
139 cursor.insertText(' ')
139 cursor.insertText(' ')
140 return False
140 return False
141
141
142 #---------------------------------------------------------------------------
142 #---------------------------------------------------------------------------
143 # 'ConsoleWidget' protected interface
143 # 'ConsoleWidget' protected interface
144 #---------------------------------------------------------------------------
144 #---------------------------------------------------------------------------
145
145
146 def _show_prompt(self, prompt=None):
146 def _show_prompt(self, prompt=None, newline=True):
147 """ Reimplemented to set a default prompt.
147 """ Reimplemented to set a default prompt.
148 """
148 """
149 if prompt is None:
149 if prompt is None:
150 prompt = '>>> '
150 prompt = '>>> '
151 super(FrontendWidget, self)._show_prompt(prompt)
151 super(FrontendWidget, self)._show_prompt(prompt, newline)
152
152
153 #---------------------------------------------------------------------------
153 #---------------------------------------------------------------------------
154 # 'FrontendWidget' interface
154 # 'FrontendWidget' interface
155 #---------------------------------------------------------------------------
155 #---------------------------------------------------------------------------
156
156
157 def execute_file(self, path, hidden=False):
157 def execute_file(self, path, hidden=False):
158 """ Attempts to execute file with 'path'. If 'hidden', no output is
158 """ Attempts to execute file with 'path'. If 'hidden', no output is
159 shown.
159 shown.
160 """
160 """
161 self.execute('execfile("%s")' % path, hidden=hidden)
161 self.execute('execfile("%s")' % path, hidden=hidden)
162
162
163 def _get_kernel_manager(self):
163 def _get_kernel_manager(self):
164 """ Returns the current kernel manager.
164 """ Returns the current kernel manager.
165 """
165 """
166 return self._kernel_manager
166 return self._kernel_manager
167
167
168 def _set_kernel_manager(self, kernel_manager):
168 def _set_kernel_manager(self, kernel_manager):
169 """ Disconnect from the current kernel manager (if any) and set a new
169 """ Disconnect from the current kernel manager (if any) and set a new
170 kernel manager.
170 kernel manager.
171 """
171 """
172 # Disconnect the old kernel manager, if necessary.
172 # Disconnect the old kernel manager, if necessary.
173 if self._kernel_manager is not None:
173 if self._kernel_manager is not None:
174 self._kernel_manager.started_channels.disconnect(
174 self._kernel_manager.started_channels.disconnect(
175 self._started_channels)
175 self._started_channels)
176 self._kernel_manager.stopped_channels.disconnect(
176 self._kernel_manager.stopped_channels.disconnect(
177 self._stopped_channels)
177 self._stopped_channels)
178
178
179 # Disconnect the old kernel manager's channels.
179 # Disconnect the old kernel manager's channels.
180 sub = self._kernel_manager.sub_channel
180 sub = self._kernel_manager.sub_channel
181 xreq = self._kernel_manager.xreq_channel
181 xreq = self._kernel_manager.xreq_channel
182 rep = self._kernel_manager.rep_channel
182 rep = self._kernel_manager.rep_channel
183 sub.message_received.disconnect(self._handle_sub)
183 sub.message_received.disconnect(self._handle_sub)
184 xreq.execute_reply.disconnect(self._handle_execute_reply)
184 xreq.execute_reply.disconnect(self._handle_execute_reply)
185 xreq.complete_reply.disconnect(self._handle_complete_reply)
185 xreq.complete_reply.disconnect(self._handle_complete_reply)
186 xreq.object_info_reply.disconnect(self._handle_object_info_reply)
186 xreq.object_info_reply.disconnect(self._handle_object_info_reply)
187 rep.readline_requested.disconnect(self._handle_req)
187 rep.readline_requested.disconnect(self._handle_req)
188
188
189 # Handle the case where the old kernel manager is still listening.
189 # Handle the case where the old kernel manager is still listening.
190 if self._kernel_manager.channels_running:
190 if self._kernel_manager.channels_running:
191 self._stopped_channels()
191 self._stopped_channels()
192
192
193 # Set the new kernel manager.
193 # Set the new kernel manager.
194 self._kernel_manager = kernel_manager
194 self._kernel_manager = kernel_manager
195 if kernel_manager is None:
195 if kernel_manager is None:
196 return
196 return
197
197
198 # Connect the new kernel manager.
198 # Connect the new kernel manager.
199 kernel_manager.started_channels.connect(self._started_channels)
199 kernel_manager.started_channels.connect(self._started_channels)
200 kernel_manager.stopped_channels.connect(self._stopped_channels)
200 kernel_manager.stopped_channels.connect(self._stopped_channels)
201
201
202 # Connect the new kernel manager's channels.
202 # Connect the new kernel manager's channels.
203 sub = kernel_manager.sub_channel
203 sub = kernel_manager.sub_channel
204 xreq = kernel_manager.xreq_channel
204 xreq = kernel_manager.xreq_channel
205 rep = kernel_manager.rep_channel
205 rep = kernel_manager.rep_channel
206 sub.message_received.connect(self._handle_sub)
206 sub.message_received.connect(self._handle_sub)
207 xreq.execute_reply.connect(self._handle_execute_reply)
207 xreq.execute_reply.connect(self._handle_execute_reply)
208 xreq.complete_reply.connect(self._handle_complete_reply)
208 xreq.complete_reply.connect(self._handle_complete_reply)
209 xreq.object_info_reply.connect(self._handle_object_info_reply)
209 xreq.object_info_reply.connect(self._handle_object_info_reply)
210 rep.readline_requested.connect(self._handle_req)
210 rep.readline_requested.connect(self._handle_req)
211
211
212 # Handle the case where the kernel manager started channels before
212 # Handle the case where the kernel manager started channels before
213 # we connected.
213 # we connected.
214 if kernel_manager.channels_running:
214 if kernel_manager.channels_running:
215 self._started_channels()
215 self._started_channels()
216
216
217 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
217 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
218
218
219 #---------------------------------------------------------------------------
219 #---------------------------------------------------------------------------
220 # 'FrontendWidget' protected interface
220 # 'FrontendWidget' protected interface
221 #---------------------------------------------------------------------------
221 #---------------------------------------------------------------------------
222
222
223 def _call_tip(self):
223 def _call_tip(self):
224 """ Shows a call tip, if appropriate, at the current cursor location.
224 """ Shows a call tip, if appropriate, at the current cursor location.
225 """
225 """
226 # Decide if it makes sense to show a call tip
226 # Decide if it makes sense to show a call tip
227 cursor = self.textCursor()
227 cursor = self.textCursor()
228 cursor.movePosition(QtGui.QTextCursor.Left)
228 cursor.movePosition(QtGui.QTextCursor.Left)
229 document = self.document()
229 document = self.document()
230 if document.characterAt(cursor.position()).toAscii() != '(':
230 if document.characterAt(cursor.position()).toAscii() != '(':
231 return False
231 return False
232 context = self._get_context(cursor)
232 context = self._get_context(cursor)
233 if not context:
233 if not context:
234 return False
234 return False
235
235
236 # Send the metadata request to the kernel
236 # Send the metadata request to the kernel
237 name = '.'.join(context)
237 name = '.'.join(context)
238 self._calltip_id = self.kernel_manager.xreq_channel.object_info(name)
238 self._calltip_id = self.kernel_manager.xreq_channel.object_info(name)
239 self._calltip_pos = self.textCursor().position()
239 self._calltip_pos = self.textCursor().position()
240 return True
240 return True
241
241
242 def _complete(self):
242 def _complete(self):
243 """ Performs completion at the current cursor location.
243 """ Performs completion at the current cursor location.
244 """
244 """
245 # Decide if it makes sense to do completion
245 # Decide if it makes sense to do completion
246 context = self._get_context()
246 context = self._get_context()
247 if not context:
247 if not context:
248 return False
248 return False
249
249
250 # Send the completion request to the kernel
250 # Send the completion request to the kernel
251 text = '.'.join(context)
251 text = '.'.join(context)
252 self._complete_id = self.kernel_manager.xreq_channel.complete(
252 self._complete_id = self.kernel_manager.xreq_channel.complete(
253 text, self.input_buffer_cursor_line, self.input_buffer)
253 text, self.input_buffer_cursor_line, self.input_buffer)
254 self._complete_pos = self.textCursor().position()
254 self._complete_pos = self.textCursor().position()
255 return True
255 return True
256
256
257 def _get_context(self, cursor=None):
257 def _get_context(self, cursor=None):
258 """ Gets the context at the current cursor location.
258 """ Gets the context at the current cursor location.
259 """
259 """
260 if cursor is None:
260 if cursor is None:
261 cursor = self.textCursor()
261 cursor = self.textCursor()
262 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
262 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
263 QtGui.QTextCursor.KeepAnchor)
263 QtGui.QTextCursor.KeepAnchor)
264 text = unicode(cursor.selectedText())
264 text = unicode(cursor.selectedText())
265 return self._completion_lexer.get_context(text)
265 return self._completion_lexer.get_context(text)
266
266
267 def _interrupt_kernel(self):
267 def _interrupt_kernel(self):
268 """ Attempts to the interrupt the kernel.
268 """ Attempts to the interrupt the kernel.
269 """
269 """
270 if self.kernel_manager.has_kernel:
270 if self.kernel_manager.has_kernel:
271 self.kernel_manager.signal_kernel(signal.SIGINT)
271 self.kernel_manager.signal_kernel(signal.SIGINT)
272 else:
272 else:
273 self.appendPlainText('Kernel process is either remote or '
273 self.appendPlainText('Kernel process is either remote or '
274 'unspecified. Cannot interrupt.\n')
274 'unspecified. Cannot interrupt.\n')
275
275
276 #------ Signal handlers ----------------------------------------------------
276 #------ Signal handlers ----------------------------------------------------
277
277
278 def _started_channels(self):
278 def _started_channels(self):
279 """ Called when the kernel manager has started listening.
279 """ Called when the kernel manager has started listening.
280 """
280 """
281 self.clear()
281 self.clear()
282
282
283 def _stopped_channels(self):
283 def _stopped_channels(self):
284 """ Called when the kernel manager has stopped listening.
284 """ Called when the kernel manager has stopped listening.
285 """
285 """
286 pass
286 pass
287
287
288 def _document_contents_change(self, position, removed, added):
288 def _document_contents_change(self, position, removed, added):
289 """ Called whenever the document's content changes. Display a calltip
289 """ Called whenever the document's content changes. Display a calltip
290 if appropriate.
290 if appropriate.
291 """
291 """
292 # Calculate where the cursor should be *after* the change:
292 # Calculate where the cursor should be *after* the change:
293 position += added
293 position += added
294
294
295 document = self.document()
295 document = self.document()
296 if position == self.textCursor().position():
296 if position == self.textCursor().position():
297 self._call_tip()
297 self._call_tip()
298
298
299 def _handle_req(self, req):
299 def _handle_req(self, req):
300 # Make sure that all output from the SUB channel has been processed
301 # before entering readline mode.
302 self.kernel_manager.sub_channel.flush()
303
300 def callback(line):
304 def callback(line):
301 self.kernel_manager.rep_channel.readline(line)
305 self.kernel_manager.rep_channel.readline(line)
302 self._readline(callback=callback)
306 self._readline(callback=callback)
303
307
304 def _handle_sub(self, omsg):
308 def _handle_sub(self, omsg):
305 if self._hidden:
309 if self._hidden:
306 return
310 return
307 handler = getattr(self, '_handle_%s' % omsg['msg_type'], None)
311 handler = getattr(self, '_handle_%s' % omsg['msg_type'], None)
308 if handler is not None:
312 if handler is not None:
309 handler(omsg)
313 handler(omsg)
310
314
311 def _handle_pyout(self, omsg):
315 def _handle_pyout(self, omsg):
312 session = omsg['parent_header']['session']
316 session = omsg['parent_header']['session']
313 if session == self.kernel_manager.session.session:
317 if session == self.kernel_manager.session.session:
314 self.appendPlainText(omsg['content']['data'] + '\n')
318 self.appendPlainText(omsg['content']['data'] + '\n')
315
319
316 def _handle_stream(self, omsg):
320 def _handle_stream(self, omsg):
317 self.appendPlainText(omsg['content']['data'])
321 self.appendPlainText(omsg['content']['data'])
318 self.moveCursor(QtGui.QTextCursor.End)
322 self.moveCursor(QtGui.QTextCursor.End)
319
323
320 def _handle_execute_reply(self, rep):
324 def _handle_execute_reply(self, rep):
321 if self._hidden:
325 if self._hidden:
322 return
326 return
323
327
324 # Make sure that all output from the SUB channel has been processed
328 # Make sure that all output from the SUB channel has been processed
325 # before writing a new prompt.
329 # before writing a new prompt.
326 self.kernel_manager.sub_channel.flush()
330 self.kernel_manager.sub_channel.flush()
327
331
328 content = rep['content']
332 content = rep['content']
329 status = content['status']
333 status = content['status']
330 if status == 'error':
334 if status == 'error':
331 self.appendPlainText(content['traceback'][-1])
335 self.appendPlainText(content['traceback'][-1])
332 elif status == 'aborted':
336 elif status == 'aborted':
333 text = "ERROR: ABORTED\n"
337 text = "ERROR: ABORTED\n"
334 self.appendPlainText(text)
338 self.appendPlainText(text)
335 self._hidden = True
339 self._hidden = True
336 self._show_prompt()
340 self._show_prompt()
337 self.executed.emit(rep)
341 self.executed.emit(rep)
338
342
339 def _handle_complete_reply(self, rep):
343 def _handle_complete_reply(self, rep):
340 cursor = self.textCursor()
344 cursor = self.textCursor()
341 if rep['parent_header']['msg_id'] == self._complete_id and \
345 if rep['parent_header']['msg_id'] == self._complete_id and \
342 cursor.position() == self._complete_pos:
346 cursor.position() == self._complete_pos:
343 text = '.'.join(self._get_context())
347 text = '.'.join(self._get_context())
344 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
348 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
345 self._complete_with_items(cursor, rep['content']['matches'])
349 self._complete_with_items(cursor, rep['content']['matches'])
346
350
347 def _handle_object_info_reply(self, rep):
351 def _handle_object_info_reply(self, rep):
348 cursor = self.textCursor()
352 cursor = self.textCursor()
349 if rep['parent_header']['msg_id'] == self._calltip_id and \
353 if rep['parent_header']['msg_id'] == self._calltip_id and \
350 cursor.position() == self._calltip_pos:
354 cursor.position() == self._calltip_pos:
351 doc = rep['content']['docstring']
355 doc = rep['content']['docstring']
352 if doc:
356 if doc:
353 self._call_tip_widget.show_docstring(doc)
357 self._call_tip_widget.show_docstring(doc)
@@ -1,452 +1,480 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 # Standard library imports.
14 # Standard library imports.
15 import __builtin__
15 import __builtin__
16 from code import CommandCompiler
16 import os
17 import os
17 import sys
18 import sys
18 import time
19 import time
19 import traceback
20 import traceback
20 from code import CommandCompiler
21
21
22 # System library imports.
22 # System library imports.
23 import zmq
23 import zmq
24
24
25 # Local imports.
25 # Local imports.
26 from IPython.external.argparse import ArgumentParser
26 from IPython.external.argparse import ArgumentParser
27 from session import Session, Message, extract_header
27 from session import Session, Message, extract_header
28 from completer import KernelCompleter
28 from completer import KernelCompleter
29
29
30
30
31 class InStream(object):
31 class InStream(object):
32 """ A file like object that reads from a 0MQ XREQ socket."""
32 """ A file like object that reads from a 0MQ XREQ socket."""
33
33
34 def __init__(self, session, socket):
34 def __init__(self, session, socket):
35 self.session = session
35 self.session = session
36 self.socket = socket
36 self.socket = socket
37
37
38 def close(self):
38 def close(self):
39 self.socket = None
39 self.socket = None
40
40
41 def flush(self):
41 def flush(self):
42 if self.socket is None:
42 if self.socket is None:
43 raise ValueError(u'I/O operation on closed file')
43 raise ValueError('I/O operation on closed file')
44
44
45 def isatty(self):
45 def isatty(self):
46 return False
46 return False
47
47
48 def next(self):
48 def next(self):
49 raise IOError('Seek not supported.')
49 raise IOError('Seek not supported.')
50
50
51 def read(self, size=-1):
51 def read(self, size=-1):
52 raise NotImplementedError
52 # FIXME: Do we want another request for this?
53 string = '\n'.join(self.readlines())
54 return self._truncate(string, size)
53
55
54 def readline(self, size=-1):
56 def readline(self, size=-1):
55 if self.socket is None:
57 if self.socket is None:
56 raise ValueError(u'I/O operation on closed file')
58 raise ValueError('I/O operation on closed file')
57 else:
59 else:
58 content = dict(size=size)
60 content = dict(size=size)
59 msg = self.session.msg('readline_request', content=content)
61 msg = self.session.msg('readline_request', content=content)
60 reply = self._request(msg)
62 reply = self._request(msg)
61 return reply['content']['line']
63 line = reply['content']['line']
64 return self._truncate(line, size)
62
65
63 def readlines(self, size=-1):
66 def readlines(self, sizehint=-1):
64 raise NotImplementedError
67 # Sizehint is ignored, as is permitted.
68 if self.socket is None:
69 raise ValueError('I/O operation on closed file')
70 else:
71 lines = []
72 while True:
73 line = self.readline()
74 if line:
75 lines.append(line)
76 else:
77 break
78 return lines
65
79
66 def seek(self, offset, whence=None):
80 def seek(self, offset, whence=None):
67 raise IOError('Seek not supported.')
81 raise IOError('Seek not supported.')
68
82
69 def write(self, string):
83 def write(self, string):
70 raise IOError('Write not supported on a read only stream.')
84 raise IOError('Write not supported on a read only stream.')
71
85
72 def writelines(self, sequence):
86 def writelines(self, sequence):
73 raise IOError('Write not supported on a read only stream.')
87 raise IOError('Write not supported on a read only stream.')
74
88
75 def _request(self, msg):
89 def _request(self, msg):
90 # Flush output before making the request. This ensures, for example,
91 # that raw_input(prompt) actually gets a prompt written.
92 sys.stderr.flush()
93 sys.stdout.flush()
94
76 self.socket.send_json(msg)
95 self.socket.send_json(msg)
77 while True:
96 while True:
78 try:
97 try:
79 reply = self.socket.recv_json(zmq.NOBLOCK)
98 reply = self.socket.recv_json(zmq.NOBLOCK)
80 except zmq.ZMQError, e:
99 except zmq.ZMQError, e:
81 if e.errno == zmq.EAGAIN:
100 if e.errno == zmq.EAGAIN:
82 pass
101 pass
83 else:
102 else:
84 raise
103 raise
85 else:
104 else:
86 break
105 break
87 return reply
106 return reply
88
107
108 def _truncate(self, string, size):
109 if size >= 0:
110 if isinstance(string, str):
111 return string[:size]
112 elif isinstance(string, unicode):
113 encoded = string.encode('utf-8')[:size]
114 return encoded.decode('utf-8', 'ignore')
115 return string
116
89
117
90 class OutStream(object):
118 class OutStream(object):
91 """A file like object that publishes the stream to a 0MQ PUB socket."""
119 """A file like object that publishes the stream to a 0MQ PUB socket."""
92
120
93 def __init__(self, session, pub_socket, name, max_buffer=200):
121 def __init__(self, session, pub_socket, name, max_buffer=200):
94 self.session = session
122 self.session = session
95 self.pub_socket = pub_socket
123 self.pub_socket = pub_socket
96 self.name = name
124 self.name = name
97 self._buffer = []
125 self._buffer = []
98 self._buffer_len = 0
126 self._buffer_len = 0
99 self.max_buffer = max_buffer
127 self.max_buffer = max_buffer
100 self.parent_header = {}
128 self.parent_header = {}
101
129
102 def set_parent(self, parent):
130 def set_parent(self, parent):
103 self.parent_header = extract_header(parent)
131 self.parent_header = extract_header(parent)
104
132
105 def close(self):
133 def close(self):
106 self.pub_socket = None
134 self.pub_socket = None
107
135
108 def flush(self):
136 def flush(self):
109 if self.pub_socket is None:
137 if self.pub_socket is None:
110 raise ValueError(u'I/O operation on closed file')
138 raise ValueError(u'I/O operation on closed file')
111 else:
139 else:
112 if self._buffer:
140 if self._buffer:
113 data = ''.join(self._buffer)
141 data = ''.join(self._buffer)
114 content = {u'name':self.name, u'data':data}
142 content = {u'name':self.name, u'data':data}
115 msg = self.session.msg(u'stream', content=content,
143 msg = self.session.msg(u'stream', content=content,
116 parent=self.parent_header)
144 parent=self.parent_header)
117 print>>sys.__stdout__, Message(msg)
145 print>>sys.__stdout__, Message(msg)
118 self.pub_socket.send_json(msg)
146 self.pub_socket.send_json(msg)
119 self._buffer_len = 0
147 self._buffer_len = 0
120 self._buffer = []
148 self._buffer = []
121
149
122 def isatty(self):
150 def isatty(self):
123 return False
151 return False
124
152
125 def next(self):
153 def next(self):
126 raise IOError('Read not supported on a write only stream.')
154 raise IOError('Read not supported on a write only stream.')
127
155
128 def read(self, size=None):
156 def read(self, size=None):
129 raise IOError('Read not supported on a write only stream.')
157 raise IOError('Read not supported on a write only stream.')
130
158
131 readline=read
159 readline=read
132
160
133 def write(self, s):
161 def write(self, s):
134 if self.pub_socket is None:
162 if self.pub_socket is None:
135 raise ValueError('I/O operation on closed file')
163 raise ValueError('I/O operation on closed file')
136 else:
164 else:
137 self._buffer.append(s)
165 self._buffer.append(s)
138 self._buffer_len += len(s)
166 self._buffer_len += len(s)
139 self._maybe_send()
167 self._maybe_send()
140
168
141 def _maybe_send(self):
169 def _maybe_send(self):
142 if '\n' in self._buffer[-1]:
170 if '\n' in self._buffer[-1]:
143 self.flush()
171 self.flush()
144 if self._buffer_len > self.max_buffer:
172 if self._buffer_len > self.max_buffer:
145 self.flush()
173 self.flush()
146
174
147 def writelines(self, sequence):
175 def writelines(self, sequence):
148 if self.pub_socket is None:
176 if self.pub_socket is None:
149 raise ValueError('I/O operation on closed file')
177 raise ValueError('I/O operation on closed file')
150 else:
178 else:
151 for s in sequence:
179 for s in sequence:
152 self.write(s)
180 self.write(s)
153
181
154
182
155 class DisplayHook(object):
183 class DisplayHook(object):
156
184
157 def __init__(self, session, pub_socket):
185 def __init__(self, session, pub_socket):
158 self.session = session
186 self.session = session
159 self.pub_socket = pub_socket
187 self.pub_socket = pub_socket
160 self.parent_header = {}
188 self.parent_header = {}
161
189
162 def __call__(self, obj):
190 def __call__(self, obj):
163 if obj is None:
191 if obj is None:
164 return
192 return
165
193
166 __builtin__._ = obj
194 __builtin__._ = obj
167 msg = self.session.msg(u'pyout', {u'data':repr(obj)},
195 msg = self.session.msg(u'pyout', {u'data':repr(obj)},
168 parent=self.parent_header)
196 parent=self.parent_header)
169 self.pub_socket.send_json(msg)
197 self.pub_socket.send_json(msg)
170
198
171 def set_parent(self, parent):
199 def set_parent(self, parent):
172 self.parent_header = extract_header(parent)
200 self.parent_header = extract_header(parent)
173
201
174
202
175 class Kernel(object):
203 class Kernel(object):
176
204
177 def __init__(self, session, reply_socket, pub_socket):
205 def __init__(self, session, reply_socket, pub_socket):
178 self.session = session
206 self.session = session
179 self.reply_socket = reply_socket
207 self.reply_socket = reply_socket
180 self.pub_socket = pub_socket
208 self.pub_socket = pub_socket
181 self.user_ns = {}
209 self.user_ns = {}
182 self.history = []
210 self.history = []
183 self.compiler = CommandCompiler()
211 self.compiler = CommandCompiler()
184 self.completer = KernelCompleter(self.user_ns)
212 self.completer = KernelCompleter(self.user_ns)
185 self.poll_ppid = False
213 self.poll_ppid = False
186
214
187 # Build dict of handlers for message types
215 # Build dict of handlers for message types
188 msg_types = [ 'execute_request', 'complete_request',
216 msg_types = [ 'execute_request', 'complete_request',
189 'object_info_request' ]
217 'object_info_request' ]
190 self.handlers = {}
218 self.handlers = {}
191 for msg_type in msg_types:
219 for msg_type in msg_types:
192 self.handlers[msg_type] = getattr(self, msg_type)
220 self.handlers[msg_type] = getattr(self, msg_type)
193
221
194 def abort_queue(self):
222 def abort_queue(self):
195 while True:
223 while True:
196 try:
224 try:
197 ident = self.reply_socket.recv(zmq.NOBLOCK)
225 ident = self.reply_socket.recv(zmq.NOBLOCK)
198 except zmq.ZMQError, e:
226 except zmq.ZMQError, e:
199 if e.errno == zmq.EAGAIN:
227 if e.errno == zmq.EAGAIN:
200 break
228 break
201 else:
229 else:
202 assert self.reply_socket.rcvmore(), "Unexpected missing message part."
230 assert self.reply_socket.rcvmore(), "Unexpected missing message part."
203 msg = self.reply_socket.recv_json()
231 msg = self.reply_socket.recv_json()
204 print>>sys.__stdout__, "Aborting:"
232 print>>sys.__stdout__, "Aborting:"
205 print>>sys.__stdout__, Message(msg)
233 print>>sys.__stdout__, Message(msg)
206 msg_type = msg['msg_type']
234 msg_type = msg['msg_type']
207 reply_type = msg_type.split('_')[0] + '_reply'
235 reply_type = msg_type.split('_')[0] + '_reply'
208 reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
236 reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
209 print>>sys.__stdout__, Message(reply_msg)
237 print>>sys.__stdout__, Message(reply_msg)
210 self.reply_socket.send(ident,zmq.SNDMORE)
238 self.reply_socket.send(ident,zmq.SNDMORE)
211 self.reply_socket.send_json(reply_msg)
239 self.reply_socket.send_json(reply_msg)
212 # We need to wait a bit for requests to come in. This can probably
240 # We need to wait a bit for requests to come in. This can probably
213 # be set shorter for true asynchronous clients.
241 # be set shorter for true asynchronous clients.
214 time.sleep(0.1)
242 time.sleep(0.1)
215
243
216 def execute_request(self, ident, parent):
244 def execute_request(self, ident, parent):
217 try:
245 try:
218 code = parent[u'content'][u'code']
246 code = parent[u'content'][u'code']
219 except:
247 except:
220 print>>sys.__stderr__, "Got bad msg: "
248 print>>sys.__stderr__, "Got bad msg: "
221 print>>sys.__stderr__, Message(parent)
249 print>>sys.__stderr__, Message(parent)
222 return
250 return
223 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)
224 self.pub_socket.send_json(pyin_msg)
252 self.pub_socket.send_json(pyin_msg)
225 try:
253 try:
226 comp_code = self.compiler(code, '<zmq-kernel>')
254 comp_code = self.compiler(code, '<zmq-kernel>')
227 sys.displayhook.set_parent(parent)
255 sys.displayhook.set_parent(parent)
228 exec comp_code in self.user_ns, self.user_ns
256 exec comp_code in self.user_ns, self.user_ns
229 except:
257 except:
230 result = u'error'
258 result = u'error'
231 etype, evalue, tb = sys.exc_info()
259 etype, evalue, tb = sys.exc_info()
232 tb = traceback.format_exception(etype, evalue, tb)
260 tb = traceback.format_exception(etype, evalue, tb)
233 exc_content = {
261 exc_content = {
234 u'status' : u'error',
262 u'status' : u'error',
235 u'traceback' : tb,
263 u'traceback' : tb,
236 u'etype' : unicode(etype),
264 u'etype' : unicode(etype),
237 u'evalue' : unicode(evalue)
265 u'evalue' : unicode(evalue)
238 }
266 }
239 exc_msg = self.session.msg(u'pyerr', exc_content, parent)
267 exc_msg = self.session.msg(u'pyerr', exc_content, parent)
240 self.pub_socket.send_json(exc_msg)
268 self.pub_socket.send_json(exc_msg)
241 reply_content = exc_content
269 reply_content = exc_content
242 else:
270 else:
243 reply_content = {'status' : 'ok'}
271 reply_content = {'status' : 'ok'}
244 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
272 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
245 print>>sys.__stdout__, Message(reply_msg)
273 print>>sys.__stdout__, Message(reply_msg)
246 self.reply_socket.send(ident, zmq.SNDMORE)
274 self.reply_socket.send(ident, zmq.SNDMORE)
247 self.reply_socket.send_json(reply_msg)
275 self.reply_socket.send_json(reply_msg)
248 if reply_msg['content']['status'] == u'error':
276 if reply_msg['content']['status'] == u'error':
249 self.abort_queue()
277 self.abort_queue()
250
278
251 def complete_request(self, ident, parent):
279 def complete_request(self, ident, parent):
252 matches = {'matches' : self.complete(parent),
280 matches = {'matches' : self.complete(parent),
253 'status' : 'ok'}
281 'status' : 'ok'}
254 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
282 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
255 matches, parent, ident)
283 matches, parent, ident)
256 print >> sys.__stdout__, completion_msg
284 print >> sys.__stdout__, completion_msg
257
285
258 def complete(self, msg):
286 def complete(self, msg):
259 return self.completer.complete(msg.content.line, msg.content.text)
287 return self.completer.complete(msg.content.line, msg.content.text)
260
288
261 def object_info_request(self, ident, parent):
289 def object_info_request(self, ident, parent):
262 context = parent['content']['oname'].split('.')
290 context = parent['content']['oname'].split('.')
263 object_info = self.object_info(context)
291 object_info = self.object_info(context)
264 msg = self.session.send(self.reply_socket, 'object_info_reply',
292 msg = self.session.send(self.reply_socket, 'object_info_reply',
265 object_info, parent, ident)
293 object_info, parent, ident)
266 print >> sys.__stdout__, msg
294 print >> sys.__stdout__, msg
267
295
268 def object_info(self, context):
296 def object_info(self, context):
269 symbol, leftover = self.symbol_from_context(context)
297 symbol, leftover = self.symbol_from_context(context)
270 if symbol is not None and not leftover:
298 if symbol is not None and not leftover:
271 doc = getattr(symbol, '__doc__', '')
299 doc = getattr(symbol, '__doc__', '')
272 else:
300 else:
273 doc = ''
301 doc = ''
274 object_info = dict(docstring = doc)
302 object_info = dict(docstring = doc)
275 return object_info
303 return object_info
276
304
277 def symbol_from_context(self, context):
305 def symbol_from_context(self, context):
278 if not context:
306 if not context:
279 return None, context
307 return None, context
280
308
281 base_symbol_string = context[0]
309 base_symbol_string = context[0]
282 symbol = self.user_ns.get(base_symbol_string, None)
310 symbol = self.user_ns.get(base_symbol_string, None)
283 if symbol is None:
311 if symbol is None:
284 symbol = __builtin__.__dict__.get(base_symbol_string, None)
312 symbol = __builtin__.__dict__.get(base_symbol_string, None)
285 if symbol is None:
313 if symbol is None:
286 return None, context
314 return None, context
287
315
288 context = context[1:]
316 context = context[1:]
289 for i, name in enumerate(context):
317 for i, name in enumerate(context):
290 new_symbol = getattr(symbol, name, None)
318 new_symbol = getattr(symbol, name, None)
291 if new_symbol is None:
319 if new_symbol is None:
292 return symbol, context[i:]
320 return symbol, context[i:]
293 else:
321 else:
294 symbol = new_symbol
322 symbol = new_symbol
295
323
296 return symbol, []
324 return symbol, []
297
325
298 def start(self):
326 def start(self):
299 while True:
327 while True:
300 if self.poll_ppid and os.getppid() == 1:
328 if self.poll_ppid and os.getppid() == 1:
301 print>>sys.__stderr__, "KILLED KERNEL. No parent process."
329 print>>sys.__stderr__, "KILLED KERNEL. No parent process."
302 os._exit(1)
330 os._exit(1)
303
331
304 ident = self.reply_socket.recv()
332 ident = self.reply_socket.recv()
305 assert self.reply_socket.rcvmore(), "Unexpected missing message part."
333 assert self.reply_socket.rcvmore(), "Unexpected missing message part."
306 msg = self.reply_socket.recv_json()
334 msg = self.reply_socket.recv_json()
307 omsg = Message(msg)
335 omsg = Message(msg)
308 print>>sys.__stdout__
336 print>>sys.__stdout__
309 print>>sys.__stdout__, omsg
337 print>>sys.__stdout__, omsg
310 handler = self.handlers.get(omsg.msg_type, None)
338 handler = self.handlers.get(omsg.msg_type, None)
311 if handler is None:
339 if handler is None:
312 print >> sys.__stderr__, "UNKNOWN MESSAGE TYPE:", omsg
340 print >> sys.__stderr__, "UNKNOWN MESSAGE TYPE:", omsg
313 else:
341 else:
314 handler(ident, omsg)
342 handler(ident, omsg)
315
343
316
344
317 def bind_port(socket, ip, port):
345 def bind_port(socket, ip, port):
318 """ Binds the specified ZMQ socket. If the port is less than zero, a random
346 """ Binds the specified ZMQ socket. If the port is less than zero, a random
319 port is chosen. Returns the port that was bound.
347 port is chosen. Returns the port that was bound.
320 """
348 """
321 connection = 'tcp://%s' % ip
349 connection = 'tcp://%s' % ip
322 if port <= 0:
350 if port <= 0:
323 port = socket.bind_to_random_port(connection)
351 port = socket.bind_to_random_port(connection)
324 else:
352 else:
325 connection += ':%i' % port
353 connection += ':%i' % port
326 socket.bind(connection)
354 socket.bind(connection)
327 return port
355 return port
328
356
329 def main():
357 def main():
330 """ Main entry point for launching a kernel.
358 """ Main entry point for launching a kernel.
331 """
359 """
332 # Parse command line arguments.
360 # Parse command line arguments.
333 parser = ArgumentParser()
361 parser = ArgumentParser()
334 parser.add_argument('--ip', type=str, default='127.0.0.1',
362 parser.add_argument('--ip', type=str, default='127.0.0.1',
335 help='set the kernel\'s IP address [default: local]')
363 help='set the kernel\'s IP address [default: local]')
336 parser.add_argument('--xrep', type=int, metavar='PORT', default=0,
364 parser.add_argument('--xrep', type=int, metavar='PORT', default=0,
337 help='set the XREP channel port [default: random]')
365 help='set the XREP channel port [default: random]')
338 parser.add_argument('--pub', type=int, metavar='PORT', default=0,
366 parser.add_argument('--pub', type=int, metavar='PORT', default=0,
339 help='set the PUB channel port [default: random]')
367 help='set the PUB channel port [default: random]')
340 parser.add_argument('--req', type=int, metavar='PORT', default=0,
368 parser.add_argument('--req', type=int, metavar='PORT', default=0,
341 help='set the REQ channel port [default: random]')
369 help='set the REQ channel port [default: random]')
342 parser.add_argument('--require-parent', action='store_true',
370 parser.add_argument('--require-parent', action='store_true',
343 help='ensure that this process dies with its parent')
371 help='ensure that this process dies with its parent')
344 namespace = parser.parse_args()
372 namespace = parser.parse_args()
345
373
346 # Create a context, a session, and the kernel sockets.
374 # Create a context, a session, and the kernel sockets.
347 print >>sys.__stdout__, "Starting the kernel..."
375 print >>sys.__stdout__, "Starting the kernel..."
348 context = zmq.Context()
376 context = zmq.Context()
349 session = Session(username=u'kernel')
377 session = Session(username=u'kernel')
350
378
351 reply_socket = context.socket(zmq.XREP)
379 reply_socket = context.socket(zmq.XREP)
352 xrep_port = bind_port(reply_socket, namespace.ip, namespace.xrep)
380 xrep_port = bind_port(reply_socket, namespace.ip, namespace.xrep)
353 print >>sys.__stdout__, "XREP Channel on port", xrep_port
381 print >>sys.__stdout__, "XREP Channel on port", xrep_port
354
382
355 pub_socket = context.socket(zmq.PUB)
383 pub_socket = context.socket(zmq.PUB)
356 pub_port = bind_port(pub_socket, namespace.ip, namespace.pub)
384 pub_port = bind_port(pub_socket, namespace.ip, namespace.pub)
357 print >>sys.__stdout__, "PUB Channel on port", pub_port
385 print >>sys.__stdout__, "PUB Channel on port", pub_port
358
386
359 req_socket = context.socket(zmq.XREQ)
387 req_socket = context.socket(zmq.XREQ)
360 req_port = bind_port(req_socket, namespace.ip, namespace.req)
388 req_port = bind_port(req_socket, namespace.ip, namespace.req)
361 print >>sys.__stdout__, "REQ Channel on port", req_port
389 print >>sys.__stdout__, "REQ Channel on port", req_port
362
390
363 # Redirect input streams and set a display hook.
391 # Redirect input streams and set a display hook.
364 sys.stdin = InStream(session, req_socket)
392 sys.stdin = InStream(session, req_socket)
365 sys.stdout = OutStream(session, pub_socket, u'stdout')
393 sys.stdout = OutStream(session, pub_socket, u'stdout')
366 sys.stderr = OutStream(session, pub_socket, u'stderr')
394 sys.stderr = OutStream(session, pub_socket, u'stderr')
367 sys.displayhook = DisplayHook(session, pub_socket)
395 sys.displayhook = DisplayHook(session, pub_socket)
368
396
369 # Create the kernel.
397 # Create the kernel.
370 kernel = Kernel(session, reply_socket, pub_socket)
398 kernel = Kernel(session, reply_socket, pub_socket)
371
399
372 # Configure this kernel/process to die on parent termination, if necessary.
400 # Configure this kernel/process to die on parent termination, if necessary.
373 if namespace.require_parent:
401 if namespace.require_parent:
374 if sys.platform == 'linux2':
402 if sys.platform == 'linux2':
375 import ctypes, ctypes.util, signal
403 import ctypes, ctypes.util, signal
376 PR_SET_PDEATHSIG = 1
404 PR_SET_PDEATHSIG = 1
377 libc = ctypes.CDLL(ctypes.util.find_library('c'))
405 libc = ctypes.CDLL(ctypes.util.find_library('c'))
378 libc.prctl(PR_SET_PDEATHSIG, signal.SIGKILL)
406 libc.prctl(PR_SET_PDEATHSIG, signal.SIGKILL)
379
407
380 elif sys.platform != 'win32':
408 elif sys.platform != 'win32':
381 kernel.poll_ppid = True
409 kernel.poll_ppid = True
382
410
383 # Start the kernel mainloop.
411 # Start the kernel mainloop.
384 kernel.start()
412 kernel.start()
385
413
386
414
387 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False):
415 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False):
388 """ Launches a localhost kernel, binding to the specified ports.
416 """ Launches a localhost kernel, binding to the specified ports.
389
417
390 Parameters
418 Parameters
391 ----------
419 ----------
392 xrep_port : int, optional
420 xrep_port : int, optional
393 The port to use for XREP channel.
421 The port to use for XREP channel.
394
422
395 pub_port : int, optional
423 pub_port : int, optional
396 The port to use for the SUB channel.
424 The port to use for the SUB channel.
397
425
398 req_port : int, optional
426 req_port : int, optional
399 The port to use for the REQ (raw input) channel.
427 The port to use for the REQ (raw input) channel.
400
428
401 independent : bool, optional (default False)
429 independent : bool, optional (default False)
402 If set, the kernel process is guaranteed to survive if this process
430 If set, the kernel process is guaranteed to survive if this process
403 dies. If not set, an effort is made to ensure that the kernel is killed
431 dies. If not set, an effort is made to ensure that the kernel is killed
404 when this process dies. Note that in this case it is still good practice
432 when this process dies. Note that in this case it is still good practice
405 to attempt to kill kernels manually before exiting.
433 to attempt to kill kernels manually before exiting.
406
434
407 Returns
435 Returns
408 -------
436 -------
409 A tuple of form:
437 A tuple of form:
410 (kernel_process, xrep_port, pub_port, req_port)
438 (kernel_process, xrep_port, pub_port, req_port)
411 where kernel_process is a Popen object and the ports are integers.
439 where kernel_process is a Popen object and the ports are integers.
412 """
440 """
413 import socket
441 import socket
414 from subprocess import Popen
442 from subprocess import Popen
415
443
416 # Find open ports as necessary.
444 # Find open ports as necessary.
417 ports = []
445 ports = []
418 ports_needed = int(xrep_port <= 0) + int(pub_port <= 0) + int(req_port <= 0)
446 ports_needed = int(xrep_port <= 0) + int(pub_port <= 0) + int(req_port <= 0)
419 for i in xrange(ports_needed):
447 for i in xrange(ports_needed):
420 sock = socket.socket()
448 sock = socket.socket()
421 sock.bind(('', 0))
449 sock.bind(('', 0))
422 ports.append(sock)
450 ports.append(sock)
423 for i, sock in enumerate(ports):
451 for i, sock in enumerate(ports):
424 port = sock.getsockname()[1]
452 port = sock.getsockname()[1]
425 sock.close()
453 sock.close()
426 ports[i] = port
454 ports[i] = port
427 if xrep_port <= 0:
455 if xrep_port <= 0:
428 xrep_port = ports.pop(0)
456 xrep_port = ports.pop(0)
429 if pub_port <= 0:
457 if pub_port <= 0:
430 pub_port = ports.pop(0)
458 pub_port = ports.pop(0)
431 if req_port <= 0:
459 if req_port <= 0:
432 req_port = ports.pop(0)
460 req_port = ports.pop(0)
433
461
434 # Spawn a kernel.
462 # Spawn a kernel.
435 command = 'from IPython.zmq.kernel import main; main()'
463 command = 'from IPython.zmq.kernel import main; main()'
436 arguments = [ sys.executable, '-c', command, '--xrep', str(xrep_port),
464 arguments = [ sys.executable, '-c', command, '--xrep', str(xrep_port),
437 '--pub', str(pub_port), '--req', str(req_port) ]
465 '--pub', str(pub_port), '--req', str(req_port) ]
438
466
439 if independent:
467 if independent:
440 if sys.platform == 'win32':
468 if sys.platform == 'win32':
441 proc = Popen(['start', '/b'] + arguments, shell=True)
469 proc = Popen(['start', '/b'] + arguments, shell=True)
442 else:
470 else:
443 proc = Popen(arguments, preexec_fn=lambda: os.setsid())
471 proc = Popen(arguments, preexec_fn=lambda: os.setsid())
444
472
445 else:
473 else:
446 proc = Popen(arguments + ['--require-parent'])
474 proc = Popen(arguments + ['--require-parent'])
447
475
448 return proc, xrep_port, pub_port, req_port
476 return proc, xrep_port, pub_port, req_port
449
477
450
478
451 if __name__ == '__main__':
479 if __name__ == '__main__':
452 main()
480 main()
General Comments 0
You need to be logged in to leave comments. Login now