diff --git a/cove-tui/src/chat.rs b/cove-tui/src/chat.rs index 6999706..184f56f 100644 --- a/cove-tui/src/chat.rs +++ b/cove-tui/src/chat.rs @@ -43,16 +43,20 @@ impl> Chat { } impl> Chat { - pub fn handle_key_event(&mut self, event: KeyEvent, frame: &mut Frame, size: Size) { + pub async fn handle_key_event(&mut self, event: KeyEvent, frame: &mut Frame, size: Size) { match self.mode { - Mode::Tree => self.tree.handle_key_event( - &mut self.store, - &self.room, - &mut self.cursor, - event, - frame, - size, - ), + Mode::Tree => { + self.tree + .handle_key_event( + &mut self.store, + &self.room, + &mut self.cursor, + event, + frame, + size, + ) + .await + } } } diff --git a/cove-tui/src/chat/tree.rs b/cove-tui/src/chat/tree.rs index 6898c1f..a998183 100644 --- a/cove-tui/src/chat/tree.rs +++ b/cove-tui/src/chat/tree.rs @@ -2,8 +2,8 @@ use std::collections::VecDeque; use std::marker::PhantomData; use chrono::{DateTime, Utc}; -use crossterm::event::KeyEvent; -use crossterm::style::ContentStyle; +use crossterm::event::{KeyCode, KeyEvent}; +use crossterm::style::{ContentStyle, Stylize}; use toss::frame::{Frame, Pos, Size}; use crate::store::{Msg, MsgStore, Tree}; @@ -317,11 +317,16 @@ impl TreeView { } } - fn render_indentation(&mut self, frame: &mut Frame, pos: Pos, indent: usize) { + fn render_indentation(&mut self, frame: &mut Frame, pos: Pos, indent: usize, cursor: bool) { for i in 0..indent { let x = TIME_WIDTH + 1 + INDENT_WIDTH * i; let pos = Pos::new(pos.x + x as i32, pos.y); - frame.write(pos, INDENT, ContentStyle::default()); + let style = if cursor { + ContentStyle::default().black().on_white() + } else { + ContentStyle::default() + }; + frame.write(pos, INDENT, style); } } @@ -336,7 +341,12 @@ impl TreeView { continue; } - self.render_indentation(frame, Pos::new(pos.x, y), block.indent); + self.render_indentation( + frame, + Pos::new(pos.x, y), + block.indent, + block.cursor, + ); let after_indent = pos.x + (TIME_WIDTH + 1 + INDENT_WIDTH * block.indent) as i32; if i == 0 { @@ -350,7 +360,7 @@ impl TreeView { } } BlockContent::Placeholder => { - self.render_indentation(frame, pos, block.indent); + self.render_indentation(frame, pos, block.indent, block.cursor); let x = pos.x + (TIME_WIDTH + 1 + INDENT_WIDTH * block.indent) as i32; let y = pos.y + block.line; frame.write(Pos::new(x, y), "[...]", ContentStyle::default()); @@ -359,7 +369,43 @@ impl TreeView { } } - pub fn handle_key_event>( + async fn move_to_prev_msg>( + &mut self, + store: &mut S, + room: &str, + cursor: &mut Option>, + ) { + let tree = if let Some(cursor) = cursor { + let path = store.path(room, &cursor.id).await; + let tree = store.tree(room, path.first()).await; + if let Some(prev_sibling) = tree.prev_sibling(&cursor.id) { + cursor.id = prev_sibling.clone(); + return; + } else if let Some(parent) = tree.parent(&cursor.id) { + cursor.id = parent; + return; + } else { + store.prev_tree(room, path.first()).await + } + } else { + store.last_tree(room).await + }; + + if let Some(tree) = tree { + let tree = store.tree(room, &tree).await; + let cursor_id = tree.last_child(tree.root().clone()); + if let Some(cursor) = cursor { + cursor.id = cursor_id; + } else { + *cursor = Some(Cursor { + id: cursor_id, + proportion: 1.0, + }); + } + } + } + + pub async fn handle_key_event>( &mut self, store: &mut S, room: &str, @@ -368,7 +414,10 @@ impl TreeView { frame: &mut Frame, size: Size, ) { - // TODO + match event.code { + KeyCode::Char('k') => self.move_to_prev_msg(store, room, cursor).await, + _ => {} + } } pub async fn render>( diff --git a/cove-tui/src/store.rs b/cove-tui/src/store.rs index 7a95917..8f28bc6 100644 --- a/cove-tui/src/store.rs +++ b/cove-tui/src/store.rs @@ -57,6 +57,7 @@ impl Tree { let mut children: HashMap> = HashMap::new(); for msg in msgs.values() { + children.entry(msg.id()).or_default(); if let Some(parent) = msg.parent() { children.entry(parent).or_default().push(msg.id()); } @@ -77,9 +78,56 @@ impl Tree { self.msgs.get(id) } + pub fn parent(&self, id: &M::Id) -> Option { + self.msg(id).and_then(|m| m.parent()) + } + pub fn children(&self, id: &M::Id) -> Option<&[M::Id]> { self.children.get(id).map(|c| c as &[M::Id]) } + + pub fn siblings(&self, id: &M::Id) -> Option<&[M::Id]> { + if let Some(parent) = self.parent(id) { + self.children(&parent) + } else { + None + } + } + + pub fn prev_sibling(&self, id: &M::Id) -> Option<&M::Id> { + if let Some(siblings) = self.siblings(id) { + siblings + .iter() + .zip(siblings.iter().skip(1)) + .find(|(_, s)| *s == id) + .map(|(s, _)| s) + } else { + None + } + } + + pub fn next_sibling(&self, id: &M::Id) -> Option<&M::Id> { + if let Some(siblings) = self.siblings(id) { + siblings + .iter() + .zip(siblings.iter().skip(1)) + .find(|(s, _)| *s == id) + .map(|(_, s)| s) + } else { + None + } + } + + pub fn last_child(&self, mut id: M::Id) -> M::Id { + while let Some(children) = self.children(&id) { + if let Some(last_child) = children.last() { + id = last_child.clone(); + } else { + break; + } + } + id + } } #[async_trait] diff --git a/cove-tui/src/ui.rs b/cove-tui/src/ui.rs index 2b1a7dc..2bbbce3 100644 --- a/cove-tui/src/ui.rs +++ b/cove-tui/src/ui.rs @@ -140,7 +140,7 @@ impl Ui { if let KeyCode::Char('Q') = event.code { return EventHandleResult::Stop; } - self.chat.handle_key_event(event, frame, size); + self.chat.handle_key_event(event, frame, size).await; EventHandleResult::Continue }