diff --git a/src/logger.rs b/src/logger.rs index 1973cfb..127811b 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -123,6 +123,22 @@ impl MsgStore for Logger { self.next_tree_id(id).await } + async fn oldest_unseen_msg_id(&self) -> Option { + None + } + + async fn newest_unseen_msg_id(&self) -> Option { + None + } + + async fn older_unseen_msg_id(&self, _id: &usize) -> Option { + None + } + + async fn newer_unseen_msg_id(&self, _id: &usize) -> Option { + None + } + async fn set_seen(&self, _id: &usize, _seen: bool) {} async fn set_older_seen(&self, _id: &usize, _seen: bool) {} diff --git a/src/store.rs b/src/store.rs index 14882cf..2a3c482 100644 --- a/src/store.rs +++ b/src/store.rs @@ -129,6 +129,10 @@ pub trait MsgStore { async fn newest_msg_id(&self) -> Option; async fn older_msg_id(&self, id: &M::Id) -> Option; async fn newer_msg_id(&self, id: &M::Id) -> Option; + async fn oldest_unseen_msg_id(&self) -> Option; + async fn newest_unseen_msg_id(&self) -> Option; + async fn older_unseen_msg_id(&self, id: &M::Id) -> Option; + async fn newer_unseen_msg_id(&self, id: &M::Id) -> Option; async fn set_seen(&self, id: &M::Id, seen: bool); async fn set_older_seen(&self, id: &M::Id, seen: bool); } diff --git a/src/ui/chat/tree.rs b/src/ui/chat/tree.rs index 7e41382..ee87a86 100644 --- a/src/ui/chat/tree.rs +++ b/src/ui/chat/tree.rs @@ -65,6 +65,7 @@ impl> InnerTreeViewState { pub fn list_movement_key_bindings(&self, bindings: &mut KeyBindingsList) { bindings.binding("j/k, ↓/↑", "move cursor up/down"); bindings.binding("h/l, ←/→", "move cursor chronologically"); + bindings.binding("H/L, ctrl+←/→", "move cursor to prev/next unseen message"); bindings.binding("g, home", "move cursor to top"); bindings.binding("G, end", "move cursor to bottom"); bindings.binding("ctrl+y/e", "scroll up/down a line"); @@ -80,6 +81,8 @@ impl> InnerTreeViewState { key!('j') | key!(Down) => self.move_cursor_down().await, key!('h') | key!(Left) => self.move_cursor_older().await, key!('l') | key!(Right) => self.move_cursor_newer().await, + key!('H') | key!(Ctrl + Left) => self.move_cursor_older_unseen().await, + key!('L') | key!(Ctrl + Right) => self.move_cursor_newer_unseen().await, key!('g') | key!(Home) => self.move_cursor_to_top().await, key!('G') | key!(End) => self.move_cursor_to_bottom().await, key!(Ctrl + 'y') => self.scroll_up(1), diff --git a/src/ui/chat/tree/cursor.rs b/src/ui/chat/tree/cursor.rs index 7f0effc..a2f940d 100644 --- a/src/ui/chat/tree/cursor.rs +++ b/src/ui/chat/tree/cursor.rs @@ -254,6 +254,40 @@ impl> InnerTreeViewState { self.correction = Some(Correction::MakeCursorVisible); } + pub async fn move_cursor_older_unseen(&mut self) { + match &mut self.cursor { + Cursor::Msg(id) => { + if let Some(prev_id) = self.store.older_unseen_msg_id(id).await { + *id = prev_id; + } + } + Cursor::Bottom | Cursor::Pseudo { .. } => { + if let Some(id) = self.store.newest_unseen_msg_id().await { + self.cursor = Cursor::Msg(id); + } + } + _ => {} + } + self.correction = Some(Correction::MakeCursorVisible); + } + + pub async fn move_cursor_newer_unseen(&mut self) { + match &mut self.cursor { + Cursor::Msg(id) => { + if let Some(prev_id) = self.store.newer_unseen_msg_id(id).await { + *id = prev_id; + } else { + self.cursor = Cursor::Bottom; + } + } + Cursor::Pseudo { .. } => { + self.cursor = Cursor::Bottom; + } + _ => {} + } + self.correction = Some(Correction::MakeCursorVisible); + } + pub async fn move_cursor_to_top(&mut self) { if let Some(first_tree_id) = self.store.first_tree_id().await { self.cursor = Cursor::Msg(first_tree_id); diff --git a/src/vault/euph.rs b/src/vault/euph.rs index 140fbdf..f9efb8f 100644 --- a/src/vault/euph.rs +++ b/src/vault/euph.rs @@ -259,6 +259,52 @@ impl MsgStore for EuphVault { rx.await.unwrap() } + async fn oldest_unseen_msg_id(&self) -> Option { + // TODO vault::Error + let (tx, rx) = oneshot::channel(); + let request = EuphRequest::GetOldestUnseenMsgId { + room: self.room.clone(), + result: tx, + }; + let _ = self.vault.tx.send(request.into()); + rx.await.unwrap() + } + + async fn newest_unseen_msg_id(&self) -> Option { + // TODO vault::Error + let (tx, rx) = oneshot::channel(); + let request = EuphRequest::GetNewestUnseenMsgId { + room: self.room.clone(), + result: tx, + }; + let _ = self.vault.tx.send(request.into()); + rx.await.unwrap() + } + + async fn older_unseen_msg_id(&self, id: &Snowflake) -> Option { + // TODO vault::Error + let (tx, rx) = oneshot::channel(); + let request = EuphRequest::GetOlderUnseenMsgId { + room: self.room.clone(), + id: *id, + result: tx, + }; + let _ = self.vault.tx.send(request.into()); + rx.await.unwrap() + } + + async fn newer_unseen_msg_id(&self, id: &Snowflake) -> Option { + // TODO vault::Error + let (tx, rx) = oneshot::channel(); + let request = EuphRequest::GetNewerUnseenMsgId { + room: self.room.clone(), + id: *id, + result: tx, + }; + let _ = self.vault.tx.send(request.into()); + rx.await.unwrap() + } + async fn set_seen(&self, id: &Snowflake, seen: bool) { let request = EuphRequest::SetSeen { room: self.room.clone(), @@ -368,6 +414,24 @@ pub(super) enum EuphRequest { id: Snowflake, result: oneshot::Sender>, }, + GetOlderUnseenMsgId { + room: String, + id: Snowflake, + result: oneshot::Sender>, + }, + GetOldestUnseenMsgId { + room: String, + result: oneshot::Sender>, + }, + GetNewestUnseenMsgId { + room: String, + result: oneshot::Sender>, + }, + GetNewerUnseenMsgId { + room: String, + id: Snowflake, + result: oneshot::Sender>, + }, SetSeen { room: String, id: Snowflake, @@ -427,6 +491,18 @@ impl EuphRequest { EuphRequest::GetNewerMsgId { room, id, result } => { Self::get_newer_msg_id(conn, room, id, result) } + EuphRequest::GetOldestUnseenMsgId { room, result } => { + Self::get_oldest_unseen_msg_id(conn, room, result) + } + EuphRequest::GetNewestUnseenMsgId { room, result } => { + Self::get_newest_unseen_msg_id(conn, room, result) + } + EuphRequest::GetOlderUnseenMsgId { room, id, result } => { + Self::get_older_unseen_msg_id(conn, room, id, result) + } + EuphRequest::GetNewerUnseenMsgId { room, id, result } => { + Self::get_newer_unseen_msg_id(conn, room, id, result) + } EuphRequest::SetSeen { room, id, seen } => Self::set_seen(conn, room, id, seen), EuphRequest::SetOlderSeen { room, id, seen } => { Self::set_older_seen(conn, room, id, seen) @@ -1030,6 +1106,98 @@ impl EuphRequest { Ok(()) } + fn get_oldest_unseen_msg_id( + conn: &Connection, + room: String, + result: oneshot::Sender>, + ) -> rusqlite::Result<()> { + let tree = conn + .prepare( + " + SELECT id + FROM euph_msgs + WHERE room = ? + AND NOT seen + ORDER BY id ASC + LIMIT 1 + ", + )? + .query_row([room], |row| row.get(0)) + .optional()?; + let _ = result.send(tree); + Ok(()) + } + + fn get_newest_unseen_msg_id( + conn: &Connection, + room: String, + result: oneshot::Sender>, + ) -> rusqlite::Result<()> { + let tree = conn + .prepare( + " + SELECT id + FROM euph_msgs + WHERE room = ? + AND NOT seen + ORDER BY id DESC + LIMIT 1 + ", + )? + .query_row([room], |row| row.get(0)) + .optional()?; + let _ = result.send(tree); + Ok(()) + } + + fn get_older_unseen_msg_id( + conn: &Connection, + room: String, + id: Snowflake, + result: oneshot::Sender>, + ) -> rusqlite::Result<()> { + let tree = conn + .prepare( + " + SELECT id + FROM euph_msgs + WHERE room = ? + AND NOT seen + AND id < ? + ORDER BY id DESC + LIMIT 1 + ", + )? + .query_row(params![room, id], |row| row.get(0)) + .optional()?; + let _ = result.send(tree); + Ok(()) + } + + fn get_newer_unseen_msg_id( + conn: &Connection, + room: String, + id: Snowflake, + result: oneshot::Sender>, + ) -> rusqlite::Result<()> { + let tree = conn + .prepare( + " + SELECT id + FROM euph_msgs + WHERE room = ? + AND NOT seen + AND id > ? + ORDER BY id ASC + LIMIT 1 + ", + )? + .query_row(params![room, id], |row| row.get(0)) + .optional()?; + let _ = result.send(tree); + Ok(()) + } + fn set_seen( conn: &Connection, room: String,