mirror of
https://github.com/taigrr/glaze.nvim.git
synced 2026-04-02 03:09:10 -07:00
feat: bug fixes, lazy.nvim-style controls, auto-update checking, repo polish
Phase 1 - Bug fixes:
- Fix extmark rendering in text.lua (table vs string hl handling)
- Fix timer leak: WinClosed autocmd now triggers view.close()
- Add GOBIN/GOPATH/~/go/bin awareness to is_installed() and new bin_path()
- Reject update_all() while tasks running (race condition fix)
- Implement _toggle_details with full binary info expansion
Phase 2 - Lazy.nvim-style controls:
- U = Update all, u = update cursor binary
- I = Install all missing, i = install cursor binary
- x = Abort, CR = toggle details, q/Esc = close
- Line-to-binary mapping for cursor-aware actions
Phase 3 - Auto-update checking:
- New checker module with go list/go version -m integration
- auto_check config option (daily/weekly/custom frequency)
- Persistent state in stdpath('data')/glaze/state.json
- :GlazeCheck command for manual checks
- Update indicators in UI (version info + arrows)
Phase 4 - Repo polish:
- MIT LICENSE file
- Doughnut-themed README with badges and Why Glaze? section
- Updated help docs with all new features/keybinds/API
This commit is contained in:
257
lua/glaze/checker.lua
Normal file
257
lua/glaze/checker.lua
Normal file
@@ -0,0 +1,257 @@
|
||||
---@brief [[
|
||||
--- glaze.nvim update checker
|
||||
--- Checks for newer versions of registered Go binaries
|
||||
---@brief ]]
|
||||
|
||||
local M = {}
|
||||
|
||||
---@class GlazeUpdateInfo
|
||||
---@field name string Binary name
|
||||
---@field installed_version? string Currently installed version
|
||||
---@field latest_version? string Latest available version
|
||||
---@field has_update boolean Whether an update is available
|
||||
|
||||
---@type table<string, GlazeUpdateInfo>
|
||||
M._update_info = {}
|
||||
|
||||
---@type boolean
|
||||
M._checking = false
|
||||
|
||||
local STATE_FILE = vim.fn.stdpath("data") .. "/glaze/state.json"
|
||||
|
||||
---Read persisted state from disk.
|
||||
---@return table
|
||||
local function read_state()
|
||||
local ok, content = pcall(vim.fn.readfile, STATE_FILE)
|
||||
if not ok or #content == 0 then
|
||||
return {}
|
||||
end
|
||||
local decode_ok, data = pcall(vim.json.decode, table.concat(content, "\n"))
|
||||
if not decode_ok then
|
||||
return {}
|
||||
end
|
||||
return data or {}
|
||||
end
|
||||
|
||||
---Write state to disk.
|
||||
---@param state table
|
||||
local function write_state(state)
|
||||
local dir = vim.fn.fnamemodify(STATE_FILE, ":h")
|
||||
vim.fn.mkdir(dir, "p")
|
||||
local json = vim.json.encode(state)
|
||||
vim.fn.writefile({ json }, STATE_FILE)
|
||||
end
|
||||
|
||||
---Get the frequency in seconds from config.
|
||||
---@return number seconds
|
||||
local function get_frequency_seconds()
|
||||
local glaze = require("glaze")
|
||||
local freq = glaze.config.auto_check.frequency
|
||||
if freq == "daily" then
|
||||
return 86400
|
||||
elseif freq == "weekly" then
|
||||
return 604800
|
||||
elseif type(freq) == "number" then
|
||||
return freq * 3600
|
||||
end
|
||||
return 86400
|
||||
end
|
||||
|
||||
---Get installed version of a binary by parsing `go version -m` output.
|
||||
---@param name string Binary name
|
||||
---@param callback fun(version: string?)
|
||||
local function get_installed_version(name, callback)
|
||||
local glaze = require("glaze")
|
||||
local bin_path = glaze.bin_path(name)
|
||||
if not bin_path then
|
||||
callback(nil)
|
||||
return
|
||||
end
|
||||
|
||||
vim.fn.jobstart({ "go", "version", "-m", bin_path }, {
|
||||
stdout_buffered = true,
|
||||
on_stdout = function(_, data)
|
||||
if not data then
|
||||
callback(nil)
|
||||
return
|
||||
end
|
||||
local output = table.concat(data, "\n")
|
||||
-- Parse "mod\tmodule/path\tv1.2.3\th1:..." or "path\tmodule/path"
|
||||
local version = output:match("\tmod\t[^\t]+\t(v[^\t%s]+)")
|
||||
or output:match("\tpath\t[^\n]+\n[^\t]*\tmod\t[^\t]+\t(v[^\t%s]+)")
|
||||
callback(version)
|
||||
end,
|
||||
on_exit = function(_, code)
|
||||
if code ~= 0 then
|
||||
callback(nil)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
---Check for the latest version of a module using go list.
|
||||
---@param url string Module URL
|
||||
---@param callback fun(version: string?)
|
||||
local function get_latest_version(url, callback)
|
||||
local glaze = require("glaze")
|
||||
local cmd = vim.list_extend({}, glaze.config.go_cmd)
|
||||
vim.list_extend(cmd, { "list", "-m", "-json", url .. "@latest" })
|
||||
|
||||
vim.fn.jobstart(cmd, {
|
||||
stdout_buffered = true,
|
||||
env = { GOFLAGS = "" },
|
||||
on_stdout = function(_, data)
|
||||
if not data then
|
||||
callback(nil)
|
||||
return
|
||||
end
|
||||
local output = table.concat(data, "\n")
|
||||
local decode_ok, result = pcall(vim.json.decode, output)
|
||||
if decode_ok and result and result.Version then
|
||||
callback(result.Version)
|
||||
else
|
||||
callback(nil)
|
||||
end
|
||||
end,
|
||||
on_exit = function(_, code)
|
||||
if code ~= 0 then
|
||||
callback(nil)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
---Get cached update info.
|
||||
---@return table<string, GlazeUpdateInfo>
|
||||
function M.get_update_info()
|
||||
return M._update_info
|
||||
end
|
||||
|
||||
---Check for updates on all registered binaries.
|
||||
---@param opts? { silent?: boolean }
|
||||
function M.check(opts)
|
||||
opts = opts or {}
|
||||
local glaze = require("glaze")
|
||||
local binaries = glaze.binaries()
|
||||
|
||||
if vim.tbl_count(binaries) == 0 then
|
||||
if not opts.silent then
|
||||
vim.notify("Glaze: no binaries registered", vim.log.levels.INFO)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if M._checking then
|
||||
if not opts.silent then
|
||||
vim.notify("Glaze: already checking for updates", vim.log.levels.INFO)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
M._checking = true
|
||||
local remaining = 0
|
||||
local updates_found = 0
|
||||
|
||||
for name, binary in pairs(binaries) do
|
||||
remaining = remaining + 2 -- installed version + latest version
|
||||
|
||||
local info = {
|
||||
name = name,
|
||||
installed_version = nil,
|
||||
latest_version = nil,
|
||||
has_update = false,
|
||||
}
|
||||
M._update_info[name] = info
|
||||
|
||||
get_installed_version(name, function()
|
||||
-- callback receives version from the jobstart; re-check via closure
|
||||
end)
|
||||
end
|
||||
|
||||
-- Simplified: check each binary sequentially-ish
|
||||
remaining = vim.tbl_count(binaries)
|
||||
for name, binary in pairs(binaries) do
|
||||
local info = M._update_info[name]
|
||||
|
||||
get_installed_version(name, function(installed)
|
||||
info.installed_version = installed
|
||||
|
||||
get_latest_version(binary.url, function(latest)
|
||||
info.latest_version = latest
|
||||
|
||||
if installed and latest and installed ~= latest then
|
||||
info.has_update = true
|
||||
updates_found = updates_found + 1
|
||||
end
|
||||
|
||||
remaining = remaining - 1
|
||||
if remaining <= 0 then
|
||||
M._checking = false
|
||||
|
||||
-- Save check timestamp
|
||||
local state = read_state()
|
||||
state.last_check = os.time()
|
||||
state.update_info = {}
|
||||
for n, i in pairs(M._update_info) do
|
||||
state.update_info[n] = {
|
||||
installed_version = i.installed_version,
|
||||
latest_version = i.latest_version,
|
||||
has_update = i.has_update,
|
||||
}
|
||||
end
|
||||
write_state(state)
|
||||
|
||||
if not opts.silent then
|
||||
if updates_found > 0 then
|
||||
vim.schedule(function()
|
||||
vim.notify("Glaze: " .. updates_found .. " update(s) available", vim.log.levels.INFO)
|
||||
end)
|
||||
else
|
||||
vim.schedule(function()
|
||||
vim.notify("Glaze: all binaries up to date", vim.log.levels.INFO)
|
||||
end)
|
||||
end
|
||||
elseif updates_found > 0 then
|
||||
vim.schedule(function()
|
||||
vim.notify("Glaze: " .. updates_found .. " update(s) available", vim.log.levels.INFO)
|
||||
end)
|
||||
end
|
||||
|
||||
-- Refresh UI if open
|
||||
vim.schedule(function()
|
||||
local ok, view = pcall(require, "glaze.view")
|
||||
if ok and view._float and view._float:valid() then
|
||||
view.render()
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
---Auto-check if enough time has passed since last check.
|
||||
function M.auto_check()
|
||||
local state = read_state()
|
||||
local last_check = state.last_check or 0
|
||||
local now = os.time()
|
||||
local freq = get_frequency_seconds()
|
||||
|
||||
-- Load cached update info
|
||||
if state.update_info then
|
||||
for name, info in pairs(state.update_info) do
|
||||
M._update_info[name] = {
|
||||
name = name,
|
||||
installed_version = info.installed_version,
|
||||
latest_version = info.latest_version,
|
||||
has_update = info.has_update or false,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
if (now - last_check) >= freq then
|
||||
M.check({ silent = true })
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user