From 79b475798d68ab6e51600f00997dc28635ba2cac Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 25 Jun 2022 02:22:26 -0400 Subject: [PATCH] wip: make element printer more comprehensive --- packages/autofmt/src/block.rs | 41 +++++ packages/autofmt/src/element.rs | 300 ++++++++++++++++++++------------ packages/autofmt/src/util.rs | 2 +- 3 files changed, 228 insertions(+), 115 deletions(-) diff --git a/packages/autofmt/src/block.rs b/packages/autofmt/src/block.rs index d3ef135d9..7930f1e15 100644 --- a/packages/autofmt/src/block.rs +++ b/packages/autofmt/src/block.rs @@ -84,10 +84,51 @@ fn format_block_basic() { h1 { "thing" } h1 { "thing" } h1 { "thing" } + h1 { "thing" "is whack" "but you're wacker" } + h1 { "thing" div {"special cases?" } } + div { + a: 123, + b: 456, + c: 789, + d: "hello", + } + div { + a: 123, + b: 456, + c: 789, + d: "hello", + p {} + c {} + } + + h3 { class: "asdasd", "asdasd" } + h3 { class: "mx-large bg-gray-900 tall-md-400", div { + "classy" + }} + + + // Some comments explaining my genius + div { + "comment compression" + } + + // Some comments explaining my genius + div { + // Some comments explaining my genius + a: 123, + + // comment compression + b: 456, + + // comments on attributes + c: 789, + } } } "#; + // div { class: "asdasd", p { "hello!" } } + let edits = get_format_blocks(block); println!("{}", edits[0].formatted); diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index bde1c33f8..4af1d2460 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -2,6 +2,14 @@ use crate::{util::*, write_ident}; use dioxus_rsx::*; use std::{fmt, fmt::Write}; +enum ShortOptimization { + // Special because we want to print the closing bracket immediately + Empty, + Oneliner, + PropsOnTop, + NoOpt, +} + pub fn write_element( Element { name, @@ -16,145 +24,209 @@ pub fn write_element( indent: usize, ) -> fmt::Result { /* - Write the tag - Write the key - Write the attributes - Write the children + 1. Write the tag + 2. Write the key + 3. Write the attributes + 4. Write the children */ write!(buf, "{name} {{")?; - let total_attr_len = extract_attr_len(attributes); - let is_long_attr_list = total_attr_len > 80; + // decide if we have any special optimizations + // Default with none, opt the cases in one-by-one + let mut opt_level = ShortOptimization::NoOpt; - if let Some(key) = key.as_ref().map(|f| f.value()) { - if is_long_attr_list { - writeln!(buf)?; - write_tabs(buf, indent + 1)?; - } else { + // 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; + } + + // If there's nothing at all, empty optimization + if attributes.is_empty() && children.is_empty() && key.is_none() { + opt_level = ShortOptimization::Empty; + } + + match opt_level { + ShortOptimization::Empty => write!(buf, "}}")?, + ShortOptimization::Oneliner => { write!(buf, " ")?; - } - write!(buf, "key: \"{key}\"")?; + write_attributes(buf, attributes, true, indent)?; - if !attributes.is_empty() { - write!(buf, ",")?; + if !children.is_empty() && !attributes.is_empty() { + write!(buf, ", ")?; + } + + // write the children + for child in children { + write_ident(buf, lines, child, indent + 1)?; + } + + write!(buf, " }}")?; + } + + ShortOptimization::PropsOnTop => { + write!(buf, " ")?; + write_attributes(buf, attributes, true, indent)?; + + if !children.is_empty() && !attributes.is_empty() { + write!(buf, ",")?; + } + + // write the children + for child in children { + writeln!(buf)?; + write_tabs(buf, indent + 1)?; + write_ident(buf, lines, child, indent + 1)?; + } + + writeln!(buf)?; + write_tabs(buf, indent)?; + write!(buf, "}}")?; + } + + ShortOptimization::NoOpt => { + // write the key + + // write the attributes + write_attributes(buf, attributes, false, indent)?; + + // write the children + for child in children { + writeln!(buf)?; + write_tabs(buf, indent + 1)?; + write_ident(buf, lines, child, indent + 1)?; + } + + writeln!(buf)?; + write_tabs(buf, indent)?; + write!(buf, "}}")?; } } + Ok(()) +} + +fn is_short_attrs(attrs: &[ElementAttrNamed]) -> bool { + let total_attr_len = extract_attr_len(attrs); + total_attr_len < 80 +} + +// check if the children are short enough to be on the same line +// We don't have the notion of current line depth - each line tries to be < 80 total +fn is_short_children(children: &[BodyNode]) -> bool { + if children.is_empty() { + return true; + } + + if children.len() == 1 { + if let BodyNode::Text(ref text) = &children[0] { + return text.value().len() < 80; + } + } + + 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)?; + // } else { + // write!(buf, " ")?; + // } + // write!(buf, "key: \"{key}\"")?; + + // if !attributes.is_empty() { + // write!(buf, ",")?; + // } + // } +} + +fn write_attributes( + buf: &mut String, + attributes: &[ElementAttrNamed], + sameline: bool, + indent: usize, +) -> fmt::Result { let mut attr_iter = attributes.iter().peekable(); while let Some(attr) = attr_iter.next() { - if is_long_attr_list { - writeln!(buf)?; - write_tabs(buf, indent + 1)?; - } else { - write!(buf, " ")?; + 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) -> fmt::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)?; } - 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::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::CustomAttrExpression { name, value } => { - let out = prettyplease::unparse_expr(value); - write!(buf, "\"{}\": {}", name.value(), out)?; - } + ElementAttr::EventTokens { name, tokens } => { + let out = prettyplease::unparse_expr(tokens); - ElementAttr::EventTokens { name, tokens } => { - let out = prettyplease::unparse_expr(tokens); + let mut lines = out.split('\n').peekable(); + let first = lines.next().unwrap(); - 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)?; - // 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)?; - } + while let Some(line) = lines.next() { + write_tabs(buf, indent + 1)?; + write!(buf, "{}", line)?; + if lines.peek().is_none() { + write!(buf, "")?; + } else { + writeln!(buf)?; } } } } - - if attr_iter.peek().is_some() || !children.is_empty() { - write!(buf, ",")?; - } } - // If the attr list is short, then we want to optimize for some cases - let is_child_optimized = match children.as_slice() { - // No children, just close the tag - [] => true, - - // Only a text node, just write it out - [BodyNode::Text(_)] => true, - - // If these have zero children and attributes, then we can just write out the tag - [BodyNode::Component(ref comp)] => comp.body.is_empty() && comp.children.is_empty(), - [BodyNode::Element(ref el)] => el.attributes.is_empty() && el.children.is_empty(), - - // Nothing else is optimized - _ => false, - }; - - if !is_long_attr_list && is_child_optimized { - write_ident(buf, lines, &children[0], indent)?; - } else { - for child in children { - writeln!(buf)?; - write_ident(buf, lines, child, indent + 1)?; - } - } - - // let text_val = text.value(); - // if total_attr_len + text_val.len() > 80 { - // writeln!(buf)?; - // write_tabs(buf, indent + 1)?; - // writeln!(buf, "\"{}\"", text.value())?; - // write_tabs(buf, indent)?; - // } else { - // write!(buf, " \"{}\" ", text.value())?; - // } - - // if is_long_attr_list { - // writeln!(buf)?; - // } - - // for child in children { - // write_ident(buf, lines, child, indent + 1)?; - // } - - // if is_long_attr_list { - // write_tabs(buf, indent)?; - // } else { - // write!(buf, " ")?; - // } - - writeln!(buf, "}}")?; - Ok(()) } diff --git a/packages/autofmt/src/util.rs b/packages/autofmt/src/util.rs index 2cf362db7..9a358e959 100644 --- a/packages/autofmt/src/util.rs +++ b/packages/autofmt/src/util.rs @@ -11,7 +11,7 @@ pub fn extract_attr_len(attributes: &[ElementAttrNamed]) -> usize { ElementAttr::CustomAttrExpression { name, value } => 10, ElementAttr::EventTokens { name, tokens } => 1000000, }) - .sum::() + .sum() } pub fn write_tabs(f: &mut dyn Write, num: usize) -> std::fmt::Result {