diff --git a/Makefile b/Makefile index 8df41f7c588..b5e829d8ddc 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ check: luals # subtasks # luacheck: - luacheck -q lua + luacheck --codes --quiet lua --exclude-files "**/_meta/**" # --diagnosis-as-error does not function for workspace, hence we post-process the output style-check: diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index 060227f8220..07985f841fd 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -52,14 +52,16 @@ CONTENTS *nvim-tree* 8.2 Highlight: Overhaul |nvim-tree-highlight-overhaul| 9. Events |nvim-tree-events| 10. Prompts |nvim-tree-prompts| - 11. OS Specific Restrictions |nvim-tree-os-specific| - 12. Netrw |nvim-tree-netrw| - 13. Legacy |nvim-tree-legacy| - 13.1 Legacy: Opts |nvim-tree-legacy-opts| - 13.2 Legacy: Highlight |nvim-tree-legacy-highlight| - 14. Index |nvim-tree-index| - 14.1 Index: Opts |nvim-tree-index-opts| - 14.2 Index: API |nvim-tree-index-api| + 11. Decorators |nvim-tree-decorators| + 11.1 Decorator Example |nvim-tree-decorator-example| + 12. OS Specific Restrictions |nvim-tree-os-specific| + 13. Netrw |nvim-tree-netrw| + 14. Legacy |nvim-tree-legacy| + 14.1 Legacy: Opts |nvim-tree-legacy-opts| + 14.2 Legacy: Highlight |nvim-tree-legacy-highlight| + 15. Index |nvim-tree-index| + 15.1 Index: Opts |nvim-tree-index-opts| + 15.2 Index: API |nvim-tree-index-api| ============================================================================== 1. INTRODUCTION *nvim-tree-introduction* @@ -425,6 +427,7 @@ Following is the default configuration. See |nvim-tree-opts| for details. >lua special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" }, hidden_display = "none", symlink_destination = true, + decorators = { "Git", "Open", "Hidden", "Modified", "Bookmark", "Diagnostics", "Copied", "Cut", }, highlight_git = "none", highlight_diagnostics = "none", highlight_opened_files = "none", @@ -842,9 +845,6 @@ Use nvim-tree in a floating window. ============================================================================== 5.3 OPTS: RENDERER *nvim-tree-opts-renderer* -Highlight precedence, additive: - git < opened < modified < bookmarked < diagnostics < copied < cut - *nvim-tree.renderer.add_trailing* Appends a trailing slash to folder names. Type: `boolean`, Default: `false` @@ -927,6 +927,22 @@ Show a summary of hidden files below the tree using `NvimTreeHiddenDisplay Whether to show the destination of the symlink. Type: `boolean`, Default: `true` +*nvim-tree.renderer.decorators* +Highlighting and icons for the nodes, in increasing order of precedence. +Uses strings to specify builtin decorators otherwise specify your +`nvim_tree.api.decorator.UserDecorator` class. + Type: `nvim_tree.api.decorator.Name[]`, Default: >lua + { + "Git", + "Open", + "Hidden", + "Modified", + "Bookmark", + "Diagnostics", + "Copied", + "Cut", + } +< *nvim-tree.renderer.highlight_git* Enable highlight for git attributes using `NvimTreeGit*HL` highlight groups. Requires |nvim-tree.git.enable| @@ -996,9 +1012,6 @@ Configuration options for tree indent markers. *nvim-tree.renderer.icons* Configuration options for icons. -Icon order and sign column precedence: - git < hidden < modified < bookmarked < diagnostics - `renderer.icons.*_placement` options may be: - `"before"` : before file/folder, after the file/folders icons - `"after"` : after file/folder @@ -2755,7 +2768,90 @@ configurations for different types of prompts. send all bookmarked to trash during |nvim-tree-api.marks.bulk.trash()| ============================================================================== - 11. OS SPECIFIC RESTRICTIONS *nvim-tree-os-specific* + 11. DECORATORS *nvim-tree-decorators* + +Highlighting and icons for nodes are provided by Decorators. You may provide +your own in addition to the builtin decorators. + +Decorators may: +- Add icons +- Set highlight group for the name or icons +- Override node icon + +Specify decorators and their precedence via |nvim-tree.renderer.decorators| +e.g. defaults with a user decorator class being overridden only by Cut: >lua + { + "Git", + "Open", + "Hidden", + "Modified", + "Bookmark", + "Diagnostics", + "Copied", + MyDecorator, + "Cut", + } + +See `nvim-tree/_meta/api_decorator.lua` for full +`nvim_tree.api.decorator.UserDecorator` class documentation. +< +============================================================================== + 11.1. DECORATOR EXAMPLE *nvim-tree-decorator-example* +>lua + ---Create your decorator class + ---@class (exact) MyDecorator: nvim_tree.api.decorator.UserDecorator + ---@field private my_icon nvim_tree.api.HighlightedString + local MyDecorator = require("nvim-tree.api").decorator.UserDecorator:extend() + + ---Mandatory constructor :new() will be called once per tree render, with no arguments. + function MyDecorator:new() + self.enabled = true + self.highlight_range = "all" + self.icon_placement = "signcolumn" + + -- create your icon once, for convenience + self.my_icon = { str = "I", hl = { "MyIcon" } } + + -- Define the icon sign only once + -- Only needed if you are using icon_placement = "signcolumn" + self:define_sign(self.my_icon) + end + + ---Override node icon + ---@param node nvim_tree.api.Node + ---@return nvim_tree.api.HighlightedString? icon_node + function MyDecorator:icon_node(node) + if node.name == "example" then + return self.my_icon + else + return nil + end + end + + ---Return one icon for DecoratorIconPlacement + ---@param node nvim_tree.api.Node + ---@return nvim_tree.api.HighlightedString[]? icons + function MyDecorator:icons(node) + if node.name == "example" then + return { self.my_icon } + else + return nil + end + end + + ---Exactly one highlight group for DecoratorHighlightRange + ---@param node nvim_tree.api.Node + ---@return string? highlight_group + function MyDecorator:highlight_group(node) + if node.name == "example" then + return "MyHighlight" + else + return nil + end + end +< +============================================================================== + 12. OS SPECIFIC RESTRICTIONS *nvim-tree-os-specific* Windows WSL and PowerShell - Trash is synchronized @@ -2767,7 +2863,7 @@ Windows WSL and PowerShell issues or disable this feature. ============================================================================== - 12. NETRW *nvim-tree-netrw* + 13. NETRW *nvim-tree-netrw* |netrw| is a standard neovim plugin that is enabled by default. It provides, amongst other functionality, a file/directory browser. @@ -2788,14 +2884,14 @@ keep using |netrw| without its browser features please ensure: |nvim-tree.hijack_netrw| ` = true` ============================================================================== - 13. LEGACY *nvim-tree-legacy* + 14. LEGACY *nvim-tree-legacy* Breaking refactors have been made however the legacy versions will be silently migrated and used. There are no plans to remove this migration. ============================================================================== - 13.1 LEGACY: OPTS *nvim-tree-legacy-opts* + 14.1 LEGACY: OPTS *nvim-tree-legacy-opts* Legacy options are translated to the current, making type and value changes as needed. @@ -2813,7 +2909,7 @@ needed. `renderer.icons.webdev_colors` |nvim-tree.renderer.icons.web_devicons.file.color| ============================================================================== - 13.2 LEGACY: HIGHLIGHT *nvim-tree-legacy-highlight* + 14.2 LEGACY: HIGHLIGHT *nvim-tree-legacy-highlight* Legacy highlight group are still obeyed when they are defined and the current highlight group is not, hard linking as follows: > @@ -2862,10 +2958,10 @@ highlight group is not, hard linking as follows: > NvimTreeLspDiagnosticsHintFolderText NvimTreeDiagnosticHintFolderHL < ============================================================================== - 14 INDEX *nvim-tree-index* + 15 INDEX *nvim-tree-index* ============================================================================== - 14.1 INDEX: OPTS *nvim-tree-index-opts* + 15.1 INDEX: OPTS *nvim-tree-index-opts* |nvim-tree.actions.change_dir| |nvim-tree.actions.change_dir.enable| @@ -2943,6 +3039,7 @@ highlight group is not, hard linking as follows: > |nvim-tree.prefer_startup_root| |nvim-tree.reload_on_bufenter| |nvim-tree.renderer.add_trailing| +|nvim-tree.renderer.decorators| |nvim-tree.renderer.full_name| |nvim-tree.renderer.group_empty| |nvim-tree.renderer.hidden_display| @@ -3033,7 +3130,7 @@ highlight group is not, hard linking as follows: > |nvim-tree.view.width.padding| ============================================================================== - 14.2 INDEX: API *nvim-tree-index-api* + 15.2 INDEX: API *nvim-tree-index-api* |nvim-tree-api.commands.get()| |nvim-tree-api.config.mappings.default_on_attach()| diff --git a/lua/nvim-tree.lua b/lua/nvim-tree.lua index 212d4c14bab..6ee2fd26800 100644 --- a/lua/nvim-tree.lua +++ b/lua/nvim-tree.lua @@ -284,6 +284,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" }, hidden_display = "none", symlink_destination = true, + decorators = { "Git", "Open", "Hidden", "Modified", "Bookmark", "Diagnostics", "Copied", "Cut", }, highlight_git = "none", highlight_diagnostics = "none", highlight_opened_files = "none", diff --git a/lua/nvim-tree/_meta/api.lua b/lua/nvim-tree/_meta/api.lua new file mode 100644 index 00000000000..d6847940781 --- /dev/null +++ b/lua/nvim-tree/_meta/api.lua @@ -0,0 +1,51 @@ +---@meta +error("Cannot require a meta file") + +-- +-- Nodes +-- + +---Base Node, Abstract +---@class (exact) nvim_tree.api.Node +---@field type "file" | "directory" | "link" uv.fs_stat.result.type +---@field absolute_path string +---@field executable boolean +---@field fs_stat uv.fs_stat.result? +---@field git_status GitNodeStatus? +---@field hidden boolean +---@field name string +---@field parent nvim_tree.api.DirectoryNode? +---@field diag_severity lsp.DiagnosticSeverity? + +---File +---@class (exact) nvim_tree.api.FileNode: nvim_tree.api.Node +---@field extension string + +---Directory +---@class (exact) nvim_tree.api.DirectoryNode: nvim_tree.api.Node +---@field has_children boolean +---@field nodes nvim_tree.api.Node[] +---@field open boolean + +---Root Directory +---@class (exact) nvim_tree.api.RootNode: nvim_tree.api.DirectoryNode + +---Link mixin +---@class (exact) nvim_tree.api.LinkNode +---@field link_to string +---@field fs_stat_target uv.fs_stat.result + +---File Link +---@class (exact) nvim_tree.api.FileLinkNode: nvim_tree.api.FileNode, nvim_tree.api.LinkNode + +---DirectoryLink +---@class (exact) nvim_tree.api.DirectoryLinkNode: nvim_tree.api.DirectoryNode, nvim_tree.api.LinkNode + +-- +-- Various Types +-- + +---A string for rendering, with optional highlight groups to apply to it +---@class (exact) nvim_tree.api.HighlightedString +---@field str string +---@field hl string[] diff --git a/lua/nvim-tree/_meta/api_decorator.lua b/lua/nvim-tree/_meta/api_decorator.lua new file mode 100644 index 00000000000..d85fe02fff0 --- /dev/null +++ b/lua/nvim-tree/_meta/api_decorator.lua @@ -0,0 +1,54 @@ +---@meta +error("Cannot require a meta file") + +local nvim_tree = { api = { decorator = {} } } + +---Highlight group range as per nvim-tree.renderer.highlight_* +---@alias nvim_tree.api.decorator.HighlightRange "none" | "icon" | "name" | "all" + +---Icon position as per renderer.icons.*_placement +---@alias nvim_tree.api.decorator.IconPlacement "none" | "before" | "after" | "signcolumn" | "right_align" + +---Names of builtin decorators or your decorator classes. Builtins are ordered lowest to highest priority. +---@alias nvim_tree.api.decorator.Name "Git" | "Opened" | "Hidden" | "Modified" | "Bookmarks" | "Diagnostics" | "Copied" | "Cut" | nvim_tree.api.decorator.UserDecorator + +---Custom decorator, see :help nvim-tree-decorators +--- +---@class (exact) nvim_tree.api.decorator.UserDecorator +---@field protected enabled boolean +---@field protected highlight_range nvim_tree.api.decorator.HighlightRange +---@field protected icon_placement nvim_tree.api.decorator.IconPlacement +nvim_tree.api.decorator.UserDecorator = {} + +---Create your decorator class +--- +function nvim_tree.api.decorator.UserDecorator:extend() end + +---Abstract: no-args constructor must be implemented and will be called once per tree render. +---Must set all fields. +--- +function nvim_tree.api.decorator.UserDecorator:new() end + +---Abstract: optionally implement to set the node's icon +--- +---@param node nvim_tree.api.Node +---@return nvim_tree.api.HighlightedString? icon_node +function nvim_tree.api.decorator.UserDecorator:icon_node(node) end + +---Abstract: optionally implement to provide icons and the highlight groups for your icon_placement. +--- +---@param node nvim_tree.api.Node +---@return nvim_tree.api.HighlightedString[]? icons +function nvim_tree.api.decorator.UserDecorator:icons(node) end + +---Abstract: optionally implement to provide one highlight group to apply to your highlight_range. +--- +---@param node nvim_tree.api.Node +---@return string? highlight_group +function nvim_tree.api.decorator.UserDecorator:highlight_group(node) end + +---Define a sign. This should be called in the constructor. +--- +---@protected +---@param icon nvim_tree.api.HighlightedString? +function nvim_tree.api.decorator.UserDecorator:define_sign(icon) end diff --git a/lua/nvim-tree/api.lua b/lua/nvim-tree/api.lua index fb1596a273f..455d4b0df08 100644 --- a/lua/nvim-tree/api.lua +++ b/lua/nvim-tree/api.lua @@ -11,6 +11,7 @@ local notify = require("nvim-tree.notify") local DirectoryNode = require("nvim-tree.node.directory") local FileLinkNode = require("nvim-tree.node.file-link") local RootNode = require("nvim-tree.node.root") +local UserDecorator = require("nvim-tree.renderer.decorator.user") local Api = { tree = {}, @@ -39,6 +40,7 @@ local Api = { }, commands = {}, diagnostics = {}, + decorator = {}, } ---Print error when setup not called. @@ -311,4 +313,9 @@ Api.commands.get = wrap(function() return require("nvim-tree.commands").get() end) +---Create a decorator class by calling :extend() +---See :help nvim-tree-decorators +---@type nvim_tree.api.decorator.UserDecorator +Api.decorator.UserDecorator = UserDecorator --[[@as nvim_tree.api.decorator.UserDecorator]] + return Api diff --git a/lua/nvim-tree/explorer/init.lua b/lua/nvim-tree/explorer/init.lua index 0c637b2a3d3..0fdd46a1d1c 100644 --- a/lua/nvim-tree/explorer/init.lua +++ b/lua/nvim-tree/explorer/init.lua @@ -530,7 +530,7 @@ function Explorer:place_cursor_on_node() end ---Api.tree.get_nodes ----@return Node +---@return nvim_tree.api.Node function Explorer:get_nodes() return self:clone() end diff --git a/lua/nvim-tree/node/directory-link.lua b/lua/nvim-tree/node/directory-link.lua index 82383088b91..9666ca4d7c2 100644 --- a/lua/nvim-tree/node/directory-link.lua +++ b/lua/nvim-tree/node/directory-link.lua @@ -73,9 +73,10 @@ function DirectoryLinkNode:highlighted_name() end ---Create a sanitized partial copy of a node, populating children recursively. ----@return DirectoryLinkNode cloned -function DirectoryLinkNode:clone() - local clone = DirectoryNode.clone(self) --[[@as DirectoryLinkNode]] +---@param api_nodes table? optional map of uids to api node to populate +---@return nvim_tree.api.DirectoryLinkNode cloned +function DirectoryLinkNode:clone(api_nodes) + local clone = DirectoryNode.clone(self, api_nodes) --[[@as nvim_tree.api.DirectoryLinkNode]] 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 899de184044..0965e4ab9fc 100644 --- a/lua/nvim-tree/node/directory.lua +++ b/lua/nvim-tree/node/directory.lua @@ -271,18 +271,20 @@ function DirectoryNode:highlighted_name() 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]] +---@param api_nodes table? optional map of uids to api node to populate +---@return nvim_tree.api.DirectoryNode cloned +function DirectoryNode:clone(api_nodes) + local clone = Node.clone(self, api_nodes) --[[@as nvim_tree.api.DirectoryNode]] clone.has_children = self.has_children - clone.group_next = nil clone.nodes = {} clone.open = self.open - clone.hidden_stats = nil + local clone_child for _, child in ipairs(self.nodes) do - table.insert(clone.nodes, child:clone()) + clone_child = child:clone(api_nodes) + clone_child.parent = clone + table.insert(clone.nodes, clone_child) end return clone diff --git a/lua/nvim-tree/node/file-link.lua b/lua/nvim-tree/node/file-link.lua index 3c5571a29a2..b13c88f0d6b 100644 --- a/lua/nvim-tree/node/file-link.lua +++ b/lua/nvim-tree/node/file-link.lua @@ -58,9 +58,10 @@ function FileLinkNode:highlighted_name() end ---Create a sanitized partial copy of a node ----@return FileLinkNode cloned -function FileLinkNode:clone() - local clone = FileNode.clone(self) --[[@as FileLinkNode]] +---@param api_nodes table? optional map of uids to api node to populate +---@return nvim_tree.api.FileLinkNode cloned +function FileLinkNode:clone(api_nodes) + local clone = FileNode.clone(self, api_nodes) --[[@as nvim_tree.api.FileLinkNode]] 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 a74a213a8eb..6e160409c29 100644 --- a/lua/nvim-tree/node/file.lua +++ b/lua/nvim-tree/node/file.lua @@ -94,9 +94,10 @@ function FileNode:highlighted_name() end ---Create a sanitized partial copy of a node ----@return FileNode cloned -function FileNode:clone() - local clone = Node.clone(self) --[[@as FileNode]] +---@param api_nodes table? optional map of uids to api node to populate +---@return nvim_tree.api.FileNode cloned +function FileNode:clone(api_nodes) + local clone = Node.clone(self, api_nodes) --[[@as nvim_tree.api.FileNode]] clone.extension = self.extension diff --git a/lua/nvim-tree/node/init.lua b/lua/nvim-tree/node/init.lua index f6ecf24d036..da0dcc5b0eb 100644 --- a/lua/nvim-tree/node/init.lua +++ b/lua/nvim-tree/node/init.lua @@ -2,6 +2,7 @@ local Class = require("nvim-tree.classic") ---Abstract Node class. ---@class (exact) Node: Class +---@field uid_node number vim.loop.hrtime() at construction time ---@field type "file" | "directory" | "link" uv.fs_stat.result.type ---@field explorer Explorer ---@field absolute_path string @@ -25,6 +26,7 @@ local Node = Class:extend() ---@protected ---@param args NodeArgs function Node:new(args) + self.uid_node = vim.loop.hrtime() self.explorer = args.explorer self.absolute_path = args.absolute_path self.executable = false @@ -112,21 +114,19 @@ end ---Highlighted name for the node ---Empty for base Node ----@return HighlightedString icon +---@return HighlightedString name function Node:highlighted_name() return self:highlighted_name_empty() end ---Create a sanitized partial copy of a node, populating children recursively. ----@return Node cloned -function Node:clone() - ---@type Explorer - local explorer_placeholder = nil - - ---@type Node +---@param api_nodes table? optional map of uids to api node to populate +---@return nvim_tree.api.Node cloned +function Node:clone(api_nodes) + ---@type nvim_tree.api.Node local clone = { + uid_node = self.uid_node, type = self.type, - explorer = explorer_placeholder, absolute_path = self.absolute_path, executable = self.executable, fs_stat = self.fs_stat, @@ -134,10 +134,13 @@ function Node:clone() hidden = self.hidden, name = self.name, parent = nil, - diag_status = nil, - is_dot = self.is_dot, + diag_severity = self.diag_status and self.diag_status.value or nil, } + if api_nodes then + api_nodes[self.uid_node] = clone + end + return clone end diff --git a/lua/nvim-tree/node/link.lua b/lua/nvim-tree/node/link.lua index ff2d8e4df5b..c3bc164b912 100644 --- a/lua/nvim-tree/node/link.lua +++ b/lua/nvim-tree/node/link.lua @@ -2,7 +2,7 @@ local Class = require("nvim-tree.classic") ---@class (exact) LinkNode: Class ---@field link_to string ----@field protected fs_stat_target uv.fs_stat.result +---@field fs_stat_target uv.fs_stat.result local LinkNode = Class:extend() ---@class (exact) LinkNodeArgs: NodeArgs diff --git a/lua/nvim-tree/node/root.lua b/lua/nvim-tree/node/root.lua index ec3c44c4a82..f991dec847d 100644 --- a/lua/nvim-tree/node/root.lua +++ b/lua/nvim-tree/node/root.lua @@ -22,4 +22,13 @@ function RootNode:destroy() DirectoryNode.destroy(self) end +---Create a sanitized partial copy of a node, populating children recursively. +---@param api_nodes table? optional map of uids to api node to populate +---@return nvim_tree.api.RootNode cloned +function RootNode:clone(api_nodes) + local clone = DirectoryNode.clone(self, api_nodes) --[[@as nvim_tree.api.RootNode]] + + return clone +end + return RootNode diff --git a/lua/nvim-tree/renderer/builder.lua b/lua/nvim-tree/renderer/builder.lua index 303e02a39d3..e08eb9e6363 100644 --- a/lua/nvim-tree/renderer/builder.lua +++ b/lua/nvim-tree/renderer/builder.lua @@ -3,22 +3,35 @@ 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") -local DecoratorCopied = require("nvim-tree.renderer.decorator.copied") -local DecoratorCut = require("nvim-tree.renderer.decorator.cut") -local DecoratorDiagnostics = require("nvim-tree.renderer.decorator.diagnostics") -local DecoratorGit = require("nvim-tree.renderer.decorator.git") -local DecoratorModified = require("nvim-tree.renderer.decorator.modified") -local DecoratorHidden = require("nvim-tree.renderer.decorator.hidden") -local DecoratorOpened = require("nvim-tree.renderer.decorator.opened") +local BookmarkDecorator = require("nvim-tree.renderer.decorator.bookmarks") +local CopiedDecorator = require("nvim-tree.renderer.decorator.copied") +local CutDecorator = require("nvim-tree.renderer.decorator.cut") +local DiagnosticsDecorator = require("nvim-tree.renderer.decorator.diagnostics") +local GitDecorator = require("nvim-tree.renderer.decorator.git") +local HiddenDecorator = require("nvim-tree.renderer.decorator.hidden") +local ModifiedDecorator = require("nvim-tree.renderer.decorator.modified") +local OpenDecorator = require("nvim-tree.renderer.decorator.opened") +local UserDecorator = require("nvim-tree.renderer.decorator.user") local pad = require("nvim-tree.renderer.components.padding") ----@class (exact) HighlightedString ----@field str string ----@field hl string[] +---@alias HighlightedString nvim_tree.api.HighlightedString + +-- Builtin Decorators +---@type table +local BUILTIN_DECORATORS = { + Git = GitDecorator, + Open = OpenDecorator, + Hidden = HiddenDecorator, + Modified = ModifiedDecorator, + Bookmark = BookmarkDecorator, + Diagnostics = DiagnosticsDecorator, + Copied = CopiedDecorator, + Cut = CutDecorator, +} ---@class (exact) AddHighlightArgs ---@field group string[] @@ -39,6 +52,7 @@ local pad = require("nvim-tree.renderer.components.padding") ---@field private markers boolean[] indent markers ---@field private decorators Decorator[] ---@field private hidden_display fun(node: Node): string|nil +---@field private api_nodes table? optional map of uids to api node for user decorators local Builder = Class:extend() ---@class Builder @@ -60,18 +74,30 @@ function Builder:new(args) 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 }) - } + self.decorators = {} self.hidden_display = Builder:setup_hidden_display_function(self.explorer.opts) + + -- instantiate all the builtin and user decorator instances + local builtin, user + for _, d in ipairs(self.explorer.opts.renderer.decorators) do + ---@type Decorator + builtin = BUILTIN_DECORATORS[d] + + ---@type UserDecorator + user = type(d) == "table" and type(d.as) == "function" and d:as(UserDecorator) + + if builtin then + table.insert(self.decorators, builtin({ explorer = self.explorer })) + elseif user then + table.insert(self.decorators, user()) + + -- clone user nodes once + if not self.api_nodes then + self.api_nodes = {} + self.explorer:clone(self.api_nodes) + end + end + end end ---Insert ranged highlight groups into self.highlights @@ -131,22 +157,25 @@ function Builder:format_line(indent_markers, arrows, icon, name, node) end end + -- use the api node for user decorators + local api_node = self.api_nodes and self.api_nodes[node.uid_node] --[[@as Node]] + local line = { indent_markers, arrows } add_to_end(line, { icon }) - for i = #self.decorators, 1, -1 do - add_to_end(line, self.decorators[i]:icons_before(node)) + for _, d in ipairs(self.decorators) do + add_to_end(line, d:icons_before(not d:is(UserDecorator) and node or api_node)) end add_to_end(line, { name }) - for i = #self.decorators, 1, -1 do - add_to_end(line, self.decorators[i]:icons_after(node)) + for _, d in ipairs(self.decorators) do + add_to_end(line, d:icons_after(not d:is(UserDecorator) and node or api_node)) end local rights = {} - for i = #self.decorators, 1, -1 do - add_to_end(rights, self.decorators[i]:icons_right_align(node)) + for _, d in ipairs(self.decorators) do + add_to_end(rights, d:icons_right_align(not d:is(UserDecorator) and node or api_node)) end if #rights > 0 then self.extmarks[self.index] = rights @@ -158,10 +187,14 @@ end ---@private ---@param node Node function Builder:build_signs(node) + -- use the api node for user decorators + local api_node = self.api_nodes and self.api_nodes[node.uid_node] --[[@as Node]] + -- first in priority order - local sign_name - for _, d in ipairs(self.decorators) do - sign_name = d:sign_name(node) + local d, sign_name + for i = #self.decorators, 1, -1 do + d = self.decorators[i] + sign_name = d:sign_name(not d:is(UserDecorator) and node or api_node) if sign_name then self.signs[self.index] = sign_name break @@ -197,43 +230,50 @@ function Builder:create_combined_group(groups) return combined_name end ----Calculate highlight group for icon and name. A combined highlight group will be created ----when there is more than one highlight. +---Calculate decorated icon and name for a node. +---A combined highlight group will be created when there is more than one highlight. ---A highlight group is always calculated and upserted for the case of highlights changing. ---@private ---@param node Node ----@return string|nil icon_hl_group ----@return string|nil name_hl_group -function Builder:add_highlights(node) - -- result - local icon_hl_group, name_hl_group +---@return HighlightedString icon +---@return HighlightedString name +function Builder:icon_name_decorated(node) + -- use the api node for user decorators + local api_node = self.api_nodes and self.api_nodes[node.uid_node] --[[@as Node]] + + -- base case + local icon = node:highlighted_icon() + local name = node:highlighted_name() - -- calculate all groups + -- calculate node icon and all decorated highlight groups local icon_groups = {} local name_groups = {} - local d, icon, name - for i = #self.decorators, 1, -1 do - d = self.decorators[i] - icon, name = d:groups_icon_name(node) - table.insert(icon_groups, icon) - table.insert(name_groups, name) + local hl_icon, hl_name + for _, d in ipairs(self.decorators) do + -- maybe overridde icon + icon = d:icon_node((not d:is(UserDecorator) and node or api_node)) or icon + + hl_icon, hl_name = d:highlight_group_icon_name((not d:is(UserDecorator) and node or api_node)) + + table.insert(icon_groups, hl_icon) + table.insert(name_groups, hl_name) end - -- one or many icon groups + -- add one or many icon groups if #icon_groups > 1 then - icon_hl_group = self:create_combined_group(icon_groups) + table.insert(icon.hl, self:create_combined_group(icon_groups)) else - icon_hl_group = icon_groups[1] + table.insert(icon.hl, icon_groups[1]) end - -- one or many name groups + -- add one or many name groups if #name_groups > 1 then - name_hl_group = self:create_combined_group(name_groups) + table.insert(name.hl, self:create_combined_group(name_groups)) else - name_hl_group = name_groups[1] + table.insert(name.hl, name_groups[1]) end - return icon_hl_group, name_hl_group + return icon, name end ---Insert node line into self.lines, calling Builder:build_lines for each directory @@ -246,13 +286,8 @@ function Builder:build_line(node, idx, num_children) local indent_markers = pad.get_indent_markers(self.depth, idx, num_children, node, self.markers) local arrows = pad.get_arrows(node) - -- main components - local icon, name = node:highlighted_icon(), node:highlighted_name() - - -- highighting - local icon_hl_group, name_hl_group = self:add_highlights(node) - table.insert(icon.hl, icon_hl_group) - table.insert(name.hl, name_hl_group) + -- decorated node icon and name + local icon, name = self:icon_name_decorated(node) local line = self:format_line(indent_markers, arrows, icon, name, node) table.insert(self.lines, self:unwrap_highlighted_strings(line)) diff --git a/lua/nvim-tree/renderer/decorator/bookmarks.lua b/lua/nvim-tree/renderer/decorator/bookmarks.lua index c83290aa4ed..c661748ef21 100644 --- a/lua/nvim-tree/renderer/decorator/bookmarks.lua +++ b/lua/nvim-tree/renderer/decorator/bookmarks.lua @@ -1,21 +1,21 @@ local Decorator = require("nvim-tree.renderer.decorator") ----@class (exact) DecoratorBookmarks: Decorator ----@field icon HighlightedString? -local DecoratorBookmarks = Decorator:extend() +---@class (exact) BookmarkDecorator: Decorator +---@field private explorer Explorer +---@field private icon HighlightedString? +local BookmarkDecorator = Decorator:extend() ----@class DecoratorBookmarks ----@overload fun(explorer: DecoratorArgs): DecoratorBookmarks +---@class BookmarkDecorator +---@overload fun(args: DecoratorArgs): BookmarkDecorator ---@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", - }) +function BookmarkDecorator:new(args) + self.explorer = args.explorer + + self.enabled = true + self.highlight_range = self.explorer.opts.renderer.highlight_bookmarks or "none" + self.icon_placement = self.explorer.opts.renderer.icons.bookmarks_placement or "none" if self.explorer.opts.renderer.icons.show.bookmarks then self.icon = { @@ -28,8 +28,8 @@ end ---Bookmark icon: renderer.icons.show.bookmarks and node is marked ---@param node Node ----@return HighlightedString[]|nil icons -function DecoratorBookmarks:calculate_icons(node) +---@return HighlightedString[]? icons +function BookmarkDecorator:icons(node) if self.explorer.marks:get(node) then return { self.icon } end @@ -37,11 +37,11 @@ end ---Bookmark highlight: renderer.highlight_bookmarks and node is marked ---@param node Node ----@return string|nil group -function DecoratorBookmarks:calculate_highlight(node) - if self.range ~= "none" and self.explorer.marks:get(node) then +---@return string? highlight_group +function BookmarkDecorator:highlight_group(node) + if self.highlight_range ~= "none" and self.explorer.marks:get(node) then return "NvimTreeBookmarkHL" end end -return DecoratorBookmarks +return BookmarkDecorator diff --git a/lua/nvim-tree/renderer/decorator/copied.lua b/lua/nvim-tree/renderer/decorator/copied.lua index 4d54e1dc290..4c452e09e2f 100644 --- a/lua/nvim-tree/renderer/decorator/copied.lua +++ b/lua/nvim-tree/renderer/decorator/copied.lua @@ -1,29 +1,29 @@ local Decorator = require("nvim-tree.renderer.decorator") ----@class (exact) DecoratorCopied: Decorator -local DecoratorCopied = Decorator:extend() +---@class (exact) CopiedDecorator: Decorator +---@field private explorer Explorer +local CopiedDecorator = Decorator:extend() ----@class DecoratorCopied ----@overload fun(explorer: DecoratorArgs): DecoratorCopied +---@class CopiedDecorator +---@overload fun(args: DecoratorArgs): CopiedDecorator ---@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", - }) +function CopiedDecorator:new(args) + self.explorer = args.explorer + + self.enabled = true + self.highlight_range = self.explorer.opts.renderer.highlight_clipboard or "none" + self.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.range ~= "none" and self.explorer.clipboard:is_copied(node) then +---@return string? highlight_group +function CopiedDecorator:highlight_group(node) + if self.highlight_range ~= "none" and self.explorer.clipboard:is_copied(node) then return "NvimTreeCopiedHL" end end -return DecoratorCopied +return CopiedDecorator diff --git a/lua/nvim-tree/renderer/decorator/cut.lua b/lua/nvim-tree/renderer/decorator/cut.lua index 92eb1b8355c..8a212da5d68 100644 --- a/lua/nvim-tree/renderer/decorator/cut.lua +++ b/lua/nvim-tree/renderer/decorator/cut.lua @@ -1,29 +1,29 @@ local Decorator = require("nvim-tree.renderer.decorator") ----@class (exact) DecoratorCut: Decorator -local DecoratorCut = Decorator:extend() +---@class (exact) CutDecorator: Decorator +---@field private explorer Explorer +local CutDecorator = Decorator:extend() ----@class DecoratorCut ----@overload fun(explorer: DecoratorArgs): DecoratorCut +---@class CutDecorator +---@overload fun(args: DecoratorArgs): CutDecorator ---@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", - }) +function CutDecorator:new(args) + self.explorer = args.explorer + + self.enabled = true + self.highlight_range = self.explorer.opts.renderer.highlight_clipboard or "none" + self.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.range ~= "none" and self.explorer.clipboard:is_cut(node) then +---@return string? highlight_group +function CutDecorator:highlight_group(node) + if self.highlight_range ~= "none" and self.explorer.clipboard:is_cut(node) then return "NvimTreeCutHL" end end -return DecoratorCut +return CutDecorator diff --git a/lua/nvim-tree/renderer/decorator/diagnostics.lua b/lua/nvim-tree/renderer/decorator/diagnostics.lua index bcff66fafad..35f76109251 100644 --- a/lua/nvim-tree/renderer/decorator/diagnostics.lua +++ b/lua/nvim-tree/renderer/decorator/diagnostics.lua @@ -30,58 +30,54 @@ local ICON_KEYS = { ["hint"] = vim.diagnostic.severity.HINT, } ----@class (exact) DecoratorDiagnostics: Decorator ----@field icons HighlightedString[]? -local DecoratorDiagnostics = Decorator:extend() +---@class (exact) DiagnosticsDecorator: Decorator +---@field private explorer Explorer +---@field private diag_icons HighlightedString[]? +local DiagnosticsDecorator = Decorator:extend() ----@class DecoratorDiagnostics ----@overload fun(explorer: DecoratorArgs): DecoratorDiagnostics +---@class DiagnosticsDecorator +---@overload fun(args: DecoratorArgs): DiagnosticsDecorator ---@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", - }) +function DiagnosticsDecorator:new(args) + self.explorer = args.explorer - if not self.enabled then - return - end + self.enabled = true + self.highlight_range = self.explorer.opts.renderer.highlight_diagnostics or "none" + self.icon_placement = self.explorer.opts.renderer.icons.diagnostics_placement or "none" if self.explorer.opts.renderer.icons.show.diagnostics then - self.icons = {} + self.diag_icons = {} for name, sev in pairs(ICON_KEYS) do - self.icons[sev] = { + self.diag_icons[sev] = { str = self.explorer.opts.diagnostics.icons[name], hl = { HG_ICON[sev] }, } - self:define_sign(self.icons[sev]) + self:define_sign(self.diag_icons[sev]) end end end ---Diagnostic icon: diagnostics.enable, renderer.icons.show.diagnostics and node has status ---@param node Node ----@return HighlightedString[]|nil icons -function DecoratorDiagnostics:calculate_icons(node) - if node and self.enabled and self.icons then +---@return HighlightedString[]? icons +function DiagnosticsDecorator:icons(node) + if node and self.diag_icons then local diag_status = diagnostics.get_diag_status(node) local diag_value = diag_status and diag_status.value if diag_value then - return { self.icons[diag_value] } + return { self.diag_icons[diag_value] } end end end ---Diagnostic highlight: diagnostics.enable, renderer.highlight_diagnostics and node has status ---@param node Node ----@return string|nil group -function DecoratorDiagnostics:calculate_highlight(node) - if not node or not self.enabled or self.range == "none" then +---@return string? highlight_group +function DiagnosticsDecorator:highlight_group(node) + if self.highlight_range == "none" then return nil end @@ -106,4 +102,4 @@ function DecoratorDiagnostics:calculate_highlight(node) end end -return DecoratorDiagnostics +return DiagnosticsDecorator diff --git a/lua/nvim-tree/renderer/decorator/git.lua b/lua/nvim-tree/renderer/decorator/git.lua index 32e1aed07ef..c62ef00ae03 100644 --- a/lua/nvim-tree/renderer/decorator/git.lua +++ b/lua/nvim-tree/renderer/decorator/git.lua @@ -3,7 +3,7 @@ local notify = require("nvim-tree.notify") local Decorator = require("nvim-tree.renderer.decorator") local DirectoryNode = require("nvim-tree.node.directory") ----@class (exact) GitHighlightedString: HighlightedString +---@class (exact) GitHighlightedString: nvim_tree.api.HighlightedString ---@field ord number decreasing priority ---@alias GitStatusStrings "deleted" | "ignored" | "renamed" | "staged" | "unmerged" | "unstaged" | "untracked" @@ -12,31 +12,31 @@ local DirectoryNode = require("nvim-tree.node.directory") ---@alias GitIconsByXY table porcelain status ---@alias GitGlyphsByStatus table from opts ----@class (exact) DecoratorGit: Decorator ----@field file_hl_by_xy table? ----@field folder_hl_by_xy table? ----@field icons_by_status GitIconsByStatus? ----@field icons_by_xy GitIconsByXY? -local DecoratorGit = Decorator:extend() +---@class (exact) GitDecorator: Decorator +---@field private explorer Explorer +---@field private file_hl_by_xy table? +---@field private folder_hl_by_xy table? +---@field private icons_by_status GitIconsByStatus? +---@field private icons_by_xy GitIconsByXY? +local GitDecorator = Decorator:extend() ----@class DecoratorGit ----@overload fun(explorer: DecoratorArgs): DecoratorGit +---@class GitDecorator +---@overload fun(args: DecoratorArgs): GitDecorator ---@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", - }) +function GitDecorator:new(args) + self.explorer = args.explorer + + self.enabled = self.explorer.opts.git.enable + self.highlight_range = self.explorer.opts.renderer.highlight_git or "none" + self.icon_placement = self.explorer.opts.renderer.icons.git_placement or "none" if not self.enabled then return end - if self.range ~= "none" then + if self.highlight_range ~= "none" then self:build_file_folder_hl_by_xy() end @@ -51,7 +51,7 @@ function DecoratorGit:new(args) end ---@param glyphs GitGlyphsByStatus -function DecoratorGit:build_icons_by_status(glyphs) +function GitDecorator: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 } @@ -63,7 +63,7 @@ function DecoratorGit:build_icons_by_status(glyphs) end ---@param icons GitIconsByXY -function DecoratorGit:build_icons_by_xy(icons) +function GitDecorator:build_icons_by_xy(icons) self.icons_by_xy = { ["M "] = { icons.staged }, [" M"] = { icons.unstaged }, @@ -100,7 +100,7 @@ function DecoratorGit:build_icons_by_xy(icons) } end -function DecoratorGit:build_file_folder_hl_by_xy() +function GitDecorator:build_file_folder_hl_by_xy() self.file_hl_by_xy = { ["M "] = "NvimTreeGitFileStagedHL", ["C "] = "NvimTreeGitFileStagedHL", @@ -142,9 +142,9 @@ end ---Git icons: git.enable, renderer.icons.show.git and node has status ---@param node Node ----@return HighlightedString[]|nil modified icon -function DecoratorGit:calculate_icons(node) - if not node or not self.enabled or not self.icons_by_xy then +---@return HighlightedString[]? icons +function GitDecorator:icons(node) + if not self.icons_by_xy then return nil end @@ -159,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.range == "none" then + if self.highlight_range == "none" then notify.warn(string.format("Unrecognized git state '%s'", git_xy)) end return nil @@ -190,12 +190,12 @@ end ---Get the first icon as the sign if appropriate ---@param node Node ---@return string|nil name -function DecoratorGit:sign_name(node) +function GitDecorator:sign_name(node) if self.icon_placement ~= "signcolumn" then return end - local icons = self:calculate_icons(node) + local icons = self:icons(node) if icons and #icons > 0 then return icons[1].hl[1] end @@ -203,9 +203,9 @@ end ---Git highlight: git.enable, renderer.highlight_git and node has status ---@param node Node ----@return string|nil group -function DecoratorGit:calculate_highlight(node) - if not node or not self.enabled or self.range == "none" then +---@return string? highlight_group +function GitDecorator:highlight_group(node) + if self.highlight_range == "none" then return nil end @@ -221,4 +221,4 @@ function DecoratorGit:calculate_highlight(node) end end -return DecoratorGit +return GitDecorator diff --git a/lua/nvim-tree/renderer/decorator/hidden.lua b/lua/nvim-tree/renderer/decorator/hidden.lua index 291d9482774..87168ceb82b 100644 --- a/lua/nvim-tree/renderer/decorator/hidden.lua +++ b/lua/nvim-tree/renderer/decorator/hidden.lua @@ -1,22 +1,22 @@ local Decorator = require("nvim-tree.renderer.decorator") local DirectoryNode = require("nvim-tree.node.directory") ----@class (exact) DecoratorHidden: Decorator ----@field icon HighlightedString? -local DecoratorHidden = Decorator:extend() +---@class (exact) HiddenDecorator: Decorator +---@field private explorer Explorer +---@field private icon HighlightedString? +local HiddenDecorator = Decorator:extend() ----@class DecoratorHidden ----@overload fun(explorer: DecoratorArgs): DecoratorHidden +---@class HiddenDecorator +---@overload fun(args: DecoratorArgs): HiddenDecorator ---@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", - }) +function HiddenDecorator:new(args) + self.explorer = args.explorer + + self.enabled = true + self.highlight_range = self.explorer.opts.renderer.highlight_hidden or "none" + self.icon_placement = self.explorer.opts.renderer.icons.hidden_placement or "none" if self.explorer.opts.renderer.icons.show.hidden then self.icon = { @@ -29,18 +29,18 @@ end ---Hidden icon: renderer.icons.show.hidden and node starts with `.` (dotfile). ---@param node Node ----@return HighlightedString[]|nil icons -function DecoratorHidden:calculate_icons(node) - if self.enabled and node:is_dotfile() then +---@return HighlightedString[]? icons +function HiddenDecorator:icons(node) + if node:is_dotfile() then return { self.icon } end end ---Hidden highlight: renderer.highlight_hidden and node starts with `.` (dotfile). ---@param node Node ----@return string|nil group -function DecoratorHidden:calculate_highlight(node) - if not self.enabled or self.range == "none" or not node:is_dotfile() then +---@return string? highlight_group +function HiddenDecorator:highlight_group(node) + if self.highlight_range == "none" or not node:is_dotfile() then return nil end @@ -51,4 +51,4 @@ function DecoratorHidden:calculate_highlight(node) end end -return DecoratorHidden +return HiddenDecorator diff --git a/lua/nvim-tree/renderer/decorator/init.lua b/lua/nvim-tree/renderer/decorator/init.lua index f83b92dcae4..97ea697f668 100644 --- a/lua/nvim-tree/renderer/decorator/init.lua +++ b/lua/nvim-tree/renderer/decorator/init.lua @@ -1,48 +1,52 @@ local Class = require("nvim-tree.classic") ----@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 range DecoratorRange ----@field protected icon_placement DecoratorIconPlacement +---@field protected highlight_range nvim_tree.api.decorator.HighlightRange +---@field protected icon_placement nvim_tree.api.decorator.IconPlacement 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 +---Abstract icon override, optionally implemented +---@param node Node +---@return HighlightedString? icon_node +function Decorator:icon_node(node) + return self:nop(node) +end + +---Abstract icons, optionally implemented +---@protected +---@param node Node +---@return HighlightedString[]? icons +function Decorator:icons(node) + self:nop(node) +end +---Abstract highlight group, optionally implemented ---@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 +---@param node Node +---@return string? highlight_group +function Decorator:highlight_group(node) + self:nop(node) end ----Maybe highlight groups +---Maybe highlight groups for icon and name ---@param node Node ----@return string|nil icon highlight group ----@return string|nil name highlight group -function Decorator:groups_icon_name(node) +---@return string? icon highlight group +---@return string? name highlight group +function Decorator:highlight_group_icon_name(node) local icon_hl, name_hl - if self.enabled and self.range ~= "none" then - local hl = self:calculate_highlight(node) + if self.enabled and self.highlight_range ~= "none" then + local hl = self:highlight_group(node) - if self.range == "all" or self.range == "icon" then + if self.highlight_range == "all" or self.highlight_range == "icon" then icon_hl = hl end - if self.range == "all" or self.range == "name" then + if self.highlight_range == "all" or self.highlight_range == "name" then name_hl = hl end end @@ -52,13 +56,13 @@ end ---Maybe icon sign ---@param node Node ----@return string|nil name +---@return string? name function Decorator:sign_name(node) if not self.enabled or self.icon_placement ~= "signcolumn" then return end - local icons = self:calculate_icons(node) + local icons = self:icons(node) if icons and #icons > 0 then return icons[1].hl[1] end @@ -66,56 +70,40 @@ end ---Icons when "before" ---@param node Node ----@return HighlightedString[]|nil icons +---@return HighlightedString[]? icons function Decorator:icons_before(node) if not self.enabled or self.icon_placement ~= "before" then return end - return self:calculate_icons(node) + return self:icons(node) end ---Icons when "after" ---@param node Node ----@return HighlightedString[]|nil icons +---@return HighlightedString[]? icons function Decorator:icons_after(node) if not self.enabled or self.icon_placement ~= "after" then return end - return self:calculate_icons(node) + return self:icons(node) end ---Icons when "right_align" ---@param node Node ----@return HighlightedString[]|nil icons +---@return HighlightedString[]? icons function Decorator:icons_right_align(node) if not self.enabled or self.icon_placement ~= "right_align" then return end - return self:calculate_icons(node) -end - ----Maybe icons, optionally implemented ----@protected ----@param _ Node ----@return HighlightedString[]|nil icons -function Decorator:calculate_icons(_) - return nil -end - ----Maybe highlight group, optionally implemented ----@protected ----@param _ Node ----@return string|nil group -function Decorator:calculate_highlight(_) - return nil + return self:icons(node) end ---Define a sign ---@protected ----@param icon HighlightedString|nil +---@param icon HighlightedString? function Decorator:define_sign(icon) if icon and #icon.hl > 0 then local name = icon.hl[1] diff --git a/lua/nvim-tree/renderer/decorator/modified.lua b/lua/nvim-tree/renderer/decorator/modified.lua index 68dc322bde1..c182e300afc 100644 --- a/lua/nvim-tree/renderer/decorator/modified.lua +++ b/lua/nvim-tree/renderer/decorator/modified.lua @@ -3,26 +3,22 @@ local buffers = require("nvim-tree.buffers") local Decorator = require("nvim-tree.renderer.decorator") local DirectoryNode = require("nvim-tree.node.directory") ----@class (exact) DecoratorModified: Decorator ----@field icon HighlightedString? -local DecoratorModified = Decorator:extend() +---@class (exact) ModifiedDecorator: Decorator +---@field private explorer Explorer +---@field private icon HighlightedString? +local ModifiedDecorator = Decorator:extend() ----@class DecoratorModified ----@overload fun(explorer: DecoratorArgs): DecoratorModified +---@class ModifiedDecorator +---@overload fun(args: DecoratorArgs): ModifiedDecorator ---@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 +function ModifiedDecorator:new(args) + self.explorer = args.explorer + + self.enabled = true + self.highlight_range = self.explorer.opts.renderer.highlight_modified or "none" + self.icon_placement = self.explorer.opts.renderer.icons.modified_placement or "none" if self.explorer.opts.renderer.icons.show.modified then self.icon = { @@ -35,18 +31,18 @@ end ---Modified icon: modified.enable, renderer.icons.show.modified and node is modified ---@param node Node ----@return HighlightedString[]|nil icons -function DecoratorModified:calculate_icons(node) - if self.enabled and buffers.is_modified(node) then +---@return HighlightedString[]? icons +function ModifiedDecorator:icons(node) + if buffers.is_modified(node) then return { self.icon } end end ---Modified highlight: modified.enable, renderer.highlight_modified and node is modified ---@param node Node ----@return string|nil group -function DecoratorModified:calculate_highlight(node) - if not self.enabled or self.range == "none" or not buffers.is_modified(node) then +---@return string? highlight_group +function ModifiedDecorator:highlight_group(node) + if self.highlight_range == "none" or not buffers.is_modified(node) then return nil end @@ -57,4 +53,4 @@ function DecoratorModified:calculate_highlight(node) end end -return DecoratorModified +return ModifiedDecorator diff --git a/lua/nvim-tree/renderer/decorator/opened.lua b/lua/nvim-tree/renderer/decorator/opened.lua index 4f47e09727d..240dce4948a 100644 --- a/lua/nvim-tree/renderer/decorator/opened.lua +++ b/lua/nvim-tree/renderer/decorator/opened.lua @@ -2,31 +2,31 @@ local buffers = require("nvim-tree.buffers") local Decorator = require("nvim-tree.renderer.decorator") ----@class (exact) DecoratorOpened: Decorator ----@field icon HighlightedString|nil -local DecoratorOpened = Decorator:extend() +---@class (exact) OpenDecorator: Decorator +---@field private explorer Explorer +---@field private icon HighlightedString|nil +local OpenDecorator = Decorator:extend() ----@class DecoratorOpened ----@overload fun(explorer: DecoratorArgs): DecoratorOpened +---@class OpenDecorator +---@overload fun(args: DecoratorArgs): OpenDecorator ---@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", - }) +function OpenDecorator:new(args) + self.explorer = args.explorer + + self.enabled = true + self.highlight_range = self.explorer.opts.renderer.highlight_opened_files or "none" + self.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.range ~= "none" and buffers.is_opened(node) then +---@return string? highlight_group +function OpenDecorator:highlight_group(node) + if self.highlight_range ~= "none" and buffers.is_opened(node) then return "NvimTreeOpenedHL" end end -return DecoratorOpened +return OpenDecorator diff --git a/lua/nvim-tree/renderer/decorator/user.lua b/lua/nvim-tree/renderer/decorator/user.lua new file mode 100644 index 00000000000..df55f543936 --- /dev/null +++ b/lua/nvim-tree/renderer/decorator/user.lua @@ -0,0 +1,7 @@ +local Decorator = require("nvim-tree.renderer.decorator") + +---Exposed as nvim_tree.api.decorator.UserDecorator +---@class (exact) UserDecorator: Decorator +local UserDecorator = Decorator:extend() + +return UserDecorator