123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- # Copyright (c) Jupyter Development Team.
- # Distributed under the terms of the Modified BSD License.
- from qtpy import QtGui
- from ipython_genutils.py3compat import unicode_type
- from traitlets import Bool
- from .console_widget import ConsoleWidget
- 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
- #---------------------------------------------------------------------------
- def __init__(self, *args, **kw):
- super(HistoryConsoleWidget, self).__init__(*args, **kw)
- # HistoryConsoleWidget protected variables.
- self._history = []
- self._history_edits = {}
- self._history_index = 0
- self._history_prefix = ''
- #---------------------------------------------------------------------------
- # 'ConsoleWidget' public interface
- #---------------------------------------------------------------------------
- def do_execute(self, source, complete, indent):
- """ Reimplemented to the store history. """
- history = self.input_buffer if source is None else source
- super(HistoryConsoleWidget, self).do_execute(source, complete, indent)
- if complete:
- # Save the command unless it was an empty string or was identical
- # to the previous command.
- history = history.rstrip()
- if history and (not self._history or self._history[-1] != history):
- self._history.append(history)
- # Emulate readline: reset all history edits.
- self._history_edits = {}
- # Move the history index to the most recent item.
- self._history_index = len(self._history)
- #---------------------------------------------------------------------------
- # 'ConsoleWidget' abstract interface
- #---------------------------------------------------------------------------
- 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.
- pos = self._get_input_buffer_cursor_pos()
- input_buffer = self.input_buffer
- # use the *shortest* of the cursor column and the history prefix
- # to determine if the prefix has changed
- n = min(pos, len(self._history_prefix))
- # prefix changed, restart search from the beginning
- if (self._history_prefix[:n] != input_buffer[:n]):
- self._history_index = len(self._history)
- # the only time we shouldn't set the history prefix
- # to the line up to the cursor is if we are already
- # in a simple scroll (no prefix),
- # and the cursor is at the end of the first line
- # check if we are at the end of the first line
- c = self._get_cursor()
- current_pos = c.position()
- c.movePosition(QtGui.QTextCursor.EndOfBlock)
- at_eol = (c.position() == current_pos)
- if self._history_index == len(self._history) or \
- not (self._history_prefix == '' and at_eol) or \
- not (self._get_edited_history(self._history_index)[:pos] == input_buffer[:pos]):
- self._history_prefix = input_buffer[:pos]
- # Perform the search.
- self.history_previous(self._history_prefix,
- as_prefix=not shift_modifier)
- # Go to the first line of the prompt for seemless history scrolling.
- # Emulate readline: keep the cursor position fixed for a prefix
- # search.
- cursor = self._get_prompt_cursor()
- if self._history_prefix:
- cursor.movePosition(QtGui.QTextCursor.Right,
- n=len(self._history_prefix))
- else:
- cursor.movePosition(QtGui.QTextCursor.EndOfBlock)
- self._set_cursor(cursor)
- return False
- return True
- 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.
- replaced = self.history_next(self._history_prefix,
- as_prefix=not shift_modifier)
- # 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 and replaced:
- cursor = self._get_prompt_cursor()
- cursor.movePosition(QtGui.QTextCursor.Right,
- n=len(self._history_prefix))
- self._set_cursor(cursor)
- return False
- return True
- #---------------------------------------------------------------------------
- # 'HistoryConsoleWidget' public interface
- #---------------------------------------------------------------------------
- def history_previous(self, substring='', as_prefix=True):
- """ If possible, set the input buffer to a previous history item.
- Parameters
- ----------
- substring : str, optional
- If specified, search for an item with this substring.
- as_prefix : bool, optional
- If True, the substring must match at the beginning (default).
- 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 == self.input_buffer:
- continue
- if (as_prefix and history.startswith(substring)) \
- or (not as_prefix and substring in history):
- replace = True
- break
- if replace:
- self._store_edits()
- self._history_index = index
- self.input_buffer = history
- return replace
- def history_next(self, substring='', as_prefix=True):
- """ If possible, set the input buffer to a subsequent history item.
- Parameters
- ----------
- substring : str, optional
- If specified, search for an item with this substring.
- as_prefix : bool, optional
- If True, the substring must match at the beginning (default).
- Returns
- -------
- Whether the input buffer was changed.
- """
- index = self._history_index
- replace = False
- while index < len(self._history):
- index += 1
- history = self._get_edited_history(index)
- if history == self.input_buffer:
- continue
- if (as_prefix and history.startswith(substring)) \
- or (not as_prefix and substring in history):
- replace = True
- break
- 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.
- Parameters
- ----------
- n : int
- The (maximum) number of history items to get.
- """
- return self._history[-n:]
- #---------------------------------------------------------------------------
- # '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_type()
- return self._history[index]
- 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
|