feat(explorer): add filesystem watchers (#1304)
* feat(explorer): add experimental watchers This commit introduces watchers to update the tree. This behavior is introduced behind an "filesystem_watchers" option which should prevent instabilities. It will become the default at some point. Co-authored-by: Alexander Courtis <alex@courtis.org>
This commit is contained in:
@@ -182,6 +182,10 @@ require'nvim-tree'.setup { -- BEGIN_DEFAULT_OPTS
|
|||||||
custom = {},
|
custom = {},
|
||||||
exclude = {},
|
exclude = {},
|
||||||
},
|
},
|
||||||
|
filesystem_watchers = {
|
||||||
|
enable = false,
|
||||||
|
interval = 100,
|
||||||
|
},
|
||||||
git = {
|
git = {
|
||||||
enable = true,
|
enable = true,
|
||||||
ignore = true,
|
ignore = true,
|
||||||
@@ -231,6 +235,7 @@ require'nvim-tree'.setup { -- BEGIN_DEFAULT_OPTS
|
|||||||
diagnostics = false,
|
diagnostics = false,
|
||||||
git = false,
|
git = false,
|
||||||
profile = false,
|
profile = false,
|
||||||
|
watcher = false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} -- END_DEFAULT_OPTS
|
} -- END_DEFAULT_OPTS
|
||||||
|
|||||||
@@ -200,6 +200,10 @@ Values may be functions. Warning: this may result in unexpected behaviour.
|
|||||||
custom = {},
|
custom = {},
|
||||||
exclude = {},
|
exclude = {},
|
||||||
},
|
},
|
||||||
|
filesystem_watchers = {
|
||||||
|
enable = false,
|
||||||
|
interval = 100,
|
||||||
|
},
|
||||||
git = {
|
git = {
|
||||||
enable = true,
|
enable = true,
|
||||||
ignore = true,
|
ignore = true,
|
||||||
@@ -249,6 +253,7 @@ Values may be functions. Warning: this may result in unexpected behaviour.
|
|||||||
diagnostics = false,
|
diagnostics = false,
|
||||||
git = false,
|
git = false,
|
||||||
profile = false,
|
profile = false,
|
||||||
|
watcher = false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} -- END_DEFAULT_OPTS
|
} -- END_DEFAULT_OPTS
|
||||||
@@ -419,6 +424,26 @@ Git integration with icons and colors.
|
|||||||
milliseconds but a few seconds), it will not render anything until the git
|
milliseconds but a few seconds), it will not render anything until the git
|
||||||
process returned the data.
|
process returned the data.
|
||||||
|
|
||||||
|
|
||||||
|
*nvim-tree.filesystem_watchers*
|
||||||
|
Will use file system watcher (libuv fs_poll) to watch the filesystem for
|
||||||
|
changes.
|
||||||
|
Using this will disable BufEnter / BufWritePost events in nvim-tree which
|
||||||
|
were used to update the whole tree. With this feature, the tree will be
|
||||||
|
updated only for the appropriate folder change, resulting in better
|
||||||
|
performance.
|
||||||
|
This will be experimental for a few weeks and will become the default.
|
||||||
|
|
||||||
|
*nvim-tree.filesystem_watchers.enable*
|
||||||
|
Enable / disable the feature.
|
||||||
|
Type: `boolean`, Default: `false`
|
||||||
|
|
||||||
|
*nvim-tree.filesystem_watchers.interval*
|
||||||
|
Milliseconds between polls for each directory.
|
||||||
|
Increase to at least 1000ms if changes are not visible. See
|
||||||
|
https://github.com/luvit/luv/blob/master/docs.md#uvfs_poll_startfs_poll-path-interval-callback
|
||||||
|
Type: `number`, Default: `100` (ms)
|
||||||
|
|
||||||
*nvim-tree.view*
|
*nvim-tree.view*
|
||||||
Window / buffer setup.
|
Window / buffer setup.
|
||||||
|
|
||||||
|
|||||||
@@ -300,13 +300,18 @@ local function setup_autocommands(opts)
|
|||||||
-- reset highlights when colorscheme is changed
|
-- reset highlights when colorscheme is changed
|
||||||
create_nvim_tree_autocmd("ColorScheme", { callback = M.reset_highlight })
|
create_nvim_tree_autocmd("ColorScheme", { callback = M.reset_highlight })
|
||||||
|
|
||||||
if opts.auto_reload_on_write then
|
local has_watchers = opts.filesystem_watchers.enable
|
||||||
|
|
||||||
|
if opts.auto_reload_on_write and not has_watchers then
|
||||||
create_nvim_tree_autocmd("BufWritePost", { callback = reloaders.reload_explorer })
|
create_nvim_tree_autocmd("BufWritePost", { callback = reloaders.reload_explorer })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if not has_watchers and opts.git.enable then
|
||||||
create_nvim_tree_autocmd("User", {
|
create_nvim_tree_autocmd("User", {
|
||||||
pattern = { "FugitiveChanged", "NeogitStatusRefreshed" },
|
pattern = { "FugitiveChanged", "NeogitStatusRefreshed" },
|
||||||
callback = reloaders.reload_git,
|
callback = reloaders.reload_git,
|
||||||
})
|
})
|
||||||
|
end
|
||||||
|
|
||||||
if opts.open_on_tab then
|
if opts.open_on_tab then
|
||||||
create_nvim_tree_autocmd("TabEnter", { callback = M.tab_change })
|
create_nvim_tree_autocmd("TabEnter", { callback = M.tab_change })
|
||||||
@@ -339,7 +344,7 @@ local function setup_autocommands(opts)
|
|||||||
create_nvim_tree_autocmd({ "BufEnter", "BufNewFile" }, { callback = M.open_on_directory })
|
create_nvim_tree_autocmd({ "BufEnter", "BufNewFile" }, { callback = M.open_on_directory })
|
||||||
end
|
end
|
||||||
|
|
||||||
if opts.reload_on_bufenter then
|
if opts.reload_on_bufenter and not has_watchers then
|
||||||
create_nvim_tree_autocmd("BufEnter", { pattern = "NvimTree_*", callback = reloaders.reload_explorer })
|
create_nvim_tree_autocmd("BufEnter", { pattern = "NvimTree_*", callback = reloaders.reload_explorer })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -456,6 +461,10 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
|
|||||||
custom = {},
|
custom = {},
|
||||||
exclude = {},
|
exclude = {},
|
||||||
},
|
},
|
||||||
|
filesystem_watchers = {
|
||||||
|
enable = false,
|
||||||
|
interval = 100,
|
||||||
|
},
|
||||||
git = {
|
git = {
|
||||||
enable = true,
|
enable = true,
|
||||||
ignore = true,
|
ignore = true,
|
||||||
@@ -505,6 +514,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
|
|||||||
diagnostics = false,
|
diagnostics = false,
|
||||||
git = false,
|
git = false,
|
||||||
profile = false,
|
profile = false,
|
||||||
|
watcher = false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} -- END_DEFAULT_OPTS
|
} -- END_DEFAULT_OPTS
|
||||||
|
|||||||
@@ -165,7 +165,9 @@ local function do_paste(node, action_type, action_fn)
|
|||||||
end
|
end
|
||||||
|
|
||||||
clipboard[action_type] = {}
|
clipboard[action_type] = {}
|
||||||
|
if not M.enable_reload then
|
||||||
return require("nvim-tree.actions.reloaders").reload_explorer()
|
return require("nvim-tree.actions.reloaders").reload_explorer()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function do_cut(source, destination)
|
local function do_cut(source, destination)
|
||||||
@@ -242,6 +244,7 @@ end
|
|||||||
|
|
||||||
function M.setup(opts)
|
function M.setup(opts)
|
||||||
M.use_system_clipboard = opts.actions.use_system_clipboard
|
M.use_system_clipboard = opts.actions.use_system_clipboard
|
||||||
|
M.enable_reload = not opts.filesystem_watchers.enable
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ local function get_num_nodes(iter)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function get_containing_folder(node)
|
local function get_containing_folder(node)
|
||||||
local is_open = M.config.create_in_closed_folder or node.open
|
local is_open = M.create_in_closed_folder or node.open
|
||||||
if node.nodes ~= nil and is_open then
|
if node.nodes ~= nil and is_open then
|
||||||
return utils.path_add_trailing(node.absolute_path)
|
return utils.path_add_trailing(node.absolute_path)
|
||||||
end
|
end
|
||||||
@@ -107,13 +107,19 @@ function M.fn(node)
|
|||||||
a.nvim_out_write(new_file_path .. " was properly created\n")
|
a.nvim_out_write(new_file_path .. " was properly created\n")
|
||||||
end
|
end
|
||||||
events._dispatch_folder_created(new_file_path)
|
events._dispatch_folder_created(new_file_path)
|
||||||
|
if M.enable_reload then
|
||||||
require("nvim-tree.actions.reloaders").reload_explorer()
|
require("nvim-tree.actions.reloaders").reload_explorer()
|
||||||
|
end
|
||||||
|
-- INFO: defer needed when reload is automatic (watchers)
|
||||||
|
vim.defer_fn(function()
|
||||||
focus_file(new_file_path)
|
focus_file(new_file_path)
|
||||||
|
end, 50)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.setup(opts)
|
function M.setup(opts)
|
||||||
M.config = opts
|
M.create_in_closed_folder = opts.create_in_closed_folder
|
||||||
|
M.enable_reload = not opts.filesystem_watchers.enable
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -403,13 +403,14 @@ local DEFAULT_MAPPING_CONFIG = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function M.setup(opts)
|
function M.setup(opts)
|
||||||
require("nvim-tree.actions.system-open").setup(opts.system_open)
|
require("nvim-tree.actions.system-open").setup(opts)
|
||||||
require("nvim-tree.actions.trash").setup(opts.trash)
|
require("nvim-tree.actions.trash").setup(opts)
|
||||||
require("nvim-tree.actions.open-file").setup(opts)
|
require("nvim-tree.actions.open-file").setup(opts)
|
||||||
require("nvim-tree.actions.change-dir").setup(opts)
|
require("nvim-tree.actions.change-dir").setup(opts)
|
||||||
|
require("nvim-tree.actions.create-file").setup(opts)
|
||||||
|
require("nvim-tree.actions.rename-file").setup(opts)
|
||||||
require("nvim-tree.actions.remove-file").setup(opts)
|
require("nvim-tree.actions.remove-file").setup(opts)
|
||||||
require("nvim-tree.actions.copy-paste").setup(opts)
|
require("nvim-tree.actions.copy-paste").setup(opts)
|
||||||
require("nvim-tree.actions.create-file").setup(opts)
|
|
||||||
require("nvim-tree.actions.expand-all").setup(opts)
|
require("nvim-tree.actions.expand-all").setup(opts)
|
||||||
|
|
||||||
local user_map_config = (opts.view or {}).mappings or {}
|
local user_map_config = (opts.view or {}).mappings or {}
|
||||||
|
|||||||
@@ -86,11 +86,14 @@ function M.fn(node)
|
|||||||
events._dispatch_file_removed(node.absolute_path)
|
events._dispatch_file_removed(node.absolute_path)
|
||||||
clear_buffer(node.absolute_path)
|
clear_buffer(node.absolute_path)
|
||||||
end
|
end
|
||||||
|
if M.enable_reload then
|
||||||
require("nvim-tree.actions.reloaders").reload_explorer()
|
require("nvim-tree.actions.reloaders").reload_explorer()
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.setup(opts)
|
function M.setup(opts)
|
||||||
|
M.enable_reload = not opts.filesystem_watchers.enable
|
||||||
M.close_window = opts.actions.remove_file.close_window
|
M.close_window = opts.actions.remove_file.close_window
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -37,9 +37,15 @@ function M.fn(with_sub)
|
|||||||
a.nvim_out_write(node.absolute_path .. " ➜ " .. new_file_path .. "\n")
|
a.nvim_out_write(node.absolute_path .. " ➜ " .. new_file_path .. "\n")
|
||||||
utils.rename_loaded_buffers(node.absolute_path, new_file_path)
|
utils.rename_loaded_buffers(node.absolute_path, new_file_path)
|
||||||
events._dispatch_node_renamed(abs_path, new_file_path)
|
events._dispatch_node_renamed(abs_path, new_file_path)
|
||||||
|
if M.enable_reload then
|
||||||
require("nvim-tree.actions.reloaders").reload_explorer()
|
require("nvim-tree.actions.reloaders").reload_explorer()
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.enable_reload = not opts.filesystem_watchers.enable
|
||||||
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ function M.fn(node)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function M.setup(opts)
|
function M.setup(opts)
|
||||||
M.config.system_open = opts or {}
|
M.config.system_open = opts.system_open or {}
|
||||||
|
|
||||||
if #M.config.system_open.cmd == 0 then
|
if #M.config.system_open.cmd == 0 then
|
||||||
if M.config.is_windows then
|
if M.config.is_windows then
|
||||||
|
|||||||
@@ -71,20 +71,25 @@ function M.fn(node)
|
|||||||
if node.nodes ~= nil and not node.link_to then
|
if node.nodes ~= nil and not node.link_to then
|
||||||
trash_path(function()
|
trash_path(function()
|
||||||
events._dispatch_folder_removed(node.absolute_path)
|
events._dispatch_folder_removed(node.absolute_path)
|
||||||
|
if M.enable_reload then
|
||||||
require("nvim-tree.actions.reloaders").reload_explorer()
|
require("nvim-tree.actions.reloaders").reload_explorer()
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
else
|
else
|
||||||
trash_path(function()
|
trash_path(function()
|
||||||
events._dispatch_file_removed(node.absolute_path)
|
events._dispatch_file_removed(node.absolute_path)
|
||||||
clear_buffer(node.absolute_path)
|
clear_buffer(node.absolute_path)
|
||||||
|
if M.enable_reload then
|
||||||
require("nvim-tree.actions.reloaders").reload_explorer()
|
require("nvim-tree.actions.reloaders").reload_explorer()
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.setup(opts)
|
function M.setup(opts)
|
||||||
M.config.trash = opts or {}
|
M.config.trash = opts.trash or {}
|
||||||
|
M.enable_reload = not opts.filesystem_watchers.enable
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ TreeExplorer = nil
|
|||||||
local first_init_done = false
|
local first_init_done = false
|
||||||
|
|
||||||
function M.init(foldername)
|
function M.init(foldername)
|
||||||
|
if TreeExplorer then
|
||||||
|
TreeExplorer:_clear_watchers()
|
||||||
|
end
|
||||||
TreeExplorer = explorer.Explorer.new(foldername)
|
TreeExplorer = explorer.Explorer.new(foldername)
|
||||||
if not first_init_done then
|
if not first_init_done then
|
||||||
events._dispatch_ready()
|
events._dispatch_ready()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
local uv = vim.loop
|
local uv = vim.loop
|
||||||
|
|
||||||
local git = require "nvim-tree.git"
|
local git = require "nvim-tree.git"
|
||||||
|
local watch = require "nvim-tree.explorer.watch"
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
@@ -15,6 +16,8 @@ function Explorer.new(cwd)
|
|||||||
local explorer = setmetatable({
|
local explorer = setmetatable({
|
||||||
absolute_path = cwd,
|
absolute_path = cwd,
|
||||||
nodes = {},
|
nodes = {},
|
||||||
|
watcher = watch.create_watcher(cwd),
|
||||||
|
open = true,
|
||||||
}, Explorer)
|
}, Explorer)
|
||||||
explorer:_load(explorer)
|
explorer:_load(explorer)
|
||||||
return explorer
|
return explorer
|
||||||
@@ -30,11 +33,30 @@ function Explorer:expand(node)
|
|||||||
self:_load(node)
|
self:_load(node)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Explorer.clear_watchers_for(root_node)
|
||||||
|
local function iterate(node)
|
||||||
|
if node.watcher then
|
||||||
|
node.watcher:stop()
|
||||||
|
for _, child in pairs(node.nodes) do
|
||||||
|
if child.watcher then
|
||||||
|
iterate(child)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
iterate(root_node)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Explorer:_clear_watchers()
|
||||||
|
Explorer.clear_watchers_for(self)
|
||||||
|
end
|
||||||
|
|
||||||
function M.setup(opts)
|
function M.setup(opts)
|
||||||
require("nvim-tree.explorer.explore").setup(opts)
|
require("nvim-tree.explorer.explore").setup(opts)
|
||||||
require("nvim-tree.explorer.filters").setup(opts)
|
require("nvim-tree.explorer.filters").setup(opts)
|
||||||
require("nvim-tree.explorer.sorters").setup(opts)
|
require("nvim-tree.explorer.sorters").setup(opts)
|
||||||
require("nvim-tree.explorer.reload").setup(opts)
|
require("nvim-tree.explorer.reload").setup(opts)
|
||||||
|
require("nvim-tree.explorer.watch").setup(opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
M.Explorer = Explorer
|
M.Explorer = Explorer
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
local uv = vim.loop
|
local uv = vim.loop
|
||||||
local utils = require "nvim-tree.utils"
|
local utils = require "nvim-tree.utils"
|
||||||
|
local watch = require "nvim-tree.explorer.watch"
|
||||||
|
|
||||||
local M = {
|
local M = {
|
||||||
is_windows = vim.fn.has "win32" == 1,
|
is_windows = vim.fn.has "win32" == 1,
|
||||||
@@ -18,6 +19,7 @@ function M.folder(parent, absolute_path, name)
|
|||||||
nodes = {},
|
nodes = {},
|
||||||
open = false,
|
open = false,
|
||||||
parent = parent,
|
parent = parent,
|
||||||
|
watcher = watch.create_watcher(absolute_path),
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -49,12 +51,13 @@ end
|
|||||||
function M.link(parent, absolute_path, name)
|
function M.link(parent, absolute_path, name)
|
||||||
--- I dont know if this is needed, because in my understanding, there isnt hard links in windows, but just to be sure i changed it.
|
--- I dont know if this is needed, because in my understanding, there isnt hard links in windows, but just to be sure i changed it.
|
||||||
local link_to = uv.fs_realpath(absolute_path)
|
local link_to = uv.fs_realpath(absolute_path)
|
||||||
local open, nodes, has_children
|
local open, nodes, has_children, watcher
|
||||||
if (link_to ~= nil) and uv.fs_stat(link_to).type == "directory" then
|
if (link_to ~= nil) and uv.fs_stat(link_to).type == "directory" then
|
||||||
local handle = uv.fs_scandir(link_to)
|
local handle = uv.fs_scandir(link_to)
|
||||||
has_children = handle and uv.fs_scandir_next(handle) ~= nil
|
has_children = handle and uv.fs_scandir_next(handle) ~= nil
|
||||||
open = false
|
open = false
|
||||||
nodes = {}
|
nodes = {}
|
||||||
|
watcher = watch.create_watcher(link_to)
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -67,6 +70,7 @@ function M.link(parent, absolute_path, name)
|
|||||||
nodes = nodes,
|
nodes = nodes,
|
||||||
open = open,
|
open = open,
|
||||||
parent = parent,
|
parent = parent,
|
||||||
|
watcher = watcher,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ function M.reload(node, status)
|
|||||||
local node_ignored = node.git_status == "!!"
|
local node_ignored = node.git_status == "!!"
|
||||||
local nodes_by_path = utils.key_by(node.nodes, "absolute_path")
|
local nodes_by_path = utils.key_by(node.nodes, "absolute_path")
|
||||||
while true do
|
while true do
|
||||||
local name, t = uv.fs_scandir_next(handle)
|
local ok, name, t = pcall(uv.fs_scandir_next, handle)
|
||||||
if not name then
|
if not ok or not name then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -48,12 +48,17 @@ function M.reload(node, status)
|
|||||||
child_names[abs] = true
|
child_names[abs] = true
|
||||||
if not nodes_by_path[abs] then
|
if not nodes_by_path[abs] then
|
||||||
if t == "directory" and uv.fs_access(abs, "R") then
|
if t == "directory" and uv.fs_access(abs, "R") then
|
||||||
table.insert(node.nodes, builders.folder(node, abs, name))
|
local folder = builders.folder(node, abs, name)
|
||||||
|
nodes_by_path[abs] = folder
|
||||||
|
table.insert(node.nodes, folder)
|
||||||
elseif t == "file" then
|
elseif t == "file" then
|
||||||
table.insert(node.nodes, builders.file(node, abs, name))
|
local file = builders.file(node, abs, name)
|
||||||
|
nodes_by_path[abs] = file
|
||||||
|
table.insert(node.nodes, file)
|
||||||
elseif t == "link" then
|
elseif t == "link" then
|
||||||
local link = builders.link(node, abs, name)
|
local link = builders.link(node, abs, name)
|
||||||
if link.link_to ~= nil then
|
if link.link_to ~= nil then
|
||||||
|
nodes_by_path[abs] = link
|
||||||
table.insert(node.nodes, link)
|
table.insert(node.nodes, link)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
59
lua/nvim-tree/explorer/watch.lua
Normal file
59
lua/nvim-tree/explorer/watch.lua
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
local log = require "nvim-tree.log"
|
||||||
|
local utils = require "nvim-tree.utils"
|
||||||
|
local git = require "nvim-tree.git"
|
||||||
|
local Watcher = require("nvim-tree.watcher").Watcher
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local function reload_and_get_git_project(path)
|
||||||
|
local project_root = git.get_project_root(path)
|
||||||
|
git.reload_project(project_root)
|
||||||
|
return project_root, git.get_project(project_root) or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function update_parent_statuses(node, project, root)
|
||||||
|
while project and node and node.absolute_path ~= root do
|
||||||
|
require("nvim-tree.explorer.common").update_git_status(node, false, project)
|
||||||
|
node = node.parent
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function is_git(path)
|
||||||
|
return path:match "%.git$" ~= nil or path:match(utils.path_add_trailing ".git") ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.create_watcher(absolute_path)
|
||||||
|
if not M.enabled then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
if is_git(absolute_path) then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
log.line("watcher", "node start '%s'", absolute_path)
|
||||||
|
Watcher.new {
|
||||||
|
absolute_path = absolute_path,
|
||||||
|
interval = M.interval,
|
||||||
|
on_event = function(path)
|
||||||
|
local n = utils.get_node_from_path(absolute_path)
|
||||||
|
if not n then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
log.line("watcher", "node event '%s'", path)
|
||||||
|
|
||||||
|
local node = utils.get_parent_of_group(n)
|
||||||
|
local project_root, project = reload_and_get_git_project(path)
|
||||||
|
require("nvim-tree.explorer.reload").reload(node, project)
|
||||||
|
update_parent_statuses(node, project, project_root)
|
||||||
|
|
||||||
|
require("nvim-tree.renderer").draw()
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.enabled = opts.filesystem_watchers.enable
|
||||||
|
M.interval = opts.filesystem_watchers.interval
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
|
local log = require "nvim-tree.log"
|
||||||
|
local utils = require "nvim-tree.utils"
|
||||||
local git_utils = require "nvim-tree.git.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 M = {
|
local M = {
|
||||||
config = nil,
|
config = nil,
|
||||||
@@ -13,6 +16,19 @@ function M.reload()
|
|||||||
end
|
end
|
||||||
|
|
||||||
for project_root in pairs(M.projects) do
|
for project_root in pairs(M.projects) do
|
||||||
|
M.reload_project(project_root)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M.projects
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.reload_project(project_root)
|
||||||
|
local project = M.projects[project_root]
|
||||||
|
if not project or not M.config.enable then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local watcher = M.projects[project_root].watcher
|
||||||
M.projects[project_root] = {}
|
M.projects[project_root] = {}
|
||||||
local git_status = Runner.run {
|
local git_status = Runner.run {
|
||||||
project_root = project_root,
|
project_root = project_root,
|
||||||
@@ -23,10 +39,12 @@ function M.reload()
|
|||||||
M.projects[project_root] = {
|
M.projects[project_root] = {
|
||||||
files = git_status,
|
files = git_status,
|
||||||
dirs = git_utils.file_status_to_dir_status(git_status, project_root),
|
dirs = git_utils.file_status_to_dir_status(git_status, project_root),
|
||||||
|
watcher = watcher,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
return M.projects
|
function M.get_project(project_root)
|
||||||
|
return M.projects[project_root]
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.get_project_root(cwd)
|
function M.get_project_root(cwd)
|
||||||
@@ -42,6 +60,35 @@ function M.get_project_root(cwd)
|
|||||||
return project_root
|
return project_root
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function M.reload_tree_at(project_root)
|
||||||
|
local root_node = utils.get_node_from_path(project_root)
|
||||||
|
if not root_node then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
M.reload_project(project_root)
|
||||||
|
local project = M.get_project(project_root)
|
||||||
|
|
||||||
|
local project_files = project.files and project.files or {}
|
||||||
|
local project_dirs = project.dirs and project.dirs or {}
|
||||||
|
|
||||||
|
local function iterate(n)
|
||||||
|
local parent_ignored = n.git_status == "!!"
|
||||||
|
for _, node in pairs(n.nodes) do
|
||||||
|
node.git_status = project_dirs[node.absolute_path] or project_files[node.absolute_path]
|
||||||
|
if not node.git_status and parent_ignored then
|
||||||
|
node.git_status = "!!"
|
||||||
|
end
|
||||||
|
|
||||||
|
if node.nodes and #node.nodes > 0 then
|
||||||
|
iterate(node)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
iterate(root_node)
|
||||||
|
end
|
||||||
|
|
||||||
function M.load_project_status(cwd)
|
function M.load_project_status(cwd)
|
||||||
if not M.config.enable then
|
if not M.config.enable then
|
||||||
return {}
|
return {}
|
||||||
@@ -64,15 +111,32 @@ function M.load_project_status(cwd)
|
|||||||
list_ignored = true,
|
list_ignored = true,
|
||||||
timeout = M.config.timeout,
|
timeout = M.config.timeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local watcher = nil
|
||||||
|
if M.config.watcher.enable then
|
||||||
|
log.line("watcher", "git start")
|
||||||
|
watcher = Watcher.new {
|
||||||
|
absolute_path = utils.path_join { project_root, ".git" },
|
||||||
|
interval = M.config.watcher.interval,
|
||||||
|
on_event = function()
|
||||||
|
log.line("watcher", "git event")
|
||||||
|
M.reload_tree_at(project_root)
|
||||||
|
require("nvim-tree.renderer").draw()
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
M.projects[project_root] = {
|
M.projects[project_root] = {
|
||||||
files = git_status,
|
files = git_status,
|
||||||
dirs = git_utils.file_status_to_dir_status(git_status, project_root),
|
dirs = git_utils.file_status_to_dir_status(git_status, project_root),
|
||||||
|
watcher = watcher,
|
||||||
}
|
}
|
||||||
return M.projects[project_root]
|
return M.projects[project_root]
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.setup(opts)
|
function M.setup(opts)
|
||||||
M.config = opts.git
|
M.config = opts.git
|
||||||
|
M.config.watcher = opts.filesystem_watchers
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -53,7 +53,9 @@ end
|
|||||||
|
|
||||||
function Runner:_log_raw_output(output)
|
function Runner:_log_raw_output(output)
|
||||||
if output and type(output) == "string" then
|
if output and type(output) == "string" then
|
||||||
log.raw("git", "%s", output)
|
-- TODO put this back after watcher feature completed
|
||||||
|
-- log.raw("git", "%s", output)
|
||||||
|
log.line("git", "done")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,8 @@ function M.get_user_input_char()
|
|||||||
return vim.fn.nr2char(c)
|
return vim.fn.nr2char(c)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- get the node from the tree that matches the predicate
|
-- get the node and index of the node from the tree that matches the predicate.
|
||||||
|
-- The explored nodes are those displayed on the view.
|
||||||
-- @param nodes list of node
|
-- @param nodes list of node
|
||||||
-- @param fn function(node): boolean
|
-- @param fn function(node): boolean
|
||||||
function M.find_node(nodes, fn)
|
function M.find_node(nodes, fn)
|
||||||
@@ -126,6 +127,46 @@ function M.find_node(nodes, fn)
|
|||||||
return node, i
|
return node, i
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- get the node in the tree state depending on the absolute path of the node
|
||||||
|
-- (grouped or hidden too)
|
||||||
|
function M.get_node_from_path(path)
|
||||||
|
local explorer = require("nvim-tree.core").get_explorer()
|
||||||
|
if explorer.absolute_path == path then
|
||||||
|
return explorer
|
||||||
|
end
|
||||||
|
|
||||||
|
local function iterate(nodes)
|
||||||
|
for _, node in pairs(nodes) do
|
||||||
|
if node.absolute_path == path or node.link_to == path then
|
||||||
|
return node
|
||||||
|
end
|
||||||
|
if node.nodes then
|
||||||
|
local res = iterate(node.nodes)
|
||||||
|
if res then
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if node.group_next then
|
||||||
|
local res = iterate { node.group_next }
|
||||||
|
if res then
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return iterate(explorer.nodes)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- get the highest parent of grouped nodes
|
||||||
|
function M.get_parent_of_group(node_)
|
||||||
|
local node = node_
|
||||||
|
while node.parent and node.parent.group_next do
|
||||||
|
node = node.parent
|
||||||
|
end
|
||||||
|
return node
|
||||||
|
end
|
||||||
|
|
||||||
-- return visible nodes indexed by line
|
-- return visible nodes indexed by line
|
||||||
-- @param nodes_all list of node
|
-- @param nodes_all list of node
|
||||||
-- @param line_start first index
|
-- @param line_start first index
|
||||||
|
|||||||
77
lua/nvim-tree/watcher.lua
Normal file
77
lua/nvim-tree/watcher.lua
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
local uv = vim.loop
|
||||||
|
|
||||||
|
local log = require "nvim-tree.log"
|
||||||
|
local utils = require "nvim-tree.utils"
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
local Watcher = {
|
||||||
|
_watchers = {},
|
||||||
|
}
|
||||||
|
Watcher.__index = Watcher
|
||||||
|
|
||||||
|
function Watcher.new(opts)
|
||||||
|
for _, existing in ipairs(Watcher._watchers) do
|
||||||
|
if existing._opts.absolute_path == opts.absolute_path then
|
||||||
|
log.line("watcher", "Watcher:new using existing '%s'", opts.absolute_path)
|
||||||
|
return existing
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
log.line("watcher", "Watcher:new '%s'", opts.absolute_path)
|
||||||
|
|
||||||
|
local watcher = setmetatable({
|
||||||
|
_opts = opts,
|
||||||
|
}, Watcher)
|
||||||
|
|
||||||
|
watcher = watcher:start()
|
||||||
|
|
||||||
|
table.insert(Watcher._watchers, watcher)
|
||||||
|
|
||||||
|
return watcher
|
||||||
|
end
|
||||||
|
|
||||||
|
function Watcher:start()
|
||||||
|
log.line("watcher", "Watcher:start '%s'", self._opts.absolute_path)
|
||||||
|
|
||||||
|
local rc, _, name
|
||||||
|
|
||||||
|
self._p, _, name = uv.new_fs_poll()
|
||||||
|
if not self._p then
|
||||||
|
self._p = nil
|
||||||
|
utils.warn(
|
||||||
|
string.format("Could not initialize an fs_poll watcher for path %s : %s", self._opts.absolute_path, name)
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local poll_cb = vim.schedule_wrap(function(err)
|
||||||
|
if err then
|
||||||
|
log.line("watcher", "poll_cb for %s fail : %s", self._opts.absolute_path, err)
|
||||||
|
else
|
||||||
|
self._opts.on_event(self._opts.absolute_path)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
rc, _, name = uv.fs_poll_start(self._p, self._opts.absolute_path, self._opts.interval, poll_cb)
|
||||||
|
if rc ~= 0 then
|
||||||
|
utils.warn(string.format("Could not start the fs_poll watcher for path %s : %s", self._opts.absolute_path, name))
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function Watcher:stop()
|
||||||
|
log.line("watcher", "Watcher:stop '%s'", self._opts.absolute_path)
|
||||||
|
if self._p then
|
||||||
|
local rc, _, name = uv.fs_poll_stop(self._p)
|
||||||
|
if rc ~= 0 then
|
||||||
|
utils.warn(string.format("Could not stop the fs_poll watcher for path %s : %s", self._opts.absolute_path, name))
|
||||||
|
end
|
||||||
|
self._p = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.Watcher = Watcher
|
||||||
|
|
||||||
|
return M
|
||||||
Reference in New Issue
Block a user