Neovim sign gutter, designed to be mostly VCS agnostic
1local M = {}
2
3local diff = require "vcsigns.diff"
4local high = require "vcsigns.high"
5local repo = require "vcsigns.repo"
6local sign = require "vcsigns.sign"
7local state = require "vcsigns.state"
8local util = require "vcsigns.util"
9local ignore = require "vclib.ignore"
10
11--- Cheap update assuming the old file contents are still fresh.
12---@param bufnr integer The buffer number.
13function M.shallow_update(bufnr)
14 local buffer_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
15 local s = state.get(bufnr)
16 local old_lines = s.diff.old_lines
17
18 if not old_lines then
19 util.verbose "No old contents available, skipping diff."
20 return
21 end
22
23 local path = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(bufnr), ":f")
24 if
25 vim.g.vcsigns_respect_gitignore
26 and #old_lines == 0
27 and ignore.is_ignored(path)
28 then
29 -- Rough proxy for checking if file is ignored and not tracked.
30 util.verbose "File ignored and had no previous contents, skipping diff."
31 return
32 end
33
34 local hunks = diff.compute_diff(old_lines, buffer_lines)
35 s.diff.hunks = hunks
36 sign.add_signs(bufnr, hunks)
37
38 if vim.b[bufnr].vcsigns_show_hunk_diffs then
39 high.highlight_hunks(bufnr, hunks)
40 else
41 high.highlight_hunks(bufnr, {})
42 end
43end
44
45---@param bufnr integer The buffer number.
46---@param vcs Vcs The VCS object for the buffer.
47---@param cb fun(bufnr: integer) Callback to call after the old file contents are refreshed.
48local function _refresh_old_file_contents(bufnr, vcs, cb)
49 local start_time = vim.uv.now() ---@diagnostic disable-line: undefined-field
50
51 repo.show_file(bufnr, vcs, function(old_lines)
52 if not old_lines then
53 -- Some kind of failure, skip the diff.
54 return
55 end
56 local s = state.get(bufnr)
57 local last = s.diff.last_update
58 if start_time <= last then
59 util.verbose "Skipping updating old file, we already have a newer update."
60 return
61 end
62 s.diff.old_lines = old_lines
63 s.diff.last_update = start_time
64 s.diff.hunks_changedtick = vim.b[bufnr].changedtick
65 cb(bufnr)
66 end)
67end
68
69---@param bufnr integer The buffer number.
70---@return Vcs|nil The VCS object if ready, nil otherwise.
71local function _get_vcs_if_ready(bufnr)
72 local vcs_state = state.get(bufnr).vcs
73 local detecting = vcs_state.detecting
74 if detecting == nil then
75 util.verbose "Buffer not initialized yet."
76 end
77 if detecting then
78 util.verbose "Busy detecting, skipping."
79 return
80 end
81 return vcs_state.vcs
82end
83
84--- Expensive update including vcs querying for file contents.
85---@param bufnr integer The buffer number.
86function M.deep_update(bufnr, clear_ignore_cache)
87 if clear_ignore_cache then
88 ignore.clear_ignored_cache()
89 end
90 if vim.bo[bufnr].buftype ~= "" then
91 -- Not a normal file buffer, don't do anything.
92 util.verbose "Not a normal file buffer, skipping."
93 return
94 end
95 local vcs = _get_vcs_if_ready(bufnr)
96 if not vcs then
97 util.verbose "No VCS detected for buffer, skipping."
98 return
99 end
100
101 _refresh_old_file_contents(bufnr, vcs, M.shallow_update)
102end
103
104return M