diff --git a/IPython/frontend/qt/console/console_widget.py b/IPython/frontend/qt/console/console_widget.py index 0cffcd3..3f8fdd6 100644 --- a/IPython/frontend/qt/console/console_widget.py +++ b/IPython/frontend/qt/console/console_widget.py @@ -652,13 +652,13 @@ class ConsoleWidget(Configurable, QtGui.QWidget): """ pass - def _up_pressed(self): + def _up_pressed(self, shift_modifier): """ Called when the up key is pressed. Returns whether to continue processing the event. """ return True - def _down_pressed(self): + def _down_pressed(self, shift_modifier): """ Called when the down key is pressed. Returns whether to continue processing the event. """ @@ -1040,14 +1040,14 @@ class ConsoleWidget(Configurable, QtGui.QWidget): intercepted = True elif key == QtCore.Qt.Key_Up: - if self._reading or not self._up_pressed(): + if self._reading or not self._up_pressed(shift_down): intercepted = True else: prompt_line = self._get_prompt_cursor().blockNumber() intercepted = cursor.blockNumber() <= prompt_line elif key == QtCore.Qt.Key_Down: - if self._reading or not self._down_pressed(): + if self._reading or not self._down_pressed(shift_down): intercepted = True else: end_line = self._get_end_cursor().blockNumber() diff --git a/IPython/frontend/qt/console/history_console_widget.py b/IPython/frontend/qt/console/history_console_widget.py index 85530a3..40de7d4 100644 --- a/IPython/frontend/qt/console/history_console_widget.py +++ b/IPython/frontend/qt/console/history_console_widget.py @@ -2,6 +2,7 @@ from IPython.external.qt import QtGui # Local imports +from IPython.utils.traitlets import Bool from console_widget import ConsoleWidget @@ -9,6 +10,13 @@ class HistoryConsoleWidget(ConsoleWidget): """ A ConsoleWidget that keeps a history of the commands that have been executed and provides a readline-esque interface to this history. """ + + #------ Configuration ------------------------------------------------------ + + # If enabled, the input buffer will become "locked" to history movement when + # an edit is made to a multi-line input buffer. To override the lock, use + # Shift in conjunction with the standard history cycling keys. + history_lock = Bool(False, config=True) #--------------------------------------------------------------------------- # 'object' interface @@ -55,12 +63,15 @@ class HistoryConsoleWidget(ConsoleWidget): # 'ConsoleWidget' abstract interface #--------------------------------------------------------------------------- - def _up_pressed(self): + def _up_pressed(self, shift_modifier): """ Called when the up key is pressed. Returns whether to continue processing the event. """ prompt_cursor = self._get_prompt_cursor() if self._get_cursor().blockNumber() == prompt_cursor.blockNumber(): + # Bail out if we're locked. + if self._history_locked() and not shift_modifier: + return False # Set a search prefix based on the cursor position. col = self._get_input_buffer_cursor_column() @@ -88,21 +99,24 @@ class HistoryConsoleWidget(ConsoleWidget): return True - def _down_pressed(self): + def _down_pressed(self, shift_modifier): """ Called when the down key is pressed. Returns whether to continue processing the event. """ end_cursor = self._get_end_cursor() if self._get_cursor().blockNumber() == end_cursor.blockNumber(): + # Bail out if we're locked. + if self._history_locked() and not shift_modifier: + return False # Perform the search. - self.history_next(self._history_prefix) + replaced = self.history_next(self._history_prefix) # Emulate readline: keep the cursor position fixed for a prefix # search. (We don't need to move the cursor to the end of the buffer # in the other case because this happens automatically when the # input buffer is set.) - if self._history_prefix: + if self._history_prefix and replaced: cursor = self._get_prompt_cursor() cursor.movePosition(QtGui.QTextCursor.Right, n=len(self._history_prefix)) @@ -123,19 +137,26 @@ class HistoryConsoleWidget(ConsoleWidget): ----------- prefix : str, optional If specified, search for an item with this prefix. + + Returns: + -------- + Whether the input buffer was changed. """ index = self._history_index + replace = False while index > 0: index -= 1 history = self._get_edited_history(index) if history.startswith(prefix): + replace = True break - else: - history = None - if history is not None: - self._set_edited_input_buffer(history) + if replace: + self._store_edits() self._history_index = index + self.input_buffer = history + + return replace def history_next(self, prefix=''): """ If possible, set the input buffer to a subsequent history item. @@ -144,19 +165,26 @@ class HistoryConsoleWidget(ConsoleWidget): ----------- prefix : str, optional If specified, search for an item with this prefix. + + Returns: + -------- + Whether the input buffer was changed. """ index = self._history_index + replace = False while self._history_index < len(self._history): index += 1 history = self._get_edited_history(index) if history.startswith(prefix): + replace = True break - else: - history = None - - if history is not None: - self._set_edited_input_buffer(history) + + if replace: + self._store_edits() self._history_index = index + self.input_buffer = history + + return replace def history_tail(self, n=10): """ Get the local history list. @@ -172,23 +200,35 @@ class HistoryConsoleWidget(ConsoleWidget): # 'HistoryConsoleWidget' protected interface #--------------------------------------------------------------------------- + def _history_locked(self): + """ Returns whether history movement is locked. + """ + return (self.history_lock and + (self._get_edited_history(self._history_index) != + self.input_buffer) and + (self._get_prompt_cursor().blockNumber() != + self._get_end_cursor().blockNumber())) + def _get_edited_history(self, index): """ Retrieves a history item, possibly with temporary edits. """ if index in self._history_edits: return self._history_edits[index] + elif index == len(self._history): + return unicode() return self._history[index] - def _set_edited_input_buffer(self, source): - """ Sets the input buffer to 'source', saving the current input buffer - as a temporary history edit. - """ - self._history_edits[self._history_index] = self.input_buffer - self.input_buffer = source - def _set_history(self, history): """ Replace the current history with a sequence of history items. """ self._history = list(history) self._history_edits = {} self._history_index = len(self._history) + + def _store_edits(self): + """ If there are edits to the current input buffer, store them. + """ + current = self.input_buffer + if self._history_index == len(self._history) or \ + self._history[self._history_index] != current: + self._history_edits[self._history_index] = current