feat: Highlight gitignored files (and fix g:nvim_tree_gitignore) (#268)

This commit is contained in:
Sindre T. Strøm
2021-04-08 22:52:56 +02:00
committed by GitHub
parent 81269a6eba
commit 50d31fb7f3
8 changed files with 224 additions and 52 deletions

View File

@@ -1,7 +1,10 @@
local luv = vim.loop
local utils = require'nvim-tree.utils'
local config = require'nvim-tree.config'
local M = {}
local roots = {}
local fstat_cache = {}
local not_git = 'not a git repo'
local is_win = vim.api.nvim_call_function("has", {"win32"}) == 1
@@ -30,8 +33,18 @@ local function update_root_status(root)
end
end
function M.get_gitexclude()
return vim.fn.system("git ls-files --others --ignored --exclude-standard --directory")
---Returns a list of all ignored files and directories in the given git directory.
---@param git_root string|nil
---@return table
function M.get_gitignored(git_root)
local result = vim.fn.systemlist(
"git -C '" .. (git_root or "") .. "' ls-files --others --ignored --exclude-standard --directory"
)
if result[1] and result[1]:match("^fatal:") then
return {}
end
return result
end
function M.reload_roots()
@@ -69,10 +82,31 @@ local function create_root(cwd)
end
update_root_status(git_root:sub(0, -2))
M.update_gitignore_map()
return true
end
function M.update_status(entries, cwd)
---Get the root of the git dir containing the given path or `nil` if it's not a
---git dir.
---@param path string
---@return string|nil
function M.git_root(path)
local git_root, git_status = get_git_root(path)
if not git_root then
if not create_root(path) then
return
end
git_root, git_status = get_git_root(path)
end
if git_status == not_git then
return
end
return git_root
end
function M.update_status(entries, cwd, parent_node)
local git_root, git_status = get_git_root(cwd)
if not git_root then
if not create_root(cwd) then
@@ -87,30 +121,125 @@ function M.update_status(entries, cwd)
return
end
if not parent_node then parent_node = {} end
local matching_cwd = utils.path_to_matching_str( utils.path_add_trailing(git_root) )
local num_ignored = 0
for _, node in pairs(entries) do
local relpath = node.absolute_path:gsub(matching_cwd, '')
if node.entries ~= nil then
relpath = utils.path_add_trailing(relpath)
node.git_status = nil
end
if parent_node.git_status == "ignored" or M.should_gitignore(node.absolute_path) then
node.git_status = "ignored"
num_ignored = num_ignored + 1
local status = git_status[relpath]
if status then
node.git_status = status
elseif node.entries ~= nil then
local matcher = '^'..utils.path_to_matching_str(relpath)
for key, entry_status in pairs(git_status) do
if key:match(matcher) then
node.git_status = entry_status
break
else
local relpath = node.absolute_path:gsub(matching_cwd, '')
if node.entries ~= nil then
relpath = utils.path_add_trailing(relpath)
node.git_status = nil
end
local status = git_status[relpath]
if status then
node.git_status = status
elseif node.entries ~= nil then
local matcher = '^'..utils.path_to_matching_str(relpath)
for key, entry_status in pairs(git_status) do
if key:match(matcher) then
node.git_status = entry_status
break
end
end
else
node.git_status = nil
end
end
end
if num_ignored > 0 and num_ignored == #entries then
parent_node.git_status = "ignored"
end
end
---A map from git roots to a list of ignored paths
local gitignore_map = {}
---Check if the given path is ignored by git.
---@param path string Absolute path
---@return boolean
function M.should_gitignore(path)
for _, paths in pairs(gitignore_map) do
if paths[path] == true then
return true
end
end
return false
end
---Updates the gitignore map if it's needed. Each entry in the map is only
---updated if changes have been made to the git root's `.gitignore` or
---`.git/info/exclude` files, or it's been invalidated by the
---`invalidate_gitignore_map` function.
function M.update_gitignore_map_sync()
if not (config.get_icon_state().show_git_icon or vim.g.nvim_tree_git_hl == 1) then
return
end
local ignore_files = { ".gitignore", utils.path_join({".git", "info", "exclude"}) }
for git_root, git_status in pairs(roots) do
if git_status ~= not_git then
-- The mtime for `.gitignore` and `.git/info/exclude` is cached such that
-- the list of ignored files is only recreated when one of the said files
-- are modified.
for _, s in ipairs(ignore_files) do
local path = utils.path_join({git_root, s})
local stat = luv.fs_stat(path)
if stat and stat.mtime then
if not (fstat_cache[path]
and fstat_cache[path].mtime == stat.mtime.sec) then
gitignore_map[git_root] = {
_valid = false
}
fstat_cache[path] = {
mtime = stat.mtime.sec
}
end
end
end
else
node.git_status = nil
end
end
for git_root, paths in pairs(gitignore_map) do
if not paths._valid then
gitignore_map[git_root] = {
_valid = true
}
paths = gitignore_map[git_root]
for _, s in ipairs(M.get_gitignored(git_root)) do
if is_win then s = s:gsub("/", "\\") end
s = utils.path_remove_trailing(s)
paths[utils.path_join({git_root, s})] = true
end
end
end
end
---Updates the gitignore map asynchronously if it's needed.
function M.update_gitignore_map()
vim.schedule(function()
M.update_gitignore_map_sync()
end)
end
---Force the ignore list of this path's git root to be recreated on the next
---call to `update_gitignore_map`.
---@param path string Absolute path
function M.invalidate_gitignore_map(path)
local git_root = get_git_root(path)
if git_root and gitignore_map[git_root] then
gitignore_map[git_root]._valid = false
end
end
return M