Skip to main content

ite_cli/
tree.rs

1//! Source-neutral tree data consumed by the application and renderer.
2
3use std::ffi::OsString;
4
5use tui_treelistview::{TreeChildren, TreeModel, TreeRevision};
6
7pub type NodeId = usize;
8
9/// Values used when the focused node is accepted or passed to a shell binding.
10#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct ActionValues {
12    /// Text written to stdout when the node is accepted.
13    pub output: OsString,
14    /// Text written to stdout by the alternate accept action.
15    pub alternate_output: OsString,
16    /// Value exported to shell bindings as `$path`.
17    pub path: OsString,
18    /// Value exported to shell bindings as `$relpath`.
19    pub relpath: OsString,
20}
21
22impl ActionValues {
23    pub fn new(
24        output: impl Into<OsString>,
25        path: impl Into<OsString>,
26        relpath: impl Into<OsString>,
27    ) -> Self {
28        let output = output.into();
29        Self {
30            alternate_output: output.clone(),
31            output,
32            path: path.into(),
33            relpath: relpath.into(),
34        }
35    }
36
37    pub fn with_alternate_output(mut self, output: impl Into<OsString>) -> Self {
38        self.alternate_output = output.into();
39        self
40    }
41}
42
43#[derive(Debug)]
44pub struct Node {
45    pub name: String,
46    /// Optional secondary text rendered after the name.
47    pub detail: Option<String>,
48    pub parent: Option<NodeId>,
49    pub children: Vec<NodeId>,
50    /// Whether this node represents a container, including an empty one.
51    pub is_container: bool,
52    /// 0 for roots.
53    pub depth: usize,
54    pub action: ActionValues,
55}
56
57#[derive(Debug, Default)]
58pub struct Tree {
59    pub(crate) nodes: Vec<Node>,
60    pub(crate) roots: Vec<NodeId>,
61}
62
63impl Tree {
64    pub fn new() -> Self {
65        Self::default()
66    }
67
68    pub fn push(
69        &mut self,
70        parent: Option<NodeId>,
71        name: impl Into<String>,
72        is_container: bool,
73        action: ActionValues,
74    ) -> NodeId {
75        self.push_with_detail(parent, name, None, is_container, action)
76    }
77
78    pub fn push_with_detail(
79        &mut self,
80        parent: Option<NodeId>,
81        name: impl Into<String>,
82        detail: Option<String>,
83        is_container: bool,
84        action: ActionValues,
85    ) -> NodeId {
86        let id = self.nodes.len();
87        let depth = parent.map_or(0, |id| self.nodes[id].depth + 1);
88        self.nodes.push(Node {
89            name: name.into(),
90            detail,
91            parent,
92            children: Vec::new(),
93            is_container,
94            depth,
95            action,
96        });
97        match parent {
98            Some(parent) => self.nodes[parent].children.push(id),
99            None => self.roots.push(id),
100        }
101        id
102    }
103
104    pub fn node(&self, id: NodeId) -> &Node {
105        &self.nodes[id]
106    }
107
108    pub fn len(&self) -> usize {
109        self.nodes.len()
110    }
111
112    pub fn is_empty(&self) -> bool {
113        self.nodes.is_empty()
114    }
115
116    pub fn root_ids(&self) -> &[NodeId] {
117        &self.roots
118    }
119
120    /// True when the node cannot be expanded.
121    pub fn is_leaf(&self, id: NodeId) -> bool {
122        self.nodes[id].children.is_empty()
123    }
124
125    /// All expandable nodes as `(id, parent)` pairs, in tree order.
126    pub fn branches(&self) -> impl Iterator<Item = (NodeId, Option<NodeId>)> + '_ {
127        self.nodes
128            .iter()
129            .enumerate()
130            .filter(|(id, _)| !self.is_leaf(*id))
131            .map(|(id, node)| (id, node.parent))
132    }
133}
134
135impl TreeModel for Tree {
136    type Id = NodeId;
137
138    fn roots(&self) -> impl Iterator<Item = NodeId> + '_ {
139        self.roots.iter().copied()
140    }
141
142    fn children(&self, id: NodeId) -> TreeChildren<'_, NodeId> {
143        TreeChildren::loaded(&self.nodes[id].children)
144    }
145
146    fn revision(&self) -> TreeRevision {
147        TreeRevision::INITIAL
148    }
149
150    fn size_hint(&self) -> usize {
151        self.nodes.len()
152    }
153}