mirror of
https://github.com/youwen5/iamb.git
synced 2025-06-19 21:29:52 -07:00
Fix entering thread view when there's no messages yet (#224)
This commit is contained in:
parent
1e9b6cc271
commit
2ac71da9a6
6 changed files with 262 additions and 238 deletions
79
src/base.rs
79
src/base.rs
|
@ -45,6 +45,7 @@ use matrix_sdk::{
|
|||
RoomMessageEventContent,
|
||||
RoomMessageEventContentWithoutRelation,
|
||||
},
|
||||
room::redaction::{OriginalSyncRoomRedactionEvent, SyncRoomRedactionEvent},
|
||||
tag::{TagName, Tags},
|
||||
MessageLikeEvent,
|
||||
},
|
||||
|
@ -54,6 +55,7 @@ use matrix_sdk::{
|
|||
OwnedRoomId,
|
||||
OwnedUserId,
|
||||
RoomId,
|
||||
RoomVersionId,
|
||||
UserId,
|
||||
},
|
||||
RoomState as MatrixRoomState,
|
||||
|
@ -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>>,
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
|
26
src/tests.rs
26
src/tests.rs
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
|
|
@ -28,7 +28,6 @@ use matrix_sdk::{
|
|||
RoomMessageEventContent,
|
||||
TextMessageEventContent,
|
||||
},
|
||||
EventId,
|
||||
OwnedEventId,
|
||||
OwnedRoomId,
|
||||
RoomId,
|
||||
|
@ -72,7 +71,6 @@ use modalkit::prelude::*;
|
|||
|
||||
use crate::base::{
|
||||
DownloadFlags,
|
||||
EventLocation,
|
||||
IambAction,
|
||||
IambBufferId,
|
||||
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> {
|
||||
let thread = self.scrollback.get_thread(info)?;
|
||||
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 {
|
||||
Some(ev)
|
||||
|
@ -205,10 +181,7 @@ impl ChatState {
|
|||
let settings = &store.application.settings;
|
||||
let info = store.application.rooms.get_or_default(self.room_id.clone());
|
||||
|
||||
let msg = self
|
||||
.scrollback
|
||||
.get_mut(&mut info.messages)
|
||||
.ok_or(IambError::NoSelectedMessage)?;
|
||||
let msg = self.scrollback.get_mut(info).ok_or(IambError::NoSelectedMessage)?;
|
||||
|
||||
match act {
|
||||
MessageAction::Cancel(skip_confirm) => {
|
||||
|
@ -441,11 +414,11 @@ impl ChatState {
|
|||
},
|
||||
MessageAction::Unreact(emoji) => {
|
||||
let room = self.get_joined(&store.application.worker)?;
|
||||
let event_id: &EventId = match &msg.event {
|
||||
MessageEvent::EncryptedOriginal(ev) => ev.event_id.as_ref(),
|
||||
MessageEvent::EncryptedRedacted(ev) => ev.event_id.as_ref(),
|
||||
MessageEvent::Original(ev) => ev.event_id.as_ref(),
|
||||
MessageEvent::Local(event_id, _) => event_id.as_ref(),
|
||||
let event_id = match &msg.event {
|
||||
MessageEvent::EncryptedOriginal(ev) => ev.event_id.clone(),
|
||||
MessageEvent::EncryptedRedacted(ev) => ev.event_id.clone(),
|
||||
MessageEvent::Original(ev) => ev.event_id.clone(),
|
||||
MessageEvent::Local(event_id, _) => event_id.clone(),
|
||||
MessageEvent::Redacted(_) => {
|
||||
let msg = "Cannot unreact to a redacted message";
|
||||
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,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
@ -518,7 +491,7 @@ impl ChatState {
|
|||
} else if let Some(thread_root) = self.scrollback.thread() {
|
||||
if let Some(m) = self.get_reply_to(info) {
|
||||
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);
|
||||
} else {
|
||||
// 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) {
|
||||
// Determine whether we have a description to show for the message bar.
|
||||
let desc_spans = match (&state.editing, &state.reply_to) {
|
||||
(None, None) => None,
|
||||
(Some(_), None) => Some(Line::from("Editing message")),
|
||||
(editing, Some(_)) => {
|
||||
state.reply_to.as_ref().and_then(|k| {
|
||||
let room = self.store.application.rooms.get(state.id())?;
|
||||
let msg = room.messages.get(k)?;
|
||||
let desc_spans = match (&state.editing, &state.reply_to, state.thread()) {
|
||||
(None, None, None) => None,
|
||||
(None, None, Some(_)) => Some(Line::from("Replying in thread")),
|
||||
(Some(_), None, None) => Some(Line::from("Editing message")),
|
||||
(Some(_), None, Some(_)) => Some(Line::from("Editing message in thread")),
|
||||
(editing, Some(_), thread) => {
|
||||
self.store.application.rooms.get(state.id()).and_then(|room| {
|
||||
let msg = state.get_reply_to(room)?;
|
||||
let user =
|
||||
self.store.application.settings.get_user_span(msg.sender.as_ref(), room);
|
||||
let prefix = if editing.is_some() {
|
||||
Span::from("Editing reply to ")
|
||||
} else {
|
||||
Span::from("Replying to ")
|
||||
let prefix = match (editing.is_some(), thread.is_some()) {
|
||||
(true, false) => Span::from("Editing reply to "),
|
||||
(true, true) => Span::from("Editing reply in thread to "),
|
||||
(false, false) => Span::from("Replying to "),
|
||||
(false, true) => Span::from("Replying in thread to "),
|
||||
};
|
||||
let spans = Line::from(vec![prefix, user]);
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//! Message scrollback
|
||||
|
||||
use ratatui_image::Image;
|
||||
use regex::Regex;
|
||||
|
||||
|
@ -59,6 +58,11 @@ use crate::{
|
|||
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 {
|
||||
let mut end = &pos;
|
||||
let iter = thread.range(..=&pos).rev().enumerate();
|
||||
|
@ -159,14 +163,16 @@ impl ScrollbackState {
|
|||
self.cursor
|
||||
.timestamp
|
||||
.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 {
|
||||
messages.get_mut(k)
|
||||
thread.get_mut(k)
|
||||
} 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()
|
||||
}
|
||||
|
||||
pub fn get_thread<'a>(&self, info: &'a RoomInfo) -> &'a Messages {
|
||||
if let Some(thread_root) = self.thread.as_ref() {
|
||||
info.threads.get(thread_root).unwrap_or(&info.messages)
|
||||
} else {
|
||||
&info.messages
|
||||
}
|
||||
pub fn get_thread<'a>(&self, info: &'a RoomInfo) -> Option<&'a Messages> {
|
||||
info.get_thread(self.thread.as_deref())
|
||||
}
|
||||
|
||||
pub fn get_thread_mut<'a>(&self, info: &'a mut RoomInfo) -> &'a mut Messages {
|
||||
if let Some(thread_root) = self.thread.as_ref() {
|
||||
info.threads.get_mut(thread_root).unwrap_or(&mut info.messages)
|
||||
} else {
|
||||
&mut info.messages
|
||||
}
|
||||
info.get_thread_mut(self.thread.clone())
|
||||
}
|
||||
|
||||
pub fn messages<'a>(
|
||||
|
@ -195,7 +193,10 @@ impl ScrollbackState {
|
|||
range: EditRange<MessageCursor>,
|
||||
info: &'a RoomInfo,
|
||||
) -> 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 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();
|
||||
|
||||
match (at_top, self.thread.as_ref()) {
|
||||
|
@ -254,7 +255,10 @@ impl ScrollbackState {
|
|||
info: &RoomInfo,
|
||||
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) {
|
||||
key
|
||||
} else {
|
||||
|
@ -319,7 +323,9 @@ impl ScrollbackState {
|
|||
}
|
||||
|
||||
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() {
|
||||
k.0
|
||||
|
@ -389,7 +395,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 = self.get_thread(info)?.first_key_value()?.0.clone();
|
||||
|
||||
Some(start.into())
|
||||
},
|
||||
|
@ -402,7 +408,7 @@ impl ScrollbackState {
|
|||
MoveType::ParagraphBegin(dir) |
|
||||
MoveType::SectionBegin(dir) |
|
||||
MoveType::SectionEnd(dir) => {
|
||||
let thread = self.get_thread(info);
|
||||
let thread = self.get_thread(info)?;
|
||||
|
||||
match dir {
|
||||
MoveDir1D::Previous => nth_before(pos, count, thread).into(),
|
||||
|
@ -455,14 +461,14 @@ impl ScrollbackState {
|
|||
RangeType::XmlTag => None,
|
||||
|
||||
RangeType::Buffer => {
|
||||
let thread = self.get_thread(info);
|
||||
let thread = self.get_thread(info)?;
|
||||
let start = thread.first_key_value()?.0.clone();
|
||||
let end = thread.last_key_value()?.0.clone();
|
||||
|
||||
Some(EditRange::inclusive(start.into(), end.into(), TargetShape::LineWise))
|
||||
},
|
||||
RangeType::Line | RangeType::Paragraph | RangeType::Sentence => {
|
||||
let thread = self.get_thread(info);
|
||||
let thread = self.get_thread(info)?;
|
||||
let count = ctx.resolve(count);
|
||||
|
||||
if count == 0 {
|
||||
|
@ -496,7 +502,7 @@ impl ScrollbackState {
|
|||
mut count: usize,
|
||||
info: &RoomInfo,
|
||||
) -> Option<MessageCursor> {
|
||||
let thread = self.get_thread(info);
|
||||
let thread = self.get_thread(info)?;
|
||||
let mut mc = None;
|
||||
|
||||
for (key, msg) in thread.range(&start..) {
|
||||
|
@ -524,9 +530,12 @@ impl ScrollbackState {
|
|||
mut count: usize,
|
||||
info: &RoomInfo,
|
||||
) -> (Option<MessageCursor>, bool) {
|
||||
let thread = self.get_thread(info);
|
||||
let mut mc = None;
|
||||
|
||||
let Some(thread) = self.get_thread(info) else {
|
||||
return (None, false);
|
||||
};
|
||||
|
||||
for (key, msg) in thread.range(..&end).rev() {
|
||||
if count == 0 {
|
||||
break;
|
||||
|
@ -614,14 +623,8 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
|
|||
store: &mut ProgramStore,
|
||||
) -> EditResult<EditInfo, IambInfo> {
|
||||
let info = store.application.rooms.get_or_default(self.room_id.clone());
|
||||
let thread = self.get_thread(info);
|
||||
let key = if let Some(k) = self.cursor.to_key(thread) {
|
||||
k.clone()
|
||||
} else {
|
||||
let msg = "No messages to select.";
|
||||
let err = EditError::Failure(msg.to_string());
|
||||
return Err(err);
|
||||
};
|
||||
let thread = self.get_thread(info).ok_or_else(no_msgs)?;
|
||||
let key = self.cursor.to_key(thread).ok_or_else(no_msgs)?.clone();
|
||||
|
||||
match operation {
|
||||
EditAction::Motion => {
|
||||
|
@ -644,7 +647,6 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
|
|||
EditTarget::CharJump(mark) | EditTarget::LineJump(mark) => {
|
||||
let mark = ctx.resolve(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) {
|
||||
Some(mc)
|
||||
|
@ -723,7 +725,6 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
|
|||
EditTarget::CharJump(mark) | EditTarget::LineJump(mark) => {
|
||||
let mark = ctx.resolve(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) {
|
||||
self._range_to(c).into()
|
||||
|
@ -821,18 +822,11 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
|
|||
store: &mut ProgramStore,
|
||||
) -> EditResult<EditInfo, IambInfo> {
|
||||
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)?;
|
||||
store.cursors.set_mark(self.id.clone(), name, cursor);
|
||||
|
||||
if let Some(cursor) = self.cursor.to_cursor(thread) {
|
||||
store.cursors.set_mark(self.id.clone(), name, cursor);
|
||||
|
||||
Ok(None)
|
||||
} else {
|
||||
let msg = "Failed to set mark for message";
|
||||
let err = EditError::Failure(msg.into());
|
||||
|
||||
Err(err)
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn complete(
|
||||
|
@ -884,6 +878,7 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
|
|||
store: &mut ProgramStore,
|
||||
) -> EditResult<EditInfo, IambInfo> {
|
||||
let info = store.application.get_room_info(self.room_id.clone());
|
||||
let thread = self.get_thread(info).ok_or_else(no_msgs)?;
|
||||
|
||||
match act {
|
||||
CursorAction::Close(_) => Ok(None),
|
||||
|
@ -901,8 +896,6 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
|
|||
self.push_jump();
|
||||
}
|
||||
|
||||
let thread = self.get_thread(info);
|
||||
|
||||
if let Some(mc) = MessageCursor::from_cursor(ngroup.leader.cursor(), thread) {
|
||||
self.cursor = mc;
|
||||
|
||||
|
@ -916,7 +909,6 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
|
|||
},
|
||||
CursorAction::Save(_) => {
|
||||
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.
|
||||
let cursor = self.cursor.to_cursor(thread).ok_or_else(|| {
|
||||
|
@ -1019,7 +1011,7 @@ impl Promptable<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
|
|||
store: &mut ProgramStore,
|
||||
) -> EditResult<Vec<(Action<IambInfo>, ProgramContext)>, IambInfo> {
|
||||
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 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 settings = &store.application.settings;
|
||||
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() {
|
||||
k.0
|
||||
|
@ -1182,7 +1174,7 @@ impl ScrollActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
|
|||
Axis::Vertical => {
|
||||
let info = store.application.rooms.get_or_default(self.room_id.clone());
|
||||
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() {
|
||||
self.scrollview(key, pos, info, settings);
|
||||
|
@ -1315,12 +1307,14 @@ impl<'a> StatefulWidget for Scrollback<'a> {
|
|||
return;
|
||||
}
|
||||
|
||||
let Some(thread) = state.get_thread(info) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if state.cursor.timestamp < state.viewctx.corner.timestamp {
|
||||
state.viewctx.corner = state.cursor.clone();
|
||||
}
|
||||
|
||||
let thread = state.get_thread(info);
|
||||
|
||||
let cursor = &state.cursor;
|
||||
let cursor_key = if let Some(k) = cursor.to_key(thread) {
|
||||
k
|
||||
|
|
|
@ -52,7 +52,7 @@ use matrix_sdk::{
|
|||
member::OriginalSyncRoomMemberEvent,
|
||||
message::{MessageType, RoomMessageEventContent},
|
||||
name::RoomNameEventContent,
|
||||
redaction::{OriginalSyncRoomRedactionEvent, SyncRoomRedactionEvent},
|
||||
redaction::OriginalSyncRoomRedactionEvent,
|
||||
},
|
||||
tag::Tags,
|
||||
typing::SyncTypingEvent,
|
||||
|
@ -94,7 +94,6 @@ use crate::{
|
|||
ChatStore,
|
||||
CreateRoomFlags,
|
||||
CreateRoomType,
|
||||
EventLocation,
|
||||
IambError,
|
||||
IambResult,
|
||||
ProgramStore,
|
||||
|
@ -1063,35 +1062,7 @@ impl ClientWorker {
|
|||
|
||||
let mut locked = store.lock().await;
|
||||
let info = locked.application.get_room_info(room_id.to_owned());
|
||||
|
||||
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);
|
||||
},
|
||||
}
|
||||
info.redact(ev, room_version);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue