* feat(#1079): add highlight NvimTreeCopiedText and NvimTreeCutText * feat(#1079): add highlight NvimTreeCopiedText and NvimTreeCutText * feat(#1079): node may not be present in copy and cut * feat(#2411): bookmark highlight and icon placement * feat(#1079): add renderer.highlight_clipboard * feat(#1079): add renderer.highlight_clipboard * feat(#2411): bookmark highlight and icon placement * feat(#2411): bookmark highlight and icon placement * style * feat(#2411): bookmark highlight and icon placement * feat(#2411): bookmark highlight and icon placement * feat(#2411): bookmark highlight and icon placement * feat(#2411): bookmark highlight and icon placement
512 lines
14 KiB
Lua
512 lines
14 KiB
Lua
local utils = require "nvim-tree.utils"
|
|
local core = require "nvim-tree.core"
|
|
|
|
local git = require "nvim-tree.renderer.components.git"
|
|
local pad = require "nvim-tree.renderer.components.padding"
|
|
local icons = require "nvim-tree.renderer.components.icons"
|
|
local modified = require "nvim-tree.renderer.components.modified"
|
|
local diagnostics = require "nvim-tree.renderer.components.diagnostics"
|
|
local bookmarks = require "nvim-tree.renderer.components.bookmarks"
|
|
|
|
local HL_POSITION = require("nvim-tree.enum").HL_POSITION
|
|
|
|
local Builder = {}
|
|
Builder.__index = Builder
|
|
|
|
local DEFAULT_ROOT_FOLDER_LABEL = ":~:s?$?/..?"
|
|
|
|
function Builder.new(root_cwd)
|
|
return setmetatable({
|
|
index = 0,
|
|
depth = 0,
|
|
highlights = {},
|
|
lines = {},
|
|
markers = {},
|
|
signs = {},
|
|
root_cwd = root_cwd,
|
|
}, Builder)
|
|
end
|
|
|
|
function Builder:configure_root_label(root_folder_label)
|
|
self.root_folder_label = root_folder_label or DEFAULT_ROOT_FOLDER_LABEL
|
|
return self
|
|
end
|
|
|
|
function Builder:configure_trailing_slash(with_trailing)
|
|
self.trailing_slash = with_trailing and "/" or ""
|
|
return self
|
|
end
|
|
|
|
function Builder:configure_special_files(special_files)
|
|
self.special_files = special_files
|
|
return self
|
|
end
|
|
|
|
function Builder:configure_picture_map(picture_map)
|
|
self.picture_map = picture_map
|
|
return self
|
|
end
|
|
|
|
function Builder:configure_filter(filter, prefix)
|
|
self.filter_prefix = prefix
|
|
self.filter = filter
|
|
return self
|
|
end
|
|
|
|
function Builder:configure_opened_file_highlighting(highlight_opened_files)
|
|
self.highlight_opened_files = highlight_opened_files
|
|
return self
|
|
end
|
|
|
|
function Builder:configure_modified_highlighting(highlight_modified)
|
|
self.highlight_modified = highlight_modified
|
|
return self
|
|
end
|
|
|
|
function Builder:configure_icon_padding(padding)
|
|
self.icon_padding = padding or " "
|
|
return self
|
|
end
|
|
|
|
function Builder:configure_git_icons_placement(where)
|
|
if where ~= "after" and where ~= "before" and where ~= "signcolumn" then
|
|
where = "before" -- default before
|
|
end
|
|
self.git_placement = where
|
|
return self
|
|
end
|
|
|
|
function Builder:configure_diagnostics_icon_placement(where)
|
|
if where ~= "after" and where ~= "before" and where ~= "signcolumn" then
|
|
where = "before" -- default before
|
|
end
|
|
self.diagnostics_placement = where
|
|
return self
|
|
end
|
|
|
|
function Builder:configure_bookmark_icon_placement(where)
|
|
if where ~= "after" and where ~= "before" and where ~= "signcolumn" then
|
|
where = "before" -- default before
|
|
end
|
|
self.bookmarks_placement = where
|
|
return self
|
|
end
|
|
|
|
function Builder:configure_modified_placement(where)
|
|
if where ~= "after" and where ~= "before" and where ~= "signcolumn" then
|
|
where = "after" -- default after
|
|
end
|
|
self.modified_placement = where
|
|
return self
|
|
end
|
|
|
|
function Builder:configure_symlink_destination(show)
|
|
self.symlink_destination = show
|
|
return self
|
|
end
|
|
|
|
function Builder:_insert_highlight(group, start, end_)
|
|
table.insert(self.highlights, { group, self.index, start, end_ or -1 })
|
|
end
|
|
|
|
function Builder:_insert_line(line)
|
|
table.insert(self.lines, line)
|
|
end
|
|
|
|
local function get_folder_name(node)
|
|
local name = node.name
|
|
local next = node.group_next
|
|
while next do
|
|
name = name .. "/" .. next.name
|
|
next = next.group_next
|
|
end
|
|
return name
|
|
end
|
|
|
|
---@class HighlightedString
|
|
---@field str string
|
|
---@field hl string[]
|
|
|
|
---@param highlighted_strings HighlightedString[]
|
|
---@return string
|
|
function Builder:_unwrap_highlighted_strings(highlighted_strings)
|
|
if not highlighted_strings then
|
|
return ""
|
|
end
|
|
|
|
local string = ""
|
|
for _, v in ipairs(highlighted_strings) do
|
|
if #v.str > 0 then
|
|
if v.hl and type(v.hl) == "table" then
|
|
self:_insert_highlight(v.hl, #string, #string + #v.str)
|
|
end
|
|
string = string .. v.str
|
|
end
|
|
end
|
|
return string
|
|
end
|
|
|
|
---@param node table
|
|
---@return HighlightedString icon
|
|
---@return HighlightedString name
|
|
function Builder:_build_folder(node)
|
|
local has_children = #node.nodes ~= 0 or node.has_children
|
|
local icon, icon_hl = icons.get_folder_icon(node, has_children)
|
|
local foldername = get_folder_name(node) .. self.trailing_slash
|
|
|
|
if #icon > 0 and icon_hl == nil then
|
|
if node.open then
|
|
icon_hl = "NvimTreeOpenedFolderIcon"
|
|
else
|
|
icon_hl = "NvimTreeClosedFolderIcon"
|
|
end
|
|
end
|
|
|
|
local foldername_hl = "NvimTreeFolderName"
|
|
if node.link_to and self.symlink_destination then
|
|
local arrow = icons.i.symlink_arrow
|
|
local link_to = utils.path_relative(node.link_to, core.get_cwd())
|
|
foldername = foldername .. arrow .. link_to
|
|
foldername_hl = "NvimTreeSymlinkFolderName"
|
|
elseif
|
|
vim.tbl_contains(self.special_files, node.absolute_path) or vim.tbl_contains(self.special_files, node.name)
|
|
then
|
|
foldername_hl = "NvimTreeSpecialFolderName"
|
|
elseif node.open then
|
|
foldername_hl = "NvimTreeOpenedFolderName"
|
|
elseif not has_children then
|
|
foldername_hl = "NvimTreeEmptyFolderName"
|
|
end
|
|
|
|
return { str = icon, hl = { icon_hl } }, { str = foldername, hl = { foldername_hl } }
|
|
end
|
|
|
|
---@param node table
|
|
---@return HighlightedString icon
|
|
---@return HighlightedString name
|
|
function Builder:_build_symlink(node)
|
|
local icon = icons.i.symlink
|
|
local arrow = icons.i.symlink_arrow
|
|
local symlink_formatted = node.name
|
|
if self.symlink_destination then
|
|
local link_to = utils.path_relative(node.link_to, core.get_cwd())
|
|
symlink_formatted = symlink_formatted .. arrow .. link_to
|
|
end
|
|
|
|
return { str = icon, hl = { "NvimTreeSymlinkIcon" } }, { str = symlink_formatted, hl = { "NvimTreeSymlink" } }
|
|
end
|
|
|
|
---@param node table
|
|
---@return HighlightedString icon
|
|
function Builder:_build_file_icon(node)
|
|
local icon, hl_group = icons.get_file_icon(node.name, node.extension)
|
|
return { str = icon, hl = { hl_group } }
|
|
end
|
|
|
|
---@param node table
|
|
---@return HighlightedString icon
|
|
---@return HighlightedString name
|
|
function Builder:_build_file(node)
|
|
local icon = self:_build_file_icon(node)
|
|
|
|
local hl
|
|
if vim.tbl_contains(self.special_files, node.absolute_path) or vim.tbl_contains(self.special_files, node.name) then
|
|
hl = "NvimTreeSpecialFile"
|
|
elseif node.executable then
|
|
hl = "NvimTreeExecFile"
|
|
elseif self.picture_map[node.extension] then
|
|
hl = "NvimTreeImageFile"
|
|
end
|
|
|
|
return icon, { str = node.name, hl = { hl } }
|
|
end
|
|
|
|
---@param node table
|
|
---@return HighlightedString[]|nil icon
|
|
function Builder:_get_git_icons(node)
|
|
local git_icons = git.get_icons(node)
|
|
if git_icons and #git_icons > 0 and self.git_placement == "signcolumn" then
|
|
table.insert(self.signs, { sign = git_icons[1].hl[1], lnum = self.index + 1, priority = 1 })
|
|
git_icons = nil
|
|
end
|
|
return git_icons
|
|
end
|
|
|
|
---@param node table
|
|
---@return HighlightedString[]|nil icon
|
|
function Builder:_get_diagnostics_icon(node)
|
|
local diagnostics_icon = diagnostics.get_icon(node)
|
|
if diagnostics_icon and self.diagnostics_placement == "signcolumn" then
|
|
table.insert(self.signs, { sign = diagnostics_icon.hl[1], lnum = self.index + 1, priority = 2 })
|
|
diagnostics_icon = nil
|
|
end
|
|
return diagnostics_icon
|
|
end
|
|
|
|
---@param node table
|
|
---@return HighlightedString|nil icon
|
|
function Builder:_get_modified_icon(node)
|
|
local modified_icon = modified.get_icon(node)
|
|
if modified_icon and self.modified_placement == "signcolumn" then
|
|
table.insert(self.signs, { sign = modified_icon.hl[1], lnum = self.index + 1, priority = 3 })
|
|
modified_icon = nil
|
|
end
|
|
return modified_icon
|
|
end
|
|
|
|
---@param node table
|
|
---@return HighlightedString[]|nil icon
|
|
function Builder:_get_bookmark_icon(node)
|
|
local bookmark_icon = bookmarks.get_icon(node)
|
|
if bookmark_icon and self.bookmarks_placement == "signcolumn" then
|
|
table.insert(self.signs, { sign = bookmark_icon.hl[1], lnum = self.index + 1, priority = 4 })
|
|
bookmark_icon = nil
|
|
end
|
|
return bookmark_icon
|
|
end
|
|
|
|
---@param node table
|
|
---@return string|nil icon_hl
|
|
---@return string|nil name_hl
|
|
function Builder:_get_highlight_override(node, unloaded_bufnr)
|
|
local name_hl, icon_hl
|
|
|
|
-- git
|
|
local git_highlight = git.get_highlight(node)
|
|
if git_highlight then
|
|
name_hl = git_highlight
|
|
end
|
|
|
|
-- opened file
|
|
if
|
|
self.highlight_opened_files
|
|
and vim.fn.bufloaded(node.absolute_path) > 0
|
|
and vim.fn.bufnr(node.absolute_path) ~= unloaded_bufnr
|
|
then
|
|
if self.highlight_opened_files == "all" or self.highlight_opened_files == "name" then
|
|
name_hl = "NvimTreeOpenedFile"
|
|
end
|
|
if self.highlight_opened_files == "all" or self.highlight_opened_files == "icon" then
|
|
icon_hl = "NvimTreeOpenedFileIcon"
|
|
end
|
|
end
|
|
|
|
-- modified file
|
|
local modified_highlight = modified.get_highlight(node)
|
|
if modified_highlight then
|
|
if self.highlight_modified == "all" or self.highlight_modified == "name" then
|
|
name_hl = modified_highlight
|
|
end
|
|
if self.highlight_modified == "all" or self.highlight_modified == "icon" then
|
|
icon_hl = modified_highlight
|
|
end
|
|
end
|
|
|
|
return icon_hl, name_hl
|
|
end
|
|
|
|
---Append optional highlighting to icon or name.
|
|
---@param node table
|
|
---@param get_hl fun(node: table): HL_POSITION, string
|
|
---@param icon_hl string[] icons to append to
|
|
---@param name_hl string[] names to append to
|
|
function Builder:_append_highlight(node, get_hl, icon_hl, name_hl)
|
|
local pos, hl = get_hl(node)
|
|
if pos ~= HL_POSITION.none and hl then
|
|
if pos == HL_POSITION.all or pos == HL_POSITION.icon then
|
|
table.insert(icon_hl, hl)
|
|
end
|
|
if pos == HL_POSITION.all or pos == HL_POSITION.name then
|
|
table.insert(name_hl, hl)
|
|
end
|
|
end
|
|
end
|
|
|
|
---@param indent_markers HighlightedString[]
|
|
---@param arrows HighlightedString[]|nil
|
|
---@param icon HighlightedString
|
|
---@param name HighlightedString
|
|
---@param git_icons HighlightedString[]|nil
|
|
---@param diagnostics_icon HighlightedString|nil
|
|
---@param modified_icon HighlightedString|nil
|
|
---@param bookmark_icon HighlightedString|nil
|
|
---@return HighlightedString[]
|
|
function Builder:_format_line(
|
|
indent_markers,
|
|
arrows,
|
|
icon,
|
|
name,
|
|
git_icons,
|
|
diagnostics_icon,
|
|
modified_icon,
|
|
bookmark_icon
|
|
)
|
|
local added_len = 0
|
|
local function add_to_end(t1, t2)
|
|
for _, v in ipairs(t2) do
|
|
if added_len > 0 then
|
|
table.insert(t1, { str = self.icon_padding })
|
|
end
|
|
table.insert(t1, v)
|
|
end
|
|
|
|
-- first add_to_end don't need padding
|
|
-- hence added_len is calculated at the end to be used next time
|
|
added_len = 0
|
|
for _, v in ipairs(t2) do
|
|
added_len = added_len + #v.str
|
|
end
|
|
end
|
|
|
|
local line = { indent_markers, arrows }
|
|
add_to_end(line, { icon })
|
|
if git_icons and self.git_placement == "before" then
|
|
add_to_end(line, git_icons)
|
|
end
|
|
if modified_icon and self.modified_placement == "before" then
|
|
add_to_end(line, { modified_icon })
|
|
end
|
|
if diagnostics_icon and self.diagnostics_placement == "before" then
|
|
add_to_end(line, { diagnostics_icon })
|
|
end
|
|
if bookmark_icon and self.bookmarks_placement == "before" then
|
|
add_to_end(line, { bookmark_icon })
|
|
end
|
|
|
|
add_to_end(line, { name })
|
|
|
|
if git_icons and self.git_placement == "after" then
|
|
add_to_end(line, git_icons)
|
|
end
|
|
if modified_icon and self.modified_placement == "after" then
|
|
add_to_end(line, { modified_icon })
|
|
end
|
|
if diagnostics_icon and self.diagnostics_placement == "after" then
|
|
add_to_end(line, { diagnostics_icon })
|
|
end
|
|
if bookmark_icon and self.bookmarks_placement == "after" then
|
|
add_to_end(line, { bookmark_icon })
|
|
end
|
|
|
|
return line
|
|
end
|
|
|
|
function Builder:_build_line(node, idx, num_children, unloaded_bufnr)
|
|
local copy_paste = require "nvim-tree.actions.fs.copy-paste"
|
|
|
|
-- various components
|
|
local indent_markers = pad.get_indent_markers(self.depth, idx, num_children, node, self.markers)
|
|
local arrows = pad.get_arrows(node)
|
|
|
|
-- adds icons to signcolumn
|
|
local bookmark_icon = self:_get_bookmark_icon(node)
|
|
local git_icons = self:_get_git_icons(node)
|
|
local modified_icon = self:_get_modified_icon(node)
|
|
local diagnostics_icon = self:_get_diagnostics_icon(node)
|
|
|
|
-- main components
|
|
local is_folder = node.nodes ~= nil
|
|
local is_symlink = node.link_to ~= nil
|
|
local icon, name
|
|
if is_folder then
|
|
icon, name = self:_build_folder(node)
|
|
elseif is_symlink then
|
|
icon, name = self:_build_symlink(node)
|
|
else
|
|
icon, name = self:_build_file(node)
|
|
end
|
|
|
|
-- highlight override
|
|
local icon_hl_override, name_hl_override = self:_get_highlight_override(node, unloaded_bufnr)
|
|
if icon_hl_override then
|
|
icon.hl = { icon_hl_override }
|
|
end
|
|
if name_hl_override then
|
|
name.hl = { name_hl_override }
|
|
end
|
|
|
|
-- extra highighting
|
|
self:_append_highlight(node, bookmarks.get_highlight, icon.hl, name.hl)
|
|
self:_append_highlight(node, diagnostics.get_highlight, icon.hl, name.hl)
|
|
self:_append_highlight(node, copy_paste.get_highlight, icon.hl, name.hl)
|
|
|
|
local line =
|
|
self:_format_line(indent_markers, arrows, icon, name, git_icons, diagnostics_icon, modified_icon, bookmark_icon)
|
|
self:_insert_line(self:_unwrap_highlighted_strings(line))
|
|
|
|
self.index = self.index + 1
|
|
|
|
node = require("nvim-tree.lib").get_last_group_node(node)
|
|
|
|
if node.open then
|
|
self.depth = self.depth + 1
|
|
self:build(node, unloaded_bufnr)
|
|
self.depth = self.depth - 1
|
|
end
|
|
end
|
|
|
|
function Builder:_get_nodes_number(nodes)
|
|
if not self.filter then
|
|
return #nodes
|
|
end
|
|
|
|
local i = 0
|
|
for _, n in pairs(nodes) do
|
|
if not n.hidden then
|
|
i = i + 1
|
|
end
|
|
end
|
|
return i
|
|
end
|
|
|
|
function Builder:build(tree, unloaded_bufnr)
|
|
local num_children = self:_get_nodes_number(tree.nodes)
|
|
local idx = 1
|
|
for _, node in ipairs(tree.nodes) do
|
|
if not node.hidden then
|
|
self:_build_line(node, idx, num_children, unloaded_bufnr)
|
|
idx = idx + 1
|
|
end
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
local function format_root_name(root_cwd, root_label)
|
|
if type(root_label) == "function" then
|
|
local label = root_label(root_cwd)
|
|
if type(label) == "string" then
|
|
return label
|
|
else
|
|
root_label = DEFAULT_ROOT_FOLDER_LABEL
|
|
end
|
|
end
|
|
return utils.path_remove_trailing(vim.fn.fnamemodify(root_cwd, root_label))
|
|
end
|
|
|
|
function Builder:build_header(show_header)
|
|
if show_header then
|
|
local root_name = format_root_name(self.root_cwd, self.root_folder_label)
|
|
self:_insert_line(root_name)
|
|
self:_insert_highlight({ "NvimTreeRootFolder" }, 0, string.len(root_name))
|
|
self.index = 1
|
|
end
|
|
|
|
if self.filter then
|
|
local filter_line = self.filter_prefix .. "/" .. self.filter .. "/"
|
|
self:_insert_line(filter_line)
|
|
local prefix_length = string.len(self.filter_prefix)
|
|
self:_insert_highlight({ "NvimTreeLiveFilterPrefix" }, 0, prefix_length)
|
|
self:_insert_highlight({ "NvimTreeLiveFilterValue" }, prefix_length, string.len(filter_line))
|
|
self.index = self.index + 1
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
function Builder:unwrap()
|
|
return self.lines, self.highlights, self.signs
|
|
end
|
|
|
|
return Builder
|