diff --git a/ftl/core/preferences.ftl b/ftl/core/preferences.ftl index facd16271..e51c5ac38 100644 --- a/ftl/core/preferences.ftl +++ b/ftl/core/preferences.ftl @@ -42,3 +42,4 @@ preferences-theme-follow-system = Follow System preferences-theme-light = Light preferences-theme-dark = Dark preferences-v3-scheduler = V3 scheduler +preferences-ignore-accents-in-search = Ignore accents in search (slower) diff --git a/proto/anki/config.proto b/proto/anki/config.proto index 40f632664..75a7f98de 100644 --- a/proto/anki/config.proto +++ b/proto/anki/config.proto @@ -40,6 +40,7 @@ message ConfigKey { PASTE_IMAGES_AS_PNG = 15; PASTE_STRIPS_FORMATTING = 16; NORMALIZE_NOTE_TEXT = 17; + IGNORE_ACCENTS_IN_SEARCH = 18; } enum String { SET_DUE_BROWSER = 0; @@ -110,6 +111,7 @@ message Preferences { bool paste_images_as_png = 2; bool paste_strips_formatting = 3; string default_search_text = 4; + bool ignore_accents_in_search = 5; } Scheduling scheduling = 1; diff --git a/qt/aqt/forms/preferences.ui b/qt/aqt/forms/preferences.ui index 9bff83670..0fa21315d 100644 --- a/qt/aqt/forms/preferences.ui +++ b/qt/aqt/forms/preferences.ui @@ -89,6 +89,13 @@ + + + + preferences_ignore_accents_in_search + + + diff --git a/qt/aqt/preferences.py b/qt/aqt/preferences.py index 75f8ff34d..952d026d8 100644 --- a/qt/aqt/preferences.py +++ b/qt/aqt/preferences.py @@ -93,6 +93,7 @@ class Preferences(QDialog): 0 if editing.adding_defaults_to_current_deck else 1 ) form.paste_strips_formatting.setChecked(editing.paste_strips_formatting) + form.ignore_accents_in_search.setChecked(editing.ignore_accents_in_search) form.pastePNG.setChecked(editing.paste_images_as_png) form.default_search_text.setText(editing.default_search_text) @@ -118,6 +119,9 @@ class Preferences(QDialog): editing.paste_images_as_png = self.form.pastePNG.isChecked() editing.paste_strips_formatting = self.form.paste_strips_formatting.isChecked() editing.default_search_text = self.form.default_search_text.text() + editing.ignore_accents_in_search = ( + self.form.ignore_accents_in_search.isChecked() + ) def after_prefs_update(changes: OpChanges) -> None: self.mw.apply_collection_options() diff --git a/rslib/src/backend/config.rs b/rslib/src/backend/config.rs index a992de29d..b4eb8fe4a 100644 --- a/rslib/src/backend/config.rs +++ b/rslib/src/backend/config.rs @@ -31,6 +31,7 @@ impl From for BoolKey { BoolKeyProto::PasteImagesAsPng => BoolKey::PasteImagesAsPng, BoolKeyProto::PasteStripsFormatting => BoolKey::PasteStripsFormatting, BoolKeyProto::NormalizeNoteText => BoolKey::NormalizeNoteText, + BoolKeyProto::IgnoreAccentsInSearch => BoolKey::IgnoreAccentsInSearch, } } } diff --git a/rslib/src/config/bool.rs b/rslib/src/config/bool.rs index 26c69734d..0d5aafc9a 100644 --- a/rslib/src/config/bool.rs +++ b/rslib/src/config/bool.rs @@ -26,6 +26,7 @@ pub enum BoolKey { PasteStripsFormatting, PreviewBothSides, Sched2021, + IgnoreAccentsInSearch, #[strum(to_string = "normalize_note_text")] NormalizeNoteText, diff --git a/rslib/src/preferences.rs b/rslib/src/preferences.rs index 20d4a6b9f..6ac5dd5b4 100644 --- a/rslib/src/preferences.rs +++ b/rslib/src/preferences.rs @@ -132,6 +132,7 @@ impl Collection { paste_images_as_png: self.get_config_bool(BoolKey::PasteImagesAsPng), paste_strips_formatting: self.get_config_bool(BoolKey::PasteStripsFormatting), default_search_text: self.get_config_string(StringKey::DefaultSearchText), + ignore_accents_in_search: self.get_config_bool(BoolKey::IgnoreAccentsInSearch), }) } @@ -144,6 +145,7 @@ impl Collection { self.set_config_bool_inner(BoolKey::PasteImagesAsPng, s.paste_images_as_png)?; self.set_config_bool_inner(BoolKey::PasteStripsFormatting, s.paste_strips_formatting)?; self.set_config_string_inner(StringKey::DefaultSearchText, &s.default_search_text)?; + self.set_config_bool_inner(BoolKey::IgnoreAccentsInSearch, s.ignore_accents_in_search)?; Ok(()) } } diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index 7f3d1821a..c919538c2 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -117,7 +117,14 @@ impl SqlWriter<'_> { use normalize_to_nfc as norm; match node { // note fields related - SearchNode::UnqualifiedText(text) => self.write_unqualified(&self.norm_note(text)), + SearchNode::UnqualifiedText(text) => { + let text = &self.norm_note(text); + if self.col.get_config_bool(BoolKey::IgnoreAccentsInSearch) { + self.write_no_combining(text) + } else { + self.write_unqualified(text) + } + } SearchNode::SingleField { field, text, is_re } => { self.write_field(&norm(field), &self.norm_note(text), *is_re)? } @@ -580,8 +587,20 @@ impl SqlWriter<'_> { self.args.push(format!(r"(?i){}", word)); } + fn write_regex_nc(&mut self, word: &str) { + let word = &without_combining(word); + self.sql + .push_str("coalesce(without_combining(n.flds), n.flds) regexp ?"); + self.args.push(format!(r"(?i){}", word)); + } + fn write_word_boundary(&mut self, word: &str) { - self.write_regex(&format!(r"\b{}\b", to_re(word))); + let re = format!(r"\b{}\b", to_re(word)); + if self.col.get_config_bool(BoolKey::IgnoreAccentsInSearch) { + self.write_regex_nc(&re); + } else { + self.write_regex(&re); + } } }