mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
commit
152eaa1798
6 changed files with 63 additions and 29 deletions
|
@ -46,6 +46,7 @@ TagUsnTuple = pb.TagUsnTuple
|
||||||
NoteType = pb.NoteType
|
NoteType = pb.NoteType
|
||||||
DeckTreeNode = pb.DeckTreeNode
|
DeckTreeNode = pb.DeckTreeNode
|
||||||
StockNoteType = pb.StockNoteType
|
StockNoteType = pb.StockNoteType
|
||||||
|
ConcatSeparator = pb.ConcatenateSearchesIn.Separator
|
||||||
SyncAuth = pb.SyncAuth
|
SyncAuth = pb.SyncAuth
|
||||||
SyncOutput = pb.SyncCollectionOut
|
SyncOutput = pb.SyncCollectionOut
|
||||||
SyncStatus = pb.SyncStatusOut
|
SyncStatus = pb.SyncStatusOut
|
||||||
|
|
|
@ -53,6 +53,10 @@ fn want_release_gil(method: u32) -> bool {
|
||||||
| BackendMethod::LatestProgress
|
| BackendMethod::LatestProgress
|
||||||
| BackendMethod::SetWantsAbort
|
| BackendMethod::SetWantsAbort
|
||||||
| BackendMethod::I18nResources
|
| BackendMethod::I18nResources
|
||||||
|
| BackendMethod::NormalizeSearch
|
||||||
|
| BackendMethod::NegateSearch
|
||||||
|
| BackendMethod::ConcatenateSearches
|
||||||
|
| BackendMethod::ReplaceSearchTerm
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|
|
@ -21,7 +21,7 @@ from anki.consts import *
|
||||||
from anki.lang import without_unicode_isolation
|
from anki.lang import without_unicode_isolation
|
||||||
from anki.models import NoteType
|
from anki.models import NoteType
|
||||||
from anki.notes import Note
|
from anki.notes import Note
|
||||||
from anki.rsbackend import DeckTreeNode, InvalidInput, pb
|
from anki.rsbackend import ConcatSeparator, DeckTreeNode, InvalidInput
|
||||||
from anki.stats import CardStats
|
from anki.stats import CardStats
|
||||||
from anki.utils import htmlToTextLine, ids2str, isMac, isWin
|
from anki.utils import htmlToTextLine, ids2str, isMac, isWin
|
||||||
from aqt import AnkiQt, gui_hooks
|
from aqt import AnkiQt, gui_hooks
|
||||||
|
@ -1236,13 +1236,13 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
elif mods & Qt.ControlModifier:
|
elif mods & Qt.ControlModifier:
|
||||||
txt = self.col.backend.concatenate_searches(
|
txt = self.col.backend.concatenate_searches(
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
sep=pb.ConcatenateSearchesIn.Separator.AND,
|
sep=ConcatSeparator.AND,
|
||||||
searches=[cur, txt],
|
searches=[cur, txt],
|
||||||
)
|
)
|
||||||
elif mods & Qt.ShiftModifier:
|
elif mods & Qt.ShiftModifier:
|
||||||
txt = self.col.backend.concatenate_searches(
|
txt = self.col.backend.concatenate_searches(
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
sep=pb.ConcatenateSearchesIn.Separator.OR,
|
sep=ConcatSeparator.OR,
|
||||||
searches=[cur, txt],
|
searches=[cur, txt],
|
||||||
)
|
)
|
||||||
except InvalidInput as e:
|
except InvalidInput as e:
|
||||||
|
@ -1413,14 +1413,20 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
return ml
|
return ml
|
||||||
|
|
||||||
def _onSaveFilter(self) -> None:
|
def _onSaveFilter(self) -> None:
|
||||||
name = getOnlyText(tr(TR.BROWSING_PLEASE_GIVE_YOUR_FILTER_A_NAME))
|
try:
|
||||||
if not name:
|
filt = self.col.backend.normalize_search(
|
||||||
return
|
self.form.searchEdit.lineEdit().text()
|
||||||
filt = self.form.searchEdit.lineEdit().text()
|
)
|
||||||
conf = self.col.get_config("savedFilters")
|
except InvalidInput as e:
|
||||||
conf[name] = filt
|
showWarning(str(e))
|
||||||
self.col.set_config("savedFilters", conf)
|
else:
|
||||||
self.maybeRefreshSidebar()
|
name = getOnlyText(tr(TR.BROWSING_PLEASE_GIVE_YOUR_FILTER_A_NAME))
|
||||||
|
if not name:
|
||||||
|
return
|
||||||
|
conf = self.col.get_config("savedFilters")
|
||||||
|
conf[name] = filt
|
||||||
|
self.col.set_config("savedFilters", conf)
|
||||||
|
self.maybeRefreshSidebar()
|
||||||
|
|
||||||
def _onRemoveFilter(self):
|
def _onRemoveFilter(self):
|
||||||
name = self._currentFilterIsSaved()
|
name = self._currentFilterIsSaved()
|
||||||
|
@ -1433,7 +1439,15 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
# returns name if found
|
# returns name if found
|
||||||
def _currentFilterIsSaved(self):
|
def _currentFilterIsSaved(self):
|
||||||
filt = self.form.searchEdit.lineEdit().text()
|
filt = self.form.searchEdit.lineEdit().text()
|
||||||
|
try:
|
||||||
|
filt = self.col.backend.normalize_search(filt)
|
||||||
|
except InvalidInput:
|
||||||
|
pass
|
||||||
for k, v in self.col.get_config("savedFilters").items():
|
for k, v in self.col.get_config("savedFilters").items():
|
||||||
|
try:
|
||||||
|
v = self.col.backend.normalize_search(v)
|
||||||
|
except InvalidInput:
|
||||||
|
pass
|
||||||
if filt == v:
|
if filt == v:
|
||||||
return k
|
return k
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -33,7 +33,8 @@ use crate::{
|
||||||
sched::cutoff::local_minutes_west_for_stamp,
|
sched::cutoff::local_minutes_west_for_stamp,
|
||||||
sched::timespan::{answer_button_time, time_span},
|
sched::timespan::{answer_button_time, time_span},
|
||||||
search::{
|
search::{
|
||||||
concatenate_searches, negate_search, normalize_search, replace_search_term, SortMode,
|
concatenate_searches, negate_search, normalize_search, replace_search_term, BoolSeparator,
|
||||||
|
SortMode,
|
||||||
},
|
},
|
||||||
stats::studied_today,
|
stats::studied_today,
|
||||||
sync::{
|
sync::{
|
||||||
|
@ -273,6 +274,17 @@ impl From<pb::DeckConfigId> for DeckConfID {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<i32> for BoolSeparator {
|
||||||
|
fn from(sep: i32) -> Self {
|
||||||
|
use pb::concatenate_searches_in::Separator;
|
||||||
|
match Separator::from_i32(sep) {
|
||||||
|
Some(Separator::And) => BoolSeparator::And,
|
||||||
|
Some(Separator::Or) => BoolSeparator::Or,
|
||||||
|
None => BoolSeparator::And,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl BackendService for Backend {
|
impl BackendService for Backend {
|
||||||
fn latest_progress(&self, _input: Empty) -> BackendResult<pb::Progress> {
|
fn latest_progress(&self, _input: Empty) -> BackendResult<pb::Progress> {
|
||||||
let progress = self.progress_state.lock().unwrap().last_progress;
|
let progress = self.progress_state.lock().unwrap().last_progress;
|
||||||
|
@ -437,7 +449,7 @@ impl BackendService for Backend {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn concatenate_searches(&self, input: pb::ConcatenateSearchesIn) -> Result<pb::String> {
|
fn concatenate_searches(&self, input: pb::ConcatenateSearchesIn) -> Result<pb::String> {
|
||||||
Ok(concatenate_searches(input.sep, &input.searches)?.into())
|
Ok(concatenate_searches(input.sep.into(), &input.searches)?.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_search_term(&self, input: pb::ReplaceSearchTermIn) -> Result<pb::String> {
|
fn replace_search_term(&self, input: pb::ReplaceSearchTermIn) -> Result<pb::String> {
|
||||||
|
|
|
@ -5,4 +5,6 @@ mod sqlwriter;
|
||||||
mod writer;
|
mod writer;
|
||||||
|
|
||||||
pub use cards::SortMode;
|
pub use cards::SortMode;
|
||||||
pub use writer::{concatenate_searches, negate_search, normalize_search, replace_search_term};
|
pub use writer::{
|
||||||
|
concatenate_searches, negate_search, normalize_search, replace_search_term, BoolSeparator,
|
||||||
|
};
|
||||||
|
|
|
@ -2,15 +2,20 @@
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend_proto::concatenate_searches_in::Separator,
|
|
||||||
decks::DeckID as DeckIDType,
|
decks::DeckID as DeckIDType,
|
||||||
err::{AnkiError, Result},
|
err::Result,
|
||||||
notetype::NoteTypeID as NoteTypeIDType,
|
notetype::NoteTypeID as NoteTypeIDType,
|
||||||
search::parser::{parse, Node, PropertyKind, SearchNode, StateKind, TemplateKind},
|
search::parser::{parse, Node, PropertyKind, SearchNode, StateKind, TemplateKind},
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum BoolSeparator {
|
||||||
|
And,
|
||||||
|
Or,
|
||||||
|
}
|
||||||
|
|
||||||
/// Take an Anki-style search string and convert it into an equivalent
|
/// Take an Anki-style search string and convert it into an equivalent
|
||||||
/// search string with normalized syntax.
|
/// search string with normalized syntax.
|
||||||
pub fn normalize_search(input: &str) -> Result<String> {
|
pub fn normalize_search(input: &str) -> Result<String> {
|
||||||
|
@ -38,11 +43,10 @@ pub fn negate_search(input: &str) -> Result<String> {
|
||||||
/// Take arbitrary Anki-style search strings and return their concatenation where they
|
/// Take arbitrary Anki-style search strings and return their concatenation where they
|
||||||
/// are separated by the provided boolean operator.
|
/// are separated by the provided boolean operator.
|
||||||
/// Empty searches (whole collection) are left out.
|
/// Empty searches (whole collection) are left out.
|
||||||
pub fn concatenate_searches(sep: i32, searches: &[String]) -> Result<String> {
|
pub fn concatenate_searches(sep: BoolSeparator, searches: &[String]) -> Result<String> {
|
||||||
let bool_node = vec![match Separator::from_i32(sep) {
|
let bool_node = vec![match sep {
|
||||||
Some(Separator::Or) => Node::Or,
|
BoolSeparator::And => Node::And,
|
||||||
Some(Separator::And) => Node::And,
|
BoolSeparator::Or => Node::Or,
|
||||||
None => return Err(AnkiError::SearchError(None)),
|
|
||||||
}];
|
}];
|
||||||
Ok(write_nodes(
|
Ok(write_nodes(
|
||||||
searches
|
searches
|
||||||
|
@ -221,25 +225,22 @@ mod test {
|
||||||
fn concatenating() -> Result<()> {
|
fn concatenating() -> Result<()> {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
r#""foo" AND "bar""#,
|
r#""foo" AND "bar""#,
|
||||||
concatenate_searches(
|
concatenate_searches(BoolSeparator::And, &["foo".to_string(), "bar".to_string()])
|
||||||
Separator::And as i32,
|
.unwrap()
|
||||||
&["foo".to_string(), "bar".to_string()]
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
r#""foo" OR "bar""#,
|
r#""foo" OR "bar""#,
|
||||||
concatenate_searches(
|
concatenate_searches(
|
||||||
Separator::Or as i32,
|
BoolSeparator::Or,
|
||||||
&["foo".to_string(), "".to_string(), "bar".to_string()]
|
&["foo".to_string(), "".to_string(), "bar".to_string()]
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"",
|
"",
|
||||||
concatenate_searches(Separator::Or as i32, &["".to_string()]).unwrap()
|
concatenate_searches(BoolSeparator::Or, &["".to_string()]).unwrap()
|
||||||
);
|
);
|
||||||
assert_eq!("", concatenate_searches(Separator::Or as i32, &[]).unwrap());
|
assert_eq!("", concatenate_searches(BoolSeparator::Or, &[]).unwrap());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue