diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index c3a27a76f..e75e5a0eb 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -171,7 +171,7 @@ class DataModel(QAbstractTableModel): def search(self, txt: str) -> None: self.beginReset() self.cards = [] - invalid = False + error_message: Optional[str] = None try: ctx = SearchContext(search=txt) gui_hooks.browser_will_search(ctx) @@ -180,13 +180,12 @@ class DataModel(QAbstractTableModel): gui_hooks.browser_did_search(ctx) self.cards = ctx.card_ids except Exception as e: - print("search failed:", e) - invalid = True + error_message = str(e) finally: self.endReset() - if invalid: - showWarning(_("Invalid search - please check for typing mistakes.")) + if error_message: + showWarning(error_message) def reset(self): self.beginReset() diff --git a/rslib/ftl/search.ftl b/rslib/ftl/search.ftl new file mode 100644 index 000000000..269080a98 --- /dev/null +++ b/rslib/ftl/search.ftl @@ -0,0 +1 @@ +search-invalid = Invalid search - please check for typing mistakes. diff --git a/rslib/src/backend/mod.rs b/rslib/src/backend/mod.rs index 4065e7a45..6ab1c0068 100644 --- a/rslib/src/backend/mod.rs +++ b/rslib/src/backend/mod.rs @@ -145,6 +145,7 @@ fn anki_error_to_proto_error(err: AnkiError, i18n: &I18n) -> pb::BackendError { AnkiError::NotFound => V::NotFoundError(Empty {}), AnkiError::Existing => V::Exists(Empty {}), AnkiError::DeckIsFiltered => V::DeckIsFiltered(Empty {}), + AnkiError::SearchError(_) => V::InvalidInput(pb::Empty {}), }; pb::BackendError { diff --git a/rslib/src/err.rs b/rslib/src/err.rs index e146db1c1..5e07264ab 100644 --- a/rslib/src/err.rs +++ b/rslib/src/err.rs @@ -54,6 +54,9 @@ pub enum AnkiError { #[fail(display = "Unable to place item in/under a filtered deck.")] DeckIsFiltered, + + #[fail(display = "Invalid search.")] + SearchError(Option), } // error helpers @@ -109,6 +112,13 @@ impl AnkiError { DBErrorKind::Corrupt => info.clone(), _ => format!("{:?}", self), }, + AnkiError::SearchError(details) => { + if let Some(details) = details { + details.to_owned() + } else { + i18n.tr(TR::SearchInvalid).to_string() + } + } _ => format!("{:?}", self), } } @@ -138,6 +148,11 @@ impl From for AnkiError { impl From for AnkiError { fn from(err: rusqlite::Error) -> Self { + if let rusqlite::Error::SqliteFailure(_error, Some(reason)) = &err { + if reason.contains("regex parse error") { + return AnkiError::SearchError(Some(reason.to_owned())); + } + } AnkiError::DBError { info: format!("{:?}", err), kind: DBErrorKind::Other, diff --git a/rslib/src/search/parser.rs b/rslib/src/search/parser.rs index 5a2ba7ac5..c5e8cd2d9 100644 --- a/rslib/src/search/parser.rs +++ b/rslib/src/search/parser.rs @@ -119,8 +119,8 @@ pub(super) fn parse(input: &str) -> Result> { return Ok(vec![Node::Search(SearchNode::WholeCollection)]); } - let (_, nodes) = all_consuming(group_inner)(input) - .map_err(|_e| AnkiError::invalid_input("unable to parse search"))?; + let (_, nodes) = + all_consuming(group_inner)(input).map_err(|_e| AnkiError::SearchError(None))?; Ok(nodes) }