diff --git a/src/base.rs b/src/base.rs index 1a424e4b..d3a5f310 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1043,6 +1043,9 @@ pub struct RoomInfo { /// The time that we last fetched scrollback for this room. pub fetch_last: Option, + /// The oldest event in the unbroken history + pub fetch_event: Option, + /// Users currently typing in this room, and when we received notification of them doing so. pub users_typing: Option<(Instant, Vec)>, @@ -1069,6 +1072,7 @@ impl Default for RoomInfo { fetching: Default::default(), fetch_id: Default::default(), fetch_last: Default::default(), + fetch_event: Default::default(), users_typing: Default::default(), display_names: Default::default(), draw_last: Default::default(), diff --git a/src/windows/room/scrollback.rs b/src/windows/room/scrollback.rs index 39af1b3c..8410d152 100644 --- a/src/windows/room/scrollback.rs +++ b/src/windows/room/scrollback.rs @@ -63,9 +63,13 @@ fn no_msgs() -> EditError { EditError::Failure(msg.to_string()) } -fn nth_key_before(pos: MessageKey, n: usize, thread: &Messages) -> MessageKey { +fn nth_key_before(pos: MessageKey, n: usize, thread: &Messages, info: &RoomInfo) -> MessageKey { let mut end = &pos; - let iter = thread.range(..=&pos).rev().enumerate(); + let iter = thread + .range(..=&pos) + .rev() + .filter(|item| msg_not_hidden(item, info)) + .enumerate(); for (i, (key, _)) in iter { end = key; @@ -78,19 +82,24 @@ fn nth_key_before(pos: MessageKey, n: usize, thread: &Messages) -> MessageKey { end.clone() } -fn nth_before(pos: MessageKey, n: usize, thread: &Messages) -> MessageCursor { - let key = nth_key_before(pos, n, thread); +fn nth_before(pos: MessageKey, n: usize, thread: &Messages, info: &RoomInfo) -> MessageCursor { + let key = nth_key_before(pos, n, thread, info); - if matches!(thread.last_key_value(), Some((last, _)) if &key == last) { + if matches!(last_key_value(thread, info), Some((last, _)) if &key == last) { MessageCursor::latest() } else { MessageCursor::from(key) } } -fn nth_key_after(pos: MessageKey, n: usize, thread: &Messages) -> Option { +fn nth_key_after( + pos: MessageKey, + n: usize, + thread: &Messages, + info: &RoomInfo, +) -> Option { let mut end = &pos; - let mut iter = thread.range(&pos..).enumerate(); + let mut iter = thread.range(&pos..).filter(|item| msg_not_hidden(item, info)).enumerate(); for (i, (key, _)) in iter.by_ref() { end = key; @@ -104,12 +113,32 @@ fn nth_key_after(pos: MessageKey, n: usize, thread: &Messages) -> Option MessageCursor { - nth_key_after(pos, n, thread).map(MessageCursor::from).unwrap_or_default() +fn nth_after(pos: MessageKey, n: usize, thread: &Messages, info: &RoomInfo) -> MessageCursor { + nth_key_after(pos, n, thread, info) + .map(MessageCursor::from) + .unwrap_or_default() } -fn prevmsg<'a>(key: &MessageKey, thread: &'a Messages) -> Option<&'a Message> { - thread.range(..key).next_back().map(|(_, v)| v) +fn prevmsg<'a>(key: &MessageKey, thread: &'a Messages, info: &RoomInfo) -> Option<&'a Message> { + thread + .range(..key) + .rfind(|item| msg_not_hidden(item, info)) + .map(|(_, v)| v) +} + +fn msg_not_hidden(item: &(&MessageKey, &Message), info: &RoomInfo) -> bool { + info.fetch_event.as_ref().is_none_or(|oldest| oldest < item.0) +} + +fn first_key<'a>(thread: &'a Messages, info: &RoomInfo) -> Option<&'a MessageKey> { + thread.iter().find(|item| msg_not_hidden(item, info)).map(|(k, _)| k) +} + +fn last_key_value<'a>( + thread: &'a Messages, + info: &RoomInfo, +) -> Option<(&'a MessageKey, &'a Message)> { + thread.iter().rfind(|item| msg_not_hidden(item, info)) } pub struct ScrollbackState { @@ -180,7 +209,7 @@ impl ScrollbackState { self.cursor .timestamp .clone() - .or_else(|| self.get_thread(info)?.last_key_value().map(|kv| kv.0.clone())) + .or_else(|| last_key_value(self.get_thread(info)?, info).map(|kv| kv.0.clone())) } pub fn get_mut<'a>(&mut self, info: &'a mut RoomInfo) -> Option<&'a mut Message> { @@ -210,26 +239,34 @@ impl ScrollbackState { range: EditRange, info: &'a RoomInfo, ) -> impl Iterator { - let Some(thread) = self.get_thread(info) else { - return Default::default(); - }; - - let start = range.start.to_key(thread); - let end = range.end.to_key(thread); - - let (start, end) = if let (Some(start), Some(end)) = (start, end) { - (start, end) - } else if let Some((last, _)) = thread.last_key_value() { - (last, last) - } else { - return thread.range(..); - }; - - if range.inclusive { - thread.range(start..=end) - } else { - thread.range(start..end) + fn messages_inner<'a>( + state: &ScrollbackState, + range: EditRange, + info: &'a RoomInfo, + ) -> impl Iterator { + let Some(thread) = state.get_thread(info) else { + return std::collections::btree_map::Range::default(); + }; + + let start = range.start.to_key(thread); + let end = range.end.to_key(thread); + + let (start, end) = if let (Some(start), Some(end)) = (start, end) { + (start, end) + } else if let Some((last, _)) = last_key_value(thread, info) { + (last, last) + } else { + return thread.range(..); + }; + + if range.inclusive { + thread.range(start..=end) + } else { + thread.range(start..end) + } } + + messages_inner(self, range, info).filter(move |item| msg_not_hidden(item, info)) } fn need_more_messages(&self, info: &RoomInfo) -> bool { @@ -241,7 +278,7 @@ impl ScrollbackState { _ => {}, } - let first_key = self.get_thread(info).and_then(|t| t.first_key_value()).map(|(k, _)| k); + let first_key = self.get_thread(info).and_then(|t| first_key(t, info)); let at_top = first_key == self.viewctx.corner.timestamp.as_ref(); match (at_top, self.thread.as_ref()) { @@ -291,9 +328,11 @@ impl ScrollbackState { let mut lines = 0; let target = self.viewctx.get_height() / 2; - for (key, item) in thread.range(..=&idx).rev() { + for (key, item) in + thread.range(..=&idx).rev().filter(|item| msg_not_hidden(item, info)) + { let sel = selidx == key; - let prev = prevmsg(key, thread); + let prev = prevmsg(key, thread, info); let len = item.show(prev, sel, &self.viewctx, info, settings, previews).lines.len(); @@ -315,9 +354,11 @@ impl ScrollbackState { let mut lines = 0; let target = self.viewctx.get_height(); - for (key, item) in thread.range(..=&idx).rev() { + for (key, item) in + thread.range(..=&idx).rev().filter(|item| msg_not_hidden(item, info)) + { let sel = key == selidx; - let prev = prevmsg(key, thread); + let prev = prevmsg(key, thread, info); let len = item.show(prev, sel, &self.viewctx, info, settings, previews).lines.len(); @@ -352,7 +393,7 @@ impl ScrollbackState { return; }; - let last_key = if let Some(k) = thread.last_key_value() { + let last_key = if let Some(k) = last_key_value(thread, info) { k.0 } else { return; @@ -369,9 +410,12 @@ impl ScrollbackState { let mut lines = 0; let cursor_key = self.cursor.timestamp.as_ref().unwrap_or(last_key); - let mut prev = prevmsg(cursor_key, thread); + let mut prev = prevmsg(cursor_key, thread, info); - for (idx, item) in thread.range(corner_key.clone()..) { + for (idx, item) in thread + .range(corner_key.clone()..) + .filter(|item| msg_not_hidden(item, info)) + { if idx == cursor_key { // Cursor is already within the viewport. break; @@ -423,7 +467,7 @@ impl ScrollbackState { MoveType::BufferLineOffset => None, MoveType::BufferLinePercent => None, MoveType::BufferPos(MovePosition::Beginning) => { - let start = self.get_thread(info)?.first_key_value()?.0.clone(); + let start = first_key(self.get_thread(info)?, info)?.clone(); Some(start.into()) }, @@ -439,8 +483,8 @@ impl ScrollbackState { let thread = self.get_thread(info)?; match dir { - MoveDir1D::Previous => nth_before(pos, count, thread).into(), - MoveDir1D::Next => nth_after(pos, count, thread).into(), + MoveDir1D::Previous => nth_before(pos, count, thread, info).into(), + MoveDir1D::Next => nth_after(pos, count, thread, info).into(), } }, MoveType::ViewportPos(MovePosition::Beginning) => { @@ -490,8 +534,8 @@ impl ScrollbackState { RangeType::Buffer => { let thread = self.get_thread(info)?; - let start = thread.first_key_value()?.0.clone(); - let end = thread.last_key_value()?.0.clone(); + let start = first_key(thread, info)?.clone(); + let end = last_key_value(thread, info)?.0.clone(); Some(EditRange::inclusive(start.into(), end.into(), TargetShape::LineWise)) }, @@ -505,7 +549,9 @@ impl ScrollbackState { let mut end = &pos; - for (i, (key, _)) in thread.range(&pos..).enumerate() { + for (i, (key, _)) in + thread.range(&pos..).filter(|item| msg_not_hidden(item, info)).enumerate() + { if i >= count { break; } @@ -533,7 +579,7 @@ impl ScrollbackState { let thread = self.get_thread(info)?; let mut mc = None; - for (key, msg) in thread.range(&start..) { + for (key, msg) in thread.range(&start..).filter(|item| msg_not_hidden(item, info)) { if count == 0 { break; } @@ -564,7 +610,7 @@ impl ScrollbackState { return (None, false); }; - for (key, msg) in thread.range(..&end).rev() { + for (key, msg) in thread.range(..&end).rev().filter(|item| msg_not_hidden(item, info)) { if count == 0 { break; } @@ -1087,7 +1133,7 @@ impl ScrollActions for ScrollbackState { let mut corner = self.viewctx.corner.clone(); let thread = self.get_thread(info).ok_or_else(no_msgs)?; - let last_key = if let Some(k) = thread.last_key_value() { + let last_key = if let Some(k) = last_key_value(thread, info) { k.0 } else { return Ok(None); @@ -1106,11 +1152,15 @@ impl ScrollActions for ScrollbackState { match dir { MoveDir2D::Up => { - let first_key = thread.first_key_value().map(|f| f.0.clone()); + let first_key = first_key(thread, info).cloned(); - for (key, item) in thread.range(..=&corner_key).rev() { + for (key, item) in thread + .range(..=&corner_key) + .rev() + .filter(|item| msg_not_hidden(item, info)) + { let sel = key == cursor_key; - let prev = prevmsg(key, thread); + let prev = prevmsg(key, thread, info); let txt = item.show(prev, sel, &self.viewctx, info, settings, previews); let len = txt.height().max(1); let max = len.saturating_sub(1); @@ -1135,9 +1185,11 @@ impl ScrollActions for ScrollbackState { } }, MoveDir2D::Down => { - let mut prev = prevmsg(&corner_key, thread); + let mut prev = prevmsg(&corner_key, thread, info); - for (key, item) in thread.range(&corner_key..) { + for (key, item) in + thread.range(&corner_key..).filter(|item| msg_not_hidden(item, info)) + { let sel = key == cursor_key; let txt = item.show(prev, sel, &self.viewctx, info, settings, previews); let len = txt.height().max(1); @@ -1354,14 +1406,14 @@ impl StatefulWidget for Scrollback<'_> { let corner_key = if let Some(k) = &corner.timestamp { k.clone() } else { - nth_key_before(cursor_key.clone(), height, thread) + nth_key_before(cursor_key.clone(), height, thread, info) }; let foc = self.focused || cursor.timestamp.is_some(); let full = std::mem::take(&mut state.show_full_on_redraw) || cursor.timestamp.is_none(); let mut lines = vec![]; let mut sawit = false; - let mut prev = prevmsg(&corner_key, thread); + let mut prev = prevmsg(&corner_key, thread, info); // load image previews for (_, item) in thread.range(&corner_key..).rev() { @@ -1385,7 +1437,7 @@ impl StatefulWidget for Scrollback<'_> { } let previews = &self.store.application.previews; - for (key, item) in thread.range(&corner_key..) { + for (key, item) in thread.range(&corner_key..).filter(|item| msg_not_hidden(item, info)) { let sel = key == cursor_key; let (txt, [mut msg_preview, mut reply_preview]) = diff --git a/src/worker.rs b/src/worker.rs index 69f7252e..03a1ca3b 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -310,6 +310,13 @@ fn load_insert( match res { Ok((fetch_id, msgs)) => { + if let Some((msg, _)) = msgs.last() { + let key = MessageKey { + ts: msg.origin_server_ts().into(), + id: msg.event_id().to_owned().into(), + }; + info.fetch_event = Some(key); + } for (msg, receipts) in msgs.into_iter() { let sender = msg.sender().to_owned(); let _ = presences.get_or_default(sender);