#![doc(html_playground_url = "https://play.rust-lang.org")]
#![doc(
html_favicon_url = "https://raw.githubusercontent.com/veeso/tui-realm-treeview/main/docs/images/cargo/tui-realm-treeview-128.png"
)]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/veeso/tui-realm-treeview/main/docs/images/cargo/tui-realm-treeview-512.png"
)]
extern crate tui_tree_widget;
extern crate tuirealm;
mod serializer;
mod stateful_tree;
use stateful_tree::StatefulTree;
use tui_tree_widget::{Tree as TuiTree, TreeItem as TuiTreeItem, TreeState as TuiTreeState};
use tuirealm::tui::{
layout::Rect,
style::{Color, Modifier, Style},
widgets::{Block, BorderType, Borders},
};
use tuirealm::{
event::{Event, KeyCode},
props::{BordersProps, TextParts, TextSpan},
Canvas, Component, Msg, Payload, PropPayload, PropValue, Props, PropsBuilder, Value,
};
#[derive(Debug)]
pub struct Tree {
root: Node,
}
impl Tree {
pub fn new(root: Node) -> Self {
Self { root }
}
pub fn root(&self) -> &Node {
&self.root
}
pub fn root_mut(&mut self) -> &mut Node {
&mut self.root
}
pub fn query(&self, id: &str) -> Option<&Node> {
self.root.query(id)
}
pub fn query_mut(&mut self, id: &str) -> Option<&mut Node> {
self.root.query_mut(id)
}
pub fn parent(&self, id: &str) -> Option<&Node> {
self.root().parent(id)
}
pub fn siblings(&self, id: &str) -> Option<Vec<&str>> {
self.root().siblings(id)
}
pub fn node_by_route(&self, route: &[usize]) -> Option<&Node> {
if route.is_empty() {
None
} else {
self.root().node_by_route(&route[1..])
}
}
pub fn route_by_node(&self, id: &str) -> Option<Vec<usize>> {
match self.root().route_by_node(id) {
None => None,
Some(route) => {
let mut r: Vec<usize> = vec![0];
r.extend(route);
Some(r)
}
}
}
}
#[derive(Debug)]
pub struct Node {
id: String, label: String, pub(crate) children: Vec<Node>,
}
impl Node {
pub fn new<S: AsRef<str>>(id: S, label: S) -> Self {
Self {
id: id.as_ref().to_string(),
label: label.as_ref().to_string(),
children: vec![],
}
}
pub fn id(&self) -> &str {
self.id.as_str()
}
pub fn label(&self) -> &str {
self.label.as_str()
}
pub fn with_children(mut self, children: Vec<Node>) -> Self {
self.children = children;
self
}
pub fn with_child(mut self, child: Node) -> Self {
self.add_child(child);
self
}
pub fn add_child(&mut self, child: Node) {
self.children.push(child);
}
pub fn clear(&mut self) {
self.children.clear();
}
pub fn truncate(&mut self, depth: usize) {
if depth == 0 {
self.children.clear();
} else {
self.children.iter_mut().for_each(|x| x.truncate(depth - 1));
}
}
pub fn is_leaf(&self) -> bool {
self.children.is_empty()
}
pub fn query(&self, id: &str) -> Option<&Self> {
if self.id() == id {
Some(&self)
} else {
self.children
.iter()
.map(|x| x.query(id))
.filter(|x| x.is_some())
.flatten()
.next()
}
}
pub(self) fn query_mut(&mut self, id: &str) -> Option<&mut Self> {
if self.id() == id {
Some(self)
} else {
self.children
.iter_mut()
.map(|x| x.query_mut(id))
.filter(|x| x.is_some())
.flatten()
.next()
}
}
pub fn count(&self) -> usize {
self.children.iter().map(|x| x.count()).sum::<usize>() + 1
}
pub fn depth(&self) -> usize {
fn depth_r(ptr: &Node, depth: usize) -> usize {
ptr.children
.iter()
.map(|x| depth_r(x, depth + 1))
.max()
.unwrap_or(depth)
}
depth_r(self, 1)
}
pub fn parent(&self, id: &str) -> Option<&Self> {
match self.route_by_node(id) {
None => None,
Some(route) => {
if route.is_empty() {
None
} else {
self.node_by_route(&route[0..route.len() - 1])
}
}
}
}
pub fn siblings(&self, id: &str) -> Option<Vec<&str>> {
self.parent(id).map(|x| {
x.children
.iter()
.filter(|&x| x.id() != id)
.map(|x| x.id())
.collect()
})
}
pub fn node_by_route(&self, route: &[usize]) -> Option<&Self> {
if route.is_empty() {
Some(self)
} else {
let next: &Node = self.children.get(route[0])?;
let route = &route[1..];
next.node_by_route(route)
}
}
pub fn route_by_node(&self, id: &str) -> Option<Vec<usize>> {
fn route_by_node_r(
node: &Node,
id: &str,
enumerator: Option<usize>,
mut route: Vec<usize>,
) -> Option<Vec<usize>> {
if let Some(enumerator) = enumerator {
route.push(enumerator);
}
if node.id() == id {
Some(route)
} else if node.children.is_empty() {
route.pop(); None
} else {
let mut result: Option<Vec<usize>> = None;
node.children.iter().enumerate().for_each(|(i, x)| {
let this_route: Vec<usize> = route.clone();
if let Some(this_route) = route_by_node_r(x, id, Some(i), this_route) {
result = Some(this_route);
}
});
result
}
}
route_by_node_r(self, id, None, Vec::with_capacity(self.depth()))
}
}
#[derive(Debug)]
struct OwnStates<'a> {
focus: bool,
tree: Tree,
tui_tree: StatefulTree<'a>,
}
impl<'a> OwnStates<'a> {
pub fn new(tree: Tree, initial_id: Option<&str>) -> Self {
let mut component: Self = Self {
focus: false,
tui_tree: StatefulTree::from(&tree),
tree,
};
component.open_first();
if let Some(id) = initial_id {
component.set_node(id);
}
component
}
pub fn focus(&self) -> bool {
self.focus
}
pub fn selected_node(&self) -> Option<&Node> {
let route: Vec<usize> = self.tui_tree.selected();
self.tree.node_by_route(route.as_slice())
}
pub fn get_tui_tree(&self) -> TuiTree {
TuiTree::new(self.tui_tree.items.clone())
}
pub fn get_tui_tree_state(&self) -> TuiTreeState {
self.tui_tree.state.clone()
}
pub fn get_available_steps_ahead(&self, max_step: usize) -> usize {
match self.selected_node() {
None => 0,
Some(node) => match self.tree.parent(node.id()) {
None => 0,
Some(parent) => match parent.children.iter().position(|x| x.id() == node.id()) {
None => 0,
Some(nth) => {
let children: usize = parent.children.len();
let diff: usize = children - nth;
match diff > max_step {
true => max_step,
false => diff,
}
}
},
},
}
}
pub fn get_available_steps_behind(&self, max_step: usize) -> usize {
match self.selected_node() {
None => 0,
Some(node) => match self.tree.parent(node.id()) {
None => 0,
Some(parent) => match parent.children.iter().position(|x| x.id() == node.id()) {
None => 0,
Some(nth) => match nth > max_step {
true => max_step,
false => nth,
},
},
},
}
}
pub fn toggle_focus(&mut self, focus: bool) {
self.focus = focus;
}
pub fn next(&mut self) {
self.tui_tree.next();
}
pub fn previous(&mut self) {
self.tui_tree.previous();
}
pub fn open(&mut self) {
self.tui_tree.open();
}
pub fn close(&mut self) {
self.tui_tree.close();
}
fn open_first(&mut self) {
self.next();
self.open();
}
pub fn update_tree(&mut self, tree: Tree, keep_state: bool, initial_id: Option<&str>) {
let selected: Option<String> = self.selected_node().map(|x| x.id().to_string());
self.tui_tree = StatefulTree::from(&tree);
self.tree = tree;
self.open_first();
if keep_state && initial_id.is_none() {
if let Some(selected) = selected {
if let Some(route) = self.tree.route_by_node(selected.as_str()) {
self.tui_tree.set_state(route.as_slice());
}
}
}
if let Some(id) = initial_id {
self.set_node(id);
}
}
fn set_node(&mut self, id: &str) {
if let Some(route) = self.tree.route_by_node(id) {
self.tui_tree.set_state(route.as_slice());
}
}
}
const PROP_TREE: &str = "tree";
const PROP_INITIAL_NODE: &str = "initial_node";
const PROP_KEEP_STATE: &str = "keep_state";
const PROP_MAX_STEPS: &str = "max_steps";
pub struct TreeViewPropsBuilder {
props: Option<Props>,
}
impl Default for TreeViewPropsBuilder {
fn default() -> Self {
Self {
props: Some(Props::default()),
}
}
}
impl PropsBuilder for TreeViewPropsBuilder {
fn build(&mut self) -> Props {
self.props.take().unwrap()
}
fn hidden(&mut self) -> &mut Self {
if let Some(props) = self.props.as_mut() {
props.visible = false;
}
self
}
fn visible(&mut self) -> &mut Self {
if let Some(props) = self.props.as_mut() {
props.visible = true;
}
self
}
}
impl From<Props> for TreeViewPropsBuilder {
fn from(props: Props) -> Self {
TreeViewPropsBuilder { props: Some(props) }
}
}
impl TreeViewPropsBuilder {
pub fn with_foreground(&mut self, color: Color) -> &mut Self {
if let Some(props) = self.props.as_mut() {
props.foreground = color;
}
self
}
pub fn with_background(&mut self, color: Color) -> &mut Self {
if let Some(props) = self.props.as_mut() {
props.background = color;
}
self
}
pub fn with_borders(
&mut self,
borders: Borders,
variant: BorderType,
color: Color,
) -> &mut Self {
if let Some(props) = self.props.as_mut() {
props.borders = BordersProps {
borders,
variant,
color,
}
}
self
}
pub fn with_title(&mut self, title: Option<String>) -> &mut Self {
if let Some(props) = self.props.as_mut() {
let spans = props.texts.spans.clone();
props.texts = TextParts::new(title, spans);
}
self
}
pub fn with_highlighted_str(&mut self, s: &str) -> &mut Self {
if let Some(props) = self.props.as_mut() {
let title = props.texts.title.clone();
let spans = vec![TextSpan::from(s)];
props.texts = TextParts::new(title, Some(spans));
}
self
}
pub fn with_tree_and_depth(&mut self, root: &Node, depth: usize) -> &mut Self {
if let Some(props) = self.props.as_mut() {
props.own.insert(PROP_TREE, root.to_prop_payload(depth, ""));
}
self
}
pub fn with_tree(&mut self, root: &Node) -> &mut Self {
self.with_tree_and_depth(root, usize::MAX)
}
pub fn with_node(&mut self, id: Option<&str>) -> &mut Self {
if let Some(props) = self.props.as_mut() {
match id {
Some(id) => props.own.insert(
PROP_INITIAL_NODE,
PropPayload::One(PropValue::Str(id.to_string())),
),
None => props.own.remove(PROP_INITIAL_NODE),
};
}
self
}
pub fn keep_state(&mut self, keep: bool) -> &mut Self {
if let Some(props) = self.props.as_mut() {
props
.own
.insert(PROP_KEEP_STATE, PropPayload::One(PropValue::Bool(keep)));
}
self
}
pub fn with_max_page_steps(&mut self, steps: usize) -> &mut Self {
if let Some(props) = self.props.as_mut() {
props
.own
.insert(PROP_MAX_STEPS, PropPayload::One(PropValue::Usize(steps)));
}
self
}
}
pub struct TreeView<'a> {
props: Props,
states: OwnStates<'a>,
}
impl<'a> TreeView<'a> {
pub fn new(props: Props) -> Self {
let tree: Tree = match props.own.get(PROP_TREE) {
Some(tree) => Tree::from(tree),
None => Tree::new(Node::new("", "")),
};
let initial_id: Option<&str> = match props.own.get(PROP_INITIAL_NODE) {
Some(PropPayload::One(PropValue::Str(id))) => Some(id.as_str()),
_ => None,
};
let states: OwnStates = OwnStates::new(tree, initial_id);
TreeView { props, states }
}
pub fn get_block(&self) -> Block<'a> {
let div: Block = Block::default()
.borders(self.props.borders.borders)
.border_style(match self.states.focus() {
true => self.props.borders.style(),
false => Style::default(),
})
.border_type(self.props.borders.variant);
match self.props.texts.title.as_ref() {
Some(t) => div.title(t.to_string()),
None => div,
}
}
}
impl<'a> Component for TreeView<'a> {
fn render(&self, render: &mut Canvas, area: Rect) {
if self.props.visible {
let (bg, fg): (Color, Color) = match &self.states.focus {
true => (self.props.foreground, self.props.background),
false => (Color::Reset, self.props.foreground),
};
let block: Block = self.get_block();
let mut tree: TuiTree = self
.states
.get_tui_tree()
.block(block)
.highlight_style(Style::default().fg(fg).bg(bg).add_modifier(Modifier::BOLD));
if let Some(spans) = self.props.texts.spans.as_ref() {
if let Some(span) = spans.get(0) {
tree = tree.highlight_symbol(&span.content);
}
}
render.render_stateful_widget(tree, area, &mut self.states.get_tui_tree_state());
}
}
fn update(&mut self, props: Props) -> Msg {
let prev_selection: Option<String> =
self.states.selected_node().map(|x| x.id().to_string());
let tree: Tree = match props.own.get(PROP_TREE) {
Some(tree) => Tree::from(tree),
None => Tree::new(Node::new("", "")),
};
let keep_state: bool = matches!(
props.own.get(PROP_KEEP_STATE),
Some(PropPayload::One(PropValue::Bool(true)))
);
let initial_id: Option<&str> = match props.own.get(PROP_INITIAL_NODE) {
Some(PropPayload::One(PropValue::Str(id))) => Some(id.as_str()),
_ => None,
};
self.states.update_tree(tree, keep_state, initial_id);
let new_selection: Option<String> = self.states.selected_node().map(|x| x.id().to_string());
self.props = props;
if prev_selection != new_selection {
Msg::OnChange(self.get_state())
} else {
Msg::None
}
}
fn get_props(&self) -> Props {
self.props.clone()
}
fn on(&mut self, ev: Event) -> Msg {
if let Event::Key(key) = ev {
match key.code {
KeyCode::Right => {
self.states.open();
Msg::OnChange(self.get_state())
}
KeyCode::Left => {
self.states.close();
Msg::OnChange(self.get_state())
}
KeyCode::Up => {
self.states.previous();
Msg::OnChange(self.get_state())
}
KeyCode::Down => {
self.states.next();
Msg::OnChange(self.get_state())
}
KeyCode::PageDown => {
let max_step: usize = match self.props.own.get(PROP_MAX_STEPS) {
Some(PropPayload::One(PropValue::Usize(steps))) => *steps,
_ => usize::MAX,
};
(0..self.states.get_available_steps_ahead(max_step))
.for_each(|_| self.states.next());
Msg::OnChange(self.get_state())
}
KeyCode::PageUp => {
let max_step: usize = match self.props.own.get(PROP_MAX_STEPS) {
Some(PropPayload::One(PropValue::Usize(steps))) => *steps,
_ => usize::MAX,
};
(0..self.states.get_available_steps_behind(max_step))
.for_each(|_| self.states.previous());
Msg::OnChange(self.get_state())
}
KeyCode::Enter => {
Msg::OnSubmit(self.get_state())
}
_ => {
Msg::OnKey(key)
}
}
} else {
Msg::None
}
}
fn get_state(&self) -> Payload {
match self.states.selected_node() {
Some(node) => Payload::One(Value::Str(node.id().to_string())),
None => Payload::None,
}
}
fn blur(&mut self) {
self.states.toggle_focus(false);
}
fn active(&mut self) {
self.states.toggle_focus(true);
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use tuirealm::event::KeyEvent;
#[test]
fn test_tree() {
let mut tree: Tree = Tree::new(
Node::new("/", "/")
.with_child(
Node::new("/bin", "bin/")
.with_child(Node::new("/bin/ls", "ls"))
.with_child(Node::new("/bin/pwd", "pwd")),
)
.with_child(
Node::new("/home", "home/").with_child(
Node::new("/home/omar", "omar/")
.with_child(Node::new("/home/omar/readme.md", "readme.md"))
.with_child(Node::new("/home/omar/changelog.md", "changelog.md")),
),
),
);
let root: &Node = tree.root();
assert_eq!(root.id(), "/");
assert_eq!(root.label(), "/");
assert_eq!(root.children.len(), 2);
let bin: &Node = &root.children[0];
assert_eq!(bin.id(), "/bin");
assert_eq!(bin.label(), "bin/");
assert_eq!(bin.children.len(), 2);
let bin_ids: Vec<&str> = bin.children.iter().map(|x| x.id()).collect();
assert_eq!(bin_ids, vec!["/bin/ls", "/bin/pwd"]);
let home: &Node = &tree.root.children[1];
assert_eq!(home.id(), "/home");
assert_eq!(home.label(), "home/");
assert_eq!(home.children.len(), 1);
let omar_home: &Node = &home.children[0];
let omar_home_ids: Vec<&str> = omar_home.children.iter().map(|x| x.id()).collect();
assert_eq!(
omar_home_ids,
vec!["/home/omar/readme.md", "/home/omar/changelog.md"]
);
assert_eq!(root.count(), 8);
assert_eq!(root.depth(), 4);
assert_eq!(
tree.query("/home/omar/changelog.md").unwrap().id(),
"/home/omar/changelog.md"
);
assert!(tree.query("ommlar").is_none());
assert_eq!(tree.query("/home/omar").unwrap().is_leaf(), false);
assert_eq!(
tree.query("/home/omar/changelog.md").unwrap().is_leaf(),
true
);
assert!(tree.parent("/").is_none());
assert_eq!(
tree.parent("/home/omar/changelog.md").unwrap().id(),
"/home/omar"
);
assert!(tree.parent("/homer").is_none());
assert_eq!(
tree.siblings("/home/omar/changelog.md").unwrap(),
vec!["/home/omar/readme.md"]
);
assert_eq!(tree.siblings("/home/omar").unwrap().len(), 0);
assert!(tree.siblings("/homer").is_none());
let _ = tree.root_mut();
tree.query_mut("/home/omar")
.unwrap()
.add_child(Node::new("/home/omar/Cargo.toml", "Cargo.toml"));
assert_eq!(
tree.query("/home/omar/Cargo.toml").unwrap().id(),
"/home/omar/Cargo.toml"
);
assert_eq!(
tree.node_by_route(&[0, 1, 0, 1]).unwrap().id(),
"/home/omar/changelog.md"
);
assert_eq!(
tree.root().node_by_route(&[1, 0, 1]).unwrap().id(),
"/home/omar/changelog.md"
);
assert!(tree.root().node_by_route(&[1, 0, 3]).is_none());
assert_eq!(
tree.route_by_node("/home/omar/changelog.md").unwrap(),
vec![0, 1, 0, 1]
);
assert_eq!(
tree.root()
.route_by_node("/home/omar/changelog.md")
.unwrap(),
vec![1, 0, 1]
);
assert!(tree.root().route_by_node("ciccio-pasticcio").is_none());
tree.query_mut("/home/omar").unwrap().clear();
assert_eq!(tree.query("/home/omar").unwrap().children.len(), 0);
let tree: Tree = Tree::new(
Node::new("a", "a").with_children(vec![Node::new("a1", "a1"), Node::new("a2", "a2")]),
);
assert!(tree.query("a").is_some());
assert!(tree.query("a1").is_some());
assert!(tree.query("a2").is_some());
let mut tree: Tree = Tree::new(
Node::new("/", "/")
.with_child(
Node::new("/bin", "bin/")
.with_child(Node::new("/bin/ls", "ls"))
.with_child(Node::new("/bin/pwd", "pwd")),
)
.with_child(
Node::new("/home", "home/").with_child(
Node::new("/home/omar", "omar/")
.with_child(Node::new("/home/omar/readme.md", "readme.md"))
.with_child(Node::new("/home/omar/changelog.md", "changelog.md")),
),
),
);
let root: &mut Node = &mut tree.root;
root.truncate(1);
assert_eq!(root.children.len(), 2);
assert_eq!(root.children[0].children.len(), 0);
assert_eq!(root.children[0].id(), "/bin");
assert_eq!(root.children[1].children.len(), 0);
assert_eq!(root.children[1].id(), "/home");
}
#[test]
fn test_states() {
let tree: Tree = Tree::new(
Node::new("/", "/")
.with_child(
Node::new("/bin", "bin/")
.with_child(Node::new("/bin/ls", "ls"))
.with_child(Node::new("/bin/pwd", "pwd")),
)
.with_child(
Node::new("/home", "home/").with_child(
Node::new("/home/omar", "omar/")
.with_child(Node::new("/home/omar/readme.md", "readme.md"))
.with_child(Node::new("/home/omar/changelog.md", "changelog.md"))
.with_child(Node::new("/home/omar/alpha.md", "alpha.md"))
.with_child(Node::new("/home/omar/bravo.md", "bravo.md"))
.with_child(Node::new("/home/omar/charlie.md", "charlie.md"))
.with_child(Node::new("/home/omar/delta.md", "delta.md"))
.with_child(Node::new("/home/omar/echo.md", "echo.md"))
.with_child(Node::new("/home/omar/foxtrot.md", "foxtrot.md")),
),
),
);
let mut states: OwnStates = OwnStates::new(tree, None);
assert_eq!(states.focus(), false);
states.toggle_focus(true);
assert_eq!(states.focus(), true);
let _ = states.get_tui_tree();
let _ = states.get_tui_tree_state();
assert_eq!(states.selected_node().unwrap().id(), "/");
states.open();
states.next();
assert_eq!(states.selected_node().unwrap().id(), "/bin");
states.next();
assert_eq!(states.selected_node().unwrap().id(), "/home");
states.previous();
assert_eq!(states.selected_node().unwrap().id(), "/bin");
states.open();
assert_eq!(states.selected_node().unwrap().id(), "/bin");
states.next();
assert_eq!(states.selected_node().unwrap().id(), "/bin/ls");
states.next();
assert_eq!(states.selected_node().unwrap().id(), "/bin/pwd");
states.close();
assert_eq!(states.selected_node().unwrap().id(), "/bin");
states.set_node("/home/omar/readme.md");
assert_eq!(states.selected_node().unwrap().id(), "/home/omar/readme.md");
assert_eq!(states.get_available_steps_ahead(usize::MAX), 8);
assert_eq!(states.get_available_steps_ahead(4), 4);
assert_eq!(states.get_available_steps_behind(usize::MAX), 0);
assert_eq!(states.get_available_steps_behind(4), 0);
states.next();
states.next();
assert_eq!(states.get_available_steps_ahead(usize::MAX), 6);
assert_eq!(states.get_available_steps_ahead(4), 4);
assert_eq!(states.get_available_steps_behind(usize::MAX), 2);
assert_eq!(states.get_available_steps_behind(1), 1);
states.set_node("/bin");
let tree: Tree = Tree::new(
Node::new("/", "/")
.with_child(Node::new("/bin", "bin/").with_child(Node::new("/bin/ls", "ls"))),
);
states.update_tree(tree, true, None);
assert_eq!(states.selected_node().unwrap().id(), "/bin");
let tree: Tree = Tree::new(
Node::new("/", "/").with_child(
Node::new("/home", "home/").with_child(
Node::new("/home/omar", "omar/")
.with_child(Node::new("/home/omar/readme.md", "readme.md"))
.with_child(Node::new("/home/omar/changelog.md", "changelog.md")),
),
),
);
states.update_tree(tree, true, None);
assert_eq!(states.selected_node().unwrap().id(), "/");
let tree: Tree = Tree::new(
Node::new("/", "/")
.with_child(
Node::new("/bin", "bin/")
.with_child(Node::new("/bin/ls", "ls"))
.with_child(Node::new("/bin/pwd", "pwd")),
)
.with_child(
Node::new("/home", "home/").with_child(
Node::new("/home/omar", "omar/")
.with_child(Node::new("/home/omar/readme.md", "readme.md"))
.with_child(Node::new("/home/omar/changelog.md", "changelog.md")),
),
),
);
states.update_tree(tree, true, Some("/home/omar"));
assert_eq!(states.selected_node().unwrap().id(), "/home/omar");
let tree: Tree = Tree::new(
Node::new("/", "/")
.with_child(
Node::new("/bin", "bin/")
.with_child(Node::new("/bin/ls", "ls"))
.with_child(Node::new("/bin/pwd", "pwd")),
)
.with_child(
Node::new("/home", "home/").with_child(
Node::new("/home/omar", "omar/")
.with_child(Node::new("/home/omar/readme.md", "readme.md"))
.with_child(Node::new("/home/omar/changelog.md", "changelog.md")),
),
),
);
let states: OwnStates = OwnStates::new(tree, Some("/home/omar/readme.md"));
assert_eq!(states.selected_node().unwrap().id(), "/home/omar/readme.md");
}
#[test]
fn test_treeview_component() {
let tree: Tree = Tree::new(
Node::new("/", "/")
.with_child(
Node::new("/bin", "bin/")
.with_child(Node::new("/bin/ls", "ls"))
.with_child(Node::new("/bin/pwd", "pwd")),
)
.with_child(
Node::new("/home", "home/").with_child(
Node::new("/home/omar", "omar/")
.with_child(Node::new("/home/omar/readme.md", "readme.md"))
.with_child(Node::new("/home/omar/changelog.md", "changelog.md"))
.with_child(Node::new("/home/omar/alpha.md", "alpha.md"))
.with_child(Node::new("/home/omar/bravo.md", "bravo.md"))
.with_child(Node::new("/home/omar/charlie.md", "charlie.md"))
.with_child(Node::new("/home/omar/delta.md", "delta.md"))
.with_child(Node::new("/home/omar/echo.md", "echo.md"))
.with_child(Node::new("/home/omar/foxtrot.md", "foxtrot.md")),
),
),
);
let mut component: TreeView = TreeView::new(
TreeViewPropsBuilder::default()
.hidden()
.visible()
.with_borders(Borders::ALL, BorderType::Double, Color::Red)
.with_background(Color::White)
.with_foreground(Color::Red)
.with_title(Some(String::from("C:\\")))
.with_highlighted_str(">>")
.with_tree(tree.root())
.keep_state(false)
.with_node(Some("/home"))
.with_max_page_steps(4)
.build(),
);
assert_eq!(component.props.foreground, Color::Red);
assert_eq!(component.props.background, Color::White);
assert_eq!(component.props.visible, true);
assert_eq!(component.props.borders.borders, Borders::ALL);
assert_eq!(component.props.borders.variant, BorderType::Double);
assert_eq!(component.props.borders.color, Color::Red);
assert_eq!(
component.get_state(),
Payload::One(Value::Str(String::from("/home")))
);
let _ = component.get_block();
assert_eq!(component.states.focus, false);
component.active();
assert_eq!(component.states.focus, true);
component.blur();
assert_eq!(component.states.focus, false);
let props = TreeViewPropsBuilder::from(component.get_props())
.with_foreground(Color::Yellow)
.with_title(Some(String::from("aaa")))
.hidden()
.with_node(None)
.build();
assert_eq!(
component.update(props),
Msg::OnChange(Payload::One(Value::Str("/".to_string())))
);
assert_eq!(component.props.visible, false);
assert_eq!(component.props.foreground, Color::Yellow);
assert_eq!(component.props.texts.title.as_ref().unwrap(), "aaa");
assert_eq!(
component.get_state(),
Payload::One(Value::Str(String::from("/")))
);
assert_eq!(
component.props.texts.spans.as_ref().unwrap()[0]
.content
.as_str(),
">>"
);
assert_eq!(
component.get_state(),
Payload::One(Value::Str(String::from("/")))
);
assert_eq!(
component.on(Event::Key(KeyEvent::from(KeyCode::Right))),
Msg::OnChange(Payload::One(Value::Str(String::from("/"))))
);
assert_eq!(
component.get_state(),
Payload::One(Value::Str(String::from("/")))
);
assert_eq!(
component.on(Event::Key(KeyEvent::from(KeyCode::Enter))),
Msg::OnSubmit(Payload::One(Value::Str(String::from("/"))))
);
assert_eq!(
component.on(Event::Key(KeyEvent::from(KeyCode::Down))),
Msg::OnChange(Payload::One(Value::Str(String::from("/bin"))))
);
assert_eq!(
component.on(Event::Key(KeyEvent::from(KeyCode::Right))),
Msg::OnChange(Payload::One(Value::Str(String::from("/bin"))))
);
assert_eq!(
component.on(Event::Key(KeyEvent::from(KeyCode::Down))),
Msg::OnChange(Payload::One(Value::Str(String::from("/bin/ls"))))
);
assert_eq!(
component.on(Event::Key(KeyEvent::from(KeyCode::Left))),
Msg::OnChange(Payload::One(Value::Str(String::from("/bin"))))
);
assert_eq!(
component.on(Event::Key(KeyEvent::from(KeyCode::Up))),
Msg::OnChange(Payload::One(Value::Str(String::from("/"))))
);
component.states.set_node("/home/omar/changelog.md");
assert_eq!(
component.on(Event::Key(KeyEvent::from(KeyCode::PageDown))),
Msg::OnChange(Payload::One(Value::Str(String::from(
"/home/omar/delta.md"
))))
);
assert_eq!(
component.on(Event::Key(KeyEvent::from(KeyCode::PageDown))),
Msg::OnChange(Payload::One(Value::Str(String::from(
"/home/omar/foxtrot.md"
))))
);
assert_eq!(
component.on(Event::Key(KeyEvent::from(KeyCode::PageDown))),
Msg::OnChange(Payload::One(Value::Str(String::from(
"/home/omar/foxtrot.md"
))))
);
assert_eq!(
component.on(Event::Key(KeyEvent::from(KeyCode::PageUp))),
Msg::OnChange(Payload::One(Value::Str(String::from(
"/home/omar/bravo.md"
))))
);
assert_eq!(
component.on(Event::Key(KeyEvent::from(KeyCode::PageUp))),
Msg::OnChange(Payload::One(Value::Str(String::from(
"/home/omar/readme.md"
))))
);
assert_eq!(
component.on(Event::Key(KeyEvent::from(KeyCode::PageUp))),
Msg::OnChange(Payload::One(Value::Str(String::from(
"/home/omar/readme.md"
))))
);
assert_eq!(
component.on(Event::Key(KeyEvent::from(KeyCode::Char('a')))),
Msg::OnKey(KeyEvent::from(KeyCode::Char('a'))),
);
assert_eq!(component.on(Event::Resize(0, 0)), Msg::None,);
component.states.set_node("/");
component.states.next();
component.states.open();
component.states.next();
assert_eq!(
component.get_state(),
Payload::One(Value::Str(String::from("/bin/ls")))
);
let tree: Tree = Tree::new(
Node::new("/", "/")
.with_child(Node::new("/bin", "bin/").with_child(Node::new("/bin/ls", "ls")))
.with_child(
Node::new("/home", "home/").with_child(
Node::new("/home/omar", "omar/")
.with_child(Node::new("/home/omar/readme.md", "readme.md"))
.with_child(Node::new("/home/omar/changelog.md", "changelog.md")),
),
),
);
let props = TreeViewPropsBuilder::from(component.get_props())
.with_tree(tree.root())
.keep_state(true)
.build();
assert_eq!(component.update(props), Msg::None);
assert_eq!(
component.get_state(),
Payload::One(Value::Str(String::from("/bin/ls")))
);
let tree: Tree = Tree::new(
Node::new("/", "/")
.with_child(Node::new("/bin", "bin/").with_child(Node::new("/bin/ls", "ls")))
.with_child(
Node::new("/home", "home/").with_child(
Node::new("/home/omar", "omar/")
.with_child(Node::new("/home/omar/changelog.md", "changelog.md")),
),
),
);
let props = TreeViewPropsBuilder::from(component.get_props())
.with_tree(tree.root())
.keep_state(false)
.build();
assert_eq!(
component.update(props),
Msg::OnChange(Payload::One(Value::Str(String::from("/"))))
);
let tree: Tree = Tree::new(
Node::new("/", "/")
.with_child(Node::new("/bin", "bin/").with_child(Node::new("/bin/pwd", "pwd")))
.with_child(
Node::new("/home", "home/").with_child(
Node::new("/home/omar", "omar/")
.with_child(Node::new("/home/omar/readme.md", "readme.md"))
.with_child(Node::new("/home/omar/changelog.md", "changelog.md")),
),
),
);
let props = TreeViewPropsBuilder::from(component.get_props())
.with_tree_and_depth(tree.root(), 1)
.build();
assert_eq!(component.update(props), Msg::None);
component.states.tui_tree = StatefulTree::new();
assert_eq!(component.get_state(), Payload::None);
}
}