Fold subtrees
This commit is contained in:
parent
26923745ad
commit
0ad3432141
4 changed files with 69 additions and 10 deletions
|
|
@ -23,6 +23,10 @@ impl<I> Path<I> {
|
||||||
Self(segments)
|
Self(segments)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parent_segments(&self) -> impl Iterator<Item = &I> {
|
||||||
|
self.0.iter().take(self.0.len() - 1)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, segment: I) {
|
pub fn push(&mut self, segment: I) {
|
||||||
self.0.push(segment)
|
self.0.push(segment)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ mod layout;
|
||||||
mod tree_blocks;
|
mod tree_blocks;
|
||||||
mod widgets;
|
mod widgets;
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
@ -39,13 +40,14 @@ struct InnerTreeViewState<M: Msg, S: MsgStore<M>> {
|
||||||
last_visible_msgs: Vec<M::Id>,
|
last_visible_msgs: Vec<M::Id>,
|
||||||
|
|
||||||
cursor: Cursor<M::Id>,
|
cursor: Cursor<M::Id>,
|
||||||
|
editor: EditorState,
|
||||||
|
|
||||||
/// Scroll the view on the next render. Positive values scroll up and
|
/// Scroll the view on the next render. Positive values scroll up and
|
||||||
/// negative values scroll down.
|
/// negative values scroll down.
|
||||||
scroll: i32,
|
scroll: i32,
|
||||||
correction: Option<Correction>,
|
correction: Option<Correction>,
|
||||||
|
|
||||||
editor: EditorState,
|
folded: HashSet<M::Id>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
|
impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
|
||||||
|
|
@ -56,9 +58,10 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
|
||||||
last_cursor_line: 0,
|
last_cursor_line: 0,
|
||||||
last_visible_msgs: vec![],
|
last_visible_msgs: vec![],
|
||||||
cursor: Cursor::Bottom,
|
cursor: Cursor::Bottom,
|
||||||
|
editor: EditorState::new(),
|
||||||
scroll: 0,
|
scroll: 0,
|
||||||
correction: None,
|
correction: None,
|
||||||
editor: EditorState::new(),
|
folded: HashSet::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,6 +101,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_action_key_bindings(&self, bindings: &mut KeyBindingsList) {
|
pub fn list_action_key_bindings(&self, bindings: &mut KeyBindingsList) {
|
||||||
|
bindings.binding("space", "fold current message's subtree");
|
||||||
bindings.binding("s", "toggle current message's seen status");
|
bindings.binding("s", "toggle current message's seen status");
|
||||||
bindings.binding("S", "mark all visible messages as seen");
|
bindings.binding("S", "mark all visible messages as seen");
|
||||||
bindings.binding("ctrl+s", "mark all older messages as seen");
|
bindings.binding("ctrl+s", "mark all older messages as seen");
|
||||||
|
|
@ -105,6 +109,14 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
|
||||||
|
|
||||||
async fn handle_action_key_event(&mut self, event: KeyEvent, id: Option<&M::Id>) -> bool {
|
async fn handle_action_key_event(&mut self, event: KeyEvent, id: Option<&M::Id>) -> bool {
|
||||||
match event {
|
match event {
|
||||||
|
key!(' ') => {
|
||||||
|
if let Some(id) = id {
|
||||||
|
if !self.folded.remove(id) {
|
||||||
|
self.folded.insert(id.clone());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
key!('s') => {
|
key!('s') => {
|
||||||
if let Some(id) = id {
|
if let Some(id) = id {
|
||||||
if let Some(msg) = self.store.tree(id).await.msg(id) {
|
if let Some(msg) = self.store.tree(id).await.msg(id) {
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,12 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn make_path_visible(&mut self, path: &Path<M::Id>) {
|
||||||
|
for segment in path.parent_segments() {
|
||||||
|
self.folded.remove(segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn cursor_line(&self, blocks: &TreeBlocks<M::Id>) -> i32 {
|
fn cursor_line(&self, blocks: &TreeBlocks<M::Id>) -> i32 {
|
||||||
if let Cursor::Bottom = self.cursor {
|
if let Cursor::Bottom = self.cursor {
|
||||||
// The value doesn't matter as it will always be ignored.
|
// The value doesn't matter as it will always be ignored.
|
||||||
|
|
@ -84,18 +90,25 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
|
||||||
blocks.blocks_mut().push_back(block);
|
blocks.blocks_mut().push_back(block);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let folded = self.folded.contains(id);
|
||||||
|
let children = tree.children(id);
|
||||||
|
let folded_info = children
|
||||||
|
.filter(|_| folded)
|
||||||
|
.map(|c| c.len())
|
||||||
|
.filter(|c| *c > 0);
|
||||||
|
|
||||||
// Main message body
|
// Main message body
|
||||||
let highlighted = self.cursor.refers_to(id);
|
let highlighted = self.cursor.refers_to(id);
|
||||||
let widget = if let Some(msg) = tree.msg(id) {
|
let widget = if let Some(msg) = tree.msg(id) {
|
||||||
widgets::msg(highlighted, indent, msg)
|
widgets::msg(highlighted, indent, msg, folded_info)
|
||||||
} else {
|
} else {
|
||||||
widgets::msg_placeholder(highlighted, indent)
|
widgets::msg_placeholder(highlighted, indent, folded_info)
|
||||||
};
|
};
|
||||||
let block = Block::new(frame, BlockId::Msg(id.clone()), widget);
|
let block = Block::new(frame, BlockId::Msg(id.clone()), widget);
|
||||||
blocks.blocks_mut().push_back(block);
|
blocks.blocks_mut().push_back(block);
|
||||||
|
|
||||||
// Children, recursively
|
// Children recursively (if not folded)
|
||||||
if let Some(children) = tree.children(id) {
|
if let Some(children) = children.filter(|_| !folded) {
|
||||||
for child in children {
|
for child in children {
|
||||||
self.layout_subtree(nick, frame, tree, indent + 1, child, blocks);
|
self.layout_subtree(nick, frame, tree, indent + 1, child, blocks);
|
||||||
}
|
}
|
||||||
|
|
@ -458,6 +471,7 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
|
||||||
|
|
||||||
let last_cursor_path = self.cursor_path(&self.last_cursor).await;
|
let last_cursor_path = self.cursor_path(&self.last_cursor).await;
|
||||||
let cursor_path = self.cursor_path(&self.cursor).await;
|
let cursor_path = self.cursor_path(&self.cursor).await;
|
||||||
|
self.make_path_visible(&cursor_path);
|
||||||
|
|
||||||
let mut blocks = self
|
let mut blocks = self
|
||||||
.layout_initial_seed(nick, frame, &last_cursor_path, &cursor_path)
|
.layout_initial_seed(nick, frame, &last_cursor_path, &cursor_path)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ mod time;
|
||||||
|
|
||||||
use crossterm::style::{ContentStyle, Stylize};
|
use crossterm::style::{ContentStyle, Stylize};
|
||||||
use toss::frame::Frame;
|
use toss::frame::Frame;
|
||||||
|
use toss::styled::Styled;
|
||||||
|
|
||||||
use super::super::ChatMsg;
|
use super::super::ChatMsg;
|
||||||
use crate::store::Msg;
|
use crate::store::Msg;
|
||||||
|
|
@ -40,6 +41,10 @@ fn style_indent(highlighted: bool) -> ContentStyle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn style_info() -> ContentStyle {
|
||||||
|
ContentStyle::default().italic().dark_grey()
|
||||||
|
}
|
||||||
|
|
||||||
fn style_editor_highlight() -> ContentStyle {
|
fn style_editor_highlight() -> ContentStyle {
|
||||||
ContentStyle::default().black().on_cyan()
|
ContentStyle::default().black().on_cyan()
|
||||||
}
|
}
|
||||||
|
|
@ -48,8 +53,20 @@ fn style_pseudo_highlight() -> ContentStyle {
|
||||||
ContentStyle::default().black().on_yellow()
|
ContentStyle::default().black().on_yellow()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn msg<M: Msg + ChatMsg>(highlighted: bool, indent: usize, msg: &M) -> BoxedWidget {
|
pub fn msg<M: Msg + ChatMsg>(
|
||||||
let (nick, content) = msg.styled();
|
highlighted: bool,
|
||||||
|
indent: usize,
|
||||||
|
msg: &M,
|
||||||
|
folded_info: Option<usize>,
|
||||||
|
) -> BoxedWidget {
|
||||||
|
let (nick, mut content) = msg.styled();
|
||||||
|
|
||||||
|
if let Some(amount) = folded_info {
|
||||||
|
content = content
|
||||||
|
.then_plain("\n")
|
||||||
|
.then(format!("[{amount} more]"), style_info());
|
||||||
|
}
|
||||||
|
|
||||||
HJoin::new(vec![
|
HJoin::new(vec![
|
||||||
Segment::new(seen::widget(msg.seen())),
|
Segment::new(seen::widget(msg.seen())),
|
||||||
Segment::new(
|
Segment::new(
|
||||||
|
|
@ -69,7 +86,19 @@ pub fn msg<M: Msg + ChatMsg>(highlighted: bool, indent: usize, msg: &M) -> Boxed
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn msg_placeholder(highlighted: bool, indent: usize) -> BoxedWidget {
|
pub fn msg_placeholder(
|
||||||
|
highlighted: bool,
|
||||||
|
indent: usize,
|
||||||
|
folded_info: Option<usize>,
|
||||||
|
) -> BoxedWidget {
|
||||||
|
let mut content = Styled::new(PLACEHOLDER, style_placeholder());
|
||||||
|
|
||||||
|
if let Some(amount) = folded_info {
|
||||||
|
content = content
|
||||||
|
.then_plain("\n")
|
||||||
|
.then(format!("[{amount} more]"), style_info());
|
||||||
|
}
|
||||||
|
|
||||||
HJoin::new(vec![
|
HJoin::new(vec![
|
||||||
Segment::new(seen::widget(true)),
|
Segment::new(seen::widget(true)),
|
||||||
Segment::new(
|
Segment::new(
|
||||||
|
|
@ -78,7 +107,7 @@ pub fn msg_placeholder(highlighted: bool, indent: usize) -> BoxedWidget {
|
||||||
.right(1),
|
.right(1),
|
||||||
),
|
),
|
||||||
Segment::new(Indent::new(indent, style_indent(highlighted))),
|
Segment::new(Indent::new(indent, style_indent(highlighted))),
|
||||||
Segment::new(Text::new((PLACEHOLDER, style_placeholder()))),
|
Segment::new(Text::new(content)),
|
||||||
])
|
])
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue