use super::{layout_canvas, NodeExtra, Placed};
use crate::tui::markdown::mermaid::{
canvas::{Canvas, Cls},
drawing::{draw_box, draw_seq_text, fit_label},
model::{Dir, Edge, Graph, Node, Shape},
painter::{MermaidArt, MermaidStyles, Oversize},
};
use std::collections::HashMap;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
enum Item {
Node(usize),
Group(usize),
}
pub(crate) fn render_grouped(
graph: &Graph,
styles: &MermaidStyles,
max_width: Option<usize>,
) -> Result<MermaidArt, Oversize> {
let mut proxy: HashMap<usize, usize> = HashMap::new();
for (gi, g) in graph.groups.iter().enumerate() {
if let Some(&ni) = graph.index.get(&g.id) {
proxy.insert(ni, gi);
}
}
let group_chain = |g: Option<usize>| -> Vec<usize> {
let mut chain = Vec::new();
let mut cur = g;
while let Some(gi) = cur {
chain.push(gi);
cur = graph.groups[gi].parent;
}
chain.reverse();
chain
};
let endpoint = |n: usize| -> (Item, Vec<usize>) {
match proxy.get(&n) {
Some(&gi) => (Item::Group(gi), group_chain(graph.groups[gi].parent)),
None => (Item::Node(n), group_chain(graph.node_group[n])),
}
};
let mut scope_edges: HashMap<Option<usize>, Vec<(Item, Item, usize)>> = HashMap::new();
let mut referenced: Vec<bool> = vec![false; graph.groups.len()];
for (ei, e) in graph.edges.iter().enumerate() {
let (item_f, chain_f) = endpoint(e.from);
let (item_t, chain_t) = endpoint(e.to);
let k = chain_f
.iter()
.zip(&chain_t)
.take_while(|(a, b)| a == b)
.count();
let scope = if k == 0 { None } else { Some(chain_f[k - 1]) };
let f = if chain_f.len() > k {
Item::Group(chain_f[k])
} else {
item_f
};
let t = if chain_t.len() > k {
Item::Group(chain_t[k])
} else {
item_t
};
if let Item::Group(gi) = f {
referenced[gi] = true;
}
if let Item::Group(gi) = t {
referenced[gi] = true;
}
scope_edges.entry(scope).or_default().push((f, t, ei));
}
let mut direct_nodes: HashMap<Option<usize>, Vec<usize>> = HashMap::new();
for (ni, g) in graph.node_group.iter().enumerate() {
if !proxy.contains_key(&ni) {
direct_nodes.entry(*g).or_default().push(ni);
}
}
let mut keep = vec![false; graph.groups.len()];
for gi in (0..graph.groups.len()).rev() {
let has_nodes = direct_nodes.get(&Some(gi)).is_some_and(|v| !v.is_empty());
let has_children =
(0..graph.groups.len()).any(|c| graph.groups[c].parent == Some(gi) && keep[c]);
keep[gi] = has_nodes || has_children || referenced[gi];
}
let mut canvas = build_scope(graph, None, &scope_edges, &direct_nodes, &keep, max_width)?;
match graph.dir {
Dir::Up => canvas.flip_vertical(),
Dir::Left => canvas.flip_horizontal(),
_ => {}
}
let (styled_lines, plain_lines) = canvas.to_lines(styles);
Ok(MermaidArt {
styled_lines,
plain_lines,
})
}
fn build_scope(
graph: &Graph,
scope: Option<usize>,
scope_edges: &HashMap<Option<usize>, Vec<(Item, Item, usize)>>,
direct_nodes: &HashMap<Option<usize>, Vec<usize>>,
keep: &[bool],
max_width: Option<usize>,
) -> Result<Canvas, Oversize> {
let mut items: Vec<Item> = Vec::new();
if let Some(nodes) = direct_nodes.get(&scope) {
items.extend(nodes.iter().map(|&n| Item::Node(n)));
}
let child_groups: Vec<usize> = (0..graph.groups.len())
.filter(|&gi| graph.groups[gi].parent == scope && keep[gi])
.collect();
items.extend(child_groups.iter().map(|&gi| Item::Group(gi)));
if items.is_empty() {
return Ok(Canvas::new(1, 1));
}
let mut index_of: HashMap<Item, usize> = HashMap::new();
let mut nodes: Vec<Node> = Vec::new();
let mut extras: Vec<NodeExtra> = Vec::new();
for item in &items {
index_of.insert(*item, nodes.len());
match item {
Item::Node(ni) => {
nodes.push(Node {
label: graph.nodes[*ni].label.clone(),
shape: graph.nodes[*ni].shape,
});
extras.push(NodeExtra::Plain);
}
Item::Group(gi) => {
let sub = build_scope(graph, Some(*gi), scope_edges, direct_nodes, keep, None)?;
nodes.push(Node {
label: graph.groups[*gi].label.clone(),
shape: Shape::Rect,
});
extras.push(NodeExtra::Frame(sub));
}
}
}
let mut edges: Vec<Edge> = Vec::new();
if let Some(list) = scope_edges.get(&scope) {
for (f, t, ei) in list {
let (Some(&fi), Some(&ti)) = (index_of.get(f), index_of.get(t)) else {
continue;
};
let e = &graph.edges[*ei];
edges.push(Edge {
from: fi,
to: ti,
label: e.label.clone(),
head_to: e.head_to,
head_from: e.head_from,
line: e.line,
});
}
}
let synth = Graph {
nodes,
edges,
index: HashMap::new(),
groups: Vec::new(),
node_group: Vec::new(),
dir: graph.dir,
};
layout_canvas(&synth, &extras, max_width)
}
pub(super) fn draw_frame(canvas: &mut Canvas, p: &Placed, title: &str, sub: &Canvas) {
draw_box(canvas, p, &[], Shape::Rect);
let t = fit_label(title, p.w.saturating_sub(4));
draw_seq_text(canvas, &format!(" {t} "), p.x + 1, p.y, Cls::Text);
let ox = p.x + 1 + (p.w - 2 - sub.w) / 2;
let oy = p.y + 1 + (p.h - 2 - sub.h) / 2;
canvas.blit(sub, ox, oy);
}