use crate::primes::Cirru;
const FOLD_CHILDREN_NON_TARGET: usize = 2;
const FOLD_CHILDREN_TARGET: usize = 3;
const FOLD_NON_TARGET_DEPTH: usize = 2;
const FOLD_TARGET_DEPTH: usize = 2;
fn folded_place(detail: &str) -> Cirru {
Cirru::leaf(format!("'FOLDED:{detail}"))
}
fn sibling_anchor(node: &Cirru) -> Cirru {
match node {
Cirru::Leaf(_) => node.clone(),
Cirru::List(xs) => {
let Some(head) = xs.first() else {
return node.clone();
};
let mut result = vec![head.clone()];
if xs.len() > 1 {
result.push(folded_place(&format!("inside:{}", xs.len() - 1)));
}
Cirru::List(result)
}
}
}
fn focused_node(node: Cirru) -> Cirru {
Cirru::List(vec![Cirru::leaf("'FOCUSED"), node])
}
fn folded_detail(node: &Cirru) -> Option<&str> {
match node {
Cirru::Leaf(value) => value.strip_prefix("'FOLDED:"),
Cirru::List(_) => None,
}
}
fn merge_folded_runs(nodes: Vec<Cirru>) -> Vec<Cirru> {
let mut result = Vec::with_capacity(nodes.len());
let mut index = 0;
while index < nodes.len() {
let Some(detail) = folded_detail(&nodes[index]) else {
result.push(nodes[index].clone());
index += 1;
continue;
};
let mut count = 1;
while index + count < nodes.len() && folded_detail(&nodes[index + count]).is_some() {
count += 1;
}
if count == 1 {
result.push(nodes[index].clone());
} else {
let same_detail = (1..count).all(|offset| folded_detail(&nodes[index + offset]) == Some(detail));
let kind = if same_detail { detail } else { "merged" };
result.push(folded_place(&format!("{kind}:{count}")));
}
index += count;
}
result
}
pub fn focus_cirru_preview(node: &Cirru, path: &[usize]) -> Cirru {
focus_cirru_preview_impl(node, path, 0)
}
fn focus_cirru_preview_impl(node: &Cirru, path: &[usize], depth: usize) -> Cirru {
match node {
Cirru::Leaf(_) => {
if path.is_empty() {
focused_node(node.clone())
} else {
node.clone()
}
}
Cirru::List(xs) => {
if path.is_empty() {
return focused_node(fold_children_impl(node, FOLD_CHILDREN_TARGET, 0, FOLD_TARGET_DEPTH));
}
let target_idx = path[0];
if target_idx >= xs.len() {
return fold_children_impl(node, FOLD_CHILDREN_NON_TARGET, 0, FOLD_NON_TARGET_DEPTH);
}
let rest_path = &path[1..];
let mut result: Vec<Cirru> = Vec::new();
if target_idx > 0 {
result.push(fold_children_impl(&xs[0], FOLD_CHILDREN_NON_TARGET, 0, FOLD_NON_TARGET_DEPTH));
}
if target_idx > 1 {
let hidden = target_idx - 1;
result.push(sibling_anchor(&xs[1]));
if hidden > 1 {
result.push(folded_place(&format!("before:{}", hidden - 1)));
}
}
result.push(focus_cirru_preview_impl(&xs[target_idx], rest_path, depth + 1));
let remaining = xs.len() - target_idx - 1;
if remaining > 0 {
result.push(sibling_anchor(&xs[target_idx + 1]));
if remaining > 1 {
result.push(folded_place(&format!("after:{}", remaining - 1)));
}
}
Cirru::List(merge_folded_runs(result))
}
}
}
fn fold_children_impl(node: &Cirru, max_children: usize, depth: usize, max_depth: usize) -> Cirru {
match node {
Cirru::Leaf(_) => node.clone(),
Cirru::List(xs) => {
if depth >= max_depth {
return folded_place("max-depth");
}
let Some((head, rest)) = xs.split_first() else {
return node.clone();
};
let mut result = vec![fold_children_impl(head, max_children, depth + 1, max_depth)];
let hidden = rest.len().saturating_sub(max_children);
if hidden <= 2 {
for c in rest {
result.push(fold_children_impl(c, max_children, depth + 1, max_depth));
}
return Cirru::List(merge_folded_runs(result));
}
for c in rest.iter().take(max_children) {
result.push(fold_children_impl(c, max_children, depth + 1, max_depth));
}
result.push(folded_place(&format!("inside:{hidden}")));
Cirru::List(merge_folded_runs(result))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn leaf(value: &str) -> Cirru {
Cirru::leaf(value)
}
#[test]
fn marks_a_focused_leaf_without_changing_its_value() {
let tree = Cirru::List(vec![leaf("call"), leaf("first"), leaf("target"), leaf("last")]);
assert_eq!(
focus_cirru_preview(&tree, &[2]),
Cirru::List(vec![
leaf("call"),
leaf("first"),
Cirru::List(vec![leaf("'FOCUSED"), leaf("target")]),
leaf("last"),
])
);
}
#[test]
fn keeps_anchors_and_fold_markers_for_sibling_ranges() {
let tree = Cirru::List(vec![
leaf("pipeline"),
leaf("name"),
Cirru::List(vec![leaf("config"), leaf("a"), leaf("b")]),
leaf("target"),
Cirru::List(vec![leaf("tail"), leaf("x"), leaf("y")]),
leaf("last"),
]);
assert_eq!(
focus_cirru_preview(&tree, &[3]),
Cirru::List(vec![
leaf("pipeline"),
leaf("name"),
leaf("'FOLDED:before:1"),
Cirru::List(vec![leaf("'FOCUSED"), leaf("target")]),
Cirru::List(vec![leaf("tail"), leaf("'FOLDED:inside:2")]),
leaf("'FOLDED:after:1"),
])
);
}
#[test]
fn merges_adjacent_folded_placeholders_with_a_count() {
let tree = Cirru::List(vec![leaf("root"), Cirru::List(vec![leaf("a")]), Cirru::List(vec![leaf("b")])]);
assert_eq!(
fold_children_impl(&tree, 3, 0, 1),
Cirru::List(vec![leaf("root"), leaf("'FOLDED:max-depth:2")])
);
}
#[test]
fn out_of_range_path_does_not_mark_a_focus_node() {
let tree = Cirru::List(vec![leaf("root"), leaf("child")]);
assert_eq!(focus_cirru_preview(&tree, &[4]), Cirru::List(vec![leaf("root"), leaf("child")]));
}
}