##// END OF EJS Templates
Add history_tail method to ConsoleWidget for retreiving the local history.
epatters -
Show More
@@ -1,604 +1,604 b''
1 from __future__ import print_function
1 from __future__ import print_function
2
2
3 # Standard library imports
3 # Standard library imports
4 from collections import namedtuple
4 from collections import namedtuple
5 import sys
5 import sys
6 import time
6 import time
7
7
8 # System library imports
8 # System library imports
9 from pygments.lexers import PythonLexer
9 from pygments.lexers import PythonLexer
10 from IPython.external.qt import QtCore, QtGui
10 from IPython.external.qt import QtCore, QtGui
11
11
12 # Local imports
12 # Local imports
13 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
13 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
14 from IPython.core.oinspect import call_tip
14 from IPython.core.oinspect import call_tip
15 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
15 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
16 from IPython.utils.traitlets import Bool
16 from IPython.utils.traitlets import Bool
17 from bracket_matcher import BracketMatcher
17 from bracket_matcher import BracketMatcher
18 from call_tip_widget import CallTipWidget
18 from call_tip_widget import CallTipWidget
19 from completion_lexer import CompletionLexer
19 from completion_lexer import CompletionLexer
20 from history_console_widget import HistoryConsoleWidget
20 from history_console_widget import HistoryConsoleWidget
21 from pygments_highlighter import PygmentsHighlighter
21 from pygments_highlighter import PygmentsHighlighter
22
22
23
23
24 class FrontendHighlighter(PygmentsHighlighter):
24 class FrontendHighlighter(PygmentsHighlighter):
25 """ A PygmentsHighlighter that can be turned on and off and that ignores
25 """ A PygmentsHighlighter that can be turned on and off and that ignores
26 prompts.
26 prompts.
27 """
27 """
28
28
29 def __init__(self, frontend):
29 def __init__(self, frontend):
30 super(FrontendHighlighter, self).__init__(frontend._control.document())
30 super(FrontendHighlighter, self).__init__(frontend._control.document())
31 self._current_offset = 0
31 self._current_offset = 0
32 self._frontend = frontend
32 self._frontend = frontend
33 self.highlighting_on = False
33 self.highlighting_on = False
34
34
35 def highlightBlock(self, string):
35 def highlightBlock(self, string):
36 """ Highlight a block of text. Reimplemented to highlight selectively.
36 """ Highlight a block of text. Reimplemented to highlight selectively.
37 """
37 """
38 if not self.highlighting_on:
38 if not self.highlighting_on:
39 return
39 return
40
40
41 # The input to this function is a unicode string that may contain
41 # The input to this function is a unicode string that may contain
42 # paragraph break characters, non-breaking spaces, etc. Here we acquire
42 # paragraph break characters, non-breaking spaces, etc. Here we acquire
43 # the string as plain text so we can compare it.
43 # the string as plain text so we can compare it.
44 current_block = self.currentBlock()
44 current_block = self.currentBlock()
45 string = self._frontend._get_block_plain_text(current_block)
45 string = self._frontend._get_block_plain_text(current_block)
46
46
47 # Decide whether to check for the regular or continuation prompt.
47 # Decide whether to check for the regular or continuation prompt.
48 if current_block.contains(self._frontend._prompt_pos):
48 if current_block.contains(self._frontend._prompt_pos):
49 prompt = self._frontend._prompt
49 prompt = self._frontend._prompt
50 else:
50 else:
51 prompt = self._frontend._continuation_prompt
51 prompt = self._frontend._continuation_prompt
52
52
53 # Don't highlight the part of the string that contains the prompt.
53 # Don't highlight the part of the string that contains the prompt.
54 if string.startswith(prompt):
54 if string.startswith(prompt):
55 self._current_offset = len(prompt)
55 self._current_offset = len(prompt)
56 string = string[len(prompt):]
56 string = string[len(prompt):]
57 else:
57 else:
58 self._current_offset = 0
58 self._current_offset = 0
59
59
60 PygmentsHighlighter.highlightBlock(self, string)
60 PygmentsHighlighter.highlightBlock(self, string)
61
61
62 def rehighlightBlock(self, block):
62 def rehighlightBlock(self, block):
63 """ Reimplemented to temporarily enable highlighting if disabled.
63 """ Reimplemented to temporarily enable highlighting if disabled.
64 """
64 """
65 old = self.highlighting_on
65 old = self.highlighting_on
66 self.highlighting_on = True
66 self.highlighting_on = True
67 super(FrontendHighlighter, self).rehighlightBlock(block)
67 super(FrontendHighlighter, self).rehighlightBlock(block)
68 self.highlighting_on = old
68 self.highlighting_on = old
69
69
70 def setFormat(self, start, count, format):
70 def setFormat(self, start, count, format):
71 """ Reimplemented to highlight selectively.
71 """ Reimplemented to highlight selectively.
72 """
72 """
73 start += self._current_offset
73 start += self._current_offset
74 PygmentsHighlighter.setFormat(self, start, count, format)
74 PygmentsHighlighter.setFormat(self, start, count, format)
75
75
76
76
77 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
77 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
78 """ A Qt frontend for a generic Python kernel.
78 """ A Qt frontend for a generic Python kernel.
79 """
79 """
80
80
81 # An option and corresponding signal for overriding the default kernel
81 # An option and corresponding signal for overriding the default kernel
82 # interrupt behavior.
82 # interrupt behavior.
83 custom_interrupt = Bool(False)
83 custom_interrupt = Bool(False)
84 custom_interrupt_requested = QtCore.Signal()
84 custom_interrupt_requested = QtCore.Signal()
85
85
86 # An option and corresponding signals for overriding the default kernel
86 # An option and corresponding signals for overriding the default kernel
87 # restart behavior.
87 # restart behavior.
88 custom_restart = Bool(False)
88 custom_restart = Bool(False)
89 custom_restart_kernel_died = QtCore.Signal(float)
89 custom_restart_kernel_died = QtCore.Signal(float)
90 custom_restart_requested = QtCore.Signal()
90 custom_restart_requested = QtCore.Signal()
91
91
92 # Emitted when an 'execute_reply' has been received from the kernel and
92 # Emitted when a user-visible 'execute_reply' has been received from the
93 # processed by the FrontendWidget.
93 # kernel and processed by the FrontendWidget. Contains the response message.
94 executed = QtCore.Signal(object)
94 executed = QtCore.Signal(object)
95
95
96 # Emitted when an exit request has been received from the kernel.
96 # Emitted when an exit request has been received from the kernel.
97 exit_requested = QtCore.Signal()
97 exit_requested = QtCore.Signal()
98
98
99 # Protected class variables.
99 # Protected class variables.
100 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
100 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
101 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
101 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
102 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
102 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
103 _input_splitter_class = InputSplitter
103 _input_splitter_class = InputSplitter
104 _local_kernel = False
104 _local_kernel = False
105
105
106 #---------------------------------------------------------------------------
106 #---------------------------------------------------------------------------
107 # 'object' interface
107 # 'object' interface
108 #---------------------------------------------------------------------------
108 #---------------------------------------------------------------------------
109
109
110 def __init__(self, *args, **kw):
110 def __init__(self, *args, **kw):
111 super(FrontendWidget, self).__init__(*args, **kw)
111 super(FrontendWidget, self).__init__(*args, **kw)
112
112
113 # FrontendWidget protected variables.
113 # FrontendWidget protected variables.
114 self._bracket_matcher = BracketMatcher(self._control)
114 self._bracket_matcher = BracketMatcher(self._control)
115 self._call_tip_widget = CallTipWidget(self._control)
115 self._call_tip_widget = CallTipWidget(self._control)
116 self._completion_lexer = CompletionLexer(PythonLexer())
116 self._completion_lexer = CompletionLexer(PythonLexer())
117 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
117 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
118 self._hidden = False
118 self._hidden = False
119 self._highlighter = FrontendHighlighter(self)
119 self._highlighter = FrontendHighlighter(self)
120 self._input_splitter = self._input_splitter_class(input_mode='cell')
120 self._input_splitter = self._input_splitter_class(input_mode='cell')
121 self._kernel_manager = None
121 self._kernel_manager = None
122 self._request_info = {}
122 self._request_info = {}
123
123
124 # Configure the ConsoleWidget.
124 # Configure the ConsoleWidget.
125 self.tab_width = 4
125 self.tab_width = 4
126 self._set_continuation_prompt('... ')
126 self._set_continuation_prompt('... ')
127
127
128 # Configure the CallTipWidget.
128 # Configure the CallTipWidget.
129 self._call_tip_widget.setFont(self.font)
129 self._call_tip_widget.setFont(self.font)
130 self.font_changed.connect(self._call_tip_widget.setFont)
130 self.font_changed.connect(self._call_tip_widget.setFont)
131
131
132 # Configure actions.
132 # Configure actions.
133 action = self._copy_raw_action
133 action = self._copy_raw_action
134 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
134 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
135 action.setEnabled(False)
135 action.setEnabled(False)
136 action.setShortcut(QtGui.QKeySequence(key))
136 action.setShortcut(QtGui.QKeySequence(key))
137 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
137 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
138 action.triggered.connect(self.copy_raw)
138 action.triggered.connect(self.copy_raw)
139 self.copy_available.connect(action.setEnabled)
139 self.copy_available.connect(action.setEnabled)
140 self.addAction(action)
140 self.addAction(action)
141
141
142 # Connect signal handlers.
142 # Connect signal handlers.
143 document = self._control.document()
143 document = self._control.document()
144 document.contentsChange.connect(self._document_contents_change)
144 document.contentsChange.connect(self._document_contents_change)
145
145
146 # Set flag for whether we are connected via localhost.
146 # Set flag for whether we are connected via localhost.
147 self._local_kernel = kw.get('local_kernel',
147 self._local_kernel = kw.get('local_kernel',
148 FrontendWidget._local_kernel)
148 FrontendWidget._local_kernel)
149
149
150 #---------------------------------------------------------------------------
150 #---------------------------------------------------------------------------
151 # 'ConsoleWidget' public interface
151 # 'ConsoleWidget' public interface
152 #---------------------------------------------------------------------------
152 #---------------------------------------------------------------------------
153
153
154 def copy(self):
154 def copy(self):
155 """ Copy the currently selected text to the clipboard, removing prompts.
155 """ Copy the currently selected text to the clipboard, removing prompts.
156 """
156 """
157 text = self._control.textCursor().selection().toPlainText()
157 text = self._control.textCursor().selection().toPlainText()
158 if text:
158 if text:
159 lines = map(transform_classic_prompt, text.splitlines())
159 lines = map(transform_classic_prompt, text.splitlines())
160 text = '\n'.join(lines)
160 text = '\n'.join(lines)
161 QtGui.QApplication.clipboard().setText(text)
161 QtGui.QApplication.clipboard().setText(text)
162
162
163 #---------------------------------------------------------------------------
163 #---------------------------------------------------------------------------
164 # 'ConsoleWidget' abstract interface
164 # 'ConsoleWidget' abstract interface
165 #---------------------------------------------------------------------------
165 #---------------------------------------------------------------------------
166
166
167 def _is_complete(self, source, interactive):
167 def _is_complete(self, source, interactive):
168 """ Returns whether 'source' can be completely processed and a new
168 """ Returns whether 'source' can be completely processed and a new
169 prompt created. When triggered by an Enter/Return key press,
169 prompt created. When triggered by an Enter/Return key press,
170 'interactive' is True; otherwise, it is False.
170 'interactive' is True; otherwise, it is False.
171 """
171 """
172 complete = self._input_splitter.push(source)
172 complete = self._input_splitter.push(source)
173 if interactive:
173 if interactive:
174 complete = not self._input_splitter.push_accepts_more()
174 complete = not self._input_splitter.push_accepts_more()
175 return complete
175 return complete
176
176
177 def _execute(self, source, hidden):
177 def _execute(self, source, hidden):
178 """ Execute 'source'. If 'hidden', do not show any output.
178 """ Execute 'source'. If 'hidden', do not show any output.
179
179
180 See parent class :meth:`execute` docstring for full details.
180 See parent class :meth:`execute` docstring for full details.
181 """
181 """
182 msg_id = self.kernel_manager.xreq_channel.execute(source, hidden)
182 msg_id = self.kernel_manager.xreq_channel.execute(source, hidden)
183 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
183 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
184 self._hidden = hidden
184 self._hidden = hidden
185
185
186 def _prompt_started_hook(self):
186 def _prompt_started_hook(self):
187 """ Called immediately after a new prompt is displayed.
187 """ Called immediately after a new prompt is displayed.
188 """
188 """
189 if not self._reading:
189 if not self._reading:
190 self._highlighter.highlighting_on = True
190 self._highlighter.highlighting_on = True
191
191
192 def _prompt_finished_hook(self):
192 def _prompt_finished_hook(self):
193 """ Called immediately after a prompt is finished, i.e. when some input
193 """ Called immediately after a prompt is finished, i.e. when some input
194 will be processed and a new prompt displayed.
194 will be processed and a new prompt displayed.
195 """
195 """
196 # Flush all state from the input splitter so the next round of
196 # Flush all state from the input splitter so the next round of
197 # reading input starts with a clean buffer.
197 # reading input starts with a clean buffer.
198 self._input_splitter.reset()
198 self._input_splitter.reset()
199
199
200 if not self._reading:
200 if not self._reading:
201 self._highlighter.highlighting_on = False
201 self._highlighter.highlighting_on = False
202
202
203 def _tab_pressed(self):
203 def _tab_pressed(self):
204 """ Called when the tab key is pressed. Returns whether to continue
204 """ Called when the tab key is pressed. Returns whether to continue
205 processing the event.
205 processing the event.
206 """
206 """
207 # Perform tab completion if:
207 # Perform tab completion if:
208 # 1) The cursor is in the input buffer.
208 # 1) The cursor is in the input buffer.
209 # 2) There is a non-whitespace character before the cursor.
209 # 2) There is a non-whitespace character before the cursor.
210 text = self._get_input_buffer_cursor_line()
210 text = self._get_input_buffer_cursor_line()
211 if text is None:
211 if text is None:
212 return False
212 return False
213 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
213 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
214 if complete:
214 if complete:
215 self._complete()
215 self._complete()
216 return not complete
216 return not complete
217
217
218 #---------------------------------------------------------------------------
218 #---------------------------------------------------------------------------
219 # 'ConsoleWidget' protected interface
219 # 'ConsoleWidget' protected interface
220 #---------------------------------------------------------------------------
220 #---------------------------------------------------------------------------
221
221
222 def _context_menu_make(self, pos):
222 def _context_menu_make(self, pos):
223 """ Reimplemented to add an action for raw copy.
223 """ Reimplemented to add an action for raw copy.
224 """
224 """
225 menu = super(FrontendWidget, self)._context_menu_make(pos)
225 menu = super(FrontendWidget, self)._context_menu_make(pos)
226 for before_action in menu.actions():
226 for before_action in menu.actions():
227 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
227 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
228 QtGui.QKeySequence.ExactMatch:
228 QtGui.QKeySequence.ExactMatch:
229 menu.insertAction(before_action, self._copy_raw_action)
229 menu.insertAction(before_action, self._copy_raw_action)
230 break
230 break
231 return menu
231 return menu
232
232
233 def _event_filter_console_keypress(self, event):
233 def _event_filter_console_keypress(self, event):
234 """ Reimplemented for execution interruption and smart backspace.
234 """ Reimplemented for execution interruption and smart backspace.
235 """
235 """
236 key = event.key()
236 key = event.key()
237 if self._control_key_down(event.modifiers(), include_command=False):
237 if self._control_key_down(event.modifiers(), include_command=False):
238
238
239 if key == QtCore.Qt.Key_C and self._executing:
239 if key == QtCore.Qt.Key_C and self._executing:
240 self.interrupt_kernel()
240 self.interrupt_kernel()
241 return True
241 return True
242
242
243 elif key == QtCore.Qt.Key_Period:
243 elif key == QtCore.Qt.Key_Period:
244 message = 'Are you sure you want to restart the kernel?'
244 message = 'Are you sure you want to restart the kernel?'
245 self.restart_kernel(message, now=False)
245 self.restart_kernel(message, now=False)
246 return True
246 return True
247
247
248 elif not event.modifiers() & QtCore.Qt.AltModifier:
248 elif not event.modifiers() & QtCore.Qt.AltModifier:
249
249
250 # Smart backspace: remove four characters in one backspace if:
250 # Smart backspace: remove four characters in one backspace if:
251 # 1) everything left of the cursor is whitespace
251 # 1) everything left of the cursor is whitespace
252 # 2) the four characters immediately left of the cursor are spaces
252 # 2) the four characters immediately left of the cursor are spaces
253 if key == QtCore.Qt.Key_Backspace:
253 if key == QtCore.Qt.Key_Backspace:
254 col = self._get_input_buffer_cursor_column()
254 col = self._get_input_buffer_cursor_column()
255 cursor = self._control.textCursor()
255 cursor = self._control.textCursor()
256 if col > 3 and not cursor.hasSelection():
256 if col > 3 and not cursor.hasSelection():
257 text = self._get_input_buffer_cursor_line()[:col]
257 text = self._get_input_buffer_cursor_line()[:col]
258 if text.endswith(' ') and not text.strip():
258 if text.endswith(' ') and not text.strip():
259 cursor.movePosition(QtGui.QTextCursor.Left,
259 cursor.movePosition(QtGui.QTextCursor.Left,
260 QtGui.QTextCursor.KeepAnchor, 4)
260 QtGui.QTextCursor.KeepAnchor, 4)
261 cursor.removeSelectedText()
261 cursor.removeSelectedText()
262 return True
262 return True
263
263
264 return super(FrontendWidget, self)._event_filter_console_keypress(event)
264 return super(FrontendWidget, self)._event_filter_console_keypress(event)
265
265
266 def _insert_continuation_prompt(self, cursor):
266 def _insert_continuation_prompt(self, cursor):
267 """ Reimplemented for auto-indentation.
267 """ Reimplemented for auto-indentation.
268 """
268 """
269 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
269 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
270 cursor.insertText(' ' * self._input_splitter.indent_spaces)
270 cursor.insertText(' ' * self._input_splitter.indent_spaces)
271
271
272 #---------------------------------------------------------------------------
272 #---------------------------------------------------------------------------
273 # 'BaseFrontendMixin' abstract interface
273 # 'BaseFrontendMixin' abstract interface
274 #---------------------------------------------------------------------------
274 #---------------------------------------------------------------------------
275
275
276 def _handle_complete_reply(self, rep):
276 def _handle_complete_reply(self, rep):
277 """ Handle replies for tab completion.
277 """ Handle replies for tab completion.
278 """
278 """
279 cursor = self._get_cursor()
279 cursor = self._get_cursor()
280 info = self._request_info.get('complete')
280 info = self._request_info.get('complete')
281 if info and info.id == rep['parent_header']['msg_id'] and \
281 if info and info.id == rep['parent_header']['msg_id'] and \
282 info.pos == cursor.position():
282 info.pos == cursor.position():
283 text = '.'.join(self._get_context())
283 text = '.'.join(self._get_context())
284 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
284 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
285 self._complete_with_items(cursor, rep['content']['matches'])
285 self._complete_with_items(cursor, rep['content']['matches'])
286
286
287 def _handle_execute_reply(self, msg):
287 def _handle_execute_reply(self, msg):
288 """ Handles replies for code execution.
288 """ Handles replies for code execution.
289 """
289 """
290 info = self._request_info.get('execute')
290 info = self._request_info.get('execute')
291 if info and info.id == msg['parent_header']['msg_id'] and \
291 if info and info.id == msg['parent_header']['msg_id'] and \
292 info.kind == 'user' and not self._hidden:
292 info.kind == 'user' and not self._hidden:
293 # Make sure that all output from the SUB channel has been processed
293 # Make sure that all output from the SUB channel has been processed
294 # before writing a new prompt.
294 # before writing a new prompt.
295 self.kernel_manager.sub_channel.flush()
295 self.kernel_manager.sub_channel.flush()
296
296
297 # Reset the ANSI style information to prevent bad text in stdout
297 # Reset the ANSI style information to prevent bad text in stdout
298 # from messing up our colors. We're not a true terminal so we're
298 # from messing up our colors. We're not a true terminal so we're
299 # allowed to do this.
299 # allowed to do this.
300 if self.ansi_codes:
300 if self.ansi_codes:
301 self._ansi_processor.reset_sgr()
301 self._ansi_processor.reset_sgr()
302
302
303 content = msg['content']
303 content = msg['content']
304 status = content['status']
304 status = content['status']
305 if status == 'ok':
305 if status == 'ok':
306 self._process_execute_ok(msg)
306 self._process_execute_ok(msg)
307 elif status == 'error':
307 elif status == 'error':
308 self._process_execute_error(msg)
308 self._process_execute_error(msg)
309 elif status == 'abort':
309 elif status == 'abort':
310 self._process_execute_abort(msg)
310 self._process_execute_abort(msg)
311
311
312 self._show_interpreter_prompt_for_reply(msg)
312 self._show_interpreter_prompt_for_reply(msg)
313 self.executed.emit(msg)
313 self.executed.emit(msg)
314
314
315 def _handle_input_request(self, msg):
315 def _handle_input_request(self, msg):
316 """ Handle requests for raw_input.
316 """ Handle requests for raw_input.
317 """
317 """
318 if self._hidden:
318 if self._hidden:
319 raise RuntimeError('Request for raw input during hidden execution.')
319 raise RuntimeError('Request for raw input during hidden execution.')
320
320
321 # Make sure that all output from the SUB channel has been processed
321 # Make sure that all output from the SUB channel has been processed
322 # before entering readline mode.
322 # before entering readline mode.
323 self.kernel_manager.sub_channel.flush()
323 self.kernel_manager.sub_channel.flush()
324
324
325 def callback(line):
325 def callback(line):
326 self.kernel_manager.rep_channel.input(line)
326 self.kernel_manager.rep_channel.input(line)
327 self._readline(msg['content']['prompt'], callback=callback)
327 self._readline(msg['content']['prompt'], callback=callback)
328
328
329 def _handle_kernel_died(self, since_last_heartbeat):
329 def _handle_kernel_died(self, since_last_heartbeat):
330 """ Handle the kernel's death by asking if the user wants to restart.
330 """ Handle the kernel's death by asking if the user wants to restart.
331 """
331 """
332 if self.custom_restart:
332 if self.custom_restart:
333 self.custom_restart_kernel_died.emit(since_last_heartbeat)
333 self.custom_restart_kernel_died.emit(since_last_heartbeat)
334 else:
334 else:
335 message = 'The kernel heartbeat has been inactive for %.2f ' \
335 message = 'The kernel heartbeat has been inactive for %.2f ' \
336 'seconds. Do you want to restart the kernel? You may ' \
336 'seconds. Do you want to restart the kernel? You may ' \
337 'first want to check the network connection.' % \
337 'first want to check the network connection.' % \
338 since_last_heartbeat
338 since_last_heartbeat
339 self.restart_kernel(message, now=True)
339 self.restart_kernel(message, now=True)
340
340
341 def _handle_object_info_reply(self, rep):
341 def _handle_object_info_reply(self, rep):
342 """ Handle replies for call tips.
342 """ Handle replies for call tips.
343 """
343 """
344 cursor = self._get_cursor()
344 cursor = self._get_cursor()
345 info = self._request_info.get('call_tip')
345 info = self._request_info.get('call_tip')
346 if info and info.id == rep['parent_header']['msg_id'] and \
346 if info and info.id == rep['parent_header']['msg_id'] and \
347 info.pos == cursor.position():
347 info.pos == cursor.position():
348 # Get the information for a call tip. For now we format the call
348 # Get the information for a call tip. For now we format the call
349 # line as string, later we can pass False to format_call and
349 # line as string, later we can pass False to format_call and
350 # syntax-highlight it ourselves for nicer formatting in the
350 # syntax-highlight it ourselves for nicer formatting in the
351 # calltip.
351 # calltip.
352 call_info, doc = call_tip(rep['content'], format_call=True)
352 call_info, doc = call_tip(rep['content'], format_call=True)
353 if call_info or doc:
353 if call_info or doc:
354 self._call_tip_widget.show_call_info(call_info, doc)
354 self._call_tip_widget.show_call_info(call_info, doc)
355
355
356 def _handle_pyout(self, msg):
356 def _handle_pyout(self, msg):
357 """ Handle display hook output.
357 """ Handle display hook output.
358 """
358 """
359 if not self._hidden and self._is_from_this_session(msg):
359 if not self._hidden and self._is_from_this_session(msg):
360 self._append_plain_text(msg['content']['data']['text/plain'] + '\n')
360 self._append_plain_text(msg['content']['data']['text/plain'] + '\n')
361
361
362 def _handle_stream(self, msg):
362 def _handle_stream(self, msg):
363 """ Handle stdout, stderr, and stdin.
363 """ Handle stdout, stderr, and stdin.
364 """
364 """
365 if not self._hidden and self._is_from_this_session(msg):
365 if not self._hidden and self._is_from_this_session(msg):
366 # Most consoles treat tabs as being 8 space characters. Convert tabs
366 # Most consoles treat tabs as being 8 space characters. Convert tabs
367 # to spaces so that output looks as expected regardless of this
367 # to spaces so that output looks as expected regardless of this
368 # widget's tab width.
368 # widget's tab width.
369 text = msg['content']['data'].expandtabs(8)
369 text = msg['content']['data'].expandtabs(8)
370
370
371 self._append_plain_text(text)
371 self._append_plain_text(text)
372 self._control.moveCursor(QtGui.QTextCursor.End)
372 self._control.moveCursor(QtGui.QTextCursor.End)
373
373
374 def _handle_shutdown_reply(self, msg):
374 def _handle_shutdown_reply(self, msg):
375 """ Handle shutdown signal, only if from other console.
375 """ Handle shutdown signal, only if from other console.
376 """
376 """
377 if not self._hidden and not self._is_from_this_session(msg):
377 if not self._hidden and not self._is_from_this_session(msg):
378 if self._local_kernel:
378 if self._local_kernel:
379 if not msg['content']['restart']:
379 if not msg['content']['restart']:
380 sys.exit(0)
380 sys.exit(0)
381 else:
381 else:
382 # we just got notified of a restart!
382 # we just got notified of a restart!
383 time.sleep(0.25) # wait 1/4 sec to reset
383 time.sleep(0.25) # wait 1/4 sec to reset
384 # lest the request for a new prompt
384 # lest the request for a new prompt
385 # goes to the old kernel
385 # goes to the old kernel
386 self.reset()
386 self.reset()
387 else: # remote kernel, prompt on Kernel shutdown/reset
387 else: # remote kernel, prompt on Kernel shutdown/reset
388 title = self.window().windowTitle()
388 title = self.window().windowTitle()
389 if not msg['content']['restart']:
389 if not msg['content']['restart']:
390 reply = QtGui.QMessageBox.question(self, title,
390 reply = QtGui.QMessageBox.question(self, title,
391 "Kernel has been shutdown permanently. "
391 "Kernel has been shutdown permanently. "
392 "Close the Console?",
392 "Close the Console?",
393 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
393 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
394 if reply == QtGui.QMessageBox.Yes:
394 if reply == QtGui.QMessageBox.Yes:
395 sys.exit(0)
395 sys.exit(0)
396 else:
396 else:
397 reply = QtGui.QMessageBox.question(self, title,
397 reply = QtGui.QMessageBox.question(self, title,
398 "Kernel has been reset. Clear the Console?",
398 "Kernel has been reset. Clear the Console?",
399 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
399 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
400 if reply == QtGui.QMessageBox.Yes:
400 if reply == QtGui.QMessageBox.Yes:
401 time.sleep(0.25) # wait 1/4 sec to reset
401 time.sleep(0.25) # wait 1/4 sec to reset
402 # lest the request for a new prompt
402 # lest the request for a new prompt
403 # goes to the old kernel
403 # goes to the old kernel
404 self.reset()
404 self.reset()
405
405
406 def _started_channels(self):
406 def _started_channels(self):
407 """ Called when the KernelManager channels have started listening or
407 """ Called when the KernelManager channels have started listening or
408 when the frontend is assigned an already listening KernelManager.
408 when the frontend is assigned an already listening KernelManager.
409 """
409 """
410 self.reset()
410 self.reset()
411
411
412 #---------------------------------------------------------------------------
412 #---------------------------------------------------------------------------
413 # 'FrontendWidget' public interface
413 # 'FrontendWidget' public interface
414 #---------------------------------------------------------------------------
414 #---------------------------------------------------------------------------
415
415
416 def copy_raw(self):
416 def copy_raw(self):
417 """ Copy the currently selected text to the clipboard without attempting
417 """ Copy the currently selected text to the clipboard without attempting
418 to remove prompts or otherwise alter the text.
418 to remove prompts or otherwise alter the text.
419 """
419 """
420 self._control.copy()
420 self._control.copy()
421
421
422 def execute_file(self, path, hidden=False):
422 def execute_file(self, path, hidden=False):
423 """ Attempts to execute file with 'path'. If 'hidden', no output is
423 """ Attempts to execute file with 'path'. If 'hidden', no output is
424 shown.
424 shown.
425 """
425 """
426 self.execute('execfile("%s")' % path, hidden=hidden)
426 self.execute('execfile("%s")' % path, hidden=hidden)
427
427
428 def interrupt_kernel(self):
428 def interrupt_kernel(self):
429 """ Attempts to interrupt the running kernel.
429 """ Attempts to interrupt the running kernel.
430 """
430 """
431 if self.custom_interrupt:
431 if self.custom_interrupt:
432 self.custom_interrupt_requested.emit()
432 self.custom_interrupt_requested.emit()
433 elif self.kernel_manager.has_kernel:
433 elif self.kernel_manager.has_kernel:
434 self.kernel_manager.interrupt_kernel()
434 self.kernel_manager.interrupt_kernel()
435 else:
435 else:
436 self._append_plain_text('Kernel process is either remote or '
436 self._append_plain_text('Kernel process is either remote or '
437 'unspecified. Cannot interrupt.\n')
437 'unspecified. Cannot interrupt.\n')
438
438
439 def reset(self):
439 def reset(self):
440 """ Resets the widget to its initial state. Similar to ``clear``, but
440 """ Resets the widget to its initial state. Similar to ``clear``, but
441 also re-writes the banner and aborts execution if necessary.
441 also re-writes the banner and aborts execution if necessary.
442 """
442 """
443 if self._executing:
443 if self._executing:
444 self._executing = False
444 self._executing = False
445 self._request_info['execute'] = None
445 self._request_info['execute'] = None
446 self._reading = False
446 self._reading = False
447 self._highlighter.highlighting_on = False
447 self._highlighter.highlighting_on = False
448
448
449 self._control.clear()
449 self._control.clear()
450 self._append_plain_text(self._get_banner())
450 self._append_plain_text(self._get_banner())
451 self._show_interpreter_prompt()
451 self._show_interpreter_prompt()
452
452
453 def restart_kernel(self, message, now=False):
453 def restart_kernel(self, message, now=False):
454 """ Attempts to restart the running kernel.
454 """ Attempts to restart the running kernel.
455 """
455 """
456 # FIXME: now should be configurable via a checkbox in the dialog. Right
456 # FIXME: now should be configurable via a checkbox in the dialog. Right
457 # now at least the heartbeat path sets it to True and the manual restart
457 # now at least the heartbeat path sets it to True and the manual restart
458 # to False. But those should just be the pre-selected states of a
458 # to False. But those should just be the pre-selected states of a
459 # checkbox that the user could override if so desired. But I don't know
459 # checkbox that the user could override if so desired. But I don't know
460 # enough Qt to go implementing the checkbox now.
460 # enough Qt to go implementing the checkbox now.
461
461
462 if self.custom_restart:
462 if self.custom_restart:
463 self.custom_restart_requested.emit()
463 self.custom_restart_requested.emit()
464
464
465 elif self.kernel_manager.has_kernel:
465 elif self.kernel_manager.has_kernel:
466 # Pause the heart beat channel to prevent further warnings.
466 # Pause the heart beat channel to prevent further warnings.
467 self.kernel_manager.hb_channel.pause()
467 self.kernel_manager.hb_channel.pause()
468
468
469 # Prompt the user to restart the kernel. Un-pause the heartbeat if
469 # Prompt the user to restart the kernel. Un-pause the heartbeat if
470 # they decline. (If they accept, the heartbeat will be un-paused
470 # they decline. (If they accept, the heartbeat will be un-paused
471 # automatically when the kernel is restarted.)
471 # automatically when the kernel is restarted.)
472 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
472 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
473 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
473 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
474 message, buttons)
474 message, buttons)
475 if result == QtGui.QMessageBox.Yes:
475 if result == QtGui.QMessageBox.Yes:
476 try:
476 try:
477 self.kernel_manager.restart_kernel(now=now)
477 self.kernel_manager.restart_kernel(now=now)
478 except RuntimeError:
478 except RuntimeError:
479 self._append_plain_text('Kernel started externally. '
479 self._append_plain_text('Kernel started externally. '
480 'Cannot restart.\n')
480 'Cannot restart.\n')
481 else:
481 else:
482 self.reset()
482 self.reset()
483 else:
483 else:
484 self.kernel_manager.hb_channel.unpause()
484 self.kernel_manager.hb_channel.unpause()
485
485
486 else:
486 else:
487 self._append_plain_text('Kernel process is either remote or '
487 self._append_plain_text('Kernel process is either remote or '
488 'unspecified. Cannot restart.\n')
488 'unspecified. Cannot restart.\n')
489
489
490 #---------------------------------------------------------------------------
490 #---------------------------------------------------------------------------
491 # 'FrontendWidget' protected interface
491 # 'FrontendWidget' protected interface
492 #---------------------------------------------------------------------------
492 #---------------------------------------------------------------------------
493
493
494 def _call_tip(self):
494 def _call_tip(self):
495 """ Shows a call tip, if appropriate, at the current cursor location.
495 """ Shows a call tip, if appropriate, at the current cursor location.
496 """
496 """
497 # Decide if it makes sense to show a call tip
497 # Decide if it makes sense to show a call tip
498 cursor = self._get_cursor()
498 cursor = self._get_cursor()
499 cursor.movePosition(QtGui.QTextCursor.Left)
499 cursor.movePosition(QtGui.QTextCursor.Left)
500 if cursor.document().characterAt(cursor.position()) != '(':
500 if cursor.document().characterAt(cursor.position()) != '(':
501 return False
501 return False
502 context = self._get_context(cursor)
502 context = self._get_context(cursor)
503 if not context:
503 if not context:
504 return False
504 return False
505
505
506 # Send the metadata request to the kernel
506 # Send the metadata request to the kernel
507 name = '.'.join(context)
507 name = '.'.join(context)
508 msg_id = self.kernel_manager.xreq_channel.object_info(name)
508 msg_id = self.kernel_manager.xreq_channel.object_info(name)
509 pos = self._get_cursor().position()
509 pos = self._get_cursor().position()
510 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
510 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
511 return True
511 return True
512
512
513 def _complete(self):
513 def _complete(self):
514 """ Performs completion at the current cursor location.
514 """ Performs completion at the current cursor location.
515 """
515 """
516 context = self._get_context()
516 context = self._get_context()
517 if context:
517 if context:
518 # Send the completion request to the kernel
518 # Send the completion request to the kernel
519 msg_id = self.kernel_manager.xreq_channel.complete(
519 msg_id = self.kernel_manager.xreq_channel.complete(
520 '.'.join(context), # text
520 '.'.join(context), # text
521 self._get_input_buffer_cursor_line(), # line
521 self._get_input_buffer_cursor_line(), # line
522 self._get_input_buffer_cursor_column(), # cursor_pos
522 self._get_input_buffer_cursor_column(), # cursor_pos
523 self.input_buffer) # block
523 self.input_buffer) # block
524 pos = self._get_cursor().position()
524 pos = self._get_cursor().position()
525 info = self._CompletionRequest(msg_id, pos)
525 info = self._CompletionRequest(msg_id, pos)
526 self._request_info['complete'] = info
526 self._request_info['complete'] = info
527
527
528 def _get_banner(self):
528 def _get_banner(self):
529 """ Gets a banner to display at the beginning of a session.
529 """ Gets a banner to display at the beginning of a session.
530 """
530 """
531 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
531 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
532 '"license" for more information.'
532 '"license" for more information.'
533 return banner % (sys.version, sys.platform)
533 return banner % (sys.version, sys.platform)
534
534
535 def _get_context(self, cursor=None):
535 def _get_context(self, cursor=None):
536 """ Gets the context for the specified cursor (or the current cursor
536 """ Gets the context for the specified cursor (or the current cursor
537 if none is specified).
537 if none is specified).
538 """
538 """
539 if cursor is None:
539 if cursor is None:
540 cursor = self._get_cursor()
540 cursor = self._get_cursor()
541 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
541 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
542 QtGui.QTextCursor.KeepAnchor)
542 QtGui.QTextCursor.KeepAnchor)
543 text = cursor.selection().toPlainText()
543 text = cursor.selection().toPlainText()
544 return self._completion_lexer.get_context(text)
544 return self._completion_lexer.get_context(text)
545
545
546 def _process_execute_abort(self, msg):
546 def _process_execute_abort(self, msg):
547 """ Process a reply for an aborted execution request.
547 """ Process a reply for an aborted execution request.
548 """
548 """
549 self._append_plain_text("ERROR: execution aborted\n")
549 self._append_plain_text("ERROR: execution aborted\n")
550
550
551 def _process_execute_error(self, msg):
551 def _process_execute_error(self, msg):
552 """ Process a reply for an execution request that resulted in an error.
552 """ Process a reply for an execution request that resulted in an error.
553 """
553 """
554 content = msg['content']
554 content = msg['content']
555 # If a SystemExit is passed along, this means exit() was called - also
555 # If a SystemExit is passed along, this means exit() was called - also
556 # all the ipython %exit magic syntax of '-k' to be used to keep
556 # all the ipython %exit magic syntax of '-k' to be used to keep
557 # the kernel running
557 # the kernel running
558 if content['ename']=='SystemExit':
558 if content['ename']=='SystemExit':
559 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
559 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
560 self._keep_kernel_on_exit = keepkernel
560 self._keep_kernel_on_exit = keepkernel
561 self.exit_requested.emit()
561 self.exit_requested.emit()
562 else:
562 else:
563 traceback = ''.join(content['traceback'])
563 traceback = ''.join(content['traceback'])
564 self._append_plain_text(traceback)
564 self._append_plain_text(traceback)
565
565
566 def _process_execute_ok(self, msg):
566 def _process_execute_ok(self, msg):
567 """ Process a reply for a successful execution equest.
567 """ Process a reply for a successful execution equest.
568 """
568 """
569 payload = msg['content']['payload']
569 payload = msg['content']['payload']
570 for item in payload:
570 for item in payload:
571 if not self._process_execute_payload(item):
571 if not self._process_execute_payload(item):
572 warning = 'Warning: received unknown payload of type %s'
572 warning = 'Warning: received unknown payload of type %s'
573 print(warning % repr(item['source']))
573 print(warning % repr(item['source']))
574
574
575 def _process_execute_payload(self, item):
575 def _process_execute_payload(self, item):
576 """ Process a single payload item from the list of payload items in an
576 """ Process a single payload item from the list of payload items in an
577 execution reply. Returns whether the payload was handled.
577 execution reply. Returns whether the payload was handled.
578 """
578 """
579 # The basic FrontendWidget doesn't handle payloads, as they are a
579 # The basic FrontendWidget doesn't handle payloads, as they are a
580 # mechanism for going beyond the standard Python interpreter model.
580 # mechanism for going beyond the standard Python interpreter model.
581 return False
581 return False
582
582
583 def _show_interpreter_prompt(self):
583 def _show_interpreter_prompt(self):
584 """ Shows a prompt for the interpreter.
584 """ Shows a prompt for the interpreter.
585 """
585 """
586 self._show_prompt('>>> ')
586 self._show_prompt('>>> ')
587
587
588 def _show_interpreter_prompt_for_reply(self, msg):
588 def _show_interpreter_prompt_for_reply(self, msg):
589 """ Shows a prompt for the interpreter given an 'execute_reply' message.
589 """ Shows a prompt for the interpreter given an 'execute_reply' message.
590 """
590 """
591 self._show_interpreter_prompt()
591 self._show_interpreter_prompt()
592
592
593 #------ Signal handlers ----------------------------------------------------
593 #------ Signal handlers ----------------------------------------------------
594
594
595 def _document_contents_change(self, position, removed, added):
595 def _document_contents_change(self, position, removed, added):
596 """ Called whenever the document's content changes. Display a call tip
596 """ Called whenever the document's content changes. Display a call tip
597 if appropriate.
597 if appropriate.
598 """
598 """
599 # Calculate where the cursor should be *after* the change:
599 # Calculate where the cursor should be *after* the change:
600 position += added
600 position += added
601
601
602 document = self._control.document()
602 document = self._control.document()
603 if position == self._get_cursor().position():
603 if position == self._get_cursor().position():
604 self._call_tip()
604 self._call_tip()
@@ -1,163 +1,173 b''
1 # System library imports
1 # System library imports
2 from IPython.external.qt import QtGui
2 from IPython.external.qt import QtGui
3
3
4 # Local imports
4 # Local imports
5 from console_widget import ConsoleWidget
5 from console_widget import ConsoleWidget
6
6
7
7
8 class HistoryConsoleWidget(ConsoleWidget):
8 class HistoryConsoleWidget(ConsoleWidget):
9 """ A ConsoleWidget that keeps a history of the commands that have been
9 """ A ConsoleWidget that keeps a history of the commands that have been
10 executed and provides a readline-esque interface to this history.
10 executed and provides a readline-esque interface to this history.
11 """
11 """
12
12
13 #---------------------------------------------------------------------------
13 #---------------------------------------------------------------------------
14 # 'object' interface
14 # 'object' interface
15 #---------------------------------------------------------------------------
15 #---------------------------------------------------------------------------
16
16
17 def __init__(self, *args, **kw):
17 def __init__(self, *args, **kw):
18 super(HistoryConsoleWidget, self).__init__(*args, **kw)
18 super(HistoryConsoleWidget, self).__init__(*args, **kw)
19
19
20 # HistoryConsoleWidget protected variables.
20 # HistoryConsoleWidget protected variables.
21 self._history = []
21 self._history = []
22 self._history_index = 0
22 self._history_index = 0
23 self._history_prefix = ''
23 self._history_prefix = ''
24
24
25 #---------------------------------------------------------------------------
25 #---------------------------------------------------------------------------
26 # 'ConsoleWidget' public interface
26 # 'ConsoleWidget' public interface
27 #---------------------------------------------------------------------------
27 #---------------------------------------------------------------------------
28
28
29 def execute(self, source=None, hidden=False, interactive=False):
29 def execute(self, source=None, hidden=False, interactive=False):
30 """ Reimplemented to the store history.
30 """ Reimplemented to the store history.
31 """
31 """
32 if not hidden:
32 if not hidden:
33 history = self.input_buffer if source is None else source
33 history = self.input_buffer if source is None else source
34
34
35 executed = super(HistoryConsoleWidget, self).execute(
35 executed = super(HistoryConsoleWidget, self).execute(
36 source, hidden, interactive)
36 source, hidden, interactive)
37
37
38 if executed and not hidden:
38 if executed and not hidden:
39 # Save the command unless it was an empty string or was identical
39 # Save the command unless it was an empty string or was identical
40 # to the previous command.
40 # to the previous command.
41 history = history.rstrip()
41 history = history.rstrip()
42 if history and (not self._history or self._history[-1] != history):
42 if history and (not self._history or self._history[-1] != history):
43 self._history.append(history)
43 self._history.append(history)
44
44
45 # Move the history index to the most recent item.
45 # Move the history index to the most recent item.
46 self._history_index = len(self._history)
46 self._history_index = len(self._history)
47
47
48 return executed
48 return executed
49
49
50 #---------------------------------------------------------------------------
50 #---------------------------------------------------------------------------
51 # 'ConsoleWidget' abstract interface
51 # 'ConsoleWidget' abstract interface
52 #---------------------------------------------------------------------------
52 #---------------------------------------------------------------------------
53
53
54 def _up_pressed(self):
54 def _up_pressed(self):
55 """ Called when the up key is pressed. Returns whether to continue
55 """ Called when the up key is pressed. Returns whether to continue
56 processing the event.
56 processing the event.
57 """
57 """
58 prompt_cursor = self._get_prompt_cursor()
58 prompt_cursor = self._get_prompt_cursor()
59 if self._get_cursor().blockNumber() == prompt_cursor.blockNumber():
59 if self._get_cursor().blockNumber() == prompt_cursor.blockNumber():
60
60
61 # Set a search prefix based on the cursor position.
61 # Set a search prefix based on the cursor position.
62 col = self._get_input_buffer_cursor_column()
62 col = self._get_input_buffer_cursor_column()
63 input_buffer = self.input_buffer
63 input_buffer = self.input_buffer
64 if self._history_index == len(self._history) or \
64 if self._history_index == len(self._history) or \
65 (self._history_prefix and col != len(self._history_prefix)):
65 (self._history_prefix and col != len(self._history_prefix)):
66 self._history_index = len(self._history)
66 self._history_index = len(self._history)
67 self._history_prefix = input_buffer[:col]
67 self._history_prefix = input_buffer[:col]
68
68
69 # Perform the search.
69 # Perform the search.
70 self.history_previous(self._history_prefix)
70 self.history_previous(self._history_prefix)
71
71
72 # Go to the first line of the prompt for seemless history scrolling.
72 # Go to the first line of the prompt for seemless history scrolling.
73 # Emulate readline: keep the cursor position fixed for a prefix
73 # Emulate readline: keep the cursor position fixed for a prefix
74 # search.
74 # search.
75 cursor = self._get_prompt_cursor()
75 cursor = self._get_prompt_cursor()
76 if self._history_prefix:
76 if self._history_prefix:
77 cursor.movePosition(QtGui.QTextCursor.Right,
77 cursor.movePosition(QtGui.QTextCursor.Right,
78 n=len(self._history_prefix))
78 n=len(self._history_prefix))
79 else:
79 else:
80 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
80 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
81 self._set_cursor(cursor)
81 self._set_cursor(cursor)
82
82
83 return False
83 return False
84
84
85 return True
85 return True
86
86
87 def _down_pressed(self):
87 def _down_pressed(self):
88 """ Called when the down key is pressed. Returns whether to continue
88 """ Called when the down key is pressed. Returns whether to continue
89 processing the event.
89 processing the event.
90 """
90 """
91 end_cursor = self._get_end_cursor()
91 end_cursor = self._get_end_cursor()
92 if self._get_cursor().blockNumber() == end_cursor.blockNumber():
92 if self._get_cursor().blockNumber() == end_cursor.blockNumber():
93
93
94 # Perform the search.
94 # Perform the search.
95 self.history_next(self._history_prefix)
95 self.history_next(self._history_prefix)
96
96
97 # Emulate readline: keep the cursor position fixed for a prefix
97 # Emulate readline: keep the cursor position fixed for a prefix
98 # search. (We don't need to move the cursor to the end of the buffer
98 # search. (We don't need to move the cursor to the end of the buffer
99 # in the other case because this happens automatically when the
99 # in the other case because this happens automatically when the
100 # input buffer is set.)
100 # input buffer is set.)
101 if self._history_prefix:
101 if self._history_prefix:
102 cursor = self._get_prompt_cursor()
102 cursor = self._get_prompt_cursor()
103 cursor.movePosition(QtGui.QTextCursor.Right,
103 cursor.movePosition(QtGui.QTextCursor.Right,
104 n=len(self._history_prefix))
104 n=len(self._history_prefix))
105 self._set_cursor(cursor)
105 self._set_cursor(cursor)
106
106
107 return False
107 return False
108
108
109 return True
109 return True
110
110
111 #---------------------------------------------------------------------------
111 #---------------------------------------------------------------------------
112 # 'HistoryConsoleWidget' public interface
112 # 'HistoryConsoleWidget' public interface
113 #---------------------------------------------------------------------------
113 #---------------------------------------------------------------------------
114
114
115 def history_previous(self, prefix=''):
115 def history_previous(self, prefix=''):
116 """ If possible, set the input buffer to a previous item in the history.
116 """ If possible, set the input buffer to a previous item in the history.
117
117
118 Parameters:
118 Parameters:
119 -----------
119 -----------
120 prefix : str, optional
120 prefix : str, optional
121 If specified, search for an item with this prefix.
121 If specified, search for an item with this prefix.
122 """
122 """
123 index = self._history_index
123 index = self._history_index
124 while index > 0:
124 while index > 0:
125 index -= 1
125 index -= 1
126 history = self._history[index]
126 history = self._history[index]
127 if history.startswith(prefix):
127 if history.startswith(prefix):
128 break
128 break
129 else:
129 else:
130 history = None
130 history = None
131
131
132 if history is not None:
132 if history is not None:
133 self._history_index = index
133 self._history_index = index
134 self.input_buffer = history
134 self.input_buffer = history
135
135
136 def history_next(self, prefix=''):
136 def history_next(self, prefix=''):
137 """ Set the input buffer to a subsequent item in the history, or to the
137 """ Set the input buffer to a subsequent item in the history, or to the
138 original search prefix if there is no such item.
138 original search prefix if there is no such item.
139
139
140 Parameters:
140 Parameters:
141 -----------
141 -----------
142 prefix : str, optional
142 prefix : str, optional
143 If specified, search for an item with this prefix.
143 If specified, search for an item with this prefix.
144 """
144 """
145 while self._history_index < len(self._history) - 1:
145 while self._history_index < len(self._history) - 1:
146 self._history_index += 1
146 self._history_index += 1
147 history = self._history[self._history_index]
147 history = self._history[self._history_index]
148 if history.startswith(prefix):
148 if history.startswith(prefix):
149 break
149 break
150 else:
150 else:
151 self._history_index = len(self._history)
151 self._history_index = len(self._history)
152 history = prefix
152 history = prefix
153 self.input_buffer = history
153 self.input_buffer = history
154
154
155 def history_tail(self, n=10):
156 """ Get the local history list.
157
158 Parameters
159 ----------
160 n : int
161 The (maximum) number of history items to get.
162 """
163 return self._history[-n:]
164
155 #---------------------------------------------------------------------------
165 #---------------------------------------------------------------------------
156 # 'HistoryConsoleWidget' protected interface
166 # 'HistoryConsoleWidget' protected interface
157 #---------------------------------------------------------------------------
167 #---------------------------------------------------------------------------
158
168
159 def _set_history(self, history):
169 def _set_history(self, history):
160 """ Replace the current history with a sequence of history items.
170 """ Replace the current history with a sequence of history items.
161 """
171 """
162 self._history = list(history)
172 self._history = list(history)
163 self._history_index = len(self._history)
173 self._history_index = len(self._history)
General Comments 0
You need to be logged in to leave comments. Login now