From 18f2f4eb066ab04c46fcc6227a4f90b58c72f0ac Mon Sep 17 00:00:00 2001 From: Marlon Sousa Date: Sat, 21 Jun 2025 08:26:27 -0300 Subject: [PATCH 1/3] use search history gui --- .../EnhancedFindDialog/__init__.py | 18 ++++++ .../EnhancedFindDialog/guiHelper.py | 64 ++++++++++++++++++- 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/addon/globalPlugins/EnhancedFindDialog/__init__.py b/addon/globalPlugins/EnhancedFindDialog/__init__.py index 01b5410..6f00383 100644 --- a/addon/globalPlugins/EnhancedFindDialog/__init__.py +++ b/addon/globalPlugins/EnhancedFindDialog/__init__.py @@ -4,21 +4,36 @@ # This file is covered by the GNU General Public License. # See the file COPYING.txt for more details. +from logHandler import log import config from . import cursorManagerHelper import globalPluginHandler import globalVars +import os +import logging module = "EnhancedFindDialog" +ADDON_LOG_FILE = os.path.join(os.path.dirname(__file__), "..", "..", "my_addon_shutdown.log") +log.info("addon log file" + ADDON_LOG_FILE) + +# Configure a separate logger for your addon's shutdown messages +shutdown_logger = logging.getLogger("my_addon_shutdown") +shutdown_logger.setLevel(logging.INFO) +file_handler = logging.FileHandler(ADDON_LOG_FILE, mode='a') # Use 'w' to overwrite on each NVDA start +formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') +file_handler.setFormatter(formatter) +shutdown_logger.addHandler(file_handler) + def initConfiguration(): confspec = { "searchCaseSensitivity": "boolean( default=False)", "searchWrap": "boolean( default=False)", "searchType": "string( default='NORMAL')", + "useSearchHistory": "boolean( default=False)", } config.conf.spec[module] = confspec @@ -35,6 +50,9 @@ def __init__(self, *args, **kwargs): super(GlobalPlugin, self).__init__(*args, **kwargs) self.injectProcessing() + def terminate(self): + shutdown_logger.info('turned off') + # the method below is responsible for modifying NVDA behavior. # we need that certain parts of NVDA behave differently than the original to insert our functionality # for example, when calling the find dialog we need to show the enhanced version provided by this addon diff --git a/addon/globalPlugins/EnhancedFindDialog/guiHelper.py b/addon/globalPlugins/EnhancedFindDialog/guiHelper.py index e4b3b4e..7dc9263 100644 --- a/addon/globalPlugins/EnhancedFindDialog/guiHelper.py +++ b/addon/globalPlugins/EnhancedFindDialog/guiHelper.py @@ -65,7 +65,14 @@ def scheduleProfileSave(profile): # get config from default profile def getDefaultConfig(key): - return config.conf[module][key] + defaultProfile = config.conf.profiles[0] + try: + value = defaultProfile[module][key] + except KeyError: + # Not set in base profile, get default from spec + spec = config.conf.spec[module][key] + value = config.conf.validator.get_default_value(spec) + return value def getConfig(profile, key): @@ -81,6 +88,13 @@ def setConfig(profile, key, value): profile[module][key] = value +def setDefaultConfig(key, value): + defaultProfile = config.conf.profiles[0] + if module not in defaultProfile: + defaultProfile[module] = {} + defaultProfile[module][key] = value + + class EnhancedFindDialog(contextHelp.ContextHelpMixin, wx.Dialog): # Noqa: E101 """A dialog used to specify text to find in a cursor manager. @@ -103,6 +117,7 @@ def __init__(self, parent, cursorManager, profile, reverseSearch): self.profile = profile self.caseSensitivity = strToBool(getConfig(profile, "searchCaseSensitivity")) self.searchWrap = strToBool(getConfig(profile, "searchWrap")) + self.useSearchHistory = strToBool(getDefaultConfig("useSearchHistory")) self.searchType = SearchType.getByName(getConfig(profile, "searchType")).name self.buildGui() self.updateUi() @@ -145,6 +160,21 @@ def buildGui(self): self.searchWrapCheckBox = wx.CheckBox(self, wx.ID_ANY, label=_("Search &wrap")) sHelper.addItem(self.searchWrapCheckBox) + searchHistoryHelper = guiHelper.BoxSizerHelper( + self, orientation=wx.HORIZONTAL) + + self.useSearchHistoryCheckBox = wx.CheckBox( + self, wx.ID_ANY, + # Translators: An option in find dialog to save search history persistently + label=_("Use search history")) + self.removeSearchHistoryButton = wx.Button( + self, wx.ID_ANY, + # Translators: A button to remove search history. + label=_("Remove search history")) + searchHistoryHelper.addItem(self.useSearchHistoryCheckBox) + searchHistoryHelper.addItem(self.removeSearchHistoryButton) + sHelper.addItem(searchHistoryHelper) + sHelper.addDialogDismissButtons(self.CreateButtonSizer(wx.OK | wx.CANCEL)) mainSizer.Add(sHelper.sizer, border=guiHelper.BORDER_FOR_DIALOGS, flag=wx.ALL) @@ -158,6 +188,7 @@ def updateUi(self): log.debug("called update ui") self.caseSensitiveCheckBox.SetValue(self.caseSensitivity) self.searchWrapCheckBox.SetValue(self.searchWrap) + self.useSearchHistoryCheckBox.SetValue(self.useSearchHistory) if not self.activeCursorManager.supportsRegexpSearch(): self.searchType = SearchType.NORMAL.name self._searchTypeCtrl.Enable(False) @@ -175,6 +206,8 @@ def bindEvents(self): self.searchWrapCheckBox.Bind(wx.EVT_CHECKBOX, self.onStatChange) self._searchTypeCtrl.Bind(wx.EVT_RADIOBOX, self.OnSearchTypeChanged) self._searchTypeCtrl.Bind(wx.EVT_CHECKBOX, self.onStatChange) + self.useSearchHistoryCheckBox.Bind(wx.EVT_CHECKBOX, self.onUseSearchHistory) + self.removeSearchHistoryButton.Bind(wx.EVT_BUTTON, self.onRemoveSearchHistory) def OnSearchTypeChanged(self, evt): log.debug("called OnSearchTypeChanged") @@ -182,6 +215,32 @@ def OnSearchTypeChanged(self, evt): self.updateUi() self.onStatChange(evt) + def onUseSearchHistory(self, evt): + log.debug("called onUseSearchHistory") + if self.useSearchHistoryCheckBox.GetValue(): + log.debug("Use search history checked") + dlg = wx.MessageDialog( + None, + # Translators: Message shown when enabling persistent search history. + _("Do you want to use your search history?"), + # Translators: Title for the save search history confirmation dialog. + _("Confirm Use Search History"), + wx.OK | wx.CANCEL | wx.ICON_QUESTION + ) + dlg.SetOKCancelLabels( + # Translators: Label for the confirm usage button + _("Confirm usage"), + # Translators: Label for the deny usage button + _("Deny usage")) + result = dlg.ShowModal() + dlg.Destroy() + self.useSearchHistory = (result == wx.ID_OK) + else: + self.useSearchHistory = False + self.useSearchHistoryCheckBox.SetValue(self.useSearchHistory) + self.useSearchHistoryCheckBox.SetFocus() + self.onStatChange(evt) + def updateSearchHistory(self, currentSearchText): if not currentSearchText: return None @@ -239,8 +298,9 @@ def updateProfile(self): scheduleProfileSave(self.profile) def onStatChange(self, evt): - log.debug("called onStatChange") + log.debug(f"called onStatChange {self.useSearchHistory}") self._mustSaveProfile = True + setDefaultConfig("useSearchHistory", self.useSearchHistory) def _truncateSearchHistory(self, entries): del entries[SEARCH_HISTORY_LEAST_RECENT_INDEX:] From 71a1b82a758d1343c4182d7e1af263de376c3938 Mon Sep 17 00:00:00 2001 From: Marlon Sousa Date: Sat, 21 Jun 2025 12:41:14 -0300 Subject: [PATCH 2/3] save and restore of search history --- .../EnhancedFindDialog/__init__.py | 17 ++--- .../EnhancedFindDialog/configUtils.py | 60 ++++++++++++++++ .../EnhancedFindDialog/guiHelper.py | 71 ++++--------------- .../EnhancedFindDialog/searchHistory.py | 36 ++++++++++ 4 files changed, 114 insertions(+), 70 deletions(-) create mode 100644 addon/globalPlugins/EnhancedFindDialog/configUtils.py diff --git a/addon/globalPlugins/EnhancedFindDialog/__init__.py b/addon/globalPlugins/EnhancedFindDialog/__init__.py index 6f00383..1aa27b5 100644 --- a/addon/globalPlugins/EnhancedFindDialog/__init__.py +++ b/addon/globalPlugins/EnhancedFindDialog/__init__.py @@ -7,6 +7,8 @@ from logHandler import log import config from . import cursorManagerHelper +from .configUtils import getDefaultConfig, initConfiguration, strToBool +from .searchHistory import SearchHistory import globalPluginHandler import globalVars @@ -14,7 +16,6 @@ import os import logging -module = "EnhancedFindDialog" ADDON_LOG_FILE = os.path.join(os.path.dirname(__file__), "..", "..", "my_addon_shutdown.log") log.info("addon log file" + ADDON_LOG_FILE) @@ -28,16 +29,6 @@ shutdown_logger.addHandler(file_handler) -def initConfiguration(): - confspec = { - "searchCaseSensitivity": "boolean( default=False)", - "searchWrap": "boolean( default=False)", - "searchType": "string( default='NORMAL')", - "useSearchHistory": "boolean( default=False)", - } - config.conf.spec[module] = confspec - - def getActiveProfile(self): if globalVars.appArgs.secure: return @@ -51,6 +42,10 @@ def __init__(self, *args, **kwargs): self.injectProcessing() def terminate(self): + if strToBool(getDefaultConfig("useSearchHistory")): + searchHistory = SearchHistory.get() + searchHistory.persist() + shutdown_logger.info('Persisted config') shutdown_logger.info('turned off') # the method below is responsible for modifying NVDA behavior. diff --git a/addon/globalPlugins/EnhancedFindDialog/configUtils.py b/addon/globalPlugins/EnhancedFindDialog/configUtils.py new file mode 100644 index 0000000..87cb1da --- /dev/null +++ b/addon/globalPlugins/EnhancedFindDialog/configUtils.py @@ -0,0 +1,60 @@ +import config + + +module = "EnhancedFindDialog" + + +def initConfiguration(): + confspec = { + "searchCaseSensitivity": "boolean( default=False)", + "searchWrap": "boolean( default=False)", + "searchType": "string( default='NORMAL')", + "useSearchHistory": "boolean( default=False)", + } + config.conf.spec[module] = confspec + + +def strToBool(value): + if not isinstance(value, str): + return value + return value == "True" + + +# we need to mark profiles we updated for save, otherwise they will not be persisted +def scheduleProfileSave(profile): + # default pprofile is always saved + if not profile.name: + return + config.conf._dirtyProfiles.add(profile.name) + + +# get config from default profile +def getDefaultConfig(key): + defaultProfile = config.conf.profiles[0] + try: + value = defaultProfile[module][key] + except KeyError: + # Not set in base profile, get default from spec + spec = config.conf.spec[module][key] + value = config.conf.validator.get_default_value(spec) + return value + + +def getConfig(profile, key): + # if this is not set on current profile, use the default config values. + if module not in profile or key not in profile[module]: + return getDefaultConfig(key) + return profile[module][key] + + +def setConfig(profile, key, value): + if module not in profile: + profile[module] = {} + profile[module][key] = value + + +def setDefaultConfig(key, value): + defaultProfile = config.conf.profiles[0] + if module not in defaultProfile: + defaultProfile[module] = {} + defaultProfile[module][key] = value diff --git a/addon/globalPlugins/EnhancedFindDialog/guiHelper.py b/addon/globalPlugins/EnhancedFindDialog/guiHelper.py index 7dc9263..11779ca 100644 --- a/addon/globalPlugins/EnhancedFindDialog/guiHelper.py +++ b/addon/globalPlugins/EnhancedFindDialog/guiHelper.py @@ -5,11 +5,11 @@ # See the file COPYING.txt for more details. import addonHandler -import config import core import gui from . import cursorManagerHelper +from .configUtils import getConfig, getDefaultConfig, scheduleProfileSave, setConfig, setDefaultConfig, strToBool # Noqa: E501 from .searchHistory import SearchHistory, SearchTerm from .searchType import SearchType from gui import contextHelp, guiHelper @@ -46,57 +46,11 @@ # case sensitivity and search wrapping checkboxes state will be persisted per profile # so we need to be able to get and set values from config -module = "EnhancedFindDialog" - -def strToBool(value): - if not isinstance(value, str): - return value - return value == "True" - - -# we need to mark profiles we updated for save, otherwise they will not be persisted -def scheduleProfileSave(profile): - # default pprofile is always saved - if not profile.name: - return - config.conf._dirtyProfiles.add(profile.name) - - -# get config from default profile -def getDefaultConfig(key): - defaultProfile = config.conf.profiles[0] - try: - value = defaultProfile[module][key] - except KeyError: - # Not set in base profile, get default from spec - spec = config.conf.spec[module][key] - value = config.conf.validator.get_default_value(spec) - return value - - -def getConfig(profile, key): - # if this is not set on current profile, use the default config values. - if module not in profile or key not in profile[module]: - return getDefaultConfig(key) - return profile[module][key] - - -def setConfig(profile, key, value): - if module not in profile: - profile[module] = {} - profile[module][key] = value - - -def setDefaultConfig(key, value): - defaultProfile = config.conf.profiles[0] - if module not in defaultProfile: - defaultProfile[module] = {} - defaultProfile[module][key] = value - - -class EnhancedFindDialog(contextHelp.ContextHelpMixin, - wx.Dialog): # Noqa: E101 +class EnhancedFindDialog( + contextHelp.ContextHelpMixin, + wx.Dialog, # Noqa: E101 +): """A dialog used to specify text to find in a cursor manager. """ @@ -144,8 +98,7 @@ def buildGui(self): # present the last searched term selected by default if searchEntries: self.findTextField.Select(SEARCH_HISTORY_MOST_RECENT_INDEX) - searchTypeHelper = guiHelper.BoxSizerHelper( - self, orientation=wx.HORIZONTAL) + searchTypeHelper = guiHelper.BoxSizerHelper(self, orientation=wx.HORIZONTAL) self._searchTypeCtrl = searchTypeHelper.addItem(wx.RadioBox( self, # Translators: A radio box to select the search type. @@ -160,8 +113,7 @@ def buildGui(self): self.searchWrapCheckBox = wx.CheckBox(self, wx.ID_ANY, label=_("Search &wrap")) sHelper.addItem(self.searchWrapCheckBox) - searchHistoryHelper = guiHelper.BoxSizerHelper( - self, orientation=wx.HORIZONTAL) + searchHistoryHelper = guiHelper.BoxSizerHelper(self, orientation=wx.HORIZONTAL) self.useSearchHistoryCheckBox = wx.CheckBox( self, wx.ID_ANY, @@ -193,7 +145,7 @@ def updateUi(self): self.searchType = SearchType.NORMAL.name self._searchTypeCtrl.Enable(False) self._searchTypeCtrl.SetSelection(SearchType.getIndexByName(self.searchType)) - if(self.searchType == SearchType.NORMAL.name): + if self.searchType == SearchType.NORMAL.name: self.caseSensitiveCheckBox.Enable(True) else: self.caseSensitiveCheckBox.Enable(False) @@ -257,16 +209,14 @@ def onOk(self, evt): except re.error: wx.CallAfter( gui.messageBox, - # Translators: Message shown when an invalid regular expression is entered. + # Translators: Message shown when an invalid regular expression is entered. _("The entered text is not a valid regular expression."), cursorManagerHelper.FIND_ERROR_DIALOG_TITLE, wx.OK | wx.ICON_ERROR ) # Noqa E101 return self.caseSensitive = self.caseSensitiveCheckBox.GetValue() - self.searchWrap = self.searchWrapCheckBox.GetValue() - self.searchType = SearchType.getByIndex(self._searchTypeCtrl.GetSelection()).name # update the list of searched entries so that it can be exibited in the next find dialog call @@ -288,6 +238,9 @@ def onCancel(self, evt): log.debug("called onCancel") self.Destroy() + def onRemoveSearchHistory(self, evt): + pass + def updateProfile(self): log.debug("called updateProfile") setConfig(self.profile, "searchType", self.searchType) diff --git a/addon/globalPlugins/EnhancedFindDialog/searchHistory.py b/addon/globalPlugins/EnhancedFindDialog/searchHistory.py index 6fa85f7..4e3ea67 100644 --- a/addon/globalPlugins/EnhancedFindDialog/searchHistory.py +++ b/addon/globalPlugins/EnhancedFindDialog/searchHistory.py @@ -5,7 +5,12 @@ # See the file COPYING.txt for more details. +from .configUtils import getDefaultConfig, strToBool +import addonHandler from logHandler import log +import pickle +import time +import os class SearchHistory: @@ -18,8 +23,39 @@ def get(cls): return cls._instance def __init__(self): + if strToBool(getDefaultConfig("useSearchHistory")): + self._loadFromDisk() + return self._terms = [] + def _loadFromDisk(self): + # Load the pickle file from the addon directory + addonPath = addonHandler.getCodeAddon().path + filePath = os.path.join(addonPath, "search_history.pkl") + if os.path.exists(filePath): + with open(filePath, "rb") as f: + data = pickle.load(f) + if data.get("version") == "1.0": + self._terms = data.get("terms", []) + else: + log.error(f"Unsupported search history version: {data.get('version')}") + self._terms = [] + else: + log.info("No search history file found, starting with an empty history.") + self._terms = [] + + def persist(self): + data = { + "version": "1.0", + "timestamp": time.time(), + "terms": self._terms, + } + # Save the pickle file in the addon directory + addonPath = addonHandler.getCodeAddon().path + filePath = os.path.join(addonPath, "search_history.pkl") + with open(filePath, "wb") as f: + pickle.dump(data, f) + def getMostRecent(self): return self._terms[0] if self._terms else None From b6ebe9a8491063a26643383779db1acc9000ad32 Mon Sep 17 00:00:00 2001 From: Marlon Sousa Date: Sat, 28 Jun 2025 18:49:52 -0300 Subject: [PATCH 3/3] search history persisted --- .../EnhancedFindDialog/guiHelper.py | 71 ++++++++++++++++--- .../EnhancedFindDialog/searchHistory.py | 42 ++++++++--- 2 files changed, 93 insertions(+), 20 deletions(-) diff --git a/addon/globalPlugins/EnhancedFindDialog/guiHelper.py b/addon/globalPlugins/EnhancedFindDialog/guiHelper.py index 11779ca..dc35c97 100644 --- a/addon/globalPlugins/EnhancedFindDialog/guiHelper.py +++ b/addon/globalPlugins/EnhancedFindDialog/guiHelper.py @@ -80,9 +80,7 @@ def __init__(self, parent, cursorManager, profile, reverseSearch): def buildGui(self): log.debug("called buildGui") supportsRegexp = self.activeCursorManager.supportsRegexpSearch() - searchEntries = self.searchHistory.getItems(None if supportsRegexp else SearchType.NORMAL.name) - # if the search type is not supported, remove it from the list of search entries - searchTerms = [entry.text for entry in searchEntries] + self.searchEntries = self.searchHistory.getItems(None if supportsRegexp else SearchType.NORMAL.name) mainSizer = wx.BoxSizer(wx.VERTICAL) sHelper = guiHelper.BoxSizerHelper(self, orientation=wx.VERTICAL) @@ -91,13 +89,11 @@ def buildGui(self): textToFind = wx.StaticText(self, wx.ID_ANY, label=__("Type the text you wish to find")) hSizer.Add(textToFind, flag=wx.ALIGN_CENTER_VERTICAL) hSizer.AddSpacer(guiHelper.SPACE_BETWEEN_ASSOCIATED_CONTROL_HORIZONTAL) - self.findTextField = wx.ComboBox(self, wx.ID_ANY, choices=searchTerms, style=wx.CB_DROPDOWN) + self.findTextField = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_DROPDOWN) + if self.searchEntries: + self.updateFindTextEntries() hSizer.Add(self.findTextField) sHelper.addItem(hSizer) - # if there is a previous list of searched entries, make sure we - # present the last searched term selected by default - if searchEntries: - self.findTextField.Select(SEARCH_HISTORY_MOST_RECENT_INDEX) searchTypeHelper = guiHelper.BoxSizerHelper(self, orientation=wx.HORIZONTAL) self._searchTypeCtrl = searchTypeHelper.addItem(wx.RadioBox( self, @@ -150,6 +146,14 @@ def updateUi(self): else: self.caseSensitiveCheckBox.Enable(False) + def updateFindTextEntries(self): + log.debug("called updateFindTextEntries") + searchTerms = [entry.text for entry in self.searchEntries] + mostRecentSearchTerm = self.searchEntries[SEARCH_HISTORY_MOST_RECENT_INDEX] + self.searchType = mostRecentSearchTerm.searchType + self.findTextField.SetItems(searchTerms) + self.findTextField.Select(SEARCH_HISTORY_MOST_RECENT_INDEX) + def bindEvents(self): log.debug("called bind events") self.Bind(wx.EVT_BUTTON, self.onOk, id=wx.ID_OK) @@ -157,7 +161,7 @@ def bindEvents(self): self.caseSensitiveCheckBox.Bind(wx.EVT_CHECKBOX, self.onStatChange) self.searchWrapCheckBox.Bind(wx.EVT_CHECKBOX, self.onStatChange) self._searchTypeCtrl.Bind(wx.EVT_RADIOBOX, self.OnSearchTypeChanged) - self._searchTypeCtrl.Bind(wx.EVT_CHECKBOX, self.onStatChange) + self.findTextField.Bind(wx.EVT_COMBOBOX, self.onSearchTermChanged) self.useSearchHistoryCheckBox.Bind(wx.EVT_CHECKBOX, self.onUseSearchHistory) self.removeSearchHistoryButton.Bind(wx.EVT_BUTTON, self.onRemoveSearchHistory) @@ -167,6 +171,14 @@ def OnSearchTypeChanged(self, evt): self.updateUi() self.onStatChange(evt) + def onSearchTermChanged(self, evt): + log.debug("called onSearchTermChanged") + selectedSearchTermindex = self.findTextField.GetSelection() + mostRecentSearchTerm = self.searchEntries[selectedSearchTermindex] + self.searchType = mostRecentSearchTerm.searchType + self.updateUi() + self.onStatChange(evt) + def onUseSearchHistory(self, evt): log.debug("called onUseSearchHistory") if self.useSearchHistoryCheckBox.GetValue(): @@ -187,6 +199,16 @@ def onUseSearchHistory(self, evt): result = dlg.ShowModal() dlg.Destroy() self.useSearchHistory = (result == wx.ID_OK) + if self.useSearchHistory: + log.debug("Use search history confirmed") + # merge with history from disk + self.searchHistory.mergeWithHistoryFromDisk() + # truncate the search history to the last 20 entries + self._truncateSearchHistory(self.searchHistory._terms) + supportsRegexp = self.activeCursorManager.supportsRegexpSearch() + self.searchEntries = self.searchHistory.getItems(None if supportsRegexp else SearchType.NORMAL.name) + if(self.searchEntries): + self.updateFindTextEntries() else: self.useSearchHistory = False self.useSearchHistoryCheckBox.SetValue(self.useSearchHistory) @@ -239,7 +261,17 @@ def onCancel(self, evt): self.Destroy() def onRemoveSearchHistory(self, evt): - pass + if self._confirmSearchHistoryDeletion(): + log.debug("called onRemoveSearchHistory") + self.searchHistory.removePersistentHistory() + self.searchHistory.clean() + self.searchEntries = [] + self.findTextField.SetItems([]) + self.findTextField.SetValue("") + self.findTextField.SetFocus() + self.useSearchHistory = False + self.useSearchHistoryCheckBox.SetValue(self.useSearchHistory) + self.onStatChange(evt) def updateProfile(self): log.debug("called updateProfile") @@ -255,5 +287,24 @@ def onStatChange(self, evt): self._mustSaveProfile = True setDefaultConfig("useSearchHistory", self.useSearchHistory) + def _confirmSearchHistoryDeletion(self): + log.debug("called confirmSearchHistoryDeletion") + dlg = wx.MessageDialog( + None, + # Translators: Message shown when removing search history. + _("Do you want to remove your search history?"), + # Translators: Title for the remove search history confirmation dialog. + _("Confirm Remove Search History"), + wx.OK | wx.CANCEL | wx.ICON_QUESTION + ) + dlg.SetOKCancelLabels( + # Translators: Label for the confirm removal button + _("Confirm removal"), + # Translators: Label for the deny removal button + _("Deny removal")) + result = dlg.ShowModal() + dlg.Destroy() + return result == wx.ID_OK + def _truncateSearchHistory(self, entries): del entries[SEARCH_HISTORY_LEAST_RECENT_INDEX:] diff --git a/addon/globalPlugins/EnhancedFindDialog/searchHistory.py b/addon/globalPlugins/EnhancedFindDialog/searchHistory.py index 4e3ea67..dae10b0 100644 --- a/addon/globalPlugins/EnhancedFindDialog/searchHistory.py +++ b/addon/globalPlugins/EnhancedFindDialog/searchHistory.py @@ -24,25 +24,22 @@ def get(cls): def __init__(self): if strToBool(getDefaultConfig("useSearchHistory")): - self._loadFromDisk() + self._terms = self.loadFromDisk() return self._terms = [] - def _loadFromDisk(self): - # Load the pickle file from the addon directory - addonPath = addonHandler.getCodeAddon().path - filePath = os.path.join(addonPath, "search_history.pkl") + def loadFromDisk(self): + filePath = self._getSearchHistoryPath() if os.path.exists(filePath): with open(filePath, "rb") as f: data = pickle.load(f) if data.get("version") == "1.0": - self._terms = data.get("terms", []) + return data.get("terms", []) else: log.error(f"Unsupported search history version: {data.get('version')}") - self._terms = [] else: log.info("No search history file found, starting with an empty history.") - self._terms = [] + return [] def persist(self): data = { @@ -51,11 +48,36 @@ def persist(self): "terms": self._terms, } # Save the pickle file in the addon directory - addonPath = addonHandler.getCodeAddon().path - filePath = os.path.join(addonPath, "search_history.pkl") + filePath = self._getSearchHistoryPath() with open(filePath, "wb") as f: pickle.dump(data, f) + def mergeWithHistoryFromDisk(self): + filePath = self._getSearchHistoryPath() + if not os.path.exists(filePath): + return + sessionTerms = self._terms + self._terms = self.loadFromDisk() + for term in sessionTerms: + self.append(term) + + def clean(self): + """Remove all search terms from the history.""" + self._terms = [] + log.info("Search history cleared.") + + def removePersistentHistory(self): + filePath = self._getSearchHistoryPath() + if os.path.exists(filePath): + os.remove(filePath) + log.info("Search history file removed.") + else: + log.info("No search history file to remove.") + + def _getSearchHistoryPath(self): + addonPath = addonHandler.getCodeAddon().path + return os.path.join(addonPath, "search_history.pkl") + def getMostRecent(self): return self._terms[0] if self._terms else None