Hotreloading of for/if/body, formatted strings, literals, component props, nested rsx, light CLI rewrite, cli TUI (#2258)
Hotreload the contents of for loops, if chains, component bodies, props, attributes, and any literals discovered in rsx! Add a TUI renderer to the CLI. Improve the CLI build system to be async and parallel. Refactor RSX to allow partial expansion of expressions. Merge autofmt implementations for consistency. Merge the representation of elements and components under the hood. Add a diagnostics system for rsx for improved error messages. Drop interprocess and move to websockets for communication between the CLI and the server. Assign IDs to nodes and attributes in a stable way to be used in non compiler contexts. Add hotreloading to any body of component/for loop/if chain/etc. --------- Co-authored-by: Evan Almloff <evanalmloff@gmail.com> Co-authored-by: Liam Mitchell <liamkarlmitchell@gmail.com>
This commit is contained in:
181 files changed
+12755
-10806
No files matched your search
@@ -8,7 +8,7 @@ use std::fmt::{Result, Write};
|
||||
|
||||
use dioxus_rsx::IfmtInput;
|
||||
|
||||
use crate::{indent::IndentOptions, write_ifmt};
|
||||
use crate::indent::IndentOptions;
|
||||
|
||||
/// The output buffer that tracks indent and string
|
||||
#[derive(Debug, Default)]
|
||||
@@ -51,7 +51,7 @@ impl Buffer {
|
||||
}
|
||||
|
||||
pub fn write_text(&mut self, text: &IfmtInput) -> Result {
|
||||
write_ifmt(text, &mut self.buf)
|
||||
write!(self.buf, "{}", text.to_string_with_quotes())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,15 @@ use syn::{visit::Visit, File, Macro};
|
||||
|
||||
type CollectedMacro<'a> = &'a Macro;
|
||||
|
||||
pub fn collect_from_file<'a>(file: &'a File, macros: &mut Vec<CollectedMacro<'a>>) {
|
||||
MacroCollector::visit_file(&mut MacroCollector { macros }, file);
|
||||
pub fn collect_from_file(file: &File) -> Vec<CollectedMacro<'_>> {
|
||||
let mut macros = vec![];
|
||||
MacroCollector::visit_file(
|
||||
&mut MacroCollector {
|
||||
macros: &mut macros,
|
||||
},
|
||||
file,
|
||||
);
|
||||
macros
|
||||
}
|
||||
|
||||
struct MacroCollector<'a, 'b> {
|
||||
@@ -46,7 +53,6 @@ pub fn byte_offset(input: &str, location: LineColumn) -> usize {
|
||||
fn parses_file_and_collects_rsx_macros() {
|
||||
let contents = include_str!("../tests/samples/long.rsx");
|
||||
let parsed = syn::parse_file(contents).unwrap();
|
||||
let mut macros = vec![];
|
||||
collect_from_file(&parsed, &mut macros);
|
||||
let macros = collect_from_file(&parsed);
|
||||
assert_eq!(macros.len(), 3);
|
||||
}
|
||||
@@ -1,291 +0,0 @@
|
||||
use crate::{ifmt_to_string, prettier_please::unparse_expr, 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,
|
||||
key,
|
||||
..
|
||||
}: &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) + self.key_len(key.as_ref());
|
||||
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() && key.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, key.as_ref(), manual_props, true)?;
|
||||
|
||||
if !children.is_empty() && !fields.is_empty() {
|
||||
write!(self.out, ", ")?;
|
||||
}
|
||||
|
||||
for (id, child) in children.iter().enumerate() {
|
||||
self.write_ident(child)?;
|
||||
if id != children.len() - 1 && children.len() > 1 {
|
||||
write!(self.out, ", ")?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
|
||||
ShortOptimization::PropsOnTop => {
|
||||
write!(self.out, " ")?;
|
||||
self.write_component_fields(fields, key.as_ref(), 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, key.as_ref(), 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],
|
||||
key: Option<&IfmtInput>,
|
||||
manual_props: &Option<syn::Expr>,
|
||||
sameline: bool,
|
||||
) -> Result {
|
||||
let mut field_iter = fields.iter().peekable();
|
||||
|
||||
// write the key
|
||||
if let Some(key) = key {
|
||||
write!(self.out, "key: {}", ifmt_to_string(key))?;
|
||||
if !fields.is_empty() {
|
||||
write!(self.out, ",")?;
|
||||
if sameline {
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) if field.can_be_shorthand() => {
|
||||
write!(self.out, "{name}")?;
|
||||
}
|
||||
|
||||
ContentField::ManExpr(exp) => {
|
||||
let out = self.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::Shorthand(e) => {
|
||||
write!(self.out, "{}", e.to_token_stream())?;
|
||||
}
|
||||
}
|
||||
|
||||
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::Shorthand(e) => e.to_token_stream().to_string().len(),
|
||||
ContentField::ManExpr(exp) => {
|
||||
let formatted = 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 = 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 = self.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(())
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
//! pretty printer for rsx!
|
||||
use std::fmt::{Result, Write};
|
||||
|
||||
use proc_macro2::Span;
|
||||
|
||||
use crate::{collect_macros::byte_offset, Writer};
|
||||
|
||||
impl Writer<'_> {
|
||||
pub fn write_raw_expr(&mut self, placement: Span) -> Result {
|
||||
/*
|
||||
We want to normalize the expr to the appropriate indent level.
|
||||
*/
|
||||
|
||||
let start = placement.start();
|
||||
let end = placement.end();
|
||||
|
||||
// if the expr is on one line, just write it directly
|
||||
if start.line == end.line {
|
||||
// split counting utf8 chars
|
||||
let start = byte_offset(self.raw_src, start);
|
||||
let end = byte_offset(self.raw_src, end);
|
||||
let row = self.raw_src[start..end].trim();
|
||||
write!(self.out, "{row}")?;
|
||||
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.out, "{}", &first_line[start.column..].trim_start())?;
|
||||
|
||||
let prev_block_indent_level = self.out.indent.count_indents(first_line);
|
||||
|
||||
for (id, line) in self.src[start.line..end.line].iter().enumerate() {
|
||||
writeln!(self.out)?;
|
||||
|
||||
// check if this is the last line
|
||||
let line = {
|
||||
if id == (end.line - start.line) - 1 {
|
||||
&line[..end.column]
|
||||
} else {
|
||||
line
|
||||
}
|
||||
};
|
||||
|
||||
// trim the leading whitespace
|
||||
let previous_indent = self.out.indent.count_indents(line);
|
||||
let offset = previous_indent.saturating_sub(prev_block_indent_level);
|
||||
let required_indent = self.out.indent_level + offset;
|
||||
self.out.write_tabs(required_indent)?;
|
||||
|
||||
let line = line.trim_start();
|
||||
write!(self.out, "{line}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+22
-80
@@ -2,22 +2,16 @@
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
|
||||
use std::fmt::{Display, Write};
|
||||
|
||||
use crate::writer::*;
|
||||
use collect_macros::byte_offset;
|
||||
use dioxus_rsx::{BodyNode, CallBody, IfmtInput};
|
||||
use dioxus_rsx::{BodyNode, CallBody};
|
||||
use proc_macro2::LineColumn;
|
||||
use quote::ToTokens;
|
||||
use syn::{parse::Parser, ExprMacro, MacroDelimiter};
|
||||
use syn::{parse::Parser, ExprMacro};
|
||||
|
||||
mod buffer;
|
||||
mod collect_macros;
|
||||
mod component;
|
||||
mod element;
|
||||
mod expr;
|
||||
mod indent;
|
||||
mod prettier_please;
|
||||
mod rsx_block;
|
||||
mod writer;
|
||||
|
||||
pub use indent::{IndentOptions, IndentType};
|
||||
@@ -55,9 +49,7 @@ pub fn fmt_file(contents: &str, indent: IndentOptions) -> Vec<FormattedBlock> {
|
||||
let mut formatted_blocks = Vec::new();
|
||||
|
||||
let parsed = syn::parse_file(contents).unwrap();
|
||||
|
||||
let mut macros = vec![];
|
||||
collect_macros::collect_from_file(&parsed, &mut macros);
|
||||
let macros = collect_macros::collect_from_file(&parsed);
|
||||
|
||||
// No macros, no work to do
|
||||
if macros.is_empty() {
|
||||
@@ -86,7 +78,11 @@ pub fn fmt_file(contents: &str, indent: IndentOptions) -> Vec<FormattedBlock> {
|
||||
.indent
|
||||
.count_indents(writer.src[rsx_start.line - 1]);
|
||||
|
||||
write_body(&mut writer, &body);
|
||||
// TESTME
|
||||
// If we fail to parse this macro then we have no choice to give up and return what we've got
|
||||
if writer.write_rsx_call(&body.body).is_err() {
|
||||
return formatted_blocks;
|
||||
}
|
||||
|
||||
// writing idents leaves the final line ended at the end of the last ident
|
||||
if writer.out.buf.contains('\n') {
|
||||
@@ -94,23 +90,15 @@ pub fn fmt_file(contents: &str, indent: IndentOptions) -> Vec<FormattedBlock> {
|
||||
writer.out.tab().unwrap();
|
||||
}
|
||||
|
||||
let span = match item.delimiter {
|
||||
MacroDelimiter::Paren(b) => b.span,
|
||||
MacroDelimiter::Brace(b) => b.span,
|
||||
MacroDelimiter::Bracket(b) => b.span,
|
||||
}
|
||||
.join();
|
||||
let span = item.delimiter.span().join();
|
||||
let mut formatted = writer.out.buf.split_off(0);
|
||||
|
||||
let mut formatted = String::new();
|
||||
|
||||
std::mem::swap(&mut formatted, &mut writer.out.buf);
|
||||
|
||||
let start = byte_offset(contents, span.start()) + 1;
|
||||
let end = byte_offset(contents, span.end()) - 1;
|
||||
let start = collect_macros::byte_offset(contents, span.start()) + 1;
|
||||
let end = collect_macros::byte_offset(contents, span.end()) - 1;
|
||||
|
||||
// Rustfmt will remove the space between the macro and the opening paren if the macro is a single expression
|
||||
let body_is_solo_expr = body.roots.len() == 1
|
||||
&& matches!(body.roots[0], BodyNode::RawExpr(_) | BodyNode::Text(_));
|
||||
let body_is_solo_expr = body.body.roots.len() == 1
|
||||
&& matches!(body.body.roots[0], BodyNode::RawExpr(_) | BodyNode::Text(_));
|
||||
|
||||
if formatted.len() <= 80 && !formatted.contains('\n') && !body_is_solo_expr {
|
||||
formatted = format!(" {formatted} ");
|
||||
@@ -132,33 +120,20 @@ pub fn fmt_file(contents: &str, indent: IndentOptions) -> Vec<FormattedBlock> {
|
||||
formatted_blocks
|
||||
}
|
||||
|
||||
/// Write a Callbody (the rsx block) to a string
|
||||
///
|
||||
/// If the tokens can't be formatted, this returns None. This is usually due to an incomplete expression
|
||||
/// that passed partial expansion but failed to parse.
|
||||
pub fn write_block_out(body: &CallBody) -> Option<String> {
|
||||
let mut buf = Writer::new("");
|
||||
|
||||
write_body(&mut buf, body);
|
||||
|
||||
buf.write_rsx_call(&body.body).ok()?;
|
||||
buf.consume()
|
||||
}
|
||||
|
||||
fn write_body(buf: &mut Writer, body: &CallBody) {
|
||||
match body.roots.len() {
|
||||
0 => {}
|
||||
1 if matches!(body.roots[0], BodyNode::Text(_)) => {
|
||||
write!(buf.out, " ").unwrap();
|
||||
buf.write_ident(&body.roots[0]).unwrap();
|
||||
write!(buf.out, " ").unwrap();
|
||||
}
|
||||
_ => buf.write_body_indented(&body.roots).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fmt_block_from_expr(raw: &str, expr: ExprMacro) -> Option<String> {
|
||||
let body = CallBody::parse_strict.parse2(expr.mac.tokens).unwrap();
|
||||
|
||||
let mut buf = Writer::new(raw);
|
||||
|
||||
write_body(&mut buf, &body);
|
||||
|
||||
buf.write_rsx_call(&body.body).ok()?;
|
||||
buf.consume()
|
||||
}
|
||||
|
||||
@@ -166,11 +141,9 @@ pub fn fmt_block(block: &str, indent_level: usize, indent: IndentOptions) -> Opt
|
||||
let body = CallBody::parse_strict.parse_str(block).unwrap();
|
||||
|
||||
let mut buf = Writer::new(block);
|
||||
|
||||
buf.out.indent = indent;
|
||||
buf.out.indent_level = indent_level;
|
||||
|
||||
write_body(&mut buf, &body);
|
||||
buf.write_rsx_call(&body.body).ok()?;
|
||||
|
||||
// writing idents leaves the final line ended at the end of the last ident
|
||||
if buf.out.buf.contains('\n') {
|
||||
@@ -180,16 +153,6 @@ pub fn fmt_block(block: &str, indent_level: usize, indent: IndentOptions) -> Opt
|
||||
buf.consume()
|
||||
}
|
||||
|
||||
pub fn apply_format(input: &str, block: FormattedBlock) -> String {
|
||||
let start = block.start;
|
||||
let end = block.end;
|
||||
|
||||
let (left, _) = input.split_at(start);
|
||||
let (_, right) = input.split_at(end);
|
||||
|
||||
format!("{}{}{}", left, block.formatted, right)
|
||||
}
|
||||
|
||||
// Apply all the blocks
|
||||
pub fn apply_formats(input: &str, blocks: Vec<FormattedBlock>) -> String {
|
||||
let mut out = String::new();
|
||||
@@ -213,24 +176,3 @@ pub fn apply_formats(input: &str, blocks: Vec<FormattedBlock>) -> String {
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
struct DisplayIfmt<'a>(&'a IfmtInput);
|
||||
|
||||
impl Display for DisplayIfmt<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let inner_tokens = self.0.source.as_ref().unwrap().to_token_stream();
|
||||
inner_tokens.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ifmt_to_string(input: &IfmtInput) -> String {
|
||||
let mut buf = String::new();
|
||||
let display = DisplayIfmt(input);
|
||||
write!(&mut buf, "{}", display).unwrap();
|
||||
buf
|
||||
}
|
||||
|
||||
pub(crate) fn write_ifmt(input: &IfmtInput, writable: &mut impl Write) -> std::fmt::Result {
|
||||
let display = DisplayIfmt(input);
|
||||
write!(writable, "{}", display)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{ifmt_to_string, prettier_please::unparse_expr, Writer};
|
||||
use crate::{prettier_please::unparse_expr, Writer};
|
||||
use dioxus_rsx::*;
|
||||
use proc_macro2::Span;
|
||||
use quote::ToTokens;
|
||||
@@ -30,47 +30,24 @@ enum ShortOptimization {
|
||||
NoOpt,
|
||||
}
|
||||
|
||||
/*
|
||||
// whitespace
|
||||
div {
|
||||
// some whitespace
|
||||
class: "asdasd"
|
||||
|
||||
// whjiot
|
||||
asdasd // whitespace
|
||||
}
|
||||
*/
|
||||
|
||||
impl Writer<'_> {
|
||||
pub fn write_element(&mut self, el: &Element) -> Result {
|
||||
let Element {
|
||||
name,
|
||||
key,
|
||||
attributes,
|
||||
children,
|
||||
brace,
|
||||
..
|
||||
} = el;
|
||||
|
||||
let brace = brace
|
||||
.as_ref()
|
||||
.expect("braces should always be present in strict mode");
|
||||
|
||||
/*
|
||||
1. Write the tag
|
||||
2. Write the key
|
||||
3. Write the attributes
|
||||
4. Write the children
|
||||
*/
|
||||
|
||||
write!(self.out, "{name} {{")?;
|
||||
|
||||
/// Basically elements and components are the same thing
|
||||
///
|
||||
/// This writes the contents out for both in one function, centralizing the annoying logic like
|
||||
/// key handling, breaks, closures, etc
|
||||
pub fn write_rsx_block(
|
||||
&mut self,
|
||||
attributes: &[Attribute],
|
||||
spreads: &[Spread],
|
||||
children: &[BodyNode],
|
||||
brace: &Brace,
|
||||
) -> Result {
|
||||
// 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.is_short_attrs(attributes);
|
||||
let attr_len = self.is_short_attrs(attributes, spreads);
|
||||
let is_short_attr_list = (attr_len + self.out.indent_level * 4) < 80;
|
||||
let children_len = self.is_short_children(children);
|
||||
let is_small_children = children_len.is_some();
|
||||
@@ -82,7 +59,7 @@ impl Writer<'_> {
|
||||
|
||||
// even if the attr is long, it should be put on one line
|
||||
// However if we have childrne we need to just spread them out for readability
|
||||
if !is_short_attr_list && attributes.len() <= 1 {
|
||||
if !is_short_attr_list && attributes.len() <= 1 && spreads.is_empty() {
|
||||
if children.is_empty() {
|
||||
opt_level = ShortOptimization::Oneliner;
|
||||
} else {
|
||||
@@ -100,7 +77,7 @@ impl Writer<'_> {
|
||||
}
|
||||
|
||||
// If there's nothing at all, empty optimization
|
||||
if attributes.is_empty() && children.is_empty() && key.is_none() {
|
||||
if attributes.is_empty() && children.is_empty() && spreads.is_empty() {
|
||||
opt_level = ShortOptimization::Empty;
|
||||
|
||||
// Write comments if they exist
|
||||
@@ -112,49 +89,42 @@ impl Writer<'_> {
|
||||
opt_level = ShortOptimization::NoOpt;
|
||||
}
|
||||
|
||||
let has_children = !children.is_empty();
|
||||
|
||||
match opt_level {
|
||||
ShortOptimization::Empty => {}
|
||||
ShortOptimization::Oneliner => {
|
||||
write!(self.out, " ")?;
|
||||
|
||||
self.write_attributes(brace, attributes, key, true)?;
|
||||
self.write_attributes(attributes, spreads, true, brace, has_children)?;
|
||||
|
||||
if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
|
||||
write!(self.out, ", ")?;
|
||||
if !children.is_empty() && !attributes.is_empty() {
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
|
||||
for (id, child) in children.iter().enumerate() {
|
||||
for child in children.iter() {
|
||||
self.write_ident(child)?;
|
||||
if id != children.len() - 1 && children.len() > 1 {
|
||||
write!(self.out, ", ")?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
|
||||
ShortOptimization::PropsOnTop => {
|
||||
if !attributes.is_empty() || key.is_some() {
|
||||
if !attributes.is_empty() {
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
self.write_attributes(brace, attributes, key, true)?;
|
||||
|
||||
if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
|
||||
write!(self.out, ",")?;
|
||||
}
|
||||
self.write_attributes(attributes, spreads, true, brace, has_children)?;
|
||||
|
||||
if !children.is_empty() {
|
||||
self.write_body_indented(children)?;
|
||||
}
|
||||
|
||||
self.out.tabbed_line()?;
|
||||
}
|
||||
|
||||
ShortOptimization::NoOpt => {
|
||||
self.write_attributes(brace, attributes, key, false)?;
|
||||
|
||||
if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
|
||||
write!(self.out, ",")?;
|
||||
}
|
||||
self.write_attributes(attributes, spreads, false, brace, has_children)?;
|
||||
|
||||
if !children.is_empty() {
|
||||
self.write_body_indented(children)?;
|
||||
@@ -164,76 +134,99 @@ impl Writer<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
write!(self.out, "}}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_attributes(
|
||||
&mut self,
|
||||
attributes: &[Attribute],
|
||||
spreads: &[Spread],
|
||||
props_same_line: bool,
|
||||
brace: &Brace,
|
||||
attributes: &[AttributeType],
|
||||
key: &Option<IfmtInput>,
|
||||
sameline: bool,
|
||||
has_children: bool,
|
||||
) -> Result {
|
||||
let mut attr_iter = attributes.iter().peekable();
|
||||
|
||||
if let Some(key) = key {
|
||||
if !sameline {
|
||||
self.out.indented_tabbed_line()?;
|
||||
}
|
||||
write!(self.out, "key: {}", ifmt_to_string(key))?;
|
||||
if !attributes.is_empty() {
|
||||
write!(self.out, ",")?;
|
||||
if sameline {
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
}
|
||||
enum AttrType<'a> {
|
||||
Attr(&'a Attribute),
|
||||
Spread(&'a Spread),
|
||||
}
|
||||
|
||||
let mut attr_iter = attributes
|
||||
.iter()
|
||||
.map(AttrType::Attr)
|
||||
.chain(spreads.iter().map(AttrType::Spread))
|
||||
.peekable();
|
||||
|
||||
while let Some(attr) = attr_iter.next() {
|
||||
self.out.indent_level += 1;
|
||||
|
||||
if !sameline {
|
||||
self.write_attr_comments(brace, attr.start())?;
|
||||
if !props_same_line {
|
||||
self.write_attr_comments(
|
||||
brace,
|
||||
match attr {
|
||||
AttrType::Attr(attr) => attr.span(),
|
||||
AttrType::Spread(attr) => attr.expr.span(),
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
||||
self.out.indent_level -= 1;
|
||||
|
||||
if !sameline {
|
||||
if !props_same_line {
|
||||
self.out.indented_tabbed_line()?;
|
||||
}
|
||||
|
||||
self.write_attribute(attr)?;
|
||||
match attr {
|
||||
AttrType::Attr(attr) => self.write_attribute(attr)?,
|
||||
AttrType::Spread(attr) => self.write_spread_attribute(&attr.expr)?,
|
||||
}
|
||||
|
||||
if attr_iter.peek().is_some() {
|
||||
write!(self.out, ",")?;
|
||||
|
||||
if sameline {
|
||||
if props_same_line {
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
let has_attributes = !attributes.is_empty() || !spreads.is_empty();
|
||||
|
||||
fn write_attribute_name(&mut self, attr: &ElementAttrName) -> Result {
|
||||
match attr {
|
||||
ElementAttrName::BuiltIn(name) => {
|
||||
write!(self.out, "{}", name)?;
|
||||
}
|
||||
ElementAttrName::Custom(name) => {
|
||||
write!(self.out, "{}", name.to_token_stream())?;
|
||||
}
|
||||
if has_attributes && has_children {
|
||||
write!(self.out, ",")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_attribute_value(&mut self, value: &ElementAttrValue) -> Result {
|
||||
fn write_attribute(&mut self, attr: &Attribute) -> Result {
|
||||
self.write_attribute_name(&attr.name)?;
|
||||
|
||||
// if the attribute is a shorthand, we don't need to write the colon, just the name
|
||||
if !attr.can_be_shorthand() {
|
||||
write!(self.out, ": ")?;
|
||||
self.write_attribute_value(&attr.value)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_attribute_name(&mut self, attr: &AttributeName) -> Result {
|
||||
match attr {
|
||||
AttributeName::BuiltIn(name) => {
|
||||
write!(self.out, "{}", name)?;
|
||||
}
|
||||
AttributeName::Custom(name) => {
|
||||
write!(self.out, "{}", name.to_token_stream())?;
|
||||
}
|
||||
AttributeName::Spread(_) => unreachable!(),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_attribute_value(&mut self, value: &AttributeValue) -> Result {
|
||||
match value {
|
||||
ElementAttrValue::AttrOptionalExpr { condition, value } => {
|
||||
AttributeValue::AttrOptionalExpr { condition, value } => {
|
||||
write!(
|
||||
self.out,
|
||||
"if {condition} {{ ",
|
||||
@@ -242,56 +235,47 @@ impl Writer<'_> {
|
||||
self.write_attribute_value(value)?;
|
||||
write!(self.out, " }}")?;
|
||||
}
|
||||
ElementAttrValue::AttrLiteral(value) => {
|
||||
write!(self.out, "{value}", value = ifmt_to_string(value))?;
|
||||
AttributeValue::AttrLiteral(value) => {
|
||||
write!(self.out, "{value}")?;
|
||||
}
|
||||
ElementAttrValue::Shorthand(value) => {
|
||||
write!(self.out, "{value}",)?;
|
||||
AttributeValue::Shorthand(value) => {
|
||||
write!(self.out, "{value}")?;
|
||||
}
|
||||
AttributeValue::EventTokens(closure) => {
|
||||
self.write_partial_closure(closure)?;
|
||||
}
|
||||
ElementAttrValue::AttrExpr(value) => {
|
||||
let out = self.unparse_expr(value);
|
||||
let mut lines = out.split('\n').peekable();
|
||||
let first = lines.next().unwrap();
|
||||
|
||||
// a one-liner for whatever reason
|
||||
// Does not need a new line
|
||||
AttributeValue::AttrExpr(value) => {
|
||||
let Ok(expr) = value.as_expr() else {
|
||||
return Err(fmt::Error);
|
||||
};
|
||||
|
||||
let pretty_expr = self.retrieve_formatted_expr(&expr).to_string();
|
||||
self.write_mulitiline_tokens(pretty_expr)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_mulitiline_tokens(&mut self, out: String) -> Result {
|
||||
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.out, "{first}")?;
|
||||
} else {
|
||||
writeln!(self.out, "{first}")?;
|
||||
|
||||
while let Some(line) = lines.next() {
|
||||
self.out.indented_tab()?;
|
||||
write!(self.out, "{line}")?;
|
||||
if lines.peek().is_none() {
|
||||
write!(self.out, "{first}")?;
|
||||
write!(self.out, "")?;
|
||||
} else {
|
||||
writeln!(self.out, "{first}")?;
|
||||
|
||||
while let Some(line) = lines.next() {
|
||||
self.out.indented_tab()?;
|
||||
write!(self.out, "{line}")?;
|
||||
if lines.peek().is_none() {
|
||||
write!(self.out, "")?;
|
||||
} else {
|
||||
writeln!(self.out)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ElementAttrValue::EventTokens(tokens) => {
|
||||
let out = self.retrieve_formatted_expr(tokens).to_string();
|
||||
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.out, "{first}")?;
|
||||
} else {
|
||||
writeln!(self.out, "{first}")?;
|
||||
|
||||
while let Some(line) = lines.next() {
|
||||
self.out.indented_tab()?;
|
||||
write!(self.out, "{line}")?;
|
||||
if lines.peek().is_none() {
|
||||
write!(self.out, "")?;
|
||||
} else {
|
||||
writeln!(self.out)?;
|
||||
}
|
||||
}
|
||||
writeln!(self.out)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,28 +283,34 @@ impl Writer<'_> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_attribute(&mut self, attr: &AttributeType) -> Result {
|
||||
match attr {
|
||||
AttributeType::Named(attr) => self.write_named_attribute(attr),
|
||||
AttributeType::Spread(attr) => self.write_spread_attribute(attr),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_named_attribute(&mut self, attr: &ElementAttrNamed) -> Result {
|
||||
self.write_attribute_name(&attr.attr.name)?;
|
||||
|
||||
// if the attribute is a shorthand, we don't need to write the colon, just the name
|
||||
if !attr.attr.can_be_shorthand() {
|
||||
write!(self.out, ": ")?;
|
||||
self.write_attribute_value(&attr.attr.value)?;
|
||||
/// Write out the special PartialClosure type from the rsx crate
|
||||
/// Basically just write token by token until we hit the block and then try and format *that*
|
||||
/// We can't just ToTokens
|
||||
fn write_partial_closure(&mut self, closure: &PartialClosure) -> Result {
|
||||
// Write the pretty version of the closure
|
||||
if let Ok(expr) = closure.as_expr() {
|
||||
let pretty_expr = self.retrieve_formatted_expr(&expr).to_string();
|
||||
self.write_mulitiline_tokens(pretty_expr)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
// If we can't parse the closure, writing it is also a failure
|
||||
// rustfmt won't be able to parse it either so no point in trying
|
||||
Err(fmt::Error)
|
||||
}
|
||||
|
||||
fn write_spread_attribute(&mut self, attr: &Expr) -> Result {
|
||||
write!(self.out, "..")?;
|
||||
write!(self.out, "{}", unparse_expr(attr))?;
|
||||
let formatted = unparse_expr(attr);
|
||||
|
||||
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(())
|
||||
}
|
||||
@@ -363,10 +353,10 @@ impl Writer<'_> {
|
||||
}
|
||||
|
||||
match children {
|
||||
[BodyNode::Text(ref text)] => Some(ifmt_to_string(text).len()),
|
||||
[BodyNode::Text(ref text)] => Some(text.input.to_string_with_quotes().len()),
|
||||
|
||||
// TODO: let rawexprs to be inlined
|
||||
[BodyNode::RawExpr(ref expr)] => get_expr_length(expr),
|
||||
[BodyNode::RawExpr(ref expr)] => Some(get_expr_length(expr.span())),
|
||||
|
||||
// TODO: let rawexprs to be inlined
|
||||
[BodyNode::Component(ref comp)] if comp.fields.is_empty() => Some(
|
||||
@@ -430,12 +420,11 @@ impl Writer<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_expr_length(expr: &impl Spanned) -> Option<usize> {
|
||||
let span = expr.span();
|
||||
fn get_expr_length(span: Span) -> usize {
|
||||
let (start, end) = (span.start(), span.end());
|
||||
if start.line == end.line {
|
||||
Some(end.column - start.column)
|
||||
end.column - start.column
|
||||
} else {
|
||||
None
|
||||
10000
|
||||
}
|
||||
}
|
||||
+185
-94
@@ -1,4 +1,9 @@
|
||||
use dioxus_rsx::{AttributeType, BodyNode, ElementAttrValue, ForLoop, IfChain, IfmtInput};
|
||||
use crate::buffer::Buffer;
|
||||
use crate::collect_macros::byte_offset;
|
||||
use dioxus_rsx::{
|
||||
Attribute as AttributeType, AttributeName, AttributeValue as ElementAttrValue, BodyNode,
|
||||
Component, Element, ForLoop, IfChain, Spread, TemplateBody,
|
||||
};
|
||||
use proc_macro2::{LineColumn, Span};
|
||||
use quote::ToTokens;
|
||||
use std::{
|
||||
@@ -7,32 +12,15 @@ use std::{
|
||||
};
|
||||
use syn::{spanned::Spanned, token::Brace, Expr};
|
||||
|
||||
use crate::buffer::Buffer;
|
||||
use crate::ifmt_to_string;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Writer<'a> {
|
||||
pub raw_src: &'a str,
|
||||
pub src: Vec<&'a str>,
|
||||
pub cached_formats: HashMap<Location, String>,
|
||||
pub cached_formats: HashMap<LineColumn, String>,
|
||||
pub comments: VecDeque<usize>,
|
||||
pub out: Buffer,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
|
||||
pub struct Location {
|
||||
pub line: usize,
|
||||
pub col: usize,
|
||||
}
|
||||
impl Location {
|
||||
pub fn new(start: LineColumn) -> Self {
|
||||
Self {
|
||||
line: start.line,
|
||||
col: start.column,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Writer<'a> {
|
||||
pub fn new(raw_src: &'a str) -> Self {
|
||||
let src = raw_src.lines().collect();
|
||||
@@ -45,20 +33,143 @@ impl<'a> Writer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn consume(self) -> Option<String> {
|
||||
Some(self.out.buf)
|
||||
}
|
||||
|
||||
pub fn write_rsx_call(&mut self, body: &TemplateBody) -> Result {
|
||||
match body.roots.len() {
|
||||
0 => {}
|
||||
1 if matches!(body.roots[0], BodyNode::Text(_)) => {
|
||||
write!(self.out, " ")?;
|
||||
self.write_ident(&body.roots[0])?;
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
_ => self.write_body_indented(&body.roots)?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Expects to be written directly into place
|
||||
pub fn write_ident(&mut self, node: &BodyNode) -> Result {
|
||||
match node {
|
||||
BodyNode::Element(el) => self.write_element(el),
|
||||
BodyNode::Component(component) => self.write_component(component),
|
||||
BodyNode::Text(text) => self.out.write_text(text),
|
||||
BodyNode::Text(text) => self.out.write_text(&text.input),
|
||||
BodyNode::RawExpr(exp) => self.write_raw_expr(exp.span()),
|
||||
BodyNode::ForLoop(forloop) => self.write_for_loop(forloop),
|
||||
BodyNode::IfChain(ifchain) => self.write_if_chain(ifchain),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn consume(self) -> Option<String> {
|
||||
Some(self.out.buf)
|
||||
pub fn write_element(&mut self, el: &Element) -> Result {
|
||||
let Element {
|
||||
name,
|
||||
raw_attributes: attributes,
|
||||
children,
|
||||
spreads,
|
||||
brace,
|
||||
..
|
||||
} = el;
|
||||
|
||||
/*
|
||||
1. Write the tag
|
||||
2. Write the key
|
||||
3. Write the attributes
|
||||
4. Write the children
|
||||
*/
|
||||
|
||||
write!(self.out, "{name} {{")?;
|
||||
|
||||
self.write_rsx_block(attributes, spreads, children, brace)?;
|
||||
|
||||
write!(self.out, "}}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_component(
|
||||
&mut self,
|
||||
Component {
|
||||
name,
|
||||
fields,
|
||||
children,
|
||||
generics,
|
||||
spreads,
|
||||
brace,
|
||||
..
|
||||
}: &Component,
|
||||
) -> Result {
|
||||
// Write the path by to_tokensing it and then removing all whitespace
|
||||
let mut name = name.to_token_stream().to_string();
|
||||
name.retain(|c| !c.is_whitespace());
|
||||
write!(self.out, "{name}")?;
|
||||
|
||||
// Same idea with generics, write those via the to_tokens method and then remove all whitespace
|
||||
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, " {{")?;
|
||||
|
||||
self.write_rsx_block(fields, spreads, &children.roots, brace)?;
|
||||
|
||||
write!(self.out, "}}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_raw_expr(&mut self, placement: Span) -> Result {
|
||||
/*
|
||||
We want to normalize the expr to the appropriate indent level.
|
||||
*/
|
||||
|
||||
let start = placement.start();
|
||||
let end = placement.end();
|
||||
|
||||
// if the expr is on one line, just write it directly
|
||||
if start.line == end.line {
|
||||
// split counting utf8 chars
|
||||
let start = byte_offset(self.raw_src, start);
|
||||
let end = byte_offset(self.raw_src, end);
|
||||
let row = self.raw_src[start..end].trim();
|
||||
write!(self.out, "{row}")?;
|
||||
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.out, "{}", &first_line[start.column..].trim_start())?;
|
||||
|
||||
let prev_block_indent_level = self.out.indent.count_indents(first_line);
|
||||
|
||||
for (id, line) in self.src[start.line..end.line].iter().enumerate() {
|
||||
writeln!(self.out)?;
|
||||
|
||||
// check if this is the last line
|
||||
let line = {
|
||||
if id == (end.line - start.line) - 1 {
|
||||
&line[..end.column]
|
||||
} else {
|
||||
line
|
||||
}
|
||||
};
|
||||
|
||||
// trim the leading whitespace
|
||||
let previous_indent = self.out.indent.count_indents(line);
|
||||
let offset = previous_indent.saturating_sub(prev_block_indent_level);
|
||||
let required_indent = self.out.indent_level + offset;
|
||||
self.out.write_tabs(required_indent)?;
|
||||
|
||||
let line = line.trim_start();
|
||||
write!(self.out, "{line}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_attr_comments(&mut self, brace: &Brace, attr_span: Span) -> Result {
|
||||
@@ -123,28 +234,13 @@ impl<'a> Writer<'a> {
|
||||
}
|
||||
|
||||
pub fn write_body_no_indent(&mut self, children: &[BodyNode]) -> Result {
|
||||
let last_child = children.len();
|
||||
let iter = children.iter().peekable().enumerate();
|
||||
|
||||
for (idx, child) in iter {
|
||||
for child in children {
|
||||
if self.current_span_is_primary(child.span()) {
|
||||
self.write_comments(child.span())?;
|
||||
}
|
||||
};
|
||||
|
||||
match child {
|
||||
// check if the expr is a short
|
||||
BodyNode::RawExpr { .. } => {
|
||||
self.out.tabbed_line()?;
|
||||
self.write_ident(child)?;
|
||||
if idx != last_child - 1 {
|
||||
write!(self.out, ",")?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.out.tabbed_line()?;
|
||||
self.write_ident(child)?;
|
||||
}
|
||||
}
|
||||
self.out.tabbed_line()?;
|
||||
self.write_ident(child)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -155,31 +251,35 @@ impl<'a> Writer<'a> {
|
||||
ElementAttrValue::AttrOptionalExpr { condition, value } => {
|
||||
let condition_len = self.retrieve_formatted_expr(condition).len();
|
||||
let value_len = self.attr_value_len(value);
|
||||
|
||||
condition_len + value_len + 6
|
||||
}
|
||||
ElementAttrValue::AttrLiteral(lit) => ifmt_to_string(lit).len(),
|
||||
ElementAttrValue::AttrLiteral(lit) => lit.to_string().len(),
|
||||
ElementAttrValue::Shorthand(expr) => expr.span().line_length(),
|
||||
ElementAttrValue::AttrExpr(expr) => {
|
||||
let out = self.retrieve_formatted_expr(expr);
|
||||
if out.contains('\n') {
|
||||
100000
|
||||
} else {
|
||||
out.len()
|
||||
}
|
||||
}
|
||||
ElementAttrValue::EventTokens(tokens) => {
|
||||
let as_str = self.retrieve_formatted_expr(tokens);
|
||||
if as_str.contains('\n') {
|
||||
100000
|
||||
} else {
|
||||
as_str.len()
|
||||
}
|
||||
}
|
||||
ElementAttrValue::AttrExpr(expr) => expr
|
||||
.as_expr()
|
||||
.map(|expr| self.attr_expr_len(&expr))
|
||||
.unwrap_or(100000),
|
||||
ElementAttrValue::EventTokens(closure) => closure
|
||||
.as_expr()
|
||||
.map(|expr| self.attr_expr_len(&expr))
|
||||
.unwrap_or(100000),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_short_attrs(&mut self, attributes: &[AttributeType]) -> usize {
|
||||
fn attr_expr_len(&mut self, expr: &Expr) -> usize {
|
||||
let out = self.retrieve_formatted_expr(expr);
|
||||
if out.contains('\n') {
|
||||
100000
|
||||
} else {
|
||||
out.len()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_short_attrs(
|
||||
&mut self,
|
||||
attributes: &[AttributeType],
|
||||
spreads: &[Spread],
|
||||
) -> usize {
|
||||
let mut total = 0;
|
||||
|
||||
// No more than 3 attributes before breaking the line
|
||||
@@ -188,8 +288,8 @@ impl<'a> Writer<'a> {
|
||||
}
|
||||
|
||||
for attr in attributes {
|
||||
if self.current_span_is_primary(attr.start()) {
|
||||
'line: for line in self.src[..attr.start().start().line - 1].iter().rev() {
|
||||
if self.current_span_is_primary(attr.span()) {
|
||||
'line: for line in self.src[..attr.span().start().line - 1].iter().rev() {
|
||||
match (line.trim().starts_with("//"), line.is_empty()) {
|
||||
(true, _) => return 100000,
|
||||
(_, true) => continue 'line,
|
||||
@@ -198,39 +298,37 @@ impl<'a> Writer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
match attr {
|
||||
AttributeType::Named(attr) => {
|
||||
let name_len = match &attr.attr.name {
|
||||
dioxus_rsx::ElementAttrName::BuiltIn(name) => {
|
||||
let name = name.to_string();
|
||||
name.len()
|
||||
}
|
||||
dioxus_rsx::ElementAttrName::Custom(name) => name.value().len() + 2,
|
||||
};
|
||||
total += name_len;
|
||||
|
||||
//
|
||||
if attr.attr.value.is_shorthand() {
|
||||
total += 2;
|
||||
} else {
|
||||
total += self.attr_value_len(&attr.attr.value);
|
||||
}
|
||||
}
|
||||
AttributeType::Spread(expr) => {
|
||||
let expr_len = self.retrieve_formatted_expr(expr).len();
|
||||
total += expr_len + 3;
|
||||
let name_len = match &attr.name {
|
||||
AttributeName::BuiltIn(name) => {
|
||||
let name = name.to_string();
|
||||
name.len()
|
||||
}
|
||||
AttributeName::Custom(name) => name.value().len() + 2,
|
||||
AttributeName::Spread(_) => unreachable!(),
|
||||
};
|
||||
total += name_len;
|
||||
|
||||
//
|
||||
if attr.can_be_shorthand() {
|
||||
total += 2;
|
||||
} else {
|
||||
total += self.attr_value_len(&attr.value);
|
||||
}
|
||||
|
||||
total += 6;
|
||||
}
|
||||
|
||||
for spread in spreads {
|
||||
let expr_len = self.retrieve_formatted_expr(&spread.expr).len();
|
||||
total += expr_len + 3;
|
||||
}
|
||||
|
||||
total
|
||||
}
|
||||
|
||||
#[allow(clippy::map_entry)]
|
||||
pub fn retrieve_formatted_expr(&mut self, expr: &Expr) -> &str {
|
||||
let loc = Location::new(expr.span().start());
|
||||
let loc = expr.span().start();
|
||||
|
||||
if !self.cached_formats.contains_key(&loc) {
|
||||
let formatted = self.unparse_expr(expr);
|
||||
@@ -254,7 +352,7 @@ impl<'a> Writer<'a> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.write_body_indented(&forloop.body)?;
|
||||
self.write_body_indented(&forloop.body.roots)?;
|
||||
|
||||
self.out.tabbed_line()?;
|
||||
write!(self.out, "}}")?;
|
||||
@@ -280,7 +378,7 @@ impl<'a> Writer<'a> {
|
||||
|
||||
self.write_inline_expr(cond)?;
|
||||
|
||||
self.write_body_indented(then_branch)?;
|
||||
self.write_body_indented(&then_branch.roots)?;
|
||||
|
||||
if let Some(else_if_branch) = else_if_branch {
|
||||
// write the closing bracket and else
|
||||
@@ -292,7 +390,7 @@ impl<'a> Writer<'a> {
|
||||
self.out.tabbed_line()?;
|
||||
write!(self.out, "}} else {{")?;
|
||||
|
||||
self.write_body_indented(else_branch)?;
|
||||
self.write_body_indented(&else_branch.roots)?;
|
||||
branch = None;
|
||||
} else {
|
||||
branch = None;
|
||||
@@ -329,13 +427,6 @@ impl<'a> Writer<'a> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn key_len(&self, key: Option<&IfmtInput>) -> usize {
|
||||
match key {
|
||||
Some(key) => ifmt_to_string(key).len() + 5,
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait SpanLength {
|
||||
|
||||
Reference in new issue
Block a user