##// END OF EJS Templates
Fixed minor bug in last checkin.
epatters -
Show More
@@ -1,1295 +1,1293 b''
1 # Standard library imports
1 # Standard library imports
2 import re
2 import re
3 import sys
3 import sys
4 from textwrap import dedent
4 from textwrap import dedent
5
5
6 # System library imports
6 # System library imports
7 from PyQt4 import QtCore, QtGui
7 from PyQt4 import QtCore, QtGui
8
8
9 # Local imports
9 # Local imports
10 from ansi_code_processor import QtAnsiCodeProcessor
10 from ansi_code_processor import QtAnsiCodeProcessor
11 from completion_widget import CompletionWidget
11 from completion_widget import CompletionWidget
12
12
13
13
14 class ConsoleWidget(QtGui.QWidget):
14 class ConsoleWidget(QtGui.QWidget):
15 """ An abstract base class for console-type widgets. This class has
15 """ An abstract base class for console-type widgets. This class has
16 functionality for:
16 functionality for:
17
17
18 * Maintaining a prompt and editing region
18 * Maintaining a prompt and editing region
19 * Providing the traditional Unix-style console keyboard shortcuts
19 * Providing the traditional Unix-style console keyboard shortcuts
20 * Performing tab completion
20 * Performing tab completion
21 * Paging text
21 * Paging text
22 * Handling ANSI escape codes
22 * Handling ANSI escape codes
23
23
24 ConsoleWidget also provides a number of utility methods that will be
24 ConsoleWidget also provides a number of utility methods that will be
25 convenient to implementors of a console-style widget.
25 convenient to implementors of a console-style widget.
26 """
26 """
27
27
28 # Whether to process ANSI escape codes.
28 # Whether to process ANSI escape codes.
29 ansi_codes = True
29 ansi_codes = True
30
30
31 # The maximum number of lines of text before truncation.
31 # The maximum number of lines of text before truncation.
32 buffer_size = 500
32 buffer_size = 500
33
33
34 # Whether to use a list widget or plain text output for tab completion.
34 # Whether to use a list widget or plain text output for tab completion.
35 gui_completion = True
35 gui_completion = True
36
36
37 # Whether to override ShortcutEvents for the keybindings defined by this
37 # Whether to override ShortcutEvents for the keybindings defined by this
38 # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
38 # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
39 # priority (when it has focus) over, e.g., window-level menu shortcuts.
39 # priority (when it has focus) over, e.g., window-level menu shortcuts.
40 override_shortcuts = False
40 override_shortcuts = False
41
41
42 # Signals that indicate ConsoleWidget state.
42 # Signals that indicate ConsoleWidget state.
43 copy_available = QtCore.pyqtSignal(bool)
43 copy_available = QtCore.pyqtSignal(bool)
44 redo_available = QtCore.pyqtSignal(bool)
44 redo_available = QtCore.pyqtSignal(bool)
45 undo_available = QtCore.pyqtSignal(bool)
45 undo_available = QtCore.pyqtSignal(bool)
46
46
47 # Signal emitted when paging is needed and the paging style has been
47 # Signal emitted when paging is needed and the paging style has been
48 # specified as 'custom'.
48 # specified as 'custom'.
49 custom_page_requested = QtCore.pyqtSignal(object)
49 custom_page_requested = QtCore.pyqtSignal(object)
50
50
51 # Protected class variables.
51 # Protected class variables.
52 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
52 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
53 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
53 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
54 QtCore.Qt.Key_A : QtCore.Qt.Key_Home,
54 QtCore.Qt.Key_A : QtCore.Qt.Key_Home,
55 QtCore.Qt.Key_E : QtCore.Qt.Key_End,
55 QtCore.Qt.Key_E : QtCore.Qt.Key_End,
56 QtCore.Qt.Key_P : QtCore.Qt.Key_Up,
56 QtCore.Qt.Key_P : QtCore.Qt.Key_Up,
57 QtCore.Qt.Key_N : QtCore.Qt.Key_Down,
57 QtCore.Qt.Key_N : QtCore.Qt.Key_Down,
58 QtCore.Qt.Key_D : QtCore.Qt.Key_Delete, }
58 QtCore.Qt.Key_D : QtCore.Qt.Key_Delete, }
59 _shortcuts = set(_ctrl_down_remap.keys() +
59 _shortcuts = set(_ctrl_down_remap.keys() +
60 [ QtCore.Qt.Key_C, QtCore.Qt.Key_V ])
60 [ QtCore.Qt.Key_C, QtCore.Qt.Key_V ])
61
61
62 #---------------------------------------------------------------------------
62 #---------------------------------------------------------------------------
63 # 'QObject' interface
63 # 'QObject' interface
64 #---------------------------------------------------------------------------
64 #---------------------------------------------------------------------------
65
65
66 def __init__(self, kind='plain', paging='inside', parent=None):
66 def __init__(self, kind='plain', paging='inside', parent=None):
67 """ Create a ConsoleWidget.
67 """ Create a ConsoleWidget.
68
68
69 Parameters
69 Parameters
70 ----------
70 ----------
71 kind : str, optional [default 'plain']
71 kind : str, optional [default 'plain']
72 The type of underlying text widget to use. Valid values are 'plain',
72 The type of underlying text widget to use. Valid values are 'plain',
73 which specifies a QPlainTextEdit, and 'rich', which specifies a
73 which specifies a QPlainTextEdit, and 'rich', which specifies a
74 QTextEdit.
74 QTextEdit.
75
75
76 paging : str, optional [default 'inside']
76 paging : str, optional [default 'inside']
77 The type of paging to use. Valid values are:
77 The type of paging to use. Valid values are:
78 'inside' : The widget pages like a traditional terminal pager.
78 'inside' : The widget pages like a traditional terminal pager.
79 'hsplit' : When paging is requested, the widget is split
79 'hsplit' : When paging is requested, the widget is split
80 horizontally. The top pane contains the console,
80 horizontally. The top pane contains the console,
81 and the bottom pane contains the paged text.
81 and the bottom pane contains the paged text.
82 'vsplit' : Similar to 'hsplit', except that a vertical splitter
82 'vsplit' : Similar to 'hsplit', except that a vertical splitter
83 used.
83 used.
84 'custom' : No action is taken by the widget beyond emitting a
84 'custom' : No action is taken by the widget beyond emitting a
85 'custom_page_requested(str)' signal.
85 'custom_page_requested(str)' signal.
86 'none' : The text is written directly to the console.
86 'none' : The text is written directly to the console.
87
87
88 parent : QWidget, optional [default None]
88 parent : QWidget, optional [default None]
89 The parent for this widget.
89 The parent for this widget.
90 """
90 """
91 super(ConsoleWidget, self).__init__(parent)
91 super(ConsoleWidget, self).__init__(parent)
92
92
93 # Create the layout and underlying text widget.
93 # Create the layout and underlying text widget.
94 layout = QtGui.QStackedLayout(self)
94 layout = QtGui.QStackedLayout(self)
95 layout.setMargin(0)
95 layout.setMargin(0)
96 self._control = self._create_control(kind)
96 self._control = self._create_control(kind)
97 self._page_control = None
97 self._page_control = None
98 self._splitter = None
98 self._splitter = None
99 if paging in ('hsplit', 'vsplit'):
99 if paging in ('hsplit', 'vsplit'):
100 self._splitter = QtGui.QSplitter()
100 self._splitter = QtGui.QSplitter()
101 if paging == 'hsplit':
101 if paging == 'hsplit':
102 self._splitter.setOrientation(QtCore.Qt.Horizontal)
102 self._splitter.setOrientation(QtCore.Qt.Horizontal)
103 else:
103 else:
104 self._splitter.setOrientation(QtCore.Qt.Vertical)
104 self._splitter.setOrientation(QtCore.Qt.Vertical)
105 self._splitter.addWidget(self._control)
105 self._splitter.addWidget(self._control)
106 layout.addWidget(self._splitter)
106 layout.addWidget(self._splitter)
107 else:
107 else:
108 layout.addWidget(self._control)
108 layout.addWidget(self._control)
109
109
110 # Create the paging widget, if necessary.
110 # Create the paging widget, if necessary.
111 self._page_style = paging
111 self._page_style = paging
112 if paging in ('inside', 'hsplit', 'vsplit'):
112 if paging in ('inside', 'hsplit', 'vsplit'):
113 self._page_control = self._create_page_control()
113 self._page_control = self._create_page_control()
114 if self._splitter:
114 if self._splitter:
115 self._page_control.hide()
115 self._page_control.hide()
116 self._splitter.addWidget(self._page_control)
116 self._splitter.addWidget(self._page_control)
117 else:
117 else:
118 layout.addWidget(self._page_control)
118 layout.addWidget(self._page_control)
119 elif paging not in ('custom', 'none'):
119 elif paging not in ('custom', 'none'):
120 raise ValueError('Paging style %s unknown.' % repr(paging))
120 raise ValueError('Paging style %s unknown.' % repr(paging))
121
121
122 # Initialize protected variables. Some variables contain useful state
122 # Initialize protected variables. Some variables contain useful state
123 # information for subclasses; they should be considered read-only.
123 # information for subclasses; they should be considered read-only.
124 self._ansi_processor = QtAnsiCodeProcessor()
124 self._ansi_processor = QtAnsiCodeProcessor()
125 self._completion_widget = CompletionWidget(self._control)
125 self._completion_widget = CompletionWidget(self._control)
126 self._continuation_prompt = '> '
126 self._continuation_prompt = '> '
127 self._continuation_prompt_html = None
127 self._continuation_prompt_html = None
128 self._executing = False
128 self._executing = False
129 self._prompt = ''
129 self._prompt = ''
130 self._prompt_html = None
130 self._prompt_html = None
131 self._prompt_pos = 0
131 self._prompt_pos = 0
132 self._reading = False
132 self._reading = False
133 self._reading_callback = None
133 self._reading_callback = None
134 self._tab_width = 8
134 self._tab_width = 8
135
135
136 # Set a monospaced font.
136 # Set a monospaced font.
137 self.reset_font()
137 self.reset_font()
138
138
139 def eventFilter(self, obj, event):
139 def eventFilter(self, obj, event):
140 """ Reimplemented to ensure a console-like behavior in the underlying
140 """ Reimplemented to ensure a console-like behavior in the underlying
141 text widget.
141 text widget.
142 """
142 """
143 # Re-map keys for all filtered widgets.
143 # Re-map keys for all filtered widgets.
144 etype = event.type()
144 etype = event.type()
145 if etype == QtCore.QEvent.KeyPress and \
145 if etype == QtCore.QEvent.KeyPress and \
146 self._control_key_down(event.modifiers()) and \
146 self._control_key_down(event.modifiers()) and \
147 event.key() in self._ctrl_down_remap:
147 event.key() in self._ctrl_down_remap:
148 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
148 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
149 self._ctrl_down_remap[event.key()],
149 self._ctrl_down_remap[event.key()],
150 QtCore.Qt.NoModifier)
150 QtCore.Qt.NoModifier)
151 QtGui.qApp.sendEvent(obj, new_event)
151 QtGui.qApp.sendEvent(obj, new_event)
152 return True
152 return True
153
153
154 # Override shortucts for all filtered widgets. Note that on Mac OS it is
154 # Override shortucts for all filtered widgets. Note that on Mac OS it is
155 # always unnecessary to override shortcuts, hence the check below (users
155 # always unnecessary to override shortcuts, hence the check below (users
156 # should just use the Control key instead of the Command key).
156 # should just use the Control key instead of the Command key).
157 elif etype == QtCore.QEvent.ShortcutOverride and \
157 elif etype == QtCore.QEvent.ShortcutOverride and \
158 sys.platform != 'darwin' and \
158 sys.platform != 'darwin' and \
159 self._control_key_down(event.modifiers()) and \
159 self._control_key_down(event.modifiers()) and \
160 event.key() in self._shortcuts:
160 event.key() in self._shortcuts:
161 event.accept()
161 event.accept()
162 return False
162 return False
163
163
164 elif obj == self._control:
164 elif obj == self._control:
165 # Disable moving text by drag and drop.
165 # Disable moving text by drag and drop.
166 if etype == QtCore.QEvent.DragMove:
166 if etype == QtCore.QEvent.DragMove:
167 return True
167 return True
168
168
169 elif etype == QtCore.QEvent.KeyPress:
169 elif etype == QtCore.QEvent.KeyPress:
170 return self._event_filter_console_keypress(event)
170 return self._event_filter_console_keypress(event)
171
171
172 elif obj == self._page_control:
172 elif obj == self._page_control:
173 if etype == QtCore.QEvent.KeyPress:
173 if etype == QtCore.QEvent.KeyPress:
174 return self._event_filter_page_keypress(event)
174 return self._event_filter_page_keypress(event)
175
175
176 return super(ConsoleWidget, self).eventFilter(obj, event)
176 return super(ConsoleWidget, self).eventFilter(obj, event)
177
177
178 #---------------------------------------------------------------------------
178 #---------------------------------------------------------------------------
179 # 'QWidget' interface
179 # 'QWidget' interface
180 #---------------------------------------------------------------------------
180 #---------------------------------------------------------------------------
181
181
182 def sizeHint(self):
182 def sizeHint(self):
183 """ Reimplemented to suggest a size that is 80 characters wide and
183 """ Reimplemented to suggest a size that is 80 characters wide and
184 25 lines high.
184 25 lines high.
185 """
185 """
186 style = self.style()
186 style = self.style()
187 opt = QtGui.QStyleOptionHeader()
187 opt = QtGui.QStyleOptionHeader()
188 font_metrics = QtGui.QFontMetrics(self.font)
188 font_metrics = QtGui.QFontMetrics(self.font)
189 splitwidth = style.pixelMetric(QtGui.QStyle.PM_SplitterWidth, opt, self)
189 splitwidth = style.pixelMetric(QtGui.QStyle.PM_SplitterWidth, opt, self)
190
190
191 width = font_metrics.width(' ') * 80
191 width = font_metrics.width(' ') * 80
192 width += style.pixelMetric(QtGui.QStyle.PM_ScrollBarExtent, opt, self)
192 width += style.pixelMetric(QtGui.QStyle.PM_ScrollBarExtent, opt, self)
193 if self._page_style == 'hsplit':
193 if self._page_style == 'hsplit':
194 width = width * 2 + splitwidth
194 width = width * 2 + splitwidth
195
195
196 height = font_metrics.height() * 25
196 height = font_metrics.height() * 25
197 if self._page_style == 'vsplit':
197 if self._page_style == 'vsplit':
198 height = height * 2 + splitwidth
198 height = height * 2 + splitwidth
199
199
200 return QtCore.QSize(width, height)
200 return QtCore.QSize(width, height)
201
201
202 #---------------------------------------------------------------------------
202 #---------------------------------------------------------------------------
203 # 'ConsoleWidget' public interface
203 # 'ConsoleWidget' public interface
204 #---------------------------------------------------------------------------
204 #---------------------------------------------------------------------------
205
205
206 def can_paste(self):
206 def can_paste(self):
207 """ Returns whether text can be pasted from the clipboard.
207 """ Returns whether text can be pasted from the clipboard.
208 """
208 """
209 # Accept only text that can be ASCII encoded.
209 # Accept only text that can be ASCII encoded.
210 if self._control.textInteractionFlags() & QtCore.Qt.TextEditable:
210 if self._control.textInteractionFlags() & QtCore.Qt.TextEditable:
211 text = QtGui.QApplication.clipboard().text()
211 text = QtGui.QApplication.clipboard().text()
212 if not text.isEmpty():
212 if not text.isEmpty():
213 try:
213 try:
214 str(text)
214 str(text)
215 return True
215 return True
216 except UnicodeEncodeError:
216 except UnicodeEncodeError:
217 pass
217 pass
218 return False
218 return False
219
219
220 def clear(self, keep_input=True):
220 def clear(self, keep_input=True):
221 """ Clear the console, then write a new prompt. If 'keep_input' is set,
221 """ Clear the console, then write a new prompt. If 'keep_input' is set,
222 restores the old input buffer when the new prompt is written.
222 restores the old input buffer when the new prompt is written.
223 """
223 """
224 if keep_input:
224 if keep_input:
225 input_buffer = self.input_buffer
225 input_buffer = self.input_buffer
226 self._control.clear()
226 self._control.clear()
227 self._show_prompt()
227 self._show_prompt()
228 if keep_input:
228 if keep_input:
229 self.input_buffer = input_buffer
229 self.input_buffer = input_buffer
230
230
231 def copy(self):
231 def copy(self):
232 """ Copy the current selected text to the clipboard.
232 """ Copy the current selected text to the clipboard.
233 """
233 """
234 self._control.copy()
234 self._control.copy()
235
235
236 def execute(self, source=None, hidden=False, interactive=False):
236 def execute(self, source=None, hidden=False, interactive=False):
237 """ Executes source or the input buffer, possibly prompting for more
237 """ Executes source or the input buffer, possibly prompting for more
238 input.
238 input.
239
239
240 Parameters:
240 Parameters:
241 -----------
241 -----------
242 source : str, optional
242 source : str, optional
243
243
244 The source to execute. If not specified, the input buffer will be
244 The source to execute. If not specified, the input buffer will be
245 used. If specified and 'hidden' is False, the input buffer will be
245 used. If specified and 'hidden' is False, the input buffer will be
246 replaced with the source before execution.
246 replaced with the source before execution.
247
247
248 hidden : bool, optional (default False)
248 hidden : bool, optional (default False)
249
249
250 If set, no output will be shown and the prompt will not be modified.
250 If set, no output will be shown and the prompt will not be modified.
251 In other words, it will be completely invisible to the user that
251 In other words, it will be completely invisible to the user that
252 an execution has occurred.
252 an execution has occurred.
253
253
254 interactive : bool, optional (default False)
254 interactive : bool, optional (default False)
255
255
256 Whether the console is to treat the source as having been manually
256 Whether the console is to treat the source as having been manually
257 entered by the user. The effect of this parameter depends on the
257 entered by the user. The effect of this parameter depends on the
258 subclass implementation.
258 subclass implementation.
259
259
260 Raises:
260 Raises:
261 -------
261 -------
262 RuntimeError
262 RuntimeError
263 If incomplete input is given and 'hidden' is True. In this case,
263 If incomplete input is given and 'hidden' is True. In this case,
264 it is not possible to prompt for more input.
264 it is not possible to prompt for more input.
265
265
266 Returns:
266 Returns:
267 --------
267 --------
268 A boolean indicating whether the source was executed.
268 A boolean indicating whether the source was executed.
269 """
269 """
270 if not hidden:
270 if not hidden:
271 if source is not None:
271 if source is not None:
272 self.input_buffer = source
272 self.input_buffer = source
273
273
274 self._append_plain_text('\n')
274 self._append_plain_text('\n')
275 self._executing_input_buffer = self.input_buffer
275 self._executing_input_buffer = self.input_buffer
276 self._executing = True
276 self._executing = True
277 self._prompt_finished()
277 self._prompt_finished()
278
278
279 real_source = self.input_buffer if source is None else source
279 real_source = self.input_buffer if source is None else source
280 complete = self._is_complete(real_source, interactive)
280 complete = self._is_complete(real_source, interactive)
281 if complete:
281 if complete:
282 if not hidden:
282 if not hidden:
283 # The maximum block count is only in effect during execution.
283 # The maximum block count is only in effect during execution.
284 # This ensures that _prompt_pos does not become invalid due to
284 # This ensures that _prompt_pos does not become invalid due to
285 # text truncation.
285 # text truncation.
286 self._control.document().setMaximumBlockCount(self.buffer_size)
286 self._control.document().setMaximumBlockCount(self.buffer_size)
287 self._execute(real_source, hidden)
287 self._execute(real_source, hidden)
288 elif hidden:
288 elif hidden:
289 raise RuntimeError('Incomplete noninteractive input: "%s"' % source)
289 raise RuntimeError('Incomplete noninteractive input: "%s"' % source)
290 else:
290 else:
291 self._show_continuation_prompt()
291 self._show_continuation_prompt()
292
292
293 return complete
293 return complete
294
294
295 def _get_input_buffer(self):
295 def _get_input_buffer(self):
296 """ The text that the user has entered entered at the current prompt.
296 """ The text that the user has entered entered at the current prompt.
297 """
297 """
298 # If we're executing, the input buffer may not even exist anymore due to
298 # If we're executing, the input buffer may not even exist anymore due to
299 # the limit imposed by 'buffer_size'. Therefore, we store it.
299 # the limit imposed by 'buffer_size'. Therefore, we store it.
300 if self._executing:
300 if self._executing:
301 return self._executing_input_buffer
301 return self._executing_input_buffer
302
302
303 cursor = self._get_end_cursor()
303 cursor = self._get_end_cursor()
304 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
304 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
305 input_buffer = str(cursor.selection().toPlainText())
305 input_buffer = str(cursor.selection().toPlainText())
306
306
307 # Strip out continuation prompts.
307 # Strip out continuation prompts.
308 return input_buffer.replace('\n' + self._continuation_prompt, '\n')
308 return input_buffer.replace('\n' + self._continuation_prompt, '\n')
309
309
310 def _set_input_buffer(self, string):
310 def _set_input_buffer(self, string):
311 """ Replaces the text in the input buffer with 'string'.
311 """ Replaces the text in the input buffer with 'string'.
312 """
312 """
313 # For now, it is an error to modify the input buffer during execution.
313 # For now, it is an error to modify the input buffer during execution.
314 if self._executing:
314 if self._executing:
315 raise RuntimeError("Cannot change input buffer during execution.")
315 raise RuntimeError("Cannot change input buffer during execution.")
316
316
317 # Remove old text.
317 # Remove old text.
318 cursor = self._get_end_cursor()
318 cursor = self._get_end_cursor()
319 cursor.beginEditBlock()
319 cursor.beginEditBlock()
320 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
320 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
321 cursor.removeSelectedText()
321 cursor.removeSelectedText()
322
322
323 # Insert new text with continuation prompts.
323 # Insert new text with continuation prompts.
324 lines = string.splitlines(True)
324 lines = string.splitlines(True)
325 if lines:
325 if lines:
326 self._append_plain_text(lines[0])
326 self._append_plain_text(lines[0])
327 for i in xrange(1, len(lines)):
327 for i in xrange(1, len(lines)):
328 if self._continuation_prompt_html is None:
328 if self._continuation_prompt_html is None:
329 self._append_plain_text(self._continuation_prompt)
329 self._append_plain_text(self._continuation_prompt)
330 else:
330 else:
331 self._append_html(self._continuation_prompt_html)
331 self._append_html(self._continuation_prompt_html)
332 self._append_plain_text(lines[i])
332 self._append_plain_text(lines[i])
333 cursor.endEditBlock()
333 cursor.endEditBlock()
334 self._control.moveCursor(QtGui.QTextCursor.End)
334 self._control.moveCursor(QtGui.QTextCursor.End)
335
335
336 input_buffer = property(_get_input_buffer, _set_input_buffer)
336 input_buffer = property(_get_input_buffer, _set_input_buffer)
337
337
338 def _get_font(self):
338 def _get_font(self):
339 """ The base font being used by the ConsoleWidget.
339 """ The base font being used by the ConsoleWidget.
340 """
340 """
341 return self._control.document().defaultFont()
341 return self._control.document().defaultFont()
342
342
343 def _set_font(self, font):
343 def _set_font(self, font):
344 """ Sets the base font for the ConsoleWidget to the specified QFont.
344 """ Sets the base font for the ConsoleWidget to the specified QFont.
345 """
345 """
346 font_metrics = QtGui.QFontMetrics(font)
346 font_metrics = QtGui.QFontMetrics(font)
347 self._control.setTabStopWidth(self.tab_width * font_metrics.width(' '))
347 self._control.setTabStopWidth(self.tab_width * font_metrics.width(' '))
348
348
349 self._completion_widget.setFont(font)
349 self._completion_widget.setFont(font)
350 self._control.document().setDefaultFont(font)
350 self._control.document().setDefaultFont(font)
351 if self._page_control:
351 if self._page_control:
352 self._page_control.document().setDefaultFont(font)
352 self._page_control.document().setDefaultFont(font)
353
353
354 font = property(_get_font, _set_font)
354 font = property(_get_font, _set_font)
355
355
356 def paste(self):
356 def paste(self):
357 """ Paste the contents of the clipboard into the input region.
357 """ Paste the contents of the clipboard into the input region.
358 """
358 """
359 if self._control.textInteractionFlags() & QtCore.Qt.TextEditable:
359 if self._control.textInteractionFlags() & QtCore.Qt.TextEditable:
360 try:
360 try:
361 text = str(QtGui.QApplication.clipboard().text())
361 text = str(QtGui.QApplication.clipboard().text())
362 except UnicodeEncodeError:
362 except UnicodeEncodeError:
363 pass
363 pass
364 else:
364 else:
365 self._insert_plain_text_into_buffer(dedent(text))
365 self._insert_plain_text_into_buffer(dedent(text))
366
366
367 def print_(self, printer):
367 def print_(self, printer):
368 """ Print the contents of the ConsoleWidget to the specified QPrinter.
368 """ Print the contents of the ConsoleWidget to the specified QPrinter.
369 """
369 """
370 self._control.print_(printer)
370 self._control.print_(printer)
371
371
372 def redo(self):
372 def redo(self):
373 """ Redo the last operation. If there is no operation to redo, nothing
373 """ Redo the last operation. If there is no operation to redo, nothing
374 happens.
374 happens.
375 """
375 """
376 self._control.redo()
376 self._control.redo()
377
377
378 def reset_font(self):
378 def reset_font(self):
379 """ Sets the font to the default fixed-width font for this platform.
379 """ Sets the font to the default fixed-width font for this platform.
380 """
380 """
381 if sys.platform == 'win32':
381 if sys.platform == 'win32':
382 name = 'Courier'
382 name = 'Courier'
383 elif sys.platform == 'darwin':
383 elif sys.platform == 'darwin':
384 name = 'Monaco'
384 name = 'Monaco'
385 else:
385 else:
386 name = 'Monospace'
386 name = 'Monospace'
387 font = QtGui.QFont(name, QtGui.qApp.font().pointSize())
387 font = QtGui.QFont(name, QtGui.qApp.font().pointSize())
388 font.setStyleHint(QtGui.QFont.TypeWriter)
388 font.setStyleHint(QtGui.QFont.TypeWriter)
389 self._set_font(font)
389 self._set_font(font)
390
390
391 def select_all(self):
391 def select_all(self):
392 """ Selects all the text in the buffer.
392 """ Selects all the text in the buffer.
393 """
393 """
394 self._control.selectAll()
394 self._control.selectAll()
395
395
396 def _get_tab_width(self):
396 def _get_tab_width(self):
397 """ The width (in terms of space characters) for tab characters.
397 """ The width (in terms of space characters) for tab characters.
398 """
398 """
399 return self._tab_width
399 return self._tab_width
400
400
401 def _set_tab_width(self, tab_width):
401 def _set_tab_width(self, tab_width):
402 """ Sets the width (in terms of space characters) for tab characters.
402 """ Sets the width (in terms of space characters) for tab characters.
403 """
403 """
404 font_metrics = QtGui.QFontMetrics(self.font)
404 font_metrics = QtGui.QFontMetrics(self.font)
405 self._control.setTabStopWidth(tab_width * font_metrics.width(' '))
405 self._control.setTabStopWidth(tab_width * font_metrics.width(' '))
406
406
407 self._tab_width = tab_width
407 self._tab_width = tab_width
408
408
409 tab_width = property(_get_tab_width, _set_tab_width)
409 tab_width = property(_get_tab_width, _set_tab_width)
410
410
411 def undo(self):
411 def undo(self):
412 """ Undo the last operation. If there is no operation to undo, nothing
412 """ Undo the last operation. If there is no operation to undo, nothing
413 happens.
413 happens.
414 """
414 """
415 self._control.undo()
415 self._control.undo()
416
416
417 #---------------------------------------------------------------------------
417 #---------------------------------------------------------------------------
418 # 'ConsoleWidget' abstract interface
418 # 'ConsoleWidget' abstract interface
419 #---------------------------------------------------------------------------
419 #---------------------------------------------------------------------------
420
420
421 def _is_complete(self, source, interactive):
421 def _is_complete(self, source, interactive):
422 """ Returns whether 'source' can be executed. When triggered by an
422 """ Returns whether 'source' can be executed. When triggered by an
423 Enter/Return key press, 'interactive' is True; otherwise, it is
423 Enter/Return key press, 'interactive' is True; otherwise, it is
424 False.
424 False.
425 """
425 """
426 raise NotImplementedError
426 raise NotImplementedError
427
427
428 def _execute(self, source, hidden):
428 def _execute(self, source, hidden):
429 """ Execute 'source'. If 'hidden', do not show any output.
429 """ Execute 'source'. If 'hidden', do not show any output.
430 """
430 """
431 raise NotImplementedError
431 raise NotImplementedError
432
432
433 def _execute_interrupt(self):
433 def _execute_interrupt(self):
434 """ Attempts to stop execution. Returns whether this method has an
434 """ Attempts to stop execution. Returns whether this method has an
435 implementation.
435 implementation.
436 """
436 """
437 return False
437 return False
438
438
439 def _prompt_started_hook(self):
439 def _prompt_started_hook(self):
440 """ Called immediately after a new prompt is displayed.
440 """ Called immediately after a new prompt is displayed.
441 """
441 """
442 pass
442 pass
443
443
444 def _prompt_finished_hook(self):
444 def _prompt_finished_hook(self):
445 """ Called immediately after a prompt is finished, i.e. when some input
445 """ Called immediately after a prompt is finished, i.e. when some input
446 will be processed and a new prompt displayed.
446 will be processed and a new prompt displayed.
447 """
447 """
448 pass
448 pass
449
449
450 def _up_pressed(self):
450 def _up_pressed(self):
451 """ Called when the up key is pressed. Returns whether to continue
451 """ Called when the up key is pressed. Returns whether to continue
452 processing the event.
452 processing the event.
453 """
453 """
454 return True
454 return True
455
455
456 def _down_pressed(self):
456 def _down_pressed(self):
457 """ Called when the down key is pressed. Returns whether to continue
457 """ Called when the down key is pressed. Returns whether to continue
458 processing the event.
458 processing the event.
459 """
459 """
460 return True
460 return True
461
461
462 def _tab_pressed(self):
462 def _tab_pressed(self):
463 """ Called when the tab key is pressed. Returns whether to continue
463 """ Called when the tab key is pressed. Returns whether to continue
464 processing the event.
464 processing the event.
465 """
465 """
466 return False
466 return False
467
467
468 #--------------------------------------------------------------------------
468 #--------------------------------------------------------------------------
469 # 'ConsoleWidget' protected interface
469 # 'ConsoleWidget' protected interface
470 #--------------------------------------------------------------------------
470 #--------------------------------------------------------------------------
471
471
472 def _append_html(self, html):
472 def _append_html(self, html):
473 """ Appends html at the end of the console buffer.
473 """ Appends html at the end of the console buffer.
474 """
474 """
475 cursor = self._get_end_cursor()
475 cursor = self._get_end_cursor()
476 self._insert_html(cursor, html)
476 self._insert_html(cursor, html)
477
477
478 def _append_html_fetching_plain_text(self, html):
478 def _append_html_fetching_plain_text(self, html):
479 """ Appends 'html', then returns the plain text version of it.
479 """ Appends 'html', then returns the plain text version of it.
480 """
480 """
481 cursor = self._get_end_cursor()
481 cursor = self._get_end_cursor()
482 return self._insert_html_fetching_plain_text(cursor, html)
482 return self._insert_html_fetching_plain_text(cursor, html)
483
483
484 def _append_plain_text(self, text):
484 def _append_plain_text(self, text):
485 """ Appends plain text at the end of the console buffer, processing
485 """ Appends plain text at the end of the console buffer, processing
486 ANSI codes if enabled.
486 ANSI codes if enabled.
487 """
487 """
488 cursor = self._get_end_cursor()
488 cursor = self._get_end_cursor()
489 self._insert_plain_text(cursor, text)
489 self._insert_plain_text(cursor, text)
490
490
491 def _append_plain_text_keeping_prompt(self, text):
491 def _append_plain_text_keeping_prompt(self, text):
492 """ Writes 'text' after the current prompt, then restores the old prompt
492 """ Writes 'text' after the current prompt, then restores the old prompt
493 with its old input buffer.
493 with its old input buffer.
494 """
494 """
495 input_buffer = self.input_buffer
495 input_buffer = self.input_buffer
496 self._append_plain_text('\n')
496 self._append_plain_text('\n')
497 self._prompt_finished()
497 self._prompt_finished()
498
498
499 self._append_plain_text(text)
499 self._append_plain_text(text)
500 self._show_prompt()
500 self._show_prompt()
501 self.input_buffer = input_buffer
501 self.input_buffer = input_buffer
502
502
503 def _complete_with_items(self, cursor, items):
503 def _complete_with_items(self, cursor, items):
504 """ Performs completion with 'items' at the specified cursor location.
504 """ Performs completion with 'items' at the specified cursor location.
505 """
505 """
506 if len(items) == 1:
506 if len(items) == 1:
507 cursor.setPosition(self._control.textCursor().position(),
507 cursor.setPosition(self._control.textCursor().position(),
508 QtGui.QTextCursor.KeepAnchor)
508 QtGui.QTextCursor.KeepAnchor)
509 cursor.insertText(items[0])
509 cursor.insertText(items[0])
510 elif len(items) > 1:
510 elif len(items) > 1:
511 if self.gui_completion:
511 if self.gui_completion:
512 self._completion_widget.show_items(cursor, items)
512 self._completion_widget.show_items(cursor, items)
513 else:
513 else:
514 text = self._format_as_columns(items)
514 text = self._format_as_columns(items)
515 self._append_plain_text_keeping_prompt(text)
515 self._append_plain_text_keeping_prompt(text)
516
516
517 def _control_key_down(self, modifiers):
517 def _control_key_down(self, modifiers):
518 """ Given a KeyboardModifiers flags object, return whether the Control
518 """ Given a KeyboardModifiers flags object, return whether the Control
519 key is down (on Mac OS, treat the Command key as a synonym for
519 key is down (on Mac OS, treat the Command key as a synonym for
520 Control).
520 Control).
521 """
521 """
522 down = bool(modifiers & QtCore.Qt.ControlModifier)
522 down = bool(modifiers & QtCore.Qt.ControlModifier)
523
523
524 # Note: on Mac OS, ControlModifier corresponds to the Command key while
524 # Note: on Mac OS, ControlModifier corresponds to the Command key while
525 # MetaModifier corresponds to the Control key.
525 # MetaModifier corresponds to the Control key.
526 if sys.platform == 'darwin':
526 if sys.platform == 'darwin':
527 down = down ^ bool(modifiers & QtCore.Qt.MetaModifier)
527 down = down ^ bool(modifiers & QtCore.Qt.MetaModifier)
528
528
529 return down
529 return down
530
530
531 def _create_control(self, kind):
531 def _create_control(self, kind):
532 """ Creates and connects the underlying text widget.
532 """ Creates and connects the underlying text widget.
533 """
533 """
534 if kind == 'plain':
534 if kind == 'plain':
535 control = QtGui.QPlainTextEdit()
535 control = QtGui.QPlainTextEdit()
536 elif kind == 'rich':
536 elif kind == 'rich':
537 control = QtGui.QTextEdit()
537 control = QtGui.QTextEdit()
538 control.setAcceptRichText(False)
538 control.setAcceptRichText(False)
539 else:
539 else:
540 raise ValueError("Kind %s unknown." % repr(kind))
540 raise ValueError("Kind %s unknown." % repr(kind))
541 control.installEventFilter(self)
541 control.installEventFilter(self)
542 control.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
542 control.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
543 control.customContextMenuRequested.connect(self._show_context_menu)
543 control.customContextMenuRequested.connect(self._show_context_menu)
544 control.copyAvailable.connect(self.copy_available)
544 control.copyAvailable.connect(self.copy_available)
545 control.redoAvailable.connect(self.redo_available)
545 control.redoAvailable.connect(self.redo_available)
546 control.undoAvailable.connect(self.undo_available)
546 control.undoAvailable.connect(self.undo_available)
547 control.setReadOnly(True)
547 control.setReadOnly(True)
548 control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
548 control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
549 return control
549 return control
550
550
551 def _create_page_control(self):
551 def _create_page_control(self):
552 """ Creates and connects the underlying paging widget.
552 """ Creates and connects the underlying paging widget.
553 """
553 """
554 control = QtGui.QPlainTextEdit()
554 control = QtGui.QPlainTextEdit()
555 control.installEventFilter(self)
555 control.installEventFilter(self)
556 control.setReadOnly(True)
556 control.setReadOnly(True)
557 control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
557 control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
558 return control
558 return control
559
559
560 def _event_filter_console_keypress(self, event):
560 def _event_filter_console_keypress(self, event):
561 """ Filter key events for the underlying text widget to create a
561 """ Filter key events for the underlying text widget to create a
562 console-like interface.
562 console-like interface.
563 """
563 """
564 intercepted = False
564 intercepted = False
565 cursor = self._control.textCursor()
565 cursor = self._control.textCursor()
566 position = cursor.position()
566 position = cursor.position()
567 key = event.key()
567 key = event.key()
568 ctrl_down = self._control_key_down(event.modifiers())
568 ctrl_down = self._control_key_down(event.modifiers())
569 alt_down = event.modifiers() & QtCore.Qt.AltModifier
569 alt_down = event.modifiers() & QtCore.Qt.AltModifier
570 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
570 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
571
571
572 if event.matches(QtGui.QKeySequence.Paste):
572 if event.matches(QtGui.QKeySequence.Paste):
573 # Call our paste instead of the underlying text widget's.
573 # Call our paste instead of the underlying text widget's.
574 self.paste()
574 self.paste()
575 intercepted = True
575 intercepted = True
576
576
577 elif ctrl_down:
577 elif ctrl_down:
578 if key == QtCore.Qt.Key_C:
578 if key == QtCore.Qt.Key_C:
579 intercepted = self._executing and self._execute_interrupt()
579 intercepted = self._executing and self._execute_interrupt()
580
580
581 elif key == QtCore.Qt.Key_K:
581 elif key == QtCore.Qt.Key_K:
582 if self._in_buffer(position):
582 if self._in_buffer(position):
583 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
583 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
584 QtGui.QTextCursor.KeepAnchor)
584 QtGui.QTextCursor.KeepAnchor)
585 cursor.removeSelectedText()
585 cursor.removeSelectedText()
586 intercepted = True
586 intercepted = True
587
587
588 elif key == QtCore.Qt.Key_X:
588 elif key == QtCore.Qt.Key_X:
589 intercepted = True
589 intercepted = True
590
590
591 elif key == QtCore.Qt.Key_Y:
591 elif key == QtCore.Qt.Key_Y:
592 self.paste()
592 self.paste()
593 intercepted = True
593 intercepted = True
594
594
595 elif alt_down:
595 elif alt_down:
596 if key == QtCore.Qt.Key_B:
596 if key == QtCore.Qt.Key_B:
597 self._set_cursor(self._get_word_start_cursor(position))
597 self._set_cursor(self._get_word_start_cursor(position))
598 intercepted = True
598 intercepted = True
599
599
600 elif key == QtCore.Qt.Key_F:
600 elif key == QtCore.Qt.Key_F:
601 self._set_cursor(self._get_word_end_cursor(position))
601 self._set_cursor(self._get_word_end_cursor(position))
602 intercepted = True
602 intercepted = True
603
603
604 elif key == QtCore.Qt.Key_Backspace:
604 elif key == QtCore.Qt.Key_Backspace:
605 cursor = self._get_word_start_cursor(position)
605 cursor = self._get_word_start_cursor(position)
606 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
606 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
607 cursor.removeSelectedText()
607 cursor.removeSelectedText()
608 intercepted = True
608 intercepted = True
609
609
610 elif key == QtCore.Qt.Key_D:
610 elif key == QtCore.Qt.Key_D:
611 cursor = self._get_word_end_cursor(position)
611 cursor = self._get_word_end_cursor(position)
612 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
612 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
613 cursor.removeSelectedText()
613 cursor.removeSelectedText()
614 intercepted = True
614 intercepted = True
615
615
616 else:
616 else:
617 if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
617 if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
618 if self._reading:
618 if self._reading:
619 self._append_plain_text('\n')
619 self._append_plain_text('\n')
620 self._reading = False
620 self._reading = False
621 if self._reading_callback:
621 if self._reading_callback:
622 self._reading_callback()
622 self._reading_callback()
623 elif not self._executing:
623 elif not self._executing:
624 self.execute(interactive=True)
624 self.execute(interactive=True)
625 intercepted = True
625 intercepted = True
626
626
627 elif key == QtCore.Qt.Key_Up:
627 elif key == QtCore.Qt.Key_Up:
628 if self._reading or not self._up_pressed():
628 if self._reading or not self._up_pressed():
629 intercepted = True
629 intercepted = True
630 else:
630 else:
631 prompt_line = self._get_prompt_cursor().blockNumber()
631 prompt_line = self._get_prompt_cursor().blockNumber()
632 intercepted = cursor.blockNumber() <= prompt_line
632 intercepted = cursor.blockNumber() <= prompt_line
633
633
634 elif key == QtCore.Qt.Key_Down:
634 elif key == QtCore.Qt.Key_Down:
635 if self._reading or not self._down_pressed():
635 if self._reading or not self._down_pressed():
636 intercepted = True
636 intercepted = True
637 else:
637 else:
638 end_line = self._get_end_cursor().blockNumber()
638 end_line = self._get_end_cursor().blockNumber()
639 intercepted = cursor.blockNumber() == end_line
639 intercepted = cursor.blockNumber() == end_line
640
640
641 elif key == QtCore.Qt.Key_Tab:
641 elif key == QtCore.Qt.Key_Tab:
642 if self._reading:
642 if self._tab_pressed():
643 intercepted = False
644 elif not self._tab_pressed():
645 intercepted = True
646 else:
647 intercepted = not self._in_buffer()
643 intercepted = not self._in_buffer()
644 else:
645 intercepted = True
648
646
649 elif key == QtCore.Qt.Key_Left:
647 elif key == QtCore.Qt.Key_Left:
650 intercepted = not self._in_buffer(position - 1)
648 intercepted = not self._in_buffer(position - 1)
651
649
652 elif key == QtCore.Qt.Key_Home:
650 elif key == QtCore.Qt.Key_Home:
653 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
651 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
654 start_line = cursor.blockNumber()
652 start_line = cursor.blockNumber()
655 if start_line == self._get_prompt_cursor().blockNumber():
653 if start_line == self._get_prompt_cursor().blockNumber():
656 start_pos = self._prompt_pos
654 start_pos = self._prompt_pos
657 else:
655 else:
658 start_pos = cursor.position()
656 start_pos = cursor.position()
659 start_pos += len(self._continuation_prompt)
657 start_pos += len(self._continuation_prompt)
660 if shift_down and self._in_buffer(position):
658 if shift_down and self._in_buffer(position):
661 self._set_selection(position, start_pos)
659 self._set_selection(position, start_pos)
662 else:
660 else:
663 self._set_position(start_pos)
661 self._set_position(start_pos)
664 intercepted = True
662 intercepted = True
665
663
666 elif key == QtCore.Qt.Key_Backspace:
664 elif key == QtCore.Qt.Key_Backspace:
667
665
668 # Line deletion (remove continuation prompt)
666 # Line deletion (remove continuation prompt)
669 len_prompt = len(self._continuation_prompt)
667 len_prompt = len(self._continuation_prompt)
670 if not self._reading and \
668 if not self._reading and \
671 cursor.columnNumber() == len_prompt and \
669 cursor.columnNumber() == len_prompt and \
672 position != self._prompt_pos:
670 position != self._prompt_pos:
673 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
671 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
674 QtGui.QTextCursor.KeepAnchor)
672 QtGui.QTextCursor.KeepAnchor)
675 cursor.removeSelectedText()
673 cursor.removeSelectedText()
676 cursor.deletePreviousChar()
674 cursor.deletePreviousChar()
677 intercepted = True
675 intercepted = True
678
676
679 # Regular backwards deletion
677 # Regular backwards deletion
680 else:
678 else:
681 anchor = cursor.anchor()
679 anchor = cursor.anchor()
682 if anchor == position:
680 if anchor == position:
683 intercepted = not self._in_buffer(position - 1)
681 intercepted = not self._in_buffer(position - 1)
684 else:
682 else:
685 intercepted = not self._in_buffer(min(anchor, position))
683 intercepted = not self._in_buffer(min(anchor, position))
686
684
687 elif key == QtCore.Qt.Key_Delete:
685 elif key == QtCore.Qt.Key_Delete:
688 anchor = cursor.anchor()
686 anchor = cursor.anchor()
689 intercepted = not self._in_buffer(min(anchor, position))
687 intercepted = not self._in_buffer(min(anchor, position))
690
688
691 # Don't move the cursor if control is down to allow copy-paste using
689 # Don't move the cursor if control is down to allow copy-paste using
692 # the keyboard in any part of the buffer.
690 # the keyboard in any part of the buffer.
693 if not ctrl_down:
691 if not ctrl_down:
694 self._keep_cursor_in_buffer()
692 self._keep_cursor_in_buffer()
695
693
696 return intercepted
694 return intercepted
697
695
698 def _event_filter_page_keypress(self, event):
696 def _event_filter_page_keypress(self, event):
699 """ Filter key events for the paging widget to create console-like
697 """ Filter key events for the paging widget to create console-like
700 interface.
698 interface.
701 """
699 """
702 key = event.key()
700 key = event.key()
703
701
704 if key in (QtCore.Qt.Key_Q, QtCore.Qt.Key_Escape):
702 if key in (QtCore.Qt.Key_Q, QtCore.Qt.Key_Escape):
705 if self._splitter:
703 if self._splitter:
706 self._page_control.hide()
704 self._page_control.hide()
707 else:
705 else:
708 self.layout().setCurrentWidget(self._control)
706 self.layout().setCurrentWidget(self._control)
709 return True
707 return True
710
708
711 elif key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
709 elif key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
712 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
710 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
713 QtCore.Qt.Key_Down,
711 QtCore.Qt.Key_Down,
714 QtCore.Qt.NoModifier)
712 QtCore.Qt.NoModifier)
715 QtGui.qApp.sendEvent(self._page_control, new_event)
713 QtGui.qApp.sendEvent(self._page_control, new_event)
716 return True
714 return True
717
715
718 return False
716 return False
719
717
720 def _format_as_columns(self, items, separator=' '):
718 def _format_as_columns(self, items, separator=' '):
721 """ Transform a list of strings into a single string with columns.
719 """ Transform a list of strings into a single string with columns.
722
720
723 Parameters
721 Parameters
724 ----------
722 ----------
725 items : sequence of strings
723 items : sequence of strings
726 The strings to process.
724 The strings to process.
727
725
728 separator : str, optional [default is two spaces]
726 separator : str, optional [default is two spaces]
729 The string that separates columns.
727 The string that separates columns.
730
728
731 Returns
729 Returns
732 -------
730 -------
733 The formatted string.
731 The formatted string.
734 """
732 """
735 # Note: this code is adapted from columnize 0.3.2.
733 # Note: this code is adapted from columnize 0.3.2.
736 # See http://code.google.com/p/pycolumnize/
734 # See http://code.google.com/p/pycolumnize/
737
735
738 width = self._control.viewport().width()
736 width = self._control.viewport().width()
739 char_width = QtGui.QFontMetrics(self.font).width(' ')
737 char_width = QtGui.QFontMetrics(self.font).width(' ')
740 displaywidth = max(5, width / char_width)
738 displaywidth = max(5, width / char_width)
741
739
742 # Some degenerate cases.
740 # Some degenerate cases.
743 size = len(items)
741 size = len(items)
744 if size == 0:
742 if size == 0:
745 return '\n'
743 return '\n'
746 elif size == 1:
744 elif size == 1:
747 return '%s\n' % str(items[0])
745 return '%s\n' % str(items[0])
748
746
749 # Try every row count from 1 upwards
747 # Try every row count from 1 upwards
750 array_index = lambda nrows, row, col: nrows*col + row
748 array_index = lambda nrows, row, col: nrows*col + row
751 for nrows in range(1, size):
749 for nrows in range(1, size):
752 ncols = (size + nrows - 1) // nrows
750 ncols = (size + nrows - 1) // nrows
753 colwidths = []
751 colwidths = []
754 totwidth = -len(separator)
752 totwidth = -len(separator)
755 for col in range(ncols):
753 for col in range(ncols):
756 # Get max column width for this column
754 # Get max column width for this column
757 colwidth = 0
755 colwidth = 0
758 for row in range(nrows):
756 for row in range(nrows):
759 i = array_index(nrows, row, col)
757 i = array_index(nrows, row, col)
760 if i >= size: break
758 if i >= size: break
761 x = items[i]
759 x = items[i]
762 colwidth = max(colwidth, len(x))
760 colwidth = max(colwidth, len(x))
763 colwidths.append(colwidth)
761 colwidths.append(colwidth)
764 totwidth += colwidth + len(separator)
762 totwidth += colwidth + len(separator)
765 if totwidth > displaywidth:
763 if totwidth > displaywidth:
766 break
764 break
767 if totwidth <= displaywidth:
765 if totwidth <= displaywidth:
768 break
766 break
769
767
770 # The smallest number of rows computed and the max widths for each
768 # The smallest number of rows computed and the max widths for each
771 # column has been obtained. Now we just have to format each of the rows.
769 # column has been obtained. Now we just have to format each of the rows.
772 string = ''
770 string = ''
773 for row in range(nrows):
771 for row in range(nrows):
774 texts = []
772 texts = []
775 for col in range(ncols):
773 for col in range(ncols):
776 i = row + nrows*col
774 i = row + nrows*col
777 if i >= size:
775 if i >= size:
778 texts.append('')
776 texts.append('')
779 else:
777 else:
780 texts.append(items[i])
778 texts.append(items[i])
781 while texts and not texts[-1]:
779 while texts and not texts[-1]:
782 del texts[-1]
780 del texts[-1]
783 for col in range(len(texts)):
781 for col in range(len(texts)):
784 texts[col] = texts[col].ljust(colwidths[col])
782 texts[col] = texts[col].ljust(colwidths[col])
785 string += '%s\n' % str(separator.join(texts))
783 string += '%s\n' % str(separator.join(texts))
786 return string
784 return string
787
785
788 def _get_block_plain_text(self, block):
786 def _get_block_plain_text(self, block):
789 """ Given a QTextBlock, return its unformatted text.
787 """ Given a QTextBlock, return its unformatted text.
790 """
788 """
791 cursor = QtGui.QTextCursor(block)
789 cursor = QtGui.QTextCursor(block)
792 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
790 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
793 cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
791 cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
794 QtGui.QTextCursor.KeepAnchor)
792 QtGui.QTextCursor.KeepAnchor)
795 return str(cursor.selection().toPlainText())
793 return str(cursor.selection().toPlainText())
796
794
797 def _get_cursor(self):
795 def _get_cursor(self):
798 """ Convenience method that returns a cursor for the current position.
796 """ Convenience method that returns a cursor for the current position.
799 """
797 """
800 return self._control.textCursor()
798 return self._control.textCursor()
801
799
802 def _get_end_cursor(self):
800 def _get_end_cursor(self):
803 """ Convenience method that returns a cursor for the last character.
801 """ Convenience method that returns a cursor for the last character.
804 """
802 """
805 cursor = self._control.textCursor()
803 cursor = self._control.textCursor()
806 cursor.movePosition(QtGui.QTextCursor.End)
804 cursor.movePosition(QtGui.QTextCursor.End)
807 return cursor
805 return cursor
808
806
809 def _get_input_buffer_cursor_column(self):
807 def _get_input_buffer_cursor_column(self):
810 """ Returns the column of the cursor in the input buffer, excluding the
808 """ Returns the column of the cursor in the input buffer, excluding the
811 contribution by the prompt, or -1 if there is no such column.
809 contribution by the prompt, or -1 if there is no such column.
812 """
810 """
813 prompt = self._get_input_buffer_cursor_prompt()
811 prompt = self._get_input_buffer_cursor_prompt()
814 if prompt is None:
812 if prompt is None:
815 return -1
813 return -1
816 else:
814 else:
817 cursor = self._control.textCursor()
815 cursor = self._control.textCursor()
818 return cursor.columnNumber() - len(prompt)
816 return cursor.columnNumber() - len(prompt)
819
817
820 def _get_input_buffer_cursor_line(self):
818 def _get_input_buffer_cursor_line(self):
821 """ Returns line of the input buffer that contains the cursor, or None
819 """ Returns line of the input buffer that contains the cursor, or None
822 if there is no such line.
820 if there is no such line.
823 """
821 """
824 prompt = self._get_input_buffer_cursor_prompt()
822 prompt = self._get_input_buffer_cursor_prompt()
825 if prompt is None:
823 if prompt is None:
826 return None
824 return None
827 else:
825 else:
828 cursor = self._control.textCursor()
826 cursor = self._control.textCursor()
829 text = self._get_block_plain_text(cursor.block())
827 text = self._get_block_plain_text(cursor.block())
830 return text[len(prompt):]
828 return text[len(prompt):]
831
829
832 def _get_input_buffer_cursor_prompt(self):
830 def _get_input_buffer_cursor_prompt(self):
833 """ Returns the (plain text) prompt for line of the input buffer that
831 """ Returns the (plain text) prompt for line of the input buffer that
834 contains the cursor, or None if there is no such line.
832 contains the cursor, or None if there is no such line.
835 """
833 """
836 if self._executing:
834 if self._executing:
837 return None
835 return None
838 cursor = self._control.textCursor()
836 cursor = self._control.textCursor()
839 if cursor.position() >= self._prompt_pos:
837 if cursor.position() >= self._prompt_pos:
840 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
838 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
841 return self._prompt
839 return self._prompt
842 else:
840 else:
843 return self._continuation_prompt
841 return self._continuation_prompt
844 else:
842 else:
845 return None
843 return None
846
844
847 def _get_prompt_cursor(self):
845 def _get_prompt_cursor(self):
848 """ Convenience method that returns a cursor for the prompt position.
846 """ Convenience method that returns a cursor for the prompt position.
849 """
847 """
850 cursor = self._control.textCursor()
848 cursor = self._control.textCursor()
851 cursor.setPosition(self._prompt_pos)
849 cursor.setPosition(self._prompt_pos)
852 return cursor
850 return cursor
853
851
854 def _get_selection_cursor(self, start, end):
852 def _get_selection_cursor(self, start, end):
855 """ Convenience method that returns a cursor with text selected between
853 """ Convenience method that returns a cursor with text selected between
856 the positions 'start' and 'end'.
854 the positions 'start' and 'end'.
857 """
855 """
858 cursor = self._control.textCursor()
856 cursor = self._control.textCursor()
859 cursor.setPosition(start)
857 cursor.setPosition(start)
860 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
858 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
861 return cursor
859 return cursor
862
860
863 def _get_word_start_cursor(self, position):
861 def _get_word_start_cursor(self, position):
864 """ Find the start of the word to the left the given position. If a
862 """ Find the start of the word to the left the given position. If a
865 sequence of non-word characters precedes the first word, skip over
863 sequence of non-word characters precedes the first word, skip over
866 them. (This emulates the behavior of bash, emacs, etc.)
864 them. (This emulates the behavior of bash, emacs, etc.)
867 """
865 """
868 document = self._control.document()
866 document = self._control.document()
869 position -= 1
867 position -= 1
870 while position >= self._prompt_pos and \
868 while position >= self._prompt_pos and \
871 not document.characterAt(position).isLetterOrNumber():
869 not document.characterAt(position).isLetterOrNumber():
872 position -= 1
870 position -= 1
873 while position >= self._prompt_pos and \
871 while position >= self._prompt_pos and \
874 document.characterAt(position).isLetterOrNumber():
872 document.characterAt(position).isLetterOrNumber():
875 position -= 1
873 position -= 1
876 cursor = self._control.textCursor()
874 cursor = self._control.textCursor()
877 cursor.setPosition(position + 1)
875 cursor.setPosition(position + 1)
878 return cursor
876 return cursor
879
877
880 def _get_word_end_cursor(self, position):
878 def _get_word_end_cursor(self, position):
881 """ Find the end of the word to the right the given position. If a
879 """ Find the end of the word to the right the given position. If a
882 sequence of non-word characters precedes the first word, skip over
880 sequence of non-word characters precedes the first word, skip over
883 them. (This emulates the behavior of bash, emacs, etc.)
881 them. (This emulates the behavior of bash, emacs, etc.)
884 """
882 """
885 document = self._control.document()
883 document = self._control.document()
886 end = self._get_end_cursor().position()
884 end = self._get_end_cursor().position()
887 while position < end and \
885 while position < end and \
888 not document.characterAt(position).isLetterOrNumber():
886 not document.characterAt(position).isLetterOrNumber():
889 position += 1
887 position += 1
890 while position < end and \
888 while position < end and \
891 document.characterAt(position).isLetterOrNumber():
889 document.characterAt(position).isLetterOrNumber():
892 position += 1
890 position += 1
893 cursor = self._control.textCursor()
891 cursor = self._control.textCursor()
894 cursor.setPosition(position)
892 cursor.setPosition(position)
895 return cursor
893 return cursor
896
894
897 def _insert_html(self, cursor, html):
895 def _insert_html(self, cursor, html):
898 """ Inserts HTML using the specified cursor in such a way that future
896 """ Inserts HTML using the specified cursor in such a way that future
899 formatting is unaffected.
897 formatting is unaffected.
900 """
898 """
901 cursor.beginEditBlock()
899 cursor.beginEditBlock()
902 cursor.insertHtml(html)
900 cursor.insertHtml(html)
903
901
904 # After inserting HTML, the text document "remembers" it's in "html
902 # After inserting HTML, the text document "remembers" it's in "html
905 # mode", which means that subsequent calls adding plain text will result
903 # mode", which means that subsequent calls adding plain text will result
906 # in unwanted formatting, lost tab characters, etc. The following code
904 # in unwanted formatting, lost tab characters, etc. The following code
907 # hacks around this behavior, which I consider to be a bug in Qt, by
905 # hacks around this behavior, which I consider to be a bug in Qt, by
908 # (crudely) resetting the document's style state.
906 # (crudely) resetting the document's style state.
909 cursor.movePosition(QtGui.QTextCursor.Left,
907 cursor.movePosition(QtGui.QTextCursor.Left,
910 QtGui.QTextCursor.KeepAnchor)
908 QtGui.QTextCursor.KeepAnchor)
911 if cursor.selection().toPlainText() == ' ':
909 if cursor.selection().toPlainText() == ' ':
912 cursor.removeSelectedText()
910 cursor.removeSelectedText()
913 else:
911 else:
914 cursor.movePosition(QtGui.QTextCursor.Right)
912 cursor.movePosition(QtGui.QTextCursor.Right)
915 cursor.insertText(' ', QtGui.QTextCharFormat())
913 cursor.insertText(' ', QtGui.QTextCharFormat())
916 cursor.endEditBlock()
914 cursor.endEditBlock()
917
915
918 def _insert_html_fetching_plain_text(self, cursor, html):
916 def _insert_html_fetching_plain_text(self, cursor, html):
919 """ Inserts HTML using the specified cursor, then returns its plain text
917 """ Inserts HTML using the specified cursor, then returns its plain text
920 version.
918 version.
921 """
919 """
922 cursor.beginEditBlock()
920 cursor.beginEditBlock()
923 cursor.removeSelectedText()
921 cursor.removeSelectedText()
924
922
925 start = cursor.position()
923 start = cursor.position()
926 self._insert_html(cursor, html)
924 self._insert_html(cursor, html)
927 end = cursor.position()
925 end = cursor.position()
928 cursor.setPosition(start, QtGui.QTextCursor.KeepAnchor)
926 cursor.setPosition(start, QtGui.QTextCursor.KeepAnchor)
929 text = str(cursor.selection().toPlainText())
927 text = str(cursor.selection().toPlainText())
930
928
931 cursor.setPosition(end)
929 cursor.setPosition(end)
932 cursor.endEditBlock()
930 cursor.endEditBlock()
933 return text
931 return text
934
932
935 def _insert_plain_text(self, cursor, text):
933 def _insert_plain_text(self, cursor, text):
936 """ Inserts plain text using the specified cursor, processing ANSI codes
934 """ Inserts plain text using the specified cursor, processing ANSI codes
937 if enabled.
935 if enabled.
938 """
936 """
939 cursor.beginEditBlock()
937 cursor.beginEditBlock()
940 if self.ansi_codes:
938 if self.ansi_codes:
941 for substring in self._ansi_processor.split_string(text):
939 for substring in self._ansi_processor.split_string(text):
942 for action in self._ansi_processor.actions:
940 for action in self._ansi_processor.actions:
943 if action.kind == 'erase' and action.area == 'screen':
941 if action.kind == 'erase' and action.area == 'screen':
944 cursor.select(QtGui.QTextCursor.Document)
942 cursor.select(QtGui.QTextCursor.Document)
945 cursor.removeSelectedText()
943 cursor.removeSelectedText()
946 format = self._ansi_processor.get_format()
944 format = self._ansi_processor.get_format()
947 cursor.insertText(substring, format)
945 cursor.insertText(substring, format)
948 else:
946 else:
949 cursor.insertText(text)
947 cursor.insertText(text)
950 cursor.endEditBlock()
948 cursor.endEditBlock()
951
949
952 def _insert_plain_text_into_buffer(self, text):
950 def _insert_plain_text_into_buffer(self, text):
953 """ Inserts text into the input buffer at the current cursor position,
951 """ Inserts text into the input buffer at the current cursor position,
954 ensuring that continuation prompts are inserted as necessary.
952 ensuring that continuation prompts are inserted as necessary.
955 """
953 """
956 lines = str(text).splitlines(True)
954 lines = str(text).splitlines(True)
957 if lines:
955 if lines:
958 self._keep_cursor_in_buffer()
956 self._keep_cursor_in_buffer()
959 cursor = self._control.textCursor()
957 cursor = self._control.textCursor()
960 cursor.beginEditBlock()
958 cursor.beginEditBlock()
961 cursor.insertText(lines[0])
959 cursor.insertText(lines[0])
962 for line in lines[1:]:
960 for line in lines[1:]:
963 if self._continuation_prompt_html is None:
961 if self._continuation_prompt_html is None:
964 cursor.insertText(self._continuation_prompt)
962 cursor.insertText(self._continuation_prompt)
965 else:
963 else:
966 self._continuation_prompt = \
964 self._continuation_prompt = \
967 self._insert_html_fetching_plain_text(
965 self._insert_html_fetching_plain_text(
968 cursor, self._continuation_prompt_html)
966 cursor, self._continuation_prompt_html)
969 cursor.insertText(line)
967 cursor.insertText(line)
970 cursor.endEditBlock()
968 cursor.endEditBlock()
971 self._control.setTextCursor(cursor)
969 self._control.setTextCursor(cursor)
972
970
973 def _in_buffer(self, position=None):
971 def _in_buffer(self, position=None):
974 """ Returns whether the current cursor (or, if specified, a position) is
972 """ Returns whether the current cursor (or, if specified, a position) is
975 inside the editing region.
973 inside the editing region.
976 """
974 """
977 cursor = self._control.textCursor()
975 cursor = self._control.textCursor()
978 if position is None:
976 if position is None:
979 position = cursor.position()
977 position = cursor.position()
980 else:
978 else:
981 cursor.setPosition(position)
979 cursor.setPosition(position)
982 line = cursor.blockNumber()
980 line = cursor.blockNumber()
983 prompt_line = self._get_prompt_cursor().blockNumber()
981 prompt_line = self._get_prompt_cursor().blockNumber()
984 if line == prompt_line:
982 if line == prompt_line:
985 return position >= self._prompt_pos
983 return position >= self._prompt_pos
986 elif line > prompt_line:
984 elif line > prompt_line:
987 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
985 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
988 prompt_pos = cursor.position() + len(self._continuation_prompt)
986 prompt_pos = cursor.position() + len(self._continuation_prompt)
989 return position >= prompt_pos
987 return position >= prompt_pos
990 return False
988 return False
991
989
992 def _keep_cursor_in_buffer(self):
990 def _keep_cursor_in_buffer(self):
993 """ Ensures that the cursor is inside the editing region. Returns
991 """ Ensures that the cursor is inside the editing region. Returns
994 whether the cursor was moved.
992 whether the cursor was moved.
995 """
993 """
996 moved = not self._in_buffer()
994 moved = not self._in_buffer()
997 if moved:
995 if moved:
998 cursor = self._control.textCursor()
996 cursor = self._control.textCursor()
999 cursor.movePosition(QtGui.QTextCursor.End)
997 cursor.movePosition(QtGui.QTextCursor.End)
1000 self._control.setTextCursor(cursor)
998 self._control.setTextCursor(cursor)
1001 return moved
999 return moved
1002
1000
1003 def _page(self, text):
1001 def _page(self, text):
1004 """ Displays text using the pager if it exceeds the height of the
1002 """ Displays text using the pager if it exceeds the height of the
1005 visible area.
1003 visible area.
1006 """
1004 """
1007 if self._page_style == 'none':
1005 if self._page_style == 'none':
1008 self._append_plain_text(text)
1006 self._append_plain_text(text)
1009 else:
1007 else:
1010 line_height = QtGui.QFontMetrics(self.font).height()
1008 line_height = QtGui.QFontMetrics(self.font).height()
1011 minlines = self._control.viewport().height() / line_height
1009 minlines = self._control.viewport().height() / line_height
1012 if re.match("(?:[^\n]*\n){%i}" % minlines, text):
1010 if re.match("(?:[^\n]*\n){%i}" % minlines, text):
1013 if self._page_style == 'custom':
1011 if self._page_style == 'custom':
1014 self.custom_page_requested.emit(text)
1012 self.custom_page_requested.emit(text)
1015 else:
1013 else:
1016 self._page_control.clear()
1014 self._page_control.clear()
1017 cursor = self._page_control.textCursor()
1015 cursor = self._page_control.textCursor()
1018 self._insert_plain_text(cursor, text)
1016 self._insert_plain_text(cursor, text)
1019 self._page_control.moveCursor(QtGui.QTextCursor.Start)
1017 self._page_control.moveCursor(QtGui.QTextCursor.Start)
1020
1018
1021 self._page_control.viewport().resize(self._control.size())
1019 self._page_control.viewport().resize(self._control.size())
1022 if self._splitter:
1020 if self._splitter:
1023 self._page_control.show()
1021 self._page_control.show()
1024 self._page_control.setFocus()
1022 self._page_control.setFocus()
1025 else:
1023 else:
1026 self.layout().setCurrentWidget(self._page_control)
1024 self.layout().setCurrentWidget(self._page_control)
1027 else:
1025 else:
1028 self._append_plain_text(text)
1026 self._append_plain_text(text)
1029
1027
1030 def _prompt_started(self):
1028 def _prompt_started(self):
1031 """ Called immediately after a new prompt is displayed.
1029 """ Called immediately after a new prompt is displayed.
1032 """
1030 """
1033 # Temporarily disable the maximum block count to permit undo/redo and
1031 # Temporarily disable the maximum block count to permit undo/redo and
1034 # to ensure that the prompt position does not change due to truncation.
1032 # to ensure that the prompt position does not change due to truncation.
1035 self._control.document().setMaximumBlockCount(0)
1033 self._control.document().setMaximumBlockCount(0)
1036 self._control.setUndoRedoEnabled(True)
1034 self._control.setUndoRedoEnabled(True)
1037
1035
1038 self._control.setReadOnly(False)
1036 self._control.setReadOnly(False)
1039 self._control.moveCursor(QtGui.QTextCursor.End)
1037 self._control.moveCursor(QtGui.QTextCursor.End)
1040
1038
1041 self._executing = False
1039 self._executing = False
1042 self._prompt_started_hook()
1040 self._prompt_started_hook()
1043
1041
1044 def _prompt_finished(self):
1042 def _prompt_finished(self):
1045 """ Called immediately after a prompt is finished, i.e. when some input
1043 """ Called immediately after a prompt is finished, i.e. when some input
1046 will be processed and a new prompt displayed.
1044 will be processed and a new prompt displayed.
1047 """
1045 """
1048 self._control.setUndoRedoEnabled(False)
1046 self._control.setUndoRedoEnabled(False)
1049 self._control.setReadOnly(True)
1047 self._control.setReadOnly(True)
1050 self._prompt_finished_hook()
1048 self._prompt_finished_hook()
1051
1049
1052 def _readline(self, prompt='', callback=None):
1050 def _readline(self, prompt='', callback=None):
1053 """ Reads one line of input from the user.
1051 """ Reads one line of input from the user.
1054
1052
1055 Parameters
1053 Parameters
1056 ----------
1054 ----------
1057 prompt : str, optional
1055 prompt : str, optional
1058 The prompt to print before reading the line.
1056 The prompt to print before reading the line.
1059
1057
1060 callback : callable, optional
1058 callback : callable, optional
1061 A callback to execute with the read line. If not specified, input is
1059 A callback to execute with the read line. If not specified, input is
1062 read *synchronously* and this method does not return until it has
1060 read *synchronously* and this method does not return until it has
1063 been read.
1061 been read.
1064
1062
1065 Returns
1063 Returns
1066 -------
1064 -------
1067 If a callback is specified, returns nothing. Otherwise, returns the
1065 If a callback is specified, returns nothing. Otherwise, returns the
1068 input string with the trailing newline stripped.
1066 input string with the trailing newline stripped.
1069 """
1067 """
1070 if self._reading:
1068 if self._reading:
1071 raise RuntimeError('Cannot read a line. Widget is already reading.')
1069 raise RuntimeError('Cannot read a line. Widget is already reading.')
1072
1070
1073 if not callback and not self.isVisible():
1071 if not callback and not self.isVisible():
1074 # If the user cannot see the widget, this function cannot return.
1072 # If the user cannot see the widget, this function cannot return.
1075 raise RuntimeError('Cannot synchronously read a line if the widget'
1073 raise RuntimeError('Cannot synchronously read a line if the widget'
1076 'is not visible!')
1074 'is not visible!')
1077
1075
1078 self._reading = True
1076 self._reading = True
1079 self._show_prompt(prompt, newline=False)
1077 self._show_prompt(prompt, newline=False)
1080
1078
1081 if callback is None:
1079 if callback is None:
1082 self._reading_callback = None
1080 self._reading_callback = None
1083 while self._reading:
1081 while self._reading:
1084 QtCore.QCoreApplication.processEvents()
1082 QtCore.QCoreApplication.processEvents()
1085 return self.input_buffer.rstrip('\n')
1083 return self.input_buffer.rstrip('\n')
1086
1084
1087 else:
1085 else:
1088 self._reading_callback = lambda: \
1086 self._reading_callback = lambda: \
1089 callback(self.input_buffer.rstrip('\n'))
1087 callback(self.input_buffer.rstrip('\n'))
1090
1088
1091 def _set_continuation_prompt(self, prompt, html=False):
1089 def _set_continuation_prompt(self, prompt, html=False):
1092 """ Sets the continuation prompt.
1090 """ Sets the continuation prompt.
1093
1091
1094 Parameters
1092 Parameters
1095 ----------
1093 ----------
1096 prompt : str
1094 prompt : str
1097 The prompt to show when more input is needed.
1095 The prompt to show when more input is needed.
1098
1096
1099 html : bool, optional (default False)
1097 html : bool, optional (default False)
1100 If set, the prompt will be inserted as formatted HTML. Otherwise,
1098 If set, the prompt will be inserted as formatted HTML. Otherwise,
1101 the prompt will be treated as plain text, though ANSI color codes
1099 the prompt will be treated as plain text, though ANSI color codes
1102 will be handled.
1100 will be handled.
1103 """
1101 """
1104 if html:
1102 if html:
1105 self._continuation_prompt_html = prompt
1103 self._continuation_prompt_html = prompt
1106 else:
1104 else:
1107 self._continuation_prompt = prompt
1105 self._continuation_prompt = prompt
1108 self._continuation_prompt_html = None
1106 self._continuation_prompt_html = None
1109
1107
1110 def _set_cursor(self, cursor):
1108 def _set_cursor(self, cursor):
1111 """ Convenience method to set the current cursor.
1109 """ Convenience method to set the current cursor.
1112 """
1110 """
1113 self._control.setTextCursor(cursor)
1111 self._control.setTextCursor(cursor)
1114
1112
1115 def _set_position(self, position):
1113 def _set_position(self, position):
1116 """ Convenience method to set the position of the cursor.
1114 """ Convenience method to set the position of the cursor.
1117 """
1115 """
1118 cursor = self._control.textCursor()
1116 cursor = self._control.textCursor()
1119 cursor.setPosition(position)
1117 cursor.setPosition(position)
1120 self._control.setTextCursor(cursor)
1118 self._control.setTextCursor(cursor)
1121
1119
1122 def _set_selection(self, start, end):
1120 def _set_selection(self, start, end):
1123 """ Convenience method to set the current selected text.
1121 """ Convenience method to set the current selected text.
1124 """
1122 """
1125 self._control.setTextCursor(self._get_selection_cursor(start, end))
1123 self._control.setTextCursor(self._get_selection_cursor(start, end))
1126
1124
1127 def _show_context_menu(self, pos):
1125 def _show_context_menu(self, pos):
1128 """ Shows a context menu at the given QPoint (in widget coordinates).
1126 """ Shows a context menu at the given QPoint (in widget coordinates).
1129 """
1127 """
1130 menu = QtGui.QMenu()
1128 menu = QtGui.QMenu()
1131
1129
1132 copy_action = menu.addAction('Copy', self.copy)
1130 copy_action = menu.addAction('Copy', self.copy)
1133 copy_action.setEnabled(self._get_cursor().hasSelection())
1131 copy_action.setEnabled(self._get_cursor().hasSelection())
1134 copy_action.setShortcut(QtGui.QKeySequence.Copy)
1132 copy_action.setShortcut(QtGui.QKeySequence.Copy)
1135
1133
1136 paste_action = menu.addAction('Paste', self.paste)
1134 paste_action = menu.addAction('Paste', self.paste)
1137 paste_action.setEnabled(self.can_paste())
1135 paste_action.setEnabled(self.can_paste())
1138 paste_action.setShortcut(QtGui.QKeySequence.Paste)
1136 paste_action.setShortcut(QtGui.QKeySequence.Paste)
1139
1137
1140 menu.addSeparator()
1138 menu.addSeparator()
1141 menu.addAction('Select All', self.select_all)
1139 menu.addAction('Select All', self.select_all)
1142
1140
1143 menu.exec_(self._control.mapToGlobal(pos))
1141 menu.exec_(self._control.mapToGlobal(pos))
1144
1142
1145 def _show_prompt(self, prompt=None, html=False, newline=True):
1143 def _show_prompt(self, prompt=None, html=False, newline=True):
1146 """ Writes a new prompt at the end of the buffer.
1144 """ Writes a new prompt at the end of the buffer.
1147
1145
1148 Parameters
1146 Parameters
1149 ----------
1147 ----------
1150 prompt : str, optional
1148 prompt : str, optional
1151 The prompt to show. If not specified, the previous prompt is used.
1149 The prompt to show. If not specified, the previous prompt is used.
1152
1150
1153 html : bool, optional (default False)
1151 html : bool, optional (default False)
1154 Only relevant when a prompt is specified. If set, the prompt will
1152 Only relevant when a prompt is specified. If set, the prompt will
1155 be inserted as formatted HTML. Otherwise, the prompt will be treated
1153 be inserted as formatted HTML. Otherwise, the prompt will be treated
1156 as plain text, though ANSI color codes will be handled.
1154 as plain text, though ANSI color codes will be handled.
1157
1155
1158 newline : bool, optional (default True)
1156 newline : bool, optional (default True)
1159 If set, a new line will be written before showing the prompt if
1157 If set, a new line will be written before showing the prompt if
1160 there is not already a newline at the end of the buffer.
1158 there is not already a newline at the end of the buffer.
1161 """
1159 """
1162 # Insert a preliminary newline, if necessary.
1160 # Insert a preliminary newline, if necessary.
1163 if newline:
1161 if newline:
1164 cursor = self._get_end_cursor()
1162 cursor = self._get_end_cursor()
1165 if cursor.position() > 0:
1163 if cursor.position() > 0:
1166 cursor.movePosition(QtGui.QTextCursor.Left,
1164 cursor.movePosition(QtGui.QTextCursor.Left,
1167 QtGui.QTextCursor.KeepAnchor)
1165 QtGui.QTextCursor.KeepAnchor)
1168 if str(cursor.selection().toPlainText()) != '\n':
1166 if str(cursor.selection().toPlainText()) != '\n':
1169 self._append_plain_text('\n')
1167 self._append_plain_text('\n')
1170
1168
1171 # Write the prompt.
1169 # Write the prompt.
1172 if prompt is None:
1170 if prompt is None:
1173 if self._prompt_html is None:
1171 if self._prompt_html is None:
1174 self._append_plain_text(self._prompt)
1172 self._append_plain_text(self._prompt)
1175 else:
1173 else:
1176 self._append_html(self._prompt_html)
1174 self._append_html(self._prompt_html)
1177 else:
1175 else:
1178 if html:
1176 if html:
1179 self._prompt = self._append_html_fetching_plain_text(prompt)
1177 self._prompt = self._append_html_fetching_plain_text(prompt)
1180 self._prompt_html = prompt
1178 self._prompt_html = prompt
1181 else:
1179 else:
1182 self._append_plain_text(prompt)
1180 self._append_plain_text(prompt)
1183 self._prompt = prompt
1181 self._prompt = prompt
1184 self._prompt_html = None
1182 self._prompt_html = None
1185
1183
1186 self._prompt_pos = self._get_end_cursor().position()
1184 self._prompt_pos = self._get_end_cursor().position()
1187 self._prompt_started()
1185 self._prompt_started()
1188
1186
1189 def _show_continuation_prompt(self):
1187 def _show_continuation_prompt(self):
1190 """ Writes a new continuation prompt at the end of the buffer.
1188 """ Writes a new continuation prompt at the end of the buffer.
1191 """
1189 """
1192 if self._continuation_prompt_html is None:
1190 if self._continuation_prompt_html is None:
1193 self._append_plain_text(self._continuation_prompt)
1191 self._append_plain_text(self._continuation_prompt)
1194 else:
1192 else:
1195 self._continuation_prompt = self._append_html_fetching_plain_text(
1193 self._continuation_prompt = self._append_html_fetching_plain_text(
1196 self._continuation_prompt_html)
1194 self._continuation_prompt_html)
1197
1195
1198 self._prompt_started()
1196 self._prompt_started()
1199
1197
1200
1198
1201 class HistoryConsoleWidget(ConsoleWidget):
1199 class HistoryConsoleWidget(ConsoleWidget):
1202 """ A ConsoleWidget that keeps a history of the commands that have been
1200 """ A ConsoleWidget that keeps a history of the commands that have been
1203 executed.
1201 executed.
1204 """
1202 """
1205
1203
1206 #---------------------------------------------------------------------------
1204 #---------------------------------------------------------------------------
1207 # 'object' interface
1205 # 'object' interface
1208 #---------------------------------------------------------------------------
1206 #---------------------------------------------------------------------------
1209
1207
1210 def __init__(self, *args, **kw):
1208 def __init__(self, *args, **kw):
1211 super(HistoryConsoleWidget, self).__init__(*args, **kw)
1209 super(HistoryConsoleWidget, self).__init__(*args, **kw)
1212 self._history = []
1210 self._history = []
1213 self._history_index = 0
1211 self._history_index = 0
1214
1212
1215 #---------------------------------------------------------------------------
1213 #---------------------------------------------------------------------------
1216 # 'ConsoleWidget' public interface
1214 # 'ConsoleWidget' public interface
1217 #---------------------------------------------------------------------------
1215 #---------------------------------------------------------------------------
1218
1216
1219 def execute(self, source=None, hidden=False, interactive=False):
1217 def execute(self, source=None, hidden=False, interactive=False):
1220 """ Reimplemented to the store history.
1218 """ Reimplemented to the store history.
1221 """
1219 """
1222 if not hidden:
1220 if not hidden:
1223 history = self.input_buffer if source is None else source
1221 history = self.input_buffer if source is None else source
1224
1222
1225 executed = super(HistoryConsoleWidget, self).execute(
1223 executed = super(HistoryConsoleWidget, self).execute(
1226 source, hidden, interactive)
1224 source, hidden, interactive)
1227
1225
1228 if executed and not hidden:
1226 if executed and not hidden:
1229 self._history.append(history.rstrip())
1227 self._history.append(history.rstrip())
1230 self._history_index = len(self._history)
1228 self._history_index = len(self._history)
1231
1229
1232 return executed
1230 return executed
1233
1231
1234 #---------------------------------------------------------------------------
1232 #---------------------------------------------------------------------------
1235 # 'ConsoleWidget' abstract interface
1233 # 'ConsoleWidget' abstract interface
1236 #---------------------------------------------------------------------------
1234 #---------------------------------------------------------------------------
1237
1235
1238 def _up_pressed(self):
1236 def _up_pressed(self):
1239 """ Called when the up key is pressed. Returns whether to continue
1237 """ Called when the up key is pressed. Returns whether to continue
1240 processing the event.
1238 processing the event.
1241 """
1239 """
1242 prompt_cursor = self._get_prompt_cursor()
1240 prompt_cursor = self._get_prompt_cursor()
1243 if self._get_cursor().blockNumber() == prompt_cursor.blockNumber():
1241 if self._get_cursor().blockNumber() == prompt_cursor.blockNumber():
1244 self.history_previous()
1242 self.history_previous()
1245
1243
1246 # Go to the first line of prompt for seemless history scrolling.
1244 # Go to the first line of prompt for seemless history scrolling.
1247 cursor = self._get_prompt_cursor()
1245 cursor = self._get_prompt_cursor()
1248 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
1246 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
1249 self._set_cursor(cursor)
1247 self._set_cursor(cursor)
1250
1248
1251 return False
1249 return False
1252 return True
1250 return True
1253
1251
1254 def _down_pressed(self):
1252 def _down_pressed(self):
1255 """ Called when the down key is pressed. Returns whether to continue
1253 """ Called when the down key is pressed. Returns whether to continue
1256 processing the event.
1254 processing the event.
1257 """
1255 """
1258 end_cursor = self._get_end_cursor()
1256 end_cursor = self._get_end_cursor()
1259 if self._get_cursor().blockNumber() == end_cursor.blockNumber():
1257 if self._get_cursor().blockNumber() == end_cursor.blockNumber():
1260 self.history_next()
1258 self.history_next()
1261 return False
1259 return False
1262 return True
1260 return True
1263
1261
1264 #---------------------------------------------------------------------------
1262 #---------------------------------------------------------------------------
1265 # 'HistoryConsoleWidget' public interface
1263 # 'HistoryConsoleWidget' public interface
1266 #---------------------------------------------------------------------------
1264 #---------------------------------------------------------------------------
1267
1265
1268 def history_previous(self):
1266 def history_previous(self):
1269 """ If possible, set the input buffer to the previous item in the
1267 """ If possible, set the input buffer to the previous item in the
1270 history.
1268 history.
1271 """
1269 """
1272 if self._history_index > 0:
1270 if self._history_index > 0:
1273 self._history_index -= 1
1271 self._history_index -= 1
1274 self.input_buffer = self._history[self._history_index]
1272 self.input_buffer = self._history[self._history_index]
1275
1273
1276 def history_next(self):
1274 def history_next(self):
1277 """ Set the input buffer to the next item in the history, or a blank
1275 """ Set the input buffer to the next item in the history, or a blank
1278 line if there is no subsequent item.
1276 line if there is no subsequent item.
1279 """
1277 """
1280 if self._history_index < len(self._history):
1278 if self._history_index < len(self._history):
1281 self._history_index += 1
1279 self._history_index += 1
1282 if self._history_index < len(self._history):
1280 if self._history_index < len(self._history):
1283 self.input_buffer = self._history[self._history_index]
1281 self.input_buffer = self._history[self._history_index]
1284 else:
1282 else:
1285 self.input_buffer = ''
1283 self.input_buffer = ''
1286
1284
1287 #---------------------------------------------------------------------------
1285 #---------------------------------------------------------------------------
1288 # 'HistoryConsoleWidget' protected interface
1286 # 'HistoryConsoleWidget' protected interface
1289 #---------------------------------------------------------------------------
1287 #---------------------------------------------------------------------------
1290
1288
1291 def _set_history(self, history):
1289 def _set_history(self, history):
1292 """ Replace the current history with a sequence of history items.
1290 """ Replace the current history with a sequence of history items.
1293 """
1291 """
1294 self._history = list(history)
1292 self._history = list(history)
1295 self._history_index = len(self._history)
1293 self._history_index = len(self._history)
General Comments 0
You need to be logged in to leave comments. Login now