use std::ffi::OsString;
use tui_treelistview::{TreeChildren, TreeModel, TreeRevision};
pub type NodeId = usize;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ActionValues {
pub output: OsString,
pub alternate_output: OsString,
pub path: OsString,
pub relpath: OsString,
}
impl ActionValues {
pub fn new(
output: impl Into<OsString>,
path: impl Into<OsString>,
relpath: impl Into<OsString>,
) -> Self {
let output = output.into();
Self {
alternate_output: output.clone(),
output,
path: path.into(),
relpath: relpath.into(),
}
}
pub fn with_alternate_output(mut self, output: impl Into<OsString>) -> Self {
self.alternate_output = output.into();
self
}
}
#[derive(Debug)]
pub struct Node {
pub name: String,
pub detail: Option<String>,
pub parent: Option<NodeId>,
pub children: Vec<NodeId>,
pub is_container: bool,
pub depth: usize,
pub action: ActionValues,
}
#[derive(Debug, Default)]
pub struct Tree {
pub(crate) nodes: Vec<Node>,
pub(crate) roots: Vec<NodeId>,
view_root: Option<NodeId>,
revision: TreeRevision,
}
impl Tree {
pub fn new() -> Self {
Self::default()
}
pub fn push(
&mut self,
parent: Option<NodeId>,
name: impl Into<String>,
is_container: bool,
action: ActionValues,
) -> NodeId {
self.push_with_detail(parent, name, None, is_container, action)
}
pub fn push_with_detail(
&mut self,
parent: Option<NodeId>,
name: impl Into<String>,
detail: Option<String>,
is_container: bool,
action: ActionValues,
) -> NodeId {
let id = self.nodes.len();
let depth = parent.map_or(0, |id| self.nodes[id].depth + 1);
self.nodes.push(Node {
name: name.into(),
detail,
parent,
children: Vec::new(),
is_container,
depth,
action,
});
match parent {
Some(parent) => self.nodes[parent].children.push(id),
None => self.roots.push(id),
}
id
}
pub fn node(&self, id: NodeId) -> &Node {
&self.nodes[id]
}
pub fn len(&self) -> usize {
self.nodes.len()
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn root_ids(&self) -> &[NodeId] {
&self.roots
}
pub(crate) fn view_root(&self) -> Option<NodeId> {
self.view_root
}
pub(crate) fn set_view_root(&mut self, root: Option<NodeId>) {
if self.view_root != root {
self.view_root = root;
self.revision.advance();
}
}
pub(crate) fn view_parent(&self, id: NodeId) -> Option<NodeId> {
if self.view_root == Some(id) {
None
} else {
self.nodes[id].parent
}
}
pub(crate) fn is_in_view(&self, id: NodeId) -> bool {
let Some(root) = self.view_root else {
return true;
};
let mut cursor = Some(id);
while let Some(current) = cursor {
if current == root {
return true;
}
cursor = self.nodes[current].parent;
}
false
}
pub fn is_leaf(&self, id: NodeId) -> bool {
self.nodes[id].children.is_empty()
}
pub fn branches(&self) -> impl Iterator<Item = (NodeId, Option<NodeId>)> + '_ {
self.nodes
.iter()
.enumerate()
.filter(|(id, _)| !self.is_leaf(*id))
.map(|(id, node)| (id, node.parent))
}
}
impl TreeModel for Tree {
type Id = NodeId;
fn roots(&self) -> impl Iterator<Item = NodeId> + '_ {
self.roots
.iter()
.copied()
.filter(|_| self.view_root.is_none())
.chain(self.view_root)
}
fn children(&self, id: NodeId) -> TreeChildren<'_, NodeId> {
TreeChildren::loaded(&self.nodes[id].children)
}
fn revision(&self) -> TreeRevision {
self.revision
}
fn size_hint(&self) -> usize {
self.nodes.len()
}
}