chore: resolve undefined-field

This commit is contained in:
Alexander Courtis
2024-10-27 17:22:04 +11:00
parent a16e67f3f4
commit 2fba44f8c3
7 changed files with 101 additions and 74 deletions

View File

@@ -178,7 +178,7 @@ local function custom(self, path)
end end
---Prepare arguments for should_filter. This is done prior to should_filter for efficiency reasons. ---Prepare arguments for should_filter. This is done prior to should_filter for efficiency reasons.
---@param git_status table|nil optional results of git.load_project_status(...) ---@param git_status table|nil optional results of git.load_project(...)
---@return table ---@return table
--- git_status: reference --- git_status: reference
--- bufinfo: empty unless no_buffer set: vim.fn.getbufinfo { buflisted = 1 } --- bufinfo: empty unless no_buffer set: vim.fn.getbufinfo { buflisted = 1 }

View File

@@ -321,7 +321,7 @@ function Explorer:refresh_parent_nodes_for_path(path)
local project = git.get_project(toplevel) or {} local project = git.get_project(toplevel) or {}
self:reload(node, project) self:reload(node, project)
git.update_parent_statuses(node, project, toplevel) git.update_parent_projects(node, project, toplevel)
end end
log.profile_end(profile) log.profile_end(profile)
@@ -331,7 +331,7 @@ end
---@param node DirectoryNode ---@param node DirectoryNode
function Explorer:_load(node) function Explorer:_load(node)
local cwd = node.link_to or node.absolute_path local cwd = node.link_to or node.absolute_path
local git_status = git.load_project_status(cwd) local git_status = git.load_project(cwd)
self:explore(node, git_status, self) self:explore(node, git_status, self)
end end
@@ -423,7 +423,7 @@ function Explorer:explore(node, status, parent)
local single_child = node:single_child_directory() local single_child = node:single_child_directory()
if config.renderer.group_empty and not is_root and single_child then if config.renderer.group_empty and not is_root and single_child then
local child_cwd = single_child.link_to or single_child.absolute_path local child_cwd = single_child.link_to or single_child.absolute_path
local child_status = git.load_project_status(child_cwd) local child_status = git.load_project(child_cwd)
node.group_next = single_child node.group_next = single_child
local ns = self:explore(single_child, child_status, parent) local ns = self:explore(single_child, child_status, parent)
node.nodes = ns or {} node.nodes = ns or {}
@@ -463,7 +463,7 @@ function Explorer:reload_explorer()
end end
event_running = true event_running = true
local projects = git.reload() local projects = git.reload_all_projects()
self:refresh_nodes(projects) self:refresh_nodes(projects)
if view.is_visible() then if view.is_visible() then
self.renderer:draw() self.renderer:draw()
@@ -477,7 +477,7 @@ function Explorer:reload_git()
end end
event_running = true event_running = true
local projects = git.reload() local projects = git.reload_all_projects()
git.reload_node_status(self, projects) git.reload_node_status(self, projects)
self.renderer:draw() self.renderer:draw()
event_running = false event_running = false

View File

