فهرست منبع

wip: more cleanup, more tests

Jonathan Kelley 3 سال پیش
والد
کامیت
d70b436157

+ 3 - 74
packages/autofmt/src/block.rs

@@ -1,79 +1,8 @@
-use crate::{util::*, write_ident};
+use crate::{util::*, FormattedBlock};
 use dioxus_rsx::*;
 use std::fmt::Write;
 use triple_accel::{levenshtein_search, Match};
 
-#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq, Hash)]
-pub struct FormattedBlock {
-    pub formatted: String,
-    pub start: usize,
-    pub end: usize,
-}
-
-pub fn get_format_blocks(contents: &str) -> Vec<FormattedBlock> {
-    let matches = levenshtein_search(b"rsx! {", contents.as_bytes()).peekable();
-
-    let mut formatted_blocks = Vec::new();
-    let mut last_bracket_end = 0;
-
-    // find the rsx! marker
-    for Match { start, end, k } in matches {
-        // ensure the marker is not nested
-        if start < last_bracket_end {
-            continue;
-        }
-
-        let remaining = &contents[end - 1..];
-        let bracket_end = find_bracket_end(remaining).unwrap();
-        let sub_string = &contents[end..bracket_end + end - 1];
-        last_bracket_end = bracket_end + end - 1;
-
-        let new = fmt_block(sub_string).unwrap();
-
-        let stripped = &contents[end + 1..bracket_end + end - 1];
-
-        if stripped == new {
-            continue;
-        }
-
-        // if we have code to push, we want the code to end up on the right lines with the right indentation
-        let mut output = String::new();
-        writeln!(output).unwrap();
-
-        for line in new.lines() {
-            writeln!(output, "{}", line).ok();
-        }
-
-        formatted_blocks.push(FormattedBlock {
-            formatted: output,
-            start: end,
-            end: end + bracket_end - 1,
-        });
-    }
-
-    formatted_blocks
-}
-
-struct Isolate<'a> {
-    contents: &'a str,
-    start: usize,
-    end: usize,
-}
-fn isolate_body_of_rsx(contents: &str, Match { start, end, k }: triple_accel::Match) -> Isolate {
-    todo!()
-}
-
-pub fn fmt_block(block: &str) -> Option<String> {
-    let mut buf = String::new();
-    let lines = block.split('\n').collect::<Vec<_>>();
-
-    for node in &syn::parse_str::<CallBody>(block).ok()?.roots {
-        write_ident(&mut buf, &lines, node, 0).ok()?;
-    }
-
-    Some(buf)
-}
-
 #[test]
 fn format_block_basic() {
     let block = r#"
@@ -129,7 +58,7 @@ fn format_block_basic() {
 
     // div { class: "asdasd", p { "hello!" } }
 
-    let edits = get_format_blocks(block);
+    // let edits = get_format_blocks(block);
 
-    println!("{}", edits[0].formatted);
+    // println!("{}", edits[0].formatted);
 }

+ 75 - 9
packages/autofmt/src/buffer.rs

@@ -1,17 +1,83 @@
-use std::fmt::Result;
+use std::fmt::{Result, Write};
 
-struct Buffer {
-    buf: String,
-    cur_line: usize,
-    cur_indent: usize,
+use dioxus_rsx::BodyNode;
+
+pub struct Buffer {
+    pub buf: String,
+    pub line: usize,
+    pub indent: usize,
 }
 
 impl Buffer {
-    fn write_tabs(&mut self, num: usize) -> Result {
-        todo!()
+    pub fn new() -> Self {
+        Self {
+            buf: String::new(),
+            line: 0,
+            indent: 0,
+        }
+    }
+
+    // Create a new line and tab it to the current tab level
+    pub fn tabbed_line(&mut self) -> Result {
+        self.new_line()?;
+        self.tab()
+    }
+
+    // Create a new line and tab it to the current tab level
+    pub fn indented_tabbed_line(&mut self) -> Result {
+        self.new_line()?;
+        self.indented_tab()
+    }
+
+    pub fn tab(&mut self) -> Result {
+        self.write_tabs(self.indent)
+    }
+
+    pub fn indented_tab(&mut self) -> Result {
+        self.write_tabs(self.indent + 1)
+    }
+
+    pub fn write_tabs(&mut self, num: usize) -> std::fmt::Result {
+        for _ in 0..num {
+            write!(self.buf, "    ")?
+        }
+        Ok(())
+    }
+
+    pub fn new_line(&mut self) -> Result {
+        writeln!(self.buf)
+    }
+
+    pub fn write_indented_ident(&mut self, lines: &[&str], node: &BodyNode) -> Result {
+        self.write_ident(lines, node)?;
+        Ok(())
     }
 
-    fn new_line(&mut self) -> Result {
-        todo!()
+    pub fn write_ident(&mut self, lines: &[&str], node: &BodyNode) -> Result {
+        match node {
+            BodyNode::Element(el) => self.write_element(el, lines),
+            BodyNode::Component(component) => self.write_component(component, lines),
+            BodyNode::Text(text) => self.write_text(text),
+            BodyNode::RawExpr(exp) => self.write_raw_expr(exp, lines),
+        }
+    }
+
+    pub fn write_text(&mut self, text: &syn::LitStr) -> Result {
+        write!(self.buf, "\"{}\"", text.value())
+    }
+
+    // Push out the indent level and write each component, line by line
+    pub fn write_body_indented(&mut self, children: &[BodyNode], lines: &[&str]) -> Result {
+        self.indent += 1;
+        for child in children {
+            // Exprs handle their own indenting/line breaks
+            if !matches!(child, BodyNode::RawExpr(_)) {
+                self.tabbed_line()?;
+            }
+
+            self.write_ident(lines, child)?;
+        }
+        self.indent -= 1;
+        Ok(())
     }
 }

+ 76 - 69
packages/autofmt/src/component.rs

@@ -1,78 +1,85 @@
-use crate::{util::*, write_ident};
+use crate::Buffer;
 use dioxus_rsx::*;
 use quote::ToTokens;
-use std::fmt::{self, Write};
+use std::fmt::{self, Result, Write};
 
-pub fn write_component(
-    component: &Component,
-    buf: &mut String,
-    indent: usize,
-    lines: &[&str],
-) -> Result<(), fmt::Error> {
-    let Component {
-        name,
-        body,
-        children,
-        manual_props,
-        prop_gen_args,
-    } = component;
-    let mut name = name.to_token_stream().to_string();
-    name.retain(|c| !c.is_whitespace());
-    write_tabs(buf, indent)?;
-    write!(buf, "{name}")?;
-    if let Some(generics) = prop_gen_args {
-        let mut written = generics.to_token_stream().to_string();
-        written.retain(|c| !c.is_whitespace());
-        write!(buf, "{}", written)?;
-    }
-    write!(buf, " {{")?;
-    if !body.is_empty() || !children.is_empty() {
-        writeln!(buf)?;
-    }
-    for field in body {
-        write_tabs(buf, indent + 1)?;
-        let name = &field.name;
-        match &field.content {
-            ContentField::ManExpr(exp) => {
-                let out = prettyplease::unparse_expr(exp);
-                writeln!(buf, "{}: {},", name, out)?;
-            }
-            ContentField::Formatted(s) => {
-                writeln!(buf, "{}: \"{}\",", name, s.value())?;
-            }
-            ContentField::OnHandlerRaw(exp) => {
-                let out = prettyplease::unparse_expr(exp);
-                let mut lines = out.split('\n').peekable();
-                let first = lines.next().unwrap();
-                write!(buf, "{}: {}", name, first)?;
-                for line in lines {
-                    writeln!(buf)?;
-                    write_tabs(buf, indent + 1)?;
-                    write!(buf, "{}", line)?;
+impl Buffer {
+    pub fn write_component(
+        &mut self,
+        Component {
+            name,
+            body,
+            children,
+            manual_props,
+            prop_gen_args,
+        }: &Component,
+        lines: &[&str],
+    ) -> Result {
+        let mut name = name.to_token_stream().to_string();
+        name.retain(|c| !c.is_whitespace());
+        self.tab()?;
+        write!(self.buf, "{name}")?;
+
+        if let Some(generics) = prop_gen_args {
+            let mut written = generics.to_token_stream().to_string();
+            written.retain(|c| !c.is_whitespace());
+            write!(self.buf, "{}", written)?;
+        }
+
+        write!(self.buf, " {{")?;
+
+        if !body.is_empty() || !children.is_empty() {
+            self.new_line()?;
+        }
+
+        for field in body {
+            self.indented_tab()?;
+            let name = &field.name;
+            match &field.content {
+                ContentField::ManExpr(exp) => {
+                    let out = prettyplease::unparse_expr(exp);
+                    writeln!(self.buf, "{}: {},", name, out)?;
+                }
+                ContentField::Formatted(s) => {
+                    writeln!(self.buf, "{}: \"{}\",", name, s.value())?;
+                }
+                ContentField::OnHandlerRaw(exp) => {
+                    let out = prettyplease::unparse_expr(exp);
+                    let mut lines = out.split('\n').peekable();
+                    let first = lines.next().unwrap();
+                    write!(self.buf, "{}: {}", name, first)?;
+                    for line in lines {
+                        self.new_line()?;
+                        self.indented_tab()?;
+                        write!(self.buf, "{}", line)?;
+                    }
+                    writeln!(self.buf, ",")?;
                 }
-                writeln!(buf, ",")?;
             }
         }
-    }
-    if let Some(exp) = manual_props {
-        write_tabs(buf, indent + 1)?;
-        let out = prettyplease::unparse_expr(exp);
-        let mut lines = out.split('\n').peekable();
-        let first = lines.next().unwrap();
-        write!(buf, "..{}", first)?;
-        for line in lines {
-            writeln!(buf)?;
-            write_tabs(buf, indent + 1)?;
-            write!(buf, "{}", line)?;
+
+        if let Some(exp) = manual_props {
+            self.indented_tab()?;
+            let out = prettyplease::unparse_expr(exp);
+            let mut lines = out.split('\n').peekable();
+            let first = lines.next().unwrap();
+            write!(self.buf, "..{}", first)?;
+            for line in lines {
+                self.new_line()?;
+                self.indented_tab()?;
+                write!(self.buf, "{}", line)?;
+            }
+            self.new_line()?;
         }
-        writeln!(buf)?;
-    }
-    for child in children {
-        write_ident(buf, lines, child, indent + 1)?;
-    }
-    if !body.is_empty() || !children.is_empty() {
-        write_tabs(buf, indent)?;
+
+        for child in children {
+            self.write_indented_ident(lines, child)?;
+        }
+
+        if !body.is_empty() || !children.is_empty() {
+            self.tab()?;
+        }
+        writeln!(self.buf, "}}")?;
+        Ok(())
     }
-    writeln!(buf, "}}")?;
-    Ok(())
 }

+ 167 - 171
packages/autofmt/src/element.rs

@@ -1,6 +1,6 @@
-use crate::{util::*, write_ident};
+use crate::{util::*, Buffer};
 use dioxus_rsx::*;
-use std::{fmt, fmt::Result, fmt::Write};
+use std::{fmt::Result, fmt::Write};
 
 enum ShortOptimization {
     // Special because we want to print the closing bracket immediately
@@ -16,108 +16,181 @@ enum ShortOptimization {
     NoOpt,
 }
 
-pub fn write_element(
-    Element {
-        name,
-        key,
-        attributes,
-        children,
-        _is_static,
-    }: &Element,
-    buf: &mut String,
-    lines: &[&str],
-    indent: usize,
-) -> Result {
-    /*
-        1. Write the tag
-        2. Write the key
-        3. Write the attributes
-        4. Write the children
-    */
-
-    write!(buf, "{name} {{")?;
-
-    // decide if we have any special optimizations
-    // Default with none, opt the cases in one-by-one
-    let mut opt_level = ShortOptimization::NoOpt;
-
-    // check if we have a lot of attributes
-    let is_short_attr_list = is_short_attrs(attributes);
-    let is_small_children = is_short_children(children);
-
-    // if we have few attributes and a lot of children, place the attrs on top
-    if is_short_attr_list && !is_small_children {
-        opt_level = ShortOptimization::PropsOnTop;
-    }
+impl Buffer {
+    pub fn write_element(
+        &mut self,
+        Element {
+            name,
+            key,
+            attributes,
+            children,
+            _is_static,
+        }: &Element,
+        lines: &[&str],
+    ) -> Result {
+        /*
+            1. Write the tag
+            2. Write the key
+            3. Write the attributes
+            4. Write the children
+        */
+
+        write!(self.buf, "{name} {{")?;
+
+        // decide if we have any special optimizations
+        // Default with none, opt the cases in one-by-one
+        let mut opt_level = ShortOptimization::NoOpt;
+
+        // check if we have a lot of attributes
+        let is_short_attr_list = is_short_attrs(attributes);
+        let is_small_children = is_short_children(children);
+
+        // if we have few attributes and a lot of children, place the attrs on top
+        if is_short_attr_list && !is_small_children {
+            opt_level = ShortOptimization::PropsOnTop;
+        }
 
-    // if we have few children and few attributes, make it a one-liner
-    if is_short_attr_list && is_small_children {
-        opt_level = ShortOptimization::Oneliner;
-    }
+        // even if the attr is long, it should be put on one line
+        if !is_short_attr_list && attributes.len() <= 1 {
+            if children.is_empty() {
+                opt_level = ShortOptimization::Oneliner;
+            } else {
+                opt_level = ShortOptimization::PropsOnTop;
+            }
+        }
 
-    // If there's nothing at all, empty optimization
-    if attributes.is_empty() && children.is_empty() && key.is_none() {
-        opt_level = ShortOptimization::Empty;
-    }
+        // if we have few children and few attributes, make it a one-liner
+        if is_short_attr_list && is_small_children {
+            opt_level = ShortOptimization::Oneliner;
+        }
 
-    match opt_level {
-        ShortOptimization::Empty => write!(buf, "}}")?,
-        ShortOptimization::Oneliner => {
-            write!(buf, " ")?;
-            write_attributes(buf, attributes, true, indent)?;
+        // If there's nothing at all, empty optimization
+        if attributes.is_empty() && children.is_empty() && key.is_none() {
+            opt_level = ShortOptimization::Empty;
+        }
 
-            if !children.is_empty() && !attributes.is_empty() {
-                write!(buf, ", ")?;
+        match opt_level {
+            ShortOptimization::Empty => write!(self.buf, "}}")?,
+            ShortOptimization::Oneliner => {
+                write!(self.buf, " ")?;
+                self.write_attributes(attributes, true)?;
+
+                if !children.is_empty() && !attributes.is_empty() {
+                    write!(self.buf, ", ")?;
+                }
+
+                // write the children
+                for child in children {
+                    self.write_ident(lines, child)?;
+                }
+
+                write!(self.buf, " }}")?;
             }
 
-            // write the children
-            for child in children {
-                write_ident(buf, lines, child, indent + 1)?;
+            ShortOptimization::PropsOnTop => {
+                write!(self.buf, " ")?;
+                self.write_attributes(attributes, true)?;
+
+                if !children.is_empty() && !attributes.is_empty() {
+                    write!(self.buf, ",")?;
+                }
+
+                // write the children
+                self.write_body_indented(children, lines)?;
+
+                self.tabbed_line()?;
+                write!(self.buf, "}}")?;
             }
 
-            write!(buf, " }}")?;
-        }
+            ShortOptimization::NoOpt => {
+                // write the key
+
+                // write the attributes
+                self.write_attributes(attributes, false)?;
 
-        ShortOptimization::PropsOnTop => {
-            write!(buf, " ")?;
-            write_attributes(buf, attributes, true, indent)?;
+                self.write_body_indented(children, lines)?;
 
-            if !children.is_empty() && !attributes.is_empty() {
-                write!(buf, ",")?;
+                self.tabbed_line()?;
+                write!(self.buf, "}}")?;
             }
+        }
 
-            // write the children
-            for child in children {
-                writeln!(buf)?;
-                write_tabs(buf, indent + 1)?;
-                write_ident(buf, lines, child, indent + 1)?;
+        Ok(())
+    }
+
+    fn write_attributes(&mut self, attributes: &[ElementAttrNamed], sameline: bool) -> Result {
+        let mut attr_iter = attributes.iter().peekable();
+
+        while let Some(attr) = attr_iter.next() {
+            if !sameline {
+                self.indented_tabbed_line()?;
             }
+            self.write_attribute(attr)?;
 
-            writeln!(buf)?;
-            write_tabs(buf, indent)?;
-            write!(buf, "}}")?;
+            if attr_iter.peek().is_some() {
+                write!(self.buf, ",")?;
+
+                if sameline {
+                    write!(self.buf, " ")?;
+                }
+            }
         }
 
-        ShortOptimization::NoOpt => {
-            // write the key
+        Ok(())
+    }
+
+    fn write_attribute(&mut self, attr: &ElementAttrNamed) -> Result {
+        match &attr.attr {
+            ElementAttr::AttrText { name, value } => {
+                write!(self.buf, "{name}: \"{value}\"", value = value.value())?;
+            }
+            ElementAttr::AttrExpression { name, value } => {
+                let out = prettyplease::unparse_expr(value);
+                write!(self.buf, "{}: {}", name, out)?;
+            }
 
-            // write the attributes
-            write_attributes(buf, attributes, false, indent)?;
+            ElementAttr::CustomAttrText { name, value } => {
+                write!(
+                    self.buf,
+                    "\"{name}\": \"{value}\"",
+                    name = name.value(),
+                    value = value.value()
+                )?;
+            }
 
-            // write the children
-            for child in children {
-                writeln!(buf)?;
-                write_tabs(buf, indent + 1)?;
-                write_ident(buf, lines, child, indent + 1)?;
+            ElementAttr::CustomAttrExpression { name, value } => {
+                let out = prettyplease::unparse_expr(value);
+                write!(self.buf, "\"{}\": {}", name.value(), out)?;
             }
 
-            writeln!(buf)?;
-            write_tabs(buf, indent)?;
-            write!(buf, "}}")?;
+            ElementAttr::EventTokens { name, tokens } => {
+                let out = prettyplease::unparse_expr(tokens);
+
+                let mut lines = out.split('\n').peekable();
+                let first = lines.next().unwrap();
+
+                // a one-liner for whatever reason
+                // Does not need a new line
+                if lines.peek().is_none() {
+                    write!(self.buf, "{}: {}", name, first)?;
+                } else {
+                    writeln!(self.buf, "{}: {}", name, first)?;
+
+                    while let Some(line) = lines.next() {
+                        self.indented_tab()?;
+                        write!(self.buf, "{}", line)?;
+                        if lines.peek().is_none() {
+                            write!(self.buf, "")?;
+                        } else {
+                            writeln!(self.buf)?;
+                        }
+                    }
+                }
+            }
         }
-    }
 
-    Ok(())
+        Ok(())
+    }
 }
 
 fn is_short_attrs(attrs: &[ElementAttrNamed]) -> bool {
@@ -132,106 +205,29 @@ fn is_short_children(children: &[BodyNode]) -> bool {
         return true;
     }
 
-    if children.len() == 1 {
-        if let BodyNode::Text(ref text) = &children[0] {
-            return text.value().len() < 80;
+    match children {
+        [BodyNode::Text(ref text)] => text.value().len() < 80,
+        [BodyNode::Element(ref el)] => {
+            // && !el.attributes.iter().any(|f| f.attr.is_expr())
+
+            extract_attr_len(&el.attributes) < 80 && is_short_children(&el.children)
         }
+        _ => false,
     }
-
-    false
 }
 
 fn write_key() {
     // if let Some(key) = key.as_ref().map(|f| f.value()) {
     //     if is_long_attr_list {
-    //         writeln!(buf)?;
-    //         write_tabs(buf, indent + 1)?;
+    //         self.new_line()?;
+    //         self.write_tabs( indent + 1)?;
     //     } else {
-    //         write!(buf, " ")?;
+    //         write!(self.buf, " ")?;
     //     }
-    //     write!(buf, "key: \"{key}\"")?;
+    //     write!(self.buf, "key: \"{key}\"")?;
 
     //     if !attributes.is_empty() {
-    //         write!(buf, ",")?;
+    //         write!(self.buf, ",")?;
     //     }
     // }
 }
-
-fn write_attributes(
-    buf: &mut String,
-    attributes: &[ElementAttrNamed],
-    sameline: bool,
-    indent: usize,
-) -> Result {
-    let mut attr_iter = attributes.iter().peekable();
-
-    while let Some(attr) = attr_iter.next() {
-        write_attribute(buf, attr, indent)?;
-
-        if attr_iter.peek().is_some() {
-            write!(buf, ",")?;
-
-            if sameline {
-                write!(buf, " ")?;
-            } else {
-                writeln!(buf)?;
-                write_tabs(buf, indent + 1)?;
-            }
-        }
-    }
-
-    Ok(())
-}
-
-fn write_attribute(buf: &mut String, attr: &ElementAttrNamed, indent: usize) -> Result {
-    match &attr.attr {
-        ElementAttr::AttrText { name, value } => {
-            write!(buf, "{name}: \"{value}\"", value = value.value())?;
-        }
-        ElementAttr::AttrExpression { name, value } => {
-            let out = prettyplease::unparse_expr(value);
-            write!(buf, "{}: {}", name, out)?;
-        }
-
-        ElementAttr::CustomAttrText { name, value } => {
-            write!(
-                buf,
-                "\"{name}\": \"{value}\"",
-                name = name.value(),
-                value = value.value()
-            )?;
-        }
-
-        ElementAttr::CustomAttrExpression { name, value } => {
-            let out = prettyplease::unparse_expr(value);
-            write!(buf, "\"{}\": {}", name.value(), out)?;
-        }
-
-        ElementAttr::EventTokens { name, tokens } => {
-            let out = prettyplease::unparse_expr(tokens);
-
-            let mut lines = out.split('\n').peekable();
-            let first = lines.next().unwrap();
-
-            // a one-liner for whatever reason
-            // Does not need a new line
-            if lines.peek().is_none() {
-                write!(buf, "{}: {}", name, first)?;
-            } else {
-                writeln!(buf, "{}: {}", name, first)?;
-
-                while let Some(line) = lines.next() {
-                    write_tabs(buf, indent + 1)?;
-                    write!(buf, "{}", line)?;
-                    if lines.peek().is_none() {
-                        write!(buf, "")?;
-                    } else {
-                        writeln!(buf)?;
-                    }
-                }
-            }
-        }
-    }
-
-    Ok(())
-}

+ 47 - 33
packages/autofmt/src/expr.rs

@@ -1,39 +1,53 @@
 //! pretty printer for rsx!
-use std::fmt::{self, Write};
-
-pub fn write_raw_expr(
-    exp: &syn::Expr,
-    indent: usize,
-    lines: &[&str],
-    buf: &mut String,
-) -> Result<(), fmt::Error> {
-    use syn::spanned::Spanned;
-    let placement = exp.span();
-    let start = placement.start();
-    let end = placement.end();
-    let num_spaces_desired = (indent * 4) as isize;
-    let first = lines[start.line - 1];
-    let num_spaces_real = first.chars().take_while(|c| c.is_whitespace()).count() as isize;
-    let offset = num_spaces_real - num_spaces_desired;
-
-    for line_id in start.line - 1..end.line {
-        let line = lines[line_id];
-
-        // trim the leading whitespace
-
-        if offset < 0 {
-            for _ in 0..-offset {
-                write!(buf, " ")?;
-            }
+use std::fmt::{self, Result, Write};
+
+use crate::Buffer;
+
+impl Buffer {
+    pub fn write_raw_expr(&mut self, exp: &syn::Expr, lines: &[&str]) -> Result {
+        /*
+        We want to normalize the expr to the appropriate indent level.
+        */
+
+        // in a perfect world, just fire up the rust pretty printer
+        // pretty_print_rust_code_as_if_it_were_rustfmt()
+
+        use syn::spanned::Spanned;
+        let placement = exp.span();
+        let start = placement.start();
+        let end = placement.end();
+        let num_spaces_desired = (self.indent * 4) as isize;
+
+        let first = lines[start.line - 1];
+        let num_spaces_real = first.chars().take_while(|c| c.is_whitespace()).count() as isize;
 
-            writeln!(buf, "{}", line)?;
-        } else {
-            let offset = offset as usize;
+        let offset = num_spaces_real - num_spaces_desired;
 
-            let right = &line[offset..];
-            writeln!(buf, "{}", right)?;
+        for line in &lines[start.line - 1..end.line] {
+            writeln!(self.buf)?;
+            // trim the leading whitespace
+            if offset < 0 {
+                for _ in 0..-offset {
+                    write!(self.buf, " ")?;
+                }
+
+                write!(self.buf, "{}", line)?;
+            } else {
+                let offset = offset as usize;
+                let right = &line[offset..];
+                write!(self.buf, "{}", right)?;
+            }
         }
-    }
 
-    Ok(())
+        Ok(())
+    }
 }
+
+// :(
+// fn pretty_print_rust_code_as_if_it_were_rustfmt(code: &str) -> String {
+//     let formatted = prettyplease::unparse_expr(exp);
+//     for line in formatted.lines() {
+//         write!(self.buf, "{}", line)?;
+//         self.new_line()?;
+//     }
+// }

+ 0 - 23
packages/autofmt/src/ident.rs

@@ -1,23 +0,0 @@
-use crate::{
-    component::write_component, element::write_element, expr::write_raw_expr, util::write_tabs,
-};
-use dioxus_rsx::*;
-use std::fmt::{self, Write};
-
-pub fn write_ident(
-    buf: &mut String,
-    lines: &[&str],
-    node: &BodyNode,
-    indent: usize,
-) -> fmt::Result {
-    match node {
-        BodyNode::Element(el) => write_element(el, buf, lines, indent),
-        BodyNode::Component(component) => write_component(component, buf, indent, lines),
-        BodyNode::Text(text) => write_text(text, buf, indent),
-        BodyNode::RawExpr(exp) => write_raw_expr(exp, indent, lines, buf),
-    }
-}
-
-fn write_text(text: &syn::LitStr, buf: &mut String, indent: usize) -> fmt::Result {
-    write!(buf, "\"{}\"", text.value())
-}

+ 72 - 6
packages/autofmt/src/lib.rs

@@ -1,8 +1,7 @@
 //! pretty printer for rsx code
 
-use dioxus_rsx::*;
-use proc_macro2::TokenStream as TokenStream2;
-use quote::ToTokens;
+pub use crate::buffer::*;
+use crate::util::*;
 
 mod block;
 mod buffer;
@@ -10,8 +9,75 @@ mod children;
 mod component;
 mod element;
 mod expr;
-mod ident;
 mod util;
 
-pub use block::{fmt_block, get_format_blocks};
-pub use ident::write_ident;
+// pub use block::{fmt_block, get_format_blocks};
+
+/// 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, 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.
+pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
+    let mut formatted_blocks = Vec::new();
+    let mut last_bracket_end = 0;
+
+    use triple_accel::{levenshtein_search, Match};
+
+    for Match { end, k, start } in levenshtein_search(b"rsx! {", contents.as_bytes()) {
+        // ensure the marker is not nested
+        if start < last_bracket_end {
+            continue;
+        }
+
+        let remaining = &contents[end - 1..];
+        let bracket_end = find_bracket_end(remaining).unwrap();
+        let sub_string = &contents[end..bracket_end + end - 1];
+        last_bracket_end = bracket_end + end - 1;
+
+        let new = fmt_block(sub_string).unwrap();
+        let stripped = &contents[end + 1..bracket_end + end - 1];
+
+        if stripped == new {
+            continue;
+        }
+
+        formatted_blocks.push(FormattedBlock {
+            formatted: new,
+            start: end,
+            end: end + bracket_end - 1,
+        });
+    }
+
+    formatted_blocks
+}
+
+pub fn fmt_block(block: &str) -> Option<String> {
+    let mut buf = Buffer::new();
+    let lines = block.split('\n').collect::<Vec<_>>();
+
+    for node in &syn::parse_str::<dioxus_rsx::CallBody>(block).ok()?.roots {
+        buf.write_ident(&lines, node).ok()?;
+    }
+
+    Some(buf.buf)
+}

+ 0 - 7
packages/autofmt/src/util.rs

@@ -14,13 +14,6 @@ pub fn extract_attr_len(attributes: &[ElementAttrNamed]) -> usize {
         .sum()
 }
 
-pub fn write_tabs(f: &mut dyn Write, num: usize) -> std::fmt::Result {
-    for _ in 0..num {
-        write!(f, "    ")?
-    }
-    Ok(())
-}
-
 pub fn find_bracket_end(contents: &str) -> Option<usize> {
     let mut depth = 0;
     let mut i = 0;

+ 106 - 27
packages/autofmt/tests/sink.rs

@@ -2,6 +2,16 @@ use dioxus_autofmt::*;
 use proc_macro2::TokenStream as TokenStream2;
 use syn::{Attribute, Meta};
 
+fn test_block(wrong: &str, right: &str) {
+    let formatted = fmt_block(wrong).unwrap();
+    assert_eq!(formatted, right);
+}
+
+fn print_block(wrong: &str) {
+    let formatted = fmt_block(wrong).unwrap();
+    println!("{}", formatted);
+}
+
 #[test]
 fn formats_block() {
     let block = r#"
@@ -46,7 +56,7 @@ fn formats_block() {
 
     let formatted = fmt_block(block).unwrap();
 
-    print!("{formatted}");
+    println!("{formatted}");
 }
 
 #[test]
@@ -62,6 +72,54 @@ fn parse_comment() {
     dbg!(parsed);
 }
 
+#[test]
+fn print_cases() {
+    print_block(
+        r#"
+        div {
+            adsasd: "asd",
+                h1 {"asd"}
+                div {
+                    div {
+                        "hello"
+                    }
+                    div {
+                        "goodbye"
+                    }
+                    div { class: "broccoli",
+                        div {
+                            "hi"
+                        }
+                    }
+                    div { class: "broccolibroccolibroccolibroccolibroccolibroccolibroccolibroccolibroccolibroccoli",
+                        div {
+                            "hi"
+                        }
+                    }
+                    div { class: "alksdjasd", onclick: move |_| {
+
+                        liberty!();
+                    },
+                        div {
+                            "hi"
+                        }
+                    }
+
+                    commented{
+                        // is unparalled
+                        class: "asdasd",
+
+                        // My genius
+                        div {
+                            "hi"
+                        }
+                    }
+                }
+        }
+    "#,
+    );
+}
+
 #[test]
 fn formats_component() {
     let block = r#"
@@ -76,7 +134,7 @@ fn formats_component() {
 
     let formatted = fmt_block(block).unwrap();
 
-    print!("{formatted}");
+    println!("{formatted}");
 }
 
 #[test]
@@ -97,7 +155,7 @@ fn formats_element() {
 
     let formatted = fmt_block(block).unwrap();
 
-    print!("{formatted}");
+    println!("{formatted}");
 }
 
 #[test]
@@ -118,7 +176,7 @@ fn formats_element_short() {
 
     let formatted = fmt_block(block).unwrap();
 
-    print!("{formatted}");
+    println!("{formatted}");
 }
 
 #[test]
@@ -132,7 +190,25 @@ fn formats_element_nested() {
 
     let formatted = fmt_block(block).unwrap();
 
-    print!("{formatted}");
+    assert_eq!(
+        formatted,
+        r#"h3 { class: "mb-2 text-xl font-bold", "Invite Member" }"#
+    );
+}
+
+#[test]
+fn formats_element_props_on_top() {
+    let block = r#"
+    h3 {
+        class: "mb-2 text-xl font-bold mb-2 text-xl font-bold mb-2 text-xl font-bold mb-2 text-xl font-bold mb-2 text-xl font-bold",
+
+        "Invite Member"
+    }
+"#;
+
+    let formatted = fmt_block(block).unwrap();
+
+    println!("{formatted}");
 }
 
 #[test]
@@ -143,7 +219,10 @@ fn formats_element_nested_no_trailing_tabs() {
 
     let formatted = fmt_block(block).unwrap();
 
-    print!("{formatted}");
+    assert_eq!(
+        formatted,
+        r#"img { class: "mb-6 mx-auto h-24", src: "artemis-assets/images/friends.png", alt: "" }"#
+    );
 }
 
 #[test]
@@ -160,20 +239,20 @@ fn formats_element_with_correct_indent() {
 
     let formatted = fmt_block(block).unwrap();
 
-    print!("{formatted}");
+    println!("{formatted}");
 }
 
 #[test]
 fn small_elements_and_text_are_small() {
     let block = r###"
-                        a { class: " text-white",
+                        a { class: "text-white",
                             "Send invitation"
           }
 "###;
 
     let formatted = fmt_block(block).unwrap();
 
-    print!("{formatted}");
+    assert_eq!(formatted, r#"a { class: "text-white", "Send invitation" }"#);
 }
 
 #[test]
@@ -192,7 +271,7 @@ fn formats_component_man_props() {
 
     let formatted = fmt_block(block).unwrap();
 
-    print!("{formatted}");
+    println!("{formatted}");
 }
 
 #[test]
@@ -204,24 +283,24 @@ fn formats_component_tiny() {
 
     let formatted = fmt_block(block).unwrap();
 
-    print!("{formatted}");
+    println!("{formatted}");
 }
 
 #[test]
 fn formats_exprs() {
     let block = r#"
     ul {
-        (0..10).map(|f| rsx!{
-            li {
-                "hi"
-            }
+        div {}
+        (0..10).map(|f| rsx! {
+            li { "hi" }
         })
+        div {}
     }
 "#;
 
     let formatted = fmt_block(block).unwrap();
 
-    print!("{formatted}");
+    println!("{formatted}");
 }
 
 #[test]
@@ -238,7 +317,7 @@ fn formats_exprs_neg_indent() {
 
     let formatted = fmt_block(block).unwrap();
 
-    print!("{formatted}");
+    println!("{formatted}");
 }
 
 #[test]
@@ -261,7 +340,7 @@ fn formats_exprs_handlers() {
 
     let formatted = fmt_block(block).unwrap();
 
-    print!("{formatted}");
+    println!("{formatted}");
 }
 
 #[test]
@@ -296,7 +375,7 @@ fn formats_complex() {
 
     let formatted = fmt_block(block).unwrap();
 
-    print!("{formatted}");
+    println!("{formatted}");
 }
 
 #[test]
@@ -314,9 +393,9 @@ rsx!{
 
 "#;
 
-    let formatted = get_format_blocks(block);
+    let formatted = fmt_file(block);
 
-    print!("{formatted:?}");
+    println!("{formatted:?}");
 }
 
 #[test]
@@ -332,9 +411,9 @@ rsx!{
 }
 "#;
 
-    let formatted = get_format_blocks(block);
+    let formatted = fmt_file(block);
 
-    print!("{formatted:?}");
+    println!("{formatted:?}");
 }
 
 #[test]
@@ -350,7 +429,7 @@ rsx! {
 }
 "#;
 
-    let formatted = get_format_blocks(src);
+    let formatted = fmt_file(src);
 
     println!("{formatted:?}");
 }
@@ -378,7 +457,7 @@ fn NavItem<'a>(cx: Scope, to: &'static str, children: Element<'a>, icon: Shape)
 "#
     .to_string();
 
-    let formatted = get_format_blocks(&src);
+    let formatted = fmt_file(&src);
 
     let block = formatted.into_iter().next().unwrap();
 
@@ -446,7 +525,7 @@ fn NavItem<'a>(cx: Scope, to: &'static str, children: Element<'a>, icon: Shape)
 "#
     .to_string();
 
-    let formatted = get_format_blocks(&src);
+    let formatted = fmt_file(&src);
 
     dbg!(&formatted);
 
@@ -469,7 +548,7 @@ pub fn Alert(cx: Scope) -> Element {
 "###
     .to_string();
 
-    let formatted = get_format_blocks(&src);
+    let formatted = fmt_file(&src);
 
     dbg!(&formatted);
 }

+ 10 - 0
packages/rsx/src/element.rs

@@ -209,6 +209,16 @@ pub enum ElementAttr {
     /// onclick: {}
     EventTokens { name: Ident, tokens: Expr },
 }
+impl ElementAttr {
+    pub fn is_expr(&self) -> bool {
+        matches!(
+            self,
+            ElementAttr::AttrExpression { .. }
+                | ElementAttr::CustomAttrExpression { .. }
+                | ElementAttr::EventTokens { .. }
+        )
+    }
+}
 
 #[derive(PartialEq, Eq)]
 pub struct ElementAttrNamed {