mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-27 14:40:44 +00:00
277 lines
8.4 KiB
Rust
277 lines
8.4 KiB
Rust
use crate::{ifmt_to_string, writer::Location, Writer};
|
|
use dioxus_rsx::*;
|
|
use quote::ToTokens;
|
|
use std::fmt::{Result, Write};
|
|
use syn::{spanned::Spanned, AngleBracketedGenericArguments};
|
|
|
|
#[derive(Debug)]
|
|
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 Writer<'_> {
|
|
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() && manual_props.is_none() {
|
|
opt_level = ShortOptimization::Empty;
|
|
}
|
|
|
|
// multiline handlers bump everything down
|
|
if attr_len > 1000 || self.out.indent.split_line_attributes() {
|
|
opt_level = ShortOptimization::NoOpt;
|
|
}
|
|
|
|
// Useful for debugging
|
|
// dbg!(
|
|
// name.to_token_stream().to_string(),
|
|
// &opt_level,
|
|
// attr_len,
|
|
// is_short_attr_list,
|
|
// is_small_children
|
|
// );
|
|
|
|
match opt_level {
|
|
ShortOptimization::Empty => {}
|
|
ShortOptimization::Oneliner => {
|
|
write!(self.out, " ")?;
|
|
|
|
self.write_component_fields(fields, manual_props, true)?;
|
|
|
|
if !children.is_empty() && !fields.is_empty() {
|
|
write!(self.out, ", ")?;
|
|
}
|
|
|
|
for child in children {
|
|
self.write_ident(child)?;
|
|
}
|
|
|
|
write!(self.out, " ")?;
|
|
}
|
|
|
|
ShortOptimization::PropsOnTop => {
|
|
write!(self.out, " ")?;
|
|
self.write_component_fields(fields, manual_props, true)?;
|
|
|
|
if !children.is_empty() && !fields.is_empty() {
|
|
write!(self.out, ",")?;
|
|
}
|
|
|
|
self.write_body_indented(children)?;
|
|
self.out.tabbed_line()?;
|
|
}
|
|
|
|
ShortOptimization::NoOpt => {
|
|
self.write_component_fields(fields, manual_props, false)?;
|
|
|
|
if !children.is_empty() && !fields.is_empty() {
|
|
write!(self.out, ",")?;
|
|
}
|
|
|
|
self.write_body_indented(children)?;
|
|
self.out.tabbed_line()?;
|
|
}
|
|
}
|
|
|
|
write!(self.out, "}}")?;
|
|
Ok(())
|
|
}
|
|
|
|
fn write_component_name(
|
|
&mut self,
|
|
name: &syn::Path,
|
|
generics: &Option<AngleBracketedGenericArguments>,
|
|
) -> Result {
|
|
let mut name = name.to_token_stream().to_string();
|
|
name.retain(|c| !c.is_whitespace());
|
|
|
|
write!(self.out, "{name}")?;
|
|
|
|
if let Some(generics) = generics {
|
|
let mut written = generics.to_token_stream().to_string();
|
|
written.retain(|c| !c.is_whitespace());
|
|
|
|
write!(self.out, "{written}")?;
|
|
}
|
|
|
|
write!(self.out, " {{")?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn write_component_fields(
|
|
&mut self,
|
|
fields: &[ComponentField],
|
|
manual_props: &Option<syn::Expr>,
|
|
sameline: bool,
|
|
) -> Result {
|
|
let mut field_iter = fields.iter().peekable();
|
|
|
|
while let Some(field) = field_iter.next() {
|
|
if !sameline {
|
|
self.out.indented_tabbed_line().unwrap();
|
|
}
|
|
|
|
let name = &field.name;
|
|
match &field.content {
|
|
ContentField::ManExpr(exp) => {
|
|
let out = prettyplease::unparse_expr(exp);
|
|
let mut lines = out.split('\n').peekable();
|
|
let first = lines.next().unwrap();
|
|
write!(self.out, "{name}: {first}")?;
|
|
for line in lines {
|
|
self.out.new_line()?;
|
|
self.out.indented_tab()?;
|
|
write!(self.out, "{line}")?;
|
|
}
|
|
}
|
|
ContentField::Formatted(s) => {
|
|
write!(
|
|
self.out,
|
|
"{}: {}",
|
|
name,
|
|
s.source.as_ref().unwrap().to_token_stream()
|
|
)?;
|
|
}
|
|
ContentField::OnHandlerRaw(exp) => {
|
|
let out = prettyplease::unparse_expr(exp);
|
|
let mut lines = out.split('\n').peekable();
|
|
let first = lines.next().unwrap();
|
|
write!(self.out, "{name}: {first}")?;
|
|
for line in lines {
|
|
self.out.new_line()?;
|
|
self.out.indented_tab()?;
|
|
write!(self.out, "{line}")?;
|
|
}
|
|
}
|
|
}
|
|
|
|
if field_iter.peek().is_some() || manual_props.is_some() {
|
|
write!(self.out, ",")?;
|
|
|
|
if sameline {
|
|
write!(self.out, " ")?;
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(exp) = manual_props {
|
|
if !sameline {
|
|
self.out.indented_tabbed_line().unwrap();
|
|
}
|
|
self.write_manual_props(exp)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn field_len(
|
|
&mut self,
|
|
fields: &[ComponentField],
|
|
manual_props: &Option<syn::Expr>,
|
|
) -> usize {
|
|
let attr_len = fields
|
|
.iter()
|
|
.map(|field| match &field.content {
|
|
ContentField::Formatted(s) => ifmt_to_string(s).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::<usize>();
|
|
|
|
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.out, "..{first_line}")?;
|
|
for line in lines {
|
|
self.out.indented_tabbed_line()?;
|
|
write!(self.out, "{line}")?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|