use crate::node::NodeId;
use crate::tree::StyledTree;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NodePath {
pub segments: Vec<NodeId>,
}
impl NodePath {
#[must_use]
pub fn empty() -> Self {
Self {
segments: Vec::new(),
}
}
#[must_use]
pub fn from_segments(segments: Vec<NodeId>) -> Self {
Self { segments }
}
#[must_use]
pub fn depth(&self) -> usize {
self.segments.len().saturating_sub(1)
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.segments.is_empty()
}
}
impl std::fmt::Display for NodePath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.segments.is_empty() {
return write!(f, "<no path>");
}
for (i, id) in self.segments.iter().enumerate() {
if i > 0 {
write!(f, " > ")?;
}
write!(f, "{id}")?;
}
Ok(())
}
}
#[must_use]
pub fn build_node_path(tree: &StyledTree, target: NodeId) -> Vec<NodeId> {
let node_count = tree.node_count();
if node_count == 0 {
return Vec::new();
}
let mut parent_of: Vec<Option<NodeId>> = vec![None; node_count];
let mut found = false;
let mut queue = std::collections::VecDeque::new();
queue.push_back(tree.root());
while let Some(id) = queue.pop_front() {
if id == target {
found = true;
}
for &child in &tree.node(id).children {
parent_of[child.raw() as usize] = Some(id);
queue.push_back(child);
}
}
if !found {
return Vec::new();
}
let mut path = vec![target];
let mut current = target;
while let Some(parent) = parent_of[current.raw() as usize] {
path.push(parent);
current = parent;
}
path.reverse();
path
}
#[cfg(test)]
mod tests {
use super::*;
use crate::node::{ContentVariant, TextContent};
use crate::style::ResolvedStyle;
use crate::tree::StyledTreeBuilder;
use crate::version::IrVersion;
#[test]
fn build_path_to_root() {
let mut b = StyledTreeBuilder::new(IrVersion::new(1, 0));
b.add_node(
ContentVariant::Container,
ResolvedStyle::default(),
None,
None,
);
let tree = b.build().unwrap();
let path = build_node_path(&tree, tree.root());
assert_eq!(path, vec![NodeId::from_raw(0)]);
}
#[test]
fn build_path_to_deep_node() {
let mut b = StyledTreeBuilder::new(IrVersion::new(1, 0));
let root = b.add_node(
ContentVariant::Container,
ResolvedStyle::default(),
None,
None,
);
let mid = b.add_child(
root,
ContentVariant::Container,
ResolvedStyle::default(),
None,
None,
);
let leaf = b.add_child(
mid,
ContentVariant::Text(TextContent::new("deep")),
ResolvedStyle::default(),
None,
None,
);
let tree = b.build().unwrap();
let path = build_node_path(&tree, leaf);
assert_eq!(
path,
vec![
NodeId::from_raw(0),
NodeId::from_raw(1),
NodeId::from_raw(2),
]
);
}
#[test]
fn node_path_display() {
let np = NodePath::from_segments(vec![
NodeId::from_raw(0),
NodeId::from_raw(3),
NodeId::from_raw(7),
]);
assert_eq!(np.to_string(), "node#0 > node#3 > node#7");
assert_eq!(np.depth(), 2);
}
#[test]
fn empty_node_path_display() {
let np = NodePath::empty();
assert_eq!(np.to_string(), "<no path>");
assert!(np.is_empty());
assert_eq!(np.depth(), 0);
}
}