##// END OF EJS Templates
Added machinery to IPythonWidget for updating the previous prompt number.
epatters -
Show More
@@ -1,972 +1,978 b''
1 # Standard library imports
1 # Standard library imports
2 import sys
2 import sys
3
3
4 # System library imports
4 # System library imports
5 from PyQt4 import QtCore, QtGui
5 from PyQt4 import QtCore, QtGui
6
6
7 # Local imports
7 # Local imports
8 from ansi_code_processor import QtAnsiCodeProcessor
8 from ansi_code_processor import QtAnsiCodeProcessor
9 from completion_widget import CompletionWidget
9 from completion_widget import CompletionWidget
10
10
11
11
12 class ConsoleWidget(QtGui.QPlainTextEdit):
12 class ConsoleWidget(QtGui.QPlainTextEdit):
13 """ Base class for console-type widgets. This class is mainly concerned with
13 """ Base class for console-type widgets. This class is mainly concerned with
14 dealing with the prompt, keeping the cursor inside the editing line, and
14 dealing with the prompt, keeping the cursor inside the editing line, and
15 handling ANSI escape sequences.
15 handling ANSI escape sequences.
16 """
16 """
17
17
18 # Whether to process ANSI escape codes.
18 # Whether to process ANSI escape codes.
19 ansi_codes = True
19 ansi_codes = True
20
20
21 # The maximum number of lines of text before truncation.
21 # The maximum number of lines of text before truncation.
22 buffer_size = 500
22 buffer_size = 500
23
23
24 # Whether to use a CompletionWidget or plain text output for tab completion.
24 # Whether to use a CompletionWidget or plain text output for tab completion.
25 gui_completion = True
25 gui_completion = True
26
26
27 # Whether to override ShortcutEvents for the keybindings defined by this
27 # Whether to override ShortcutEvents for the keybindings defined by this
28 # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
28 # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
29 # priority (when it has focus) over, e.g., window-level menu shortcuts.
29 # priority (when it has focus) over, e.g., window-level menu shortcuts.
30 override_shortcuts = False
30 override_shortcuts = False
31
31
32 # Protected class variables.
32 # Protected class variables.
33 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
33 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
34 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
34 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
35 QtCore.Qt.Key_A : QtCore.Qt.Key_Home,
35 QtCore.Qt.Key_A : QtCore.Qt.Key_Home,
36 QtCore.Qt.Key_E : QtCore.Qt.Key_End,
36 QtCore.Qt.Key_E : QtCore.Qt.Key_End,
37 QtCore.Qt.Key_P : QtCore.Qt.Key_Up,
37 QtCore.Qt.Key_P : QtCore.Qt.Key_Up,
38 QtCore.Qt.Key_N : QtCore.Qt.Key_Down,
38 QtCore.Qt.Key_N : QtCore.Qt.Key_Down,
39 QtCore.Qt.Key_D : QtCore.Qt.Key_Delete, }
39 QtCore.Qt.Key_D : QtCore.Qt.Key_Delete, }
40 _shortcuts = set(_ctrl_down_remap.keys() +
40 _shortcuts = set(_ctrl_down_remap.keys() +
41 [ QtCore.Qt.Key_C, QtCore.Qt.Key_V ])
41 [ QtCore.Qt.Key_C, QtCore.Qt.Key_V ])
42
42
43 #---------------------------------------------------------------------------
43 #---------------------------------------------------------------------------
44 # 'QObject' interface
44 # 'QObject' interface
45 #---------------------------------------------------------------------------
45 #---------------------------------------------------------------------------
46
46
47 def __init__(self, parent=None):
47 def __init__(self, parent=None):
48 QtGui.QPlainTextEdit.__init__(self, parent)
48 QtGui.QPlainTextEdit.__init__(self, parent)
49
49
50 # Initialize protected variables. Some variables contain useful state
50 # Initialize protected variables. Some variables contain useful state
51 # information for subclasses; they should be considered read-only.
51 # information for subclasses; they should be considered read-only.
52 self._ansi_processor = QtAnsiCodeProcessor()
52 self._ansi_processor = QtAnsiCodeProcessor()
53 self._completion_widget = CompletionWidget(self)
53 self._completion_widget = CompletionWidget(self)
54 self._continuation_prompt = '> '
54 self._continuation_prompt = '> '
55 self._continuation_prompt_html = None
55 self._continuation_prompt_html = None
56 self._executing = False
56 self._executing = False
57 self._prompt = ''
57 self._prompt = ''
58 self._prompt_html = None
58 self._prompt_html = None
59 self._prompt_pos = 0
59 self._prompt_pos = 0
60 self._reading = False
60 self._reading = False
61 self._reading_callback = None
61 self._reading_callback = None
62 self._tab_width = 8
62 self._tab_width = 8
63
63
64 # Set a monospaced font.
64 # Set a monospaced font.
65 self.reset_font()
65 self.reset_font()
66
66
67 # Define a custom context menu.
67 # Define a custom context menu.
68 self._context_menu = QtGui.QMenu(self)
68 self._context_menu = QtGui.QMenu(self)
69
69
70 copy_action = QtGui.QAction('Copy', self)
70 copy_action = QtGui.QAction('Copy', self)
71 copy_action.triggered.connect(self.copy)
71 copy_action.triggered.connect(self.copy)
72 self.copyAvailable.connect(copy_action.setEnabled)
72 self.copyAvailable.connect(copy_action.setEnabled)
73 self._context_menu.addAction(copy_action)
73 self._context_menu.addAction(copy_action)
74
74
75 self._paste_action = QtGui.QAction('Paste', self)
75 self._paste_action = QtGui.QAction('Paste', self)
76 self._paste_action.triggered.connect(self.paste)
76 self._paste_action.triggered.connect(self.paste)
77 self._context_menu.addAction(self._paste_action)
77 self._context_menu.addAction(self._paste_action)
78 self._context_menu.addSeparator()
78 self._context_menu.addSeparator()
79
79
80 select_all_action = QtGui.QAction('Select All', self)
80 select_all_action = QtGui.QAction('Select All', self)
81 select_all_action.triggered.connect(self.selectAll)
81 select_all_action.triggered.connect(self.selectAll)
82 self._context_menu.addAction(select_all_action)
82 self._context_menu.addAction(select_all_action)
83
83
84 def event(self, event):
84 def event(self, event):
85 """ Reimplemented to override shortcuts, if necessary.
85 """ Reimplemented to override shortcuts, if necessary.
86 """
86 """
87 # On Mac OS, it is always unnecessary to override shortcuts, hence the
87 # On Mac OS, it is always unnecessary to override shortcuts, hence the
88 # check below. Users should just use the Control key instead of the
88 # check below. Users should just use the Control key instead of the
89 # Command key.
89 # Command key.
90 if self.override_shortcuts and \
90 if self.override_shortcuts and \
91 sys.platform != 'darwin' and \
91 sys.platform != 'darwin' and \
92 event.type() == QtCore.QEvent.ShortcutOverride and \
92 event.type() == QtCore.QEvent.ShortcutOverride and \
93 self._control_down(event.modifiers()) and \
93 self._control_down(event.modifiers()) and \
94 event.key() in self._shortcuts:
94 event.key() in self._shortcuts:
95 event.accept()
95 event.accept()
96 return True
96 return True
97 else:
97 else:
98 return QtGui.QPlainTextEdit.event(self, event)
98 return QtGui.QPlainTextEdit.event(self, event)
99
99
100 #---------------------------------------------------------------------------
100 #---------------------------------------------------------------------------
101 # 'QWidget' interface
101 # 'QWidget' interface
102 #---------------------------------------------------------------------------
102 #---------------------------------------------------------------------------
103
103
104 def contextMenuEvent(self, event):
104 def contextMenuEvent(self, event):
105 """ Reimplemented to create a menu without destructive actions like
105 """ Reimplemented to create a menu without destructive actions like
106 'Cut' and 'Delete'.
106 'Cut' and 'Delete'.
107 """
107 """
108 clipboard_empty = QtGui.QApplication.clipboard().text().isEmpty()
108 clipboard_empty = QtGui.QApplication.clipboard().text().isEmpty()
109 self._paste_action.setEnabled(not clipboard_empty)
109 self._paste_action.setEnabled(not clipboard_empty)
110
110
111 self._context_menu.exec_(event.globalPos())
111 self._context_menu.exec_(event.globalPos())
112
112
113 def dragMoveEvent(self, event):
113 def dragMoveEvent(self, event):
114 """ Reimplemented to disable moving text by drag and drop.
114 """ Reimplemented to disable moving text by drag and drop.
115 """
115 """
116 event.ignore()
116 event.ignore()
117
117
118 def keyPressEvent(self, event):
118 def keyPressEvent(self, event):
119 """ Reimplemented to create a console-like interface.
119 """ Reimplemented to create a console-like interface.
120 """
120 """
121 intercepted = False
121 intercepted = False
122 cursor = self.textCursor()
122 cursor = self.textCursor()
123 position = cursor.position()
123 position = cursor.position()
124 key = event.key()
124 key = event.key()
125 ctrl_down = self._control_down(event.modifiers())
125 ctrl_down = self._control_down(event.modifiers())
126 alt_down = event.modifiers() & QtCore.Qt.AltModifier
126 alt_down = event.modifiers() & QtCore.Qt.AltModifier
127 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
127 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
128
128
129 # Even though we have reimplemented 'paste', the C++ level slot is still
129 # Even though we have reimplemented 'paste', the C++ level slot is still
130 # called by Qt. So we intercept the key press here.
130 # called by Qt. So we intercept the key press here.
131 if event.matches(QtGui.QKeySequence.Paste):
131 if event.matches(QtGui.QKeySequence.Paste):
132 self.paste()
132 self.paste()
133 intercepted = True
133 intercepted = True
134
134
135 elif ctrl_down:
135 elif ctrl_down:
136 if key in self._ctrl_down_remap:
136 if key in self._ctrl_down_remap:
137 ctrl_down = False
137 ctrl_down = False
138 key = self._ctrl_down_remap[key]
138 key = self._ctrl_down_remap[key]
139 event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, key,
139 event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, key,
140 QtCore.Qt.NoModifier)
140 QtCore.Qt.NoModifier)
141
141
142 elif key == QtCore.Qt.Key_K:
142 elif key == QtCore.Qt.Key_K:
143 if self._in_buffer(position):
143 if self._in_buffer(position):
144 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
144 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
145 QtGui.QTextCursor.KeepAnchor)
145 QtGui.QTextCursor.KeepAnchor)
146 cursor.removeSelectedText()
146 cursor.removeSelectedText()
147 intercepted = True
147 intercepted = True
148
148
149 elif key == QtCore.Qt.Key_X:
149 elif key == QtCore.Qt.Key_X:
150 intercepted = True
150 intercepted = True
151
151
152 elif key == QtCore.Qt.Key_Y:
152 elif key == QtCore.Qt.Key_Y:
153 self.paste()
153 self.paste()
154 intercepted = True
154 intercepted = True
155
155
156 elif alt_down:
156 elif alt_down:
157 if key == QtCore.Qt.Key_B:
157 if key == QtCore.Qt.Key_B:
158 self.setTextCursor(self._get_word_start_cursor(position))
158 self.setTextCursor(self._get_word_start_cursor(position))
159 intercepted = True
159 intercepted = True
160
160
161 elif key == QtCore.Qt.Key_F:
161 elif key == QtCore.Qt.Key_F:
162 self.setTextCursor(self._get_word_end_cursor(position))
162 self.setTextCursor(self._get_word_end_cursor(position))
163 intercepted = True
163 intercepted = True
164
164
165 elif key == QtCore.Qt.Key_Backspace:
165 elif key == QtCore.Qt.Key_Backspace:
166 cursor = self._get_word_start_cursor(position)
166 cursor = self._get_word_start_cursor(position)
167 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
167 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
168 cursor.removeSelectedText()
168 cursor.removeSelectedText()
169 intercepted = True
169 intercepted = True
170
170
171 elif key == QtCore.Qt.Key_D:
171 elif key == QtCore.Qt.Key_D:
172 cursor = self._get_word_end_cursor(position)
172 cursor = self._get_word_end_cursor(position)
173 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
173 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
174 cursor.removeSelectedText()
174 cursor.removeSelectedText()
175 intercepted = True
175 intercepted = True
176
176
177 if self._completion_widget.isVisible():
177 if self._completion_widget.isVisible():
178 self._completion_widget.keyPressEvent(event)
178 self._completion_widget.keyPressEvent(event)
179 intercepted = event.isAccepted()
179 intercepted = event.isAccepted()
180
180
181 else:
181 else:
182 if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
182 if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
183 if self._reading:
183 if self._reading:
184 self.appendPlainText('\n')
184 self.appendPlainText('\n')
185 self._reading = False
185 self._reading = False
186 if self._reading_callback:
186 if self._reading_callback:
187 self._reading_callback()
187 self._reading_callback()
188 elif not self._executing:
188 elif not self._executing:
189 self.execute(interactive=True)
189 self.execute(interactive=True)
190 intercepted = True
190 intercepted = True
191
191
192 elif key == QtCore.Qt.Key_Up:
192 elif key == QtCore.Qt.Key_Up:
193 if self._reading or not self._up_pressed():
193 if self._reading or not self._up_pressed():
194 intercepted = True
194 intercepted = True
195 else:
195 else:
196 prompt_line = self._get_prompt_cursor().blockNumber()
196 prompt_line = self._get_prompt_cursor().blockNumber()
197 intercepted = cursor.blockNumber() <= prompt_line
197 intercepted = cursor.blockNumber() <= prompt_line
198
198
199 elif key == QtCore.Qt.Key_Down:
199 elif key == QtCore.Qt.Key_Down:
200 if self._reading or not self._down_pressed():
200 if self._reading or not self._down_pressed():
201 intercepted = True
201 intercepted = True
202 else:
202 else:
203 end_line = self._get_end_cursor().blockNumber()
203 end_line = self._get_end_cursor().blockNumber()
204 intercepted = cursor.blockNumber() == end_line
204 intercepted = cursor.blockNumber() == end_line
205
205
206 elif key == QtCore.Qt.Key_Tab:
206 elif key == QtCore.Qt.Key_Tab:
207 if self._reading:
207 if self._reading:
208 intercepted = False
208 intercepted = False
209 else:
209 else:
210 intercepted = not self._tab_pressed()
210 intercepted = not self._tab_pressed()
211
211
212 elif key == QtCore.Qt.Key_Left:
212 elif key == QtCore.Qt.Key_Left:
213 intercepted = not self._in_buffer(position - 1)
213 intercepted = not self._in_buffer(position - 1)
214
214
215 elif key == QtCore.Qt.Key_Home:
215 elif key == QtCore.Qt.Key_Home:
216 cursor.movePosition(QtGui.QTextCursor.StartOfLine)
216 cursor.movePosition(QtGui.QTextCursor.StartOfLine)
217 start_pos = cursor.position()
218 start_line = cursor.blockNumber()
217 start_line = cursor.blockNumber()
219 if start_line == self._get_prompt_cursor().blockNumber():
218 if start_line == self._get_prompt_cursor().blockNumber():
220 start_pos += len(self._prompt)
219 start_pos = self._prompt_pos
221 else:
220 else:
221 start_pos = cursor.position()
222 start_pos += len(self._continuation_prompt)
222 start_pos += len(self._continuation_prompt)
223 if shift_down and self._in_buffer(position):
223 if shift_down and self._in_buffer(position):
224 self._set_selection(position, start_pos)
224 self._set_selection(position, start_pos)
225 else:
225 else:
226 self._set_position(start_pos)
226 self._set_position(start_pos)
227 intercepted = True
227 intercepted = True
228
228
229 elif key == QtCore.Qt.Key_Backspace and not alt_down:
229 elif key == QtCore.Qt.Key_Backspace and not alt_down:
230
230
231 # Line deletion (remove continuation prompt)
231 # Line deletion (remove continuation prompt)
232 len_prompt = len(self._continuation_prompt)
232 len_prompt = len(self._continuation_prompt)
233 if not self._reading and \
233 if not self._reading and \
234 cursor.columnNumber() == len_prompt and \
234 cursor.columnNumber() == len_prompt and \
235 position != self._prompt_pos:
235 position != self._prompt_pos:
236 cursor.setPosition(position - len_prompt,
236 cursor.setPosition(position - len_prompt,
237 QtGui.QTextCursor.KeepAnchor)
237 QtGui.QTextCursor.KeepAnchor)
238 cursor.removeSelectedText()
238 cursor.removeSelectedText()
239
239
240 # Regular backwards deletion
240 # Regular backwards deletion
241 else:
241 else:
242 anchor = cursor.anchor()
242 anchor = cursor.anchor()
243 if anchor == position:
243 if anchor == position:
244 intercepted = not self._in_buffer(position - 1)
244 intercepted = not self._in_buffer(position - 1)
245 else:
245 else:
246 intercepted = not self._in_buffer(min(anchor, position))
246 intercepted = not self._in_buffer(min(anchor, position))
247
247
248 elif key == QtCore.Qt.Key_Delete:
248 elif key == QtCore.Qt.Key_Delete:
249 anchor = cursor.anchor()
249 anchor = cursor.anchor()
250 intercepted = not self._in_buffer(min(anchor, position))
250 intercepted = not self._in_buffer(min(anchor, position))
251
251
252 # Don't move cursor if control is down to allow copy-paste using
252 # Don't move cursor if control is down to allow copy-paste using
253 # the keyboard in any part of the buffer.
253 # the keyboard in any part of the buffer.
254 if not ctrl_down:
254 if not ctrl_down:
255 self._keep_cursor_in_buffer()
255 self._keep_cursor_in_buffer()
256
256
257 if not intercepted:
257 if not intercepted:
258 QtGui.QPlainTextEdit.keyPressEvent(self, event)
258 QtGui.QPlainTextEdit.keyPressEvent(self, event)
259
259
260 #--------------------------------------------------------------------------
260 #--------------------------------------------------------------------------
261 # 'QPlainTextEdit' interface
261 # 'QPlainTextEdit' interface
262 #--------------------------------------------------------------------------
262 #--------------------------------------------------------------------------
263
263
264 def appendHtml(self, html):
264 def appendHtml(self, html):
265 """ Reimplemented to not append HTML as a new paragraph, which doesn't
265 """ Reimplemented to not append HTML as a new paragraph, which doesn't
266 make sense for a console widget.
266 make sense for a console widget.
267 """
267 """
268 cursor = self._get_end_cursor()
268 cursor = self._get_end_cursor()
269 cursor.insertHtml(html)
269 self._insert_html(cursor, html)
270
271 # After appending HTML, the text document "remembers" the current
272 # formatting, which means that subsequent calls to 'appendPlainText'
273 # will be formatted similarly, a behavior that we do not want. To
274 # prevent this, we make sure that the last character has no formatting.
275 cursor.movePosition(QtGui.QTextCursor.Left,
276 QtGui.QTextCursor.KeepAnchor)
277 if cursor.selection().toPlainText().trimmed().isEmpty():
278 # If the last character is whitespace, it doesn't matter how it's
279 # formatted, so just clear the formatting.
280 cursor.setCharFormat(QtGui.QTextCharFormat())
281 else:
282 # Otherwise, add an unformatted space.
283 cursor.movePosition(QtGui.QTextCursor.Right)
284 cursor.insertText(' ', QtGui.QTextCharFormat())
285
270
286 def appendPlainText(self, text):
271 def appendPlainText(self, text):
287 """ Reimplemented to not append text as a new paragraph, which doesn't
272 """ Reimplemented to not append text as a new paragraph, which doesn't
288 make sense for a console widget. Also, if enabled, handle ANSI
273 make sense for a console widget. Also, if enabled, handle ANSI
289 codes.
274 codes.
290 """
275 """
291 cursor = self._get_end_cursor()
276 cursor = self._get_end_cursor()
292 if self.ansi_codes:
277 if self.ansi_codes:
293 for substring in self._ansi_processor.split_string(text):
278 for substring in self._ansi_processor.split_string(text):
294 format = self._ansi_processor.get_format()
279 format = self._ansi_processor.get_format()
295 cursor.insertText(substring, format)
280 cursor.insertText(substring, format)
296 else:
281 else:
297 cursor.insertText(text)
282 cursor.insertText(text)
298
283
299 def clear(self, keep_input=False):
284 def clear(self, keep_input=False):
300 """ Reimplemented to write a new prompt. If 'keep_input' is set,
285 """ Reimplemented to write a new prompt. If 'keep_input' is set,
301 restores the old input buffer when the new prompt is written.
286 restores the old input buffer when the new prompt is written.
302 """
287 """
303 QtGui.QPlainTextEdit.clear(self)
288 QtGui.QPlainTextEdit.clear(self)
304 if keep_input:
289 if keep_input:
305 input_buffer = self.input_buffer
290 input_buffer = self.input_buffer
306 self._show_prompt()
291 self._show_prompt()
307 if keep_input:
292 if keep_input:
308 self.input_buffer = input_buffer
293 self.input_buffer = input_buffer
309
294
310 def paste(self):
295 def paste(self):
311 """ Reimplemented to ensure that text is pasted in the editing region.
296 """ Reimplemented to ensure that text is pasted in the editing region.
312 """
297 """
313 self._keep_cursor_in_buffer()
298 self._keep_cursor_in_buffer()
314 QtGui.QPlainTextEdit.paste(self)
299 QtGui.QPlainTextEdit.paste(self)
315
300
316 def print_(self, printer):
301 def print_(self, printer):
317 """ Reimplemented to work around a bug in PyQt: the C++ level 'print_'
302 """ Reimplemented to work around a bug in PyQt: the C++ level 'print_'
318 slot has the wrong signature.
303 slot has the wrong signature.
319 """
304 """
320 QtGui.QPlainTextEdit.print_(self, printer)
305 QtGui.QPlainTextEdit.print_(self, printer)
321
306
322 #---------------------------------------------------------------------------
307 #---------------------------------------------------------------------------
323 # 'ConsoleWidget' public interface
308 # 'ConsoleWidget' public interface
324 #---------------------------------------------------------------------------
309 #---------------------------------------------------------------------------
325
310
326 def execute(self, source=None, hidden=False, interactive=False):
311 def execute(self, source=None, hidden=False, interactive=False):
327 """ Executes source or the input buffer, possibly prompting for more
312 """ Executes source or the input buffer, possibly prompting for more
328 input.
313 input.
329
314
330 Parameters:
315 Parameters:
331 -----------
316 -----------
332 source : str, optional
317 source : str, optional
333
318
334 The source to execute. If not specified, the input buffer will be
319 The source to execute. If not specified, the input buffer will be
335 used. If specified and 'hidden' is False, the input buffer will be
320 used. If specified and 'hidden' is False, the input buffer will be
336 replaced with the source before execution.
321 replaced with the source before execution.
337
322
338 hidden : bool, optional (default False)
323 hidden : bool, optional (default False)
339
324
340 If set, no output will be shown and the prompt will not be modified.
325 If set, no output will be shown and the prompt will not be modified.
341 In other words, it will be completely invisible to the user that
326 In other words, it will be completely invisible to the user that
342 an execution has occurred.
327 an execution has occurred.
343
328
344 interactive : bool, optional (default False)
329 interactive : bool, optional (default False)
345
330
346 Whether the console is to treat the source as having been manually
331 Whether the console is to treat the source as having been manually
347 entered by the user. The effect of this parameter depends on the
332 entered by the user. The effect of this parameter depends on the
348 subclass implementation.
333 subclass implementation.
349
334
350 Raises:
335 Raises:
351 -------
336 -------
352 RuntimeError
337 RuntimeError
353 If incomplete input is given and 'hidden' is True. In this case,
338 If incomplete input is given and 'hidden' is True. In this case,
354 it not possible to prompt for more input.
339 it not possible to prompt for more input.
355
340
356 Returns:
341 Returns:
357 --------
342 --------
358 A boolean indicating whether the source was executed.
343 A boolean indicating whether the source was executed.
359 """
344 """
360 if not hidden:
345 if not hidden:
361 if source is not None:
346 if source is not None:
362 self.input_buffer = source
347 self.input_buffer = source
363
348
364 self.appendPlainText('\n')
349 self.appendPlainText('\n')
365 self._executing_input_buffer = self.input_buffer
350 self._executing_input_buffer = self.input_buffer
366 self._executing = True
351 self._executing = True
367 self._prompt_finished()
352 self._prompt_finished()
368
353
369 real_source = self.input_buffer if source is None else source
354 real_source = self.input_buffer if source is None else source
370 complete = self._is_complete(real_source, interactive)
355 complete = self._is_complete(real_source, interactive)
371 if complete:
356 if complete:
372 if not hidden:
357 if not hidden:
373 # The maximum block count is only in effect during execution.
358 # The maximum block count is only in effect during execution.
374 # This ensures that _prompt_pos does not become invalid due to
359 # This ensures that _prompt_pos does not become invalid due to
375 # text truncation.
360 # text truncation.
376 self.setMaximumBlockCount(self.buffer_size)
361 self.setMaximumBlockCount(self.buffer_size)
377 self._execute(real_source, hidden)
362 self._execute(real_source, hidden)
378 elif hidden:
363 elif hidden:
379 raise RuntimeError('Incomplete noninteractive input: "%s"' % source)
364 raise RuntimeError('Incomplete noninteractive input: "%s"' % source)
380 else:
365 else:
381 self._show_continuation_prompt()
366 self._show_continuation_prompt()
382
367
383 return complete
368 return complete
384
369
385 def _get_input_buffer(self):
370 def _get_input_buffer(self):
386 """ The text that the user has entered entered at the current prompt.
371 """ The text that the user has entered entered at the current prompt.
387 """
372 """
388 # If we're executing, the input buffer may not even exist anymore due to
373 # If we're executing, the input buffer may not even exist anymore due to
389 # the limit imposed by 'buffer_size'. Therefore, we store it.
374 # the limit imposed by 'buffer_size'. Therefore, we store it.
390 if self._executing:
375 if self._executing:
391 return self._executing_input_buffer
376 return self._executing_input_buffer
392
377
393 cursor = self._get_end_cursor()
378 cursor = self._get_end_cursor()
394 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
379 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
395 input_buffer = str(cursor.selection().toPlainText())
380 input_buffer = str(cursor.selection().toPlainText())
396
381
397 # Strip out continuation prompts.
382 # Strip out continuation prompts.
398 return input_buffer.replace('\n' + self._continuation_prompt, '\n')
383 return input_buffer.replace('\n' + self._continuation_prompt, '\n')
399
384
400 def _set_input_buffer(self, string):
385 def _set_input_buffer(self, string):
401 """ Replaces the text in the input buffer with 'string'.
386 """ Replaces the text in the input buffer with 'string'.
402 """
387 """
403 # Remove old text.
388 # Remove old text.
404 cursor = self._get_end_cursor()
389 cursor = self._get_end_cursor()
405 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
390 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
406 cursor.removeSelectedText()
391 cursor.removeSelectedText()
407
392
408 # Insert new text with continuation prompts.
393 # Insert new text with continuation prompts.
409 lines = string.splitlines(True)
394 lines = string.splitlines(True)
410 if lines:
395 if lines:
411 self.appendPlainText(lines[0])
396 self.appendPlainText(lines[0])
412 for i in xrange(1, len(lines)):
397 for i in xrange(1, len(lines)):
413 if self._continuation_prompt_html is None:
398 if self._continuation_prompt_html is None:
414 self.appendPlainText(self._continuation_prompt)
399 self.appendPlainText(self._continuation_prompt)
415 else:
400 else:
416 self.appendHtml(self._continuation_prompt_html)
401 self.appendHtml(self._continuation_prompt_html)
417 self.appendPlainText(lines[i])
402 self.appendPlainText(lines[i])
418 self.moveCursor(QtGui.QTextCursor.End)
403 self.moveCursor(QtGui.QTextCursor.End)
419
404
420 input_buffer = property(_get_input_buffer, _set_input_buffer)
405 input_buffer = property(_get_input_buffer, _set_input_buffer)
421
406
422 def _get_input_buffer_cursor_line(self):
407 def _get_input_buffer_cursor_line(self):
423 """ The text in the line of the input buffer in which the user's cursor
408 """ The text in the line of the input buffer in which the user's cursor
424 rests. Returns a string if there is such a line; otherwise, None.
409 rests. Returns a string if there is such a line; otherwise, None.
425 """
410 """
426 if self._executing:
411 if self._executing:
427 return None
412 return None
428 cursor = self.textCursor()
413 cursor = self.textCursor()
429 if cursor.position() >= self._prompt_pos:
414 if cursor.position() >= self._prompt_pos:
430 text = self._get_block_plain_text(cursor.block())
415 text = self._get_block_plain_text(cursor.block())
431 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
416 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
432 return text[len(self._prompt):]
417 return text[len(self._prompt):]
433 else:
418 else:
434 return text[len(self._continuation_prompt):]
419 return text[len(self._continuation_prompt):]
435 else:
420 else:
436 return None
421 return None
437
422
438 input_buffer_cursor_line = property(_get_input_buffer_cursor_line)
423 input_buffer_cursor_line = property(_get_input_buffer_cursor_line)
439
424
440 def _get_font(self):
425 def _get_font(self):
441 """ The base font being used by the ConsoleWidget.
426 """ The base font being used by the ConsoleWidget.
442 """
427 """
443 return self.document().defaultFont()
428 return self.document().defaultFont()
444
429
445 def _set_font(self, font):
430 def _set_font(self, font):
446 """ Sets the base font for the ConsoleWidget to the specified QFont.
431 """ Sets the base font for the ConsoleWidget to the specified QFont.
447 """
432 """
448 font_metrics = QtGui.QFontMetrics(font)
433 font_metrics = QtGui.QFontMetrics(font)
449 self.setTabStopWidth(self.tab_width * font_metrics.width(' '))
434 self.setTabStopWidth(self.tab_width * font_metrics.width(' '))
450
435
451 self._completion_widget.setFont(font)
436 self._completion_widget.setFont(font)
452 self.document().setDefaultFont(font)
437 self.document().setDefaultFont(font)
453
438
454 font = property(_get_font, _set_font)
439 font = property(_get_font, _set_font)
455
440
456 def reset_font(self):
441 def reset_font(self):
457 """ Sets the font to the default fixed-width font for this platform.
442 """ Sets the font to the default fixed-width font for this platform.
458 """
443 """
459 if sys.platform == 'win32':
444 if sys.platform == 'win32':
460 name = 'Courier'
445 name = 'Courier'
461 elif sys.platform == 'darwin':
446 elif sys.platform == 'darwin':
462 name = 'Monaco'
447 name = 'Monaco'
463 else:
448 else:
464 name = 'Monospace'
449 name = 'Monospace'
465 font = QtGui.QFont(name, QtGui.qApp.font().pointSize())
450 font = QtGui.QFont(name, QtGui.qApp.font().pointSize())
466 font.setStyleHint(QtGui.QFont.TypeWriter)
451 font.setStyleHint(QtGui.QFont.TypeWriter)
467 self._set_font(font)
452 self._set_font(font)
468
453
469 def _get_tab_width(self):
454 def _get_tab_width(self):
470 """ The width (in terms of space characters) for tab characters.
455 """ The width (in terms of space characters) for tab characters.
471 """
456 """
472 return self._tab_width
457 return self._tab_width
473
458
474 def _set_tab_width(self, tab_width):
459 def _set_tab_width(self, tab_width):
475 """ Sets the width (in terms of space characters) for tab characters.
460 """ Sets the width (in terms of space characters) for tab characters.
476 """
461 """
477 font_metrics = QtGui.QFontMetrics(self.font)
462 font_metrics = QtGui.QFontMetrics(self.font)
478 self.setTabStopWidth(tab_width * font_metrics.width(' '))
463 self.setTabStopWidth(tab_width * font_metrics.width(' '))
479
464
480 self._tab_width = tab_width
465 self._tab_width = tab_width
481
466
482 tab_width = property(_get_tab_width, _set_tab_width)
467 tab_width = property(_get_tab_width, _set_tab_width)
483
468
484 #---------------------------------------------------------------------------
469 #---------------------------------------------------------------------------
485 # 'ConsoleWidget' abstract interface
470 # 'ConsoleWidget' abstract interface
486 #---------------------------------------------------------------------------
471 #---------------------------------------------------------------------------
487
472
488 def _is_complete(self, source, interactive):
473 def _is_complete(self, source, interactive):
489 """ Returns whether 'source' can be executed. When triggered by an
474 """ Returns whether 'source' can be executed. When triggered by an
490 Enter/Return key press, 'interactive' is True; otherwise, it is
475 Enter/Return key press, 'interactive' is True; otherwise, it is
491 False.
476 False.
492 """
477 """
493 raise NotImplementedError
478 raise NotImplementedError
494
479
495 def _execute(self, source, hidden):
480 def _execute(self, source, hidden):
496 """ Execute 'source'. If 'hidden', do not show any output.
481 """ Execute 'source'. If 'hidden', do not show any output.
497 """
482 """
498 raise NotImplementedError
483 raise NotImplementedError
499
484
500 def _prompt_started_hook(self):
485 def _prompt_started_hook(self):
501 """ Called immediately after a new prompt is displayed.
486 """ Called immediately after a new prompt is displayed.
502 """
487 """
503 pass
488 pass
504
489
505 def _prompt_finished_hook(self):
490 def _prompt_finished_hook(self):
506 """ Called immediately after a prompt is finished, i.e. when some input
491 """ Called immediately after a prompt is finished, i.e. when some input
507 will be processed and a new prompt displayed.
492 will be processed and a new prompt displayed.
508 """
493 """
509 pass
494 pass
510
495
511 def _up_pressed(self):
496 def _up_pressed(self):
512 """ Called when the up key is pressed. Returns whether to continue
497 """ Called when the up key is pressed. Returns whether to continue
513 processing the event.
498 processing the event.
514 """
499 """
515 return True
500 return True
516
501
517 def _down_pressed(self):
502 def _down_pressed(self):
518 """ Called when the down key is pressed. Returns whether to continue
503 """ Called when the down key is pressed. Returns whether to continue
519 processing the event.
504 processing the event.
520 """
505 """
521 return True
506 return True
522
507
523 def _tab_pressed(self):
508 def _tab_pressed(self):
524 """ Called when the tab key is pressed. Returns whether to continue
509 """ Called when the tab key is pressed. Returns whether to continue
525 processing the event.
510 processing the event.
526 """
511 """
527 return False
512 return False
528
513
529 #--------------------------------------------------------------------------
514 #--------------------------------------------------------------------------
530 # 'ConsoleWidget' protected interface
515 # 'ConsoleWidget' protected interface
531 #--------------------------------------------------------------------------
516 #--------------------------------------------------------------------------
532
517
533 def _append_html_fetching_plain_text(self, html):
518 def _append_html_fetching_plain_text(self, html):
534 """ Appends 'html', then returns the plain text version of it.
519 """ Appends 'html', then returns the plain text version of it.
535 """
520 """
536 anchor = self._get_end_cursor().position()
521 anchor = self._get_end_cursor().position()
537 self.appendHtml(html)
522 self.appendHtml(html)
538 cursor = self._get_end_cursor()
523 cursor = self._get_end_cursor()
539 cursor.setPosition(anchor, QtGui.QTextCursor.KeepAnchor)
524 cursor.setPosition(anchor, QtGui.QTextCursor.KeepAnchor)
540 return str(cursor.selection().toPlainText())
525 return str(cursor.selection().toPlainText())
541
526
542 def _append_plain_text_keeping_prompt(self, text):
527 def _append_plain_text_keeping_prompt(self, text):
543 """ Writes 'text' after the current prompt, then restores the old prompt
528 """ Writes 'text' after the current prompt, then restores the old prompt
544 with its old input buffer.
529 with its old input buffer.
545 """
530 """
546 input_buffer = self.input_buffer
531 input_buffer = self.input_buffer
547 self.appendPlainText('\n')
532 self.appendPlainText('\n')
548 self._prompt_finished()
533 self._prompt_finished()
549
534
550 self.appendPlainText(text)
535 self.appendPlainText(text)
551 self._show_prompt()
536 self._show_prompt()
552 self.input_buffer = input_buffer
537 self.input_buffer = input_buffer
553
538
554 def _control_down(self, modifiers):
539 def _control_down(self, modifiers):
555 """ Given a KeyboardModifiers flags object, return whether the Control
540 """ Given a KeyboardModifiers flags object, return whether the Control
556 key is down (on Mac OS, treat the Command key as a synonym for
541 key is down (on Mac OS, treat the Command key as a synonym for
557 Control).
542 Control).
558 """
543 """
559 down = bool(modifiers & QtCore.Qt.ControlModifier)
544 down = bool(modifiers & QtCore.Qt.ControlModifier)
560
545
561 # Note: on Mac OS, ControlModifier corresponds to the Command key while
546 # Note: on Mac OS, ControlModifier corresponds to the Command key while
562 # MetaModifier corresponds to the Control key.
547 # MetaModifier corresponds to the Control key.
563 if sys.platform == 'darwin':
548 if sys.platform == 'darwin':
564 down = down ^ bool(modifiers & QtCore.Qt.MetaModifier)
549 down = down ^ bool(modifiers & QtCore.Qt.MetaModifier)
565
550
566 return down
551 return down
567
552
568 def _complete_with_items(self, cursor, items):
553 def _complete_with_items(self, cursor, items):
569 """ Performs completion with 'items' at the specified cursor location.
554 """ Performs completion with 'items' at the specified cursor location.
570 """
555 """
571 if len(items) == 1:
556 if len(items) == 1:
572 cursor.setPosition(self.textCursor().position(),
557 cursor.setPosition(self.textCursor().position(),
573 QtGui.QTextCursor.KeepAnchor)
558 QtGui.QTextCursor.KeepAnchor)
574 cursor.insertText(items[0])
559 cursor.insertText(items[0])
575 elif len(items) > 1:
560 elif len(items) > 1:
576 if self.gui_completion:
561 if self.gui_completion:
577 self._completion_widget.show_items(cursor, items)
562 self._completion_widget.show_items(cursor, items)
578 else:
563 else:
579 text = self._format_as_columns(items)
564 text = self._format_as_columns(items)
580 self._append_plain_text_keeping_prompt(text)
565 self._append_plain_text_keeping_prompt(text)
581
566
582 def _format_as_columns(self, items, separator=' '):
567 def _format_as_columns(self, items, separator=' '):
583 """ Transform a list of strings into a single string with columns.
568 """ Transform a list of strings into a single string with columns.
584
569
585 Parameters
570 Parameters
586 ----------
571 ----------
587 items : sequence of strings
572 items : sequence of strings
588 The strings to process.
573 The strings to process.
589
574
590 separator : str, optional [default is two spaces]
575 separator : str, optional [default is two spaces]
591 The string that separates columns.
576 The string that separates columns.
592
577
593 Returns
578 Returns
594 -------
579 -------
595 The formatted string.
580 The formatted string.
596 """
581 """
597 # Note: this code is adapted from columnize 0.3.2.
582 # Note: this code is adapted from columnize 0.3.2.
598 # See http://code.google.com/p/pycolumnize/
583 # See http://code.google.com/p/pycolumnize/
599
584
600 font_metrics = QtGui.QFontMetrics(self.font)
585 font_metrics = QtGui.QFontMetrics(self.font)
601 displaywidth = max(5, (self.width() / font_metrics.width(' ')) - 1)
586 displaywidth = max(5, (self.width() / font_metrics.width(' ')) - 1)
602
587
603 # Some degenerate cases.
588 # Some degenerate cases.
604 size = len(items)
589 size = len(items)
605 if size == 0:
590 if size == 0:
606 return '\n'
591 return '\n'
607 elif size == 1:
592 elif size == 1:
608 return '%s\n' % str(items[0])
593 return '%s\n' % str(items[0])
609
594
610 # Try every row count from 1 upwards
595 # Try every row count from 1 upwards
611 array_index = lambda nrows, row, col: nrows*col + row
596 array_index = lambda nrows, row, col: nrows*col + row
612 for nrows in range(1, size):
597 for nrows in range(1, size):
613 ncols = (size + nrows - 1) // nrows
598 ncols = (size + nrows - 1) // nrows
614 colwidths = []
599 colwidths = []
615 totwidth = -len(separator)
600 totwidth = -len(separator)
616 for col in range(ncols):
601 for col in range(ncols):
617 # Get max column width for this column
602 # Get max column width for this column
618 colwidth = 0
603 colwidth = 0
619 for row in range(nrows):
604 for row in range(nrows):
620 i = array_index(nrows, row, col)
605 i = array_index(nrows, row, col)
621 if i >= size: break
606 if i >= size: break
622 x = items[i]
607 x = items[i]
623 colwidth = max(colwidth, len(x))
608 colwidth = max(colwidth, len(x))
624 colwidths.append(colwidth)
609 colwidths.append(colwidth)
625 totwidth += colwidth + len(separator)
610 totwidth += colwidth + len(separator)
626 if totwidth > displaywidth:
611 if totwidth > displaywidth:
627 break
612 break
628 if totwidth <= displaywidth:
613 if totwidth <= displaywidth:
629 break
614 break
630
615
631 # The smallest number of rows computed and the max widths for each
616 # The smallest number of rows computed and the max widths for each
632 # column has been obtained. Now we just have to format each of the rows.
617 # column has been obtained. Now we just have to format each of the rows.
633 string = ''
618 string = ''
634 for row in range(nrows):
619 for row in range(nrows):
635 texts = []
620 texts = []
636 for col in range(ncols):
621 for col in range(ncols):
637 i = row + nrows*col
622 i = row + nrows*col
638 if i >= size:
623 if i >= size:
639 texts.append('')
624 texts.append('')
640 else:
625 else:
641 texts.append(items[i])
626 texts.append(items[i])
642 while texts and not texts[-1]:
627 while texts and not texts[-1]:
643 del texts[-1]
628 del texts[-1]
644 for col in range(len(texts)):
629 for col in range(len(texts)):
645 texts[col] = texts[col].ljust(colwidths[col])
630 texts[col] = texts[col].ljust(colwidths[col])
646 string += '%s\n' % str(separator.join(texts))
631 string += '%s\n' % str(separator.join(texts))
647 return string
632 return string
648
633
649 def _get_block_plain_text(self, block):
634 def _get_block_plain_text(self, block):
650 """ Given a QTextBlock, return its unformatted text.
635 """ Given a QTextBlock, return its unformatted text.
651 """
636 """
652 cursor = QtGui.QTextCursor(block)
637 cursor = QtGui.QTextCursor(block)
653 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
638 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
654 cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
639 cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
655 QtGui.QTextCursor.KeepAnchor)
640 QtGui.QTextCursor.KeepAnchor)
656 return str(cursor.selection().toPlainText())
641 return str(cursor.selection().toPlainText())
657
642
658 def _get_end_cursor(self):
643 def _get_end_cursor(self):
659 """ Convenience method that returns a cursor for the last character.
644 """ Convenience method that returns a cursor for the last character.
660 """
645 """
661 cursor = self.textCursor()
646 cursor = self.textCursor()
662 cursor.movePosition(QtGui.QTextCursor.End)
647 cursor.movePosition(QtGui.QTextCursor.End)
663 return cursor
648 return cursor
664
649
665 def _get_prompt_cursor(self):
650 def _get_prompt_cursor(self):
666 """ Convenience method that returns a cursor for the prompt position.
651 """ Convenience method that returns a cursor for the prompt position.
667 """
652 """
668 cursor = self.textCursor()
653 cursor = self.textCursor()
669 cursor.setPosition(self._prompt_pos)
654 cursor.setPosition(self._prompt_pos)
670 return cursor
655 return cursor
671
656
672 def _get_selection_cursor(self, start, end):
657 def _get_selection_cursor(self, start, end):
673 """ Convenience method that returns a cursor with text selected between
658 """ Convenience method that returns a cursor with text selected between
674 the positions 'start' and 'end'.
659 the positions 'start' and 'end'.
675 """
660 """
676 cursor = self.textCursor()
661 cursor = self.textCursor()
677 cursor.setPosition(start)
662 cursor.setPosition(start)
678 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
663 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
679 return cursor
664 return cursor
680
665
681 def _get_word_start_cursor(self, position):
666 def _get_word_start_cursor(self, position):
682 """ Find the start of the word to the left the given position. If a
667 """ Find the start of the word to the left the given position. If a
683 sequence of non-word characters precedes the first word, skip over
668 sequence of non-word characters precedes the first word, skip over
684 them. (This emulates the behavior of bash, emacs, etc.)
669 them. (This emulates the behavior of bash, emacs, etc.)
685 """
670 """
686 document = self.document()
671 document = self.document()
687 position -= 1
672 position -= 1
688 while self._in_buffer(position) and \
673 while self._in_buffer(position) and \
689 not document.characterAt(position).isLetterOrNumber():
674 not document.characterAt(position).isLetterOrNumber():
690 position -= 1
675 position -= 1
691 while self._in_buffer(position) and \
676 while self._in_buffer(position) and \
692 document.characterAt(position).isLetterOrNumber():
677 document.characterAt(position).isLetterOrNumber():
693 position -= 1
678 position -= 1
694 cursor = self.textCursor()
679 cursor = self.textCursor()
695 cursor.setPosition(position + 1)
680 cursor.setPosition(position + 1)
696 return cursor
681 return cursor
697
682
698 def _get_word_end_cursor(self, position):
683 def _get_word_end_cursor(self, position):
699 """ Find the end of the word to the right the given position. If a
684 """ Find the end of the word to the right the given position. If a
700 sequence of non-word characters precedes the first word, skip over
685 sequence of non-word characters precedes the first word, skip over
701 them. (This emulates the behavior of bash, emacs, etc.)
686 them. (This emulates the behavior of bash, emacs, etc.)
702 """
687 """
703 document = self.document()
688 document = self.document()
704 end = self._get_end_cursor().position()
689 end = self._get_end_cursor().position()
705 while position < end and \
690 while position < end and \
706 not document.characterAt(position).isLetterOrNumber():
691 not document.characterAt(position).isLetterOrNumber():
707 position += 1
692 position += 1
708 while position < end and \
693 while position < end and \
709 document.characterAt(position).isLetterOrNumber():
694 document.characterAt(position).isLetterOrNumber():
710 position += 1
695 position += 1
711 cursor = self.textCursor()
696 cursor = self.textCursor()
712 cursor.setPosition(position)
697 cursor.setPosition(position)
713 return cursor
698 return cursor
714
699
700 def _insert_html(self, cursor, html):
701 """ Insert HTML using the specified cursor in such a way that future
702 formatting is unaffected.
703 """
704 cursor.insertHtml(html)
705
706 # After inserting HTML, the text document "remembers" the current
707 # formatting, which means that subsequent calls adding plain text
708 # will result in similar formatting, a behavior that we do not want. To
709 # prevent this, we make sure that the last character has no formatting.
710 cursor.movePosition(QtGui.QTextCursor.Left,
711 QtGui.QTextCursor.KeepAnchor)
712 if cursor.selection().toPlainText().trimmed().isEmpty():
713 # If the last character is whitespace, it doesn't matter how it's
714 # formatted, so just clear the formatting.
715 cursor.setCharFormat(QtGui.QTextCharFormat())
716 else:
717 # Otherwise, add an unformatted space.
718 cursor.movePosition(QtGui.QTextCursor.Right)
719 cursor.insertText(' ', QtGui.QTextCharFormat())
720
715 def _prompt_started(self):
721 def _prompt_started(self):
716 """ Called immediately after a new prompt is displayed.
722 """ Called immediately after a new prompt is displayed.
717 """
723 """
718 # Temporarily disable the maximum block count to permit undo/redo and
724 # Temporarily disable the maximum block count to permit undo/redo and
719 # to ensure that the prompt position does not change due to truncation.
725 # to ensure that the prompt position does not change due to truncation.
720 self.setMaximumBlockCount(0)
726 self.setMaximumBlockCount(0)
721 self.setUndoRedoEnabled(True)
727 self.setUndoRedoEnabled(True)
722
728
723 self.setReadOnly(False)
729 self.setReadOnly(False)
724 self.moveCursor(QtGui.QTextCursor.End)
730 self.moveCursor(QtGui.QTextCursor.End)
725 self.centerCursor()
731 self.centerCursor()
726
732
727 self._executing = False
733 self._executing = False
728 self._prompt_started_hook()
734 self._prompt_started_hook()
729
735
730 def _prompt_finished(self):
736 def _prompt_finished(self):
731 """ Called immediately after a prompt is finished, i.e. when some input
737 """ Called immediately after a prompt is finished, i.e. when some input
732 will be processed and a new prompt displayed.
738 will be processed and a new prompt displayed.
733 """
739 """
734 self.setUndoRedoEnabled(False)
740 self.setUndoRedoEnabled(False)
735 self.setReadOnly(True)
741 self.setReadOnly(True)
736 self._prompt_finished_hook()
742 self._prompt_finished_hook()
737
743
738 def _readline(self, prompt='', callback=None):
744 def _readline(self, prompt='', callback=None):
739 """ Reads one line of input from the user.
745 """ Reads one line of input from the user.
740
746
741 Parameters
747 Parameters
742 ----------
748 ----------
743 prompt : str, optional
749 prompt : str, optional
744 The prompt to print before reading the line.
750 The prompt to print before reading the line.
745
751
746 callback : callable, optional
752 callback : callable, optional
747 A callback to execute with the read line. If not specified, input is
753 A callback to execute with the read line. If not specified, input is
748 read *synchronously* and this method does not return until it has
754 read *synchronously* and this method does not return until it has
749 been read.
755 been read.
750
756
751 Returns
757 Returns
752 -------
758 -------
753 If a callback is specified, returns nothing. Otherwise, returns the
759 If a callback is specified, returns nothing. Otherwise, returns the
754 input string with the trailing newline stripped.
760 input string with the trailing newline stripped.
755 """
761 """
756 if self._reading:
762 if self._reading:
757 raise RuntimeError('Cannot read a line. Widget is already reading.')
763 raise RuntimeError('Cannot read a line. Widget is already reading.')
758
764
759 if not callback and not self.isVisible():
765 if not callback and not self.isVisible():
760 # If the user cannot see the widget, this function cannot return.
766 # If the user cannot see the widget, this function cannot return.
761 raise RuntimeError('Cannot synchronously read a line if the widget'
767 raise RuntimeError('Cannot synchronously read a line if the widget'
762 'is not visible!')
768 'is not visible!')
763
769
764 self._reading = True
770 self._reading = True
765 self._show_prompt(prompt, newline=False)
771 self._show_prompt(prompt, newline=False)
766
772
767 if callback is None:
773 if callback is None:
768 self._reading_callback = None
774 self._reading_callback = None
769 while self._reading:
775 while self._reading:
770 QtCore.QCoreApplication.processEvents()
776 QtCore.QCoreApplication.processEvents()
771 return self.input_buffer.rstrip('\n')
777 return self.input_buffer.rstrip('\n')
772
778
773 else:
779 else:
774 self._reading_callback = lambda: \
780 self._reading_callback = lambda: \
775 callback(self.input_buffer.rstrip('\n'))
781 callback(self.input_buffer.rstrip('\n'))
776
782
777 def _reset(self):
783 def _reset(self):
778 """ Clears the console and resets internal state variables.
784 """ Clears the console and resets internal state variables.
779 """
785 """
780 QtGui.QPlainTextEdit.clear(self)
786 QtGui.QPlainTextEdit.clear(self)
781 self._executing = self._reading = False
787 self._executing = self._reading = False
782
788
783 def _set_continuation_prompt(self, prompt, html=False):
789 def _set_continuation_prompt(self, prompt, html=False):
784 """ Sets the continuation prompt.
790 """ Sets the continuation prompt.
785
791
786 Parameters
792 Parameters
787 ----------
793 ----------
788 prompt : str
794 prompt : str
789 The prompt to show when more input is needed.
795 The prompt to show when more input is needed.
790
796
791 html : bool, optional (default False)
797 html : bool, optional (default False)
792 If set, the prompt will be inserted as formatted HTML. Otherwise,
798 If set, the prompt will be inserted as formatted HTML. Otherwise,
793 the prompt will be treated as plain text, though ANSI color codes
799 the prompt will be treated as plain text, though ANSI color codes
794 will be handled.
800 will be handled.
795 """
801 """
796 if html:
802 if html:
797 self._continuation_prompt_html = prompt
803 self._continuation_prompt_html = prompt
798 else:
804 else:
799 self._continuation_prompt = prompt
805 self._continuation_prompt = prompt
800 self._continuation_prompt_html = None
806 self._continuation_prompt_html = None
801
807
802 def _set_position(self, position):
808 def _set_position(self, position):
803 """ Convenience method to set the position of the cursor.
809 """ Convenience method to set the position of the cursor.
804 """
810 """
805 cursor = self.textCursor()
811 cursor = self.textCursor()
806 cursor.setPosition(position)
812 cursor.setPosition(position)
807 self.setTextCursor(cursor)
813 self.setTextCursor(cursor)
808
814
809 def _set_selection(self, start, end):
815 def _set_selection(self, start, end):
810 """ Convenience method to set the current selected text.
816 """ Convenience method to set the current selected text.
811 """
817 """
812 self.setTextCursor(self._get_selection_cursor(start, end))
818 self.setTextCursor(self._get_selection_cursor(start, end))
813
819
814 def _show_prompt(self, prompt=None, html=False, newline=True):
820 def _show_prompt(self, prompt=None, html=False, newline=True):
815 """ Writes a new prompt at the end of the buffer.
821 """ Writes a new prompt at the end of the buffer.
816
822
817 Parameters
823 Parameters
818 ----------
824 ----------
819 prompt : str, optional
825 prompt : str, optional
820 The prompt to show. If not specified, the previous prompt is used.
826 The prompt to show. If not specified, the previous prompt is used.
821
827
822 html : bool, optional (default False)
828 html : bool, optional (default False)
823 Only relevant when a prompt is specified. If set, the prompt will
829 Only relevant when a prompt is specified. If set, the prompt will
824 be inserted as formatted HTML. Otherwise, the prompt will be treated
830 be inserted as formatted HTML. Otherwise, the prompt will be treated
825 as plain text, though ANSI color codes will be handled.
831 as plain text, though ANSI color codes will be handled.
826
832
827 newline : bool, optional (default True)
833 newline : bool, optional (default True)
828 If set, a new line will be written before showing the prompt if
834 If set, a new line will be written before showing the prompt if
829 there is not already a newline at the end of the buffer.
835 there is not already a newline at the end of the buffer.
830 """
836 """
831 # Insert a preliminary newline, if necessary.
837 # Insert a preliminary newline, if necessary.
832 if newline:
838 if newline:
833 cursor = self._get_end_cursor()
839 cursor = self._get_end_cursor()
834 if cursor.position() > 0:
840 if cursor.position() > 0:
835 cursor.movePosition(QtGui.QTextCursor.Left,
841 cursor.movePosition(QtGui.QTextCursor.Left,
836 QtGui.QTextCursor.KeepAnchor)
842 QtGui.QTextCursor.KeepAnchor)
837 if str(cursor.selection().toPlainText()) != '\n':
843 if str(cursor.selection().toPlainText()) != '\n':
838 self.appendPlainText('\n')
844 self.appendPlainText('\n')
839
845
840 # Write the prompt.
846 # Write the prompt.
841 if prompt is None:
847 if prompt is None:
842 if self._prompt_html is None:
848 if self._prompt_html is None:
843 self.appendPlainText(self._prompt)
849 self.appendPlainText(self._prompt)
844 else:
850 else:
845 self.appendHtml(self._prompt_html)
851 self.appendHtml(self._prompt_html)
846 else:
852 else:
847 if html:
853 if html:
848 self._prompt = self._append_html_fetching_plain_text(prompt)
854 self._prompt = self._append_html_fetching_plain_text(prompt)
849 self._prompt_html = prompt
855 self._prompt_html = prompt
850 else:
856 else:
851 self.appendPlainText(prompt)
857 self.appendPlainText(prompt)
852 self._prompt = prompt
858 self._prompt = prompt
853 self._prompt_html = None
859 self._prompt_html = None
854
860
855 self._prompt_pos = self._get_end_cursor().position()
861 self._prompt_pos = self._get_end_cursor().position()
856 self._prompt_started()
862 self._prompt_started()
857
863
858 def _show_continuation_prompt(self):
864 def _show_continuation_prompt(self):
859 """ Writes a new continuation prompt at the end of the buffer.
865 """ Writes a new continuation prompt at the end of the buffer.
860 """
866 """
861 if self._continuation_prompt_html is None:
867 if self._continuation_prompt_html is None:
862 self.appendPlainText(self._continuation_prompt)
868 self.appendPlainText(self._continuation_prompt)
863 else:
869 else:
864 self._continuation_prompt = self._append_html_fetching_plain_text(
870 self._continuation_prompt = self._append_html_fetching_plain_text(
865 self._continuation_prompt_html)
871 self._continuation_prompt_html)
866
872
867 self._prompt_started()
873 self._prompt_started()
868
874
869 def _in_buffer(self, position):
875 def _in_buffer(self, position):
870 """ Returns whether the given position is inside the editing region.
876 """ Returns whether the given position is inside the editing region.
871 """
877 """
872 return position >= self._prompt_pos
878 return position >= self._prompt_pos
873
879
874 def _keep_cursor_in_buffer(self):
880 def _keep_cursor_in_buffer(self):
875 """ Ensures that the cursor is inside the editing region. Returns
881 """ Ensures that the cursor is inside the editing region. Returns
876 whether the cursor was moved.
882 whether the cursor was moved.
877 """
883 """
878 cursor = self.textCursor()
884 cursor = self.textCursor()
879 if cursor.position() < self._prompt_pos:
885 if cursor.position() < self._prompt_pos:
880 cursor.movePosition(QtGui.QTextCursor.End)
886 cursor.movePosition(QtGui.QTextCursor.End)
881 self.setTextCursor(cursor)
887 self.setTextCursor(cursor)
882 return True
888 return True
883 else:
889 else:
884 return False
890 return False
885
891
886
892
887 class HistoryConsoleWidget(ConsoleWidget):
893 class HistoryConsoleWidget(ConsoleWidget):
888 """ A ConsoleWidget that keeps a history of the commands that have been
894 """ A ConsoleWidget that keeps a history of the commands that have been
889 executed.
895 executed.
890 """
896 """
891
897
892 #---------------------------------------------------------------------------
898 #---------------------------------------------------------------------------
893 # 'QObject' interface
899 # 'QObject' interface
894 #---------------------------------------------------------------------------
900 #---------------------------------------------------------------------------
895
901
896 def __init__(self, parent=None):
902 def __init__(self, parent=None):
897 super(HistoryConsoleWidget, self).__init__(parent)
903 super(HistoryConsoleWidget, self).__init__(parent)
898
904
899 self._history = []
905 self._history = []
900 self._history_index = 0
906 self._history_index = 0
901
907
902 #---------------------------------------------------------------------------
908 #---------------------------------------------------------------------------
903 # 'ConsoleWidget' public interface
909 # 'ConsoleWidget' public interface
904 #---------------------------------------------------------------------------
910 #---------------------------------------------------------------------------
905
911
906 def execute(self, source=None, hidden=False, interactive=False):
912 def execute(self, source=None, hidden=False, interactive=False):
907 """ Reimplemented to the store history.
913 """ Reimplemented to the store history.
908 """
914 """
909 if not hidden:
915 if not hidden:
910 history = self.input_buffer if source is None else source
916 history = self.input_buffer if source is None else source
911
917
912 executed = super(HistoryConsoleWidget, self).execute(
918 executed = super(HistoryConsoleWidget, self).execute(
913 source, hidden, interactive)
919 source, hidden, interactive)
914
920
915 if executed and not hidden:
921 if executed and not hidden:
916 self._history.append(history.rstrip())
922 self._history.append(history.rstrip())
917 self._history_index = len(self._history)
923 self._history_index = len(self._history)
918
924
919 return executed
925 return executed
920
926
921 #---------------------------------------------------------------------------
927 #---------------------------------------------------------------------------
922 # 'ConsoleWidget' abstract interface
928 # 'ConsoleWidget' abstract interface
923 #---------------------------------------------------------------------------
929 #---------------------------------------------------------------------------
924
930
925 def _up_pressed(self):
931 def _up_pressed(self):
926 """ Called when the up key is pressed. Returns whether to continue
932 """ Called when the up key is pressed. Returns whether to continue
927 processing the event.
933 processing the event.
928 """
934 """
929 prompt_cursor = self._get_prompt_cursor()
935 prompt_cursor = self._get_prompt_cursor()
930 if self.textCursor().blockNumber() == prompt_cursor.blockNumber():
936 if self.textCursor().blockNumber() == prompt_cursor.blockNumber():
931 self.history_previous()
937 self.history_previous()
932
938
933 # Go to the first line of prompt for seemless history scrolling.
939 # Go to the first line of prompt for seemless history scrolling.
934 cursor = self._get_prompt_cursor()
940 cursor = self._get_prompt_cursor()
935 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
941 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
936 self.setTextCursor(cursor)
942 self.setTextCursor(cursor)
937
943
938 return False
944 return False
939 return True
945 return True
940
946
941 def _down_pressed(self):
947 def _down_pressed(self):
942 """ Called when the down key is pressed. Returns whether to continue
948 """ Called when the down key is pressed. Returns whether to continue
943 processing the event.
949 processing the event.
944 """
950 """
945 end_cursor = self._get_end_cursor()
951 end_cursor = self._get_end_cursor()
946 if self.textCursor().blockNumber() == end_cursor.blockNumber():
952 if self.textCursor().blockNumber() == end_cursor.blockNumber():
947 self.history_next()
953 self.history_next()
948 return False
954 return False
949 return True
955 return True
950
956
951 #---------------------------------------------------------------------------
957 #---------------------------------------------------------------------------
952 # 'HistoryConsoleWidget' interface
958 # 'HistoryConsoleWidget' interface
953 #---------------------------------------------------------------------------
959 #---------------------------------------------------------------------------
954
960
955 def history_previous(self):
961 def history_previous(self):
956 """ If possible, set the input buffer to the previous item in the
962 """ If possible, set the input buffer to the previous item in the
957 history.
963 history.
958 """
964 """
959 if self._history_index > 0:
965 if self._history_index > 0:
960 self._history_index -= 1
966 self._history_index -= 1
961 self.input_buffer = self._history[self._history_index]
967 self.input_buffer = self._history[self._history_index]
962
968
963 def history_next(self):
969 def history_next(self):
964 """ Set the input buffer to the next item in the history, or a blank
970 """ Set the input buffer to the next item in the history, or a blank
965 line if there is no subsequent item.
971 line if there is no subsequent item.
966 """
972 """
967 if self._history_index < len(self._history):
973 if self._history_index < len(self._history):
968 self._history_index += 1
974 self._history_index += 1
969 if self._history_index < len(self._history):
975 if self._history_index < len(self._history):
970 self.input_buffer = self._history[self._history_index]
976 self.input_buffer = self._history[self._history_index]
971 else:
977 else:
972 self.input_buffer = ''
978 self.input_buffer = ''
@@ -1,158 +1,206 b''
1 # System library imports
1 # System library imports
2 from PyQt4 import QtCore, QtGui
2 from PyQt4 import QtCore, QtGui
3
3
4 # Local imports
4 # Local imports
5 from IPython.core.usage import default_banner
5 from IPython.core.usage import default_banner
6 from frontend_widget import FrontendWidget
6 from frontend_widget import FrontendWidget
7
7
8
8
9 class IPythonWidget(FrontendWidget):
9 class IPythonWidget(FrontendWidget):
10 """ A FrontendWidget for an IPython kernel.
10 """ A FrontendWidget for an IPython kernel.
11 """
11 """
12
12
13 # The default stylesheet: black text on a white background.
13 # The default stylesheet: black text on a white background.
14 default_stylesheet = """
14 default_stylesheet = """
15 .error { color: red; }
15 .error { color: red; }
16 .in-prompt { color: navy; }
16 .in-prompt { color: navy; }
17 .in-prompt-number { font-weight: bold; }
17 .in-prompt-number { font-weight: bold; }
18 .out-prompt { color: darkred; }
18 .out-prompt { color: darkred; }
19 .out-prompt-number { font-weight: bold; }
19 .out-prompt-number { font-weight: bold; }
20 """
20 """
21
21
22 # A dark stylesheet: white text on a black background.
22 # A dark stylesheet: white text on a black background.
23 dark_stylesheet = """
23 dark_stylesheet = """
24 QPlainTextEdit { background-color: black; color: white }
24 QPlainTextEdit { background-color: black; color: white }
25 QFrame { border: 1px solid grey; }
25 QFrame { border: 1px solid grey; }
26 .error { color: red; }
26 .error { color: red; }
27 .in-prompt { color: lime; }
27 .in-prompt { color: lime; }
28 .in-prompt-number { color: lime; font-weight: bold; }
28 .in-prompt-number { color: lime; font-weight: bold; }
29 .out-prompt { color: red; }
29 .out-prompt { color: red; }
30 .out-prompt-number { color: red; font-weight: bold; }
30 .out-prompt-number { color: red; font-weight: bold; }
31 """
31 """
32
32
33 # Default prompts.
34 in_prompt = '<br/>In [<span class="in-prompt-number">%i</span>]: '
35 out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
36
33 #---------------------------------------------------------------------------
37 #---------------------------------------------------------------------------
34 # 'QObject' interface
38 # 'QObject' interface
35 #---------------------------------------------------------------------------
39 #---------------------------------------------------------------------------
36
40
37 def __init__(self, parent=None):
41 def __init__(self, parent=None):
38 super(IPythonWidget, self).__init__(parent)
42 super(IPythonWidget, self).__init__(parent)
39
43
40 # Initialize protected variables.
44 # Initialize protected variables.
45 self._previous_prompt_blocks = []
41 self._prompt_count = 0
46 self._prompt_count = 0
42
47
43 # Set a default stylesheet.
48 # Set a default stylesheet.
44 self.reset_styling()
49 self.reset_styling()
45
50
46 #---------------------------------------------------------------------------
51 #---------------------------------------------------------------------------
47 # 'FrontendWidget' interface
52 # 'FrontendWidget' interface
48 #---------------------------------------------------------------------------
53 #---------------------------------------------------------------------------
49
54
50 def execute_file(self, path, hidden=False):
55 def execute_file(self, path, hidden=False):
51 """ Reimplemented to use the 'run' magic.
56 """ Reimplemented to use the 'run' magic.
52 """
57 """
53 self.execute('run %s' % path, hidden=hidden)
58 self.execute('run %s' % path, hidden=hidden)
54
59
55 #---------------------------------------------------------------------------
60 #---------------------------------------------------------------------------
56 # 'FrontendWidget' protected interface
61 # 'FrontendWidget' protected interface
57 #---------------------------------------------------------------------------
62 #---------------------------------------------------------------------------
58
63
59 def _get_banner(self):
64 def _get_banner(self):
60 """ Reimplemented to return IPython's default banner.
65 """ Reimplemented to return IPython's default banner.
61 """
66 """
62 return default_banner
67 return default_banner
63
68
64 def _show_interpreter_prompt(self):
69 def _show_interpreter_prompt(self):
65 """ Reimplemented for IPython-style prompts.
70 """ Reimplemented for IPython-style prompts.
66 """
71 """
72 # Update old prompt numbers if necessary.
73 previous_prompt_number = self._prompt_count
74 if previous_prompt_number != self._prompt_count:
75 for i, (block, length) in enumerate(self._previous_prompt_blocks):
76 if block.isValid():
77 cursor = QtGui.QTextCursor(block)
78 cursor.movePosition(QtGui.QTextCursor.Right,
79 QtGui.QTextCursor.KeepAnchor, length-1)
80 if i == 0:
81 prompt = self._make_in_prompt(previous_prompt_number)
82 else:
83 prompt = self._make_out_prompt(previous_prompt_number)
84 self._insert_html(cursor, prompt)
85 self._previous_prompt_blocks = []
86
87 # Show a new prompt.
67 self._prompt_count += 1
88 self._prompt_count += 1
68 prompt_template = '<span class="in-prompt">%s</span>'
89 self._show_prompt(self._make_in_prompt(self._prompt_count), html=True)
69 prompt_body = '<br/>In [<span class="in-prompt-number">%i</span>]: '
90 self._save_prompt_block()
70 prompt = (prompt_template % prompt_body) % self._prompt_count
71 self._show_prompt(prompt, html=True)
72
91
73 # Update continuation prompt to reflect (possibly) new prompt length.
92 # Update continuation prompt to reflect (possibly) new prompt length.
74 cont_prompt_chars = '...: '
93 self._set_continuation_prompt(
75 space_count = len(self._prompt.lstrip()) - len(cont_prompt_chars)
94 self._make_continuation_prompt(self._prompt), html=True)
76 cont_prompt_body = '&nbsp;' * space_count + cont_prompt_chars
77 self._continuation_prompt_html = prompt_template % cont_prompt_body
78
95
79 #------ Signal handlers ----------------------------------------------------
96 #------ Signal handlers ----------------------------------------------------
80
97
81 def _handle_execute_error(self, reply):
98 def _handle_execute_error(self, reply):
82 """ Reimplemented for IPython-style traceback formatting.
99 """ Reimplemented for IPython-style traceback formatting.
83 """
100 """
84 content = reply['content']
101 content = reply['content']
85 traceback_lines = content['traceback'][:]
102 traceback_lines = content['traceback'][:]
86 traceback = ''.join(traceback_lines)
103 traceback = ''.join(traceback_lines)
87 traceback = traceback.replace(' ', '&nbsp;')
104 traceback = traceback.replace(' ', '&nbsp;')
88 traceback = traceback.replace('\n', '<br/>')
105 traceback = traceback.replace('\n', '<br/>')
89
106
90 ename = content['ename']
107 ename = content['ename']
91 ename_styled = '<span class="error">%s</span>' % ename
108 ename_styled = '<span class="error">%s</span>' % ename
92 traceback = traceback.replace(ename, ename_styled)
109 traceback = traceback.replace(ename, ename_styled)
93
110
94 self.appendHtml(traceback)
111 self.appendHtml(traceback)
95
112
96 def _handle_pyout(self, omsg):
113 def _handle_pyout(self, omsg):
97 """ Reimplemented for IPython-style "display hook".
114 """ Reimplemented for IPython-style "display hook".
98 """
115 """
99 prompt_template = '<span class="out-prompt">%s</span>'
116 self.appendHtml(self._make_out_prompt(self._prompt_count))
100 prompt_body = 'Out[<span class="out-prompt-number">%i</span>]: '
117 self._save_prompt_block()
101 prompt = (prompt_template % prompt_body) % self._prompt_count
118
102 self.appendHtml(prompt)
103 self.appendPlainText(omsg['content']['data'] + '\n')
119 self.appendPlainText(omsg['content']['data'] + '\n')
104
120
105 #---------------------------------------------------------------------------
121 #---------------------------------------------------------------------------
106 # 'IPythonWidget' interface
122 # 'IPythonWidget' interface
107 #---------------------------------------------------------------------------
123 #---------------------------------------------------------------------------
108
124
109 def reset_styling(self):
125 def reset_styling(self):
110 """ Restores the default IPythonWidget styling.
126 """ Restores the default IPythonWidget styling.
111 """
127 """
112 self.set_styling(self.default_stylesheet, syntax_style='default')
128 self.set_styling(self.default_stylesheet, syntax_style='default')
113 #self.set_styling(self.dark_stylesheet, syntax_style='monokai')
129 #self.set_styling(self.dark_stylesheet, syntax_style='monokai')
114
130
115 def set_styling(self, stylesheet, syntax_style=None):
131 def set_styling(self, stylesheet, syntax_style=None):
116 """ Sets the IPythonWidget styling.
132 """ Sets the IPythonWidget styling.
117
133
118 Parameters:
134 Parameters:
119 -----------
135 -----------
120 stylesheet : str
136 stylesheet : str
121 A CSS stylesheet. The stylesheet can contain classes for:
137 A CSS stylesheet. The stylesheet can contain classes for:
122 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
138 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
123 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
139 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
124 3. IPython: .error, .in-prompt, .out-prompt, etc.
140 3. IPython: .error, .in-prompt, .out-prompt, etc.
125
141
126 syntax_style : str or None [default None]
142 syntax_style : str or None [default None]
127 If specified, use the Pygments style with given name. Otherwise,
143 If specified, use the Pygments style with given name. Otherwise,
128 the stylesheet is queried for Pygments style information.
144 the stylesheet is queried for Pygments style information.
129 """
145 """
130 self.setStyleSheet(stylesheet)
146 self.setStyleSheet(stylesheet)
131 self.document().setDefaultStyleSheet(stylesheet)
147 self.document().setDefaultStyleSheet(stylesheet)
132
148
133 if syntax_style is None:
149 if syntax_style is None:
134 self._highlighter.set_style_sheet(stylesheet)
150 self._highlighter.set_style_sheet(stylesheet)
135 else:
151 else:
136 self._highlighter.set_style(syntax_style)
152 self._highlighter.set_style(syntax_style)
137
153
154 #---------------------------------------------------------------------------
155 # 'IPythonWidget' protected interface
156 #---------------------------------------------------------------------------
157
158 def _make_in_prompt(self, number):
159 """ Given a prompt number, returns an HTML In prompt.
160 """
161 body = self.in_prompt % number
162 return '<span class="in-prompt">%s</span>' % body
163
164 def _make_continuation_prompt(self, prompt):
165 """ Given a plain text version of an In prompt, returns an HTML
166 continuation prompt.
167 """
168 end_chars = '...: '
169 space_count = len(prompt.lstrip('\n')) - len(end_chars)
170 body = '&nbsp;' * space_count + end_chars
171 return '<span class="in-prompt">%s</span>' % body
172
173 def _make_out_prompt(self, number):
174 """ Given a prompt number, returns an HTML Out prompt.
175 """
176 body = self.out_prompt % number
177 return '<span class="out-prompt">%s</span>' % body
178
179 def _save_prompt_block(self):
180 """ Assuming a prompt has just been written at the end of the buffer,
181 store the QTextBlock that contains it and its length.
182 """
183 block = self.document().lastBlock()
184 self._previous_prompt_blocks.append((block, block.length()))
185
138
186
139 if __name__ == '__main__':
187 if __name__ == '__main__':
140 from IPython.frontend.qt.kernelmanager import QtKernelManager
188 from IPython.frontend.qt.kernelmanager import QtKernelManager
141
189
142 # Don't let Qt or ZMQ swallow KeyboardInterupts.
190 # Don't let Qt or ZMQ swallow KeyboardInterupts.
143 import signal
191 import signal
144 signal.signal(signal.SIGINT, signal.SIG_DFL)
192 signal.signal(signal.SIGINT, signal.SIG_DFL)
145
193
146 # Create a KernelManager.
194 # Create a KernelManager.
147 kernel_manager = QtKernelManager()
195 kernel_manager = QtKernelManager()
148 kernel_manager.start_kernel()
196 kernel_manager.start_kernel()
149 kernel_manager.start_channels()
197 kernel_manager.start_channels()
150
198
151 # Launch the application.
199 # Launch the application.
152 app = QtGui.QApplication([])
200 app = QtGui.QApplication([])
153 widget = IPythonWidget()
201 widget = IPythonWidget()
154 widget.kernel_manager = kernel_manager
202 widget.kernel_manager = kernel_manager
155 widget.setWindowTitle('Python')
203 widget.setWindowTitle('Python')
156 widget.resize(640, 480)
204 widget.resize(640, 480)
157 widget.show()
205 widget.show()
158 app.exec_()
206 app.exec_()
General Comments 0
You need to be logged in to leave comments. Login now