air-verse / air

☁️ Live reload for Go apps
GNU General Public License v3.0
16.35k stars 771 forks source link

Make pre_cmd background forkable #557

Open omani opened 3 months ago

omani commented 3 months ago

Im trying to use pre_cmd = ["/usr/bin/xdotool sleep 1 search --onlyvisible --classname Navigator windowactivate --sync key F5 &"]

to refresh the browser once the binary (golang echo framework) started. hence the sleep so the refresh happens after the http listening on the port is available.

I need to put the xdotool command into background & because otherwise air will wait for the command to finish and then run the webserver. which will obviously fail.

I checked the code for the runner. it seems that forking into background exits the process immediately (the exec commad in utils_linux.go is "/bin/sh -c"...so ofc air wont wait and dismiss the command right away.

so my question is: any chance to implement a background forkable pre_cmd and the appropriate directive in air.toml?

omani commented 3 months ago

I solved this issue by moving the command invocation into my main:go (where I start the echo webserver):

cmd := exec.Command("/usr/bin/xdotool", "search", "--onlyvisible", "--classname", "Navigator", "key", "F5")
err := cmd.Start()
if err != nil {
   log.Printf("Failed to start cmd: %v", err)
} else {
   if err := cmd.Wait(); err != nil {
    log.Printf("Cmd returned error: %v", err)
}
}

this refreshes the browser WITHOUT leaving neovim and losing focus from the IDE.

feel free to close this issue if you are not interested in implementing a forkable pre_cmd for air.

awoelf commented 3 months ago

I have a similar solution for hot-reloading the code with air, but I used goroutines.

refresh.sh

set -o errexit
set -o nounset

keystroke="CTRL+F5"
browser="${1:-firefox}"

# find all visible browser windows
browser_windows="$(xdotool search --sync --all --onlyvisible --name ${browser})"

# Send keystroke
for bw in $browser_windows; do
    xdotool key --window "$bw" "$keystroke"
done

In my main.go file, I run the .sh file and the listen command as goroutines.

main.go

// ... other code

func main() {
    // Locate .sh file
    cmd := exec.Command("/bin/sh", "refresh.sh")

    engine := html.New("./views", ".html")

    app := fiber.New(fiber.Config{
    Views: engine,
    })

    app.Static("/", "./public")

    app.Get("/", func(c *fiber.Ctx) error {
    return c.Render("index", fiber.Map{
        "title": "Hello, World!",
    }, "layouts/main")
    })

    // Run both the refresh command and start the app
    go cmd.Run()
    go log.Fatal(app.Listen(":3000"))
}

The main drawback with this refresh command is that it will only refresh the current, visible tab. So it won't refresh the working page unless it's open. I wish there was a way to refresh the working tab even when it's not visible.

omani commented 3 months ago

The main drawback with this refresh command is that it will only refresh the current, visible tab. So it won't refresh the working page unless it's open. I wish there was a way to refresh the working tab even when it's not visible.

for me this is ok since I only work with the browser page/tab open side by side to my neovim environment, so I can see the changes immediately when eg. working on the tailwindcss part.

for now Im happy with this solution.