feat(ui): Implement the “fuzzy match room name” filter.

WIP
This commit is contained in:
Ivan Enderlin
2023-07-26 18:22:14 +02:00
parent 163d8ca517
commit 63ca82c66c
7 changed files with 127 additions and 36 deletions

10
Cargo.lock generated
View File

@@ -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",

View File

@@ -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 }

View File

@@ -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"));
}
*/
}

View File

@@ -0,0 +1,3 @@
mod fuzzy_match_room_name;
pub use fuzzy_match_room_name::new_filter as new_filter_fuzzy_match_room_name;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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();