diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index 1a7292248f0..27ecf71289a 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -388,6 +388,7 @@ Following is the default configuration. See |nvim-tree-opts| for details. highlight_diagnostics = false, highlight_opened_files = "none", highlight_modified = "none", + highlight_clipboard = "name", indent_markers = { enable = false, inline_arrows = true, @@ -843,6 +844,12 @@ Value can be `"none"`, `"icon"`, `"name"` or `"all"` This can be used with or without the icons. Type: `string`, Default `"none"` +*nvim-tree.renderer.highlight_clipboard* +Enable highlight for clipboard items using the `NvimTreeCutHL` and +`NvimTreeCopiedHL` groups. +Value can be `"none"`, `"icon"`, `"name"` or `"all"`. + Type: `string`, Default: `"name"` + *nvim-tree.renderer.indent_markers* Configuration options for tree indent markers. @@ -2171,6 +2178,10 @@ Standard: > NvimTreeStatusLine StatusLine NvimTreeStatusLineNC StatusLineNC < +Clipboard: > + NvimTreeCopiedHL SpellRare + NvimTreeCutHL SpellBad +< Picker: > NvimTreeWindowPicker < diff --git a/lua/nvim-tree.lua b/lua/nvim-tree.lua index 6b9a0d23086..cfb2c3a1ab0 100644 --- a/lua/nvim-tree.lua +++ b/lua/nvim-tree.lua @@ -417,6 +417,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS highlight_diagnostics = false, highlight_opened_files = "none", highlight_modified = "none", + highlight_clipboard = "name", indent_markers = { enable = false, inline_arrows = true, diff --git a/lua/nvim-tree/actions/fs/copy-paste.lua b/lua/nvim-tree/actions/fs/copy-paste.lua index 1b26ef12678..de21d0edbf9 100644 --- a/lua/nvim-tree/actions/fs/copy-paste.lua +++ b/lua/nvim-tree/actions/fs/copy-paste.lua @@ -4,6 +4,9 @@ local utils = require "nvim-tree.utils" local core = require "nvim-tree.core" local events = require "nvim-tree.events" local notify = require "nvim-tree.notify" +local renderer = require "nvim-tree.renderer" + +local HL_POSITION = require("nvim-tree.enum").HL_POSITION local find_file = require("nvim-tree.actions.finders.find-file").fn @@ -12,7 +15,7 @@ local M = { } local clipboard = { - move = {}, + cut = {}, copy = {}, } @@ -130,34 +133,37 @@ local function do_single_paste(source, dest, action_type, action_fn) end end -local function add_to_clipboard(node, clip) +local function toggle(node, clip) if node.name == ".." then return end local notify_node = notify.render_path(node.absolute_path) - for idx, _node in ipairs(clip) do - if _node.absolute_path == node.absolute_path then - table.remove(clip, idx) - return notify.info(notify_node .. " removed from clipboard.") - end + if utils.array_remove(clip, node) then + return notify.info(notify_node .. " removed from clipboard.") end + table.insert(clip, node) notify.info(notify_node .. " added to clipboard.") end function M.clear_clipboard() - clipboard.move = {} + clipboard.cut = {} clipboard.copy = {} notify.info "Clipboard has been emptied." + renderer.draw() end function M.copy(node) - add_to_clipboard(node, clipboard.copy) + utils.array_remove(clipboard.cut, node) + toggle(node, clipboard.copy) + renderer.draw() end function M.cut(node) - add_to_clipboard(node, clipboard.move) + utils.array_remove(clipboard.copy, node) + toggle(node, clipboard.cut) + renderer.draw() end local function do_paste(node, action_type, action_fn) @@ -213,8 +219,8 @@ local function do_cut(source, destination) end function M.paste(node) - if clipboard.move[1] ~= nil then - return do_paste(node, "move", do_cut) + if clipboard.cut[1] ~= nil then + return do_paste(node, "cut", do_cut) end return do_paste(node, "copy", do_copy) @@ -222,16 +228,16 @@ end function M.print_clipboard() local content = {} - if #clipboard.move > 0 then + if #clipboard.cut > 0 then table.insert(content, "Cut") - for _, item in pairs(clipboard.move) do - table.insert(content, " * " .. (notify.render_path(item.absolute_path))) + for _, node in pairs(clipboard.cut) do + table.insert(content, " * " .. (notify.render_path(node.absolute_path))) end end if #clipboard.copy > 0 then table.insert(content, "Copy") - for _, item in pairs(clipboard.copy) do - table.insert(content, " * " .. (notify.render_path(item.absolute_path))) + for _, node in pairs(clipboard.copy) do + table.insert(content, " * " .. (notify.render_path(node.absolute_path))) end end @@ -267,9 +273,34 @@ function M.copy_absolute_path(node) return copy_to_clipboard(content) end +---Clipboard text highlight group and position when highlight_clipboard. +---@param node table +---@return HL_POSITION position none when clipboard empty +---@return string|nil group only when node present in clipboard +function M.get_highlight(node) + if M.hl_pos == HL_POSITION.none then + return HL_POSITION.none, nil + end + + for _, n in ipairs(clipboard.cut) do + if node == n then + return M.hl_pos, "NvimTreeCutHL" + end + end + + for _, n in ipairs(clipboard.copy) do + if node == n then + return M.hl_pos, "NvimTreeCopiedHL" + end + end + + return HL_POSITION.none, nil +end + function M.setup(opts) M.config.filesystem_watchers = opts.filesystem_watchers M.config.actions = opts.actions + M.hl_pos = HL_POSITION[opts.renderer.highlight_clipboard] end return M diff --git a/lua/nvim-tree/colors.lua b/lua/nvim-tree/colors.lua index d06957106db..62b676589dd 100644 --- a/lua/nvim-tree/colors.lua +++ b/lua/nvim-tree/colors.lua @@ -106,6 +106,8 @@ local function get_links() StatusLine = "StatusLine", StatusLineNC = "StatusLineNC", SignColumn = "NvimTreeNormal", + CutHL = "SpellBad", + CopiedHL = "SpellRare", } end diff --git a/lua/nvim-tree/enum.lua b/lua/nvim-tree/enum.lua new file mode 100644 index 00000000000..e3791427e31 --- /dev/null +++ b/lua/nvim-tree/enum.lua @@ -0,0 +1,20 @@ +local M = {} + +---Setup options for "highlight_*" +---@enum HL_POSITION +M.HL_POSITION = { + none = 0, + icon = 1, + name = 2, + all = 4, +} + +---Setup options for "*_placement" +---@enum ICON_PLACEMENT +M.ICON_PLACEMENT = { + signcolumn = 0, + before = 1, + after = 2, +} + +return M diff --git a/lua/nvim-tree/renderer/builder.lua b/lua/nvim-tree/renderer/builder.lua index ba5b5478dce..ad39e7f3684 100644 --- a/lua/nvim-tree/renderer/builder.lua +++ b/lua/nvim-tree/renderer/builder.lua @@ -7,6 +7,8 @@ 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 HL_POSITION = require("nvim-tree.enum").HL_POSITION + local Builder = {} Builder.__index = Builder @@ -114,7 +116,7 @@ end ---@class HighlightedString ---@field str string ----@field hl string|nil +---@field hl string[] ---@param highlighted_strings HighlightedString[] ---@return string @@ -126,7 +128,7 @@ function Builder:_unwrap_highlighted_strings(highlighted_strings) local string = "" for _, v in ipairs(highlighted_strings) do if #v.str > 0 then - if v.hl then + if v.hl and type(v.hl) == "table" then self:_insert_highlight(v.hl, #string, #string + #v.str) end string = string .. v.str @@ -136,7 +138,8 @@ function Builder:_unwrap_highlighted_strings(highlighted_strings) end ---@param node table ----@return HighlightedString icon, HighlightedString name +---@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) @@ -166,11 +169,12 @@ function Builder:_build_folder(node) foldername_hl = "NvimTreeEmptyFolderName" end - return { str = icon, hl = icon_hl }, { str = foldername, hl = foldername_hl } + return { str = icon, hl = { icon_hl } }, { str = foldername, hl = { foldername_hl } } end ---@param node table ----@return HighlightedString icon, HighlightedString name +---@return HighlightedString icon +---@return HighlightedString name function Builder:_build_symlink(node) local icon = icons.i.symlink local arrow = icons.i.symlink_arrow @@ -180,21 +184,19 @@ function Builder:_build_symlink(node) symlink_formatted = symlink_formatted .. arrow .. link_to end - local link_highlight = "NvimTreeSymlink" - local icon_hl = "NvimTreeSymlinkIcon" - - return { str = icon, hl = icon_hl }, { str = symlink_formatted, hl = link_highlight } + 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 } + return { str = icon, hl = { hl_group } } end ---@param node table ----@return HighlightedString icon, HighlightedString name +---@return HighlightedString icon +---@return HighlightedString name function Builder:_build_file(node) local icon = self:_build_file_icon(node) @@ -207,7 +209,7 @@ function Builder:_build_file(node) hl = "NvimTreeImageFile" end - return icon, { str = node.name, hl = hl } + return icon, { str = node.name, hl = { hl } } end ---@param node table @@ -215,7 +217,7 @@ end 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, lnum = self.index + 1, priority = 1 }) + table.insert(self.signs, { sign = git_icons[1].hl[1], lnum = self.index + 1, priority = 1 }) git_icons = nil end return git_icons @@ -226,7 +228,7 @@ end 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, lnum = self.index + 1, priority = 2 }) + table.insert(self.signs, { sign = diagnostics_icon.hl[1], lnum = self.index + 1, priority = 2 }) diagnostics_icon = nil end return diagnostics_icon @@ -237,14 +239,15 @@ end 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, lnum = self.index + 1, priority = 3 }) + 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 string icon_highlight, string name_highlight +---@return string|nil icon_hl +---@return string|nil name_hl function Builder:_get_highlight_override(node, unloaded_bufnr) -- highlights precedence: -- original < git < opened_file < modified @@ -281,15 +284,26 @@ function Builder:_get_highlight_override(node, unloaded_bufnr) end end - -- diagnostic status - local diagnostic_highlight = diagnostics.get_highlight(node) - if diagnostic_highlight then - name_hl = diagnostic_highlight - 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 @@ -344,6 +358,8 @@ function Builder:_format_line(indent_markers, arrows, icon, name, git_icons, dia 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) @@ -364,14 +380,18 @@ function Builder:_build_line(node, idx, num_children, unloaded_bufnr) end -- highlight override - local icon_hl, name_hl = self:_get_highlight_override(node, unloaded_bufnr) - if icon_hl then - icon.hl = icon_hl + 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 then - name.hl = name_hl + if name_hl_override then + name.hl = { name_hl_override } end + -- extra highighting + 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) self:_insert_line(self:_unwrap_highlighted_strings(line)) @@ -429,7 +449,7 @@ 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:_insert_highlight({ "NvimTreeRootFolder" }, 0, string.len(root_name)) self.index = 1 end @@ -437,8 +457,8 @@ function Builder:build_header(show_header) 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:_insert_highlight({ "NvimTreeLiveFilterPrefix" }, 0, prefix_length) + self:_insert_highlight({ "NvimTreeLiveFilterValue" }, prefix_length, string.len(filter_line)) self.index = self.index + 1 end diff --git a/lua/nvim-tree/renderer/components/diagnostics.lua b/lua/nvim-tree/renderer/components/diagnostics.lua index e62ebd22398..5475aa5c5fe 100644 --- a/lua/nvim-tree/renderer/components/diagnostics.lua +++ b/lua/nvim-tree/renderer/components/diagnostics.lua @@ -1,19 +1,32 @@ -local M = {} +local HL_POSITION = require("nvim-tree.enum").HL_POSITION -local HS_FILE = {} -local HS_FOLDER = {} -local ICON = {} +local M = { + HS_FILE = {}, + HS_FOLDER = {}, + ICON = {}, + hl_pos = HL_POSITION.none, +} ----diagnostics text highlight group if there is a status +---Diagnostics text highlight group when highlight_diagnostics. ---@param node table ----@return string|nil highlight +---@return HL_POSITION position none when no status +---@return string|nil group only when status function M.get_highlight(node) - if node and M.config.diagnostics.enable and M.config.renderer.highlight_diagnostics then - if node.nodes then - return HS_FOLDER[node.diag_status] - else - return HS_FILE[node.diag_status] - end + if not node or M.hl_pos == HL_POSITION.none then + return HL_POSITION.none, nil + end + + local group + if node.nodes then + group = M.HS_FOLDER[node.diag_status] + else + group = M.HS_FILE[node.diag_status] + end + + if group then + return M.hl_pos, group + else + return HL_POSITION.none, nil end end @@ -22,7 +35,7 @@ end ---@return HighlightedString|nil modified icon function M.get_icon(node) if node and M.config.diagnostics.enable and M.config.renderer.icons.show.diagnostics then - return ICON[node.diag_status] + return M.ICON[node.diag_status] end end @@ -32,36 +45,42 @@ function M.setup(opts) renderer = opts.renderer, } - HS_FILE[vim.diagnostic.severity.ERROR] = "NvimTreeLspDiagnosticsErrorText" - HS_FILE[vim.diagnostic.severity.WARN] = "NvimTreeLspDiagnosticsWarningText" - HS_FILE[vim.diagnostic.severity.INFO] = "NvimTreeLspDiagnosticsInfoText" - HS_FILE[vim.diagnostic.severity.HINT] = "NvimTreeLspDiagnosticsHintText" + if opts.diagnostics.enable and opts.renderer.highlight_diagnostics then + -- TODO add a HL_POSITION + -- M.hl_pos = HL_POSITION[opts.renderer.highlight_diagnostics] + M.hl_pos = HL_POSITION.name + end + + M.HS_FILE[vim.diagnostic.severity.ERROR] = "NvimTreeLspDiagnosticsErrorText" + M.HS_FILE[vim.diagnostic.severity.WARN] = "NvimTreeLspDiagnosticsWarningText" + M.HS_FILE[vim.diagnostic.severity.INFO] = "NvimTreeLspDiagnosticsInfoText" + M.HS_FILE[vim.diagnostic.severity.HINT] = "NvimTreeLspDiagnosticsHintText" - HS_FOLDER[vim.diagnostic.severity.ERROR] = "NvimTreeLspDiagnosticsErrorFolderText" - HS_FOLDER[vim.diagnostic.severity.WARN] = "NvimTreeLspDiagnosticsWarningFolderText" - HS_FOLDER[vim.diagnostic.severity.INFO] = "NvimTreeLspDiagnosticsInfoFolderText" - HS_FOLDER[vim.diagnostic.severity.HINT] = "NvimTreeLspDiagnosticsHintFolderText" + M.HS_FOLDER[vim.diagnostic.severity.ERROR] = "NvimTreeLspDiagnosticsErrorFolderText" + M.HS_FOLDER[vim.diagnostic.severity.WARN] = "NvimTreeLspDiagnosticsWarningFolderText" + M.HS_FOLDER[vim.diagnostic.severity.INFO] = "NvimTreeLspDiagnosticsInfoFolderText" + M.HS_FOLDER[vim.diagnostic.severity.HINT] = "NvimTreeLspDiagnosticsHintFolderText" - ICON[vim.diagnostic.severity.ERROR] = { + M.ICON[vim.diagnostic.severity.ERROR] = { str = M.config.diagnostics.icons.error, - hl = "NvimTreeLspDiagnosticsError", + hl = { "NvimTreeLspDiagnosticsError" }, } - ICON[vim.diagnostic.severity.WARN] = { + M.ICON[vim.diagnostic.severity.WARN] = { str = M.config.diagnostics.icons.warning, - hl = "NvimTreeLspDiagnosticsWarning", + hl = { "NvimTreeLspDiagnosticsWarning" }, } - ICON[vim.diagnostic.severity.INFO] = { + M.ICON[vim.diagnostic.severity.INFO] = { str = M.config.diagnostics.icons.info, - hl = "NvimTreeLspDiagnosticsInfo", + hl = { "NvimTreeLspDiagnosticsInfo" }, } - ICON[vim.diagnostic.severity.HINT] = { + M.ICON[vim.diagnostic.severity.HINT] = { str = M.config.diagnostics.icons.hint, - hl = "NvimTreeLspDiagnosticsHint", + hl = { "NvimTreeLspDiagnosticsHint" }, } - for _, i in ipairs(ICON) do - vim.fn.sign_define(i.hl, { text = i.str, texthl = i.hl }) + for _, i in ipairs(M.ICON) do + vim.fn.sign_define(i.hl[1], { text = i.str, texthl = i.hl[1] }) end end diff --git a/lua/nvim-tree/renderer/components/git.lua b/lua/nvim-tree/renderer/components/git.lua index fdfa8b81077..977031fd945 100644 --- a/lua/nvim-tree/renderer/components/git.lua +++ b/lua/nvim-tree/renderer/components/git.lua @@ -5,13 +5,13 @@ local M = {} local function build_icons_table(i) local icons = { - staged = { str = i.staged, hl = "NvimTreeGitStaged", ord = 1 }, - unstaged = { str = i.unstaged, hl = "NvimTreeGitDirty", ord = 2 }, - renamed = { str = i.renamed, hl = "NvimTreeGitRenamed", ord = 3 }, - deleted = { str = i.deleted, hl = "NvimTreeGitDeleted", ord = 4 }, - unmerged = { str = i.unmerged, hl = "NvimTreeGitMerge", ord = 5 }, - untracked = { str = i.untracked, hl = "NvimTreeGitNew", ord = 6 }, - ignored = { str = i.ignored, hl = "NvimTreeGitIgnored", ord = 7 }, + staged = { str = i.staged, hl = { "NvimTreeGitStaged" }, ord = 1 }, + unstaged = { str = i.unstaged, hl = { "NvimTreeGitDirty" }, ord = 2 }, + renamed = { str = i.renamed, hl = { "NvimTreeGitRenamed" }, ord = 3 }, + deleted = { str = i.deleted, hl = { "NvimTreeGitDeleted" }, ord = 4 }, + unmerged = { str = i.unmerged, hl = { "NvimTreeGitMerge" }, ord = 5 }, + untracked = { str = i.untracked, hl = { "NvimTreeGitNew" }, ord = 6 }, + ignored = { str = i.ignored, hl = { "NvimTreeGitIgnored" }, ord = 7 }, } return { ["M "] = { icons.staged }, diff --git a/lua/nvim-tree/renderer/components/modified.lua b/lua/nvim-tree/renderer/components/modified.lua index 1ae9870b405..f247d7b2046 100644 --- a/lua/nvim-tree/renderer/components/modified.lua +++ b/lua/nvim-tree/renderer/components/modified.lua @@ -12,7 +12,7 @@ function M.get_icon(node) return nil end - return { str = M.icon, hl = HIGHLIGHT } + return { str = M.icon, hl = { HIGHLIGHT } } end function M.setup_signs() diff --git a/lua/nvim-tree/renderer/components/padding.lua b/lua/nvim-tree/renderer/components/padding.lua index b20091f0c57..f3196f88908 100644 --- a/lua/nvim-tree/renderer/components/padding.lua +++ b/lua/nvim-tree/renderer/components/padding.lua @@ -78,7 +78,7 @@ function M.get_indent_markers(depth, idx, nodes_number, node, markers) str = str .. string.rep(" ", depth * indent_width) end - return { str = str, hl = "NvimTreeIndentMarker" } + return { str = str, hl = { "NvimTreeIndentMarker" } } end ---@param node table @@ -104,7 +104,7 @@ function M.get_arrows(node) str = " " end - return { str = str, hl = hl } + return { str = str, hl = { hl } } end function M.setup(opts) diff --git a/lua/nvim-tree/renderer/init.lua b/lua/nvim-tree/renderer/init.lua index 8785e175ef3..72e04df017d 100644 --- a/lua/nvim-tree/renderer/init.lua +++ b/lua/nvim-tree/renderer/init.lua @@ -38,7 +38,11 @@ function M.render_hl(bufnr, hl) end vim.api.nvim_buf_clear_namespace(bufnr, namespace_id, 0, -1) for _, data in ipairs(hl or M.last_highlights) do - vim.api.nvim_buf_add_highlight(bufnr, namespace_id, data[1], data[2], data[3], data[4]) + if type(data[1]) == "table" then + for _, group in ipairs(data[1]) do + vim.api.nvim_buf_add_highlight(bufnr, namespace_id, group, data[2], data[3], data[4]) + end + end end end diff --git a/lua/nvim-tree/utils.lua b/lua/nvim-tree/utils.lua index 386472eaa85..9181d87296f 100644 --- a/lua/nvim-tree/utils.lua +++ b/lua/nvim-tree/utils.lua @@ -425,12 +425,18 @@ function M.array_shallow_clone(array) return to end --- remove item from array if it exists +--- Remove and return item from array if present. +--- @param array table +--- @param item any +--- @return any|nil removed function M.array_remove(array, item) + if not array then + return nil + end for i, v in ipairs(array) do if v == item then table.remove(array, i) - break + return v end end end