Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/iamb.1
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ Redact the selected message with the optional reason.
Reply to the selected message.
.It Sy ":cancel"
Cancel the currently drafted message including replies.
.It Sy ":replied"
Go to the message the current message replied to.
.It Sy ":upload [path]"
Upload an attachment and send it to the currently selected room.
.El
Expand Down
58 changes: 44 additions & 14 deletions src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ pub enum MessageAction {
/// Reply to a message.
Reply,

/// Go to the message the hovered message replied to.
Replied,

/// Unreact to a message.
///
/// If no specific Emoji to remove to is specified, then all reactions from the user on the
Expand Down Expand Up @@ -1474,14 +1477,19 @@ impl SyncInfo {
}
}

bitflags::bitflags! {
/// Load-needs
#[derive(Debug, Default, PartialEq)]
pub struct Need: u32 {
const EMPTY = 0b00000000;
const MESSAGES = 0b00000001;
const MEMBERS = 0b00000010;
}
static MESSAGE_NEED_TTL: u8 = 30;

#[derive(Debug, PartialEq)]
/// Load messages until the event is loaded or `ttl` loads are exceeded
pub struct MessageNeed {
pub event_id: OwnedEventId,
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.
Expand All @@ -1491,9 +1499,31 @@ pub struct RoomNeeds {
}

impl RoomNeeds {
/// Mark a room for needing something to be loaded.
pub fn insert(&mut self, room_id: OwnedRoomId, need: Need) {
self.needs.entry(room_id).or_default().insert(need);
/// Mark a room for needing to load members.
pub fn need_members(&mut self, room_id: OwnedRoomId) {
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 {
Expand Down Expand Up @@ -2275,12 +2305,12 @@ pub mod tests {

let mut need_load = RoomNeeds::default();

need_load.insert(room_id.clone(), Need::MESSAGES);
need_load.insert(room_id.clone(), Need::MEMBERS);
need_load.need_messages(room_id.clone());
need_load.need_members(room_id.clone());

assert_eq!(need_load.into_iter().collect::<Vec<(OwnedRoomId, Need)>>(), vec![(
room_id,
Need::MESSAGES | Need::MEMBERS,
Need { members: true, messages: Some(Vec::new()) }
)],);
}

Expand Down
16 changes: 16 additions & 0 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,17 @@ fn iamb_reply(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
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 {
if !desc.arg.text.is_empty() {
return Result::Err(CommandError::InvalidArgument);
Expand Down Expand Up @@ -751,6 +762,11 @@ fn add_iamb_commands(cmds: &mut ProgramCommands) {
aliases: vec![],
f: iamb_reply,
});
cmds.add_command(ProgramCommand {
name: "replied".into(),
aliases: vec![],
f: iamb_replied,
});
cmds.add_command(ProgramCommand {
name: "rooms".into(),
aliases: vec![],
Expand Down
5 changes: 2 additions & 3 deletions src/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ use crate::base::{
IambInfo,
IambResult,
MessageAction,
Need,
ProgramAction,
ProgramContext,
ProgramStore,
Expand Down Expand Up @@ -801,7 +800,7 @@ impl Window<IambInfo> for IambWindow {
let (room, name, tags) = store.application.worker.get_room(room_id)?;
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());
},
IambId::DirectList => {
Expand Down Expand Up @@ -863,7 +862,7 @@ impl Window<IambInfo> for IambWindow {
let (room, name, tags) = store.application.worker.get_room(room_id)?;
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())
}
}
Expand Down
15 changes: 15 additions & 0 deletions src/windows/room/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,21 @@ impl ChatState {

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) => {
let emoji = match reaction {
reaction if literal => reaction,
Expand Down
31 changes: 12 additions & 19 deletions src/windows/room/scrollback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ use crate::{
IambId,
IambInfo,
IambResult,
Need,
ProgramContext,
ProgramStore,
RoomFetchStatus,
Expand Down Expand Up @@ -165,6 +164,12 @@ impl ScrollbackState {
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.
pub fn set_term_info(&mut self, area: Rect) {
self.viewctx.dimensions = (area.width as usize, area.height as usize);
Expand Down Expand Up @@ -689,10 +694,7 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {

let (mc, needs_load) = self.find_message(key, dir, &needle, count, info);
if needs_load {
store
.application
.need_load
.insert(self.room_id.clone(), Need::MESSAGES);
store.application.need_load.need_messages(self.room_id.clone());
}
mc
},
Expand Down Expand Up @@ -768,10 +770,7 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {

let (mc, needs_load) = self.find_message(key, dir, &needle, count, info);
if needs_load {
store
.application
.need_load
.insert(self.room_id.to_owned(), Need::MESSAGES);
store.application.need_load.need_messages(self.room_id.to_owned());
}

mc.map(|c| self._range_to(c))
Expand Down Expand Up @@ -1328,10 +1327,7 @@ impl StatefulWidget for Scrollback<'_> {
k
} else {
if state.need_more_messages(info) {
self.store
.application
.need_load
.insert(state.room_id.to_owned(), Need::MESSAGES);
self.store.application.need_load.need_messages(state.room_id.to_owned());
}
return;
};
Expand Down Expand Up @@ -1435,10 +1431,7 @@ impl StatefulWidget for Scrollback<'_> {
// Check whether we should load older messages for this room.
if state.need_more_messages(info) {
// If the top of the screen is the older message, load more.
self.store
.application
.need_load
.insert(state.room_id.to_owned(), Need::MESSAGES);
self.store.application.need_load.need_messages(state.room_id.to_owned());
}

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

#[tokio::test]
async fn test_search_messages() {
Expand Down Expand Up @@ -1493,7 +1486,7 @@ mod tests {
std::mem::take(&mut store.application.need_load)
.into_iter()
.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.
Expand Down
41 changes: 25 additions & 16 deletions src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ use matrix_sdk::{
use modalkit::errors::UIError;
use modalkit::prelude::{EditInfo, InfoMessage};

use crate::base::Need;
use crate::base::MessageNeed;
use crate::notifications::register_notifications;
use crate::{
base::{
Expand Down Expand Up @@ -216,7 +216,7 @@ async fn update_event_receipts(info: &mut RoomInfo, room: &MatrixRoom, event_id:

#[derive(Debug)]
enum Plan {
Messages(OwnedRoomId, Option<String>),
Messages(OwnedRoomId, Option<String>, Vec<MessageNeed>),
Members(OwnedRoomId),
}

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

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

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

plan.push(Plan::Messages(room_id.to_owned(), fetch_id));
need.remove(Need::MESSAGES);
plan.push(Plan::Messages(room_id.to_owned(), fetch_id, message_need));
}
}
if need.contains(Need::MEMBERS) {
if need.members {
plan.push(Plan::Members(room_id.to_owned()));
need.remove(Need::MEMBERS);
}
if !need.is_empty() {
need_load.insert(room_id, need);
}
}

Expand All @@ -258,14 +253,14 @@ async fn load_plans(store: &AsyncProgramStore) -> Vec<Plan> {
async fn run_plan(client: &Client, store: &AsyncProgramStore, plan: Plan, permits: &Semaphore) {
let permit = permits.acquire().await;
match plan {
Plan::Messages(room_id, fetch_id) => {
Plan::Messages(room_id, fetch_id, message_need) => {
let limit = MIN_MSG_LOAD;
let client = client.clone();
let store_clone = store.clone();

let res = load_older_one(&client, &room_id, fetch_id, limit).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) => {
let res = members_load(client, &room_id).await;
Expand Down Expand Up @@ -325,6 +320,7 @@ fn load_insert(
res: MessageFetchResult,
locked: &mut ProgramStore,
store: AsyncProgramStore,
message_needs: Vec<MessageNeed>,
) {
let ChatStore { presences, rooms, worker, picker, settings, .. } = &mut locked.application;
let info = rooms.get_or_default(room_id.clone());
Expand Down Expand Up @@ -370,12 +366,25 @@ fn load_insert(
}

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) => {
warn!(room_id = room_id.as_str(), err = e.to_string(), "Failed to load older messages");

// Wait and try again.
locked.application.need_load.insert(room_id, Need::MESSAGES);
locked.application.need_load.need_messages_all(room_id, message_needs);
},
}
}
Expand Down Expand Up @@ -566,12 +575,12 @@ pub async fn do_first_sync(client: &Client, store: &AsyncProgramStore) -> Result

for room in sync_info.rooms.iter() {
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() {
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(())
Expand Down
Loading