Skip to content

Commit

Permalink
feat(#2948): use renderer.decorators to define order and register
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-courtis committed Dec 1, 2024
1 parent ba296e6 commit cb351ae
Show file tree
Hide file tree
Showing 16 changed files with 97 additions and 145 deletions.
22 changes: 20 additions & 2 deletions doc/nvim-tree-lua.txt
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,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", "Opened", "Hidden", "Modified", "Bookmarks", "Diagnostics", "Copied", "Cut", },
highlight_git = "none",
highlight_diagnostics = "none",
highlight_opened_files = "none",
Expand Down Expand Up @@ -842,7 +843,7 @@ Use nvim-tree in a floating window.
==============================================================================
5.3 OPTS: RENDERER *nvim-tree-opts-renderer*

Highlight precedence, additive:
Highlight precedence, additive, change via |nvim-tree.renderer.decorators|
git < opened < modified < bookmarked < diagnostics < copied < cut

*nvim-tree.renderer.add_trailing*
Expand Down Expand Up @@ -927,6 +928,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.DecoratorUser` class.
Type: `nvim_tree.api.decorator.Name[]`, Default: >lua
{
"Git",
"Opened",
"Hidden",
"Modified",
"Bookmarks",
"Diagnostics",
"Copied",
"Cut",
}
<
*nvim-tree.renderer.highlight_git*
Enable highlight for git attributes using `NvimTreeGit*HL` highlight groups.
Requires |nvim-tree.git.enable|
Expand Down Expand Up @@ -996,7 +1013,7 @@ Configuration options for tree indent markers.
*nvim-tree.renderer.icons*
Configuration options for icons.

Icon order and sign column precedence:
Icon order and sign column precedence, change via |nvim-tree.renderer.decorators|
git < hidden < modified < bookmarked < diagnostics

`renderer.icons.*_placement` options may be:
Expand Down Expand Up @@ -2943,6 +2960,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|
Expand Down
1 change: 1 addition & 0 deletions lua/nvim-tree.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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", "Opened", "Hidden", "Modified", "Bookmarks", "Diagnostics", "Copied", "Cut", },
highlight_git = "none",
highlight_diagnostics = "none",
highlight_opened_files = "none",
Expand Down
39 changes: 18 additions & 21 deletions lua/nvim-tree/_meta/api_decorator.lua
Original file line number Diff line number Diff line change
@@ -1,87 +1,87 @@
---@meta
error("Cannot require a meta file")

local nvim_tree = { api = { decorator = { AbstractDecorator = {} } } }
local nvim_tree = { api = { decorator = { DecoratorUser = {} } } }

---Custom decorator
---It may:
--- Add icons
--- Set highlight group for the name or icons
--- Override node icon
---Class must be created via nvim_tree.api.decorator.create()
---Register it via :help nvim-tree.renderer.decorators
---Create class via require("nvim-tree.api").decorator.DecoratorUser:extend()
---Mandatory constructor :new() will be called once per tree render, with no arguments.
---Constructor must call:
--- :init
--- :define_sign when using "signcolumn" range
---Decorator must be registered via api.decorator.register

---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 predefined decorators or your decorator classes
---@alias nvim_tree.api.decorator.Name "Cut" | "Copied" | "Diagnostics" | "Bookmarks" | "Modified" | "Hidden" | "Opened" | "Git" | nvim_tree.api.decorator.AbstractDecorator
---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.DecoratorUser

---Abstract decorator class, your decorator will extend this.
---Your decorator will extend this class via require("nvim-tree.api").decorator.DecoratorUser:extend()
---
---@class (exact) nvim_tree.api.decorator.AbstractDecorator
---@class (exact) nvim_tree.api.decorator.DecoratorUser
---@field protected enabled boolean
---@field protected highlight_range nvim_tree.api.decorator.HighlightRange
---@field protected icon_placement nvim_tree.api.decorator.IconPlacement

---Abstract: no-args constructor must be implemented.
---
function nvim_tree.api.decorator.AbstractDecorator:new() end
function nvim_tree.api.decorator.DecoratorUser:new() end

---Abstract: optionally implement to set the node's icon
---
---@param node nvim_tree.api.Node
---@return HighlightedString? icon_node
function nvim_tree.api.decorator.AbstractDecorator:icon_node(node) end
function nvim_tree.api.decorator.DecoratorUser: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 HighlightedString[]? icons
function nvim_tree.api.decorator.AbstractDecorator:icons(node) end
function nvim_tree.api.decorator.DecoratorUser: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.AbstractDecorator:highlight_group(node) end
function nvim_tree.api.decorator.DecoratorUser:highlight_group(node) end

---Must be called from your constructor.
---
---@class (exact) nvim_tree.api.decorator.AbstractDecoratorInitArgs
---@class (exact) nvim_tree.api.decorator.InitArgs
---@field enabled boolean
---@field highlight_range nvim_tree.api.decorator.HighlightRange
---@field icon_placement nvim_tree.api.decorator.IconPlacement
---
---@protected
---@param args nvim_tree.api.decorator.AbstractDecoratorInitArgs
function nvim_tree.api.decorator.AbstractDecorator:init(args) end
---@param args nvim_tree.api.decorator.InitArgs
function nvim_tree.api.decorator.DecoratorUser:init(args) end

---Define a sign. This should be called in the constructor.
---
---@protected
---@param icon HighlightedString?
function nvim_tree.api.decorator.AbstractDecorator:define_sign(icon) end
function nvim_tree.api.decorator.DecoratorUser:define_sign(icon) end


--
-- Example Decorator
--

---@class (exact) MyDecorator: nvim_tree.api.decorator.AbstractDecorator
---@class (exact) MyDecorator: nvim_tree.api.decorator.DecoratorUser
---@field private my_icon nvim_tree.api.HighlightedString
local MyDecorator = require("nvim-tree.api").decorator.create()
local MyDecorator = require("nvim-tree.api").decorator.DecoratorUser:extend()

---Mandatory constructor :new() will be called once per tree render, with no arguments.
function MyDecorator:new()
---@type nvim_tree.api.decorator.AbstractDecoratorInitArgs
---@type nvim_tree.api.decorator.InitArgs
local args = {
enabled = true,
highlight_range = "all",
Expand Down Expand Up @@ -131,6 +131,3 @@ function MyDecorator:highlight_group(node)
return nil
end
end

---Register the decorator, below Cut in priority
require("nvim-tree.api").decorator.register({ decorator = MyDecorator, below = "Cut" })
25 changes: 3 additions & 22 deletions lua/nvim-tree/api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ local events = require("nvim-tree.events")
local help = require("nvim-tree.help")
local keymap = require("nvim-tree.keymap")
local notify = require("nvim-tree.notify")
local decorator_registry = require("nvim-tree.renderer.decorator.registry")

local DirectoryNode = require("nvim-tree.node.directory")
local FileLinkNode = require("nvim-tree.node.file-link")
Expand Down Expand Up @@ -314,26 +313,8 @@ Api.commands.get = wrap(function()
return require("nvim-tree.commands").get()
end)

---Create a new decorator class
---
---@return nvim_tree.api.decorator.AbstractDecorator
Api.decorator.create = function() return DecoratorUser:extend() end

---Register a decorator class
---
---@class RegisterOpts
---@field decorator nvim_tree.api.decorator.AbstractDecorator
---@field below nvim_tree.api.decorator.Name?
---
---@param opts RegisterOpts
Api.decorator.register = function(opts) decorator_registry.register(opts) end

---Unregister a decorator class
---
---@class UnRegisterOpts
---@field decorator nvim_tree.api.decorator.AbstractDecorator
---
---@param opts UnRegisterOpts
Api.decorator.unregister = function(opts) decorator_registry.unregister(opts) end
---User provided decorator. Extend this class via :extend()
---@type nvim_tree.api.decorator.DecoratorUser
Api.decorator.DecoratorUser = DecoratorUser --[[@as nvim_tree.api.decorator.DecoratorUser]]

return Api
84 changes: 53 additions & 31 deletions lua/nvim-tree/renderer/builder.lua
Original file line number Diff line number Diff line change
@@ -1,14 +1,36 @@
local decorator_registry = require("nvim-tree.renderer.decorator.registry")
local notify = require("nvim-tree.notify")
local utils = require("nvim-tree.utils")
local view = require("nvim-tree.view")

local Class = require("nvim-tree.classic")

local DirectoryNode = require("nvim-tree.node.directory")

local DecoratorBookmarks = require("nvim-tree.renderer.decorator.bookmarks")
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 DecoratorHidden = require("nvim-tree.renderer.decorator.hidden")
local DecoratorModified = require("nvim-tree.renderer.decorator.modified")
local DecoratorOpened = require("nvim-tree.renderer.decorator.opened")
local DecoratorUser = require("nvim-tree.renderer.decorator.user")

local pad = require("nvim-tree.renderer.components.padding")

-- Builtin Decorators
---@type table<nvim_tree.api.decorator.Name, Decorator>
local BUILTIN_DECORATORS = {
Git = DecoratorGit,
Opened = DecoratorOpened,
Hidden = DecoratorHidden,
Modified = DecoratorModified,
Bookmarks = DecoratorBookmarks,
Diagnostics = DecoratorDiagnostics,
Copied = DecoratorCopied,
Cut = DecoratorCut,
}

---@class (exact) AddHighlightArgs
---@field group string[]
---@field line number
Expand Down Expand Up @@ -52,31 +74,28 @@ function Builder:new(args)
self.virtual_lines = {}
self.decorators = {}
self.hidden_display = Builder:setup_hidden_display_function(self.explorer.opts)
self.api_nodes = nil

---@type DecoratorArgs
local decorator_args = { explorer = self.explorer }

-- instantiate all the decorators
for _, d in ipairs(decorator_registry.registered) do
if d:is(DecoratorUser) then
self:build_api_nodes()
table.insert(self.decorators, d())
else
table.insert(self.decorators, d(decorator_args))
end
end
end

---Create and populate api_nodes if not present
---@private
function Builder:build_api_nodes()
if self.api_nodes then
return
end
-- 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]

self.api_nodes = {}
self.explorer:clone(self.api_nodes)
---@type DecoratorUser
user = d.as and d:as(DecoratorUser)

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
Expand Down Expand Up @@ -143,18 +162,18 @@ function Builder:format_line(indent_markers, arrows, icon, name, node)
add_to_end(line, { icon })

for _, d in ipairs(self.decorators) do
add_to_end(line, d:icons_before(d:is(DecoratorUser) and api_node or node))
add_to_end(line, d:icons_before(not d:is(DecoratorUser) and node or api_node))
end

add_to_end(line, { name })

for _, d in ipairs(self.decorators) do
add_to_end(line, d:icons_after(d:is(DecoratorUser) and api_node or node))
add_to_end(line, d:icons_after(not d:is(DecoratorUser) and node or api_node))
end

local rights = {}
for _, d in ipairs(self.decorators) do
add_to_end(rights, d:icons_right_align(d:is(DecoratorUser) and api_node or node))
add_to_end(rights, d:icons_right_align(not d:is(DecoratorUser) and node or api_node))
end
if #rights > 0 then
self.extmarks[self.index] = rights
Expand All @@ -173,7 +192,7 @@ function Builder:build_signs(node)
local d, sign_name
for i = #self.decorators, 1, -1 do
d = self.decorators[i]
sign_name = d:sign_name(d:is(DecoratorUser) and api_node or node)
sign_name = d:sign_name(not d:is(DecoratorUser) and node or api_node)
if sign_name then
self.signs[self.index] = sign_name
break
Expand Down Expand Up @@ -217,6 +236,9 @@ end
---@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()
Expand All @@ -225,11 +247,11 @@ function Builder:icon_name_decorated(node)
local icon_groups = {}
local name_groups = {}
local hl_icon, hl_name
for _, decorator in ipairs(self.decorators) do
for _, d in ipairs(self.decorators) do
-- maybe overridde icon
icon = decorator:icon_node(node) or icon
icon = d:icon_node((not d:is(DecoratorUser) and node or api_node)) or icon

hl_icon, hl_name = decorator:highlight_group_icon_name(node)
hl_icon, hl_name = d:highlight_group_icon_name((not d:is(DecoratorUser) and node or api_node))

table.insert(icon_groups, hl_icon)
table.insert(name_groups, hl_name)
Expand Down
1 change: 0 additions & 1 deletion lua/nvim-tree/renderer/decorator/bookmarks.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ local Decorator = require("nvim-tree.renderer.decorator")
---@field private explorer Explorer
---@field private icon HighlightedString?
local DecoratorBookmarks = Decorator:extend()
DecoratorBookmarks.name = "Bookmarks"

---@class DecoratorBookmarks
---@overload fun(args: DecoratorArgs): DecoratorBookmarks
Expand Down
1 change: 0 additions & 1 deletion lua/nvim-tree/renderer/decorator/copied.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ local Decorator = require("nvim-tree.renderer.decorator")
---@class (exact) DecoratorCopied: Decorator
---@field private explorer Explorer
local DecoratorCopied = Decorator:extend()
DecoratorCopied.name = "Copied"

---@class DecoratorCopied
---@overload fun(args: DecoratorArgs): DecoratorCopied
Expand Down
1 change: 0 additions & 1 deletion lua/nvim-tree/renderer/decorator/cut.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ local Decorator = require("nvim-tree.renderer.decorator")
---@class (exact) DecoratorCut: Decorator
---@field private explorer Explorer
local DecoratorCut = Decorator:extend()
DecoratorCut.name = "Cut"

---@class DecoratorCut
---@overload fun(args: DecoratorArgs): DecoratorCut
Expand Down
1 change: 0 additions & 1 deletion lua/nvim-tree/renderer/decorator/diagnostics.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ local ICON_KEYS = {
---@field private explorer Explorer
---@field private diag_icons HighlightedString[]?
local DecoratorDiagnostics = Decorator:extend()
DecoratorDiagnostics.name = "Diagnostics"

---@class DecoratorDiagnostics
---@overload fun(args: DecoratorArgs): DecoratorDiagnostics
Expand Down
Loading

0 comments on commit cb351ae

Please sign in to comment.