From 5d3e0ef73cb168e2564fee0454588a6d337f2055 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sun, 31 Jul 2022 20:33:41 +0200 Subject: [PATCH] Reenable cursor movement This also moves the Cursor definition back to the cursor module, and modifies it to include info about the last non-editor/non-pseudo position in editor/pseudo cursors (to be used when editing or waiting for the server reply is aborted via Escape) --- src/ui/chat/tree.rs | 46 +++++++---------- src/ui/chat/tree/cursor.rs | 102 +++++++++++++++++++++++-------------- src/ui/chat/tree/layout.rs | 43 ++++++++++++---- 3 files changed, 115 insertions(+), 76 deletions(-) diff --git a/src/ui/chat/tree.rs b/src/ui/chat/tree.rs index f078465..d8ad638 100644 --- a/src/ui/chat/tree.rs +++ b/src/ui/chat/tree.rs @@ -1,3 +1,4 @@ +mod cursor; mod layout; mod time; mod tree_blocks; @@ -6,7 +7,7 @@ mod widgets; use std::sync::Arc; use async_trait::async_trait; -use crossterm::event::KeyEvent; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use parking_lot::FairMutex; use tokio::sync::Mutex; use toss::frame::{Frame, Pos, Size}; @@ -16,38 +17,12 @@ use crate::store::{Msg, MsgStore}; use crate::ui::widgets::editor::EditorState; use crate::ui::widgets::Widget; -use self::tree_blocks::TreeBlocks; +use self::cursor::Cursor; /////////// // State // /////////// -#[derive(Debug, Clone, Copy)] -pub enum Cursor { - Bottom, - Msg(I), - Editor(Option), - Pseudo(Option), -} - -impl Cursor { - pub fn refers_to(&self, id: &I) -> bool { - if let Self::Msg(own_id) = self { - own_id == id - } else { - false - } - } - - pub fn refers_to_last_child_of(&self, id: &I) -> bool { - if let Self::Editor(Some(parent)) | Self::Pseudo(Some(parent)) = self { - parent == id - } else { - false - } - } -} - struct InnerTreeViewState> { store: S, @@ -76,7 +51,20 @@ impl> InnerTreeViewState { } async fn handle_navigation(&mut self, event: KeyEvent) -> bool { - false + match event.code { + KeyCode::Char('k') | KeyCode::Up => self.move_cursor_up().await, + KeyCode::Char('j') | KeyCode::Down => self.move_cursor_down().await, + KeyCode::Char('g') | KeyCode::Home => self.move_cursor_to_top().await, + KeyCode::Char('G') | KeyCode::End => self.move_cursor_to_bottom().await, + KeyCode::Char('y') if event.modifiers == KeyModifiers::CONTROL => { + self.last_cursor_line += 1; + } + KeyCode::Char('e') if event.modifiers == KeyModifiers::CONTROL => { + self.last_cursor_line -= 1; + } + _ => return false, + } + true } async fn handle_messaging( diff --git a/src/ui/chat/tree/cursor.rs b/src/ui/chat/tree/cursor.rs index f299707..1b9b7f0 100644 --- a/src/ui/chat/tree/cursor.rs +++ b/src/ui/chat/tree/cursor.rs @@ -2,47 +2,44 @@ use crate::store::{Msg, MsgStore, Tree}; -use super::blocks::{Block, BlockBody, MarkerBlock}; use super::InnerTreeViewState; -/// Position of a cursor that is displayed as the last child of its parent -/// message, or last thread if it has no parent. -#[derive(Debug, Clone, Copy)] -pub struct LastChild { - pub coming_from: Option, - pub after: Option, -} - #[derive(Debug, Clone, Copy)] pub enum Cursor { - /// No cursor visible because it is at the bottom of the chat history. - /// - /// See also [`Anchor::Bottom`]. Bottom, - /// The cursor points to a message. Msg(I), - /// The cursor has turned into an editor because we're composing a new - /// message. - Compose(LastChild), - /// A placeholder message is being displayed for a message that was just - /// sent by the user. - /// - /// Will be replaced by a [`Cursor::Msg`] as soon as the server replies to - /// the send command with the sent message. - Placeholder(LastChild), + Editor { + coming_from: Option, + parent: Option, + }, + Pseudo { + coming_from: Option, + parent: Option, + }, } impl Cursor { - pub fn matches_block(&self, block: &Block) -> bool { - match self { - Self::Bottom => matches!(&block.body, BlockBody::Marker(MarkerBlock::Bottom)), - Self::Msg(id) => matches!(&block.body, BlockBody::Msg(msg) if msg.id == *id), - Self::Compose(lc) | Self::Placeholder(lc) => match &lc.after { - Some(bid) => { - matches!(&block.body, BlockBody::Marker(MarkerBlock::After(aid)) if aid == bid) - } - None => matches!(&block.body, BlockBody::Marker(MarkerBlock::Bottom)), - }, + pub fn refers_to(&self, id: &I) -> bool { + if let Self::Msg(own_id) = self { + own_id == id + } else { + false + } + } + + pub fn refers_to_last_child_of(&self, id: &I) -> bool { + if let Self::Editor { + parent: Some(parent), + .. + } + | Self::Pseudo { + parent: Some(parent), + .. + } = self + { + parent == id + } else { + false } } } @@ -156,7 +153,7 @@ impl> InnerTreeViewState { pub async fn move_cursor_up(&mut self) { match &mut self.cursor { - Cursor::Bottom => { + Cursor::Bottom | Cursor::Pseudo { parent: None, .. } => { if let Some(last_tree_id) = self.store.last_tree_id().await { let tree = self.store.tree(&last_tree_id).await; let mut id = last_tree_id; @@ -169,18 +166,47 @@ impl> InnerTreeViewState { let mut tree = self.store.tree(path.first()).await; Self::find_prev_msg(&self.store, &mut tree, msg).await; } - _ => {} + Cursor::Editor { .. } => {} + Cursor::Pseudo { + parent: Some(parent), + .. + } => { + let tree = self.store.tree(parent).await; + let mut id = parent.clone(); + while Self::find_last_child(&tree, &mut id) {} + self.cursor = Cursor::Msg(id); + } } self.make_cursor_visible = true; } pub async fn move_cursor_down(&mut self) { - if let Cursor::Msg(ref mut msg) = &mut self.cursor { - let path = self.store.path(msg).await; - let mut tree = self.store.tree(path.first()).await; - if !Self::find_next_msg(&self.store, &mut tree, msg).await { + match &mut self.cursor { + Cursor::Msg(ref mut msg) => { + let path = self.store.path(msg).await; + let mut tree = self.store.tree(path.first()).await; + if !Self::find_next_msg(&self.store, &mut tree, msg).await { + self.cursor = Cursor::Bottom; + } + } + Cursor::Pseudo { parent: None, .. } => { self.cursor = Cursor::Bottom; } + Cursor::Pseudo { + parent: Some(parent), + .. + } => { + let mut tree = self.store.tree(parent).await; + let mut id = parent.clone(); + while Self::find_last_child(&tree, &mut id) {} + // Now we're at the previous message + if Self::find_next_msg(&self.store, &mut tree, &mut id).await { + self.cursor = Cursor::Msg(id); + } else { + self.cursor = Cursor::Bottom; + } + } + _ => {} } self.make_cursor_visible = true; } diff --git a/src/ui/chat/tree/layout.rs b/src/ui/chat/tree/layout.rs index d21f1a0..0954862 100644 --- a/src/ui/chat/tree/layout.rs +++ b/src/ui/chat/tree/layout.rs @@ -12,10 +12,17 @@ impl> InnerTreeViewState { async fn cursor_path(&self, cursor: &Cursor) -> Path { match cursor { Cursor::Msg(id) => self.store.path(id).await, - Cursor::Bottom | Cursor::Editor(None) | Cursor::Pseudo(None) => { - Path::new(vec![M::last_possible_id()]) + Cursor::Bottom + | Cursor::Editor { parent: None, .. } + | Cursor::Pseudo { parent: None, .. } => Path::new(vec![M::last_possible_id()]), + Cursor::Editor { + parent: Some(parent), + .. } - Cursor::Editor(Some(parent)) | Cursor::Pseudo(Some(parent)) => { + | Cursor::Pseudo { + parent: Some(parent), + .. + } => { let mut path = self.store.path(parent).await; path.push(M::last_possible_id()); path @@ -99,13 +106,17 @@ impl> InnerTreeViewState { let mut blocks = TreeBlocks::new(Root::Bottom, Root::Bottom); // Ghost cursor, for positioning according to last cursor line - if let Cursor::Editor(None) | Cursor::Pseudo(None) = self.last_cursor { + if let Cursor::Editor { parent: None, .. } | Cursor::Pseudo { parent: None, .. } = + self.last_cursor + { let block = Block::new(frame, BlockId::LastCursor, Empty); blocks.blocks_mut().push_back(block); } // Editor or pseudomessage - if let Cursor::Editor(None) | Cursor::Pseudo(None) = self.cursor { + if let Cursor::Editor { parent: None, .. } | Cursor::Pseudo { parent: None, .. } = + self.cursor + { // TODO Render proper editor or pseudocursor let block = Block::new(frame, BlockId::Cursor, Text::new("TODO")); blocks.blocks_mut().push_back(block); @@ -187,7 +198,7 @@ impl> InnerTreeViewState { blocks } - Cursor::Editor(None) | Cursor::Pseudo(None) => { + Cursor::Editor { parent: None, .. } | Cursor::Pseudo { parent: None, .. } => { let mut blocks = self.layout_bottom(frame); blocks @@ -196,7 +207,13 @@ impl> InnerTreeViewState { blocks } - Cursor::Msg(_) | Cursor::Editor(Some(_)) | Cursor::Pseudo(Some(_)) => { + Cursor::Msg(_) + | Cursor::Editor { + parent: Some(_), .. + } + | Cursor::Pseudo { + parent: Some(_), .. + } => { let root = last_cursor_path.first(); let tree = self.store.tree(root).await; let mut blocks = self.layout_tree(frame, tree); @@ -219,14 +236,22 @@ impl> InnerTreeViewState { let bottom_line = frame.size().height as i32 - 1; match &self.cursor { - Cursor::Bottom | Cursor::Editor(None) | Cursor::Pseudo(None) => { + Cursor::Bottom + | Cursor::Editor { parent: None, .. } + | Cursor::Pseudo { parent: None, .. } => { let mut blocks = self.layout_bottom(frame); blocks.blocks_mut().set_bottom_line(bottom_line); blocks } - Cursor::Msg(_) | Cursor::Editor(Some(_)) | Cursor::Pseudo(Some(_)) => { + Cursor::Msg(_) + | Cursor::Editor { + parent: Some(_), .. + } + | Cursor::Pseudo { + parent: Some(_), .. + } => { let root = cursor_path.first(); let tree = self.store.tree(root).await; let mut blocks = self.layout_tree(frame, tree);