refactor(#2886): multi instance: node class refactoring: extract links, *_git_status (#2944)

* extract DirectoryLinkNode and FileLinkNode, move Node methods to children

* temporarily move DirectoryNode methods into BaseNode for easier reviewing

* move mostly unchanged DirectoryNode methods back to BaseNode

* tidy

* git.git_status_file takes an array

* update git status of links

* luacheck hack
This commit is contained in:
Alexander Courtis
2024-10-11 17:49:34 +11:00
parent 8859bbb3b8
commit 98ca98cd87
9 changed files with 256 additions and 184 deletions

View File

@@ -0,0 +1,54 @@
local git = require("nvim-tree.git")
local DirectoryNode = require("nvim-tree.node.directory")
---@class (exact) DirectoryLinkNode: DirectoryNode
---@field link_to string absolute path
---@field fs_stat_target uv.fs_stat.result
local DirectoryLinkNode = DirectoryNode:new()
---Static factory method
---@param explorer Explorer
---@param parent Node
---@param absolute_path string
---@param link_to string
---@param name string
---@param fs_stat uv.fs_stat.result?
---@param fs_stat_target uv.fs_stat.result
---@return DirectoryLinkNode? nil on vim.loop.fs_realpath failure
function DirectoryLinkNode:create(explorer, parent, absolute_path, link_to, name, fs_stat, fs_stat_target)
-- create DirectoryNode with the target path for the watcher
local o = DirectoryNode:create(explorer, parent, link_to, name, fs_stat)
o = self:new(o) --[[@as DirectoryLinkNode]]
-- reset absolute path to the link itself
o.absolute_path = absolute_path
o.type = "link"
o.link_to = link_to
o.fs_stat_target = fs_stat_target
return o
end
-----Update the directory GitStatus of link target and the file status of the link itself
-----@param parent_ignored boolean
-----@param status table|nil
function DirectoryLinkNode:update_git_status(parent_ignored, status)
self.git_status = git.git_status_dir(parent_ignored, status, self.link_to, self.absolute_path)
end
---Create a sanitized partial copy of a node, populating children recursively.
---@return DirectoryLinkNode cloned
function DirectoryLinkNode:clone()
local clone = DirectoryNode.clone(self) --[[@as DirectoryLinkNode]]
clone.type = self.type
clone.link_to = self.link_to
clone.fs_stat_target = self.fs_stat_target
return clone
end
return DirectoryLinkNode

View File

@@ -1,3 +1,4 @@
local git = require("nvim-tree.git")
local watch = require("nvim-tree.explorer.watch")
local BaseNode = require("nvim-tree.node")
@@ -58,6 +59,63 @@ function DirectoryNode:destroy()
end
end
---Update the GitStatus of the directory
---@param parent_ignored boolean
---@param status table|nil
function DirectoryNode:update_git_status(parent_ignored, status)
self.git_status = git.git_status_dir(parent_ignored, status, self.absolute_path, nil)
end
---@return GitStatus|nil
function DirectoryNode:get_git_status()
if not self.git_status or not self.explorer.opts.git.show_on_dirs then
return nil
end
local status = {}
if not self:last_group_node().open or self.explorer.opts.git.show_on_open_dirs then
-- dir is closed or we should show on open_dirs
if self.git_status.file ~= nil then
table.insert(status, self.git_status.file)
end
if self.git_status.dir ~= nil then
if self.git_status.dir.direct ~= nil then
for _, s in pairs(self.git_status.dir.direct) do
table.insert(status, s)
end
end
if self.git_status.dir.indirect ~= nil then
for _, s in pairs(self.git_status.dir.indirect) do
table.insert(status, s)
end
end
end
else
-- dir is open and we shouldn't show on open_dirs
if self.git_status.file ~= nil then
table.insert(status, self.git_status.file)
end
if self.git_status.dir ~= nil and self.git_status.dir.direct ~= nil then
local deleted = {
[" D"] = true,
["D "] = true,
["RD"] = true,
["DD"] = true,
}
for _, s in pairs(self.git_status.dir.direct) do
if deleted[s] then
table.insert(status, s)
end
end
end
end
if #status == 0 then
return nil
else
return status
end
end
---Create a sanitized partial copy of a node, populating children recursively.
---@return DirectoryNode cloned
function DirectoryNode:clone()

View File

@@ -1,5 +1,6 @@
local DirectoryLinkNode = require("nvim-tree.node.directory-link")
local DirectoryNode = require("nvim-tree.node.directory")
local LinkNode = require("nvim-tree.node.link")
local FileLinkNode = require("nvim-tree.node.file-link")
local FileNode = require("nvim-tree.node.file")
local Watcher = require("nvim-tree.watcher")
@@ -8,21 +9,37 @@ local M = {}
---Factory function to create the appropriate Node
---@param explorer Explorer
---@param parent Node
---@param abs string
---@param absolute_path string
---@param stat uv.fs_stat.result? -- on nil stat return nil Node
---@param name string
---@return Node?
function M.create_node(explorer, parent, abs, stat, name)
function M.create_node(explorer, parent, absolute_path, stat, name)
if not stat then
return nil
end
if stat.type == "directory" and vim.loop.fs_access(abs, "R") and Watcher.is_fs_event_capable(abs) then
return DirectoryNode:create(explorer, parent, abs, name, stat)
if stat.type == "directory" then
-- directory must be readable and enumerable
if vim.loop.fs_access(absolute_path, "R") and Watcher.is_fs_event_capable(absolute_path) then
return DirectoryNode:create(explorer, parent, absolute_path, name, stat)
end
elseif stat.type == "file" then
return FileNode:create(explorer, parent, abs, name, stat)
-- any file
return FileNode:create(explorer, parent, absolute_path, name, stat)
elseif stat.type == "link" then
return LinkNode:create(explorer, parent, abs, name, stat)
-- link target path and stat must resolve
local link_to = vim.loop.fs_realpath(absolute_path)
local link_to_stat = link_to and vim.loop.fs_stat(link_to)
if not link_to or not link_to_stat then
return
end
-- choose directory or file
if link_to_stat.type == "directory" then
return DirectoryLinkNode:create(explorer, parent, absolute_path, link_to, name, stat, link_to_stat)
else
return FileLinkNode:create(explorer, parent, absolute_path, link_to, name, stat, link_to_stat)
end
end
return nil

View File

@@ -0,0 +1,50 @@
local git = require("nvim-tree.git")
local FileNode = require("nvim-tree.node.file")
---@class (exact) FileLinkNode: FileNode
---@field link_to string absolute path
---@field fs_stat_target uv.fs_stat.result
local FileLinkNode = FileNode:new()
---Static factory method
---@param explorer Explorer
---@param parent Node
---@param absolute_path string
---@param link_to string
---@param name string
---@param fs_stat uv.fs_stat.result?
---@param fs_stat_target uv.fs_stat.result
---@return FileLinkNode? nil on vim.loop.fs_realpath failure
function FileLinkNode:create(explorer, parent, absolute_path, link_to, name, fs_stat, fs_stat_target)
local o = FileNode:create(explorer, parent, absolute_path, name, fs_stat)
o = self:new(o) --[[@as FileLinkNode]]
o.type = "link"
o.link_to = link_to
o.fs_stat_target = fs_stat_target
return o
end
-----Update the GitStatus of the target otherwise the link itself
-----@param parent_ignored boolean
-----@param status table|nil
function FileLinkNode:update_git_status(parent_ignored, status)
self.git_status = git.git_status_file(parent_ignored, status, self.link_to, self.absolute_path)
end
---Create a sanitized partial copy of a node
---@return FileLinkNode cloned
function FileLinkNode:clone()
local clone = FileNode.clone(self) --[[@as FileLinkNode]]
clone.type = self.type
clone.link_to = self.link_to
clone.fs_stat_target = self.fs_stat_target
return clone
end
return FileLinkNode

View File

@@ -1,3 +1,4 @@
local git = require("nvim-tree.git")
local utils = require("nvim-tree.utils")
local BaseNode = require("nvim-tree.node")
@@ -36,7 +37,23 @@ function FileNode:create(explorer, parent, absolute_path, name, fs_stat)
return o
end
---Create a sanitized partial copy of a node, populating children recursively.
---Update the GitStatus of the file
---@param parent_ignored boolean
---@param status table|nil
function FileNode:update_git_status(parent_ignored, status)
self.git_status = git.git_status_file(parent_ignored, status, self.absolute_path, nil)
end
---@return GitStatus|nil
function FileNode:get_git_status()
if not self.git_status then
return nil
end
return self.git_status.file and { self.git_status.file }
end
---Create a sanitized partial copy of a node
---@return FileNode cloned
function FileNode:clone()
local clone = BaseNode.clone(self) --[[@as FileNode]]

View File

@@ -21,7 +21,7 @@ local git = require("nvim-tree.git")
---@field diag_status DiagStatus?
local BaseNode = {}
---@alias Node RootNode|BaseNode|DirectoryNode|FileNode|LinkNode
---@alias Node RootNode|BaseNode|DirectoryNode|FileNode|DirectoryLinkNode|FileLinkNode
---@param o BaseNode?
---@return BaseNode
@@ -63,84 +63,16 @@ function BaseNode:has_one_child_folder()
return #self.nodes == 1 and self.nodes[1].nodes and vim.loop.fs_access(self.nodes[1].absolute_path, "R") or false
end
--luacheck: push ignore 212
---Update the GitStatus of the node
---@param parent_ignored boolean
---@param status table|nil
function BaseNode:update_git_status(parent_ignored, status)
local get_status
if self.nodes then
get_status = git.git_status_dir
else
get_status = git.git_status_file
end
-- status of the node's absolute path
self.git_status = get_status(parent_ignored, status, self.absolute_path)
-- status of the link target, if the link itself is not dirty
if self.link_to and not self.git_status then
self.git_status = get_status(parent_ignored, status, self.link_to)
end
---@param status table?
function BaseNode:update_git_status(parent_ignored, status) ---@diagnostic disable-line: unused-local
end
--luacheck: pop
---@return GitStatus|nil
---@return GitStatus?
function BaseNode:get_git_status()
if not self.git_status then
-- status doesn't exist
return nil
end
if not self.nodes then
-- file
return self.git_status.file and { self.git_status.file }
end
-- dir
if not self.explorer.opts.git.show_on_dirs then
return nil
end
local status = {}
if not self:last_group_node().open or self.explorer.opts.git.show_on_open_dirs then
-- dir is closed or we should show on open_dirs
if self.git_status.file ~= nil then
table.insert(status, self.git_status.file)
end
if self.git_status.dir ~= nil then
if self.git_status.dir.direct ~= nil then
for _, s in pairs(self.git_status.dir.direct) do
table.insert(status, s)
end
end
if self.git_status.dir.indirect ~= nil then
for _, s in pairs(self.git_status.dir.indirect) do
table.insert(status, s)
end
end
end
else
-- dir is open and we shouldn't show on open_dirs
if self.git_status.file ~= nil then
table.insert(status, self.git_status.file)
end
if self.git_status.dir ~= nil and self.git_status.dir.direct ~= nil then
local deleted = {
[" D"] = true,
["D "] = true,
["RD"] = true,
["DD"] = true,
}
for _, s in pairs(self.git_status.dir.direct) do
if deleted[s] then
table.insert(status, s)
end
end
end
end
if #status == 0 then
return nil
else
return status
end
end
---@param projects table
@@ -176,7 +108,8 @@ end
-- If node is grouped, return the last node in the group. Otherwise, return the given node.
---@return Node
function BaseNode:last_group_node()
local node = self --[[@as BaseNode]]
local node = self
--- @cast node BaseNode
while node.group_next do
node = node.group_next
@@ -185,8 +118,8 @@ function BaseNode:last_group_node()
return node
end
---@param project table|nil
---@param root string|nil
---@param project table?
---@param root string?
function BaseNode:update_parent_statuses(project, root)
local node = self
while project and node do

View File

@@ -1,89 +0,0 @@
local watch = require("nvim-tree.explorer.watch")
local BaseNode = require("nvim-tree.node")
---@class (exact) LinkNode: BaseNode
---@field has_children boolean
---@field group_next Node? -- If node is grouped, this points to the next child dir/link node
---@field link_to string absolute path
---@field nodes Node[]
---@field open boolean
local LinkNode = BaseNode:new()
---Static factory method
---@param explorer Explorer
---@param parent Node
---@param absolute_path string
---@param name string
---@param fs_stat uv.fs_stat.result?
---@return LinkNode? nil on vim.loop.fs_realpath failure
function LinkNode:create(explorer, parent, absolute_path, name, fs_stat)
-- INFO: sometimes fs_realpath returns nil
-- I expect this be a bug in glibc, because it fails to retrieve the path for some
-- links (for instance libr2.so in /usr/lib) and thus even with a C program realpath fails
-- when it has no real reason to. Maybe there is a reason, but errno is definitely wrong.
local link_to = vim.loop.fs_realpath(absolute_path)
if not link_to then
return nil
end
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 and link_to then
local handle = vim.loop.fs_scandir(link_to)
has_children = handle and vim.loop.fs_scandir_next(handle) ~= nil or false
open = false
nodes = {}
end
---@type LinkNode
local o = {
type = "link",
explorer = explorer,
absolute_path = absolute_path,
executable = false,
fs_stat = fs_stat,
hidden = false,
is_dot = false,
name = name,
parent = parent,
watcher = nil,
diag_status = nil,
has_children = has_children,
group_next = nil,
link_to = link_to,
nodes = nodes,
open = open,
}
o = self:new(o) --[[@as LinkNode]]
if is_dir_link then
o.watcher = watch.create_watcher(o)
end
return o
end
---Create a sanitized partial copy of a node, populating children recursively.
---@return LinkNode cloned
function LinkNode:clone()
local clone = BaseNode.clone(self) --[[@as LinkNode]]
clone.has_children = self.has_children
clone.group_next = nil
clone.link_to = self.link_to
clone.nodes = {}
clone.open = self.open
if self.nodes then
for _, child in ipairs(self.nodes) do
table.insert(clone.nodes, child:clone())
end
end
return clone
end
return LinkNode