From 2821807e1454bbc9ebb4aa21e668e3842e41957e Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 21 Aug 2023 10:19:20 +0200 Subject: [PATCH 1/2] feat(ui): Create a new `normalized_match_room_name` filter. This patch creates a new `normalized_match_room_name` filter for `RoomListService`. --- .../src/room_list_service/filters/mod.rs | 2 + .../filters/normalized_match_room_name.rs | 99 +++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 crates/matrix-sdk-ui/src/room_list_service/filters/normalized_match_room_name.rs 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 1f8cb9d09f2..fa7e35517d5 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,8 +1,10 @@ mod all; mod fuzzy_match_room_name; +mod normalized_match_room_name; pub use all::new_filter as new_filter_all; pub use fuzzy_match_room_name::new_filter as new_filter_fuzzy_match_room_name; +pub use normalized_match_room_name::new_filter as new_filter_normalized_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 diff --git a/crates/matrix-sdk-ui/src/room_list_service/filters/normalized_match_room_name.rs b/crates/matrix-sdk-ui/src/room_list_service/filters/normalized_match_room_name.rs new file mode 100644 index 00000000000..b39de829ebd --- /dev/null +++ b/crates/matrix-sdk-ui/src/room_list_service/filters/normalized_match_room_name.rs @@ -0,0 +1,99 @@ +use matrix_sdk::{Client, RoomListEntry}; + +use super::normalize_string; + +struct NormalizedMatcher { + pattern: Option, +} + +impl NormalizedMatcher { + fn new() -> Self { + Self { pattern: None } + } + + fn with_pattern(mut self, pattern: &str) -> Self { + self.pattern = Some(normalize_string(&pattern.to_lowercase())); + + self + } + + fn normalized_match(&self, subject: &str) -> bool { + // No pattern means there is a match. + let Some(pattern) = self.pattern.as_ref() else { return true }; + + let subject = normalize_string(&subject.to_lowercase()); + + subject.contains(pattern) + } +} + +/// Create a new filter that will “normalized” 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) -> impl Fn(&RoomListEntry) -> bool { + let searcher = NormalizedMatcher::new().with_pattern(pattern); + + 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.normalized_match(&room_name) + } +} + +#[cfg(test)] +mod tests { + use std::ops::Not; + + use super::*; + + #[test] + fn test_no_pattern() { + let matcher = NormalizedMatcher::new(); + + assert!(matcher.normalized_match("hello")); + } + + #[test] + fn test_literal() { + let matcher = NormalizedMatcher::new(); + + let matcher = matcher.with_pattern("matrix"); + assert!(matcher.normalized_match("matrix")); + + let matcher = matcher.with_pattern("matrxi"); + assert!(matcher.normalized_match("matrix").not()); + } + + #[test] + fn test_ignore_case() { + let matcher = NormalizedMatcher::new(); + + let matcher = matcher.with_pattern("matrix"); + assert!(matcher.normalized_match("MaTrIX")); + + let matcher = matcher.with_pattern("matrxi"); + assert!(matcher.normalized_match("MaTrIX").not()); + } + + #[test] + fn test_normalization() { + let matcher = NormalizedMatcher::new(); + + let matcher = matcher.with_pattern("un été"); + + // First, assert that the pattern has been normalized. + assert_eq!(matcher.pattern, Some("un ete".to_owned())); + + // Second, assert that the subject is normalized too. + assert!(matcher.normalized_match("un été magnifique")); + + // Another concrete test. + let matcher = matcher.with_pattern("stefan"); + assert!(matcher.normalized_match("Ștefan")); + } +} From 9973d307007558183fca04730c3353440bc616b4 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 21 Aug 2023 10:22:06 +0200 Subject: [PATCH 2/2] feat(ffi): Implement `RoomListEntriesDynamicFilterKind::NormalizedMatchRoomName`. --- bindings/matrix-sdk-ffi/src/room_list.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bindings/matrix-sdk-ffi/src/room_list.rs b/bindings/matrix-sdk-ffi/src/room_list.rs index 52163d779d0..df8caef7e80 100644 --- a/bindings/matrix-sdk-ffi/src/room_list.rs +++ b/bindings/matrix-sdk-ffi/src/room_list.rs @@ -12,7 +12,9 @@ use matrix_sdk::{ }, RoomListEntry as MatrixRoomListEntry, }; -use matrix_sdk_ui::room_list_service::filters::{new_filter_all, new_filter_fuzzy_match_room_name}; +use matrix_sdk_ui::room_list_service::filters::{ + new_filter_all, new_filter_fuzzy_match_room_name, new_filter_normalized_match_room_name, +}; use tokio::sync::RwLock; use crate::{ @@ -316,6 +318,9 @@ impl RoomListEntriesDynamicFilter { match kind { Kind::All => self.inner.set(new_filter_all()), + Kind::NormalizedMatchRoomName { pattern } => { + self.inner.set(new_filter_normalized_match_room_name(&self.client, &pattern)) + } Kind::FuzzyMatchRoomName { pattern } => { self.inner.set(new_filter_fuzzy_match_room_name(&self.client, &pattern)) } @@ -326,6 +331,7 @@ impl RoomListEntriesDynamicFilter { #[derive(uniffi::Enum)] pub enum RoomListEntriesDynamicFilterKind { All, + NormalizedMatchRoomName { pattern: String }, FuzzyMatchRoomName { pattern: String }, }