##// END OF EJS Templates
* Fixed history breakage due to recent refactoring....
epatters -
Show More
@@ -1,817 +1,823 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
144
145 # Set a monospaced font.
145 # Set a monospaced font.
146 self.reset_font()
146 self.reset_font()
147
147
148 # Define a custom context menu.
148 # Define a custom context menu.
149 self._context_menu = QtGui.QMenu(self)
149 self._context_menu = QtGui.QMenu(self)
150
150
151 copy_action = QtGui.QAction('Copy', self)
151 copy_action = QtGui.QAction('Copy', self)
152 copy_action.triggered.connect(self.copy)
152 copy_action.triggered.connect(self.copy)
153 self.copyAvailable.connect(copy_action.setEnabled)
153 self.copyAvailable.connect(copy_action.setEnabled)
154 self._context_menu.addAction(copy_action)
154 self._context_menu.addAction(copy_action)
155
155
156 self._paste_action = QtGui.QAction('Paste', self)
156 self._paste_action = QtGui.QAction('Paste', self)
157 self._paste_action.triggered.connect(self.paste)
157 self._paste_action.triggered.connect(self.paste)
158 self._context_menu.addAction(self._paste_action)
158 self._context_menu.addAction(self._paste_action)
159 self._context_menu.addSeparator()
159 self._context_menu.addSeparator()
160
160
161 select_all_action = QtGui.QAction('Select All', self)
161 select_all_action = QtGui.QAction('Select All', self)
162 select_all_action.triggered.connect(self.selectAll)
162 select_all_action.triggered.connect(self.selectAll)
163 self._context_menu.addAction(select_all_action)
163 self._context_menu.addAction(select_all_action)
164
164
165 def event(self, event):
165 def event(self, event):
166 """ Reimplemented to override shortcuts, if necessary.
166 """ Reimplemented to override shortcuts, if necessary.
167 """
167 """
168 # On Mac OS, it is always unnecessary to override shortcuts, hence the
168 # 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
169 # check below. Users should just use the Control key instead of the
170 # Command key.
170 # Command key.
171 if self.override_shortcuts and \
171 if self.override_shortcuts and \
172 sys.platform != 'darwin' and \
172 sys.platform != 'darwin' and \
173 event.type() == QtCore.QEvent.ShortcutOverride and \
173 event.type() == QtCore.QEvent.ShortcutOverride and \
174 self._control_down(event.modifiers()) and \
174 self._control_down(event.modifiers()) and \
175 event.key() in self._shortcuts:
175 event.key() in self._shortcuts:
176 event.accept()
176 event.accept()
177 return True
177 return True
178 else:
178 else:
179 return QtGui.QPlainTextEdit.event(self, event)
179 return QtGui.QPlainTextEdit.event(self, event)
180
180
181 #---------------------------------------------------------------------------
181 #---------------------------------------------------------------------------
182 # 'QWidget' interface
182 # 'QWidget' interface
183 #---------------------------------------------------------------------------
183 #---------------------------------------------------------------------------
184
184
185 def contextMenuEvent(self, event):
185 def contextMenuEvent(self, event):
186 """ Reimplemented to create a menu without destructive actions like
186 """ Reimplemented to create a menu without destructive actions like
187 'Cut' and 'Delete'.
187 'Cut' and 'Delete'.
188 """
188 """
189 clipboard_empty = QtGui.QApplication.clipboard().text().isEmpty()
189 clipboard_empty = QtGui.QApplication.clipboard().text().isEmpty()
190 self._paste_action.setEnabled(not clipboard_empty)
190 self._paste_action.setEnabled(not clipboard_empty)
191
191
192 self._context_menu.exec_(event.globalPos())
192 self._context_menu.exec_(event.globalPos())
193
193
194 def keyPressEvent(self, event):
194 def keyPressEvent(self, event):
195 """ Reimplemented to create a console-like interface.
195 """ Reimplemented to create a console-like interface.
196 """
196 """
197 intercepted = False
197 intercepted = False
198 cursor = self.textCursor()
198 cursor = self.textCursor()
199 position = cursor.position()
199 position = cursor.position()
200 key = event.key()
200 key = event.key()
201 ctrl_down = self._control_down(event.modifiers())
201 ctrl_down = self._control_down(event.modifiers())
202 alt_down = event.modifiers() & QtCore.Qt.AltModifier
202 alt_down = event.modifiers() & QtCore.Qt.AltModifier
203 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
203 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
204
204
205 # Even though we have reimplemented 'paste', the C++ level slot is still
205 # Even though we have reimplemented 'paste', the C++ level slot is still
206 # called by Qt. So we intercept the key press here.
206 # called by Qt. So we intercept the key press here.
207 if event.matches(QtGui.QKeySequence.Paste):
207 if event.matches(QtGui.QKeySequence.Paste):
208 self.paste()
208 self.paste()
209 intercepted = True
209 intercepted = True
210
210
211 elif ctrl_down:
211 elif ctrl_down:
212 if key in self._ctrl_down_remap:
212 if key in self._ctrl_down_remap:
213 ctrl_down = False
213 ctrl_down = False
214 key = self._ctrl_down_remap[key]
214 key = self._ctrl_down_remap[key]
215 event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, key,
215 event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, key,
216 QtCore.Qt.NoModifier)
216 QtCore.Qt.NoModifier)
217
217
218 elif key == QtCore.Qt.Key_K:
218 elif key == QtCore.Qt.Key_K:
219 if self._in_buffer(position):
219 if self._in_buffer(position):
220 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
220 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
221 QtGui.QTextCursor.KeepAnchor)
221 QtGui.QTextCursor.KeepAnchor)
222 cursor.removeSelectedText()
222 cursor.removeSelectedText()
223 intercepted = True
223 intercepted = True
224
224
225 elif key == QtCore.Qt.Key_X:
225 elif key == QtCore.Qt.Key_X:
226 intercepted = True
226 intercepted = True
227
227
228 elif key == QtCore.Qt.Key_Y:
228 elif key == QtCore.Qt.Key_Y:
229 self.paste()
229 self.paste()
230 intercepted = True
230 intercepted = True
231
231
232 elif alt_down:
232 elif alt_down:
233 if key == QtCore.Qt.Key_B:
233 if key == QtCore.Qt.Key_B:
234 self.setTextCursor(self._get_word_start_cursor(position))
234 self.setTextCursor(self._get_word_start_cursor(position))
235 intercepted = True
235 intercepted = True
236
236
237 elif key == QtCore.Qt.Key_F:
237 elif key == QtCore.Qt.Key_F:
238 self.setTextCursor(self._get_word_end_cursor(position))
238 self.setTextCursor(self._get_word_end_cursor(position))
239 intercepted = True
239 intercepted = True
240
240
241 elif key == QtCore.Qt.Key_Backspace:
241 elif key == QtCore.Qt.Key_Backspace:
242 cursor = self._get_word_start_cursor(position)
242 cursor = self._get_word_start_cursor(position)
243 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
243 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
244 cursor.removeSelectedText()
244 cursor.removeSelectedText()
245 intercepted = True
245 intercepted = True
246
246
247 elif key == QtCore.Qt.Key_D:
247 elif key == QtCore.Qt.Key_D:
248 cursor = self._get_word_end_cursor(position)
248 cursor = self._get_word_end_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 if self._completion_widget.isVisible():
253 if self._completion_widget.isVisible():
254 self._completion_widget.keyPressEvent(event)
254 self._completion_widget.keyPressEvent(event)
255 intercepted = event.isAccepted()
255 intercepted = event.isAccepted()
256
256
257 else:
257 else:
258 if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
258 if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
259 if self._reading:
259 if self._reading:
260 self._reading = False
260 self._reading = False
261 elif not self._executing:
261 elif not self._executing:
262 self.execute(interactive=True)
262 self.execute(interactive=True)
263 intercepted = True
263 intercepted = True
264
264
265 elif key == QtCore.Qt.Key_Up:
265 elif key == QtCore.Qt.Key_Up:
266 if self._reading or not self._up_pressed():
266 if self._reading or not self._up_pressed():
267 intercepted = True
267 intercepted = True
268 else:
268 else:
269 prompt_line = self._get_prompt_cursor().blockNumber()
269 prompt_line = self._get_prompt_cursor().blockNumber()
270 intercepted = cursor.blockNumber() <= prompt_line
270 intercepted = cursor.blockNumber() <= prompt_line
271
271
272 elif key == QtCore.Qt.Key_Down:
272 elif key == QtCore.Qt.Key_Down:
273 if self._reading or not self._down_pressed():
273 if self._reading or not self._down_pressed():
274 intercepted = True
274 intercepted = True
275 else:
275 else:
276 end_line = self._get_end_cursor().blockNumber()
276 end_line = self._get_end_cursor().blockNumber()
277 intercepted = cursor.blockNumber() == end_line
277 intercepted = cursor.blockNumber() == end_line
278
278
279 elif key == QtCore.Qt.Key_Tab:
279 elif key == QtCore.Qt.Key_Tab:
280 if self._reading:
280 if self._reading:
281 intercepted = False
281 intercepted = False
282 else:
282 else:
283 intercepted = not self._tab_pressed()
283 intercepted = not self._tab_pressed()
284
284
285 elif key == QtCore.Qt.Key_Left:
285 elif key == QtCore.Qt.Key_Left:
286 intercepted = not self._in_buffer(position - 1)
286 intercepted = not self._in_buffer(position - 1)
287
287
288 elif key == QtCore.Qt.Key_Home:
288 elif key == QtCore.Qt.Key_Home:
289 cursor.movePosition(QtGui.QTextCursor.StartOfLine)
289 cursor.movePosition(QtGui.QTextCursor.StartOfLine)
290 start_pos = cursor.position()
290 start_pos = cursor.position()
291 start_line = cursor.blockNumber()
291 start_line = cursor.blockNumber()
292 if start_line == self._get_prompt_cursor().blockNumber():
292 if start_line == self._get_prompt_cursor().blockNumber():
293 start_pos += len(self._prompt)
293 start_pos += len(self._prompt)
294 else:
294 else:
295 start_pos += len(self._continuation_prompt)
295 start_pos += len(self._continuation_prompt)
296 if shift_down and self._in_buffer(position):
296 if shift_down and self._in_buffer(position):
297 self._set_selection(position, start_pos)
297 self._set_selection(position, start_pos)
298 else:
298 else:
299 self._set_position(start_pos)
299 self._set_position(start_pos)
300 intercepted = True
300 intercepted = True
301
301
302 elif key == QtCore.Qt.Key_Backspace and not alt_down:
302 elif key == QtCore.Qt.Key_Backspace and not alt_down:
303
303
304 # Line deletion (remove continuation prompt)
304 # Line deletion (remove continuation prompt)
305 len_prompt = len(self._continuation_prompt)
305 len_prompt = len(self._continuation_prompt)
306 if cursor.columnNumber() == len_prompt and \
306 if cursor.columnNumber() == len_prompt and \
307 position != self._prompt_pos:
307 position != self._prompt_pos:
308 cursor.setPosition(position - len_prompt,
308 cursor.setPosition(position - len_prompt,
309 QtGui.QTextCursor.KeepAnchor)
309 QtGui.QTextCursor.KeepAnchor)
310 cursor.removeSelectedText()
310 cursor.removeSelectedText()
311
311
312 # Regular backwards deletion
312 # Regular backwards deletion
313 else:
313 else:
314 anchor = cursor.anchor()
314 anchor = cursor.anchor()
315 if anchor == position:
315 if anchor == position:
316 intercepted = not self._in_buffer(position - 1)
316 intercepted = not self._in_buffer(position - 1)
317 else:
317 else:
318 intercepted = not self._in_buffer(min(anchor, position))
318 intercepted = not self._in_buffer(min(anchor, position))
319
319
320 elif key == QtCore.Qt.Key_Delete:
320 elif key == QtCore.Qt.Key_Delete:
321 anchor = cursor.anchor()
321 anchor = cursor.anchor()
322 intercepted = not self._in_buffer(min(anchor, position))
322 intercepted = not self._in_buffer(min(anchor, position))
323
323
324 # Don't move cursor if control is down to allow copy-paste using
324 # Don't move cursor if control is down to allow copy-paste using
325 # the keyboard in any part of the buffer.
325 # the keyboard in any part of the buffer.
326 if not ctrl_down:
326 if not ctrl_down:
327 self._keep_cursor_in_buffer()
327 self._keep_cursor_in_buffer()
328
328
329 if not intercepted:
329 if not intercepted:
330 QtGui.QPlainTextEdit.keyPressEvent(self, event)
330 QtGui.QPlainTextEdit.keyPressEvent(self, event)
331
331
332 #--------------------------------------------------------------------------
332 #--------------------------------------------------------------------------
333 # 'QPlainTextEdit' interface
333 # 'QPlainTextEdit' interface
334 #--------------------------------------------------------------------------
334 #--------------------------------------------------------------------------
335
335
336 def appendPlainText(self, text):
336 def appendPlainText(self, text):
337 """ Reimplemented to not append text as a new paragraph, which doesn't
337 """ Reimplemented to not append text as a new paragraph, which doesn't
338 make sense for a console widget. Also, if enabled, handle ANSI
338 make sense for a console widget. Also, if enabled, handle ANSI
339 codes.
339 codes.
340 """
340 """
341 cursor = self.textCursor()
341 cursor = self.textCursor()
342 cursor.movePosition(QtGui.QTextCursor.End)
342 cursor.movePosition(QtGui.QTextCursor.End)
343
343
344 if self.ansi_codes:
344 if self.ansi_codes:
345 format = QtGui.QTextCharFormat()
345 format = QtGui.QTextCharFormat()
346 previous_end = 0
346 previous_end = 0
347 for match in self._ansi_pattern.finditer(text):
347 for match in self._ansi_pattern.finditer(text):
348 cursor.insertText(text[previous_end:match.start()], format)
348 cursor.insertText(text[previous_end:match.start()], format)
349 previous_end = match.end()
349 previous_end = match.end()
350 for code in match.group(1).split(';'):
350 for code in match.group(1).split(';'):
351 self._ansi_processor.set_code(int(code))
351 self._ansi_processor.set_code(int(code))
352 format = self._ansi_processor.get_format()
352 format = self._ansi_processor.get_format()
353 cursor.insertText(text[previous_end:], format)
353 cursor.insertText(text[previous_end:], format)
354 else:
354 else:
355 cursor.insertText(text)
355 cursor.insertText(text)
356
356
357 def clear(self, keep_input=False):
357 def clear(self, keep_input=False):
358 """ Reimplemented to write a new prompt. If 'keep_input' is set,
358 """ Reimplemented to write a new prompt. If 'keep_input' is set,
359 restores the old input buffer when the new prompt is written.
359 restores the old input buffer when the new prompt is written.
360 """
360 """
361 super(ConsoleWidget, self).clear()
361 super(ConsoleWidget, self).clear()
362
362
363 if keep_input:
363 if keep_input:
364 input_buffer = self.input_buffer
364 input_buffer = self.input_buffer
365 self._show_prompt()
365 self._show_prompt()
366 if keep_input:
366 if keep_input:
367 self.input_buffer = input_buffer
367 self.input_buffer = input_buffer
368
368
369 def paste(self):
369 def paste(self):
370 """ Reimplemented to ensure that text is pasted in the editing region.
370 """ Reimplemented to ensure that text is pasted in the editing region.
371 """
371 """
372 self._keep_cursor_in_buffer()
372 self._keep_cursor_in_buffer()
373 QtGui.QPlainTextEdit.paste(self)
373 QtGui.QPlainTextEdit.paste(self)
374
374
375 def print_(self, printer):
375 def print_(self, printer):
376 """ Reimplemented to work around a bug in PyQt: the C++ level 'print_'
376 """ Reimplemented to work around a bug in PyQt: the C++ level 'print_'
377 slot has the wrong signature.
377 slot has the wrong signature.
378 """
378 """
379 QtGui.QPlainTextEdit.print_(self, printer)
379 QtGui.QPlainTextEdit.print_(self, printer)
380
380
381 #---------------------------------------------------------------------------
381 #---------------------------------------------------------------------------
382 # 'ConsoleWidget' public interface
382 # 'ConsoleWidget' public interface
383 #---------------------------------------------------------------------------
383 #---------------------------------------------------------------------------
384
384
385 def execute(self, source=None, hidden=False, interactive=False):
385 def execute(self, source=None, hidden=False, interactive=False):
386 """ Executes source or the input buffer, possibly prompting for more
386 """ Executes source or the input buffer, possibly prompting for more
387 input.
387 input.
388
388
389 Parameters:
389 Parameters:
390 -----------
390 -----------
391 source : str, optional
391 source : str, optional
392
392
393 The source to execute. If not specified, the input buffer will be
393 The source to execute. If not specified, the input buffer will be
394 used. If specified and 'hidden' is False, the input buffer will be
394 used. If specified and 'hidden' is False, the input buffer will be
395 replaced with the source before execution.
395 replaced with the source before execution.
396
396
397 hidden : bool, optional (default False)
397 hidden : bool, optional (default False)
398
398
399 If set, no output will be shown and the prompt will not be modified.
399 If set, no output will be shown and the prompt will not be modified.
400 In other words, it will be completely invisible to the user that
400 In other words, it will be completely invisible to the user that
401 an execution has occurred.
401 an execution has occurred.
402
402
403 interactive : bool, optional (default False)
403 interactive : bool, optional (default False)
404
404
405 Whether the console is to treat the source as having been manually
405 Whether the console is to treat the source as having been manually
406 entered by the user. The effect of this parameter depends on the
406 entered by the user. The effect of this parameter depends on the
407 subclass implementation.
407 subclass implementation.
408
408
409 Raises:
410 -------
411 RuntimeError
412 If incomplete input is given and 'hidden' is True. In this case,
413 it not possible to prompt for more input.
414
409 Returns:
415 Returns:
410 --------
416 --------
411 A boolean indicating whether the source was executed.
417 A boolean indicating whether the source was executed.
412 """
418 """
413 if not hidden:
419 if not hidden:
414 if source is not None:
420 if source is not None:
415 self.input_buffer = source
421 self.input_buffer = source
416
422
417 self.appendPlainText('\n')
423 self.appendPlainText('\n')
418 self._executing_input_buffer = self.input_buffer
424 self._executing_input_buffer = self.input_buffer
419 self._executing = True
425 self._executing = True
420 self._prompt_finished()
426 self._prompt_finished()
421
427
422 real_source = self.input_buffer if source is None else source
428 real_source = self.input_buffer if source is None else source
423 complete = self._is_complete(real_source, interactive)
429 complete = self._is_complete(real_source, interactive)
424 if complete:
430 if complete:
425 if not hidden:
431 if not hidden:
426 # The maximum block count is only in effect during execution.
432 # The maximum block count is only in effect during execution.
427 # This ensures that _prompt_pos does not become invalid due to
433 # This ensures that _prompt_pos does not become invalid due to
428 # text truncation.
434 # text truncation.
429 self.setMaximumBlockCount(self.buffer_size)
435 self.setMaximumBlockCount(self.buffer_size)
430 self._execute(real_source, hidden)
436 self._execute(real_source, hidden)
431 elif hidden:
437 elif hidden:
432 raise RuntimeError('Incomplete noninteractive input: "%s"' % source)
438 raise RuntimeError('Incomplete noninteractive input: "%s"' % source)
433 else:
439 else:
434 self._show_continuation_prompt()
440 self._show_continuation_prompt()
435
441
436 return complete
442 return complete
437
443
438 def _get_input_buffer(self):
444 def _get_input_buffer(self):
439 """ The text that the user has entered entered at the current prompt.
445 """ The text that the user has entered entered at the current prompt.
440 """
446 """
441 # If we're executing, the input buffer may not even exist anymore due to
447 # If we're executing, the input buffer may not even exist anymore due to
442 # the limit imposed by 'buffer_size'. Therefore, we store it.
448 # the limit imposed by 'buffer_size'. Therefore, we store it.
443 if self._executing:
449 if self._executing:
444 return self._executing_input_buffer
450 return self._executing_input_buffer
445
451
446 cursor = self._get_end_cursor()
452 cursor = self._get_end_cursor()
447 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
453 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
448
454
449 # Use QTextDocumentFragment intermediate object because it strips
455 # Use QTextDocumentFragment intermediate object because it strips
450 # out the Unicode line break characters that Qt insists on inserting.
456 # out the Unicode line break characters that Qt insists on inserting.
451 input_buffer = str(cursor.selection().toPlainText())
457 input_buffer = str(cursor.selection().toPlainText())
452
458
453 # Strip out continuation prompts.
459 # Strip out continuation prompts.
454 return input_buffer.replace('\n' + self._continuation_prompt, '\n')
460 return input_buffer.replace('\n' + self._continuation_prompt, '\n')
455
461
456 def _set_input_buffer(self, string):
462 def _set_input_buffer(self, string):
457 """ Replaces the text in the input buffer with 'string'.
463 """ Replaces the text in the input buffer with 'string'.
458 """
464 """
459 # Add continuation prompts where necessary.
465 # Add continuation prompts where necessary.
460 lines = string.splitlines()
466 lines = string.splitlines()
461 for i in xrange(1, len(lines)):
467 for i in xrange(1, len(lines)):
462 lines[i] = self._continuation_prompt + lines[i]
468 lines[i] = self._continuation_prompt + lines[i]
463 string = '\n'.join(lines)
469 string = '\n'.join(lines)
464
470
465 # Replace buffer with new text.
471 # Replace buffer with new text.
466 cursor = self._get_end_cursor()
472 cursor = self._get_end_cursor()
467 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
473 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
468 cursor.insertText(string)
474 cursor.insertText(string)
469 self.moveCursor(QtGui.QTextCursor.End)
475 self.moveCursor(QtGui.QTextCursor.End)
470
476
471 input_buffer = property(_get_input_buffer, _set_input_buffer)
477 input_buffer = property(_get_input_buffer, _set_input_buffer)
472
478
473 def _get_input_buffer_cursor_line(self):
479 def _get_input_buffer_cursor_line(self):
474 """ The text in the line of the input buffer in which the user's cursor
480 """ The text in the line of the input buffer in which the user's cursor
475 rests. Returns a string if there is such a line; otherwise, None.
481 rests. Returns a string if there is such a line; otherwise, None.
476 """
482 """
477 if self._executing:
483 if self._executing:
478 return None
484 return None
479 cursor = self.textCursor()
485 cursor = self.textCursor()
480 if cursor.position() >= self._prompt_pos:
486 if cursor.position() >= self._prompt_pos:
481 text = str(cursor.block().text())
487 text = str(cursor.block().text())
482 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
488 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
483 return text[len(self._prompt):]
489 return text[len(self._prompt):]
484 else:
490 else:
485 return text[len(self._continuation_prompt):]
491 return text[len(self._continuation_prompt):]
486 else:
492 else:
487 return None
493 return None
488
494
489 input_buffer_cursor_line = property(_get_input_buffer_cursor_line)
495 input_buffer_cursor_line = property(_get_input_buffer_cursor_line)
490
496
491 def _get_font(self):
497 def _get_font(self):
492 """ The base font being used by the ConsoleWidget.
498 """ The base font being used by the ConsoleWidget.
493 """
499 """
494 return self.document().defaultFont()
500 return self.document().defaultFont()
495
501
496 def _set_font(self, font):
502 def _set_font(self, font):
497 """ Sets the base font for the ConsoleWidget to the specified QFont.
503 """ Sets the base font for the ConsoleWidget to the specified QFont.
498 """
504 """
499 self._completion_widget.setFont(font)
505 self._completion_widget.setFont(font)
500 self.document().setDefaultFont(font)
506 self.document().setDefaultFont(font)
501
507
502 font = property(_get_font, _set_font)
508 font = property(_get_font, _set_font)
503
509
504 def reset_font(self):
510 def reset_font(self):
505 """ Sets the font to the default fixed-width font for this platform.
511 """ Sets the font to the default fixed-width font for this platform.
506 """
512 """
507 if sys.platform == 'win32':
513 if sys.platform == 'win32':
508 name = 'Courier'
514 name = 'Courier'
509 elif sys.platform == 'darwin':
515 elif sys.platform == 'darwin':
510 name = 'Monaco'
516 name = 'Monaco'
511 else:
517 else:
512 name = 'Monospace'
518 name = 'Monospace'
513 font = QtGui.QFont(name, QtGui.qApp.font().pointSize())
519 font = QtGui.QFont(name, QtGui.qApp.font().pointSize())
514 font.setStyleHint(QtGui.QFont.TypeWriter)
520 font.setStyleHint(QtGui.QFont.TypeWriter)
515 self._set_font(font)
521 self._set_font(font)
516
522
517 #---------------------------------------------------------------------------
523 #---------------------------------------------------------------------------
518 # 'ConsoleWidget' abstract interface
524 # 'ConsoleWidget' abstract interface
519 #---------------------------------------------------------------------------
525 #---------------------------------------------------------------------------
520
526
521 def _is_complete(self, source, interactive):
527 def _is_complete(self, source, interactive):
522 """ Returns whether 'source' can be executed. When triggered by an
528 """ Returns whether 'source' can be executed. When triggered by an
523 Enter/Return key press, 'interactive' is True; otherwise, it is
529 Enter/Return key press, 'interactive' is True; otherwise, it is
524 False.
530 False.
525 """
531 """
526 raise NotImplementedError
532 raise NotImplementedError
527
533
528 def _execute(self, source, hidden):
534 def _execute(self, source, hidden):
529 """ Execute 'source'. If 'hidden', do not show any output.
535 """ Execute 'source'. If 'hidden', do not show any output.
530 """
536 """
531 raise NotImplementedError
537 raise NotImplementedError
532
538
533 def _prompt_started_hook(self):
539 def _prompt_started_hook(self):
534 """ Called immediately after a new prompt is displayed.
540 """ Called immediately after a new prompt is displayed.
535 """
541 """
536 pass
542 pass
537
543
538 def _prompt_finished_hook(self):
544 def _prompt_finished_hook(self):
539 """ Called immediately after a prompt is finished, i.e. when some input
545 """ Called immediately after a prompt is finished, i.e. when some input
540 will be processed and a new prompt displayed.
546 will be processed and a new prompt displayed.
541 """
547 """
542 pass
548 pass
543
549
544 def _up_pressed(self):
550 def _up_pressed(self):
545 """ Called when the up key is pressed. Returns whether to continue
551 """ Called when the up key is pressed. Returns whether to continue
546 processing the event.
552 processing the event.
547 """
553 """
548 return True
554 return True
549
555
550 def _down_pressed(self):
556 def _down_pressed(self):
551 """ Called when the down key is pressed. Returns whether to continue
557 """ Called when the down key is pressed. Returns whether to continue
552 processing the event.
558 processing the event.
553 """
559 """
554 return True
560 return True
555
561
556 def _tab_pressed(self):
562 def _tab_pressed(self):
557 """ Called when the tab key is pressed. Returns whether to continue
563 """ Called when the tab key is pressed. Returns whether to continue
558 processing the event.
564 processing the event.
559 """
565 """
560 return False
566 return False
561
567
562 #--------------------------------------------------------------------------
568 #--------------------------------------------------------------------------
563 # 'ConsoleWidget' protected interface
569 # 'ConsoleWidget' protected interface
564 #--------------------------------------------------------------------------
570 #--------------------------------------------------------------------------
565
571
566 def _control_down(self, modifiers):
572 def _control_down(self, modifiers):
567 """ Given a KeyboardModifiers flags object, return whether the Control
573 """ Given a KeyboardModifiers flags object, return whether the Control
568 key is down (on Mac OS, treat the Command key as a synonym for
574 key is down (on Mac OS, treat the Command key as a synonym for
569 Control).
575 Control).
570 """
576 """
571 down = bool(modifiers & QtCore.Qt.ControlModifier)
577 down = bool(modifiers & QtCore.Qt.ControlModifier)
572
578
573 # Note: on Mac OS, ControlModifier corresponds to the Command key while
579 # Note: on Mac OS, ControlModifier corresponds to the Command key while
574 # MetaModifier corresponds to the Control key.
580 # MetaModifier corresponds to the Control key.
575 if sys.platform == 'darwin':
581 if sys.platform == 'darwin':
576 down = down ^ bool(modifiers & QtCore.Qt.MetaModifier)
582 down = down ^ bool(modifiers & QtCore.Qt.MetaModifier)
577
583
578 return down
584 return down
579
585
580 def _complete_with_items(self, cursor, items):
586 def _complete_with_items(self, cursor, items):
581 """ Performs completion with 'items' at the specified cursor location.
587 """ Performs completion with 'items' at the specified cursor location.
582 """
588 """
583 if len(items) == 1:
589 if len(items) == 1:
584 cursor.setPosition(self.textCursor().position(),
590 cursor.setPosition(self.textCursor().position(),
585 QtGui.QTextCursor.KeepAnchor)
591 QtGui.QTextCursor.KeepAnchor)
586 cursor.insertText(items[0])
592 cursor.insertText(items[0])
587 elif len(items) > 1:
593 elif len(items) > 1:
588 if self.gui_completion:
594 if self.gui_completion:
589 self._completion_widget.show_items(cursor, items)
595 self._completion_widget.show_items(cursor, items)
590 else:
596 else:
591 text = '\n'.join(items) + '\n'
597 text = '\n'.join(items) + '\n'
592 self._write_text_keeping_prompt(text)
598 self._write_text_keeping_prompt(text)
593
599
594 def _get_end_cursor(self):
600 def _get_end_cursor(self):
595 """ Convenience method that returns a cursor for the last character.
601 """ Convenience method that returns a cursor for the last character.
596 """
602 """
597 cursor = self.textCursor()
603 cursor = self.textCursor()
598 cursor.movePosition(QtGui.QTextCursor.End)
604 cursor.movePosition(QtGui.QTextCursor.End)
599 return cursor
605 return cursor
600
606
601 def _get_prompt_cursor(self):
607 def _get_prompt_cursor(self):
602 """ Convenience method that returns a cursor for the prompt position.
608 """ Convenience method that returns a cursor for the prompt position.
603 """
609 """
604 cursor = self.textCursor()
610 cursor = self.textCursor()
605 cursor.setPosition(self._prompt_pos)
611 cursor.setPosition(self._prompt_pos)
606 return cursor
612 return cursor
607
613
608 def _get_selection_cursor(self, start, end):
614 def _get_selection_cursor(self, start, end):
609 """ Convenience method that returns a cursor with text selected between
615 """ Convenience method that returns a cursor with text selected between
610 the positions 'start' and 'end'.
616 the positions 'start' and 'end'.
611 """
617 """
612 cursor = self.textCursor()
618 cursor = self.textCursor()
613 cursor.setPosition(start)
619 cursor.setPosition(start)
614 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
620 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
615 return cursor
621 return cursor
616
622
617 def _get_word_start_cursor(self, position):
623 def _get_word_start_cursor(self, position):
618 """ Find the start of the word to the left the given position. If a
624 """ Find the start of the word to the left the given position. If a
619 sequence of non-word characters precedes the first word, skip over
625 sequence of non-word characters precedes the first word, skip over
620 them. (This emulates the behavior of bash, emacs, etc.)
626 them. (This emulates the behavior of bash, emacs, etc.)
621 """
627 """
622 document = self.document()
628 document = self.document()
623 position -= 1
629 position -= 1
624 while self._in_buffer(position) and \
630 while self._in_buffer(position) and \
625 not document.characterAt(position).isLetterOrNumber():
631 not document.characterAt(position).isLetterOrNumber():
626 position -= 1
632 position -= 1
627 while self._in_buffer(position) and \
633 while self._in_buffer(position) and \
628 document.characterAt(position).isLetterOrNumber():
634 document.characterAt(position).isLetterOrNumber():
629 position -= 1
635 position -= 1
630 cursor = self.textCursor()
636 cursor = self.textCursor()
631 cursor.setPosition(position + 1)
637 cursor.setPosition(position + 1)
632 return cursor
638 return cursor
633
639
634 def _get_word_end_cursor(self, position):
640 def _get_word_end_cursor(self, position):
635 """ Find the end of the word to the right the given position. If a
641 """ Find the end of the word to the right the given position. If a
636 sequence of non-word characters precedes the first word, skip over
642 sequence of non-word characters precedes the first word, skip over
637 them. (This emulates the behavior of bash, emacs, etc.)
643 them. (This emulates the behavior of bash, emacs, etc.)
638 """
644 """
639 document = self.document()
645 document = self.document()
640 end = self._get_end_cursor().position()
646 end = self._get_end_cursor().position()
641 while position < end and \
647 while position < end and \
642 not document.characterAt(position).isLetterOrNumber():
648 not document.characterAt(position).isLetterOrNumber():
643 position += 1
649 position += 1
644 while position < end and \
650 while position < end and \
645 document.characterAt(position).isLetterOrNumber():
651 document.characterAt(position).isLetterOrNumber():
646 position += 1
652 position += 1
647 cursor = self.textCursor()
653 cursor = self.textCursor()
648 cursor.setPosition(position)
654 cursor.setPosition(position)
649 return cursor
655 return cursor
650
656
651 def _prompt_started(self):
657 def _prompt_started(self):
652 """ Called immediately after a new prompt is displayed.
658 """ Called immediately after a new prompt is displayed.
653 """
659 """
654 # Temporarily disable the maximum block count to permit undo/redo and
660 # Temporarily disable the maximum block count to permit undo/redo and
655 # to ensure that the prompt position does not change due to truncation.
661 # to ensure that the prompt position does not change due to truncation.
656 self.setMaximumBlockCount(0)
662 self.setMaximumBlockCount(0)
657 self.setUndoRedoEnabled(True)
663 self.setUndoRedoEnabled(True)
658
664
659 self.setReadOnly(False)
665 self.setReadOnly(False)
660 self.moveCursor(QtGui.QTextCursor.End)
666 self.moveCursor(QtGui.QTextCursor.End)
661 self.centerCursor()
667 self.centerCursor()
662
668
663 self._executing = False
669 self._executing = False
664 self._prompt_started_hook()
670 self._prompt_started_hook()
665
671
666 def _prompt_finished(self):
672 def _prompt_finished(self):
667 """ Called immediately after a prompt is finished, i.e. when some input
673 """ Called immediately after a prompt is finished, i.e. when some input
668 will be processed and a new prompt displayed.
674 will be processed and a new prompt displayed.
669 """
675 """
670 self.setUndoRedoEnabled(False)
676 self.setUndoRedoEnabled(False)
671 self.setReadOnly(True)
677 self.setReadOnly(True)
672 self._prompt_finished_hook()
678 self._prompt_finished_hook()
673
679
674 def _set_position(self, position):
680 def _set_position(self, position):
675 """ Convenience method to set the position of the cursor.
681 """ Convenience method to set the position of the cursor.
676 """
682 """
677 cursor = self.textCursor()
683 cursor = self.textCursor()
678 cursor.setPosition(position)
684 cursor.setPosition(position)
679 self.setTextCursor(cursor)
685 self.setTextCursor(cursor)
680
686
681 def _set_selection(self, start, end):
687 def _set_selection(self, start, end):
682 """ Convenience method to set the current selected text.
688 """ Convenience method to set the current selected text.
683 """
689 """
684 self.setTextCursor(self._get_selection_cursor(start, end))
690 self.setTextCursor(self._get_selection_cursor(start, end))
685
691
686 def _show_prompt(self, prompt=None):
692 def _show_prompt(self, prompt=None):
687 """ Writes a new prompt at the end of the buffer. If 'prompt' is not
693 """ Writes a new prompt at the end of the buffer. If 'prompt' is not
688 specified, uses the previous prompt.
694 specified, uses the previous prompt.
689 """
695 """
690 if prompt is not None:
696 if prompt is not None:
691 self._prompt = prompt
697 self._prompt = prompt
692 self.appendPlainText('\n' + self._prompt)
698 self.appendPlainText('\n' + self._prompt)
693 self._prompt_pos = self._get_end_cursor().position()
699 self._prompt_pos = self._get_end_cursor().position()
694 self._prompt_started()
700 self._prompt_started()
695
701
696 def _show_continuation_prompt(self):
702 def _show_continuation_prompt(self):
697 """ Writes a new continuation prompt at the end of the buffer.
703 """ Writes a new continuation prompt at the end of the buffer.
698 """
704 """
699 self.appendPlainText(self._continuation_prompt)
705 self.appendPlainText(self._continuation_prompt)
700 self._prompt_started()
706 self._prompt_started()
701
707
702 def _write_text_keeping_prompt(self, text):
708 def _write_text_keeping_prompt(self, text):
703 """ Writes 'text' after the current prompt, then restores the old prompt
709 """ Writes 'text' after the current prompt, then restores the old prompt
704 with its old input buffer.
710 with its old input buffer.
705 """
711 """
706 input_buffer = self.input_buffer
712 input_buffer = self.input_buffer
707 self.appendPlainText('\n')
713 self.appendPlainText('\n')
708 self._prompt_finished()
714 self._prompt_finished()
709
715
710 self.appendPlainText(text)
716 self.appendPlainText(text)
711 self._show_prompt()
717 self._show_prompt()
712 self.input_buffer = input_buffer
718 self.input_buffer = input_buffer
713
719
714 def _in_buffer(self, position):
720 def _in_buffer(self, position):
715 """ Returns whether the given position is inside the editing region.
721 """ Returns whether the given position is inside the editing region.
716 """
722 """
717 return position >= self._prompt_pos
723 return position >= self._prompt_pos
718
724
719 def _keep_cursor_in_buffer(self):
725 def _keep_cursor_in_buffer(self):
720 """ Ensures that the cursor is inside the editing region. Returns
726 """ Ensures that the cursor is inside the editing region. Returns
721 whether the cursor was moved.
727 whether the cursor was moved.
722 """
728 """
723 cursor = self.textCursor()
729 cursor = self.textCursor()
724 if cursor.position() < self._prompt_pos:
730 if cursor.position() < self._prompt_pos:
725 cursor.movePosition(QtGui.QTextCursor.End)
731 cursor.movePosition(QtGui.QTextCursor.End)
726 self.setTextCursor(cursor)
732 self.setTextCursor(cursor)
727 return True
733 return True
728 else:
734 else:
729 return False
735 return False
730
736
731
737
732 class HistoryConsoleWidget(ConsoleWidget):
738 class HistoryConsoleWidget(ConsoleWidget):
733 """ A ConsoleWidget that keeps a history of the commands that have been
739 """ A ConsoleWidget that keeps a history of the commands that have been
734 executed.
740 executed.
735 """
741 """
736
742
737 #---------------------------------------------------------------------------
743 #---------------------------------------------------------------------------
738 # 'QObject' interface
744 # 'QObject' interface
739 #---------------------------------------------------------------------------
745 #---------------------------------------------------------------------------
740
746
741 def __init__(self, parent=None):
747 def __init__(self, parent=None):
742 super(HistoryConsoleWidget, self).__init__(parent)
748 super(HistoryConsoleWidget, self).__init__(parent)
743
749
744 self._history = []
750 self._history = []
745 self._history_index = 0
751 self._history_index = 0
746
752
747 #---------------------------------------------------------------------------
753 #---------------------------------------------------------------------------
748 # 'ConsoleWidget' public interface
754 # 'ConsoleWidget' public interface
749 #---------------------------------------------------------------------------
755 #---------------------------------------------------------------------------
750
756
751 def execute(self, source=None, hidden=False, interactive=False):
757 def execute(self, source=None, hidden=False, interactive=False):
752 """ Reimplemented to the store history.
758 """ Reimplemented to the store history.
753 """
759 """
754 if source is None and not hidden:
760 if not hidden:
755 history = self.input_buffer.rstrip()
761 history = self.input_buffer if source is None else source
756
762
757 executed = super(HistoryConsoleWidget, self).execute(
763 executed = super(HistoryConsoleWidget, self).execute(
758 source, hidden, interactive)
764 source, hidden, interactive)
759
765
760 if executed and not hidden:
766 if executed and not hidden:
761 self._history.append(history)
767 self._history.append(history.rstrip())
762 self._history_index = len(self._history)
768 self._history_index = len(self._history)
763
769
764 return executed
770 return executed
765
771
766 #---------------------------------------------------------------------------
772 #---------------------------------------------------------------------------
767 # 'ConsoleWidget' abstract interface
773 # 'ConsoleWidget' abstract interface
768 #---------------------------------------------------------------------------
774 #---------------------------------------------------------------------------
769
775
770 def _up_pressed(self):
776 def _up_pressed(self):
771 """ Called when the up key is pressed. Returns whether to continue
777 """ Called when the up key is pressed. Returns whether to continue
772 processing the event.
778 processing the event.
773 """
779 """
774 prompt_cursor = self._get_prompt_cursor()
780 prompt_cursor = self._get_prompt_cursor()
775 if self.textCursor().blockNumber() == prompt_cursor.blockNumber():
781 if self.textCursor().blockNumber() == prompt_cursor.blockNumber():
776 self.history_previous()
782 self.history_previous()
777
783
778 # Go to the first line of prompt for seemless history scrolling.
784 # Go to the first line of prompt for seemless history scrolling.
779 cursor = self._get_prompt_cursor()
785 cursor = self._get_prompt_cursor()
780 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
786 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
781 self.setTextCursor(cursor)
787 self.setTextCursor(cursor)
782
788
783 return False
789 return False
784 return True
790 return True
785
791
786 def _down_pressed(self):
792 def _down_pressed(self):
787 """ Called when the down key is pressed. Returns whether to continue
793 """ Called when the down key is pressed. Returns whether to continue
788 processing the event.
794 processing the event.
789 """
795 """
790 end_cursor = self._get_end_cursor()
796 end_cursor = self._get_end_cursor()
791 if self.textCursor().blockNumber() == end_cursor.blockNumber():
797 if self.textCursor().blockNumber() == end_cursor.blockNumber():
792 self.history_next()
798 self.history_next()
793 return False
799 return False
794 return True
800 return True
795
801
796 #---------------------------------------------------------------------------
802 #---------------------------------------------------------------------------
797 # 'HistoryConsoleWidget' interface
803 # 'HistoryConsoleWidget' interface
798 #---------------------------------------------------------------------------
804 #---------------------------------------------------------------------------
799
805
800 def history_previous(self):
806 def history_previous(self):
801 """ If possible, set the input buffer to the previous item in the
807 """ If possible, set the input buffer to the previous item in the
802 history.
808 history.
803 """
809 """
804 if self._history_index > 0:
810 if self._history_index > 0:
805 self._history_index -= 1
811 self._history_index -= 1
806 self.input_buffer = self._history[self._history_index]
812 self.input_buffer = self._history[self._history_index]
807
813
808 def history_next(self):
814 def history_next(self):
809 """ Set the input buffer to the next item in the history, or a blank
815 """ Set the input buffer to the next item in the history, or a blank
810 line if there is no subsequent item.
816 line if there is no subsequent item.
811 """
817 """
812 if self._history_index < len(self._history):
818 if self._history_index < len(self._history):
813 self._history_index += 1
819 self._history_index += 1
814 if self._history_index < len(self._history):
820 if self._history_index < len(self._history):
815 self.input_buffer = self._history[self._history_index]
821 self.input_buffer = self._history[self._history_index]
816 else:
822 else:
817 self.input_buffer = ''
823 self.input_buffer = ''
@@ -1,107 +1,105 b''
1 # System library imports
1 # System library imports
2 from PyQt4 import QtCore, QtGui
2 from PyQt4 import QtCore, QtGui
3
3
4 # Local imports
4 # Local imports
5 from frontend_widget import FrontendWidget
5 from frontend_widget import FrontendWidget
6
6
7
7
8 class IPythonWidget(FrontendWidget):
8 class IPythonWidget(FrontendWidget):
9 """ A FrontendWidget for an IPython kernel.
9 """ A FrontendWidget for an IPython kernel.
10 """
10 """
11
11
12 #---------------------------------------------------------------------------
12 #---------------------------------------------------------------------------
13 # 'QObject' interface
13 # 'QObject' interface
14 #---------------------------------------------------------------------------
14 #---------------------------------------------------------------------------
15
15
16 def __init__(self, parent=None):
16 def __init__(self, parent=None):
17 super(IPythonWidget, self).__init__(parent)
17 super(IPythonWidget, self).__init__(parent)
18
18
19 self._magic_overrides = {}
19 self._magic_overrides = {}
20
20
21 #---------------------------------------------------------------------------
21 #---------------------------------------------------------------------------
22 # 'ConsoleWidget' abstract interface
22 # 'ConsoleWidget' abstract interface
23 #---------------------------------------------------------------------------
23 #---------------------------------------------------------------------------
24
24
25 def _execute(self, source, hidden):
25 def _execute(self, source, hidden):
26 """ Reimplemented to override magic commands.
26 """ Reimplemented to override magic commands.
27 """
27 """
28 magic_source = source.strip()
28 magic_source = source.strip()
29 if magic_source.startswith('%'):
29 if magic_source.startswith('%'):
30 magic_source = magic_source[1:]
30 magic_source = magic_source[1:]
31 magic, sep, arguments = magic_source.partition(' ')
31 magic, sep, arguments = magic_source.partition(' ')
32 if not magic:
32 if not magic:
33 magic = magic_source
33 magic = magic_source
34
34
35 callback = self._magic_overrides.get(magic)
35 callback = self._magic_overrides.get(magic)
36 if callback:
36 if callback:
37 output = callback(arguments)
37 output = callback(arguments)
38 if output:
38 if output:
39 self.appendPlainText(output)
39 self.appendPlainText(output)
40 self._show_prompt()
40 self._show_prompt()
41 else:
41 else:
42 super(IPythonWidget, self)._execute(source, hidden)
42 super(IPythonWidget, self)._execute(source, hidden)
43
43
44 #---------------------------------------------------------------------------
44 #---------------------------------------------------------------------------
45 # 'FrontendWidget' interface
45 # 'FrontendWidget' interface
46 #---------------------------------------------------------------------------
46 #---------------------------------------------------------------------------
47
47
48 def execute_file(self, path, hidden=False):
48 def execute_file(self, path, hidden=False):
49 """ Reimplemented to use the 'run' magic.
49 """ Reimplemented to use the 'run' magic.
50 """
50 """
51 self.execute('run %s' % path, hidden=hidden)
51 self.execute('run %s' % path, hidden=hidden)
52
52
53 #---------------------------------------------------------------------------
53 #---------------------------------------------------------------------------
54 # 'IPythonWidget' interface
54 # 'IPythonWidget' interface
55 #---------------------------------------------------------------------------
55 #---------------------------------------------------------------------------
56
56
57 def set_magic_override(self, magic, callback):
57 def set_magic_override(self, magic, callback):
58 """ Overrides an IPython magic command. This magic will be intercepted
58 """ Overrides an IPython magic command. This magic will be intercepted
59 by the frontend rather than passed on to the kernel and 'callback'
59 by the frontend rather than passed on to the kernel and 'callback'
60 will be called with a single argument: a string of argument(s) for
60 will be called with a single argument: a string of argument(s) for
61 the magic. The callback can (optionally) return text to print to the
61 the magic. The callback can (optionally) return text to print to the
62 console.
62 console.
63 """
63 """
64 self._magic_overrides[magic] = callback
64 self._magic_overrides[magic] = callback
65
65
66 def remove_magic_override(self, magic):
66 def remove_magic_override(self, magic):
67 """ Removes the override for the specified magic, if there is one.
67 """ Removes the override for the specified magic, if there is one.
68 """
68 """
69 try:
69 try:
70 del self._magic_overrides[magic]
70 del self._magic_overrides[magic]
71 except KeyError:
71 except KeyError:
72 pass
72 pass
73
73
74
74
75 if __name__ == '__main__':
75 if __name__ == '__main__':
76 import signal
76 import signal
77 from IPython.frontend.qt.kernelmanager import QtKernelManager
77 from IPython.frontend.qt.kernelmanager import QtKernelManager
78
78
79 # Create a KernelManager.
79 # Create a KernelManager.
80 kernel_manager = QtKernelManager()
80 kernel_manager = QtKernelManager()
81 kernel_manager.start_kernel()
81 kernel_manager.start_kernel()
82 kernel_manager.start_listening()
82 kernel_manager.start_listening()
83
83
84 # Don't let Qt or ZMQ swallow KeyboardInterupts.
84 # Don't let Qt or ZMQ swallow KeyboardInterupts.
85 # FIXME: Gah, ZMQ swallows even custom signal handlers. So for now we leave
85 # FIXME: Gah, ZMQ swallows even custom signal handlers. So for now we leave
86 # behind a kernel process when Ctrl-C is pressed.
86 # behind a kernel process when Ctrl-C is pressed.
87 #def sigint_hook(signum, frame):
87 #def sigint_hook(signum, frame):
88 # QtGui.qApp.quit()
88 # QtGui.qApp.quit()
89 #signal.signal(signal.SIGINT, sigint_hook)
89 #signal.signal(signal.SIGINT, sigint_hook)
90 signal.signal(signal.SIGINT, signal.SIG_DFL)
90 signal.signal(signal.SIGINT, signal.SIG_DFL)
91
91
92 # Create the application, making sure to clean up nicely when we exit.
92 # Create the application, making sure to clean up nicely when we exit.
93 app = QtGui.QApplication([])
93 app = QtGui.QApplication([])
94 def quit_hook():
94 def quit_hook():
95 kernel_manager.stop_listening()
95 kernel_manager.stop_listening()
96 kernel_manager.kill_kernel()
96 kernel_manager.kill_kernel()
97 app.aboutToQuit.connect(quit_hook)
97 app.aboutToQuit.connect(quit_hook)
98
98
99 # Launch the application.
99 # Launch the application.
100 widget = IPythonWidget()
100 widget = IPythonWidget()
101 widget.kernel_manager = kernel_manager
101 widget.kernel_manager = kernel_manager
102 widget.setWindowTitle('Python')
102 widget.setWindowTitle('Python')
103 widget.resize(640, 480)
103 widget.resize(640, 480)
104 widget.show()
104 widget.show()
105 app.exec_()
105 app.exec_()
106
107
General Comments 0
You need to be logged in to leave comments. Login now