Skip to content

Commit a58f63a

Browse files
committed
Add logic for searching messages in a room
1 parent 79490dc commit a58f63a

File tree

4 files changed

+173
-12
lines changed

4 files changed

+173
-12
lines changed

src/home/room_screen.rs

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,9 @@ use crate::{
2929
user_profile::{AvatarState, ShowUserProfileAction, UserProfile, UserProfileAndRoomId, UserProfilePaneInfo, UserProfileSlidingPaneRef, UserProfileSlidingPaneWidgetExt},
3030
user_profile_cache,
3131
}, shared::{
32-
avatar::{AvatarRef, AvatarWidgetRefExt},
33-
html_or_plaintext::{HtmlOrPlaintextRef, HtmlOrPlaintextWidgetRefExt},
34-
text_or_image::{TextOrImageRef, TextOrImageWidgetRefExt},
35-
typing_animation::TypingAnimationWidgetExt,
32+
avatar::{AvatarRef, AvatarWidgetRefExt}, html_or_plaintext::{HtmlOrPlaintextRef, HtmlOrPlaintextWidgetRefExt}, search::SearchUpdate, text_or_image::{TextOrImageRef, TextOrImageWidgetRefExt}, typing_animation::TypingAnimationWidgetExt
3633
},
37-
sliding_sync::{get_client, submit_async_request, take_timeline_update_receiver, MatrixRequest}, utils::{self, unix_time_millis_to_datetime, MediaFormatConst}
34+
sliding_sync::{get_client, submit_async_request, take_timeline_update_receiver, MatrixRequest, SEARCH_RESULTS_RECEIVER}, utils::{self, unix_time_millis_to_datetime, MediaFormatConst}
3835
};
3936
use rangemap::RangeSet;
4037

@@ -940,9 +937,71 @@ struct RoomScreen {
940937
#[rust] tl_state: Option<TimelineUiState>,
941938
/// 5 secs timer when scroll ends
942939
#[rust] fully_read_timer: Timer,
940+
/// The current search state for the room.
941+
#[rust] search_state: Option<SearchUiState>,
942+
/// The last search term used in the room.
943+
#[rust] last_search_term: String,
943944
}
944945

