diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index 5bfd07c93..d19dab499 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -1227,11 +1227,13 @@ QTableView {{ gridline-color: {grid} }} if mods & Qt.ControlModifier and mods & Qt.ShiftModifier: txt = self.col.backend.replace_search_term(search=cur, replacement=txt) elif mods & Qt.ControlModifier: - and_sep = pb.ConcatenateSearchesIn.Separator.AND - txt = self.col.backend.concatenate_searches(sep=and_sep, searches=[cur, txt]) + txt = self.col.backend.concatenate_searches( + sep=pb.ConcatenateSearchesIn.Separator.AND, searches=[cur, txt] + ) elif mods & Qt.ShiftModifier: - or_sep = pb.ConcatenateSearchesIn.Separator.OR - txt = self.col.backend.concatenate_searches(sep=or_sep, searches=[cur, txt]) + txt = self.col.backend.concatenate_searches( + sep=pb.ConcatenateSearchesIn.Separator.OR, searches=[cur, txt] + ) self.form.searchEdit.lineEdit().setText(txt) self.onSearchActivated() diff --git a/rslib/src/search/writer.rs b/rslib/src/search/writer.rs index 773270e2a..613dd539c 100644 --- a/rslib/src/search/writer.rs +++ b/rslib/src/search/writer.rs @@ -4,7 +4,7 @@ use crate::{ backend_proto::concatenate_searches_in::Separator, decks::DeckID as DeckIDType, - err::Result, + err::{AnkiError, Result}, notetype::NoteTypeID as NoteTypeIDType, search::parser::{parse, Node, PropertyKind, SearchNode, StateKind, TemplateKind}, }; @@ -39,15 +39,15 @@ pub fn negate_search(input: &str) -> Result { /// are separated by the provided boolean operator. /// Empty searches (whole collection) are left out. pub fn concatenate_searches(sep: i32, searches: &[String]) -> Result { - let bool_node = vec![if let Some(Separator::Or) = Separator::from_i32(sep) { - Node::Or - } else { - Node::And + let bool_node = vec![match Separator::from_i32(sep) { + Some(Separator::Or) => Node::Or, + Some(Separator::And) => Node::And, + None => return Err(AnkiError::SearchError(None)), }]; Ok(write_nodes( searches .iter() - .map(|s: &String| -> Result> { parse(s) }) + .map(|s| parse(s)) .collect::>>>()? .iter() .filter(|v| v[0] != Node::Search(SearchNode::WholeCollection)) @@ -184,3 +184,88 @@ fn write_property(operator: &str, kind: &PropertyKind) -> String { Ease(f) => format!("\"prop:ease{}{}\"", operator, f), } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn normalizing() -> Result<()> { + assert_eq!(r#""(" AND "-""#, normalize_search(r"\( \-").unwrap()); + assert_eq!(r#""deck::""#, normalize_search(r"deck:\:").unwrap()); + assert_eq!(r#""\*" OR "\:""#, normalize_search(r"\* or \:").unwrap()); + assert_eq!( + r#""field:foo""#, + normalize_search(r#"field:"foo""#).unwrap() + ); + assert_eq!( + r#""prop:ease>1""#, + normalize_search("prop:ease>1.0").unwrap() + ); + + Ok(()) + } + + #[test] + fn negating() -> Result<()> { + assert_eq!(r#"-("foo" AND "bar")"#, negate_search("foo bar").unwrap()); + assert_eq!(r#""foo""#, negate_search("-foo").unwrap()); + assert_eq!(r#"("foo")"#, negate_search("-(foo)").unwrap()); + assert_eq!("", negate_search("").unwrap()); + + Ok(()) + } + + #[test] + fn concatenating() -> Result<()> { + assert_eq!( + r#""foo" AND "bar""#, + concatenate_searches( + Separator::And as i32, + &["foo".to_string(), "bar".to_string()] + ) + .unwrap() + ); + assert_eq!( + r#""foo" OR "bar""#, + concatenate_searches( + Separator::Or as i32, + &["foo".to_string(), "".to_string(), "bar".to_string()] + ) + .unwrap() + ); + assert_eq!( + "", + concatenate_searches(Separator::Or as i32, &["".to_string()]).unwrap() + ); + assert_eq!("", concatenate_searches(Separator::Or as i32, &[]).unwrap()); + + Ok(()) + } + + #[test] + fn replacing() -> Result<()> { + assert_eq!( + r#""deck:foo" AND "bar""#, + replace_search_term("deck:baz bar", "deck:foo").unwrap() + ); + assert_eq!( + r#""tag:baz" OR "tag:baz""#, + replace_search_term("tag:foo Or tag:bar", "tag:baz").unwrap() + ); + assert_eq!( + r#""bar" OR (-"bar" AND "tag:baz")"#, + replace_search_term("foo or (-foo tag:baz)", "bar").unwrap() + ); + assert_eq!( + r#""is:due""#, + replace_search_term("is:due", "-is:new").unwrap() + ); + assert_eq!( + r#""added:1""#, + replace_search_term("added:1", "is:due").unwrap() + ); + + Ok(()) + } +}