chore: migrate to classic (#2991)

* add classic, migrating nodes classes

* add mixins to classic

* typechecked optargs constructors for nodes

* typechecked optargs constructors for watcher and event

* luacheck

* typechecked optargs constructors for GitRunner

* typechecked optargs constructors for Sorter

* typechecked optargs constructors for decorators, WIP

* typechecked optargs constructors for decorators, WIP

* typechecked optargs constructors for decorators

* remove class

* replace enums with named maps

* Renderer and Builder use classic, tidy opts

* LiveFilter uses classic, tidy opts

* Filter uses classic, tidy opts

* add FilterTypes named map

* move toggles into filters

* Marks uses classic, tidy opts

* Sorter uses classic, tidy opts

* Clipboard uses classic, tidy opts

* use supers for node methods

* HighlightDisplay uses classic

* protected :new

* Watcher tidy

* Revert "use supers for node methods"

This reverts commit 9fc7a866ec.

* Watcher tidy

* format

* format

* Filters private methods

* format

* Sorter type safety

* Sorter type safety

* Sorter type safety

* Sorter type safety

* Sorter type safety

* Sorter type safety

* tidy Runner

* tidy hi-test name
This commit is contained in:
Alexander Courtis 2024-11-09 14:14:04 +11:00 committed by GitHub
parent 610a1c189b
commit 3fc8de198c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
42 changed files with 995 additions and 1115 deletions

View File

@ -7,6 +7,7 @@ local notify = require("nvim-tree.notify")
local find_file = require("nvim-tree.actions.finders.find-file").fn local find_file = require("nvim-tree.actions.finders.find-file").fn
local Class = require("nvim-tree.classic")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
---@alias ClipboardAction "copy" | "cut" ---@alias ClipboardAction "copy" | "cut"
@ -14,35 +15,31 @@ local DirectoryNode = require("nvim-tree.node.directory")
---@alias ClipboardActionFn fun(source: string, dest: string): boolean, string? ---@alias ClipboardActionFn fun(source: string, dest: string): boolean, string?
---@class Clipboard to handle all actions.fs clipboard API ---@class (exact) Clipboard: Class
---@field config table hydrated user opts.filters
---@field private explorer Explorer ---@field private explorer Explorer
---@field private data ClipboardData ---@field private data ClipboardData
---@field private clipboard_name string ---@field private clipboard_name string
---@field private reg string ---@field private reg string
local Clipboard = {} local Clipboard = Class:extend()
---@param opts table user options ---@class Clipboard
---@param explorer Explorer ---@overload fun(args: ClipboardArgs): Clipboard
---@return Clipboard
function Clipboard:new(opts, explorer) ---@class (exact) ClipboardArgs
---@type Clipboard ---@field explorer Explorer
local o = {
explorer = explorer, ---@protected
data = { ---@param args ClipboardArgs
copy = {}, function Clipboard:new(args)
cut = {}, self.explorer = args.explorer
},
clipboard_name = opts.actions.use_system_clipboard and "system" or "neovim", self.data = {
reg = opts.actions.use_system_clipboard and "+" or "1", copy = {},
config = { cut = {},
filesystem_watchers = opts.filesystem_watchers,
},
} }
setmetatable(o, self) self.clipboard_name = self.explorer.opts.actions.use_system_clipboard and "system" or "neovim"
self.__index = self self.reg = self.explorer.opts.actions.use_system_clipboard and "+" or "1"
return o
end end
---@param source string ---@param source string
@ -252,7 +249,7 @@ function Clipboard:do_paste(node, action, action_fn)
end end
self.data[action] = {} self.data[action] = {}
if not self.config.filesystem_watchers.enable then if not self.explorer.opts.filesystem_watchers.enable then
self.explorer:reload_explorer() self.explorer:reload_explorer()
end end
end end

View File

@ -2,7 +2,6 @@ local M = {}
M.collapse_all = require("nvim-tree.actions.tree.modifiers.collapse-all") M.collapse_all = require("nvim-tree.actions.tree.modifiers.collapse-all")
M.expand_all = require("nvim-tree.actions.tree.modifiers.expand-all") M.expand_all = require("nvim-tree.actions.tree.modifiers.expand-all")
M.toggles = require("nvim-tree.actions.tree.modifiers.toggles")
function M.setup(opts) function M.setup(opts)
M.expand_all.setup(opts) M.expand_all.setup(opts)

View File

@ -1,73 +0,0 @@
local utils = require("nvim-tree.utils")
local core = require("nvim-tree.core")
local M = {}
---@param explorer Explorer
local function reload(explorer)
local node = explorer:get_node_at_cursor()
explorer:reload_explorer()
if node then
utils.focus_node_or_parent(node)
end
end
local function wrap_explorer(fn)
return function(...)
local explorer = core.get_explorer()
if explorer then
return fn(explorer, ...)
end
end
end
---@param explorer Explorer
local function custom(explorer)
explorer.filters.config.filter_custom = not explorer.filters.config.filter_custom
reload(explorer)
end
---@param explorer Explorer
local function git_ignored(explorer)
explorer.filters.config.filter_git_ignored = not explorer.filters.config.filter_git_ignored
reload(explorer)
end
---@param explorer Explorer
local function git_clean(explorer)
explorer.filters.config.filter_git_clean = not explorer.filters.config.filter_git_clean
reload(explorer)
end
---@param explorer Explorer
local function no_buffer(explorer)
explorer.filters.config.filter_no_buffer = not explorer.filters.config.filter_no_buffer
reload(explorer)
end
---@param explorer Explorer
local function no_bookmark(explorer)
explorer.filters.config.filter_no_bookmark = not explorer.filters.config.filter_no_bookmark
reload(explorer)
end
---@param explorer Explorer
local function dotfiles(explorer)
explorer.filters.config.filter_dotfiles = not explorer.filters.config.filter_dotfiles
reload(explorer)
end
---@param explorer Explorer
local function enable(explorer)
explorer.filters.config.enable = not explorer.filters.config.enable
reload(explorer)
end
M.custom = wrap_explorer(custom)
M.git_ignored = wrap_explorer(git_ignored)
M.git_clean = wrap_explorer(git_clean)
M.no_buffer = wrap_explorer(no_buffer)
M.no_bookmark = wrap_explorer(no_bookmark)
M.dotfiles = wrap_explorer(dotfiles)
M.enable = wrap_explorer(enable)
return M

View File

@ -2,7 +2,7 @@ local core = require("nvim-tree.core")
local view = require("nvim-tree.view") local view = require("nvim-tree.view")
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local actions = require("nvim-tree.actions") local actions = require("nvim-tree.actions")
local appearance_diagnostics = require("nvim-tree.appearance.diagnostics") local appearance_hi_test = require("nvim-tree.appearance.hi-test")
local events = require("nvim-tree.events") local events = require("nvim-tree.events")
local help = require("nvim-tree.help") local help = require("nvim-tree.help")
local keymap = require("nvim-tree.keymap") local keymap = require("nvim-tree.keymap")
@ -89,6 +89,22 @@ local function wrap_node_or_nil(fn)
end end
end end
---Invoke a member's method on the singleton explorer.
---Print error when setup not called.
---@param explorer_member string explorer member name
---@param member_method string method name to invoke on member
---@param ... any passed to method
---@return fun(...): any
local function wrap_explorer_member_args(explorer_member, member_method, ...)
local method_args = ...
return wrap(function(...)
local explorer = core.get_explorer()
if explorer then
return explorer[explorer_member][member_method](explorer[explorer_member], method_args, ...)
end
end)
end
---Invoke a member's method on the singleton explorer. ---Invoke a member's method on the singleton explorer.
---Print error when setup not called. ---Print error when setup not called.
---@param explorer_member string explorer member name ---@param explorer_member string explorer member name
@ -165,13 +181,13 @@ Api.tree.find_file = wrap(actions.tree.find_file.fn)
Api.tree.search_node = wrap(actions.finders.search_node.fn) Api.tree.search_node = wrap(actions.finders.search_node.fn)
Api.tree.collapse_all = wrap(actions.tree.modifiers.collapse_all.fn) Api.tree.collapse_all = wrap(actions.tree.modifiers.collapse_all.fn)
Api.tree.expand_all = wrap_node(actions.tree.modifiers.expand_all.fn) Api.tree.expand_all = wrap_node(actions.tree.modifiers.expand_all.fn)
Api.tree.toggle_enable_filters = wrap(actions.tree.modifiers.toggles.enable) Api.tree.toggle_enable_filters = wrap_explorer_member("filters", "toggle")
Api.tree.toggle_gitignore_filter = wrap(actions.tree.modifiers.toggles.git_ignored) Api.tree.toggle_gitignore_filter = wrap_explorer_member_args("filters", "toggle", "git_ignored")
Api.tree.toggle_git_clean_filter = wrap(actions.tree.modifiers.toggles.git_clean) Api.tree.toggle_git_clean_filter = wrap_explorer_member_args("filters", "toggle", "git_clean")
Api.tree.toggle_no_buffer_filter = wrap(actions.tree.modifiers.toggles.no_buffer) Api.tree.toggle_no_buffer_filter = wrap_explorer_member_args("filters", "toggle", "no_buffer")
Api.tree.toggle_custom_filter = wrap(actions.tree.modifiers.toggles.custom) Api.tree.toggle_custom_filter = wrap_explorer_member_args("filters", "toggle", "custom")
Api.tree.toggle_hidden_filter = wrap(actions.tree.modifiers.toggles.dotfiles) Api.tree.toggle_hidden_filter = wrap_explorer_member_args("filters", "toggle", "dotfiles")
Api.tree.toggle_no_bookmark_filter = wrap(actions.tree.modifiers.toggles.no_bookmark) Api.tree.toggle_no_bookmark_filter = wrap_explorer_member_args("filters", "toggle", "no_bookmark")
Api.tree.toggle_help = wrap(help.toggle) Api.tree.toggle_help = wrap(help.toggle)
Api.tree.is_tree_buf = wrap(utils.is_nvim_tree_buf) Api.tree.is_tree_buf = wrap(utils.is_nvim_tree_buf)
@ -289,7 +305,7 @@ Api.config.mappings.get_keymap = wrap(keymap.get_keymap)
Api.config.mappings.get_keymap_default = wrap(keymap.get_keymap_default) Api.config.mappings.get_keymap_default = wrap(keymap.get_keymap_default)
Api.config.mappings.default_on_attach = keymap.default_on_attach Api.config.mappings.default_on_attach = keymap.default_on_attach
Api.diagnostics.hi_test = wrap(appearance_diagnostics.hi_test) Api.diagnostics.hi_test = wrap(appearance_hi_test)
Api.commands.get = wrap(function() Api.commands.get = wrap(function()
return require("nvim-tree.commands").get() return require("nvim-tree.commands").get()

View File

@ -1,45 +1,46 @@
local appearance = require("nvim-tree.appearance") local appearance = require("nvim-tree.appearance")
local Class = require("nvim-tree.classic")
-- others with name and links less than this arbitrary value are short -- others with name and links less than this arbitrary value are short
local SHORT_LEN = 50 local SHORT_LEN = 50
local M = {} ---@class (exact) HighlightDisplay: Class for :NvimTreeHiTest
---@class HighlightDisplay for :NvimTreeHiTest
---@field group string nvim-tree highlight group name ---@field group string nvim-tree highlight group name
---@field links string link chain to a concretely defined group ---@field links string link chain to a concretely defined group
---@field def string :hi concrete definition after following any links ---@field def string :hi concrete definition after following any links
local HighlightDisplay = {} local HighlightDisplay = Class:extend()
---@param group string nvim-tree highlight group name ---@class HighlightDisplay
---@return HighlightDisplay ---@overload fun(args: HighlightDisplayArgs): HighlightDisplay
function HighlightDisplay:new(group)
local o = {}
setmetatable(o, self)
self.__index = self
o.group = group ---@class (exact) HighlightDisplayArgs
local concrete = o.group ---@field group string nvim-tree highlight group name
---@protected
---@param args HighlightDisplayArgs
function HighlightDisplay:new(args)
self.group = args.group
local concrete = self.group
-- maybe follow links -- maybe follow links
local links = {} local links = {}
local link = vim.api.nvim_get_hl(0, { name = o.group }).link local link = vim.api.nvim_get_hl(0, { name = self.group }).link
while link do while link do
table.insert(links, link) table.insert(links, link)
concrete = link concrete = link
link = vim.api.nvim_get_hl(0, { name = link }).link link = vim.api.nvim_get_hl(0, { name = link }).link
end end
o.links = table.concat(links, " ") self.links = table.concat(links, " ")
-- concrete definition -- concrete definition
local ok, res = pcall(vim.api.nvim_cmd, { cmd = "highlight", args = { concrete } }, { output = true }) local ok, res = pcall(vim.api.nvim_cmd, { cmd = "highlight", args = { concrete } }, { output = true })
if ok and type(res) == "string" then if ok and type(res) == "string" then
o.def = res:gsub(".*xxx *", "") self.def = res:gsub(".*xxx *", "")
else else
o.def = "" self.def = ""
end end
return o
end end
---Render one group. ---Render one group.
@ -87,7 +88,7 @@ end
---Run a test similar to :so $VIMRUNTIME/syntax/hitest.vim ---Run a test similar to :so $VIMRUNTIME/syntax/hitest.vim
---Display all nvim-tree and neovim highlight groups, their link chain and actual definition ---Display all nvim-tree and neovim highlight groups, their link chain and actual definition
function M.hi_test() return function()
-- create a buffer -- create a buffer
local bufnr = vim.api.nvim_create_buf(false, true) local bufnr = vim.api.nvim_create_buf(false, true)
@ -96,7 +97,7 @@ function M.hi_test()
-- nvim-tree groups, ordered -- nvim-tree groups, ordered
local displays = {} local displays = {}
for _, highlight_group in ipairs(appearance.HIGHLIGHT_GROUPS) do for _, highlight_group in ipairs(appearance.HIGHLIGHT_GROUPS) do
local display = HighlightDisplay:new(highlight_group.group) local display = HighlightDisplay({ group = highlight_group.group })
table.insert(displays, display) table.insert(displays, display)
end end
l = render_displays("nvim-tree", displays, bufnr, l) l = render_displays("nvim-tree", displays, bufnr, l)
@ -110,7 +111,7 @@ function M.hi_test()
if ok then if ok then
for group in string.gmatch(out, "(%w*)%s+xxx") do for group in string.gmatch(out, "(%w*)%s+xxx") do
if group:find("NvimTree", 1, true) ~= 1 then if group:find("NvimTree", 1, true) ~= 1 then
local display = HighlightDisplay:new(group) local display = HighlightDisplay({ group = group })
if #display.group + #display.links > SHORT_LEN then if #display.group + #display.links > SHORT_LEN then
table.insert(displays_long, display) table.insert(displays_long, display)
else else
@ -137,5 +138,3 @@ function M.hi_test()
vim.cmd.buffer(bufnr) vim.cmd.buffer(bufnr)
end end
return M

View File

@ -1,47 +0,0 @@
---Generic class, useful for inheritence.
---@class (exact) Class
local Class = {}
---@generic T
---@param self T
---@param o T|nil
---@return T
function Class:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self ---@diagnostic disable-line: inject-field
return o
end
---Object is an instance of class
---This will start with the lowest class and loop over all the superclasses.
---@generic T
---@param class T
---@return boolean
function Class:is(class)
local mt = getmetatable(self)
while mt do
if mt == class then
return true
end
mt = getmetatable(mt)
end
return false
end
---Return object if it is an instance of class, otherwise nil
---@generic T
---@param class T
---@return T|nil
function Class:as(class)
return self:is(class) and self or nil
end
-- avoid unused param warnings in abstract methods
---@param ... any
function Class:nop(...) --luacheck: ignore 212
end
return Class

91
lua/nvim-tree/classic.lua Normal file
View File

@ -0,0 +1,91 @@
--
-- classic
--
-- Copyright (c) 2014, rxi
--
-- This module is free software; you can redistribute it and/or modify it under
-- the terms of the MIT license. See LICENSE for details.
--
-- https://github.com/rxi/classic
--
---@class (exact) Class
---@field super Class
---@field private implements table<Class, boolean>
local Class = {}
Class.__index = Class ---@diagnostic disable-line: inject-field
---Default constructor
---@protected
function Class:new(...) --luacheck: ignore 212
end
---Extend a class, setting .super
function Class:extend()
local cls = {}
for k, v in pairs(self) do
if k:find("__") == 1 then
cls[k] = v
end
end
cls.__index = cls
cls.super = self
setmetatable(cls, self)
return cls
end
---Implement the functions of a mixin
---Add the mixin to .implements
---@param mixin Class
function Class:implement(mixin)
if not rawget(self, "implements") then
-- set on the class itself instead of parents
rawset(self, "implements", {})
end
self.implements[mixin] = true
for k, v in pairs(mixin) do
if self[k] == nil and type(v) == "function" then
self[k] = v
end
end
end
---Object is an instance of class or implements a mixin
---@generic T
---@param class T
---@return boolean
function Class:is(class)
local mt = getmetatable(self)
while mt do
if mt == class then
return true
end
if mt.implements and mt.implements[class] then
return true
end
mt = getmetatable(mt)
end
return false
end
---Return object if :is otherwise nil
---@generic T
---@param class T
---@return T|nil
function Class:as(class)
return self:is(class) and self or nil
end
---Constructor to create instance, call :new and return
function Class:__call(...)
local obj = setmetatable({}, self)
obj:new(...)
return obj
end
-- avoid unused param warnings in abstract methods
---@param ... any
function Class:nop(...) --luacheck: ignore 212
end
return Class

View File

@ -1,4 +1,5 @@
local events = require("nvim-tree.events") local events = require("nvim-tree.events")
local notify = require("nvim-tree.notify")
local view = require("nvim-tree.view") local view = require("nvim-tree.view")
local log = require("nvim-tree.log") local log = require("nvim-tree.log")
@ -15,7 +16,21 @@ function M.init(foldername)
if TreeExplorer then if TreeExplorer then
TreeExplorer:destroy() TreeExplorer:destroy()
end end
TreeExplorer = require("nvim-tree.explorer"):create(foldername)
local err, path
if foldername then
path, err = vim.loop.fs_realpath(foldername)
else
path, err = vim.loop.cwd()
end
if path then
TreeExplorer = require("nvim-tree.explorer")({ path = path })
else
notify.error(err)
TreeExplorer = nil
end
if not first_init_done then if not first_init_done then
events._dispatch_ready() events._dispatch_ready()
first_init_done = true first_init_done = true

View File

@ -1,32 +1,5 @@
local M = {} local M = {}
---Must be synced with uv.fs_stat.result as it is compared with it
---@enum (key) NODE_TYPE
M.NODE_TYPE = {
directory = 1,
file = 2,
link = 4,
}
---Setup options for "highlight_*"
---@enum HL_POSITION
M.HL_POSITION = {
none = 0,
icon = 1,
name = 2,
all = 4,
}
---Setup options for "*_placement"
---@enum ICON_PLACEMENT
M.ICON_PLACEMENT = {
none = 0,
signcolumn = 1,
before = 2,
after = 3,
right_align = 4,
}
---Reason for filter in filter.lua ---Reason for filter in filter.lua
---@enum FILTER_REASON ---@enum FILTER_REASON
M.FILTER_REASON = { M.FILTER_REASON = {

View File

@ -1,52 +1,59 @@
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON
---@class Filters to handle all opts.filters and related API local Class = require("nvim-tree.classic")
---@field config table hydrated user opts.filters
---@alias FilterType "custom" | "dotfiles" | "git_ignored" | "git_clean" | "no_buffer" | "no_bookmark"
---@class (exact) Filters: Class
---@field enabled boolean
---@field state table<FilterType, boolean>
---@field private explorer Explorer ---@field private explorer Explorer
---@field private exclude_list string[] filters.exclude ---@field private exclude_list string[] filters.exclude
---@field private ignore_list string[] filters.custom string table ---@field private ignore_list table<string, boolean> filters.custom string table
---@field private custom_function (fun(absolute_path: string): boolean)|nil filters.custom function ---@field private custom_function (fun(absolute_path: string): boolean)|nil filters.custom function
local Filters = {} local Filters = Class:extend()
---@param opts table user options ---@class Filters
---@param explorer Explorer ---@overload fun(args: FiltersArgs): Filters
---@return Filters
function Filters:new(opts, explorer) ---@class (exact) FiltersArgs
local o = { ---@field explorer Explorer
explorer = explorer,
ignore_list = {}, ---@protected
exclude_list = opts.filters.exclude, ---@param args FiltersArgs
custom_function = nil, function Filters:new(args)
config = { self.explorer = args.explorer
enable = opts.filters.enable, self.ignore_list = {}
filter_custom = true, self.exclude_list = self.explorer.opts.filters.exclude
filter_dotfiles = opts.filters.dotfiles, self.custom_function = nil
filter_git_ignored = opts.filters.git_ignored,
filter_git_clean = opts.filters.git_clean, self.enabled = self.explorer.opts.filters.enable
filter_no_buffer = opts.filters.no_buffer, self.state = {
filter_no_bookmark = opts.filters.no_bookmark, custom = true,
}, dotfiles = self.explorer.opts.filters.dotfiles,
git_ignored = self.explorer.opts.filters.git_ignored,
git_clean = self.explorer.opts.filters.git_clean,
no_buffer = self.explorer.opts.filters.no_buffer,
no_bookmark = self.explorer.opts.filters.no_bookmark,
} }
local custom_filter = opts.filters.custom local custom_filter = self.explorer.opts.filters.custom
if type(custom_filter) == "function" then if type(custom_filter) == "function" then
o.custom_function = custom_filter self.custom_function = custom_filter
else else
if custom_filter and #custom_filter > 0 then if custom_filter and #custom_filter > 0 then
for _, filter_name in pairs(custom_filter) do for _, filter_name in pairs(custom_filter) do
o.ignore_list[filter_name] = true self.ignore_list[filter_name] = true
end end
end end
end end
setmetatable(o, self)
self.__index = self
return o
end end
---@private
---@param path string ---@param path string
---@return boolean ---@return boolean
local function is_excluded(self, path) function Filters:is_excluded(path)
for _, node in ipairs(self.exclude_list) do for _, node in ipairs(self.exclude_list) do
if path:match(node) then if path:match(node) then
return true return true
@ -56,10 +63,11 @@ local function is_excluded(self, path)
end end
---Check if the given path is git clean/ignored ---Check if the given path is git clean/ignored
---@private
---@param path string Absolute path ---@param path string Absolute path
---@param project GitProject from prepare ---@param project GitProject from prepare
---@return boolean ---@return boolean
local function git(self, path, project) function Filters:git(path, project)
if type(project) ~= "table" or type(project.files) ~= "table" or type(project.dirs) ~= "table" then if type(project) ~= "table" or type(project.files) ~= "table" or type(project.dirs) ~= "table" then
return false return false
end end
@ -70,12 +78,12 @@ local function git(self, path, project)
xy = xy or project.dirs.indirect[path] and project.dirs.indirect[path][1] xy = xy or project.dirs.indirect[path] and project.dirs.indirect[path][1]
-- filter ignored; overrides clean as they are effectively dirty -- filter ignored; overrides clean as they are effectively dirty
if self.config.filter_git_ignored and xy == "!!" then if self.state.git_ignored and xy == "!!" then
return true return true
end end
-- filter clean -- filter clean
if self.config.filter_git_clean and not xy then if self.state.git_clean and not xy then
return true return true
end end
@ -83,11 +91,12 @@ local function git(self, path, project)
end end
---Check if the given path has no listed buffer ---Check if the given path has no listed buffer
---@private
---@param path string Absolute path ---@param path string Absolute path
---@param bufinfo table vim.fn.getbufinfo { buflisted = 1 } ---@param bufinfo table vim.fn.getbufinfo { buflisted = 1 }
---@return boolean ---@return boolean
local function buf(self, path, bufinfo) function Filters:buf(path, bufinfo)
if not self.config.filter_no_buffer or type(bufinfo) ~= "table" then if not self.state.no_buffer or type(bufinfo) ~= "table" then
return false return false
end end
@ -101,19 +110,21 @@ local function buf(self, path, bufinfo)
return true return true
end end
---@private
---@param path string ---@param path string
---@return boolean ---@return boolean
local function dotfile(self, path) function Filters:dotfile(path)
return self.config.filter_dotfiles and utils.path_basename(path):sub(1, 1) == "." return self.state.dotfiles and utils.path_basename(path):sub(1, 1) == "."
end end
---Bookmark is present ---Bookmark is present
---@private
---@param path string ---@param path string
---@param path_type string|nil filetype of path ---@param path_type string|nil filetype of path
---@param bookmarks table<string, string|nil> path, filetype table of bookmarked files ---@param bookmarks table<string, string|nil> path, filetype table of bookmarked files
---@return boolean ---@return boolean
local function bookmark(self, path, path_type, bookmarks) function Filters:bookmark(path, path_type, bookmarks)
if not self.config.filter_no_bookmark then if not self.state.no_bookmark then
return false return false
end end
-- if bookmark is empty, we should see a empty filetree -- if bookmark is empty, we should see a empty filetree
@ -145,10 +156,11 @@ local function bookmark(self, path, path_type, bookmarks)
return true return true
end end
---@private
---@param path string ---@param path string
---@return boolean ---@return boolean
local function custom(self, path) function Filters:custom(path)
if not self.config.filter_custom then if not self.state.custom then
return false return false
end end
@ -190,7 +202,7 @@ function Filters:prepare(project)
bookmarks = {}, bookmarks = {},
} }
if self.config.filter_no_buffer then if self.state.no_buffer then
status.bufinfo = vim.fn.getbufinfo({ buflisted = 1 }) status.bufinfo = vim.fn.getbufinfo({ buflisted = 1 })
end end
@ -210,20 +222,20 @@ end
---@param status table from prepare ---@param status table from prepare
---@return boolean ---@return boolean
function Filters:should_filter(path, fs_stat, status) function Filters:should_filter(path, fs_stat, status)
if not self.config.enable then if not self.enabled then
return false return false
end end
-- exclusions override all filters -- exclusions override all filters
if is_excluded(self, path) then if self:is_excluded(path) then
return false return false
end end
return git(self, path, status.project) return self:git(path, status.project)
or buf(self, path, status.bufinfo) or self:buf(path, status.bufinfo)
or dotfile(self, path) or self:dotfile(path)
or custom(self, path) or self:custom(path)
or bookmark(self, path, fs_stat and fs_stat.type, status.bookmarks) or self:bookmark(path, fs_stat and fs_stat.type, status.bookmarks)
end end
--- Check if the given path should be filtered, and provide the reason why it was --- Check if the given path should be filtered, and provide the reason why it was
@ -232,27 +244,44 @@ end
---@param status table from prepare ---@param status table from prepare
---@return FILTER_REASON ---@return FILTER_REASON
function Filters:should_filter_as_reason(path, fs_stat, status) function Filters:should_filter_as_reason(path, fs_stat, status)
if not self.config.enable then if not self.enabled then
return FILTER_REASON.none return FILTER_REASON.none
end end
if is_excluded(self, path) then if self:is_excluded(path) then
return FILTER_REASON.none return FILTER_REASON.none
end end
if git(self, path, status.project) then if self:git(path, status.project) then
return FILTER_REASON.git return FILTER_REASON.git
elseif buf(self, path, status.bufinfo) then elseif self:buf(path, status.bufinfo) then
return FILTER_REASON.buf return FILTER_REASON.buf
elseif dotfile(self, path) then elseif self:dotfile(path) then
return FILTER_REASON.dotfile return FILTER_REASON.dotfile
elseif custom(self, path) then elseif self:custom(path) then
return FILTER_REASON.custom return FILTER_REASON.custom
elseif bookmark(self, path, fs_stat and fs_stat.type, status.bookmarks) then elseif self:bookmark(path, fs_stat and fs_stat.type, status.bookmarks) then
return FILTER_REASON.bookmark return FILTER_REASON.bookmark
else else
return FILTER_REASON.none return FILTER_REASON.none
end end
end end
---Toggle a type and refresh
---@private
---@param type FilterType? nil to disable all
function Filters:toggle(type)
if not type or self.state[type] == nil then
self.enabled = not self.enabled
else
self.state[type] = not self.state[type]
end
local node = self.explorer:get_node_at_cursor()
self.explorer:reload_explorer()
if node then
utils.focus_node_or_parent(node)
end
end
return Filters return Filters

View File

@ -3,7 +3,6 @@ local buffers = require("nvim-tree.buffers")
local core = require("nvim-tree.core") local core = require("nvim-tree.core")
local git = require("nvim-tree.git") local git = require("nvim-tree.git")
local log = require("nvim-tree.log") local log = require("nvim-tree.log")
local notify = require("nvim-tree.notify")
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local view = require("nvim-tree.view") local view = require("nvim-tree.view")
local node_factory = require("nvim-tree.node.factory") local node_factory = require("nvim-tree.node.factory")
@ -18,7 +17,7 @@ local NodeIterator = require("nvim-tree.iterators.node-iterator")
local Filters = require("nvim-tree.explorer.filters") local Filters = require("nvim-tree.explorer.filters")
local Marks = require("nvim-tree.marks") local Marks = require("nvim-tree.marks")
local LiveFilter = require("nvim-tree.explorer.live-filter") local LiveFilter = require("nvim-tree.explorer.live-filter")
local Sorters = require("nvim-tree.explorer.sorters") local Sorter = require("nvim-tree.explorer.sorter")
local Clipboard = require("nvim-tree.actions.fs.clipboard") local Clipboard = require("nvim-tree.actions.fs.clipboard")
local Renderer = require("nvim-tree.renderer") local Renderer = require("nvim-tree.renderer")
@ -36,51 +35,39 @@ local config
---@field sorters Sorter ---@field sorters Sorter
---@field marks Marks ---@field marks Marks
---@field clipboard Clipboard ---@field clipboard Clipboard
local Explorer = RootNode:new() local Explorer = RootNode:extend()
---Static factory method ---@class Explorer
---@param path string? ---@overload fun(args: ExplorerArgs): Explorer
---@return Explorer?
function Explorer:create(path)
local err
if path then ---@class (exact) ExplorerArgs
path, err = vim.loop.fs_realpath(path) ---@field path string
else
path, err = vim.loop.cwd()
end
if not path then
notify.error(err)
return nil
end
---@type Explorer ---@protected
local explorer_placeholder = nil ---@param args ExplorerArgs
function Explorer:new(args)
Explorer.super.new(self, {
explorer = self,
absolute_path = args.path,
name = "..",
})
local o = RootNode:create(explorer_placeholder, path, "..", nil) self.uid_explorer = vim.loop.hrtime()
self.augroup_id = vim.api.nvim_create_augroup("NvimTree_Explorer_" .. self.uid_explorer, {})
o = self:new(o) self.open = true
self.opts = config
o.explorer = o self.sorters = Sorter({ explorer = self })
self.renderer = Renderer({ explorer = self })
self.filters = Filters({ explorer = self })
self.live_filter = LiveFilter({ explorer = self })
self.marks = Marks({ explorer = self })
self.clipboard = Clipboard({ explorer = self })
o.uid_explorer = vim.loop.hrtime() self:create_autocmds()
o.augroup_id = vim.api.nvim_create_augroup("NvimTree_Explorer_" .. o.uid_explorer, {})
o.open = true self:_load(self)
o.opts = config
o.sorters = Sorters:create(config)
o.renderer = Renderer:new(config, o)
o.filters = Filters:new(config, o)
o.live_filter = LiveFilter:new(config, o)
o.marks = Marks:new(config, o)
o.clipboard = Clipboard:new(config, o)
o:create_autocmds()
o:_load(o)
return o
end end
function Explorer:destroy() function Explorer:destroy()
@ -114,7 +101,7 @@ function Explorer:create_autocmds()
vim.api.nvim_create_autocmd("BufReadPost", { vim.api.nvim_create_autocmd("BufReadPost", {
group = self.augroup_id, group = self.augroup_id,
callback = function(data) callback = function(data)
if (self.filters.config.filter_no_buffer or self.opts.highlight_opened_files ~= "none") and vim.bo[data.buf].buftype == "" then if (self.filters.state.no_buffer or self.opts.highlight_opened_files ~= "none") and vim.bo[data.buf].buftype == "" then
utils.debounce("Buf:filter_buffer_" .. self.uid_explorer, self.opts.view.debounce_delay, function() utils.debounce("Buf:filter_buffer_" .. self.uid_explorer, self.opts.view.debounce_delay, function()
self:reload_explorer() self:reload_explorer()
end) end)
@ -126,7 +113,7 @@ function Explorer:create_autocmds()
vim.api.nvim_create_autocmd("BufUnload", { vim.api.nvim_create_autocmd("BufUnload", {
group = self.augroup_id, group = self.augroup_id,
callback = function(data) callback = function(data)
if (self.filters.config.filter_no_buffer or self.opts.highlight_opened_files ~= "none") and vim.bo[data.buf].buftype == "" then if (self.filters.state.no_buffer or self.opts.highlight_opened_files ~= "none") and vim.bo[data.buf].buftype == "" then
utils.debounce("Buf:filter_buffer_" .. self.uid_explorer, self.opts.view.debounce_delay, function() utils.debounce("Buf:filter_buffer_" .. self.uid_explorer, self.opts.view.debounce_delay, function()
self:reload_explorer() self:reload_explorer()
end) end)
@ -213,10 +200,10 @@ function Explorer:reload(node, project)
-- To reset we must 'zero' everything that we use -- To reset we must 'zero' everything that we use
node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, { node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, {
git = 0, git = 0,
buf = 0, buf = 0,
dotfile = 0, dotfile = 0,
custom = 0, custom = 0,
bookmark = 0, bookmark = 0,
}) })
@ -246,7 +233,13 @@ function Explorer:reload(node, project)
end end
if not nodes_by_path[abs] then if not nodes_by_path[abs] then
local new_child = node_factory.create_node(self, node, abs, stat, name) local new_child = node_factory.create({
explorer = self,
parent = node,
absolute_path = abs,
name = name,
fs_stat = stat
})
if new_child then if new_child then
table.insert(node.nodes, new_child) table.insert(node.nodes, new_child)
nodes_by_path[abs] = new_child nodes_by_path[abs] = new_child
@ -362,10 +355,10 @@ function Explorer:populate_children(handle, cwd, node, project, parent)
local filter_status = parent.filters:prepare(project) local filter_status = parent.filters:prepare(project)
node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, { node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, {
git = 0, git = 0,
buf = 0, buf = 0,
dotfile = 0, dotfile = 0,
custom = 0, custom = 0,
bookmark = 0, bookmark = 0,
}) })
@ -384,7 +377,13 @@ function Explorer:populate_children(handle, cwd, node, project, parent)
local stat = vim.loop.fs_lstat(abs) local stat = vim.loop.fs_lstat(abs)
local filter_reason = parent.filters:should_filter_as_reason(abs, stat, filter_status) local filter_reason = parent.filters:should_filter_as_reason(abs, stat, filter_status)
if filter_reason == FILTER_REASON.none and not nodes_by_path[abs] then if filter_reason == FILTER_REASON.none and not nodes_by_path[abs] then
local child = node_factory.create_node(self, node, abs, stat, name) local child = node_factory.create({
explorer = self,
parent = node,
absolute_path = abs,
name = name,
fs_stat = stat
})
if child then if child then
table.insert(node.nodes, child) table.insert(node.nodes, child)
nodes_by_path[child.absolute_path] = true nodes_by_path[child.absolute_path] = true

View File

@ -1,29 +1,30 @@
local view = require("nvim-tree.view") local view = require("nvim-tree.view")
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local Class = require("nvim-tree.classic")
local Iterator = require("nvim-tree.iterators.node-iterator") local Iterator = require("nvim-tree.iterators.node-iterator")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
---@class LiveFilter ---@class (exact) LiveFilter: Class
---@field explorer Explorer ---@field explorer Explorer
---@field prefix string ---@field prefix string
---@field always_show_folders boolean ---@field always_show_folders boolean
---@field filter string ---@field filter string
local LiveFilter = {} local LiveFilter = Class:extend()
---@param opts table ---@class LiveFilter
---@param explorer Explorer ---@overload fun(args: LiveFilterArgs): LiveFilter
---@return LiveFilter
function LiveFilter:new(opts, explorer) ---@class (exact) LiveFilterArgs
local o = { ---@field explorer Explorer
explorer = explorer,
prefix = opts.live_filter.prefix, ---@protected
always_show_folders = opts.live_filter.always_show_folders, ---@param args LiveFilterArgs
filter = nil, function LiveFilter:new(args)
} self.explorer = args.explorer
setmetatable(o, self) self.prefix = self.explorer.opts.live_filter.prefix
self.__index = self self.always_show_folders = self.explorer.opts.live_filter.always_show_folders
return o self.filter = nil
end end
---@param node_ Node? ---@param node_ Node?
@ -81,7 +82,7 @@ end
---@param node Node ---@param node Node
---@return boolean ---@return boolean
local function matches(self, node) local function matches(self, node)
if not self.explorer.filters.config.enable then if not self.explorer.filters.enabled then
return true return true
end end
@ -168,21 +169,21 @@ local function create_overlay(self)
if view.View.float.enable then if view.View.float.enable then
-- don't close nvim-tree float when focus is changed to filter window -- don't close nvim-tree float when focus is changed to filter window
vim.api.nvim_clear_autocmds({ vim.api.nvim_clear_autocmds({
event = "WinLeave", event = "WinLeave",
pattern = "NvimTree_*", pattern = "NvimTree_*",
group = vim.api.nvim_create_augroup("NvimTree", { clear = false }), group = vim.api.nvim_create_augroup("NvimTree", { clear = false }),
}) })
end end
configure_buffer_overlay(self) configure_buffer_overlay(self)
overlay_winnr = vim.api.nvim_open_win(overlay_bufnr, true, { overlay_winnr = vim.api.nvim_open_win(overlay_bufnr, true, {
col = 1, col = 1,
row = 0, row = 0,
relative = "cursor", relative = "cursor",
width = calculate_overlay_win_width(self), width = calculate_overlay_win_width(self),
height = 1, height = 1,
border = "none", border = "none",
style = "minimal", style = "minimal",
}) })
if vim.fn.has("nvim-0.10") == 1 then if vim.fn.has("nvim-0.10") == 1 then

View File

@ -1,43 +1,25 @@
local Class = require("nvim-tree.class") local Class = require("nvim-tree.classic")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
local C = {} ---@alias SorterType "name" | "case_sensitive" | "modification_time" | "extension" | "suffix" | "filetype"
---@alias SorterComparator fun(self: Sorter, a: Node, b: Node): boolean?
---@class (exact) SorterCfg ---@alias SorterUser fun(nodes: Node[]): SorterType?
---@field sorter string|fun(nodes: Node[])
---@field folders_first boolean
---@field files_first boolean
---@class (exact) Sorter: Class ---@class (exact) Sorter: Class
---@field cfg SorterCfg ---@field private explorer Explorer
---@field user fun(nodes: Node[])? local Sorter = Class:extend()
---@field pre string?
local Sorter = Class:new()
---@param opts table user options ---@class Sorter
---@return Sorter ---@overload fun(args: SorterArgs): Sorter
function Sorter:create(opts)
---@type Sorter
local o = {
cfg = vim.deepcopy(opts.sort),
}
o = self:new(o)
if type(o.cfg.sorter) == "function" then ---@class (exact) SorterArgs
o.user = o.cfg.sorter --[[@as fun(nodes: Node[])]] ---@field explorer Explorer
elseif type(o.cfg.sorter) == "string" then
o.pre = o.cfg.sorter --[[@as string]]
end
return o
end
--- Predefined comparator, defaulting to name ---@protected
---@param sorter string as per options ---@param args SorterArgs
---@return fun(a: Node, b: Node): boolean function Sorter:new(args)
function Sorter:get_comparator(sorter) self.explorer = args.explorer
return function(a, b)
return (C[sorter] or C.name)(a, b, self.cfg)
end
end end
---Create a shallow copy of a portion of a list. ---Create a shallow copy of a portion of a list.
@ -54,31 +36,32 @@ local function tbl_slice(t, first, last)
return slice return slice
end end
---Evaluate `sort.folders_first` and `sort.files_first` ---Evaluate folders_first and sort.files_first returning nil when no order is necessary
---@param a Node ---@private
---@param b Node ---@type SorterComparator
---@param cfg SorterCfg function Sorter:folders_or_files_first(a, b)
---@return boolean|nil if not (self.explorer.opts.sort.folders_first or self.explorer.opts.sort.files_first) then
local function folders_or_files_first(a, b, cfg) return nil
if not (cfg.folders_first or cfg.files_first) then
return
end end
if not a:is(DirectoryNode) and b:is(DirectoryNode) then if not a:is(DirectoryNode) and b:is(DirectoryNode) then
-- file <> folder -- file <> folder
return cfg.files_first return self.explorer.opts.sort.files_first
elseif a:is(DirectoryNode) and not b:is(DirectoryNode) then elseif a:is(DirectoryNode) and not b:is(DirectoryNode) then
-- folder <> file -- folder <> file
return not cfg.files_first return not self.explorer.opts.sort.files_first
end end
return nil
end end
---@param t table ---@private
---@param t Node[]
---@param first number ---@param first number
---@param mid number ---@param mid number
---@param last number ---@param last number
---@param comparator fun(a: Node, b: Node): boolean ---@param comparator SorterComparator
local function merge(t, first, mid, last, comparator) function Sorter:merge(t, first, mid, last, comparator)
local n1 = mid - first + 1 local n1 = mid - first + 1
local n2 = last - mid local n2 = last - mid
local ls = tbl_slice(t, first, mid) local ls = tbl_slice(t, first, mid)
@ -88,7 +71,7 @@ local function merge(t, first, mid, last, comparator)
local k = first local k = first
while i <= n1 and j <= n2 do while i <= n1 and j <= n2 do
if comparator(ls[i], rs[j]) then if comparator(self, ls[i], rs[j]) then
t[k] = ls[i] t[k] = ls[i]
i = i + 1 i = i + 1
else else
@ -111,45 +94,49 @@ local function merge(t, first, mid, last, comparator)
end end
end end
---@param t table ---@private
---@param t Node[]
---@param first number ---@param first number
---@param last number ---@param last number
---@param comparator fun(a: Node, b: Node): boolean ---@param comparator SorterComparator
local function split_merge(t, first, last, comparator) function Sorter:split_merge(t, first, last, comparator)
if (last - first) < 1 then if (last - first) < 1 then
return return
end end
local mid = math.floor((first + last) / 2) local mid = math.floor((first + last) / 2)
split_merge(t, first, mid, comparator) self:split_merge(t, first, mid, comparator)
split_merge(t, mid + 1, last, comparator) self:split_merge(t, mid + 1, last, comparator)
merge(t, first, mid, last, comparator) self:merge(t, first, mid, last, comparator)
end end
---Perform a merge sort using sorter option. ---Perform a merge sort using sorter option.
---@param t Node[] ---@param t Node[]
function Sorter:sort(t) function Sorter:sort(t)
if self.user then if self[self.explorer.opts.sort.sorter] then
self:split_merge(t, 1, #t, self[self.explorer.opts.sort.sorter])
elseif type(self.explorer.opts.sort.sorter) == "function" then
local t_user = {} local t_user = {}
local origin_index = {} local origin_index = {}
for _, n in ipairs(t) do for _, n in ipairs(t) do
table.insert(t_user, { table.insert(t_user, {
absolute_path = n.absolute_path, absolute_path = n.absolute_path,
executable = n.executable, executable = n.executable,
extension = n.extension, extension = n.extension,
filetype = vim.filetype.match({ filename = n.name }), filetype = vim.filetype.match({ filename = n.name }),
link_to = n.link_to, link_to = n.link_to,
name = n.name, name = n.name,
type = n.type, type = n.type,
}) })
table.insert(origin_index, n) table.insert(origin_index, n)
end end
local predefined = self.user(t_user) -- user may return a SorterType
if predefined then local ret = self.explorer.opts.sort.sorter(t_user)
split_merge(t, 1, #t, self:get_comparator(predefined)) if self[ret] then
self:split_merge(t, 1, #t, self[ret])
return return
end end
@ -162,7 +149,7 @@ function Sorter:sort(t)
end end
-- if missing value found, then using origin_index -- if missing value found, then using origin_index
local mini_comparator = function(a, b) local mini_comparator = function(_, a, b)
local a_index = user_index[a.absolute_path] or origin_index[a.absolute_path] local a_index = user_index[a.absolute_path] or origin_index[a.absolute_path]
local b_index = user_index[b.absolute_path] or origin_index[b.absolute_path] local b_index = user_index[b.absolute_path] or origin_index[b.absolute_path]
@ -172,48 +159,52 @@ function Sorter:sort(t)
return (a_index or 0) <= (b_index or 0) return (a_index or 0) <= (b_index or 0)
end end
split_merge(t, 1, #t, mini_comparator) -- sort by user order self:split_merge(t, 1, #t, mini_comparator) -- sort by user order
elseif self.pre then
split_merge(t, 1, #t, self:get_comparator(self.pre))
end end
end end
---@private
---@param a Node ---@param a Node
---@param b Node ---@param b Node
---@param ignorecase boolean|nil ---@param ignore_case boolean
---@param cfg SorterCfg
---@return boolean ---@return boolean
local function node_comparator_name_ignorecase_or_not(a, b, ignorecase, cfg) function Sorter:name_case(a, b, ignore_case)
if not (a and b) then if not (a and b) then
return true return true
end end
local early_return = folders_or_files_first(a, b, cfg) local early_return = self:folders_or_files_first(a, b)
if early_return ~= nil then if early_return ~= nil then
return early_return return early_return
end end
if ignorecase then if ignore_case then
return a.name:lower() <= b.name:lower() return a.name:lower() <= b.name:lower()
else else
return a.name <= b.name return a.name <= b.name
end end
end end
function C.case_sensitive(a, b, cfg) ---@private
return node_comparator_name_ignorecase_or_not(a, b, false, cfg) ---@type SorterComparator
function Sorter:case_sensitive(a, b)
return self:name_case(a, b, false)
end end
function C.name(a, b, cfg) ---@private
return node_comparator_name_ignorecase_or_not(a, b, true, cfg) ---@type SorterComparator
function Sorter:name(a, b)
return self:name_case(a, b, true)
end end
function C.modification_time(a, b, cfg) ---@private
---@type SorterComparator
function Sorter:modification_time(a, b)
if not (a and b) then if not (a and b) then
return true return true
end end
local early_return = folders_or_files_first(a, b, cfg) local early_return = self:folders_or_files_first(a, b)
if early_return ~= nil then if early_return ~= nil then
return early_return return early_return
end end
@ -232,17 +223,19 @@ function C.modification_time(a, b, cfg)
return last_modified_b <= last_modified_a return last_modified_b <= last_modified_a
end end
function C.suffix(a, b, cfg) ---@private
---@type SorterComparator
function Sorter:suffix(a, b)
if not (a and b) then if not (a and b) then
return true return true
end end
-- directories go first -- directories go first
local early_return = folders_or_files_first(a, b, cfg) local early_return = self:folders_or_files_first(a, b)
if early_return ~= nil then if early_return ~= nil then
return early_return return early_return
elseif a.nodes and b.nodes then elseif a.nodes and b.nodes then
return C.name(a, b, cfg) return self:name(a, b)
end end
-- dotfiles go second -- dotfiles go second
@ -251,7 +244,7 @@ function C.suffix(a, b, cfg)
elseif a.name:sub(1, 1) ~= "." and b.name:sub(1, 1) == "." then elseif a.name:sub(1, 1) ~= "." and b.name:sub(1, 1) == "." then
return false return false
elseif a.name:sub(1, 1) == "." and b.name:sub(1, 1) == "." then elseif a.name:sub(1, 1) == "." and b.name:sub(1, 1) == "." then
return C.name(a, b, cfg) return self:name(a, b)
end end
-- unsuffixed go third -- unsuffixed go third
@ -263,7 +256,7 @@ function C.suffix(a, b, cfg)
elseif a_suffix_ndx and not b_suffix_ndx then elseif a_suffix_ndx and not b_suffix_ndx then
return false return false
elseif not (a_suffix_ndx and b_suffix_ndx) then elseif not (a_suffix_ndx and b_suffix_ndx) then
return C.name(a, b, cfg) return self:name(a, b)
end end
-- finally, compare by suffixes -- finally, compare by suffixes
@ -275,18 +268,20 @@ function C.suffix(a, b, cfg)
elseif not a_suffix and b_suffix then elseif not a_suffix and b_suffix then
return false return false
elseif a_suffix:lower() == b_suffix:lower() then elseif a_suffix:lower() == b_suffix:lower() then
return C.name(a, b, cfg) return self:name(a, b)
end end
return a_suffix:lower() < b_suffix:lower() return a_suffix:lower() < b_suffix:lower()
end end
function C.extension(a, b, cfg) ---@private
---@type SorterComparator
function Sorter:extension(a, b)
if not (a and b) then if not (a and b) then
return true return true
end end
local early_return = folders_or_files_first(a, b, cfg) local early_return = self:folders_or_files_first(a, b)
if early_return ~= nil then if early_return ~= nil then
return early_return return early_return
end end
@ -300,18 +295,20 @@ function C.extension(a, b, cfg)
local a_ext = (a.extension or ""):lower() local a_ext = (a.extension or ""):lower()
local b_ext = (b.extension or ""):lower() local b_ext = (b.extension or ""):lower()
if a_ext == b_ext then if a_ext == b_ext then
return C.name(a, b, cfg) return self:name(a, b)
end end
return a_ext < b_ext return a_ext < b_ext
end end
function C.filetype(a, b, cfg) ---@private
---@type SorterComparator
function Sorter:filetype(a, b)
local a_ft = vim.filetype.match({ filename = a.name }) local a_ft = vim.filetype.match({ filename = a.name })
local b_ft = vim.filetype.match({ filename = b.name }) local b_ft = vim.filetype.match({ filename = b.name })
-- directories first -- directories first
local early_return = folders_or_files_first(a, b, cfg) local early_return = self:folders_or_files_first(a, b)
if early_return ~= nil then if early_return ~= nil then
return early_return return early_return
end end
@ -325,7 +322,7 @@ function C.filetype(a, b, cfg)
-- same filetype or both nil, sort by name -- same filetype or both nil, sort by name
if a_ft == b_ft then if a_ft == b_ft then
return C.name(a, b, cfg) return self:name(a, b)
end end
return a_ft < b_ft return a_ft < b_ft

View File

@ -83,8 +83,12 @@ function M.create_watcher(node)
end end
M.uid = M.uid + 1 M.uid = M.uid + 1
return Watcher:create(path, nil, callback, { return Watcher:create({
context = "explorer:watch:" .. path .. ":" .. M.uid, path = path,
callback = callback,
data = {
context = "explorer:watch:" .. path .. ":" .. M.uid
}
}) })
end end

View File

@ -128,25 +128,25 @@ function M.reload_project(toplevel, path, callback)
return return
end end
---@type GitRunnerOpts ---@type GitRunnerArgs
local runner_opts = { local args = {
toplevel = toplevel, toplevel = toplevel,
path = path, path = path,
list_untracked = git_utils.should_show_untracked(toplevel), list_untracked = git_utils.should_show_untracked(toplevel),
list_ignored = true, list_ignored = true,
timeout = M.config.git.timeout, timeout = M.config.git.timeout,
} }
if callback then if callback then
---@param path_xy GitPathXY ---@param path_xy GitPathXY
runner_opts.callback = function(path_xy) args.callback = function(path_xy)
reload_git_project(toplevel, path, project, path_xy) reload_git_project(toplevel, path, project, path_xy)
callback() callback()
end end
GitRunner:run(runner_opts) GitRunner:run(args)
else else
-- TODO #1974 use callback once async/await is available -- TODO #1974 use callback once async/await is available
reload_git_project(toplevel, path, project, GitRunner:run(runner_opts)) reload_git_project(toplevel, path, project, GitRunner:run(args))
end end
end end
@ -276,10 +276,10 @@ function M.load_project(path)
end end
local path_xys = GitRunner:run({ local path_xys = GitRunner:run({
toplevel = toplevel, toplevel = toplevel,
list_untracked = git_utils.should_show_untracked(toplevel), list_untracked = git_utils.should_show_untracked(toplevel),
list_ignored = true, list_ignored = true,
timeout = M.config.git.timeout, timeout = M.config.git.timeout,
}) })
local watcher = nil local watcher = nil
@ -298,15 +298,20 @@ function M.load_project(path)
end end
local git_dir = vim.env.GIT_DIR or M._git_dirs_by_toplevel[toplevel] or utils.path_join({ toplevel, ".git" }) local git_dir = vim.env.GIT_DIR or M._git_dirs_by_toplevel[toplevel] or utils.path_join({ toplevel, ".git" })
watcher = Watcher:create(git_dir, WATCHED_FILES, callback, { watcher = Watcher:create({
toplevel = toplevel, path = git_dir,
files = WATCHED_FILES,
callback = callback,
data = {
toplevel = toplevel,
}
}) })
end end
if path_xys then if path_xys then
M._projects_by_toplevel[toplevel] = { M._projects_by_toplevel[toplevel] = {
files = path_xys, files = path_xys,
dirs = git_utils.project_files_to_project_dirs(path_xys, toplevel), dirs = git_utils.project_files_to_project_dirs(path_xys, toplevel),
watcher = watcher, watcher = watcher,
} }
return M._projects_by_toplevel[toplevel] return M._projects_by_toplevel[toplevel]

View File

@ -2,9 +2,23 @@ local log = require("nvim-tree.log")
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local notify = require("nvim-tree.notify") local notify = require("nvim-tree.notify")
local Class = require("nvim-tree.class") local Class = require("nvim-tree.classic")
---@class (exact) GitRunnerOpts ---@class (exact) GitRunner: Class
---@field private toplevel string absolute path
---@field private path string? absolute path
---@field private list_untracked boolean
---@field private list_ignored boolean
---@field private timeout integer
---@field private callback fun(path_xy: GitPathXY)?
---@field private path_xy GitPathXY
---@field private rc integer? -- -1 indicates timeout
local GitRunner = Class:extend()
---@class GitRunner
---@overload fun(args: GitRunnerArgs): GitRunner
---@class (exact) GitRunnerArgs
---@field toplevel string absolute path ---@field toplevel string absolute path
---@field path string? absolute path ---@field path string? absolute path
---@field list_untracked boolean ---@field list_untracked boolean
@ -12,15 +26,23 @@ local Class = require("nvim-tree.class")
---@field timeout integer ---@field timeout integer
---@field callback fun(path_xy: GitPathXY)? ---@field callback fun(path_xy: GitPathXY)?
---@class (exact) GitRunner: Class
---@field private opts GitRunnerOpts
---@field private path_xy GitPathXY
---@field private rc integer? -- -1 indicates timeout
local GitRunner = Class:new()
local timeouts = 0 local timeouts = 0
local MAX_TIMEOUTS = 5 local MAX_TIMEOUTS = 5
---@protected
---@param args GitRunnerArgs
function GitRunner:new(args)
self.toplevel = args.toplevel
self.path = args.path
self.list_untracked = args.list_untracked
self.list_ignored = args.list_ignored
self.timeout = args.timeout
self.callback = args.callback
self.path_xy = {}
self.rc = nil
end
---@private ---@private
---@param status string ---@param status string
---@param path string|nil ---@param path string|nil
@ -34,7 +56,7 @@ function GitRunner:parse_status_output(status, path)
path = path:gsub("/", "\\") path = path:gsub("/", "\\")
end end
if #status > 0 and #path > 0 then if #status > 0 and #path > 0 then
self.path_xy[utils.path_remove_trailing(utils.path_join({ self.opts.toplevel, path }))] = status self.path_xy[utils.path_remove_trailing(utils.path_join({ self.toplevel, path }))] = status
end end
end end
@ -81,11 +103,11 @@ end
---@param stderr_handle uv.uv_pipe_t ---@param stderr_handle uv.uv_pipe_t
---@return uv.spawn.options ---@return uv.spawn.options
function GitRunner:get_spawn_options(stdout_handle, stderr_handle) function GitRunner:get_spawn_options(stdout_handle, stderr_handle)
local untracked = self.opts.list_untracked and "-u" or nil local untracked = self.list_untracked and "-u" or nil
local ignored = (self.opts.list_untracked and self.opts.list_ignored) and "--ignored=matching" or "--ignored=no" local ignored = (self.list_untracked and self.list_ignored) and "--ignored=matching" or "--ignored=no"
return { return {
args = { "--no-optional-locks", "status", "--porcelain=v1", "-z", ignored, untracked, self.opts.path }, args = { "--no-optional-locks", "status", "--porcelain=v1", "-z", ignored, untracked, self.path },
cwd = self.opts.toplevel, cwd = self.toplevel,
stdio = { nil, stdout_handle, stderr_handle }, stdio = { nil, stdout_handle, stderr_handle },
} }
end end
@ -139,7 +161,7 @@ function GitRunner:run_git_job(callback)
end end
local spawn_options = self:get_spawn_options(stdout, stderr) local spawn_options = self:get_spawn_options(stdout, stderr)
log.line("git", "running job with timeout %dms", self.opts.timeout) log.line("git", "running job with timeout %dms", self.timeout)
log.line("git", "git %s", table.concat(utils.array_remove_nils(spawn_options.args), " ")) log.line("git", "git %s", table.concat(utils.array_remove_nils(spawn_options.args), " "))
handle, pid = vim.loop.spawn( handle, pid = vim.loop.spawn(
@ -151,7 +173,7 @@ function GitRunner:run_git_job(callback)
) )
timer:start( timer:start(
self.opts.timeout, self.timeout,
0, 0,
vim.schedule_wrap(function() vim.schedule_wrap(function()
on_finish(-1) on_finish(-1)
@ -191,17 +213,17 @@ end
---@private ---@private
function GitRunner:finalise() function GitRunner:finalise()
if self.rc == -1 then if self.rc == -1 then
log.line("git", "job timed out %s %s", self.opts.toplevel, self.opts.path) log.line("git", "job timed out %s %s", self.toplevel, self.path)
timeouts = timeouts + 1 timeouts = timeouts + 1
if timeouts == MAX_TIMEOUTS then if timeouts == MAX_TIMEOUTS then
notify.warn(string.format("%d git jobs have timed out after git.timeout %dms, disabling git integration.", timeouts, notify.warn(string.format("%d git jobs have timed out after git.timeout %dms, disabling git integration.", timeouts,
self.opts.timeout)) self.timeout))
require("nvim-tree.git").disable_git_integration() require("nvim-tree.git").disable_git_integration()
end end
elseif self.rc ~= 0 then elseif self.rc ~= 0 then
log.line("git", "job fail rc %d %s %s", self.rc, self.opts.toplevel, self.opts.path) log.line("git", "job fail rc %d %s %s", self.rc, self.toplevel, self.path)
else else
log.line("git", "job success %s %s", self.opts.toplevel, self.opts.path) log.line("git", "job success %s %s", self.toplevel, self.path)
end end
end end
@ -209,17 +231,17 @@ end
---@private ---@private
---@return GitPathXY? ---@return GitPathXY?
function GitRunner:execute() function GitRunner:execute()
local async = self.opts.callback ~= nil local async = self.callback ~= nil
local profile = log.profile_start("git %s job %s %s", async and "async" or "sync", self.opts.toplevel, self.opts.path) local profile = log.profile_start("git %s job %s %s", async and "async" or "sync", self.toplevel, self.path)
if async and self.opts.callback then if async and self.callback then
-- async, always call back -- async, always call back
self:run_git_job(function() self:run_git_job(function()
log.profile_end(profile) log.profile_end(profile)
self:finalise() self:finalise()
self.opts.callback(self.path_xy) self.callback(self.path_xy)
end) end)
else else
-- sync, maybe call back -- sync, maybe call back
@ -230,8 +252,8 @@ function GitRunner:execute()
self:finalise() self:finalise()
if self.opts.callback then if self.callback then
self.opts.callback(self.path_xy) self.callback(self.path_xy)
else else
return self.path_xy return self.path_xy
end end
@ -240,15 +262,10 @@ end
---Static method to run a git process, which will be killed if it takes more than timeout ---Static method to run a git process, which will be killed if it takes more than timeout
---Return nil when callback present ---Return nil when callback present
---@param opts GitRunnerOpts ---@param args GitRunnerArgs
---@return GitPathXY? ---@return GitPathXY?
function GitRunner:run(opts) function GitRunner:run(args)
---@type GitRunner local runner = GitRunner(args)
local runner = {
opts = opts,
path_xy = {},
}
runner = GitRunner:new(runner)
return runner:execute() return runner:execute()
end end

View File

@ -172,8 +172,8 @@ function M.git_status_dir(parent_ignored, project, path, path_fallback)
elseif project then elseif project then
ns = { ns = {
file = project.files and (project.files[path] or project.files[path_fallback]), file = project.files and (project.files[path] or project.files[path_fallback]),
dir = project.dirs and { dir = project.dirs and {
direct = project.dirs.direct and project.dirs.direct[path], direct = project.dirs.direct and project.dirs.direct[path],
indirect = project.dirs.indirect and project.dirs.indirect[path], indirect = project.dirs.indirect and project.dirs.indirect[path],
}, },
} }

View File

@ -8,37 +8,33 @@ local rename_file = require("nvim-tree.actions.fs.rename-file")
local trash = require("nvim-tree.actions.fs.trash") local trash = require("nvim-tree.actions.fs.trash")
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local Class = require("nvim-tree.classic")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
---@class Marks ---@class (exact) Marks: Class
---@field config table hydrated user opts.filters
---@field private explorer Explorer ---@field private explorer Explorer
---@field private marks table<string, Node> by absolute path ---@field private marks table<string, Node> by absolute path
local Marks = {} local Marks = Class:extend()
---@return Marks ---@class Marks
---@param opts table user options ---@overload fun(args: MarksArgs): Marks
---@param explorer Explorer
function Marks:new(opts, explorer)
local o = {
explorer = explorer,
config = {
ui = opts.ui,
filesystem_watchers = opts.filesystem_watchers,
},
marks = {},
}
setmetatable(o, self) ---@class (exact) MarksArgs
self.__index = self ---@field explorer Explorer
return o
---@protected
---@param args MarksArgs
function Marks:new(args)
self.explorer = args.explorer
self.marks = {}
end end
---Clear all marks and reload if watchers disabled ---Clear all marks and reload if watchers disabled
---@private ---@private
function Marks:clear_reload() function Marks:clear_reload()
self:clear() self:clear()
if not self.config.filesystem_watchers.enable then if not self.explorer.opts.filesystem_watchers.enable then
self.explorer:reload_explorer() self.explorer:reload_explorer()
end end
end end
@ -100,7 +96,7 @@ function Marks:bulk_delete()
self:clear_reload() self:clear_reload()
end end
if self.config.ui.confirm.remove then if self.explorer.opts.ui.confirm.remove then
local prompt_select = "Remove bookmarked ?" local prompt_select = "Remove bookmarked ?"
local prompt_input = prompt_select .. " y/N: " local prompt_input = prompt_select .. " y/N: "
lib.prompt(prompt_input, prompt_select, { "", "y" }, { "No", "Yes" }, "nvimtree_bulk_delete", function(item_short) lib.prompt(prompt_input, prompt_select, { "", "y" }, { "No", "Yes" }, "nvimtree_bulk_delete", function(item_short)
@ -129,7 +125,7 @@ function Marks:bulk_trash()
self:clear_reload() self:clear_reload()
end end
if self.config.ui.confirm.trash then if self.explorer.opts.ui.confirm.trash then
local prompt_select = "Trash bookmarked ?" local prompt_select = "Trash bookmarked ?"
local prompt_input = prompt_select .. " y/N: " local prompt_input = prompt_select .. " y/N: "
lib.prompt(prompt_input, prompt_select, { "", "y" }, { "No", "Yes" }, "nvimtree_bulk_trash", function(item_short) lib.prompt(prompt_input, prompt_select, { "", "y" }, { "No", "Yes" }, "nvimtree_bulk_trash", function(item_short)

View File

@ -2,35 +2,29 @@ local git_utils = require("nvim-tree.git.utils")
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
local LinkNode = require("nvim-tree.node.link")
---@class (exact) DirectoryLinkNode: DirectoryNode ---@class (exact) DirectoryLinkNode: DirectoryNode, LinkNode
---@field link_to string absolute path local DirectoryLinkNode = DirectoryNode:extend()
---@field private fs_stat_target uv.fs_stat.result DirectoryLinkNode:implement(LinkNode)
local DirectoryLinkNode = DirectoryNode:new()
---Static factory method ---@class DirectoryLinkNode
---@param explorer Explorer ---@overload fun(args: LinkNodeArgs): DirectoryLinkNode
---@param parent DirectoryNode
---@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) ---@protected
---@param args LinkNodeArgs
function DirectoryLinkNode:new(args)
LinkNode.new(self, args)
-- create DirectoryNode with watcher on link_to
local absolute_path = args.absolute_path
args.absolute_path = args.link_to
DirectoryLinkNode.super.new(self, args)
self.type = "link"
-- reset absolute path to the link itself -- reset absolute path to the link itself
o.absolute_path = absolute_path self.absolute_path = absolute_path
o.type = "link"
o.link_to = link_to
o.fs_stat_target = fs_stat_target
return o
end end
function DirectoryLinkNode:destroy() function DirectoryLinkNode:destroy()
@ -54,10 +48,10 @@ function DirectoryLinkNode:highlighted_icon()
if self.open then if self.open then
str = self.explorer.opts.renderer.icons.glyphs.folder.symlink_open str = self.explorer.opts.renderer.icons.glyphs.folder.symlink_open
hl = "NvimTreeOpenedFolderIcon" hl = "NvimTreeOpenedFolderIcon"
else else
str = self.explorer.opts.renderer.icons.glyphs.folder.symlink str = self.explorer.opts.renderer.icons.glyphs.folder.symlink
hl = "NvimTreeClosedFolderIcon" hl = "NvimTreeClosedFolderIcon"
end end
return { str = str, hl = { hl } } return { str = str, hl = { hl } }
@ -70,8 +64,9 @@ function DirectoryLinkNode:highlighted_name()
if self.explorer.opts.renderer.symlink_destination then if self.explorer.opts.renderer.symlink_destination then
local link_to = utils.path_relative(self.link_to, self.explorer.absolute_path) local link_to = utils.path_relative(self.link_to, self.explorer.absolute_path)
name.str = string.format("%s%s%s", name.str, self.explorer.opts.renderer.icons.symlink_arrow, link_to)
name.hl = { "NvimTreeSymlinkFolderName" } name.str = string.format("%s%s%s", name.str, self.explorer.opts.renderer.icons.symlink_arrow, link_to)
name.hl = { "NvimTreeSymlinkFolderName" }
end end
return name return name
@ -82,7 +77,6 @@ end
function DirectoryLinkNode:clone() function DirectoryLinkNode:clone()
local clone = DirectoryNode.clone(self) --[[@as DirectoryLinkNode]] local clone = DirectoryNode.clone(self) --[[@as DirectoryLinkNode]]
clone.type = self.type
clone.link_to = self.link_to clone.link_to = self.link_to
clone.fs_stat_target = self.fs_stat_target clone.fs_stat_target = self.fs_stat_target

View File

@ -1,6 +1,7 @@
local git_utils = require("nvim-tree.git.utils") local git_utils = require("nvim-tree.git.utils")
local icons = require("nvim-tree.renderer.components.devicons") local icons = require("nvim-tree.renderer.components.devicons")
local notify = require("nvim-tree.notify") local notify = require("nvim-tree.notify")
local Node = require("nvim-tree.node") local Node = require("nvim-tree.node")
---@class (exact) DirectoryNode: Node ---@class (exact) DirectoryNode: Node
@ -10,45 +11,28 @@ local Node = require("nvim-tree.node")
---@field open boolean ---@field open boolean
---@field hidden_stats table? -- Each field of this table is a key for source and value for count ---@field hidden_stats table? -- Each field of this table is a key for source and value for count
---@field private watcher Watcher? ---@field private watcher Watcher?
local DirectoryNode = Node:new() local DirectoryNode = Node:extend()
---Static factory method ---@class DirectoryNode
---@param explorer Explorer ---@overload fun(args: NodeArgs): DirectoryNode
---@param parent DirectoryNode?
---@param absolute_path string ---@protected
---@param name string ---@param args NodeArgs
---@param fs_stat uv.fs_stat.result|nil function DirectoryNode:new(args)
---@return DirectoryNode DirectoryNode.super.new(self, args)
function DirectoryNode:create(explorer, parent, absolute_path, name, fs_stat)
local handle = vim.loop.fs_scandir(absolute_path) local handle = vim.loop.fs_scandir(args.absolute_path)
local has_children = handle and vim.loop.fs_scandir_next(handle) ~= nil or false local has_children = handle and vim.loop.fs_scandir_next(handle) ~= nil or false
---@type DirectoryNode self.type = "directory"
local o = {
type = "directory",
explorer = explorer,
absolute_path = absolute_path,
executable = false,
fs_stat = fs_stat,
git_status = nil,
hidden = false,
name = name,
parent = parent,
watcher = nil,
diag_status = nil,
is_dot = false,
has_children = has_children, self.has_children = has_children
group_next = nil, self.group_next = nil
nodes = {}, self.nodes = {}
open = false, self.open = false
hidden_stats = nil, self.hidden_stats = nil
}
o = self:new(o)
o.watcher = require("nvim-tree.explorer.watch").create_watcher(o) self.watcher = require("nvim-tree.explorer.watch").create_watcher(self)
return o
end end
function DirectoryNode:destroy() function DirectoryNode:destroy()
@ -289,12 +273,12 @@ end
---Create a sanitized partial copy of a node, populating children recursively. ---Create a sanitized partial copy of a node, populating children recursively.
---@return DirectoryNode cloned ---@return DirectoryNode cloned
function DirectoryNode:clone() function DirectoryNode:clone()
local clone = Node.clone(self) --[[@as DirectoryNode]] local clone = Node.clone(self) --[[@as DirectoryNode]]
clone.has_children = self.has_children clone.has_children = self.has_children
clone.group_next = nil clone.group_next = nil
clone.nodes = {} clone.nodes = {}
clone.open = self.open clone.open = self.open
clone.hidden_stats = nil clone.hidden_stats = nil
for _, child in ipairs(self.nodes) do for _, child in ipairs(self.nodes) do

View File

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

View File

@ -2,31 +2,22 @@ local git_utils = require("nvim-tree.git.utils")
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local FileNode = require("nvim-tree.node.file") local FileNode = require("nvim-tree.node.file")
local LinkNode = require("nvim-tree.node.link")
---@class (exact) FileLinkNode: FileNode ---@class (exact) FileLinkNode: FileNode, LinkNode
---@field link_to string absolute path local FileLinkNode = FileNode:extend()
---@field private fs_stat_target uv.fs_stat.result FileLinkNode:implement(LinkNode)
local FileLinkNode = FileNode:new()
---Static factory method ---@class FileLinkNode
---@param explorer Explorer ---@overload fun(args: LinkNodeArgs): FileLinkNode
---@param parent DirectoryNode
---@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) ---@protected
---@param args LinkNodeArgs
function FileLinkNode:new(args)
LinkNode.new(self, args)
FileLinkNode.super.new(self, args)
o.type = "link" self.type = "link"
o.link_to = link_to
o.fs_stat_target = fs_stat_target
return o
end end
function FileLinkNode:destroy() function FileLinkNode:destroy()
@ -71,7 +62,6 @@ end
function FileLinkNode:clone() function FileLinkNode:clone()
local clone = FileNode.clone(self) --[[@as FileLinkNode]] local clone = FileNode.clone(self) --[[@as FileLinkNode]]
clone.type = self.type
clone.link_to = self.link_to clone.link_to = self.link_to
clone.fs_stat_target = self.fs_stat_target clone.fs_stat_target = self.fs_stat_target

View File

@ -15,35 +15,19 @@ local PICTURE_MAP = {
---@class (exact) FileNode: Node ---@class (exact) FileNode: Node
---@field extension string ---@field extension string
local FileNode = Node:new() local FileNode = Node:extend()
---Static factory method ---@class FileNode
---@param explorer Explorer ---@overload fun(args: NodeArgs): FileNode
---@param parent DirectoryNode
---@param absolute_path string
---@param name string
---@param fs_stat uv.fs_stat.result?
---@return FileNode
function FileNode:create(explorer, parent, absolute_path, name, fs_stat)
---@type FileNode
local o = {
type = "file",
explorer = explorer,
absolute_path = absolute_path,
executable = utils.is_executable(absolute_path),
fs_stat = fs_stat,
git_status = nil,
hidden = false,
name = name,
parent = parent,
diag_status = nil,
is_dot = false,
extension = string.match(name, ".?[^.]+%.(.*)") or "", ---@protected
} ---@param args NodeArgs
o = self:new(o) function FileNode:new(args)
FileNode.super.new(self, args)
return o self.type = "file"
self.extension = string.match(args.name, ".?[^.]+%.(.*)") or ""
self.executable = utils.is_executable(args.absolute_path)
end end
function FileNode:destroy() function FileNode:destroy()

View File

@ -1,9 +1,8 @@
local Class = require("nvim-tree.class") local Class = require("nvim-tree.classic")
---Abstract Node class. ---Abstract Node class.
---Uses the abstract factory pattern to instantiate child instances.
---@class (exact) Node: Class ---@class (exact) Node: Class
---@field type NODE_TYPE ---@field type "file" | "directory" | "link" uv.fs_stat.result.type
---@field explorer Explorer ---@field explorer Explorer
---@field absolute_path string ---@field absolute_path string
---@field executable boolean ---@field executable boolean
@ -14,7 +13,29 @@ local Class = require("nvim-tree.class")
---@field parent DirectoryNode? ---@field parent DirectoryNode?
---@field diag_status DiagStatus? ---@field diag_status DiagStatus?
---@field private is_dot boolean cached is_dotfile ---@field private is_dot boolean cached is_dotfile
local Node = Class:new() local Node = Class:extend()
---@class (exact) NodeArgs
---@field explorer Explorer
---@field parent DirectoryNode?
---@field absolute_path string
---@field name string
---@field fs_stat uv.fs_stat.result?
---@protected
---@param args NodeArgs
function Node:new(args)
self.explorer = args.explorer
self.absolute_path = args.absolute_path
self.executable = false
self.fs_stat = args.fs_stat
self.git_status = nil
self.hidden = false
self.name = args.name
self.parent = args.parent
self.diag_status = nil
self.is_dot = false
end
function Node:destroy() function Node:destroy()
end end
@ -104,17 +125,17 @@ function Node:clone()
---@type Node ---@type Node
local clone = { local clone = {
type = self.type, type = self.type,
explorer = explorer_placeholder, explorer = explorer_placeholder,
absolute_path = self.absolute_path, absolute_path = self.absolute_path,
executable = self.executable, executable = self.executable,
fs_stat = self.fs_stat, fs_stat = self.fs_stat,
git_status = self.git_status, git_status = self.git_status,
hidden = self.hidden, hidden = self.hidden,
name = self.name, name = self.name,
parent = nil, parent = nil,
diag_status = nil, diag_status = nil,
is_dot = self.is_dot, is_dot = self.is_dot,
} }
return clone return clone

View File

@ -0,0 +1,19 @@
local Class = require("nvim-tree.classic")
---@class (exact) LinkNode: Class
---@field link_to string
---@field protected fs_stat_target uv.fs_stat.result
local LinkNode = Class:extend()
---@class (exact) LinkNodeArgs: NodeArgs
---@field link_to string
---@field fs_stat_target uv.fs_stat.result
---@protected
---@param args LinkNodeArgs
function LinkNode:new(args)
self.link_to = args.link_to
self.fs_stat_target = args.fs_stat_target
end
return LinkNode

View File

@ -1,20 +1,15 @@
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
---@class (exact) RootNode: DirectoryNode ---@class (exact) RootNode: DirectoryNode
local RootNode = DirectoryNode:new() local RootNode = DirectoryNode:extend()
---Static factory method ---@class RootNode
---@param explorer Explorer ---@overload fun(args: NodeArgs): RootNode
---@param absolute_path string
---@param name string
---@param fs_stat uv.fs_stat.result|nil
---@return RootNode
function RootNode:create(explorer, absolute_path, name, fs_stat)
local o = DirectoryNode:create(explorer, nil, absolute_path, name, fs_stat)
o = self:new(o) ---@protected
---@param args NodeArgs
return o function RootNode:new(args)
RootNode.super.new(self, args)
end end
---Root is never a dotfile ---Root is never a dotfile

View File

@ -2,6 +2,7 @@ local notify = require("nvim-tree.notify")
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local view = require("nvim-tree.view") local view = require("nvim-tree.view")
local Class = require("nvim-tree.classic")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
local DecoratorBookmarks = require("nvim-tree.renderer.decorator.bookmarks") local DecoratorBookmarks = require("nvim-tree.renderer.decorator.bookmarks")
@ -26,57 +27,51 @@ local pad = require("nvim-tree.renderer.components.padding")
---@field col_end number ---@field col_end number
---@class (exact) Builder ---@class (exact) Builder
---@field private __index? table
---@field lines string[] includes icons etc. ---@field lines string[] includes icons etc.
---@field hl_args AddHighlightArgs[] line highlights ---@field hl_args AddHighlightArgs[] line highlights
---@field signs string[] line signs ---@field signs string[] line signs
---@field extmarks table[] extra marks for right icon placement ---@field extmarks table[] extra marks for right icon placement
---@field virtual_lines table[] virtual lines for hidden count display ---@field virtual_lines table[] virtual lines for hidden count display
---@field private explorer Explorer ---@field private explorer Explorer
---@field private opts table
---@field private index number ---@field private index number
---@field private depth number ---@field private depth number
---@field private combined_groups table<string, boolean> combined group names ---@field private combined_groups table<string, boolean> combined group names
---@field private markers boolean[] indent markers ---@field private markers boolean[] indent markers
---@field private decorators Decorator[] ---@field private decorators Decorator[]
---@field private hidden_display fun(node: Node): string|nil ---@field private hidden_display fun(node: Node): string|nil
local Builder = {} local Builder = Class:extend()
---@param opts table user options ---@class Builder
---@param explorer Explorer ---@overload fun(args: BuilderArgs): Builder
---@return Builder
function Builder:new(opts, explorer) ---@class (exact) BuilderArgs
---@type Builder ---@field explorer Explorer
local o = {
opts = opts, ---@protected
explorer = explorer, ---@param args BuilderArgs
index = 0, function Builder:new(args)
depth = 0, self.explorer = args.explorer
hl_args = {}, self.index = 0
combined_groups = {}, self.depth = 0
lines = {}, self.hl_args = {}
markers = {}, self.combined_groups = {}
signs = {}, self.lines = {}
extmarks = {}, self.markers = {}
virtual_lines = {}, self.signs = {}
decorators = { self.extmarks = {}
-- priority order self.virtual_lines = {}
DecoratorCut:create(opts, explorer), self.decorators = {
DecoratorCopied:create(opts, explorer), -- priority order
DecoratorDiagnostics:create(opts, explorer), DecoratorCut({ explorer = args.explorer }),
DecoratorBookmarks:create(opts, explorer), DecoratorCopied({ explorer = args.explorer }),
DecoratorModified:create(opts, explorer), DecoratorDiagnostics({ explorer = args.explorer }),
DecoratorHidden:create(opts, explorer), DecoratorBookmarks({ explorer = args.explorer }),
DecoratorOpened:create(opts, explorer), DecoratorModified({ explorer = args.explorer }),
DecoratorGit:create(opts, explorer), DecoratorHidden({ explorer = args.explorer }),
}, DecoratorOpened({ explorer = args.explorer }),
hidden_display = Builder:setup_hidden_display_function(opts), DecoratorGit({ explorer = args.explorer })
} }
self.hidden_display = Builder:setup_hidden_display_function(self.explorer.opts)
setmetatable(o, self)
self.__index = self
return o
end end
---Insert ranged highlight groups into self.highlights ---Insert ranged highlight groups into self.highlights
@ -123,7 +118,7 @@ function Builder:format_line(indent_markers, arrows, icon, name, node)
end end
for _, v in ipairs(t2) do for _, v in ipairs(t2) do
if added_len > 0 then if added_len > 0 then
table.insert(t1, { str = self.opts.renderer.icons.padding }) table.insert(t1, { str = self.explorer.opts.renderer.icons.padding })
end end
table.insert(t1, v) table.insert(t1, v)
end end
@ -284,7 +279,7 @@ function Builder:add_hidden_count_string(node, idx, num_children)
local hidden_count_string = self.hidden_display(node.hidden_stats) local hidden_count_string = self.hidden_display(node.hidden_stats)
if hidden_count_string and hidden_count_string ~= "" then if hidden_count_string and hidden_count_string ~= "" then
local indent_markers = pad.get_indent_markers(self.depth, idx or 0, num_children or 0, node, self.markers, 1) local indent_markers = pad.get_indent_markers(self.depth, idx or 0, num_children or 0, node, self.markers, 1)
local indent_width = self.opts.renderer.indent_width local indent_width = self.explorer.opts.renderer.indent_width
local indent_padding = string.rep(" ", indent_width) local indent_padding = string.rep(" ", indent_width)
local indent_string = indent_padding .. indent_markers.str local indent_string = indent_padding .. indent_markers.str
@ -354,16 +349,16 @@ end
---@private ---@private
function Builder:build_header() function Builder:build_header()
if view.is_root_folder_visible(self.explorer.absolute_path) then if view.is_root_folder_visible(self.explorer.absolute_path) then
local root_name = self:format_root_name(self.opts.renderer.root_folder_label) local root_name = self:format_root_name(self.explorer.opts.renderer.root_folder_label)
table.insert(self.lines, root_name) table.insert(self.lines, root_name)
self:insert_highlight({ "NvimTreeRootFolder" }, 0, string.len(root_name)) self:insert_highlight({ "NvimTreeRootFolder" }, 0, string.len(root_name))
self.index = 1 self.index = 1
end end
if self.explorer.live_filter.filter then if self.explorer.live_filter.filter then
local filter_line = string.format("%s/%s/", self.opts.live_filter.prefix, self.explorer.live_filter.filter) local filter_line = string.format("%s/%s/", self.explorer.opts.live_filter.prefix, self.explorer.live_filter.filter)
table.insert(self.lines, filter_line) table.insert(self.lines, filter_line)
local prefix_length = string.len(self.opts.live_filter.prefix) local prefix_length = string.len(self.explorer.opts.live_filter.prefix)
self:insert_highlight({ "NvimTreeLiveFilterPrefix" }, 0, prefix_length) self:insert_highlight({ "NvimTreeLiveFilterPrefix" }, 0, prefix_length)
self:insert_highlight({ "NvimTreeLiveFilterValue" }, prefix_length, string.len(filter_line)) self:insert_highlight({ "NvimTreeLiveFilterValue" }, prefix_length, string.len(filter_line))
self.index = self.index + 1 self.index = self.index + 1
@ -413,11 +408,11 @@ function Builder:setup_hidden_display_function(opts)
-- In case of missing field such as live_filter we zero it, otherwise keep field as is -- In case of missing field such as live_filter we zero it, otherwise keep field as is
hidden_stats = vim.tbl_deep_extend("force", { hidden_stats = vim.tbl_deep_extend("force", {
live_filter = 0, live_filter = 0,
git = 0, git = 0,
buf = 0, buf = 0,
dotfile = 0, dotfile = 0,
custom = 0, custom = 0,
bookmark = 0, bookmark = 0,
}, hidden_stats or {}) }, hidden_stats or {})
local ok, result = pcall(hidden_display, hidden_stats) local ok, result = pcall(hidden_display, hidden_stats)

View File

@ -1,95 +0,0 @@
local HL_POSITION = require("nvim-tree.enum").HL_POSITION
local diagnostics = require("nvim-tree.diagnostics")
local DirectoryNode = require("nvim-tree.node.directory")
local M = {
-- highlight strings for the icons
HS_ICON = {},
-- highlight groups for HL
HG_FILE = {},
HG_FOLDER = {},
-- position for HL
HL_POS = HL_POSITION.none,
}
---Diagnostics highlight group and position when highlight_diagnostics.
---@param node Node
---@return HL_POSITION position none when no status
---@return string|nil group only when status
function M.get_highlight(node)
if not node or M.HL_POS == HL_POSITION.none then
return HL_POSITION.none, nil
end
local group
local diag_status = diagnostics.get_diag_status(node)
if node:is(DirectoryNode) then
group = M.HS_FOLDER[diag_status and diag_status.value]
else
group = M.HS_FILE[diag_status and diag_status.value]
end
if group then
return M.HL_POS, group
else
return HL_POSITION.none, nil
end
end
---diagnostics icon if there is a status
---@param node Node
---@return HighlightedString|nil modified icon
function M.get_icon(node)
if node and M.config.diagnostics.enable and M.config.renderer.icons.show.diagnostics then
local diag_status = diagnostics.get_diag_status(node)
return M.ICON[diag_status and diag_status.value]
end
end
function M.setup(opts)
M.config = {
diagnostics = opts.diagnostics,
renderer = opts.renderer,
}
if opts.diagnostics.enable and opts.renderer.highlight_diagnostics then
M.HL_POS = HL_POSITION[opts.renderer.highlight_diagnostics]
end
M.HG_FILE[vim.diagnostic.severity.ERROR] = "NvimTreeDiagnosticErrorFileHL"
M.HG_FILE[vim.diagnostic.severity.WARN] = "NvimTreeDiagnosticWarningFileHL"
M.HG_FILE[vim.diagnostic.severity.INFO] = "NvimTreeDiagnosticInfoFileHL"
M.HG_FILE[vim.diagnostic.severity.HINT] = "NvimTreeDiagnosticHintFileHL"
M.HG_FOLDER[vim.diagnostic.severity.ERROR] = "NvimTreeDiagnosticErrorFolderHL"
M.HG_FOLDER[vim.diagnostic.severity.WARN] = "NvimTreeDiagnosticWarningFolderHL"
M.HG_FOLDER[vim.diagnostic.severity.INFO] = "NvimTreeDiagnosticInfoFolderHL"
M.HG_FOLDER[vim.diagnostic.severity.HINT] = "NvimTreeDiagnosticHintFolderHL"
M.HS_ICON[vim.diagnostic.severity.ERROR] = {
str = M.config.diagnostics.icons.error,
hl = { "NvimTreeDiagnosticErrorIcon" },
}
M.HS_ICON[vim.diagnostic.severity.WARN] = {
str = M.config.diagnostics.icons.warning,
hl = { "NvimTreeDiagnosticWarningIcon" },
}
M.HS_ICON[vim.diagnostic.severity.INFO] = {
str = M.config.diagnostics.icons.info,
hl = { "NvimTreeDiagnosticInfoIcon" },
}
M.HS_ICON[vim.diagnostic.severity.HINT] = {
str = M.config.diagnostics.icons.hint,
hl = { "NvimTreeDiagnosticHintIcon" },
}
for _, i in ipairs(M.HS_ICON) do
vim.fn.sign_define(i.hl[1], { text = i.str, texthl = i.hl[1] })
end
end
return M

View File

@ -57,13 +57,13 @@ local function show()
end end
M.popup_win = vim.api.nvim_open_win(vim.api.nvim_create_buf(false, false), false, { M.popup_win = vim.api.nvim_open_win(vim.api.nvim_create_buf(false, false), false, {
relative = "win", relative = "win",
row = 0, row = 0,
bufpos = { vim.api.nvim_win_get_cursor(0)[1] - 1, 0 }, bufpos = { vim.api.nvim_win_get_cursor(0)[1] - 1, 0 },
width = math.min(text_width, vim.o.columns - 2), width = math.min(text_width, vim.o.columns - 2),
height = 1, height = 1,
noautocmd = true, noautocmd = true,
style = "minimal", style = "minimal",
}) })
local ns_id = vim.api.nvim_get_namespaces()["NvimTreeHighlights"] local ns_id = vim.api.nvim_get_namespaces()["NvimTreeHighlights"]

View File

@ -1,12 +1,10 @@
local M = {} local M = {}
M.diagnostics = require("nvim-tree.renderer.components.diagnostics")
M.full_name = require("nvim-tree.renderer.components.full-name") M.full_name = require("nvim-tree.renderer.components.full-name")
M.devicons = require("nvim-tree.renderer.components.devicons") M.devicons = require("nvim-tree.renderer.components.devicons")
M.padding = require("nvim-tree.renderer.components.padding") M.padding = require("nvim-tree.renderer.components.padding")
function M.setup(opts) function M.setup(opts)
M.diagnostics.setup(opts)
M.full_name.setup(opts) M.full_name.setup(opts)
M.devicons.setup(opts) M.devicons.setup(opts)
M.padding.setup(opts) M.padding.setup(opts)

View File

@ -1,35 +1,29 @@
local HL_POSITION = require("nvim-tree.enum").HL_POSITION
local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT
local Decorator = require("nvim-tree.renderer.decorator") local Decorator = require("nvim-tree.renderer.decorator")
---@class (exact) DecoratorBookmarks: Decorator ---@class (exact) DecoratorBookmarks: Decorator
---@field icon HighlightedString? ---@field icon HighlightedString?
local DecoratorBookmarks = Decorator:new() local DecoratorBookmarks = Decorator:extend()
---Static factory method ---@class DecoratorBookmarks
---@param opts table ---@overload fun(explorer: DecoratorArgs): DecoratorBookmarks
---@param explorer Explorer
---@return DecoratorBookmarks
function DecoratorBookmarks:create(opts, explorer)
---@type DecoratorBookmarks
local o = {
explorer = explorer,
enabled = true,
hl_pos = HL_POSITION[opts.renderer.highlight_bookmarks] or HL_POSITION.none,
icon_placement = ICON_PLACEMENT[opts.renderer.icons.bookmarks_placement] or ICON_PLACEMENT.none,
}
o = self:new(o)
if opts.renderer.icons.show.bookmarks then ---@protected
o.icon = { ---@param args DecoratorArgs
str = opts.renderer.icons.glyphs.bookmark, function DecoratorBookmarks:new(args)
Decorator.new(self, {
explorer = args.explorer,
enabled = true,
hl_pos = args.explorer.opts.renderer.highlight_bookmarks or "none",
icon_placement = args.explorer.opts.renderer.icons.bookmarks_placement or "none",
})
if self.explorer.opts.renderer.icons.show.bookmarks then
self.icon = {
str = self.explorer.opts.renderer.icons.glyphs.bookmark,
hl = { "NvimTreeBookmarkIcon" }, hl = { "NvimTreeBookmarkIcon" },
} }
o:define_sign(o.icon) self:define_sign(self.icon)
end end
return o
end end
---Bookmark icon: renderer.icons.show.bookmarks and node is marked ---Bookmark icon: renderer.icons.show.bookmarks and node is marked
@ -45,7 +39,7 @@ end
---@param node Node ---@param node Node
---@return string|nil group ---@return string|nil group
function DecoratorBookmarks:calculate_highlight(node) function DecoratorBookmarks:calculate_highlight(node)
if self.hl_pos ~= HL_POSITION.none and self.explorer.marks:get(node) then if self.range ~= "none" and self.explorer.marks:get(node) then
return "NvimTreeBookmarkHL" return "NvimTreeBookmarkHL"
end end
end end

View File

@ -1,34 +1,27 @@
local HL_POSITION = require("nvim-tree.enum").HL_POSITION
local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT
local Decorator = require("nvim-tree.renderer.decorator") local Decorator = require("nvim-tree.renderer.decorator")
---@class (exact) DecoratorCopied: Decorator ---@class (exact) DecoratorCopied: Decorator
---@field icon HighlightedString? local DecoratorCopied = Decorator:extend()
local DecoratorCopied = Decorator:new()
---Static factory method ---@class DecoratorCopied
---@param opts table ---@overload fun(explorer: DecoratorArgs): DecoratorCopied
---@param explorer Explorer
---@return DecoratorCopied
function DecoratorCopied:create(opts, explorer)
---@type DecoratorCopied
local o = {
explorer = explorer,
enabled = true,
hl_pos = HL_POSITION[opts.renderer.highlight_clipboard] or HL_POSITION.none,
icon_placement = ICON_PLACEMENT.none,
}
o = self:new(o)
return o ---@protected
---@param args DecoratorArgs
function DecoratorCopied:new(args)
Decorator.new(self, {
explorer = args.explorer,
enabled = true,
hl_pos = args.explorer.opts.renderer.highlight_clipboard or "none",
icon_placement = "none",
})
end end
---Copied highlight: renderer.highlight_clipboard and node is copied ---Copied highlight: renderer.highlight_clipboard and node is copied
---@param node Node ---@param node Node
---@return string|nil group ---@return string|nil group
function DecoratorCopied:calculate_highlight(node) function DecoratorCopied:calculate_highlight(node)
if self.hl_pos ~= HL_POSITION.none and self.explorer.clipboard:is_copied(node) then if self.range ~= "none" and self.explorer.clipboard:is_copied(node) then
return "NvimTreeCopiedHL" return "NvimTreeCopiedHL"
end end
end end

View File

@ -1,33 +1,27 @@
local HL_POSITION = require("nvim-tree.enum").HL_POSITION
local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT
local Decorator = require("nvim-tree.renderer.decorator") local Decorator = require("nvim-tree.renderer.decorator")
---@class (exact) DecoratorCut: Decorator ---@class (exact) DecoratorCut: Decorator
local DecoratorCut = Decorator:new() local DecoratorCut = Decorator:extend()
---Static factory method ---@class DecoratorCut
---@param opts table ---@overload fun(explorer: DecoratorArgs): DecoratorCut
---@param explorer Explorer
---@return DecoratorCut
function DecoratorCut:create(opts, explorer)
---@type DecoratorCut
local o = {
explorer = explorer,
enabled = true,
hl_pos = HL_POSITION[opts.renderer.highlight_clipboard] or HL_POSITION.none,
icon_placement = ICON_PLACEMENT.none,
}
o = self:new(o)
return o ---@protected
---@param args DecoratorArgs
function DecoratorCut:new(args)
Decorator.new(self, {
explorer = args.explorer,
enabled = true,
hl_pos = args.explorer.opts.renderer.highlight_clipboard or "none",
icon_placement = "none",
})
end end
---Cut highlight: renderer.highlight_clipboard and node is cut ---Cut highlight: renderer.highlight_clipboard and node is cut
---@param node Node ---@param node Node
---@return string|nil group ---@return string|nil group
function DecoratorCut:calculate_highlight(node) function DecoratorCut:calculate_highlight(node)
if self.hl_pos ~= HL_POSITION.none and self.explorer.clipboard:is_cut(node) then if self.range ~= "none" and self.explorer.clipboard:is_cut(node) then
return "NvimTreeCutHL" return "NvimTreeCutHL"
end end
end end

View File

@ -1,8 +1,5 @@
local diagnostics = require("nvim-tree.diagnostics") local diagnostics = require("nvim-tree.diagnostics")
local HL_POSITION = require("nvim-tree.enum").HL_POSITION
local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT
local Decorator = require("nvim-tree.renderer.decorator") local Decorator = require("nvim-tree.renderer.decorator")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
@ -35,38 +32,35 @@ local ICON_KEYS = {
---@class (exact) DecoratorDiagnostics: Decorator ---@class (exact) DecoratorDiagnostics: Decorator
---@field icons HighlightedString[]? ---@field icons HighlightedString[]?
local DecoratorDiagnostics = Decorator:new() local DecoratorDiagnostics = Decorator:extend()
---Static factory method ---@class DecoratorDiagnostics
---@param opts table ---@overload fun(explorer: DecoratorArgs): DecoratorDiagnostics
---@param explorer Explorer
---@return DecoratorDiagnostics
function DecoratorDiagnostics:create(opts, explorer)
---@type DecoratorDiagnostics
local o = {
explorer = explorer,
enabled = opts.diagnostics.enable,
hl_pos = HL_POSITION[opts.renderer.highlight_diagnostics] or HL_POSITION.none,
icon_placement = ICON_PLACEMENT[opts.renderer.icons.diagnostics_placement] or ICON_PLACEMENT.none,
}
o = self:new(o)
if not o.enabled then ---@protected
return o ---@param args DecoratorArgs
function DecoratorDiagnostics:new(args)
Decorator.new(self, {
explorer = args.explorer,
enabled = true,
hl_pos = args.explorer.opts.renderer.highlight_diagnostics or "none",
icon_placement = args.explorer.opts.renderer.icons.diagnostics_placement or "none",
})
if not self.enabled then
return
end end
if opts.renderer.icons.show.diagnostics then if self.explorer.opts.renderer.icons.show.diagnostics then
o.icons = {} self.icons = {}
for name, sev in pairs(ICON_KEYS) do for name, sev in pairs(ICON_KEYS) do
o.icons[sev] = { self.icons[sev] = {
str = opts.diagnostics.icons[name], str = self.explorer.opts.diagnostics.icons[name],
hl = { HG_ICON[sev] }, hl = { HG_ICON[sev] },
} }
o:define_sign(o.icons[sev]) self:define_sign(self.icons[sev])
end end
end end
return o
end end
---Diagnostic icon: diagnostics.enable, renderer.icons.show.diagnostics and node has status ---Diagnostic icon: diagnostics.enable, renderer.icons.show.diagnostics and node has status
@ -87,7 +81,7 @@ end
---@param node Node ---@param node Node
---@return string|nil group ---@return string|nil group
function DecoratorDiagnostics:calculate_highlight(node) function DecoratorDiagnostics:calculate_highlight(node)
if not node or not self.enabled or self.hl_pos == HL_POSITION.none then if not node or not self.enabled or self.range == "none" then
return nil return nil
end end

View File

@ -1,8 +1,5 @@
local notify = require("nvim-tree.notify") local notify = require("nvim-tree.notify")
local HL_POSITION = require("nvim-tree.enum").HL_POSITION
local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT
local Decorator = require("nvim-tree.renderer.decorator") local Decorator = require("nvim-tree.renderer.decorator")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
@ -20,52 +17,49 @@ local DirectoryNode = require("nvim-tree.node.directory")
---@field folder_hl_by_xy table<GitXY, string>? ---@field folder_hl_by_xy table<GitXY, string>?
---@field icons_by_status GitIconsByStatus? ---@field icons_by_status GitIconsByStatus?
---@field icons_by_xy GitIconsByXY? ---@field icons_by_xy GitIconsByXY?
local DecoratorGit = Decorator:new() local DecoratorGit = Decorator:extend()
---Static factory method ---@class DecoratorGit
---@param opts table ---@overload fun(explorer: DecoratorArgs): DecoratorGit
---@param explorer Explorer
---@return DecoratorGit
function DecoratorGit:create(opts, explorer)
---@type DecoratorGit
local o = {
explorer = explorer,
enabled = opts.git.enable,
hl_pos = HL_POSITION[opts.renderer.highlight_git] or HL_POSITION.none,
icon_placement = ICON_PLACEMENT[opts.renderer.icons.git_placement] or ICON_PLACEMENT.none,
}
o = self:new(o)
if not o.enabled then ---@protected
return o ---@param args DecoratorArgs
function DecoratorGit:new(args)
Decorator.new(self, {
explorer = args.explorer,
enabled = args.explorer.opts.git.enable,
hl_pos = args.explorer.opts.renderer.highlight_git or "none",
icon_placement = args.explorer.opts.renderer.icons.git_placement or "none",
})
if not self.enabled then
return
end end
if o.hl_pos ~= HL_POSITION.none then if self.range ~= "none" then
o:build_file_folder_hl_by_xy() self:build_file_folder_hl_by_xy()
end end
if opts.renderer.icons.show.git then if self.explorer.opts.renderer.icons.show.git then
o:build_icons_by_status(opts.renderer.icons.glyphs.git) self:build_icons_by_status(self.explorer.opts.renderer.icons.glyphs.git)
o:build_icons_by_xy(o.icons_by_status) self:build_icons_by_xy(self.icons_by_status)
for _, icon in pairs(o.icons_by_status) do for _, icon in pairs(self.icons_by_status) do
self:define_sign(icon) self:define_sign(icon)
end end
end end
return o
end end
---@param glyphs GitGlyphsByStatus ---@param glyphs GitGlyphsByStatus
function DecoratorGit:build_icons_by_status(glyphs) function DecoratorGit:build_icons_by_status(glyphs)
self.icons_by_status = {} self.icons_by_status = {}
self.icons_by_status.staged = { str = glyphs.staged, hl = { "NvimTreeGitStagedIcon" }, ord = 1 } self.icons_by_status.staged = { str = glyphs.staged, hl = { "NvimTreeGitStagedIcon" }, ord = 1 }
self.icons_by_status.unstaged = { str = glyphs.unstaged, hl = { "NvimTreeGitDirtyIcon" }, ord = 2 } self.icons_by_status.unstaged = { str = glyphs.unstaged, hl = { "NvimTreeGitDirtyIcon" }, ord = 2 }
self.icons_by_status.renamed = { str = glyphs.renamed, hl = { "NvimTreeGitRenamedIcon" }, ord = 3 } self.icons_by_status.renamed = { str = glyphs.renamed, hl = { "NvimTreeGitRenamedIcon" }, ord = 3 }
self.icons_by_status.deleted = { str = glyphs.deleted, hl = { "NvimTreeGitDeletedIcon" }, ord = 4 } self.icons_by_status.deleted = { str = glyphs.deleted, hl = { "NvimTreeGitDeletedIcon" }, ord = 4 }
self.icons_by_status.unmerged = { str = glyphs.unmerged, hl = { "NvimTreeGitMergeIcon" }, ord = 5 } self.icons_by_status.unmerged = { str = glyphs.unmerged, hl = { "NvimTreeGitMergeIcon" }, ord = 5 }
self.icons_by_status.untracked = { str = glyphs.untracked, hl = { "NvimTreeGitNewIcon" }, ord = 6 } self.icons_by_status.untracked = { str = glyphs.untracked, hl = { "NvimTreeGitNewIcon" }, ord = 6 }
self.icons_by_status.ignored = { str = glyphs.ignored, hl = { "NvimTreeGitIgnoredIcon" }, ord = 7 } self.icons_by_status.ignored = { str = glyphs.ignored, hl = { "NvimTreeGitIgnoredIcon" }, ord = 7 }
end end
---@param icons GitIconsByXY ---@param icons GitIconsByXY
@ -102,7 +96,7 @@ function DecoratorGit:build_icons_by_xy(icons)
["DD"] = { icons.deleted }, ["DD"] = { icons.deleted },
["DU"] = { icons.deleted, icons.unmerged }, ["DU"] = { icons.deleted, icons.unmerged },
["!!"] = { icons.ignored }, ["!!"] = { icons.ignored },
dirty = { icons.unstaged }, dirty = { icons.unstaged },
} }
end end
@ -121,7 +115,7 @@ function DecoratorGit:build_file_folder_hl_by_xy()
[" T"] = "NvimTreeGitFileDirtyHL", [" T"] = "NvimTreeGitFileDirtyHL",
["MM"] = "NvimTreeGitFileDirtyHL", ["MM"] = "NvimTreeGitFileDirtyHL",
["AM"] = "NvimTreeGitFileDirtyHL", ["AM"] = "NvimTreeGitFileDirtyHL",
dirty = "NvimTreeGitFileDirtyHL", dirty = "NvimTreeGitFileDirtyHL",
["A "] = "NvimTreeGitFileStagedHL", ["A "] = "NvimTreeGitFileStagedHL",
["??"] = "NvimTreeGitFileNewHL", ["??"] = "NvimTreeGitFileNewHL",
["AU"] = "NvimTreeGitFileMergeHL", ["AU"] = "NvimTreeGitFileMergeHL",
@ -165,7 +159,7 @@ function DecoratorGit:calculate_icons(node)
for _, s in pairs(git_xy) do for _, s in pairs(git_xy) do
local icons = self.icons_by_xy[s] local icons = self.icons_by_xy[s]
if not icons then if not icons then
if self.hl_pos == HL_POSITION.none then if self.range == "none" then
notify.warn(string.format("Unrecognized git state '%s'", git_xy)) notify.warn(string.format("Unrecognized git state '%s'", git_xy))
end end
return nil return nil
@ -197,7 +191,7 @@ end
---@param node Node ---@param node Node
---@return string|nil name ---@return string|nil name
function DecoratorGit:sign_name(node) function DecoratorGit:sign_name(node)
if self.icon_placement ~= ICON_PLACEMENT.signcolumn then if self.icon_placement ~= "signcolumn" then
return return
end end
@ -211,7 +205,7 @@ end
---@param node Node ---@param node Node
---@return string|nil group ---@return string|nil group
function DecoratorGit:calculate_highlight(node) function DecoratorGit:calculate_highlight(node)
if not node or not self.enabled or self.hl_pos == HL_POSITION.none then if not node or not self.enabled or self.range == "none" then
return nil return nil
end end

View File

@ -1,36 +1,30 @@
local HL_POSITION = require("nvim-tree.enum").HL_POSITION
local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT
local Decorator = require("nvim-tree.renderer.decorator") local Decorator = require("nvim-tree.renderer.decorator")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
---@class (exact) DecoratorHidden: Decorator ---@class (exact) DecoratorHidden: Decorator
---@field icon HighlightedString? ---@field icon HighlightedString?
local DecoratorHidden = Decorator:new() local DecoratorHidden = Decorator:extend()
---Static factory method ---@class DecoratorHidden
---@param opts table ---@overload fun(explorer: DecoratorArgs): DecoratorHidden
---@param explorer Explorer
---@return DecoratorHidden
function DecoratorHidden:create(opts, explorer)
---@type DecoratorHidden
local o = {
explorer = explorer,
enabled = true,
hl_pos = HL_POSITION[opts.renderer.highlight_hidden] or HL_POSITION.none,
icon_placement = ICON_PLACEMENT[opts.renderer.icons.hidden_placement] or ICON_PLACEMENT.none,
}
o = self:new(o)
if opts.renderer.icons.show.hidden then ---@protected
o.icon = { ---@param args DecoratorArgs
str = opts.renderer.icons.glyphs.hidden, function DecoratorHidden:new(args)
Decorator.new(self, {
explorer = args.explorer,
enabled = true,
hl_pos = args.explorer.opts.renderer.highlight_hidden or "none",
icon_placement = args.explorer.opts.renderer.icons.hidden_placement or "none",
})
if self.explorer.opts.renderer.icons.show.hidden then
self.icon = {
str = self.explorer.opts.renderer.icons.glyphs.hidden,
hl = { "NvimTreeHiddenIcon" }, hl = { "NvimTreeHiddenIcon" },
} }
o:define_sign(o.icon) self:define_sign(self.icon)
end end
return o
end end
---Hidden icon: renderer.icons.show.hidden and node starts with `.` (dotfile). ---Hidden icon: renderer.icons.show.hidden and node starts with `.` (dotfile).
@ -46,7 +40,7 @@ end
---@param node Node ---@param node Node
---@return string|nil group ---@return string|nil group
function DecoratorHidden:calculate_highlight(node) function DecoratorHidden:calculate_highlight(node)
if not self.enabled or self.hl_pos == HL_POSITION.none or not node:is_dotfile() then if not self.enabled or self.range == "none" or not node:is_dotfile() then
return nil return nil
end end

View File

@ -1,16 +1,33 @@
local Class = require("nvim-tree.class") local Class = require("nvim-tree.classic")
local HL_POSITION = require("nvim-tree.enum").HL_POSITION ---@alias DecoratorRange "none" | "icon" | "name" | "all"
local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT ---@alias DecoratorIconPlacement "none" | "before" | "after" | "signcolumn" | "right_align"
---Abstract Decorator ---Abstract Decorator
---Uses the factory pattern to instantiate child instances. ---Uses the factory pattern to instantiate child instances.
---@class (exact) Decorator: Class ---@class (exact) Decorator: Class
---@field protected explorer Explorer ---@field protected explorer Explorer
---@field protected enabled boolean ---@field protected enabled boolean
---@field protected hl_pos HL_POSITION ---@field protected range DecoratorRange
---@field protected icon_placement ICON_PLACEMENT ---@field protected icon_placement DecoratorIconPlacement
local Decorator = Class:new() local Decorator = Class:extend()
---@class (exact) DecoratorArgs
---@field explorer Explorer
---@class (exact) AbstractDecoratorArgs: DecoratorArgs
---@field enabled boolean
---@field hl_pos DecoratorRange
---@field icon_placement DecoratorIconPlacement
---@protected
---@param args AbstractDecoratorArgs
function Decorator:new(args)
self.explorer = args.explorer
self.enabled = args.enabled
self.range = args.hl_pos
self.icon_placement = args.icon_placement
end
---Maybe highlight groups ---Maybe highlight groups
---@param node Node ---@param node Node
@ -19,13 +36,13 @@ local Decorator = Class:new()
function Decorator:groups_icon_name(node) function Decorator:groups_icon_name(node)
local icon_hl, name_hl local icon_hl, name_hl
if self.enabled and self.hl_pos ~= HL_POSITION.none then if self.enabled and self.range ~= "none" then
local hl = self:calculate_highlight(node) local hl = self:calculate_highlight(node)
if self.hl_pos == HL_POSITION.all or self.hl_pos == HL_POSITION.icon then if self.range == "all" or self.range == "icon" then
icon_hl = hl icon_hl = hl
end end
if self.hl_pos == HL_POSITION.all or self.hl_pos == HL_POSITION.name then if self.range == "all" or self.range == "name" then
name_hl = hl name_hl = hl
end end
end end
@ -37,7 +54,7 @@ end
---@param node Node ---@param node Node
---@return string|nil name ---@return string|nil name
function Decorator:sign_name(node) function Decorator:sign_name(node)
if not self.enabled or self.icon_placement ~= ICON_PLACEMENT.signcolumn then if not self.enabled or self.icon_placement ~= "signcolumn" then
return return
end end
@ -47,33 +64,33 @@ function Decorator:sign_name(node)
end end
end end
---Icons when ICON_PLACEMENT.before ---Icons when "before"
---@param node Node ---@param node Node
---@return HighlightedString[]|nil icons ---@return HighlightedString[]|nil icons
function Decorator:icons_before(node) function Decorator:icons_before(node)
if not self.enabled or self.icon_placement ~= ICON_PLACEMENT.before then if not self.enabled or self.icon_placement ~= "before" then
return return
end end
return self:calculate_icons(node) return self:calculate_icons(node)
end end
---Icons when ICON_PLACEMENT.after ---Icons when "after"
---@param node Node ---@param node Node
---@return HighlightedString[]|nil icons ---@return HighlightedString[]|nil icons
function Decorator:icons_after(node) function Decorator:icons_after(node)
if not self.enabled or self.icon_placement ~= ICON_PLACEMENT.after then if not self.enabled or self.icon_placement ~= "after" then
return return
end end
return self:calculate_icons(node) return self:calculate_icons(node)
end end
---Icons when ICON_PLACEMENT.right_align ---Icons when "right_align"
---@param node Node ---@param node Node
---@return HighlightedString[]|nil icons ---@return HighlightedString[]|nil icons
function Decorator:icons_right_align(node) function Decorator:icons_right_align(node)
if not self.enabled or self.icon_placement ~= ICON_PLACEMENT.right_align then if not self.enabled or self.icon_placement ~= "right_align" then
return return
end end
@ -109,7 +126,7 @@ function Decorator:define_sign(icon)
-- don't use sign if not defined -- don't use sign if not defined
if #icon.str < 1 then if #icon.str < 1 then
self.icon_placement = ICON_PLACEMENT.none self.icon_placement = "none"
return return
end end

View File

@ -1,42 +1,36 @@
local buffers = require("nvim-tree.buffers") local buffers = require("nvim-tree.buffers")
local HL_POSITION = require("nvim-tree.enum").HL_POSITION
local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT
local Decorator = require("nvim-tree.renderer.decorator") local Decorator = require("nvim-tree.renderer.decorator")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
---@class (exact) DecoratorModified: Decorator ---@class (exact) DecoratorModified: Decorator
---@field icon HighlightedString|nil ---@field icon HighlightedString?
local DecoratorModified = Decorator:new() local DecoratorModified = Decorator:extend()
---Static factory method ---@class DecoratorModified
---@param opts table ---@overload fun(explorer: DecoratorArgs): DecoratorModified
---@param explorer Explorer
---@return DecoratorModified
function DecoratorModified:create(opts, explorer)
---@type DecoratorModified
local o = {
explorer = explorer,
enabled = opts.modified.enable,
hl_pos = HL_POSITION[opts.renderer.highlight_modified] or HL_POSITION.none,
icon_placement = ICON_PLACEMENT[opts.renderer.icons.modified_placement] or ICON_PLACEMENT.none,
}
o = self:new(o)
if not o.enabled then ---@protected
return o ---@param args DecoratorArgs
function DecoratorModified:new(args)
Decorator.new(self, {
explorer = args.explorer,
enabled = true,
hl_pos = args.explorer.opts.renderer.highlight_modified or "none",
icon_placement = args.explorer.opts.renderer.icons.modified_placement or "none",
})
if not self.enabled then
return
end end
if opts.renderer.icons.show.modified then if self.explorer.opts.renderer.icons.show.modified then
o.icon = { self.icon = {
str = opts.renderer.icons.glyphs.modified, str = self.explorer.opts.renderer.icons.glyphs.modified,
hl = { "NvimTreeModifiedIcon" }, hl = { "NvimTreeModifiedIcon" },
} }
o:define_sign(o.icon) self:define_sign(self.icon)
end end
return o
end end
---Modified icon: modified.enable, renderer.icons.show.modified and node is modified ---Modified icon: modified.enable, renderer.icons.show.modified and node is modified
@ -52,7 +46,7 @@ end
---@param node Node ---@param node Node
---@return string|nil group ---@return string|nil group
function DecoratorModified:calculate_highlight(node) function DecoratorModified:calculate_highlight(node)
if not self.enabled or self.hl_pos == HL_POSITION.none or not buffers.is_modified(node) then if not self.enabled or self.range == "none" or not buffers.is_modified(node) then
return nil return nil
end end

View File

@ -1,36 +1,30 @@
local buffers = require("nvim-tree.buffers") local buffers = require("nvim-tree.buffers")
local HL_POSITION = require("nvim-tree.enum").HL_POSITION
local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT
local Decorator = require("nvim-tree.renderer.decorator") local Decorator = require("nvim-tree.renderer.decorator")
---@class (exact) DecoratorOpened: Decorator ---@class (exact) DecoratorOpened: Decorator
---@field icon HighlightedString|nil ---@field icon HighlightedString|nil
local DecoratorOpened = Decorator:new() local DecoratorOpened = Decorator:extend()
---Static factory method ---@class DecoratorOpened
---@param opts table ---@overload fun(explorer: DecoratorArgs): DecoratorOpened
---@param explorer Explorer
---@return DecoratorOpened
function DecoratorOpened:create(opts, explorer)
---@type DecoratorOpened
local o = {
explorer = explorer,
enabled = true,
hl_pos = HL_POSITION[opts.renderer.highlight_opened_files] or HL_POSITION.none,
icon_placement = ICON_PLACEMENT.none,
}
o = self:new(o)
return o ---@protected
---@param args DecoratorArgs
function DecoratorOpened:new(args)
Decorator.new(self, {
explorer = args.explorer,
enabled = true,
hl_pos = args.explorer.opts.renderer.highlight_opened_files or "none",
icon_placement = "none",
})
end end
---Opened highlight: renderer.highlight_opened_files and node has an open buffer ---Opened highlight: renderer.highlight_opened_files and node has an open buffer
---@param node Node ---@param node Node
---@return string|nil group ---@return string|nil group
function DecoratorOpened:calculate_highlight(node) function DecoratorOpened:calculate_highlight(node)
if self.hl_pos ~= HL_POSITION.none and buffers.is_opened(node) then if self.range ~= "none" and buffers.is_opened(node) then
return "NvimTreeOpenedHL" return "NvimTreeOpenedHL"
end end
end end

View File

@ -2,6 +2,7 @@ local log = require("nvim-tree.log")
local view = require("nvim-tree.view") local view = require("nvim-tree.view")
local events = require("nvim-tree.events") local events = require("nvim-tree.events")
local Class = require("nvim-tree.classic")
local Builder = require("nvim-tree.renderer.builder") local Builder = require("nvim-tree.renderer.builder")
local SIGN_GROUP = "NvimTreeRendererSigns" local SIGN_GROUP = "NvimTreeRendererSigns"
@ -10,26 +11,20 @@ local namespace_highlights_id = vim.api.nvim_create_namespace("NvimTreeHighlight
local namespace_extmarks_id = vim.api.nvim_create_namespace("NvimTreeExtmarks") local namespace_extmarks_id = vim.api.nvim_create_namespace("NvimTreeExtmarks")
local namespace_virtual_lines_id = vim.api.nvim_create_namespace("NvimTreeVirtualLines") local namespace_virtual_lines_id = vim.api.nvim_create_namespace("NvimTreeVirtualLines")
---@class (exact) Renderer ---@class (exact) Renderer: Class
---@field private __index? table ---@field explorer Explorer
---@field private opts table user options local Renderer = Class:extend()
---@field private explorer Explorer
local Renderer = {}
---@param opts table user options ---@class Renderer
---@param explorer Explorer ---@overload fun(args: RendererArgs): Renderer
---@return Renderer
function Renderer:new(opts, explorer)
---@type Renderer
local o = {
opts = opts,
explorer = explorer,
}
setmetatable(o, self) ---@class (exact) RendererArgs
self.__index = self ---@field explorer Explorer
return o ---@protected
---@param args RendererArgs
function Renderer:new(args)
self.explorer = args.explorer
end end
---@private ---@private
@ -64,9 +59,9 @@ function Renderer:_draw(bufnr, lines, hl_args, signs, extmarks, virtual_lines)
for i, extname in pairs(extmarks) do for i, extname in pairs(extmarks) do
for _, mark in ipairs(extname) do for _, mark in ipairs(extname) do
vim.api.nvim_buf_set_extmark(bufnr, namespace_extmarks_id, i, -1, { vim.api.nvim_buf_set_extmark(bufnr, namespace_extmarks_id, i, -1, {
virt_text = { { mark.str, mark.hl } }, virt_text = { { mark.str, mark.hl } },
virt_text_pos = "right_align", virt_text_pos = "right_align",
hl_mode = "combine", hl_mode = "combine",
}) })
end end
end end
@ -74,8 +69,8 @@ function Renderer:_draw(bufnr, lines, hl_args, signs, extmarks, virtual_lines)
vim.api.nvim_buf_clear_namespace(bufnr, namespace_virtual_lines_id, 0, -1) vim.api.nvim_buf_clear_namespace(bufnr, namespace_virtual_lines_id, 0, -1)
for line_nr, vlines in pairs(virtual_lines) do for line_nr, vlines in pairs(virtual_lines) do
vim.api.nvim_buf_set_extmark(bufnr, namespace_virtual_lines_id, line_nr, 0, { vim.api.nvim_buf_set_extmark(bufnr, namespace_virtual_lines_id, line_nr, 0, {
virt_lines = vlines, virt_lines = vlines,
virt_lines_above = false, virt_lines_above = false,
virt_lines_leftcol = true, virt_lines_leftcol = true,
}) })
end end
@ -106,7 +101,7 @@ function Renderer:draw()
local cursor = vim.api.nvim_win_get_cursor(view.get_winnr() or 0) local cursor = vim.api.nvim_win_get_cursor(view.get_winnr() or 0)
local builder = Builder:new(self.opts, self.explorer):build() local builder = Builder(self.explorer):build()
self:_draw(bufnr, builder.lines, builder.hl_args, builder.signs, builder.extmarks, builder.virtual_lines) self:_draw(bufnr, builder.lines, builder.hl_args, builder.signs, builder.extmarks, builder.virtual_lines)

View File

@ -15,31 +15,31 @@ local DEFAULT_MAX_WIDTH = -1
local DEFAULT_PADDING = 1 local DEFAULT_PADDING = 1
M.View = { M.View = {
adaptive_size = false, adaptive_size = false,
centralize_selection = false, centralize_selection = false,
tabpages = {}, tabpages = {},
cursors = {}, cursors = {},
hide_root_folder = false, hide_root_folder = false,
live_filter = { live_filter = {
prev_focused_node = nil, prev_focused_node = nil,
}, },
winopts = { winopts = {
relativenumber = false, relativenumber = false,
number = false, number = false,
list = false, list = false,
foldenable = false, foldenable = false,
winfixwidth = true, winfixwidth = true,
winfixheight = true, winfixheight = true,
spell = false, spell = false,
signcolumn = "yes", signcolumn = "yes",
foldmethod = "manual", foldmethod = "manual",
foldcolumn = "0", foldcolumn = "0",
cursorcolumn = false, cursorcolumn = false,
cursorline = true, cursorline = true,
cursorlineopt = "both", cursorlineopt = "both",
colorcolumn = "0", colorcolumn = "0",
wrap = false, wrap = false,
winhl = table.concat({ winhl = table.concat({
"EndOfBuffer:NvimTreeEndOfBuffer", "EndOfBuffer:NvimTreeEndOfBuffer",
"CursorLine:NvimTreeCursorLine", "CursorLine:NvimTreeCursorLine",
"CursorLineNr:NvimTreeCursorLineNr", "CursorLineNr:NvimTreeCursorLineNr",
@ -152,7 +152,6 @@ local function set_window_options_and_buffer()
pcall(vim.api.nvim_command, "buffer " .. M.get_bufnr()) pcall(vim.api.nvim_command, "buffer " .. M.get_bufnr())
if vim.fn.has("nvim-0.10") == 1 then if vim.fn.has("nvim-0.10") == 1 then
local eventignore = vim.api.nvim_get_option_value("eventignore", {}) local eventignore = vim.api.nvim_get_option_value("eventignore", {})
vim.api.nvim_set_option_value("eventignore", "all", {}) vim.api.nvim_set_option_value("eventignore", "all", {})
@ -161,9 +160,7 @@ local function set_window_options_and_buffer()
end end
vim.api.nvim_set_option_value("eventignore", eventignore, {}) vim.api.nvim_set_option_value("eventignore", eventignore, {})
else else
local eventignore = vim.api.nvim_get_option("eventignore") ---@diagnostic disable-line: deprecated local eventignore = vim.api.nvim_get_option("eventignore") ---@diagnostic disable-line: deprecated
vim.api.nvim_set_option("eventignore", "all") ---@diagnostic disable-line: deprecated vim.api.nvim_set_option("eventignore", "all") ---@diagnostic disable-line: deprecated
@ -172,7 +169,6 @@ local function set_window_options_and_buffer()
end end
vim.api.nvim_set_option("eventignore", eventignore) ---@diagnostic disable-line: deprecated vim.api.nvim_set_option("eventignore", eventignore) ---@diagnostic disable-line: deprecated
end end
end end

View File

@ -2,7 +2,7 @@ local notify = require("nvim-tree.notify")
local log = require("nvim-tree.log") local log = require("nvim-tree.log")
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local Class = require("nvim-tree.class") local Class = require("nvim-tree.classic")
local FS_EVENT_FLAGS = { local FS_EVENT_FLAGS = {
-- inotify or equivalent will be used; fallback to stat has not yet been implemented -- inotify or equivalent will be used; fallback to stat has not yet been implemented
@ -15,36 +15,45 @@ local M = {
config = {}, config = {},
} }
---Registry of all events
---@type Event[]
local events = {}
---@class (exact) Event: Class ---@class (exact) Event: Class
---@field destroyed boolean ---@field destroyed boolean
---@field private path string ---@field private path string
---@field private fs_event uv.uv_fs_event_t? ---@field private fs_event uv.uv_fs_event_t?
---@field private listeners function[] ---@field private listeners function[]
local Event = Class:new() local Event = Class:extend()
---Registry of all events ---@class Event
---@type Event[] ---@overload fun(args: EventArgs): Event
local events = {}
---@class (exact) EventArgs
---@field path string
---@protected
---@param args EventArgs
function Event:new(args)
self.destroyed = false
self.path = args.path
self.fs_event = nil
self.listeners = {}
end
---Static factory method ---Static factory method
---Creates and starts an Event ---Creates and starts an Event
---@param path string ---nil on failure to start
---@return Event|nil ---@param args EventArgs
function Event:create(path) ---@return Event?
log.line("watcher", "Event:create '%s'", path) function Event:create(args)
log.line("watcher", "Event:create '%s'", args.path)
---@type Event local event = Event(args)
local o = {
destroyed = false,
path = path,
fs_event = nil,
listeners = {},
}
o = self:new(o)
if o:start() then if event:start() then
events[path] = o events[event.path] = event
return o return event
else else
return nil return nil
end end
@ -128,8 +137,10 @@ function Event:destroy(message)
events[self.path] = nil events[self.path] = nil
end end
---Static factory method ---Registry of all watchers
---Creates and starts a Watcher ---@type Watcher[]
local watchers = {}
---@class (exact) Watcher: Class ---@class (exact) Watcher: Class
---@field data table user data ---@field data table user data
---@field destroyed boolean ---@field destroyed boolean
@ -138,43 +149,50 @@ end
---@field private files string[]? ---@field private files string[]?
---@field private listener fun(filename: string)? ---@field private listener fun(filename: string)?
---@field private event Event ---@field private event Event
local Watcher = Class:new() local Watcher = Class:extend()
---Registry of all watchers ---@class Watcher
---@type Watcher[] ---@overload fun(args: WatcherArgs): Watcher
local watchers = {}
---@class (exact) WatcherArgs
---@field path string
---@field files string[]|nil
---@field callback fun(watcher: Watcher)
---@field data table? user data
---@protected
---@param args WatcherArgs
function Watcher:new(args)
self.data = args.data
self.destroyed = false
self.path = args.path
self.callback = args.callback
self.files = args.files
self.listener = nil
end
---Static factory method ---Static factory method
---@param path string ---Creates and starts a Watcher
---@param files string[]|nil ---nil on failure to create Event
---@param callback fun(watcher: Watcher) ---@param args WatcherArgs
---@param data table user data
---@return Watcher|nil ---@return Watcher|nil
function Watcher:create(path, files, callback, data) function Watcher:create(args)
log.line("watcher", "Watcher:create '%s' %s", path, vim.inspect(files)) log.line("watcher", "Watcher:create '%s' %s", args.path, vim.inspect(args.files))
local event = events[path] or Event:create(path) local event = events[args.path] or Event:create({ path = args.path })
if not event then if not event then
return nil return nil
end end
---@type Watcher local watcher = Watcher(args)
local o = {
data = data,
destroyed = false,
path = path,
callback = callback,
files = files,
listener = nil,
event = event,
}
o = self:new(o)
o:start() watcher.event = event
table.insert(watchers, o) watcher:start()
return o table.insert(watchers, watcher)
return watcher
end end
function Watcher:start() function Watcher:start()