Add :replied to go to the message the selected message replied to (#452)
This commit is contained in:
@@ -116,6 +116,8 @@ Redact the selected message with the optional reason.
|
|||||||
Reply to the selected message.
|
Reply to the selected message.
|
||||||
.It Sy ":cancel"
|
.It Sy ":cancel"
|
||||||
Cancel the currently drafted message including replies.
|
Cancel the currently drafted message including replies.
|
||||||
|
.It Sy ":replied"
|
||||||
|
Go to the message the current message replied to.
|
||||||
.It Sy ":upload [path]"
|
.It Sy ":upload [path]"
|
||||||
Upload an attachment and send it to the currently selected room.
|
Upload an attachment and send it to the currently selected room.
|
||||||
.El
|
.El
|
||||||
|
|||||||
56
src/base.rs
56
src/base.rs
@@ -169,6 +169,9 @@ pub enum MessageAction {
|
|||||||
/// Reply to a message.
|
/// Reply to a message.
|
||||||
Reply,
|
Reply,
|
||||||
|
|
||||||
|
/// Go to the message the hovered message replied to.
|
||||||
|
Replied,
|
||||||
|
|
||||||
/// Unreact to a message.
|
/// Unreact to a message.
|
||||||
///
|
///
|
||||||
/// If no specific Emoji to remove to is specified, then all reactions from the user on the
|
/// If no specific Emoji to remove to is specified, then all reactions from the user on the
|
||||||
@@ -1503,14 +1506,19 @@ impl SyncInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags::bitflags! {
|
static MESSAGE_NEED_TTL: u8 = 30;
|
||||||
/// Load-needs
|
|
||||||
#[derive(Debug, Default, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Need: u32 {
|
/// Load messages until the event is loaded or `ttl` loads are exceeded
|
||||||
const EMPTY = 0b00000000;
|
pub struct MessageNeed {
|
||||||
const MESSAGES = 0b00000001;
|
pub event_id: OwnedEventId,
|
||||||
const MEMBERS = 0b00000010;
|
pub ttl: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, PartialEq)]
|
||||||
|
pub struct Need {
|
||||||
|
pub members: bool,
|
||||||
|
pub messages: Option<Vec<MessageNeed>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Things that need loading for different rooms.
|
/// Things that need loading for different rooms.
|
||||||
@@ -1520,9 +1528,31 @@ pub struct RoomNeeds {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RoomNeeds {
|
impl RoomNeeds {
|
||||||
/// Mark a room for needing something to be loaded.
|
/// Mark a room for needing to load members.
|
||||||
pub fn insert(&mut self, room_id: OwnedRoomId, need: Need) {
|
pub fn need_members(&mut self, room_id: OwnedRoomId) {
|
||||||
self.needs.entry(room_id).or_default().insert(need);
|
self.needs.entry(room_id).or_default().members = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark a room for needing to load messages.
|
||||||
|
pub fn need_messages(&mut self, room_id: OwnedRoomId) {
|
||||||
|
self.needs.entry(room_id).or_default().messages.get_or_insert_default();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark a room for needing to load messages until the given message is loaded or a retry limit
|
||||||
|
/// is exceeded.
|
||||||
|
pub fn need_message(&mut self, room_id: OwnedRoomId, event_id: OwnedEventId) {
|
||||||
|
let messages = &mut self.needs.entry(room_id).or_default().messages.get_or_insert_default();
|
||||||
|
|
||||||
|
messages.push(MessageNeed { event_id, ttl: MESSAGE_NEED_TTL });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn need_messages_all(&mut self, room_id: OwnedRoomId, message_needs: Vec<MessageNeed>) {
|
||||||
|
self.needs
|
||||||
|
.entry(room_id)
|
||||||
|
.or_default()
|
||||||
|
.messages
|
||||||
|
.get_or_insert_default()
|
||||||
|
.extend(message_needs);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rooms(&self) -> usize {
|
pub fn rooms(&self) -> usize {
|
||||||
@@ -2300,12 +2330,12 @@ pub mod tests {
|
|||||||
|
|
||||||
let mut need_load = RoomNeeds::default();
|
let mut need_load = RoomNeeds::default();
|
||||||
|
|
||||||
need_load.insert(room_id.clone(), Need::MESSAGES);
|
need_load.need_messages(room_id.clone());
|
||||||
need_load.insert(room_id.clone(), Need::MEMBERS);
|
need_load.need_members(room_id.clone());
|
||||||
|
|
||||||
assert_eq!(need_load.into_iter().collect::<Vec<(OwnedRoomId, Need)>>(), vec![(
|
assert_eq!(need_load.into_iter().collect::<Vec<(OwnedRoomId, Need)>>(), vec![(
|
||||||
room_id,
|
room_id,
|
||||||
Need::MESSAGES | Need::MEMBERS,
|
Need { members: true, messages: Some(Vec::new()) }
|
||||||
)],);
|
)],);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -286,6 +286,17 @@ fn iamb_reply(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
|
|||||||
return Ok(step);
|
return Ok(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn iamb_replied(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
|
||||||
|
if !desc.arg.text.is_empty() {
|
||||||
|
return Result::Err(CommandError::InvalidArgument);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ract = IambAction::from(MessageAction::Replied);
|
||||||
|
let step = CommandStep::Continue(ract.into(), ctx.context.clone());
|
||||||
|
|
||||||
|
return Ok(step);
|
||||||
|
}
|
||||||
|
|
||||||
fn iamb_editor(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
|
fn iamb_editor(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
|
||||||
if !desc.arg.text.is_empty() {
|
if !desc.arg.text.is_empty() {
|
||||||
return Result::Err(CommandError::InvalidArgument);
|
return Result::Err(CommandError::InvalidArgument);
|
||||||
@@ -767,6 +778,11 @@ fn add_iamb_commands(cmds: &mut ProgramCommands) {
|
|||||||
aliases: vec![],
|
aliases: vec![],
|
||||||
f: iamb_reply,
|
f: iamb_reply,
|
||||||
});
|
});
|
||||||
|
cmds.add_command(ProgramCommand {
|
||||||
|
name: "replied".into(),
|
||||||
|
aliases: vec![],
|
||||||
|
f: iamb_replied,
|
||||||
|
});
|
||||||
cmds.add_command(ProgramCommand {
|
cmds.add_command(ProgramCommand {
|
||||||
name: "rooms".into(),
|
name: "rooms".into(),
|
||||||
aliases: vec![],
|
aliases: vec![],
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ use crate::base::{
|
|||||||
IambInfo,
|
IambInfo,
|
||||||
IambResult,
|
IambResult,
|
||||||
MessageAction,
|
MessageAction,
|
||||||
Need,
|
|
||||||
ProgramAction,
|
ProgramAction,
|
||||||
ProgramContext,
|
ProgramContext,
|
||||||
ProgramStore,
|
ProgramStore,
|
||||||
@@ -801,7 +800,7 @@ impl Window<IambInfo> for IambWindow {
|
|||||||
let (room, name, tags) = store.application.worker.get_room(room_id)?;
|
let (room, name, tags) = store.application.worker.get_room(room_id)?;
|
||||||
let room = RoomState::new(room, thread, name, tags, store);
|
let room = RoomState::new(room, thread, name, tags, store);
|
||||||
|
|
||||||
store.application.need_load.insert(room.id().to_owned(), Need::MEMBERS);
|
store.application.need_load.need_members(room.id().to_owned());
|
||||||
return Ok(room.into());
|
return Ok(room.into());
|
||||||
},
|
},
|
||||||
IambId::DirectList => {
|
IambId::DirectList => {
|
||||||
@@ -863,7 +862,7 @@ impl Window<IambInfo> for IambWindow {
|
|||||||
let (room, name, tags) = store.application.worker.get_room(room_id)?;
|
let (room, name, tags) = store.application.worker.get_room(room_id)?;
|
||||||
let room = RoomState::new(room, None, name, tags, store);
|
let room = RoomState::new(room, None, name, tags, store);
|
||||||
|
|
||||||
store.application.need_load.insert(room.id().to_owned(), Need::MEMBERS);
|
store.application.need_load.need_members(room.id().to_owned());
|
||||||
Ok(room.into())
|
Ok(room.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -450,6 +450,21 @@ impl ChatState {
|
|||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
},
|
},
|
||||||
|
MessageAction::Replied => {
|
||||||
|
let Some(reply) = msg.reply_to() else {
|
||||||
|
let msg = "Selected message is not a reply";
|
||||||
|
return Err(UIError::Failure(msg.into()));
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(key) = info.get_message_key(&reply) else {
|
||||||
|
store.application.need_load.need_message(self.room_id.clone(), reply);
|
||||||
|
let msg = "Replied to message will be loaded in the background";
|
||||||
|
return Err(UIError::Failure(msg.into()));
|
||||||
|
};
|
||||||
|
|
||||||
|
self.scrollback.goto_message(key.clone());
|
||||||
|
Ok(None)
|
||||||
|
},
|
||||||
MessageAction::Unreact(reaction, literal) => {
|
MessageAction::Unreact(reaction, literal) => {
|
||||||
let emoji = match reaction {
|
let emoji = match reaction {
|
||||||
reaction if literal => reaction,
|
reaction if literal => reaction,
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ use crate::{
|
|||||||
IambId,
|
IambId,
|
||||||
IambInfo,
|
IambInfo,
|
||||||
IambResult,
|
IambResult,
|
||||||
Need,
|
|
||||||
ProgramContext,
|
ProgramContext,
|
||||||
ProgramStore,
|
ProgramStore,
|
||||||
RoomFetchStatus,
|
RoomFetchStatus,
|
||||||
@@ -165,6 +164,12 @@ impl ScrollbackState {
|
|||||||
self.cursor = MessageCursor::latest();
|
self.cursor = MessageCursor::latest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn goto_message(&mut self, target: MessageKey) {
|
||||||
|
let mut cursor = MessageCursor::new(target, 0);
|
||||||
|
std::mem::swap(&mut cursor, &mut self.cursor);
|
||||||
|
self.jumped.push(cursor);
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the dimensions and placement within the terminal window for this list.
|
/// Set the dimensions and placement within the terminal window for this list.
|
||||||
pub fn set_term_info(&mut self, area: Rect) {
|
pub fn set_term_info(&mut self, area: Rect) {
|
||||||
self.viewctx.dimensions = (area.width as usize, area.height as usize);
|
self.viewctx.dimensions = (area.width as usize, area.height as usize);
|
||||||
@@ -689,10 +694,7 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
|
|||||||
|
|
||||||
let (mc, needs_load) = self.find_message(key, dir, &needle, count, info);
|
let (mc, needs_load) = self.find_message(key, dir, &needle, count, info);
|
||||||
if needs_load {
|
if needs_load {
|
||||||
store
|
store.application.need_load.need_messages(self.room_id.clone());
|
||||||
.application
|
|
||||||
.need_load
|
|
||||||
.insert(self.room_id.clone(), Need::MESSAGES);
|
|
||||||
}
|
}
|
||||||
mc
|
mc
|
||||||
},
|
},
|
||||||
@@ -768,10 +770,7 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
|
|||||||
|
|
||||||
let (mc, needs_load) = self.find_message(key, dir, &needle, count, info);
|
let (mc, needs_load) = self.find_message(key, dir, &needle, count, info);
|
||||||
if needs_load {
|
if needs_load {
|
||||||
store
|
store.application.need_load.need_messages(self.room_id.to_owned());
|
||||||
.application
|
|
||||||
.need_load
|
|
||||||
.insert(self.room_id.to_owned(), Need::MESSAGES);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mc.map(|c| self._range_to(c))
|
mc.map(|c| self._range_to(c))
|
||||||
@@ -1328,10 +1327,7 @@ impl StatefulWidget for Scrollback<'_> {
|
|||||||
k
|
k
|
||||||
} else {
|
} else {
|
||||||
if state.need_more_messages(info) {
|
if state.need_more_messages(info) {
|
||||||
self.store
|
self.store.application.need_load.need_messages(state.room_id.to_owned());
|
||||||
.application
|
|
||||||
.need_load
|
|
||||||
.insert(state.room_id.to_owned(), Need::MESSAGES);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -1435,10 +1431,7 @@ impl StatefulWidget for Scrollback<'_> {
|
|||||||
// Check whether we should load older messages for this room.
|
// Check whether we should load older messages for this room.
|
||||||
if state.need_more_messages(info) {
|
if state.need_more_messages(info) {
|
||||||
// If the top of the screen is the older message, load more.
|
// If the top of the screen is the older message, load more.
|
||||||
self.store
|
self.store.application.need_load.need_messages(state.room_id.to_owned());
|
||||||
.application
|
|
||||||
.need_load
|
|
||||||
.insert(state.room_id.to_owned(), Need::MESSAGES);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info.draw_last = self.store.application.draw_curr;
|
info.draw_last = self.store.application.draw_curr;
|
||||||
@@ -1448,7 +1441,7 @@ impl StatefulWidget for Scrollback<'_> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::tests::*;
|
use crate::{base::Need, tests::*};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_search_messages() {
|
async fn test_search_messages() {
|
||||||
@@ -1493,7 +1486,7 @@ mod tests {
|
|||||||
std::mem::take(&mut store.application.need_load)
|
std::mem::take(&mut store.application.need_load)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect::<Vec<(OwnedRoomId, Need)>>(),
|
.collect::<Vec<(OwnedRoomId, Need)>>(),
|
||||||
vec![(room_id.clone(), Need::MESSAGES)]
|
vec![(room_id.clone(), Need { messages: Some(Vec::new()), members: false })]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Search forward twice to MSG1.
|
// Search forward twice to MSG1.
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ use matrix_sdk::{
|
|||||||
use modalkit::errors::UIError;
|
use modalkit::errors::UIError;
|
||||||
use modalkit::prelude::{EditInfo, InfoMessage};
|
use modalkit::prelude::{EditInfo, InfoMessage};
|
||||||
|
|
||||||
use crate::base::Need;
|
use crate::base::MessageNeed;
|
||||||
use crate::notifications::register_notifications;
|
use crate::notifications::register_notifications;
|
||||||
use crate::{
|
use crate::{
|
||||||
base::{
|
base::{
|
||||||
@@ -216,7 +216,7 @@ async fn update_event_receipts(info: &mut RoomInfo, room: &MatrixRoom, event_id:
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum Plan {
|
enum Plan {
|
||||||
Messages(OwnedRoomId, Option<String>),
|
Messages(OwnedRoomId, Option<String>, Vec<MessageNeed>),
|
||||||
Members(OwnedRoomId),
|
Members(OwnedRoomId),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,8 +225,8 @@ async fn load_plans(store: &AsyncProgramStore) -> Vec<Plan> {
|
|||||||
let ChatStore { need_load, rooms, .. } = &mut locked.application;
|
let ChatStore { need_load, rooms, .. } = &mut locked.application;
|
||||||
let mut plan = Vec::with_capacity(need_load.rooms() * 2);
|
let mut plan = Vec::with_capacity(need_load.rooms() * 2);
|
||||||
|
|
||||||
for (room_id, mut need) in std::mem::take(need_load).into_iter() {
|
for (room_id, need) in std::mem::take(need_load).into_iter() {
|
||||||
if need.contains(Need::MESSAGES) {
|
if let Some(message_need) = need.messages {
|
||||||
let info = rooms.get_or_default(room_id.clone());
|
let info = rooms.get_or_default(room_id.clone());
|
||||||
|
|
||||||
if !info.recently_fetched() && !info.fetching {
|
if !info.recently_fetched() && !info.fetching {
|
||||||
@@ -239,16 +239,11 @@ async fn load_plans(store: &AsyncProgramStore) -> Vec<Plan> {
|
|||||||
RoomFetchStatus::NotStarted => None,
|
RoomFetchStatus::NotStarted => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
plan.push(Plan::Messages(room_id.to_owned(), fetch_id));
|
plan.push(Plan::Messages(room_id.to_owned(), fetch_id, message_need));
|
||||||
need.remove(Need::MESSAGES);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if need.contains(Need::MEMBERS) {
|
if need.members {
|
||||||
plan.push(Plan::Members(room_id.to_owned()));
|
plan.push(Plan::Members(room_id.to_owned()));
|
||||||
need.remove(Need::MEMBERS);
|
|
||||||
}
|
|
||||||
if !need.is_empty() {
|
|
||||||
need_load.insert(room_id, need);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,14 +253,14 @@ async fn load_plans(store: &AsyncProgramStore) -> Vec<Plan> {
|
|||||||
async fn run_plan(client: &Client, store: &AsyncProgramStore, plan: Plan, permits: &Semaphore) {
|
async fn run_plan(client: &Client, store: &AsyncProgramStore, plan: Plan, permits: &Semaphore) {
|
||||||
let permit = permits.acquire().await;
|
let permit = permits.acquire().await;
|
||||||
match plan {
|
match plan {
|
||||||
Plan::Messages(room_id, fetch_id) => {
|
Plan::Messages(room_id, fetch_id, message_need) => {
|
||||||
let limit = MIN_MSG_LOAD;
|
let limit = MIN_MSG_LOAD;
|
||||||
let client = client.clone();
|
let client = client.clone();
|
||||||
let store_clone = store.clone();
|
let store_clone = store.clone();
|
||||||
|
|
||||||
let res = load_older_one(&client, &room_id, fetch_id, limit).await;
|
let res = load_older_one(&client, &room_id, fetch_id, limit).await;
|
||||||
let mut locked = store.lock().await;
|
let mut locked = store.lock().await;
|
||||||
load_insert(room_id, res, locked.deref_mut(), store_clone);
|
load_insert(room_id, res, locked.deref_mut(), store_clone, message_need);
|
||||||
},
|
},
|
||||||
Plan::Members(room_id) => {
|
Plan::Members(room_id) => {
|
||||||
let res = members_load(client, &room_id).await;
|
let res = members_load(client, &room_id).await;
|
||||||
@@ -328,6 +323,7 @@ fn load_insert(
|
|||||||
res: MessageFetchResult,
|
res: MessageFetchResult,
|
||||||
locked: &mut ProgramStore,
|
locked: &mut ProgramStore,
|
||||||
store: AsyncProgramStore,
|
store: AsyncProgramStore,
|
||||||
|
message_needs: Vec<MessageNeed>,
|
||||||
) {
|
) {
|
||||||
let ChatStore { presences, rooms, worker, picker, settings, .. } = &mut locked.application;
|
let ChatStore { presences, rooms, worker, picker, settings, .. } = &mut locked.application;
|
||||||
let info = rooms.get_or_default(room_id.clone());
|
let info = rooms.get_or_default(room_id.clone());
|
||||||
@@ -373,12 +369,25 @@ fn load_insert(
|
|||||||
}
|
}
|
||||||
|
|
||||||
info.fetch_id = fetch_id.map_or(RoomFetchStatus::Done, RoomFetchStatus::HaveMore);
|
info.fetch_id = fetch_id.map_or(RoomFetchStatus::Done, RoomFetchStatus::HaveMore);
|
||||||
|
|
||||||
|
// check if more are needed
|
||||||
|
let needs: Vec<_> = message_needs
|
||||||
|
.into_iter()
|
||||||
|
.filter(|need| !info.keys.contains_key(&need.event_id) && need.ttl > 0)
|
||||||
|
.map(|mut need| {
|
||||||
|
need.ttl -= 1;
|
||||||
|
need
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if !needs.is_empty() {
|
||||||
|
locked.application.need_load.need_messages_all(room_id, needs);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!(room_id = room_id.as_str(), err = e.to_string(), "Failed to load older messages");
|
warn!(room_id = room_id.as_str(), err = e.to_string(), "Failed to load older messages");
|
||||||
|
|
||||||
// Wait and try again.
|
// Wait and try again.
|
||||||
locked.application.need_load.insert(room_id, Need::MESSAGES);
|
locked.application.need_load.need_messages_all(room_id, message_needs);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -573,12 +582,12 @@ pub async fn do_first_sync(client: &Client, store: &AsyncProgramStore) -> Result
|
|||||||
|
|
||||||
for room in sync_info.rooms.iter() {
|
for room in sync_info.rooms.iter() {
|
||||||
let room_id = room.as_ref().0.room_id().to_owned();
|
let room_id = room.as_ref().0.room_id().to_owned();
|
||||||
need_load.insert(room_id, Need::MESSAGES);
|
need_load.need_messages(room_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
for room in sync_info.dms.iter() {
|
for room in sync_info.dms.iter() {
|
||||||
let room_id = room.as_ref().0.room_id().to_owned();
|
let room_id = room.as_ref().0.room_id().to_owned();
|
||||||
need_load.insert(room_id, Need::MESSAGES);
|
need_load.need_messages(room_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in New Issue
Block a user