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