feat(tui): search a room's log and members, and a conversation

A busy room's log scrolls past and its member list runs to a thousand
names, with nothing to narrow either. In a room / now filters the log
to lines mentioning the text and u filters the member list, the same
way the room list and the results filter: type, Enter keeps it, Esc
clears it before it leaves the room. The inbox takes / over the open
conversation. The member keys, b and m among them, act on the narrowed
list, and the titles say what is filtered and how many made it.
This commit is contained in:
Michel de Graaf
2026-09-08 11:24:53 +02:00
parent 6c4e60bb0e
commit 607532cc44
11 changed files with 325 additions and 43 deletions
+58 -1
View File
@@ -1,6 +1,6 @@
use super::MainTui;
use super::input::{jumped, list_jump, scroll_log};
use crate::models::RoomsView;
use crate::models::{ChatFilter, RoomsView};
use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
impl MainTui {
@@ -30,6 +30,14 @@ impl MainTui {
self.handle_room_filter_input(key);
return;
}
// So does typing a filter over the log or the member list; the
// other keys still move around.
if self.state.rooms.view == RoomsView::Chat
&& self.state.rooms.filtering.is_some()
&& self.handle_chat_filter_input(key)
{
return;
}
match self.state.rooms.view {
RoomsView::List => self.handle_rooms_list_input(key),
@@ -113,11 +121,24 @@ impl MainTui {
if key.modifiers.contains(KeyModifiers::CONTROL) {
return;
}
let filtered = !self.state.rooms.log_filter.is_empty()
|| !self.state.rooms.user_filter.is_empty();
match key.code {
KeyCode::Char('q') => self.state.show_rooms = false,
// Esc peels back one level: the filters first, then the list.
KeyCode::Esc if filtered => {
self.state.rooms.set_log_filter(String::new());
self.state.rooms.set_user_filter(String::new());
}
KeyCode::Esc | KeyCode::Char('l') => {
self.state.rooms.view = RoomsView::List;
}
KeyCode::Char('/') => {
self.state.rooms.filtering = Some(ChatFilter::Log);
}
KeyCode::Char('u') => {
self.state.rooms.filtering = Some(ChatFilter::Users);
}
KeyCode::Tab => self.state.rooms.next_tab(),
KeyCode::BackTab => self.state.rooms.prev_tab(),
KeyCode::Char('x') => self.leave_active_room(),
@@ -137,6 +158,42 @@ impl MainTui {
}
}
/// The keys that edit whichever filter is being typed, the log's or the
/// member list's. Says whether `key` was one of them.
fn handle_chat_filter_input(&mut self, key: KeyEvent) -> bool {
let rooms = &mut self.state.rooms;
let over_log = rooms.filtering == Some(ChatFilter::Log);
let current = if over_log {
rooms.log_filter.clone()
} else {
rooms.user_filter.clone()
};
let next = match key.code {
KeyCode::Esc => String::new(),
KeyCode::Enter => current,
KeyCode::Char(c) => {
let mut next = current;
next.push(c);
next
}
KeyCode::Backspace => {
let mut next = current;
next.pop();
next
}
_ => return false,
};
if matches!(key.code, KeyCode::Esc | KeyCode::Enter) {
rooms.filtering = None;
}
if over_log {
rooms.set_log_filter(next);
} else {
rooms.set_user_filter(next);
}
true
}
/// Browse the shares of the member highlighted in the active room. Closes
/// the rooms popup so the browse tree is the sole overlay.
fn browse_selected_room_user(&mut self) {