##// END OF EJS Templates
Minor cleanup (again).
epatters -
Show More
@@ -1,1293 +1,1291 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._tab_pressed():
642 if not self._reading:
643 intercepted = not self._in_buffer()
643 intercepted = not self._tab_pressed()
644 else:
645 intercepted = True
646
644
647 elif key == QtCore.Qt.Key_Left:
645 elif key == QtCore.Qt.Key_Left:
648 intercepted = not self._in_buffer(position - 1)
646 intercepted = not self._in_buffer(position - 1)
649
647
650 elif key == QtCore.Qt.Key_Home:
648 elif key == QtCore.Qt.Key_Home:
651 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
649 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
652 start_line = cursor.blockNumber()
650 start_line = cursor.blockNumber()
653 if start_line == self._get_prompt_cursor().blockNumber():
651 if start_line == self._get_prompt_cursor().blockNumber():
654 start_pos = self._prompt_pos
652 start_pos = self._prompt_pos
655 else:
653 else:
656 start_pos = cursor.position()
654 start_pos = cursor.position()
657 start_pos += len(self._continuation_prompt)
655 start_pos += len(self._continuation_prompt)
658 if shift_down and self._in_buffer(position):
656 if shift_down and self._in_buffer(position):
659 self._set_selection(position, start_pos)
657 self._set_selection(position, start_pos)
660 else:
658 else:
661 self._set_position(start_pos)
659 self._set_position(start_pos)
662 intercepted = True
660 intercepted = True
663
661
664 elif key == QtCore.Qt.Key_Backspace:
662 elif key == QtCore.Qt.Key_Backspace:
665
663
666 # Line deletion (remove continuation prompt)
664 # Line deletion (remove continuation prompt)
667 len_prompt = len(self._continuation_prompt)
665 len_prompt = len(self._continuation_prompt)
668 if not self._reading and \
666 if not self._reading and \
669 cursor.columnNumber() == len_prompt and \
667 cursor.columnNumber() == len_prompt and \
670 position != self._prompt_pos:
668 position != self._prompt_pos:
671 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
669 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
672 QtGui.QTextCursor.KeepAnchor)
670 QtGui.QTextCursor.KeepAnchor)
673 cursor.removeSelectedText()
671 cursor.removeSelectedText()
674 cursor.deletePreviousChar()
672 cursor.deletePreviousChar()
675 intercepted = True
673 intercepted = True
676
674
677 # Regular backwards deletion
675 # Regular backwards deletion
678 else:
676 else:
679 anchor = cursor.anchor()
677 anchor = cursor.anchor()
680 if anchor == position:
678 if anchor == position:
681 intercepted = not self._in_buffer(position - 1)
679 intercepted = not self._in_buffer(position - 1)
682 else:
680 else:
683 intercepted = not self._in_buffer(min(anchor, position))
681 intercepted = not self._in_buffer(min(anchor, position))
684
682
685 elif key == QtCore.Qt.Key_Delete:
683 elif key == QtCore.Qt.Key_Delete:
686 anchor = cursor.anchor()
684 anchor = cursor.anchor()
687 intercepted = not self._in_buffer(min(anchor, position))
685 intercepted = not self._in_buffer(min(anchor, position))
688
686
689 # Don't move the cursor if control is down to allow copy-paste using
687 # Don't move the cursor if control is down to allow copy-paste using
690 # the keyboard in any part of the buffer.
688 # the keyboard in any part of the buffer.
691 if not ctrl_down:
689 if not ctrl_down:
692 self._keep_cursor_in_buffer()
690 self._keep_cursor_in_buffer()
693
691
694 return intercepted
692 return intercepted
695
693
696 def _event_filter_page_keypress(self, event):
694 def _event_filter_page_keypress(self, event):
697 """ Filter key events for the paging widget to create console-like
695 """ Filter key events for the paging widget to create console-like
698 interface.
696 interface.
699 """
697 """
700 key = event.key()
698 key = event.key()
701
699
702 if key in (QtCore.Qt.Key_Q, QtCore.Qt.Key_Escape):
700 if key in (QtCore.Qt.Key_Q, QtCore.Qt.Key_Escape):
703 if self._splitter:
701 if self._splitter:
704 self._page_control.hide()
702 self._page_control.hide()
705 else:
703 else:
706 self.layout().setCurrentWidget(self._control)
704 self.layout().setCurrentWidget(self._control)
707 return True
705 return True
708
706
709 elif key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
707 elif key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
710 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
708 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
711 QtCore.Qt.Key_Down,
709 QtCore.Qt.Key_Down,
712 QtCore.Qt.NoModifier)
710 QtCore.Qt.NoModifier)
713 QtGui.qApp.sendEvent(self._page_control, new_event)
711 QtGui.qApp.sendEvent(self._page_control, new_event)
714 return True
712 return True
715
713
716 return False
714 return False
717
715
718 def _format_as_columns(self, items, separator=' '):
716 def _format_as_columns(self, items, separator=' '):
719 """ Transform a list of strings into a single string with columns.
717 """ Transform a list of strings into a single string with columns.
720
718
721 Parameters
719 Parameters
722 ----------
720 ----------
723 items : sequence of strings
721 items : sequence of strings
724 The strings to process.
722 The strings to process.
725
723
726 separator : str, optional [default is two spaces]
724 separator : str, optional [default is two spaces]
727 The string that separates columns.
725 The string that separates columns.
728
726
729 Returns
727 Returns
730 -------
728 -------
731 The formatted string.
729 The formatted string.
732 """
730 """
733 # Note: this code is adapted from columnize 0.3.2.
731 # Note: this code is adapted from columnize 0.3.2.
734 # See http://code.google.com/p/pycolumnize/
732 # See http://code.google.com/p/pycolumnize/
735
733
736 width = self._control.viewport().width()
734 width = self._control.viewport().width()
737 char_width = QtGui.QFontMetrics(self.font).width(' ')
735 char_width = QtGui.QFontMetrics(self.font).width(' ')
738 displaywidth = max(5, width / char_width)
736 displaywidth = max(5, width / char_width)
739
737
740 # Some degenerate cases.
738 # Some degenerate cases.
741 size = len(items)
739 size = len(items)
742 if size == 0:
740 if size == 0:
743 return '\n'
741 return '\n'
744 elif size == 1:
742 elif size == 1:
745 return '%s\n' % str(items[0])
743 return '%s\n' % str(items[0])
746
744
747 # Try every row count from 1 upwards
745 # Try every row count from 1 upwards
748 array_index = lambda nrows, row, col: nrows*col + row
746 array_index = lambda nrows, row, col: nrows*col + row
749 for nrows in range(1, size):
747 for nrows in range(1, size):
750 ncols = (size + nrows - 1) // nrows
748 ncols = (size + nrows - 1) // nrows
751 colwidths = []
749 colwidths = []
752 totwidth = -len(separator)
750 totwidth = -len(separator)
753 for col in range(ncols):
751 for col in range(ncols):
754 # Get max column width for this column
752 # Get max column width for this column
755 colwidth = 0
753 colwidth = 0
756 for row in range(nrows):
754 for row in range(nrows):
757 i = array_index(nrows, row, col)
755 i = array_index(nrows, row, col)
758 if i >= size: break
756 if i >= size: break
759 x = items[i]
757 x = items[i]
760 colwidth = max(colwidth, len(x))
758 colwidth = max(colwidth, len(x))
761 colwidths.append(colwidth)
759 colwidths.append(colwidth)
762 totwidth += colwidth + len(separator)
760 totwidth += colwidth + len(separator)
763 if totwidth > displaywidth:
761 if totwidth > displaywidth:
764 break
762 break
765 if totwidth <= displaywidth:
763 if totwidth <= displaywidth:
766 break
764 break
767
765
768 # The smallest number of rows computed and the max widths for each
766 # The smallest number of rows computed and the max widths for each
769 # column has been obtained. Now we just have to format each of the rows.
767 # column has been obtained. Now we just have to format each of the rows.
770 string = ''
768 string = ''
771 for row in range(nrows):
769 for row in range(nrows):
772 texts = []
770 texts = []
773 for col in range(ncols):
771 for col in range(ncols):
774 i = row + nrows*col
772 i = row + nrows*col
775 if i >= size:
773 if i >= size:
776 texts.append('')
774 texts.append('')
777 else:
775 else:
778 texts.append(items[i])
776 texts.append(items[i])
779 while texts and not texts[-1]:
777 while texts and not texts[-1]:
780 del texts[-1]
778 del texts[-1]
781 for col in range(len(texts)):
779 for col in range(len(texts)):
782 texts[col] = texts[col].ljust(colwidths[col])
780 texts[col] = texts[col].ljust(colwidths[col])
783 string += '%s\n' % str(separator.join(texts))
781 string += '%s\n' % str(separator.join(texts))
784 return string
782 return string
785
783
786 def _get_block_plain_text(self, block):
784 def _get_block_plain_text(self, block):
787 """ Given a QTextBlock, return its unformatted text.
785 """ Given a QTextBlock, return its unformatted text.
788 """
786 """
789 cursor = QtGui.QTextCursor(block)
787 cursor = QtGui.QTextCursor(block)
790 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
788 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
791 cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
789 cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
792 QtGui.QTextCursor.KeepAnchor)
790 QtGui.QTextCursor.KeepAnchor)
793 return str(cursor.selection().toPlainText())
791 return str(cursor.selection().toPlainText())
794
792
795 def _get_cursor(self):
793 def _get_cursor(self):
796 """ Convenience method that returns a cursor for the current position.
794 """ Convenience method that returns a cursor for the current position.
797 """
795 """
798 return self._control.textCursor()
796 return self._control.textCursor()
799
797
800 def _get_end_cursor(self):
798 def _get_end_cursor(self):
801 """ Convenience method that returns a cursor for the last character.
799 """ Convenience method that returns a cursor for the last character.
802 """
800 """
803 cursor = self._control.textCursor()
801 cursor = self._control.textCursor()
804 cursor.movePosition(QtGui.QTextCursor.End)
802 cursor.movePosition(QtGui.QTextCursor.End)
805 return cursor
803 return cursor
806
804
807 def _get_input_buffer_cursor_column(self):
805 def _get_input_buffer_cursor_column(self):
808 """ Returns the column of the cursor in the input buffer, excluding the
806 """ Returns the column of the cursor in the input buffer, excluding the
809 contribution by the prompt, or -1 if there is no such column.
807 contribution by the prompt, or -1 if there is no such column.
810 """
808 """
811 prompt = self._get_input_buffer_cursor_prompt()
809 prompt = self._get_input_buffer_cursor_prompt()
812 if prompt is None:
810 if prompt is None:
813 return -1
811 return -1
814 else:
812 else:
815 cursor = self._control.textCursor()
813 cursor = self._control.textCursor()
816 return cursor.columnNumber() - len(prompt)
814 return cursor.columnNumber() - len(prompt)
817
815
818 def _get_input_buffer_cursor_line(self):
816 def _get_input_buffer_cursor_line(self):
819 """ Returns line of the input buffer that contains the cursor, or None
817 """ Returns line of the input buffer that contains the cursor, or None
820 if there is no such line.
818 if there is no such line.
821 """
819 """
822 prompt = self._get_input_buffer_cursor_prompt()
820 prompt = self._get_input_buffer_cursor_prompt()
823 if prompt is None:
821 if prompt is None:
824 return None
822 return None
825 else:
823 else:
826 cursor = self._control.textCursor()
824 cursor = self._control.textCursor()
827 text = self._get_block_plain_text(cursor.block())
825 text = self._get_block_plain_text(cursor.block())
828 return text[len(prompt):]
826 return text[len(prompt):]
829
827
830 def _get_input_buffer_cursor_prompt(self):
828 def _get_input_buffer_cursor_prompt(self):
831 """ Returns the (plain text) prompt for line of the input buffer that
829 """ Returns the (plain text) prompt for line of the input buffer that
832 contains the cursor, or None if there is no such line.
830 contains the cursor, or None if there is no such line.
833 """
831 """
834 if self._executing:
832 if self._executing:
835 return None
833 return None
836 cursor = self._control.textCursor()
834 cursor = self._control.textCursor()
837 if cursor.position() >= self._prompt_pos:
835 if cursor.position() >= self._prompt_pos:
838 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
836 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
839 return self._prompt
837 return self._prompt
840 else:
838 else:
841 return self._continuation_prompt
839 return self._continuation_prompt
842 else:
840 else:
843 return None
841 return None
844
842
845 def _get_prompt_cursor(self):
843 def _get_prompt_cursor(self):
846 """ Convenience method that returns a cursor for the prompt position.
844 """ Convenience method that returns a cursor for the prompt position.
847 """
845 """
848 cursor = self._control.textCursor()
846 cursor = self._control.textCursor()
849 cursor.setPosition(self._prompt_pos)
847 cursor.setPosition(self._prompt_pos)
850 return cursor
848 return cursor
851
849
852 def _get_selection_cursor(self, start, end):
850 def _get_selection_cursor(self, start, end):
853 """ Convenience method that returns a cursor with text selected between
851 """ Convenience method that returns a cursor with text selected between
854 the positions 'start' and 'end'.
852 the positions 'start' and 'end'.
855 """
853 """
856 cursor = self._control.textCursor()
854 cursor = self._control.textCursor()
857 cursor.setPosition(start)
855 cursor.setPosition(start)
858 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
856 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
859 return cursor
857 return cursor
860
858
861 def _get_word_start_cursor(self, position):
859 def _get_word_start_cursor(self, position):
862 """ Find the start of the word to the left the given position. If a
860 """ Find the start of the word to the left the given position. If a
863 sequence of non-word characters precedes the first word, skip over
861 sequence of non-word characters precedes the first word, skip over
864 them. (This emulates the behavior of bash, emacs, etc.)
862 them. (This emulates the behavior of bash, emacs, etc.)
865 """
863 """
866 document = self._control.document()
864 document = self._control.document()
867 position -= 1
865 position -= 1
868 while position >= self._prompt_pos and \
866 while position >= self._prompt_pos and \
869 not document.characterAt(position).isLetterOrNumber():
867 not document.characterAt(position).isLetterOrNumber():
870 position -= 1
868 position -= 1
871 while position >= self._prompt_pos and \
869 while position >= self._prompt_pos and \
872 document.characterAt(position).isLetterOrNumber():
870 document.characterAt(position).isLetterOrNumber():
873 position -= 1
871 position -= 1
874 cursor = self._control.textCursor()
872 cursor = self._control.textCursor()
875 cursor.setPosition(position + 1)
873 cursor.setPosition(position + 1)
876 return cursor
874 return cursor
877
875
878 def _get_word_end_cursor(self, position):
876 def _get_word_end_cursor(self, position):
879 """ Find the end of the word to the right the given position. If a
877 """ Find the end of the word to the right the given position. If a
880 sequence of non-word characters precedes the first word, skip over
878 sequence of non-word characters precedes the first word, skip over
881 them. (This emulates the behavior of bash, emacs, etc.)
879 them. (This emulates the behavior of bash, emacs, etc.)
882 """
880 """
883 document = self._control.document()
881 document = self._control.document()
884 end = self._get_end_cursor().position()
882 end = self._get_end_cursor().position()
885 while position < end and \
883 while position < end and \
886 not document.characterAt(position).isLetterOrNumber():
884 not document.characterAt(position).isLetterOrNumber():
887 position += 1
885 position += 1
888 while position < end and \
886 while position < end and \
889 document.characterAt(position).isLetterOrNumber():
887 document.characterAt(position).isLetterOrNumber():
890 position += 1
888 position += 1
891 cursor = self._control.textCursor()
889 cursor = self._control.textCursor()
892 cursor.setPosition(position)
890 cursor.setPosition(position)
893 return cursor
891 return cursor
894
892
895 def _insert_html(self, cursor, html):
893 def _insert_html(self, cursor, html):
896 """ Inserts HTML using the specified cursor in such a way that future
894 """ Inserts HTML using the specified cursor in such a way that future
897 formatting is unaffected.
895 formatting is unaffected.
898 """
896 """
899 cursor.beginEditBlock()
897 cursor.beginEditBlock()
900 cursor.insertHtml(html)
898 cursor.insertHtml(html)
901
899
902 # After inserting HTML, the text document "remembers" it's in "html
900 # After inserting HTML, the text document "remembers" it's in "html
903 # mode", which means that subsequent calls adding plain text will result
901 # mode", which means that subsequent calls adding plain text will result
904 # in unwanted formatting, lost tab characters, etc. The following code
902 # in unwanted formatting, lost tab characters, etc. The following code
905 # hacks around this behavior, which I consider to be a bug in Qt, by
903 # hacks around this behavior, which I consider to be a bug in Qt, by
906 # (crudely) resetting the document's style state.
904 # (crudely) resetting the document's style state.
907 cursor.movePosition(QtGui.QTextCursor.Left,
905 cursor.movePosition(QtGui.QTextCursor.Left,
908 QtGui.QTextCursor.KeepAnchor)
906 QtGui.QTextCursor.KeepAnchor)
909 if cursor.selection().toPlainText() == ' ':
907 if cursor.selection().toPlainText() == ' ':
910 cursor.removeSelectedText()
908 cursor.removeSelectedText()
911 else:
909 else:
912 cursor.movePosition(QtGui.QTextCursor.Right)
910 cursor.movePosition(QtGui.QTextCursor.Right)
913 cursor.insertText(' ', QtGui.QTextCharFormat())
911 cursor.insertText(' ', QtGui.QTextCharFormat())
914 cursor.endEditBlock()
912 cursor.endEditBlock()
915
913
916 def _insert_html_fetching_plain_text(self, cursor, html):
914 def _insert_html_fetching_plain_text(self, cursor, html):
917 """ Inserts HTML using the specified cursor, then returns its plain text
915 """ Inserts HTML using the specified cursor, then returns its plain text
918 version.
916 version.
919 """
917 """
920 cursor.beginEditBlock()
918 cursor.beginEditBlock()
921 cursor.removeSelectedText()
919 cursor.removeSelectedText()
922
920
923 start = cursor.position()
921 start = cursor.position()
924 self._insert_html(cursor, html)
922 self._insert_html(cursor, html)
925 end = cursor.position()
923 end = cursor.position()
926 cursor.setPosition(start, QtGui.QTextCursor.KeepAnchor)
924 cursor.setPosition(start, QtGui.QTextCursor.KeepAnchor)
927 text = str(cursor.selection().toPlainText())
925 text = str(cursor.selection().toPlainText())
928
926
929 cursor.setPosition(end)
927 cursor.setPosition(end)
930 cursor.endEditBlock()
928 cursor.endEditBlock()
931 return text
929 return text
932
930
933 def _insert_plain_text(self, cursor, text):
931 def _insert_plain_text(self, cursor, text):
934 """ Inserts plain text using the specified cursor, processing ANSI codes
932 """ Inserts plain text using the specified cursor, processing ANSI codes
935 if enabled.
933 if enabled.
936 """
934 """
937 cursor.beginEditBlock()
935 cursor.beginEditBlock()
938 if self.ansi_codes:
936 if self.ansi_codes:
939 for substring in self._ansi_processor.split_string(text):
937 for substring in self._ansi_processor.split_string(text):
940 for action in self._ansi_processor.actions:
938 for action in self._ansi_processor.actions:
941 if action.kind == 'erase' and action.area == 'screen':
939 if action.kind == 'erase' and action.area == 'screen':
942 cursor.select(QtGui.QTextCursor.Document)
940 cursor.select(QtGui.QTextCursor.Document)
943 cursor.removeSelectedText()
941 cursor.removeSelectedText()
944 format = self._ansi_processor.get_format()
942 format = self._ansi_processor.get_format()
945 cursor.insertText(substring, format)
943 cursor.insertText(substring, format)
946 else:
944 else:
947 cursor.insertText(text)
945 cursor.insertText(text)
948 cursor.endEditBlock()
946 cursor.endEditBlock()
949
947
950 def _insert_plain_text_into_buffer(self, text):
948 def _insert_plain_text_into_buffer(self, text):
951 """ Inserts text into the input buffer at the current cursor position,
949 """ Inserts text into the input buffer at the current cursor position,
952 ensuring that continuation prompts are inserted as necessary.
950 ensuring that continuation prompts are inserted as necessary.
953 """
951 """
954 lines = str(text).splitlines(True)
952 lines = str(text).splitlines(True)
955 if lines:
953 if lines:
956 self._keep_cursor_in_buffer()
954 self._keep_cursor_in_buffer()
957 cursor = self._control.textCursor()
955 cursor = self._control.textCursor()
958 cursor.beginEditBlock()
956 cursor.beginEditBlock()
959 cursor.insertText(lines[0])
957 cursor.insertText(lines[0])
960 for line in lines[1:]:
958 for line in lines[1:]:
961 if self._continuation_prompt_html is None:
959 if self._continuation_prompt_html is None:
962 cursor.insertText(self._continuation_prompt)
960 cursor.insertText(self._continuation_prompt)
963 else:
961 else:
964 self._continuation_prompt = \
962 self._continuation_prompt = \
965 self._insert_html_fetching_plain_text(
963 self._insert_html_fetching_plain_text(
966 cursor, self._continuation_prompt_html)
964 cursor, self._continuation_prompt_html)
967 cursor.insertText(line)
965 cursor.insertText(line)
968 cursor.endEditBlock()
966 cursor.endEditBlock()
969 self._control.setTextCursor(cursor)
967 self._control.setTextCursor(cursor)
970
968
971 def _in_buffer(self, position=None):
969 def _in_buffer(self, position=None):
972 """ Returns whether the current cursor (or, if specified, a position) is
970 """ Returns whether the current cursor (or, if specified, a position) is
973 inside the editing region.
971 inside the editing region.
974 """
972 """
975 cursor = self._control.textCursor()
973 cursor = self._control.textCursor()
976 if position is None:
974 if position is None:
977 position = cursor.position()
975 position = cursor.position()
978 else:
976 else:
979 cursor.setPosition(position)
977 cursor.setPosition(position)
980 line = cursor.blockNumber()
978 line = cursor.blockNumber()
981 prompt_line = self._get_prompt_cursor().blockNumber()
979 prompt_line = self._get_prompt_cursor().blockNumber()
982 if line == prompt_line:
980 if line == prompt_line:
983 return position >= self._prompt_pos
981 return position >= self._prompt_pos
984 elif line > prompt_line:
982 elif line > prompt_line:
985 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
983 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
986 prompt_pos = cursor.position() + len(self._continuation_prompt)
984 prompt_pos = cursor.position() + len(self._continuation_prompt)
987 return position >= prompt_pos
985 return position >= prompt_pos
988 return False
986 return False
989
987
990 def _keep_cursor_in_buffer(self):
988 def _keep_cursor_in_buffer(self):
991 """ Ensures that the cursor is inside the editing region. Returns
989 """ Ensures that the cursor is inside the editing region. Returns
992 whether the cursor was moved.
990 whether the cursor was moved.
993 """
991 """
994 moved = not self._in_buffer()
992 moved = not self._in_buffer()
995 if moved:
993 if moved:
996 cursor = self._control.textCursor()
994 cursor = self._control.textCursor()
997 cursor.movePosition(QtGui.QTextCursor.End)
995 cursor.movePosition(QtGui.QTextCursor.End)
998 self._control.setTextCursor(cursor)
996 self._control.setTextCursor(cursor)
999 return moved
997 return moved
1000
998
1001 def _page(self, text):
999 def _page(self, text):
1002 """ Displays text using the pager if it exceeds the height of the
1000 """ Displays text using the pager if it exceeds the height of the
1003 visible area.
1001 visible area.
1004 """
1002 """
1005 if self._page_style == 'none':
1003 if self._page_style == 'none':
1006 self._append_plain_text(text)
1004 self._append_plain_text(text)
1007 else:
1005 else:
1008 line_height = QtGui.QFontMetrics(self.font).height()
1006 line_height = QtGui.QFontMetrics(self.font).height()
1009 minlines = self._control.viewport().height() / line_height
1007 minlines = self._control.viewport().height() / line_height
1010 if re.match("(?:[^\n]*\n){%i}" % minlines, text):
1008 if re.match("(?:[^\n]*\n){%i}" % minlines, text):
1011 if self._page_style == 'custom':
1009 if self._page_style == 'custom':
1012 self.custom_page_requested.emit(text)
1010 self.custom_page_requested.emit(text)
1013 else:
1011 else:
1014 self._page_control.clear()
1012 self._page_control.clear()
1015 cursor = self._page_control.textCursor()
1013 cursor = self._page_control.textCursor()
1016 self._insert_plain_text(cursor, text)
1014 self._insert_plain_text(cursor, text)
1017 self._page_control.moveCursor(QtGui.QTextCursor.Start)
1015 self._page_control.moveCursor(QtGui.QTextCursor.Start)
1018
1016
1019 self._page_control.viewport().resize(self._control.size())
1017 self._page_control.viewport().resize(self._control.size())
1020 if self._splitter:
1018 if self._splitter:
1021 self._page_control.show()
1019 self._page_control.show()
1022 self._page_control.setFocus()
1020 self._page_control.setFocus()
1023 else:
1021 else:
1024 self.layout().setCurrentWidget(self._page_control)
1022 self.layout().setCurrentWidget(self._page_control)
1025 else:
1023 else:
1026 self._append_plain_text(text)
1024 self._append_plain_text(text)
1027
1025
1028 def _prompt_started(self):
1026 def _prompt_started(self):
1029 """ Called immediately after a new prompt is displayed.
1027 """ Called immediately after a new prompt is displayed.
1030 """
1028 """
1031 # Temporarily disable the maximum block count to permit undo/redo and
1029 # Temporarily disable the maximum block count to permit undo/redo and
1032 # to ensure that the prompt position does not change due to truncation.
1030 # to ensure that the prompt position does not change due to truncation.
1033 self._control.document().setMaximumBlockCount(0)
1031 self._control.document().setMaximumBlockCount(0)
1034 self._control.setUndoRedoEnabled(True)
1032 self._control.setUndoRedoEnabled(True)
1035
1033
1036 self._control.setReadOnly(False)
1034 self._control.setReadOnly(False)
1037 self._control.moveCursor(QtGui.QTextCursor.End)
1035 self._control.moveCursor(QtGui.QTextCursor.End)
1038
1036
1039 self._executing = False
1037 self._executing = False
1040 self._prompt_started_hook()
1038 self._prompt_started_hook()
1041
1039
1042 def _prompt_finished(self):
1040 def _prompt_finished(self):
1043 """ Called immediately after a prompt is finished, i.e. when some input
1041 """ Called immediately after a prompt is finished, i.e. when some input
1044 will be processed and a new prompt displayed.
1042 will be processed and a new prompt displayed.
1045 """
1043 """
1046 self._control.setUndoRedoEnabled(False)
1044 self._control.setUndoRedoEnabled(False)
1047 self._control.setReadOnly(True)
1045 self._control.setReadOnly(True)
1048 self._prompt_finished_hook()
1046 self._prompt_finished_hook()
1049
1047
1050 def _readline(self, prompt='', callback=None):
1048 def _readline(self, prompt='', callback=None):
1051 """ Reads one line of input from the user.
1049 """ Reads one line of input from the user.
1052
1050
1053 Parameters
1051 Parameters
1054 ----------
1052 ----------
1055 prompt : str, optional
1053 prompt : str, optional
1056 The prompt to print before reading the line.
1054 The prompt to print before reading the line.
1057
1055
1058 callback : callable, optional
1056 callback : callable, optional
1059 A callback to execute with the read line. If not specified, input is
1057 A callback to execute with the read line. If not specified, input is
1060 read *synchronously* and this method does not return until it has
1058 read *synchronously* and this method does not return until it has
1061 been read.
1059 been read.
1062
1060
1063 Returns
1061 Returns
1064 -------
1062 -------
1065 If a callback is specified, returns nothing. Otherwise, returns the
1063 If a callback is specified, returns nothing. Otherwise, returns the
1066 input string with the trailing newline stripped.
1064 input string with the trailing newline stripped.
1067 """
1065 """
1068 if self._reading:
1066 if self._reading:
1069 raise RuntimeError('Cannot read a line. Widget is already reading.')
1067 raise RuntimeError('Cannot read a line. Widget is already reading.')
1070
1068
1071 if not callback and not self.isVisible():
1069 if not callback and not self.isVisible():
1072 # If the user cannot see the widget, this function cannot return.
1070 # If the user cannot see the widget, this function cannot return.
1073 raise RuntimeError('Cannot synchronously read a line if the widget'
1071 raise RuntimeError('Cannot synchronously read a line if the widget'
1074 'is not visible!')
1072 'is not visible!')
1075
1073
1076 self._reading = True
1074 self._reading = True
1077 self._show_prompt(prompt, newline=False)
1075 self._show_prompt(prompt, newline=False)
1078
1076
1079 if callback is None:
1077 if callback is None:
1080 self._reading_callback = None
1078 self._reading_callback = None
1081 while self._reading:
1079 while self._reading:
1082 QtCore.QCoreApplication.processEvents()
1080 QtCore.QCoreApplication.processEvents()
1083 return self.input_buffer.rstrip('\n')
1081 return self.input_buffer.rstrip('\n')
1084
1082
1085 else:
1083 else:
1086 self._reading_callback = lambda: \
1084 self._reading_callback = lambda: \
1087 callback(self.input_buffer.rstrip('\n'))
1085 callback(self.input_buffer.rstrip('\n'))
1088
1086
1089 def _set_continuation_prompt(self, prompt, html=False):
1087 def _set_continuation_prompt(self, prompt, html=False):
1090 """ Sets the continuation prompt.
1088 """ Sets the continuation prompt.
1091
1089
1092 Parameters
1090 Parameters
1093 ----------
1091 ----------
1094 prompt : str
1092 prompt : str
1095 The prompt to show when more input is needed.
1093 The prompt to show when more input is needed.
1096
1094
1097 html : bool, optional (default False)
1095 html : bool, optional (default False)
1098 If set, the prompt will be inserted as formatted HTML. Otherwise,
1096 If set, the prompt will be inserted as formatted HTML. Otherwise,
1099 the prompt will be treated as plain text, though ANSI color codes
1097 the prompt will be treated as plain text, though ANSI color codes
1100 will be handled.
1098 will be handled.
1101 """
1099 """
1102 if html:
1100 if html:
1103 self._continuation_prompt_html = prompt
1101 self._continuation_prompt_html = prompt
1104 else:
1102 else:
1105 self._continuation_prompt = prompt
1103 self._continuation_prompt = prompt
1106 self._continuation_prompt_html = None
1104 self._continuation_prompt_html = None
1107
1105
1108 def _set_cursor(self, cursor):
1106 def _set_cursor(self, cursor):
1109 """ Convenience method to set the current cursor.
1107 """ Convenience method to set the current cursor.
1110 """
1108 """
1111 self._control.setTextCursor(cursor)
1109 self._control.setTextCursor(cursor)
1112
1110
1113 def _set_position(self, position):
1111 def _set_position(self, position):
1114 """ Convenience method to set the position of the cursor.
1112 """ Convenience method to set the position of the cursor.
1115 """
1113 """
1116 cursor = self._control.textCursor()
1114 cursor = self._control.textCursor()
1117 cursor.setPosition(position)
1115 cursor.setPosition(position)
1118 self._control.setTextCursor(cursor)
1116 self._control.setTextCursor(cursor)
1119
1117
1120 def _set_selection(self, start, end):
1118 def _set_selection(self, start, end):
1121 """ Convenience method to set the current selected text.
1119 """ Convenience method to set the current selected text.
1122 """
1120 """
1123 self._control.setTextCursor(self._get_selection_cursor(start, end))
1121 self._control.setTextCursor(self._get_selection_cursor(start, end))
1124
1122
1125 def _show_context_menu(self, pos):
1123 def _show_context_menu(self, pos):
1126 """ Shows a context menu at the given QPoint (in widget coordinates).
1124 """ Shows a context menu at the given QPoint (in widget coordinates).
1127 """
1125 """
1128 menu = QtGui.QMenu()
1126 menu = QtGui.QMenu()
1129
1127
1130 copy_action = menu.addAction('Copy', self.copy)
1128 copy_action = menu.addAction('Copy', self.copy)
1131 copy_action.setEnabled(self._get_cursor().hasSelection())
1129 copy_action.setEnabled(self._get_cursor().hasSelection())
1132 copy_action.setShortcut(QtGui.QKeySequence.Copy)
1130 copy_action.setShortcut(QtGui.QKeySequence.Copy)
1133
1131
1134 paste_action = menu.addAction('Paste', self.paste)
1132 paste_action = menu.addAction('Paste', self.paste)
1135 paste_action.setEnabled(self.can_paste())
1133 paste_action.setEnabled(self.can_paste())
1136 paste_action.setShortcut(QtGui.QKeySequence.Paste)
1134 paste_action.setShortcut(QtGui.QKeySequence.Paste)
1137
1135
1138 menu.addSeparator()
1136 menu.addSeparator()
1139 menu.addAction('Select All', self.select_all)
1137 menu.addAction('Select All', self.select_all)
1140
1138
1141 menu.exec_(self._control.mapToGlobal(pos))
1139 menu.exec_(self._control.mapToGlobal(pos))
1142
1140
1143 def _show_prompt(self, prompt=None, html=False, newline=True):
1141 def _show_prompt(self, prompt=None, html=False, newline=True):
1144 """ Writes a new prompt at the end of the buffer.
1142 """ Writes a new prompt at the end of the buffer.
1145
1143
1146 Parameters
1144 Parameters
1147 ----------
1145 ----------
1148 prompt : str, optional
1146 prompt : str, optional
1149 The prompt to show. If not specified, the previous prompt is used.
1147 The prompt to show. If not specified, the previous prompt is used.
1150
1148
1151 html : bool, optional (default False)
1149 html : bool, optional (default False)
1152 Only relevant when a prompt is specified. If set, the prompt will
1150 Only relevant when a prompt is specified. If set, the prompt will
1153 be inserted as formatted HTML. Otherwise, the prompt will be treated
1151 be inserted as formatted HTML. Otherwise, the prompt will be treated
1154 as plain text, though ANSI color codes will be handled.
1152 as plain text, though ANSI color codes will be handled.
1155
1153
1156 newline : bool, optional (default True)
1154 newline : bool, optional (default True)
1157 If set, a new line will be written before showing the prompt if
1155 If set, a new line will be written before showing the prompt if
1158 there is not already a newline at the end of the buffer.
1156 there is not already a newline at the end of the buffer.
1159 """
1157 """
1160 # Insert a preliminary newline, if necessary.
1158 # Insert a preliminary newline, if necessary.
1161 if newline:
1159 if newline:
1162 cursor = self._get_end_cursor()
1160 cursor = self._get_end_cursor()
1163 if cursor.position() > 0:
1161 if cursor.position() > 0:
1164 cursor.movePosition(QtGui.QTextCursor.Left,
1162 cursor.movePosition(QtGui.QTextCursor.Left,
1165 QtGui.QTextCursor.KeepAnchor)
1163 QtGui.QTextCursor.KeepAnchor)
1166 if str(cursor.selection().toPlainText()) != '\n':
1164 if str(cursor.selection().toPlainText()) != '\n':
1167 self._append_plain_text('\n')
1165 self._append_plain_text('\n')
1168
1166
1169 # Write the prompt.
1167 # Write the prompt.
1170 if prompt is None:
1168 if prompt is None:
1171 if self._prompt_html is None:
1169 if self._prompt_html is None:
1172 self._append_plain_text(self._prompt)
1170 self._append_plain_text(self._prompt)
1173 else:
1171 else:
1174 self._append_html(self._prompt_html)
1172 self._append_html(self._prompt_html)
1175 else:
1173 else:
1176 if html:
1174 if html:
1177 self._prompt = self._append_html_fetching_plain_text(prompt)
1175 self._prompt = self._append_html_fetching_plain_text(prompt)
1178 self._prompt_html = prompt
1176 self._prompt_html = prompt
1179 else:
1177 else:
1180 self._append_plain_text(prompt)
1178 self._append_plain_text(prompt)
1181 self._prompt = prompt
1179 self._prompt = prompt
1182 self._prompt_html = None
1180 self._prompt_html = None
1183
1181
1184 self._prompt_pos = self._get_end_cursor().position()
1182 self._prompt_pos = self._get_end_cursor().position()
1185 self._prompt_started()
1183 self._prompt_started()
1186
1184
1187 def _show_continuation_prompt(self):
1185 def _show_continuation_prompt(self):
1188 """ Writes a new continuation prompt at the end of the buffer.
1186 """ Writes a new continuation prompt at the end of the buffer.
1189 """
1187 """
1190 if self._continuation_prompt_html is None:
1188 if self._continuation_prompt_html is None:
1191 self._append_plain_text(self._continuation_prompt)
1189 self._append_plain_text(self._continuation_prompt)
1192 else:
1190 else:
1193 self._continuation_prompt = self._append_html_fetching_plain_text(
1191 self._continuation_prompt = self._append_html_fetching_plain_text(
1194 self._continuation_prompt_html)
1192 self._continuation_prompt_html)
1195
1193
1196 self._prompt_started()
1194 self._prompt_started()
1197
1195
1198
1196
1199 class HistoryConsoleWidget(ConsoleWidget):
1197 class HistoryConsoleWidget(ConsoleWidget):
1200 """ A ConsoleWidget that keeps a history of the commands that have been
1198 """ A ConsoleWidget that keeps a history of the commands that have been
1201 executed.
1199 executed.
1202 """
1200 """
1203
1201
1204 #---------------------------------------------------------------------------
1202 #---------------------------------------------------------------------------
1205 # 'object' interface
1203 # 'object' interface
1206 #---------------------------------------------------------------------------
1204 #---------------------------------------------------------------------------
1207
1205
1208 def __init__(self, *args, **kw):
1206 def __init__(self, *args, **kw):
1209 super(HistoryConsoleWidget, self).__init__(*args, **kw)
1207 super(HistoryConsoleWidget, self).__init__(*args, **kw)
1210 self._history = []
1208 self._history = []
1211 self._history_index = 0
1209 self._history_index = 0
1212
1210
1213 #---------------------------------------------------------------------------
1211 #---------------------------------------------------------------------------
1214 # 'ConsoleWidget' public interface
1212 # 'ConsoleWidget' public interface
1215 #---------------------------------------------------------------------------
1213 #---------------------------------------------------------------------------
1216
1214
1217 def execute(self, source=None, hidden=False, interactive=False):
1215 def execute(self, source=None, hidden=False, interactive=False):
1218 """ Reimplemented to the store history.
1216 """ Reimplemented to the store history.
1219 """
1217 """
1220 if not hidden:
1218 if not hidden:
1221 history = self.input_buffer if source is None else source
1219 history = self.input_buffer if source is None else source
1222
1220
1223 executed = super(HistoryConsoleWidget, self).execute(
1221 executed = super(HistoryConsoleWidget, self).execute(
1224 source, hidden, interactive)
1222 source, hidden, interactive)
1225
1223
1226 if executed and not hidden:
1224 if executed and not hidden:
1227 self._history.append(history.rstrip())
1225 self._history.append(history.rstrip())
1228 self._history_index = len(self._history)
1226 self._history_index = len(self._history)
1229
1227
1230 return executed
1228 return executed
1231
1229
1232 #---------------------------------------------------------------------------
1230 #---------------------------------------------------------------------------
1233 # 'ConsoleWidget' abstract interface
1231 # 'ConsoleWidget' abstract interface
1234 #---------------------------------------------------------------------------
1232 #---------------------------------------------------------------------------
1235
1233
1236 def _up_pressed(self):
1234 def _up_pressed(self):
1237 """ Called when the up key is pressed. Returns whether to continue
1235 """ Called when the up key is pressed. Returns whether to continue
1238 processing the event.
1236 processing the event.
1239 """
1237 """
1240 prompt_cursor = self._get_prompt_cursor()
1238 prompt_cursor = self._get_prompt_cursor()
1241 if self._get_cursor().blockNumber() == prompt_cursor.blockNumber():
1239 if self._get_cursor().blockNumber() == prompt_cursor.blockNumber():
1242 self.history_previous()
1240 self.history_previous()
1243
1241
1244 # Go to the first line of prompt for seemless history scrolling.
1242 # Go to the first line of prompt for seemless history scrolling.
1245 cursor = self._get_prompt_cursor()
1243 cursor = self._get_prompt_cursor()
1246 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
1244 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
1247 self._set_cursor(cursor)
1245 self._set_cursor(cursor)
1248
1246
1249 return False
1247 return False
1250 return True
1248 return True
1251
1249
1252 def _down_pressed(self):
1250 def _down_pressed(self):
1253 """ Called when the down key is pressed. Returns whether to continue
1251 """ Called when the down key is pressed. Returns whether to continue
1254 processing the event.
1252 processing the event.
1255 """
1253 """
1256 end_cursor = self._get_end_cursor()
1254 end_cursor = self._get_end_cursor()
1257 if self._get_cursor().blockNumber() == end_cursor.blockNumber():
1255 if self._get_cursor().blockNumber() == end_cursor.blockNumber():
1258 self.history_next()
1256 self.history_next()
1259 return False
1257 return False
1260 return True
1258 return True
1261
1259
1262 #---------------------------------------------------------------------------
1260 #---------------------------------------------------------------------------
1263 # 'HistoryConsoleWidget' public interface
1261 # 'HistoryConsoleWidget' public interface
1264 #---------------------------------------------------------------------------
1262 #---------------------------------------------------------------------------
1265
1263
1266 def history_previous(self):
1264 def history_previous(self):
1267 """ If possible, set the input buffer to the previous item in the
1265 """ If possible, set the input buffer to the previous item in the
1268 history.
1266 history.
1269 """
1267 """
1270 if self._history_index > 0:
1268 if self._history_index > 0:
1271 self._history_index -= 1
1269 self._history_index -= 1
1272 self.input_buffer = self._history[self._history_index]
1270 self.input_buffer = self._history[self._history_index]
1273
1271
1274 def history_next(self):
1272 def history_next(self):
1275 """ Set the input buffer to the next item in the history, or a blank
1273 """ Set the input buffer to the next item in the history, or a blank
1276 line if there is no subsequent item.
1274 line if there is no subsequent item.
1277 """
1275 """
1278 if self._history_index < len(self._history):
1276 if self._history_index < len(self._history):
1279 self._history_index += 1
1277 self._history_index += 1
1280 if self._history_index < len(self._history):
1278 if self._history_index < len(self._history):
1281 self.input_buffer = self._history[self._history_index]
1279 self.input_buffer = self._history[self._history_index]
1282 else:
1280 else:
1283 self.input_buffer = ''
1281 self.input_buffer = ''
1284
1282
1285 #---------------------------------------------------------------------------
1283 #---------------------------------------------------------------------------
1286 # 'HistoryConsoleWidget' protected interface
1284 # 'HistoryConsoleWidget' protected interface
1287 #---------------------------------------------------------------------------
1285 #---------------------------------------------------------------------------
1288
1286
1289 def _set_history(self, history):
1287 def _set_history(self, history):
1290 """ Replace the current history with a sequence of history items.
1288 """ Replace the current history with a sequence of history items.
1291 """
1289 """
1292 self._history = list(history)
1290 self._history = list(history)
1293 self._history_index = len(self._history)
1291 self._history_index = len(self._history)
General Comments 0
You need to be logged in to leave comments. Login now