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