From d2cbf23f2cd1c63b50363b8d2bb0383ef05c3a01 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Sun, 7 Jan 2024 16:04:52 +1100 Subject: [PATCH] collapse Builder --- lua/nvim-tree/renderer/builder.lua | 318 +++++++++++++++-------------- lua/nvim-tree/renderer/init.lua | 63 ++---- 2 files changed, 181 insertions(+), 200 deletions(-) diff --git a/lua/nvim-tree/renderer/builder.lua b/lua/nvim-tree/renderer/builder.lua index c0d3dcc4e92..2b21318c909 100644 --- a/lua/nvim-tree/renderer/builder.lua +++ b/lua/nvim-tree/renderer/builder.lua @@ -1,104 +1,94 @@ local appearance = require "nvim-tree.appearance" -local utils = require "nvim-tree.utils" +local core = require "nvim-tree.core" +local live_filter = require "nvim-tree.live-filter" local notify = require "nvim-tree.notify" +local utils = require "nvim-tree.utils" +local view = require "nvim-tree.view" +local log = require "nvim-tree.log" + +local DecoratorBookmarks = require "nvim-tree.renderer.decorator.bookmarks" +local DecoratorCopied = require "nvim-tree.renderer.decorator.copied" +local DecoratorCut = require "nvim-tree.renderer.decorator.cut" +local DecoratorDiagnostics = require "nvim-tree.renderer.decorator.diagnostics" +local DecoratorGit = require "nvim-tree.renderer.decorator.git" +local DecoratorModified = require "nvim-tree.renderer.decorator.modified" +local DecoratorOpened = require "nvim-tree.renderer.decorator.opened" local pad = require "nvim-tree.renderer.components.padding" local icons = require "nvim-tree.renderer.components.icons" +local M = { + opts = {}, + decorators = {}, + picture_map = { + jpg = true, + jpeg = true, + png = true, + gif = true, + webp = true, + jxl = true, + }, +} + +---@class HighlightedString +---@field str string +---@field hl string[] + +---@class AddHighlightArgs +---@field group string[] +---@field line number +---@field col_start number +---@field col_end number + ---@class Builder +---@field lines string[] includes icons etc. +---@field hl_args AddHighlightArgs[] line highlights +---@field signs string[] line signs +---@field private root_cwd string absolute path ---@field private index number ---@field private depth number ----@field private highlights table[] hl_group, line, col_start, col_end arguments for vim.api.nvim_buf_add_highlight ---@field private combined_groups string[] combined group names ----@field private lines string[] includes icons etc. ---@field private markers boolean[] indent markers ----@field private sign_names string[] line signs ----@field private root_cwd string absolute path ----@field private decorators Decorator[] in priority order local Builder = {} -Builder.__index = Builder -local DEFAULT_ROOT_FOLDER_LABEL = ":~:s?$?/..?" - -function Builder.new(root_cwd, decorators) - return setmetatable({ +---@return Builder +function Builder:new() + local o = { + root_cwd = core.get_cwd(), index = 0, depth = 0, - highlights = {}, + hl_args = {}, combined_groups = {}, lines = {}, markers = {}, - sign_names = {}, - root_cwd = root_cwd, - decorators = decorators, - }, 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 + signs = {}, + } + setmetatable(o, self) + self.__index = self -function Builder:configure_icon_padding(padding) - self.icon_padding = padding or " " - return self -end - -function Builder:configure_symlink_destination(show) - self.symlink_destination = show - return self -end - -function Builder:configure_group_name_modifier(group_name_modifier) - if type(group_name_modifier) == "function" then - self.group_name_modifier = group_name_modifier - end - return self + return o end ---Insert ranged highlight groups into self.highlights +---@private ---@param groups string[] ---@param start number ---@param end_ number|nil -function Builder:_insert_highlight(groups, start, end_) - table.insert(self.highlights, { groups, self.index, start, end_ or -1 }) -end - -function Builder:_insert_line(line) - table.insert(self.lines, line) +function Builder:insert_highlight(groups, start, end_) + table.insert(self.hl_args, { groups, self.index, start, end_ or -1 }) end -function Builder:_get_folder_name(node) +---@private +function Builder:get_folder_name(node) local name = node.name local next = node.group_next while next do - name = name .. "/" .. next.name + name = string.format("%s/%s", name, next.name) next = next.group_next end - if node.group_next and self.group_name_modifier then - local new_name = self.group_name_modifier(name) + if node.group_next and type(M.opts.renderer.group_empty == "function") then + local new_name = M.opts.renderer.group_empty(name) if type(new_name) == "string" then name = new_name else @@ -106,16 +96,13 @@ function Builder:_get_folder_name(node) end end - return name .. self.trailing_slash + return string.format("%s%s", name, M.opts.renderer.add_trailing and "/" or "") end ----@class HighlightedString ----@field str string ----@field hl string[] - +---@private ---@param highlighted_strings HighlightedString[] ---@return string -function Builder:_unwrap_highlighted_strings(highlighted_strings) +function Builder:unwrap_highlighted_strings(highlighted_strings) if not highlighted_strings then return "" end @@ -124,21 +111,22 @@ function Builder:_unwrap_highlighted_strings(highlighted_strings) 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) + self:insert_highlight(v.hl, #string, #string + #v.str) end - string = string .. v.str + string = string.format("%s%s", string, v.str) end end return string end +---@private ---@param node table ---@return HighlightedString icon ---@return HighlightedString name -function Builder:_build_folder(node) +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 = self:_get_folder_name(node) + local foldername = self:get_folder_name(node) if #icon > 0 and icon_hl == nil then if node.open then @@ -149,12 +137,14 @@ function Builder:_build_folder(node) end local foldername_hl = "NvimTreeFolderName" - if node.link_to and self.symlink_destination then + if node.link_to and M.opts.renderer.symlink_destination then local arrow = icons.i.symlink_arrow local link_to = utils.path_relative(node.link_to, self.root_cwd) - foldername = foldername .. arrow .. link_to + foldername = string.format("%s%s%s", 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 + elseif + vim.tbl_contains(M.opts.renderer.special_files, node.absolute_path) or vim.tbl_contains(M.opts.renderer.special_files, node.name) + then foldername_hl = "NvimTreeSpecialFolderName" elseif node.open then foldername_hl = "NvimTreeOpenedFolderName" @@ -165,53 +155,48 @@ function Builder:_build_folder(node) return { str = icon, hl = { icon_hl } }, { str = foldername, hl = { foldername_hl } } end +---@private ---@param node table ---@return HighlightedString icon ---@return HighlightedString name -function Builder:_build_symlink(node) +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 + if M.opts.renderer.symlink_destination then local link_to = utils.path_relative(node.link_to, self.root_cwd) - symlink_formatted = symlink_formatted .. arrow .. link_to + symlink_formatted = string.format("%s%s%s", 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 - +---@private ---@param node table ---@return HighlightedString icon ---@return HighlightedString name -function Builder:_build_file(node) - local icon = self:_build_file_icon(node) - +function Builder:build_file(node) local hl - if vim.tbl_contains(self.special_files, node.absolute_path) or vim.tbl_contains(self.special_files, node.name) then + if vim.tbl_contains(M.opts.renderer.special_files, node.absolute_path) or vim.tbl_contains(M.opts.renderer.special_files, node.name) then hl = "NvimTreeSpecialFile" elseif node.executable then hl = "NvimTreeExecFile" - elseif self.picture_map[node.extension] then + elseif M.picture_map[node.extension] then hl = "NvimTreeImageFile" end - return icon, { str = node.name, hl = { hl } } + local icon, hl_group = icons.get_file_icon(node.name, node.extension) + return { str = icon, hl = { hl_group } }, { str = node.name, hl = { hl } } end +---@private ---@param indent_markers HighlightedString[] ---@param arrows HighlightedString[]|nil ---@param icon HighlightedString ---@param name HighlightedString ---@param node table ---@return HighlightedString[] -function Builder:_format_line(indent_markers, arrows, icon, name, node) +function Builder:format_line(indent_markers, arrows, icon, name, node) local added_len = 0 local function add_to_end(t1, t2) if not t2 then @@ -219,7 +204,7 @@ function Builder:_format_line(indent_markers, arrows, icon, name, node) end for _, v in ipairs(t2) do if added_len > 0 then - table.insert(t1, { str = self.icon_padding }) + table.insert(t1, { str = M.opts.renderer.icons.padding }) end table.insert(t1, v) end @@ -235,42 +220,47 @@ function Builder:_format_line(indent_markers, arrows, icon, name, node) local line = { indent_markers, arrows } add_to_end(line, { icon }) - for i = #self.decorators, 1, -1 do - add_to_end(line, self.decorators[i]:icons_before(node)) + for i = #M.decorators, 1, -1 do + add_to_end(line, M.decorators[i]:icons_before(node)) end add_to_end(line, { name }) - for i = #self.decorators, 1, -1 do - add_to_end(line, self.decorators[i]:icons_after(node)) + for i = #M.decorators, 1, -1 do + add_to_end(line, M.decorators[i]:icons_after(node)) end + log.line("dev", "line = %s", vim.inspect(line)) return line end -function Builder:_build_signs(node) +---@private +---@param node Node +function Builder:build_signs(node) -- first in priority order local sign_name - for _, d in ipairs(self.decorators) do + for _, d in ipairs(M.decorators) do sign_name = d:sign_name(node) if sign_name then - self.sign_names[self.index] = sign_name + self.signs[self.index] = sign_name break end end end ---Combined group name less than the 200 byte limit of highlight group names +---@private ---@param groups string[] highlight group names ---@return string name "NvimTreeCombinedHL" .. sha256 -function Builder:_combined_group_name(groups) +function Builder:combined_group_name(groups) return string.format("NvimTreeCombinedHL%s", vim.fn.sha256(table.concat(groups))) end ---Create a highlight group for groups with later groups overriding previous. +---@private ---@param groups string[] highlight group names -function Builder:_create_combined_group(groups) - local combined_name = self:_combined_group_name(groups) +function Builder:create_combined_group(groups) + local combined_name = self:combined_group_name(groups) -- only create if necessary if not vim.tbl_contains(self.combined_groups, combined_name) then @@ -292,10 +282,11 @@ end ---Calculate highlight group for icon and name. A combined highlight group will be created ---when there is more than one highlight. ---A highlight group is always calculated and upserted for the case of highlights changing. +---@private ---@param node Node ---@return string|nil icon_hl_group ---@return string|nil name_hl_group -function Builder:_add_highlights(node) +function Builder:add_highlights(node) -- result local icon_hl_group, name_hl_group @@ -303,8 +294,8 @@ function Builder:_add_highlights(node) local icon_groups = {} local name_groups = {} local d, icon, name - for i = #self.decorators, 1, -1 do - d = self.decorators[i] + for i = #M.decorators, 1, -1 do + d = M.decorators[i] icon, name = d:groups_icon_name(node) table.insert(icon_groups, icon) table.insert(name_groups, name) @@ -312,16 +303,16 @@ function Builder:_add_highlights(node) -- one or many icon groups if #icon_groups > 1 then - icon_hl_group = self:_combined_group_name(icon_groups) - self:_create_combined_group(icon_groups) + icon_hl_group = self:combined_group_name(icon_groups) + self:create_combined_group(icon_groups) else icon_hl_group = icon_groups[1] end -- one or many name groups if #name_groups > 1 then - name_hl_group = self:_combined_group_name(name_groups) - self:_create_combined_group(name_groups) + name_hl_group = self:combined_group_name(name_groups) + self:create_combined_group(name_groups) else name_hl_group = name_groups[1] end @@ -329,7 +320,8 @@ function Builder:_add_highlights(node) return icon_hl_group, name_hl_group end -function Builder:_build_line(node, idx, num_children) +---@private +function Builder:build_line(node, idx, num_children) -- various components local indent_markers = pad.get_indent_markers(self.depth, idx, num_children, node, self.markers) local arrows = pad.get_arrows(node) @@ -339,20 +331,20 @@ function Builder:_build_line(node, idx, num_children) local is_symlink = node.link_to ~= nil local icon, name if is_folder then - icon, name = self:_build_folder(node) + icon, name = self:build_folder(node) elseif is_symlink then - icon, name = self:_build_symlink(node) + icon, name = self:build_symlink(node) else - icon, name = self:_build_file(node) + icon, name = self:build_file(node) end -- highighting - local icon_hl_group, name_hl_group = self:_add_highlights(node) + 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) - self:_insert_line(self:_unwrap_highlighted_strings(line)) + local line = self:format_line(indent_markers, arrows, icon, name, node) + table.insert(self.lines, self:unwrap_highlighted_strings(line)) self.index = self.index + 1 @@ -360,13 +352,14 @@ function Builder:_build_line(node, idx, num_children) if node.open then self.depth = self.depth + 1 - self:build(node) + self:build_lines(node) self.depth = self.depth - 1 end end -function Builder:_get_nodes_number(nodes) - if not self.filter then +---@private +function Builder:get_nodes_number(nodes) + if not live_filter.filter then return #nodes end @@ -379,54 +372,77 @@ function Builder:_get_nodes_number(nodes) return i end -function Builder:build(tree) - local num_children = self:_get_nodes_number(tree.nodes) +---@private +function Builder:build_lines(node) + if not node then + node = core.get_explorer() + end + local num_children = self:get_nodes_number(node.nodes) local idx = 1 - for _, node in ipairs(tree.nodes) do - if not node.hidden then - self:_build_signs(node) - self:_build_line(node, idx, num_children) + for _, n in ipairs(node.nodes) do + if not n.hidden then + self:build_signs(n) + self:build_line(n, idx, num_children) idx = idx + 1 end end - - return self end -local function format_root_name(root_cwd, root_label) +---@private +---@param root_label function|string +---@return string +function Builder:format_root_name(root_label) if type(root_label) == "function" then - local label = root_label(root_cwd) + local label = root_label(self.root_cwd) if type(label) == "string" then return label else - root_label = DEFAULT_ROOT_FOLDER_LABEL + return "???" end end - return utils.path_remove_trailing(vim.fn.fnamemodify(root_cwd, root_label)) + return utils.path_remove_trailing(vim.fn.fnamemodify(self.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)) +---@private +function Builder:build_header() + if view.is_root_folder_visible(core.get_cwd()) then + local root_name = self:format_root_name(M.opts.renderer.root_folder_label) + table.insert(self.lines, root_name) + self:insert_highlight({ "NvimTreeRootFolder" }, 0, string.len(root_name)) self.index = 1 end - if self.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)) + if live_filter.filter then + local filter_line = string.format("%s/%s/", M.opts.live_filter.prefix, live_filter.filter) + table.insert(self.lines, filter_line) + local prefix_length = string.len(M.opts.live_filter.prefix) + self:insert_highlight({ "NvimTreeLiveFilterPrefix" }, 0, prefix_length) + self:insert_highlight({ "NvimTreeLiveFilterValue" }, prefix_length, string.len(filter_line)) self.index = self.index + 1 end +end +---Build all lines with highlights and signs +---@return Builder +function Builder:build() + self:build_header() + self:build_lines() return self end -function Builder:unwrap() - return self.lines, self.highlights, self.sign_names +function Builder.setup(opts) + M.opts = opts + + -- priority order + M.decorators = { + DecoratorCut:new(opts), + DecoratorCopied:new(opts), + DecoratorDiagnostics:new(opts), + DecoratorBookmarks:new(opts), + DecoratorModified:new(opts), + DecoratorOpened:new(opts), + DecoratorGit:new(opts), + } end return Builder diff --git a/lua/nvim-tree/renderer/init.lua b/lua/nvim-tree/renderer/init.lua index 351c54b5306..ce918682743 100644 --- a/lua/nvim-tree/renderer/init.lua +++ b/lua/nvim-tree/renderer/init.lua @@ -8,30 +8,24 @@ local _padding = require "nvim-tree.renderer.components.padding" local icon_component = require "nvim-tree.renderer.components.icons" local full_name = require "nvim-tree.renderer.components.full-name" local Builder = require "nvim-tree.renderer.builder" -local live_filter = require "nvim-tree.live-filter" - -local DecoratorBookmarks = require "nvim-tree.renderer.decorator.bookmarks" -local DecoratorCopied = require "nvim-tree.renderer.decorator.copied" -local DecoratorCut = require "nvim-tree.renderer.decorator.cut" -local DecoratorDiagnostics = require "nvim-tree.renderer.decorator.diagnostics" -local DecoratorGit = require "nvim-tree.renderer.decorator.git" -local DecoratorModified = require "nvim-tree.renderer.decorator.modified" -local DecoratorOpened = require "nvim-tree.renderer.decorator.opened" local M = { - last_highlights = {}, - decorators = {}, + last_hl_args = {}, } local SIGN_GROUP = "NvimTreeRendererSigns" -local function _draw(bufnr, lines, hl, sign_names) +---@param bufnr number +---@param lines string[] +---@param hl_args AddHighlightArgs[] +---@param signs string[] +local function _draw(bufnr, lines, hl_args, signs) vim.api.nvim_buf_set_option(bufnr, "modifiable", true) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) - M.render_hl(bufnr, hl) + M.render_hl(bufnr, hl_args) vim.api.nvim_buf_set_option(bufnr, "modifiable", false) vim.fn.sign_unplace(SIGN_GROUP) - for i, sign_name in pairs(sign_names) do + for i, sign_name in pairs(signs) do vim.fn.sign_place(0, SIGN_GROUP, sign_name, bufnr, { lnum = i + 1 }) end end @@ -41,7 +35,7 @@ function M.render_hl(bufnr, hl) return end vim.api.nvim_buf_clear_namespace(bufnr, appearance.NS_ID, 0, -1) - for _, data in ipairs(hl or M.last_highlights) do + for _, data in ipairs(hl or M.last_hl_args) do if type(data[1]) == "table" then for _, group in ipairs(data[1]) do vim.api.nvim_buf_add_highlight(bufnr, appearance.NS_ID, group, data[2], data[3], data[4]) @@ -50,15 +44,6 @@ function M.render_hl(bufnr, hl) end end -local picture_map = { - jpg = true, - jpeg = true, - png = true, - gif = true, - webp = true, - jxl = true, -} - function M.draw() local bufnr = view.get_bufnr() if not core.get_explorer() or not bufnr or not vim.api.nvim_buf_is_loaded(bufnr) then @@ -70,24 +55,13 @@ function M.draw() local cursor = vim.api.nvim_win_get_cursor(view.get_winnr()) icon_component.reset_config() - local lines, hl, sign_names = Builder.new(core.get_cwd(), M.decorators) - :configure_root_label(M.config.root_folder_label) - :configure_trailing_slash(M.config.add_trailing) - :configure_special_files(M.config.special_files) - :configure_picture_map(picture_map) - :configure_icon_padding(M.config.icons.padding) - :configure_symlink_destination(M.config.symlink_destination) - :configure_filter(live_filter.filter, live_filter.prefix) - :configure_group_name_modifier(M.config.group_empty) - :build_header(view.is_root_folder_visible(core.get_cwd())) - :build(core.get_explorer()) - :unwrap() + local builder = Builder:new():build() - _draw(bufnr, lines, hl, sign_names) + _draw(bufnr, builder.lines, builder.hl_args, builder.signs) - M.last_highlights = hl + M.last_hl_args = builder.hl_args - if cursor and #lines >= cursor[1] then + if cursor and #builder.lines >= cursor[1] then vim.api.nvim_win_set_cursor(view.get_winnr(), cursor) end @@ -105,16 +79,7 @@ function M.setup(opts) full_name.setup(opts) icon_component.setup(opts) - -- priority order - M.decorators = { - DecoratorCut:new(opts), - DecoratorCopied:new(opts), - DecoratorDiagnostics:new(opts), - DecoratorBookmarks:new(opts), - DecoratorModified:new(opts), - DecoratorOpened:new(opts), - DecoratorGit:new(opts), - } + Builder.setup(opts) end return M