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