Skip to content

Commit

Permalink
Fix entering thread view when there's no messages yet (#224)
Browse files Browse the repository at this point in the history
  • Loading branch information
ulyssa authored Mar 24, 2024
1 parent 1e9b6cc commit 2ac71da
Show file tree
Hide file tree
Showing 6 changed files with 262 additions and 238 deletions.
79 changes: 77 additions & 2 deletions src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ use matrix_sdk::{
RoomMessageEventContent,
RoomMessageEventContentWithoutRelation,
},
room::redaction::{OriginalSyncRoomRedactionEvent, SyncRoomRedactionEvent},
tag::{TagName, Tags},
MessageLikeEvent,
},
Expand All @@ -54,6 +55,7 @@ use matrix_sdk::{
OwnedRoomId,
OwnedUserId,
RoomId,
RoomVersionId,
UserId,
},
RoomState as MatrixRoomState,
Expand Down Expand Up @@ -729,7 +731,7 @@ pub struct RoomInfo {
pub keys: HashMap<OwnedEventId, EventLocation>,

/// The messages loaded for this room.
pub messages: Messages,
messages: Messages,

/// A map of read markers to display on different events.
pub event_receipts: HashMap<OwnedEventId, HashSet<OwnedUserId>>,
Expand All @@ -745,7 +747,7 @@ pub struct RoomInfo {
pub reactions: HashMap<OwnedEventId, MessageReactions>,

/// 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.
pub fetching: bool,
Expand All @@ -767,6 +769,48 @@ pub struct 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.
pub fn get_reactions(&self, event_id: &EventId) -> Vec<(&str, usize)> {
if let Some(reacts) = self.reactions.get(event_id) {
Expand Down Expand Up @@ -801,6 +845,37 @@ impl RoomInfo {
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.
pub fn insert_reaction(&mut self, react: ReactionEvent) {
match react {
Expand Down
187 changes: 105 additions & 82 deletions src/message/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,106 @@ impl<'a> MessageFormatter<'a> {
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 {
Expand Down Expand Up @@ -835,33 +935,7 @@ impl Message {
.and_then(|e| info.get_event(&e));

if let Some(r) = &reply {
let w = width.saturating_sub(2);
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);
fmt.push_in_reply(r, style, &mut text, info);
}

// Now show the message contents, and the inlined reply if we couldn't find it above.
Expand All @@ -879,63 +953,12 @@ impl Message {
}

if settings.tunables.reaction_display {
// Pass false for emoji_shortcodes parameter because we handle shortcodes ourselves
// before pushing to the printer.
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(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 {
fmt.push_text(emojis.finish(), style, &mut text);
}
let reactions = info.get_reactions(self.event.event_id());
fmt.push_reactions(reactions, 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);
}
if let Some(thread) = info.get_thread(Some(self.event.event_id())) {
fmt.push_thread_reply_count(thread.len(), &mut text);
}

text
Expand Down
26 changes: 6 additions & 20 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use tracing::Level;
use url::Url;

use crate::{
base::{ChatStore, EventLocation, ProgramStore, RoomFetchStatus, RoomInfo},
base::{ChatStore, EventLocation, ProgramStore, RoomInfo},
config::{
user_color,
user_style_from_color,
Expand Down Expand Up @@ -148,25 +148,11 @@ pub fn mock_messages() -> Messages {
}

pub fn mock_room() -> RoomInfo {
RoomInfo {
name: Some("Watercooler Discussion".into()),
tags: None,

keys: mock_keys(),
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,
}
let mut room = RoomInfo::default();
room.name = Some("Watercooler Discussion".into());
room.keys = mock_keys();
*room.get_thread_mut(None) = mock_messages();
room
}

pub fn mock_dirs() -> DirectoryValues {
Expand Down
Loading

0 comments on commit 2ac71da

Please sign in to comment.