Fix entering thread view when there's no messages yet (#224)

This commit is contained in:
Ulyssa 2024-03-23 19:20:06 -07:00 committed by GitHub
parent 1e9b6cc271
commit 2ac71da9a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 262 additions and 238 deletions

View file

@ -45,6 +45,7 @@ use matrix_sdk::{
RoomMessageEventContent, RoomMessageEventContent,
RoomMessageEventContentWithoutRelation, RoomMessageEventContentWithoutRelation,
}, },
room::redaction::{OriginalSyncRoomRedactionEvent, SyncRoomRedactionEvent},
tag::{TagName, Tags}, tag::{TagName, Tags},
MessageLikeEvent, MessageLikeEvent,
}, },
@ -54,6 +55,7 @@ use matrix_sdk::{
OwnedRoomId, OwnedRoomId,
OwnedUserId, OwnedUserId,
RoomId, RoomId,
RoomVersionId,
UserId, UserId,
}, },
RoomState as MatrixRoomState, RoomState as MatrixRoomState,
@ -729,7 +731,7 @@ pub struct RoomInfo {
pub keys: HashMap<OwnedEventId, EventLocation>, pub keys: HashMap<OwnedEventId, EventLocation>,
/// The messages loaded for this room. /// The messages loaded for this room.
pub messages: Messages, messages: Messages,
/// A map of read markers to display on different events. /// A map of read markers to display on different events.
pub event_receipts: HashMap<OwnedEventId, HashSet<OwnedUserId>>, pub event_receipts: HashMap<OwnedEventId, HashSet<OwnedUserId>>,
@ -745,7 +747,7 @@ pub struct RoomInfo {
pub reactions: HashMap<OwnedEventId, MessageReactions>, pub reactions: HashMap<OwnedEventId, MessageReactions>,
/// A map of message identifiers to thread replies. /// A map of message identifiers to thread replies.
pub threads: HashMap<OwnedEventId, Messages>, threads: HashMap<OwnedEventId, Messages>,
/// Whether the scrollback for this room is currently being fetched. /// Whether the scrollback for this room is currently being fetched.
pub fetching: bool, pub fetching: bool,
@ -767,6 +769,48 @@ pub struct RoomInfo {
} }
impl RoomInfo { impl RoomInfo {
pub fn get_thread(&self, root: Option<&EventId>) -> Option<&Messages> {
if let Some(thread_root) = root {
self.threads.get(thread_root)
} else {
Some(&self.messages)
}
}
pub fn get_thread_mut(&mut self, root: Option<OwnedEventId>) -> &mut Messages {
if let Some(thread_root) = root {
self.threads.entry(thread_root).or_default()
} else {
&mut self.messages
}
}
/// Get the event for the last message in a thread (or the thread root if there are no
/// in-thread replies yet).
///
/// This returns `None` if the event identifier isn't in the room.
pub fn get_thread_last<'a>(
&'a self,
thread_root: &OwnedEventId,
) -> Option<&'a OriginalRoomMessageEvent> {
let last = self.threads.get(thread_root).and_then(|t| Some(t.last_key_value()?.1));
let msg = if let Some(last) = last {
&last.event
} else if let EventLocation::Message(_, key) = self.keys.get(thread_root)? {
let msg = self.messages.get(key)?;
&msg.event
} else {
return None;
};
if let MessageEvent::Original(ev) = &msg {
Some(ev)
} else {
None
}
}
/// Get the reactions and their counts for a message. /// Get the reactions and their counts for a message.
pub fn get_reactions(&self, event_id: &EventId) -> Vec<(&str, usize)> { pub fn get_reactions(&self, event_id: &EventId) -> Vec<(&str, usize)> {
if let Some(reacts) = self.reactions.get(event_id) { if let Some(reacts) = self.reactions.get(event_id) {
@ -801,6 +845,37 @@ impl RoomInfo {
self.messages.get_mut(self.keys.get(event_id)?.to_message_key()?) self.messages.get_mut(self.keys.get(event_id)?.to_message_key()?)
} }
pub fn redact(&mut self, ev: OriginalSyncRoomRedactionEvent, room_version: &RoomVersionId) {
let Some(redacts) = &ev.redacts else {
return;
};
match self.keys.get(redacts) {
None => return,
Some(EventLocation::Message(None, key)) => {
if let Some(msg) = self.messages.get_mut(key) {
let ev = SyncRoomRedactionEvent::Original(ev);
msg.redact(ev, room_version);
}
},
Some(EventLocation::Message(Some(root), key)) => {
if let Some(thread) = self.threads.get_mut(root) {
if let Some(msg) = thread.get_mut(key) {
let ev = SyncRoomRedactionEvent::Original(ev);
msg.redact(ev, room_version);
}
}
},
Some(EventLocation::Reaction(event_id)) => {
if let Some(reactions) = self.reactions.get_mut(event_id) {
reactions.remove(redacts);
}
self.keys.remove(redacts);
},
}
}
/// Insert a reaction to a message. /// Insert a reaction to a message.
pub fn insert_reaction(&mut self, react: ReactionEvent) { pub fn insert_reaction(&mut self, react: ReactionEvent) {
match react { match react {

View file

@ -638,6 +638,106 @@ impl<'a> MessageFormatter<'a> {
self.push_spans(line, style, text); self.push_spans(line, style, text);
} }
} }
fn push_in_reply(
&mut self,
msg: &'a Message,
style: Style,
text: &mut Text<'a>,
info: &'a RoomInfo,
) {
let width = self.width();
let w = width.saturating_sub(2);
let shortcodes = self.settings.tunables.message_shortcode_display;
let mut replied = msg.show_msg(w, style, true, shortcodes);
let mut sender = msg.sender_span(info, self.settings);
let sender_width = UnicodeWidthStr::width(sender.content.as_ref());
let trailing = w.saturating_sub(sender_width + 1);
sender.style = sender.style.patch(style);
self.push_spans(
Line::from(vec![
Span::styled(" ", style),
Span::styled(THICK_VERTICAL, style),
sender,
Span::styled(":", style),
space_span(trailing, style),
]),
style,
text,
);
for line in replied.lines.iter_mut() {
line.spans.insert(0, Span::styled(THICK_VERTICAL, style));
line.spans.insert(0, Span::styled(" ", style));
}
self.push_text(replied, style, text);
}
fn push_reactions(&mut self, counts: Vec<(&'a str, usize)>, style: Style, text: &mut Text<'a>) {
let mut emojis = printer::TextPrinter::new(self.width(), style, false, false);
let mut reactions = 0;
for (key, count) in counts {
if reactions != 0 {
emojis.push_str(" ", style);
}
let name = if self.settings.tunables.reaction_shortcode_display {
if let Some(emoji) = emojis::get(key) {
if let Some(short) = emoji.shortcode() {
short
} else {
// No ASCII shortcode name to show.
continue;
}
} else if key.chars().all(|c| c.is_ascii_alphanumeric()) {
key
} else {
// Not an Emoji or a printable ASCII string.
continue;
}
} else {
key
};
emojis.push_str("[", style);
emojis.push_str(name, style);
emojis.push_str(" ", style);
emojis.push_span_nobreak(Span::styled(count.to_string(), style));
emojis.push_str("]", style);
reactions += 1;
}
if reactions > 0 {
self.push_text(emojis.finish(), style, text);
}
}
fn push_thread_reply_count(&mut self, len: usize, text: &mut Text<'a>) {
if len == 0 {
return;
}
// If we have threaded replies to this message, show how many.
let plural = len != 1;
let style = Style::default();
let mut threaded =
printer::TextPrinter::new(self.width(), style, false, false).literal(true);
let len = Span::styled(len.to_string(), style.add_modifier(StyleModifier::BOLD));
threaded.push_str(" \u{2937} ", style);
threaded.push_span_nobreak(len);
if plural {
threaded.push_str(" replies in thread", style);
} else {
threaded.push_str(" reply in thread", style);
}
self.push_text(threaded.finish(), style, text);
}
} }
pub enum ImageStatus { pub enum ImageStatus {
@ -835,33 +935,7 @@ impl Message {
.and_then(|e| info.get_event(&e)); .and_then(|e| info.get_event(&e));
if let Some(r) = &reply { if let Some(r) = &reply {
let w = width.saturating_sub(2); fmt.push_in_reply(r, style, &mut text, info);
let mut replied =
r.show_msg(w, style, true, settings.tunables.message_shortcode_display);
let mut sender = r.sender_span(info, settings);
let sender_width = UnicodeWidthStr::width(sender.content.as_ref());
let trailing = w.saturating_sub(sender_width + 1);
sender.style = sender.style.patch(style);
fmt.push_spans(
Line::from(vec![
Span::styled(" ", style),
Span::styled(THICK_VERTICAL, style),
sender,
Span::styled(":", style),
space_span(trailing, style),
]),
style,
&mut text,
);
for line in replied.lines.iter_mut() {
line.spans.insert(0, Span::styled(THICK_VERTICAL, style));
line.spans.insert(0, Span::styled(" ", style));
}
fmt.push_text(replied, style, &mut text);
} }
// Now show the message contents, and the inlined reply if we couldn't find it above. // Now show the message contents, and the inlined reply if we couldn't find it above.
@ -879,63 +953,12 @@ impl Message {
} }
if settings.tunables.reaction_display { if settings.tunables.reaction_display {
// Pass false for emoji_shortcodes parameter because we handle shortcodes ourselves let reactions = info.get_reactions(self.event.event_id());
// before pushing to the printer. fmt.push_reactions(reactions, style, &mut text);
let mut emojis = printer::TextPrinter::new(width, style, false, false);
let mut reactions = 0;
for (key, count) in info.get_reactions(self.event.event_id()).into_iter() {
if reactions != 0 {
emojis.push_str(" ", style);
} }
let name = if settings.tunables.reaction_shortcode_display { if let Some(thread) = info.get_thread(Some(self.event.event_id())) {
if let Some(emoji) = emojis::get(key) { fmt.push_thread_reply_count(thread.len(), &mut text);
if let Some(short) = emoji.shortcode() {
short
} else {
// No ASCII shortcode name to show.
continue;
}
} else if key.chars().all(|c| c.is_ascii_alphanumeric()) {
key
} else {
// Not an Emoji or a printable ASCII string.
continue;
}
} else {
key
};
emojis.push_str("[", style);
emojis.push_str(name, style);
emojis.push_str(" ", style);
emojis.push_span_nobreak(Span::styled(count.to_string(), style));
emojis.push_str("]", style);
reactions += 1;
}
if reactions > 0 {
fmt.push_text(emojis.finish(), style, &mut text);
}
}
if let Some(thread) = info.threads.get(self.event.event_id()) {
// If we have threaded replies to this message, show how many.
let len = thread.len();
if len > 0 {
let style = Style::default();
let emoji_shortcodes = settings.tunables.message_shortcode_display;
let mut threaded =
printer::TextPrinter::new(width, style, false, emoji_shortcodes).literal(true);
let len = Span::styled(len.to_string(), style.add_modifier(StyleModifier::BOLD));
threaded.push_str(" \u{2937} ", style);
threaded.push_span_nobreak(len);
threaded.push_str(" replies in thread", style);
fmt.push_text(threaded.finish(), style, &mut text);
}
} }
text text

View file

@ -21,7 +21,7 @@ use tracing::Level;
use url::Url; use url::Url;
use crate::{ use crate::{
base::{ChatStore, EventLocation, ProgramStore, RoomFetchStatus, RoomInfo}, base::{ChatStore, EventLocation, ProgramStore, RoomInfo},
config::{ config::{
user_color, user_color,
user_style_from_color, user_style_from_color,
@ -148,25 +148,11 @@ pub fn mock_messages() -> Messages {
} }
pub fn mock_room() -> RoomInfo { pub fn mock_room() -> RoomInfo {
RoomInfo { let mut room = RoomInfo::default();
name: Some("Watercooler Discussion".into()), room.name = Some("Watercooler Discussion".into());
tags: None, room.keys = mock_keys();
*room.get_thread_mut(None) = mock_messages();
keys: mock_keys(), room
messages: mock_messages(),
threads: HashMap::default(),
event_receipts: HashMap::new(),
user_receipts: HashMap::new(),
reactions: HashMap::new(),
fetching: false,
fetch_id: RoomFetchStatus::NotStarted,
fetch_last: None,
users_typing: None,
display_names: HashMap::new(),
draw_last: None,
}
} }
pub fn mock_dirs() -> DirectoryValues { pub fn mock_dirs() -> DirectoryValues {

View file

@ -28,7 +28,6 @@ use matrix_sdk::{
RoomMessageEventContent, RoomMessageEventContent,
TextMessageEventContent, TextMessageEventContent,
}, },
EventId,
OwnedEventId, OwnedEventId,
OwnedRoomId, OwnedRoomId,
RoomId, RoomId,
@ -72,7 +71,6 @@ use modalkit::prelude::*;
use crate::base::{ use crate::base::{
DownloadFlags, DownloadFlags,
EventLocation,
IambAction, IambAction,
IambBufferId, IambBufferId,
IambError, IambError,
@ -148,32 +146,10 @@ impl ChatState {
} }
} }
fn get_thread_last<'a>(
&self,
thread_root: &OwnedEventId,
info: &'a RoomInfo,
) -> Option<&'a OriginalRoomMessageEvent> {
let last = info.threads.get(thread_root).and_then(|t| Some(t.last_key_value()?.1));
let msg = if let Some(last) = last {
&last.event
} else if let EventLocation::Message(_, key) = info.keys.get(thread_root)? {
let msg = info.messages.get(key)?;
&msg.event
} else {
return None;
};
if let MessageEvent::Original(ev) = &msg {
Some(ev)
} else {
None
}
}
fn get_reply_to<'a>(&self, info: &'a RoomInfo) -> Option<&'a OriginalRoomMessageEvent> { fn get_reply_to<'a>(&self, info: &'a RoomInfo) -> Option<&'a OriginalRoomMessageEvent> {
let thread = self.scrollback.get_thread(info)?;
let key = self.reply_to.as_ref()?; let key = self.reply_to.as_ref()?;
let msg = info.messages.get(key)?; let msg = thread.get(key)?;
if let MessageEvent::Original(ev) = &msg.event { if let MessageEvent::Original(ev) = &msg.event {
Some(ev) Some(ev)
@ -205,10 +181,7 @@ impl ChatState {
let settings = &store.application.settings; let settings = &store.application.settings;
let info = store.application.rooms.get_or_default(self.room_id.clone()); let info = store.application.rooms.get_or_default(self.room_id.clone());
let msg = self let msg = self.scrollback.get_mut(info).ok_or(IambError::NoSelectedMessage)?;
.scrollback
.get_mut(&mut info.messages)
.ok_or(IambError::NoSelectedMessage)?;
match act { match act {
MessageAction::Cancel(skip_confirm) => { MessageAction::Cancel(skip_confirm) => {
@ -441,11 +414,11 @@ impl ChatState {
}, },
MessageAction::Unreact(emoji) => { MessageAction::Unreact(emoji) => {
let room = self.get_joined(&store.application.worker)?; let room = self.get_joined(&store.application.worker)?;
let event_id: &EventId = match &msg.event { let event_id = match &msg.event {
MessageEvent::EncryptedOriginal(ev) => ev.event_id.as_ref(), MessageEvent::EncryptedOriginal(ev) => ev.event_id.clone(),
MessageEvent::EncryptedRedacted(ev) => ev.event_id.as_ref(), MessageEvent::EncryptedRedacted(ev) => ev.event_id.clone(),
MessageEvent::Original(ev) => ev.event_id.as_ref(), MessageEvent::Original(ev) => ev.event_id.clone(),
MessageEvent::Local(event_id, _) => event_id.as_ref(), MessageEvent::Local(event_id, _) => event_id.clone(),
MessageEvent::Redacted(_) => { MessageEvent::Redacted(_) => {
let msg = "Cannot unreact to a redacted message"; let msg = "Cannot unreact to a redacted message";
let err = UIError::Failure(msg.into()); let err = UIError::Failure(msg.into());
@ -454,7 +427,7 @@ impl ChatState {
}, },
}; };
let reactions = match info.reactions.get(event_id) { let reactions = match info.reactions.get(&event_id) {
Some(r) => r, Some(r) => r,
None => return Ok(None), None => return Ok(None),
}; };
@ -518,7 +491,7 @@ impl ChatState {
} else if let Some(thread_root) = self.scrollback.thread() { } else if let Some(thread_root) = self.scrollback.thread() {
if let Some(m) = self.get_reply_to(info) { if let Some(m) = self.get_reply_to(info) {
msg = msg.make_for_thread(m, ReplyWithinThread::Yes, AddMentions::No); msg = msg.make_for_thread(m, ReplyWithinThread::Yes, AddMentions::No);
} else if let Some(m) = self.get_thread_last(thread_root, info) { } else if let Some(m) = info.get_thread_last(thread_root) {
msg = msg.make_for_thread(m, ReplyWithinThread::No, AddMentions::No); msg = msg.make_for_thread(m, ReplyWithinThread::No, AddMentions::No);
} else { } else {
// Internal state is wonky? // Internal state is wonky?
@ -884,19 +857,21 @@ impl<'a> StatefulWidget for Chat<'a> {
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
// Determine whether we have a description to show for the message bar. // Determine whether we have a description to show for the message bar.
let desc_spans = match (&state.editing, &state.reply_to) { let desc_spans = match (&state.editing, &state.reply_to, state.thread()) {
(None, None) => None, (None, None, None) => None,
(Some(_), None) => Some(Line::from("Editing message")), (None, None, Some(_)) => Some(Line::from("Replying in thread")),
(editing, Some(_)) => { (Some(_), None, None) => Some(Line::from("Editing message")),
state.reply_to.as_ref().and_then(|k| { (Some(_), None, Some(_)) => Some(Line::from("Editing message in thread")),
let room = self.store.application.rooms.get(state.id())?; (editing, Some(_), thread) => {
let msg = room.messages.get(k)?; self.store.application.rooms.get(state.id()).and_then(|room| {
let msg = state.get_reply_to(room)?;
let user = let user =
self.store.application.settings.get_user_span(msg.sender.as_ref(), room); self.store.application.settings.get_user_span(msg.sender.as_ref(), room);
let prefix = if editing.is_some() { let prefix = match (editing.is_some(), thread.is_some()) {
Span::from("Editing reply to ") (true, false) => Span::from("Editing reply to "),
} else { (true, true) => Span::from("Editing reply in thread to "),
Span::from("Replying to ") (false, false) => Span::from("Replying to "),
(false, true) => Span::from("Replying in thread to "),
}; };
let spans = Line::from(vec![prefix, user]); let spans = Line::from(vec![prefix, user]);

View file

@ -1,5 +1,4 @@
//! Message scrollback //! Message scrollback
use ratatui_image::Image; use ratatui_image::Image;
use regex::Regex; use regex::Regex;
@ -59,6 +58,11 @@ use crate::{
message::{Message, MessageCursor, MessageKey, Messages}, message::{Message, MessageCursor, MessageKey, Messages},
}; };
fn no_msgs() -> EditError<IambInfo> {
let msg = "No messages to select.";
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) -> MessageKey {
let mut end = &pos; let mut end = &pos;
let iter = thread.range(..=&pos).rev().enumerate(); let iter = thread.range(..=&pos).rev().enumerate();
@ -159,14 +163,16 @@ impl ScrollbackState {
self.cursor self.cursor
.timestamp .timestamp
.clone() .clone()
.or_else(|| self.get_thread(info).last_key_value().map(|kv| kv.0.clone())) .or_else(|| self.get_thread(info)?.last_key_value().map(|kv| kv.0.clone()))
} }
pub fn get_mut<'a>(&mut self, messages: &'a mut Messages) -> Option<&'a mut Message> { pub fn get_mut<'a>(&mut self, info: &'a mut RoomInfo) -> Option<&'a mut Message> {
let thread = self.get_thread_mut(info);
if let Some(k) = &self.cursor.timestamp { if let Some(k) = &self.cursor.timestamp {
messages.get_mut(k) thread.get_mut(k)
} else { } else {
messages.last_entry().map(|o| o.into_mut()) thread.last_entry().map(|o| o.into_mut())
} }
} }
@ -174,20 +180,12 @@ impl ScrollbackState {
self.thread.as_ref() self.thread.as_ref()
} }
pub fn get_thread<'a>(&self, info: &'a RoomInfo) -> &'a Messages { pub fn get_thread<'a>(&self, info: &'a RoomInfo) -> Option<&'a Messages> {
if let Some(thread_root) = self.thread.as_ref() { info.get_thread(self.thread.as_deref())
info.threads.get(thread_root).unwrap_or(&info.messages)
} else {
&info.messages
}
} }
pub fn get_thread_mut<'a>(&self, info: &'a mut RoomInfo) -> &'a mut Messages { pub fn get_thread_mut<'a>(&self, info: &'a mut RoomInfo) -> &'a mut Messages {
if let Some(thread_root) = self.thread.as_ref() { info.get_thread_mut(self.thread.clone())
info.threads.get_mut(thread_root).unwrap_or(&mut info.messages)
} else {
&mut info.messages
}
} }
pub fn messages<'a>( pub fn messages<'a>(
@ -195,7 +193,10 @@ impl ScrollbackState {
range: EditRange<MessageCursor>, range: EditRange<MessageCursor>,
info: &'a RoomInfo, info: &'a RoomInfo,
) -> impl Iterator<Item = (&'a MessageKey, &'a Message)> { ) -> impl Iterator<Item = (&'a MessageKey, &'a Message)> {
let thread = self.get_thread(info); let Some(thread) = self.get_thread(info) else {
return Default::default();
};
let start = range.start.to_key(thread); let start = range.start.to_key(thread);
let end = range.end.to_key(thread); let end = range.end.to_key(thread);
@ -223,7 +224,7 @@ impl ScrollbackState {
_ => {}, _ => {},
} }
let first_key = self.get_thread(info).first_key_value().map(|(k, _)| k); let first_key = self.get_thread(info).and_then(|t| t.first_key_value()).map(|(k, _)| k);
let at_top = first_key == self.viewctx.corner.timestamp.as_ref(); let at_top = first_key == self.viewctx.corner.timestamp.as_ref();
match (at_top, self.thread.as_ref()) { match (at_top, self.thread.as_ref()) {
@ -254,7 +255,10 @@ impl ScrollbackState {
info: &RoomInfo, info: &RoomInfo,
settings: &ApplicationSettings, settings: &ApplicationSettings,
) { ) {
let thread = self.get_thread(info); let Some(thread) = self.get_thread(info) else {
return;
};
let selidx = if let Some(key) = self.cursor.to_key(thread) { let selidx = if let Some(key) = self.cursor.to_key(thread) {
key key
} else { } else {
@ -319,7 +323,9 @@ impl ScrollbackState {
} }
fn shift_cursor(&mut self, info: &RoomInfo, settings: &ApplicationSettings) { fn shift_cursor(&mut self, info: &RoomInfo, settings: &ApplicationSettings) {
let thread = self.get_thread(info); let Some(thread) = self.get_thread(info) else {
return;
};
let last_key = if let Some(k) = thread.last_key_value() { let last_key = if let Some(k) = thread.last_key_value() {
k.0 k.0
@ -389,7 +395,7 @@ impl ScrollbackState {
MoveType::BufferLineOffset => None, MoveType::BufferLineOffset => None,
MoveType::BufferLinePercent => None, MoveType::BufferLinePercent => None,
MoveType::BufferPos(MovePosition::Beginning) => { MoveType::BufferPos(MovePosition::Beginning) => {
let start = self.get_thread(info).first_key_value()?.0.clone(); let start = self.get_thread(info)?.first_key_value()?.0.clone();
Some(start.into()) Some(start.into())
}, },
@ -402,7 +408,7 @@ impl ScrollbackState {
MoveType::ParagraphBegin(dir) | MoveType::ParagraphBegin(dir) |
MoveType::SectionBegin(dir) | MoveType::SectionBegin(dir) |
MoveType::SectionEnd(dir) => { MoveType::SectionEnd(dir) => {
let thread = self.get_thread(info); let thread = self.get_thread(info)?;
match dir { match dir {
MoveDir1D::Previous => nth_before(pos, count, thread).into(), MoveDir1D::Previous => nth_before(pos, count, thread).into(),
@ -455,14 +461,14 @@ impl ScrollbackState {
RangeType::XmlTag => None, RangeType::XmlTag => None,
RangeType::Buffer => { RangeType::Buffer => {
let thread = self.get_thread(info); let thread = self.get_thread(info)?;
let start = thread.first_key_value()?.0.clone(); let start = thread.first_key_value()?.0.clone();
let end = thread.last_key_value()?.0.clone(); let end = thread.last_key_value()?.0.clone();
Some(EditRange::inclusive(start.into(), end.into(), TargetShape::LineWise)) Some(EditRange::inclusive(start.into(), end.into(), TargetShape::LineWise))
}, },
RangeType::Line | RangeType::Paragraph | RangeType::Sentence => { RangeType::Line | RangeType::Paragraph | RangeType::Sentence => {
let thread = self.get_thread(info); let thread = self.get_thread(info)?;
let count = ctx.resolve(count); let count = ctx.resolve(count);
if count == 0 { if count == 0 {
@ -496,7 +502,7 @@ impl ScrollbackState {
mut count: usize, mut count: usize,
info: &RoomInfo, info: &RoomInfo,
) -> Option<MessageCursor> { ) -> Option<MessageCursor> {
let thread = self.get_thread(info); let thread = self.get_thread(info)?;
let mut mc = None; let mut mc = None;
for (key, msg) in thread.range(&start..) { for (key, msg) in thread.range(&start..) {
@ -524,9 +530,12 @@ impl ScrollbackState {
mut count: usize, mut count: usize,
info: &RoomInfo, info: &RoomInfo,
) -> (Option<MessageCursor>, bool) { ) -> (Option<MessageCursor>, bool) {
let thread = self.get_thread(info);
let mut mc = None; let mut mc = None;
let Some(thread) = self.get_thread(info) else {
return (None, false);
};
for (key, msg) in thread.range(..&end).rev() { for (key, msg) in thread.range(..&end).rev() {
if count == 0 { if count == 0 {
break; break;
@ -614,14 +623,8 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
store: &mut ProgramStore, store: &mut ProgramStore,
) -> EditResult<EditInfo, IambInfo> { ) -> EditResult<EditInfo, IambInfo> {
let info = store.application.rooms.get_or_default(self.room_id.clone()); let info = store.application.rooms.get_or_default(self.room_id.clone());
let thread = self.get_thread(info); let thread = self.get_thread(info).ok_or_else(no_msgs)?;
let key = if let Some(k) = self.cursor.to_key(thread) { let key = self.cursor.to_key(thread).ok_or_else(no_msgs)?.clone();
k.clone()
} else {
let msg = "No messages to select.";
let err = EditError::Failure(msg.to_string());
return Err(err);
};
match operation { match operation {
EditAction::Motion => { EditAction::Motion => {
@ -644,7 +647,6 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
EditTarget::CharJump(mark) | EditTarget::LineJump(mark) => { EditTarget::CharJump(mark) | EditTarget::LineJump(mark) => {
let mark = ctx.resolve(mark); let mark = ctx.resolve(mark);
let cursor = store.cursors.get_mark(self.id.clone(), mark)?; let cursor = store.cursors.get_mark(self.id.clone(), mark)?;
let thread = self.get_thread(info);
if let Some(mc) = MessageCursor::from_cursor(&cursor, thread) { if let Some(mc) = MessageCursor::from_cursor(&cursor, thread) {
Some(mc) Some(mc)
@ -723,7 +725,6 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
EditTarget::CharJump(mark) | EditTarget::LineJump(mark) => { EditTarget::CharJump(mark) | EditTarget::LineJump(mark) => {
let mark = ctx.resolve(mark); let mark = ctx.resolve(mark);
let cursor = store.cursors.get_mark(self.id.clone(), mark)?; let cursor = store.cursors.get_mark(self.id.clone(), mark)?;
let thread = self.get_thread(info);
if let Some(c) = MessageCursor::from_cursor(&cursor, thread) { if let Some(c) = MessageCursor::from_cursor(&cursor, thread) {
self._range_to(c).into() self._range_to(c).into()
@ -821,18 +822,11 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
store: &mut ProgramStore, store: &mut ProgramStore,
) -> EditResult<EditInfo, IambInfo> { ) -> EditResult<EditInfo, IambInfo> {
let info = store.application.get_room_info(self.room_id.clone()); let info = store.application.get_room_info(self.room_id.clone());
let thread = self.get_thread(info); let thread = self.get_thread(info).ok_or_else(no_msgs)?;
let cursor = self.cursor.to_cursor(thread).ok_or_else(no_msgs)?;
if let Some(cursor) = self.cursor.to_cursor(thread) {
store.cursors.set_mark(self.id.clone(), name, cursor); store.cursors.set_mark(self.id.clone(), name, cursor);
Ok(None) Ok(None)
} else {
let msg = "Failed to set mark for message";
let err = EditError::Failure(msg.into());
Err(err)
}
} }
fn complete( fn complete(
@ -884,6 +878,7 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
store: &mut ProgramStore, store: &mut ProgramStore,
) -> EditResult<EditInfo, IambInfo> { ) -> EditResult<EditInfo, IambInfo> {
let info = store.application.get_room_info(self.room_id.clone()); let info = store.application.get_room_info(self.room_id.clone());
let thread = self.get_thread(info).ok_or_else(no_msgs)?;
match act { match act {
CursorAction::Close(_) => Ok(None), CursorAction::Close(_) => Ok(None),
@ -901,8 +896,6 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
self.push_jump(); self.push_jump();
} }
let thread = self.get_thread(info);
if let Some(mc) = MessageCursor::from_cursor(ngroup.leader.cursor(), thread) { if let Some(mc) = MessageCursor::from_cursor(ngroup.leader.cursor(), thread) {
self.cursor = mc; self.cursor = mc;
@ -916,7 +909,6 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
}, },
CursorAction::Save(_) => { CursorAction::Save(_) => {
let reg = ctx.get_register().unwrap_or(Register::UnnamedCursorGroup); let reg = ctx.get_register().unwrap_or(Register::UnnamedCursorGroup);
let thread = self.get_thread(info);
// Lists don't have groups; override any previously saved group. // Lists don't have groups; override any previously saved group.
let cursor = self.cursor.to_cursor(thread).ok_or_else(|| { let cursor = self.cursor.to_cursor(thread).ok_or_else(|| {
@ -1019,7 +1011,7 @@ impl Promptable<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
store: &mut ProgramStore, store: &mut ProgramStore,
) -> EditResult<Vec<(Action<IambInfo>, ProgramContext)>, IambInfo> { ) -> EditResult<Vec<(Action<IambInfo>, ProgramContext)>, IambInfo> {
let info = store.application.get_room_info(self.room_id.clone()); let info = store.application.get_room_info(self.room_id.clone());
let thread = self.get_thread(info); let thread = self.get_thread(info).ok_or_else(no_msgs)?;
let Some(key) = self.cursor.to_key(thread) else { let Some(key) = self.cursor.to_key(thread) else {
let msg = "No message currently selected"; let msg = "No message currently selected";
@ -1068,7 +1060,7 @@ impl ScrollActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
let info = store.application.rooms.get_or_default(self.room_id.clone()); let info = store.application.rooms.get_or_default(self.room_id.clone());
let settings = &store.application.settings; let settings = &store.application.settings;
let mut corner = self.viewctx.corner.clone(); let mut corner = self.viewctx.corner.clone();
let thread = self.get_thread(info); 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) = thread.last_key_value() {
k.0 k.0
@ -1182,7 +1174,7 @@ impl ScrollActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
Axis::Vertical => { Axis::Vertical => {
let info = store.application.rooms.get_or_default(self.room_id.clone()); let info = store.application.rooms.get_or_default(self.room_id.clone());
let settings = &store.application.settings; let settings = &store.application.settings;
let thread = self.get_thread(info); let thread = self.get_thread(info).ok_or_else(no_msgs)?;
if let Some(key) = self.cursor.to_key(thread).cloned() { if let Some(key) = self.cursor.to_key(thread).cloned() {
self.scrollview(key, pos, info, settings); self.scrollview(key, pos, info, settings);
@ -1315,12 +1307,14 @@ impl<'a> StatefulWidget for Scrollback<'a> {
return; return;
} }
let Some(thread) = state.get_thread(info) else {
return;
};
if state.cursor.timestamp < state.viewctx.corner.timestamp { if state.cursor.timestamp < state.viewctx.corner.timestamp {
state.viewctx.corner = state.cursor.clone(); state.viewctx.corner = state.cursor.clone();
} }
let thread = state.get_thread(info);
let cursor = &state.cursor; let cursor = &state.cursor;
let cursor_key = if let Some(k) = cursor.to_key(thread) { let cursor_key = if let Some(k) = cursor.to_key(thread) {
k k

View file

@ -52,7 +52,7 @@ use matrix_sdk::{
member::OriginalSyncRoomMemberEvent, member::OriginalSyncRoomMemberEvent,
message::{MessageType, RoomMessageEventContent}, message::{MessageType, RoomMessageEventContent},
name::RoomNameEventContent, name::RoomNameEventContent,
redaction::{OriginalSyncRoomRedactionEvent, SyncRoomRedactionEvent}, redaction::OriginalSyncRoomRedactionEvent,
}, },
tag::Tags, tag::Tags,
typing::SyncTypingEvent, typing::SyncTypingEvent,
@ -94,7 +94,6 @@ use crate::{
ChatStore, ChatStore,
CreateRoomFlags, CreateRoomFlags,
CreateRoomType, CreateRoomType,
EventLocation,
IambError, IambError,
IambResult, IambResult,
ProgramStore, ProgramStore,
@ -1063,35 +1062,7 @@ impl ClientWorker {
let mut locked = store.lock().await; let mut locked = store.lock().await;
let info = locked.application.get_room_info(room_id.to_owned()); let info = locked.application.get_room_info(room_id.to_owned());
info.redact(ev, room_version);
let Some(redacts) = &ev.redacts else {
return;
};
match info.keys.get(redacts) {
None => return,
Some(EventLocation::Message(None, key)) => {
if let Some(msg) = info.messages.get_mut(key) {
let ev = SyncRoomRedactionEvent::Original(ev);
msg.redact(ev, room_version);
}
},
Some(EventLocation::Message(Some(root), key)) => {
if let Some(thread) = info.threads.get_mut(root) {
if let Some(msg) = thread.get_mut(key) {
let ev = SyncRoomRedactionEvent::Original(ev);
msg.redact(ev, room_version);
}
}
},
Some(EventLocation::Reaction(event_id)) => {
if let Some(reactions) = info.reactions.get_mut(event_id) {
reactions.remove(redacts);
}
info.keys.remove(redacts);
},
}
} }
}, },
); );