@@ -7,20 +7,39 @@ local Watcher = require("nvim-tree.watcher").Watcher
local Iterator = require("nvim-tree.iterators.node-iterator") local Iterator = require("nvim-tree.iterators.node-iterator")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
---@class (exact) GitStatus -- xy short-format statuses ---Git xy short-format statuses for a single node
---@class (exact) GitStatus
---@field file string? ---@field file string?
---@field dir table<"direct" | "indirect", string[]>? ---@field dir table<"direct" | "indirect", string[]>?
-- Git xy short-format status
---@alias GitPathXY table<string, string>
-- Git xy short-format statuses
---@alias GitPathXYs table<string, string[]>
---@alias GitProjectFiles GitPathXY
---@alias GitProjectDirs table<"direct" | "indirect", GitPathXYs>
---Git state for an entire repo
---@class (exact) GitProject
---@field files GitProjectFiles?
---@field dirs GitProjectDirs?
---@field watcher Watcher?
local M = { local M = {
config = {}, config = {},
-- all projects keyed by toplevel ---all projects keyed by toplevel
---@type table<string, GitProject>
_projects_by_toplevel = {}, _projects_by_toplevel = {},
-- index of paths inside toplevels, false when not inside a project ---index of paths inside toplevels, false when not inside a project
---@type table<string, string|false>
_toplevels_by_path = {}, _toplevels_by_path = {},
-- git dirs by toplevel -- git dirs by toplevel
---@type table<string, string>
_git_dirs_by_toplevel = {}, _git_dirs_by_toplevel = {},
} }
@@ -36,33 +55,33 @@ local WATCHED_FILES = {
---@param toplevel string|nil ---@param toplevel string|nil
---@param path string|nil ---@param path string|nil
---@param project table ---@param project GitProject
---@param statuses GitXYByPath? ---@param project_files GitProjectFiles?
local function reload_git_statuses(toplevel, path, project, statuses) local function reload_git_project(toplevel, path, project, project_files)
if path then if path then
for p in pairs(project.files) do for p in pairs(project.files) do
if p:find(path, 1, true) == 1 then if p:find(path, 1, true) == 1 then
project.files[p] = nil project.files[p] = nil
end end
end end
project.files = vim.tbl_deep_extend("force", project.files, statuses) project.files = vim.tbl_deep_extend("force", project.files, project_files)
else else
project.files = statuses project.files = project_files or {}
end end
project.dirs = git_utils.file_status_to_dir_status(project.files, toplevel) project.dirs = git_utils.project_files_to_project_dirs(project.files, toplevel)
end end
--- Is this path in a known ignored directory? --- Is this path in a known ignored directory?
---@param path string ---@param path string
---@param project table git status ---@param project GitProject
---@return boolean ---@return boolean
local function path_ignored_in_project(path, project) local function path_ignored_in_project(path, project)
if not path or not project then if not path or not project then
return false return false
end end
if project and project.files then if project.files then
for file, status in pairs(project.files) do for file, status in pairs(project.files) do
if status == "!!" and vim.startswith(path, file) then if status == "!!" and vim.startswith(path, file) then
return true return true
@@ -72,9 +91,8 @@ local function path_ignored_in_project(path, project)
return false return false
end end
--- Reload all projects ---@return GitProject[] maybe empty
---@return table projects maybe empty function M.reload_all_projects()
function M.reload()
if not M.config.git.enable then if not M.config.git.enable then
return {} return {}
end end
@@ -87,11 +105,12 @@ function M.reload()
end end
--- Reload one project. Does nothing when no project or path is ignored --- Reload one project. Does nothing when no project or path is ignored
---@param toplevel string|nil ---@param toplevel string?
---@param path string|nil optional path to update only ---@param path string? optional path to update only
---@param callback function|nil ---@param callback function?
function M.reload_project(toplevel, path, callback) function M.reload_project(toplevel, path, callback)
local project = M._projects_by_toplevel[toplevel] local project = M._projects_by_toplevel[toplevel] --[[@as GitProject]]
if not toplevel or not project or not M.config.git.enable then if not toplevel or not project or not M.config.git.enable then
if callback then if callback then
callback() callback()
@@ -116,21 +135,21 @@ function M.reload_project(toplevel, path, callback)
} }
if callback then if callback then
---@param statuses GitXYByPath ---@param statuses GitPathXY
runner_opts.callback = function(statuses) runner_opts.callback = function(statuses)
reload_git_statuses(toplevel, path, project, statuses) reload_git_project(toplevel, path, project, statuses)
callback() callback()
end end
GitRunner:run(runner_opts) GitRunner:run(runner_opts)
else else
-- TODO #1974 use callback once async/await is available -- TODO #1974 use callback once async/await is available
reload_git_statuses(toplevel, path, project, GitRunner:run(runner_opts)) reload_git_project(toplevel, path, project, GitRunner:run(runner_opts))
end end
end end
--- Retrieve a known project --- Retrieve a known project
---@param toplevel string|nil ---@param toplevel string?
---@return table|nil project ---@return GitProject? project
function M.get_project(toplevel) function M.get_project(toplevel)
return M._projects_by_toplevel[toplevel] return M._projects_by_toplevel[toplevel]
end end
@@ -151,11 +170,10 @@ function M.get_toplevel(path)
return nil return nil
end end
if M._toplevels_by_path[path] then local tl = M._toplevels_by_path[path]
return M._toplevels_by_path[path] if tl then
end return tl
elseif tl == false then
if M._toplevels_by_path[path] == false then
return nil return nil
end end
@@ -194,8 +212,15 @@ function M.get_toplevel(path)
end end
M._toplevels_by_path[path] = toplevel M._toplevels_by_path[path] = toplevel
M._git_dirs_by_toplevel[toplevel] = git_dir M._git_dirs_by_toplevel[toplevel] = git_dir
return M._toplevels_by_path[path]
toplevel = M._toplevels_by_path[path]
if toplevel == false then
return nil
else
return toplevel
end
end end
local function reload_tree_at(toplevel) local function reload_tree_at(toplevel)
@@ -230,8 +255,8 @@ end
--- Load the project status for a path. Does nothing when no toplevel for path. --- Load the project status for a path. Does nothing when no toplevel for path.
--- Only fetches project status when unknown, otherwise returns existing. --- Only fetches project status when unknown, otherwise returns existing.
---@param path string absolute ---@param path string absolute
---@return table project maybe empty ---@return GitProject maybe empty
function M.load_project_status(path) function M.load_project(path)
if not M.config.git.enable then if not M.config.git.enable then
return {} return {}
end end
@@ -242,12 +267,12 @@ function M.load_project_status(path)
return {} return {}
end end
local status = M._projects_by_toplevel[toplevel] local project = M._projects_by_toplevel[toplevel]
if status then if project then
return status return project
end end
local statuses = GitRunner:run({ local path_xys = GitRunner:run({
toplevel = toplevel, toplevel = toplevel,
list_untracked = git_utils.should_show_untracked(toplevel), list_untracked = git_utils.should_show_untracked(toplevel),
list_ignored = true, list_ignored = true,
@@ -275,10 +300,10 @@ function M.load_project_status(path)
}) })
end end
if statuses then if path_xys then
M._projects_by_toplevel[toplevel] = { M._projects_by_toplevel[toplevel] = {
files = statuses, files = path_xys,
dirs = git_utils.file_status_to_dir_status(statuses, toplevel), dirs = git_utils.project_files_to_project_dirs(path_xys, toplevel),
watcher = watcher, watcher = watcher,
} }
return M._projects_by_toplevel[toplevel] return M._projects_by_toplevel[toplevel]
@@ -289,9 +314,9 @@ function M.load_project_status(path)
end end
---@param dir DirectoryNode ---@param dir DirectoryNode
---@param project table? ---@param project GitProject?
---@param root string? ---@param root string?
function M.update_parent_statuses(dir, project, root) function M.update_parent_projects(dir, project, root)
while project and dir do while project and dir do
-- step up to the containing project -- step up to the containing project
if dir.absolute_path == root then if dir.absolute_path == root then
@@ -331,14 +356,14 @@ function M.refresh_dir(dir)
dir.explorer:reload(node, project) dir.explorer:reload(node, project)
M.update_parent_statuses(dir, project, toplevel) M.update_parent_projects(dir, project, toplevel)
dir.explorer.renderer:draw() dir.explorer.renderer:draw()
end) end)
end end
---@param dir DirectoryNode? ---@param dir DirectoryNode?
---@param projects table ---@param projects GitProject[]
function M.reload_node_status(dir, projects) function M.reload_node_status(dir, projects)
dir = dir and dir:as(DirectoryNode) dir = dir and dir:as(DirectoryNode)
if not dir or #dir.nodes == 0 then if not dir or #dir.nodes == 0 then

