use super::icons::get_icon;
use crate::error::FormatError;
use crate::format::Format;
use lex_core::lex::ast::traits::{AstNode, Container, VisualStructure};
use lex_core::lex::ast::{ContentItem, Document};
use std::collections::HashMap;
fn format_content_item(
item: &ContentItem,
prefix: &str,
child_index: usize,
child_count: usize,
show_linum: bool,
) -> String {
let mut output = String::new();
let is_last = child_index == child_count - 1;
let connector = if is_last { "└─" } else { "├─" };
let collapses = match item {
ContentItem::Paragraph(p) => p.collapses_with_children(),
ContentItem::List(l) => l.collapses_with_children(),
ContentItem::Session(s) => s.collapses_with_children(),
ContentItem::Definition(d) => d.collapses_with_children(),
ContentItem::Annotation(a) => a.collapses_with_children(),
ContentItem::VerbatimBlock(v) => v.collapses_with_children(),
_ => false,
};
if collapses {
let parent_icon = get_icon(item.node_type());
let children: Vec<&dyn AstNode> = match item {
ContentItem::Paragraph(p) => p.lines.iter().map(|l| l as &dyn AstNode).collect(),
ContentItem::List(l) => l.items.iter().map(|i| i as &dyn AstNode).collect(),
_ => Vec::new(),
};
for (i, child) in children.iter().enumerate() {
let child_is_last = i == children.len() - 1;
let child_icon = get_icon(child.node_type());
if i == 0 {
let linum_prefix = if show_linum {
format!("{:02} ", child.range().start.line + 1)
} else {
String::new()
};
output.push_str(&format!(
"{}{}{} {} {} {}\n",
linum_prefix,
prefix,
connector,
parent_icon,
child_icon,
child.display_label()
));
} else {
let child_prefix = format!("{}{}", prefix, if is_last { " " } else { "│ " });
let child_connector = if child_is_last { "└─" } else { "├─" };
let linum_prefix = if show_linum {
format!("{:02} ", child.range().start.line + 1)
} else {
String::new()
};
output.push_str(&format!(
"{}{}{} {} {} {}\n",
linum_prefix,
child_prefix,
child_connector,
parent_icon,
child_icon,
child.display_label()
));
}
}
} else {
let icon = get_icon(item.node_type());
let linum_prefix = if show_linum {
format!("{:02} ", item.range().start.line + 1)
} else {
String::new()
};
output.push_str(&format!(
"{}{}{} {} {}\n",
linum_prefix,
prefix,
connector,
icon,
item.display_label()
));
let children = match item {
ContentItem::Session(s) => s.children(),
ContentItem::Definition(d) => d.children(),
ContentItem::ListItem(li) => li.children(),
ContentItem::Annotation(a) => a.children(),
_ => &[],
};
if !children.is_empty() {
let child_prefix = format!("{}{}", prefix, if is_last { " " } else { "│ " });
for (i, child) in children.iter().enumerate() {
output.push_str(&format_content_item(
child,
&child_prefix,
i,
children.len(),
show_linum,
));
}
}
}
output
}
pub fn to_linetreeviz_str(doc: &Document) -> String {
to_linetreeviz_str_with_params(doc, &HashMap::new())
}
pub fn to_linetreeviz_str_with_params(doc: &Document, params: &HashMap<String, String>) -> String {
let show_linum = params
.get("show-linum")
.map(|v| v != "false")
.unwrap_or(false);
let icon = get_icon("Document");
let mut output = format!(
"{} Document ({} annotations, {} items)\n",
icon,
doc.annotations.len(),
doc.root.children.len()
);
let children = &doc.root.children;
for (i, child) in children.iter().enumerate() {
output.push_str(&format_content_item(
child,
"",
i,
children.len(),
show_linum,
));
}
output
}
pub struct LinetreevizFormat;
impl Format for LinetreevizFormat {
fn name(&self) -> &str {
"linetreeviz"
}
fn description(&self) -> &str {
"Tree visualization with collapsed containers (Paragraph/List)"
}
fn file_extensions(&self) -> &[&str] {
&["linetree"]
}
fn supports_serialization(&self) -> bool {
true
}
fn supports_parsing(&self) -> bool {
false
}
fn serialize(&self, doc: &Document) -> Result<String, FormatError> {
Ok(to_linetreeviz_str(doc))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_icon_mapping() {
assert_eq!(get_icon("Session"), "§");
assert_eq!(get_icon("TextLine"), "↵");
assert_eq!(get_icon("ListItem"), "•");
assert_eq!(get_icon("Definition"), "≔");
assert_eq!(get_icon("Paragraph"), "¶");
assert_eq!(get_icon("List"), "☰");
}
}