lex_babel/formats/linetreeviz/
mod.rs1use super::icons::get_icon;
26use crate::error::FormatError;
27use crate::format::Format;
28use lex_core::lex::ast::traits::{AstNode, Container, VisualStructure};
29use lex_core::lex::ast::{ContentItem, Document};
30use std::collections::HashMap;
31
32fn format_content_item(
34 item: &ContentItem,
35 prefix: &str,
36 child_index: usize,
37 child_count: usize,
38 show_linum: bool,
39) -> String {
40 let mut output = String::new();
41 let is_last = child_index == child_count - 1;
42 let connector = if is_last { "└─" } else { "├─" };
43
44 let collapses = match item {
46 ContentItem::Paragraph(p) => p.collapses_with_children(),
47 ContentItem::List(l) => l.collapses_with_children(),
48 ContentItem::Session(s) => s.collapses_with_children(),
49 ContentItem::Definition(d) => d.collapses_with_children(),
50 ContentItem::Annotation(a) => a.collapses_with_children(),
51 ContentItem::VerbatimBlock(v) => v.collapses_with_children(),
52 _ => false,
53 };
54
55 if collapses {
56 let parent_icon = get_icon(item.node_type());
58 let children: Vec<&dyn AstNode> = match item {
59 ContentItem::Paragraph(p) => p.lines.iter().map(|l| l as &dyn AstNode).collect(),
60 ContentItem::List(l) => l.items.iter().map(|i| i as &dyn AstNode).collect(),
61 _ => Vec::new(),
62 };
63
64 for (i, child) in children.iter().enumerate() {
66 let child_is_last = i == children.len() - 1;
67 let child_icon = get_icon(child.node_type());
68
69 if i == 0 {
71 let linum_prefix = if show_linum {
72 format!("{:02} ", child.range().start.line + 1)
73 } else {
74 String::new()
75 };
76
77 output.push_str(&format!(
78 "{}{}{} {} {} {}\n",
79 linum_prefix,
80 prefix,
81 connector,
82 parent_icon,
83 child_icon,
84 child.display_label()
85 ));
86 } else {
87 let child_prefix = format!("{}{}", prefix, if is_last { " " } else { "│ " });
89 let child_connector = if child_is_last { "└─" } else { "├─" };
90 let linum_prefix = if show_linum {
91 format!("{:02} ", child.range().start.line + 1)
92 } else {
93 String::new()
94 };
95
96 output.push_str(&format!(
97 "{}{}{} {} {} {}\n",
98 linum_prefix,
99 child_prefix,
100 child_connector,
101 parent_icon,
102 child_icon,
103 child.display_label()
104 ));
105 }
106
107 }
110 } else {
111 let icon = get_icon(item.node_type());
113 let linum_prefix = if show_linum {
114 format!("{:02} ", item.range().start.line + 1)
115 } else {
116 String::new()
117 };
118
119 output.push_str(&format!(
120 "{}{}{} {} {}\n",
121 linum_prefix,
122 prefix,
123 connector,
124 icon,
125 item.display_label()
126 ));
127
128 let children = match item {
130 ContentItem::Session(s) => s.children(),
131 ContentItem::Definition(d) => d.children(),
132 ContentItem::ListItem(li) => li.children(),
133 ContentItem::Annotation(a) => a.children(),
134 _ => &[],
135 };
136
137 if !children.is_empty() {
138 let child_prefix = format!("{}{}", prefix, if is_last { " " } else { "│ " });
139 for (i, child) in children.iter().enumerate() {
140 output.push_str(&format_content_item(
141 child,
142 &child_prefix,
143 i,
144 children.len(),
145 show_linum,
146 ));
147 }
148 }
149 }
150
151 output
152}
153
154pub fn to_linetreeviz_str(doc: &Document) -> String {
156 to_linetreeviz_str_with_params(doc, &HashMap::new())
157}
158
159pub fn to_linetreeviz_str_with_params(doc: &Document, params: &HashMap<String, String>) -> String {
166 let show_linum = params
167 .get("show-linum")
168 .map(|v| v != "false")
169 .unwrap_or(false);
170
171 let icon = get_icon("Document");
172 let mut output = format!(
173 "{} Document ({} annotations, {} items)\n",
174 icon,
175 doc.annotations.len(),
176 doc.root.children.len()
177 );
178
179 let children = &doc.root.children;
180 for (i, child) in children.iter().enumerate() {
181 output.push_str(&format_content_item(
182 child,
183 "",
184 i,
185 children.len(),
186 show_linum,
187 ));
188 }
189
190 output
191}
192
193pub struct LinetreevizFormat;
195
196impl Format for LinetreevizFormat {
197 fn name(&self) -> &str {
198 "linetreeviz"
199 }
200
201 fn description(&self) -> &str {
202 "Tree visualization with collapsed containers (Paragraph/List)"
203 }
204
205 fn file_extensions(&self) -> &[&str] {
206 &["linetree"]
207 }
208
209 fn supports_serialization(&self) -> bool {
210 true
211 }
212
213 fn supports_parsing(&self) -> bool {
214 false
215 }
216
217 fn serialize(&self, doc: &Document) -> Result<String, FormatError> {
218 Ok(to_linetreeviz_str(doc))
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn test_icon_mapping() {
228 assert_eq!(get_icon("Session"), "§");
229 assert_eq!(get_icon("TextLine"), "↵");
230 assert_eq!(get_icon("ListItem"), "•");
231 assert_eq!(get_icon("Definition"), "≔");
232 assert_eq!(get_icon("Paragraph"), "¶");
233 assert_eq!(get_icon("List"), "☰");
234 }
235}