use std::collections::{BTreeMap, HashMap};
use tan::expr::Expr;
use crate::{
layout::{Arranger, Layout},
types::Dialect,
util::{ensure_ends_with_empty_line, trim_separators},
};
const DEFAULT_INDENT_SIZE: usize = 4;
const DEFAULT_LINE_SIZE: usize = 80;
pub struct Formatter<'a> {
arranger: Arranger<'a>,
indent_size: usize,
#[allow(dead_code)]
line_size: usize,
pub dialect: Dialect,
indent: usize,
#[allow(dead_code)]
col: usize,
}
impl<'a> Formatter<'a> {
pub fn new(exprs: &'a [Expr]) -> Self {
Self::for_dialect(exprs, Dialect::default())
}
pub fn for_dialect(exprs: &'a [Expr], dialect: Dialect) -> Self {
Self {
arranger: Arranger::new(exprs, dialect),
indent: 0,
indent_size: DEFAULT_INDENT_SIZE,
line_size: DEFAULT_LINE_SIZE,
dialect,
col: 0,
}
}
fn apply_indent(&self, s: String, indent: usize) -> String {
format!("{ }{s}", " ".repeat(indent))
}
fn format_annotations(&self, ann: &HashMap<String, Expr>) -> String {
if ann.is_empty() {
return "".to_string();
}
let ann = BTreeMap::from_iter(ann);
let mut output = String::new();
for (key, value) in ann {
if key == "range" {
continue;
} else if let Expr::Bool(true) = value {
output.push_str(&format!("#{key} "));
} else {
output.push_str(&format!("#{value} "));
}
}
output
}
fn format_layout(&mut self, layout: &Layout) -> String {
match layout {
Layout::Item(s) => s.clone(),
Layout::Row(v, separator) => v
.iter()
.map(|l| self.format_layout(l))
.collect::<Vec<String>>()
.join(separator),
Layout::Stack(v) => v
.iter()
.map(|l| self.format_layout(l))
.collect::<Vec<String>>()
.join("\n"),
Layout::Indent(v, indent_size) => {
let indent_size = indent_size.unwrap_or(self.indent_size);
self.indent += indent_size;
let string = v
.iter()
.map(|l| {
let string = self.format_layout(l);
self.apply_indent(string, self.indent)
})
.collect::<Vec<String>>()
.join("\n");
self.indent -= indent_size;
string
}
Layout::Apply(l) => {
let string = self.format_layout(l);
self.apply_indent(string, self.indent)
}
Layout::Ann(ann, l) => {
let ann = self.format_annotations(ann);
let string = self.format_layout(l);
format!("{ann}{string}")
}
Layout::Separator => "".to_owned(),
}
}
pub fn format(mut self) -> String {
let layout = self.arranger.arrange();
let output = self.format_layout(&layout);
let output = trim_separators(&output);
ensure_ends_with_empty_line(&output)
}
}