feat(#1974): experimental.git.async see https://github.com/nvim-tree/nvim-tree.lua/issues/2104 (#2094)
* async git watcher reload; callback hell for now * async git watcher reload; revert unnecessary extractions * async git watcher reload; callback and non-callback functions are required for sync codepaths that loop * async git watcher reload * async git watcher reload * feat(#1974): experimental.git.async * feat(#1974): experimental.git.async
This commit is contained in:
parent
7ad1c204c4
commit
0ef3d4613f
@ -438,6 +438,11 @@ applying configuration.
|
|||||||
trash = true,
|
trash = true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
experimental = {
|
||||||
|
git = {
|
||||||
|
async = false,
|
||||||
|
},
|
||||||
|
},
|
||||||
log = {
|
log = {
|
||||||
enable = false,
|
enable = false,
|
||||||
truncate = false,
|
truncate = false,
|
||||||
@ -1224,6 +1229,16 @@ General UI configuration.
|
|||||||
Prompt before trashing.
|
Prompt before trashing.
|
||||||
Type: `boolean`, Default: `true`
|
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. 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`
|
||||||
|
|
||||||
*nvim-tree.log*
|
*nvim-tree.log*
|
||||||
Configuration for diagnostic logging.
|
Configuration for diagnostic logging.
|
||||||
|
|
||||||
|
|||||||
@ -636,6 +636,11 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
|
|||||||
trash = true,
|
trash = true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
experimental = {
|
||||||
|
git = {
|
||||||
|
async = false,
|
||||||
|
},
|
||||||
|
},
|
||||||
log = {
|
log = {
|
||||||
enable = false,
|
enable = false,
|
||||||
truncate = false,
|
truncate = false,
|
||||||
@ -759,6 +764,7 @@ function M.setup(conf)
|
|||||||
require("nvim-tree.diagnostics").setup(opts)
|
require("nvim-tree.diagnostics").setup(opts)
|
||||||
require("nvim-tree.explorer").setup(opts)
|
require("nvim-tree.explorer").setup(opts)
|
||||||
require("nvim-tree.git").setup(opts)
|
require("nvim-tree.git").setup(opts)
|
||||||
|
require("nvim-tree.git.runner").setup(opts)
|
||||||
require("nvim-tree.view").setup(opts)
|
require("nvim-tree.view").setup(opts)
|
||||||
require("nvim-tree.lib").setup(opts)
|
require("nvim-tree.lib").setup(opts)
|
||||||
require("nvim-tree.renderer").setup(opts)
|
require("nvim-tree.renderer").setup(opts)
|
||||||
|
|||||||
@ -21,10 +21,18 @@ local function update_status(nodes_by_path, node_ignored, status)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function reload_and_get_git_project(path)
|
-- 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)
|
local project_root = git.get_project_root(path)
|
||||||
git.reload_project(project_root, path)
|
|
||||||
return project_root, git.get_project(project_root) or {}
|
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
|
end
|
||||||
|
|
||||||
local function update_parent_statuses(node, project, root)
|
local function update_parent_statuses(node, project, root)
|
||||||
@ -142,18 +150,32 @@ end
|
|||||||
|
|
||||||
---Refresh contents and git status for a single node
|
---Refresh contents and git status for a single node
|
||||||
---@param node table
|
---@param node table
|
||||||
function M.refresh_node(node)
|
function M.refresh_node(node, callback)
|
||||||
if type(node) ~= "table" then
|
if type(node) ~= "table" then
|
||||||
|
if callback then
|
||||||
|
callback()
|
||||||
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local parent_node = utils.get_parent_of_group(node)
|
local parent_node = utils.get_parent_of_group(node)
|
||||||
|
|
||||||
local project_root, project = reload_and_get_git_project(node.absolute_path)
|
if callback then
|
||||||
|
reload_and_get_git_project(node.absolute_path, function(project_root, project)
|
||||||
|
require("nvim-tree.explorer.reload").reload(parent_node, project)
|
||||||
|
|
||||||
require("nvim-tree.explorer.reload").reload(parent_node, project)
|
update_parent_statuses(parent_node, project, project_root)
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
update_parent_statuses(parent_node, project, project_root)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---Refresh contents and git status for all nodes to a path: actual directory and links
|
---Refresh contents and git status for all nodes to a path: actual directory and links
|
||||||
|
|||||||
@ -59,8 +59,9 @@ function M.create_watcher(node)
|
|||||||
else
|
else
|
||||||
log.line("watcher", "node event executing refresh '%s'", node.absolute_path)
|
log.line("watcher", "node event executing refresh '%s'", node.absolute_path)
|
||||||
end
|
end
|
||||||
require("nvim-tree.explorer.reload").refresh_node(node)
|
require("nvim-tree.explorer.reload").refresh_node(node, function()
|
||||||
require("nvim-tree.renderer").draw()
|
require("nvim-tree.renderer").draw()
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -22,36 +22,7 @@ local WATCHED_FILES = {
|
|||||||
"index", -- staging area
|
"index", -- staging area
|
||||||
}
|
}
|
||||||
|
|
||||||
function M.reload()
|
local function reload_git_status(project_root, path, project, git_status)
|
||||||
if not M.config.git.enable then
|
|
||||||
return {}
|
|
||||||
end
|
|
||||||
|
|
||||||
for project_root in pairs(M.projects) do
|
|
||||||
M.reload_project(project_root)
|
|
||||||
end
|
|
||||||
|
|
||||||
return M.projects
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.reload_project(project_root, path)
|
|
||||||
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
|
|
||||||
|
|
||||||
local git_status = Runner.run {
|
|
||||||
project_root = project_root,
|
|
||||||
path = path,
|
|
||||||
list_untracked = git_utils.should_show_untracked(project_root),
|
|
||||||
list_ignored = true,
|
|
||||||
timeout = M.config.git.timeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
@ -66,6 +37,54 @@ function M.reload_project(project_root, path)
|
|||||||
project.dirs = git_utils.file_status_to_dir_status(project.files, project_root)
|
project.dirs = git_utils.file_status_to_dir_status(project.files, project_root)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function M.reload()
|
||||||
|
if not M.config.git.enable then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
for project_root in pairs(M.projects) do
|
||||||
|
M.reload_project(project_root)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M.projects
|
||||||
|
end
|
||||||
|
|
||||||
|
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 opts = {
|
||||||
|
project_root = project_root,
|
||||||
|
path = path,
|
||||||
|
list_untracked = git_utils.should_show_untracked(project_root),
|
||||||
|
list_ignored = true,
|
||||||
|
timeout = M.config.git.timeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
if callback then
|
||||||
|
Runner.run(opts, function(git_status)
|
||||||
|
reload_git_status(project_root, path, project, git_status)
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
function M.get_project(project_root)
|
function M.get_project(project_root)
|
||||||
return M.projects[project_root]
|
return M.projects[project_root]
|
||||||
end
|
end
|
||||||
@ -103,21 +122,22 @@ local function reload_tree_at(project_root)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
M.reload_project(project_root)
|
M.reload_project(project_root, nil, function()
|
||||||
local git_status = M.get_project(project_root)
|
local git_status = M.get_project(project_root)
|
||||||
|
|
||||||
Iterator.builder(root_node.nodes)
|
Iterator.builder(root_node.nodes)
|
||||||
:hidden()
|
:hidden()
|
||||||
:applier(function(node)
|
:applier(function(node)
|
||||||
local parent_ignored = explorer_node.is_git_ignored(node.parent)
|
local parent_ignored = explorer_node.is_git_ignored(node.parent)
|
||||||
explorer_node.update_git_status(node, parent_ignored, git_status)
|
explorer_node.update_git_status(node, parent_ignored, git_status)
|
||||||
end)
|
end)
|
||||||
:recursor(function(node)
|
:recursor(function(node)
|
||||||
return node.nodes and #node.nodes > 0 and node.nodes
|
return node.nodes and #node.nodes > 0 and node.nodes
|
||||||
end)
|
end)
|
||||||
:iterate()
|
:iterate()
|
||||||
|
|
||||||
require("nvim-tree.renderer").draw()
|
require("nvim-tree.renderer").draw()
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.load_project_status(cwd)
|
function M.load_project_status(cwd)
|
||||||
|
|||||||
@ -69,7 +69,7 @@ function Runner:_log_raw_output(output)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Runner:_run_git_job()
|
function Runner:_run_git_job(callback)
|
||||||
local handle, pid
|
local handle, pid
|
||||||
local stdout = vim.loop.new_pipe(false)
|
local stdout = vim.loop.new_pipe(false)
|
||||||
local stderr = vim.loop.new_pipe(false)
|
local stderr = vim.loop.new_pipe(false)
|
||||||
@ -78,6 +78,9 @@ function Runner:_run_git_job()
|
|||||||
local function on_finish(rc)
|
local function on_finish(rc)
|
||||||
self.rc = rc or 0
|
self.rc = rc or 0
|
||||||
if timer:is_closing() or stdout:is_closing() or stderr:is_closing() or (handle and handle:is_closing()) then
|
if timer:is_closing() or stdout:is_closing() or stderr:is_closing() or (handle and handle:is_closing()) then
|
||||||
|
if callback then
|
||||||
|
callback()
|
||||||
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
timer:stop()
|
timer:stop()
|
||||||
@ -91,6 +94,10 @@ function Runner:_run_git_job()
|
|||||||
end
|
end
|
||||||
|
|
||||||
pcall(vim.loop.kill, pid)
|
pcall(vim.loop.kill, pid)
|
||||||
|
|
||||||
|
if callback then
|
||||||
|
callback()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local opts = self:_getopts(stdout, stderr)
|
local opts = self:_getopts(stdout, stderr)
|
||||||
@ -142,25 +149,7 @@ function Runner:_wait()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- This module runs a git process, which will be killed if it takes more than timeout which defaults to 400ms
|
function Runner:_finalise(opts)
|
||||||
function Runner.run(opts)
|
|
||||||
local profile = log.profile_start("git 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()
|
|
||||||
self:_wait()
|
|
||||||
|
|
||||||
log.profile_end(profile)
|
|
||||||
|
|
||||||
if self.rc == -1 then
|
if self.rc == -1 then
|
||||||
log.line("git", "job timed out %s %s", opts.project_root, opts.path)
|
log.line("git", "job timed out %s %s", opts.project_root, opts.path)
|
||||||
timeouts = timeouts + 1
|
timeouts = timeouts + 1
|
||||||
@ -179,8 +168,55 @@ function Runner.run(opts)
|
|||||||
else
|
else
|
||||||
log.line("git", "job success %s %s", opts.project_root, opts.path)
|
log.line("git", "job success %s %s", opts.project_root, opts.path)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return self.output
|
--- 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,
|
||||||
|
list_untracked = opts.list_untracked,
|
||||||
|
list_ignored = opts.list_ignored,
|
||||||
|
timeout = opts.timeout or 400,
|
||||||
|
output = {},
|
||||||
|
rc = nil, -- -1 indicates timeout
|
||||||
|
}, Runner)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
if async and callback then
|
||||||
|
-- async, always call back
|
||||||
|
self:_run_git_job(function()
|
||||||
|
log.profile_end(profile)
|
||||||
|
|
||||||
|
self:_finalise(opts)
|
||||||
|
|
||||||
|
callback(self.output)
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
-- sync, maybe call back
|
||||||
|
self:_run_git_job()
|
||||||
|
self:_wait()
|
||||||
|
|
||||||
|
log.profile_end(profile)
|
||||||
|
|
||||||
|
self:_finalise(opts)
|
||||||
|
|
||||||
|
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
|
end
|
||||||
|
|
||||||
return Runner
|
return Runner
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user