From 2a98d56c3ee1a68e93b68b94e110a4e89dd5740f Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Fri, 25 Oct 2024 19:32:57 +1100 Subject: [PATCH] chore: resolve undefined-field --- lua/nvim-tree/git/init.lua | 32 +++++---- lua/nvim-tree/git/runner.lua | 136 ++++++++++++++++++++--------------- lua/nvim-tree/git/utils.lua | 3 +- 3 files changed, 96 insertions(+), 75 deletions(-) diff --git a/lua/nvim-tree/git/init.lua b/lua/nvim-tree/git/init.lua index 7ec8f396..7fd03a02 100644 --- a/lua/nvim-tree/git/init.lua +++ b/lua/nvim-tree/git/init.lua @@ -1,7 +1,7 @@ local log = require("nvim-tree.log") local utils = require("nvim-tree.utils") local git_utils = require("nvim-tree.git.utils") -local Runner = require("nvim-tree.git.runner") +local runner = require("nvim-tree.git.runner") local Watcher = require("nvim-tree.watcher").Watcher local Iterator = require("nvim-tree.iterators.node-iterator") local DirectoryNode = nil -- circular dependency @@ -36,17 +36,17 @@ local WATCHED_FILES = { ---@param toplevel string|nil ---@param path string|nil ---@param project table ----@param git_status table|nil -local function reload_git_status(toplevel, path, project, git_status) +---@param statuses GitStatusesXYByPath? +local function reload_git_statuses(toplevel, path, project, statuses) if path then for p in pairs(project.files) do if p:find(path, 1, true) == 1 then project.files[p] = nil end end - project.files = vim.tbl_deep_extend("force", project.files, git_status) + project.files = vim.tbl_deep_extend("force", project.files, statuses) else - project.files = git_status + project.files = statuses end project.dirs = git_utils.file_status_to_dir_status(project.files, toplevel) @@ -105,7 +105,8 @@ function M.reload_project(toplevel, path, callback) return end - local opts = { + ---@type RunnerOpts + local runner_opts = { toplevel = toplevel, path = path, list_untracked = git_utils.should_show_untracked(toplevel), @@ -114,14 +115,15 @@ function M.reload_project(toplevel, path, callback) } if callback then - Runner.run(opts, function(git_status) - reload_git_status(toplevel, path, project, git_status) + ---@param statuses GitStatusesXYByPath + runner_opts.callback = function(statuses) + reload_git_statuses(toplevel, path, project, statuses) callback() - end) + end + runner(runner_opts) else -- TODO #1974 use callback once async/await is available - local git_status = Runner.run(opts) - reload_git_status(toplevel, path, project, git_status) + reload_git_statuses(toplevel, path, project, runner(runner_opts)) end end @@ -246,7 +248,7 @@ function M.load_project_status(path) return status end - local git_status = Runner.run({ + local statuses = runner({ toplevel = toplevel, list_untracked = git_utils.should_show_untracked(toplevel), list_ignored = true, @@ -273,10 +275,10 @@ function M.load_project_status(path) }) end - if git_status then + if statuses then M._projects_by_toplevel[toplevel] = { - files = git_status, - dirs = git_utils.file_status_to_dir_status(git_status, toplevel), + files = statuses, + dirs = git_utils.file_status_to_dir_status(statuses, toplevel), watcher = watcher, } return M._projects_by_toplevel[toplevel] diff --git a/lua/nvim-tree/git/runner.lua b/lua/nvim-tree/git/runner.lua index 06235ea5..1a9000f9 100644 --- a/lua/nvim-tree/git/runner.lua +++ b/lua/nvim-tree/git/runner.lua @@ -2,9 +2,23 @@ local log = require("nvim-tree.log") local utils = require("nvim-tree.utils") local notify = require("nvim-tree.notify") ----@class Runner -local Runner = {} -Runner.__index = Runner +local Class = require("nvim-tree.class") + +---@alias GitStatusesXYByPath table + +---@class (exact) RunnerOpts +---@field toplevel string absolute path +---@field path string? absolute path +---@field list_untracked boolean +---@field list_ignored boolean +---@field timeout integer +---@field callback fun(statuses: GitStatusesXYByPath)? + +---@class (exact) Runner: Class +---@field opts RunnerOpts +---@field statuses GitStatusesXYByPath +---@field rc integer? -- -1 indicates timeout +local Runner = Class:new() local timeouts = 0 local MAX_TIMEOUTS = 5 @@ -12,7 +26,7 @@ local MAX_TIMEOUTS = 5 ---@private ---@param status string ---@param path string|nil -function Runner:_parse_status_output(status, path) +function Runner:parse_status_output(status, path) if not path then return end @@ -22,7 +36,7 @@ function Runner:_parse_status_output(status, path) path = path:gsub("/", "\\") end if #status > 0 and #path > 0 then - self.output[utils.path_remove_trailing(utils.path_join({ self.toplevel, path }))] = status + self.statuses[utils.path_remove_trailing(utils.path_join({ self.opts.toplevel, path }))] = status end end @@ -30,7 +44,7 @@ end ---@param prev_output string ---@param incoming string ---@return string -function Runner:_handle_incoming_data(prev_output, incoming) +function Runner:handle_incoming_data(prev_output, incoming) if incoming and utils.str_find(incoming, "\n") then local prev = prev_output .. incoming local i = 1 @@ -45,7 +59,7 @@ function Runner:_handle_incoming_data(prev_output, incoming) -- skip next line if it is a rename entry skip_next_line = true end - self:_parse_status_output(status, path) + self:parse_status_output(status, path) end i = i + #line end @@ -58,35 +72,38 @@ function Runner:_handle_incoming_data(prev_output, incoming) end for line in prev_output:gmatch("[^\n]*\n") do - self:_parse_status_output(line) + self:parse_status_output(line) end return "" end +---@private ---@param stdout_handle uv.uv_pipe_t ---@param stderr_handle uv.uv_pipe_t ----@return table -function Runner:_getopts(stdout_handle, stderr_handle) - local untracked = self.list_untracked and "-u" or nil - local ignored = (self.list_untracked and self.list_ignored) and "--ignored=matching" or "--ignored=no" +---@return uv.spawn.options +function Runner:get_spawn_options(stdout_handle, stderr_handle) + local untracked = self.opts.list_untracked and "-u" or nil + local ignored = (self.opts.list_untracked and self.opts.list_ignored) and "--ignored=matching" or "--ignored=no" return { - args = { "--no-optional-locks", "status", "--porcelain=v1", "-z", ignored, untracked, self.path }, - cwd = self.toplevel, + args = { "--no-optional-locks", "status", "--porcelain=v1", "-z", ignored, untracked, self.opts.path }, + cwd = self.opts.toplevel, stdio = { nil, stdout_handle, stderr_handle }, } end +---@private ---@param output string -function Runner:_log_raw_output(output) +function Runner:log_raw_output(output) if log.enabled("git") and output and type(output) == "string" then log.raw("git", "%s", output) log.line("git", "done") end end +---@private ---@param callback function|nil -function Runner:_run_git_job(callback) +function Runner:run_git_job(callback) local handle, pid local stdout = vim.loop.new_pipe(false) local stderr = vim.loop.new_pipe(false) @@ -123,20 +140,20 @@ function Runner:_run_git_job(callback) end end - local opts = self:_getopts(stdout, stderr) - log.line("git", "running job with timeout %dms", self.timeout) - log.line("git", "git %s", table.concat(utils.array_remove_nils(opts.args), " ")) + local spawn_options = self:get_spawn_options(stdout, stderr) + log.line("git", "running job with timeout %dms", self.opts.timeout) + log.line("git", "git %s", table.concat(utils.array_remove_nils(spawn_options.args), " ")) handle, pid = vim.loop.spawn( "git", - opts, + spawn_options, vim.schedule_wrap(function(rc) on_finish(rc) end) ) timer:start( - self.timeout, + self.opts.timeout, 0, vim.schedule_wrap(function() on_finish(-1) @@ -151,19 +168,20 @@ function Runner:_run_git_job(callback) if data then data = data:gsub("%z", "\n") end - self:_log_raw_output(data) - output_leftover = self:_handle_incoming_data(output_leftover, data) + self:log_raw_output(data) + output_leftover = self:handle_incoming_data(output_leftover, data) end local function manage_stderr(_, data) - self:_log_raw_output(data) + self:log_raw_output(data) end vim.loop.read_start(stdout, vim.schedule_wrap(manage_stdout)) vim.loop.read_start(stderr, vim.schedule_wrap(manage_stderr)) end -function Runner:_wait() +---@private +function Runner:wait() local function is_done() return self.rc ~= nil end @@ -172,64 +190,64 @@ function Runner:_wait() end end ----@param opts table -function Runner:_finalise(opts) +---@private +function Runner:finalise() if self.rc == -1 then - log.line("git", "job timed out %s %s", opts.toplevel, opts.path) + log.line("git", "job timed out %s %s", self.opts.toplevel, self.opts.path) timeouts = timeouts + 1 if timeouts == MAX_TIMEOUTS then - notify.warn(string.format("%d git jobs have timed out after git.timeout %dms, disabling git integration.", timeouts, opts.timeout)) + notify.warn(string.format("%d git jobs have timed out after git.timeout %dms, disabling git integration.", timeouts, + self.opts.timeout)) require("nvim-tree.git").disable_git_integration() end elseif self.rc ~= 0 then - log.line("git", "job fail rc %d %s %s", self.rc, opts.toplevel, opts.path) + log.line("git", "job fail rc %d %s %s", self.rc, self.opts.toplevel, self.opts.path) else - log.line("git", "job success %s %s", opts.toplevel, opts.path) + log.line("git", "job success %s %s", self.opts.toplevel, self.opts.path) end end ---- Runs a git process, which will be killed if it takes more than timeout which defaults to 400ms ----@param opts table ----@param callback function|nil executed passing return when complete ----@return table|nil status by absolute path, nil if callback present -function Runner.run(opts, callback) - local self = setmetatable({ - toplevel = opts.toplevel, - path = opts.path, - list_untracked = opts.list_untracked, - list_ignored = opts.list_ignored, - timeout = opts.timeout or 400, - output = {}, - rc = nil, -- -1 indicates timeout - }, Runner) +---@return GitStatusesXYByPath? statuses nil if callback present +function Runner:run() + 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 async = callback ~= nil - local profile = log.profile_start("git %s job %s %s", async and "async" or "sync", opts.toplevel, opts.path) - - if async and callback then + if async and self.opts.callback then -- async, always call back - self:_run_git_job(function() + self:run_git_job(function() log.profile_end(profile) - self:_finalise(opts) + self:finalise() - callback(self.output) + self.opts.callback(self.statuses) end) else -- sync, maybe call back - self:_run_git_job() - self:_wait() + self:run_git_job() + self:wait() log.profile_end(profile) - self:_finalise(opts) + self:finalise() - if callback then - callback(self.output) + if self.opts.callback then + self.opts.callback(self.statuses) else - return self.output + return self.statuses end end end -return Runner +---Runs a git process, which will be killed if it takes more than timeout which defaults to 400ms +---@param opts RunnerOpts +---@return GitStatusesXYByPath? statuses nil if callback present +return function(opts) + ---@type Runner + local runner = { + opts = opts, + statuses = {}, + } + runner = Runner:new(runner) --[[@as Runner]] + + return runner:run() +end diff --git a/lua/nvim-tree/git/utils.lua b/lua/nvim-tree/git/utils.lua index 6a32b632..8caaf73a 100644 --- a/lua/nvim-tree/git/utils.lua +++ b/lua/nvim-tree/git/utils.lua @@ -58,10 +58,11 @@ function M.get_toplevel(cwd) return toplevel, git_dir end +---@type table local untracked = {} ---@param cwd string ----@return string|nil +---@return boolean function M.should_show_untracked(cwd) if untracked[cwd] ~= nil then return untracked[cwd]