Skip to content

fix(#1731 #1723 #1716): handle all external file system changes #1757

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Nov 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions lua/nvim-tree/actions/finders/find-file.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ local view = require "nvim-tree.view"
local utils = require "nvim-tree.utils"
local renderer = require "nvim-tree.renderer"
local core = require "nvim-tree.core"
local reload = require "nvim-tree.explorer.reload"
local Iterator = require "nvim-tree.iterators.node-iterator"

local M = {}
Expand All @@ -12,18 +13,26 @@ local running = {}
---Find a path in the tree, expand it and focus it
---@param fname string full path
function M.fn(fname)
if running[fname] or not core.get_explorer() then
if not core.get_explorer() then
return
end
running[fname] = true

local ps = log.profile_start("find file %s", fname)
-- always match against the real path
local fname_real = vim.loop.fs_realpath(fname)
if not fname_real then
return
end

if running[fname_real] then
return
end
running[fname_real] = true

local ps = log.profile_start("find file %s", fname_real)

-- we cannot wait for watchers
reload.refresh_nodes_for_path(vim.fn.fnamemodify(fname_real, ":h"))

local line = core.get_nodes_starting_line()

local absolute_paths_searched = {}
Expand Down Expand Up @@ -60,9 +69,9 @@ function M.fn(fname)
view.set_cursor { line, 0 }
end

running[fname] = false
running[fname_real] = false

log.profile_end(ps, "find file %s", fname)
log.profile_end(ps, "find file %s", fname_real)
end

return M
18 changes: 6 additions & 12 deletions lua/nvim-tree/actions/fs/create-file.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ local utils = require "nvim-tree.utils"
local events = require "nvim-tree.events"
local lib = require "nvim-tree.lib"
local core = require "nvim-tree.core"
local watch = require "nvim-tree.explorer.watch"
local notify = require "nvim-tree.notify"

local find_file = require("nvim-tree.actions.finders.find-file").fn

local M = {}

local function create_and_notify(file)
Expand Down Expand Up @@ -99,22 +100,15 @@ function M.fn(node)
is_error = true
break
end
events._dispatch_folder_created(new_file_path)
end
end
if not is_error then
notify.info(new_file_path .. " was properly created")
end
events._dispatch_folder_created(new_file_path)
if M.enable_reload then
require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
else
-- synchronous call required so that we may focus the file now
node = node.nodes ~= nil and node or node.parent
if node then
watch.refresh_path(node.absolute_path)
end
end
utils.focus_file(utils.path_remove_trailing(new_file_path))

-- synchronously refreshes as we can't wait for the watchers
find_file(utils.path_remove_trailing(new_file_path))
end)
end

Expand Down
2 changes: 1 addition & 1 deletion lua/nvim-tree/explorer/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ function Explorer.new(cwd)
local explorer = setmetatable({
absolute_path = cwd,
nodes = {},
watcher = watch.create_watcher(cwd),
open = true,
}, Explorer)
explorer.watcher = watch.create_watcher(explorer)
explorer:_load(explorer)
return explorer
end
Expand Down
24 changes: 17 additions & 7 deletions lua/nvim-tree/explorer/node-builders.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function M.folder(parent, absolute_path, name)
local handle = vim.loop.fs_scandir(absolute_path)
local has_children = handle and vim.loop.fs_scandir_next(handle) ~= nil

