Skip to content

Commit 3149f79

Browse files
authored
Add :replied to go to the message the selected message replied to (#452)
1 parent 7ccb1cb commit 3149f79

File tree

7 files changed

+116
-52
lines changed

7 files changed

+116
-52
lines changed

docs/iamb.1

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ Redact the selected message with the optional reason.
116116
Reply to the selected message.
117117
.It Sy ":cancel"
118118
Cancel the currently drafted message including replies.
119+
.It Sy ":replied"
120+
Go to the message the current message replied to.
119121
.It Sy ":upload [path]"
120122
Upload an attachment and send it to the currently selected room.
121123
.El

src/base.rs

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@ pub enum MessageAction {
169169
/// Reply to a message.
170170
Reply,
171171

172+
/// Go to the message the hovered message replied to.
173+
Replied,
174+
172175
/// Unreact to a message.
173176
///
174177
/// If no specific Emoji to remove to is specified, then all reactions from the user on the
@@ -1503,14 +1506,19 @@ impl SyncInfo {
15031506
}
15041507
}
15051508

1506-
bitflags::bitflags! {
1507-
/// Load-needs
1508-
#[derive(Debug, Default, PartialEq)]
1509-
pub struct Need: u32 {
1510-
const EMPTY = 0b00000000;
1511-
const MESSAGES = 0b00000001;
1512-
const MEMBERS = 0b00000010;
1513-
}
1509+
static MESSAGE_NEED_TTL: u8 = 30;
1510+
1511+
#[derive(Debug, PartialEq)]
1512+
/// Load messages until the event is loaded or `ttl` loads are exceeded
1513+
pub struct MessageNeed {
1514+
pub event_id: OwnedEventId,
1515+
pub ttl: u8,
1516+
}
1517+
1518+
#[derive(Default, Debug, PartialEq)]
1519+
pub struct Need {
1520+
pub members: bool,
1521+
pub messages: Option<Vec<MessageNeed>>,
15141522
}
15151523

15161524
/// Things that need loading for different rooms.
@@ -1520,9 +1528,31 @@ pub struct RoomNeeds {
15201528
}
15211529

15221530
impl RoomNeeds {
1523-
/// Mark a room for needing something to be loaded.
1524-
pub fn insert(&mut self, room_id: OwnedRoomId, need: Need) {
1525-
self.needs.entry(room_id).or_default().insert(need);
1531+
/// Mark a room for needing to load members.
1532+
pub fn need_members(&mut self, room_id: OwnedRoomId) {
1533+
self.needs.entry(room_id).or_default().members = true;
1534+
}
1535+
1536+
/// Mark a room for needing to load messages.
1537+
pub fn need_messages(&mut self, room_id: OwnedRoomId) {
1538+
self.needs.entry(room_id).or_default().messages.get_or_insert_default();
1539+
}
1540+
1541+
/// Mark a room for needing to load messages until the given message is loaded or a retry limit
1542+
/// is exceeded.
1543+
pub fn need_message(&mut self, room_id: OwnedRoomId, event_id: OwnedEventId) {
1544+
let messages = &mut self.needs.entry(room_id).or_default().messages.get_or_insert_default();
1545+
1546+
messages.push(MessageNeed { event_id, ttl: MESSAGE_NEED_TTL });
1547+
}
1548+
1549+
pub fn need_messages_all(&mut self, room_id: OwnedRoomId, message_needs: Vec<MessageNeed>) {
1550+
self.needs
1551+
.entry(room_id)
1552+
.or_default()
1553+
.messages
1554+
.get_or_insert_default()
1555+
.extend(message_needs);
15261556
}
15271557

15281558
pub fn rooms(&self) -> usize {
@@ -2300,12 +2330,12 @@ pub mod tests {
23002330

23012331
let mut need_load = RoomNeeds::default();
23022332

2303-
need_load.insert(room_id.clone(), Need::MESSAGES);
2304-
need_load.insert(room_id.clone(), Need::MEMBERS);
2333+
need_load.need_messages(room_id.clone());
2334+
need_load.need_members(room_id.clone());
23052335

23062336
assert_eq!(need_load.into_iter().collect::<Vec<(OwnedRoomId, Need)>>(), vec![(
23072337
room_id,
2308-
Need::MESSAGES | Need::MEMBERS,
2338+
Need { members: true, messages: Some(Vec::new()) }
23092339
)],);
23102340
}
23112341

src/commands.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,17 @@ fn iamb_reply(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
286286
return Ok(step);
287287
}
288288

289+
fn iamb_replied(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
290+
if !desc.arg.text.is_empty() {
291+
return Result::Err(CommandError::InvalidArgument);
292+
}
293+
294+
let ract = IambAction::from(MessageAction::Replied);
295+
let step = CommandStep::Continue(ract.into(), ctx.context.clone());
296+
297+
return Ok(step);
298+
}
299+
289300
fn iamb_editor(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
290301
if !desc.arg.text.is_empty() {
291302
return Result::Err(CommandError::InvalidArgument);
@@ -767,6 +778,11 @@ fn add_iamb_commands(cmds: &mut ProgramCommands) {
767778
aliases: vec![],
768779
f: iamb_reply,
769780
});
781+
cmds.add_command(ProgramCommand {
782+
name: "replied".into(),
783+
aliases: vec![],
784+
f: iamb_replied,
785+
});
770786
cmds.add_command(ProgramCommand {
771787
name: "rooms".into(),
772788
aliases: vec![],

src/windows/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ use crate::base::{
6666
IambInfo,
6767
IambResult,
6868
MessageAction,
69-
Need,
7069
ProgramAction,
7170
ProgramContext,
7271
ProgramStore,
@@ -801,7 +800,7 @@ impl Window<IambInfo> for IambWindow {
801800
let (room, name, tags) = store.application.worker.get_room(room_id)?;
802801
let room = RoomState::new(room, thread, name, tags, store);
803802

804-
store.application.need_load.insert(room.id().to_owned(), Need::MEMBERS);
803+
store.application.need_load.need_members(room.id().to_owned());
805804
return Ok(room.into());
806805
},
807806
IambId::DirectList => {
@@ -863,7 +862,7 @@ impl Window<IambInfo> for IambWindow {
863862
let (room, name, tags) = store.application.worker.get_room(room_id)?;
864863
let room = RoomState::new(room, None, name, tags, store);
865864

866-
store.application.need_load.insert(room.id().to_owned(), Need::MEMBERS);
865+
store.application.need_load.need_members(room.id().to_owned());
867866
Ok(room.into())
868867
}
869868
}

src/windows/room/chat.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,21 @@ impl ChatState {
450450

451451
Ok(None)
452452
},
453+
MessageAction::Replied => {
454+
let Some(reply) = msg.reply_to() else {
455+
let msg = "Selected message is not a reply";
456+
return Err(UIError::Failure(msg.into()));
457+
};
458+
459+
let Some(key) = info.get_message_key(&reply) else {
460+
store.application.need_load.need_message(self.room_id.clone(), reply);
461+
let msg = "Replied to message will be loaded in the background";
462+
return Err(UIError::Failure(msg.into()));
463+
};
464+
465+
self.scrollback.goto_message(key.clone());
466+
Ok(None)
467+
},
453468
MessageAction::Unreact(reaction, literal) => {
454469
let emoji = match reaction {
455470
reaction if literal => reaction,

src/windows/room/scrollback.rs

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ use crate::{
4747
IambId,
4848
IambInfo,
4949
IambResult,
50-
Need,
5150
ProgramContext,
5251
ProgramStore,
5352
RoomFetchStatus,
@@ -165,6 +164,12 @@ impl ScrollbackState {
165164
self.cursor = MessageCursor::latest();
166165
}
167166

167+
pub fn goto_message(&mut self, target: MessageKey) {
168+
let mut cursor = MessageCursor::new(target, 0);
169+
std::mem::swap(&mut cursor, &mut self.cursor);
170+
self.jumped.push(cursor);
171+
}
172+
168173
/// Set the dimensions and placement within the terminal window for this list.
169174
pub fn set_term_info(&mut self, area: Rect) {
170175
self.viewctx.dimensions = (area.width as usize, area.height as usize);
@@ -689,10 +694,7 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
689694

690695
let (mc, needs_load) = self.find_message(key, dir, &needle, count, info);
691696
if needs_load {
692-
store
693-
.application
694-
.need_load
695-
.insert(self.room_id.clone(), Need::MESSAGES);
697+
store.application.need_load.need_messages(self.room_id.clone());
696698
}
697699
mc
698700
},
@@ -768,10 +770,7 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
768770

769771
let (mc, needs_load) = self.find_message(key, dir, &needle, count, info);
770772
if needs_load {
771-
store
772-
.application
773-
.need_load
774-
.insert(self.room_id.to_owned(), Need::MESSAGES);
773+
store.application.need_load.need_messages(self.room_id.to_owned());
775774
}
776775

777776
mc.map(|c| self._range_to(c))
@@ -1328,10 +1327,7 @@ impl StatefulWidget for Scrollback<'_> {
13281327
k
13291328
} else {
13301329
if state.need_more_messages(info) {
1331-
self.store
1332-
.application
1333-
.need_load
1334-
.insert(state.room_id.to_owned(), Need::MESSAGES);
1330+
self.store.application.need_load.need_messages(state.room_id.to_owned());
13351331
}
13361332
return;
13371333
};
@@ -1435,10 +1431,7 @@ impl StatefulWidget for Scrollback<'_> {
14351431
// Check whether we should load older messages for this room.
14361432
if state.need_more_messages(info) {
14371433
// If the top of the screen is the older message, load more.
1438-
self.store
1439-
.application
1440-
.need_load
1441-
.insert(state.room_id.to_owned(), Need::MESSAGES);
1434+
self.store.application.need_load.need_messages(state.room_id.to_owned());
14421435
}
14431436

14441437
info.draw_last = self.store.application.draw_curr;
@@ -1448,7 +1441,7 @@ impl StatefulWidget for Scrollback<'_> {
14481441
#[cfg(test)]
14491442
mod tests {
14501443
use super::*;
1451-
use crate::tests::*;
1444+
use crate::{base::Need, tests::*};
14521445

14531446
#[tokio::test]
14541447
async fn test_search_messages() {
@@ -1493,7 +1486,7 @@ mod tests {
14931486
std::mem::take(&mut store.application.need_load)
14941487
.into_iter()
14951488
.collect::<Vec<(OwnedRoomId, Need)>>(),
1496-
vec![(room_id.clone(), Need::MESSAGES)]
1489+
vec![(room_id.clone(), Need { messages: Some(Vec::new()), members: false })]
14971490
);
14981491

14991492
// Search forward twice to MSG1.

src/worker.rs

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ use matrix_sdk::{
8888
use modalkit::errors::UIError;
8989
use modalkit::prelude::{EditInfo, InfoMessage};
9090

91-
use crate::base::Need;
91+
use crate::base::MessageNeed;
9292
use crate::notifications::register_notifications;
9393
use crate::{
9494
base::{
@@ -216,7 +216,7 @@ async fn update_event_receipts(info: &mut RoomInfo, room: &MatrixRoom, event_id:
216216

217217
#[derive(Debug)]
218218
enum Plan {
219-
Messages(OwnedRoomId, Option<String>),
219+
Messages(OwnedRoomId, Option<String>, Vec<MessageNeed>),
220220
Members(OwnedRoomId),
221221
}
222222

@@ -225,8 +225,8 @@ async fn load_plans(store: &AsyncProgramStore) -> Vec<Plan> {
225225
let ChatStore { need_load, rooms, .. } = &mut locked.application;
226226
let mut plan = Vec::with_capacity(need_load.rooms() * 2);
227227

228-
for (room_id, mut need) in std::mem::take(need_load).into_iter() {
229-
if need.contains(Need::MESSAGES) {
228+
for (room_id, need) in std::mem::take(need_load).into_iter() {
229+
if let Some(message_need) = need.messages {
230230
let info = rooms.get_or_default(room_id.clone());
231231

232232
if !info.recently_fetched() && !info.fetching {
@@ -239,16 +239,11 @@ async fn load_plans(store: &AsyncProgramStore) -> Vec<Plan> {
239239
RoomFetchStatus::NotStarted => None,
240240
};
241241

242-
plan.push(Plan::Messages(room_id.to_owned(), fetch_id));
243-
need.remove(Need::MESSAGES);
242+
plan.push(Plan::Messages(room_id.to_owned(), fetch_id, message_need));
244243
}
245244
}
246-
if need.contains(Need::MEMBERS) {
245+
if need.members {
247246
plan.push(Plan::Members(room_id.to_owned()));
248-
need.remove(Need::MEMBERS);
249-
}
250-
if !need.is_empty() {
251-
need_load.insert(room_id, need);
252247
}
253248
}
254249

@@ -258,14 +253,14 @@ async fn load_plans(store: &AsyncProgramStore) -> Vec<Plan> {
258253
async fn run_plan(client: &Client, store: &AsyncProgramStore, plan: Plan, permits: &Semaphore) {
259254
let permit = permits.acquire().await;
260255
match plan {
261-
Plan::Messages(room_id, fetch_id) => {
256+
Plan::Messages(room_id, fetch_id, message_need) => {
262257
let limit = MIN_MSG_LOAD;
263258
let client = client.clone();
264259
let store_clone = store.clone();
265260

266261
let res = load_older_one(&client, &room_id, fetch_id, limit).await;
267262
let mut locked = store.lock().await;
268-
load_insert(room_id, res, locked.deref_mut(), store_clone);
263+
load_insert(room_id, res, locked.deref_mut(), store_clone, message_need);
269264
},
270265
Plan::Members(room_id) => {
271266
let res = members_load(client, &room_id).await;
@@ -328,6 +323,7 @@ fn load_insert(
328323
res: MessageFetchResult,
329324
locked: &mut ProgramStore,
330325
store: AsyncProgramStore,
326+
message_needs: Vec<MessageNeed>,
331327
) {
332328
let ChatStore { presences, rooms, worker, picker, settings, .. } = &mut locked.application;
333329
let info = rooms.get_or_default(room_id.clone());
@@ -373,12 +369,25 @@ fn load_insert(
373369
}
374370

375371
info.fetch_id = fetch_id.map_or(RoomFetchStatus::Done, RoomFetchStatus::HaveMore);
372+
373+
// check if more are needed
374+
let needs: Vec<_> = message_needs
375+
.into_iter()
376+
.filter(|need| !info.keys.contains_key(&need.event_id) && need.ttl > 0)
377+
.map(|mut need| {
378+
need.ttl -= 1;
379+
need
380+
})
381+
.collect();
382+
if !needs.is_empty() {
383+
locked.application.need_load.need_messages_all(room_id, needs);
384+
}
376385
},
377386
Err(e) => {
378387
warn!(room_id = room_id.as_str(), err = e.to_string(), "Failed to load older messages");
379388

380389
// Wait and try again.
381-
locked.application.need_load.insert(room_id, Need::MESSAGES);
390+
locked.application.need_load.need_messages_all(room_id, message_needs);
382391
},
383392
}
384393
}
@@ -573,12 +582,12 @@ pub async fn do_first_sync(client: &Client, store: &AsyncProgramStore) -> Result
573582

574583
for room in sync_info.rooms.iter() {
575584
let room_id = room.as_ref().0.room_id().to_owned();
576-
need_load.insert(room_id, Need::MESSAGES);
585+
need_load.need_messages(room_id);
577586
}
578587

579588
for room in sync_info.dms.iter() {
580589
let room_id = room.as_ref().0.room_id().to_owned();
581-
need_load.insert(room_id, Need::MESSAGES);
590+
need_load.need_messages(room_id);
582591
}
583592

584593
Ok(())

0 commit comments

Comments
 (0)