From d5aefeb8253f067a4902ca37fb0e433fb900a0eb Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Mon, 27 Mar 2023 15:27:59 +1100 Subject: [PATCH 1/7] async git watcher reload; callback hell for now --- lua/nvim-tree.lua | 5 + lua/nvim-tree/explorer/reload.lua | 21 ++++ lua/nvim-tree/explorer/watch.lua | 11 +- lua/nvim-tree/git/init.lua | 47 ++++++-- lua/nvim-tree/git/runner.lua | 179 ++++++++++++++++++++---------- 5 files changed, 194 insertions(+), 69 deletions(-) diff --git a/lua/nvim-tree.lua b/lua/nvim-tree.lua index 3fd4d697791..a36dd1128e2 100644 --- a/lua/nvim-tree.lua +++ b/lua/nvim-tree.lua @@ -674,6 +674,11 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS trash = true, }, }, + experimental = { + git = { + async = false, + }, + }, log = { enable = false, truncate = false, diff --git a/lua/nvim-tree/explorer/reload.lua b/lua/nvim-tree/explorer/reload.lua index b42d27cd314..c422028a2d9 100644 --- a/lua/nvim-tree/explorer/reload.lua +++ b/lua/nvim-tree/explorer/reload.lua @@ -27,6 +27,13 @@ local function reload_and_get_git_project(path) return project_root, git.get_project(project_root) or {} end +local function reload_and_get_git_project_async(path, callback) + local project_root = git.get_project_root(path) + git.reload_project_async(project_root, path, function() + callback(project_root, git.get_project(project_root) or {}) + end) +end + local function update_parent_statuses(node, project, root) while project and node and node.absolute_path ~= root do explorer_node.update_git_status(node, false, project) @@ -156,6 +163,20 @@ function M.refresh_node(node) update_parent_statuses(parent_node, project, project_root) end +function M.refresh_node_async(node, callback) + if type(node) ~= "table" then + return + end + + local parent_node = utils.get_parent_of_group(node) + + reload_and_get_git_project_async(node.absolute_path, function(project_root, project) + require("nvim-tree.explorer.reload").reload(parent_node, project) + update_parent_statuses(parent_node, project, project_root) + callback() + end) +end + ---Refresh contents and git status for all nodes to a path: actual directory and links ---@param path string absolute path function M.refresh_nodes_for_path(path) diff --git a/lua/nvim-tree/explorer/watch.lua b/lua/nvim-tree/explorer/watch.lua index e80d8829176..460b4270ac2 100644 --- a/lua/nvim-tree/explorer/watch.lua +++ b/lua/nvim-tree/explorer/watch.lua @@ -59,8 +59,14 @@ function M.create_watcher(node) else log.line("watcher", "node event executing refresh '%s'", node.absolute_path) end - require("nvim-tree.explorer.reload").refresh_node(node) - require("nvim-tree.renderer").draw() + if M.git_async then + require("nvim-tree.explorer.reload").refresh_node_async(node, function() + require("nvim-tree.renderer").draw() + end) + else + require("nvim-tree.explorer.reload").refresh_node(node) + require("nvim-tree.renderer").draw() + end end) end @@ -74,6 +80,7 @@ function M.setup(opts) M.enabled = opts.filesystem_watchers.enable M.debounce_delay = opts.filesystem_watchers.debounce_delay M.ignore_dirs = opts.filesystem_watchers.ignore_dirs + M.git_async = opts.experimental.git.async M.uid = 0 end diff --git a/lua/nvim-tree/git/init.lua b/lua/nvim-tree/git/init.lua index c5be4ef02f1..2c39d36af4f 100644 --- a/lua/nvim-tree/git/init.lua +++ b/lua/nvim-tree/git/init.lua @@ -22,6 +22,22 @@ local WATCHED_FILES = { "index", -- staging area } +-- TODO fold back into reload_project following git async experiment completion +local function reload_git_status(project_root, path, project, git_status) + 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) + else + project.files = git_status + end + + project.dirs = git_utils.file_status_to_dir_status(project.files, project_root) +end + function M.reload() if not M.config.git.enable then return {} @@ -52,18 +68,29 @@ function M.reload_project(project_root, path) timeout = M.config.git.timeout, } - 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) - else - project.files = git_status + reload_git_status(project_root, path, project, git_status) +end + +function M.reload_project_async(project_root, path, callback) + local project = M.projects[project_root] + if not project or not M.config.git.enable then + return end - project.dirs = git_utils.file_status_to_dir_status(project.files, project_root) + if path and path:find(project_root, 1, true) ~= 1 then + return + end + + Runner.run_async({ + project_root = project_root, + path = path, + list_untracked = git_utils.should_show_untracked(project_root), + list_ignored = true, + timeout = M.config.git.timeout, + }, function(git_status) + reload_git_status(project_root, path, project, git_status) + callback() + end) end function M.get_project(project_root) diff --git a/lua/nvim-tree/git/runner.lua b/lua/nvim-tree/git/runner.lua index 6c31995b3ee..959dcda7be3 100644 --- a/lua/nvim-tree/git/runner.lua +++ b/lua/nvim-tree/git/runner.lua @@ -69,68 +69,106 @@ function Runner:_log_raw_output(output) end end -function Runner:_run_git_job() - local handle, pid - local stdout = vim.loop.new_pipe(false) - local stderr = vim.loop.new_pipe(false) - local timer = vim.loop.new_timer() - - local function on_finish(rc) - self.rc = rc or 0 - if timer:is_closing() or stdout:is_closing() or stderr:is_closing() or (handle and handle:is_closing()) then +function Runner:_on_finish(rc) + self.rc = rc or 0 + if + self.timer:is_closing() + or self.stdout:is_closing() + or self.stderr:is_closing() + or (self.handle and self.handle:is_closing()) + then + return + end + self.timer:stop() + self.timer:close() + self.stdout:read_stop() + self.stderr:read_stop() + self.stdout:close() + self.stderr:close() + if self.handle then + self.handle:close() + end + + pcall(vim.loop.kill, self.pid) +end + +function Runner:_start_readers() + local output_leftover = "" + local function manage_stdout(err, data) + if err then return end - timer:stop() - timer:close() - stdout:read_stop() - stderr:read_stop() - stdout:close() - stderr:close() - if handle then - handle:close() + if data then + data = data:gsub("%z", "\n") end + self:_log_raw_output(data) + output_leftover = self:_handle_incoming_data(output_leftover, data) + end - pcall(vim.loop.kill, pid) + local function manage_stderr(_, data) + self:_log_raw_output(data) end - local opts = self:_getopts(stdout, stderr) + vim.loop.read_start(self.stdout, vim.schedule_wrap(manage_stdout)) + vim.loop.read_start(self.stderr, vim.schedule_wrap(manage_stderr)) +end + +function Runner:_run_git_job() + self.stdout = vim.loop.new_pipe(false) + self.stderr = vim.loop.new_pipe(false) + self.timer = vim.loop.new_timer() + + local opts = self:_getopts(self.stdout, self.stderr) log.line("git", "running job with timeout %dms", self.timeout) log.line("git", "git %s", table.concat(utils.array_remove_nils(opts.args), " ")) - handle, pid = vim.loop.spawn( + self.handle, self.pid = vim.loop.spawn( "git", opts, vim.schedule_wrap(function(rc) - on_finish(rc) + self:_on_finish(rc) end) ) - timer:start( + self.timer:start( self.timeout, 0, vim.schedule_wrap(function() - on_finish(-1) + self:_on_finish(-1) end) ) - local output_leftover = "" - local function manage_stdout(err, data) - if err then - return - end - if data then - data = data:gsub("%z", "\n") - end - self:_log_raw_output(data) - output_leftover = self:_handle_incoming_data(output_leftover, data) - end + self:_start_readers() +end - local function manage_stderr(_, data) - self:_log_raw_output(data) - end +function Runner:_run_git_job_async(callback) + self.stdout = vim.loop.new_pipe(false) + self.stderr = vim.loop.new_pipe(false) + self.timer = vim.loop.new_timer() + + local opts = self:_getopts(self.stdout, self.stderr) + log.line("git", "running async job with timeout %dms", self.timeout) + log.line("git", "git %s", table.concat(utils.array_remove_nils(opts.args), " ")) - vim.loop.read_start(stdout, vim.schedule_wrap(manage_stdout)) - vim.loop.read_start(stderr, vim.schedule_wrap(manage_stderr)) + self.handle, self.pid = vim.loop.spawn( + "git", + opts, + vim.schedule_wrap(function(rc) + self:_on_finish(rc) + callback() + end) + ) + + self.timer:start( + self.timeout, + 0, + vim.schedule_wrap(function() + self:_on_finish(-1) + callback() + end) + ) + + self:_start_readers() end function Runner:_wait() @@ -142,6 +180,28 @@ function Runner:_wait() end end +-- TODO fold back into run following git async experiment completion +function Runner:_finalise(opts) + if self.rc == -1 then + log.line("git", "job timed out %s %s", opts.project_root, opts.path) + timeouts = timeouts + 1 + if timeouts == MAX_TIMEOUTS then + notify.warn( + string.format( + "%d git jobs have timed out after %dms, disabling git integration. Try increasing git.timeout", + timeouts, + 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.project_root, opts.path) + else + log.line("git", "job success %s %s", opts.project_root, opts.path) + end +end + -- This module runs a git process, which will be killed if it takes more than timeout which defaults to 400ms function Runner.run(opts) local profile = log.profile_start("git job %s %s", opts.project_root, opts.path) @@ -161,26 +221,31 @@ function Runner.run(opts) log.profile_end(profile) - if self.rc == -1 then - log.line("git", "job timed out %s %s", opts.project_root, opts.path) - timeouts = timeouts + 1 - if timeouts == MAX_TIMEOUTS then - notify.warn( - string.format( - "%d git jobs have timed out after %dms, disabling git integration. Try increasing git.timeout", - timeouts, - 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.project_root, opts.path) - else - log.line("git", "job success %s %s", opts.project_root, opts.path) - end + self:_finalise(opts) return self.output end +function Runner.run_async(opts, callback) + local profile = log.profile_start("git async job %s %s", opts.project_root, opts.path) + + local self = setmetatable({ + project_root = opts.project_root, + 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) + + self:_run_git_job_async(function() + log.profile_end(profile) + + self:_finalise(opts) + + callback(self.output) + end) +end + return Runner From d6940d412b42d298ac44e05ee9a0c8bc6eb10337 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Mon, 27 Mar 2023 15:38:35 +1100 Subject: [PATCH 2/7] async git watcher reload; revert unnecessary extractions --- lua/nvim-tree/git/runner.lua | 122 +++++++++++++---------------------- 1 file changed, 44 insertions(+), 78 deletions(-) diff --git a/lua/nvim-tree/git/runner.lua b/lua/nvim-tree/git/runner.lua index 959dcda7be3..e0bff5f5c80 100644 --- a/lua/nvim-tree/git/runner.lua +++ b/lua/nvim-tree/git/runner.lua @@ -69,106 +69,72 @@ function Runner:_log_raw_output(output) end end -function Runner:_on_finish(rc) - self.rc = rc or 0 - if - self.timer:is_closing() - or self.stdout:is_closing() - or self.stderr:is_closing() - or (self.handle and self.handle:is_closing()) - then - return - end - self.timer:stop() - self.timer:close() - self.stdout:read_stop() - self.stderr:read_stop() - self.stdout:close() - self.stderr:close() - if self.handle then - self.handle:close() - end - - pcall(vim.loop.kill, self.pid) -end - -function Runner:_start_readers() - local output_leftover = "" - local function manage_stdout(err, data) - if err then +function Runner:_run_git_job(callback) + local handle, pid + local stdout = vim.loop.new_pipe(false) + local stderr = vim.loop.new_pipe(false) + local timer = vim.loop.new_timer() + + local function on_finish(rc) + self.rc = rc or 0 + if timer:is_closing() or stdout:is_closing() or stderr:is_closing() or (handle and handle:is_closing()) then return end - if data then - data = data:gsub("%z", "\n") + timer:stop() + timer:close() + stdout:read_stop() + stderr:read_stop() + stdout:close() + stderr:close() + if handle then + handle:close() end - 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) - end + pcall(vim.loop.kill, pid) - vim.loop.read_start(self.stdout, vim.schedule_wrap(manage_stdout)) - vim.loop.read_start(self.stderr, vim.schedule_wrap(manage_stderr)) -end - -function Runner:_run_git_job() - self.stdout = vim.loop.new_pipe(false) - self.stderr = vim.loop.new_pipe(false) - self.timer = vim.loop.new_timer() + if callback then + callback() + end + end - local opts = self:_getopts(self.stdout, self.stderr) + 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), " ")) - self.handle, self.pid = vim.loop.spawn( + handle, pid = vim.loop.spawn( "git", opts, vim.schedule_wrap(function(rc) - self:_on_finish(rc) + on_finish(rc) end) ) - self.timer:start( + timer:start( self.timeout, 0, vim.schedule_wrap(function() - self:_on_finish(-1) + on_finish(-1) end) ) - self:_start_readers() -end - -function Runner:_run_git_job_async(callback) - self.stdout = vim.loop.new_pipe(false) - self.stderr = vim.loop.new_pipe(false) - self.timer = vim.loop.new_timer() - - local opts = self:_getopts(self.stdout, self.stderr) - log.line("git", "running async job with timeout %dms", self.timeout) - log.line("git", "git %s", table.concat(utils.array_remove_nils(opts.args), " ")) - - self.handle, self.pid = vim.loop.spawn( - "git", - opts, - vim.schedule_wrap(function(rc) - self:_on_finish(rc) - callback() - end) - ) + local output_leftover = "" + local function manage_stdout(err, data) + if err then + return + end + if data then + data = data:gsub("%z", "\n") + end + self:_log_raw_output(data) + output_leftover = self:_handle_incoming_data(output_leftover, data) + end - self.timer:start( - self.timeout, - 0, - vim.schedule_wrap(function() - self:_on_finish(-1) - callback() - end) - ) + local function manage_stderr(_, data) + self:_log_raw_output(data) + end - self:_start_readers() + vim.loop.read_start(stdout, vim.schedule_wrap(manage_stdout)) + vim.loop.read_start(stderr, vim.schedule_wrap(manage_stderr)) end function Runner:_wait() @@ -239,7 +205,7 @@ function Runner.run_async(opts, callback) rc = nil, -- -1 indicates timeout }, Runner) - self:_run_git_job_async(function() + self:_run_git_job(function() log.profile_end(profile) self:_finalise(opts) From 00507f9d981397109599f3f3ca98f860175a8a4c Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Tue, 28 Mar 2023 13:03:50 +1100 Subject: [PATCH 3/7] async git watcher reload; callback and non-callback functions are required for sync codepaths that loop --- lua/nvim-tree.lua | 1 + lua/nvim-tree/explorer/explore.lua | 4 --- lua/nvim-tree/explorer/reload.lua | 49 ++++++++++++------------- lua/nvim-tree/explorer/watch.lua | 10 ++---- lua/nvim-tree/git/init.lua | 41 +++++++++------------ lua/nvim-tree/git/runner.lua | 57 ++++++++++++++++-------------- 6 files changed, 72 insertions(+), 90 deletions(-) diff --git a/lua/nvim-tree.lua b/lua/nvim-tree.lua index aaa8f7c8b1f..f0f372e65a4 100644 --- a/lua/nvim-tree.lua +++ b/lua/nvim-tree.lua @@ -764,6 +764,7 @@ function M.setup(conf) require("nvim-tree.diagnostics").setup(opts) require("nvim-tree.explorer").setup(opts) require("nvim-tree.git").setup(opts) + require("nvim-tree.git.runner").setup(opts) require("nvim-tree.view").setup(opts) require("nvim-tree.lib").setup(opts) require("nvim-tree.renderer").setup(opts) diff --git a/lua/nvim-tree/explorer/explore.lua b/lua/nvim-tree/explorer/explore.lua index f3b0ea2b7b2..1ca45fb4a77 100644 --- a/lua/nvim-tree/explorer/explore.lua +++ b/lua/nvim-tree/explorer/explore.lua @@ -26,8 +26,6 @@ local function populate_children(handle, cwd, node, git_status) local abs = utils.path_join { cwd, name } - local profile = log.profile_start("explore populate_children %s", abs) - t = get_type_from(t, abs) if not filters.should_filter(abs, filter_status) @@ -51,8 +49,6 @@ local function populate_children(handle, cwd, node, git_status) explorer_node.update_git_status(child, node_ignored, git_status) end end - - log.profile_end(profile) end end diff --git a/lua/nvim-tree/explorer/reload.lua b/lua/nvim-tree/explorer/reload.lua index c422028a2d9..98b41629a94 100644 --- a/lua/nvim-tree/explorer/reload.lua +++ b/lua/nvim-tree/explorer/reload.lua @@ -21,17 +21,17 @@ local function update_status(nodes_by_path, node_ignored, status) end end -local function reload_and_get_git_project(path) +local function reload_and_get_git_project(path, callback) local project_root = git.get_project_root(path) - git.reload_project(project_root, path) - return project_root, git.get_project(project_root) or {} -end -local function reload_and_get_git_project_async(path, callback) - local project_root = git.get_project_root(path) - git.reload_project_async(project_root, path, function() - callback(project_root, git.get_project(project_root) or {}) - end) + if callback then + git.reload_project(project_root, path, function() + callback(project_root, git.get_project(project_root) or {}) + end) + else + git.reload_project(project_root, path) + return project_root, git.get_project(project_root) or {} + end end local function update_parent_statuses(node, project, root) @@ -149,32 +149,29 @@ end ---Refresh contents and git status for a single node ---@param node table -function M.refresh_node(node) +function M.refresh_node(node, callback) if type(node) ~= "table" then + if callback then + callback() + end return end local parent_node = utils.get_parent_of_group(node) - local project_root, project = reload_and_get_git_project(node.absolute_path) - - require("nvim-tree.explorer.reload").reload(parent_node, project) - - update_parent_statuses(parent_node, project, project_root) -end - -function M.refresh_node_async(node, callback) - if type(node) ~= "table" then - return - end - - local parent_node = utils.get_parent_of_group(node) + if callback then + reload_and_get_git_project(node.absolute_path, function(project_root, project) + require("nvim-tree.explorer.reload").reload(parent_node, project) + update_parent_statuses(parent_node, project, project_root) + callback() + end) + else + local project_root, project = reload_and_get_git_project(node.absolute_path) - reload_and_get_git_project_async(node.absolute_path, function(project_root, project) require("nvim-tree.explorer.reload").reload(parent_node, project) + update_parent_statuses(parent_node, project, project_root) - callback() - end) + end end ---Refresh contents and git status for all nodes to a path: actual directory and links diff --git a/lua/nvim-tree/explorer/watch.lua b/lua/nvim-tree/explorer/watch.lua index 460b4270ac2..50afb5a3da8 100644 --- a/lua/nvim-tree/explorer/watch.lua +++ b/lua/nvim-tree/explorer/watch.lua @@ -59,14 +59,9 @@ function M.create_watcher(node) else log.line("watcher", "node event executing refresh '%s'", node.absolute_path) end - if M.git_async then - require("nvim-tree.explorer.reload").refresh_node_async(node, function() - require("nvim-tree.renderer").draw() - end) - else - require("nvim-tree.explorer.reload").refresh_node(node) + require("nvim-tree.explorer.reload").refresh_node(node, function() require("nvim-tree.renderer").draw() - end + end) end) end @@ -80,7 +75,6 @@ function M.setup(opts) M.enabled = opts.filesystem_watchers.enable M.debounce_delay = opts.filesystem_watchers.debounce_delay M.ignore_dirs = opts.filesystem_watchers.ignore_dirs - M.git_async = opts.experimental.git.async M.uid = 0 end diff --git a/lua/nvim-tree/git/init.lua b/lua/nvim-tree/git/init.lua index 2c39d36af4f..15307a95de8 100644 --- a/lua/nvim-tree/git/init.lua +++ b/lua/nvim-tree/git/init.lua @@ -22,7 +22,6 @@ local WATCHED_FILES = { "index", -- staging area } --- TODO fold back into reload_project following git async experiment completion local function reload_git_status(project_root, path, project, git_status) if path then for p in pairs(project.files) do @@ -50,17 +49,23 @@ function M.reload() return M.projects end -function M.reload_project(project_root, path) +function M.reload_project(project_root, path, callback) local project = M.projects[project_root] if not project or not M.config.git.enable then + if callback then + callback() + end return end if path and path:find(project_root, 1, true) ~= 1 then + if callback then + callback() + end return end - local git_status = Runner.run { + local opts = { project_root = project_root, path = path, list_untracked = git_utils.should_show_untracked(project_root), @@ -68,29 +73,15 @@ function M.reload_project(project_root, path) timeout = M.config.git.timeout, } - reload_git_status(project_root, path, project, git_status) -end - -function M.reload_project_async(project_root, path, callback) - local project = M.projects[project_root] - if not project or not M.config.git.enable then - return - end - - if path and path:find(project_root, 1, true) ~= 1 then - return - end - - Runner.run_async({ - project_root = project_root, - path = path, - list_untracked = git_utils.should_show_untracked(project_root), - list_ignored = true, - timeout = M.config.git.timeout, - }, function(git_status) + if callback then + Runner.run(opts, function(git_status) + reload_git_status(project_root, path, project, git_status) + callback() + end) + else + local git_status = Runner.run(opts) reload_git_status(project_root, path, project, git_status) - callback() - end) + end end function M.get_project(project_root) diff --git a/lua/nvim-tree/git/runner.lua b/lua/nvim-tree/git/runner.lua index e0bff5f5c80..ff4fb0397ff 100644 --- a/lua/nvim-tree/git/runner.lua +++ b/lua/nvim-tree/git/runner.lua @@ -78,6 +78,7 @@ function Runner:_run_git_job(callback) local function on_finish(rc) self.rc = rc or 0 if timer:is_closing() or stdout:is_closing() or stderr:is_closing() or (handle and handle:is_closing()) then + callback() return end timer:stop() @@ -146,7 +147,6 @@ function Runner:_wait() end end --- TODO fold back into run following git async experiment completion function Runner:_finalise(opts) if self.rc == -1 then log.line("git", "job timed out %s %s", opts.project_root, opts.path) @@ -168,10 +168,11 @@ function Runner:_finalise(opts) end end --- This module runs a git process, which will be killed if it takes more than timeout which defaults to 400ms -function Runner.run(opts) - local profile = log.profile_start("git job %s %s", opts.project_root, opts.path) - +--- 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({ project_root = opts.project_root, path = opts.path, @@ -182,36 +183,38 @@ function Runner.run(opts) rc = nil, -- -1 indicates timeout }, Runner) - self:_run_git_job() - self:_wait() - - log.profile_end(profile) + local async = callback ~= nil and self.config.git_async + local profile = log.profile_start("git %s job %s %s", async and "async" or "sync", opts.project_root, opts.path) - self:_finalise(opts) + if async and callback then + -- async + self:_run_git_job(function() + log.profile_end(profile) - return self.output -end - -function Runner.run_async(opts, callback) - local profile = log.profile_start("git async job %s %s", opts.project_root, opts.path) + self:_finalise(opts) - local self = setmetatable({ - project_root = opts.project_root, - 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) + callback(self.output) + end) + else + -- sync + self:_run_git_job() + self:_wait() - self:_run_git_job(function() log.profile_end(profile) self:_finalise(opts) - callback(self.output) - end) + if callback then + callback(self.output) + else + return self.output + end + end +end + +function Runner.setup(opts) + Runner.config = {} + Runner.config.git_async = opts.experimental.git.async end return Runner From 6da2a029ba4ba093aac2d60d3c38d8b456b34daa Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Tue, 28 Mar 2023 13:27:16 +1100 Subject: [PATCH 4/7] async git watcher reload --- lua/nvim-tree/explorer/reload.lua | 3 +++ lua/nvim-tree/git/init.lua | 28 +++++++++++++++------------- lua/nvim-tree/git/runner.lua | 4 +++- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/lua/nvim-tree/explorer/reload.lua b/lua/nvim-tree/explorer/reload.lua index 98b41629a94..a10753d9ab5 100644 --- a/lua/nvim-tree/explorer/reload.lua +++ b/lua/nvim-tree/explorer/reload.lua @@ -162,10 +162,13 @@ function M.refresh_node(node, callback) if callback then reload_and_get_git_project(node.absolute_path, function(project_root, project) require("nvim-tree.explorer.reload").reload(parent_node, project) + update_parent_statuses(parent_node, project, project_root) + callback() end) else + -- TODO use callback once async/await is available local project_root, project = reload_and_get_git_project(node.absolute_path) require("nvim-tree.explorer.reload").reload(parent_node, project) diff --git a/lua/nvim-tree/git/init.lua b/lua/nvim-tree/git/init.lua index 15307a95de8..a843b00c11a 100644 --- a/lua/nvim-tree/git/init.lua +++ b/lua/nvim-tree/git/init.lua @@ -79,6 +79,7 @@ function M.reload_project(project_root, path, callback) callback() end) else + -- TODO use callback once async/await is available local git_status = Runner.run(opts) reload_git_status(project_root, path, project, git_status) end @@ -121,21 +122,22 @@ local function reload_tree_at(project_root) return end - M.reload_project(project_root) - local git_status = M.get_project(project_root) + M.reload_project(project_root, nil, function() + local git_status = M.get_project(project_root) - Iterator.builder(root_node.nodes) - :hidden() - :applier(function(node) - local parent_ignored = explorer_node.is_git_ignored(node.parent) - explorer_node.update_git_status(node, parent_ignored, git_status) - end) - :recursor(function(node) - return node.nodes and #node.nodes > 0 and node.nodes - end) - :iterate() + Iterator.builder(root_node.nodes) + :hidden() + :applier(function(node) + local parent_ignored = explorer_node.is_git_ignored(node.parent) + explorer_node.update_git_status(node, parent_ignored, git_status) + end) + :recursor(function(node) + return node.nodes and #node.nodes > 0 and node.nodes + end) + :iterate() - require("nvim-tree.renderer").draw() + require("nvim-tree.renderer").draw() + end) end function M.load_project_status(cwd) diff --git a/lua/nvim-tree/git/runner.lua b/lua/nvim-tree/git/runner.lua index ff4fb0397ff..08164f28d93 100644 --- a/lua/nvim-tree/git/runner.lua +++ b/lua/nvim-tree/git/runner.lua @@ -78,7 +78,9 @@ function Runner:_run_git_job(callback) local function on_finish(rc) self.rc = rc or 0 if timer:is_closing() or stdout:is_closing() or stderr:is_closing() or (handle and handle:is_closing()) then - callback() + if callback then + callback() + end return end timer:stop() From 7490216ba58dead31d01feb5794d4035e022ec0d Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Tue, 28 Mar 2023 13:31:27 +1100 Subject: [PATCH 5/7] async git watcher reload --- lua/nvim-tree/explorer/explore.lua | 4 ++++ lua/nvim-tree/explorer/reload.lua | 1 + lua/nvim-tree/git/runner.lua | 4 ++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lua/nvim-tree/explorer/explore.lua b/lua/nvim-tree/explorer/explore.lua index 1ca45fb4a77..f3b0ea2b7b2 100644 --- a/lua/nvim-tree/explorer/explore.lua +++ b/lua/nvim-tree/explorer/explore.lua @@ -26,6 +26,8 @@ local function populate_children(handle, cwd, node, git_status) local abs = utils.path_join { cwd, name } + local profile = log.profile_start("explore populate_children %s", abs) + t = get_type_from(t, abs) if not filters.should_filter(abs, filter_status) @@ -49,6 +51,8 @@ local function populate_children(handle, cwd, node, git_status) explorer_node.update_git_status(child, node_ignored, git_status) end end + + log.profile_end(profile) end end diff --git a/lua/nvim-tree/explorer/reload.lua b/lua/nvim-tree/explorer/reload.lua index a10753d9ab5..31f2f835e5a 100644 --- a/lua/nvim-tree/explorer/reload.lua +++ b/lua/nvim-tree/explorer/reload.lua @@ -21,6 +21,7 @@ local function update_status(nodes_by_path, node_ignored, status) end end +-- TODO always use callback once async/await is available local function reload_and_get_git_project(path, callback) local project_root = git.get_project_root(path) diff --git a/lua/nvim-tree/git/runner.lua b/lua/nvim-tree/git/runner.lua index 08164f28d93..9649fa272cf 100644 --- a/lua/nvim-tree/git/runner.lua +++ b/lua/nvim-tree/git/runner.lua @@ -189,7 +189,7 @@ function Runner.run(opts, callback) local profile = log.profile_start("git %s job %s %s", async and "async" or "sync", opts.project_root, opts.path) if async and callback then - -- async + -- async, always call back self:_run_git_job(function() log.profile_end(profile) @@ -198,7 +198,7 @@ function Runner.run(opts, callback) callback(self.output) end) else - -- sync + -- sync, maybe call back self:_run_git_job() self:_wait() From 4eb97566ee1ec63c63f212250fa58ea9a8319a40 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Mon, 3 Apr 2023 16:13:39 +1000 Subject: [PATCH 6/7] feat(#1974): experimental.git.async --- doc/nvim-tree-lua.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index 91632426c2f..f2f755ccf8f 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -438,6 +438,11 @@ applying configuration. trash = true, }, }, + experimental = { + git = { + async = false, + }, + }, log = { enable = false, truncate = false, @@ -1222,6 +1227,16 @@ General UI configuration. Prompt before trashing. Type: `boolean`, Default: `true` +*nvim-tree.experimental* +Experimental features that may become default or optional functionality. + + *nvim-tree.experimental.git.async* + Direct file writes and `.git/` writes are executed asynchronously: the + git process runs in the background and the tree updated on completion. + Other git actions such as first tree draw and explicit refreshes are still + done in the foreground. + Type: `boolean`, Default: `false` + *nvim-tree.log* Configuration for diagnostic logging. From faba33bd6d7498a8c9a2af426f30364916da8fa2 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Mon, 3 Apr 2023 16:15:13 +1000 Subject: [PATCH 7/7] feat(#1974): experimental.git.async --- doc/nvim-tree-lua.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index f2f755ccf8f..b9a43a2f422 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -1232,7 +1232,7 @@ Experimental features that may become default or optional functionality. *nvim-tree.experimental.git.async* Direct file writes and `.git/` writes are executed asynchronously: the - git process runs in the background and the tree updated on completion. + git process runs in the background. The tree updates on completion. Other git actions such as first tree draw and explicit refreshes are still done in the foreground. Type: `boolean`, Default: `false`