From 9ef691c06f77bd78465d1ed25f00b63521134bc0 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sat, 9 Jan 2021 10:50:08 +0100 Subject: [PATCH 1/5] Provide filter searches through backend --- rslib/backend.proto | 30 +++++++++++++++++++++++ rslib/src/backend/mod.rs | 50 +++++++++++++++++++++++++++++++++++--- rslib/src/search/mod.rs | 4 ++- rslib/src/search/parser.rs | 10 ++++---- rslib/src/search/writer.rs | 2 +- rslib/src/text.rs | 8 ++++++ 6 files changed, 94 insertions(+), 10 deletions(-) diff --git a/rslib/backend.proto b/rslib/backend.proto index a3966e47a..820b0a832 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -84,6 +84,7 @@ service BackendService { // searching + rpc FilterToSearch (FilterToSearchIn) returns (String); rpc NormalizeSearch (String) returns (String); rpc SearchCards (SearchCardsIn) returns (SearchCardsOut); rpc SearchNotes (SearchNotesIn) returns (SearchNotesOut); @@ -756,6 +757,35 @@ message BuiltinSearchOrder { bool reverse = 2; } +message FilterToSearchIn { + enum NamedFilter { + WHOLE_COLLECTION = 0; + CURRENT_DECK = 1; + ADDED_TODAY = 2; + STUDIED_TODAY = 3; + AGAIN_TODAY = 4; + NEW = 5; + LEARN = 6; + REVIEW = 7; + DUE = 8; + SUSPENDED = 9; + BURIED = 10; + RED_FLAG = 11; + ORANGE_FLAG = 12; + GREEN_FLAG = 13; + BLUE_FLAG = 14; + NO_FLAG = 15; + ANY_FLAG = 16; + } + oneof filter { + NamedFilter name = 1; + string tag = 2; + string deck = 3; + string note = 4; + uint32 template = 5; + } +} + message ConcatenateSearchesIn { enum Separator { AND = 0; diff --git a/rslib/src/backend/mod.rs b/rslib/src/backend/mod.rs index 22868773f..18ee036bf 100644 --- a/rslib/src/backend/mod.rs +++ b/rslib/src/backend/mod.rs @@ -36,8 +36,8 @@ use crate::{ sched::new::NewCardSortOrder, sched::timespan::{answer_button_time, time_span}, search::{ - concatenate_searches, negate_search, normalize_search, replace_search_term, BoolSeparator, - SortMode, + concatenate_searches, negate_search, normalize_search, replace_search_term, write_nodes, + BoolSeparator, Node, SearchNode, SortMode, StateKind, TemplateKind, }, stats::studied_today, sync::{ @@ -45,7 +45,7 @@ use crate::{ SyncActionRequired, SyncAuth, SyncMeta, SyncOutput, SyncStage, }, template::RenderedNode, - text::{extract_av_tags, strip_av_tags, AVTag}, + text::{escape_anki_wildcards, extract_av_tags, strip_av_tags, AVTag}, timestamp::TimestampSecs, types::Usn, }; @@ -277,6 +277,46 @@ impl From for DeckConfID { } } +impl From for Node<'_> { + fn from(msg: pb::FilterToSearchIn) -> Self { + use pb::filter_to_search_in::Filter as F; + use pb::filter_to_search_in::NamedFilter as NF; + use Node as N; + use SearchNode as SN; + match msg.filter.unwrap_or(F::Name(NF::WholeCollection as i32)) { + F::Name(name) => match NF::from_i32(name).unwrap_or(NF::WholeCollection) { + NF::WholeCollection => N::Search(SN::WholeCollection), + NF::CurrentDeck => N::Search(SN::Deck("current".into())), + NF::AddedToday => N::Search(SN::AddedInDays(1)), + NF::StudiedToday => N::Search(SN::Rated { + days: 1, + ease: None, + }), + NF::AgainToday => N::Search(SN::Rated { + days: 1, + ease: Some(1), + }), + NF::New => N::Search(SN::State(StateKind::New)), + NF::Learn => N::Search(SN::State(StateKind::Learning)), + NF::Review => N::Search(SN::State(StateKind::Review)), + NF::Due => N::Search(SN::State(StateKind::Due)), + NF::Suspended => N::Search(SN::State(StateKind::Suspended)), + NF::Buried => N::Search(SN::State(StateKind::Buried)), + NF::RedFlag => N::Search(SN::Flag(1)), + NF::OrangeFlag => N::Search(SN::Flag(2)), + NF::GreenFlag => N::Search(SN::Flag(3)), + NF::BlueFlag => N::Search(SN::Flag(4)), + NF::NoFlag => N::Search(SN::Flag(0)), + NF::AnyFlag => N::Not(Box::new(N::Search(SN::Flag(0)))), + }, + F::Tag(s) => N::Search(SN::Tag(escape_anki_wildcards(&s).into_owned().into())), + F::Deck(s) => N::Search(SN::Deck(escape_anki_wildcards(&s).into_owned().into())), + F::Note(s) => N::Search(SN::NoteType(escape_anki_wildcards(&s).into_owned().into())), + F::Template(u) => N::Search(SN::CardTemplate(TemplateKind::Ordinal(u as u16))), + } + } +} + impl From for BoolSeparator { fn from(sep: BoolSeparatorProto) -> Self { match sep { @@ -408,6 +448,10 @@ impl BackendService for Backend { // searching //----------------------------------------------- + fn filter_to_search(&self, input: pb::FilterToSearchIn) -> Result { + Ok(write_nodes(&[input.into()]).into()) + } + fn normalize_search(&self, input: pb::String) -> Result { Ok(normalize_search(&input.val)?.into()) } diff --git a/rslib/src/search/mod.rs b/rslib/src/search/mod.rs index 607b9d767..5eef7e86e 100644 --- a/rslib/src/search/mod.rs +++ b/rslib/src/search/mod.rs @@ -5,6 +5,8 @@ mod sqlwriter; mod writer; pub use cards::SortMode; +pub use parser::{Node, PropertyKind, SearchNode, StateKind, TemplateKind}; pub use writer::{ - concatenate_searches, negate_search, normalize_search, replace_search_term, BoolSeparator, + concatenate_searches, negate_search, normalize_search, replace_search_term, write_nodes, + BoolSeparator, }; diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index 9e03abcc5..7763c965f 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -42,7 +42,7 @@ impl From> for ParseError { type ParseResult = std::result::Result; #[derive(Debug, PartialEq)] -pub(super) enum Node<'a> { +pub enum Node<'a> { And, Or, Not(Box>), @@ -51,7 +51,7 @@ pub(super) enum Node<'a> { } #[derive(Debug, PartialEq, Clone)] -pub(super) enum SearchNode<'a> { +pub enum SearchNode<'a> { // text without a colon UnqualifiedText(Cow<'a, str>), // foo:bar, where foo doesn't match a term below @@ -91,7 +91,7 @@ pub(super) enum SearchNode<'a> { } #[derive(Debug, PartialEq, Clone)] -pub(super) enum PropertyKind { +pub enum PropertyKind { Due(i32), Interval(u32), Reps(u32), @@ -101,7 +101,7 @@ pub(super) enum PropertyKind { } #[derive(Debug, PartialEq, Clone)] -pub(super) enum StateKind { +pub enum StateKind { New, Review, Learning, @@ -113,7 +113,7 @@ pub(super) enum StateKind { } #[derive(Debug, PartialEq, Clone)] -pub(super) enum TemplateKind<'a> { +pub enum TemplateKind<'a> { Ordinal(u16), Name(Cow<'a, str>), } diff --git a/rslib/src/search/writer.rs b/rslib/src/search/writer.rs index ec7051893..e112ebcf5 100644 --- a/rslib/src/search/writer.rs +++ b/rslib/src/search/writer.rs @@ -87,7 +87,7 @@ pub fn replace_search_term(search: &str, replacement: &str) -> Result { Ok(write_nodes(&nodes)) } -fn write_nodes<'a, I>(nodes: I) -> String +pub fn write_nodes<'a, I>(nodes: I) -> String where I: IntoIterator>, { diff --git a/rslib/src/text.rs b/rslib/src/text.rs index 0f892ecd0..10391334e 100644 --- a/rslib/src/text.rs +++ b/rslib/src/text.rs @@ -331,6 +331,14 @@ pub(crate) fn escape_sql(txt: &str) -> Cow { RE.replace_all(&txt, r"\$0") } +/// Escape Anki wildcards and the backslash for escaping them: \*_ +pub(crate) fn escape_anki_wildcards(txt: &str) -> Cow { + lazy_static! { + static ref RE: Regex = Regex::new(r"[\\*_]").unwrap(); + } + RE.replace_all(&txt, r"\$0") +} + /// Compare text with a possible glob, folding case. pub(crate) fn matches_glob(text: &str, search: &str) -> bool { if is_glob(search) { From fda2bfdb4e77721bde478dda21812067da024cb8 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sat, 9 Jan 2021 10:51:15 +0100 Subject: [PATCH 2/5] Use backend filters instead of literal searches --- pylib/anki/rsbackend.py | 7 +++ qt/aqt/browser.py | 113 ++++++++++++++++++---------------------- 2 files changed, 59 insertions(+), 61 deletions(-) diff --git a/pylib/anki/rsbackend.py b/pylib/anki/rsbackend.py index 1653bfe88..26bbc87ec 100644 --- a/pylib/anki/rsbackend.py +++ b/pylib/anki/rsbackend.py @@ -46,6 +46,7 @@ TagUsnTuple = pb.TagUsnTuple NoteType = pb.NoteType DeckTreeNode = pb.DeckTreeNode StockNoteType = pb.StockNoteType +NamedFilter = pb.FilterToSearchIn.NamedFilter ConcatSeparator = pb.ConcatenateSearchesIn.Separator SyncAuth = pb.SyncAuth SyncOutput = pb.SyncCollectionOut @@ -261,6 +262,12 @@ class RustBackend(RustBackendGenerated): err.ParseFromString(err_bytes) raise proto_exception_to_native(err) + def filters_to_searches(self, **kwargs) -> List[str]: + return [ + self.filter_to_search(pb.FilterToSearchIn(**dict([f]))) + for f in kwargs.items() + ] + def translate_string_in( key: TRValue, **kwargs: Union[str, int, float] diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index 3c448f89b..bb5a5be55 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -4,7 +4,6 @@ from __future__ import annotations import html -import re import time from concurrent.futures import Future from dataclasses import dataclass @@ -21,7 +20,7 @@ from anki.consts import * from anki.lang import without_unicode_isolation from anki.models import NoteType from anki.notes import Note -from anki.rsbackend import ConcatSeparator, DeckTreeNode, InvalidInput +from anki.rsbackend import ConcatSeparator, DeckTreeNode, InvalidInput, NamedFilter from anki.stats import CardStats from anki.utils import htmlToTextLine, ids2str, isMac, isWin from aqt import AnkiQt, gui_hooks @@ -1110,14 +1109,14 @@ QTableView {{ gridline-color: {grid} }} item = SidebarItem( tr(TR.BROWSING_WHOLE_COLLECTION), ":/icons/collection.svg", - self._filterFunc(""), + self._filterFunc(name=NamedFilter.WHOLE_COLLECTION), item_type=SidebarItemType.COLLECTION, ) root.addChild(item) item = SidebarItem( tr(TR.BROWSING_CURRENT_DECK), ":/icons/deck.svg", - self._filterFunc("deck:current"), + self._filterFunc(name=NamedFilter.CURRENT_DECK), item_type=SidebarItemType.CURRENT_DECK, ) root.addChild(item) @@ -1140,7 +1139,7 @@ QTableView {{ gridline-color: {grid} }} item = SidebarItem( t, ":/icons/tag.svg", - lambda t=t: self.setFilter("tag", t), # type: ignore + lambda t=t: self.setFilter(tag=t), # type: ignore item_type=SidebarItemType.TAG, ) root.addChild(item) @@ -1153,7 +1152,7 @@ QTableView {{ gridline-color: {grid} }} def set_filter(): full_name = head + node.name # pylint: disable=cell-var-from-loop - return lambda: self.setFilter("deck", full_name) + return lambda: self.setFilter(deck=full_name) def toggle_expand(): did = node.deck_id # pylint: disable=cell-var-from-loop @@ -1180,7 +1179,7 @@ QTableView {{ gridline-color: {grid} }} item = SidebarItem( m.name, ":/icons/notetype.svg", - lambda m=m: self.setFilter("note", m.name), # type: ignore + lambda m=m: self.setFilter(note=m.name), # type: ignore item_type=SidebarItemType.NOTETYPE, ) root.addChild(item) @@ -1208,47 +1207,38 @@ QTableView {{ gridline-color: {grid} }} ml.popupOver(self.form.filter) - def setFilter(self, *args): - if len(args) == 1: - txt = args[0] - else: - txt = "" - items = [] - for i, a in enumerate(args): - if i % 2 == 0: - txt += a + ":" - else: - txt += re.sub(r'["*_\\]', r"\\\g<0>", a) - txt = '"{}"'.format(txt.replace('"', '\\"')) - items.append(txt) - txt = "" - txt = " AND ".join(items) + def setFilter(self, *args, **kwargs): + # args are literal searches, kwargs are searches provided by the backend try: - if self.mw.app.keyboardModifiers() & Qt.AltModifier: - txt = self.col.backend.negate_search(txt) + filters = self.col.backend.filters_to_searches(**kwargs) + search = self.col.backend.concatenate_searches( + sep=ConcatSeparator.AND, searches=list(args) + filters + ) + mods = self.mw.app.keyboardModifiers() + if mods & Qt.AltModifier: + search = self.col.backend.negate_search(search) cur = str(self.form.searchEdit.lineEdit().text()) if cur != self._searchPrompt: - mods = self.mw.app.keyboardModifiers() if mods & Qt.ControlModifier and mods & Qt.ShiftModifier: - txt = self.col.backend.replace_search_term( - search=cur, replacement=txt + search = self.col.backend.replace_search_term( + search=cur, replacement=search ) elif mods & Qt.ControlModifier: - txt = self.col.backend.concatenate_searches( + search = self.col.backend.concatenate_searches( # pylint: disable=no-member sep=ConcatSeparator.AND, - searches=[cur, txt], + searches=[cur, search], ) elif mods & Qt.ShiftModifier: - txt = self.col.backend.concatenate_searches( + search = self.col.backend.concatenate_searches( # pylint: disable=no-member sep=ConcatSeparator.OR, - searches=[cur, txt], + searches=[cur, search], ) except InvalidInput as e: showWarning(str(e)) else: - self.form.searchEdit.lineEdit().setText(txt) + self.form.searchEdit.lineEdit().setText(search) self.onSearchActivated() def _simpleFilters(self, items): @@ -1257,18 +1247,21 @@ QTableView {{ gridline-color: {grid} }} if row is None: ml.addSeparator() else: - label, filter = row - ml.addItem(label, self._filterFunc(filter)) + label, filter_name = row + ml.addItem(label, self._filterFunc(name=filter_name)) return ml - def _filterFunc(self, *args): - return lambda *, f=args: self.setFilter(*f) + def _filterFunc(self, *args, **kwargs): + return lambda: self.setFilter(*args, **kwargs) def _commonFilters(self): return self._simpleFilters( ( - (tr(TR.BROWSING_WHOLE_COLLECTION), ""), - (tr(TR.BROWSING_CURRENT_DECK), '"deck:current"'), + (tr(TR.BROWSING_WHOLE_COLLECTION), NamedFilter.WHOLE_COLLECTION), + ( + tr(TR.BROWSING_CURRENT_DECK), + NamedFilter.CURRENT_DECK, + ), ) ) @@ -1277,9 +1270,9 @@ QTableView {{ gridline-color: {grid} }} subm.addChild( self._simpleFilters( ( - (tr(TR.BROWSING_ADDED_TODAY), '"added:1"'), - (tr(TR.BROWSING_STUDIED_TODAY), '"rated:1"'), - (tr(TR.BROWSING_AGAIN_TODAY), '"rated:1:1"'), + (tr(TR.BROWSING_ADDED_TODAY), NamedFilter.ADDED_TODAY), + (tr(TR.BROWSING_STUDIED_TODAY), NamedFilter.STUDIED_TODAY), + (tr(TR.BROWSING_AGAIN_TODAY), NamedFilter.AGAIN_TODAY), ) ) ) @@ -1290,20 +1283,20 @@ QTableView {{ gridline-color: {grid} }} subm.addChild( self._simpleFilters( ( - (tr(TR.ACTIONS_NEW), '"is:new"'), - (tr(TR.SCHEDULING_LEARNING), '"is:learn"'), - (tr(TR.SCHEDULING_REVIEW), '"is:review"'), - (tr(TR.FILTERING_IS_DUE), '"is:due"'), + (tr(TR.ACTIONS_NEW), NamedFilter.NEW), + (tr(TR.SCHEDULING_LEARNING), NamedFilter.LEARN), + (tr(TR.SCHEDULING_REVIEW), NamedFilter.REVIEW), + (tr(TR.FILTERING_IS_DUE), NamedFilter.DUE), None, - (tr(TR.BROWSING_SUSPENDED), '"is:suspended"'), - (tr(TR.BROWSING_BURIED), '"is:buried"'), + (tr(TR.BROWSING_SUSPENDED), NamedFilter.SUSPENDED), + (tr(TR.BROWSING_BURIED), NamedFilter.BURIED), None, - (tr(TR.ACTIONS_RED_FLAG), '"flag:1"'), - (tr(TR.ACTIONS_ORANGE_FLAG), '"flag:2"'), - (tr(TR.ACTIONS_GREEN_FLAG), '"flag:3"'), - (tr(TR.ACTIONS_BLUE_FLAG), '"flag:4"'), - (tr(TR.BROWSING_NO_FLAG), '"flag:0"'), - (tr(TR.BROWSING_ANY_FLAG), '"-flag:0"'), + (tr(TR.ACTIONS_RED_FLAG), NamedFilter.RED_FLAG), + (tr(TR.ACTIONS_ORANGE_FLAG), NamedFilter.ORANGE_FLAG), + (tr(TR.ACTIONS_GREEN_FLAG), NamedFilter.GREEN_FLAG), + (tr(TR.ACTIONS_BLUE_FLAG), NamedFilter.BLUE_FLAG), + (tr(TR.BROWSING_NO_FLAG), NamedFilter.NO_FLAG), + (tr(TR.BROWSING_ANY_FLAG), NamedFilter.ANY_FLAG), ) ) ) @@ -1320,7 +1313,7 @@ QTableView {{ gridline-color: {grid} }} tagList = MenuList() for t in sorted(self.col.tags.all(), key=lambda s: s.lower()): - tagList.addItem(self._escapeMenuItem(t), self._filterFunc("tag", t)) + tagList.addItem(self._escapeMenuItem(t), self._filterFunc(tag=t)) m.addChild(tagList.chunked()) return m @@ -1333,13 +1326,11 @@ QTableView {{ gridline-color: {grid} }} fullname = parent_prefix + node.name if node.children: subm = parent.addMenu(escaped_name) - subm.addItem( - tr(TR.ACTIONS_FILTER), self._filterFunc("deck", fullname) - ) + subm.addItem(tr(TR.ACTIONS_FILTER), self._filterFunc(deck=fullname)) subm.addSeparator() addDecks(subm, node.children, fullname + "::") else: - parent.addItem(escaped_name, self._filterFunc("deck", fullname)) + parent.addItem(escaped_name, self._filterFunc(deck=fullname)) alldecks = self.col.decks.deck_tree() ml = MenuList() @@ -1361,12 +1352,12 @@ QTableView {{ gridline-color: {grid} }} escaped_nt_name = self._escapeMenuItem(nt["name"]) # no sub menu if it's a single template if len(nt["tmpls"]) == 1: - noteTypes.addItem(escaped_nt_name, self._filterFunc("note", nt["name"])) + noteTypes.addItem(escaped_nt_name, self._filterFunc(note=nt["name"])) else: subm = noteTypes.addMenu(escaped_nt_name) subm.addItem( - tr(TR.BROWSING_ALL_CARD_TYPES), self._filterFunc("note", nt["name"]) + tr(TR.BROWSING_ALL_CARD_TYPES), self._filterFunc(note=nt["name"]) ) subm.addSeparator() @@ -1380,7 +1371,7 @@ QTableView {{ gridline-color: {grid} }} name=self._escapeMenuItem(tmpl["name"]), ) subm.addItem( - name, self._filterFunc("note", nt["name"], "card", str(c + 1)) + name, self._filterFunc(note=nt["name"], template=c + 1) ) m.addChild(noteTypes.chunked()) From b99d9cda7421a0868a9962d1816f3a4533078607 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sat, 9 Jan 2021 12:34:46 +0100 Subject: [PATCH 3/5] Prettify frontend filter code --- pylib/anki/rsbackend.py | 6 +++--- qt/aqt/browser.py | 18 +++++++----------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/pylib/anki/rsbackend.py b/pylib/anki/rsbackend.py index 26bbc87ec..935381739 100644 --- a/pylib/anki/rsbackend.py +++ b/pylib/anki/rsbackend.py @@ -262,10 +262,10 @@ class RustBackend(RustBackendGenerated): err.ParseFromString(err_bytes) raise proto_exception_to_native(err) - def filters_to_searches(self, **kwargs) -> List[str]: + def filters_to_searches(self, filters: dict) -> List[str]: return [ - self.filter_to_search(pb.FilterToSearchIn(**dict([f]))) - for f in kwargs.items() + self.filter_to_search(pb.FilterToSearchIn(**{key: val})) + for key, val in filters.items() ] diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index bb5a5be55..b4a37422b 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -1128,7 +1128,7 @@ QTableView {{ gridline-color: {grid} }} item = SidebarItem( name, ":/icons/heart.svg", - lambda s=filt: self.setFilter(s), # type: ignore + self._filterFunc(filt), item_type=SidebarItemType.FILTER, ) root.addChild(item) @@ -1139,7 +1139,7 @@ QTableView {{ gridline-color: {grid} }} item = SidebarItem( t, ":/icons/tag.svg", - lambda t=t: self.setFilter(tag=t), # type: ignore + self._filterFunc(tag=t), item_type=SidebarItemType.TAG, ) root.addChild(item) @@ -1179,7 +1179,7 @@ QTableView {{ gridline-color: {grid} }} item = SidebarItem( m.name, ":/icons/notetype.svg", - lambda m=m: self.setFilter(note=m.name), # type: ignore + self._filterFunc(note=m.name), item_type=SidebarItemType.NOTETYPE, ) root.addChild(item) @@ -1207,12 +1207,11 @@ QTableView {{ gridline-color: {grid} }} ml.popupOver(self.form.filter) - def setFilter(self, *args, **kwargs): - # args are literal searches, kwargs are searches provided by the backend + def setFilter(self, *search_strings, **filters): try: - filters = self.col.backend.filters_to_searches(**kwargs) + filter_searches = self.col.backend.filters_to_searches(filters) search = self.col.backend.concatenate_searches( - sep=ConcatSeparator.AND, searches=list(args) + filters + sep=ConcatSeparator.AND, searches=list(search_strings) + filter_searches ) mods = self.mw.app.keyboardModifiers() if mods & Qt.AltModifier: @@ -1258,10 +1257,7 @@ QTableView {{ gridline-color: {grid} }} return self._simpleFilters( ( (tr(TR.BROWSING_WHOLE_COLLECTION), NamedFilter.WHOLE_COLLECTION), - ( - tr(TR.BROWSING_CURRENT_DECK), - NamedFilter.CURRENT_DECK, - ), + (tr(TR.BROWSING_CURRENT_DECK), NamedFilter.CURRENT_DECK), ) ) From b763fc5b2ab2524e9c70252ed8f67629c761e59e Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sat, 9 Jan 2021 16:48:47 +0100 Subject: [PATCH 4/5] Use explicit wrapper functions to get filters --- pylib/anki/rsbackend.py | 7 +--- qt/aqt/browser.py | 77 +++++++++++++++++++++++++++-------------- 2 files changed, 52 insertions(+), 32 deletions(-) diff --git a/pylib/anki/rsbackend.py b/pylib/anki/rsbackend.py index 935381739..7b4454ca0 100644 --- a/pylib/anki/rsbackend.py +++ b/pylib/anki/rsbackend.py @@ -46,6 +46,7 @@ TagUsnTuple = pb.TagUsnTuple NoteType = pb.NoteType DeckTreeNode = pb.DeckTreeNode StockNoteType = pb.StockNoteType +FilterToSearchIn = pb.FilterToSearchIn NamedFilter = pb.FilterToSearchIn.NamedFilter ConcatSeparator = pb.ConcatenateSearchesIn.Separator SyncAuth = pb.SyncAuth @@ -262,12 +263,6 @@ class RustBackend(RustBackendGenerated): err.ParseFromString(err_bytes) raise proto_exception_to_native(err) - def filters_to_searches(self, filters: dict) -> List[str]: - return [ - self.filter_to_search(pb.FilterToSearchIn(**{key: val})) - for key, val in filters.items() - ] - def translate_string_in( key: TRValue, **kwargs: Union[str, int, float] diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index b4a37422b..be56be53f 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -20,7 +20,13 @@ from anki.consts import * from anki.lang import without_unicode_isolation from anki.models import NoteType from anki.notes import Note -from anki.rsbackend import ConcatSeparator, DeckTreeNode, InvalidInput, NamedFilter +from anki.rsbackend import ( + ConcatSeparator, + DeckTreeNode, + FilterToSearchIn, + InvalidInput, + NamedFilter, +) from anki.stats import CardStats from anki.utils import htmlToTextLine, ids2str, isMac, isWin from aqt import AnkiQt, gui_hooks @@ -1109,14 +1115,14 @@ QTableView {{ gridline-color: {grid} }} item = SidebarItem( tr(TR.BROWSING_WHOLE_COLLECTION), ":/icons/collection.svg", - self._filterFunc(name=NamedFilter.WHOLE_COLLECTION), + self._named_filter(NamedFilter.WHOLE_COLLECTION), item_type=SidebarItemType.COLLECTION, ) root.addChild(item) item = SidebarItem( tr(TR.BROWSING_CURRENT_DECK), ":/icons/deck.svg", - self._filterFunc(name=NamedFilter.CURRENT_DECK), + self._named_filter(NamedFilter.CURRENT_DECK), item_type=SidebarItemType.CURRENT_DECK, ) root.addChild(item) @@ -1128,7 +1134,7 @@ QTableView {{ gridline-color: {grid} }} item = SidebarItem( name, ":/icons/heart.svg", - self._filterFunc(filt), + self._saved_filter(filt), item_type=SidebarItemType.FILTER, ) root.addChild(item) @@ -1139,7 +1145,7 @@ QTableView {{ gridline-color: {grid} }} item = SidebarItem( t, ":/icons/tag.svg", - self._filterFunc(tag=t), + self._tag_filter(t), item_type=SidebarItemType.TAG, ) root.addChild(item) @@ -1150,10 +1156,6 @@ QTableView {{ gridline-color: {grid} }} def fillGroups(root, nodes: Sequence[DeckTreeNode], head=""): for node in nodes: - def set_filter(): - full_name = head + node.name # pylint: disable=cell-var-from-loop - return lambda: self.setFilter(deck=full_name) - def toggle_expand(): did = node.deck_id # pylint: disable=cell-var-from-loop return lambda _: self.mw.col.decks.collapseBrowser(did) @@ -1161,7 +1163,7 @@ QTableView {{ gridline-color: {grid} }} item = SidebarItem( node.name, ":/icons/deck.svg", - set_filter(), + self._deck_filter(head + node.name), toggle_expand(), not node.collapsed, item_type=SidebarItemType.DECK, @@ -1179,7 +1181,7 @@ QTableView {{ gridline-color: {grid} }} item = SidebarItem( m.name, ":/icons/notetype.svg", - self._filterFunc(note=m.name), + self._note_filter(m.name), item_type=SidebarItemType.NOTETYPE, ) root.addChild(item) @@ -1207,11 +1209,10 @@ QTableView {{ gridline-color: {grid} }} ml.popupOver(self.form.filter) - def setFilter(self, *search_strings, **filters): + def setFilter(self, *searches): try: - filter_searches = self.col.backend.filters_to_searches(filters) search = self.col.backend.concatenate_searches( - sep=ConcatSeparator.AND, searches=list(search_strings) + filter_searches + sep=ConcatSeparator.AND, searches=searches ) mods = self.mw.app.keyboardModifiers() if mods & Qt.AltModifier: @@ -1247,11 +1248,37 @@ QTableView {{ gridline-color: {grid} }} ml.addSeparator() else: label, filter_name = row - ml.addItem(label, self._filterFunc(name=filter_name)) + ml.addItem(label, self._named_filter(filter_name)) return ml - def _filterFunc(self, *args, **kwargs): - return lambda: self.setFilter(*args, **kwargs) + def _named_filter(self, name: Any) -> Callable: + return lambda: self.setFilter( + self.col.backend.filter_to_search(FilterToSearchIn(name=name)) + ) + + def _tag_filter(self, tag: str) -> Callable: + return lambda: self.setFilter( + self.col.backend.filter_to_search(FilterToSearchIn(tag=tag)) + ) + + def _deck_filter(self, deck: str) -> Callable: + return lambda: self.setFilter( + self.col.backend.filter_to_search(FilterToSearchIn(deck=deck)) + ) + + def _note_filter(self, note: str) -> Callable: + return lambda: self.setFilter( + self.col.backend.filter_to_search(FilterToSearchIn(note=note)) + ) + + def _template_filter(self, note: str, template: int) -> Callable: + return lambda: self.setFilter( + self.col.backend.filter_to_search(FilterToSearchIn(note=note)), + self.col.backend.filter_to_search(FilterToSearchIn(template=template)), + ) + + def _saved_filter(self, saved: str) -> Callable: + return lambda: self.setFilter(saved) def _commonFilters(self): return self._simpleFilters( @@ -1309,7 +1336,7 @@ QTableView {{ gridline-color: {grid} }} tagList = MenuList() for t in sorted(self.col.tags.all(), key=lambda s: s.lower()): - tagList.addItem(self._escapeMenuItem(t), self._filterFunc(tag=t)) + tagList.addItem(self._escapeMenuItem(t), self._tag_filter(t)) m.addChild(tagList.chunked()) return m @@ -1322,11 +1349,11 @@ QTableView {{ gridline-color: {grid} }} fullname = parent_prefix + node.name if node.children: subm = parent.addMenu(escaped_name) - subm.addItem(tr(TR.ACTIONS_FILTER), self._filterFunc(deck=fullname)) + subm.addItem(tr(TR.ACTIONS_FILTER), self._deck_filter(fullname)) subm.addSeparator() addDecks(subm, node.children, fullname + "::") else: - parent.addItem(escaped_name, self._filterFunc(deck=fullname)) + parent.addItem(escaped_name, self._deck_filter(fullname)) alldecks = self.col.decks.deck_tree() ml = MenuList() @@ -1348,12 +1375,12 @@ QTableView {{ gridline-color: {grid} }} escaped_nt_name = self._escapeMenuItem(nt["name"]) # no sub menu if it's a single template if len(nt["tmpls"]) == 1: - noteTypes.addItem(escaped_nt_name, self._filterFunc(note=nt["name"])) + noteTypes.addItem(escaped_nt_name, self._note_filter(nt["name"])) else: subm = noteTypes.addMenu(escaped_nt_name) subm.addItem( - tr(TR.BROWSING_ALL_CARD_TYPES), self._filterFunc(note=nt["name"]) + tr(TR.BROWSING_ALL_CARD_TYPES), self._note_filter(nt["name"]) ) subm.addSeparator() @@ -1366,9 +1393,7 @@ QTableView {{ gridline-color: {grid} }} num=c + 1, name=self._escapeMenuItem(tmpl["name"]), ) - subm.addItem( - name, self._filterFunc(note=nt["name"], template=c + 1) - ) + subm.addItem(name, self._template_filter(nt["name"], c + 1)) m.addChild(noteTypes.chunked()) return m @@ -1395,7 +1420,7 @@ QTableView {{ gridline-color: {grid} }} ml.addSeparator() for name, filt in sorted(saved.items()): - ml.addItem(self._escapeMenuItem(name), self._filterFunc(filt)) + ml.addItem(self._escapeMenuItem(name), self._saved_filter(filt)) return ml From 0629f80aeb1845cb84faa7a07a658770f9e6bbed Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sat, 9 Jan 2021 20:09:47 +0100 Subject: [PATCH 5/5] Format backend.proto --- rslib/backend.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rslib/backend.proto b/rslib/backend.proto index 10597806f..d4e3df1e1 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -84,7 +84,7 @@ service BackendService { // searching - rpc FilterToSearch (FilterToSearchIn) returns (String); + rpc FilterToSearch(FilterToSearchIn) returns (String); rpc NormalizeSearch(String) returns (String); rpc SearchCards(SearchCardsIn) returns (SearchCardsOut); rpc SearchNotes(SearchNotesIn) returns (SearchNotesOut);