View File

@@ -4,19 +4,17 @@ local notify = require("nvim-tree.notify")
local Class = require("nvim-tree.class") local Class = require("nvim-tree.class")
---@alias GitXYByPath table<string, string> -- short-format statuses
---@class (exact) GitRunnerOpts ---@class (exact) GitRunnerOpts
---@field toplevel string absolute path ---@field toplevel string absolute path
---@field path string? absolute path ---@field path string? absolute path
---@field list_untracked boolean ---@field list_untracked boolean
---@field list_ignored boolean ---@field list_ignored boolean
---@field timeout integer ---@field timeout integer
---@field callback fun(statuses: GitXYByPath)? ---@field callback fun(statuses: GitPathXY)?
---@class (exact) GitRunner: Class ---@class (exact) GitRunner: Class
---@field private opts GitRunnerOpts ---@field private opts GitRunnerOpts
---@field private statuses GitXYByPath ---@field private statuses GitPathXY
---@field private rc integer? -- -1 indicates timeout ---@field private rc integer? -- -1 indicates timeout
local GitRunner = Class:new() local GitRunner = Class:new()
@@ -207,7 +205,8 @@ function GitRunner:finalise()
end end
end end
---@return GitXYByPath? statuses nil if callback present ---@private
---@return GitPathXY? statuses nil if callback present
function GitRunner:execute() function GitRunner:execute()
local async = self.opts.callback ~= nil local async = self.opts.callback ~= nil
local profile = log.profile_start("git %s job %s %s", async and "async" or "sync", self.opts.toplevel, self.opts.path) local profile = log.profile_start("git %s job %s %s", async and "async" or "sync", self.opts.toplevel, self.opts.path)
@@ -240,7 +239,7 @@ end
---Static method to run a git process, which will be killed if it takes more than timeout ---Static method to run a git process, which will be killed if it takes more than timeout
---@param opts GitRunnerOpts ---@param opts GitRunnerOpts
---@return GitXYByPath? statuses nil if callback present ---@return GitPathXY? statuses nil if callback present
function GitRunner:run(opts) function GitRunner:run(opts)
---@type GitRunner ---@type GitRunner
local runner = { local runner = {

View File

@@ -82,8 +82,8 @@ function M.should_show_untracked(cwd)
return untracked[cwd] return untracked[cwd]
end end
---@param t table|nil ---@param t table<string|integer, boolean>?
---@param k string ---@param k string|integer
---@return table ---@return table
local function nil_insert(t, k) local function nil_insert(t, k)
t = t or {} t = t or {}
@@ -91,31 +91,33 @@ local function nil_insert(t, k)
return t return t
end end
---@param status table ---@param project_files GitProjectFiles
---@param cwd string|nil ---@param cwd string|nil
---@return table ---@return GitProjectDirs
function M.file_status_to_dir_status(status, cwd) function M.project_files_to_project_dirs(project_files, cwd)
local direct = {} ---@type GitProjectDirs
for p, s in pairs(status) do local project_dirs = {}
project_dirs.direct = {}
for p, s in pairs(project_files) do
if s ~= "!!" then if s ~= "!!" then
local modified = vim.fn.fnamemodify(p, ":h") local modified = vim.fn.fnamemodify(p, ":h")
direct[modified] = nil_insert(direct[modified], s) project_dirs.direct[modified] = nil_insert(project_dirs.direct[modified], s)
end end
end end
local indirect = {} project_dirs.indirect = {}
for dirname, statuses in pairs(direct) do for dirname, statuses in pairs(project_dirs.direct) do
for s, _ in pairs(statuses) do for s, _ in pairs(statuses) do
local modified = dirname local modified = dirname
while modified ~= cwd and modified ~= "/" do while modified ~= cwd and modified ~= "/" do
modified = vim.fn.fnamemodify(modified, ":h") modified = vim.fn.fnamemodify(modified, ":h")
indirect[modified] = nil_insert(indirect[modified], s) project_dirs.indirect[modified] = nil_insert(project_dirs.indirect[modified], s)
end end
end end
end end
local r = { indirect = indirect, direct = direct } for _, d in pairs(project_dirs) do
for _, d in pairs(r) do
for dirname, statuses in pairs(d) do for dirname, statuses in pairs(d) do
local new_statuses = {} local new_statuses = {}
for s, _ in pairs(statuses) do for s, _ in pairs(statuses) do
@@ -124,7 +126,8 @@ function M.file_status_to_dir_status(status, cwd)
d[dirname] = new_statuses d[dirname] = new_statuses
end end
end end
return r
return project_dirs
end end
---Git file status for an absolute path with optional fallback ---Git file status for an absolute path with optional fallback

View File

@@ -36,9 +36,9 @@ function DirectoryLinkNode:destroy()
DirectoryNode.destroy(self) DirectoryNode.destroy(self)
end end
-----Update the directory GitStatus of link target and the file status of the link itself ---Update the directory GitStatus of link target and the file status of the link itself
-----@param parent_ignored boolean ---@param parent_ignored boolean
-----@param status table|nil ---@param status table|nil
function DirectoryLinkNode:update_git_status(parent_ignored, status) function DirectoryLinkNode:update_git_status(parent_ignored, status)
self.git_status = git_utils.git_status_dir(parent_ignored, status, self.link_to, self.absolute_path) self.git_status = git_utils.git_status_dir(parent_ignored, status, self.link_to, self.absolute_path)
end end

View File

@@ -32,9 +32,9 @@ function FileLinkNode:destroy()
FileNode.destroy(self) FileNode.destroy(self)
end end
-----Update the GitStatus of the target otherwise the link itself ---Update the GitStatus of the target otherwise the link itself
-----@param parent_ignored boolean ---@param parent_ignored boolean
-----@param status table|nil ---@param status table|nil
function FileLinkNode:update_git_status(parent_ignored, status) function FileLinkNode:update_git_status(parent_ignored, status)
self.git_status = git_utils.git_status_file(parent_ignored, status, self.link_to, self.absolute_path) self.git_status = git_utils.git_status_file(parent_ignored, status, self.link_to, self.absolute_path)
end end