1# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: 2 3# Copyright 2014-2021 Florian Bruhin (The Compiler) <mail@qutebrowser.org> 4# 5# This file is part of qutebrowser. 6# 7# qutebrowser is free software: you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation, either version 3 of the License, or 10# (at your option) any later version. 11# 12# qutebrowser is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. 19 20"""Command history for the status bar.""" 21 22from typing import MutableSequence 23 24from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject 25 26from qutebrowser.utils import usertypes, log, standarddir, objreg 27from qutebrowser.misc import lineparser 28 29 30class HistoryEmptyError(Exception): 31 32 """Raised when the history is empty.""" 33 34 35class HistoryEndReachedError(Exception): 36 37 """Raised when the end of the history is reached.""" 38 39 40class History(QObject): 41 42 """Command history. 43 44 Attributes: 45 history: A list of executed commands, with newer commands at the end. 46 _tmphist: Temporary history for history browsing (as NeighborList) 47 48 Signals: 49 changed: Emitted when an entry was added to the history. 50 """ 51 52 changed = pyqtSignal() 53 54 def __init__(self, *, history=None, parent=None): 55 """Constructor. 56 57 Args: 58 history: The initial history to set. 59 """ 60 super().__init__(parent) 61 self._tmphist = None 62 if history is None: 63 self.history: MutableSequence[str] = [] 64 else: 65 self.history = history 66 67 def __getitem__(self, idx): 68 return self.history[idx] 69 70 def is_browsing(self): 71 """Check _tmphist to see if we're browsing.""" 72 return self._tmphist is not None 73 74 def start(self, text): 75 """Start browsing to the history. 76 77 Called when the user presses the up/down key and wasn't browsing the 78 history already. 79 80 Args: 81 text: The preset text. 82 """ 83 log.misc.debug("Preset text: '{}'".format(text)) 84 if text: 85 items: MutableSequence[str] = [ 86 e for e in self.history 87 if e.startswith(text)] 88 else: 89 items = self.history 90 if not items: 91 raise HistoryEmptyError 92 self._tmphist = usertypes.NeighborList(items) 93 return self._tmphist.lastitem() 94 95 @pyqtSlot() 96 def stop(self): 97 """Stop browsing the history.""" 98 self._tmphist = None 99 100 def previtem(self): 101 """Get the previous item in the temp history. 102 103 start() needs to be called before calling this. 104 """ 105 if not self.is_browsing(): 106 raise ValueError("Currently not browsing history") 107 assert self._tmphist is not None 108 109 try: 110 return self._tmphist.previtem() 111 except IndexError: 112 raise HistoryEndReachedError 113 114 def nextitem(self): 115 """Get the next item in the temp history. 116 117 start() needs to be called before calling this. 118 """ 119 if not self.is_browsing(): 120 raise ValueError("Currently not browsing history") 121 assert self._tmphist is not None 122 123 try: 124 return self._tmphist.nextitem() 125 except IndexError: 126 raise HistoryEndReachedError 127 128 def append(self, text): 129 """Append a new item to the history. 130 131 Args: 132 text: The text to append. 133 """ 134 if not self.history or text != self.history[-1]: 135 self.history.append(text) 136 self.changed.emit() 137 138 139def init(): 140 """Initialize the LimitLineParser storing the history.""" 141 save_manager = objreg.get('save-manager') 142 command_history = lineparser.LimitLineParser( 143 standarddir.data(), 'cmd-history', 144 limit='completion.cmd_history_max_items') 145 objreg.register('command-history', command_history) 146 save_manager.add_saveable('command-history', command_history.save, 147 command_history.changed) 148