use std::fmt::{Display, Write};
use crate::writer::*;
use collect_macros::byte_offset;
use dioxus_rsx::{BodyNode, CallBody, IfmtInput};
use proc_macro2::LineColumn;
use quote::ToTokens;
use syn::{ExprMacro, MacroDelimiter};
mod buffer;
mod collect_macros;
mod component;
mod element;
mod expr;
mod writer;
#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq, Eq, Hash)]
pub struct FormattedBlock {
pub formatted: String,
pub start: usize,
pub end: usize,
}
pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
let mut formatted_blocks = Vec::new();
let parsed = syn::parse_file(contents).unwrap();
let mut macros = vec![];
collect_macros::collect_from_file(&parsed, &mut macros);
if macros.is_empty() {
return formatted_blocks;
}
let mut writer = Writer::new(contents);
let mut end_span = LineColumn { column: 0, line: 0 };
for item in macros {
let macro_path = &item.path.segments[0].ident;
if macro_path.span().start() < end_span {
continue;
}
let body = item.parse_body::<CallBody>().unwrap();
let rsx_start = macro_path.span().start();
writer.out.indent = leading_whitespaces(writer.src[rsx_start.line - 1]) / 4;
write_body(&mut writer, &body);
if writer.out.buf.contains('\n') {
writer.out.new_line().unwrap();
writer.out.tab().unwrap();
}
let span = match item.delimiter {
MacroDelimiter::Paren(b) => b.span,
MacroDelimiter::Brace(b) => b.span,
MacroDelimiter::Bracket(b) => b.span,
}
.join();
let mut formatted = String::new();
std::mem::swap(&mut formatted, &mut writer.out.buf);
let start = byte_offset(contents, span.start()) + 1;
let end = byte_offset(contents, span.end()) - 1;
let body_is_solo_expr = body.roots.len() == 1
&& matches!(body.roots[0], BodyNode::RawExpr(_) | BodyNode::Text(_));
if formatted.len() <= 80 && !formatted.contains('\n') && !body_is_solo_expr {
formatted = format!(" {formatted} ");
}
end_span = span.end();
if contents[start..end] == formatted {
continue;
}
formatted_blocks.push(FormattedBlock {
formatted,
start,
end,
});
}
formatted_blocks
}
pub fn write_block_out(body: CallBody) -> Option<String> {
let mut buf = Writer::new("");
write_body(&mut buf, &body);
buf.consume()
}
fn write_body(buf: &mut Writer, body: &CallBody) {
if buf.is_short_children(&body.roots).is_some() {
for idx in 0..body.roots.len() - 1 {
let ident = &body.roots[idx];
buf.write_ident(ident).unwrap();
write!(&mut buf.out.buf, ", ").unwrap();
}
let ident = &body.roots[body.roots.len() - 1];
buf.write_ident(ident).unwrap();
} else {
buf.write_body_indented(&body.roots).unwrap();
}
}
pub fn fmt_block_from_expr(raw: &str, expr: ExprMacro) -> Option<String> {
let body = syn::parse2::<CallBody>(expr.mac.tokens).unwrap();
let mut buf = Writer::new(raw);
write_body(&mut buf, &body);
buf.consume()
}
pub fn fmt_block(block: &str, indent_level: usize) -> Option<String> {
let body = syn::parse_str::<dioxus_rsx::CallBody>(block).unwrap();
let mut buf = Writer::new(block);
buf.out.indent = indent_level;
write_body(&mut buf, &body);
if buf.out.buf.contains('\n') {
buf.out.new_line().unwrap();
}
buf.consume()
}
pub fn apply_format(input: &str, block: FormattedBlock) -> String {
let start = block.start;
let end = block.end;
let (left, _) = input.split_at(start);
let (_, right) = input.split_at(end);
format!("{}{}{}", left, block.formatted, right)
}
pub fn apply_formats(input: &str, blocks: Vec<FormattedBlock>) -> String {
let mut out = String::new();
let mut last = 0;
for FormattedBlock {
formatted,
start,
end,
} in blocks
{
let prefix = &input[last..start];
out.push_str(prefix);
out.push_str(&formatted);
last = end;
}
let suffix = &input[last..];
out.push_str(suffix);
out
}
struct DisplayIfmt<'a>(&'a IfmtInput);
impl Display for DisplayIfmt<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let inner_tokens = self.0.source.as_ref().unwrap().to_token_stream();
inner_tokens.fmt(f)
}
}
pub(crate) fn ifmt_to_string(input: &IfmtInput) -> String {
let mut buf = String::new();
let display = DisplayIfmt(input);
write!(&mut buf, "{}", display).unwrap();
buf
}
pub(crate) fn write_ifmt(input: &IfmtInput, writable: &mut impl Write) -> std::fmt::Result {
let display = DisplayIfmt(input);
write!(writable, "{}", display)
}
pub fn leading_whitespaces(input: &str) -> usize {
input
.chars()
.map_while(|c| match c {
' ' => Some(1),
'\t' => Some(4),
_ => None,
})
.sum()
}