##// END OF EJS Templates
Reading a line is now correctly implemented in ConsoleWidget.
epatters -
Show More
@@ -1,846 +1,883 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
145
145 # Set a monospaced font.
146 # Set a monospaced font.
146 self.reset_font()
147 self.reset_font()
147
148
148 # Define a custom context menu.
149 # Define a custom context menu.
149 self._context_menu = QtGui.QMenu(self)
150 self._context_menu = QtGui.QMenu(self)
150
151
151 copy_action = QtGui.QAction('Copy', self)
152 copy_action = QtGui.QAction('Copy', self)
152 copy_action.triggered.connect(self.copy)
153 copy_action.triggered.connect(self.copy)
153 self.copyAvailable.connect(copy_action.setEnabled)
154 self.copyAvailable.connect(copy_action.setEnabled)
154 self._context_menu.addAction(copy_action)
155 self._context_menu.addAction(copy_action)
155
156
156 self._paste_action = QtGui.QAction('Paste', self)
157 self._paste_action = QtGui.QAction('Paste', self)
157 self._paste_action.triggered.connect(self.paste)
158 self._paste_action.triggered.connect(self.paste)
158 self._context_menu.addAction(self._paste_action)
159 self._context_menu.addAction(self._paste_action)
159 self._context_menu.addSeparator()
160 self._context_menu.addSeparator()
160
161
161 select_all_action = QtGui.QAction('Select All', self)
162 select_all_action = QtGui.QAction('Select All', self)
162 select_all_action.triggered.connect(self.selectAll)
163 select_all_action.triggered.connect(self.selectAll)
163 self._context_menu.addAction(select_all_action)
164 self._context_menu.addAction(select_all_action)
164
165
165 def event(self, event):
166 def event(self, event):
166 """ Reimplemented to override shortcuts, if necessary.
167 """ Reimplemented to override shortcuts, if necessary.
167 """
168 """
168 # 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
169 # 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
170 # Command key.
171 # Command key.
171 if self.override_shortcuts and \
172 if self.override_shortcuts and \
172 sys.platform != 'darwin' and \
173 sys.platform != 'darwin' and \
173 event.type() == QtCore.QEvent.ShortcutOverride and \
174 event.type() == QtCore.QEvent.ShortcutOverride and \
174 self._control_down(event.modifiers()) and \
175 self._control_down(event.modifiers()) and \
175 event.key() in self._shortcuts:
176 event.key() in self._shortcuts:
176 event.accept()
177 event.accept()
177 return True
178 return True
178 else:
179 else:
179 return QtGui.QPlainTextEdit.event(self, event)
180 return QtGui.QPlainTextEdit.event(self, event)
180
181
181 #---------------------------------------------------------------------------
182 #---------------------------------------------------------------------------
182 # 'QWidget' interface
183 # 'QWidget' interface
183 #---------------------------------------------------------------------------
184 #---------------------------------------------------------------------------
184
185
185 def contextMenuEvent(self, event):
186 def contextMenuEvent(self, event):
186 """ Reimplemented to create a menu without destructive actions like
187 """ Reimplemented to create a menu without destructive actions like
187 'Cut' and 'Delete'.
188 'Cut' and 'Delete'.
188 """
189 """
189 clipboard_empty = QtGui.QApplication.clipboard().text().isEmpty()
190 clipboard_empty = QtGui.QApplication.clipboard().text().isEmpty()
190 self._paste_action.setEnabled(not clipboard_empty)
191 self._paste_action.setEnabled(not clipboard_empty)
191
192
192 self._context_menu.exec_(event.globalPos())
193 self._context_menu.exec_(event.globalPos())
193
194
195 def dragMoveEvent(self, event):
196 """ Reimplemented to disable dropping text.
197 """
198 event.ignore()
199
194 def keyPressEvent(self, event):
200 def keyPressEvent(self, event):
195 """ Reimplemented to create a console-like interface.
201 """ Reimplemented to create a console-like interface.
196 """
202 """
197 intercepted = False
203 intercepted = False
198 cursor = self.textCursor()
204 cursor = self.textCursor()
199 position = cursor.position()
205 position = cursor.position()
200 key = event.key()
206 key = event.key()
201 ctrl_down = self._control_down(event.modifiers())
207 ctrl_down = self._control_down(event.modifiers())
202 alt_down = event.modifiers() & QtCore.Qt.AltModifier
208 alt_down = event.modifiers() & QtCore.Qt.AltModifier
203 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
209 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
204
210
205 # 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
206 # called by Qt. So we intercept the key press here.
212 # called by Qt. So we intercept the key press here.
207 if event.matches(QtGui.QKeySequence.Paste):
213 if event.matches(QtGui.QKeySequence.Paste):
208 self.paste()
214 self.paste()
209 intercepted = True
215 intercepted = True
210
216
211 elif ctrl_down:
217 elif ctrl_down:
212 if key in self._ctrl_down_remap:
218 if key in self._ctrl_down_remap:
213 ctrl_down = False
219 ctrl_down = False
214 key = self._ctrl_down_remap[key]
220 key = self._ctrl_down_remap[key]
215 event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, key,
221 event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, key,
216 QtCore.Qt.NoModifier)
222 QtCore.Qt.NoModifier)
217
223
218 elif key == QtCore.Qt.Key_K:
224 elif key == QtCore.Qt.Key_K:
219 if self._in_buffer(position):
225 if self._in_buffer(position):
220 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
226 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
221 QtGui.QTextCursor.KeepAnchor)
227 QtGui.QTextCursor.KeepAnchor)
222 cursor.removeSelectedText()
228 cursor.removeSelectedText()
223 intercepted = True
229 intercepted = True
224
230
225 elif key == QtCore.Qt.Key_X:
231 elif key == QtCore.Qt.Key_X:
226 intercepted = True
232 intercepted = True
227
233
228 elif key == QtCore.Qt.Key_Y:
234 elif key == QtCore.Qt.Key_Y:
229 self.paste()
235 self.paste()
230 intercepted = True
236 intercepted = True
231
237
232 elif alt_down:
238 elif alt_down:
233 if key == QtCore.Qt.Key_B:
239 if key == QtCore.Qt.Key_B:
234 self.setTextCursor(self._get_word_start_cursor(position))
240 self.setTextCursor(self._get_word_start_cursor(position))
235 intercepted = True
241 intercepted = True
236
242
237 elif key == QtCore.Qt.Key_F:
243 elif key == QtCore.Qt.Key_F:
238 self.setTextCursor(self._get_word_end_cursor(position))
244 self.setTextCursor(self._get_word_end_cursor(position))
239 intercepted = True
245 intercepted = True
240
246
241 elif key == QtCore.Qt.Key_Backspace:
247 elif key == QtCore.Qt.Key_Backspace:
242 cursor = self._get_word_start_cursor(position)
248 cursor = self._get_word_start_cursor(position)
243 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
249 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
244 cursor.removeSelectedText()
250 cursor.removeSelectedText()
245 intercepted = True
251 intercepted = True
246
252
247 elif key == QtCore.Qt.Key_D:
253 elif key == QtCore.Qt.Key_D:
248 cursor = self._get_word_end_cursor(position)
254 cursor = self._get_word_end_cursor(position)
249 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
255 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
250 cursor.removeSelectedText()
256 cursor.removeSelectedText()
251 intercepted = True
257 intercepted = True
252
258
253 if self._completion_widget.isVisible():
259 if self._completion_widget.isVisible():
254 self._completion_widget.keyPressEvent(event)
260 self._completion_widget.keyPressEvent(event)
255 intercepted = event.isAccepted()
261 intercepted = event.isAccepted()
256
262
257 else:
263 else:
258 if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
264 if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
259 intercepted = True
260 if self._reading:
265 if self._reading:
261 intercepted = False
266 self.appendPlainText('\n')
262 self._reading = False
267 self._reading = False
268 if self._reading_callback:
269 self._reading_callback()
270 self._reading_callback = None
263 elif not self._executing:
271 elif not self._executing:
264 self.execute(interactive=True)
272 self.execute(interactive=True)
273 intercepted = True
265
274
266 elif key == QtCore.Qt.Key_Up:
275 elif key == QtCore.Qt.Key_Up:
267 if self._reading or not self._up_pressed():
276 if self._reading or not self._up_pressed():
268 intercepted = True
277 intercepted = True
269 else:
278 else:
270 prompt_line = self._get_prompt_cursor().blockNumber()
279 prompt_line = self._get_prompt_cursor().blockNumber()
271 intercepted = cursor.blockNumber() <= prompt_line
280 intercepted = cursor.blockNumber() <= prompt_line
272
281
273 elif key == QtCore.Qt.Key_Down:
282 elif key == QtCore.Qt.Key_Down:
274 if self._reading or not self._down_pressed():
283 if self._reading or not self._down_pressed():
275 intercepted = True
284 intercepted = True
276 else:
285 else:
277 end_line = self._get_end_cursor().blockNumber()
286 end_line = self._get_end_cursor().blockNumber()
278 intercepted = cursor.blockNumber() == end_line
287 intercepted = cursor.blockNumber() == end_line
279
288
280 elif key == QtCore.Qt.Key_Tab:
289 elif key == QtCore.Qt.Key_Tab:
281 if self._reading:
290 if self._reading:
282 intercepted = False
291 intercepted = False
283 else:
292 else:
284 intercepted = not self._tab_pressed()
293 intercepted = not self._tab_pressed()
285
294
286 elif key == QtCore.Qt.Key_Left:
295 elif key == QtCore.Qt.Key_Left:
287 intercepted = not self._in_buffer(position - 1)
296 intercepted = not self._in_buffer(position - 1)
288
297
289 elif key == QtCore.Qt.Key_Home:
298 elif key == QtCore.Qt.Key_Home:
290 cursor.movePosition(QtGui.QTextCursor.StartOfLine)
299 cursor.movePosition(QtGui.QTextCursor.StartOfLine)
291 start_pos = cursor.position()
300 start_pos = cursor.position()
292 start_line = cursor.blockNumber()
301 start_line = cursor.blockNumber()
293 if start_line == self._get_prompt_cursor().blockNumber():
302 if start_line == self._get_prompt_cursor().blockNumber():
294 start_pos += len(self._prompt)
303 start_pos += len(self._prompt)
295 else:
304 else:
296 start_pos += len(self._continuation_prompt)
305 start_pos += len(self._continuation_prompt)
297 if shift_down and self._in_buffer(position):
306 if shift_down and self._in_buffer(position):
298 self._set_selection(position, start_pos)
307 self._set_selection(position, start_pos)
299 else:
308 else:
300 self._set_position(start_pos)
309 self._set_position(start_pos)
301 intercepted = True
310 intercepted = True
302
311
303 elif key == QtCore.Qt.Key_Backspace and not alt_down:
312 elif key == QtCore.Qt.Key_Backspace and not alt_down:
304
313
305 # Line deletion (remove continuation prompt)
314 # Line deletion (remove continuation prompt)
306 len_prompt = len(self._continuation_prompt)
315 len_prompt = len(self._continuation_prompt)
307 if cursor.columnNumber() == len_prompt and \
316 if not self._reading and \
317 cursor.columnNumber() == len_prompt and \
308 position != self._prompt_pos:
318 position != self._prompt_pos:
309 cursor.setPosition(position - len_prompt,
319 cursor.setPosition(position - len_prompt,
310 QtGui.QTextCursor.KeepAnchor)
320 QtGui.QTextCursor.KeepAnchor)
311 cursor.removeSelectedText()
321 cursor.removeSelectedText()
312
322
313 # Regular backwards deletion
323 # Regular backwards deletion
314 else:
324 else:
315 anchor = cursor.anchor()
325 anchor = cursor.anchor()
316 if anchor == position:
326 if anchor == position:
317 intercepted = not self._in_buffer(position - 1)
327 intercepted = not self._in_buffer(position - 1)
318 else:
328 else:
319 intercepted = not self._in_buffer(min(anchor, position))
329 intercepted = not self._in_buffer(min(anchor, position))
320
330
321 elif key == QtCore.Qt.Key_Delete:
331 elif key == QtCore.Qt.Key_Delete:
322 anchor = cursor.anchor()
332 anchor = cursor.anchor()
323 intercepted = not self._in_buffer(min(anchor, position))
333 intercepted = not self._in_buffer(min(anchor, position))
324
334
325 # 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
326 # the keyboard in any part of the buffer.
336 # the keyboard in any part of the buffer.
327 if not ctrl_down:
337 if not ctrl_down:
328 self._keep_cursor_in_buffer()
338 self._keep_cursor_in_buffer()
329
339
330 if not intercepted:
340 if not intercepted:
331 QtGui.QPlainTextEdit.keyPressEvent(self, event)
341 QtGui.QPlainTextEdit.keyPressEvent(self, event)
332
342
333 #--------------------------------------------------------------------------
343 #--------------------------------------------------------------------------
334 # 'QPlainTextEdit' interface
344 # 'QPlainTextEdit' interface
335 #--------------------------------------------------------------------------
345 #--------------------------------------------------------------------------
336
346
337 def appendPlainText(self, text):
347 def appendPlainText(self, text):
338 """ 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
339 make sense for a console widget. Also, if enabled, handle ANSI
349 make sense for a console widget. Also, if enabled, handle ANSI
340 codes.
350 codes.
341 """
351 """
342 cursor = self.textCursor()
352 cursor = self.textCursor()
343 cursor.movePosition(QtGui.QTextCursor.End)
353 cursor.movePosition(QtGui.QTextCursor.End)
344
354
345 if self.ansi_codes:
355 if self.ansi_codes:
346 format = QtGui.QTextCharFormat()
356 format = QtGui.QTextCharFormat()
347 previous_end = 0
357 previous_end = 0
348 for match in self._ansi_pattern.finditer(text):
358 for match in self._ansi_pattern.finditer(text):
349 cursor.insertText(text[previous_end:match.start()], format)
359 cursor.insertText(text[previous_end:match.start()], format)
350 previous_end = match.end()
360 previous_end = match.end()
351 for code in match.group(1).split(';'):
361 for code in match.group(1).split(';'):
352 self._ansi_processor.set_code(int(code))
362 self._ansi_processor.set_code(int(code))
353 format = self._ansi_processor.get_format()
363 format = self._ansi_processor.get_format()
354 cursor.insertText(text[previous_end:], format)
364 cursor.insertText(text[previous_end:], format)
355 else:
365 else:
356 cursor.insertText(text)
366 cursor.insertText(text)
357
367
358 def clear(self, keep_input=False):
368 def clear(self, keep_input=False):
359 """ Reimplemented to write a new prompt. If 'keep_input' is set,
369 """ Reimplemented to write a new prompt. If 'keep_input' is set,
360 restores the old input buffer when the new prompt is written.
370 restores the old input buffer when the new prompt is written.
361 """
371 """
362 super(ConsoleWidget, self).clear()
372 super(ConsoleWidget, self).clear()
363
373
364 if keep_input:
374 if keep_input:
365 input_buffer = self.input_buffer
375 input_buffer = self.input_buffer
366 self._show_prompt()
376 self._show_prompt()
367 if keep_input:
377 if keep_input:
368 self.input_buffer = input_buffer
378 self.input_buffer = input_buffer
369
379
370 def paste(self):
380 def paste(self):
371 """ Reimplemented to ensure that text is pasted in the editing region.
381 """ Reimplemented to ensure that text is pasted in the editing region.
372 """
382 """
373 self._keep_cursor_in_buffer()
383 self._keep_cursor_in_buffer()
374 QtGui.QPlainTextEdit.paste(self)
384 QtGui.QPlainTextEdit.paste(self)
375
385
376 def print_(self, printer):
386 def print_(self, printer):
377 """ 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_'
378 slot has the wrong signature.
388 slot has the wrong signature.
379 """
389 """
380 QtGui.QPlainTextEdit.print_(self, printer)
390 QtGui.QPlainTextEdit.print_(self, printer)
381
391
382 #---------------------------------------------------------------------------
392 #---------------------------------------------------------------------------
383 # 'ConsoleWidget' public interface
393 # 'ConsoleWidget' public interface
384 #---------------------------------------------------------------------------
394 #---------------------------------------------------------------------------
385
395
386 def execute(self, source=None, hidden=False, interactive=False):
396 def execute(self, source=None, hidden=False, interactive=False):
387 """ Executes source or the input buffer, possibly prompting for more
397 """ Executes source or the input buffer, possibly prompting for more
388 input.
398 input.
389
399
390 Parameters:
400 Parameters:
391 -----------
401 -----------
392 source : str, optional
402 source : str, optional
393
403
394 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
395 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
396 replaced with the source before execution.
406 replaced with the source before execution.
397
407
398 hidden : bool, optional (default False)
408 hidden : bool, optional (default False)
399
409
400 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.
401 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
402 an execution has occurred.
412 an execution has occurred.
403
413
404 interactive : bool, optional (default False)
414 interactive : bool, optional (default False)
405
415
406 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
407 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
408 subclass implementation.
418 subclass implementation.
409
419
410 Raises:
420 Raises:
411 -------
421 -------
412 RuntimeError
422 RuntimeError
413 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,
414 it not possible to prompt for more input.
424 it not possible to prompt for more input.
415
425
416 Returns:
426 Returns:
417 --------
427 --------
418 A boolean indicating whether the source was executed.
428 A boolean indicating whether the source was executed.
419 """
429 """
420 if not hidden:
430 if not hidden:
421 if source is not None:
431 if source is not None:
422 self.input_buffer = source
432 self.input_buffer = source
423
433
424 self.appendPlainText('\n')
434 self.appendPlainText('\n')
425 self._executing_input_buffer = self.input_buffer
435 self._executing_input_buffer = self.input_buffer
426 self._executing = True
436 self._executing = True
427 self._prompt_finished()
437 self._prompt_finished()
428
438
429 real_source = self.input_buffer if source is None else source
439 real_source = self.input_buffer if source is None else source
430 complete = self._is_complete(real_source, interactive)
440 complete = self._is_complete(real_source, interactive)
431 if complete:
441 if complete:
432 if not hidden:
442 if not hidden:
433 # The maximum block count is only in effect during execution.
443 # The maximum block count is only in effect during execution.
434 # This ensures that _prompt_pos does not become invalid due to
444 # This ensures that _prompt_pos does not become invalid due to
435 # text truncation.
445 # text truncation.
436 self.setMaximumBlockCount(self.buffer_size)
446 self.setMaximumBlockCount(self.buffer_size)
437 self._execute(real_source, hidden)
447 self._execute(real_source, hidden)
438 elif hidden:
448 elif hidden:
439 raise RuntimeError('Incomplete noninteractive input: "%s"' % source)
449 raise RuntimeError('Incomplete noninteractive input: "%s"' % source)
440 else:
450 else:
441 self._show_continuation_prompt()
451 self._show_continuation_prompt()
442
452
443 return complete
453 return complete
444
454
445 def _get_input_buffer(self):
455 def _get_input_buffer(self):
446 """ 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.
447 """
457 """
448 # 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
449 # the limit imposed by 'buffer_size'. Therefore, we store it.
459 # the limit imposed by 'buffer_size'. Therefore, we store it.
450 if self._executing:
460 if self._executing:
451 return self._executing_input_buffer
461 return self._executing_input_buffer
452
462
453 cursor = self._get_end_cursor()
463 cursor = self._get_end_cursor()
454 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
464 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
455
465
456 # Use QTextDocumentFragment intermediate object because it strips
466 # Use QTextDocumentFragment intermediate object because it strips
457 # out the Unicode line break characters that Qt insists on inserting.
467 # out the Unicode line break characters that Qt insists on inserting.
458 input_buffer = str(cursor.selection().toPlainText())
468 input_buffer = str(cursor.selection().toPlainText())
459
469
460 # Strip out continuation prompts.
470 # Strip out continuation prompts.
461 return input_buffer.replace('\n' + self._continuation_prompt, '\n')
471 return input_buffer.replace('\n' + self._continuation_prompt, '\n')
462
472
463 def _set_input_buffer(self, string):
473 def _set_input_buffer(self, string):
464 """ Replaces the text in the input buffer with 'string'.
474 """ Replaces the text in the input buffer with 'string'.
465 """
475 """
466 # Add continuation prompts where necessary.
476 # Add continuation prompts where necessary.
467 lines = string.splitlines()
477 lines = string.splitlines()
468 for i in xrange(1, len(lines)):
478 for i in xrange(1, len(lines)):
469 lines[i] = self._continuation_prompt + lines[i]
479 lines[i] = self._continuation_prompt + lines[i]
470 string = '\n'.join(lines)
480 string = '\n'.join(lines)
471
481
472 # Replace buffer with new text.
482 # Replace buffer with new text.
473 cursor = self._get_end_cursor()
483 cursor = self._get_end_cursor()
474 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
484 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
475 cursor.insertText(string)
485 cursor.insertText(string)
476 self.moveCursor(QtGui.QTextCursor.End)
486 self.moveCursor(QtGui.QTextCursor.End)
477
487
478 input_buffer = property(_get_input_buffer, _set_input_buffer)
488 input_buffer = property(_get_input_buffer, _set_input_buffer)
479
489
480 def _get_input_buffer_cursor_line(self):
490 def _get_input_buffer_cursor_line(self):
481 """ 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
482 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.
483 """
493 """
484 if self._executing:
494 if self._executing:
485 return None
495 return None
486 cursor = self.textCursor()
496 cursor = self.textCursor()
487 if cursor.position() >= self._prompt_pos:
497 if cursor.position() >= self._prompt_pos:
488 text = str(cursor.block().text())
498 text = str(cursor.block().text())
489 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
499 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
490 return text[len(self._prompt):]
500 return text[len(self._prompt):]
491 else:
501 else:
492 return text[len(self._continuation_prompt):]
502 return text[len(self._continuation_prompt):]
493 else:
503 else:
494 return None
504 return None
495
505
496 input_buffer_cursor_line = property(_get_input_buffer_cursor_line)
506 input_buffer_cursor_line = property(_get_input_buffer_cursor_line)
497
507
498 def _get_font(self):
508 def _get_font(self):
499 """ The base font being used by the ConsoleWidget.
509 """ The base font being used by the ConsoleWidget.
500 """
510 """
501 return self.document().defaultFont()
511 return self.document().defaultFont()
502
512
503 def _set_font(self, font):
513 def _set_font(self, font):
504 """ Sets the base font for the ConsoleWidget to the specified QFont.
514 """ Sets the base font for the ConsoleWidget to the specified QFont.
505 """
515 """
506 self._completion_widget.setFont(font)
516 self._completion_widget.setFont(font)
507 self.document().setDefaultFont(font)
517 self.document().setDefaultFont(font)
508
518
509 font = property(_get_font, _set_font)
519 font = property(_get_font, _set_font)
510
520
511 def reset_font(self):
521 def reset_font(self):
512 """ 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.
513 """
523 """
514 if sys.platform == 'win32':
524 if sys.platform == 'win32':
515 name = 'Courier'
525 name = 'Courier'
516 elif sys.platform == 'darwin':
526 elif sys.platform == 'darwin':
517 name = 'Monaco'
527 name = 'Monaco'
518 else:
528 else:
519 name = 'Monospace'
529 name = 'Monospace'
520 font = QtGui.QFont(name, QtGui.qApp.font().pointSize())
530 font = QtGui.QFont(name, QtGui.qApp.font().pointSize())
521 font.setStyleHint(QtGui.QFont.TypeWriter)
531 font.setStyleHint(QtGui.QFont.TypeWriter)
522 self._set_font(font)
532 self._set_font(font)
523
533
524 #---------------------------------------------------------------------------
534 #---------------------------------------------------------------------------
525 # 'ConsoleWidget' abstract interface
535 # 'ConsoleWidget' abstract interface
526 #---------------------------------------------------------------------------
536 #---------------------------------------------------------------------------
527
537
528 def _is_complete(self, source, interactive):
538 def _is_complete(self, source, interactive):
529 """ Returns whether 'source' can be executed. When triggered by an
539 """ Returns whether 'source' can be executed. When triggered by an
530 Enter/Return key press, 'interactive' is True; otherwise, it is
540 Enter/Return key press, 'interactive' is True; otherwise, it is
531 False.
541 False.
532 """
542 """
533 raise NotImplementedError
543 raise NotImplementedError
534
544
535 def _execute(self, source, hidden):
545 def _execute(self, source, hidden):
536 """ Execute 'source'. If 'hidden', do not show any output.
546 """ Execute 'source'. If 'hidden', do not show any output.
537 """
547 """
538 raise NotImplementedError
548 raise NotImplementedError
539
549
540 def _prompt_started_hook(self):
550 def _prompt_started_hook(self):
541 """ Called immediately after a new prompt is displayed.
551 """ Called immediately after a new prompt is displayed.
542 """
552 """
543 pass
553 pass
544
554
545 def _prompt_finished_hook(self):
555 def _prompt_finished_hook(self):
546 """ 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
547 will be processed and a new prompt displayed.
557 will be processed and a new prompt displayed.
548 """
558 """
549 pass
559 pass
550
560
551 def _up_pressed(self):
561 def _up_pressed(self):
552 """ Called when the up key is pressed. Returns whether to continue
562 """ Called when the up key is pressed. Returns whether to continue
553 processing the event.
563 processing the event.
554 """
564 """
555 return True
565 return True
556
566
557 def _down_pressed(self):
567 def _down_pressed(self):
558 """ Called when the down key is pressed. Returns whether to continue
568 """ Called when the down key is pressed. Returns whether to continue
559 processing the event.
569 processing the event.
560 """
570 """
561 return True
571 return True
562
572
563 def _tab_pressed(self):
573 def _tab_pressed(self):
564 """ Called when the tab key is pressed. Returns whether to continue
574 """ Called when the tab key is pressed. Returns whether to continue
565 processing the event.
575 processing the event.
566 """
576 """
567 return False
577 return False
568
578
569 #--------------------------------------------------------------------------
579 #--------------------------------------------------------------------------
570 # 'ConsoleWidget' protected interface
580 # 'ConsoleWidget' protected interface
571 #--------------------------------------------------------------------------
581 #--------------------------------------------------------------------------
572
582
573 def _control_down(self, modifiers):
583 def _control_down(self, modifiers):
574 """ Given a KeyboardModifiers flags object, return whether the Control
584 """ Given a KeyboardModifiers flags object, return whether the Control
575 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
576 Control).
586 Control).
577 """
587 """
578 down = bool(modifiers & QtCore.Qt.ControlModifier)
588 down = bool(modifiers & QtCore.Qt.ControlModifier)
579
589
580 # Note: on Mac OS, ControlModifier corresponds to the Command key while
590 # Note: on Mac OS, ControlModifier corresponds to the Command key while
581 # MetaModifier corresponds to the Control key.
591 # MetaModifier corresponds to the Control key.
582 if sys.platform == 'darwin':
592 if sys.platform == 'darwin':
583 down = down ^ bool(modifiers & QtCore.Qt.MetaModifier)
593 down = down ^ bool(modifiers & QtCore.Qt.MetaModifier)
584
594
585 return down
595 return down
586
596
587 def _complete_with_items(self, cursor, items):
597 def _complete_with_items(self, cursor, items):
588 """ Performs completion with 'items' at the specified cursor location.
598 """ Performs completion with 'items' at the specified cursor location.
589 """
599 """
590 if len(items) == 1:
600 if len(items) == 1:
591 cursor.setPosition(self.textCursor().position(),
601 cursor.setPosition(self.textCursor().position(),
592 QtGui.QTextCursor.KeepAnchor)
602 QtGui.QTextCursor.KeepAnchor)
593 cursor.insertText(items[0])
603 cursor.insertText(items[0])
594 elif len(items) > 1:
604 elif len(items) > 1:
595 if self.gui_completion:
605 if self.gui_completion:
596 self._completion_widget.show_items(cursor, items)
606 self._completion_widget.show_items(cursor, items)
597 else:
607 else:
598 text = '\n'.join(items) + '\n'
608 text = '\n'.join(items) + '\n'
599 self._write_text_keeping_prompt(text)
609 self._write_text_keeping_prompt(text)
600
610
601 def _get_end_cursor(self):
611 def _get_end_cursor(self):
602 """ Convenience method that returns a cursor for the last character.
612 """ Convenience method that returns a cursor for the last character.
603 """
613 """
604 cursor = self.textCursor()
614 cursor = self.textCursor()
605 cursor.movePosition(QtGui.QTextCursor.End)
615 cursor.movePosition(QtGui.QTextCursor.End)
606 return cursor
616 return cursor
607
617
608 def _get_prompt_cursor(self):
618 def _get_prompt_cursor(self):
609 """ Convenience method that returns a cursor for the prompt position.
619 """ Convenience method that returns a cursor for the prompt position.
610 """
620 """
611 cursor = self.textCursor()
621 cursor = self.textCursor()
612 cursor.setPosition(self._prompt_pos)
622 cursor.setPosition(self._prompt_pos)
613 return cursor
623 return cursor
614
624
615 def _get_selection_cursor(self, start, end):
625 def _get_selection_cursor(self, start, end):
616 """ Convenience method that returns a cursor with text selected between
626 """ Convenience method that returns a cursor with text selected between
617 the positions 'start' and 'end'.
627 the positions 'start' and 'end'.
618 """
628 """
619 cursor = self.textCursor()
629 cursor = self.textCursor()
620 cursor.setPosition(start)
630 cursor.setPosition(start)
621 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
631 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
622 return cursor
632 return cursor
623
633
624 def _get_word_start_cursor(self, position):
634 def _get_word_start_cursor(self, position):
625 """ 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
626 sequence of non-word characters precedes the first word, skip over
636 sequence of non-word characters precedes the first word, skip over
627 them. (This emulates the behavior of bash, emacs, etc.)
637 them. (This emulates the behavior of bash, emacs, etc.)
628 """
638 """
629 document = self.document()
639 document = self.document()
630 position -= 1
640 position -= 1
631 while self._in_buffer(position) and \
641 while self._in_buffer(position) and \
632 not document.characterAt(position).isLetterOrNumber():
642 not document.characterAt(position).isLetterOrNumber():
633 position -= 1
643 position -= 1
634 while self._in_buffer(position) and \
644 while self._in_buffer(position) and \
635 document.characterAt(position).isLetterOrNumber():
645 document.characterAt(position).isLetterOrNumber():
636 position -= 1
646 position -= 1
637 cursor = self.textCursor()
647 cursor = self.textCursor()
638 cursor.setPosition(position + 1)
648 cursor.setPosition(position + 1)
639 return cursor
649 return cursor
640
650
641 def _get_word_end_cursor(self, position):
651 def _get_word_end_cursor(self, position):
642 """ 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
643 sequence of non-word characters precedes the first word, skip over
653 sequence of non-word characters precedes the first word, skip over
644 them. (This emulates the behavior of bash, emacs, etc.)
654 them. (This emulates the behavior of bash, emacs, etc.)
645 """
655 """
646 document = self.document()
656 document = self.document()
647 end = self._get_end_cursor().position()
657 end = self._get_end_cursor().position()
648 while position < end and \
658 while position < end and \
649 not document.characterAt(position).isLetterOrNumber():
659 not document.characterAt(position).isLetterOrNumber():
650 position += 1
660 position += 1
651 while position < end and \
661 while position < end and \
652 document.characterAt(position).isLetterOrNumber():
662 document.characterAt(position).isLetterOrNumber():
653 position += 1
663 position += 1
654 cursor = self.textCursor()
664 cursor = self.textCursor()
655 cursor.setPosition(position)
665 cursor.setPosition(position)
656 return cursor
666 return cursor
657
667
658 def _prompt_started(self):
668 def _prompt_started(self):
659 """ Called immediately after a new prompt is displayed.
669 """ Called immediately after a new prompt is displayed.
660 """
670 """
661 # Temporarily disable the maximum block count to permit undo/redo and
671 # Temporarily disable the maximum block count to permit undo/redo and
662 # 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.
663 self.setMaximumBlockCount(0)
673 self.setMaximumBlockCount(0)
664 self.setUndoRedoEnabled(True)
674 self.setUndoRedoEnabled(True)
665
675
666 self.setReadOnly(False)
676 self.setReadOnly(False)
667 self.moveCursor(QtGui.QTextCursor.End)
677 self.moveCursor(QtGui.QTextCursor.End)
668 self.centerCursor()
678 self.centerCursor()
669
679
670 self._executing = False
680 self._executing = False
671 self._prompt_started_hook()
681 self._prompt_started_hook()
672
682
673 def _prompt_finished(self):
683 def _prompt_finished(self):
674 """ 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
675 will be processed and a new prompt displayed.
685 will be processed and a new prompt displayed.
676 """
686 """
677 self.setUndoRedoEnabled(False)
687 self.setUndoRedoEnabled(False)
678 self.setReadOnly(True)
688 self.setReadOnly(True)
679 self._prompt_finished_hook()
689 self._prompt_finished_hook()
680
690
681 def _readline(self, prompt=''):
691 def _readline(self, prompt='', callback=None):
682 """ Read and return one line of input from the user. The trailing
692 """ Reads one line of input from the user.
683 newline is stripped.
693
694 Parameters
695 ----------
696 prompt : str, optional
697 The prompt to print before reading the line.
698
699 callback : callable, optional
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
702 been read.
703
704 Returns
705 -------
706 If a callback is specified, returns nothing. Otherwise, returns the
707 input string with the trailing newline stripped.
684 """
708 """
685 if not self.isVisible():
709 if self._reading:
686 raise RuntimeError('Cannot read a line if widget is not visible!')
710 raise RuntimeError('Cannot read a line. Widget is already reading.')
711
712 if not callback and not self.isVisible():
713 # If the user cannot see the widget, this function cannot return.
714 raise RuntimeError('Cannot synchronously read a line if the widget'
715 'is not visible!')
687
716
688 self._reading = True
717 self._reading = True
689 self._show_prompt(prompt)
718 self._show_prompt(prompt)
690 while self._reading:
719
691 QtCore.QCoreApplication.processEvents()
720 if callback is None:
692 return self.input_buffer.rstrip('\n\r')
721 self._reading_callback = None
722 while self._reading:
723 QtCore.QCoreApplication.processEvents()
724 return self.input_buffer.rstrip('\n')
725
726 else:
727 self._reading_callback = lambda: \
728 callback(self.input_buffer.rstrip('\n'))
693
729
694 def _set_position(self, position):
730 def _set_position(self, position):
695 """ Convenience method to set the position of the cursor.
731 """ Convenience method to set the position of the cursor.
696 """
732 """
697 cursor = self.textCursor()
733 cursor = self.textCursor()
698 cursor.setPosition(position)
734 cursor.setPosition(position)
699 self.setTextCursor(cursor)
735 self.setTextCursor(cursor)
700
736
701 def _set_selection(self, start, end):
737 def _set_selection(self, start, end):
702 """ Convenience method to set the current selected text.
738 """ Convenience method to set the current selected text.
703 """
739 """
704 self.setTextCursor(self._get_selection_cursor(start, end))
740 self.setTextCursor(self._get_selection_cursor(start, end))
705
741
706 def _show_prompt(self, prompt=None):
742 def _show_prompt(self, prompt=None):
707 """ 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. If 'prompt' is not
708 specified, the previous prompt is used.
744 specified, the previous prompt is used.
709 """
745 """
710 # Use QTextDocumentFragment intermediate object because it strips
746 # Use QTextDocumentFragment intermediate object because it strips
711 # out the Unicode line break characters that Qt insists on inserting.
747 # out the Unicode line break characters that Qt insists on inserting.
712 cursor = self._get_end_cursor()
748 cursor = self._get_end_cursor()
713 cursor.movePosition(QtGui.QTextCursor.Left,
749 if cursor.position() > 0:
714 QtGui.QTextCursor.KeepAnchor)
750 cursor.movePosition(QtGui.QTextCursor.Left,
715 if str(cursor.selection().toPlainText()) not in '\r\n':
751 QtGui.QTextCursor.KeepAnchor)
716 self.appendPlainText('\n')
752 if str(cursor.selection().toPlainText()) != '\n':
753 self.appendPlainText('\n')
717
754
718 if prompt is not None:
755 if prompt is not None:
719 self._prompt = prompt
756 self._prompt = prompt
720 self.appendPlainText(self._prompt)
757 self.appendPlainText(self._prompt)
721
758
722 self._prompt_pos = self._get_end_cursor().position()
759 self._prompt_pos = self._get_end_cursor().position()
723 self._prompt_started()
760 self._prompt_started()
724
761
725 def _show_continuation_prompt(self):
762 def _show_continuation_prompt(self):
726 """ Writes a new continuation prompt at the end of the buffer.
763 """ Writes a new continuation prompt at the end of the buffer.
727 """
764 """
728 self.appendPlainText(self._continuation_prompt)
765 self.appendPlainText(self._continuation_prompt)
729 self._prompt_started()
766 self._prompt_started()
730
767
731 def _write_text_keeping_prompt(self, text):
768 def _write_text_keeping_prompt(self, text):
732 """ Writes 'text' after the current prompt, then restores the old prompt
769 """ Writes 'text' after the current prompt, then restores the old prompt
733 with its old input buffer.
770 with its old input buffer.
734 """
771 """
735 input_buffer = self.input_buffer
772 input_buffer = self.input_buffer
736 self.appendPlainText('\n')
773 self.appendPlainText('\n')
737 self._prompt_finished()
774 self._prompt_finished()
738
775
739 self.appendPlainText(text)
776 self.appendPlainText(text)
740 self._show_prompt()
777 self._show_prompt()
741 self.input_buffer = input_buffer
778 self.input_buffer = input_buffer
742
779
743 def _in_buffer(self, position):
780 def _in_buffer(self, position):
744 """ Returns whether the given position is inside the editing region.
781 """ Returns whether the given position is inside the editing region.
745 """
782 """
746 return position >= self._prompt_pos
783 return position >= self._prompt_pos
747
784
748 def _keep_cursor_in_buffer(self):
785 def _keep_cursor_in_buffer(self):
749 """ Ensures that the cursor is inside the editing region. Returns
786 """ Ensures that the cursor is inside the editing region. Returns
750 whether the cursor was moved.
787 whether the cursor was moved.
751 """
788 """
752 cursor = self.textCursor()
789 cursor = self.textCursor()
753 if cursor.position() < self._prompt_pos:
790 if cursor.position() < self._prompt_pos:
754 cursor.movePosition(QtGui.QTextCursor.End)
791 cursor.movePosition(QtGui.QTextCursor.End)
755 self.setTextCursor(cursor)
792 self.setTextCursor(cursor)
756 return True
793 return True
757 else:
794 else:
758 return False
795 return False
759
796
760
797
761 class HistoryConsoleWidget(ConsoleWidget):
798 class HistoryConsoleWidget(ConsoleWidget):
762 """ A ConsoleWidget that keeps a history of the commands that have been
799 """ A ConsoleWidget that keeps a history of the commands that have been
763 executed.
800 executed.
764 """
801 """
765
802
766 #---------------------------------------------------------------------------
803 #---------------------------------------------------------------------------
767 # 'QObject' interface
804 # 'QObject' interface
768 #---------------------------------------------------------------------------
805 #---------------------------------------------------------------------------
769
806
770 def __init__(self, parent=None):
807 def __init__(self, parent=None):
771 super(HistoryConsoleWidget, self).__init__(parent)
808 super(HistoryConsoleWidget, self).__init__(parent)
772
809
773 self._history = []
810 self._history = []
774 self._history_index = 0
811 self._history_index = 0
775
812
776 #---------------------------------------------------------------------------
813 #---------------------------------------------------------------------------
777 # 'ConsoleWidget' public interface
814 # 'ConsoleWidget' public interface
778 #---------------------------------------------------------------------------
815 #---------------------------------------------------------------------------
779
816
780 def execute(self, source=None, hidden=False, interactive=False):
817 def execute(self, source=None, hidden=False, interactive=False):
781 """ Reimplemented to the store history.
818 """ Reimplemented to the store history.
782 """
819 """
783 if not hidden:
820 if not hidden:
784 history = self.input_buffer if source is None else source
821 history = self.input_buffer if source is None else source
785
822
786 executed = super(HistoryConsoleWidget, self).execute(
823 executed = super(HistoryConsoleWidget, self).execute(
787 source, hidden, interactive)
824 source, hidden, interactive)
788
825
789 if executed and not hidden:
826 if executed and not hidden:
790 self._history.append(history.rstrip())
827 self._history.append(history.rstrip())
791 self._history_index = len(self._history)
828 self._history_index = len(self._history)
792
829
793 return executed
830 return executed
794
831
795 #---------------------------------------------------------------------------
832 #---------------------------------------------------------------------------
796 # 'ConsoleWidget' abstract interface
833 # 'ConsoleWidget' abstract interface
797 #---------------------------------------------------------------------------
834 #---------------------------------------------------------------------------
798
835
799 def _up_pressed(self):
836 def _up_pressed(self):
800 """ Called when the up key is pressed. Returns whether to continue
837 """ Called when the up key is pressed. Returns whether to continue
801 processing the event.
838 processing the event.
802 """
839 """
803 prompt_cursor = self._get_prompt_cursor()
840 prompt_cursor = self._get_prompt_cursor()
804 if self.textCursor().blockNumber() == prompt_cursor.blockNumber():
841 if self.textCursor().blockNumber() == prompt_cursor.blockNumber():
805 self.history_previous()
842 self.history_previous()
806
843
807 # Go to the first line of prompt for seemless history scrolling.
844 # Go to the first line of prompt for seemless history scrolling.
808 cursor = self._get_prompt_cursor()
845 cursor = self._get_prompt_cursor()
809 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
846 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
810 self.setTextCursor(cursor)
847 self.setTextCursor(cursor)
811
848
812 return False
849 return False
813 return True
850 return True
814
851
815 def _down_pressed(self):
852 def _down_pressed(self):
816 """ Called when the down key is pressed. Returns whether to continue
853 """ Called when the down key is pressed. Returns whether to continue
817 processing the event.
854 processing the event.
818 """
855 """
819 end_cursor = self._get_end_cursor()
856 end_cursor = self._get_end_cursor()
820 if self.textCursor().blockNumber() == end_cursor.blockNumber():
857 if self.textCursor().blockNumber() == end_cursor.blockNumber():
821 self.history_next()
858 self.history_next()
822 return False
859 return False
823 return True
860 return True
824
861
825 #---------------------------------------------------------------------------
862 #---------------------------------------------------------------------------
826 # 'HistoryConsoleWidget' interface
863 # 'HistoryConsoleWidget' interface
827 #---------------------------------------------------------------------------
864 #---------------------------------------------------------------------------
828
865
829 def history_previous(self):
866 def history_previous(self):
830 """ If possible, set the input buffer to the previous item in the
867 """ If possible, set the input buffer to the previous item in the
831 history.
868 history.
832 """
869 """
833 if self._history_index > 0:
870 if self._history_index > 0:
834 self._history_index -= 1
871 self._history_index -= 1
835 self.input_buffer = self._history[self._history_index]
872 self.input_buffer = self._history[self._history_index]
836
873
837 def history_next(self):
874 def history_next(self):
838 """ Set the input buffer to the next item in the history, or a blank
875 """ Set the input buffer to the next item in the history, or a blank
839 line if there is no subsequent item.
876 line if there is no subsequent item.
840 """
877 """
841 if self._history_index < len(self._history):
878 if self._history_index < len(self._history):
842 self._history_index += 1
879 self._history_index += 1
843 if self._history_index < len(self._history):
880 if self._history_index < len(self._history):
844 self.input_buffer = self._history[self._history_index]
881 self.input_buffer = self._history[self._history_index]
845 else:
882 else:
846 self.input_buffer = ''
883 self.input_buffer = ''
@@ -1,349 +1,350 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):
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)
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 sub.message_received.disconnect(self._handle_sub)
182 sub.message_received.disconnect(self._handle_sub)
183 xreq.execute_reply.disconnect(self._handle_execute_reply)
183 xreq.execute_reply.disconnect(self._handle_execute_reply)
184 xreq.complete_reply.disconnect(self._handle_complete_reply)
184 xreq.complete_reply.disconnect(self._handle_complete_reply)
185 xreq.object_info_reply.disconnect(self._handle_object_info_reply)
185 xreq.object_info_reply.disconnect(self._handle_object_info_reply)
186
186
187 # Handle the case where the old kernel manager is still listening.
187 # Handle the case where the old kernel manager is still listening.
188 if self._kernel_manager.channels_running:
188 if self._kernel_manager.channels_running:
189 self._stopped_channels()
189 self._stopped_channels()
190
190
191 # Set the new kernel manager.
191 # Set the new kernel manager.
192 self._kernel_manager = kernel_manager
192 self._kernel_manager = kernel_manager
193 if kernel_manager is None:
193 if kernel_manager is None:
194 return
194 return
195
195
196 # Connect the new kernel manager.
196 # Connect the new kernel manager.
197 kernel_manager.started_channels.connect(self._started_channels)
197 kernel_manager.started_channels.connect(self._started_channels)
198 kernel_manager.stopped_channels.connect(self._stopped_channels)
198 kernel_manager.stopped_channels.connect(self._stopped_channels)
199
199
200 # Connect the new kernel manager's channels.
200 # Connect the new kernel manager's channels.
201 sub = kernel_manager.sub_channel
201 sub = kernel_manager.sub_channel
202 xreq = kernel_manager.xreq_channel
202 xreq = kernel_manager.xreq_channel
203 sub.message_received.connect(self._handle_sub)
203 sub.message_received.connect(self._handle_sub)
204 xreq.execute_reply.connect(self._handle_execute_reply)
204 xreq.execute_reply.connect(self._handle_execute_reply)
205 xreq.complete_reply.connect(self._handle_complete_reply)
205 xreq.complete_reply.connect(self._handle_complete_reply)
206 xreq.object_info_reply.connect(self._handle_object_info_reply)
206 xreq.object_info_reply.connect(self._handle_object_info_reply)
207
207
208 # Handle the case where the kernel manager started channels before
208 # Handle the case where the kernel manager started channels before
209 # we connected.
209 # we connected.
210 if kernel_manager.channels_running:
210 if kernel_manager.channels_running:
211 self._started_channels()
211 self._started_channels()
212
212
213 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
213 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
214
214
215 #---------------------------------------------------------------------------
215 #---------------------------------------------------------------------------
216 # 'FrontendWidget' protected interface
216 # 'FrontendWidget' protected interface
217 #---------------------------------------------------------------------------
217 #---------------------------------------------------------------------------
218
218
219 def _call_tip(self):
219 def _call_tip(self):
220 """ Shows a call tip, if appropriate, at the current cursor location.
220 """ Shows a call tip, if appropriate, at the current cursor location.
221 """
221 """
222 # Decide if it makes sense to show a call tip
222 # Decide if it makes sense to show a call tip
223 cursor = self.textCursor()
223 cursor = self.textCursor()
224 cursor.movePosition(QtGui.QTextCursor.Left)
224 cursor.movePosition(QtGui.QTextCursor.Left)
225 document = self.document()
225 document = self.document()
226 if document.characterAt(cursor.position()).toAscii() != '(':
226 if document.characterAt(cursor.position()).toAscii() != '(':
227 return False
227 return False
228 context = self._get_context(cursor)
228 context = self._get_context(cursor)
229 if not context:
229 if not context:
230 return False
230 return False
231
231
232 # Send the metadata request to the kernel
232 # Send the metadata request to the kernel
233 name = '.'.join(context)
233 name = '.'.join(context)
234 self._calltip_id = self.kernel_manager.xreq_channel.object_info(name)
234 self._calltip_id = self.kernel_manager.xreq_channel.object_info(name)
235 self._calltip_pos = self.textCursor().position()
235 self._calltip_pos = self.textCursor().position()
236 return True
236 return True
237
237
238 def _complete(self):
238 def _complete(self):
239 """ Performs completion at the current cursor location.
239 """ Performs completion at the current cursor location.
240 """
240 """
241 # Decide if it makes sense to do completion
241 # Decide if it makes sense to do completion
242 context = self._get_context()
242 context = self._get_context()
243 if not context:
243 if not context:
244 return False
244 return False
245
245
246 # Send the completion request to the kernel
246 # Send the completion request to the kernel
247 text = '.'.join(context)
247 text = '.'.join(context)
248 self._complete_id = self.kernel_manager.xreq_channel.complete(
248 self._complete_id = self.kernel_manager.xreq_channel.complete(
249 text, self.input_buffer_cursor_line, self.input_buffer)
249 text, self.input_buffer_cursor_line, self.input_buffer)
250 self._complete_pos = self.textCursor().position()
250 self._complete_pos = self.textCursor().position()
251 return True
251 return True
252
252
253 def _get_context(self, cursor=None):
253 def _get_context(self, cursor=None):
254 """ Gets the context at the current cursor location.
254 """ Gets the context at the current cursor location.
255 """
255 """
256 if cursor is None:
256 if cursor is None:
257 cursor = self.textCursor()
257 cursor = self.textCursor()
258 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
258 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
259 QtGui.QTextCursor.KeepAnchor)
259 QtGui.QTextCursor.KeepAnchor)
260 text = unicode(cursor.selectedText())
260 text = unicode(cursor.selectedText())
261 return self._completion_lexer.get_context(text)
261 return self._completion_lexer.get_context(text)
262
262
263 def _interrupt_kernel(self):
263 def _interrupt_kernel(self):
264 """ Attempts to the interrupt the kernel.
264 """ Attempts to the interrupt the kernel.
265 """
265 """
266 if self.kernel_manager.has_kernel:
266 if self.kernel_manager.has_kernel:
267 self.kernel_manager.signal_kernel(signal.SIGINT)
267 self.kernel_manager.signal_kernel(signal.SIGINT)
268 else:
268 else:
269 self.appendPlainText('Kernel process is either remote or '
269 self.appendPlainText('Kernel process is either remote or '
270 'unspecified. Cannot interrupt.\n')
270 'unspecified. Cannot interrupt.\n')
271
271
272 #------ Signal handlers ----------------------------------------------------
272 #------ Signal handlers ----------------------------------------------------
273
273
274 def _started_channels(self):
274 def _started_channels(self):
275 """ Called when the kernel manager has started listening.
275 """ Called when the kernel manager has started listening.
276 """
276 """
277 self.clear()
277 self.clear()
278
278
279 def _stopped_channels(self):
279 def _stopped_channels(self):
280 """ Called when the kernel manager has stopped listening.
280 """ Called when the kernel manager has stopped listening.
281 """
281 """
282 pass
282 pass
283
283
284 def _document_contents_change(self, position, removed, added):
284 def _document_contents_change(self, position, removed, added):
285 """ Called whenever the document's content changes. Display a calltip
285 """ Called whenever the document's content changes. Display a calltip
286 if appropriate.
286 if appropriate.
287 """
287 """
288 # Calculate where the cursor should be *after* the change:
288 # Calculate where the cursor should be *after* the change:
289 position += added
289 position += added
290
290
291 document = self.document()
291 document = self.document()
292 if position == self.textCursor().position():
292 if position == self.textCursor().position():
293 self._call_tip()
293 self._call_tip()
294
294
295 def _handle_req(self):
295 def _handle_req(self):
296 print self._readline()
296 def callback(line):
297 print repr(line)
298 self._show_prompt()
299 self._readline(callback=callback)
297
300
298 def _handle_sub(self, omsg):
301 def _handle_sub(self, omsg):
299 if self._hidden:
302 if self._hidden:
300 return
303 return
301 handler = getattr(self, '_handle_%s' % omsg['msg_type'], None)
304 handler = getattr(self, '_handle_%s' % omsg['msg_type'], None)
302 if handler is not None:
305 if handler is not None:
303 handler(omsg)
306 handler(omsg)
304
307
305 def _handle_pyout(self, omsg):
308 def _handle_pyout(self, omsg):
306 session = omsg['parent_header']['session']
309 session = omsg['parent_header']['session']
307 if session == self.kernel_manager.session.session:
310 if session == self.kernel_manager.session.session:
308 self.appendPlainText(omsg['content']['data'] + '\n')
311 self.appendPlainText(omsg['content']['data'] + '\n')
309
312
310 def _handle_stream(self, omsg):
313 def _handle_stream(self, omsg):
311 self.appendPlainText(omsg['content']['data'])
314 self.appendPlainText(omsg['content']['data'])
312 self.moveCursor(QtGui.QTextCursor.End)
315 self.moveCursor(QtGui.QTextCursor.End)
313
316
314 def _handle_execute_reply(self, rep):
317 def _handle_execute_reply(self, rep):
315 if self._hidden:
318 if self._hidden:
316 return
319 return
317
320
318 # Make sure that all output from the SUB channel has been processed
321 # Make sure that all output from the SUB channel has been processed
319 # before writing a new prompt.
322 # before writing a new prompt.
320 self.kernel_manager.sub_channel.flush()
323 self.kernel_manager.sub_channel.flush()
321
324
322 content = rep['content']
325 content = rep['content']
323 status = content['status']
326 status = content['status']
324 if status == 'error':
327 if status == 'error':
325 self.appendPlainText(content['traceback'][-1])
328 self.appendPlainText(content['traceback'][-1])
326 elif status == 'aborted':
329 elif status == 'aborted':
327 text = "ERROR: ABORTED\n"
330 text = "ERROR: ABORTED\n"
328 self.appendPlainText(text)
331 self.appendPlainText(text)
329 self._hidden = True
332 self._hidden = True
330 self._show_prompt()
333 self._show_prompt()
331 self.executed.emit(rep)
334 self.executed.emit(rep)
332
335
333 def _handle_complete_reply(self, rep):
336 def _handle_complete_reply(self, rep):
334 cursor = self.textCursor()
337 cursor = self.textCursor()
335 if rep['parent_header']['msg_id'] == self._complete_id and \
338 if rep['parent_header']['msg_id'] == self._complete_id and \
336 cursor.position() == self._complete_pos:
339 cursor.position() == self._complete_pos:
337 text = '.'.join(self._get_context())
340 text = '.'.join(self._get_context())
338 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
341 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
339 self._complete_with_items(cursor, rep['content']['matches'])
342 self._complete_with_items(cursor, rep['content']['matches'])
340
343
341 def _handle_object_info_reply(self, rep):
344 def _handle_object_info_reply(self, rep):
342 cursor = self.textCursor()
345 cursor = self.textCursor()
343 if rep['parent_header']['msg_id'] == self._calltip_id and \
346 if rep['parent_header']['msg_id'] == self._calltip_id and \
344 cursor.position() == self._calltip_pos:
347 cursor.position() == self._calltip_pos:
345 doc = rep['content']['docstring']
348 doc = rep['content']['docstring']
346 if doc:
349 if doc:
347 self._call_tip_widget.show_docstring(doc)
350 self._call_tip_widget.show_docstring(doc)
348
349
General Comments 0
You need to be logged in to leave comments. Login now