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)
}