Skip to content

Commit d69db52

Browse files
authored
Merge pull request #788 from NeogitOrg/basic-filewatcher
2 parents e3f488c + d5c93f1 commit d69db52

File tree

6 files changed

+129
-3
lines changed

6 files changed

+129
-3
lines changed

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,16 @@ neogit.setup {
6363
disable_commit_confirmation = false,
6464
-- Uses `vim.notify` instead of the built-in notification system.
6565
disable_builtin_notifications = false,
66-
-- Changes what mode the Commit Editor starts in. `true` will leave nvim in normal mode, `false` will change nvim to insert mode, and `"auto"` will change nvim to insert mode IF the commit message is empty, otherwise leaving it in normal mode.
66+
-- Changes what mode the Commit Editor starts in. `true` will leave nvim in normal mode, `false` will change nvim to
67+
-- insert mode, and `"auto"` will change nvim to insert mode IF the commit message is empty, otherwise leaving it in
68+
-- normal mode.
6769
disable_insert_on_commit = true,
70+
-- When enabled, will watch the `.git/` directory for changes and refresh the status buffer in response to filesystem
71+
-- events.
72+
filewatcher = {
73+
interval = 1000,
74+
enabled = true,
75+
},
6876
-- Allows a different telescope sorter. Defaults to 'fuzzy_with_index_bias'. The example below will use the native fzf
6977
-- sorter instead. By default, this function returns `nil`.
7078
telescope_sorter = function()

lua/neogit/config.lua

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ end
5353
---@field rebase NeogitConfigSection|nil
5454
---@field sequencer NeogitConfigSection|nil
5555

56+
---@class NeogitFilewatcherConfig
57+
---@field interval number
58+
---@field enabled boolean
59+
---@field filewatcher NeogitFilewatcherConfig|nil
60+
5661
---@alias NeogitConfigMappingsFinder "Select" | "Close" | "Next" | "Previous" | "MultiselectToggleNext" | "MultiselectTogglePrevious" | "NOP" | false
5762
---@alias NeogitConfigMappingsStatus "Close" | "InitRepo" | "Depth1" | "Depth2" | "Depth3" | "Depth4" | "Toggle" | "Discard" | "Stage" | "StageUnstaged" | "StageAll" | "Unstage" | "UnstageStaged" | "DiffAtFile" | "CommandHistory" | "Console" | "RefreshBuffer" | "GoToFile" | "VSplitOpen" | "SplitOpen" | "TabOpen" | "HelpPopup" | "DiffPopup" | "PullPopup" | "RebasePopup" | "MergePopup" | "PushPopup" | "CommitPopup" | "LogPopup" | "RevertPopup" | "StashPopup" | "CherryPickPopup" | "BranchPopup" | "FetchPopup" | "ResetPopup" | "RemotePopup" | "GoToPreviousHunkHeader" | "GoToNextHunkHeader" | false | fun()
5863

@@ -61,6 +66,7 @@ end
6166
---@field status? { [string]: NeogitConfigMappingsStatus } A dictionary that uses status commands to set a single keybind
6267

6368
---@class NeogitConfig Neogit configuration settings
69+
---@field filewatcher? NeogitFilewatcherConfig Values for filewatcher
6470
---@field disable_hint? boolean Remove the top hint in the Status buffer
6571
---@field disable_context_highlighting? boolean Disable context highlights based on cursor position
6672
---@field disable_signs? boolean Special signs to draw for sections etc. in Neogit
@@ -100,6 +106,10 @@ function M.get_default_values()
100106
disable_signs = false,
101107
disable_commit_confirmation = false,
102108
disable_builtin_notifications = false,
109+
filewatcher = {
110+
interval = 1000,
111+
enabled = true,
112+
},
103113
telescope_sorter = function()
104114
return nil
105115
end,

lua/neogit/lib/git/repository.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ local function empty_state()
1212
local Path = require("plenary.path")
1313

1414
return {
15-
git_path = function(path)
16-
return Path.new(root):joinpath(".git", path)
15+
git_path = function(...)
16+
return Path.new(root):joinpath(".git", ...)
1717
end,
1818
cwd = vim.fn.getcwd(),
1919
git_root = root,

lua/neogit/lib/util.lua

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
local a = require("plenary.async")
2+
local uv = vim.loop
23

34
---@generic T: any
45
---@generic U: any
@@ -360,6 +361,23 @@ local function pad_right(s, len)
360361
return s .. string.rep(" ", math.max(len - #s, 0))
361362
end
362363

364+
--- Debounces a function on the trailing edge.
365+
---
366+
--- @generic F: function
367+
--- @param ms number Timeout in ms
368+
--- @param fn F Function to debounce
369+
--- @return F Debounced function.
370+
local function debounce_trailing(ms, fn)
371+
local timer = assert(uv.new_timer())
372+
return function(...)
373+
local argv = { ... }
374+
timer:start(ms, 0, function()
375+
timer:stop()
376+
fn(unpack(argv))
377+
end)
378+
end
379+
end
380+
363381
return {
364382
time = time,
365383
time_async = time_async,
@@ -394,4 +412,5 @@ return {
394412
reverse = reverse,
395413
flat_map = flat_map,
396414
str_wrap = str_wrap,
415+
debounce_trailing = debounce_trailing,
397416
}

lua/neogit/status.lua

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ local LineBuffer = require("neogit.lib.line_buffer")
1313
local fs = require("neogit.lib.fs")
1414
local input = require("neogit.lib.input")
1515
local util = require("neogit.lib.util")
16+
local watcher = require("neogit.watcher")
1617

1718
local map = require("neogit.lib.util").map
1819
local api = vim.api
@@ -602,6 +603,8 @@ local function close(skip_close)
602603
if not skip_close then
603604
M.status_buffer:close()
604605
end
606+
607+
M.watcher:stop()
605608
notif.delete_all()
606609
M.status_buffer = nil
607610
vim.o.autochdir = M.prev_autochdir
@@ -1091,6 +1094,7 @@ local cmd_func_map = function()
10911094
end),
10921095

10931096
["RefreshBuffer"] = function()
1097+
notif.create("Refreshing Status", vim.log.levels.INFO)
10941098
dispatch_refresh(true)
10951099
end,
10961100

@@ -1259,6 +1263,9 @@ function M.create(kind, cwd)
12591263
logger.debug("[STATUS BUFFER]: Dispatching initial render")
12601264
refresh(true, "Buffer.create")
12611265
end,
1266+
after = function()
1267+
M.watcher = watcher.new(git.repo.git_path():absolute())
1268+
end,
12621269
}
12631270
end
12641271

lua/neogit/watcher.lua

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
local uv = vim.loop
2+
3+
local config = require("neogit.config")
4+
local logger = require("neogit.logger")
5+
local util = require("neogit.lib.util")
6+
7+
local a = require("plenary.async")
8+
9+
local watch_gitdir_handler = a.void(function()
10+
logger.debug("[WATCHER] Dispatching Refresh")
11+
require("neogit.status").dispatch_refresh()
12+
end)
13+
14+
local watch_gitdir_handler_db =
15+
util.debounce_trailing(config.values.filewatcher.interval, watch_gitdir_handler)
16+
17+
local fs_event_handler = function(err, filename, events)
18+
if err then
19+
logger.error(string.format("[WATCHER] Git dir update error: %s", err))
20+
return
21+
end
22+
23+
local info = string.format(
24+
"[WATCHER] Git dir update: '%s' %s",
25+
filename,
26+
vim.inspect(events, { indent = "", newline = " " })
27+
)
28+
29+
-- stylua: ignore
30+
if
31+
filename == nil or
32+
filename:match("%.lock$") or
33+
filename:match("COMMIT_EDITMSG") or
34+
filename:match("~$")
35+
then
36+
logger.debug(string.format("%s (ignoring)", info))
37+
return
38+
end
39+
40+
logger.debug(info)
41+
watch_gitdir_handler_db()
42+
end
43+
44+
-- Adapted from https://github.com/lewis6991/gitsigns.nvim/blob/main/lua/gitsigns/watcher.lua#L103
45+
--- @param gitdir string
46+
--- @return uv_fs_event_t
47+
local function start(gitdir)
48+
local w = assert(uv.new_fs_event())
49+
w:start(gitdir, {}, fs_event_handler)
50+
51+
return w
52+
end
53+
54+
---@class Watcher
55+
---@field gitdir string
56+
---@field fs_event_handler uv_fs_event_t|nil
57+
local Watcher = {}
58+
Watcher.__index = Watcher
59+
60+
function Watcher:stop()
61+
if self.fs_event_handler then
62+
logger.debug("[WATCHER] Stopped watching git dir: " .. self.gitdir)
63+
self.fs_event_handler:stop()
64+
end
65+
end
66+
67+
function Watcher:create(gitdir)
68+
self.gitdir = gitdir
69+
70+
if config.values.filewatcher.enabled then
71+
logger.debug("[WATCHER] Watching git dir: " .. gitdir)
72+
self.fs_event_handler = start(gitdir)
73+
end
74+
75+
return self
76+
end
77+
78+
function Watcher.new(...)
79+
return Watcher:create(...)
80+
end
81+
82+
return Watcher

0 commit comments

Comments
 (0)