mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-17 04:58:41 -04:00
feat(ui): Implement the “fuzzy match room name” filter.
WIP
This commit is contained in:
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
mod fuzzy_match_room_name;
|
||||
|
||||
pub use fuzzy_match_room_name::new_filter as new_filter_fuzzy_match_room_name;
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user