From 3fc8de198c15ec4e5395f57b70579b3959976960 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Sat, 9 Nov 2024 14:14:04 +1100 Subject: [PATCH] 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 9fc7a866ec019215d7abc66c979b0859eed816ab. * 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 --- lua/nvim-tree/actions/fs/clipboard.lua | 45 ++--- lua/nvim-tree/actions/tree/modifiers/init.lua | 1 - .../actions/tree/modifiers/toggles.lua | 73 ------- lua/nvim-tree/api.lua | 34 +++- .../{diagnostics.lua => hi-test.lua} | 45 +++-- lua/nvim-tree/class.lua | 47 ----- lua/nvim-tree/classic.lua | 91 +++++++++ lua/nvim-tree/core.lua | 17 +- lua/nvim-tree/enum.lua | 27 --- lua/nvim-tree/explorer/filters.lua | 141 ++++++++------ lua/nvim-tree/explorer/init.lua | 101 +++++----- lua/nvim-tree/explorer/live-filter.lua | 51 ++--- .../explorer/{sorters.lua => sorter.lua} | 179 +++++++++--------- lua/nvim-tree/explorer/watch.lua | 8 +- lua/nvim-tree/git/init.lua | 37 ++-- lua/nvim-tree/git/runner.lua | 83 ++++---- lua/nvim-tree/git/utils.lua | 4 +- lua/nvim-tree/marks/init.lua | 40 ++-- lua/nvim-tree/node/directory-link.lua | 54 +++--- lua/nvim-tree/node/directory.lua | 66 +++---- lua/nvim-tree/node/factory.lua | 34 ++-- lua/nvim-tree/node/file-link.lua | 40 ++-- lua/nvim-tree/node/file.lua | 42 ++-- lua/nvim-tree/node/init.lua | 49 +++-- lua/nvim-tree/node/link.lua | 19 ++ lua/nvim-tree/node/root.lua | 19 +- lua/nvim-tree/renderer/builder.lua | 91 +++++---- .../renderer/components/diagnostics.lua | 95 ---------- .../renderer/components/full-name.lua | 12 +- lua/nvim-tree/renderer/components/init.lua | 2 - .../renderer/decorator/bookmarks.lua | 46 ++--- lua/nvim-tree/renderer/decorator/copied.lua | 33 ++-- lua/nvim-tree/renderer/decorator/cut.lua | 32 ++-- .../renderer/decorator/diagnostics.lua | 52 +++-- lua/nvim-tree/renderer/decorator/git.lua | 76 ++++---- lua/nvim-tree/renderer/decorator/hidden.lua | 46 ++--- lua/nvim-tree/renderer/decorator/init.lua | 51 +++-- lua/nvim-tree/renderer/decorator/modified.lua | 52 +++-- lua/nvim-tree/renderer/decorator/opened.lua | 32 ++-- lua/nvim-tree/renderer/init.lua | 45 ++--- lua/nvim-tree/view.lua | 46 ++--- lua/nvim-tree/watcher.lua | 118 +++++++----- 42 files changed, 1028 insertions(+), 1148 deletions(-) delete mode 100644 lua/nvim-tree/actions/tree/modifiers/toggles.lua rename lua/nvim-tree/appearance/{diagnostics.lua => hi-test.lua} (82%) delete mode 100644 lua/nvim-tree/class.lua create mode 100644 lua/nvim-tree/classic.lua rename lua/nvim-tree/explorer/{sorters.lua => sorter.lua} (60%) create mode 100644 lua/nvim-tree/node/link.lua delete mode 100644 lua/nvim-tree/renderer/components/diagnostics.lua diff --git a/lua/nvim-tree/actions/fs/clipboard.lua b/lua/nvim-tree/actions/fs/clipboard.lua index 1e786f025db..3f3ed37c94d 100644 --- a/lua/nvim-tree/actions/fs/clipboard.lua +++ b/lua/nvim-tree/actions/fs/clipboard.lua @@ -7,6 +7,7 @@ local notify = require("nvim-tree.notify") local find_file = require("nvim-tree.actions.finders.find-file").fn +local Class = require("nvim-tree.classic") local DirectoryNode = require("nvim-tree.node.directory") ---@alias ClipboardAction "copy" | "cut" @@ -14,35 +15,31 @@ local DirectoryNode = require("nvim-tree.node.directory") ---@alias ClipboardActionFn fun(source: string, dest: string): boolean, string? ----@class Clipboard to handle all actions.fs clipboard API ----@field config table hydrated user opts.filters +---@class (exact) Clipboard: Class ---@field private explorer Explorer ---@field private data ClipboardData ---@field private clipboard_name string ---@field private reg string -local Clipboard = {} - ----@param opts table user options ----@param explorer Explorer ----@return Clipboard -function Clipboard:new(opts, explorer) - ---@type Clipboard - local o = { - explorer = explorer, - data = { - copy = {}, - cut = {}, - }, - clipboard_name = opts.actions.use_system_clipboard and "system" or "neovim", - reg = opts.actions.use_system_clipboard and "+" or "1", - config = { - filesystem_watchers = opts.filesystem_watchers, - }, +local Clipboard = Class:extend() + +---@class Clipboard +---@overload fun(args: ClipboardArgs): Clipboard + +---@class (exact) ClipboardArgs +---@field explorer Explorer + +---@protected +---@param args ClipboardArgs +function Clipboard:new(args) + self.explorer = args.explorer + + self.data = { + copy = {}, + cut = {}, } - setmetatable(o, self) - self.__index = self - return o + self.clipboard_name = self.explorer.opts.actions.use_system_clipboard and "system" or "neovim" + self.reg = self.explorer.opts.actions.use_system_clipboard and "+" or "1" end ---@param source string @@ -252,7 +249,7 @@ function Clipboard:do_paste(node, action, action_fn) end self.data[action] = {} - if not self.config.filesystem_watchers.enable then + if not self.explorer.opts.filesystem_watchers.enable then self.explorer:reload_explorer() end end diff --git a/lua/nvim-tree/actions/tree/modifiers/init.lua b/lua/nvim-tree/actions/tree/modifiers/init.lua index 717b8d7e8f0..f3ce27fc616 100644 --- a/lua/nvim-tree/actions/tree/modifiers/init.lua +++ b/lua/nvim-tree/actions/tree/modifiers/init.lua @@ -2,7 +2,6 @@ local M = {} M.collapse_all = require("nvim-tree.actions.tree.modifiers.collapse-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) M.expand_all.setup(opts) diff --git a/lua/nvim-tree/actions/tree/modifiers/toggles.lua b/lua/nvim-tree/actions/tree/modifiers/toggles.lua deleted file mode 100644 index 8a468a959f2..00000000000 --- a/lua/nvim-tree/actions/tree/modifiers/toggles.lua +++ /dev/null @@ -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 diff --git a/lua/nvim-tree/api.lua b/lua/nvim-tree/api.lua index 85762656a1d..00118c3acb0 100644 --- a/lua/nvim-tree/api.lua +++ b/lua/nvim-tree/api.lua @@ -2,7 +2,7 @@ local core = require("nvim-tree.core") local view = require("nvim-tree.view") local utils = require("nvim-tree.utils") 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 help = require("nvim-tree.help") local keymap = require("nvim-tree.keymap") @@ -89,6 +89,22 @@ local function wrap_node_or_nil(fn) 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. ---Print error when setup not called. ---@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.collapse_all = wrap(actions.tree.modifiers.collapse_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_gitignore_filter = wrap(actions.tree.modifiers.toggles.git_ignored) -Api.tree.toggle_git_clean_filter = wrap(actions.tree.modifiers.toggles.git_clean) -Api.tree.toggle_no_buffer_filter = wrap(actions.tree.modifiers.toggles.no_buffer) -Api.tree.toggle_custom_filter = wrap(actions.tree.modifiers.toggles.custom) -Api.tree.toggle_hidden_filter = wrap(actions.tree.modifiers.toggles.dotfiles) -Api.tree.toggle_no_bookmark_filter = wrap(actions.tree.modifiers.toggles.no_bookmark) +Api.tree.toggle_enable_filters = wrap_explorer_member("filters", "toggle") +Api.tree.toggle_gitignore_filter = wrap_explorer_member_args("filters", "toggle", "git_ignored") +Api.tree.toggle_git_clean_filter = wrap_explorer_member_args("filters", "toggle", "git_clean") +Api.tree.toggle_no_buffer_filter = wrap_explorer_member_args("filters", "toggle", "no_buffer") +Api.tree.toggle_custom_filter = wrap_explorer_member_args("filters", "toggle", "custom") +Api.tree.toggle_hidden_filter = wrap_explorer_member_args("filters", "toggle", "dotfiles") +Api.tree.toggle_no_bookmark_filter = wrap_explorer_member_args("filters", "toggle", "no_bookmark") Api.tree.toggle_help = wrap(help.toggle) 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.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() return require("nvim-tree.commands").get() diff --git a/lua/nvim-tree/appearance/diagnostics.lua b/lua/nvim-tree/appearance/hi-test.lua similarity index 82% rename from lua/nvim-tree/appearance/diagnostics.lua rename to lua/nvim-tree/appearance/hi-test.lua index 5c292104655..25edb9ce4b6 100644 --- a/lua/nvim-tree/appearance/diagnostics.lua +++ b/lua/nvim-tree/appearance/hi-test.lua @@ -1,45 +1,46 @@ local appearance = require("nvim-tree.appearance") +local Class = require("nvim-tree.classic") + -- others with name and links less than this arbitrary value are short local SHORT_LEN = 50 -local M = {} - ----@class HighlightDisplay for :NvimTreeHiTest +---@class (exact) HighlightDisplay: Class for :NvimTreeHiTest ---@field group string nvim-tree highlight group name ---@field links string link chain to a concretely defined group ---@field def string :hi concrete definition after following any links -local HighlightDisplay = {} +local HighlightDisplay = Class:extend() ----@param group string nvim-tree highlight group name ----@return HighlightDisplay -function HighlightDisplay:new(group) - local o = {} - setmetatable(o, self) - self.__index = self +---@class HighlightDisplay +---@overload fun(args: HighlightDisplayArgs): HighlightDisplay + +---@class (exact) HighlightDisplayArgs +---@field group string nvim-tree highlight group name - o.group = group - local concrete = o.group +---@protected +---@param args HighlightDisplayArgs +function HighlightDisplay:new(args) + self.group = args.group + + local concrete = self.group -- maybe follow 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 table.insert(links, link) concrete = link link = vim.api.nvim_get_hl(0, { name = link }).link end - o.links = table.concat(links, " ") + self.links = table.concat(links, " ") -- concrete definition local ok, res = pcall(vim.api.nvim_cmd, { cmd = "highlight", args = { concrete } }, { output = true }) if ok and type(res) == "string" then - o.def = res:gsub(".*xxx *", "") + self.def = res:gsub(".*xxx *", "") else - o.def = "" + self.def = "" end - - return o end ---Render one group. @@ -87,7 +88,7 @@ end ---Run a test similar to :so $VIMRUNTIME/syntax/hitest.vim ---Display all nvim-tree and neovim highlight groups, their link chain and actual definition -function M.hi_test() +return function() -- create a buffer local bufnr = vim.api.nvim_create_buf(false, true) @@ -96,7 +97,7 @@ function M.hi_test() -- nvim-tree groups, ordered local displays = {} 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) end l = render_displays("nvim-tree", displays, bufnr, l) @@ -110,7 +111,7 @@ function M.hi_test() if ok then for group in string.gmatch(out, "(%w*)%s+xxx") do 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 table.insert(displays_long, display) else @@ -137,5 +138,3 @@ function M.hi_test() vim.cmd.buffer(bufnr) end - -return M diff --git a/lua/nvim-tree/class.lua b/lua/nvim-tree/class.lua deleted file mode 100644 index 33b0b5bd33d..00000000000 --- a/lua/nvim-tree/class.lua +++ /dev/null @@ -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 diff --git a/lua/nvim-tree/classic.lua b/lua/nvim-tree/classic.lua new file mode 100644 index 00000000000..6e856bc90c0 --- /dev/null +++ b/lua/nvim-tree/classic.lua @@ -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 +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 diff --git a/lua/nvim-tree/core.lua b/lua/nvim-tree/core.lua index 186b1ed74e1..60d4a0a6f6a 100644 --- a/lua/nvim-tree/core.lua +++ b/lua/nvim-tree/core.lua @@ -1,4 +1,5 @@ local events = require("nvim-tree.events") +local notify = require("nvim-tree.notify") local view = require("nvim-tree.view") local log = require("nvim-tree.log") @@ -15,7 +16,21 @@ function M.init(foldername) if TreeExplorer then TreeExplorer:destroy() 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 events._dispatch_ready() first_init_done = true diff --git a/lua/nvim-tree/enum.lua b/lua/nvim-tree/enum.lua index a680c2b3fdc..e99082282ac 100644 --- a/lua/nvim-tree/enum.lua +++ b/lua/nvim-tree/enum.lua @@ -1,32 +1,5 @@ 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 ---@enum FILTER_REASON M.FILTER_REASON = { diff --git a/lua/nvim-tree/explorer/filters.lua b/lua/nvim-tree/explorer/filters.lua index 8de9f15076b..62230687e40 100644 --- a/lua/nvim-tree/explorer/filters.lua +++ b/lua/nvim-tree/explorer/filters.lua @@ -1,52 +1,59 @@ local utils = require("nvim-tree.utils") local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON ----@class Filters to handle all opts.filters and related API ----@field config table hydrated user opts.filters +local Class = require("nvim-tree.classic") + +---@alias FilterType "custom" | "dotfiles" | "git_ignored" | "git_clean" | "no_buffer" | "no_bookmark" + +---@class (exact) Filters: Class +---@field enabled boolean +---@field state table ---@field private explorer Explorer ---@field private exclude_list string[] filters.exclude ----@field private ignore_list string[] filters.custom string table +---@field private ignore_list table filters.custom string table ---@field private custom_function (fun(absolute_path: string): boolean)|nil filters.custom function -local Filters = {} - ----@param opts table user options ----@param explorer Explorer ----@return Filters -function Filters:new(opts, explorer) - local o = { - explorer = explorer, - ignore_list = {}, - exclude_list = opts.filters.exclude, - custom_function = nil, - config = { - enable = opts.filters.enable, - filter_custom = true, - filter_dotfiles = opts.filters.dotfiles, - filter_git_ignored = opts.filters.git_ignored, - filter_git_clean = opts.filters.git_clean, - filter_no_buffer = opts.filters.no_buffer, - filter_no_bookmark = opts.filters.no_bookmark, - }, +local Filters = Class:extend() + +---@class Filters +---@overload fun(args: FiltersArgs): Filters + +---@class (exact) FiltersArgs +---@field explorer Explorer + +---@protected +---@param args FiltersArgs +function Filters:new(args) + self.explorer = args.explorer + self.ignore_list = {} + self.exclude_list = self.explorer.opts.filters.exclude + self.custom_function = nil + + self.enabled = self.explorer.opts.filters.enable + self.state = { + 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 - o.custom_function = custom_filter + self.custom_function = custom_filter else if custom_filter and #custom_filter > 0 then for _, filter_name in pairs(custom_filter) do - o.ignore_list[filter_name] = true + self.ignore_list[filter_name] = true end end end - setmetatable(o, self) - self.__index = self - return o end +---@private ---@param path string ---@return boolean -local function is_excluded(self, path) +function Filters:is_excluded(path) for _, node in ipairs(self.exclude_list) do if path:match(node) then return true @@ -56,10 +63,11 @@ local function is_excluded(self, path) end ---Check if the given path is git clean/ignored +---@private ---@param path string Absolute path ---@param project GitProject from prepare ---@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 return false end @@ -70,12 +78,12 @@ local function git(self, path, project) xy = xy or project.dirs.indirect[path] and project.dirs.indirect[path][1] -- 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 end -- filter clean - if self.config.filter_git_clean and not xy then + if self.state.git_clean and not xy then return true end @@ -83,11 +91,12 @@ local function git(self, path, project) end ---Check if the given path has no listed buffer +---@private ---@param path string Absolute path ---@param bufinfo table vim.fn.getbufinfo { buflisted = 1 } ---@return boolean -local function buf(self, path, bufinfo) - if not self.config.filter_no_buffer or type(bufinfo) ~= "table" then +function Filters:buf(path, bufinfo) + if not self.state.no_buffer or type(bufinfo) ~= "table" then return false end @@ -101,19 +110,21 @@ local function buf(self, path, bufinfo) return true end +---@private ---@param path string ---@return boolean -local function dotfile(self, path) - return self.config.filter_dotfiles and utils.path_basename(path):sub(1, 1) == "." +function Filters:dotfile(path) + return self.state.dotfiles and utils.path_basename(path):sub(1, 1) == "." end ---Bookmark is present +---@private ---@param path string ---@param path_type string|nil filetype of path ---@param bookmarks table path, filetype table of bookmarked files ---@return boolean -local function bookmark(self, path, path_type, bookmarks) - if not self.config.filter_no_bookmark then +function Filters:bookmark(path, path_type, bookmarks) + if not self.state.no_bookmark then return false end -- if bookmark is empty, we should see a empty filetree @@ -145,10 +156,11 @@ local function bookmark(self, path, path_type, bookmarks) return true end +---@private ---@param path string ---@return boolean -local function custom(self, path) - if not self.config.filter_custom then +function Filters:custom(path) + if not self.state.custom then return false end @@ -190,7 +202,7 @@ function Filters:prepare(project) bookmarks = {}, } - if self.config.filter_no_buffer then + if self.state.no_buffer then status.bufinfo = vim.fn.getbufinfo({ buflisted = 1 }) end @@ -210,20 +222,20 @@ end ---@param status table from prepare ---@return boolean function Filters:should_filter(path, fs_stat, status) - if not self.config.enable then + if not self.enabled then return false end -- exclusions override all filters - if is_excluded(self, path) then + if self:is_excluded(path) then return false end - return git(self, path, status.project) - or buf(self, path, status.bufinfo) - or dotfile(self, path) - or custom(self, path) - or bookmark(self, path, fs_stat and fs_stat.type, status.bookmarks) + return self:git(path, status.project) + or self:buf(path, status.bufinfo) + or self:dotfile(path) + or self:custom(path) + or self:bookmark(path, fs_stat and fs_stat.type, status.bookmarks) end --- 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 ---@return FILTER_REASON 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 end - if is_excluded(self, path) then + if self:is_excluded(path) then return FILTER_REASON.none end - if git(self, path, status.project) then + if self:git(path, status.project) then return FILTER_REASON.git - elseif buf(self, path, status.bufinfo) then + elseif self:buf(path, status.bufinfo) then return FILTER_REASON.buf - elseif dotfile(self, path) then + elseif self:dotfile(path) then return FILTER_REASON.dotfile - elseif custom(self, path) then + elseif self:custom(path) then 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 else return FILTER_REASON.none 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 diff --git a/lua/nvim-tree/explorer/init.lua b/lua/nvim-tree/explorer/init.lua index ecb042d2dd3..0c637b2a3d3 100644 --- a/lua/nvim-tree/explorer/init.lua +++ b/lua/nvim-tree/explorer/init.lua @@ -3,7 +3,6 @@ local buffers = require("nvim-tree.buffers") local core = require("nvim-tree.core") local git = require("nvim-tree.git") local log = require("nvim-tree.log") -local notify = require("nvim-tree.notify") local utils = require("nvim-tree.utils") local view = require("nvim-tree.view") 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 Marks = require("nvim-tree.marks") 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 Renderer = require("nvim-tree.renderer") @@ -36,51 +35,39 @@ local config ---@field sorters Sorter ---@field marks Marks ---@field clipboard Clipboard -local Explorer = RootNode:new() - ----Static factory method ----@param path string? ----@return Explorer? -function Explorer:create(path) - local err - - if path then - path, err = vim.loop.fs_realpath(path) - else - path, err = vim.loop.cwd() - end - if not path then - notify.error(err) - return nil - end - - ---@type Explorer - local explorer_placeholder = nil +local Explorer = RootNode:extend() - local o = RootNode:create(explorer_placeholder, path, "..", nil) +---@class Explorer +---@overload fun(args: ExplorerArgs): Explorer - o = self:new(o) +---@class (exact) ExplorerArgs +---@field path string - o.explorer = o - - o.uid_explorer = vim.loop.hrtime() - o.augroup_id = vim.api.nvim_create_augroup("NvimTree_Explorer_" .. o.uid_explorer, {}) +---@protected +---@param args ExplorerArgs +function Explorer:new(args) + Explorer.super.new(self, { + explorer = self, + absolute_path = args.path, + name = "..", + }) - o.open = true - o.opts = config + self.uid_explorer = vim.loop.hrtime() + self.augroup_id = vim.api.nvim_create_augroup("NvimTree_Explorer_" .. self.uid_explorer, {}) - 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) + self.open = true + self.opts = config - o:create_autocmds() + 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:_load(o) + self:create_autocmds() - return o + self:_load(self) end function Explorer:destroy() @@ -114,7 +101,7 @@ function Explorer:create_autocmds() vim.api.nvim_create_autocmd("BufReadPost", { group = self.augroup_id, 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() self:reload_explorer() end) @@ -126,7 +113,7 @@ function Explorer:create_autocmds() vim.api.nvim_create_autocmd("BufUnload", { group = self.augroup_id, 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() self:reload_explorer() end) @@ -213,10 +200,10 @@ function Explorer:reload(node, project) -- To reset we must 'zero' everything that we use node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, { - git = 0, - buf = 0, - dotfile = 0, - custom = 0, + git = 0, + buf = 0, + dotfile = 0, + custom = 0, bookmark = 0, }) @@ -246,7 +233,13 @@ function Explorer:reload(node, project) end 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 table.insert(node.nodes, 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) node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, { - git = 0, - buf = 0, - dotfile = 0, - custom = 0, + git = 0, + buf = 0, + dotfile = 0, + custom = 0, bookmark = 0, }) @@ -384,7 +377,13 @@ function Explorer:populate_children(handle, cwd, node, project, parent) local stat = vim.loop.fs_lstat(abs) 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 - 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 table.insert(node.nodes, child) nodes_by_path[child.absolute_path] = true diff --git a/lua/nvim-tree/explorer/live-filter.lua b/lua/nvim-tree/explorer/live-filter.lua index 9861195c4ba..62a7dd9ef64 100644 --- a/lua/nvim-tree/explorer/live-filter.lua +++ b/lua/nvim-tree/explorer/live-filter.lua @@ -1,29 +1,30 @@ local view = require("nvim-tree.view") local utils = require("nvim-tree.utils") +local Class = require("nvim-tree.classic") local Iterator = require("nvim-tree.iterators.node-iterator") local DirectoryNode = require("nvim-tree.node.directory") ----@class LiveFilter +---@class (exact) LiveFilter: Class ---@field explorer Explorer ---@field prefix string ---@field always_show_folders boolean ---@field filter string -local LiveFilter = {} - ----@param opts table ----@param explorer Explorer ----@return LiveFilter -function LiveFilter:new(opts, explorer) - local o = { - explorer = explorer, - prefix = opts.live_filter.prefix, - always_show_folders = opts.live_filter.always_show_folders, - filter = nil, - } - setmetatable(o, self) - self.__index = self - return o +local LiveFilter = Class:extend() + +---@class LiveFilter +---@overload fun(args: LiveFilterArgs): LiveFilter + +---@class (exact) LiveFilterArgs +---@field explorer Explorer + +---@protected +---@param args LiveFilterArgs +function LiveFilter:new(args) + self.explorer = args.explorer + self.prefix = self.explorer.opts.live_filter.prefix + self.always_show_folders = self.explorer.opts.live_filter.always_show_folders + self.filter = nil end ---@param node_ Node? @@ -81,7 +82,7 @@ end ---@param node Node ---@return boolean local function matches(self, node) - if not self.explorer.filters.config.enable then + if not self.explorer.filters.enabled then return true end @@ -168,21 +169,21 @@ local function create_overlay(self) if view.View.float.enable then -- don't close nvim-tree float when focus is changed to filter window vim.api.nvim_clear_autocmds({ - event = "WinLeave", + event = "WinLeave", pattern = "NvimTree_*", - group = vim.api.nvim_create_augroup("NvimTree", { clear = false }), + group = vim.api.nvim_create_augroup("NvimTree", { clear = false }), }) end configure_buffer_overlay(self) overlay_winnr = vim.api.nvim_open_win(overlay_bufnr, true, { - col = 1, - row = 0, + col = 1, + row = 0, relative = "cursor", - width = calculate_overlay_win_width(self), - height = 1, - border = "none", - style = "minimal", + width = calculate_overlay_win_width(self), + height = 1, + border = "none", + style = "minimal", }) if vim.fn.has("nvim-0.10") == 1 then diff --git a/lua/nvim-tree/explorer/sorters.lua b/lua/nvim-tree/explorer/sorter.lua similarity index 60% rename from lua/nvim-tree/explorer/sorters.lua rename to lua/nvim-tree/explorer/sorter.lua index 15ed921da05..799cfa481b1 100644 --- a/lua/nvim-tree/explorer/sorters.lua +++ b/lua/nvim-tree/explorer/sorter.lua @@ -1,43 +1,25 @@ -local Class = require("nvim-tree.class") +local Class = require("nvim-tree.classic") 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 ----@field sorter string|fun(nodes: Node[]) ----@field folders_first boolean ----@field files_first boolean +---@alias SorterUser fun(nodes: Node[]): SorterType? ---@class (exact) Sorter: Class ----@field cfg SorterCfg ----@field user fun(nodes: Node[])? ----@field pre string? -local Sorter = Class:new() - ----@param opts table user options ----@return 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 - o.user = o.cfg.sorter --[[@as fun(nodes: Node[])]] - elseif type(o.cfg.sorter) == "string" then - o.pre = o.cfg.sorter --[[@as string]] - end - return o -end +---@field private explorer Explorer +local Sorter = Class:extend() ---- Predefined comparator, defaulting to name ----@param sorter string as per options ----@return fun(a: Node, b: Node): boolean -function Sorter:get_comparator(sorter) - return function(a, b) - return (C[sorter] or C.name)(a, b, self.cfg) - end +---@class Sorter +---@overload fun(args: SorterArgs): Sorter + +---@class (exact) SorterArgs +---@field explorer Explorer + +---@protected +---@param args SorterArgs +function Sorter:new(args) + self.explorer = args.explorer end ---Create a shallow copy of a portion of a list. @@ -54,31 +36,32 @@ local function tbl_slice(t, first, last) return slice end ----Evaluate `sort.folders_first` and `sort.files_first` ----@param a Node ----@param b Node ----@param cfg SorterCfg ----@return boolean|nil -local function folders_or_files_first(a, b, cfg) - if not (cfg.folders_first or cfg.files_first) then - return +---Evaluate folders_first and sort.files_first returning nil when no order is necessary +---@private +---@type SorterComparator +function Sorter:folders_or_files_first(a, b) + if not (self.explorer.opts.sort.folders_first or self.explorer.opts.sort.files_first) then + return nil end if not a:is(DirectoryNode) and b:is(DirectoryNode) then -- file <> folder - return cfg.files_first + return self.explorer.opts.sort.files_first elseif a:is(DirectoryNode) and not b:is(DirectoryNode) then -- folder <> file - return not cfg.files_first + return not self.explorer.opts.sort.files_first end + + return nil end ----@param t table +---@private +---@param t Node[] ---@param first number ---@param mid number ---@param last number ----@param comparator fun(a: Node, b: Node): boolean -local function merge(t, first, mid, last, comparator) +---@param comparator SorterComparator +function Sorter:merge(t, first, mid, last, comparator) local n1 = mid - first + 1 local n2 = last - mid local ls = tbl_slice(t, first, mid) @@ -88,7 +71,7 @@ local function merge(t, first, mid, last, comparator) local k = first 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] i = i + 1 else @@ -111,45 +94,49 @@ local function merge(t, first, mid, last, comparator) end end ----@param t table +---@private +---@param t Node[] ---@param first number ---@param last number ----@param comparator fun(a: Node, b: Node): boolean -local function split_merge(t, first, last, comparator) +---@param comparator SorterComparator +function Sorter:split_merge(t, first, last, comparator) if (last - first) < 1 then return end local mid = math.floor((first + last) / 2) - split_merge(t, first, mid, comparator) - split_merge(t, mid + 1, last, comparator) - merge(t, first, mid, last, comparator) + self:split_merge(t, first, mid, comparator) + self:split_merge(t, mid + 1, last, comparator) + self:merge(t, first, mid, last, comparator) end ---Perform a merge sort using sorter option. ---@param t Node[] 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 origin_index = {} for _, n in ipairs(t) do table.insert(t_user, { absolute_path = n.absolute_path, - executable = n.executable, - extension = n.extension, - filetype = vim.filetype.match({ filename = n.name }), - link_to = n.link_to, - name = n.name, - type = n.type, + executable = n.executable, + extension = n.extension, + filetype = vim.filetype.match({ filename = n.name }), + link_to = n.link_to, + name = n.name, + type = n.type, }) table.insert(origin_index, n) end - local predefined = self.user(t_user) - if predefined then - split_merge(t, 1, #t, self:get_comparator(predefined)) + -- user may return a SorterType + local ret = self.explorer.opts.sort.sorter(t_user) + if self[ret] then + self:split_merge(t, 1, #t, self[ret]) return end @@ -162,7 +149,7 @@ function Sorter:sort(t) end -- 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 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) end - 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)) + self:split_merge(t, 1, #t, mini_comparator) -- sort by user order end end +---@private ---@param a Node ---@param b Node ----@param ignorecase boolean|nil ----@param cfg SorterCfg +---@param ignore_case 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 return true 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 return early_return end - if ignorecase then + if ignore_case then return a.name:lower() <= b.name:lower() else return a.name <= b.name end end -function C.case_sensitive(a, b, cfg) - return node_comparator_name_ignorecase_or_not(a, b, false, cfg) +---@private +---@type SorterComparator +function Sorter:case_sensitive(a, b) + return self:name_case(a, b, false) end -function C.name(a, b, cfg) - return node_comparator_name_ignorecase_or_not(a, b, true, cfg) +---@private +---@type SorterComparator +function Sorter:name(a, b) + return self:name_case(a, b, true) end -function C.modification_time(a, b, cfg) +---@private +---@type SorterComparator +function Sorter:modification_time(a, b) if not (a and b) then return true 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 return early_return end @@ -232,17 +223,19 @@ function C.modification_time(a, b, cfg) return last_modified_b <= last_modified_a end -function C.suffix(a, b, cfg) +---@private +---@type SorterComparator +function Sorter:suffix(a, b) if not (a and b) then return true end -- 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 return early_return elseif a.nodes and b.nodes then - return C.name(a, b, cfg) + return self:name(a, b) end -- 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 return false 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 -- unsuffixed go third @@ -263,7 +256,7 @@ function C.suffix(a, b, cfg) elseif a_suffix_ndx and not b_suffix_ndx then return false elseif not (a_suffix_ndx and b_suffix_ndx) then - return C.name(a, b, cfg) + return self:name(a, b) end -- finally, compare by suffixes @@ -275,18 +268,20 @@ function C.suffix(a, b, cfg) elseif not a_suffix and b_suffix then return false elseif a_suffix:lower() == b_suffix:lower() then - return C.name(a, b, cfg) + return self:name(a, b) end return a_suffix:lower() < b_suffix:lower() end -function C.extension(a, b, cfg) +---@private +---@type SorterComparator +function Sorter:extension(a, b) if not (a and b) then return true 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 return early_return end @@ -300,18 +295,20 @@ function C.extension(a, b, cfg) local a_ext = (a.extension or ""):lower() local b_ext = (b.extension or ""):lower() if a_ext == b_ext then - return C.name(a, b, cfg) + return self:name(a, b) end return a_ext < b_ext 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 b_ft = vim.filetype.match({ filename = b.name }) -- 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 return early_return end @@ -325,7 +322,7 @@ function C.filetype(a, b, cfg) -- same filetype or both nil, sort by name if a_ft == b_ft then - return C.name(a, b, cfg) + return self:name(a, b) end return a_ft < b_ft diff --git a/lua/nvim-tree/explorer/watch.lua b/lua/nvim-tree/explorer/watch.lua index bd65ae53607..06eb429ba9d 100644 --- a/lua/nvim-tree/explorer/watch.lua +++ b/lua/nvim-tree/explorer/watch.lua @@ -83,8 +83,12 @@ function M.create_watcher(node) end M.uid = M.uid + 1 - return Watcher:create(path, nil, callback, { - context = "explorer:watch:" .. path .. ":" .. M.uid, + return Watcher:create({ + path = path, + callback = callback, + data = { + context = "explorer:watch:" .. path .. ":" .. M.uid + } }) end diff --git a/lua/nvim-tree/git/init.lua b/lua/nvim-tree/git/init.lua index 10354f85f30..a0294b776cd 100644 --- a/lua/nvim-tree/git/init.lua +++ b/lua/nvim-tree/git/init.lua @@ -128,25 +128,25 @@ function M.reload_project(toplevel, path, callback) return end - ---@type GitRunnerOpts - local runner_opts = { - toplevel = toplevel, - path = path, + ---@type GitRunnerArgs + local args = { + toplevel = toplevel, + path = path, list_untracked = git_utils.should_show_untracked(toplevel), - list_ignored = true, - timeout = M.config.git.timeout, + list_ignored = true, + timeout = M.config.git.timeout, } if callback then ---@param path_xy GitPathXY - runner_opts.callback = function(path_xy) + args.callback = function(path_xy) reload_git_project(toplevel, path, project, path_xy) callback() end - GitRunner:run(runner_opts) + GitRunner:run(args) else -- 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 @@ -276,10 +276,10 @@ function M.load_project(path) end local path_xys = GitRunner:run({ - toplevel = toplevel, + toplevel = toplevel, list_untracked = git_utils.should_show_untracked(toplevel), - list_ignored = true, - timeout = M.config.git.timeout, + list_ignored = true, + timeout = M.config.git.timeout, }) local watcher = nil @@ -298,15 +298,20 @@ function M.load_project(path) end 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, { - toplevel = toplevel, + watcher = Watcher:create({ + path = git_dir, + files = WATCHED_FILES, + callback = callback, + data = { + toplevel = toplevel, + } }) end if path_xys then M._projects_by_toplevel[toplevel] = { - files = path_xys, - dirs = git_utils.project_files_to_project_dirs(path_xys, toplevel), + files = path_xys, + dirs = git_utils.project_files_to_project_dirs(path_xys, toplevel), watcher = watcher, } return M._projects_by_toplevel[toplevel] diff --git a/lua/nvim-tree/git/runner.lua b/lua/nvim-tree/git/runner.lua index 13b57383cf1..0c540575198 100644 --- a/lua/nvim-tree/git/runner.lua +++ b/lua/nvim-tree/git/runner.lua @@ -2,9 +2,23 @@ local log = require("nvim-tree.log") local utils = require("nvim-tree.utils") 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 path string? absolute path ---@field list_untracked boolean @@ -12,15 +26,23 @@ local Class = require("nvim-tree.class") ---@field timeout integer ---@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 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 ---@param status string ---@param path string|nil @@ -34,7 +56,7 @@ function GitRunner:parse_status_output(status, path) path = path:gsub("/", "\\") end 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 @@ -81,11 +103,11 @@ end ---@param stderr_handle uv.uv_pipe_t ---@return uv.spawn.options function GitRunner:get_spawn_options(stdout_handle, stderr_handle) - local untracked = self.opts.list_untracked and "-u" or nil - local ignored = (self.opts.list_untracked and self.opts.list_ignored) and "--ignored=matching" or "--ignored=no" + local untracked = self.list_untracked and "-u" or nil + local ignored = (self.list_untracked and self.list_ignored) and "--ignored=matching" or "--ignored=no" return { - args = { "--no-optional-locks", "status", "--porcelain=v1", "-z", ignored, untracked, self.opts.path }, - cwd = self.opts.toplevel, + args = { "--no-optional-locks", "status", "--porcelain=v1", "-z", ignored, untracked, self.path }, + cwd = self.toplevel, stdio = { nil, stdout_handle, stderr_handle }, } end @@ -139,7 +161,7 @@ function GitRunner:run_git_job(callback) end 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), " ")) handle, pid = vim.loop.spawn( @@ -151,7 +173,7 @@ function GitRunner:run_git_job(callback) ) timer:start( - self.opts.timeout, + self.timeout, 0, vim.schedule_wrap(function() on_finish(-1) @@ -191,17 +213,17 @@ end ---@private function GitRunner:finalise() 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 if timeouts == MAX_TIMEOUTS then 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() end 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 - 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 @@ -209,17 +231,17 @@ end ---@private ---@return GitPathXY? function GitRunner:execute() - local async = self.opts.callback ~= nil - local profile = log.profile_start("git %s job %s %s", async and "async" or "sync", self.opts.toplevel, self.opts.path) + local async = self.callback ~= nil + 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 self:run_git_job(function() log.profile_end(profile) self:finalise() - self.opts.callback(self.path_xy) + self.callback(self.path_xy) end) else -- sync, maybe call back @@ -230,8 +252,8 @@ function GitRunner:execute() self:finalise() - if self.opts.callback then - self.opts.callback(self.path_xy) + if self.callback then + self.callback(self.path_xy) else return self.path_xy end @@ -240,15 +262,10 @@ end ---Static method to run a git process, which will be killed if it takes more than timeout ---Return nil when callback present ----@param opts GitRunnerOpts +---@param args GitRunnerArgs ---@return GitPathXY? -function GitRunner:run(opts) - ---@type GitRunner - local runner = { - opts = opts, - path_xy = {}, - } - runner = GitRunner:new(runner) +function GitRunner:run(args) + local runner = GitRunner(args) return runner:execute() end diff --git a/lua/nvim-tree/git/utils.lua b/lua/nvim-tree/git/utils.lua index a5a1efc21b1..b805ebfe4cd 100644 --- a/lua/nvim-tree/git/utils.lua +++ b/lua/nvim-tree/git/utils.lua @@ -172,8 +172,8 @@ function M.git_status_dir(parent_ignored, project, path, path_fallback) elseif project then ns = { file = project.files and (project.files[path] or project.files[path_fallback]), - dir = project.dirs and { - direct = project.dirs.direct and project.dirs.direct[path], + dir = project.dirs and { + direct = project.dirs.direct and project.dirs.direct[path], indirect = project.dirs.indirect and project.dirs.indirect[path], }, } diff --git a/lua/nvim-tree/marks/init.lua b/lua/nvim-tree/marks/init.lua index ddeb5cc9200..c940f999983 100644 --- a/lua/nvim-tree/marks/init.lua +++ b/lua/nvim-tree/marks/init.lua @@ -8,37 +8,33 @@ local rename_file = require("nvim-tree.actions.fs.rename-file") local trash = require("nvim-tree.actions.fs.trash") local utils = require("nvim-tree.utils") +local Class = require("nvim-tree.classic") local DirectoryNode = require("nvim-tree.node.directory") ----@class Marks ----@field config table hydrated user opts.filters +---@class (exact) Marks: Class ---@field private explorer Explorer ---@field private marks table by absolute path -local Marks = {} - ----@return Marks ----@param opts table user options ----@param explorer Explorer -function Marks:new(opts, explorer) - local o = { - explorer = explorer, - config = { - ui = opts.ui, - filesystem_watchers = opts.filesystem_watchers, - }, - marks = {}, - } +local Marks = Class:extend() + +---@class Marks +---@overload fun(args: MarksArgs): Marks - setmetatable(o, self) - self.__index = self - return o +---@class (exact) MarksArgs +---@field explorer Explorer + +---@protected +---@param args MarksArgs +function Marks:new(args) + self.explorer = args.explorer + + self.marks = {} end ---Clear all marks and reload if watchers disabled ---@private function Marks:clear_reload() self:clear() - if not self.config.filesystem_watchers.enable then + if not self.explorer.opts.filesystem_watchers.enable then self.explorer:reload_explorer() end end @@ -100,7 +96,7 @@ function Marks:bulk_delete() self:clear_reload() end - if self.config.ui.confirm.remove then + if self.explorer.opts.ui.confirm.remove then local prompt_select = "Remove bookmarked ?" local prompt_input = prompt_select .. " y/N: " 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() end - if self.config.ui.confirm.trash then + if self.explorer.opts.ui.confirm.trash then local prompt_select = "Trash bookmarked ?" local prompt_input = prompt_select .. " y/N: " lib.prompt(prompt_input, prompt_select, { "", "y" }, { "No", "Yes" }, "nvimtree_bulk_trash", function(item_short) diff --git a/lua/nvim-tree/node/directory-link.lua b/lua/nvim-tree/node/directory-link.lua index 8cfa3760a9e..82383088b91 100644 --- a/lua/nvim-tree/node/directory-link.lua +++ b/lua/nvim-tree/node/directory-link.lua @@ -2,35 +2,29 @@ local git_utils = require("nvim-tree.git.utils") local utils = require("nvim-tree.utils") local DirectoryNode = require("nvim-tree.node.directory") +local LinkNode = require("nvim-tree.node.link") ----@class (exact) DirectoryLinkNode: DirectoryNode ----@field link_to string absolute path ----@field private fs_stat_target uv.fs_stat.result -local DirectoryLinkNode = DirectoryNode:new() - ----Static factory method ----@param explorer Explorer ----@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) +---@class (exact) DirectoryLinkNode: DirectoryNode, LinkNode +local DirectoryLinkNode = DirectoryNode:extend() +DirectoryLinkNode:implement(LinkNode) - -- reset absolute path to the link itself - o.absolute_path = absolute_path +---@class DirectoryLinkNode +---@overload fun(args: LinkNodeArgs): DirectoryLinkNode + +---@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) - o.type = "link" - o.link_to = link_to - o.fs_stat_target = fs_stat_target + self.type = "link" - return o + -- reset absolute path to the link itself + self.absolute_path = absolute_path end function DirectoryLinkNode:destroy() @@ -54,10 +48,10 @@ function DirectoryLinkNode:highlighted_icon() if self.open then str = self.explorer.opts.renderer.icons.glyphs.folder.symlink_open - hl = "NvimTreeOpenedFolderIcon" + hl = "NvimTreeOpenedFolderIcon" else str = self.explorer.opts.renderer.icons.glyphs.folder.symlink - hl = "NvimTreeClosedFolderIcon" + hl = "NvimTreeClosedFolderIcon" end return { str = str, hl = { hl } } @@ -70,8 +64,9 @@ function DirectoryLinkNode:highlighted_name() if self.explorer.opts.renderer.symlink_destination then 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 return name @@ -82,7 +77,6 @@ end function DirectoryLinkNode:clone() local clone = DirectoryNode.clone(self) --[[@as DirectoryLinkNode]] - clone.type = self.type clone.link_to = self.link_to clone.fs_stat_target = self.fs_stat_target diff --git a/lua/nvim-tree/node/directory.lua b/lua/nvim-tree/node/directory.lua index 3d87465aa04..899de184044 100644 --- a/lua/nvim-tree/node/directory.lua +++ b/lua/nvim-tree/node/directory.lua @@ -1,6 +1,7 @@ local git_utils = require("nvim-tree.git.utils") local icons = require("nvim-tree.renderer.components.devicons") local notify = require("nvim-tree.notify") + local Node = require("nvim-tree.node") ---@class (exact) DirectoryNode: Node @@ -10,45 +11,28 @@ local Node = require("nvim-tree.node") ---@field open boolean ---@field hidden_stats table? -- Each field of this table is a key for source and value for count ---@field private watcher Watcher? -local DirectoryNode = Node:new() - ----Static factory method ----@param explorer Explorer ----@param parent DirectoryNode? ----@param absolute_path string ----@param name string ----@param fs_stat uv.fs_stat.result|nil ----@return DirectoryNode -function DirectoryNode:create(explorer, parent, absolute_path, name, fs_stat) - local handle = vim.loop.fs_scandir(absolute_path) +local DirectoryNode = Node:extend() + +---@class DirectoryNode +---@overload fun(args: NodeArgs): DirectoryNode + +---@protected +---@param args NodeArgs +function DirectoryNode:new(args) + DirectoryNode.super.new(self, args) + + local handle = vim.loop.fs_scandir(args.absolute_path) local has_children = handle and vim.loop.fs_scandir_next(handle) ~= nil or false - ---@type DirectoryNode - 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, - group_next = nil, - nodes = {}, - open = false, - hidden_stats = nil, - } - o = self:new(o) - - o.watcher = require("nvim-tree.explorer.watch").create_watcher(o) - - return o + self.type = "directory" + + self.has_children = has_children + self.group_next = nil + self.nodes = {} + self.open = false + self.hidden_stats = nil + + self.watcher = require("nvim-tree.explorer.watch").create_watcher(self) end function DirectoryNode:destroy() @@ -289,12 +273,12 @@ end ---Create a sanitized partial copy of a node, populating children recursively. ---@return DirectoryNode cloned function DirectoryNode:clone() - local clone = Node.clone(self) --[[@as DirectoryNode]] + local clone = Node.clone(self) --[[@as DirectoryNode]] clone.has_children = self.has_children - clone.group_next = nil - clone.nodes = {} - clone.open = self.open + clone.group_next = nil + clone.nodes = {} + clone.open = self.open clone.hidden_stats = nil for _, child in ipairs(self.nodes) do diff --git a/lua/nvim-tree/node/factory.lua b/lua/nvim-tree/node/factory.lua index ee0504fc807..adaaa5a740c 100644 --- a/lua/nvim-tree/node/factory.lua +++ b/lua/nvim-tree/node/factory.lua @@ -7,38 +7,38 @@ local Watcher = require("nvim-tree.watcher") local M = {} ---Factory function to create the appropriate Node ----@param explorer Explorer ----@param parent DirectoryNode ----@param absolute_path string ----@param stat uv.fs_stat.result? -- on nil stat return nil Node ----@param name string +---nil on invalid stat or invalid link target stat +---@param args NodeArgs ---@return Node? -function M.create_node(explorer, parent, absolute_path, stat, name) - if not stat then +function M.create(args) + if not args.fs_stat then return nil end - if stat.type == "directory" then + if args.fs_stat.type == "directory" then -- directory must be readable and enumerable - if vim.loop.fs_access(absolute_path, "R") and Watcher.is_fs_event_capable(absolute_path) then - return DirectoryNode:create(explorer, parent, absolute_path, name, stat) + if vim.loop.fs_access(args.absolute_path, "R") and Watcher.is_fs_event_capable(args.absolute_path) then + return DirectoryNode(args) end - elseif stat.type == "file" then - -- any file - return FileNode:create(explorer, parent, absolute_path, name, stat) - elseif stat.type == "link" then + elseif args.fs_stat.type == "file" then + return FileNode(args) + elseif args.fs_stat.type == "link" then -- 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) if not link_to or not link_to_stat then return end + ---@cast args LinkNodeArgs + args.link_to = link_to + args.fs_stat_target = link_to_stat + -- choose directory or file if link_to_stat.type == "directory" then - return DirectoryLinkNode:create(explorer, parent, absolute_path, link_to, name, stat, link_to_stat) + return DirectoryLinkNode(args) else - return FileLinkNode:create(explorer, parent, absolute_path, link_to, name, stat, link_to_stat) + return FileLinkNode(args) end end diff --git a/lua/nvim-tree/node/file-link.lua b/lua/nvim-tree/node/file-link.lua index 0c168a50a93..3c5571a29a2 100644 --- a/lua/nvim-tree/node/file-link.lua +++ b/lua/nvim-tree/node/file-link.lua @@ -2,31 +2,22 @@ local git_utils = require("nvim-tree.git.utils") local utils = require("nvim-tree.utils") local FileNode = require("nvim-tree.node.file") +local LinkNode = require("nvim-tree.node.link") ----@class (exact) FileLinkNode: FileNode ----@field link_to string absolute path ----@field private fs_stat_target uv.fs_stat.result -local FileLinkNode = FileNode:new() - ----Static factory method ----@param explorer Explorer ----@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) - - o.type = "link" - o.link_to = link_to - o.fs_stat_target = fs_stat_target - - return o +---@class (exact) FileLinkNode: FileNode, LinkNode +local FileLinkNode = FileNode:extend() +FileLinkNode:implement(LinkNode) + +---@class FileLinkNode +---@overload fun(args: LinkNodeArgs): FileLinkNode + +---@protected +---@param args LinkNodeArgs +function FileLinkNode:new(args) + LinkNode.new(self, args) + FileLinkNode.super.new(self, args) + + self.type = "link" end function FileLinkNode:destroy() @@ -71,7 +62,6 @@ end function FileLinkNode:clone() local clone = FileNode.clone(self) --[[@as FileLinkNode]] - clone.type = self.type clone.link_to = self.link_to clone.fs_stat_target = self.fs_stat_target diff --git a/lua/nvim-tree/node/file.lua b/lua/nvim-tree/node/file.lua index 18555fef26c..a74a213a8eb 100644 --- a/lua/nvim-tree/node/file.lua +++ b/lua/nvim-tree/node/file.lua @@ -15,35 +15,19 @@ local PICTURE_MAP = { ---@class (exact) FileNode: Node ---@field extension string -local FileNode = Node:new() - ----Static factory method ----@param explorer Explorer ----@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 "", - } - o = self:new(o) - - return o +local FileNode = Node:extend() + +---@class FileNode +---@overload fun(args: NodeArgs): FileNode + +---@protected +---@param args NodeArgs +function FileNode:new(args) + FileNode.super.new(self, args) + + self.type = "file" + self.extension = string.match(args.name, ".?[^.]+%.(.*)") or "" + self.executable = utils.is_executable(args.absolute_path) end function FileNode:destroy() diff --git a/lua/nvim-tree/node/init.lua b/lua/nvim-tree/node/init.lua index 48bf783c421..f6ecf24d036 100644 --- a/lua/nvim-tree/node/init.lua +++ b/lua/nvim-tree/node/init.lua @@ -1,9 +1,8 @@ -local Class = require("nvim-tree.class") +local Class = require("nvim-tree.classic") ---Abstract Node class. ----Uses the abstract factory pattern to instantiate child instances. ---@class (exact) Node: Class ----@field type NODE_TYPE +---@field type "file" | "directory" | "link" uv.fs_stat.result.type ---@field explorer Explorer ---@field absolute_path string ---@field executable boolean @@ -14,7 +13,29 @@ local Class = require("nvim-tree.class") ---@field parent DirectoryNode? ---@field diag_status DiagStatus? ---@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() end @@ -104,17 +125,17 @@ function Node:clone() ---@type Node local clone = { - type = self.type, - explorer = explorer_placeholder, + type = self.type, + explorer = explorer_placeholder, absolute_path = self.absolute_path, - executable = self.executable, - fs_stat = self.fs_stat, - git_status = self.git_status, - hidden = self.hidden, - name = self.name, - parent = nil, - diag_status = nil, - is_dot = self.is_dot, + executable = self.executable, + fs_stat = self.fs_stat, + git_status = self.git_status, + hidden = self.hidden, + name = self.name, + parent = nil, + diag_status = nil, + is_dot = self.is_dot, } return clone diff --git a/lua/nvim-tree/node/link.lua b/lua/nvim-tree/node/link.lua new file mode 100644 index 00000000000..ff2d8e4df5b --- /dev/null +++ b/lua/nvim-tree/node/link.lua @@ -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 diff --git a/lua/nvim-tree/node/root.lua b/lua/nvim-tree/node/root.lua index 0544a141a12..ec3c44c4a82 100644 --- a/lua/nvim-tree/node/root.lua +++ b/lua/nvim-tree/node/root.lua @@ -1,20 +1,15 @@ local DirectoryNode = require("nvim-tree.node.directory") ---@class (exact) RootNode: DirectoryNode -local RootNode = DirectoryNode:new() +local RootNode = DirectoryNode:extend() ----Static factory method ----@param explorer Explorer ----@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) +---@class RootNode +---@overload fun(args: NodeArgs): RootNode - o = self:new(o) - - return o +---@protected +---@param args NodeArgs +function RootNode:new(args) + RootNode.super.new(self, args) end ---Root is never a dotfile diff --git a/lua/nvim-tree/renderer/builder.lua b/lua/nvim-tree/renderer/builder.lua index 37359269822..303e02a39d3 100644 --- a/lua/nvim-tree/renderer/builder.lua +++ b/lua/nvim-tree/renderer/builder.lua @@ -2,6 +2,7 @@ local notify = require("nvim-tree.notify") local utils = require("nvim-tree.utils") local view = require("nvim-tree.view") +local Class = require("nvim-tree.classic") local DirectoryNode = require("nvim-tree.node.directory") local DecoratorBookmarks = require("nvim-tree.renderer.decorator.bookmarks") @@ -26,57 +27,51 @@ local pad = require("nvim-tree.renderer.components.padding") ---@field col_end number ---@class (exact) Builder ----@field private __index? table ---@field lines string[] includes icons etc. ---@field hl_args AddHighlightArgs[] line highlights ---@field signs string[] line signs ---@field extmarks table[] extra marks for right icon placement ---@field virtual_lines table[] virtual lines for hidden count display ---@field private explorer Explorer ----@field private opts table ---@field private index number ---@field private depth number ---@field private combined_groups table combined group names ---@field private markers boolean[] indent markers ---@field private decorators Decorator[] ---@field private hidden_display fun(node: Node): string|nil -local Builder = {} - ----@param opts table user options ----@param explorer Explorer ----@return Builder -function Builder:new(opts, explorer) - ---@type Builder - local o = { - opts = opts, - explorer = explorer, - index = 0, - depth = 0, - hl_args = {}, - combined_groups = {}, - lines = {}, - markers = {}, - signs = {}, - extmarks = {}, - virtual_lines = {}, - decorators = { - -- priority order - DecoratorCut:create(opts, explorer), - DecoratorCopied:create(opts, explorer), - DecoratorDiagnostics:create(opts, explorer), - DecoratorBookmarks:create(opts, explorer), - DecoratorModified:create(opts, explorer), - DecoratorHidden:create(opts, explorer), - DecoratorOpened:create(opts, explorer), - DecoratorGit:create(opts, explorer), - }, - hidden_display = Builder:setup_hidden_display_function(opts), +local Builder = Class:extend() + +---@class Builder +---@overload fun(args: BuilderArgs): Builder + +---@class (exact) BuilderArgs +---@field explorer Explorer + +---@protected +---@param args BuilderArgs +function Builder:new(args) + self.explorer = args.explorer + self.index = 0 + self.depth = 0 + self.hl_args = {} + self.combined_groups = {} + self.lines = {} + self.markers = {} + self.signs = {} + self.extmarks = {} + self.virtual_lines = {} + self.decorators = { + -- priority order + DecoratorCut({ explorer = args.explorer }), + DecoratorCopied({ explorer = args.explorer }), + DecoratorDiagnostics({ explorer = args.explorer }), + DecoratorBookmarks({ explorer = args.explorer }), + DecoratorModified({ explorer = args.explorer }), + DecoratorHidden({ explorer = args.explorer }), + DecoratorOpened({ explorer = args.explorer }), + DecoratorGit({ explorer = args.explorer }) } - - setmetatable(o, self) - self.__index = self - - return o + self.hidden_display = Builder:setup_hidden_display_function(self.explorer.opts) end ---Insert ranged highlight groups into self.highlights @@ -123,7 +118,7 @@ function Builder:format_line(indent_markers, arrows, icon, name, node) end for _, v in ipairs(t2) do 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 table.insert(t1, v) 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) 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_width = self.opts.renderer.indent_width + local indent_width = self.explorer.opts.renderer.indent_width local indent_padding = string.rep(" ", indent_width) local indent_string = indent_padding .. indent_markers.str @@ -354,16 +349,16 @@ end ---@private function Builder:build_header() 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) self:insert_highlight({ "NvimTreeRootFolder" }, 0, string.len(root_name)) self.index = 1 end 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) - 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({ "NvimTreeLiveFilterValue" }, prefix_length, string.len(filter_line)) 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 hidden_stats = vim.tbl_deep_extend("force", { live_filter = 0, - git = 0, - buf = 0, - dotfile = 0, - custom = 0, - bookmark = 0, + git = 0, + buf = 0, + dotfile = 0, + custom = 0, + bookmark = 0, }, hidden_stats or {}) local ok, result = pcall(hidden_display, hidden_stats) diff --git a/lua/nvim-tree/renderer/components/diagnostics.lua b/lua/nvim-tree/renderer/components/diagnostics.lua deleted file mode 100644 index e91873e07ca..00000000000 --- a/lua/nvim-tree/renderer/components/diagnostics.lua +++ /dev/null @@ -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 diff --git a/lua/nvim-tree/renderer/components/full-name.lua b/lua/nvim-tree/renderer/components/full-name.lua index 39729ffa1e8..54742812630 100644 --- a/lua/nvim-tree/renderer/components/full-name.lua +++ b/lua/nvim-tree/renderer/components/full-name.lua @@ -57,13 +57,13 @@ local function show() end M.popup_win = vim.api.nvim_open_win(vim.api.nvim_create_buf(false, false), false, { - relative = "win", - row = 0, - bufpos = { vim.api.nvim_win_get_cursor(0)[1] - 1, 0 }, - width = math.min(text_width, vim.o.columns - 2), - height = 1, + relative = "win", + row = 0, + bufpos = { vim.api.nvim_win_get_cursor(0)[1] - 1, 0 }, + width = math.min(text_width, vim.o.columns - 2), + height = 1, noautocmd = true, - style = "minimal", + style = "minimal", }) local ns_id = vim.api.nvim_get_namespaces()["NvimTreeHighlights"] diff --git a/lua/nvim-tree/renderer/components/init.lua b/lua/nvim-tree/renderer/components/init.lua index 776350b4b77..748bf889497 100644 --- a/lua/nvim-tree/renderer/components/init.lua +++ b/lua/nvim-tree/renderer/components/init.lua @@ -1,12 +1,10 @@ local M = {} -M.diagnostics = require("nvim-tree.renderer.components.diagnostics") M.full_name = require("nvim-tree.renderer.components.full-name") M.devicons = require("nvim-tree.renderer.components.devicons") M.padding = require("nvim-tree.renderer.components.padding") function M.setup(opts) - M.diagnostics.setup(opts) M.full_name.setup(opts) M.devicons.setup(opts) M.padding.setup(opts) diff --git a/lua/nvim-tree/renderer/decorator/bookmarks.lua b/lua/nvim-tree/renderer/decorator/bookmarks.lua index 3c188721c0c..c83290aa4ed 100644 --- a/lua/nvim-tree/renderer/decorator/bookmarks.lua +++ b/lua/nvim-tree/renderer/decorator/bookmarks.lua @@ -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") ---@class (exact) DecoratorBookmarks: Decorator ---@field icon HighlightedString? -local DecoratorBookmarks = Decorator:new() - ----Static factory method ----@param opts table ----@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 - o.icon = { - str = opts.renderer.icons.glyphs.bookmark, +local DecoratorBookmarks = Decorator:extend() + +---@class DecoratorBookmarks +---@overload fun(explorer: DecoratorArgs): DecoratorBookmarks + +---@protected +---@param args DecoratorArgs +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" }, } - o:define_sign(o.icon) + self:define_sign(self.icon) end - - return o end ---Bookmark icon: renderer.icons.show.bookmarks and node is marked @@ -45,7 +39,7 @@ end ---@param node Node ---@return string|nil group 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" end end diff --git a/lua/nvim-tree/renderer/decorator/copied.lua b/lua/nvim-tree/renderer/decorator/copied.lua index 3d760a97a55..4d54e1dc290 100644 --- a/lua/nvim-tree/renderer/decorator/copied.lua +++ b/lua/nvim-tree/renderer/decorator/copied.lua @@ -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") ---@class (exact) DecoratorCopied: Decorator ----@field icon HighlightedString? -local DecoratorCopied = Decorator:new() +local DecoratorCopied = Decorator:extend() ----Static factory method ----@param opts table ----@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) +---@class DecoratorCopied +---@overload fun(explorer: DecoratorArgs): DecoratorCopied - 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 ---Copied highlight: renderer.highlight_clipboard and node is copied ---@param node Node ---@return string|nil group 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" end end diff --git a/lua/nvim-tree/renderer/decorator/cut.lua b/lua/nvim-tree/renderer/decorator/cut.lua index 45428969e7e..92eb1b8355c 100644 --- a/lua/nvim-tree/renderer/decorator/cut.lua +++ b/lua/nvim-tree/renderer/decorator/cut.lua @@ -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") ---@class (exact) DecoratorCut: Decorator -local DecoratorCut = Decorator:new() +local DecoratorCut = Decorator:extend() ----Static factory method ----@param opts table ----@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) +---@class DecoratorCut +---@overload fun(explorer: DecoratorArgs): DecoratorCut - 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 ---Cut highlight: renderer.highlight_clipboard and node is cut ---@param node Node ---@return string|nil group 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" end end diff --git a/lua/nvim-tree/renderer/decorator/diagnostics.lua b/lua/nvim-tree/renderer/decorator/diagnostics.lua index ee495ca674e..bcff66fafad 100644 --- a/lua/nvim-tree/renderer/decorator/diagnostics.lua +++ b/lua/nvim-tree/renderer/decorator/diagnostics.lua @@ -1,8 +1,5 @@ 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 DirectoryNode = require("nvim-tree.node.directory") @@ -35,38 +32,35 @@ local ICON_KEYS = { ---@class (exact) DecoratorDiagnostics: Decorator ---@field icons HighlightedString[]? -local DecoratorDiagnostics = Decorator:new() - ----Static factory method ----@param opts table ----@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 - return o +local DecoratorDiagnostics = Decorator:extend() + +---@class DecoratorDiagnostics +---@overload fun(explorer: DecoratorArgs): DecoratorDiagnostics + +---@protected +---@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 - if opts.renderer.icons.show.diagnostics then - o.icons = {} + if self.explorer.opts.renderer.icons.show.diagnostics then + self.icons = {} for name, sev in pairs(ICON_KEYS) do - o.icons[sev] = { - str = opts.diagnostics.icons[name], + self.icons[sev] = { + str = self.explorer.opts.diagnostics.icons[name], hl = { HG_ICON[sev] }, } - o:define_sign(o.icons[sev]) + self:define_sign(self.icons[sev]) end end - - return o end ---Diagnostic icon: diagnostics.enable, renderer.icons.show.diagnostics and node has status @@ -87,7 +81,7 @@ end ---@param node Node ---@return string|nil group 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 end diff --git a/lua/nvim-tree/renderer/decorator/git.lua b/lua/nvim-tree/renderer/decorator/git.lua index 4a543bcd30a..32e1aed07ef 100644 --- a/lua/nvim-tree/renderer/decorator/git.lua +++ b/lua/nvim-tree/renderer/decorator/git.lua @@ -1,8 +1,5 @@ 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 DirectoryNode = require("nvim-tree.node.directory") @@ -20,52 +17,49 @@ local DirectoryNode = require("nvim-tree.node.directory") ---@field folder_hl_by_xy table? ---@field icons_by_status GitIconsByStatus? ---@field icons_by_xy GitIconsByXY? -local DecoratorGit = Decorator:new() - ----Static factory method ----@param opts table ----@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 - return o +local DecoratorGit = Decorator:extend() + +---@class DecoratorGit +---@overload fun(explorer: DecoratorArgs): DecoratorGit + +---@protected +---@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 - if o.hl_pos ~= HL_POSITION.none then - o:build_file_folder_hl_by_xy() + if self.range ~= "none" then + self:build_file_folder_hl_by_xy() end - if opts.renderer.icons.show.git then - o:build_icons_by_status(opts.renderer.icons.glyphs.git) - o:build_icons_by_xy(o.icons_by_status) + if self.explorer.opts.renderer.icons.show.git then + self:build_icons_by_status(self.explorer.opts.renderer.icons.glyphs.git) + 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) end end - - return o end ---@param glyphs GitGlyphsByStatus function DecoratorGit:build_icons_by_status(glyphs) - self.icons_by_status = {} - 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.renamed = { str = glyphs.renamed, hl = { "NvimTreeGitRenamedIcon" }, ord = 3 } - 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 = {} + 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.renamed = { str = glyphs.renamed, hl = { "NvimTreeGitRenamedIcon" }, ord = 3 } + 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.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 ---@param icons GitIconsByXY @@ -102,7 +96,7 @@ function DecoratorGit:build_icons_by_xy(icons) ["DD"] = { icons.deleted }, ["DU"] = { icons.deleted, icons.unmerged }, ["!!"] = { icons.ignored }, - dirty = { icons.unstaged }, + dirty = { icons.unstaged }, } end @@ -121,7 +115,7 @@ function DecoratorGit:build_file_folder_hl_by_xy() [" T"] = "NvimTreeGitFileDirtyHL", ["MM"] = "NvimTreeGitFileDirtyHL", ["AM"] = "NvimTreeGitFileDirtyHL", - dirty = "NvimTreeGitFileDirtyHL", + dirty = "NvimTreeGitFileDirtyHL", ["A "] = "NvimTreeGitFileStagedHL", ["??"] = "NvimTreeGitFileNewHL", ["AU"] = "NvimTreeGitFileMergeHL", @@ -165,7 +159,7 @@ function DecoratorGit:calculate_icons(node) for _, s in pairs(git_xy) do local icons = self.icons_by_xy[s] 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)) end return nil @@ -197,7 +191,7 @@ end ---@param node Node ---@return string|nil name function DecoratorGit:sign_name(node) - if self.icon_placement ~= ICON_PLACEMENT.signcolumn then + if self.icon_placement ~= "signcolumn" then return end @@ -211,7 +205,7 @@ end ---@param node Node ---@return string|nil group 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 end diff --git a/lua/nvim-tree/renderer/decorator/hidden.lua b/lua/nvim-tree/renderer/decorator/hidden.lua index 7c62f51af5f..291d9482774 100644 --- a/lua/nvim-tree/renderer/decorator/hidden.lua +++ b/lua/nvim-tree/renderer/decorator/hidden.lua @@ -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 DirectoryNode = require("nvim-tree.node.directory") ---@class (exact) DecoratorHidden: Decorator ---@field icon HighlightedString? -local DecoratorHidden = Decorator:new() - ----Static factory method ----@param opts table ----@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 - o.icon = { - str = opts.renderer.icons.glyphs.hidden, +local DecoratorHidden = Decorator:extend() + +---@class DecoratorHidden +---@overload fun(explorer: DecoratorArgs): DecoratorHidden + +---@protected +---@param args DecoratorArgs +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" }, } - o:define_sign(o.icon) + self:define_sign(self.icon) end - - return o end ---Hidden icon: renderer.icons.show.hidden and node starts with `.` (dotfile). @@ -46,7 +40,7 @@ end ---@param node Node ---@return string|nil group 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 end diff --git a/lua/nvim-tree/renderer/decorator/init.lua b/lua/nvim-tree/renderer/decorator/init.lua index 44f05f8e23e..f83b92dcae4 100644 --- a/lua/nvim-tree/renderer/decorator/init.lua +++ b/lua/nvim-tree/renderer/decorator/init.lua @@ -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 -local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT +---@alias DecoratorRange "none" | "icon" | "name" | "all" +---@alias DecoratorIconPlacement "none" | "before" | "after" | "signcolumn" | "right_align" ---Abstract Decorator ---Uses the factory pattern to instantiate child instances. ---@class (exact) Decorator: Class ---@field protected explorer Explorer ---@field protected enabled boolean ----@field protected hl_pos HL_POSITION ----@field protected icon_placement ICON_PLACEMENT -local Decorator = Class:new() +---@field protected range DecoratorRange +---@field protected icon_placement DecoratorIconPlacement +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 ---@param node Node @@ -19,13 +36,13 @@ local Decorator = Class:new() function Decorator:groups_icon_name(node) 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) - 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 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 end end @@ -37,7 +54,7 @@ end ---@param node Node ---@return string|nil name 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 end @@ -47,33 +64,33 @@ function Decorator:sign_name(node) end end ----Icons when ICON_PLACEMENT.before +---Icons when "before" ---@param node Node ---@return HighlightedString[]|nil icons 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 end return self:calculate_icons(node) end ----Icons when ICON_PLACEMENT.after +---Icons when "after" ---@param node Node ---@return HighlightedString[]|nil icons 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 end return self:calculate_icons(node) end ----Icons when ICON_PLACEMENT.right_align +---Icons when "right_align" ---@param node Node ---@return HighlightedString[]|nil icons 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 end @@ -109,7 +126,7 @@ function Decorator:define_sign(icon) -- don't use sign if not defined if #icon.str < 1 then - self.icon_placement = ICON_PLACEMENT.none + self.icon_placement = "none" return end diff --git a/lua/nvim-tree/renderer/decorator/modified.lua b/lua/nvim-tree/renderer/decorator/modified.lua index 2126379cd14..68dc322bde1 100644 --- a/lua/nvim-tree/renderer/decorator/modified.lua +++ b/lua/nvim-tree/renderer/decorator/modified.lua @@ -1,42 +1,36 @@ 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 DirectoryNode = require("nvim-tree.node.directory") ---@class (exact) DecoratorModified: Decorator ----@field icon HighlightedString|nil -local DecoratorModified = Decorator:new() - ----Static factory method ----@param opts table ----@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 - return o +---@field icon HighlightedString? +local DecoratorModified = Decorator:extend() + +---@class DecoratorModified +---@overload fun(explorer: DecoratorArgs): DecoratorModified + +---@protected +---@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 - if opts.renderer.icons.show.modified then - o.icon = { - str = opts.renderer.icons.glyphs.modified, + if self.explorer.opts.renderer.icons.show.modified then + self.icon = { + str = self.explorer.opts.renderer.icons.glyphs.modified, hl = { "NvimTreeModifiedIcon" }, } - o:define_sign(o.icon) + self:define_sign(self.icon) end - - return o end ---Modified icon: modified.enable, renderer.icons.show.modified and node is modified @@ -52,7 +46,7 @@ end ---@param node Node ---@return string|nil group 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 end diff --git a/lua/nvim-tree/renderer/decorator/opened.lua b/lua/nvim-tree/renderer/decorator/opened.lua index cb1843311c7..4f47e09727d 100644 --- a/lua/nvim-tree/renderer/decorator/opened.lua +++ b/lua/nvim-tree/renderer/decorator/opened.lua @@ -1,36 +1,30 @@ 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") ---@class (exact) DecoratorOpened: Decorator ---@field icon HighlightedString|nil -local DecoratorOpened = Decorator:new() +local DecoratorOpened = Decorator:extend() ----Static factory method ----@param opts table ----@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) +---@class DecoratorOpened +---@overload fun(explorer: DecoratorArgs): DecoratorOpened - 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 ---Opened highlight: renderer.highlight_opened_files and node has an open buffer ---@param node Node ---@return string|nil group 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" end end diff --git a/lua/nvim-tree/renderer/init.lua b/lua/nvim-tree/renderer/init.lua index f61c4be4e5f..00d49f7f86f 100644 --- a/lua/nvim-tree/renderer/init.lua +++ b/lua/nvim-tree/renderer/init.lua @@ -2,6 +2,7 @@ local log = require("nvim-tree.log") local view = require("nvim-tree.view") local events = require("nvim-tree.events") +local Class = require("nvim-tree.classic") local Builder = require("nvim-tree.renderer.builder") 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_virtual_lines_id = vim.api.nvim_create_namespace("NvimTreeVirtualLines") ----@class (exact) Renderer ----@field private __index? table ----@field private opts table user options ----@field private explorer Explorer -local Renderer = {} - ----@param opts table user options ----@param explorer Explorer ----@return Renderer -function Renderer:new(opts, explorer) - ---@type Renderer - local o = { - opts = opts, - explorer = explorer, - } - - setmetatable(o, self) - self.__index = self - - return o +---@class (exact) Renderer: Class +---@field explorer Explorer +local Renderer = Class:extend() + +---@class Renderer +---@overload fun(args: RendererArgs): Renderer + +---@class (exact) RendererArgs +---@field explorer Explorer + +---@protected +---@param args RendererArgs +function Renderer:new(args) + self.explorer = args.explorer end ---@private @@ -64,9 +59,9 @@ function Renderer:_draw(bufnr, lines, hl_args, signs, extmarks, virtual_lines) for i, extname in pairs(extmarks) do for _, mark in ipairs(extname) do 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", - hl_mode = "combine", + hl_mode = "combine", }) 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) for line_nr, vlines in pairs(virtual_lines) do vim.api.nvim_buf_set_extmark(bufnr, namespace_virtual_lines_id, line_nr, 0, { - virt_lines = vlines, - virt_lines_above = false, + virt_lines = vlines, + virt_lines_above = false, virt_lines_leftcol = true, }) end @@ -106,7 +101,7 @@ function Renderer:draw() 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) diff --git a/lua/nvim-tree/view.lua b/lua/nvim-tree/view.lua index e76bf089058..eb72b774ce1 100644 --- a/lua/nvim-tree/view.lua +++ b/lua/nvim-tree/view.lua @@ -15,31 +15,31 @@ local DEFAULT_MAX_WIDTH = -1 local DEFAULT_PADDING = 1 M.View = { - adaptive_size = false, + adaptive_size = false, centralize_selection = false, - tabpages = {}, - cursors = {}, - hide_root_folder = false, - live_filter = { + tabpages = {}, + cursors = {}, + hide_root_folder = false, + live_filter = { prev_focused_node = nil, }, - winopts = { + winopts = { relativenumber = false, - number = false, - list = false, - foldenable = false, - winfixwidth = true, - winfixheight = true, - spell = false, - signcolumn = "yes", - foldmethod = "manual", - foldcolumn = "0", - cursorcolumn = false, - cursorline = true, - cursorlineopt = "both", - colorcolumn = "0", - wrap = false, - winhl = table.concat({ + number = false, + list = false, + foldenable = false, + winfixwidth = true, + winfixheight = true, + spell = false, + signcolumn = "yes", + foldmethod = "manual", + foldcolumn = "0", + cursorcolumn = false, + cursorline = true, + cursorlineopt = "both", + colorcolumn = "0", + wrap = false, + winhl = table.concat({ "EndOfBuffer:NvimTreeEndOfBuffer", "CursorLine:NvimTreeCursorLine", "CursorLineNr:NvimTreeCursorLineNr", @@ -152,7 +152,6 @@ local function set_window_options_and_buffer() pcall(vim.api.nvim_command, "buffer " .. M.get_bufnr()) if vim.fn.has("nvim-0.10") == 1 then - local eventignore = vim.api.nvim_get_option_value("eventignore", {}) vim.api.nvim_set_option_value("eventignore", "all", {}) @@ -161,9 +160,7 @@ local function set_window_options_and_buffer() end vim.api.nvim_set_option_value("eventignore", eventignore, {}) - else - local eventignore = vim.api.nvim_get_option("eventignore") ---@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 vim.api.nvim_set_option("eventignore", eventignore) ---@diagnostic disable-line: deprecated - end end diff --git a/lua/nvim-tree/watcher.lua b/lua/nvim-tree/watcher.lua index 0dfdbf0afde..960e9246d75 100644 --- a/lua/nvim-tree/watcher.lua +++ b/lua/nvim-tree/watcher.lua @@ -2,7 +2,7 @@ local notify = require("nvim-tree.notify") local log = require("nvim-tree.log") local utils = require("nvim-tree.utils") -local Class = require("nvim-tree.class") +local Class = require("nvim-tree.classic") local FS_EVENT_FLAGS = { -- inotify or equivalent will be used; fallback to stat has not yet been implemented @@ -15,36 +15,45 @@ local M = { config = {}, } +---Registry of all events +---@type Event[] +local events = {} + ---@class (exact) Event: Class ---@field destroyed boolean ---@field private path string ---@field private fs_event uv.uv_fs_event_t? ---@field private listeners function[] -local Event = Class:new() +local Event = Class:extend() ----Registry of all events ----@type Event[] -local events = {} +---@class Event +---@overload fun(args: EventArgs): Event + +---@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 ---Creates and starts an Event ----@param path string ----@return Event|nil -function Event:create(path) - log.line("watcher", "Event:create '%s'", path) - - ---@type Event - local o = { - destroyed = false, - path = path, - fs_event = nil, - listeners = {}, - } - o = self:new(o) - - if o:start() then - events[path] = o - return o +---nil on failure to start +---@param args EventArgs +---@return Event? +function Event:create(args) + log.line("watcher", "Event:create '%s'", args.path) + + local event = Event(args) + + if event:start() then + events[event.path] = event + return event else return nil end @@ -128,8 +137,10 @@ function Event:destroy(message) events[self.path] = nil end ----Static factory method ----Creates and starts a Watcher +---Registry of all watchers +---@type Watcher[] +local watchers = {} + ---@class (exact) Watcher: Class ---@field data table user data ---@field destroyed boolean @@ -138,43 +149,50 @@ end ---@field private files string[]? ---@field private listener fun(filename: string)? ---@field private event Event -local Watcher = Class:new() - ----Registry of all watchers ----@type Watcher[] -local watchers = {} +local Watcher = Class:extend() + +---@class Watcher +---@overload fun(args: WatcherArgs): Watcher + +---@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 ----@param path string ----@param files string[]|nil ----@param callback fun(watcher: Watcher) ----@param data table user data +---Creates and starts a Watcher +---nil on failure to create Event +---@param args WatcherArgs ---@return Watcher|nil -function Watcher:create(path, files, callback, data) - log.line("watcher", "Watcher:create '%s' %s", path, vim.inspect(files)) +function Watcher:create(args) + 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 return nil end - ---@type Watcher - local o = { - data = data, - destroyed = false, - path = path, - callback = callback, - files = files, - listener = nil, - event = event, - } - o = self:new(o) + local watcher = Watcher(args) + + watcher.event = event - o:start() + watcher:start() - table.insert(watchers, o) + table.insert(watchers, watcher) - return o + return watcher end function Watcher:start()