use std::fmt::Write;
use crate::{Atom, Capitalisation, FormatterError, FormatterResult};
pub fn render(atoms: &[Atom], indent: &str) -> FormatterResult<String> {
let mut buffer = String::new();
let mut indent_level: usize = 0;
for atom in atoms {
match atom {
Atom::Blankline => write!(buffer, "\n\n{}", indent.repeat(indent_level))?,
Atom::Empty => (),
Atom::Hardline => write!(buffer, "\n{}", indent.repeat(indent_level))?,
Atom::IndentEnd => {
if indent_level == 0 {
return Err(FormatterError::Query(
"Trying to close an unopened indentation block".into(),
None,
));
}
indent_level -= 1;
}
Atom::IndentStart => indent_level += 1,
Atom::Leaf {
content,
original_position,
single_line_no_indent,
multi_line_indent_all,
keep_whitespace,
capitalisation,
..
} => {
if *single_line_no_indent {
writeln!(buffer)?;
}
let content = if *keep_whitespace {
content
} else {
content.trim_end_matches('\n')
};
let mut content = if *multi_line_indent_all {
let cursor = current_column(&buffer) as i32;
let original_column = original_position.column as i32 - 1;
let indenting = cursor - original_column;
match indenting {
0 => content.into(),
n if n > 0 => add_spaces_after_newlines(content, indenting),
_ => try_removing_spaces_after_newlines(content, -indenting),
}
} else {
content.into()
};
match capitalisation {
Capitalisation::UpperCase => {
content = content.to_uppercase();
}
Capitalisation::LowerCase => {
content = content.to_lowercase();
}
_ => {}
}
write!(buffer, "{content}")?;
}
Atom::Literal(s) => write!(buffer, "{s}")?,
Atom::Space => write!(buffer, " ")?,
other => {
return Err(FormatterError::Internal(
format!("Found atom that should have been removed before rendering: {other:?}",),
None,
));
}
};
}
Ok(buffer)
}
fn current_column(s: &str) -> usize {
s.chars().rev().take_while(|c| *c != '\n').count()
}
fn add_spaces_after_newlines(s: &str, n: i32) -> String {
let mut result = String::new();
let chars = s.chars().peekable();
for c in chars {
result.push(c);
if c == '\n' {
for _ in 0..n {
result.push(' ');
}
}
}
result
}
fn try_removing_spaces_after_newlines(s: &str, n: i32) -> String {
let mut result = String::new();
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
result.push(c);
if c == '\n' {
for _ in 0..n {
if let Some(' ') = chars.peek() {
chars.next();
} else {
break;
}
}
}
}
result
}