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)
This commit is contained in:
parent
297d62d173
commit
5d3e0ef73c
3 changed files with 115 additions and 76 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod cursor;
|
||||||
mod layout;
|
mod layout;
|
||||||
mod time;
|
mod time;
|
||||||
mod tree_blocks;
|
mod tree_blocks;
|
||||||
|
|
@ -6,7 +7,7 @@ mod widgets;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use crossterm::event::KeyEvent;
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||||
use parking_lot::FairMutex;
|
use parking_lot::FairMutex;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use toss::frame::{Frame, Pos, Size};
|
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::editor::EditorState;
|
||||||
use crate::ui::widgets::Widget;
|
use crate::ui::widgets::Widget;
|
||||||
|
|
||||||
use self::tree_blocks::TreeBlocks;
|
use self::cursor::Cursor;
|
||||||
|
|
||||||
///////////
|
///////////
|
||||||
// State //
|
// State //
|
||||||
///////////
|
///////////
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum Cursor<I> {
|
|
||||||
Bottom,
|
|
||||||
Msg(I),
|
|
||||||
Editor(Option<I>),
|
|
||||||
Pseudo(Option<I>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I: Eq> Cursor<I> {
|
|
||||||
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<M: Msg, S: MsgStore<M>> {
|
struct InnerTreeViewState<M: Msg, S: MsgStore<M>> {
|
||||||
store: S,
|
store: S,
|
||||||
|
|
||||||
|
|
@ -76,7 +51,20 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_navigation(&mut self, event: KeyEvent) -> bool {
|
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(
|
async fn handle_messaging(
|
||||||
|
|
|
||||||
|
|
@ -2,47 +2,44 @@
|
||||||
|
|
||||||
use crate::store::{Msg, MsgStore, Tree};
|
use crate::store::{Msg, MsgStore, Tree};
|
||||||
|
|
||||||
use super::blocks::{Block, BlockBody, MarkerBlock};
|
|
||||||
use super::InnerTreeViewState;
|
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<I> {
|
|
||||||
pub coming_from: Option<I>,
|
|
||||||
pub after: Option<I>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum Cursor<I> {
|
pub enum Cursor<I> {
|
||||||
/// No cursor visible because it is at the bottom of the chat history.
|
|
||||||
///
|
|
||||||
/// See also [`Anchor::Bottom`].
|
|
||||||
Bottom,
|
Bottom,
|
||||||
/// The cursor points to a message.
|
|
||||||
Msg(I),
|
Msg(I),
|
||||||
/// The cursor has turned into an editor because we're composing a new
|
Editor {
|
||||||
/// message.
|
coming_from: Option<I>,
|
||||||
Compose(LastChild<I>),
|
parent: Option<I>,
|
||||||
/// A placeholder message is being displayed for a message that was just
|
},
|
||||||
/// sent by the user.
|
Pseudo {
|
||||||
///
|
coming_from: Option<I>,
|
||||||
/// Will be replaced by a [`Cursor::Msg`] as soon as the server replies to
|
parent: Option<I>,
|
||||||
/// the send command with the sent message.
|
},
|
||||||
Placeholder(LastChild<I>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I: Eq> Cursor<I> {
|
impl<I: Eq> Cursor<I> {
|
||||||
pub fn matches_block(&self, block: &Block<I>) -> bool {
|
pub fn refers_to(&self, id: &I) -> bool {
|
||||||
match self {
|
if let Self::Msg(own_id) = self {
|
||||||
Self::Bottom => matches!(&block.body, BlockBody::Marker(MarkerBlock::Bottom)),
|
own_id == id
|
||||||
Self::Msg(id) => matches!(&block.body, BlockBody::Msg(msg) if msg.id == *id),
|
} else {
|
||||||
Self::Compose(lc) | Self::Placeholder(lc) => match &lc.after {
|
false
|
||||||
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_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<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
|
||||||
|
|
||||||
pub async fn move_cursor_up(&mut self) {
|
pub async fn move_cursor_up(&mut self) {
|
||||||
match &mut self.cursor {
|
match &mut self.cursor {
|
||||||
Cursor::Bottom => {
|
Cursor::Bottom | Cursor::Pseudo { parent: None, .. } => {
|
||||||
if let Some(last_tree_id) = self.store.last_tree_id().await {
|
if let Some(last_tree_id) = self.store.last_tree_id().await {
|
||||||
let tree = self.store.tree(&last_tree_id).await;
|
let tree = self.store.tree(&last_tree_id).await;
|
||||||
let mut id = last_tree_id;
|
let mut id = last_tree_id;
|
||||||
|
|
@ -169,18 +166,47 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
|
||||||
let mut tree = self.store.tree(path.first()).await;
|
let mut tree = self.store.tree(path.first()).await;
|
||||||
Self::find_prev_msg(&self.store, &mut tree, msg).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;
|
self.make_cursor_visible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn move_cursor_down(&mut self) {
|
pub async fn move_cursor_down(&mut self) {
|
||||||
if let Cursor::Msg(ref mut msg) = &mut self.cursor {
|
match &mut self.cursor {
|
||||||
let path = self.store.path(msg).await;
|
Cursor::Msg(ref mut msg) => {
|
||||||
let mut tree = self.store.tree(path.first()).await;
|
let path = self.store.path(msg).await;
|
||||||
if !Self::find_next_msg(&self.store, &mut tree, 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;
|
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;
|
self.make_cursor_visible = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,17 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
|
||||||
async fn cursor_path(&self, cursor: &Cursor<M::Id>) -> Path<M::Id> {
|
async fn cursor_path(&self, cursor: &Cursor<M::Id>) -> Path<M::Id> {
|
||||||
match cursor {
|
match cursor {
|
||||||
Cursor::Msg(id) => self.store.path(id).await,
|
Cursor::Msg(id) => self.store.path(id).await,
|
||||||
Cursor::Bottom | Cursor::Editor(None) | Cursor::Pseudo(None) => {
|
Cursor::Bottom
|
||||||
Path::new(vec![M::last_possible_id()])
|
| 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;
|
let mut path = self.store.path(parent).await;
|
||||||
path.push(M::last_possible_id());
|
path.push(M::last_possible_id());
|
||||||
path
|
path
|
||||||
|
|
@ -99,13 +106,17 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
|
||||||
let mut blocks = TreeBlocks::new(Root::Bottom, Root::Bottom);
|
let mut blocks = TreeBlocks::new(Root::Bottom, Root::Bottom);
|
||||||
|
|
||||||
// Ghost cursor, for positioning according to last cursor line
|
// 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);
|
let block = Block::new(frame, BlockId::LastCursor, Empty);
|
||||||
blocks.blocks_mut().push_back(block);
|
blocks.blocks_mut().push_back(block);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Editor or pseudomessage
|
// 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
|
// TODO Render proper editor or pseudocursor
|
||||||
let block = Block::new(frame, BlockId::Cursor, Text::new("TODO"));
|
let block = Block::new(frame, BlockId::Cursor, Text::new("TODO"));
|
||||||
blocks.blocks_mut().push_back(block);
|
blocks.blocks_mut().push_back(block);
|
||||||
|
|
@ -187,7 +198,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
|
||||||
|
|
||||||
blocks
|
blocks
|
||||||
}
|
}
|
||||||
Cursor::Editor(None) | Cursor::Pseudo(None) => {
|
Cursor::Editor { parent: None, .. } | Cursor::Pseudo { parent: None, .. } => {
|
||||||
let mut blocks = self.layout_bottom(frame);
|
let mut blocks = self.layout_bottom(frame);
|
||||||
|
|
||||||
blocks
|
blocks
|
||||||
|
|
@ -196,7 +207,13 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
|
||||||
|
|
||||||
blocks
|
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 root = last_cursor_path.first();
|
||||||
let tree = self.store.tree(root).await;
|
let tree = self.store.tree(root).await;
|
||||||
let mut blocks = self.layout_tree(frame, tree);
|
let mut blocks = self.layout_tree(frame, tree);
|
||||||
|
|
@ -219,14 +236,22 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
|
||||||
let bottom_line = frame.size().height as i32 - 1;
|
let bottom_line = frame.size().height as i32 - 1;
|
||||||
|
|
||||||
match &self.cursor {
|
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);
|
let mut blocks = self.layout_bottom(frame);
|
||||||
|
|
||||||
blocks.blocks_mut().set_bottom_line(bottom_line);
|
blocks.blocks_mut().set_bottom_line(bottom_line);
|
||||||
|
|
||||||
blocks
|
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 root = cursor_path.first();
|
||||||
let tree = self.store.tree(root).await;
|
let tree = self.store.tree(root).await;
|
||||||
let mut blocks = self.layout_tree(frame, tree);
|
let mut blocks = self.layout_tree(frame, tree);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue