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