From 63ca82c66cbb40125ee24c40a252a5f0c2fee59e Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 26 Jul 2023 18:22:14 +0200 Subject: [PATCH 1/4] =?UTF-8?q?feat(ui):=20Implement=20the=20=E2=80=9Cfuzz?= =?UTF-8?q?y=20match=20room=20name=E2=80=9D=20filter.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WIP --- Cargo.lock | 10 +++ crates/matrix-sdk-ui/Cargo.toml | 1 + .../filters/fuzzy_match_room_name.rs | 78 +++++++++++++++++++ .../src/room_list_service/filters/mod.rs | 3 + .../src/room_list_service/mod.rs | 1 + .../tests/integration/room_list_service.rs | 69 ++++++++-------- .../matrix-sdk/src/sliding_sync/list/mod.rs | 1 + 7 files changed, 127 insertions(+), 36 deletions(-) create mode 100644 crates/matrix-sdk-ui/src/room_list_service/filters/fuzzy_match_room_name.rs create mode 100644 crates/matrix-sdk-ui/src/room_list_service/filters/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 7eaf1bef765..17a58e4708a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1748,6 +1748,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -2911,6 +2920,7 @@ dependencies = [ "eyeball-im-util", "futures-core", "futures-util", + "fuzzy-matcher", "imbl", "indexmap 2.0.0", "itertools 0.11.0", diff --git a/crates/matrix-sdk-ui/Cargo.toml b/crates/matrix-sdk-ui/Cargo.toml index d99af4b96ea..72ce7edee8d 100644 --- a/crates/matrix-sdk-ui/Cargo.toml +++ b/crates/matrix-sdk-ui/Cargo.toml @@ -25,6 +25,7 @@ eyeball-im = { workspace = true } eyeball-im-util = { workspace = true } futures-core = { workspace = true } futures-util = { workspace = true } +fuzzy-matcher = "0.3.7" imbl = { version = "2.0.0", features = ["serde"] } indexmap = "2.0.0" itertools = { workspace = true } diff --git a/crates/matrix-sdk-ui/src/room_list_service/filters/fuzzy_match_room_name.rs b/crates/matrix-sdk-ui/src/room_list_service/filters/fuzzy_match_room_name.rs new file mode 100644 index 00000000000..b889c93b469 --- /dev/null +++ b/crates/matrix-sdk-ui/src/room_list_service/filters/fuzzy_match_room_name.rs @@ -0,0 +1,78 @@ +pub use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher as _}; +use matrix_sdk::{Client, RoomListEntry}; + +struct FuzzyMatcher { + matcher: SkimMatcherV2, +} + +impl FuzzyMatcher { + fn new() -> Self { + Self { matcher: SkimMatcherV2::default().smart_case().use_cache(true) } + } + + fn fuzzy_match(&self, subject: &str, pattern: &str) -> bool { + self.matcher.fuzzy_match(subject, pattern).is_some() + } +} + +pub fn new_filter( + client: &Client, + pattern: String, +) -> impl Fn(&RoomListEntry) -> bool + Send + Sync + 'static { + let searcher = FuzzyMatcher::new(); + let client = client.clone(); + + move |room_list_entry| -> bool { + let Some(room_id) = room_list_entry.as_room_id() else { return false }; + let Some(room) = client.get_room(room_id) else { return false }; + let Some(room_name) = room.name() else { return false }; + + searcher.fuzzy_match(&room_name, &pattern) + } +} + +#[cfg(test)] +mod tests { + use std::ops::Not; + + use super::*; + + #[test] + fn test_literal() { + let matcher = FuzzyMatcher::new(); + + assert!(matcher.fuzzy_match("matrix", "mtx")); + assert!(matcher.fuzzy_match("matrix", "mxt").not()); + } + + #[test] + fn test_ignore_case() { + let matcher = FuzzyMatcher::new(); + + assert!(matcher.fuzzy_match("MaTrIX", "mtx")); + assert!(matcher.fuzzy_match("MaTrIX", "mxt").not()); + } + + #[test] + fn test_smart_case() { + let matcher = FuzzyMatcher::new(); + + assert!(matcher.fuzzy_match("Matrix", "mtx")); + assert!(matcher.fuzzy_match("Matrix", "mtx")); + assert!(matcher.fuzzy_match("MatriX", "Mtx").not()); + } + + // This is not supported yet. + /* + #[test] + fn test_transliteration_and_normalization() { + let matcher = FuzzyMatcher::new(); + + assert!(matcher.fuzzy_match("un bel été", "été")); + assert!(matcher.fuzzy_match("un bel été", "ete")); + assert!(matcher.fuzzy_match("un bel été", "éte")); + assert!(matcher.fuzzy_match("un bel été", "étè").not()); + assert!(matcher.fuzzy_match("Ștefan", "stef")); + } + */ +} diff --git a/crates/matrix-sdk-ui/src/room_list_service/filters/mod.rs b/crates/matrix-sdk-ui/src/room_list_service/filters/mod.rs new file mode 100644 index 00000000000..5b9b55589f1 --- /dev/null +++ b/crates/matrix-sdk-ui/src/room_list_service/filters/mod.rs @@ -0,0 +1,3 @@ +mod fuzzy_match_room_name; + +pub use fuzzy_match_room_name::new_filter as new_filter_fuzzy_match_room_name; diff --git a/crates/matrix-sdk-ui/src/room_list_service/mod.rs b/crates/matrix-sdk-ui/src/room_list_service/mod.rs index 26cb980f5aa..58b145cfa03 100644 --- a/crates/matrix-sdk-ui/src/room_list_service/mod.rs +++ b/crates/matrix-sdk-ui/src/room_list_service/mod.rs @@ -62,6 +62,7 @@ //! [`RoomListService::state`] provides a way to get a stream of the state //! machine's state, which can be pretty helpful for the client app. +pub mod filters; mod room; mod room_list; mod state; diff --git a/crates/matrix-sdk-ui/tests/integration/room_list_service.rs b/crates/matrix-sdk-ui/tests/integration/room_list_service.rs index 737ffde9943..1aaa73de3fa 100644 --- a/crates/matrix-sdk-ui/tests/integration/room_list_service.rs +++ b/crates/matrix-sdk-ui/tests/integration/room_list_service.rs @@ -4,12 +4,13 @@ use assert_matches::assert_matches; use eyeball_im::VectorDiff; use futures_util::{pin_mut, FutureExt, StreamExt}; use imbl::vector; +use matrix_sdk::Client; use matrix_sdk_test::async_test; use matrix_sdk_ui::{ room_list_service::{ - Error, Input, InputResult, RoomListEntry, RoomListLoadingState, State, - ALL_ROOMS_LIST_NAME as ALL_ROOMS, INVITES_LIST_NAME as INVITES, - VISIBLE_ROOMS_LIST_NAME as VISIBLE_ROOMS, + filters::new_filter_fuzzy_match_room_name, Error, Input, InputResult, RoomListEntry, + RoomListLoadingState, State, ALL_ROOMS_LIST_NAME as ALL_ROOMS, + INVITES_LIST_NAME as INVITES, VISIBLE_ROOMS_LIST_NAME as VISIBLE_ROOMS, }, timeline::{TimelineItemKind, VirtualTimelineItem}, RoomListService, @@ -30,11 +31,11 @@ use crate::{ timeline::sliding_sync::{assert_timeline_stream, timeline_event}, }; -async fn new_room_list_service() -> Result<(MockServer, RoomListService), Error> { +async fn new_room_list_service() -> Result<(Client, MockServer, RoomListService), Error> { let (client, server) = logged_in_client().await; - let room_list = RoomListService::new(client).await?; + let room_list = RoomListService::new(client.clone()).await?; - Ok((server, room_list)) + Ok((client, server, room_list)) } // Same macro as in the main, with additional checking that the state @@ -215,7 +216,7 @@ macro_rules! assert_entries_stream { #[async_test] async fn test_sync_all_states() -> Result<(), Error> { - let (server, room_list) = new_room_list_service().await?; + let (_, server, room_list) = new_room_list_service().await?; let sync = room_list.sync(); pin_mut!(sync); @@ -471,7 +472,7 @@ async fn test_sync_all_states() -> Result<(), Error> { #[async_test] async fn test_sync_resumes_from_previous_state() -> Result<(), Error> { - let (server, room_list) = new_room_list_service().await?; + let (_, server, room_list) = new_room_list_service().await?; // Start a sync, and drop it at the end of the block. { @@ -590,7 +591,7 @@ async fn test_sync_resumes_from_previous_state() -> Result<(), Error> { #[async_test] async fn test_sync_resumes_from_error() -> Result<(), Error> { - let (server, room_list) = new_room_list_service().await?; + let (_, server, room_list) = new_room_list_service().await?; let sync = room_list.sync(); pin_mut!(sync); @@ -891,7 +892,7 @@ async fn test_sync_resumes_from_error() -> Result<(), Error> { #[async_test] async fn test_sync_resumes_from_terminated() -> Result<(), Error> { - let (server, room_list) = new_room_list_service().await?; + let (_, server, room_list) = new_room_list_service().await?; // Let's stop the sync before actually syncing (we never know!). // We get an error, obviously. @@ -1129,7 +1130,7 @@ async fn test_sync_resumes_from_terminated() -> Result<(), Error> { #[async_test] async fn test_loading_states() -> Result<(), Error> { - let (server, room_list) = new_room_list_service().await?; + let (_, server, room_list) = new_room_list_service().await?; let sync = room_list.sync(); pin_mut!(sync); @@ -1237,7 +1238,7 @@ async fn test_loading_states() -> Result<(), Error> { #[async_test] async fn test_entries_stream() -> Result<(), Error> { - let (server, room_list) = new_room_list_service().await?; + let (_, server, room_list) = new_room_list_service().await?; let sync = room_list.sync(); pin_mut!(sync); @@ -1372,7 +1373,7 @@ async fn test_entries_stream() -> Result<(), Error> { #[async_test] async fn test_entries_stream_with_updated_filter() -> Result<(), Error> { - let (server, room_list) = new_room_list_service().await?; + let (client, server, room_list) = new_room_list_service().await?; let sync = room_list.sync(); pin_mut!(sync); @@ -1410,7 +1411,7 @@ async fn test_entries_stream_with_updated_filter() -> Result<(), Error> { }, "rooms": { "!r0:bar.org": { - "name": "Room #0", + "name": "Matrix Foobar", "initial": true, "timeline": [], }, @@ -1426,12 +1427,8 @@ async fn test_entries_stream_with_updated_filter() -> Result<(), Error> { pending; }; - let (previous_entries, entries_stream) = all_rooms.entries_filtered(|room_list_entry| { - matches!( - room_list_entry.as_room_id(), - Some(room_id) if room_id.server_name() == "bar.org" - ) - }); + let (previous_entries, entries_stream) = + all_rooms.entries_filtered(new_filter_fuzzy_match_room_name(&client, "mat ba".to_string())); pin_mut!(entries_stream); sync_then_assert_request_and_fake_response! { @@ -1461,8 +1458,8 @@ async fn test_entries_stream_with_updated_filter() -> Result<(), Error> { "range": [1, 4], "room_ids": [ "!r1:bar.org", - "!r2:qux.org", - "!r3:qux.org", + "!r2:bar.org", + "!r3:bar.org", "!r4:bar.org", ], }, @@ -1477,22 +1474,22 @@ async fn test_entries_stream_with_updated_filter() -> Result<(), Error> { }, "rooms": { "!r1:bar.org": { - "name": "Room #1", + "name": "Matrix Bar", "initial": true, "timeline": [], }, - "!r2:qux.org": { - "name": "Room #2", + "!r2:bar.org": { + "name": "Hello", "initial": true, "timeline": [], }, - "!r3:qux.org": { - "name": "Room #3", + "!r3:bar.org": { + "name": "Matrix Qux", "initial": true, "timeline": [], }, "!r4:bar.org": { - "name": "Room #4", + "name": "Matrix Baz", "initial": true, "timeline": [], }, @@ -1513,7 +1510,7 @@ async fn test_entries_stream_with_updated_filter() -> Result<(), Error> { #[async_test] async fn test_invites_stream() -> Result<(), Error> { - let (server, room_list) = new_room_list_service().await?; + let (_, server, room_list) = new_room_list_service().await?; let sync = room_list.sync(); pin_mut!(sync); @@ -1666,7 +1663,7 @@ async fn test_invites_stream() -> Result<(), Error> { #[async_test] async fn test_room() -> Result<(), Error> { - let (server, room_list) = new_room_list_service().await?; + let (_, server, room_list) = new_room_list_service().await?; let sync = room_list.sync(); pin_mut!(sync); @@ -1749,7 +1746,7 @@ async fn test_room() -> Result<(), Error> { #[async_test] async fn test_room_not_found() -> Result<(), Error> { - let (_server, room_list) = new_room_list_service().await?; + let (_, _, room_list) = new_room_list_service().await?; let room_id = room_id!("!foo:bar.org"); @@ -1765,7 +1762,7 @@ async fn test_room_not_found() -> Result<(), Error> { #[async_test] async fn test_room_subscription() -> Result<(), Error> { - let (server, room_list) = new_room_list_service().await?; + let (_, server, room_list) = new_room_list_service().await?; let sync = room_list.sync(); pin_mut!(sync); @@ -1886,7 +1883,7 @@ async fn test_room_subscription() -> Result<(), Error> { #[async_test] async fn test_room_unread_notifications() -> Result<(), Error> { - let (server, room_list) = new_room_list_service().await?; + let (_, server, room_list) = new_room_list_service().await?; let sync = room_list.sync(); pin_mut!(sync); @@ -1973,7 +1970,7 @@ async fn test_room_unread_notifications() -> Result<(), Error> { #[async_test] async fn test_room_timeline() -> Result<(), Error> { - let (server, room_list) = new_room_list_service().await?; + let (_, server, room_list) = new_room_list_service().await?; let sync = room_list.sync(); pin_mut!(sync); @@ -2057,7 +2054,7 @@ async fn test_room_timeline() -> Result<(), Error> { #[async_test] async fn test_room_latest_event() -> Result<(), Error> { - let (server, room_list) = new_room_list_service().await?; + let (_, server, room_list) = new_room_list_service().await?; let sync = room_list.sync(); pin_mut!(sync); @@ -2148,7 +2145,7 @@ async fn test_room_latest_event() -> Result<(), Error> { #[async_test] async fn test_input_viewport() -> Result<(), Error> { - let (server, room_list) = new_room_list_service().await?; + let (_, server, room_list) = new_room_list_service().await?; let sync = room_list.sync(); pin_mut!(sync); diff --git a/crates/matrix-sdk/src/sliding_sync/list/mod.rs b/crates/matrix-sdk/src/sliding_sync/list/mod.rs index 18a3c68cf0f..c0d8a177962 100644 --- a/crates/matrix-sdk/src/sliding_sync/list/mod.rs +++ b/crates/matrix-sdk/src/sliding_sync/list/mod.rs @@ -373,6 +373,7 @@ impl SlidingSyncListInner { #[instrument(skip(self), fields(name = self.name))] fn request(&self, ranges: Ranges, txn_id: &mut LazyTransactionId) -> v4::SyncRequestList { use ruma::UInt; + let ranges = ranges.into_iter().map(|r| (UInt::from(*r.start()), UInt::from(*r.end()))).collect(); From 3e93bdbc3f19a03cfaee892e4f93371df6936c4a Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 27 Jul 2023 08:33:41 +0200 Subject: [PATCH 2/4] feat(ui): Normallize strings when doing fuzzy matching. --- Cargo.lock | 1 + crates/matrix-sdk-ui/Cargo.toml | 1 + .../filters/fuzzy_match_room_name.rs | 76 +++++++++++++------ .../src/room_list_service/filters/mod.rs | 18 +++++ .../tests/integration/room_list_service.rs | 2 +- 5 files changed, 75 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17a58e4708a..6d6b57fca29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2939,6 +2939,7 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", + "unicode-normalization", "wiremock", ] diff --git a/crates/matrix-sdk-ui/Cargo.toml b/crates/matrix-sdk-ui/Cargo.toml index 72ce7edee8d..8e121bbeac3 100644 --- a/crates/matrix-sdk-ui/Cargo.toml +++ b/crates/matrix-sdk-ui/Cargo.toml @@ -41,6 +41,7 @@ serde_json = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } tracing = { workspace = true, features = ["attributes"] } +unicode-normalization = "0.1.22" [dev-dependencies] anyhow = { workspace = true } diff --git a/crates/matrix-sdk-ui/src/room_list_service/filters/fuzzy_match_room_name.rs b/crates/matrix-sdk-ui/src/room_list_service/filters/fuzzy_match_room_name.rs index b889c93b469..5d1e65c6263 100644 --- a/crates/matrix-sdk-ui/src/room_list_service/filters/fuzzy_match_room_name.rs +++ b/crates/matrix-sdk-ui/src/room_list_service/filters/fuzzy_match_room_name.rs @@ -1,25 +1,38 @@ pub use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher as _}; use matrix_sdk::{Client, RoomListEntry}; +use super::normalize_string; + struct FuzzyMatcher { matcher: SkimMatcherV2, + pattern: Option, } impl FuzzyMatcher { fn new() -> Self { - Self { matcher: SkimMatcherV2::default().smart_case().use_cache(true) } + Self { matcher: SkimMatcherV2::default().smart_case().use_cache(true), pattern: None } } - fn fuzzy_match(&self, subject: &str, pattern: &str) -> bool { - self.matcher.fuzzy_match(subject, pattern).is_some() + fn with_pattern(mut self, pattern: &str) -> Self { + self.pattern = Some(normalize_string(pattern)); + + self + } + + fn fuzzy_match(&self, subject: &str) -> bool { + // No pattern means there is a match. + let Some(pattern) = self.pattern.as_ref() else { return true }; + + self.matcher.fuzzy_match(&normalize_string(subject), pattern).is_some() } } pub fn new_filter( client: &Client, - pattern: String, + pattern: &str, ) -> impl Fn(&RoomListEntry) -> bool + Send + Sync + 'static { - let searcher = FuzzyMatcher::new(); + let searcher = FuzzyMatcher::new().with_pattern(pattern); + let client = client.clone(); move |room_list_entry| -> bool { @@ -27,7 +40,7 @@ pub fn new_filter( let Some(room) = client.get_room(room_id) else { return false }; let Some(room_name) = room.name() else { return false }; - searcher.fuzzy_match(&room_name, &pattern) + searcher.fuzzy_match(&room_name) } } @@ -37,42 +50,61 @@ mod tests { use super::*; + #[test] + fn test_no_pattern() { + let matcher = FuzzyMatcher::new(); + + assert!(matcher.fuzzy_match("hello")); + } + #[test] fn test_literal() { let matcher = FuzzyMatcher::new(); - assert!(matcher.fuzzy_match("matrix", "mtx")); - assert!(matcher.fuzzy_match("matrix", "mxt").not()); + let matcher = matcher.with_pattern("mtx"); + assert!(matcher.fuzzy_match("matrix")); + + let matcher = matcher.with_pattern("mxt"); + assert!(matcher.fuzzy_match("matrix").not()); } #[test] fn test_ignore_case() { let matcher = FuzzyMatcher::new(); - assert!(matcher.fuzzy_match("MaTrIX", "mtx")); - assert!(matcher.fuzzy_match("MaTrIX", "mxt").not()); + let matcher = matcher.with_pattern("mtx"); + assert!(matcher.fuzzy_match("MaTrIX")); + + let matcher = matcher.with_pattern("mxt"); + assert!(matcher.fuzzy_match("MaTrIX").not()); } #[test] fn test_smart_case() { let matcher = FuzzyMatcher::new(); - assert!(matcher.fuzzy_match("Matrix", "mtx")); - assert!(matcher.fuzzy_match("Matrix", "mtx")); - assert!(matcher.fuzzy_match("MatriX", "Mtx").not()); + let matcher = matcher.with_pattern("mtx"); + assert!(matcher.fuzzy_match("Matrix")); + assert!(matcher.fuzzy_match("Matrix")); + + let matcher = matcher.with_pattern("Mtx"); + assert!(matcher.fuzzy_match("MatriX").not()); } - // This is not supported yet. - /* #[test] - fn test_transliteration_and_normalization() { + fn test_normalization() { let matcher = FuzzyMatcher::new(); - assert!(matcher.fuzzy_match("un bel été", "été")); - assert!(matcher.fuzzy_match("un bel été", "ete")); - assert!(matcher.fuzzy_match("un bel été", "éte")); - assert!(matcher.fuzzy_match("un bel été", "étè").not()); - assert!(matcher.fuzzy_match("Ștefan", "stef")); + let matcher = matcher.with_pattern("ubété"); + + // First, assert that the pattern has been normalized. + assert_eq!(matcher.pattern, Some("ubete".to_string())); + + // Second, assert that the subject is normalized too. + assert!(matcher.fuzzy_match("un bel été")); + + // Another concrete test. + let matcher = matcher.with_pattern("stf"); + assert!(matcher.fuzzy_match("Ștefan")); } - */ } diff --git a/crates/matrix-sdk-ui/src/room_list_service/filters/mod.rs b/crates/matrix-sdk-ui/src/room_list_service/filters/mod.rs index 5b9b55589f1..bc4eff73ee5 100644 --- a/crates/matrix-sdk-ui/src/room_list_service/filters/mod.rs +++ b/crates/matrix-sdk-ui/src/room_list_service/filters/mod.rs @@ -1,3 +1,21 @@ mod fuzzy_match_room_name; pub use fuzzy_match_room_name::new_filter as new_filter_fuzzy_match_room_name; +use unicode_normalization::{char::is_combining_mark, UnicodeNormalization}; + +fn normalize_string(str: &str) -> String { + str.nfd().filter(|c| !is_combining_mark(*c)).collect::() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_normalize_string() { + assert_eq!(&normalize_string("abc"), "abc"); + assert_eq!(&normalize_string("Ștefan Été"), "Stefan Ete"); + assert_eq!(&normalize_string("Ç ṩ ḋ Å"), "C s d A"); + assert_eq!(&normalize_string("هند"), "هند"); + } +} diff --git a/crates/matrix-sdk-ui/tests/integration/room_list_service.rs b/crates/matrix-sdk-ui/tests/integration/room_list_service.rs index 1aaa73de3fa..cdf76cbd53d 100644 --- a/crates/matrix-sdk-ui/tests/integration/room_list_service.rs +++ b/crates/matrix-sdk-ui/tests/integration/room_list_service.rs @@ -1428,7 +1428,7 @@ async fn test_entries_stream_with_updated_filter() -> Result<(), Error> { }; let (previous_entries, entries_stream) = - all_rooms.entries_filtered(new_filter_fuzzy_match_room_name(&client, "mat ba".to_string())); + all_rooms.entries_filtered(new_filter_fuzzy_match_room_name(&client, "mat ba")); pin_mut!(entries_stream); sync_then_assert_request_and_fake_response! { From d1d6bcdcab8b9610c862246cb6c71ff92b47ba6f Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 27 Jul 2023 10:00:28 +0200 Subject: [PATCH 3/4] doc(ui): Add missing documentation. --- .../src/room_list_service/filters/fuzzy_match_room_name.rs | 4 ++++ crates/matrix-sdk-ui/src/room_list_service/filters/mod.rs | 3 +++ 2 files changed, 7 insertions(+) diff --git a/crates/matrix-sdk-ui/src/room_list_service/filters/fuzzy_match_room_name.rs b/crates/matrix-sdk-ui/src/room_list_service/filters/fuzzy_match_room_name.rs index 5d1e65c6263..39752ad4aec 100644 --- a/crates/matrix-sdk-ui/src/room_list_service/filters/fuzzy_match_room_name.rs +++ b/crates/matrix-sdk-ui/src/room_list_service/filters/fuzzy_match_room_name.rs @@ -27,6 +27,10 @@ impl FuzzyMatcher { } } +/// Create a new filter that will fuzzy match a pattern on room names. +/// +/// Rooms are fetched from the `Client`. The pattern and the room names are +/// normalized with `normalize_string`. pub fn new_filter( client: &Client, pattern: &str, diff --git a/crates/matrix-sdk-ui/src/room_list_service/filters/mod.rs b/crates/matrix-sdk-ui/src/room_list_service/filters/mod.rs index bc4eff73ee5..4b7b39ff835 100644 --- a/crates/matrix-sdk-ui/src/room_list_service/filters/mod.rs +++ b/crates/matrix-sdk-ui/src/room_list_service/filters/mod.rs @@ -3,6 +3,9 @@ mod fuzzy_match_room_name; pub use fuzzy_match_room_name::new_filter as new_filter_fuzzy_match_room_name; use unicode_normalization::{char::is_combining_mark, UnicodeNormalization}; +/// Normalize a string, i.e. decompose it into NFD (Normalization Form D, i.e. a +/// canonical decomposition, see http://www.unicode.org/reports/tr15/) and +/// filter out the combining marks. fn normalize_string(str: &str) -> String { str.nfd().filter(|c| !is_combining_mark(*c)).collect::() } From 38a8ad0a66e47e5dd12ec8bce3946c862b10c2f8 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 27 Jul 2023 10:07:46 +0200 Subject: [PATCH 4/4] chore(ci): Make `typos` happy. --- .typos.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.typos.toml b/.typos.toml index 0105b4c9616..92c5c4ad195 100644 --- a/.typos.toml +++ b/.typos.toml @@ -19,4 +19,8 @@ WeeChat = "WeeChat" [files] # Our json files contain a bunch of base64 encoded ed25519 keys which aren't # automatically ignored, we ignore them here. -extend-exclude = ["*.json"] +extend-exclude = [ + "*.json", + # We are using some fuzzy match patterns that can be understood as typos confusingly. + "crates/matrix-sdk-ui/tests/integration/room_list_service.rs", +]