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);
+ }
}
}