Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[dioxus-cli@0.5.0-alpha.0] dx fmt breaks file #2104

Closed
3 tasks
mh-trimble opened this issue Mar 18, 2024 · 1 comment · Fixed by #2106
Closed
3 tasks

[dioxus-cli@0.5.0-alpha.0] dx fmt breaks file #2104

mh-trimble opened this issue Mar 18, 2024 · 1 comment · Fixed by #2106
Assignees

Comments

@mh-trimble
Copy link

Problem

When I format the following valid code. I get a file with mismatched curly braces and some extra empty lines I would not expect.

#![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<Instant>,
}

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<Instant>,
}

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<Option<Instant>>, Duration) -> Option<VNode>,
) -> Element {
    let mut trigger: Signal<Option<Instant>> = 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<VNode> = 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()))) }}
                       )  }}

    }
}

I get the following formatted file

#![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<Instant>,
}

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<Instant>,
}

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<Option<Instant>>, Duration) -> Option<VNode>,
) -> Element {
    let mut trigger: Signal<Option<Instant>> = 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<VNode> = 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()))) }}
                    )  }}
    }
}

Steps To Reproduce

Steps to reproduce the behavior:

  • start an empty cargo project
  • put in dioxus (github dependency) + features desktop
  • replace main file with the above content
  • cargo run will compile and run
  • install dioxus cli as documented cargo install dioxus-cli@0.5.0-alpha.0
  • run dx fmt to introduce mismatched parens and unnecessary newlines

Expected behavior

dx fmt should format the file without breaking it

Environment:

  • Dioxus version: master
  • Rust version: 1.76.0
  • OS info: OSX Sonoma on a M1 mac
  • App platform: desktop

Questionnaire

  • I'm interested in fixing this myself but don't know where to start
  • I would like to fix and I have a solution
  • I don't have time to fix this right now, but maybe later
@jkelleyrtp
Copy link
Member

Great test case, caught an issue where exprs were dragging one extra character with them, causing the extra curly.

We can't do much about the nested rsx! case in the file expr though, but everything else should be fixed in #2106

jkelleyrtp added a commit that referenced this issue Mar 19, 2024
* Fix #2104: fmt incorrectly using 1-indexing for columns

* Clippy...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants