feat(#2948): add custom decorators, :help nvim-tree-decorators (#2996)

* feat(#2948): add UserDecorator, proof of concept

* feat(#2948): add UserDecorator, proof of concept

* feat(#2948): add UserDecorator, proof of concept

* feat(#2948): add UserDecorator

* feat(#2948): add UserDecorator

* feat(#2948): add UserDecorator

* feat(#2948): add Decorator node icon override

* feat(#2948): add nvim_tree.api.* node classes

* feat(#2948): extract _meta following nvim pattern

* feat(#2948): extract _meta following nvim pattern

* feat(#2948): add decorator registry and order

* feat(#2948): add decorator registry and order

* feat(#2948): tidy

* feat(#2948): document API

* feat(#2948): document API

* feat(#2948): document API

* feat(#2948): pass api nodes to user decorators

* feat(#2948): document API

* feat(#2948): use renderer.decorators to define order and register

* feat(#2948): tidy decorator args and complete documentation

* feat(#2948): decorator classes specified by prefix rather than suffix

* feat(#2948): improve doc

* feat(#2948): improve doc

* feat(#2948): improve doc

* feat(#2948): additional user decorator safety

* feat(#2948): create nvim_tree.api.decorator.UserDecorator class in API, add :extend

* feat(#2948): improve doc
This commit is contained in:
Alexander Courtis 2024-12-07 16:03:29 +11:00 committed by GitHub
parent ca7c4c33ca
commit 7a4ff1a516
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 571 additions and 322 deletions

View File

@ -13,7 +13,7 @@ check: luals
# subtasks # subtasks
# #
luacheck: 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 # --diagnosis-as-error does not function for workspace, hence we post-process the output
style-check: style-check:

View File

@ -52,14 +52,16 @@ CONTENTS *nvim-tree*
8.2 Highlight: Overhaul |nvim-tree-highlight-overhaul| 8.2 Highlight: Overhaul |nvim-tree-highlight-overhaul|
9. Events |nvim-tree-events| 9. Events |nvim-tree-events|
10. Prompts |nvim-tree-prompts| 10. Prompts |nvim-tree-prompts|
11. OS Specific Restrictions |nvim-tree-os-specific| 11. Decorators |nvim-tree-decorators|
12. Netrw |nvim-tree-netrw| 11.1 Decorator Example |nvim-tree-decorator-example|
13. Legacy |nvim-tree-legacy| 12. OS Specific Restrictions |nvim-tree-os-specific|
13.1 Legacy: Opts |nvim-tree-legacy-opts| 13. Netrw |nvim-tree-netrw|
13.2 Legacy: Highlight |nvim-tree-legacy-highlight| 14. Legacy |nvim-tree-legacy|
14. Index |nvim-tree-index| 14.1 Legacy: Opts |nvim-tree-legacy-opts|
14.1 Index: Opts |nvim-tree-index-opts| 14.2 Legacy: Highlight |nvim-tree-legacy-highlight|
14.2 Index: API |nvim-tree-index-api| 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* 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" }, special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" },
hidden_display = "none", hidden_display = "none",
symlink_destination = true, symlink_destination = true,
decorators = { "Git", "Open", "Hidden", "Modified", "Bookmark", "Diagnostics", "Copied", "Cut", },
highlight_git = "none", highlight_git = "none",
highlight_diagnostics = "none", highlight_diagnostics = "none",
highlight_opened_files = "none", highlight_opened_files = "none",
@ -842,9 +845,6 @@ Use nvim-tree in a floating window.
============================================================================== ==============================================================================
5.3 OPTS: RENDERER *nvim-tree-opts-renderer* 5.3 OPTS: RENDERER *nvim-tree-opts-renderer*
Highlight precedence, additive:
git < opened < modified < bookmarked < diagnostics < copied < cut
*nvim-tree.renderer.add_trailing* *nvim-tree.renderer.add_trailing*
Appends a trailing slash to folder names. Appends a trailing slash to folder names.
Type: `boolean`, Default: `false` 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. Whether to show the destination of the symlink.
Type: `boolean`, Default: `true` 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* *nvim-tree.renderer.highlight_git*
Enable highlight for git attributes using `NvimTreeGit*HL` highlight groups. Enable highlight for git attributes using `NvimTreeGit*HL` highlight groups.
Requires |nvim-tree.git.enable| Requires |nvim-tree.git.enable|
@ -996,9 +1012,6 @@ Configuration options for tree indent markers.
*nvim-tree.renderer.icons* *nvim-tree.renderer.icons*
Configuration options for icons. Configuration options for icons.
Icon order and sign column precedence:
git < hidden < modified < bookmarked < diagnostics
`renderer.icons.*_placement` options may be: `renderer.icons.*_placement` options may be:
- `"before"` : before file/folder, after the file/folders icons - `"before"` : before file/folder, after the file/folders icons
- `"after"` : after file/folder - `"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()| 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 Windows WSL and PowerShell
- Trash is synchronized - Trash is synchronized
@ -2767,7 +2863,7 @@ Windows WSL and PowerShell
issues or disable this feature. 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, |netrw| is a standard neovim plugin that is enabled by default. It provides,
amongst other functionality, a file/directory browser. 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` |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 Breaking refactors have been made however the legacy versions will be silently
migrated and used. migrated and used.
There are no plans to remove this migration. 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 Legacy options are translated to the current, making type and value changes as
needed. needed.
@ -2813,7 +2909,7 @@ needed.
`renderer.icons.webdev_colors` |nvim-tree.renderer.icons.web_devicons.file.color| `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 Legacy highlight group are still obeyed when they are defined and the current
highlight group is not, hard linking as follows: > highlight group is not, hard linking as follows: >
@ -2862,10 +2958,10 @@ highlight group is not, hard linking as follows: >
NvimTreeLspDiagnosticsHintFolderText NvimTreeDiagnosticHintFolderHL 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|
|nvim-tree.actions.change_dir.enable| |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.prefer_startup_root|
|nvim-tree.reload_on_bufenter| |nvim-tree.reload_on_bufenter|
|nvim-tree.renderer.add_trailing| |nvim-tree.renderer.add_trailing|
|nvim-tree.renderer.decorators|
|nvim-tree.renderer.full_name| |nvim-tree.renderer.full_name|
|nvim-tree.renderer.group_empty| |nvim-tree.renderer.group_empty|
|nvim-tree.renderer.hidden_display| |nvim-tree.renderer.hidden_display|
@ -3033,7 +3130,7 @@ highlight group is not, hard linking as follows: >
|nvim-tree.view.width.padding| |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.commands.get()|
|nvim-tree-api.config.mappings.default_on_attach()| |nvim-tree-api.config.mappings.default_on_attach()|

View File

@ -284,6 +284,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" }, special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" },
hidden_display = "none", hidden_display = "none",
symlink_destination = true, symlink_destination = true,
decorators = { "Git", "Open", "Hidden", "Modified", "Bookmark", "Diagnostics", "Copied", "Cut", },
highlight_git = "none", highlight_git = "none",
highlight_diagnostics = "none", highlight_diagnostics = "none",
highlight_opened_files = "none", highlight_opened_files = "none",

View File

@ -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[]

View File

@ -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

View File

@ -11,6 +11,7 @@ local notify = require("nvim-tree.notify")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
local FileLinkNode = require("nvim-tree.node.file-link") local FileLinkNode = require("nvim-tree.node.file-link")
local RootNode = require("nvim-tree.node.root") local RootNode = require("nvim-tree.node.root")
local UserDecorator = require("nvim-tree.renderer.decorator.user")
local Api = { local Api = {
tree = {}, tree = {},
@ -39,6 +40,7 @@ local Api = {
}, },
commands = {}, commands = {},
diagnostics = {}, diagnostics = {},
decorator = {},
} }
---Print error when setup not called. ---Print error when setup not called.
@ -311,4 +313,9 @@ Api.commands.get = wrap(function()
return require("nvim-tree.commands").get() return require("nvim-tree.commands").get()
end) 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 return Api

View File

@ -530,7 +530,7 @@ function Explorer:place_cursor_on_node()
end end
---Api.tree.get_nodes ---Api.tree.get_nodes
---@return Node ---@return nvim_tree.api.Node
function Explorer:get_nodes() function Explorer:get_nodes()
return self:clone() return self:clone()
end end

View File

@ -73,9 +73,10 @@ function DirectoryLinkNode:highlighted_name()
end end
---Create a sanitized partial copy of a node, populating children recursively. ---Create a sanitized partial copy of a node, populating children recursively.
---@return DirectoryLinkNode cloned ---@param api_nodes table<number, nvim_tree.api.Node>? optional map of uids to api node to populate
function DirectoryLinkNode:clone() ---@return nvim_tree.api.DirectoryLinkNode cloned
local clone = DirectoryNode.clone(self) --[[@as DirectoryLinkNode]] function DirectoryLinkNode:clone(api_nodes)
local clone = DirectoryNode.clone(self, api_nodes) --[[@as nvim_tree.api.DirectoryLinkNode]]
clone.link_to = self.link_to clone.link_to = self.link_to
clone.fs_stat_target = self.fs_stat_target clone.fs_stat_target = self.fs_stat_target

View File

@ -271,18 +271,20 @@ function DirectoryNode:highlighted_name()
end end
---Create a sanitized partial copy of a node, populating children recursively. ---Create a sanitized partial copy of a node, populating children recursively.
---@return DirectoryNode cloned ---@param api_nodes table<number, nvim_tree.api.Node>? optional map of uids to api node to populate
function DirectoryNode:clone() ---@return nvim_tree.api.DirectoryNode cloned
local clone = Node.clone(self) --[[@as DirectoryNode]] function DirectoryNode:clone(api_nodes)
local clone = Node.clone(self, api_nodes) --[[@as nvim_tree.api.DirectoryNode]]
clone.has_children = self.has_children clone.has_children = self.has_children
clone.group_next = nil
clone.nodes = {} clone.nodes = {}
clone.open = self.open clone.open = self.open
clone.hidden_stats = nil
local clone_child
for _, child in ipairs(self.nodes) do 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 end
return clone return clone

View File

@ -58,9 +58,10 @@ function FileLinkNode:highlighted_name()
end end
---Create a sanitized partial copy of a node ---Create a sanitized partial copy of a node
---@return FileLinkNode cloned ---@param api_nodes table<number, nvim_tree.api.Node>? optional map of uids to api node to populate
function FileLinkNode:clone() ---@return nvim_tree.api.FileLinkNode cloned
local clone = FileNode.clone(self) --[[@as FileLinkNode]] function FileLinkNode:clone(api_nodes)
local clone = FileNode.clone(self, api_nodes) --[[@as nvim_tree.api.FileLinkNode]]
clone.link_to = self.link_to clone.link_to = self.link_to
clone.fs_stat_target = self.fs_stat_target clone.fs_stat_target = self.fs_stat_target

View File

@ -94,9 +94,10 @@ function FileNode:highlighted_name()
end end
---Create a sanitized partial copy of a node ---Create a sanitized partial copy of a node
---@return FileNode cloned ---@param api_nodes table<number, nvim_tree.api.Node>? optional map of uids to api node to populate
function FileNode:clone() ---@return nvim_tree.api.FileNode cloned
local clone = Node.clone(self) --[[@as FileNode]] function FileNode:clone(api_nodes)
local clone = Node.clone(self, api_nodes) --[[@as nvim_tree.api.FileNode]]
clone.extension = self.extension clone.extension = self.extension

View File

@ -2,6 +2,7 @@ local Class = require("nvim-tree.classic")
---Abstract Node class. ---Abstract Node class.
---@class (exact) 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 type "file" | "directory" | "link" uv.fs_stat.result.type
---@field explorer Explorer ---@field explorer Explorer
---@field absolute_path string ---@field absolute_path string
@ -25,6 +26,7 @@ local Node = Class:extend()
---@protected ---@protected
---@param args NodeArgs ---@param args NodeArgs
function Node:new(args) function Node:new(args)
self.uid_node = vim.loop.hrtime()
self.explorer = args.explorer self.explorer = args.explorer
self.absolute_path = args.absolute_path self.absolute_path = args.absolute_path
self.executable = false self.executable = false
@ -112,21 +114,19 @@ end
---Highlighted name for the node ---Highlighted name for the node
---Empty for base Node ---Empty for base Node
---@return HighlightedString icon ---@return HighlightedString name
function Node:highlighted_name() function Node:highlighted_name()
return self:highlighted_name_empty() return self:highlighted_name_empty()
end end
---Create a sanitized partial copy of a node, populating children recursively. ---Create a sanitized partial copy of a node, populating children recursively.
---@return Node cloned ---@param api_nodes table<number, nvim_tree.api.Node>? optional map of uids to api node to populate
function Node:clone() ---@return nvim_tree.api.Node cloned
---@type Explorer function Node:clone(api_nodes)
local explorer_placeholder = nil ---@type nvim_tree.api.Node
---@type Node
local clone = { local clone = {
uid_node = self.uid_node,
type = self.type, type = self.type,
explorer = explorer_placeholder,
absolute_path = self.absolute_path, absolute_path = self.absolute_path,
executable = self.executable, executable = self.executable,
fs_stat = self.fs_stat, fs_stat = self.fs_stat,
@ -134,10 +134,13 @@ function Node:clone()
hidden = self.hidden, hidden = self.hidden,
name = self.name, name = self.name,
parent = nil, parent = nil,
diag_status = nil, diag_severity = self.diag_status and self.diag_status.value or nil,
is_dot = self.is_dot,
} }
if api_nodes then
api_nodes[self.uid_node] = clone
end
return clone return clone
end end

View File

@ -2,7 +2,7 @@ local Class = require("nvim-tree.classic")
---@class (exact) LinkNode: Class ---@class (exact) LinkNode: Class
---@field link_to string ---@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() local LinkNode = Class:extend()
---@class (exact) LinkNodeArgs: NodeArgs ---@class (exact) LinkNodeArgs: NodeArgs

View File

@ -22,4 +22,13 @@ function RootNode:destroy()
DirectoryNode.destroy(self) DirectoryNode.destroy(self)
end end
---Create a sanitized partial copy of a node, populating children recursively.
---@param api_nodes table<number, nvim_tree.api.Node>? 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 return RootNode

View File

@ -3,22 +3,35 @@ local utils = require("nvim-tree.utils")
local view = require("nvim-tree.view") local view = require("nvim-tree.view")
local Class = require("nvim-tree.classic") local Class = require("nvim-tree.classic")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
local DecoratorBookmarks = require("nvim-tree.renderer.decorator.bookmarks") local BookmarkDecorator = require("nvim-tree.renderer.decorator.bookmarks")
local DecoratorCopied = require("nvim-tree.renderer.decorator.copied") local CopiedDecorator = require("nvim-tree.renderer.decorator.copied")
local DecoratorCut = require("nvim-tree.renderer.decorator.cut") local CutDecorator = require("nvim-tree.renderer.decorator.cut")
local DecoratorDiagnostics = require("nvim-tree.renderer.decorator.diagnostics") local DiagnosticsDecorator = require("nvim-tree.renderer.decorator.diagnostics")
local DecoratorGit = require("nvim-tree.renderer.decorator.git") local GitDecorator = require("nvim-tree.renderer.decorator.git")
local DecoratorModified = require("nvim-tree.renderer.decorator.modified") local HiddenDecorator = require("nvim-tree.renderer.decorator.hidden")
local DecoratorHidden = require("nvim-tree.renderer.decorator.hidden") local ModifiedDecorator = require("nvim-tree.renderer.decorator.modified")
local DecoratorOpened = require("nvim-tree.renderer.decorator.opened") local OpenDecorator = require("nvim-tree.renderer.decorator.opened")
local UserDecorator = require("nvim-tree.renderer.decorator.user")
local pad = require("nvim-tree.renderer.components.padding") local pad = require("nvim-tree.renderer.components.padding")
---@class (exact) HighlightedString ---@alias HighlightedString nvim_tree.api.HighlightedString
---@field str string
---@field hl string[] -- Builtin Decorators
---@type table<nvim_tree.api.decorator.Name, Decorator>
local BUILTIN_DECORATORS = {
Git = GitDecorator,
Open = OpenDecorator,
Hidden = HiddenDecorator,
Modified = ModifiedDecorator,
Bookmark = BookmarkDecorator,
Diagnostics = DiagnosticsDecorator,
Copied = CopiedDecorator,
Cut = CutDecorator,
}
---@class (exact) AddHighlightArgs ---@class (exact) AddHighlightArgs
---@field group string[] ---@field group string[]
@ -39,6 +52,7 @@ local pad = require("nvim-tree.renderer.components.padding")
---@field private markers boolean[] indent markers ---@field private markers boolean[] indent markers
---@field private decorators Decorator[] ---@field private decorators Decorator[]
---@field private hidden_display fun(node: Node): string|nil ---@field private hidden_display fun(node: Node): string|nil
---@field private api_nodes table<number, nvim_tree.api.Node>? optional map of uids to api node for user decorators
local Builder = Class:extend() local Builder = Class:extend()
---@class Builder ---@class Builder
@ -60,18 +74,30 @@ function Builder:new(args)
self.signs = {} self.signs = {}
self.extmarks = {} self.extmarks = {}
self.virtual_lines = {} self.virtual_lines = {}
self.decorators = { 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.hidden_display = Builder:setup_hidden_display_function(self.explorer.opts) 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 end
---Insert ranged highlight groups into self.highlights ---Insert ranged highlight groups into self.highlights
@ -131,22 +157,25 @@ function Builder:format_line(indent_markers, arrows, icon, name, node)
end end
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 } local line = { indent_markers, arrows }
add_to_end(line, { icon }) add_to_end(line, { icon })
for i = #self.decorators, 1, -1 do for _, d in ipairs(self.decorators) do
add_to_end(line, self.decorators[i]:icons_before(node)) add_to_end(line, d:icons_before(not d:is(UserDecorator) and node or api_node))
end end
add_to_end(line, { name }) add_to_end(line, { name })
for i = #self.decorators, 1, -1 do for _, d in ipairs(self.decorators) do
add_to_end(line, self.decorators[i]:icons_after(node)) add_to_end(line, d:icons_after(not d:is(UserDecorator) and node or api_node))
end end
local rights = {} local rights = {}
for i = #self.decorators, 1, -1 do for _, d in ipairs(self.decorators) do
add_to_end(rights, self.decorators[i]:icons_right_align(node)) add_to_end(rights, d:icons_right_align(not d:is(UserDecorator) and node or api_node))
end end
if #rights > 0 then if #rights > 0 then
self.extmarks[self.index] = rights self.extmarks[self.index] = rights
@ -158,10 +187,14 @@ end
---@private ---@private
---@param node Node ---@param node Node
function Builder:build_signs(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 -- first in priority order
local sign_name local d, sign_name
for _, d in ipairs(self.decorators) do for i = #self.decorators, 1, -1 do
sign_name = d:sign_name(node) d = self.decorators[i]
sign_name = d:sign_name(not d:is(UserDecorator) and node or api_node)
if sign_name then if sign_name then
self.signs[self.index] = sign_name self.signs[self.index] = sign_name
break break
@ -197,43 +230,50 @@ function Builder:create_combined_group(groups)
return combined_name return combined_name
end end
---Calculate highlight group for icon and name. A combined highlight group will be created ---Calculate decorated icon and name for a node.
---when there is more than one highlight. ---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. ---A highlight group is always calculated and upserted for the case of highlights changing.
---@private ---@private
---@param node Node ---@param node Node
---@return string|nil icon_hl_group ---@return HighlightedString icon
---@return string|nil name_hl_group ---@return HighlightedString name
function Builder:add_highlights(node) function Builder:icon_name_decorated(node)
-- result -- use the api node for user decorators
local icon_hl_group, name_hl_group local api_node = self.api_nodes and self.api_nodes[node.uid_node] --[[@as Node]]
-- calculate all groups -- base case
local icon = node:highlighted_icon()
local name = node:highlighted_name()
-- calculate node icon and all decorated highlight groups
local icon_groups = {} local icon_groups = {}
local name_groups = {} local name_groups = {}
local d, icon, name local hl_icon, hl_name
for i = #self.decorators, 1, -1 do for _, d in ipairs(self.decorators) do
d = self.decorators[i] -- maybe overridde icon
icon, name = d:groups_icon_name(node) icon = d:icon_node((not d:is(UserDecorator) and node or api_node)) or icon
table.insert(icon_groups, icon)
table.insert(name_groups, name) 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 end
-- one or many icon groups -- add one or many icon groups
if #icon_groups > 1 then 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 else
icon_hl_group = icon_groups[1] table.insert(icon.hl, icon_groups[1])
end end
-- one or many name groups -- add one or many name groups
if #name_groups > 1 then 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 else
name_hl_group = name_groups[1] table.insert(name.hl, name_groups[1])
end end
return icon_hl_group, name_hl_group return icon, name
end end
---Insert node line into self.lines, calling Builder:build_lines for each directory ---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 indent_markers = pad.get_indent_markers(self.depth, idx, num_children, node, self.markers)
local arrows = pad.get_arrows(node) local arrows = pad.get_arrows(node)
-- main components -- decorated node icon and name
local icon, name = node:highlighted_icon(), node:highlighted_name() local icon, name = self:icon_name_decorated(node)
-- 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)
local line = self:format_line(indent_markers, arrows, icon, name, node) local line = self:format_line(indent_markers, arrows, icon, name, node)
table.insert(self.lines, self:unwrap_highlighted_strings(line)) table.insert(self.lines, self:unwrap_highlighted_strings(line))

View File

@ -1,21 +1,21 @@
local Decorator = require("nvim-tree.renderer.decorator") local Decorator = require("nvim-tree.renderer.decorator")
---@class (exact) DecoratorBookmarks: Decorator ---@class (exact) BookmarkDecorator: Decorator
---@field icon HighlightedString? ---@field private explorer Explorer
local DecoratorBookmarks = Decorator:extend() ---@field private icon HighlightedString?
local BookmarkDecorator = Decorator:extend()
---@class DecoratorBookmarks ---@class BookmarkDecorator
---@overload fun(explorer: DecoratorArgs): DecoratorBookmarks ---@overload fun(args: DecoratorArgs): BookmarkDecorator
---@protected ---@protected
---@param args DecoratorArgs ---@param args DecoratorArgs
function DecoratorBookmarks:new(args) function BookmarkDecorator:new(args)
Decorator.new(self, { self.explorer = args.explorer
explorer = args.explorer,
enabled = true, self.enabled = true
hl_pos = args.explorer.opts.renderer.highlight_bookmarks or "none", self.highlight_range = self.explorer.opts.renderer.highlight_bookmarks or "none"
icon_placement = args.explorer.opts.renderer.icons.bookmarks_placement or "none", self.icon_placement = self.explorer.opts.renderer.icons.bookmarks_placement or "none"
})
if self.explorer.opts.renderer.icons.show.bookmarks then if self.explorer.opts.renderer.icons.show.bookmarks then
self.icon = { self.icon = {
@ -28,8 +28,8 @@ end
---Bookmark icon: renderer.icons.show.bookmarks and node is marked ---Bookmark icon: renderer.icons.show.bookmarks and node is marked
---@param node Node ---@param node Node
---@return HighlightedString[]|nil icons ---@return HighlightedString[]? icons
function DecoratorBookmarks:calculate_icons(node) function BookmarkDecorator:icons(node)
if self.explorer.marks:get(node) then if self.explorer.marks:get(node) then
return { self.icon } return { self.icon }
end end
@ -37,11 +37,11 @@ end
---Bookmark highlight: renderer.highlight_bookmarks and node is marked ---Bookmark highlight: renderer.highlight_bookmarks and node is marked
---@param node Node ---@param node Node
---@return string|nil group ---@return string? highlight_group
function DecoratorBookmarks:calculate_highlight(node) function BookmarkDecorator:highlight_group(node)
if self.range ~= "none" and self.explorer.marks:get(node) then if self.highlight_range ~= "none" and self.explorer.marks:get(node) then
return "NvimTreeBookmarkHL" return "NvimTreeBookmarkHL"
end end
end end
return DecoratorBookmarks return BookmarkDecorator

View File

@ -1,29 +1,29 @@
local Decorator = require("nvim-tree.renderer.decorator") local Decorator = require("nvim-tree.renderer.decorator")
---@class (exact) DecoratorCopied: Decorator ---@class (exact) CopiedDecorator: Decorator
local DecoratorCopied = Decorator:extend() ---@field private explorer Explorer
local CopiedDecorator = Decorator:extend()
---@class DecoratorCopied ---@class CopiedDecorator
---@overload fun(explorer: DecoratorArgs): DecoratorCopied ---@overload fun(args: DecoratorArgs): CopiedDecorator
---@protected ---@protected
---@param args DecoratorArgs ---@param args DecoratorArgs
function DecoratorCopied:new(args) function CopiedDecorator:new(args)
Decorator.new(self, { self.explorer = args.explorer
explorer = args.explorer,
enabled = true, self.enabled = true
hl_pos = args.explorer.opts.renderer.highlight_clipboard or "none", self.highlight_range = self.explorer.opts.renderer.highlight_clipboard or "none"
icon_placement = "none", self.icon_placement = "none"
})
end end
---Copied highlight: renderer.highlight_clipboard and node is copied ---Copied highlight: renderer.highlight_clipboard and node is copied
---@param node Node ---@param node Node
---@return string|nil group ---@return string? highlight_group
function DecoratorCopied:calculate_highlight(node) function CopiedDecorator:highlight_group(node)
if self.range ~= "none" and self.explorer.clipboard:is_copied(node) then if self.highlight_range ~= "none" and self.explorer.clipboard:is_copied(node) then
return "NvimTreeCopiedHL" return "NvimTreeCopiedHL"
end end
end end
return DecoratorCopied return CopiedDecorator

View File

@ -1,29 +1,29 @@
local Decorator = require("nvim-tree.renderer.decorator") local Decorator = require("nvim-tree.renderer.decorator")
---@class (exact) DecoratorCut: Decorator ---@class (exact) CutDecorator: Decorator
local DecoratorCut = Decorator:extend() ---@field private explorer Explorer
local CutDecorator = Decorator:extend()
---@class DecoratorCut ---@class CutDecorator
---@overload fun(explorer: DecoratorArgs): DecoratorCut ---@overload fun(args: DecoratorArgs): CutDecorator
---@protected ---@protected
---@param args DecoratorArgs ---@param args DecoratorArgs
function DecoratorCut:new(args) function CutDecorator:new(args)
Decorator.new(self, { self.explorer = args.explorer
explorer = args.explorer,
enabled = true, self.enabled = true
hl_pos = args.explorer.opts.renderer.highlight_clipboard or "none", self.highlight_range = self.explorer.opts.renderer.highlight_clipboard or "none"
icon_placement = "none", self.icon_placement = "none"
})
end end
---Cut highlight: renderer.highlight_clipboard and node is cut ---Cut highlight: renderer.highlight_clipboard and node is cut
---@param node Node ---@param node Node
---@return string|nil group ---@return string? highlight_group
function DecoratorCut:calculate_highlight(node) function CutDecorator:highlight_group(node)
if self.range ~= "none" and self.explorer.clipboard:is_cut(node) then if self.highlight_range ~= "none" and self.explorer.clipboard:is_cut(node) then
return "NvimTreeCutHL" return "NvimTreeCutHL"
end end
end end
return DecoratorCut return CutDecorator

View File

@ -30,58 +30,54 @@ local ICON_KEYS = {
["hint"] = vim.diagnostic.severity.HINT, ["hint"] = vim.diagnostic.severity.HINT,
} }
---@class (exact) DecoratorDiagnostics: Decorator ---@class (exact) DiagnosticsDecorator: Decorator
---@field icons HighlightedString[]? ---@field private explorer Explorer
local DecoratorDiagnostics = Decorator:extend() ---@field private diag_icons HighlightedString[]?
local DiagnosticsDecorator = Decorator:extend()
---@class DecoratorDiagnostics ---@class DiagnosticsDecorator
---@overload fun(explorer: DecoratorArgs): DecoratorDiagnostics ---@overload fun(args: DecoratorArgs): DiagnosticsDecorator
---@protected ---@protected
---@param args DecoratorArgs ---@param args DecoratorArgs
function DecoratorDiagnostics:new(args) function DiagnosticsDecorator:new(args)
Decorator.new(self, { self.explorer = args.explorer
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 self.enabled = true
return self.highlight_range = self.explorer.opts.renderer.highlight_diagnostics or "none"
end self.icon_placement = self.explorer.opts.renderer.icons.diagnostics_placement or "none"
if self.explorer.opts.renderer.icons.show.diagnostics then if self.explorer.opts.renderer.icons.show.diagnostics then
self.icons = {} self.diag_icons = {}
for name, sev in pairs(ICON_KEYS) do for name, sev in pairs(ICON_KEYS) do
self.icons[sev] = { self.diag_icons[sev] = {
str = self.explorer.opts.diagnostics.icons[name], str = self.explorer.opts.diagnostics.icons[name],
hl = { HG_ICON[sev] }, hl = { HG_ICON[sev] },
} }
self:define_sign(self.icons[sev]) self:define_sign(self.diag_icons[sev])
end end
end end
end end
---Diagnostic icon: diagnostics.enable, renderer.icons.show.diagnostics and node has status ---Diagnostic icon: diagnostics.enable, renderer.icons.show.diagnostics and node has status
---@param node Node ---@param node Node
---@return HighlightedString[]|nil icons ---@return HighlightedString[]? icons
function DecoratorDiagnostics:calculate_icons(node) function DiagnosticsDecorator:icons(node)
if node and self.enabled and self.icons then if node and self.diag_icons then
local diag_status = diagnostics.get_diag_status(node) local diag_status = diagnostics.get_diag_status(node)
local diag_value = diag_status and diag_status.value local diag_value = diag_status and diag_status.value
if diag_value then if diag_value then
return { self.icons[diag_value] } return { self.diag_icons[diag_value] }
end end
end end
end end
---Diagnostic highlight: diagnostics.enable, renderer.highlight_diagnostics and node has status ---Diagnostic highlight: diagnostics.enable, renderer.highlight_diagnostics and node has status
---@param node Node ---@param node Node
---@return string|nil group ---@return string? highlight_group
function DecoratorDiagnostics:calculate_highlight(node) function DiagnosticsDecorator:highlight_group(node)
if not node or not self.enabled or self.range == "none" then if self.highlight_range == "none" then
return nil return nil
end end
@ -106,4 +102,4 @@ function DecoratorDiagnostics:calculate_highlight(node)
end end
end end
return DecoratorDiagnostics return DiagnosticsDecorator

View File

@ -3,7 +3,7 @@ local notify = require("nvim-tree.notify")
local Decorator = require("nvim-tree.renderer.decorator") local Decorator = require("nvim-tree.renderer.decorator")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
---@class (exact) GitHighlightedString: HighlightedString ---@class (exact) GitHighlightedString: nvim_tree.api.HighlightedString
---@field ord number decreasing priority ---@field ord number decreasing priority
---@alias GitStatusStrings "deleted" | "ignored" | "renamed" | "staged" | "unmerged" | "unstaged" | "untracked" ---@alias GitStatusStrings "deleted" | "ignored" | "renamed" | "staged" | "unmerged" | "unstaged" | "untracked"
@ -12,31 +12,31 @@ local DirectoryNode = require("nvim-tree.node.directory")
---@alias GitIconsByXY table<GitXY, GitHighlightedString[]> porcelain status ---@alias GitIconsByXY table<GitXY, GitHighlightedString[]> porcelain status
---@alias GitGlyphsByStatus table<GitStatusStrings, string> from opts ---@alias GitGlyphsByStatus table<GitStatusStrings, string> from opts
---@class (exact) DecoratorGit: Decorator ---@class (exact) GitDecorator: Decorator
---@field file_hl_by_xy table<GitXY, string>? ---@field private explorer Explorer
---@field folder_hl_by_xy table<GitXY, string>? ---@field private file_hl_by_xy table<GitXY, string>?
---@field icons_by_status GitIconsByStatus? ---@field private folder_hl_by_xy table<GitXY, string>?
---@field icons_by_xy GitIconsByXY? ---@field private icons_by_status GitIconsByStatus?
local DecoratorGit = Decorator:extend() ---@field private icons_by_xy GitIconsByXY?
local GitDecorator = Decorator:extend()
---@class DecoratorGit ---@class GitDecorator
---@overload fun(explorer: DecoratorArgs): DecoratorGit ---@overload fun(args: DecoratorArgs): GitDecorator
---@protected ---@protected
---@param args DecoratorArgs ---@param args DecoratorArgs
function DecoratorGit:new(args) function GitDecorator:new(args)
Decorator.new(self, { self.explorer = args.explorer
explorer = args.explorer,
enabled = args.explorer.opts.git.enable, self.enabled = self.explorer.opts.git.enable
hl_pos = args.explorer.opts.renderer.highlight_git or "none", self.highlight_range = self.explorer.opts.renderer.highlight_git or "none"
icon_placement = args.explorer.opts.renderer.icons.git_placement or "none", self.icon_placement = self.explorer.opts.renderer.icons.git_placement or "none"
})
if not self.enabled then if not self.enabled then
return return
end end
if self.range ~= "none" then if self.highlight_range ~= "none" then
self:build_file_folder_hl_by_xy() self:build_file_folder_hl_by_xy()
end end
@ -51,7 +51,7 @@ function DecoratorGit:new(args)
end end
---@param glyphs GitGlyphsByStatus ---@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 = {}
self.icons_by_status.staged = { str = glyphs.staged, hl = { "NvimTreeGitStagedIcon" }, ord = 1 } self.icons_by_status.staged = { str = glyphs.staged, hl = { "NvimTreeGitStagedIcon" }, ord = 1 }
self.icons_by_status.unstaged = { str = glyphs.unstaged, hl = { "NvimTreeGitDirtyIcon" }, ord = 2 } self.icons_by_status.unstaged = { str = glyphs.unstaged, hl = { "NvimTreeGitDirtyIcon" }, ord = 2 }
@ -63,7 +63,7 @@ function DecoratorGit:build_icons_by_status(glyphs)
end end
---@param icons GitIconsByXY ---@param icons GitIconsByXY
function DecoratorGit:build_icons_by_xy(icons) function GitDecorator:build_icons_by_xy(icons)
self.icons_by_xy = { self.icons_by_xy = {
["M "] = { icons.staged }, ["M "] = { icons.staged },
[" M"] = { icons.unstaged }, [" M"] = { icons.unstaged },
@ -100,7 +100,7 @@ function DecoratorGit:build_icons_by_xy(icons)
} }
end end
function DecoratorGit:build_file_folder_hl_by_xy() function GitDecorator:build_file_folder_hl_by_xy()
self.file_hl_by_xy = { self.file_hl_by_xy = {
["M "] = "NvimTreeGitFileStagedHL", ["M "] = "NvimTreeGitFileStagedHL",
["C "] = "NvimTreeGitFileStagedHL", ["C "] = "NvimTreeGitFileStagedHL",
@ -142,9 +142,9 @@ end
---Git icons: git.enable, renderer.icons.show.git and node has status ---Git icons: git.enable, renderer.icons.show.git and node has status
---@param node Node ---@param node Node
---@return HighlightedString[]|nil modified icon ---@return HighlightedString[]? icons
function DecoratorGit:calculate_icons(node) function GitDecorator:icons(node)
if not node or not self.enabled or not self.icons_by_xy then if not self.icons_by_xy then
return nil return nil
end end
@ -159,7 +159,7 @@ function DecoratorGit:calculate_icons(node)
for _, s in pairs(git_xy) do for _, s in pairs(git_xy) do
local icons = self.icons_by_xy[s] local icons = self.icons_by_xy[s]
if not icons then if not icons then
if self.range == "none" then if self.highlight_range == "none" then
notify.warn(string.format("Unrecognized git state '%s'", git_xy)) notify.warn(string.format("Unrecognized git state '%s'", git_xy))
end end
return nil return nil
@ -190,12 +190,12 @@ end
---Get the first icon as the sign if appropriate ---Get the first icon as the sign if appropriate
---@param node Node ---@param node Node
---@return string|nil name ---@return string|nil name
function DecoratorGit:sign_name(node) function GitDecorator:sign_name(node)
if self.icon_placement ~= "signcolumn" then if self.icon_placement ~= "signcolumn" then
return return
end end
local icons = self:calculate_icons(node) local icons = self:icons(node)
if icons and #icons > 0 then if icons and #icons > 0 then
return icons[1].hl[1] return icons[1].hl[1]
end end
@ -203,9 +203,9 @@ end
---Git highlight: git.enable, renderer.highlight_git and node has status ---Git highlight: git.enable, renderer.highlight_git and node has status
---@param node Node ---@param node Node
---@return string|nil group ---@return string? highlight_group
function DecoratorGit:calculate_highlight(node) function GitDecorator:highlight_group(node)
if not node or not self.enabled or self.range == "none" then if self.highlight_range == "none" then
return nil return nil
end end
@ -221,4 +221,4 @@ function DecoratorGit:calculate_highlight(node)
end end
end end
return DecoratorGit return GitDecorator

View File

@ -1,22 +1,22 @@
local Decorator = require("nvim-tree.renderer.decorator") local Decorator = require("nvim-tree.renderer.decorator")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
---@class (exact) DecoratorHidden: Decorator ---@class (exact) HiddenDecorator: Decorator
---@field icon HighlightedString? ---@field private explorer Explorer
local DecoratorHidden = Decorator:extend() ---@field private icon HighlightedString?
local HiddenDecorator = Decorator:extend()
---@class DecoratorHidden ---@class HiddenDecorator
---@overload fun(explorer: DecoratorArgs): DecoratorHidden ---@overload fun(args: DecoratorArgs): HiddenDecorator
---@protected ---@protected
---@param args DecoratorArgs ---@param args DecoratorArgs
function DecoratorHidden:new(args) function HiddenDecorator:new(args)
Decorator.new(self, { self.explorer = args.explorer
explorer = args.explorer,
enabled = true, self.enabled = true
hl_pos = args.explorer.opts.renderer.highlight_hidden or "none", self.highlight_range = self.explorer.opts.renderer.highlight_hidden or "none"
icon_placement = args.explorer.opts.renderer.icons.hidden_placement or "none", self.icon_placement = self.explorer.opts.renderer.icons.hidden_placement or "none"
})
if self.explorer.opts.renderer.icons.show.hidden then if self.explorer.opts.renderer.icons.show.hidden then
self.icon = { self.icon = {
@ -29,18 +29,18 @@ end
---Hidden icon: renderer.icons.show.hidden and node starts with `.` (dotfile). ---Hidden icon: renderer.icons.show.hidden and node starts with `.` (dotfile).
---@param node Node ---@param node Node
---@return HighlightedString[]|nil icons ---@return HighlightedString[]? icons
function DecoratorHidden:calculate_icons(node) function HiddenDecorator:icons(node)
if self.enabled and node:is_dotfile() then if node:is_dotfile() then
return { self.icon } return { self.icon }
end end
end end
---Hidden highlight: renderer.highlight_hidden and node starts with `.` (dotfile). ---Hidden highlight: renderer.highlight_hidden and node starts with `.` (dotfile).
---@param node Node ---@param node Node
---@return string|nil group ---@return string? highlight_group
function DecoratorHidden:calculate_highlight(node) function HiddenDecorator:highlight_group(node)
if not self.enabled or self.range == "none" or not node:is_dotfile() then if self.highlight_range == "none" or not node:is_dotfile() then
return nil return nil
end end
@ -51,4 +51,4 @@ function DecoratorHidden:calculate_highlight(node)
end end
end end
return DecoratorHidden return HiddenDecorator

View File

@ -1,48 +1,52 @@
local Class = require("nvim-tree.classic") local Class = require("nvim-tree.classic")
---@alias DecoratorRange "none" | "icon" | "name" | "all"
---@alias DecoratorIconPlacement "none" | "before" | "after" | "signcolumn" | "right_align"
---Abstract Decorator ---Abstract Decorator
---Uses the factory pattern to instantiate child instances.
---@class (exact) Decorator: Class ---@class (exact) Decorator: Class
---@field protected explorer Explorer
---@field protected enabled boolean ---@field protected enabled boolean
---@field protected range DecoratorRange ---@field protected highlight_range nvim_tree.api.decorator.HighlightRange
---@field protected icon_placement DecoratorIconPlacement ---@field protected icon_placement nvim_tree.api.decorator.IconPlacement
local Decorator = Class:extend() local Decorator = Class:extend()
---@class (exact) DecoratorArgs ---@class (exact) DecoratorArgs
---@field explorer Explorer ---@field explorer Explorer
---@class (exact) AbstractDecoratorArgs: DecoratorArgs ---Abstract icon override, optionally implemented
---@field enabled boolean ---@param node Node
---@field hl_pos DecoratorRange ---@return HighlightedString? icon_node
---@field icon_placement DecoratorIconPlacement function Decorator:icon_node(node)
return self:nop(node)
---@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 end
---Maybe highlight groups ---Abstract icons, optionally implemented
---@protected
---@param node Node ---@param node Node
---@return string|nil icon highlight group ---@return HighlightedString[]? icons
---@return string|nil name highlight group function Decorator:icons(node)
function Decorator:groups_icon_name(node) self:nop(node)
end
---Abstract highlight group, optionally implemented
---@protected
---@param node Node
---@return string? highlight_group
function Decorator:highlight_group(node)
self:nop(node)
end
---Maybe highlight groups for icon and name
---@param node Node
---@return string? icon highlight group
---@return string? name highlight group
function Decorator:highlight_group_icon_name(node)
local icon_hl, name_hl local icon_hl, name_hl
if self.enabled and self.range ~= "none" then if self.enabled and self.highlight_range ~= "none" then
local hl = self:calculate_highlight(node) 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 icon_hl = hl
end end
if self.range == "all" or self.range == "name" then if self.highlight_range == "all" or self.highlight_range == "name" then
name_hl = hl name_hl = hl
end end
end end
@ -52,13 +56,13 @@ end
---Maybe icon sign ---Maybe icon sign
---@param node Node ---@param node Node
---@return string|nil name ---@return string? name
function Decorator:sign_name(node) function Decorator:sign_name(node)
if not self.enabled or self.icon_placement ~= "signcolumn" then if not self.enabled or self.icon_placement ~= "signcolumn" then
return return
end end
local icons = self:calculate_icons(node) local icons = self:icons(node)
if icons and #icons > 0 then if icons and #icons > 0 then
return icons[1].hl[1] return icons[1].hl[1]
end end
@ -66,56 +70,40 @@ end
---Icons when "before" ---Icons when "before"
---@param node Node ---@param node Node
---@return HighlightedString[]|nil icons ---@return HighlightedString[]? icons
function Decorator:icons_before(node) function Decorator:icons_before(node)
if not self.enabled or self.icon_placement ~= "before" then if not self.enabled or self.icon_placement ~= "before" then
return return
end end
return self:calculate_icons(node) return self:icons(node)
end end
---Icons when "after" ---Icons when "after"
---@param node Node ---@param node Node
---@return HighlightedString[]|nil icons ---@return HighlightedString[]? icons
function Decorator:icons_after(node) function Decorator:icons_after(node)
if not self.enabled or self.icon_placement ~= "after" then if not self.enabled or self.icon_placement ~= "after" then
return return
end end
return self:calculate_icons(node) return self:icons(node)
end end
---Icons when "right_align" ---Icons when "right_align"
---@param node Node ---@param node Node
---@return HighlightedString[]|nil icons ---@return HighlightedString[]? icons
function Decorator:icons_right_align(node) function Decorator:icons_right_align(node)
if not self.enabled or self.icon_placement ~= "right_align" then if not self.enabled or self.icon_placement ~= "right_align" then
return return
end end
return self:calculate_icons(node) return self: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
end end
---Define a sign ---Define a sign
---@protected ---@protected
---@param icon HighlightedString|nil ---@param icon HighlightedString?
function Decorator:define_sign(icon) function Decorator:define_sign(icon)
if icon and #icon.hl > 0 then if icon and #icon.hl > 0 then
local name = icon.hl[1] local name = icon.hl[1]

View File

@ -3,26 +3,22 @@ local buffers = require("nvim-tree.buffers")
local Decorator = require("nvim-tree.renderer.decorator") local Decorator = require("nvim-tree.renderer.decorator")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
---@class (exact) DecoratorModified: Decorator ---@class (exact) ModifiedDecorator: Decorator
---@field icon HighlightedString? ---@field private explorer Explorer
local DecoratorModified = Decorator:extend() ---@field private icon HighlightedString?
local ModifiedDecorator = Decorator:extend()
---@class DecoratorModified ---@class ModifiedDecorator
---@overload fun(explorer: DecoratorArgs): DecoratorModified ---@overload fun(args: DecoratorArgs): ModifiedDecorator
---@protected ---@protected
---@param args DecoratorArgs ---@param args DecoratorArgs
function DecoratorModified:new(args) function ModifiedDecorator:new(args)
Decorator.new(self, { self.explorer = args.explorer
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 self.enabled = true
return self.highlight_range = self.explorer.opts.renderer.highlight_modified or "none"
end self.icon_placement = self.explorer.opts.renderer.icons.modified_placement or "none"
if self.explorer.opts.renderer.icons.show.modified then if self.explorer.opts.renderer.icons.show.modified then
self.icon = { self.icon = {
@ -35,18 +31,18 @@ end
---Modified icon: modified.enable, renderer.icons.show.modified and node is modified ---Modified icon: modified.enable, renderer.icons.show.modified and node is modified
---@param node Node ---@param node Node
---@return HighlightedString[]|nil icons ---@return HighlightedString[]? icons
function DecoratorModified:calculate_icons(node) function ModifiedDecorator:icons(node)
if self.enabled and buffers.is_modified(node) then if buffers.is_modified(node) then
return { self.icon } return { self.icon }
end end
end end
---Modified highlight: modified.enable, renderer.highlight_modified and node is modified ---Modified highlight: modified.enable, renderer.highlight_modified and node is modified
---@param node Node ---@param node Node
---@return string|nil group ---@return string? highlight_group
function DecoratorModified:calculate_highlight(node) function ModifiedDecorator:highlight_group(node)
if not self.enabled or self.range == "none" or not buffers.is_modified(node) then if self.highlight_range == "none" or not buffers.is_modified(node) then
return nil return nil
end end
@ -57,4 +53,4 @@ function DecoratorModified:calculate_highlight(node)
end end
end end
return DecoratorModified return ModifiedDecorator

View File

@ -2,31 +2,31 @@ local buffers = require("nvim-tree.buffers")
local Decorator = require("nvim-tree.renderer.decorator") local Decorator = require("nvim-tree.renderer.decorator")
---@class (exact) DecoratorOpened: Decorator ---@class (exact) OpenDecorator: Decorator
---@field icon HighlightedString|nil ---@field private explorer Explorer
local DecoratorOpened = Decorator:extend() ---@field private icon HighlightedString|nil
local OpenDecorator = Decorator:extend()
---@class DecoratorOpened ---@class OpenDecorator
---@overload fun(explorer: DecoratorArgs): DecoratorOpened ---@overload fun(args: DecoratorArgs): OpenDecorator
---@protected ---@protected
---@param args DecoratorArgs ---@param args DecoratorArgs
function DecoratorOpened:new(args) function OpenDecorator:new(args)
Decorator.new(self, { self.explorer = args.explorer
explorer = args.explorer,
enabled = true, self.enabled = true
hl_pos = args.explorer.opts.renderer.highlight_opened_files or "none", self.highlight_range = self.explorer.opts.renderer.highlight_opened_files or "none"
icon_placement = "none", self.icon_placement = "none"
})
end end
---Opened highlight: renderer.highlight_opened_files and node has an open buffer ---Opened highlight: renderer.highlight_opened_files and node has an open buffer
---@param node Node ---@param node Node
---@return string|nil group ---@return string? highlight_group
function DecoratorOpened:calculate_highlight(node) function OpenDecorator:highlight_group(node)
if self.range ~= "none" and buffers.is_opened(node) then if self.highlight_range ~= "none" and buffers.is_opened(node) then
return "NvimTreeOpenedHL" return "NvimTreeOpenedHL"
end end
end end
return DecoratorOpened return OpenDecorator

View File

@ -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