diff --git a/packages/autofmt/README.md b/packages/autofmt/README.md index f1c425c56..eec3b590e 100644 --- a/packages/autofmt/README.md +++ b/packages/autofmt/README.md @@ -15,3 +15,74 @@ Sorted roughly in order of what's possible - [ ] Format regular exprs - [ ] Fix prettyplease around chaining - [ ] Don't eat comments in prettyplease + + +# Technique + + +div { + div {} + div {} +} + + +div + +possible line break +div +div + + + +string of possible items within a nesting +div { + attr_pair + expr + text + comment +} +a nesting is either a component or an element + +idea: +collect all items into a queue +q +```rust +section { + div { + h1 { p { "asdasd" } } + h1 { p { "asdasd" } } + } +} + +section {} +``` + + +// space +// space +// space + + +3 - section +3 - section div +3 - section div h1 +3 - section div h1 p +3 - section div h1 p text +3 - section +3 - section div +3 - section div h1 +3 - section div h1 p +3 - section div h1 p text + +block + +- when we hit the end of a trail, we can make a decision what needs to be hard breaked +- most nestings cannot be merged into a single one, so at some point we need to write the line break +- this is the scan section. we scan forward until it's obvious where to place a hard break +- when a line is finished, we can print it out by unloading our queued items +- never double nested + + +Terms +- break is a whitespace than can flex, dependent on the situation +- ‹⁠› diff --git a/packages/autofmt/src/buffer.rs b/packages/autofmt/src/buffer.rs index 45799ca62..81c35a702 100644 --- a/packages/autofmt/src/buffer.rs +++ b/packages/autofmt/src/buffer.rs @@ -125,16 +125,26 @@ impl Buffer { } pub fn write_body_no_indent(&mut self, children: &[BodyNode]) -> Result { - for child in children { - // Exprs handle their own indenting/line breaks - if !matches!(child, BodyNode::RawExpr(_)) { - if self.current_span_is_primary(child.span()) { - self.write_comments(child.span())?; - } - self.tabbed_line()?; - } + let last_child = children.len(); - self.write_ident(child)?; + for (idx, child) in children.iter().enumerate() { + match child { + // check if the expr is a short + BodyNode::RawExpr { .. } => { + self.tabbed_line()?; + self.write_ident(child)?; + if idx != last_child - 1 { + write!(self.buf, ",")?; + } + } + _ => { + if self.current_span_is_primary(child.span()) { + self.write_comments(child.span())?; + } + self.tabbed_line()?; + self.write_ident(child)?; + } + } } Ok(()) diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index fd4529766..f17aac462 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -2,6 +2,7 @@ use crate::Buffer; use dioxus_rsx::*; use proc_macro2::Span; use std::{fmt::Result, fmt::Write}; +use syn::{spanned::Spanned, Expr}; #[derive(Debug)] enum ShortOptimization { @@ -83,12 +84,15 @@ impl Buffer { self.write_attributes(attributes, key, true)?; - if !children.is_empty() && !attributes.is_empty() { + if !children.is_empty() && (!attributes.is_empty() || key.is_some()) { write!(self.buf, ", ")?; } - for child in children { + for (id, child) in children.iter().enumerate() { self.write_ident(child)?; + if id != children.len() - 1 && children.len() > 1 { + write!(self.buf, ", ")?; + } } write!(self.buf, " ")?; @@ -100,7 +104,7 @@ impl Buffer { } self.write_attributes(attributes, key, true)?; - if !children.is_empty() && !attributes.is_empty() { + if !children.is_empty() && (!attributes.is_empty() || key.is_some()) { write!(self.buf, ",")?; } @@ -113,7 +117,7 @@ impl Buffer { ShortOptimization::NoOpt => { self.write_attributes(attributes, key, false)?; - if !children.is_empty() && !attributes.is_empty() { + if !children.is_empty() && (!attributes.is_empty() || key.is_some()) { write!(self.buf, ",")?; } @@ -286,33 +290,66 @@ impl Buffer { if attr_len > 80 { None + } else if comp.children.is_empty() { + Some(attr_len) } else { - self.is_short_children(&comp.children) - .map(|child_len| child_len + attr_len) + None } } - [BodyNode::RawExpr(ref _expr)] => { + [BodyNode::RawExpr(ref expr)] => { // TODO: let rawexprs to be inlined - // let span = syn::spanned::Spanned::span(&text); - // let (start, end) = (span.start(), span.end()); - // if start.line == end.line { - // Some(end.column - start.column) - // } else { - // None - // } - None + get_expr_length(expr) } [BodyNode::Element(ref el)] => { let attr_len = self.is_short_attrs(&el.attributes); - if attr_len > 80 { - None - } else { - self.is_short_children(&el.children) - .map(|child_len| child_len + attr_len) + if el.children.is_empty() && attr_len < 80 { + return Some(el.name.to_string().len()); } + + if el.children.len() == 1 { + if let BodyNode::Text(ref text) = el.children[0] { + let value = text.source.as_ref().unwrap().value(); + + if value.len() + el.name.to_string().len() + attr_len < 80 { + return Some(value.len() + el.name.to_string().len() + attr_len); + } + } + } + + None + } + // todo, allow non-elements to be on the same line + items => { + let mut total_count = 0; + + for item in items { + match item { + BodyNode::Component(_) | BodyNode::Element(_) => return None, + BodyNode::Text(text) => { + total_count += text.source.as_ref().unwrap().value().len() + } + BodyNode::RawExpr(expr) => match get_expr_length(expr) { + Some(len) => total_count += len, + None => return None, + }, + BodyNode::ForLoop(_) => todo!(), + BodyNode::IfChain(_) => todo!(), + } + } + + Some(total_count) } - _ => None, } } } + +fn get_expr_length(expr: &Expr) -> Option { + let span = expr.span(); + let (start, end) = (span.start(), span.end()); + if start.line == end.line { + Some(end.column - start.column) + } else { + None + } +} diff --git a/packages/autofmt/src/expr.rs b/packages/autofmt/src/expr.rs index d89a5cb39..48ea6fcc7 100644 --- a/packages/autofmt/src/expr.rs +++ b/packages/autofmt/src/expr.rs @@ -16,16 +16,63 @@ impl Buffer { let placement = exp.span(); let start = placement.start(); let end = placement.end(); - let num_spaces_desired = (self.indent * 4) as isize; + // let num_spaces_desired = (self.indent * 4) as isize; - let first = &self.src[start.line - 1]; - let num_spaces_real = first.chars().take_while(|c| c.is_whitespace()).count() as isize; + // print comments + // let mut queued_comments = vec![]; + // let mut offset = 2; + // loop { + // let line = &self.src[start.line - offset]; + // if line.trim_start().starts_with("//") { + // queued_comments.push(line); + // } else { + // break; + // } - let offset = num_spaces_real - num_spaces_desired; + // offset += 1; + // } + // let had_comments = !queued_comments.is_empty(); + // for comment in queued_comments.into_iter().rev() { + // writeln!(self.buf, "{}", comment)?; + // } - for line in &self.src[start.line - 1..end.line] { + // if the expr is on one line, just write it directly + if start.line == end.line { + write!( + self.buf, + "{}", + &self.src[start.line - 1][start.column - 1..end.column].trim() + )?; + return Ok(()); + } + + // If the expr is multiline, we want to collect all of its lines together and write them out properly + // This involves unshifting the first line if it's aligned + let first_line = &self.src[start.line - 1]; + write!( + self.buf, + "{}", + &first_line[start.column - 1..first_line.len()].trim() + )?; + + let first_prefix = &self.src[start.line - 1][..start.column]; + let offset = match first_prefix.trim() { + "" => 0, + _ => first_prefix + .chars() + .rev() + .take_while(|c| c.is_whitespace()) + .count() as isize, + }; + + for (id, line) in self.src[start.line..end.line].iter().enumerate() { writeln!(self.buf)?; // trim the leading whitespace + let line = match id { + x if x == (end.line - start.line) - 1 => &line[..end.column], + _ => line, + }; + if offset < 0 { for _ in 0..-offset { write!(self.buf, " ")?; @@ -39,6 +86,32 @@ impl Buffer { } } + // let first = &self.src[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 (row, line) in self.src[start.line - 1..end.line].iter().enumerate() { + // let line = match row { + // 0 => &line[start.column - 1..], + // a if a == (end.line - start.line) => &line[..end.column - 1], + // _ => 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(()) } } diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs index 7a14eae3e..aca9f64b0 100644 --- a/packages/autofmt/src/lib.rs +++ b/packages/autofmt/src/lib.rs @@ -124,7 +124,7 @@ pub fn apply_format(input: &str, block: FormattedBlock) -> String { let (left, _) = input.split_at(start); let (_, right) = input.split_at(end); - dbg!(&block.formatted); + // dbg!(&block.formatted); format!("{}{}{}", left, block.formatted, right) } diff --git a/packages/autofmt/tests/samples.rs b/packages/autofmt/tests/samples.rs index 29332b7da..1f9c359c1 100644 --- a/packages/autofmt/tests/samples.rs +++ b/packages/autofmt/tests/samples.rs @@ -23,3 +23,10 @@ twoway! ("complex" => complex); twoway! ("tiny" => tiny); twoway! ("tinynoopt" => tinynoopt); + +twoway! ("long" => long); + +twoway! ("key" => key); + +// Disabled because we can't handle comments on exprs yet +twoway! ("multirsx" => multirsx); diff --git a/packages/autofmt/tests/samples/complex.rsx b/packages/autofmt/tests/samples/complex.rsx index d3e45c67b..598d08a82 100644 --- a/packages/autofmt/tests/samples/complex.rsx +++ b/packages/autofmt/tests/samples/complex.rsx @@ -27,9 +27,7 @@ rsx! { } }) } - div { class: "px-4", - is_current.then(|| rsx!{ children }) - } + div { class: "px-4", is_current.then(|| rsx!{ children }) } } // No nesting @@ -47,4 +45,6 @@ rsx! { let blah = 120; } } + + div { asdbascasdbasd, asbdasbdabsd, asbdabsdbasdbas } } diff --git a/packages/autofmt/tests/samples/key.rsx b/packages/autofmt/tests/samples/key.rsx new file mode 100644 index 000000000..c97b6db38 --- /dev/null +++ b/packages/autofmt/tests/samples/key.rsx @@ -0,0 +1,9 @@ +rsx! { + li { key: "{link}", + Link { class: "py-1 px-2 {hover} {hover_bg}", to: "{link}", "{name}" } + } + + li { key: "{link}", asd: "asd", + Link { class: "py-1 px-2 {hover} {hover_bg}", to: "{link}", "{name}" } + } +} diff --git a/packages/autofmt/tests/samples/long.rsx b/packages/autofmt/tests/samples/long.rsx new file mode 100644 index 000000000..2464a1905 --- /dev/null +++ b/packages/autofmt/tests/samples/long.rsx @@ -0,0 +1,38 @@ +use dioxus::prelude::*; + +#[inline_props] +pub fn Explainer<'a>( + cx: Scope<'a>, + invert: bool, + title: &'static str, + content: Element<'a>, + flasher: Element<'a>, +) -> Element { + // pt-5 sm:pt-24 lg:pt-24 + + let mut right = rsx! { + div { class: "relative w-1/2", flasher } + }; + + let align = match invert { + true => "mr-auto ml-16", + false => "ml-auto mr-16", + }; + + let mut left = rsx! { + div { class: "relative w-1/2 {align} max-w-md leading-8", + h2 { class: "mb-6 text-3xl leading-tight md:text-4xl md:leading-tight lg:text-3xl lg:leading-tight font-heading font-mono font-bold", + "{title}" + } + content + } + }; + + if *invert { + std::mem::swap(&mut left, &mut right); + } + + cx.render(rsx! { + div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", left, right } + }) +} diff --git a/packages/autofmt/tests/samples/multirsx.rsx b/packages/autofmt/tests/samples/multirsx.rsx new file mode 100644 index 000000000..380ef3487 --- /dev/null +++ b/packages/autofmt/tests/samples/multirsx.rsx @@ -0,0 +1,25 @@ +rsx! { + + // hi + div {} + + // hi + div { abcd, ball, s } + + // + // + // + div { abcd, ball, s } + + // + // + // + div { + abcd, + ball, + s, + + // + "asdasd" + } +} diff --git a/packages/autofmt/tests/samples/simple.rsx b/packages/autofmt/tests/samples/simple.rsx index a60d69f2a..54ff5f867 100644 --- a/packages/autofmt/tests/samples/simple.rsx +++ b/packages/autofmt/tests/samples/simple.rsx @@ -1,9 +1,6 @@ rsx! { div { "hello world!" } - div { - "hello world!" - "goodbye world!" - } + div { "hello world!", "goodbye world!" } // Simple div div { "hello world!" } @@ -15,7 +12,16 @@ rsx! { div { div { "nested" } } // Nested two level - div { div { h1 { "highly nested" } } } + div { + div { h1 { "highly nested" } } + } + + // Anti-Nested two level + div { + div { + div { h1 { "highly nested" } } + } + } // Compression h3 { class: "mb-2 text-xl font-bold", "Invite Member" } @@ -30,7 +36,9 @@ rsx! { img { class: "mb-6 mx-auto h-24", src: "artemis-assets/images/friends.png", alt: "" } // One level compression - div { a { class: "py-2 px-3 bg-indigo-500 hover:bg-indigo-600 rounded text-xs text-white", href: "#", "Send invitation" } } + div { + a { class: "py-2 px-3 bg-indigo-500 hover:bg-indigo-600 rounded text-xs text-white", href: "#", "Send invitation" } + } // Components Component { ..Props {} } diff --git a/packages/autofmt/tests/wrong.rs b/packages/autofmt/tests/wrong.rs index 4666da79c..acf930b40 100644 --- a/packages/autofmt/tests/wrong.rs +++ b/packages/autofmt/tests/wrong.rs @@ -14,3 +14,5 @@ macro_rules! twoway { twoway!("comments" => comments); twoway!("multi" => multi); + +twoway!("multiexpr" => multiexpr); diff --git a/packages/autofmt/tests/wrong/multiexpr.rsx b/packages/autofmt/tests/wrong/multiexpr.rsx new file mode 100644 index 000000000..1c3f38434 --- /dev/null +++ b/packages/autofmt/tests/wrong/multiexpr.rsx @@ -0,0 +1,3 @@ +cx.render(rsx! { + div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", left, right } +}) diff --git a/packages/autofmt/tests/wrong/multiexpr.wrong.rsx b/packages/autofmt/tests/wrong/multiexpr.wrong.rsx new file mode 100644 index 000000000..1c3f38434 --- /dev/null +++ b/packages/autofmt/tests/wrong/multiexpr.wrong.rsx @@ -0,0 +1,3 @@ +cx.render(rsx! { + div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", left, right } +}) diff --git a/packages/rsx/README.md b/packages/rsx/README.md new file mode 100644 index 000000000..920fcd88c --- /dev/null +++ b/packages/rsx/README.md @@ -0,0 +1 @@ +# The actual RSX language implemented using syn parsers. diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index cd2778428..a3e93fe95 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -34,7 +34,7 @@ use syn::{ }; /// Fundametnally, every CallBody is a template -#[derive(Default)] +#[derive(Default, Debug)] pub struct CallBody { pub roots: Vec, diff --git a/packages/rsx/src/node.rs b/packages/rsx/src/node.rs index cb604654e..29f18fa31 100644 --- a/packages/rsx/src/node.rs +++ b/packages/rsx/src/node.rs @@ -1,3 +1,5 @@ +// use crate::{raw_expr::RawExprNode, text::TextNode}; + use super::*; use proc_macro2::{Span, TokenStream as TokenStream2}; @@ -29,7 +31,7 @@ pub enum BodyNode { impl BodyNode { pub fn is_litstr(&self) -> bool { - matches!(self, BodyNode::Text(_)) + matches!(self, BodyNode::Text { .. }) } pub fn span(&self) -> Span {