use crate::syntax::{SyntaxKind, SyntaxNode, SyntaxToken};
use rowan::TextSize;
const INDENT_WIDTH: usize = 4;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum Sep {
#[default]
None,
Space,
Newline,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
enum Width {
#[default]
Open,
Blank,
Settled,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
struct Gap {
sep: Sep,
width: Width,
}
pub(crate) struct Formatter<'a> {
src: &'a str,
out: String,
indent: usize,
gap: Gap,
blank_lines: bool,
saw_newline: bool,
after_comment: bool,
eol: &'static str,
}
pub(crate) fn line_ending(src: &str) -> &'static str {
match src.find('\n') {
Some(i) if src.as_bytes()[..i].last() == Some(&b'\r') => "\r\n",
_ => "\n",
}
}
impl<'a> Formatter<'a> {
pub(crate) fn new(src: &'a str) -> Self {
Formatter {
src,
out: String::with_capacity(src.len()),
indent: 0,
gap: Gap::default(),
blank_lines: true,
saw_newline: false,
after_comment: false,
eol: line_ending(src),
}
}
pub(crate) fn finish(mut self) -> String {
let trimmed = self.out.trim_end().len();
self.out.truncate(trimmed);
if !self.out.is_empty() {
self.out.push_str(self.eol);
}
self.out
}
pub(crate) fn request(&mut self, sep: Sep) {
self.gap.sep = self.gap.sep.max(sep);
}
pub(crate) fn blank_line(&mut self) {
if self.blank_lines && self.gap.width == Width::Open {
self.gap.width = Width::Blank;
}
}
pub(crate) fn pin(&mut self, sep: Sep) {
self.gap = Gap {
sep,
width: Width::Settled,
};
}
pub(crate) fn settle_width(&mut self) {
self.gap.width = Width::Settled;
}
pub(crate) fn allow_blank_lines(&mut self, allow: bool) -> bool {
std::mem::replace(&mut self.blank_lines, allow)
}
pub(crate) fn indent(&mut self) {
self.indent += 1;
}
pub(crate) fn dedent(&mut self) {
self.indent = self.indent.saturating_sub(1);
}
fn materialize(&mut self) {
let gap = std::mem::take(&mut self.gap);
if self.out.is_empty() {
return;
}
match gap.sep {
Sep::None => {}
Sep::Space => self.out.push(' '),
Sep::Newline => self.newline(if gap.width == Width::Blank { 2 } else { 1 }),
}
}
fn newline(&mut self, count: usize) {
for _ in 0..count {
self.out.push_str(self.eol);
}
for _ in 0..self.indent * INDENT_WIDTH {
self.out.push(' ');
}
}
fn write_raw(&mut self, text: &str) {
self.materialize();
self.out.push_str(text);
}
pub(crate) fn token(&mut self, tok: &SyntaxToken) {
debug_assert!(!tok.kind().is_trivia(), "trivia must go through trivia()");
self.write_raw(tok.text());
self.saw_newline = false;
self.after_comment = false;
}
pub(crate) fn trivia(&mut self, tok: &SyntaxToken) {
match tok.kind() {
SyntaxKind::WHITESPACE => {
let newlines = tok.text().bytes().filter(|&b| b == b'\n').count();
if newlines >= 2 {
self.blank_line();
} else if newlines == 1 && self.after_comment {
self.request(Sep::Newline);
}
self.saw_newline |= newlines >= 1;
}
kind if kind.is_directive() => {
self.request(Sep::Newline);
self.write_raw(tok.text().trim_end());
self.request(Sep::Newline);
self.saw_newline = false;
self.after_comment = false;
}
kind if kind.is_comment() => {
if self.saw_newline {
self.request(Sep::Newline);
} else if kind == SyntaxKind::LINE_COMMENT {
self.pin(Sep::Space);
} else {
self.request(Sep::Space);
}
self.write_raw(tok.text());
if kind == SyntaxKind::LINE_COMMENT {
self.request(Sep::Newline);
} else {
self.request(Sep::Space);
}
self.saw_newline = false;
self.after_comment = true;
}
kind => unreachable!("not trivia: {kind:?}"),
}
}
pub(crate) fn verbatim(&mut self, node: &SyntaxNode) {
let src = self.src;
let mut start = node.text_range().start();
let mut end = node.text_range().end();
for tok in leading_trivia(node) {
self.trivia(&tok);
start = tok.text_range().end();
}
let trailing = trailing_trivia(node, start);
if let Some(first) = trailing.first() {
end = first.text_range().start();
}
if start < end {
self.write_raw(&src[usize::from(start)..usize::from(end)]);
self.saw_newline = false;
self.after_comment = false;
}
for tok in &trailing {
self.trivia(tok);
}
}
}
fn leading_trivia(node: &SyntaxNode) -> impl Iterator<Item = SyntaxToken> {
let end = node.text_range().end();
std::iter::successors(node.first_token(), |tok: &SyntaxToken| tok.next_token())
.take_while(move |tok| tok.text_range().end() <= end && tok.kind().is_trivia())
}
fn trailing_trivia(node: &SyntaxNode, floor: TextSize) -> Vec<SyntaxToken> {
let mut out: Vec<SyntaxToken> =
std::iter::successors(node.last_token(), |tok: &SyntaxToken| tok.prev_token())
.take_while(|tok| tok.text_range().start() >= floor && tok.kind().is_trivia())
.collect();
out.reverse();
out
}