diff --git a/README.md b/README.md index 87a16d63860..8ecc7632f77 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Setup the plugin in your `init.lua` vim.g.loaded_netrw = 1 vim.g.loaded_netrwPlugin = 1 --- set termguicolors to enable highlight groups +-- optionally enable 24-bit colour vim.opt.termguicolors = true -- empty setup using defaults diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index baec6ca8a26..4a4dee03612 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -46,6 +46,7 @@ CONTENTS *nvim-tree* 7. Mappings |nvim-tree-mappings| 7.1 Mappings: Default |nvim-tree-mappings-default| 8. Highlight |nvim-tree-highlight| + 8.1 Highlight Overhaul |nvim-tree-highlight-overhaul| 9. Events |nvim-tree-events| 10. Prompts |nvim-tree-prompts| 11. OS Specific Restrictions |nvim-tree-os-specific| @@ -113,7 +114,7 @@ Setup the plugin in your `init.lua` > vim.g.loaded_netrw = 1 vim.g.loaded_netrwPlugin = 1 - -- set termguicolors to enable highlight groups + -- optionally enable 24-bit colour vim.opt.termguicolors = true -- empty setup using defaults @@ -387,8 +388,8 @@ Following is the default configuration. See |nvim-tree-opts| for details. indent_width = 2, special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" }, symlink_destination = true, - highlight_git = false, - highlight_diagnostics = false, + highlight_git = "none", + highlight_diagnostics = "none", highlight_opened_files = "none", highlight_modified = "none", highlight_bookmarks = "none", @@ -789,8 +790,8 @@ Use nvim-tree in a floating window. ============================================================================== 5.3 OPTS: RENDERER *nvim-tree-opts-renderer* -Highlight precedence: - clipboard > diagnostics > bookmarked > modified > opened > git +Highlight precedence, additive: + git < opened < modified < bookmarked < diagnostics < copied < cut *nvim-tree.renderer.add_trailing* Appends a trailing slash to folder names. @@ -832,14 +833,16 @@ Whether to show the destination of the symlink. Type: `boolean`, Default: `true` *nvim-tree.renderer.highlight_git* -Enable highlight for git attributes using `NvimTreeGit*` highlight groups. +Enable highlight for git attributes using `NvimTreeGit*HL` highlight groups. Requires |nvim-tree.git.enable| - Type: `boolean`, Default: `false` +Value can be `"none"`, `"icon"`, `"name"` or `"all"`. + Type: `string`, Default: `"none"` *nvim-tree.renderer.highlight_diagnostics* -Enable highlight for diagnostics using `LspDiagnosticsError*Text` highlight groups. +Enable highlight for diagnostics using `NvimTreeDiagnostic*HL` highlight groups. Requires |nvim-tree.diagnostics.enable| - Type: `boolean`, Default: `false` +Value can be `"none"`, `"icon"`, `"name"` or `"all"`. + Type: `string`, Default: `"none"` *nvim-tree.renderer.highlight_opened_files* Highlight icons and/or names for |bufloaded()| files using the @@ -892,8 +895,8 @@ Configuration options for tree indent markers. *nvim-tree.renderer.icons* Configuration options for icons. -Icon sign column precedence: - diagnostics > modified > git > bookmarked +Icon order and sign column precedence: + git < modified < bookmarked < diagnostics *nvim-tree.renderer.icons.web_devicons* Configure optional plugin `"nvim-tree/nvim-web-devicons"` @@ -907,7 +910,7 @@ Icon sign column precedence: Type: `boolean`, Default: `true` *nvim-tree.renderer.icons.web_devicons.file.color* - Use icon colors for files. + Use icon colors for files. Overrides highlight groups. Type: `boolean`, Default: `true` *nvim-tree.renderer.icons.web_devicons.folder* @@ -919,7 +922,7 @@ Icon sign column precedence: Type: `boolean`, Default: `false` *nvim-tree.renderer.icons.web_devicons.folder.color* - Use icon colors for folders. + Use icon colors for folders. Overrides highlight groups. Type: `boolean`, Default: `true` *nvim-tree.renderer.icons.git_placement* @@ -2237,42 +2240,13 @@ groups. Example |:highlight| > :hi NvimTreeSymlink guifg=blue gui=bold,underline < -You should have 'termguicolors' enabled, otherwise, colors will not be -applied. +It is recommended to enable 'termguicolors' for the more pleasant 24-bit colours. To view the active highlight groups run `:so $VIMRUNTIME/syntax/hitest.vim` as per |:highlight| -Default linked group follows name. +Default linked group or definition follows name. -File Text: > - NvimTreeSymlink - NvimTreeExecFile - NvimTreeOpenedFile - NvimTreeModifiedFile - NvimTreeSpecialFile - NvimTreeImageFile -< -Folder Text: > - NvimTreeFolderName Directory - NvimTreeEmptyFolderName Directory - NvimTreeOpenedFolderName Directory - NvimTreeSymlinkFolderName Directory - NvimTreeRootFolder -< -Icon: > - NvimTreeFileIcon - NvimTreeOpenedFileIcon NvimTreeOpenedFile - NvimTreeSymlinkIcon - NvimTreeFolderIcon - NvimTreeOpenedFolderIcon NvimTreeFolderIcon - NvimTreeClosedFolderIcon NvimTreeFolderIcon - NvimTreeFolderArrowClosed NvimTreeIndentMarker - NvimTreeFolderArrowOpen NvimTreeIndentMarker -< -Indent: > - NvimTreeIndentMarker -< Standard: > NvimTreeNormal Normal NvimTreeNormalFloat NormalFloat @@ -2291,67 +2265,163 @@ Standard: > NvimTreeStatusLine StatusLine NvimTreeStatusLineNC StatusLineNC < +File Text: > + NvimTreeExecFile Constant + NvimTreeImageFile PreProc + NvimTreeOpenedFile Constant + NvimTreeSpecialFile PreProc + NvimTreeSymlink Statement +< +Folder Text: > + NvimTreeRootFolder PreProc + NvimTreeFolderName Directory + NvimTreeEmptyFolderName Directory + NvimTreeOpenedFolderName Directory + NvimTreeSymlinkFolderName Directory +< +File Icons: > + NvimTreeFileIcon NvimTreeNormal + NvimTreeSymlinkIcon NvimTreeNormal + NvimTreeOpenedFileIcon NvimTreeOpenedFile +< +Folder Icons: > + NvimTreeFolderIcon guifg=#8094b4 ctermfg=Blue + NvimTreeOpenedFolderIcon NvimTreeFolderIcon + NvimTreeClosedFolderIcon NvimTreeFolderIcon + NvimTreeFolderArrowClosed NvimTreeIndentMarker + NvimTreeFolderArrowOpen NvimTreeIndentMarker +< +Indent: > + NvimTreeIndentMarker NvimTreeFileIcon +< +Picker: > + NvimTreeWindowPicker guifg=#ededed guibg=#4493c8 gui=bold ctermfg=White ctermbg=Cyan +< +Live Filter: > + NvimTreeLiveFilterPrefix PreProc + NvimTreeLiveFilterValue ModeMsg +< Clipboard: > NvimTreeCopiedHL SpellRare NvimTreeCutHL SpellBad < -Bookmark Icon: > - NvimTreeBookmark -< -Bookmark Highlight: > +Bookmarks: > + NvimTreeBookmarkIcon Constant NvimTreeBookmarkHL SpellLocal < -Picker: > - NvimTreeWindowPicker +Modified: > + NvimTreeModifiedIcon Constant + NvimTreeModifiedFileHL NvimTreeModifiedIcon + NvimTreeModifiedFolderHL NvimTreeModifiedIcon < -Live Filter: > - NvimTreeLiveFilterPrefix - NvimTreeLiveFilterValue +Opened: > + NvimTreeOpenedHL Constant < Git Icon: > - NvimTreeGitDirty - NvimTreeGitStaged - NvimTreeGitMerge - NvimTreeGitRenamed - NvimTreeGitNew - NvimTreeGitDeleted - NvimTreeGitIgnored Comment + NvimTreeGitDeletedIcon Statement + NvimTreeGitDirtyIcon Statement + NvimTreeGitIgnoredIcon Comment + NvimTreeGitMergeIcon Constant + NvimTreeGitNewIcon PreProc + NvimTreeGitRenamedIcon PreProc + NvimTreeGitStagedIcon Constant < -Git File Text: > - NvimTreeFileDirty NvimTreeGitDirty - NvimTreeFileStaged NvimTreeGitStaged - NvimTreeFileMerge NvimTreeGitMerge - NvimTreeFileRenamed NvimTreeGitRenamed - NvimTreeFileNew NvimTreeGitNew - NvimTreeFileDeleted NvimTreeGitDeleted - NvimTreeFileIgnored NvimTreeGitIgnored +Git File File Highlight: > + NvimTreeGitFileDeletedHL NvimTreeGitDeletedIcon + NvimTreeGitFileDirtyHL NvimTreeGitDirtyIcon + NvimTreeGitFileIgnoredHL NvimTreeGitIgnoredIcon + NvimTreeGitFileMergeHL NvimTreeGitMergeIcon + NvimTreeGitFileNewHL NvimTreeGitNewIcon + NvimTreeGitFileRenamedHL NvimTreeGitRenamedIcon + NvimTreeGitFileStagedHL NvimTreeGitStagedIcon < -Git Folder Text: > - NvimTreeFolderDirty NvimTreeFileDirty - NvimTreeFolderStaged NvimTreeFileStaged - NvimTreeFolderMerge NvimTreeFileMerge - NvimTreeFolderRenamed NvimTreeFileRenamed - NvimTreeFolderNew NvimTreeFileNew - NvimTreeFolderDeleted NvimTreeFileDeleted - NvimTreeFolderIgnored NvimTreeFileIgnored +Git Folder Folder Highlight: > + NvimTreeGitFolderDeletedHL NvimTreeGitFileDeletedHL + NvimTreeGitFolderDirtyHL NvimTreeGitFileDirtyHL + NvimTreeGitFolderIgnoredHL NvimTreeGitFileIgnoredHL + NvimTreeGitFolderMergeHL NvimTreeGitFileMergeHL + NvimTreeGitFolderNewHL NvimTreeGitFileNewHL + NvimTreeGitFolderRenamedHL NvimTreeGitFileRenamedHL + NvimTreeGitFolderStagedHL NvimTreeGitFileStagedHL < Diagnostics Icon: > - NvimTreeLspDiagnosticsError DiagnosticError - NvimTreeLspDiagnosticsWarning DiagnosticWarn - NvimTreeLspDiagnosticsInformation DiagnosticInfo - NvimTreeLspDiagnosticsHint DiagnosticHint + NvimTreeDiagnosticErrorIcon DiagnosticError + NvimTreeDiagnosticWarnIcon DiagnosticWarn + NvimTreeDiagnosticInfoIcon DiagnosticInfo + NvimTreeDiagnosticHintIcon DiagnosticHint +< +Diagnostics File Highlight: > + NvimTreeDiagnosticErrorFileHL DiagnosticUnderlineError + NvimTreeDiagnosticWarnFileHL DiagnosticUnderlineWarn + NvimTreeDiagnosticInfoFileHL DiagnosticUnderlineInfo + NvimTreeDiagnosticHintFileHL DiagnosticUnderlineHint < -Diagnostics File Text: > - NvimTreeLspDiagnosticsErrorText NvimTreeLspDiagnosticsError - NvimTreeLspDiagnosticsWarningText NvimTreeLspDiagnosticsWarning - NvimTreeLspDiagnosticsInfoText NvimTreeLspDiagnosticsInformation - NvimTreeLspDiagnosticsHintText NvimTreeLspDiagnosticsHint +Diagnostics Folder Highlight: > + NvimTreeDiagnosticErrorFolderHL NvimTreeDiagnosticErrorFileHL + NvimTreeDiagnosticWarnFolderHL NvimTreeDiagnosticWarnFileHL + NvimTreeDiagnosticInfoFolderHL NvimTreeDiagnosticInfoFileHL + NvimTreeDiagnosticHintFolderHL NvimTreeDiagnosticHintFileHL < -Diagnostics Folder Text: > - NvimTreeLspDiagnosticsErrorFolderText NvimTreeLspDiagnosticsErrorText - NvimTreeLspDiagnosticsWarningFolderText NvimTreeLspDiagnosticsWarningText - NvimTreeLspDiagnosticsInfoFolderText NvimTreeLspDiagnosticsInfoText - NvimTreeLspDiagnosticsHintFolderText NvimTreeLspDiagnosticsHintText +============================================================================== + 8.1 HIGHLIGHT OVERHAUL *nvim-tree-highlight-overhaul* + +2024-01-20: significant highlighting changes, some breaking: + +- Full cterm support. +- Standard vim highlight groups such |DiagnosticUnderlineError| are now the + defaults. +- Highlight groups named consistently. +- All `highlight_xxx` e.g. |nvim-tree.renderer.highlight_git| are granular, + allowing `"none"`, `"icon"`, `"name"` or `"all"` +- `highlight_xxx` has highlight groups for both File and Folder +- `highlight_xxx` is additive instead of overwriting. See + |nvim-tree-opts-renderer| for precedence. + +Legacy highlight group are still obeyed when they are defined and the current +highlight group is not, hard linking as follows: > + + NvimTreeModifiedIcon NvimTreeModifiedFile + NvimTreeOpenedHL NvimTreeOpenedFile + NvimTreeBookmarkIcon NvimTreeBookmark + + NvimTreeGitDeletedIcon NvimTreeGitDeleted + NvimTreeGitDirtyIcon NvimTreeGitDirty + NvimTreeGitIgnoredIcon NvimTreeGitIgnored + NvimTreeGitMergeIcon NvimTreeGitMerge + NvimTreeGitNewIcon NvimTreeGitNew + NvimTreeGitRenamedIcon NvimTreeGitRenamed + NvimTreeGitStagedIcon NvimTreeGitStaged + + NvimTreeGitFileDeletedHL NvimTreeFileDeleted + NvimTreeGitFileDirtyHL NvimTreeFileDirty + NvimTreeGitFileIgnoredHL NvimTreeFileIgnored + NvimTreeGitFileMergeHL NvimTreeFileMerge + NvimTreeGitFileNewHL NvimTreeFileNew + NvimTreeGitFileRenamedHL NvimTreeFileRenamed + NvimTreeGitFileStagedHL NvimTreeFileStaged + + NvimTreeGitFolderDeletedHL NvimTreeFolderDeleted + NvimTreeGitFolderDirtyHL NvimTreeFolderDirty + NvimTreeGitFolderIgnoredHL NvimTreeFolderIgnored + NvimTreeGitFolderMergeHL NvimTreeFolderMerge + NvimTreeGitFolderNewHL NvimTreeFolderNew + NvimTreeGitFolderRenamedHL NvimTreeFolderRenamed + NvimTreeGitFolderStagedHL NvimTreeFolderStaged + + NvimTreeLspDiagnosticsError NvimTreeDiagnosticErrorIcon + NvimTreeLspDiagnosticsWarning NvimTreeDiagnosticWarnIcon + NvimTreeLspDiagnosticsInformation NvimTreeDiagnosticInfoIcon + NvimTreeLspDiagnosticsHint NvimTreeDiagnosticHintIcon + + NvimTreeLspDiagnosticsErrorText NvimTreeDiagnosticErrorFileHL + NvimTreeLspDiagnosticsWarningText NvimTreeDiagnosticWarnFileHL + NvimTreeLspDiagnosticsInformationText NvimTreeDiagnosticInfoFileHL + NvimTreeLspDiagnosticsHintText NvimTreeDiagnosticHintFileHL + + NvimTreeLspDiagnosticsErrorFolderText NvimTreeDiagnosticErrorFolderHL + NvimTreeLspDiagnosticsWarningFolderText NvimTreeDiagnosticWarnFolderHL + NvimTreeLspDiagnosticsInformationFolderText NvimTreeDiagnosticInfoFolderHL + NvimTreeLspDiagnosticsHintFolderText NvimTreeDiagnosticHintFolderHL < ============================================================================== 9. EVENTS *nvim-tree-events* diff --git a/lua/nvim-tree.lua b/lua/nvim-tree.lua index 8c0fb2274aa..15e229b667a 100644 --- a/lua/nvim-tree.lua +++ b/lua/nvim-tree.lua @@ -1,6 +1,6 @@ local lib = require "nvim-tree.lib" local log = require "nvim-tree.log" -local colors = require "nvim-tree.colors" +local appearance = require "nvim-tree.appearance" local renderer = require "nvim-tree.renderer" local view = require "nvim-tree.view" local commands = require "nvim-tree.commands" @@ -104,12 +104,6 @@ function M.open_on_directory() actions.root.change_dir.force_dirchange(bufname, true) end -function M.reset_highlight() - colors.setup() - view.reset_winhl() - renderer.render_hl(view.get_bufnr()) -end - function M.place_cursor_on_node() local search = vim.fn.searchcount() if search and search.exact_match == 1 then @@ -168,8 +162,13 @@ local function setup_autocommands(opts) vim.api.nvim_create_autocmd(name, vim.tbl_extend("force", default_opts, custom_opts)) end - -- reset highlights when colorscheme is changed - create_nvim_tree_autocmd("ColorScheme", { callback = M.reset_highlight }) + -- reset and draw highlights when colorscheme is changed + create_nvim_tree_autocmd("ColorScheme", { + callback = function() + appearance.setup() + renderer.render_hl(view.get_bufnr()) + end, + }) -- prevent new opened file from opening in the same window as nvim-tree create_nvim_tree_autocmd("BufWipeout", { @@ -210,7 +209,7 @@ local function setup_autocommands(opts) -- update opened file buffers if (filters.config.filter_no_buffer or renderer.config.highlight_opened_files ~= "none") and vim.bo[data.buf].buftype == "" then utils.debounce("Buf:filter_buffer", opts.view.debounce_delay, function() - actions.reloaders.reload_explorer(nil, data.buf) + actions.reloaders.reload_explorer() end) end end, @@ -386,8 +385,8 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS indent_width = 2, special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" }, symlink_destination = true, - highlight_git = false, - highlight_diagnostics = false, + highlight_git = "none", + highlight_diagnostics = "none", highlight_opened_files = "none", highlight_modified = "none", highlight_bookmarks = "none", @@ -641,9 +640,11 @@ local ACCEPTED_STRINGS = { signcolumn = { "yes", "no", "auto" }, }, renderer = { + highlight_git = { "none", "icon", "name", "all" }, highlight_opened_files = { "none", "icon", "name", "all" }, highlight_modified = { "none", "icon", "name", "all" }, highlight_bookmarks = { "none", "icon", "name", "all" }, + highlight_diagnostics = { "none", "icon", "name", "all" }, highlight_clipboard = { "none", "icon", "name", "all" }, icons = { git_placement = { "before", "after", "signcolumn" }, @@ -786,7 +787,7 @@ function M.setup(conf) require("nvim-tree.actions").setup(opts) require("nvim-tree.keymap").setup(opts) - require("nvim-tree.colors").setup() + require("nvim-tree.appearance").setup() require("nvim-tree.diagnostics").setup(opts) require("nvim-tree.explorer").setup(opts) require("nvim-tree.git").setup(opts) @@ -796,7 +797,7 @@ function M.setup(conf) require("nvim-tree.renderer").setup(opts) require("nvim-tree.live-filter").setup(opts) require("nvim-tree.marks").setup(opts) - require("nvim-tree.modified").setup(opts) + require("nvim-tree.buffers").setup(opts) require("nvim-tree.help").setup(opts) require("nvim-tree.watcher").setup(opts) if M.config.renderer.icons.show.file and pcall(require, "nvim-web-devicons") then diff --git a/lua/nvim-tree/actions/fs/copy-paste.lua b/lua/nvim-tree/actions/fs/copy-paste.lua index 2c6d701dab1..87f9bbb545b 100644 --- a/lua/nvim-tree/actions/fs/copy-paste.lua +++ b/lua/nvim-tree/actions/fs/copy-paste.lua @@ -7,8 +7,6 @@ local notify = require "nvim-tree.notify" local renderer = require "nvim-tree.renderer" local reloaders = require "nvim-tree.actions.reloaders" -local HL_POSITION = require("nvim-tree.enum").HL_POSITION - local find_file = require("nvim-tree.actions.finders.find-file").fn local M = { @@ -317,34 +315,23 @@ function M.copy_absolute_path(node) copy_to_clipboard(content) end ---- Clipboard text highlight group and position when highlight_clipboard. +---Node is cut. Will not be copied. ---@param node Node ----@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 boolean +function M.is_cut(node) + return vim.tbl_contains(clipboard.cut, node) +end - return HL_POSITION.none, nil +---Node is copied. Will not be cut. +---@param node Node +---@return boolean +function M.is_copied(node) + return vim.tbl_contains(clipboard.copy, node) 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] or HL_POSITION.none end return M diff --git a/lua/nvim-tree/actions/reloaders.lua b/lua/nvim-tree/actions/reloaders.lua index 14f4b0394ec..626b280db7f 100644 --- a/lua/nvim-tree/actions/reloaders.lua +++ b/lua/nvim-tree/actions/reloaders.lua @@ -10,13 +10,12 @@ local M = {} ---@param node Explorer|nil ---@param projects table ----@param unloaded_bufnr number|nil -local function refresh_nodes(node, projects, unloaded_bufnr) +local function refresh_nodes(node, projects) Iterator.builder({ node }) :applier(function(n) if n.nodes then local toplevel = git.get_toplevel(n.cwd or n.link_to or n.absolute_path) - explorer_module.reload(n, projects[toplevel] or {}, unloaded_bufnr) + explorer_module.reload(n, projects[toplevel] or {}) end end) :recursor(function(n) @@ -43,18 +42,16 @@ function M.reload_node_status(parent_node, projects) end local event_running = false ----@param _ table|nil unused node passed by action ----@param unloaded_bufnr number|nil optional bufnr recently unloaded via BufUnload event -function M.reload_explorer(_, unloaded_bufnr) +function M.reload_explorer() if event_running or not core.get_explorer() or vim.v.exiting ~= vim.NIL then return end event_running = true local projects = git.reload() - refresh_nodes(core.get_explorer(), projects, unloaded_bufnr) + refresh_nodes(core.get_explorer(), projects) if view.is_visible() then - renderer.draw(unloaded_bufnr) + renderer.draw() end event_running = false end diff --git a/lua/nvim-tree/appearance.lua b/lua/nvim-tree/appearance.lua new file mode 100644 index 00000000000..dcddf29b459 --- /dev/null +++ b/lua/nvim-tree/appearance.lua @@ -0,0 +1,217 @@ +local M = { + -- namespace for all tree window highlights + NS_ID = vim.api.nvim_create_namespace "nvim_tree", +} + +-- directly defined groups, please keep these to an absolute minimum +local DEFAULT_DEFS = { + + NvimTreeFolderIcon = "guifg=#8094b4 ctermfg=Blue", + NvimTreeWindowPicker = "guifg=#ededed guibg=#4493c8 gui=bold ctermfg=White ctermbg=Cyan", +} + +-- nvim-tree default highlight group links, please attempt to keep in order with help +local DEFAULT_LINKS = { + + -- Standard + NvimTreeNormal = "Normal", + NvimTreeNormalFloat = "NormalFloat", + NvimTreeNormalNC = "NvimTreeNormal", + + NvimTreeLineNr = "LineNr", + NvimTreeWinSeparator = "WinSeparator", + NvimTreeEndOfBuffer = "EndOfBuffer", + NvimTreePopup = "Normal", + NvimTreeSignColumn = "NvimTreeNormal", + + NvimTreeCursorColumn = "CursorColumn", + NvimTreeCursorLine = "CursorLine", + NvimTreeCursorLineNr = "CursorLineNr", + + NvimTreeStatusLine = "StatusLine", + NvimTreeStatusLineNC = "StatusLineNC", + + -- File Text + NvimTreeExecFile = "Constant", + NvimTreeImageFile = "PreProc", + NvimTreeOpenedFile = "Constant", + NvimTreeSpecialFile = "PreProc", + NvimTreeSymlink = "Statement", + + -- Folder Text + NvimTreeRootFolder = "PreProc", + NvimTreeFolderName = "Directory", + NvimTreeEmptyFolderName = "Directory", + NvimTreeOpenedFolderName = "Directory", + NvimTreeSymlinkFolderName = "Directory", + + -- File Icons + NvimTreeFileIcon = "NvimTreeNormal", + NvimTreeSymlinkIcon = "NvimTreeNormal", + NvimTreeOpenedFileIcon = "NvimTreeOpenedFile", + + -- Folder Icons + NvimTreeOpenedFolderIcon = "NvimTreeFolderIcon", + NvimTreeClosedFolderIcon = "NvimTreeFolderIcon", + NvimTreeFolderArrowClosed = "NvimTreeIndentMarker", + NvimTreeFolderArrowOpen = "NvimTreeIndentMarker", + + -- Indent + NvimTreeIndentMarker = "NvimTreeFileIcon", + + -- LiveFilter + NvimTreeLiveFilterPrefix = "PreProc", + NvimTreeLiveFilterValue = "ModeMsg", + + -- Clipboard + NvimTreeCutHL = "SpellBad", + NvimTreeCopiedHL = "SpellRare", + + -- Bookmark + NvimTreeBookmarkIcon = "Constant", + NvimTreeBookmarkHL = "SpellLocal", + + -- Modified + NvimTreeModifiedIcon = "Constant", + NvimTreeModifiedFileHL = "NvimTreeModifiedIcon", + NvimTreeModifiedFolderHL = "NvimTreeModifiedFileHL", + + -- Opened + NvimTreeOpenedHL = "Constant", + + -- Git Icon + NvimTreeGitDeletedIcon = "Statement", + NvimTreeGitDirtyIcon = "Statement", + NvimTreeGitIgnoredIcon = "Comment", + NvimTreeGitMergeIcon = "Constant", + NvimTreeGitNewIcon = "PreProc", + NvimTreeGitRenamedIcon = "PreProc", + NvimTreeGitStagedIcon = "Constant", + + -- Git File Highlight + NvimTreeGitFileDeletedHL = "NvimTreeGitDeletedIcon", + NvimTreeGitFileDirtyHL = "NvimTreeGitDirtyIcon", + NvimTreeGitFileIgnoredHL = "NvimTreeGitIgnoredIcon", + NvimTreeGitFileMergeHL = "NvimTreeGitMergeIcon", + NvimTreeGitFileNewHL = "NvimTreeGitNewIcon", + NvimTreeGitFileRenamedHL = "NvimTreeGitRenamedIcon", + NvimTreeGitFileStagedHL = "NvimTreeGitStagedIcon", + + -- Git Folder Highlight + NvimTreeGitFolderDeletedHL = "NvimTreeGitFileDeletedHL", + NvimTreeGitFolderDirtyHL = "NvimTreeGitFileDirtyHL", + NvimTreeGitFolderIgnoredHL = "NvimTreeGitFileIgnoredHL", + NvimTreeGitFolderMergeHL = "NvimTreeGitFileMergeHL", + NvimTreeGitFolderNewHL = "NvimTreeGitFileNewHL", + NvimTreeGitFolderRenamedHL = "NvimTreeGitFileRenamedHL", + NvimTreeGitFolderStagedHL = "NvimTreeGitFileStagedHL", + + -- Diagnostics Icon + NvimTreeDiagnosticErrorIcon = "DiagnosticError", + NvimTreeDiagnosticWarnIcon = "DiagnosticWarn", + NvimTreeDiagnosticInfoIcon = "DiagnosticInfo", + NvimTreeDiagnosticHintIcon = "DiagnosticHint", + + -- Diagnostics File Highlight + NvimTreeDiagnosticErrorFileHL = "DiagnosticUnderlineError", + NvimTreeDiagnosticWarnFileHL = "DiagnosticUnderlineWarn", + NvimTreeDiagnosticInfoFileHL = "DiagnosticUnderlineInfo", + NvimTreeDiagnosticHintFileHL = "DiagnosticUnderlineHint", + + -- Diagnostics Folder Highlight + NvimTreeDiagnosticErrorFolderHL = "NvimTreeDiagnosticErrorFileHL", + NvimTreeDiagnosticWarnFolderHL = "NvimTreeDiagnosticWarnFileHL", + NvimTreeDiagnosticInfoFolderHL = "NvimTreeDiagnosticInfoFileHL", + NvimTreeDiagnosticHintFolderHL = "NvimTreeDiagnosticHintFileHL", +} + +-- namespace standard links +local NS_LINKS = { + EndOfBuffer = "NvimTreeEndOfBuffer", + CursorLine = "NvimTreeCursorLine", + CursorLineNr = "NvimTreeCursorLineNr", + LineNr = "NvimTreeLineNr", + WinSeparator = "NvimTreeWinSeparator", + StatusLine = "NvimTreeStatusLine", + StatusLineNC = "NvimTreeStatuslineNC", + SignColumn = "NvimTreeSignColumn", + Normal = "NvimTreeNormal", + NormalNC = "NvimTreeNormalNC", + NormalFloat = "NvimTreeNormalFloat", +} + +-- nvim-tree highlight groups to legacy +local LEGACY_LINKS = { + NvimTreeModifiedIcon = "NvimTreeModifiedFile", + + NvimTreeOpenedHL = "NvimTreeOpenedFile", + + NvimTreeBookmarkIcon = "NvimTreeBookmark", + + NvimTreeGitDeletedIcon = "NvimTreeGitDeleted", + NvimTreeGitDirtyIcon = "NvimTreeGitDirty", + NvimTreeGitIgnoredIcon = "NvimTreeGitIgnored", + NvimTreeGitMergeIcon = "NvimTreeGitMerge", + NvimTreeGitNewIcon = "NvimTreeGitNew", + NvimTreeGitRenamedIcon = "NvimTreeGitRenamed", + NvimTreeGitStagedIcon = "NvimTreeGitStaged", + + NvimTreeGitFileDeletedHL = "NvimTreeFileDeleted", + NvimTreeGitFileDirtyHL = "NvimTreeFileDirty", + NvimTreeGitFileIgnoredHL = "NvimTreeFileIgnored", + NvimTreeGitFileMergeHL = "NvimTreeFileMerge", + NvimTreeGitFileNewHL = "NvimTreeFileNew", + NvimTreeGitFileRenamedHL = "NvimTreeFileRenamed", + NvimTreeGitFileStagedHL = "NvimTreeFileStaged", + + NvimTreeGitFolderDeletedHL = "NvimTreeFolderDeleted", + NvimTreeGitFolderDirtyHL = "NvimTreeFolderDirty", + NvimTreeGitFolderIgnoredHL = "NvimTreeFolderIgnored", + NvimTreeGitFolderMergeHL = "NvimTreeFolderMerge", + NvimTreeGitFolderNewHL = "NvimTreeFolderNew", + NvimTreeGitFolderRenamedHL = "NvimTreeFolderRenamed", + NvimTreeGitFolderStagedHL = "NvimTreeFolderStaged", + + NvimTreeDiagnosticErrorIcon = "NvimTreeLspDiagnosticsError", + NvimTreeDiagnosticWarnIcon = "NvimTreeLspDiagnosticsWarning", + NvimTreeDiagnosticInfoIcon = "NvimTreeLspDiagnosticsInformation", + NvimTreeDiagnosticHintIcon = "NvimTreeLspDiagnosticsHint", + + NvimTreeDiagnosticErrorFileHL = "NvimTreeLspDiagnosticsErrorText", + NvimTreeDiagnosticWarnFileHL = "NvimTreeLspDiagnosticsWarningText", + NvimTreeDiagnosticInfoFileHL = "NvimTreeLspDiagnosticsInformationText", + NvimTreeDiagnosticHintFileHL = "NvimTreeLspDiagnosticsHintText", + + NvimTreeDiagnosticErrorFolderHL = "NvimTreeLspDiagnosticsErrorFolderText", + NvimTreeDiagnosticWarnFolderHL = "NvimTreeLspDiagnosticsWarningFolderText", + NvimTreeDiagnosticInfoFolderHL = "NvimTreeLspDiagnosticsInformationFolderText", + NvimTreeDiagnosticHintFolderHL = "NvimTreeLspDiagnosticsHintFolderText", +} + +function M.setup() + -- non-linked + for k, d in pairs(DEFAULT_DEFS) do + vim.api.nvim_command("hi " .. k .. " " .. d) + end + + -- hard link override when legacy only is present + for from, to in pairs(LEGACY_LINKS) do + local hl_from = vim.api.nvim_get_hl(0, { name = from }) + local hl_to = vim.api.nvim_get_hl(0, { name = to }) + if vim.tbl_isempty(hl_from) and not vim.tbl_isempty(hl_to) then + vim.api.nvim_command("hi link " .. from .. " " .. to) + end + end + + -- default links + for from, to in pairs(DEFAULT_LINKS) do + vim.api.nvim_command("hi def link " .. from .. " " .. to) + end + + -- window standard; this doesn't appear to clear on ColorScheme however we err on the side of caution + for from, to in pairs(NS_LINKS) do + vim.api.nvim_set_hl(M.NS_ID, from, { link = to }) + end +end + +return M diff --git a/lua/nvim-tree/buffers.lua b/lua/nvim-tree/buffers.lua new file mode 100644 index 00000000000..04f48a05fcc --- /dev/null +++ b/lua/nvim-tree/buffers.lua @@ -0,0 +1,50 @@ +local M = {} + +---@type table record of which file is modified +M._modified = {} + +---refresh M._modified +function M.reload_modified() + M._modified = {} + local bufs = vim.fn.getbufinfo { bufmodified = true, buflisted = true } + for _, buf in pairs(bufs) do + local path = buf.name + if path ~= "" then -- not a [No Name] buffer + -- mark all the parent as modified as well + while + M._modified[path] ~= true + -- no need to keep going if already recorded + -- This also prevents an infinite loop + do + M._modified[path] = true + path = vim.fn.fnamemodify(path, ":h") + end + end + end +end + +---@param node table +---@return boolean +function M.is_modified(node) + return node + and M.config.modified.enable + and M._modified[node.absolute_path] + and (not node.nodes or M.config.modified.show_on_dirs) + and (not node.open or M.config.modified.show_on_open_dirs) +end + +---A buffer exists for the node's absolute path +---@param node table +---@return boolean +function M.is_opened(node) + return node and vim.fn.bufloaded(node.absolute_path) > 0 +end + +---@param opts table +function M.setup(opts) + M.config = { + modified = opts.modified, + } +end + +return M diff --git a/lua/nvim-tree/colors.lua b/lua/nvim-tree/colors.lua deleted file mode 100644 index 7487c05e0eb..00000000000 --- a/lua/nvim-tree/colors.lua +++ /dev/null @@ -1,130 +0,0 @@ -local M = {} - -local function get_color_from_hl(hl_name, fallback) - local id = vim.api.nvim_get_hl_id_by_name(hl_name) - if not id then - return fallback - end - - local foreground = vim.fn.synIDattr(vim.fn.synIDtrans(id), "fg") - if not foreground or foreground == "" then - return fallback - end - - return foreground -end - -local function get_colors() - return { - red = vim.g.terminal_color_1 or get_color_from_hl("Keyword", "Red"), - green = vim.g.terminal_color_2 or get_color_from_hl("Character", "Green"), - yellow = vim.g.terminal_color_3 or get_color_from_hl("PreProc", "Yellow"), - blue = vim.g.terminal_color_4 or get_color_from_hl("Include", "Blue"), - purple = vim.g.terminal_color_5 or get_color_from_hl("Define", "Purple"), - cyan = vim.g.terminal_color_6 or get_color_from_hl("Conditional", "Cyan"), - dark_red = vim.g.terminal_color_9 or get_color_from_hl("Keyword", "DarkRed"), - orange = vim.g.terminal_color_11 or get_color_from_hl("Number", "Orange"), - } -end - -local function get_hl_groups() - local colors = get_colors() - - return { - IndentMarker = { fg = "#8094b4" }, - Symlink = { gui = "bold", fg = colors.cyan }, - FolderIcon = { fg = "#8094b4" }, - RootFolder = { fg = colors.purple }, - - ExecFile = { gui = "bold", fg = colors.green }, - SpecialFile = { gui = "bold,underline", fg = colors.yellow }, - ImageFile = { gui = "bold", fg = colors.purple }, - OpenedFile = { gui = "bold", fg = colors.green }, - ModifiedFile = { fg = colors.green }, - - GitDirty = { fg = colors.dark_red }, - GitDeleted = { fg = colors.dark_red }, - GitStaged = { fg = colors.green }, - GitMerge = { fg = colors.orange }, - GitRenamed = { fg = colors.purple }, - GitNew = { fg = colors.yellow }, - - WindowPicker = { gui = "bold", fg = "#ededed", bg = "#4493c8" }, - LiveFilterPrefix = { gui = "bold", fg = colors.purple }, - LiveFilterValue = { gui = "bold", fg = "#fff" }, - - Bookmark = { fg = colors.green }, - } -end - -local function get_links() - return { - FolderName = "Directory", - EmptyFolderName = "Directory", - OpenedFolderName = "Directory", - SymlinkFolderName = "Directory", - OpenedFolderIcon = "NvimTreeFolderIcon", - ClosedFolderIcon = "NvimTreeFolderIcon", - OpenedFileIcon = "NvimTreeOpenedFile", - Normal = "Normal", - NormalFloat = "NormalFloat", - NormalNC = "NvimTreeNormal", - EndOfBuffer = "EndOfBuffer", - CursorLineNr = "CursorLineNr", - LineNr = "LineNr", - CursorLine = "CursorLine", - WinSeparator = "WinSeparator", - CursorColumn = "CursorColumn", - FileDirty = "NvimTreeGitDirty", - FileNew = "NvimTreeGitNew", - FileRenamed = "NvimTreeGitRenamed", - FileMerge = "NvimTreeGitMerge", - FileStaged = "NvimTreeGitStaged", - FileDeleted = "NvimTreeGitDeleted", - FileIgnored = "NvimTreeGitIgnored", - FolderDirty = "NvimTreeFileDirty", - FolderNew = "NvimTreeFileNew", - FolderRenamed = "NvimTreeFileRenamed", - FolderMerge = "NvimTreeFileMerge", - FolderStaged = "NvimTreeFileStaged", - FolderDeleted = "NvimTreeFileDeleted", - FolderIgnored = "NvimTreeFileIgnored", - LspDiagnosticsError = "DiagnosticError", - LspDiagnosticsWarning = "DiagnosticWarn", - LspDiagnosticsInformation = "DiagnosticInfo", - LspDiagnosticsHint = "DiagnosticHint", - LspDiagnosticsErrorText = "NvimTreeLspDiagnosticsError", - LspDiagnosticsWarningText = "NvimTreeLspDiagnosticsWarning", - LspDiagnosticsInformationText = "NvimTreeLspDiagnosticsInformation", - LspDiagnosticsHintText = "NvimTreeLspDiagnosticsHintFile", - LspDiagnosticsErrorFolderText = "NvimTreeLspDiagnosticsErrorText", - LspDiagnosticsWarningFolderText = "NvimTreeLspDiagnosticsWarningText", - LspDiagnosticsInformationFolderText = "NvimTreeLspDiagnosticsInformationText", - LspDiagnosticsHintFolderText = "NvimTreeLspDiagnosticsHintFileText", - Popup = "Normal", - GitIgnored = "Comment", - StatusLine = "StatusLine", - StatusLineNC = "StatusLineNC", - SignColumn = "NvimTreeNormal", - CutHL = "SpellBad", - CopiedHL = "SpellRare", - BookmarkHL = "SpellLocal", - } -end - -function M.setup() - local highlight_groups = get_hl_groups() - for k, d in pairs(highlight_groups) do - local gui = d.gui and " gui=" .. d.gui or "" - local fg = d.fg and " guifg=" .. d.fg or "" - local bg = d.bg and " guibg=" .. d.bg or "" - vim.api.nvim_command("hi def NvimTree" .. k .. gui .. fg .. bg) - end - - local links = get_links() - for k, d in pairs(links) do - vim.api.nvim_command("hi def link NvimTree" .. k .. " " .. d) - end -end - -return M diff --git a/lua/nvim-tree/enum.lua b/lua/nvim-tree/enum.lua index e3791427e31..92ea2bb4ca0 100644 --- a/lua/nvim-tree/enum.lua +++ b/lua/nvim-tree/enum.lua @@ -12,9 +12,10 @@ M.HL_POSITION = { ---Setup options for "*_placement" ---@enum ICON_PLACEMENT M.ICON_PLACEMENT = { - signcolumn = 0, - before = 1, - after = 2, + none = 0, + signcolumn = 1, + before = 2, + after = 3, } return M diff --git a/lua/nvim-tree/explorer/filters.lua b/lua/nvim-tree/explorer/filters.lua index 18bce0281b5..b05fe4edbdc 100644 --- a/lua/nvim-tree/explorer/filters.lua +++ b/lua/nvim-tree/explorer/filters.lua @@ -47,16 +47,15 @@ end ---Check if the given path has no listed buffer ---@param path string Absolute path ---@param bufinfo table vim.fn.getbufinfo { buflisted = 1 } ----@param unloaded_bufnr number optional bufnr recently unloaded via BufUnload event ---@return boolean -local function buf(path, bufinfo, unloaded_bufnr) +local function buf(path, bufinfo) if not M.config.filter_no_buffer or type(bufinfo) ~= "table" then return false end -- filter files with no open buffer and directories containing no open buffers for _, b in ipairs(bufinfo) do - if b.name == path or b.name:find(path .. "/", 1, true) and b.bufnr ~= unloaded_bufnr then + if b.name == path or b.name:find(path .. "/", 1, true) then return false end end @@ -105,16 +104,13 @@ end ---Prepare arguments for should_filter. This is done prior to should_filter for efficiency reasons. ---@param git_status table|nil optional results of git.load_project_status(...) ----@param unloaded_bufnr number|nil optional bufnr recently unloaded via BufUnload event ---@return table --- git_status: reference ---- unloaded_bufnr: copy --- bufinfo: empty unless no_buffer set: vim.fn.getbufinfo { buflisted = 1 } --- bookmarks: absolute paths to boolean -function M.prepare(git_status, unloaded_bufnr) +function M.prepare(git_status) local status = { git_status = git_status or {}, - unloaded_bufnr = unloaded_bufnr, bufinfo = {}, bookmarks = {}, } @@ -140,11 +136,7 @@ function M.should_filter(path, status) return false end - return git(path, status.git_status) - or buf(path, status.bufinfo, status.unloaded_bufnr) - or dotfile(path) - or custom(path) - or bookmark(path, status.bookmarks) + return git(path, status.git_status) or buf(path, status.bufinfo) or dotfile(path) or custom(path) or bookmark(path, status.bookmarks) end function M.setup(opts) diff --git a/lua/nvim-tree/explorer/reload.lua b/lua/nvim-tree/explorer/reload.lua index d921d033f97..7444491e39f 100644 --- a/lua/nvim-tree/explorer/reload.lua +++ b/lua/nvim-tree/explorer/reload.lua @@ -69,8 +69,7 @@ end ---@param node Node ---@param git_status table ----@param unloaded_bufnr number|nil -function M.reload(node, git_status, unloaded_bufnr) +function M.reload(node, git_status) local cwd = node.link_to or node.absolute_path local handle = vim.loop.fs_scandir(cwd) if not handle then @@ -79,7 +78,7 @@ function M.reload(node, git_status, unloaded_bufnr) local profile = log.profile_start("reload %s", node.absolute_path) - local filter_status = filters.prepare(git_status, unloaded_bufnr) + local filter_status = filters.prepare(git_status) if node.group_next then node.nodes = { node.group_next } diff --git a/lua/nvim-tree/legacy.lua b/lua/nvim-tree/legacy.lua index 6f8c60c7317..9d95da69326 100644 --- a/lua/nvim-tree/legacy.lua +++ b/lua/nvim-tree/legacy.lua @@ -41,6 +41,16 @@ local function refactored(opts) -- 2023/08/26 utils.move_missing_val(opts, "renderer.icons", "webdev_colors", opts, "renderer.icons.web_devicons.file", "color", true) + + -- 2023/10/08 + if type(opts.renderer) == "table" and type(opts.renderer.highlight_diagnostics) == "boolean" then + opts.renderer.highlight_diagnostics = opts.renderer.highlight_diagnostics and "name" or "none" + end + + -- 2023/10/21 + if type(opts.renderer) == "table" and type(opts.renderer.highlight_git) == "boolean" then + opts.renderer.highlight_git = opts.renderer.highlight_git and "name" or "none" + end end local function deprecated(opts) diff --git a/lua/nvim-tree/marks/init.lua b/lua/nvim-tree/marks/init.lua index 10804d5c21c..d92e43e8bc7 100644 --- a/lua/nvim-tree/marks/init.lua +++ b/lua/nvim-tree/marks/init.lua @@ -45,7 +45,7 @@ end ---@param node Node|MinimalNode ---@return table|nil function M.get_mark(node) - return NvimTreeMarks[node.absolute_path] + return node and NvimTreeMarks[node.absolute_path] end ---@return table diff --git a/lua/nvim-tree/modified.lua b/lua/nvim-tree/modified.lua index 8f77ef668fb..e16c0750e7e 100644 --- a/lua/nvim-tree/modified.lua +++ b/lua/nvim-tree/modified.lua @@ -26,7 +26,8 @@ end ---@param node Node ---@return boolean function M.is_modified(node) - return M.config.enable + return node + and M.config.enable and M._record[node.absolute_path] and (not node.nodes or M.config.show_on_dirs) and (not node.open or M.config.show_on_open_dirs) diff --git a/lua/nvim-tree/renderer/builder.lua b/lua/nvim-tree/renderer/builder.lua index bfd72f7aac1..fd2771c0712 100644 --- a/lua/nvim-tree/renderer/builder.lua +++ b/lua/nvim-tree/renderer/builder.lua @@ -1,136 +1,94 @@ -local utils = require "nvim-tree.utils" +local appearance = require "nvim-tree.appearance" 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 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 M = { + opts = {}, + decorators = {}, + picture_map = { + jpg = true, + jpeg = true, + png = true, + gif = true, + webp = true, + jxl = true, + }, +} -local Builder = {} -Builder.__index = Builder +---@class HighlightedString +---@field str string +---@field hl string[] -local DEFAULT_ROOT_FOLDER_LABEL = ":~:s?$?/..?" +---@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 combined_groups string[] combined group names +---@field private markers boolean[] indent markers +local Builder = {} -function Builder.new(root_cwd) - 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 = {}, 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 + } + setmetatable(o, self) + self.__index = self -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 + return o end -function Builder:configure_icon_padding(padding) - self.icon_padding = padding or " " - return self +---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.hl_args, { groups, self.index, start, end_ or -1 }) 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:configure_group_name_modifier(group_name_modifier) - if type(group_name_modifier) == "function" then - self.group_name_modifier = group_name_modifier - end - 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 - -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 @@ -138,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 @@ -156,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 @@ -181,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, core.get_cwd()) - foldername = foldername .. arrow .. link_to + local link_to = utils.path_relative(node.link_to, self.root_cwd) + 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" @@ -197,174 +155,56 @@ 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 - local link_to = utils.path_relative(node.link_to, core.get_cwd()) - symlink_formatted = symlink_formatted .. arrow .. link_to + if M.opts.renderer.symlink_destination then + local link_to = utils.path_relative(node.link_to, self.root_cwd) + 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 } } -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 + 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 git_icons HighlightedString[]|nil ----@param diagnostics_icon HighlightedString|nil ----@param modified_icon HighlightedString|nil ----@param bookmark_icon HighlightedString|nil +---@param node table ---@return HighlightedString[] -function Builder:_format_line(indent_markers, arrows, icon, name, git_icons, diagnostics_icon, modified_icon, bookmark_icon) +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 + return + 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 @@ -379,78 +219,132 @@ function Builder:_format_line(indent_markers, arrows, icon, name, git_icons, dia 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 }) + + for i = #M.decorators, 1, -1 do + add_to_end(line, M.decorators[i]:icons_before(node)) end - if diagnostics_icon and self.diagnostics_placement == "before" then - add_to_end(line, { diagnostics_icon }) + + add_to_end(line, { name }) + + for i = #M.decorators, 1, -1 do + add_to_end(line, M.decorators[i]:icons_after(node)) end - if bookmark_icon and self.bookmarks_placement == "before" then - add_to_end(line, { bookmark_icon }) + + log.line("dev", "line = %s", vim.inspect(line)) + return line +end + +---@private +---@param node Node +function Builder:build_signs(node) + -- first in priority order + local sign_name + for _, d in ipairs(M.decorators) do + sign_name = d:sign_name(node) + if sign_name then + self.signs[self.index] = sign_name + break + end end +end - add_to_end(line, { name }) +---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) + 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) + + -- only create if necessary + if not vim.tbl_contains(self.combined_groups, combined_name) then + local combined_hl = {} + + -- build the highlight, overriding values + for _, group in ipairs(groups) do + local hl = vim.api.nvim_get_hl(0, { name = group, link = false }) + combined_hl = vim.tbl_extend("force", combined_hl, hl) + end - if git_icons and self.git_placement == "after" then - add_to_end(line, git_icons) + -- highlight directly in the namespace + vim.api.nvim_set_hl(appearance.NS_ID, combined_name, combined_hl) + + table.insert(self.combined_groups, combined_name) end - if modified_icon and self.modified_placement == "after" then - add_to_end(line, { modified_icon }) +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) + -- result + local icon_hl_group, name_hl_group + + -- calculate all groups + local icon_groups = {} + local name_groups = {} + local d, icon, name + 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) end - if diagnostics_icon and self.diagnostics_placement == "after" then - add_to_end(line, { diagnostics_icon }) + + -- 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) + else + icon_hl_group = icon_groups[1] end - if bookmark_icon and self.bookmarks_placement == "after" then - add_to_end(line, { bookmark_icon }) + + -- 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) + else + name_hl_group = name_groups[1] end - return line + return icon_hl_group, name_hl_group end -function Builder:_build_line(node, idx, num_children, unloaded_bufnr) - local copy_paste = require "nvim-tree.actions.fs.copy-paste" - +---@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) - -- 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) + 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 - -- 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 + -- 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) - -- 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)) + 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 @@ -458,13 +352,14 @@ function Builder:_build_line(node, idx, num_children, unloaded_bufnr) if node.open then self.depth = self.depth + 1 - self:build(node, unloaded_bufnr) + 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 @@ -477,53 +372,77 @@ function Builder:_get_nodes_number(nodes) return i end -function Builder:build(tree, unloaded_bufnr) - 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_line(node, idx, num_children, unloaded_bufnr) + 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.signs +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/components/bookmarks.lua b/lua/nvim-tree/renderer/components/bookmarks.lua deleted file mode 100644 index d9dfb7297d5..00000000000 --- a/lua/nvim-tree/renderer/components/bookmarks.lua +++ /dev/null @@ -1,51 +0,0 @@ -local marks = require "nvim-tree.marks" - -local HL_POSITION = require("nvim-tree.enum").HL_POSITION - -local M = { - ICON = {}, - hl_pos = HL_POSITION.none, -} - ----Bookmark highlight group and position when highlight_bookmark. ----@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 - - local mark = marks.get_mark(node) - if mark then - return M.hl_pos, "NvimTreeBookmarkHL" - else - return HL_POSITION.none, nil - end -end - ----bookmark icon if marked ----@param node table ----@return HighlightedString|nil bookmark icon -function M.get_icon(node) - if M.config.renderer.icons.show.bookmarks and marks.get_mark(node) then - return M.ICON - end -end - -function M.setup(opts) - M.config = { - renderer = opts.renderer, - } - - M.hl_pos = HL_POSITION[opts.renderer.highlight_bookmarks] or HL_POSITION.none - - M.ICON = { - str = opts.renderer.icons.glyphs.bookmark, - hl = { "NvimTreeBookmark" }, - } - - vim.fn.sign_define(M.ICON.hl[1], { text = M.ICON.str, texthl = M.ICON.hl[1] }) -end - -return M diff --git a/lua/nvim-tree/renderer/components/diagnostics.lua b/lua/nvim-tree/renderer/components/diagnostics.lua index 1fa864deac9..50369ec62f1 100644 --- a/lua/nvim-tree/renderer/components/diagnostics.lua +++ b/lua/nvim-tree/renderer/components/diagnostics.lua @@ -2,18 +2,23 @@ local HL_POSITION = require("nvim-tree.enum").HL_POSITION local diagnostics = require "nvim-tree.diagnostics" local M = { - HS_FILE = {}, - HS_FOLDER = {}, - ICON = {}, - hl_pos = HL_POSITION.none, + -- highlight strings for the icons + HS_ICON = {}, + + -- highlight groups for HL + HG_FILE = {}, + HG_FOLDER = {}, + + -- position for HL + HL_POS = HL_POSITION.none, } ----Diagnostics text highlight group when highlight_diagnostics. +---Diagnostics highlight group and position when highlight_diagnostics. ---@param node table ---@return HL_POSITION position none when no status ---@return string|nil group only when status function M.get_highlight(node) - if not node or M.hl_pos == HL_POSITION.none then + if not node or M.HL_POS == HL_POSITION.none then return HL_POSITION.none, nil end @@ -26,7 +31,7 @@ function M.get_highlight(node) end if group then - return M.hl_pos, group + return M.HL_POS, group else return HL_POSITION.none, nil end @@ -49,40 +54,38 @@ function M.setup(opts) } 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 + M.HL_POS = HL_POSITION[opts.renderer.highlight_diagnostics] 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" + M.HG_FILE[vim.diagnostic.severity.ERROR] = "NvimTreeDiagnosticErrorFileHL" + M.HG_FILE[vim.diagnostic.severity.WARN] = "NvimTreeDiagnosticWarningFileHL" + M.HG_FILE[vim.diagnostic.severity.INFO] = "NvimTreeDiagnosticInfoFileHL" + M.HG_FILE[vim.diagnostic.severity.HINT] = "NvimTreeDiagnosticHintFileHL" - 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" + M.HG_FOLDER[vim.diagnostic.severity.ERROR] = "NvimTreeDiagnosticErrorFolderHL" + M.HG_FOLDER[vim.diagnostic.severity.WARN] = "NvimTreeDiagnosticWarningFolderHL" + M.HG_FOLDER[vim.diagnostic.severity.INFO] = "NvimTreeDiagnosticInfoFolderHL" + M.HG_FOLDER[vim.diagnostic.severity.HINT] = "NvimTreeDiagnosticHintFolderHL" - M.ICON[vim.diagnostic.severity.ERROR] = { + M.HS_ICON[vim.diagnostic.severity.ERROR] = { str = M.config.diagnostics.icons.error, - hl = { "NvimTreeLspDiagnosticsError" }, + hl = { "NvimTreeDiagnosticErrorIcon" }, } - M.ICON[vim.diagnostic.severity.WARN] = { + M.HS_ICON[vim.diagnostic.severity.WARN] = { str = M.config.diagnostics.icons.warning, - hl = { "NvimTreeLspDiagnosticsWarning" }, + hl = { "NvimTreeDiagnosticWarningIcon" }, } - M.ICON[vim.diagnostic.severity.INFO] = { + M.HS_ICON[vim.diagnostic.severity.INFO] = { str = M.config.diagnostics.icons.info, - hl = { "NvimTreeLspDiagnosticsInformation" }, + hl = { "NvimTreeDiagnosticInfoIcon" }, } - M.ICON[vim.diagnostic.severity.HINT] = { + M.HS_ICON[vim.diagnostic.severity.HINT] = { str = M.config.diagnostics.icons.hint, - hl = { "NvimTreeLspDiagnosticsHint" }, + hl = { "NvimTreeDiagnosticHintIcon" }, } - for _, i in ipairs(M.ICON) do + for _, i in ipairs(M.HS_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/full-name.lua b/lua/nvim-tree/renderer/components/full-name.lua index 7ab5e6b3efe..b937e6ca1a5 100644 --- a/lua/nvim-tree/renderer/components/full-name.lua +++ b/lua/nvim-tree/renderer/components/full-name.lua @@ -1,3 +1,5 @@ +local appearance = require "nvim-tree.appearance" + local M = {} local utils = require "nvim-tree.utils" @@ -66,13 +68,12 @@ local function show() style = "minimal", }) - local ns_id = vim.api.nvim_get_namespaces()["NvimTreeHighlights"] - local extmarks = vim.api.nvim_buf_get_extmarks(0, ns_id, { line_nr - 1, 0 }, { line_nr - 1, -1 }, { details = 1 }) + local extmarks = vim.api.nvim_buf_get_extmarks(0, appearance.NS_ID, { line_nr - 1, 0 }, { line_nr - 1, -1 }, { details = 1 }) vim.api.nvim_win_call(M.popup_win, function() vim.api.nvim_buf_set_lines(0, 0, -1, true, { line }) for _, extmark in ipairs(extmarks) do local hl = extmark[4] - vim.api.nvim_buf_add_highlight(0, ns_id, hl.hl_group, 0, extmark[3], hl.end_col) + vim.api.nvim_buf_add_highlight(0, appearance.NS_ID, hl.hl_group, 0, extmark[3], hl.end_col) end vim.cmd [[ setlocal nowrap cursorline noswapfile nobuflisted buftype=nofile bufhidden=hide ]] end) diff --git a/lua/nvim-tree/renderer/components/git.lua b/lua/nvim-tree/renderer/components/git.lua deleted file mode 100644 index f4d1258bacd..00000000000 --- a/lua/nvim-tree/renderer/components/git.lua +++ /dev/null @@ -1,191 +0,0 @@ -local notify = require "nvim-tree.notify" -local explorer_node = require "nvim-tree.explorer.node" - -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 }, - } - return { - ["M "] = { icons.staged }, - [" M"] = { icons.unstaged }, - ["C "] = { icons.staged }, - [" C"] = { icons.unstaged }, - ["CM"] = { icons.unstaged }, - [" T"] = { icons.unstaged }, - ["T "] = { icons.staged }, - ["TM"] = { icons.staged, icons.unstaged }, - ["MM"] = { icons.staged, icons.unstaged }, - ["MD"] = { icons.staged }, - ["A "] = { icons.staged }, - ["AD"] = { icons.staged }, - [" A"] = { icons.untracked }, - -- not sure about this one - ["AA"] = { icons.unmerged, icons.untracked }, - ["AU"] = { icons.unmerged, icons.untracked }, - ["AM"] = { icons.staged, icons.unstaged }, - ["??"] = { icons.untracked }, - ["R "] = { icons.renamed }, - [" R"] = { icons.renamed }, - ["RM"] = { icons.unstaged, icons.renamed }, - ["UU"] = { icons.unmerged }, - ["UD"] = { icons.unmerged }, - ["UA"] = { icons.unmerged }, - [" D"] = { icons.deleted }, - ["D "] = { icons.deleted }, - ["DA"] = { icons.unstaged }, - ["RD"] = { icons.deleted }, - ["DD"] = { icons.deleted }, - ["DU"] = { icons.deleted, icons.unmerged }, - ["!!"] = { icons.ignored }, - dirty = { icons.unstaged }, - } -end - -local function build_hl_table() - local file = { - ["M "] = "NvimTreeFileStaged", - ["C "] = "NvimTreeFileStaged", - ["AA"] = "NvimTreeFileStaged", - ["AD"] = "NvimTreeFileStaged", - ["MD"] = "NvimTreeFileStaged", - ["T "] = "NvimTreeFileStaged", - ["TT"] = "NvimTreeFileStaged", - [" M"] = "NvimTreeFileDirty", - ["CM"] = "NvimTreeFileDirty", - [" C"] = "NvimTreeFileDirty", - [" T"] = "NvimTreeFileDirty", - ["MM"] = "NvimTreeFileDirty", - ["AM"] = "NvimTreeFileDirty", - dirty = "NvimTreeFileDirty", - ["A "] = "NvimTreeFileStaged", - ["??"] = "NvimTreeFileNew", - ["AU"] = "NvimTreeFileMerge", - ["UU"] = "NvimTreeFileMerge", - ["UD"] = "NvimTreeFileMerge", - ["DU"] = "NvimTreeFileMerge", - ["UA"] = "NvimTreeFileMerge", - [" D"] = "NvimTreeFileDeleted", - ["DD"] = "NvimTreeFileDeleted", - ["RD"] = "NvimTreeFileDeleted", - ["D "] = "NvimTreeFileDeleted", - ["R "] = "NvimTreeFileRenamed", - ["RM"] = "NvimTreeFileRenamed", - [" R"] = "NvimTreeFileRenamed", - ["!!"] = "NvimTreeFileIgnored", - [" A"] = "none", - } - - local folder = {} - for k, v in pairs(file) do - folder[k] = v:gsub("File", "Folder") - end - - return file, folder -end - -local function nil_() end - -local function warn_status(git_status) - notify.warn(string.format("Unrecognized git state '%s'", git_status)) -end - ----@param node table ----@return HighlightedString[]|nil -local function get_icons_(node) - local git_status = explorer_node.get_git_status(node) - if git_status == nil then - return nil - end - - local inserted = {} - local iconss = {} - - for _, s in pairs(git_status) do - local icons = M.git_icons[s] - if not icons then - if not M.config.highlight_git then - warn_status(s) - end - return nil - end - - for _, icon in pairs(icons) do - if #icon.str > 0 then - if not inserted[icon] then - table.insert(iconss, icon) - inserted[icon] = true - end - end - end - end - - if #iconss == 0 then - return nil - end - - -- sort icons so it looks slightly better - table.sort(iconss, function(a, b) - return a.ord < b.ord - end) - - return iconss -end - -function M.setup_signs(i) - vim.fn.sign_define("NvimTreeGitDirty", { text = i.unstaged, texthl = "NvimTreeGitDirty" }) - vim.fn.sign_define("NvimTreeGitStaged", { text = i.staged, texthl = "NvimTreeGitStaged" }) - vim.fn.sign_define("NvimTreeGitMerge", { text = i.unmerged, texthl = "NvimTreeGitMerge" }) - vim.fn.sign_define("NvimTreeGitRenamed", { text = i.renamed, texthl = "NvimTreeGitRenamed" }) - vim.fn.sign_define("NvimTreeGitNew", { text = i.untracked, texthl = "NvimTreeGitNew" }) - vim.fn.sign_define("NvimTreeGitDeleted", { text = i.deleted, texthl = "NvimTreeGitDeleted" }) - vim.fn.sign_define("NvimTreeGitIgnored", { text = i.ignored, texthl = "NvimTreeGitIgnored" }) -end - -local function get_highlight_(node) - local git_status = explorer_node.get_git_status(node) - if git_status == nil then - return - end - - if node.nodes then - return M.folder_hl[git_status[1]] - else - return M.file_hl[git_status[1]] - end -end - -function M.setup(opts) - M.config = opts.renderer - - M.git_icons = build_icons_table(opts.renderer.icons.glyphs.git) - - M.file_hl, M.folder_hl = build_hl_table() - - if opts.renderer.icons.git_placement == "signcolumn" then - M.setup_signs(opts.renderer.icons.glyphs.git) - end - - if opts.renderer.icons.show.git then - M.get_icons = get_icons_ - else - M.get_icons = nil_ - end - - if opts.renderer.highlight_git then - M.get_highlight = get_highlight_ - else - M.get_highlight = nil_ - end - - M.git_show_on_open_dirs = opts.git.show_on_open_dirs -end - -return M diff --git a/lua/nvim-tree/renderer/components/modified.lua b/lua/nvim-tree/renderer/components/modified.lua deleted file mode 100644 index f247d7b2046..00000000000 --- a/lua/nvim-tree/renderer/components/modified.lua +++ /dev/null @@ -1,41 +0,0 @@ -local modified = require "nvim-tree.modified" - -local M = {} - -local HIGHLIGHT = "NvimTreeModifiedFile" - ----return modified icon if node is modified, otherwise return empty string ----@param node table ----@return HighlightedString|nil modified icon -function M.get_icon(node) - if not modified.is_modified(node) or not M.show_icon then - return nil - end - - return { str = M.icon, hl = { HIGHLIGHT } } -end - -function M.setup_signs() - vim.fn.sign_define(HIGHLIGHT, { text = M.icon, texthl = HIGHLIGHT }) -end - ----@param node table ----@return string|nil -function M.get_highlight(node) - if not modified.is_modified(node) then - return nil - end - - return HIGHLIGHT -end - -function M.setup(opts) - M.icon = opts.renderer.icons.glyphs.modified - M.show_icon = opts.renderer.icons.show.modified - - if opts.renderer.icons.modified_placement == "signcolumn" then - M.setup_signs() - end -end - -return M diff --git a/lua/nvim-tree/renderer/decorator/bookmarks.lua b/lua/nvim-tree/renderer/decorator/bookmarks.lua new file mode 100644 index 00000000000..8692c22b579 --- /dev/null +++ b/lua/nvim-tree/renderer/decorator/bookmarks.lua @@ -0,0 +1,51 @@ +local marks = require "nvim-tree.marks" + +local HL_POSITION = require("nvim-tree.enum").HL_POSITION +local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT + +local Decorator = require "nvim-tree.renderer.decorator" + +---@class DecoratorBookmarks: Decorator +---@field icon HighlightedString +local DecoratorBookmarks = Decorator:new() + +---@param opts table +---@return DecoratorBookmarks +function DecoratorBookmarks:new(opts) + local o = Decorator.new(self, { + enabled = true, + hl_pos = HL_POSITION[opts.renderer.highlight_bookmarks] or HL_POSITION.none, + icon_placement = ICON_PLACEMENT[opts.renderer.icons.bookmarks_placement] or ICON_PLACEMENT.none, + }) + ---@cast o DecoratorBookmarks + + if opts.renderer.icons.show.bookmarks then + o.icon = { + str = opts.renderer.icons.glyphs.bookmark, + hl = { "NvimTreeBookmarkIcon" }, + } + o:define_sign(o.icon) + end + + return o +end + +---Bookmark icon: renderer.icons.show.bookmarks and node is marked +---@param node Node +---@return HighlightedString[]|nil icons +function DecoratorBookmarks:calculate_icons(node) + if marks.get_mark(node) then + return { self.icon } + end +end + +---Bookmark highlight: renderer.highlight_bookmarks and node is marked +---@param node Node +---@return string|nil group +function DecoratorBookmarks:calculate_highlight(node) + if self.hl_pos ~= HL_POSITION.none and marks.get_mark(node) then + return "NvimTreeBookmarkHL" + end +end + +return DecoratorBookmarks diff --git a/lua/nvim-tree/renderer/decorator/copied.lua b/lua/nvim-tree/renderer/decorator/copied.lua new file mode 100644 index 00000000000..f7ceba0a293 --- /dev/null +++ b/lua/nvim-tree/renderer/decorator/copied.lua @@ -0,0 +1,38 @@ +local copy_paste + +local HL_POSITION = require("nvim-tree.enum").HL_POSITION +local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT + +local Decorator = require "nvim-tree.renderer.decorator" + +---@class DecoratorCopied: Decorator +---@field enabled boolean +---@field icon HighlightedString|nil +local DecoratorCopied = Decorator:new() + +---@param opts table +---@return DecoratorCopied +function DecoratorCopied:new(opts) + local o = Decorator.new(self, { + enabled = true, + hl_pos = HL_POSITION[opts.renderer.highlight_clipboard] or HL_POSITION.none, + icon_placement = ICON_PLACEMENT.none, + }) + ---@cast o DecoratorCopied + + -- cyclic + copy_paste = copy_paste or require "nvim-tree.actions.fs.copy-paste" + + return o +end + +---Copied highlight: renderer.highlight_clipboard and node is copied +---@param node Node +---@return string|nil group +function DecoratorCopied:calculate_highlight(node) + if self.hl_pos ~= HL_POSITION.none and copy_paste.is_copied(node) then + return "NvimTreeCopiedHL" + end +end + +return DecoratorCopied diff --git a/lua/nvim-tree/renderer/decorator/cut.lua b/lua/nvim-tree/renderer/decorator/cut.lua new file mode 100644 index 00000000000..f02872ff6f5 --- /dev/null +++ b/lua/nvim-tree/renderer/decorator/cut.lua @@ -0,0 +1,38 @@ +local copy_paste + +local HL_POSITION = require("nvim-tree.enum").HL_POSITION +local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT + +local Decorator = require "nvim-tree.renderer.decorator" + +---@class DecoratorCut: Decorator +---@field enabled boolean +---@field icon HighlightedString|nil +local DecoratorCut = Decorator:new() + +---@param opts table +---@return DecoratorCut +function DecoratorCut:new(opts) + local o = Decorator.new(self, { + enabled = true, + hl_pos = HL_POSITION[opts.renderer.highlight_clipboard] or HL_POSITION.none, + icon_placement = ICON_PLACEMENT.none, + }) + ---@cast o DecoratorCut + + -- cyclic + copy_paste = copy_paste or require "nvim-tree.actions.fs.copy-paste" + + return o +end + +---Cut highlight: renderer.highlight_clipboard and node is cut +---@param node Node +---@return string|nil group +function DecoratorCut:calculate_highlight(node) + if self.hl_pos ~= HL_POSITION.none and copy_paste.is_cut(node) then + return "NvimTreeCutHL" + end +end + +return DecoratorCut diff --git a/lua/nvim-tree/renderer/decorator/diagnostics.lua b/lua/nvim-tree/renderer/decorator/diagnostics.lua new file mode 100644 index 00000000000..d18ff4d43d2 --- /dev/null +++ b/lua/nvim-tree/renderer/decorator/diagnostics.lua @@ -0,0 +1,110 @@ +local diagnostics = require "nvim-tree.diagnostics" + +local HL_POSITION = require("nvim-tree.enum").HL_POSITION +local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT + +local Decorator = require "nvim-tree.renderer.decorator" + +-- highlight groups by severity +local HG_ICON = { + [vim.diagnostic.severity.ERROR] = "NvimTreeDiagnosticErrorIcon", + [vim.diagnostic.severity.WARN] = "NvimTreeDiagnosticWarnIcon", + [vim.diagnostic.severity.INFO] = "NvimTreeDiagnosticInfoIcon", + [vim.diagnostic.severity.HINT] = "NvimTreeDiagnosticHintIcon", +} +local HG_FILE = { + [vim.diagnostic.severity.ERROR] = "NvimTreeDiagnosticErrorFileHL", + [vim.diagnostic.severity.WARN] = "NvimTreeDiagnosticWarnFileHL", + [vim.diagnostic.severity.INFO] = "NvimTreeDiagnosticInfoFileHL", + [vim.diagnostic.severity.HINT] = "NvimTreeDiagnosticHintFileHL", +} +local HG_FOLDER = { + [vim.diagnostic.severity.ERROR] = "NvimTreeDiagnosticErrorFolderHL", + [vim.diagnostic.severity.WARN] = "NvimTreeDiagnosticWarnFolderHL", + [vim.diagnostic.severity.INFO] = "NvimTreeDiagnosticInfoFolderHL", + [vim.diagnostic.severity.HINT] = "NvimTreeDiagnosticHintFolderHL", +} +-- opts.diagnostics.icons. +local ICON_KEYS = { + ["error"] = vim.diagnostic.severity.ERROR, + ["warning"] = vim.diagnostic.severity.WARN, + ["info"] = vim.diagnostic.severity.INFO, + ["hint"] = vim.diagnostic.severity.HINT, +} + +---@class DecoratorDiagnostics: Decorator +---@field icons HighlightedString[] +local DecoratorDiagnostics = Decorator:new() + +---@param opts table +---@return DecoratorDiagnostics +function DecoratorDiagnostics:new(opts) + local o = Decorator.new(self, { + enabled = opts.diagnostics.enable, + hl_pos = HL_POSITION[opts.renderer.highlight_diagnostics] or HL_POSITION.none, + icon_placement = ICON_PLACEMENT[opts.renderer.icons.diagnostics_placement] or ICON_PLACEMENT.none, + }) + ---@cast o DecoratorDiagnostics + + if not o.enabled then + return o + end + + if opts.renderer.icons.show.diagnostics then + o.icons = {} + for name, sev in pairs(ICON_KEYS) do + o.icons[sev] = { + str = opts.diagnostics.icons[name], + hl = { HG_ICON[sev] }, + } + o:define_sign(o.icons[sev]) + end + end + + return o +end + +---Diagnostic icon: diagnostics.enable, renderer.icons.show.diagnostics and node has status +---@param node Node +---@return HighlightedString[]|nil icons +function DecoratorDiagnostics:calculate_icons(node) + if node and self.enabled and self.icons then + local diag_status = diagnostics.get_diag_status(node) + local diag_value = diag_status and diag_status.value + + if diag_value then + return { self.icons[diag_value] } + end + end +end + +---Diagnostic highlight: diagnostics.enable, renderer.highlight_diagnostics and node has status +---@param node Node +---@return string|nil group +function DecoratorDiagnostics:calculate_highlight(node) + if not node or not self.enabled or self.hl_pos == HL_POSITION.none then + return nil + end + + local diag_status = diagnostics.get_diag_status(node) + local diag_value = diag_status and diag_status.value + + if not diag_value then + return nil + end + + local group + if node.nodes then + group = HG_FOLDER[diag_value] + else + group = HG_FILE[diag_value] + end + + if group then + return group + else + return nil + end +end + +return DecoratorDiagnostics diff --git a/lua/nvim-tree/renderer/decorator/git.lua b/lua/nvim-tree/renderer/decorator/git.lua new file mode 100644 index 00000000000..de2477880bd --- /dev/null +++ b/lua/nvim-tree/renderer/decorator/git.lua @@ -0,0 +1,221 @@ +local notify = require "nvim-tree.notify" +local explorer_node = require "nvim-tree.explorer.node" + +local HL_POSITION = require("nvim-tree.enum").HL_POSITION +local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT + +local Decorator = require "nvim-tree.renderer.decorator" + +---@class HighlightedStringGit: HighlightedString +---@field ord number decreasing priority + +---@class DecoratorGit: Decorator +---@field file_hl table by porcelain status e.g. "AM" +---@field folder_hl table by porcelain status +---@field icons_by_status HighlightedStringGit[] by human status +---@field icons_by_xy table by porcelain status +local DecoratorGit = Decorator:new() + +---@param opts table +---@return DecoratorGit +function DecoratorGit:new(opts) + local o = Decorator.new(self, { + enabled = opts.git.enable, + hl_pos = HL_POSITION[opts.renderer.highlight_git] or HL_POSITION.none, + icon_placement = ICON_PLACEMENT[opts.renderer.icons.git_placement] or ICON_PLACEMENT.none, + }) + ---@cast o DecoratorGit + + if not o.enabled then + return o + end + + if o.hl_pos ~= HL_POSITION.none then + o:build_hl_table() + end + + if opts.renderer.icons.show.git then + o:build_icons_by_status(opts.renderer.icons.glyphs.git) + o:build_icons_by_xy(o.icons_by_status) + + for _, icon in pairs(o.icons_by_status) do + self:define_sign(icon) + end + end + + return o +end + +---@param glyphs table user glyps +function DecoratorGit:build_icons_by_status(glyphs) + self.icons_by_status = { + staged = { str = glyphs.staged, hl = { "NvimTreeGitStagedIcon" }, ord = 1 }, + unstaged = { str = glyphs.unstaged, hl = { "NvimTreeGitDirtyIcon" }, ord = 2 }, + renamed = { str = glyphs.renamed, hl = { "NvimTreeGitRenamedIcon" }, ord = 3 }, + deleted = { str = glyphs.deleted, hl = { "NvimTreeGitDeletedIcon" }, ord = 4 }, + unmerged = { str = glyphs.unmerged, hl = { "NvimTreeGitMergeIcon" }, ord = 5 }, + untracked = { str = glyphs.untracked, hl = { "NvimTreeGitNewIcon" }, ord = 6 }, + ignored = { str = glyphs.ignored, hl = { "NvimTreeGitIgnoredIcon" }, ord = 7 }, + } +end + +---@param icons HighlightedStringGit[] +function DecoratorGit:build_icons_by_xy(icons) + self.icons_by_xy = { + ["M "] = { icons.staged }, + [" M"] = { icons.unstaged }, + ["C "] = { icons.staged }, + [" C"] = { icons.unstaged }, + ["CM"] = { icons.unstaged }, + [" T"] = { icons.unstaged }, + ["T "] = { icons.staged }, + ["TM"] = { icons.staged, icons.unstaged }, + ["MM"] = { icons.staged, icons.unstaged }, + ["MD"] = { icons.staged }, + ["A "] = { icons.staged }, + ["AD"] = { icons.staged }, + [" A"] = { icons.untracked }, + -- not sure about this one + ["AA"] = { icons.unmerged, icons.untracked }, + ["AU"] = { icons.unmerged, icons.untracked }, + ["AM"] = { icons.staged, icons.unstaged }, + ["??"] = { icons.untracked }, + ["R "] = { icons.renamed }, + [" R"] = { icons.renamed }, + ["RM"] = { icons.unstaged, icons.renamed }, + ["UU"] = { icons.unmerged }, + ["UD"] = { icons.unmerged }, + ["UA"] = { icons.unmerged }, + [" D"] = { icons.deleted }, + ["D "] = { icons.deleted }, + ["DA"] = { icons.unstaged }, + ["RD"] = { icons.deleted }, + ["DD"] = { icons.deleted }, + ["DU"] = { icons.deleted, icons.unmerged }, + ["!!"] = { icons.ignored }, + dirty = { icons.unstaged }, + } +end + +function DecoratorGit:build_hl_table() + self.file_hl = { + ["M "] = "NvimTreeGitFileStagedHL", + ["C "] = "NvimTreeGitFileStagedHL", + ["AA"] = "NvimTreeGitFileStagedHL", + ["AD"] = "NvimTreeGitFileStagedHL", + ["MD"] = "NvimTreeGitFileStagedHL", + ["T "] = "NvimTreeGitFileStagedHL", + ["TT"] = "NvimTreeGitFileStagedHL", + [" M"] = "NvimTreeGitFileDirtyHL", + ["CM"] = "NvimTreeGitFileDirtyHL", + [" C"] = "NvimTreeGitFileDirtyHL", + [" T"] = "NvimTreeGitFileDirtyHL", + ["MM"] = "NvimTreeGitFileDirtyHL", + ["AM"] = "NvimTreeGitFileDirtyHL", + dirty = "NvimTreeGitFileDirtyHL", + ["A "] = "NvimTreeGitFileStagedHL", + ["??"] = "NvimTreeGitFileNewHL", + ["AU"] = "NvimTreeGitFileMergeHL", + ["UU"] = "NvimTreeGitFileMergeHL", + ["UD"] = "NvimTreeGitFileMergeHL", + ["DU"] = "NvimTreeGitFileMergeHL", + ["UA"] = "NvimTreeGitFileMergeHL", + [" D"] = "NvimTreeGitFileDeletedHL", + ["DD"] = "NvimTreeGitFileDeletedHL", + ["RD"] = "NvimTreeGitFileDeletedHL", + ["D "] = "NvimTreeGitFileDeletedHL", + ["R "] = "NvimTreeGitFileRenamedHL", + ["RM"] = "NvimTreeGitFileRenamedHL", + [" R"] = "NvimTreeGitFileRenamedHL", + ["!!"] = "NvimTreeGitFileIgnoredHL", + [" A"] = "none", + } + + self.folder_hl = {} + for k, v in pairs(self.file_hl) do + self.folder_hl[k] = v:gsub("File", "Folder") + end +end + +---Git icons: git.enable, renderer.icons.show.git and node has status +---@param node Node +---@return HighlightedString[]|nil modified icon +function DecoratorGit:calculate_icons(node) + if not node or not self.enabled or not self.icons_by_xy then + return nil + end + + local git_status = explorer_node.get_git_status(node) + if git_status == nil then + return nil + end + + local inserted = {} + local iconss = {} + + for _, s in pairs(git_status) do + local icons = self.icons_by_xy[s] + if not icons then + if self.hl_pos == HL_POSITION.none then + notify.warn(string.format("Unrecognized git state '%s'", git_status)) + end + return nil + end + + for _, icon in pairs(icons) do + if #icon.str > 0 then + if not inserted[icon] then + table.insert(iconss, icon) + inserted[icon] = true + end + end + end + end + + if #iconss == 0 then + return nil + end + + -- sort icons so it looks slightly better + table.sort(iconss, function(a, b) + return a.ord < b.ord + end) + + return iconss +end + +---Get the first icon as the sign if appropriate +---@param node Node +---@return string|nil name +function DecoratorGit:sign_name(node) + if self.icon_placement ~= ICON_PLACEMENT.signcolumn then + return + end + + local icons = self:calculate_icons(node) + if icons and #icons > 0 then + return icons[1].hl[1] + end +end + +---Git highlight: git.enable, renderer.highlight_git and node has status +---@param node Node +---@return string|nil group +function DecoratorGit:calculate_highlight(node) + if not node or not self.enabled or self.hl_pos == HL_POSITION.none then + return nil + end + + local git_status = explorer_node.get_git_status(node) + if not git_status then + return nil + end + + if node.nodes then + return self.folder_hl[git_status[1]] + else + return self.file_hl[git_status[1]] + end +end + +return DecoratorGit diff --git a/lua/nvim-tree/renderer/decorator/init.lua b/lua/nvim-tree/renderer/decorator/init.lua new file mode 100644 index 00000000000..7748b11cf49 --- /dev/null +++ b/lua/nvim-tree/renderer/decorator/init.lua @@ -0,0 +1,122 @@ +local HL_POSITION = require("nvim-tree.enum").HL_POSITION +local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT + +---@class Decorator +---@field protected enabled boolean +---@field protected hl_pos HL_POSITION +---@field protected icon_placement ICON_PLACEMENT +local Decorator = {} + +---@param o Decorator|nil +---@return Decorator +function Decorator:new(o) + o = o or {} + setmetatable(o, self) + self.__index = self + + return o +end + +---Maybe highlight groups +---@param node Node +---@return string|nil icon highlight group +---@return string|nil name highlight group +function Decorator:groups_icon_name(node) + local icon_hl, name_hl + + if self.enabled and self.hl_pos ~= HL_POSITION.none then + local hl = self:calculate_highlight(node) + + if self.hl_pos == HL_POSITION.all or self.hl_pos == HL_POSITION.icon then + icon_hl = hl + end + if self.hl_pos == HL_POSITION.all or self.hl_pos == HL_POSITION.name then + name_hl = hl + end + end + + return icon_hl, name_hl +end + +---Maybe icon sign +---@param node Node +---@return string|nil name +function Decorator:sign_name(node) + if not self.enabled or self.icon_placement ~= ICON_PLACEMENT.signcolumn then + return + end + + local icons = self:calculate_icons(node) + if icons and #icons > 0 then + return icons[1].hl[1] + end +end + +---Icons when ICON_PLACEMENT.before +---@param node Node +---@return HighlightedString[]|nil icons +function Decorator:icons_before(node) + if not self.enabled or self.icon_placement ~= ICON_PLACEMENT.before then + return + end + + return self:calculate_icons(node) +end + +---Icons when ICON_PLACEMENT.after +---@param node Node +---@return HighlightedString[]|nil icons +function Decorator:icons_after(node) + if not self.enabled or self.icon_placement ~= ICON_PLACEMENT.after then + return + end + + return self:calculate_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 + +---Define a sign +---@protected +---@param icon HighlightedString|nil +function Decorator:define_sign(icon) + if icon and #icon.hl > 0 then + local name = icon.hl[1] + + if not vim.tbl_isempty(vim.fn.sign_getdefined(name)) then + vim.fn.sign_undefine(name) + end + + -- don't use sign if not defined + if #icon.str < 1 then + self.icon_placement = ICON_PLACEMENT.none + return + end + + -- byte index of the next character, allowing for wide + local bi = vim.fn.byteidx(icon.str, 1) + + -- first (wide) character, falls back to empty string + local text = string.sub(icon.str, 1, bi) + vim.fn.sign_define(name, { + text = text, + texthl = name, + }) + end +end + +return Decorator diff --git a/lua/nvim-tree/renderer/decorator/modified.lua b/lua/nvim-tree/renderer/decorator/modified.lua new file mode 100644 index 00000000000..1ec546d1d65 --- /dev/null +++ b/lua/nvim-tree/renderer/decorator/modified.lua @@ -0,0 +1,61 @@ +local buffers = require "nvim-tree.buffers" + +local HL_POSITION = require("nvim-tree.enum").HL_POSITION +local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT + +local Decorator = require "nvim-tree.renderer.decorator" + +---@class DecoratorModified: Decorator +---@field icon HighlightedString|nil +local DecoratorModified = Decorator:new() + +---@param opts table +---@return DecoratorModified +function DecoratorModified:new(opts) + local o = Decorator.new(self, { + enabled = opts.modified.enable, + hl_pos = HL_POSITION[opts.renderer.highlight_modified] or HL_POSITION.none, + icon_placement = ICON_PLACEMENT[opts.renderer.icons.modified_placement] or ICON_PLACEMENT.none, + }) + ---@cast o DecoratorModified + + if not o.enabled then + return o + end + + if opts.renderer.icons.show.modified then + o.icon = { + str = opts.renderer.icons.glyphs.modified, + hl = { "NvimTreeModifiedIcon" }, + } + o:define_sign(o.icon) + end + + return o +end + +---Modified icon: modified.enable, renderer.icons.show.modified and node is modified +---@param node Node +---@return HighlightedString[]|nil icons +function DecoratorModified:calculate_icons(node) + if self.enabled and buffers.is_modified(node) then + return { self.icon } + end +end + +---Modified highlight: modified.enable, renderer.highlight_modified and node is modified +---@param node Node +---@return string|nil group +function DecoratorModified:calculate_highlight(node) + if not self.enabled or self.hl_pos == HL_POSITION.none or not buffers.is_modified(node) then + return nil + end + + if node.nodes then + return "NvimTreeModifiedFolderHL" + else + return "NvimTreeModifiedFileHL" + end +end + +return DecoratorModified diff --git a/lua/nvim-tree/renderer/decorator/opened.lua b/lua/nvim-tree/renderer/decorator/opened.lua new file mode 100644 index 00000000000..848faae42e6 --- /dev/null +++ b/lua/nvim-tree/renderer/decorator/opened.lua @@ -0,0 +1,35 @@ +local buffers = require "nvim-tree.buffers" + +local HL_POSITION = require("nvim-tree.enum").HL_POSITION +local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT + +local Decorator = require "nvim-tree.renderer.decorator" + +---@class DecoratorOpened: Decorator +---@field enabled boolean +---@field icon HighlightedString|nil +local DecoratorOpened = Decorator:new() + +---@param opts table +---@return DecoratorOpened +function DecoratorOpened:new(opts) + local o = Decorator.new(self, { + enabled = true, + hl_pos = HL_POSITION[opts.renderer.highlight_opened_files] or HL_POSITION.none, + icon_placement = ICON_PLACEMENT.none, + }) + ---@cast o DecoratorOpened + + return o +end + +---Opened highlight: renderer.highlight_opened_files and node has an open buffer +---@param node Node +---@return string|nil group +function DecoratorOpened:calculate_highlight(node) + if self.hl_pos ~= HL_POSITION.none and buffers.is_opened(node) then + return "NvimTreeOpenedHL" + end +end + +return DecoratorOpened diff --git a/lua/nvim-tree/renderer/init.lua b/lua/nvim-tree/renderer/init.lua index b9908ec8fb8..ce918682743 100644 --- a/lua/nvim-tree/renderer/init.lua +++ b/lua/nvim-tree/renderer/init.lua @@ -1,34 +1,32 @@ +local appearance = require "nvim-tree.appearance" local core = require "nvim-tree.core" local log = require "nvim-tree.log" local view = require "nvim-tree.view" local events = require "nvim-tree.events" -local modified = require "nvim-tree.renderer.components.modified" 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 git = require "nvim-tree.renderer.components.git" -local diagnostics = require "nvim-tree.renderer.components.diagnostics" local Builder = require "nvim-tree.renderer.builder" -local live_filter = require "nvim-tree.live-filter" -local bookmarks = require "nvim-tree.renderer.components.bookmarks" local M = { - last_highlights = {}, + last_hl_args = {}, } local SIGN_GROUP = "NvimTreeRendererSigns" -local namespace_id = vim.api.nvim_create_namespace "NvimTreeHighlights" - -local function _draw(bufnr, lines, hl, signs) +---@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 _, sign in pairs(signs) do - vim.fn.sign_place(0, SIGN_GROUP, sign.sign, bufnr, { lnum = sign.lnum, priority = sign.priority }) + for i, sign_name in pairs(signs) do + vim.fn.sign_place(0, SIGN_GROUP, sign_name, bufnr, { lnum = i + 1 }) end end @@ -36,26 +34,17 @@ function M.render_hl(bufnr, hl) if not bufnr or not vim.api.nvim_buf_is_loaded(bufnr) then return 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_clear_namespace(bufnr, appearance.NS_ID, 0, -1) + 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, namespace_id, group, data[2], data[3], data[4]) + vim.api.nvim_buf_add_highlight(bufnr, appearance.NS_ID, group, data[2], data[3], data[4]) end end end end -local picture_map = { - jpg = true, - jpeg = true, - png = true, - gif = true, - webp = true, - jxl = true, -} - -function M.draw(unloaded_bufnr) +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 return @@ -66,30 +55,13 @@ function M.draw(unloaded_bufnr) local cursor = vim.api.nvim_win_get_cursor(view.get_winnr()) icon_component.reset_config() - local lines, hl, signs = Builder.new(core.get_cwd()) - :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_opened_file_highlighting(M.config.highlight_opened_files) - :configure_modified_highlighting(M.config.highlight_modified) - :configure_icon_padding(M.config.icons.padding) - :configure_git_icons_placement(M.config.icons.git_placement) - :configure_diagnostics_icon_placement(M.config.icons.diagnostics_placement) - :configure_bookmark_icon_placement(M.config.icons.bookmarks_placement) - :configure_modified_placement(M.config.icons.modified_placement) - :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(), unloaded_bufnr) - :unwrap() - - _draw(bufnr, lines, hl, signs) - - M.last_highlights = hl - - if cursor and #lines >= cursor[1] then + local builder = Builder:new():build() + + _draw(bufnr, builder.lines, builder.hl_args, builder.signs) + + M.last_hl_args = builder.hl_args + + if cursor and #builder.lines >= cursor[1] then vim.api.nvim_win_set_cursor(view.get_winnr(), cursor) end @@ -102,15 +74,12 @@ end function M.setup(opts) M.config = opts.renderer - M.config.modified = opts.modified _padding.setup(opts) full_name.setup(opts) - git.setup(opts) - modified.setup(opts) - diagnostics.setup(opts) - bookmarks.setup(opts) icon_component.setup(opts) + + Builder.setup(opts) end return M diff --git a/lua/nvim-tree/view.lua b/lua/nvim-tree/view.lua index 387105b1af3..b08ba2baa98 100644 --- a/lua/nvim-tree/view.lua +++ b/lua/nvim-tree/view.lua @@ -1,3 +1,4 @@ +local appearance = require "nvim-tree.appearance" local events = require "nvim-tree.events" local utils = require "nvim-tree.utils" local log = require "nvim-tree.log" @@ -38,19 +39,6 @@ M.View = { cursorlineopt = "both", colorcolumn = "0", wrap = false, - winhl = table.concat({ - "EndOfBuffer:NvimTreeEndOfBuffer", - "CursorLine:NvimTreeCursorLine", - "CursorLineNr:NvimTreeCursorLineNr", - "LineNr:NvimTreeLineNr", - "WinSeparator:NvimTreeWinSeparator", - "StatusLine:NvimTreeStatusLine", - "StatusLineNC:NvimTreeStatuslineNC", - "SignColumn:NvimTreeSignColumn", - "Normal:NvimTreeNormal", - "NormalNC:NvimTreeNormalNC", - "NormalFloat:NvimTreeNormalFloat", - }, ","), }, } @@ -147,6 +135,9 @@ local function set_window_options_and_buffer() vim.opt_local[k] = v end vim.opt.eventignore = eventignore + + -- use highlights from the nvim_tree namespace + vim.api.nvim_win_set_hl_ns(M.get_winnr(), appearance.NS_ID) end ---@return table @@ -539,13 +530,6 @@ function M.is_root_folder_visible(cwd) return cwd ~= "/" and not M.View.hide_root_folder end --- used on ColorScheme event -function M.reset_winhl() - if M.get_winnr() and vim.api.nvim_win_is_valid(M.get_winnr()) then - vim.wo[M.get_winnr()].winhl = M.View.winopts.winhl - end -end - function M.setup(opts) local options = opts.view or {} M.View.centralize_selection = options.centralize_selection