diff --git a/pylib/anki/tags.py b/pylib/anki/tags.py index 3eba489a7..f4f8008e3 100644 --- a/pylib/anki/tags.py +++ b/pylib/anki/tags.py @@ -13,7 +13,7 @@ from __future__ import annotations import pprint import re -from typing import Collection, List, Optional, Tuple +from typing import Collection, List, Optional, Sequence, Tuple import anki # pylint: disable=unused-import from anki.utils import ids2str @@ -92,7 +92,7 @@ class TagManager: return self.col.backend.add_note_tags(nids=nids, tags=tags) def bulk_update( - self, nids: List[int], tags: str, replacement: str, regex: bool + self, nids: Sequence[int], tags: str, replacement: str, regex: bool ) -> int: """Replace space-separated tags, returning changed count. Tags replaced with an empty string will be removed.""" @@ -100,6 +100,15 @@ class TagManager: nids=nids, tags=tags, replacement=replacement, regex=regex ) + def rename_tag(self, old: str, new: str) -> int: + "Rename provided tag, returning number of changed notes." + escaped_name = re.sub(r"[*_\\]", r"\\\g<0>", old) + escaped_name = '"{}"'.format(escaped_name.replace('"', '\\"')) + nids = self.col.find_notes("tag:" + escaped_name) + if not nids: + return 0 + return self.col.tags.bulk_update(nids, old, new, False) + # legacy routines def bulkAdd(self, ids: List[int], tags: str, add: bool = True) -> None: diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index 1dbfd71a1..1350cee74 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -6,6 +6,7 @@ from __future__ import annotations import html import re import time +from concurrent.futures import Future from dataclasses import dataclass from enum import Enum from operator import itemgetter @@ -1649,8 +1650,11 @@ where id in %s""" self.editor.saveNow(self._clearUnusedTags) def _clearUnusedTags(self): - self.col.tags.registerNotes() - self.on_tag_list_update() + def on_done(fut: Future): + fut.result() + self.on_tag_list_update() + + self.mw.taskman.run_in_background(self.col.tags.registerNotes, on_done) # Suspending ###################################################################### diff --git a/qt/aqt/sidebar.py b/qt/aqt/sidebar.py index 02d90ace0..7b8c068b0 100644 --- a/qt/aqt/sidebar.py +++ b/qt/aqt/sidebar.py @@ -4,6 +4,7 @@ from __future__ import annotations import re +from concurrent.futures import Future from enum import Enum import aqt @@ -92,7 +93,7 @@ class NewSidebarTreeView(SidebarTreeViewBase): a.triggered.connect(lambda _, func=act_func: func(item)) # type: ignore m.exec_(QCursor.pos()) - def rename_deck(self, item: aqt.browser.SidebarItem) -> None: + def rename_deck(self, item: "aqt.browser.SidebarItem") -> None: deck = self.mw.col.decks.get(item.id) old_name = deck["name"] new_name = getOnlyText(tr(TR.DECKS_NEW_DECK_NAME), default=old_name) @@ -107,24 +108,29 @@ class NewSidebarTreeView(SidebarTreeViewBase): self.browser.maybeRefreshSidebar() self.mw.deckBrowser.refresh() - def rename_tag(self, item: aqt.browser.SidebarItem) -> None: + def rename_tag(self, item: "aqt.browser.SidebarItem") -> None: self.browser.editor.saveNow(lambda: self._rename_tag(item)) - def _rename_tag(self, item: aqt.browser.SidebarItem) -> None: + def _rename_tag(self, item: "aqt.browser.SidebarItem") -> None: old_name = item.name - escaped_name = re.sub(r"[*_\\]", r"\\\g<0>", old_name) - escaped_name = '"{}"'.format(escaped_name.replace('"', '\\"')) - nids = self.col.find_notes("tag:" + escaped_name) - if len(nids) == 0: - showInfo(tr(TR.BROWSING_TAG_RENAME_WARNING_EMPTY)) - return new_name = getOnlyText(tr(TR.ACTIONS_NEW_NAME), default=old_name) if new_name == old_name or not new_name: return + + def do_rename(): + return self.col.tags.rename_tag(old_name, new_name) + + def on_done(fut: Future): + self.mw.requireReset(reason=ResetReason.BrowserAddTags, context=self) + self.browser.model.endReset() + + count = fut.result() + if not count: + showInfo(tr(TR.BROWSING_TAG_RENAME_WARNING_EMPTY)) + return + + self.browser.clearUnusedTags() + self.mw.checkpoint(tr(TR.ACTIONS_RENAME_TAG)) self.browser.model.beginReset() - self.col.tags.bulk_update(list(nids), old_name, new_name, False) - self.browser.model.endReset() - self.browser.clearUnusedTags() - self.mw.requireReset(reason=ResetReason.BrowserAddTags, context=self) - self.browser.maybeRefreshSidebar() + self.mw.taskman.run_in_background(do_rename, on_done)