123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- #![doc = include_str!("../README.md")]
- #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
- #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
- 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 indent;
- mod prettier_please;
- mod writer;
- pub use indent::{IndentOptions, IndentType};
- /// A modification to the original file to be applied by an IDE
- ///
- /// Right now this re-writes entire rsx! blocks at a time, instead of precise line-by-line changes.
- ///
- /// In a "perfect" world we would have tiny edits to preserve things like cursor states and selections. The API here makes
- /// it possible to migrate to a more precise modification approach in the future without breaking existing code.
- ///
- /// Note that this is tailored to VSCode's TextEdit API and not a general Diff API. Line numbers are not accurate if
- /// multiple edits are applied in a single file without tracking text shifts.
- #[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq, Eq, Hash)]
- pub struct FormattedBlock {
- /// The new contents of the block
- pub formatted: String,
- /// The line number of the first line of the block.
- pub start: usize,
- /// The end of the block, exclusive.
- pub end: usize,
- }
- /// Format a file into a list of `FormattedBlock`s to be applied by an IDE for autoformatting.
- ///
- /// This function expects a complete file, not just a block of code. To format individual rsx! blocks, use fmt_block instead.
- ///
- /// The point here is to provide precise modifications of a source file so an accompanying IDE tool can map these changes
- /// back to the file precisely.
- ///
- /// Nested blocks of RSX will be handled automatically
- pub fn fmt_file(contents: &str, indent: IndentOptions) -> 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);
- // No macros, no work to do
- if macros.is_empty() {
- return formatted_blocks;
- }
- let mut writer = Writer::new(contents);
- writer.out.indent = indent;
- // Don't parse nested macros
- let mut end_span = LineColumn { column: 0, line: 0 };
- for item in macros {
- let macro_path = &item.path.segments[0].ident;
- // this macro is inside the last macro we parsed, skip it
- 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_level = writer
- .out
- .indent
- .count_indents(writer.src[rsx_start.line - 1]);
- write_body(&mut writer, &body);
- // writing idents leaves the final line ended at the end of the last ident
- 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;
- // Rustfmt will remove the space between the macro and the opening paren if the macro is a single expression
- 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) {
- match body.roots.len() {
- 0 => {}
- 1 if matches!(body.roots[0], BodyNode::Text(_)) => {
- write!(buf.out, " ").unwrap();
- buf.write_ident(&body.roots[0]).unwrap();
- write!(buf.out, " ").unwrap();
- }
- _ => 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, indent: IndentOptions) -> Option<String> {
- let body = syn::parse_str::<dioxus_rsx::CallBody>(block).unwrap();
- let mut buf = Writer::new(block);
- buf.out.indent = indent;
- buf.out.indent_level = indent_level;
- write_body(&mut buf, &body);
- // writing idents leaves the final line ended at the end of the last ident
- 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)
- }
- // Apply all the blocks
- 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)
- }
|