Fix: #2604, Fix: #2240, Fix: #2341, Fix #1355 - Better error handling and and spaces handling in autofmt (#2736)
* add new autofmt sample * Feat: implement rustfmt::skip support for rsx * generally improve error handling with better expect messages * wip: nested rsx formatting and expression formatting * nested rsx formatting works * collapse autofmt crate * cast indent through macros * use proper whitespace * no more eating comments! * Use proper error handling
This commit is contained in:
1 parent
ef0202f999
commit
828cc502f1
47 files changed
+1621
-1055
No files matched your search
Generated
+1
@@ -2616,6 +2616,7 @@ dependencies = [
|
|||||||
"dioxus-autofmt",
|
"dioxus-autofmt",
|
||||||
"html_parser",
|
"html_parser",
|
||||||
"rsx-rosetta",
|
"rsx-rosetta",
|
||||||
|
"syn 2.0.72",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -104,7 +104,7 @@ wasm-bindgen = "0.2.92"
|
|||||||
wasm-bindgen-futures = "0.4.42"
|
wasm-bindgen-futures = "0.4.42"
|
||||||
html_parser = "0.7.0"
|
html_parser = "0.7.0"
|
||||||
thiserror = "1.0.40"
|
thiserror = "1.0.40"
|
||||||
prettyplease = { version = "0.2.16", features = ["verbatim"] }
|
prettyplease = { version = "0.2.20", features = ["verbatim"] }
|
||||||
manganis-cli-support = { git = "hhttps://github.com/DioxusLabs/manganis", features = ["html"] }
|
manganis-cli-support = { git = "hhttps://github.com/DioxusLabs/manganis", features = ["html"] }
|
||||||
manganis = { git = "https://github.com/DioxusLabs/manganis" }
|
manganis = { git = "https://github.com/DioxusLabs/manganis" }
|
||||||
const_format = "0.2.32"
|
const_format = "0.2.32"
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
//! The output buffer that supports some helpful methods
|
//! The output buffer that supports some helpful methods
|
||||||
//! These are separate from the input so we can lend references between the two
|
//! These are separate from the input so we can lend references between the two
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
|
|
||||||
use std::fmt::{Result, Write};
|
use std::fmt::{Result, Write};
|
||||||
|
|
||||||
|
|||||||
@@ -3,37 +3,66 @@
|
|||||||
//! Returns all macros that match a pattern. You can use this information to autoformat them later
|
//! Returns all macros that match a pattern. You can use this information to autoformat them later
|
||||||
|
|
||||||
use proc_macro2::LineColumn;
|
use proc_macro2::LineColumn;
|
||||||
use syn::{visit::Visit, File, Macro};
|
use syn::{visit::Visit, File, Macro, Meta};
|
||||||
|
|
||||||
type CollectedMacro<'a> = &'a Macro;
|
type CollectedMacro<'a> = &'a Macro;
|
||||||
|
|
||||||
pub fn collect_from_file(file: &File) -> Vec<CollectedMacro<'_>> {
|
pub fn collect_from_file(file: &File) -> Vec<CollectedMacro<'_>> {
|
||||||
let mut macros = vec![];
|
let mut macros = vec![];
|
||||||
MacroCollector::visit_file(
|
let mut collector = MacroCollector::new(&mut macros);
|
||||||
&mut MacroCollector {
|
MacroCollector::visit_file(&mut collector, file);
|
||||||
macros: &mut macros,
|
|
||||||
},
|
|
||||||
file,
|
|
||||||
);
|
|
||||||
macros
|
macros
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MacroCollector<'a, 'b> {
|
struct MacroCollector<'a, 'b> {
|
||||||
macros: &'a mut Vec<CollectedMacro<'b>>,
|
macros: &'a mut Vec<CollectedMacro<'b>>,
|
||||||
|
skip_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> MacroCollector<'a, 'b> {
|
||||||
|
fn new(macros: &'a mut Vec<CollectedMacro<'b>>) -> Self {
|
||||||
|
Self {
|
||||||
|
macros,
|
||||||
|
skip_count: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Visit<'b> for MacroCollector<'a, 'b> {
|
impl<'a, 'b> Visit<'b> for MacroCollector<'a, 'b> {
|
||||||
fn visit_macro(&mut self, i: &'b Macro) {
|
fn visit_macro(&mut self, i: &'b Macro) {
|
||||||
if let Some("rsx" | "render") = i
|
// Visit the regular stuff - this will also ensure paths/attributes are visited
|
||||||
.path
|
syn::visit::visit_macro(self, i);
|
||||||
.segments
|
|
||||||
.last()
|
let name = &i.path.segments.last().map(|i| i.ident.to_string());
|
||||||
.map(|i| i.ident.to_string())
|
if let Some("rsx" | "render") = name.as_deref() {
|
||||||
.as_deref()
|
if self.skip_count == 0 {
|
||||||
{
|
self.macros.push(i)
|
||||||
self.macros.push(i)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// attributes can occur on stmts and items - we need to make sure the stack is reset when we exit
|
||||||
|
// this means we save the skipped length and set it back to its original length
|
||||||
|
fn visit_stmt(&mut self, i: &'b syn::Stmt) {
|
||||||
|
let skipped_len = self.skip_count;
|
||||||
|
syn::visit::visit_stmt(self, i);
|
||||||
|
self.skip_count = skipped_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_item(&mut self, i: &'b syn::Item) {
|
||||||
|
let skipped_len = self.skip_count;
|
||||||
|
syn::visit::visit_item(self, i);
|
||||||
|
self.skip_count = skipped_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_attribute(&mut self, i: &'b syn::Attribute) {
|
||||||
|
// we need to communicate that this stmt is skipped up the tree
|
||||||
|
if attr_is_rustfmt_skip(i) {
|
||||||
|
self.skip_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
syn::visit::visit_attribute(self, i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn byte_offset(input: &str, location: LineColumn) -> usize {
|
pub fn byte_offset(input: &str, location: LineColumn) -> usize {
|
||||||
@@ -49,10 +78,32 @@ pub fn byte_offset(input: &str, location: LineColumn) -> usize {
|
|||||||
.sum::<usize>()
|
.sum::<usize>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if an attribute is a rustfmt skip attribute
|
||||||
|
fn attr_is_rustfmt_skip(i: &syn::Attribute) -> bool {
|
||||||
|
match &i.meta {
|
||||||
|
Meta::Path(path) => {
|
||||||
|
path.segments.len() == 2
|
||||||
|
&& matches!(i.style, syn::AttrStyle::Outer)
|
||||||
|
&& path.segments[0].ident == "rustfmt"
|
||||||
|
&& path.segments[1].ident == "skip"
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parses_file_and_collects_rsx_macros() {
|
fn parses_file_and_collects_rsx_macros() {
|
||||||
let contents = include_str!("../tests/samples/long.rsx");
|
let contents = include_str!("../tests/samples/long.rsx");
|
||||||
let parsed = syn::parse_file(contents).unwrap();
|
let parsed = syn::parse_file(contents).expect("parse file okay");
|
||||||
let macros = collect_from_file(&parsed);
|
let macros = collect_from_file(&parsed);
|
||||||
assert_eq!(macros.len(), 3);
|
assert_eq!(macros.len(), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensure that we only collect non-skipped macros
|
||||||
|
#[test]
|
||||||
|
fn dont_collect_skipped_macros() {
|
||||||
|
let contents = include_str!("../tests/samples/skip.rsx");
|
||||||
|
let parsed = syn::parse_file(contents).expect("parse file okay");
|
||||||
|
let macros = collect_from_file(&parsed);
|
||||||
|
assert_eq!(macros.len(), 2);
|
||||||
|
}
|
||||||
+35
-32
@@ -4,14 +4,13 @@
|
|||||||
|
|
||||||
use crate::writer::*;
|
use crate::writer::*;
|
||||||
use dioxus_rsx::{BodyNode, CallBody};
|
use dioxus_rsx::{BodyNode, CallBody};
|
||||||
use proc_macro2::LineColumn;
|
use proc_macro2::{LineColumn, Span};
|
||||||
use syn::{parse::Parser, ExprMacro};
|
use syn::parse::Parser;
|
||||||
|
|
||||||
mod buffer;
|
mod buffer;
|
||||||
mod collect_macros;
|
mod collect_macros;
|
||||||
mod indent;
|
mod indent;
|
||||||
mod prettier_please;
|
mod prettier_please;
|
||||||
mod rsx_block;
|
|
||||||
mod writer;
|
mod writer;
|
||||||
|
|
||||||
pub use indent::{IndentOptions, IndentType};
|
pub use indent::{IndentOptions, IndentType};
|
||||||
@@ -37,6 +36,16 @@ pub struct FormattedBlock {
|
|||||||
pub end: usize,
|
pub end: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Format a file into a list of `FormattedBlock`s to be applied by an IDE for autoformatting.
|
||||||
|
///
|
||||||
|
/// It accepts
|
||||||
|
#[deprecated(note = "Use try_fmt_file instead - this function panics on error.")]
|
||||||
|
pub fn fmt_file(contents: &str, indent: IndentOptions) -> Vec<FormattedBlock> {
|
||||||
|
let parsed =
|
||||||
|
syn::parse_file(contents).expect("fmt_file should only be called on valid syn::File files");
|
||||||
|
try_fmt_file(contents, &parsed, indent).expect("Failed to format file")
|
||||||
|
}
|
||||||
|
|
||||||
/// Format a file into a list of `FormattedBlock`s to be applied by an IDE for autoformatting.
|
/// Format a file into a list of `FormattedBlock`s to be applied by an IDE for autoformatting.
|
||||||
///
|
///
|
||||||
/// This function expects a complete file, not just a block of code. To format individual rsx! blocks, use fmt_block instead.
|
/// This function expects a complete file, not just a block of code. To format individual rsx! blocks, use fmt_block instead.
|
||||||
@@ -45,19 +54,27 @@ pub struct FormattedBlock {
|
|||||||
/// back to the file precisely.
|
/// back to the file precisely.
|
||||||
///
|
///
|
||||||
/// Nested blocks of RSX will be handled automatically
|
/// Nested blocks of RSX will be handled automatically
|
||||||
pub fn fmt_file(contents: &str, indent: IndentOptions) -> Vec<FormattedBlock> {
|
///
|
||||||
|
/// This returns an error if the rsx itself is invalid.
|
||||||
|
///
|
||||||
|
/// Will early return if any of the expressions are not complete. Even though we *could* return the
|
||||||
|
/// expressions, eventually we'll want to pass off expression formatting to rustfmt which will reject
|
||||||
|
/// those.
|
||||||
|
pub fn try_fmt_file(
|
||||||
|
contents: &str,
|
||||||
|
parsed: &syn::File,
|
||||||
|
indent: IndentOptions,
|
||||||
|
) -> syn::Result<Vec<FormattedBlock>> {
|
||||||
let mut formatted_blocks = Vec::new();
|
let mut formatted_blocks = Vec::new();
|
||||||
|
|
||||||
let parsed = syn::parse_file(contents).unwrap();
|
let macros = collect_macros::collect_from_file(parsed);
|
||||||
let macros = collect_macros::collect_from_file(&parsed);
|
|
||||||
|
|
||||||
// No macros, no work to do
|
// No macros, no work to do
|
||||||
if macros.is_empty() {
|
if macros.is_empty() {
|
||||||
return formatted_blocks;
|
return Ok(formatted_blocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut writer = Writer::new(contents);
|
let mut writer = Writer::new(contents, indent);
|
||||||
writer.out.indent = indent;
|
|
||||||
|
|
||||||
// Don't parse nested macros
|
// Don't parse nested macros
|
||||||
let mut end_span = LineColumn { column: 0, line: 0 };
|
let mut end_span = LineColumn { column: 0, line: 0 };
|
||||||
@@ -69,14 +86,7 @@ pub fn fmt_file(contents: &str, indent: IndentOptions) -> Vec<FormattedBlock> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let body = match item.parse_body_with(CallBody::parse_strict) {
|
let body = item.parse_body_with(CallBody::parse_strict)?;
|
||||||
Ok(v) => v,
|
|
||||||
//there is aparsing error, we give up and don't format the rsx
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Error while parsing rsx {:?} ", e);
|
|
||||||
return formatted_blocks;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let rsx_start = macro_path.span().start();
|
let rsx_start = macro_path.span().start();
|
||||||
|
|
||||||
@@ -86,15 +96,16 @@ pub fn fmt_file(contents: &str, indent: IndentOptions) -> Vec<FormattedBlock> {
|
|||||||
.count_indents(writer.src[rsx_start.line - 1]);
|
.count_indents(writer.src[rsx_start.line - 1]);
|
||||||
|
|
||||||
// TESTME
|
// TESTME
|
||||||
// If we fail to parse this macro then we have no choice to give up and return what we've got
|
// Writing *should* not fail but it's possible that it does
|
||||||
if writer.write_rsx_call(&body.body).is_err() {
|
if writer.write_rsx_call(&body.body).is_err() {
|
||||||
return formatted_blocks;
|
let span = writer.invalid_exprs.pop().unwrap_or_else(Span::call_site);
|
||||||
|
return Err(syn::Error::new(span, "Failed emit valid rsx - likely due to partially complete expressions in the rsx! macro"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// writing idents leaves the final line ended at the end of the last ident
|
// writing idents leaves the final line ended at the end of the last ident
|
||||||
if writer.out.buf.contains('\n') {
|
if writer.out.buf.contains('\n') {
|
||||||
writer.out.new_line().unwrap();
|
_ = writer.out.new_line();
|
||||||
writer.out.tab().unwrap();
|
_ = writer.out.tab();
|
||||||
}
|
}
|
||||||
|
|
||||||
let span = item.delimiter.span().join();
|
let span = item.delimiter.span().join();
|
||||||
@@ -124,7 +135,7 @@ pub fn fmt_file(contents: &str, indent: IndentOptions) -> Vec<FormattedBlock> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
formatted_blocks
|
Ok(formatted_blocks)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a Callbody (the rsx block) to a string
|
/// Write a Callbody (the rsx block) to a string
|
||||||
@@ -132,14 +143,7 @@ pub fn fmt_file(contents: &str, indent: IndentOptions) -> Vec<FormattedBlock> {
|
|||||||
/// If the tokens can't be formatted, this returns None. This is usually due to an incomplete expression
|
/// 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.
|
/// that passed partial expansion but failed to parse.
|
||||||
pub fn write_block_out(body: &CallBody) -> Option<String> {
|
pub fn write_block_out(body: &CallBody) -> Option<String> {
|
||||||
let mut buf = Writer::new("");
|
let mut buf = Writer::new("", IndentOptions::default());
|
||||||
buf.write_rsx_call(&body.body).ok()?;
|
|
||||||
buf.consume()
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
buf.write_rsx_call(&body.body).ok()?;
|
buf.write_rsx_call(&body.body).ok()?;
|
||||||
buf.consume()
|
buf.consume()
|
||||||
}
|
}
|
||||||
@@ -147,8 +151,7 @@ pub fn fmt_block_from_expr(raw: &str, expr: ExprMacro) -> Option<String> {
|
|||||||
pub fn fmt_block(block: &str, indent_level: usize, indent: IndentOptions) -> Option<String> {
|
pub fn fmt_block(block: &str, indent_level: usize, indent: IndentOptions) -> Option<String> {
|
||||||
let body = CallBody::parse_strict.parse_str(block).unwrap();
|
let body = CallBody::parse_strict.parse_str(block).unwrap();
|
||||||
|
|
||||||
let mut buf = Writer::new(block);
|
let mut buf = Writer::new(block, indent);
|
||||||
buf.out.indent = indent;
|
|
||||||
buf.out.indent_level = indent_level;
|
buf.out.indent_level = indent_level;
|
||||||
buf.write_rsx_call(&body.body).ok()?;
|
buf.write_rsx_call(&body.body).ok()?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,142 +1,141 @@
|
|||||||
use prettyplease::unparse;
|
use dioxus_rsx::CallBody;
|
||||||
use syn::{visit_mut::VisitMut, Expr, File, Item};
|
use syn::{parse::Parser, visit_mut::VisitMut, Expr, File, Item};
|
||||||
|
|
||||||
use crate::Writer;
|
use crate::{IndentOptions, Writer};
|
||||||
|
|
||||||
impl Writer<'_> {
|
impl Writer<'_> {
|
||||||
pub fn unparse_expr(&mut self, expr: &Expr) -> String {
|
pub fn unparse_expr(&mut self, expr: &Expr) -> String {
|
||||||
struct ReplaceMacros<'a, 'b> {
|
unparse_expr(expr, self.raw_src, &self.out.indent)
|
||||||
writer: &'a mut Writer<'b>,
|
}
|
||||||
formatted_stack: Vec<String>,
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl VisitMut for ReplaceMacros<'_, '_> {
|
const MARKER: &str = "dioxus_autofmt_block__________";
|
||||||
fn visit_stmt_mut(&mut self, _expr: &mut syn::Stmt) {
|
const MARKER_REPLACE: &str = "dioxus_autofmt_block__________! {}";
|
||||||
if let syn::Stmt::Macro(i) = _expr {
|
|
||||||
// replace the macro with a block that roughly matches the macro
|
|
||||||
if let Some("rsx" | "render") = i
|
|
||||||
.mac
|
|
||||||
.path
|
|
||||||
.segments
|
|
||||||
.last()
|
|
||||||
.map(|i| i.ident.to_string())
|
|
||||||
.as_deref()
|
|
||||||
{
|
|
||||||
// format the macro in place
|
|
||||||
// we'll use information about the macro to replace it with another formatted block
|
|
||||||
// once we've written out the unparsed expr from prettyplease, we can replace
|
|
||||||
// this dummy block with the actual formatted block
|
|
||||||
let formatted = crate::fmt_block_from_expr(
|
|
||||||
self.writer.raw_src,
|
|
||||||
syn::ExprMacro {
|
|
||||||
attrs: i.attrs.clone(),
|
|
||||||
mac: i.mac.clone(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
*_expr = syn::Stmt::Expr(
|
pub fn unparse_expr(expr: &Expr, src: &str, cfg: &IndentOptions) -> String {
|
||||||
syn::parse_quote!(dioxus_autofmt_block__________),
|
struct ReplaceMacros<'a> {
|
||||||
i.semi_token,
|
src: &'a str,
|
||||||
);
|
formatted_stack: Vec<String>,
|
||||||
|
cfg: &'a IndentOptions,
|
||||||
|
}
|
||||||
|
|
||||||
// Save this formatted block for later, when we apply it to the original expr
|
impl VisitMut for ReplaceMacros<'_> {
|
||||||
self.formatted_stack.push(formatted);
|
fn visit_macro_mut(&mut self, i: &mut syn::Macro) {
|
||||||
}
|
// replace the macro with a block that roughly matches the macro
|
||||||
|
if let Some("rsx" | "render") = i
|
||||||
|
.path
|
||||||
|
.segments
|
||||||
|
.last()
|
||||||
|
.map(|i| i.ident.to_string())
|
||||||
|
.as_deref()
|
||||||
|
{
|
||||||
|
// format the macro in place
|
||||||
|
// we'll use information about the macro to replace it with another formatted block
|
||||||
|
// once we've written out the unparsed expr from prettyplease, we can replace
|
||||||
|
// this dummy block with the actual formatted block
|
||||||
|
let body = CallBody::parse_strict.parse2(i.tokens.clone()).unwrap();
|
||||||
|
let multiline = !Writer::is_short_rsx_call(&body.body.roots);
|
||||||
|
let mut formatted = {
|
||||||
|
let mut writer = Writer::new(self.src, self.cfg.clone());
|
||||||
|
_ = writer.write_body_nodes(&body.body.roots).ok();
|
||||||
|
writer.consume()
|
||||||
|
}
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// always push out the rsx to require a new line
|
||||||
|
i.path = syn::parse_str(MARKER).unwrap();
|
||||||
|
i.tokens = Default::default();
|
||||||
|
|
||||||
|
// Push out the indent level of the formatted block if it's multiline
|
||||||
|
if multiline || formatted.contains('\n') {
|
||||||
|
formatted = formatted
|
||||||
|
.lines()
|
||||||
|
.map(|line| format!("{}{line}", self.cfg.indent_str()))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
syn::visit_mut::visit_stmt_mut(self, _expr);
|
// Save this formatted block for later, when we apply it to the original expr
|
||||||
|
self.formatted_stack.push(formatted)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_expr_mut(&mut self, _expr: &mut syn::Expr) {
|
syn::visit_mut::visit_macro_mut(self, i);
|
||||||
if let syn::Expr::Macro(i) = _expr {
|
}
|
||||||
// replace the macro with a block that roughly matches the macro
|
}
|
||||||
if let Some("rsx" | "render") = i
|
|
||||||
.mac
|
|
||||||
.path
|
|
||||||
.segments
|
|
||||||
.last()
|
|
||||||
.map(|i| i.ident.to_string())
|
|
||||||
.as_deref()
|
|
||||||
{
|
|
||||||
// format the macro in place
|
|
||||||
// we'll use information about the macro to replace it with another formatted block
|
|
||||||
// once we've written out the unparsed expr from prettyplease, we can replace
|
|
||||||
// this dummy block with the actual formatted block
|
|
||||||
let formatted = crate::fmt_block_from_expr(
|
|
||||||
self.writer.raw_src,
|
|
||||||
syn::ExprMacro {
|
|
||||||
attrs: i.attrs.clone(),
|
|
||||||
mac: i.mac.clone(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
*_expr = syn::parse_quote!(dioxus_autofmt_block__________);
|
// Visit the expr and replace the macros with formatted blocks
|
||||||
|
let mut replacer = ReplaceMacros {
|
||||||
|
src,
|
||||||
|
cfg,
|
||||||
|
formatted_stack: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
// Save this formatted block for later, when we apply it to the original expr
|
// builds the expression stack
|
||||||
self.formatted_stack.push(formatted);
|
let mut modified_expr = expr.clone();
|
||||||
}
|
replacer.visit_expr_mut(&mut modified_expr);
|
||||||
}
|
|
||||||
|
|
||||||
syn::visit_mut::visit_expr_mut(self, _expr);
|
// now unparsed with the modified expression
|
||||||
|
let mut unparsed = unparse_inner(&modified_expr);
|
||||||
|
|
||||||
|
// now we can replace the macros with the formatted blocks
|
||||||
|
for fmted in replacer.formatted_stack.drain(..) {
|
||||||
|
let is_multiline = fmted.contains('{');
|
||||||
|
|
||||||
|
let mut out_fmt = String::from("rsx! {");
|
||||||
|
if is_multiline {
|
||||||
|
out_fmt.push('\n');
|
||||||
|
} else {
|
||||||
|
out_fmt.push(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut whitespace = 0;
|
||||||
|
|
||||||
|
for line in unparsed.lines() {
|
||||||
|
if line.contains(MARKER) {
|
||||||
|
whitespace = line.matches(cfg.indent_str()).count();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visit the expr and replace the macros with formatted blocks
|
let mut lines = fmted.lines().enumerate().peekable();
|
||||||
let mut replacer = ReplaceMacros {
|
|
||||||
writer: self,
|
|
||||||
formatted_stack: vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
// builds the expression stack
|
while let Some((_idx, fmt_line)) = lines.next() {
|
||||||
let mut modified_expr = expr.clone();
|
// Push the indentation
|
||||||
replacer.visit_expr_mut(&mut modified_expr);
|
if is_multiline {
|
||||||
|
out_fmt.push_str(&cfg.indent_str().repeat(whitespace));
|
||||||
// now unparsed with the modified expression
|
|
||||||
let mut unparsed = unparse_expr(&modified_expr);
|
|
||||||
|
|
||||||
// walk each line looking for the dioxus_autofmt_block__________ token
|
|
||||||
// if we find it, replace it with the formatted block
|
|
||||||
// if there's indentation we want to presreve it
|
|
||||||
|
|
||||||
// now we can replace the macros with the formatted blocks
|
|
||||||
for formatted in replacer.formatted_stack.drain(..) {
|
|
||||||
let fmted = if formatted.contains('\n') {
|
|
||||||
format!("rsx! {{{formatted}\n}}")
|
|
||||||
} else {
|
|
||||||
format!("rsx! {{{formatted}}}")
|
|
||||||
};
|
|
||||||
let mut out_fmt = String::new();
|
|
||||||
let mut whitespace = 0;
|
|
||||||
|
|
||||||
for line in unparsed.lines() {
|
|
||||||
if line.contains("dioxus_autofmt_block__________") {
|
|
||||||
whitespace = line.chars().take_while(|c| c.is_whitespace()).count();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (idx, fmt_line) in fmted.lines().enumerate() {
|
// Calculate delta between indentations - the block indentation is too much
|
||||||
// Push the indentation
|
out_fmt.push_str(fmt_line);
|
||||||
if idx > 0 {
|
|
||||||
out_fmt.push_str(&" ".repeat(whitespace));
|
|
||||||
}
|
|
||||||
|
|
||||||
out_fmt.push_str(fmt_line);
|
// Push a newline if there's another line
|
||||||
|
if lines.peek().is_some() {
|
||||||
// Push a newline
|
|
||||||
out_fmt.push('\n');
|
out_fmt.push('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the last newline
|
|
||||||
out_fmt.pop();
|
|
||||||
|
|
||||||
// Replace the dioxus_autofmt_block__________ token with the formatted block
|
|
||||||
unparsed = unparsed.replacen("dioxus_autofmt_block__________", &out_fmt, 1);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if is_multiline {
|
||||||
|
out_fmt.push('\n');
|
||||||
|
out_fmt.push_str(&cfg.indent_str().repeat(whitespace));
|
||||||
|
} else {
|
||||||
|
out_fmt.push(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the dioxus_autofmt_block__________ token with the formatted block
|
||||||
|
out_fmt.push('}');
|
||||||
|
|
||||||
|
unparsed = unparsed.replacen(MARKER_REPLACE, &out_fmt, 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stylistic choice to trim whitespace around the expr
|
||||||
|
if unparsed.starts_with("{ ") && unparsed.ends_with(" }") {
|
||||||
|
let mut out_fmt = String::new();
|
||||||
|
out_fmt.push('{');
|
||||||
|
out_fmt.push_str(&unparsed[2..unparsed.len() - 2]);
|
||||||
|
out_fmt.push('}');
|
||||||
|
out_fmt
|
||||||
|
} else {
|
||||||
unparsed
|
unparsed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,9 +144,9 @@ impl Writer<'_> {
|
|||||||
///
|
///
|
||||||
/// This creates a new temporary file, parses the expression into it, and then formats the file.
|
/// This creates a new temporary file, parses the expression into it, and then formats the file.
|
||||||
/// This is a bit of a hack, but dtonlay doesn't want to support this very simple usecase, forcing us to clone the expr
|
/// This is a bit of a hack, but dtonlay doesn't want to support this very simple usecase, forcing us to clone the expr
|
||||||
pub fn unparse_expr(expr: &Expr) -> String {
|
pub fn unparse_inner(expr: &Expr) -> String {
|
||||||
let file = wrapped(expr);
|
let file = wrapped(expr);
|
||||||
let wrapped = unparse(&file);
|
let wrapped = prettyplease::unparse(&file);
|
||||||
unwrapped(wrapped)
|
unwrapped(wrapped)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +163,9 @@ fn unwrapped(raw: String) -> String {
|
|||||||
.join("\n");
|
.join("\n");
|
||||||
|
|
||||||
// remove the semicolon
|
// remove the semicolon
|
||||||
o.pop();
|
if o.ends_with(';') {
|
||||||
|
o.pop();
|
||||||
|
}
|
||||||
|
|
||||||
o
|
o
|
||||||
}
|
}
|
||||||
@@ -184,36 +185,202 @@ fn wrapped(expr: &Expr) -> File {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[cfg(test)]
|
||||||
fn unparses_raw() {
|
mod tests {
|
||||||
let expr = syn::parse_str("1 + 1").unwrap();
|
use super::*;
|
||||||
let unparsed = unparse(&wrapped(&expr));
|
use proc_macro2::TokenStream;
|
||||||
assert_eq!(unparsed, "fn main() {\n 1 + 1;\n}\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
fn fmt_block_from_expr(raw: &str, tokens: TokenStream, cfg: IndentOptions) -> Option<String> {
|
||||||
fn unparses_completely() {
|
let body = CallBody::parse_strict.parse2(tokens).unwrap();
|
||||||
let expr = syn::parse_str("1 + 1").unwrap();
|
let mut writer = Writer::new(raw, cfg);
|
||||||
let unparsed = unparse_expr(&expr);
|
writer.write_body_nodes(&body.body.roots).ok()?;
|
||||||
assert_eq!(unparsed, "1 + 1");
|
writer.consume()
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unparses_let_guard() {
|
|
||||||
let expr = syn::parse_str("let Some(url) = &link.location").unwrap();
|
|
||||||
let unparsed = unparse_expr(&expr);
|
|
||||||
assert_eq!(unparsed, "let Some(url) = &link.location");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn weird_ifcase() {
|
|
||||||
let contents = r##"
|
|
||||||
fn main() {
|
|
||||||
move |_| timer.with_mut(|t| if t.started_at.is_none() { Some(Instant::now()) } else { None })
|
|
||||||
}
|
}
|
||||||
"##;
|
|
||||||
|
|
||||||
let expr: File = syn::parse_file(contents).unwrap();
|
#[test]
|
||||||
let out = unparse(&expr);
|
fn unparses_raw() {
|
||||||
println!("{}", out);
|
let expr = syn::parse_str("1 + 1").expect("Failed to parse");
|
||||||
|
let unparsed = prettyplease::unparse(&wrapped(&expr));
|
||||||
|
assert_eq!(unparsed, "fn main() {\n 1 + 1;\n}\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn weird_ifcase() {
|
||||||
|
let contents = r##"
|
||||||
|
fn main() {
|
||||||
|
move |_| timer.with_mut(|t| if t.started_at.is_none() { Some(Instant::now()) } else { None })
|
||||||
|
}
|
||||||
|
"##;
|
||||||
|
|
||||||
|
let expr: File = syn::parse_file(contents).unwrap();
|
||||||
|
let out = prettyplease::unparse(&expr);
|
||||||
|
println!("{}", out);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiline_madness() {
|
||||||
|
let contents = r##"
|
||||||
|
{
|
||||||
|
{children.is_some().then(|| rsx! {
|
||||||
|
span {
|
||||||
|
class: "inline-block ml-auto hover:bg-gray-500",
|
||||||
|
onclick: move |evt| {
|
||||||
|
evt.cancel_bubble();
|
||||||
|
},
|
||||||
|
icons::icon_5 {}
|
||||||
|
{rsx! {
|
||||||
|
icons::icon_6 {}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
{children.is_some().then(|| rsx! {
|
||||||
|
span {
|
||||||
|
class: "inline-block ml-auto hover:bg-gray-500",
|
||||||
|
onclick: move |evt| {
|
||||||
|
evt.cancel_bubble();
|
||||||
|
},
|
||||||
|
icons::icon_10 {}
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
"##;
|
||||||
|
|
||||||
|
let expr: Expr = syn::parse_str(contents).unwrap();
|
||||||
|
let out = unparse_expr(&expr, contents, &IndentOptions::default());
|
||||||
|
println!("{}", out);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_body_no_indent() {
|
||||||
|
let src = r##"
|
||||||
|
span {
|
||||||
|
class: "inline-block ml-auto hover:bg-gray-500",
|
||||||
|
onclick: move |evt| {
|
||||||
|
evt.cancel_bubble();
|
||||||
|
},
|
||||||
|
icons::icon_10 {}
|
||||||
|
icons::icon_10 {}
|
||||||
|
icons::icon_10 {}
|
||||||
|
icons::icon_10 {}
|
||||||
|
div { "hi" }
|
||||||
|
div { div {} }
|
||||||
|
div { div {} div {} div {} }
|
||||||
|
{children}
|
||||||
|
{
|
||||||
|
some_big_long()
|
||||||
|
.some_big_long()
|
||||||
|
.some_big_long()
|
||||||
|
.some_big_long()
|
||||||
|
.some_big_long()
|
||||||
|
.some_big_long()
|
||||||
|
}
|
||||||
|
div { class: "px-4", {is_current.then(|| rsx! { {children} })} }
|
||||||
|
Thing {
|
||||||
|
field: rsx! {
|
||||||
|
div { "hi" }
|
||||||
|
Component {
|
||||||
|
onrender: rsx! {
|
||||||
|
div { "hi" }
|
||||||
|
Component {
|
||||||
|
onclick: move |_| {
|
||||||
|
another_macro! {
|
||||||
|
div { class: "max-w-lg lg:max-w-2xl mx-auto mb-16 text-center",
|
||||||
|
"gomg"
|
||||||
|
"hi!!"
|
||||||
|
"womh"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
rsx! {
|
||||||
|
div { class: "max-w-lg lg:max-w-2xl mx-auto mb-16 text-center",
|
||||||
|
"gomg"
|
||||||
|
"hi!!"
|
||||||
|
"womh"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
println!("hi")
|
||||||
|
},
|
||||||
|
onrender: move |_| {
|
||||||
|
let _ = 12;
|
||||||
|
let r = rsx! {
|
||||||
|
div { "hi" }
|
||||||
|
};
|
||||||
|
rsx! {
|
||||||
|
div { "hi" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
rsx! {
|
||||||
|
BarChart {
|
||||||
|
id: "bar-plot".to_string(),
|
||||||
|
x: value,
|
||||||
|
y: label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"##;
|
||||||
|
|
||||||
|
let tokens: TokenStream = syn::parse_str(src).unwrap();
|
||||||
|
let out = fmt_block_from_expr(src, tokens, IndentOptions::default()).unwrap();
|
||||||
|
println!("{}", out);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_component_body() {
|
||||||
|
let src = r##"
|
||||||
|
div { class: "px-4", {is_current.then(|| rsx! { {children} })} }
|
||||||
|
"##;
|
||||||
|
|
||||||
|
let tokens: TokenStream = syn::parse_str(src).unwrap();
|
||||||
|
let out = fmt_block_from_expr(src, tokens, IndentOptions::default()).unwrap();
|
||||||
|
println!("{}", out);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn weird_macro() {
|
||||||
|
let contents = r##"
|
||||||
|
fn main() {
|
||||||
|
move |_| {
|
||||||
|
drop_macro_semi! {
|
||||||
|
"something_very_long_something_very_long_something_very_long_something_very_long"
|
||||||
|
};
|
||||||
|
let _ = drop_macro_semi! {
|
||||||
|
"something_very_long_something_very_long_something_very_long_something_very_long"
|
||||||
|
};
|
||||||
|
drop_macro_semi! {
|
||||||
|
"something_very_long_something_very_long_something_very_long_something_very_long"
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"##;
|
||||||
|
|
||||||
|
let expr: File = syn::parse_file(contents).unwrap();
|
||||||
|
let out = prettyplease::unparse(&expr);
|
||||||
|
println!("{}", out);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn comments_on_nodes() {
|
||||||
|
let src = r##"// hiasdasds
|
||||||
|
div {
|
||||||
|
attr: "value", // comment
|
||||||
|
div {}
|
||||||
|
"hi" // hello!
|
||||||
|
"hi" // hello!
|
||||||
|
"hi" // hello!
|
||||||
|
// hi!
|
||||||
|
}
|
||||||
|
"##;
|
||||||
|
|
||||||
|
let tokens: TokenStream = syn::parse_str(src).unwrap();
|
||||||
|
let out = fmt_block_from_expr(src, tokens, IndentOptions::default()).unwrap();
|
||||||
|
println!("{}", out);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,444 +0,0 @@
|
|||||||
use crate::{prettier_please::unparse_expr, Writer};
|
|
||||||
use dioxus_rsx::*;
|
|
||||||
use proc_macro2::Span;
|
|
||||||
use quote::ToTokens;
|
|
||||||
use std::{
|
|
||||||
fmt::Result,
|
|
||||||
fmt::{self, Write},
|
|
||||||
};
|
|
||||||
use syn::{spanned::Spanned, token::Brace, Expr};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum ShortOptimization {
|
|
||||||
/// Special because we want to print the closing bracket immediately
|
|
||||||
///
|
|
||||||
/// IE
|
|
||||||
/// `div {}` instead of `div { }`
|
|
||||||
Empty,
|
|
||||||
|
|
||||||
/// Special optimization to put everything on the same line and add some buffer spaces
|
|
||||||
///
|
|
||||||
/// IE
|
|
||||||
///
|
|
||||||
/// `div { "asdasd" }` instead of a multiline variant
|
|
||||||
Oneliner,
|
|
||||||
|
|
||||||
/// Optimization where children flow but props remain fixed on top
|
|
||||||
PropsOnTop,
|
|
||||||
|
|
||||||
/// The noisiest optimization where everything flows
|
|
||||||
NoOpt,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Writer<'_> {
|
|
||||||
/// 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, 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();
|
|
||||||
|
|
||||||
// if we have one long attribute 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
|
|
||||||
// However if we have childrne we need to just spread them out for readability
|
|
||||||
if !is_short_attr_list && attributes.len() <= 1 && spreads.is_empty() {
|
|
||||||
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 {
|
|
||||||
if children_len.unwrap() + attr_len + self.out.indent_level * 4 < 100 {
|
|
||||||
opt_level = ShortOptimization::Oneliner;
|
|
||||||
} else {
|
|
||||||
opt_level = ShortOptimization::PropsOnTop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there's nothing at all, empty optimization
|
|
||||||
if attributes.is_empty() && children.is_empty() && spreads.is_empty() {
|
|
||||||
opt_level = ShortOptimization::Empty;
|
|
||||||
|
|
||||||
// Write comments if they exist
|
|
||||||
self.write_todo_body(brace)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// multiline handlers bump everything down
|
|
||||||
if attr_len > 1000 || self.out.indent.split_line_attributes() {
|
|
||||||
opt_level = ShortOptimization::NoOpt;
|
|
||||||
}
|
|
||||||
|
|
||||||
let has_children = !children.is_empty();
|
|
||||||
|
|
||||||
match opt_level {
|
|
||||||
ShortOptimization::Empty => {}
|
|
||||||
ShortOptimization::Oneliner => {
|
|
||||||
write!(self.out, " ")?;
|
|
||||||
|
|
||||||
self.write_attributes(attributes, spreads, true, brace, has_children)?;
|
|
||||||
|
|
||||||
if !children.is_empty() && !attributes.is_empty() {
|
|
||||||
write!(self.out, " ")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
for child in children.iter() {
|
|
||||||
self.write_ident(child)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(self.out, " ")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShortOptimization::PropsOnTop => {
|
|
||||||
if !attributes.is_empty() {
|
|
||||||
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(attributes, spreads, false, brace, has_children)?;
|
|
||||||
|
|
||||||
if !children.is_empty() {
|
|
||||||
self.write_body_indented(children)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.out.tabbed_line()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_attributes(
|
|
||||||
&mut self,
|
|
||||||
attributes: &[Attribute],
|
|
||||||
spreads: &[Spread],
|
|
||||||
props_same_line: bool,
|
|
||||||
brace: &Brace,
|
|
||||||
has_children: bool,
|
|
||||||
) -> Result {
|
|
||||||
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 !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 !props_same_line {
|
|
||||||
self.out.indented_tabbed_line()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 props_same_line {
|
|
||||||
write!(self.out, " ")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let has_attributes = !attributes.is_empty() || !spreads.is_empty();
|
|
||||||
|
|
||||||
if has_attributes && has_children {
|
|
||||||
write!(self.out, ",")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
AttributeValue::IfExpr(if_chain) => {
|
|
||||||
self.write_attribute_if_chain(if_chain)?;
|
|
||||||
}
|
|
||||||
AttributeValue::AttrLiteral(value) => {
|
|
||||||
write!(self.out, "{value}")?;
|
|
||||||
}
|
|
||||||
AttributeValue::Shorthand(value) => {
|
|
||||||
write!(self.out, "{value}")?;
|
|
||||||
}
|
|
||||||
AttributeValue::EventTokens(closure) => {
|
|
||||||
self.write_partial_closure(closure)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
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_attribute_if_chain(&mut self, if_chain: &IfAttributeValue) -> Result {
|
|
||||||
write!(self.out, "if {} {{ ", unparse_expr(&if_chain.condition))?;
|
|
||||||
self.write_attribute_value(&if_chain.then_value)?;
|
|
||||||
write!(self.out, " }}")?;
|
|
||||||
match if_chain.else_value.as_deref() {
|
|
||||||
Some(AttributeValue::IfExpr(else_if_chain)) => {
|
|
||||||
write!(self.out, "else ")?;
|
|
||||||
self.write_attribute_if_chain(else_if_chain)?;
|
|
||||||
}
|
|
||||||
Some(other) => {
|
|
||||||
write!(self.out, "else {{")?;
|
|
||||||
self.write_attribute_value(other)?;
|
|
||||||
write!(self.out, " }}")?;
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
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, "")?;
|
|
||||||
} else {
|
|
||||||
writeln!(self.out)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the comments are actually relevant to this element.
|
|
||||||
// test by making sure this element is the primary element on this line
|
|
||||||
pub fn current_span_is_primary(&self, location: Span) -> bool {
|
|
||||||
let start = location.start();
|
|
||||||
let line_start = start.line - 1;
|
|
||||||
|
|
||||||
let beginning = self
|
|
||||||
.src
|
|
||||||
.get(line_start)
|
|
||||||
.filter(|this_line| this_line.len() > start.column)
|
|
||||||
.map(|this_line| this_line[..start.column].trim())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
beginning.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// returns the total line length if it's short
|
|
||||||
// returns none if the length exceeds the limit
|
|
||||||
// I think this eventually becomes quadratic :(
|
|
||||||
pub fn is_short_children(&mut self, children: &[BodyNode]) -> Option<usize> {
|
|
||||||
if children.is_empty() {
|
|
||||||
// todo: allow elements with comments but no children
|
|
||||||
// like div { /* comment */ }
|
|
||||||
// or
|
|
||||||
// div {
|
|
||||||
// // some helpful
|
|
||||||
// }
|
|
||||||
return Some(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Any comments push us over the limit automatically
|
|
||||||
if self.children_have_comments(children) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
match children {
|
|
||||||
[BodyNode::Text(ref text)] => Some(text.input.to_string_with_quotes().len()),
|
|
||||||
|
|
||||||
// TODO: let rawexprs to be inlined
|
|
||||||
[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(
|
|
||||||
comp.name
|
|
||||||
.segments
|
|
||||||
.iter()
|
|
||||||
.map(|s| s.ident.to_string().len() + 2)
|
|
||||||
.sum::<usize>(),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Feedback on discord indicates folks don't like combining multiple children on the same line
|
|
||||||
// We used to do a lot of math to figure out if we should expand out the line, but folks just
|
|
||||||
// don't like it.
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn children_have_comments(&self, children: &[BodyNode]) -> bool {
|
|
||||||
for child in children {
|
|
||||||
if self.current_span_is_primary(child.span()) {
|
|
||||||
'line: for line in self.src[..child.span().start().line - 1].iter().rev() {
|
|
||||||
match (line.trim().starts_with("//"), line.is_empty()) {
|
|
||||||
(true, _) => return true,
|
|
||||||
(_, true) => continue 'line,
|
|
||||||
_ => break 'line,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// empty everything except for some comments
|
|
||||||
fn write_todo_body(&mut self, brace: &Brace) -> fmt::Result {
|
|
||||||
let span = brace.span.span();
|
|
||||||
let start = span.start();
|
|
||||||
let end = span.end();
|
|
||||||
|
|
||||||
if start.line == end.line {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
writeln!(self.out)?;
|
|
||||||
|
|
||||||
for idx in start.line..end.line {
|
|
||||||
let line = &self.src[idx];
|
|
||||||
if line.trim().starts_with("//") {
|
|
||||||
for _ in 0..self.out.indent_level + 1 {
|
|
||||||
write!(self.out, " ")?
|
|
||||||
}
|
|
||||||
writeln!(self.out, "{}", line.trim()).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _ in 0..self.out.indent_level {
|
|
||||||
write!(self.out, " ")?
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_expr_length(span: Span) -> usize {
|
|
||||||
let (start, end) = (span.start(), span.end());
|
|
||||||
if start.line == end.line {
|
|
||||||
end.column - start.column
|
|
||||||
} else {
|
|
||||||
10000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+766
-270
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,21 @@
|
|||||||
|
#[test]
|
||||||
|
fn no_parse() {
|
||||||
|
let src = include_str!("./partials/no_parse.rsx");
|
||||||
|
assert!(syn::parse_file(src).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_but_fmt_fails() {
|
||||||
|
let src = include_str!("./partials/wrong.rsx");
|
||||||
|
let file = syn::parse_file(src).unwrap();
|
||||||
|
let formatted = dioxus_autofmt::try_fmt_file(src, &file, Default::default());
|
||||||
|
assert!(&formatted.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_and_is_okay() {
|
||||||
|
let src = include_str!("./partials/okay.rsx");
|
||||||
|
let file = syn::parse_file(src).unwrap();
|
||||||
|
let formatted = dioxus_autofmt::try_fmt_file(src, &file, Default::default()).unwrap();
|
||||||
|
assert_ne!(formatted.len(), 0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#[component]
|
||||||
|
fn SidebarSection() -> Element {
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
{ .doesnt_work) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#[component]
|
||||||
|
fn SidebarSection() -> Element {
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
onclick: move |_| {
|
||||||
|
works()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#[component]
|
||||||
|
fn SidebarSection() -> Element {
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
onclick: move |_| {
|
||||||
|
.doesnt_work()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![allow(deprecated)]
|
||||||
|
|
||||||
macro_rules! twoway {
|
macro_rules! twoway {
|
||||||
(
|
(
|
||||||
$(
|
$(
|
||||||
@@ -22,35 +24,38 @@ macro_rules! twoway {
|
|||||||
)*
|
)*
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
twoway![
|
twoway![
|
||||||
attributes,
|
attributes,
|
||||||
|
basic_expr,
|
||||||
collapse_expr,
|
collapse_expr,
|
||||||
comments,
|
comments,
|
||||||
commentshard,
|
commentshard,
|
||||||
complex,
|
complex,
|
||||||
|
docsite,
|
||||||
emoji,
|
emoji,
|
||||||
|
fat_exprs,
|
||||||
ifchain_forloop,
|
ifchain_forloop,
|
||||||
immediate_expr,
|
immediate_expr,
|
||||||
key,
|
key,
|
||||||
|
letsome,
|
||||||
long_exprs,
|
long_exprs,
|
||||||
long,
|
long,
|
||||||
manual_props,
|
manual_props,
|
||||||
|
many_exprs,
|
||||||
messy_indent,
|
messy_indent,
|
||||||
|
misplaced,
|
||||||
multirsx,
|
multirsx,
|
||||||
|
nested,
|
||||||
raw_strings,
|
raw_strings,
|
||||||
reallylong,
|
reallylong,
|
||||||
|
shorthand,
|
||||||
simple,
|
simple,
|
||||||
|
skip,
|
||||||
|
spaces,
|
||||||
|
staged,
|
||||||
t2,
|
t2,
|
||||||
tiny,
|
tiny,
|
||||||
tinynoopt,
|
tinynoopt,
|
||||||
trailing_expr,
|
trailing_expr,
|
||||||
many_exprs,
|
oneline
|
||||||
shorthand,
|
|
||||||
docsite,
|
|
||||||
letsome,
|
|
||||||
fat_exprs,
|
|
||||||
nested,
|
|
||||||
staged,
|
|
||||||
misplaced
|
|
||||||
];
|
];
|
||||||
@@ -30,7 +30,7 @@ rsx! {
|
|||||||
a: "123",
|
a: "123",
|
||||||
a: "123",
|
a: "123",
|
||||||
a: "123",
|
a: "123",
|
||||||
a: "123"
|
a: "123",
|
||||||
}
|
}
|
||||||
|
|
||||||
div {
|
div {
|
||||||
@@ -42,6 +42,6 @@ rsx! {
|
|||||||
a: "123",
|
a: "123",
|
||||||
a: "123",
|
a: "123",
|
||||||
a: "123",
|
a: "123",
|
||||||
a: "123"
|
a: "123",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fn itworks() {
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
"hi"
|
||||||
|
{children}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,4 +33,30 @@ rsx! {
|
|||||||
class: "asd",
|
class: "asd",
|
||||||
"Jon"
|
"Jon"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// comments inline
|
||||||
|
div { // inline
|
||||||
|
// Collapse
|
||||||
|
class: "asd", // super inline
|
||||||
|
class: "asd", // super inline
|
||||||
|
"Jon" // all the inline
|
||||||
|
// Comments at the end too
|
||||||
|
}
|
||||||
|
|
||||||
|
// please dont eat me 1
|
||||||
|
div { // please dont eat me 2
|
||||||
|
// please dont eat me 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// please dont eat me 1
|
||||||
|
div { // please dont eat me 2
|
||||||
|
// please dont eat me 3
|
||||||
|
abc: 123,
|
||||||
|
}
|
||||||
|
|
||||||
|
// please dont eat me 1
|
||||||
|
div {
|
||||||
|
// please dont eat me 3
|
||||||
|
abc: 123,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,7 @@ rsx! {
|
|||||||
class: "hello world",
|
class: "hello world",
|
||||||
|
|
||||||
// todo some work in here
|
// todo some work in here
|
||||||
class: "hello world"
|
class: "hello world",
|
||||||
}
|
}
|
||||||
|
|
||||||
div {
|
div {
|
||||||
|
|||||||
@@ -22,14 +22,17 @@ rsx! {
|
|||||||
span {
|
span {
|
||||||
class: "inline-block ml-auto hover:bg-gray-500",
|
class: "inline-block ml-auto hover:bg-gray-500",
|
||||||
onclick: move |evt| {
|
onclick: move |evt| {
|
||||||
// open.set(!open.get());
|
|
||||||
evt.cancel_bubble();
|
evt.cancel_bubble();
|
||||||
},
|
},
|
||||||
icons::icon_8 {}
|
icons::icon_8 {}
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
}
|
}
|
||||||
div { class: "px-4", {is_current.then(|| rsx!{ children })} }
|
div { class: "px-4",
|
||||||
|
{is_current.then(|| rsx! {
|
||||||
|
{children}
|
||||||
|
})}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No nesting
|
// No nesting
|
||||||
@@ -37,7 +40,7 @@ rsx! {
|
|||||||
adsasd: "asd",
|
adsasd: "asd",
|
||||||
onclick: move |_| {
|
onclick: move |_| {
|
||||||
let blah = 120;
|
let blah = 120;
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Component path
|
// Component path
|
||||||
@@ -45,7 +48,7 @@ rsx! {
|
|||||||
adsasd: "asd",
|
adsasd: "asd",
|
||||||
onclick: move |_| {
|
onclick: move |_| {
|
||||||
let blah = 120;
|
let blah = 120;
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in 0..10 {
|
for i in 0..10 {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ pub(crate) fn Nav() -> Element {
|
|||||||
MaterialIcon {
|
MaterialIcon {
|
||||||
name: "menu",
|
name: "menu",
|
||||||
size: 24,
|
size: 24,
|
||||||
color: MaterialIconColor::Dark
|
color: MaterialIconColor::Dark,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div { class: "flex z-50 md:flex-1 px-2", LinkList {} }
|
div { class: "flex z-50 md:flex-1 px-2", LinkList {} }
|
||||||
@@ -59,7 +59,7 @@ pub(crate) fn Nav() -> Element {
|
|||||||
Link { to: Route::Homepage {},
|
Link { to: Route::Homepage {},
|
||||||
img {
|
img {
|
||||||
src: "https://avatars.githubusercontent.com/u/10237910?s=40&v=4",
|
src: "https://avatars.githubusercontent.com/u/10237910?s=40&v=4",
|
||||||
class: "ml-4 h-10 rounded-full w-auto"
|
class: "ml-4 h-10 rounded-full w-auto",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,4 +24,15 @@ rsx! {
|
|||||||
} else {
|
} else {
|
||||||
h3 {}
|
h3 {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: "asdasd",
|
||||||
|
class: if expr { "asdasd" } else { "asdasd" },
|
||||||
|
class: if expr { "asdasd" },
|
||||||
|
class: if expr { "asdasd" } else if expr { "asdasd" } else { "asdasd" },
|
||||||
|
|
||||||
|
// comments?
|
||||||
|
class: if expr { "asdasd" } else if expr { "asdasd" } else { "asdasd" }, // comments!!?
|
||||||
|
// comments?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,9 @@ rsx! {
|
|||||||
section { class: "body-font overflow-hidden dark:bg-ideblack",
|
section { class: "body-font overflow-hidden dark:bg-ideblack",
|
||||||
div { class: "container px-6 mx-auto",
|
div { class: "container px-6 mx-auto",
|
||||||
div { class: "-my-8 divide-y-2 divide-gray-100",
|
div { class: "-my-8 divide-y-2 divide-gray-100",
|
||||||
{POSTS.iter().enumerate().map(|(id, post)| rsx! { BlogPostItem { post: post, id: id } })}
|
{POSTS.iter().enumerate().map(|(id, post)| rsx! {
|
||||||
|
BlogPostItem { post, id }
|
||||||
|
})}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ rsx! {
|
|||||||
Component {
|
Component {
|
||||||
asdasd: "asdasd",
|
asdasd: "asdasd",
|
||||||
asdasd: "asdasdasdasdasdasdasdasdasdasd",
|
asdasd: "asdasdasdasdasdasdasdasdasdasd",
|
||||||
..Props { a: 10, b: 20 }
|
..Props { a: 10, b: 20 },
|
||||||
}
|
}
|
||||||
Component {
|
Component {
|
||||||
asdasd: "asdasd",
|
asdasd: "asdasd",
|
||||||
|
|||||||
@@ -103,12 +103,21 @@ fn app() -> Element {
|
|||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
{
|
{
|
||||||
let millis = timer.with(|t| t.duration().saturating_sub(t.started_at.map(|x| x.elapsed()).unwrap_or(Duration::ZERO)).as_millis());
|
let millis = timer
|
||||||
format!("{:02}:{:02}:{:02}.{:01}",
|
.with(|t| {
|
||||||
millis / 1000 / 3600 % 3600,
|
t.duration()
|
||||||
millis / 1000 / 60 % 60,
|
.saturating_sub(
|
||||||
millis / 1000 % 60,
|
t.started_at.map(|x| x.elapsed()).unwrap_or(Duration::ZERO),
|
||||||
millis / 100 % 10)
|
)
|
||||||
|
.as_millis()
|
||||||
|
});
|
||||||
|
format!(
|
||||||
|
"{:02}:{:02}:{:02}.{:01}",
|
||||||
|
millis / 1000 / 3600 % 3600,
|
||||||
|
millis / 1000 / 60 % 60,
|
||||||
|
millis / 1000 % 60,
|
||||||
|
millis / 100 % 10,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div {
|
div {
|
||||||
@@ -119,7 +128,7 @@ fn app() -> Element {
|
|||||||
value: format!("{:02}", timer.read().hours),
|
value: format!("{:02}", timer.read().hours),
|
||||||
oninput: move |e| {
|
oninput: move |e| {
|
||||||
timer.write().hours = e.value().parse().unwrap_or(0);
|
timer.write().hours = e.value().parse().unwrap_or(0);
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
@@ -129,7 +138,7 @@ fn app() -> Element {
|
|||||||
value: format!("{:02}", timer.read().minutes),
|
value: format!("{:02}", timer.read().minutes),
|
||||||
oninput: move |e| {
|
oninput: move |e| {
|
||||||
timer.write().minutes = e.value().parse().unwrap_or(0);
|
timer.write().minutes = e.value().parse().unwrap_or(0);
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
@@ -139,7 +148,7 @@ fn app() -> Element {
|
|||||||
value: format!("{:02}", timer.read().seconds),
|
value: format!("{:02}", timer.read().seconds),
|
||||||
oninput: move |e| {
|
oninput: move |e| {
|
||||||
timer.write().seconds = e.value().parse().unwrap_or(0);
|
timer.write().seconds = e.value().parse().unwrap_or(0);
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +164,7 @@ fn app() -> Element {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{ timer.with(|t| if t.started_at.is_none() { "Start" } else { "Stop" }) }
|
{timer.with(|t| if t.started_at.is_none() { "Start" } else { "Stop" })}
|
||||||
}
|
}
|
||||||
div { id: "app",
|
div { id: "app",
|
||||||
button {
|
button {
|
||||||
@@ -165,7 +174,10 @@ fn app() -> Element {
|
|||||||
window_preferences.write().with_decorations = !decorations;
|
window_preferences.write().with_decorations = !decorations;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
format!("with decorations{}", if window_preferences.read().with_decorations { " ✓" } else { "" }).to_string()
|
format!(
|
||||||
|
"with decorations{}",
|
||||||
|
if window_preferences.read().with_decorations { " ✓" } else { "" },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
@@ -178,16 +190,28 @@ fn app() -> Element {
|
|||||||
},
|
},
|
||||||
width: 100,
|
width: 100,
|
||||||
{
|
{
|
||||||
format!("always on top{}", if window_preferences.read().always_on_top { " ✓" } else { "" })
|
format!(
|
||||||
|
"always on top{}",
|
||||||
|
if window_preferences.read().always_on_top { " ✓" } else { "" },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
exit_button(
|
exit_button(
|
||||||
Duration::from_secs(3),
|
Duration::from_secs(3),
|
||||||
|trigger, delay| rsx! {
|
|trigger, delay| {
|
||||||
{format!("{:0.1?}", trigger.read().map(|inst| (delay.as_secs_f32() - inst.elapsed().as_secs_f32()))) }
|
rsx! {
|
||||||
}
|
{
|
||||||
|
format!(
|
||||||
|
"{:0.1?}",
|
||||||
|
trigger
|
||||||
|
.read()
|
||||||
|
.map(|inst| (delay.as_secs_f32() - inst.elapsed().as_secs_f32())),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ pub(crate) fn Nav() -> Element {
|
|||||||
MaterialIcon {
|
MaterialIcon {
|
||||||
name: "menu",
|
name: "menu",
|
||||||
size: 24,
|
size: 24,
|
||||||
color: MaterialIconColor::Dark
|
color: MaterialIconColor::Dark,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div { class: "flex z-50 md:flex-1 px-2", LinkList {} }
|
div { class: "flex z-50 md:flex-1 px-2", LinkList {} }
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ fn App() -> Element {
|
|||||||
"hi!!"
|
"hi!!"
|
||||||
"womh"
|
"womh"
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
println!("hi")
|
println!("hi")
|
||||||
},
|
},
|
||||||
"hi"
|
"hi"
|
||||||
@@ -83,7 +83,7 @@ fn App() -> Element {
|
|||||||
"so22mething nested?"
|
"so22mething nested?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -93,11 +93,11 @@ fn App() -> Element {
|
|||||||
"something nested?"
|
"something nested?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onrender: move |_| {
|
onrender: move |_| {
|
||||||
@@ -121,12 +121,14 @@ fn App() -> Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{rsx! {
|
{
|
||||||
div2 {
|
rsx! {
|
||||||
h12 { "hi" }
|
div2 {
|
||||||
"so22mething nested?"
|
h12 { "hi" }
|
||||||
|
"so22mething nested?"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -138,7 +140,7 @@ fn App() -> Element {
|
|||||||
"something nested?"
|
"something nested?"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
rsx! { "hello world" }
|
||||||
@@ -12,6 +12,6 @@ rsx! {
|
|||||||
width: r#"10px"#,
|
width: r#"10px"#,
|
||||||
height: r##"{10}px"##,
|
height: r##"{10}px"##,
|
||||||
"raw-attr": r###"raw-attr"###,
|
"raw-attr": r###"raw-attr"###,
|
||||||
"raw-attr2": r###"{100}"###
|
"raw-attr2": r###"{100}"###,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,7 @@ rsx! {
|
|||||||
id: "{a}",
|
id: "{a}",
|
||||||
class: "ban",
|
class: "ban",
|
||||||
style: "color: red",
|
style: "color: red",
|
||||||
value: "{b}"
|
value: "{b}",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nested one level
|
// Nested one level
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/// dont format this component
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[component]
|
||||||
|
fn SidebarSection() -> Element {
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
"hi" div {} div {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// dont format this component
|
||||||
|
#[component]
|
||||||
|
fn SidebarSection() -> Element {
|
||||||
|
// format this
|
||||||
|
rsx! {
|
||||||
|
div { "hi" }
|
||||||
|
}
|
||||||
|
|
||||||
|
// and this
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
"hi"
|
||||||
|
div {}
|
||||||
|
div {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// but not this
|
||||||
|
#[rustfmt::skip]
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
"hi" div {} div {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
rsx! {
|
||||||
|
if let Some(Some(record)) = &*records.read_unchecked() {
|
||||||
|
{
|
||||||
|
let (label, value): (Vec<String>, Vec<f64>) = record
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.map(|d| (d.model.clone().expect("work"), d.row_total))
|
||||||
|
.collect();
|
||||||
|
rsx! {
|
||||||
|
BarChart { id: "bar-plot".to_string(), x: value, y: label }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,18 +9,20 @@ rsx! {
|
|||||||
|
|
||||||
div { {some_expr} }
|
div { {some_expr} }
|
||||||
div {
|
div {
|
||||||
{
|
{POSTS.iter().enumerate().map(|(id, post)| rsx! {
|
||||||
POSTS.iter().enumerate().map(|(id, post)| rsx! {
|
BlogPostItem { post, id }
|
||||||
BlogPostItem { post, id }
|
})}
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div { class: "123123123123123123123123123123123123",
|
div { class: "123123123123123123123123123123123123",
|
||||||
{some_really_long_expr_some_really_long_expr_some_really_long_expr_some_really_long_expr_}
|
{
|
||||||
|
some_really_long_expr_some_really_long_expr_some_really_long_expr_some_really_long_expr_
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div { class: "-my-8 divide-y-2 divide-gray-100",
|
div { class: "-my-8 divide-y-2 divide-gray-100",
|
||||||
{POSTS.iter().enumerate().map(|(id, post)| rsx! { BlogPostItem { post: post, id: id } })}
|
{POSTS.iter().enumerate().map(|(id, post)| rsx! {
|
||||||
|
BlogPostItem { post, id }
|
||||||
|
})}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![allow(deprecated)]
|
||||||
|
|
||||||
use dioxus_autofmt::{IndentOptions, IndentType};
|
use dioxus_autofmt::{IndentOptions, IndentType};
|
||||||
|
|
||||||
macro_rules! twoway {
|
macro_rules! twoway {
|
||||||
@@ -6,7 +8,12 @@ macro_rules! twoway {
|
|||||||
fn $name() {
|
fn $name() {
|
||||||
let src_right = include_str!(concat!("./wrong/", $val, ".rsx"));
|
let src_right = include_str!(concat!("./wrong/", $val, ".rsx"));
|
||||||
let src_wrong = include_str!(concat!("./wrong/", $val, ".wrong.rsx"));
|
let src_wrong = include_str!(concat!("./wrong/", $val, ".wrong.rsx"));
|
||||||
let formatted = dioxus_autofmt::fmt_file(src_wrong, $indent);
|
|
||||||
|
let parsed = syn::parse_file(src_wrong)
|
||||||
|
.expect("fmt_file should only be called on valid syn::File files");
|
||||||
|
|
||||||
|
let formatted =
|
||||||
|
dioxus_autofmt::try_fmt_file(src_wrong, &parsed, $indent).unwrap_or_default();
|
||||||
let out = dioxus_autofmt::apply_formats(src_wrong, formatted);
|
let out = dioxus_autofmt::apply_formats(src_wrong, formatted);
|
||||||
|
|
||||||
// normalize line endings
|
// normalize line endings
|
||||||
@@ -31,3 +38,4 @@ twoway!("simple-combo-expr" => simple_combo_expr (IndentOptions::new(IndentType:
|
|||||||
twoway!("oneline-expand" => online_expand (IndentOptions::new(IndentType::Spaces, 4, false)));
|
twoway!("oneline-expand" => online_expand (IndentOptions::new(IndentType::Spaces, 4, false)));
|
||||||
twoway!("shortened" => shortened (IndentOptions::new(IndentType::Spaces, 4, false)));
|
twoway!("shortened" => shortened (IndentOptions::new(IndentType::Spaces, 4, false)));
|
||||||
twoway!("syntax_error" => syntax_error (IndentOptions::new(IndentType::Spaces, 4, false)));
|
twoway!("syntax_error" => syntax_error (IndentOptions::new(IndentType::Spaces, 4, false)));
|
||||||
|
twoway!("skipfail" => skipfail (IndentOptions::new(IndentType::Spaces, 4, false)));
|
||||||
@@ -103,12 +103,22 @@ fn app() -> Element {
|
|||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
{
|
{
|
||||||
let millis = timer.with(|t| t.duration().saturating_sub(t.started_at.map(|x| x.elapsed()).unwrap_or(Duration::ZERO)).as_millis());
|
let millis = timer
|
||||||
format!("{:02}:{:02}:{:02}.{:01}",
|
.with(|t| {
|
||||||
millis / 1000 / 3600 % 3600,
|
t
|
||||||
millis / 1000 / 60 % 60,
|
.duration()
|
||||||
millis / 1000 % 60,
|
.saturating_sub(
|
||||||
millis / 100 % 10)
|
t.started_at.map(|x| x.elapsed()).unwrap_or(Duration::ZERO),
|
||||||
|
)
|
||||||
|
.as_millis()
|
||||||
|
});
|
||||||
|
format!(
|
||||||
|
"{:02}:{:02}:{:02}.{:01}",
|
||||||
|
millis / 1000 / 3600 % 3600,
|
||||||
|
millis / 1000 / 60 % 60,
|
||||||
|
millis / 1000 % 60,
|
||||||
|
millis / 100 % 10,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div {
|
div {
|
||||||
@@ -119,7 +129,7 @@ fn app() -> Element {
|
|||||||
value: format!("{:02}", timer.read().hours),
|
value: format!("{:02}", timer.read().hours),
|
||||||
oninput: move |e| {
|
oninput: move |e| {
|
||||||
timer.write().hours = e.value().parse().unwrap_or(0);
|
timer.write().hours = e.value().parse().unwrap_or(0);
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
@@ -129,7 +139,7 @@ fn app() -> Element {
|
|||||||
value: format!("{:02}", timer.read().minutes),
|
value: format!("{:02}", timer.read().minutes),
|
||||||
oninput: move |e| {
|
oninput: move |e| {
|
||||||
timer.write().minutes = e.value().parse().unwrap_or(0);
|
timer.write().minutes = e.value().parse().unwrap_or(0);
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
@@ -139,7 +149,7 @@ fn app() -> Element {
|
|||||||
value: format!("{:02}", timer.read().seconds),
|
value: format!("{:02}", timer.read().seconds),
|
||||||
oninput: move |e| {
|
oninput: move |e| {
|
||||||
timer.write().seconds = e.value().parse().unwrap_or(0);
|
timer.write().seconds = e.value().parse().unwrap_or(0);
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +165,7 @@ fn app() -> Element {
|
|||||||
};
|
};
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{ timer.with(|t| if t.started_at.is_none() { "Start" } else { "Stop" }) }
|
{timer.with(|t| if t.started_at.is_none() { "Start" } else { "Stop" })}
|
||||||
}
|
}
|
||||||
div { id: "app",
|
div { id: "app",
|
||||||
button {
|
button {
|
||||||
@@ -165,7 +175,11 @@ fn app() -> Element {
|
|||||||
window_preferences.write().with_decorations = !decorations;
|
window_preferences.write().with_decorations = !decorations;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
format!("with decorations{}", if window_preferences.read().with_decorations { " ✓" } else { "" }).to_string()
|
format!(
|
||||||
|
"with decorations{}",
|
||||||
|
if window_preferences.read().with_decorations { " ✓" } else { "" },
|
||||||
|
)
|
||||||
|
.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
@@ -178,7 +192,10 @@ fn app() -> Element {
|
|||||||
},
|
},
|
||||||
width: 100,
|
width: 100,
|
||||||
{
|
{
|
||||||
format!("always on top{}", if window_preferences.read().always_on_top { " ✓" } else { "" })
|
format!(
|
||||||
|
"always on top{}",
|
||||||
|
if window_preferences.read().always_on_top { " ✓" } else { "" },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,8 +203,15 @@ fn app() -> Element {
|
|||||||
exit_button(
|
exit_button(
|
||||||
Duration::from_secs(3),
|
Duration::from_secs(3),
|
||||||
|trigger, delay| rsx! {
|
|trigger, delay| rsx! {
|
||||||
{format!("{:0.1?}", trigger.read().map(|inst| (delay.as_secs_f32() - inst.elapsed().as_secs_f32()))) }
|
{
|
||||||
}
|
format!(
|
||||||
|
"{:0.1?}",
|
||||||
|
trigger
|
||||||
|
.read()
|
||||||
|
.map(|inst| (delay.as_secs_f32() - inst.elapsed().as_secs_f32())),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ fn main() {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,12 +2,22 @@ fn main() {
|
|||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
{
|
{
|
||||||
let millis = timer.with(|t| t.duration().saturating_sub(t.started_at.map(|x| x.elapsed()).unwrap_or(Duration::ZERO)).as_millis());
|
let millis = timer
|
||||||
format!("{:02}:{:02}:{:02}.{:01}",
|
.with(|t| {
|
||||||
millis / 1000 / 3600 % 3600,
|
t
|
||||||
millis / 1000 / 60 % 60,
|
.duration()
|
||||||
millis / 1000 % 60,
|
.saturating_sub(
|
||||||
millis / 100 % 10)
|
t.started_at.map(|x| x.elapsed()).unwrap_or(Duration::ZERO),
|
||||||
|
)
|
||||||
|
.as_millis()
|
||||||
|
});
|
||||||
|
format!(
|
||||||
|
"{:02}:{:02}:{:02}.{:01}",
|
||||||
|
millis / 1000 / 3600 % 3600,
|
||||||
|
millis / 1000 / 60 % 60,
|
||||||
|
millis / 1000 % 60,
|
||||||
|
millis / 100 % 10,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div {
|
div {
|
||||||
@@ -18,7 +28,7 @@ fn main() {
|
|||||||
value: format!("{:02}", timer.read().hours),
|
value: format!("{:02}", timer.read().hours),
|
||||||
oninput: move |e| {
|
oninput: move |e| {
|
||||||
timer.write().hours = e.value().parse().unwrap_or(0);
|
timer.write().hours = e.value().parse().unwrap_or(0);
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
// some comment
|
// some comment
|
||||||
input {
|
input {
|
||||||
@@ -28,7 +38,7 @@ fn main() {
|
|||||||
value: format!("{:02}", timer.read().hours),
|
value: format!("{:02}", timer.read().hours),
|
||||||
oninput: move |e| {
|
oninput: move |e| {
|
||||||
timer.write().hours = e.value().parse().unwrap_or(0);
|
timer.write().hours = e.value().parse().unwrap_or(0);
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/// dont format this component
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[component]
|
||||||
|
fn SidebarSection() -> Element {
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
"hi" div {} div {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// dont format this component
|
||||||
|
#[component]
|
||||||
|
fn SidebarSection() -> Element {
|
||||||
|
// format this
|
||||||
|
rsx! {
|
||||||
|
div { "hi" }
|
||||||
|
}
|
||||||
|
|
||||||
|
// and this
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
"hi"
|
||||||
|
div {}
|
||||||
|
div {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// but not this
|
||||||
|
#[rustfmt::skip]
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
"hi" div {} div {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/// dont format this component
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[component]
|
||||||
|
fn SidebarSection() -> Element {
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
"hi" div {} div {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// dont format this component
|
||||||
|
#[component]
|
||||||
|
fn SidebarSection() -> Element {
|
||||||
|
// format this
|
||||||
|
rsx! {
|
||||||
|
div { "hi" }
|
||||||
|
}
|
||||||
|
|
||||||
|
// and this
|
||||||
|
rsx! {
|
||||||
|
div { "hi" div {} div {} }
|
||||||
|
}
|
||||||
|
|
||||||
|
// but not this
|
||||||
|
#[rustfmt::skip]
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
"hi" div {} div {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -115,7 +115,13 @@ fn refactor_file(
|
|||||||
s = format_rust(&s)?;
|
s = format_rust(&s)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let edits = dioxus_autofmt::fmt_file(&s, indent);
|
let Ok(Ok(edits)) =
|
||||||
|
syn::parse_file(&s).map(|file| dioxus_autofmt::try_fmt_file(&s, &file, indent))
|
||||||
|
else {
|
||||||
|
eprintln!("failed to format file: {}", s);
|
||||||
|
exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
let out = dioxus_autofmt::apply_formats(&s, edits);
|
let out = dioxus_autofmt::apply_formats(&s, edits);
|
||||||
|
|
||||||
if file == "-" {
|
if file == "-" {
|
||||||
@@ -159,7 +165,10 @@ fn format_file(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let edits = dioxus_autofmt::fmt_file(&contents, indent);
|
let parsed = syn::parse_file(&contents)
|
||||||
|
.map_err(|err| Error::ParseError(format!("Failed to parse file: {}", err)))?;
|
||||||
|
let edits = dioxus_autofmt::try_fmt_file(&contents, &parsed, indent)
|
||||||
|
.map_err(|err| Error::ParseError(format!("Failed to format file: {}", err)))?;
|
||||||
let len = edits.len();
|
let len = edits.len();
|
||||||
|
|
||||||
if !edits.is_empty() {
|
if !edits.is_empty() {
|
||||||
@@ -313,29 +322,3 @@ async fn test_auto_fmt() {
|
|||||||
|
|
||||||
fmt.autoformat().unwrap();
|
fmt.autoformat().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*#[test]
|
|
||||||
fn spawn_properly() {
|
|
||||||
let out = Command::new("dioxus")
|
|
||||||
.args([
|
|
||||||
"fmt",
|
|
||||||
"-f",
|
|
||||||
r#"
|
|
||||||
//
|
|
||||||
|
|
||||||
rsx! {
|
|
||||||
|
|
||||||
div {}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
"#,
|
|
||||||
])
|
|
||||||
.output()
|
|
||||||
.expect("failed to execute process");
|
|
||||||
|
|
||||||
dbg!(out);
|
|
||||||
}*/
|
|
||||||
@@ -11,6 +11,7 @@ wasm-bindgen = { workspace = true }
|
|||||||
dioxus-autofmt = { workspace = true }
|
dioxus-autofmt = { workspace = true }
|
||||||
rsx-rosetta = { workspace = true }
|
rsx-rosetta = { workspace = true }
|
||||||
html_parser = { workspace = true }
|
html_parser = { workspace = true }
|
||||||
|
syn ={ workspace = true }
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
@@ -65,18 +65,26 @@ impl FormatBlockInstance {
|
|||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn format_file(contents: String, use_tabs: bool, indent_size: usize) -> FormatBlockInstance {
|
pub fn format_file(contents: String, use_tabs: bool, indent_size: usize) -> FormatBlockInstance {
|
||||||
let _edits = dioxus_autofmt::fmt_file(
|
// todo: use rustfmt for this instead
|
||||||
&contents,
|
let options = IndentOptions::new(
|
||||||
IndentOptions::new(
|
if use_tabs {
|
||||||
if use_tabs {
|
IndentType::Tabs
|
||||||
IndentType::Tabs
|
} else {
|
||||||
} else {
|
IndentType::Spaces
|
||||||
IndentType::Spaces
|
},
|
||||||
},
|
indent_size,
|
||||||
indent_size,
|
false,
|
||||||
false,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let Ok(Ok(_edits)) = syn::parse_file(&contents)
|
||||||
|
.map(|file| dioxus_autofmt::try_fmt_file(&contents, &file, options))
|
||||||
|
else {
|
||||||
|
return FormatBlockInstance {
|
||||||
|
new: contents,
|
||||||
|
_edits: Vec::new(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
let out = dioxus_autofmt::apply_formats(&contents, _edits.clone());
|
let out = dioxus_autofmt::apply_formats(&contents, _edits.clone());
|
||||||
FormatBlockInstance { new: out, _edits }
|
FormatBlockInstance { new: out, _edits }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,9 +65,9 @@ function fmtSelection() {
|
|||||||
|
|
||||||
// Select full lines of selection
|
// Select full lines of selection
|
||||||
let selection_range = new vscode.Range(
|
let selection_range = new vscode.Range(
|
||||||
editor.selection.start.line,
|
editor.selection.start.line,
|
||||||
0,
|
0,
|
||||||
end_line,
|
end_line,
|
||||||
editor.document.lineAt(end_line).range.end.character
|
editor.document.lineAt(end_line).range.end.character
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -83,9 +83,9 @@ function fmtSelection() {
|
|||||||
end_line += 1;
|
end_line += 1;
|
||||||
|
|
||||||
selection_range = new vscode.Range(
|
selection_range = new vscode.Range(
|
||||||
editor.selection.start.line,
|
editor.selection.start.line,
|
||||||
0,
|
0,
|
||||||
end_line,
|
end_line,
|
||||||
editor.document.lineAt(end_line).range.end.character
|
editor.document.lineAt(end_line).range.end.character
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -103,8 +103,8 @@ function fmtSelection() {
|
|||||||
|
|
||||||
let lines_above = editor.document.getText(
|
let lines_above = editor.document.getText(
|
||||||
new vscode.Range(
|
new vscode.Range(
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
end_above,
|
end_above,
|
||||||
editor.document.lineAt(end_above).range.end.character
|
editor.document.lineAt(end_above).range.end.character
|
||||||
)
|
)
|
||||||
@@ -115,7 +115,7 @@ function fmtSelection() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
let formatted = dioxus.format_selection(unformatted, !editor.options.insertSpaces, tabSize, base_indentation);
|
let formatted = dioxus.format_selection(unformatted, !editor.options.insertSpaces, tabSize, base_indentation);
|
||||||
for(let i = 0; i <= base_indentation; i++) {
|
for (let i = 0; i <= base_indentation; i++) {
|
||||||
formatted = (editor.options.insertSpaces ? " ".repeat(tabSize) : "\t") + formatted;
|
formatted = (editor.options.insertSpaces ? " ".repeat(tabSize) : "\t") + formatted;
|
||||||
}
|
}
|
||||||
if (formatted.length > 0) {
|
if (formatted.length > 0) {
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ pub fn collect_svgs(children: &mut [BodyNode], out: &mut Vec<BodyNode>) {
|
|||||||
diagnostics: Default::default(),
|
diagnostics: Default::default(),
|
||||||
fields: vec![],
|
fields: vec![],
|
||||||
children: TemplateBody::new(vec![]),
|
children: TemplateBody::new(vec![]),
|
||||||
brace: Default::default(),
|
brace: Some(Default::default()),
|
||||||
dyn_idx: Default::default(),
|
dyn_idx: Default::default(),
|
||||||
component_literal_dyn_idx: vec![],
|
component_literal_dyn_idx: vec![],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ pub struct Component {
|
|||||||
pub fields: Vec<Attribute>,
|
pub fields: Vec<Attribute>,
|
||||||
pub component_literal_dyn_idx: Vec<DynIdx>,
|
pub component_literal_dyn_idx: Vec<DynIdx>,
|
||||||
pub spreads: Vec<Spread>,
|
pub spreads: Vec<Spread>,
|
||||||
pub brace: token::Brace,
|
pub brace: Option<token::Brace>,
|
||||||
pub children: TemplateBody,
|
pub children: TemplateBody,
|
||||||
pub dyn_idx: DynIdx,
|
pub dyn_idx: DynIdx,
|
||||||
pub diagnostics: Diagnostics,
|
pub diagnostics: Diagnostics,
|
||||||
@@ -69,8 +69,8 @@ impl Parse for Component {
|
|||||||
name,
|
name,
|
||||||
generics,
|
generics,
|
||||||
fields,
|
fields,
|
||||||
|
brace: Some(brace),
|
||||||
component_literal_dyn_idx,
|
component_literal_dyn_idx,
|
||||||
brace,
|
|
||||||
spreads,
|
spreads,
|
||||||
diagnostics,
|
diagnostics,
|
||||||
};
|
};
|
||||||
@@ -308,7 +308,7 @@ impl Component {
|
|||||||
Component {
|
Component {
|
||||||
name,
|
name,
|
||||||
generics,
|
generics,
|
||||||
brace: token::Brace::default(),
|
brace: None,
|
||||||
fields: vec![],
|
fields: vec![],
|
||||||
spreads: vec![],
|
spreads: vec![],
|
||||||
children: TemplateBody::new(vec![]),
|
children: TemplateBody::new(vec![]),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use location::DynIdx;
|
use location::DynIdx;
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use syn::{braced, Expr, Pat};
|
use syn::{braced, token::Brace, Expr, Pat};
|
||||||
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||||
@@ -10,6 +10,7 @@ pub struct ForLoop {
|
|||||||
pub pat: Pat,
|
pub pat: Pat,
|
||||||
pub in_token: Token![in],
|
pub in_token: Token![in],
|
||||||
pub expr: Box<Expr>,
|
pub expr: Box<Expr>,
|
||||||
|
pub brace: Brace,
|
||||||
pub body: TemplateBody,
|
pub body: TemplateBody,
|
||||||
pub dyn_idx: DynIdx,
|
pub dyn_idx: DynIdx,
|
||||||
}
|
}
|
||||||
@@ -24,13 +25,14 @@ impl Parse for ForLoop {
|
|||||||
let expr = input.call(Expr::parse_without_eager_brace)?;
|
let expr = input.call(Expr::parse_without_eager_brace)?;
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
let _brace = braced!(content in input);
|
let brace = braced!(content in input);
|
||||||
let body = content.parse()?;
|
let body = content.parse()?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
for_token,
|
for_token,
|
||||||
pat,
|
pat,
|
||||||
in_token,
|
in_token,
|
||||||
|
brace,
|
||||||
expr: Box::new(expr),
|
expr: Box::new(expr),
|
||||||
body,
|
body,
|
||||||
dyn_idx: DynIdx::default(),
|
dyn_idx: DynIdx::default(),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use quote::quote;
|
|||||||
use quote::{ToTokens, TokenStreamExt};
|
use quote::{ToTokens, TokenStreamExt};
|
||||||
use syn::{
|
use syn::{
|
||||||
parse::{Parse, ParseStream},
|
parse::{Parse, ParseStream},
|
||||||
|
token::Brace,
|
||||||
Expr, Result, Token,
|
Expr, Result, Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -14,8 +15,10 @@ use crate::TemplateBody;
|
|||||||
pub struct IfChain {
|
pub struct IfChain {
|
||||||
pub if_token: Token![if],
|
pub if_token: Token![if],
|
||||||
pub cond: Box<Expr>,
|
pub cond: Box<Expr>,
|
||||||
|
pub then_brace: Brace,
|
||||||
pub then_branch: TemplateBody,
|
pub then_branch: TemplateBody,
|
||||||
pub else_if_branch: Option<Box<IfChain>>,
|
pub else_if_branch: Option<Box<IfChain>>,
|
||||||
|
pub else_brace: Option<Brace>,
|
||||||
pub else_branch: Option<TemplateBody>,
|
pub else_branch: Option<TemplateBody>,
|
||||||
pub dyn_idx: DynIdx,
|
pub dyn_idx: DynIdx,
|
||||||
}
|
}
|
||||||
@@ -42,10 +45,11 @@ impl Parse for IfChain {
|
|||||||
let cond = Box::new(input.call(Expr::parse_without_eager_brace)?);
|
let cond = Box::new(input.call(Expr::parse_without_eager_brace)?);
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
syn::braced!(content in input);
|
let then_brace = syn::braced!(content in input);
|
||||||
|
|
||||||
let then_branch = content.parse()?;
|
let then_branch = content.parse()?;
|
||||||
|
|
||||||
|
let mut else_brace = None;
|
||||||
let mut else_branch = None;
|
let mut else_branch = None;
|
||||||
let mut else_if_branch = None;
|
let mut else_if_branch = None;
|
||||||
|
|
||||||
@@ -56,7 +60,7 @@ impl Parse for IfChain {
|
|||||||
else_if_branch = Some(Box::new(input.parse::<IfChain>()?));
|
else_if_branch = Some(Box::new(input.parse::<IfChain>()?));
|
||||||
} else {
|
} else {
|
||||||
let content;
|
let content;
|
||||||
syn::braced!(content in input);
|
else_brace = Some(syn::braced!(content in input));
|
||||||
else_branch = Some(content.parse()?);
|
else_branch = Some(content.parse()?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,6 +71,8 @@ impl Parse for IfChain {
|
|||||||
then_branch,
|
then_branch,
|
||||||
else_if_branch,
|
else_if_branch,
|
||||||
else_branch,
|
else_branch,
|
||||||
|
then_brace,
|
||||||
|
else_brace,
|
||||||
dyn_idx: DynIdx::default(),
|
dyn_idx: DynIdx::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,12 +17,6 @@ pub struct IfmtInput {
|
|||||||
pub segments: Vec<Segment>,
|
pub segments: Vec<Segment>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for IfmtInput {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new(Span::call_site())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IfmtInput {
|
impl IfmtInput {
|
||||||
pub fn new(span: Span) -> Self {
|
pub fn new(span: Span) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -213,7 +207,7 @@ impl ToTokens for IfmtInput {
|
|||||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
// If the input is a string literal, we can just return it
|
// If the input is a string literal, we can just return it
|
||||||
if let Some(static_str) = self.to_static() {
|
if let Some(static_str) = self.to_static() {
|
||||||
return static_str.to_tokens(tokens);
|
return quote_spanned! { self.span() => #static_str }.to_tokens(tokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to turn it into a single _.to_string() call
|
// Try to turn it into a single _.to_string() call
|
||||||
|
|||||||
@@ -151,6 +151,14 @@ pub struct HotReloadFormattedSegment {
|
|||||||
pub dynamic_node_indexes: Vec<DynIdx>,
|
pub dynamic_node_indexes: Vec<DynIdx>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HotReloadFormattedSegment {
|
||||||
|
/// This method is very important!
|
||||||
|
/// Deref + Spanned + .span() methods leads to name collisions
|
||||||
|
pub fn span(&self) -> Span {
|
||||||
|
self.formatted_input.span()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Deref for HotReloadFormattedSegment {
|
impl Deref for HotReloadFormattedSegment {
|
||||||
type Target = IfmtInput;
|
type Target = IfmtInput;
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user