use crate::{buffer::Location, Buffer}; use dioxus_rsx::*; use quote::ToTokens; use std::fmt::{Result, Write}; use syn::{spanned::Spanned, AngleBracketedGenericArguments}; 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_component( &mut self, Component { name, fields, children, manual_props, prop_gen_args, }: &Component, ) -> Result { self.write_component_name(name, prop_gen_args)?; // 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 attr_len = self.field_len(fields, manual_props); let is_short_attr_list = attr_len < 80; let is_small_children = self.is_short_children(children).is_some(); // 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 && (fields.len() <= 1 && manual_props.is_none()) { 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 fields.is_empty() && children.is_empty() { opt_level = ShortOptimization::Empty; } // multiline handlers bump everything down if attr_len > 1000 { opt_level = ShortOptimization::NoOpt; } match opt_level { ShortOptimization::Empty => {} ShortOptimization::Oneliner => { write!(self.buf, " ")?; self.write_component_fields(fields, manual_props, true)?; if !children.is_empty() && !fields.is_empty() { write!(self.buf, ", ")?; } for child in children { self.write_ident(child)?; } write!(self.buf, " ")?; } ShortOptimization::PropsOnTop => { write!(self.buf, " ")?; self.write_component_fields(fields, manual_props, true)?; if !children.is_empty() && !fields.is_empty() { write!(self.buf, ",")?; } self.write_body_indented(children)?; self.tabbed_line()?; } ShortOptimization::NoOpt => { self.write_component_fields(fields, manual_props, false)?; self.write_body_indented(children)?; self.tabbed_line()?; } } write!(self.buf, "}}")?; Ok(()) } fn write_component_name( &mut self, name: &syn::Path, generics: &Option, ) -> Result { let mut name = name.to_token_stream().to_string(); name.retain(|c| !c.is_whitespace()); write!(self.buf, "{name}")?; if let Some(generics) = generics { let mut written = generics.to_token_stream().to_string(); written.retain(|c| !c.is_whitespace()); write!(self.buf, "{}", written)?; } write!(self.buf, " {{")?; Ok(()) } fn write_component_fields( &mut self, fields: &[ComponentField], manual_props: &Option, sameline: bool, ) -> Result { let mut field_iter = fields.iter().peekable(); while let Some(field) = field_iter.next() { if !sameline { self.indented_tabbed_line()?; } let name = &field.name; match &field.content { ContentField::ManExpr(exp) => { let out = prettyplease::unparse_expr(exp); write!(self.buf, "{}: {}", name, out)?; } ContentField::Formatted(s) => { write!(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)?; } } } if field_iter.peek().is_some() || manual_props.is_some() { write!(self.buf, ",")?; if sameline { write!(self.buf, " ")?; } } } if let Some(exp) = manual_props { if !sameline { self.indented_tabbed_line()?; } self.write_manual_props(exp)?; } Ok(()) } pub fn field_len( &mut self, fields: &[ComponentField], manual_props: &Option, ) -> usize { let attr_len = fields .iter() .map(|field| match &field.content { ContentField::Formatted(s) => s.value().len() , ContentField::OnHandlerRaw(exp) | ContentField::ManExpr(exp) => { let formatted = prettyplease::unparse_expr(exp); let len = if formatted.contains('\n') { 10000 } else { formatted.len() }; self.cached_formats.insert(Location::new(exp.span().start()) , formatted); len }, } + 10) .sum::() + self.indent * 4; match manual_props { Some(p) => { let content = prettyplease::unparse_expr(p); if content.len() + attr_len > 80 { return 100000; } let mut lines = content.lines(); lines.next().unwrap(); if lines.next().is_none() { attr_len + content.len() } else { 100000 } } None => attr_len, } } fn write_manual_props(&mut self, exp: &syn::Expr) -> Result { /* We want to normalize the expr to the appropriate indent level. */ let formatted = prettyplease::unparse_expr(exp); let mut lines = formatted.lines(); let first_line = lines.next().unwrap(); write!(self.buf, "..{first_line}")?; for line in lines { self.indented_tabbed_line()?; write!(self.buf, "{line}")?; } Ok(()) } }