From d8942a255ba7a901e2581ea1248727647269e5c0 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 18 Mar 2024 18:23:45 -0700 Subject: [PATCH] Fix #2104: fmt incorrectly using 1-indexing for columns (#2106) * Fix #2104: fmt incorrectly using 1-indexing for columns * Clippy... --- Cargo.lock | 14 +- Cargo.toml | 10 +- packages/autofmt/src/component.rs | 12 +- packages/autofmt/src/element.rs | 19 +- packages/autofmt/src/expr.rs | 3 +- packages/autofmt/src/lib.rs | 1 + packages/autofmt/src/prettier_please.rs | 66 ++++++ packages/autofmt/src/writer.rs | 29 ++- packages/autofmt/tests/samples.rs | 1 + packages/autofmt/tests/samples/many_exprs.rsx | 195 ++++++++++++++++++ packages/autofmt/tests/wrong.rs | 3 + .../autofmt/tests/wrong/multiexpr-many.rsx | 195 ++++++++++++++++++ .../tests/wrong/multiexpr-many.wrong.rsx | 164 +++++++++++++++ .../autofmt/tests/wrong/oneline-expand.rsx | 18 ++ .../tests/wrong/oneline-expand.wrong.rsx | 8 + .../autofmt/tests/wrong/simple-combo-expr.rsx | 35 ++++ .../tests/wrong/simple-combo-expr.wrong.rsx | 23 +++ 17 files changed, 756 insertions(+), 40 deletions(-) create mode 100644 packages/autofmt/src/prettier_please.rs create mode 100644 packages/autofmt/tests/samples/many_exprs.rsx create mode 100644 packages/autofmt/tests/wrong/multiexpr-many.rsx create mode 100644 packages/autofmt/tests/wrong/multiexpr-many.wrong.rsx create mode 100644 packages/autofmt/tests/wrong/oneline-expand.rsx create mode 100644 packages/autofmt/tests/wrong/oneline-expand.wrong.rsx create mode 100644 packages/autofmt/tests/wrong/simple-combo-expr.rsx create mode 100644 packages/autofmt/tests/wrong/simple-combo-expr.wrong.rsx diff --git a/Cargo.lock b/Cargo.lock index 3eb5fd6d1..32855b311 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1982,8 +1982,8 @@ name = "dioxus-autofmt" version = "0.5.0-alpha.2" dependencies = [ "dioxus-rsx", - "prettier-please", "pretty_assertions", + "prettyplease", "proc-macro2", "quote", "serde", @@ -2045,7 +2045,7 @@ dependencies = [ "mlua", "notify", "open", - "prettier-please", + "prettyplease", "rayon", "reqwest", "rsx-rosetta", @@ -6670,16 +6670,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" -[[package]] -name = "prettier-please" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22020dfcf177fcc7bf5deaf7440af371400c67c0de14c399938d8ed4fb4645d3" -dependencies = [ - "proc-macro2", - "syn 2.0.52", -] - [[package]] name = "pretty_assertions" version = "1.4.0" diff --git a/Cargo.toml b/Cargo.toml index 2061f005a..9c91f99a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,16 +89,10 @@ rustc-hash = "1.1.0" wasm-bindgen = "0.2.92" html_parser = "0.7.0" thiserror = "1.0.40" -prettyplease = { package = "prettier-please", version = "0.2", features = [ - "verbatim", -] } -manganis-cli-support = { version = "0.2.1", features = [ - "html", -] } +prettyplease = { version = "0.2.16", features = ["verbatim"] } +manganis-cli-support = { version = "0.2.1", features = ["html"] } manganis = { version = "0.2.1" } - interprocess = { version = "1.2.1" } -# interprocess = { git = "https://github.com/kotauskas/interprocess" } lru = "0.12.2" async-trait = "0.1.77" diff --git a/packages/autofmt/src/component.rs b/packages/autofmt/src/component.rs index c83f9b333..26cfb9b42 100644 --- a/packages/autofmt/src/component.rs +++ b/packages/autofmt/src/component.rs @@ -1,4 +1,4 @@ -use crate::{ifmt_to_string, writer::Location, Writer}; +use crate::{ifmt_to_string, prettier_please::unparse_expr, writer::Location, Writer}; use dioxus_rsx::*; use quote::ToTokens; use std::fmt::{Result, Write}; @@ -164,7 +164,7 @@ impl Writer<'_> { let name = &field.name; match &field.content { ContentField::ManExpr(exp) => { - let out = prettyplease::unparse_expr(exp); + let out = unparse_expr(exp); let mut lines = out.split('\n').peekable(); let first = lines.next().unwrap(); write!(self.out, "{name}: {first}")?; @@ -186,7 +186,7 @@ impl Writer<'_> { write!(self.out, "{}", e.to_token_stream())?; } ContentField::OnHandlerRaw(exp) => { - let out = prettyplease::unparse_expr(exp); + let out = unparse_expr(exp); let mut lines = out.split('\n').peekable(); let first = lines.next().unwrap(); write!(self.out, "{name}: {first}")?; @@ -228,7 +228,7 @@ impl Writer<'_> { ContentField::Formatted(s) => ifmt_to_string(s).len() , ContentField::Shorthand(e) => e.to_token_stream().to_string().len(), ContentField::OnHandlerRaw(exp) | ContentField::ManExpr(exp) => { - let formatted = prettyplease::unparse_expr(exp); + let formatted = unparse_expr(exp); let len = if formatted.contains('\n') { 10000 } else { @@ -242,7 +242,7 @@ impl Writer<'_> { match manual_props { Some(p) => { - let content = prettyplease::unparse_expr(p); + let content = unparse_expr(p); if content.len() + attr_len > 80 { return 100000; } @@ -264,7 +264,7 @@ impl Writer<'_> { We want to normalize the expr to the appropriate indent level. */ - let formatted = prettyplease::unparse_expr(exp); + let formatted = unparse_expr(exp); let mut lines = formatted.lines(); diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index d386f8b72..12f75dcb2 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -1,4 +1,4 @@ -use crate::{ifmt_to_string, Writer}; +use crate::{ifmt_to_string, prettier_please::unparse_expr, Writer}; use dioxus_rsx::*; use proc_macro2::Span; use quote::ToTokens; @@ -112,7 +112,7 @@ impl Writer<'_> { ShortOptimization::Oneliner => { write!(self.out, " ")?; - self.write_attributes(attributes, key, true)?; + self.write_attributes(brace, attributes, key, true)?; if !children.is_empty() && (!attributes.is_empty() || key.is_some()) { write!(self.out, ", ")?; @@ -132,7 +132,7 @@ impl Writer<'_> { if !attributes.is_empty() || key.is_some() { write!(self.out, " ")?; } - self.write_attributes(attributes, key, true)?; + self.write_attributes(brace, attributes, key, true)?; if !children.is_empty() && (!attributes.is_empty() || key.is_some()) { write!(self.out, ",")?; @@ -145,7 +145,7 @@ impl Writer<'_> { } ShortOptimization::NoOpt => { - self.write_attributes(attributes, key, false)?; + self.write_attributes(brace, attributes, key, false)?; if !children.is_empty() && (!attributes.is_empty() || key.is_some()) { write!(self.out, ",")?; @@ -166,6 +166,7 @@ impl Writer<'_> { fn write_attributes( &mut self, + brace: &Brace, attributes: &[AttributeType], key: &Option, sameline: bool, @@ -187,9 +188,11 @@ impl Writer<'_> { while let Some(attr) = attr_iter.next() { self.out.indent_level += 1; + if !sameline { - self.write_comments(attr.start())?; + self.write_attr_comments(brace, attr.start())?; } + self.out.indent_level -= 1; if !sameline { @@ -229,7 +232,7 @@ impl Writer<'_> { write!( self.out, "if {condition} {{ ", - condition = prettyplease::unparse_expr(condition), + condition = unparse_expr(condition), )?; self.write_attribute_value(value)?; write!(self.out, " }}")?; @@ -241,7 +244,7 @@ impl Writer<'_> { write!(self.out, "{value}",)?; } ElementAttrValue::AttrExpr(value) => { - let out = prettyplease::unparse_expr(value); + let out = unparse_expr(value); let mut lines = out.split('\n').peekable(); let first = lines.next().unwrap(); @@ -308,7 +311,7 @@ impl Writer<'_> { fn write_spread_attribute(&mut self, attr: &Expr) -> Result { write!(self.out, "..")?; - write!(self.out, "{}", prettyplease::unparse_expr(attr))?; + write!(self.out, "{}", unparse_expr(attr))?; Ok(()) } diff --git a/packages/autofmt/src/expr.rs b/packages/autofmt/src/expr.rs index b7a93a4fc..75bd62715 100644 --- a/packages/autofmt/src/expr.rs +++ b/packages/autofmt/src/expr.rs @@ -27,12 +27,13 @@ impl Writer<'_> { // 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 - 1..].trim_start())?; + 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 { diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs index e40b873d6..f17a72d18 100644 --- a/packages/autofmt/src/lib.rs +++ b/packages/autofmt/src/lib.rs @@ -17,6 +17,7 @@ mod component; mod element; mod expr; mod indent; +mod prettier_please; mod writer; pub use indent::{IndentOptions, IndentType}; diff --git a/packages/autofmt/src/prettier_please.rs b/packages/autofmt/src/prettier_please.rs new file mode 100644 index 000000000..c6b21edc2 --- /dev/null +++ b/packages/autofmt/src/prettier_please.rs @@ -0,0 +1,66 @@ +use prettyplease::unparse; +use syn::{Expr, File, Item}; + +/// Unparse an expression back into a string +/// +/// 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 +pub fn unparse_expr(expr: &Expr) -> String { + let file = wrapped(expr); + let wrapped = unparse(&file); + unwrapped(wrapped) +} + +// Split off the fn main and then cut the tabs off the front +fn unwrapped(raw: String) -> String { + raw.strip_prefix("fn main() {\n") + .unwrap() + .strip_suffix("}\n") + .unwrap() + .lines() + .map(|line| line.strip_prefix(" ").unwrap()) // todo: set this to tab level + .collect::>() + .join("\n") +} + +fn wrapped(expr: &Expr) -> File { + File { + shebang: None, + attrs: vec![], + items: vec![ + // + Item::Verbatim(quote::quote! { + fn main() { + #expr + } + }), + ], + } +} + +#[test] +fn unparses_raw() { + let expr = syn::parse_str("1 + 1").unwrap(); + let unparsed = unparse(&wrapped(&expr)); + assert_eq!(unparsed, "fn main() {\n 1 + 1\n}\n"); +} + +#[test] +fn unparses_completely() { + let expr = syn::parse_str("1 + 1").unwrap(); + let unparsed = unparse_expr(&expr); + assert_eq!(unparsed, "1 + 1"); +} + +#[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 = unparse(&expr); + println!("{}", out); +} diff --git a/packages/autofmt/src/writer.rs b/packages/autofmt/src/writer.rs index be375aa9d..79d153779 100644 --- a/packages/autofmt/src/writer.rs +++ b/packages/autofmt/src/writer.rs @@ -1,3 +1,4 @@ +use crate::prettier_please::unparse_expr; use dioxus_rsx::{AttributeType, BodyNode, ElementAttrValue, ForLoop, IfChain}; use proc_macro2::{LineColumn, Span}; use quote::ToTokens; @@ -5,7 +6,7 @@ use std::{ collections::{HashMap, VecDeque}, fmt::{Result, Write}, }; -use syn::{spanned::Spanned, Expr}; +use syn::{spanned::Spanned, token::Brace, Expr}; use crate::buffer::Buffer; use crate::ifmt_to_string; @@ -61,8 +62,26 @@ impl<'a> Writer<'a> { Some(self.out.buf) } + pub fn write_attr_comments(&mut self, brace: &Brace, attr_span: Span) -> Result { + // There's a chance this line actually shares the same line as the previous + // Only write comments if the comments actually belong to this line + // + // to do this, we check if the attr span starts on the same line as the brace + // if it doesn't, we write the comments + let brace_line = brace.span.span().start().line; + let attr_line = attr_span.start().line; + + if brace_line != attr_line { + self.write_comments(attr_span)?; + } + + Ok(()) + } + pub fn write_comments(&mut self, child: Span) -> Result { // collect all comments upwards + // make sure we don't collect the comments of the node that we're currently under. + let start = child.start(); let line_start = start.line - 1; @@ -149,7 +168,7 @@ impl<'a> Writer<'a> { let len = if let std::collections::hash_map::Entry::Vacant(e) = self.cached_formats.entry(location) { - let formatted = prettyplease::unparse_expr(tokens); + let formatted = unparse_expr(tokens); let len = if formatted.contains('\n') { 10000 } else { @@ -207,7 +226,7 @@ impl<'a> Writer<'a> { pub fn retrieve_formatted_expr(&mut self, expr: &Expr) -> &str { self.cached_formats .entry(Location::new(expr.span().start())) - .or_insert_with(|| prettyplease::unparse_expr(expr)) + .or_insert_with(|| unparse_expr(expr)) .as_str() } @@ -216,7 +235,7 @@ impl<'a> Writer<'a> { self.out, "for {} in {} {{", forloop.pat.clone().into_token_stream(), - prettyplease::unparse_expr(&forloop.expr) + unparse_expr(&forloop.expr) )?; if forloop.body.is_empty() { @@ -249,7 +268,7 @@ impl<'a> Writer<'a> { self.out, "{} {} {{", if_token.to_token_stream(), - prettyplease::unparse_expr(cond) + unparse_expr(cond) )?; self.write_body_indented(then_branch)?; diff --git a/packages/autofmt/tests/samples.rs b/packages/autofmt/tests/samples.rs index 9431d700e..fda3531aa 100644 --- a/packages/autofmt/tests/samples.rs +++ b/packages/autofmt/tests/samples.rs @@ -45,4 +45,5 @@ twoway![ tiny, tinynoopt, trailing_expr, + many_exprs, ]; diff --git a/packages/autofmt/tests/samples/many_exprs.rsx b/packages/autofmt/tests/samples/many_exprs.rsx new file mode 100644 index 000000000..e4e724801 --- /dev/null +++ b/packages/autofmt/tests/samples/many_exprs.rsx @@ -0,0 +1,195 @@ +#![allow(dead_code, unused)] +use dioxus::desktop::use_window; +use dioxus::prelude::*; +use std::{ + process::exit, + time::{Duration, Instant}, +}; +use tokio::time::sleep; + +fn main() { + LaunchBuilder::desktop().launch(app); +} + +struct WindowPreferences { + always_on_top: bool, + with_decorations: bool, + exiting: Option, +} + +impl Default for WindowPreferences { + fn default() -> Self { + Self { + with_decorations: true, + always_on_top: false, + exiting: None, + } + } +} + +impl WindowPreferences { + fn new() -> Self { + Self::default() + } +} + +#[derive(Default)] +struct Timer { + hours: u8, + minutes: u8, + seconds: u8, + started_at: Option, +} + +impl Timer { + fn new() -> Self { + Self::default() + } + + fn duration(&self) -> Duration { + Duration::from_secs( + (self.hours as u64 * 60 + self.minutes as u64) * 60 + self.seconds as u64, + ) + } +} + +const UPD_FREQ: Duration = Duration::from_millis(100); + +fn exit_button( + delay: Duration, + label: fn(Signal>, Duration) -> Option, +) -> Element { + let mut trigger: Signal> = use_signal(|| None); + use_future(move || async move { + loop { + sleep(UPD_FREQ).await; + if let Some(true) = trigger.read().map(|e| e.elapsed() > delay) { + exit(0); + } + } + }); + let stuff: Option = rsx! { + button { + onmouseup: move |_| { + trigger.set(None); + }, + onmousedown: move |_| { + trigger.set(Some(Instant::now())); + }, + width: 100, + {label(trigger, delay)} + } + }; + stuff +} + +fn app() -> Element { + let mut timer = use_signal(Timer::new); + let mut window_preferences = use_signal(WindowPreferences::new); + + use_future(move || async move { + loop { + sleep(UPD_FREQ).await; + timer.with_mut(|t| { + if let Some(started_at) = t.started_at { + if t.duration().saturating_sub(started_at.elapsed()) == Duration::ZERO { + t.started_at = None; + } + } + }); + } + }); + + rsx! { + div { + { + let millis = timer.with(|t| t.duration().saturating_sub(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 { + input { + r#type: "number", + min: 0, + max: 99, + value: format!("{:02}", timer.read().hours), + oninput: move |e| { + timer.write().hours = e.value().parse().unwrap_or(0); + } + } + + input { + r#type: "number", + min: 0, + max: 59, + value: format!("{:02}", timer.read().minutes), + oninput: move |e| { + timer.write().minutes = e.value().parse().unwrap_or(0); + } + } + + input { + r#type: "number", + min: 0, + max: 59, + value: format!("{:02}", timer.read().seconds), + oninput: move |e| { + timer.write().seconds = e.value().parse().unwrap_or(0); + } + } + } + + button { + id: "start_stop", + onclick: move |_| { + timer + .with_mut(|t| { + t + .started_at = if t.started_at.is_none() { + Some(Instant::now()) + } else { + None + } + }) + }, + { timer.with(|t| if t.started_at.is_none() { "Start" } else { "Stop" }) } + } + div { id: "app", + button { + onclick: move |_| { + let decorations = window_preferences.read().with_decorations; + use_window().set_decorations(!decorations); + window_preferences.write().with_decorations = !decorations; + }, + { + format!("with decorations{}", if window_preferences.read().with_decorations { " ✓" } else { "" }).to_string() + } + } + button { + onclick: move |_| { + window_preferences + .with_mut(|wp| { + use_window().set_always_on_top(!wp.always_on_top); + wp.always_on_top = !wp.always_on_top; + }) + }, + width: 100, + { + format!("always on top{}", if window_preferences.read().always_on_top { " ✓" } else { "" }) + } + } + } + { + exit_button( + Duration::from_secs(3), + |trigger, delay| rsx! { + {format!("{:0.1?}", trigger.read().map(|inst| (delay.as_secs_f32() - inst.elapsed().as_secs_f32()))) } + } + ) + } + } +} diff --git a/packages/autofmt/tests/wrong.rs b/packages/autofmt/tests/wrong.rs index 8f2d08452..abecc2228 100644 --- a/packages/autofmt/tests/wrong.rs +++ b/packages/autofmt/tests/wrong.rs @@ -26,3 +26,6 @@ twoway!("multi-tab" => multi_tab (IndentOptions::new(IndentType::Tabs, 4, false) twoway!("multiexpr-4sp" => multiexpr_4sp (IndentOptions::new(IndentType::Spaces, 4, false))); twoway!("multiexpr-tab" => multiexpr_tab (IndentOptions::new(IndentType::Tabs, 4, false))); +twoway!("multiexpr-many" => multiexpr_many (IndentOptions::new(IndentType::Spaces, 4, false))); +twoway!("simple-combo-expr" => simple_combo_expr (IndentOptions::new(IndentType::Spaces, 4, false))); +twoway!("oneline-expand" => online_expand (IndentOptions::new(IndentType::Spaces, 4, false))); diff --git a/packages/autofmt/tests/wrong/multiexpr-many.rsx b/packages/autofmt/tests/wrong/multiexpr-many.rsx new file mode 100644 index 000000000..631d227f2 --- /dev/null +++ b/packages/autofmt/tests/wrong/multiexpr-many.rsx @@ -0,0 +1,195 @@ +#![allow(dead_code, unused)] +use dioxus::desktop::use_window; +use dioxus::prelude::*; +use std::{ + process::exit, + time::{Duration, Instant}, +}; +use tokio::time::sleep; + +fn main() { + LaunchBuilder::desktop().launch(app); +} + +struct WindowPreferences { + always_on_top: bool, + with_decorations: bool, + exiting: Option, +} + +impl Default for WindowPreferences { + fn default() -> Self { + Self { + with_decorations: true, + always_on_top: false, + exiting: None, + } + } +} + +impl WindowPreferences { + fn new() -> Self { + Self::default() + } +} + +#[derive(Default)] +struct Timer { + hours: u8, + minutes: u8, + seconds: u8, + started_at: Option, +} + +impl Timer { + fn new() -> Self { + Self::default() + } + + fn duration(&self) -> Duration { + Duration::from_secs( + (self.hours as u64 * 60 + self.minutes as u64) * 60 + self.seconds as u64, + ) + } +} + +const UPD_FREQ: Duration = Duration::from_millis(100); + +fn exit_button( + delay: Duration, + label: fn(Signal>, Duration) -> Option, +) -> Element { + let mut trigger: Signal> = use_signal(|| None); + use_future(move || async move { + loop { + sleep(UPD_FREQ).await; + if let Some(true) = trigger.read().map(|e| e.elapsed() > delay) { + exit(0); + } + } + }); + let stuff: Option = rsx! { + button { + onmouseup: move |_| { + trigger.set(None); + }, + onmousedown: move |_| { + trigger.set(Some(Instant::now())); + }, + width: 100, + {label(trigger, delay)} + } + }; + stuff +} + +fn app() -> Element { + let mut timer = use_signal(Timer::new); + let mut window_preferences = use_signal(WindowPreferences::new); + + use_future(move || async move { + loop { + sleep(UPD_FREQ).await; + timer.with_mut(|t| { + if let Some(started_at) = t.started_at { + if t.duration().saturating_sub(started_at.elapsed()) == Duration::ZERO { + t.started_at = None; + } + } + }); + } + }); + + rsx! { + div { + { + let millis = timer.with(|t| t.duration().saturating_sub(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 { + input { + r#type: "number", + min: 0, + max: 99, + value: format!("{:02}", timer.read().hours), + oninput: move |e| { + timer.write().hours = e.value().parse().unwrap_or(0); + } + } + + input { + r#type: "number", + min: 0, + max: 59, + value: format!("{:02}", timer.read().minutes), + oninput: move |e| { + timer.write().minutes = e.value().parse().unwrap_or(0); + } + } + + input { + r#type: "number", + min: 0, + max: 59, + value: format!("{:02}", timer.read().seconds), + oninput: move |e| { + timer.write().seconds = e.value().parse().unwrap_or(0); + } + } + } + + button { + id: "start_stop", + onclick: move |_| { + timer + .with_mut(|t| { + t + .started_at = if t.started_at.is_none() { + Some(Instant::now()) + } else { + None + }; + }) + }, + { timer.with(|t| if t.started_at.is_none() { "Start" } else { "Stop" }) } + } + div { id: "app", + button { + onclick: move |_| { + let decorations = window_preferences.read().with_decorations; + use_window().set_decorations(!decorations); + window_preferences.write().with_decorations = !decorations; + }, + { + format!("with decorations{}", if window_preferences.read().with_decorations { " ✓" } else { "" }).to_string() + } + } + button { + onclick: move |_| { + window_preferences + .with_mut(|wp| { + use_window().set_always_on_top(!wp.always_on_top); + wp.always_on_top = !wp.always_on_top; + }) + }, + width: 100, + { + format!("always on top{}", if window_preferences.read().always_on_top { " ✓" } else { "" }) + } + } + } + { + exit_button( + Duration::from_secs(3), + |trigger, delay| rsx! { + {format!("{:0.1?}", trigger.read().map(|inst| (delay.as_secs_f32() - inst.elapsed().as_secs_f32()))) } + } + ) + } + } +} diff --git a/packages/autofmt/tests/wrong/multiexpr-many.wrong.rsx b/packages/autofmt/tests/wrong/multiexpr-many.wrong.rsx new file mode 100644 index 000000000..66f87ee38 --- /dev/null +++ b/packages/autofmt/tests/wrong/multiexpr-many.wrong.rsx @@ -0,0 +1,164 @@ +#![allow(dead_code, unused)] +use dioxus::desktop::use_window; +use dioxus::prelude::*; +use std::{ + process::exit, + time::{Duration, Instant}, +}; +use tokio::time::sleep; + +fn main() { + LaunchBuilder::desktop().launch(app); +} + +struct WindowPreferences { + always_on_top: bool, + with_decorations: bool, + exiting: Option, +} + +impl Default for WindowPreferences { + fn default() -> Self { + Self { + with_decorations: true, + always_on_top: false, + exiting: None, + } + } +} + +impl WindowPreferences { + fn new() -> Self { + Self::default() + } +} + +#[derive(Default)] +struct Timer { + hours: u8, + minutes: u8, + seconds: u8, + started_at: Option, +} + +impl Timer { + fn new() -> Self { + Self::default() + } + + fn duration(&self) -> Duration { + Duration::from_secs( + (self.hours as u64 * 60 + self.minutes as u64) * 60 + self.seconds as u64, + ) + } +} + +const UPD_FREQ: Duration = Duration::from_millis(100); + +fn exit_button( + delay: Duration, + label: fn(Signal>, Duration) -> Option, +) -> Element { + let mut trigger: Signal> = use_signal(|| None); + use_future(move || async move { + loop { + sleep(UPD_FREQ).await; + if let Some(true) = trigger.read().map(|e| e.elapsed() > delay) { + exit(0); + } + } + }); + let stuff: Option = rsx! { + button { + onmouseup: move |_| { + trigger.set(None); + }, + onmousedown: move |_| { + trigger.set(Some(Instant::now())); + }, + width: 100, + {label(trigger, delay)}, + } + }; + stuff +} + +fn app() -> Element { + let mut timer = use_signal(Timer::new); + let mut window_preferences = use_signal(WindowPreferences::new); + + use_future(move || async move { + loop { + sleep(UPD_FREQ).await; + timer.with_mut(|t| { + if let Some(started_at) = t.started_at { + if t.duration().saturating_sub(started_at.elapsed()) == Duration::ZERO { + t.started_at = None; + } + } + }); + } + }); + + rsx! { + div {{ + let millis = timer.with(|t| t.duration().saturating_sub(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 { + input { r#type: "number", min: 0, max: 99, value: format!("{:02}",timer.read().hours), oninput: move |e| { + timer.write().hours = e.value().parse().unwrap_or(0); + } + } + + input { r#type: "number", min: 0, max: 59, value: format!("{:02}",timer.read().minutes), oninput: move |e| { + timer.write().minutes = e.value().parse().unwrap_or(0); + } + } + + input { r#type: "number", min: 0, max: 59, value: format!("{:02}",timer.read().seconds), oninput: move |e| { + timer.write().seconds = e.value().parse().unwrap_or(0); + } + } + } + + button { + id: "start_stop", + onclick: move |_| timer.with_mut(|t| t.started_at = if t.started_at.is_none() { Some(Instant::now()) } else { None } ), + { timer.with(|t| if t.started_at.is_none() { "Start" } else { "Stop" }) }, + } + div { id: "app", + button { onclick: move |_| { + let decorations = window_preferences.read().with_decorations; + use_window().set_decorations(!decorations); + window_preferences.write().with_decorations = !decorations; + }, { + format!("with decorations{}", if window_preferences.read().with_decorations { " ✓" } else { "" }).to_string() + } + } + button { + onclick: move |_| { + window_preferences.with_mut(|wp| { + use_window().set_always_on_top(!wp.always_on_top); + wp.always_on_top = !wp.always_on_top; + })}, + width: 100, + { + format!("always on top{}", if window_preferences.read().always_on_top { " ✓" } else { "" }) + } + } + } + { + exit_button( + Duration::from_secs(3), + |trigger, delay| rsx! { + {format!("{:0.1?}", trigger.read().map(|inst| (delay.as_secs_f32() - inst.elapsed().as_secs_f32()))) } + } + ) + } + } +} diff --git a/packages/autofmt/tests/wrong/oneline-expand.rsx b/packages/autofmt/tests/wrong/oneline-expand.rsx new file mode 100644 index 000000000..2e76aabd8 --- /dev/null +++ b/packages/autofmt/tests/wrong/oneline-expand.rsx @@ -0,0 +1,18 @@ +fn main() { + rsx! { + button { + id: "start_stop", + onclick: move |_| { + timer + .with_mut(|t| { + t + .started_at = if t.started_at.is_none() { + Some(Instant::now()) + } else { + None + }; + }) + } + } + } +} diff --git a/packages/autofmt/tests/wrong/oneline-expand.wrong.rsx b/packages/autofmt/tests/wrong/oneline-expand.wrong.rsx new file mode 100644 index 000000000..8f825c916 --- /dev/null +++ b/packages/autofmt/tests/wrong/oneline-expand.wrong.rsx @@ -0,0 +1,8 @@ +fn main() { + rsx! { + button { + id: "start_stop", + onclick: move |_| timer.with_mut(|t| t.started_at = if t.started_at.is_none() { Some(Instant::now()) } else { None } ) + } + } +} diff --git a/packages/autofmt/tests/wrong/simple-combo-expr.rsx b/packages/autofmt/tests/wrong/simple-combo-expr.rsx new file mode 100644 index 000000000..30c47d9ad --- /dev/null +++ b/packages/autofmt/tests/wrong/simple-combo-expr.rsx @@ -0,0 +1,35 @@ +fn main() { + rsx! { + div { + { + let millis = timer.with(|t| t.duration().saturating_sub(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 { + input { + r#type: "number", + min: 0, + max: 99, + value: format!("{:02}", timer.read().hours), + oninput: move |e| { + timer.write().hours = e.value().parse().unwrap_or(0); + } + } + // some comment + input { + r#type: "number", + min: 0, + max: 99, + value: format!("{:02}", timer.read().hours), + oninput: move |e| { + timer.write().hours = e.value().parse().unwrap_or(0); + } + } + } + } +} diff --git a/packages/autofmt/tests/wrong/simple-combo-expr.wrong.rsx b/packages/autofmt/tests/wrong/simple-combo-expr.wrong.rsx new file mode 100644 index 000000000..79d493827 --- /dev/null +++ b/packages/autofmt/tests/wrong/simple-combo-expr.wrong.rsx @@ -0,0 +1,23 @@ +fn main() { + rsx! { + div {{ + let millis = timer.with(|t| t.duration().saturating_sub(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 { + input { r#type: "number", min: 0, max: 99, value: format!("{:02}",timer.read().hours), oninput: move |e| { + timer.write().hours = e.value().parse().unwrap_or(0); + } + } + // some comment + input { r#type: "number", min: 0, max: 99, value: format!("{:02}",timer.read().hours), oninput: move |e| { + timer.write().hours = e.value().parse().unwrap_or(0); + } + } + } + } +}