use crate::{util::*, Buffer}; use dioxus_rsx::*; use std::{fmt::Result, fmt::Write}; enum ShortOptimization { // Special because we want to print the closing bracket immediately Empty, // Special optimization to put everything on the same line Oneliner, // Optimization where children flow but props remain fixed on top PropsOnTop, // The noisiest optimization where everything flows NoOpt, } impl Buffer { pub fn write_element( &mut self, Element { name, key, attributes, children, _is_static, }: &Element, ) -> 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 = self.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; } // 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 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 => {} ShortOptimization::Oneliner => { write!(self.buf, " ")?; self.write_attributes(attributes, key, true)?; if !children.is_empty() && !attributes.is_empty() { write!(self.buf, ", ")?; } for child in children { self.write_ident(child)?; } write!(self.buf, " ")?; } ShortOptimization::PropsOnTop => { write!(self.buf, " ")?; self.write_attributes(attributes, key, true)?; if !children.is_empty() && !attributes.is_empty() { write!(self.buf, ",")?; } self.write_body_indented(children)?; self.tabbed_line()?; } ShortOptimization::NoOpt => { self.write_attributes(attributes, key, false)?; self.write_body_indented(children)?; self.tabbed_line()?; } } write!(self.buf, "}}")?; Ok(()) } fn write_attributes( &mut self, attributes: &[ElementAttrNamed], key: &Option, sameline: bool, ) -> Result { let mut attr_iter = attributes.iter().peekable(); if let Some(key) = key { if !sameline { self.indented_tabbed_line()?; } write!(self.buf, "key: \"{}\"", key.value())?; if !attributes.is_empty() { write!(self.buf, ", ")?; } } while let Some(attr) = attr_iter.next() { if !sameline { self.indented_tabbed_line()?; } self.write_attribute(attr)?; if attr_iter.peek().is_some() { write!(self.buf, ",")?; if sameline { write!(self.buf, " ")?; } } } 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)?; } ElementAttr::CustomAttrText { name, value } => { write!( self.buf, "\"{name}\": \"{value}\"", name = name.value(), value = value.value() )?; } ElementAttr::CustomAttrExpression { name, value } => { let out = prettyplease::unparse_expr(value); write!(self.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!(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(()) } // 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(&self, children: &[BodyNode]) -> bool { if children.is_empty() { // todo: allow elements with comments but no children // like div { /* comment */ } return true; } for child in children { 'line: for line in self.src[..child.span().start().line - 1].iter().rev() { if line.trim().starts_with("//") { return false; } else if line.is_empty() { continue; } else { break 'line; } } } match children { [BodyNode::Text(ref text)] => text.value().len() < 80, [BodyNode::Element(ref el)] => { extract_attr_len(&el.attributes) < 80 && self.is_short_children(&el.children) } _ => false, } } } fn is_short_attrs(attrs: &[ElementAttrNamed]) -> bool { let total_attr_len = extract_attr_len(attrs); total_attr_len < 80 }