From bb82cde1333beeb7e350ea5bd3900ca61b858508 Mon Sep 17 00:00:00 2001 From: Tai Groot Date: Sun, 5 Apr 2026 06:04:20 +0000 Subject: [PATCH] fix(runner): batch auto-install to prevent race conditions and silent UI - Auto-install now batches all registrations into a single deferred install call (200ms debounce), preventing race conditions where multiple deferred installs would reject each other - Silent auto-install no longer pops open the Glaze UI window - runner.install() accepts { silent: true } option to suppress UI and notifications - Add CI workflow (stylua format check + luacheck lint) - Add .stylua.toml and .luacheckrc configs - Fix empty if branch lint warning in runner.lua - Apply stylua formatting to checker.lua and health.lua --- .github/workflows/ci.yml | 25 +++++++++++++++++++++++++ .luacheckrc | 7 +++++++ .stylua.toml | 6 ++++++ lua/glaze/checker.lua | 13 +++++++------ lua/glaze/health.lua | 4 +--- lua/glaze/init.lua | 37 +++++++++++++++++++++++++++++-------- lua/glaze/runner.lua | 29 ++++++++++++++++++----------- 7 files changed, 93 insertions(+), 28 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .luacheckrc create mode 100644 .stylua.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8b8de1a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check formatting with StyLua + uses: JohnnyMorganz/stylua-action@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + version: latest + args: --check . + + - name: Lint with luacheck + uses: lunarmodules/luacheck@v1 + with: + args: lua/ diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 0000000..0fce313 --- /dev/null +++ b/.luacheckrc @@ -0,0 +1,7 @@ +std = "luajit" +globals = { "vim" } +max_line_length = 150 + +ignore = { + "212", -- unused argument (common in callbacks) +} diff --git a/.stylua.toml b/.stylua.toml new file mode 100644 index 0000000..6090f42 --- /dev/null +++ b/.stylua.toml @@ -0,0 +1,6 @@ +column_width = 120 +line_endings = "Unix" +indent_type = "Spaces" +indent_width = 2 +quote_style = "AutoPreferDouble" +call_parentheses = "Always" diff --git a/lua/glaze/checker.lua b/lua/glaze/checker.lua index 071bcdf..4f0b60b 100644 --- a/lua/glaze/checker.lua +++ b/lua/glaze/checker.lua @@ -141,12 +141,13 @@ function M.refresh_version(name, callback) end get_installed_version(name, function(installed) - local info = M._update_info[name] or { - name = name, - installed_version = nil, - latest_version = nil, - has_update = false, - } + local info = M._update_info[name] + or { + name = name, + installed_version = nil, + latest_version = nil, + has_update = false, + } info.installed_version = installed -- If we have latest version cached, check if still needs update diff --git a/lua/glaze/health.lua b/lua/glaze/health.lua index 1123906..9bae6a2 100644 --- a/lua/glaze/health.lua +++ b/lua/glaze/health.lua @@ -72,9 +72,7 @@ function M.check() else vim.health.ok(count .. " binary(ies) registered") for name, binary in pairs(binaries) do - local pi = (binary.plugins and #binary.plugins > 0) - and (" (" .. table.concat(binary.plugins, ", ") .. ")") - or "" + local pi = (binary.plugins and #binary.plugins > 0) and (" (" .. table.concat(binary.plugins, ", ") .. ")") or "" if glaze.is_installed(name) then vim.health.ok(" " .. name .. " — installed" .. pi) else diff --git a/lua/glaze/init.lua b/lua/glaze/init.lua index 1a61439..1dde7a9 100644 --- a/lua/glaze/init.lua +++ b/lua/glaze/init.lua @@ -85,6 +85,12 @@ M._binaries = {} ---@type number? M._ns = nil +---@type table Binaries queued for auto-install +M._auto_install_queue = {} + +---@type number? Timer for batched auto-install +M._auto_install_timer = nil + ---@param opts? GlazeConfig function M.setup(opts) M.config = vim.tbl_deep_extend("force", M.config, opts or {}) @@ -184,16 +190,31 @@ function M.register(name, url, opts) callbacks = opts.callback and { [plugin] = opts.callback } or {}, } - -- Auto-install if enabled and binary is missing + -- Queue for auto-install if enabled and binary is missing. + -- Uses a batched timer so multiple registers don't race. if M.config.auto_install.enabled and not M.is_installed(name) then - vim.defer_fn(function() - if not M.is_installed(name) then - if not M.config.auto_install.silent then - vim.notify("[glaze] Auto-installing " .. name .. "…", vim.log.levels.INFO) + M._auto_install_queue[name] = true + if M._auto_install_timer then + vim.fn.timer_stop(M._auto_install_timer) + end + M._auto_install_timer = vim.fn.timer_start(200, function() + M._auto_install_timer = nil + vim.schedule(function() + local to_install = {} + for queued_name, _ in pairs(M._auto_install_queue) do + if not M.is_installed(queued_name) then + table.insert(to_install, queued_name) + end end - require("glaze.runner").install({ name }) - end - end, 100) + M._auto_install_queue = {} + if #to_install > 0 then + if not M.config.auto_install.silent then + vim.notify("[glaze] Auto-installing " .. table.concat(to_install, ", ") .. "…", vim.log.levels.INFO) + end + require("glaze.runner").install(to_install, { silent = M.config.auto_install.silent }) + end + end) + end) end end diff --git a/lua/glaze/runner.lua b/lua/glaze/runner.lua index a9cecaa..88afbef 100644 --- a/lua/glaze/runner.lua +++ b/lua/glaze/runner.lua @@ -143,12 +143,16 @@ end ---Run tasks for specified binaries. ---@param names string[] ---@param mode "install"|"update" -local function run(names, mode) +---@param opts? { silent?: boolean } +local function run(names, mode, opts) + opts = opts or {} local glaze = require("glaze") -- Reject if already running (race condition fix) if M._running then - vim.notify("Glaze: tasks already running. Wait or abort first.", vim.log.levels.WARN) + if not opts.silent then + vim.notify("Glaze: tasks already running. Wait or abort first.", vim.log.levels.WARN) + end return end @@ -164,18 +168,18 @@ local function run(names, mode) for _, name in ipairs(names) do local binary = glaze._binaries[name] if binary then - if mode == "install" and glaze.is_installed(name) then - -- Skip already installed - else + if not (mode == "install" and glaze.is_installed(name)) then table.insert(binaries, binary) end else - vim.notify("Unknown binary: " .. name, vim.log.levels.WARN) + if not opts.silent then + vim.notify("Unknown binary: " .. name, vim.log.levels.WARN) + end end end if #binaries == 0 then - if mode == "install" then + if mode == "install" and not opts.silent then vim.notify("All binaries already installed", vim.log.levels.INFO) end return @@ -191,8 +195,10 @@ local function run(names, mode) M._notify() M._process_queue() - -- Open UI - require("glaze.view").open() + -- Open UI (skip for silent/auto-install operations) + if not opts.silent then + require("glaze.view").open() + end end ---Update specific binaries. @@ -209,8 +215,9 @@ end ---Install specific binaries. ---@param names string[] -function M.install(names) - run(names, "install") +---@param opts? { silent?: boolean } +function M.install(names, opts) + run(names, "install", opts) end ---Install all missing binaries.