aboutsummaryrefslogtreecommitdiffstats
path: root/plugin/50-autoreload.lua
blob: d6f3cf83036aaba1dabcc73997c6758b97d75a49 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
--[[ 50-autoreload.lua — handle files changed outside Neovim, with opt-in silent reload.

Every loaded buffer is re-stat'd on focus, so an external change surfaces Neovim's usual reload prompt.
A buffer flagged for autoreload is instead reloaded silently on that change, and is re-stat'd on idle /
buffer-enter too so the reload doesn't wait for a focus change. Conflicts (the buffer was also edited
in Neovim) and deletions always defer to the prompt. Autoreload is off by default and per-buffer: the
state lives in `vim.b.autoreload` and shows as the `+R` flag in the statusline (see 50-color.lua).

`autoread` is off so the prompt fires even for unmodified buffers, and the autocmds run `checktime`
explicitly because FocusGained does not trigger FileChangedShell on its own.

User commands:
  `AutoreloadToggle`:  toggle disk-change autoreload for the current buffer
]]

vim.opt.autoread = false

-- `checktime` is invalid in the command-line window (`q:`/`q/`), which still fires our autocmds (see
-- <nvim-help://E11>).
local function in_cmdwin()
	return vim.fn.getcmdwintype() ~= ""
end

-- Re-stat every loaded buffer in one pass; the FileChangedShell handler below decides per buffer.
local function check_all_buffers()
	if not in_cmdwin() then
		vim.cmd.checktime() -- no argument → all loaded buffers at once
	end
end

-- Re-stat only the autoreload-flagged buffers, so an unflagged-but-changed buffer gets no surprise
-- prompt at idle. `checktime {buf}` keeps each check scoped to its buffer.
local function check_flagged_buffers()
	if in_cmdwin() then
		return
	end
	for _, buf in ipairs(vim.api.nvim_list_bufs()) do
		if vim.api.nvim_buf_is_loaded(buf) and vim.b[buf].autoreload then
			vim.cmd.checktime(buf)
		end
	end
end

-- `nested` so the checktime calls fire the FileChangedShell handler below; without it Neovim
-- suppresses the triggered event and falls back to its default prompt. Focus re-stats every buffer
-- (the baseline prompt-on-change); the idle / buffer-enter events re-stat only flagged buffers.
vim.api.nvim_create_autocmd("FocusGained", {
	group = vim.g.dotfiles.augroup,
	nested = true,
	callback = check_all_buffers,
})
vim.api.nvim_create_autocmd({ "BufEnter", "CursorHold", "CursorHoldI", "TermLeave" }, {
	group = vim.g.dotfiles.augroup,
	nested = true,
	callback = check_flagged_buffers,
})

--[[ Decide what happens when Neovim notices a file changed under it. When the affected buffer has
autoreload on, a plain on-disk change reloads it with no prompt; otherwise a change or conflict falls
back to `ask` and any other reason (deletion, …) to Neovim's default handling.
See <nvim-help://v:fcs_choice>. ]]
vim.api.nvim_create_autocmd("FileChangedShell", {
	group = vim.g.dotfiles.augroup,
	pattern = "*",
	callback = function(args)
		if vim.b[args.buf].autoreload and vim.v.fcs_reason == "changed" then
			vim.v.fcs_choice = "reload"
		elseif vim.v.fcs_reason == "changed" or vim.v.fcs_reason == "conflict" then
			vim.v.fcs_choice = "ask"
		end
	end,
})

-- Flip autoreload for the current buffer; check immediately when turning it on so pending changes
-- catch up.
local function toggle_autoreload()
	vim.b.autoreload = not vim.b.autoreload
	vim.notify(
		"Buffer autoreload " .. (vim.b.autoreload and "enabled" or "disabled") .. " for this buffer"
	)
	check_flagged_buffers()
	vim.cmd.redrawstatus()
end

vim.api.nvim_create_user_command(
	"AutoreloadToggle",
	toggle_autoreload,
	{ desc = "Toggle buffer autoreload" }
)