945946
impl RoomScreen{
947+
948+
fn start_message_search(&mut self, room_id: OwnedRoomId, search_term: String) {
949+
let (update_sender, update_receiver) = crossbeam_channel::unbounded();
950+
let search_state = SearchUiState {
951+
room_id: room_id.clone(),
952+
fully_paginated: false,
953+
items: Vector::new(),
954+
next_batch: None,
955+
update_receiver,
956+
};
957+
self.search_state = Some(search_state);
958+
959+
submit_async_request(MatrixRequest::SearchRoomMessages {
960+
room_id,
961+
search_term,
962+
next_batch: None,
963+
});
964+
}
965+
966+
fn process_search_updates(&mut self, cx: &mut Cx) {
967+
let Some(search_state) = self.search_state.as_mut() else { return };
968+
969+
let Some(receiver) = SEARCH_RESULTS_RECEIVER.get() else { return };
970+
971+
while let Ok(update) = receiver.try_recv() {
972+
match update {
973+
SearchUpdate::NewResults { room_id, results, next_batch } => {
974+
if search_state.room_id == room_id {
975+
search_state.items.extend(results);
976+
search_state.next_batch = next_batch.clone();
977+
978+
// Stop pagination if no more results
979+
if next_batch.is_none() {
980+
search_state.fully_paginated = true;
981+
}
982+
}
983+
}
984+
}
985+
}
986+
self.redraw(cx);
987+
}
988+
989+
990+
fn paginate_search_results(&mut self) {
991+
let Some(search_state) = self.search_state.as_mut() else { return };
992+
if search_state.fully_paginated {
993+
return;
994+
}
995+
996+
if let Some(next_batch) = search_state.next_batch.clone() {
997+
submit_async_request(MatrixRequest::SearchRoomMessages {
998+
room_id: search_state.room_id.clone(),
999+
search_term: self.last_search_term.clone(),
1000+
next_batch: Some(next_batch),
1001+
});
1002+
}
1003+
}
1004+
9461005
fn send_user_read_receipts_based_on_scroll_pos(
9471006
&mut self,
9481007
cx: &mut Cx,
@@ -1919,6 +1978,14 @@ struct TimelineUiState {
19191978
marked_fully_read_queue: HashMap<String, (OwnedRoomId, OwnedEventId)>,
19201979
}
19211980

1981+
struct SearchUiState {
1982+
room_id: OwnedRoomId,
1983+
fully_paginated: bool,
1984+
items: Vector<Arc<TimelineItem>>,
1985+
next_batch: Option<String>,
1986+
update_receiver: crossbeam_channel::Receiver<SearchUpdate>,
1987+
}
1988+
19221989
/// The item index, scroll position, and optional unique IDs of the first `N` events
19231990
/// that have been drawn in the most recent draw pass of a timeline's PortalList.
19241991
#[derive(Debug)]

src/shared/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub mod search_bar;
1111
pub mod styles;
1212
pub mod text_or_image;
1313
pub mod typing_animation;
14+
pub mod search;
1415

1516
pub fn live_design(cx: &mut Cx) {
1617
// Order matters here, as some widget definitions depend on others.

src/shared/search.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use matrix_sdk::ruma::OwnedRoomId;
2+
use std::sync::Arc;
3+
use matrix_sdk_ui::timeline::TimelineItem;
4+
5+
/// Search result updates sent from async worker to UI.
6+
pub enum SearchUpdate {
7+
// A new batch of search results has been received.
8+
NewResults {
9+
room_id: OwnedRoomId,
10+
results: Vec<Arc<TimelineItem>>, // List of matching search results
11+
next_batch: Option<String>, // Token for pagination
12+
},
13+
}

src/sliding_sync.rs

Lines changed: 87 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ use matrix_sdk::{
1010
media::MediaRequest,
1111
room::{Receipts, RoomMember},
1212
ruma::{
13-
api::client::{receipt::create_receipt::v3::ReceiptType, session::get_login_types::v3::LoginType},
13+
api::client::{receipt::create_receipt::v3::ReceiptType, session::get_login_types::v3::LoginType,
14+
search::search_events::v3::{Criteria, Categories, Request,}
15+
},
1416
events::{
1517
receipt::ReceiptThread, room::{
1618
message::{ForwardThread, RoomMessageEventContent},
1719
MediaSource,
18-
}, FullStateEventContent
20+
}, FullStateEventContent, AnyTimelineEvent,
1921
},
2022
OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId, UserId
2123
},
@@ -26,12 +28,12 @@ use matrix_sdk::{
2628
use matrix_sdk_ui::{
2729
room_list_service::{self, RoomListLoadingState},
2830
sync_service::{self, SyncService},
29-
timeline::{AnyOtherFullStateEventContent, EventTimelineItem, LiveBackPaginationStatus, RepliedToInfo, TimelineDetails, TimelineItemContent},
31+
timeline::{AnyOtherFullStateEventContent, EventTimelineItem, LiveBackPaginationStatus, RepliedToInfo, TimelineDetails, TimelineItem, TimelineItemContent},
3032
Timeline,
3133
};
3234
use tokio::{
3335
runtime::Handle,
34-
sync::mpsc::{UnboundedSender, UnboundedReceiver},
36+
sync::mpsc::{UnboundedSender, UnboundedReceiver, unbounded_channel},
3537
task::JoinHandle,
3638
};
3739
use unicode_segmentation::UnicodeSegmentation;
@@ -43,7 +45,7 @@ use crate::{
4345
}, media_cache::MediaCacheEntry, persistent_state::{self, ClientSessionPersisted}, profile::{
4446
user_profile::{AvatarState, UserProfile},
4547
user_profile_cache::{enqueue_user_profile_update, UserProfileUpdate},
46-
}, utils::MEDIA_THUMBNAIL_FORMAT, verification::add_verification_event_handlers_and_sync_client
48+
}, shared::search::SearchUpdate, utils::MEDIA_THUMBNAIL_FORMAT, verification::add_verification_event_handlers_and_sync_client
4749
};
4850

4951

@@ -264,9 +266,28 @@ pub enum MatrixRequest {
264266
FullyReadReceipt{
265267
room_id: OwnedRoomId,
266268
event_id: OwnedEventId,
267-
}
269+
},
270+
271+
/// Request to search for messages in a room.
272+
SearchRoomMessages {
273+
room_id: OwnedRoomId,
274+
search_term: String,
275+
next_batch: Option<String>,
276+
},
268277
}
269278

279+
// Create a global sender/receiver pair for search results
280+
pub static SEARCH_RESULTS_SENDER: OnceLock<crossbeam_channel::Sender<SearchUpdate>> = OnceLock::new();
281+
pub static SEARCH_RESULTS_RECEIVER: OnceLock<crossbeam_channel::Receiver<SearchUpdate>> = OnceLock::new();
282+
283+
// Initialize the search channel
284+
pub fn init_search_channel() {
285+
let (sender, receiver) = crossbeam_channel::unbounded();
286+
SEARCH_RESULTS_SENDER.set(sender).unwrap();
287+
SEARCH_RESULTS_RECEIVER.set(receiver).unwrap();
288+
}
289+
290+
270291
/// Submits a request to the worker thread to be executed asynchronously.
271292
pub fn submit_async_request(req: MatrixRequest) {
272293
REQUEST_SENDER.get()
@@ -671,7 +692,66 @@ async fn async_worker(mut receiver: UnboundedReceiver<MatrixRequest>) -> Result<
671692
Err(_e) => error!("Failed to send fully read receipt to room {room_id}, event {event_id}; error: {_e:?}"),
672693
}
673694
});
674-
}
695+
},
696+
697+
MatrixRequest::SearchRoomMessages { room_id, search_term, next_batch } => {
698+
let mut categories = Categories::new();
699+
categories.room_events = Some(Criteria::new(search_term));
700+
701+
let mut search_request = Request::new(categories);
702+
search_request.next_batch = next_batch;
703+
704+
let client = get_client().unwrap();
705+
706+
match client.send(search_request, None).await {
707+
Ok(response) => {
708+
let room_events = response.search_categories.room_events;
709+
710+
let event_ids: Vec<OwnedEventId> = room_events
711+
.results
712+
.into_iter()
713+
.filter_map(|search_result| {
714+
search_result.result
715+
.and_then(|raw_event| raw_event.deserialize().ok())
716+
.and_then(|event| match event {
717+
AnyTimelineEvent::MessageLike(msg) => Some(msg.event_id().to_owned()),
718+
AnyTimelineEvent::State(state) => Some(state.event_id().to_owned()),
719+
_ => None,
720+
})
721+
})
722+
.collect();
723+
724+
if event_ids.is_empty() {
725+
log!("Search found no events.");
726+
return Ok(());
727+
}
728+
729+
// Now that we have event IDs, request pagination of those events.
730+
let timeline = {
731+
let mut all_room_info = ALL_ROOM_INFO.lock().unwrap();
732+
let Some(room_info) = all_room_info.get_mut(&room_id) else {
733+
log!("Skipping search request for not-yet-known room {room_id}");
734+
return Ok(());
735+
};
736+
room_info.timeline.clone()
737+
};
738+
739+
// Fetch events by requesting timeline pagination for each found event.
740+
let _fetch_events_task = Handle::current().spawn(async move {
741+
log!("Fetching {} events for search results in room {}", event_ids.len(), room_id);
742+
743+
for event_id in event_ids {
744+
let _ = timeline.fetch_details_for_event(&event_id).await;
745+
}
746+
747+
log!("Finished fetching search results.");
748+
});
749+
}
750+
Err(e) => {
751+
error!("Search request failed: {:?}", e);
752+
}
753+
}
754+
}
675755
}
676756
}
677757

0 commit comments

Comments
 (0)