use std::collections::BTreeSet;
use std::fmt::{self, Write as _};
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ProtoDepth {
Fixed(usize),
All,
}
impl Default for ProtoDepth {
fn default() -> Self {
Self::Fixed(0)
}
}
impl ProtoDepth {
pub fn includes(self, relative: usize) -> bool {
match self {
Self::Fixed(limit) => relative <= limit,
Self::All => true,
}
}
}
impl fmt::Display for ProtoDepth {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Fixed(n) => write!(f, "{n}"),
Self::All => f.write_str("all"),
}
}
}
#[derive(Debug, Clone)]
pub struct ProtoNode {
pub parent: Option<usize>,
pub children: Vec<usize>,
}
pub fn build_proto_nodes(parents: &[Option<usize>]) -> Vec<ProtoNode> {
let mut nodes: Vec<ProtoNode> = (0..parents.len())
.map(|_| ProtoNode {
parent: None,
children: Vec::new(),
})
.collect();
for (id, parent) in parents.iter().enumerate() {
nodes[id].parent = *parent;
if let Some(parent) = parent
&& *parent < nodes.len()
{
nodes[*parent].children.push(id);
}
}
nodes
}
#[derive(Debug, Clone, Default)]
pub struct FocusPlan {
pub focus: Option<usize>,
pub ancestors: Vec<usize>,
pub visible: BTreeSet<usize>,
pub elided_at: Vec<usize>,
}
impl FocusPlan {
pub fn is_visible(&self, id: usize) -> bool {
self.visible.contains(&id)
}
pub fn is_elided(&self, id: usize) -> bool {
self.elided_at.contains(&id)
}
}
pub fn compute_focus_plan(nodes: &[ProtoNode], filters: &FocusRequest) -> FocusPlan {
if nodes.is_empty() {
return FocusPlan::default();
}
let focus_id = filters.proto.unwrap_or(0);
if focus_id >= nodes.len() {
return FocusPlan::default();
}
let mut ancestors = Vec::new();
let mut cursor = nodes[focus_id].parent;
while let Some(parent) = cursor {
ancestors.push(parent);
cursor = nodes[parent].parent;
}
ancestors.reverse();
let mut visible = BTreeSet::new();
let mut elided_at = Vec::new();
walk_below(nodes, focus_id, 0, filters.depth, &mut visible, &mut elided_at);
FocusPlan {
focus: Some(focus_id),
ancestors,
visible,
elided_at,
}
}
fn walk_below(
nodes: &[ProtoNode],
node_id: usize,
relative_depth: usize,
depth: ProtoDepth,
visible: &mut BTreeSet<usize>,
elided_at: &mut Vec<usize>,
) {
if !depth.includes(relative_depth) {
elided_at.push(node_id);
return;
}
visible.insert(node_id);
for child in &nodes[node_id].children {
walk_below(nodes, *child, relative_depth + 1, depth, visible, elided_at);
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
pub struct FocusRequest {
pub proto: Option<usize>,
pub depth: ProtoDepth,
}
#[derive(Debug, Clone, Default)]
pub struct ProtoSummaryRow {
pub id: usize,
pub depth_below_focus: usize,
pub name: Option<String>,
pub first: Option<String>,
pub lines: Option<(u32, u32)>,
pub instrs: Option<usize>,
pub children: Option<usize>,
}
pub fn format_proto_summary_row(row: &ProtoSummaryRow) -> String {
let mut output = String::new();
let _ = write!(output, "proto#{} <elided>", row.id);
let name = row.name.as_deref().unwrap_or("-");
let _ = write!(output, " name={name}");
match row.lines {
Some((start, end)) => {
let _ = write!(output, " lines={start}..{end}");
}
None => {
let _ = write!(output, " lines=-");
}
}
let first_rendered = row
.first
.as_deref()
.map(truncate_first)
.unwrap_or_else(|| "-".to_owned());
let _ = write!(output, " first={first_rendered}");
if let Some(instrs) = row.instrs {
let _ = write!(output, " instrs={instrs}");
}
if let Some(children) = row.children {
let _ = write!(output, " children={children}");
}
output
}
const FIRST_SNIPPET_MAX_CHARS: usize = 80;
fn truncate_first(raw: &str) -> String {
let mut snippet = String::new();
for (count, ch) in raw.chars().enumerate() {
if ch == '\n' || ch == '\r' {
break;
}
if count >= FIRST_SNIPPET_MAX_CHARS {
snippet.push('…');
break;
}
snippet.push(ch);
}
format!("\"{snippet}\"")
}
pub fn format_breadcrumb(plan: &FocusPlan) -> Option<String> {
let focus = plan.focus?;
if plan.ancestors.is_empty() {
return None;
}
let mut output = format!("focus proto#{focus} path=");
let mut first = true;
for ancestor in &plan.ancestors {
if !first {
output.push_str(" -> ");
}
first = false;
let _ = write!(output, "proto#{ancestor}");
}
let _ = write!(output, " -> proto#{focus}");
Some(output)
}
#[cfg(test)]
mod tests {
use super::*;
fn make_tree() -> Vec<ProtoNode> {
vec![
ProtoNode {
parent: None,
children: vec![1, 3],
},
ProtoNode {
parent: Some(0),
children: vec![2],
},
ProtoNode {
parent: Some(1),
children: vec![],
},
ProtoNode {
parent: Some(0),
children: vec![],
},
]
}
#[test]
fn default_focus_is_root_with_depth_zero() {
let plan = compute_focus_plan(
&make_tree(),
&FocusRequest {
proto: None,
depth: ProtoDepth::Fixed(0),
},
);
assert_eq!(plan.focus, Some(0));
assert!(plan.ancestors.is_empty());
assert_eq!(plan.visible.iter().copied().collect::<Vec<_>>(), vec![0]);
assert_eq!(plan.elided_at, vec![1, 3]);
}
#[test]
fn depth_one_expands_direct_children_and_elides_grandchildren() {
let plan = compute_focus_plan(
&make_tree(),
&FocusRequest {
proto: None,
depth: ProtoDepth::Fixed(1),
},
);
assert_eq!(plan.visible.iter().copied().collect::<Vec<_>>(), vec![0, 1, 3]);
assert_eq!(plan.elided_at, vec![2]);
}
#[test]
fn all_depth_is_fully_visible() {
let plan = compute_focus_plan(
&make_tree(),
&FocusRequest {
proto: None,
depth: ProtoDepth::All,
},
);
assert_eq!(
plan.visible.iter().copied().collect::<Vec<_>>(),
vec![0, 1, 2, 3]
);
assert!(plan.elided_at.is_empty());
}
#[test]
fn focus_on_child_records_ancestors() {
let plan = compute_focus_plan(
&make_tree(),
&FocusRequest {
proto: Some(2),
depth: ProtoDepth::Fixed(0),
},
);
assert_eq!(plan.focus, Some(2));
assert_eq!(plan.ancestors, vec![0, 1]);
assert_eq!(plan.visible.iter().copied().collect::<Vec<_>>(), vec![2]);
assert!(plan.elided_at.is_empty());
}
#[test]
fn unknown_focus_yields_empty_plan() {
let plan = compute_focus_plan(
&make_tree(),
&FocusRequest {
proto: Some(99),
depth: ProtoDepth::Fixed(0),
},
);
assert_eq!(plan.focus, None);
assert!(plan.visible.is_empty());
assert!(plan.elided_at.is_empty());
}
#[test]
fn format_row_renders_stable_shape() {
let row = ProtoSummaryRow {
id: 5,
depth_below_focus: 1,
name: Some("test_setfenv".to_owned()),
first: Some("local function read_value()".to_owned()),
lines: Some((3, 6)),
instrs: Some(27),
children: Some(2),
};
assert_eq!(
format_proto_summary_row(&row),
r#"proto#5 <elided> name=test_setfenv lines=3..6 first="local function read_value()" instrs=27 children=2"#
);
}
#[test]
fn format_row_truncates_long_first() {
let row = ProtoSummaryRow {
id: 1,
depth_below_focus: 0,
name: None,
first: Some("a".repeat(200)),
lines: None,
instrs: None,
children: None,
};
let text = format_proto_summary_row(&row);
assert!(text.contains("…"));
assert!(!text.contains(&"a".repeat(150)));
}
#[test]
fn breadcrumb_is_skipped_when_no_ancestors() {
let plan = FocusPlan {
focus: Some(0),
ancestors: Vec::new(),
visible: BTreeSet::from([0]),
elided_at: Vec::new(),
};
assert!(format_breadcrumb(&plan).is_none());
}
#[test]
fn breadcrumb_renders_path() {
let plan = FocusPlan {
focus: Some(2),
ancestors: vec![0, 1],
visible: BTreeSet::from([2]),
elided_at: Vec::new(),
};
assert_eq!(
format_breadcrumb(&plan).as_deref(),
Some("focus proto#2 path=proto#0 -> proto#1 -> proto#2")
);
}
}