return {
local node = {
type = "directory",
absolute_path = absolute_path,
fs_stat = vim.loop.fs_stat(absolute_path),
Expand All @@ -20,8 +20,11 @@ function M.folder(parent, absolute_path, name)
nodes = {},
open = false,
parent = parent,
watcher = watch.create_watcher(absolute_path),
}

node.watcher = watch.create_watcher(node)

return node
end

function M.is_executable(parent, absolute_path, ext)
Expand Down Expand Up @@ -63,16 +66,18 @@ end
function M.link(parent, absolute_path, name)
--- I dont know if this is needed, because in my understanding, there isn't hard links in windows, but just to be sure i changed it.
local link_to = vim.loop.fs_realpath(absolute_path)
local open, nodes, has_children, watcher
if (link_to ~= nil) and vim.loop.fs_stat(link_to).type == "directory" then
local open, nodes, has_children

local is_dir_link = (link_to ~= nil) and vim.loop.fs_stat(link_to).type == "directory"

if is_dir_link then
local handle = vim.loop.fs_scandir(link_to)
has_children = handle and vim.loop.fs_scandir_next(handle) ~= nil
open = false
nodes = {}
watcher = watch.create_watcher(link_to)
end

return {
local node = {
type = "link",
absolute_path = absolute_path,
fs_stat = vim.loop.fs_stat(absolute_path),
Expand All @@ -83,8 +88,13 @@ function M.link(parent, absolute_path, name)
nodes = nodes,
open = open,
parent = parent,
watcher = watcher,
}

if is_dir_link then
node.watcher = watch.create_watcher(node)
end

return node
end

return M
70 changes: 70 additions & 0 deletions lua/nvim-tree/explorer/reload.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ local filters = require "nvim-tree.explorer.filters"
local sorters = require "nvim-tree.explorer.sorters"
local live_filter = require "nvim-tree.live-filter"
local notify = require "nvim-tree.notify"
local git = require "nvim-tree.git"
local log = require "nvim-tree.log"

local NodeIterator = require "nvim-tree.iterators.node-iterator"

local M = {}

Expand All @@ -17,6 +21,19 @@ local function update_status(nodes_by_path, node_ignored, status)
end
end

local function reload_and_get_git_project(path)
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 update_parent_statuses(node, project, root)
while project and node and node.absolute_path ~= root do
common.update_git_status(node, false, project)
node = node.parent
end
end

function M.reload(node, status)
local cwd = node.link_to or node.absolute_path
local handle = vim.loop.fs_scandir(cwd)
Expand All @@ -25,6 +42,8 @@ function M.reload(node, status)
return
end

local ps = log.profile_start("reload %s", node.absolute_path)

if node.group_next then
node.nodes = { node.group_next }
node.group_next = nil
Expand Down Expand Up @@ -110,14 +129,65 @@ function M.reload(node, status)
node.group_next = child_folder_only
local ns = M.reload(child_folder_only, status)
node.nodes = ns or {}
log.profile_end(ps, "reload %s", node.absolute_path)
return ns
end

sorters.merge_sort(node.nodes, sorters.node_comparator)
live_filter.apply_filter(node)
log.profile_end(ps, "reload %s", node.absolute_path)
return node.nodes
end

---Refresh contents and git status for a single node
---@param node table
function M.refresh_node(node)
if type(node) ~= "table" then
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

---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)
local explorer = require("nvim-tree.core").get_explorer()
if not explorer then
return
end

local pn = string.format("refresh_nodes_for_path %s", path)
local ps = log.profile_start(pn)

NodeIterator.builder({ explorer })
:hidden()
:recursor(function(node)
if node.group_next then
return { node.group_next }
end
if node.nodes then
return node.nodes
end
end)
:applier(function(node)
local abs_contains = node.absolute_path and path:match("^" .. node.absolute_path)
local link_contains = node.link_to and path:match("^" .. node.link_to)
if abs_contains or link_contains then
M.refresh_node(node)
end
end)
:iterate()

log.profile_end(ps, pn)
end

function M.setup(opts)
M.config = opts.renderer
end
Expand Down
55 changes: 20 additions & 35 deletions lua/nvim-tree/explorer/watch.lua
Original file line number Diff line number Diff line change
@@ -1,23 +1,9 @@
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, path)
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 vim.fn.fnamemodify(path, ":t") == ".git"
end
Expand Down Expand Up @@ -46,39 +32,38 @@ local function is_folder_ignored(path)
return false
end

function M.refresh_path(path)
log.line("watcher", "node event executing '%s'", path)
local n = utils.get_node_from_path(path)
if not n then
return
function M.create_watcher(node)
if not M.enabled or type(node) ~= "table" then
return nil
end

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

function M.create_watcher(absolute_path)
if not M.enabled then
return nil
local path
if node.type == "link" then
path = node.link_to
else
path = node.absolute_path
end
if is_git(absolute_path) or is_folder_ignored(absolute_path) then

if is_git(path) or is_folder_ignored(path) then
return nil
end

local function callback(watcher)
log.line("watcher", "node event scheduled %s", watcher.context)
log.line("watcher", "node event scheduled refresh %s", watcher.context)
utils.debounce(watcher.context, M.debounce_delay, function()
M.refresh_path(watcher._path)
if node.link_to then
log.line("watcher", "node event executing refresh '%s' -> '%s'", node.link_to, node.absolute_path)
else
log.line("watcher", "node event executing refresh '%s'", node.absolute_path)
end
require("nvim-tree.explorer.reload").refresh_node(node)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will still result in one refresh for each instance of the symlinked dir, however optimising that away requires significant complexity.

require("nvim-tree.renderer").draw()
end)
end

M.uid = M.uid + 1
return Watcher:new(absolute_path, nil, callback, {
context = "explorer:watch:" .. absolute_path .. ":" .. M.uid,
return Watcher:new(path, nil, callback, {
context = "explorer:watch:" .. path .. ":" .. M.uid,
})
end

Expand Down