feat(explorer): git index watcher

This commit is contained in:
Folke Lemaitre 2025-02-05 11:10:02 +01:00
parent 268aa41e55
commit 4c12475e80
No known key found for this signature in database
GPG key ID: 41F8B1FBACAE2040
4 changed files with 85 additions and 45 deletions

View file

@ -20,6 +20,15 @@ function M.refresh(path)
end
end
---@param cwd string
function M.is_dirty(cwd)
local root = Snacks.git.get_root(cwd)
if not root then
return false
end
return M.state[root] == nil
end
---@param cwd string
---@param opts? {on_update?: fun(), ttl?: number, force?: boolean}
function M.update(cwd, opts)

View file

@ -220,6 +220,9 @@ end
---@param opts? {hidden?: boolean, ignored?: boolean}
function Tree:is_dirty(cwd, opts)
opts = opts or {}
if require("snacks.explorer.git").is_dirty(cwd) then
return true
end
local dirty = false
self:get(cwd, function(n)
if n.dir and n.open and not n.expanded then

View file

@ -1,5 +1,8 @@
local M = {}
local Git = require("snacks.explorer.git")
local Tree = require("snacks.explorer.tree")
local uv = vim.uv or vim.loop
---@alias snacks.explorer.Watcher fun(path:string, opts:vim._watch.watch.Opts?, callback:vim._watch.Callback):fun()
@ -27,51 +30,84 @@ function M.abort()
running = {}
end
local timer = assert(uv.new_timer())
function M.refresh()
-- batch updates and give explorer the time to update before the watcher
timer:start(500, 0, function()
local picker = Snacks.picker.get({ source = "explorer" })[1]
if picker and not picker.closed and Tree:is_dirty(picker:cwd(), picker.opts) then
if not picker.list.target then
picker.list:set_target()
end
picker:find()
end
end)
end
---@param cwd string
function M.watch_git(cwd)
local root = Snacks.git.get_root(cwd)
if not root then
return
end
local handle = assert(vim.uv.new_fs_event())
handle:start(root .. "/.git", {}, function(_, file)
if file == "index" then
Git.refresh(root)
M.refresh()
end
end)
return function()
if handle and not handle:is_closing() then
handle:close()
end
end
end
---@param cwd string
function M.watch_files(cwd)
local watch = M.watcher()
if not watch then
return
end
return watch(cwd, {
uvflags = { recursive = true },
}, function(path, changes)
-- handle deletes
while not uv.fs_stat(path) do
local p = vim.fs.dirname(path)
if p == path then
return
end
path = p
end
Tree:refresh(path)
M.refresh()
end)
end
---@param cwd string
function M.watch(cwd)
if running[cwd] then
return
end
local Tree = require("snacks.explorer.tree")
local watch = M.watcher()
if not watch then
return
end
M.abort()
pcall(function()
local timer = assert(uv.new_timer())
local cancel = watch(cwd, {
uvflags = { recursive = true },
}, function(path)
-- handle deletes
while not uv.fs_stat(path) do
local p = vim.fs.dirname(path)
if p == path then
return
end
path = p
end
Tree:refresh(path)
local watchers = { M.watch_git, M.watch_files }
local cancel = {} ---@type (fun())[]
-- batch updates and give explorer the time to update before the watcher
timer:start(100, 0, function()
local picker = Snacks.picker.get({ source = "explorer" })[1]
if picker and Tree:is_dirty(picker:cwd(), picker.opts) then
if not picker.list.target then
picker.list:set_target()
end
picker:find()
end
end)
end)
running[cwd] = function()
if not timer:is_closing() then
timer:close()
end
cancel()
for _, watch in ipairs(watchers) do
local ok, c = pcall(watch, cwd)
if ok and c then
cancel[#cancel + 1] = c
end
end)
end
running[cwd] = function()
vim.tbl_map(pcall, cancel)
running[cwd] = nil
end
end
return M

View file

@ -49,14 +49,6 @@ function State.new(picker)
end
end
picker.list.win:on("TermClose", function()
local p = ref()
if p then
Tree:refresh(p:cwd())
Actions.update(p)
end
end, { pattern = "*lazygit" })
picker.list.win:on("BufWritePost", function(_, ev)
local p = ref()
if p then