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