use super::boxdrawing::{line_start, preprocess_box_drawing, remap_error_lines};
use super::parser::{TreeViewAst, parse_tree_view};
use crate::errors::{Error, locate};
use crate::js::chars::{drop_indents, substring, trim_start};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum NodeType {
File,
Directory,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct TreeNode {
pub(crate) level: i64,
pub(crate) name: String,
pub(crate) node_type: NodeType,
pub(crate) icon: Option<String>,
pub(crate) css_class: Option<String>,
pub(crate) description: Option<String>,
pub(crate) children: Vec<usize>,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct TreeViewModel {
pub(crate) title: Option<String>,
pub(crate) acc_title: Option<String>,
pub(crate) acc_descr: Option<String>,
pub(crate) nodes: Vec<TreeNode>,
}
impl TreeViewModel {
pub(crate) fn root(&self) -> &TreeNode {
&self.nodes[0]
}
}
struct TreeViewDb {
nodes: Vec<TreeNode>,
stack: Vec<usize>,
}
impl TreeViewDb {
fn new() -> TreeViewDb {
let root = TreeNode {
level: -1,
name: "/".into(),
node_type: NodeType::Directory,
icon: None,
css_class: None,
description: None,
children: Vec::new(),
};
TreeViewDb {
nodes: vec![root],
stack: vec![0],
}
}
fn add_node(
&mut self,
level: i64,
name: String,
node_type: NodeType,
css_class: Option<String>,
icon: Option<String>,
description: Option<String>,
) {
while level <= self.nodes[*self.stack.last().expect("the root")].level {
self.stack.pop();
}
let id = self.nodes.len();
self.nodes.push(TreeNode {
level,
name,
node_type,
icon,
css_class,
description,
children: Vec::new(),
});
let parent = *self.stack.last().expect("the root");
self.nodes[parent].children.push(id);
self.stack.push(id);
}
}
fn populate(ast: &TreeViewAst, db: &mut TreeViewDb) {
let some = |s: &Option<String>| s.clone().filter(|s| !s.is_empty());
for node in &ast.nodes {
let level = node.indent.unwrap_or(0) as i64;
let (name, node_type) = match node.name.strip_suffix('/') {
Some(name) => (name, NodeType::Directory),
None => (node.name.as_str(), NodeType::File),
};
let icon = node.icon_annotation.as_ref().map(|icon| {
if icon.is_empty() {
"none".to_string()
} else {
icon.clone()
}
});
db.add_node(
level,
name.to_string(),
node_type,
some(&node.class_annotation),
icon,
some(&node.desc_annotation),
);
}
}
pub(crate) fn read_tree_view(source: &str) -> Result<TreeViewAst, Error> {
let pre = preprocess_box_drawing(source)?;
let error = match parse_tree_view(&pre.text) {
Ok(ast) => return Ok(ast),
Err(e) => e,
};
if pre.text == source {
return Err(error);
}
let Some(out) = error.line.checked_sub(1) else {
return Err(error);
};
let Some(&line) = pre.line_map.get(out) else {
return Err(error);
};
let start = line_start(source, line);
let at = match (error.column.checked_sub(1), pre.columns[out]) {
(None, _) => 0,
(Some(at), Some(column)) => column.at(at),
(Some(at), None) => {
let line = &source[start..];
line.len() - substring(line, at).len()
}
};
let written = locate(source, start + at);
let head = error.message.find('\n').unwrap_or(error.message.len());
let rest = &error.message[head..];
let mut message = remap_error_lines(&error.message[..head], &[(error.line, written.line)]);
if error.snippet.is_empty() {
message.push_str(rest);
} else {
message.push_str(&rest.replacen(&error.snippet, &written.snippet, 1));
}
Err(Error {
message,
line: written.line,
column: if error.column > 0 { written.column } else { 0 },
snippet: if error.snippet.is_empty() {
String::new()
} else {
written.snippet
},
..error
})
}
pub(crate) fn build_tree_view(ast: &TreeViewAst, title: Option<&str>) -> TreeViewModel {
let mut db = TreeViewDb::new();
populate(ast, &mut db);
let title = match ast.title.as_deref() {
Some(t) if !t.is_empty() => Some(t.to_string()),
_ => title.map(str::to_string),
};
TreeViewModel {
title,
acc_title: ast
.acc_title
.as_deref()
.filter(|t| !t.is_empty())
.map(|t| trim_start(t).to_string()),
acc_descr: ast
.acc_descr
.as_deref()
.filter(|d| !d.is_empty())
.map(drop_indents),
nodes: db.nodes,
}
}