feat(#2415): colour and highlight overhaul, see :help nvim-tree-highlight-overhaul (#2455)

* feat(#2415): granular highlight_diagnostics, normalise groups (#2454)

* chore: normalise colours and enable cterm (#2471)

* feat(#2415): granular highlight_git, normalise git groups (#2487)

* docs: update CONTRIBUTING.md (#2485)

* feat(#2415): granular highlight_git, normalise git groups

* feat(#2415): normalise and add modified groups

* feat(#2415): create Decorator class for modified and bookmarks

* feat(#2415): create DecoratorDiagnostics

* feat(#2415): create DecoratorGit

* feat(#2415): create DecoratorGit

* add DecoratorCopied DecoratorCut

* add DecoratorOpened

* remove unloaded_bufnr checks as the view debouncer takes care of it

* Add `renderer.highlight_git` to accepted strings

* fix(#2415): builder refactor (#2538)

* simplify builder signs

* decorators take care of themselves and are priority ordered

* simplify builder hl groups

* refactor builder for icon arrays

* builder use decorators generically

* fix(#2415): harden sign creation (#2539)

* fix(#2415): harden unicode signs

* Decorator tidy

* normalise git sign creation and tidy

* tidy builder

* NvimTreeBookmarkIcon

* tidy HL doc

* tidy HL doc

* tidy HL doc

* tidy builder doc

* standardise on '---@param'

* DiagnosticWarning -> DiagnosticWarn

* annotate decorators

* limit to two highlight groups for line rendering

* style

* apply #2519

* feat(#2415): combined hl groups (#2601)

* feat(#2415): create combined highlight groups

* feat(#2415): create combined highlight groups

* feat(#2415): create combined highlight groups

* ci: allow workflow_dispatch (#2620)

* one and only one hl namespace, required winhl removal

* small tidies

* colors.lua -> appearance.lua

* full-name uses one and only namespace

* don't highlight fast, just apply to namespace, safer win_set_hl

* gut builder (#2622)

collapse Builder

* fix group_empty function check

* feat(#2415): highlight-overhaul release date

---------

Co-authored-by: Akmadan23 <azadahmadi@mailo.com>
This commit is contained in:
Alexander Courtis 2024-01-20 16:12:13 +11:00 committed by GitHub
parent f24afa2cef
commit e9c5abe073
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 1468 additions and 1004 deletions

View File

@ -53,7 +53,7 @@ Setup the plugin in your `init.lua`
vim.g.loaded_netrw = 1 vim.g.loaded_netrw = 1
vim.g.loaded_netrwPlugin = 1 vim.g.loaded_netrwPlugin = 1
-- set termguicolors to enable highlight groups -- optionally enable 24-bit colour
vim.opt.termguicolors = true vim.opt.termguicolors = true
-- empty setup using defaults -- empty setup using defaults

View File

@ -46,6 +46,7 @@ CONTENTS *nvim-tree*
7. Mappings |nvim-tree-mappings| 7. Mappings |nvim-tree-mappings|
7.1 Mappings: Default |nvim-tree-mappings-default| 7.1 Mappings: Default |nvim-tree-mappings-default|
8. Highlight |nvim-tree-highlight| 8. Highlight |nvim-tree-highlight|
8.1 Highlight Overhaul |nvim-tree-highlight-overhaul|
9. Events |nvim-tree-events| 9. Events |nvim-tree-events|
10. Prompts |nvim-tree-prompts| 10. Prompts |nvim-tree-prompts|
11. OS Specific Restrictions |nvim-tree-os-specific| 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_netrw = 1
vim.g.loaded_netrwPlugin = 1 vim.g.loaded_netrwPlugin = 1
-- set termguicolors to enable highlight groups -- optionally enable 24-bit colour
vim.opt.termguicolors = true vim.opt.termguicolors = true
-- empty setup using defaults -- empty setup using defaults
@ -387,8 +388,8 @@ Following is the default configuration. See |nvim-tree-opts| for details.
indent_width = 2, indent_width = 2,
special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" }, special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" },
symlink_destination = true, symlink_destination = true,
highlight_git = false, highlight_git = "none",
highlight_diagnostics = false, highlight_diagnostics = "none",
highlight_opened_files = "none", highlight_opened_files = "none",
highlight_modified = "none", highlight_modified = "none",
highlight_bookmarks = "none", highlight_bookmarks = "none",
@ -789,8 +790,8 @@ Use nvim-tree in a floating window.
============================================================================== ==============================================================================
5.3 OPTS: RENDERER *nvim-tree-opts-renderer* 5.3 OPTS: RENDERER *nvim-tree-opts-renderer*
Highlight precedence: Highlight precedence, additive:
clipboard > diagnostics > bookmarked > modified > opened > git git < opened < modified < bookmarked < diagnostics < copied < cut
*nvim-tree.renderer.add_trailing* *nvim-tree.renderer.add_trailing*
Appends a trailing slash to folder names. Appends a trailing slash to folder names.
@ -832,14 +833,16 @@ Whether to show the destination of the symlink.
Type: `boolean`, Default: `true` Type: `boolean`, Default: `true`
*nvim-tree.renderer.highlight_git* *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| 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* *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| 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* *nvim-tree.renderer.highlight_opened_files*
Highlight icons and/or names for |bufloaded()| files using the Highlight icons and/or names for |bufloaded()| files using the
@ -892,8 +895,8 @@ Configuration options for tree indent markers.
*nvim-tree.renderer.icons* *nvim-tree.renderer.icons*
Configuration options for icons. Configuration options for icons.
Icon sign column precedence: Icon order and sign column precedence:
diagnostics > modified > git > bookmarked git < modified < bookmarked < diagnostics
*nvim-tree.renderer.icons.web_devicons* *nvim-tree.renderer.icons.web_devicons*
Configure optional plugin `"nvim-tree/nvim-web-devicons"` Configure optional plugin `"nvim-tree/nvim-web-devicons"`
@ -907,7 +910,7 @@ Icon sign column precedence:
Type: `boolean`, Default: `true` Type: `boolean`, Default: `true`
*nvim-tree.renderer.icons.web_devicons.file.color* *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` Type: `boolean`, Default: `true`
*nvim-tree.renderer.icons.web_devicons.folder* *nvim-tree.renderer.icons.web_devicons.folder*
@ -919,7 +922,7 @@ Icon sign column precedence:
Type: `boolean`, Default: `false` Type: `boolean`, Default: `false`
*nvim-tree.renderer.icons.web_devicons.folder.color* *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` Type: `boolean`, Default: `true`
*nvim-tree.renderer.icons.git_placement* *nvim-tree.renderer.icons.git_placement*
@ -2237,42 +2240,13 @@ groups.
Example |:highlight| > Example |:highlight| >
:hi NvimTreeSymlink guifg=blue gui=bold,underline :hi NvimTreeSymlink guifg=blue gui=bold,underline
< <
You should have 'termguicolors' enabled, otherwise, colors will not be It is recommended to enable 'termguicolors' for the more pleasant 24-bit colours.
applied.
To view the active highlight groups run `:so $VIMRUNTIME/syntax/hitest.vim` To view the active highlight groups run `:so $VIMRUNTIME/syntax/hitest.vim`
as per |:highlight| 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: > Standard: >
NvimTreeNormal Normal NvimTreeNormal Normal
NvimTreeNormalFloat NormalFloat NvimTreeNormalFloat NormalFloat
@ -2291,67 +2265,163 @@ Standard: >
NvimTreeStatusLine StatusLine NvimTreeStatusLine StatusLine
NvimTreeStatusLineNC StatusLineNC 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: > Clipboard: >
NvimTreeCopiedHL SpellRare NvimTreeCopiedHL SpellRare
NvimTreeCutHL SpellBad NvimTreeCutHL SpellBad
< <
Bookmark Icon: > Bookmarks: >
NvimTreeBookmark NvimTreeBookmarkIcon Constant
<
Bookmark Highlight: >
NvimTreeBookmarkHL SpellLocal NvimTreeBookmarkHL SpellLocal
< <
Picker: > Modified: >
NvimTreeWindowPicker NvimTreeModifiedIcon Constant
NvimTreeModifiedFileHL NvimTreeModifiedIcon
NvimTreeModifiedFolderHL NvimTreeModifiedIcon
< <
Live Filter: > Opened: >
NvimTreeLiveFilterPrefix NvimTreeOpenedHL Constant
NvimTreeLiveFilterValue
< <
Git Icon: > Git Icon: >
NvimTreeGitDirty NvimTreeGitDeletedIcon Statement
NvimTreeGitStaged NvimTreeGitDirtyIcon Statement
NvimTreeGitMerge NvimTreeGitIgnoredIcon Comment
NvimTreeGitRenamed NvimTreeGitMergeIcon Constant
NvimTreeGitNew NvimTreeGitNewIcon PreProc
NvimTreeGitDeleted NvimTreeGitRenamedIcon PreProc
NvimTreeGitIgnored Comment NvimTreeGitStagedIcon Constant
< <
Git File Text: > Git File File Highlight: >
NvimTreeFileDirty NvimTreeGitDirty NvimTreeGitFileDeletedHL NvimTreeGitDeletedIcon
NvimTreeFileStaged NvimTreeGitStaged NvimTreeGitFileDirtyHL NvimTreeGitDirtyIcon
NvimTreeFileMerge NvimTreeGitMerge NvimTreeGitFileIgnoredHL NvimTreeGitIgnoredIcon
NvimTreeFileRenamed NvimTreeGitRenamed NvimTreeGitFileMergeHL NvimTreeGitMergeIcon
NvimTreeFileNew NvimTreeGitNew NvimTreeGitFileNewHL NvimTreeGitNewIcon
NvimTreeFileDeleted NvimTreeGitDeleted NvimTreeGitFileRenamedHL NvimTreeGitRenamedIcon
NvimTreeFileIgnored NvimTreeGitIgnored NvimTreeGitFileStagedHL NvimTreeGitStagedIcon
< <
Git Folder Text: > Git Folder Folder Highlight: >
NvimTreeFolderDirty NvimTreeFileDirty NvimTreeGitFolderDeletedHL NvimTreeGitFileDeletedHL
NvimTreeFolderStaged NvimTreeFileStaged NvimTreeGitFolderDirtyHL NvimTreeGitFileDirtyHL
NvimTreeFolderMerge NvimTreeFileMerge NvimTreeGitFolderIgnoredHL NvimTreeGitFileIgnoredHL
NvimTreeFolderRenamed NvimTreeFileRenamed NvimTreeGitFolderMergeHL NvimTreeGitFileMergeHL
NvimTreeFolderNew NvimTreeFileNew NvimTreeGitFolderNewHL NvimTreeGitFileNewHL
NvimTreeFolderDeleted NvimTreeFileDeleted NvimTreeGitFolderRenamedHL NvimTreeGitFileRenamedHL
NvimTreeFolderIgnored NvimTreeFileIgnored NvimTreeGitFolderStagedHL NvimTreeGitFileStagedHL
< <
Diagnostics Icon: > Diagnostics Icon: >
NvimTreeLspDiagnosticsError DiagnosticError NvimTreeDiagnosticErrorIcon DiagnosticError
NvimTreeLspDiagnosticsWarning DiagnosticWarn NvimTreeDiagnosticWarnIcon DiagnosticWarn
NvimTreeLspDiagnosticsInformation DiagnosticInfo NvimTreeDiagnosticInfoIcon DiagnosticInfo
NvimTreeLspDiagnosticsHint DiagnosticHint NvimTreeDiagnosticHintIcon DiagnosticHint
< <
Diagnostics File Text: > Diagnostics File Highlight: >
NvimTreeLspDiagnosticsErrorText NvimTreeLspDiagnosticsError NvimTreeDiagnosticErrorFileHL DiagnosticUnderlineError
NvimTreeLspDiagnosticsWarningText NvimTreeLspDiagnosticsWarning NvimTreeDiagnosticWarnFileHL DiagnosticUnderlineWarn
NvimTreeLspDiagnosticsInfoText NvimTreeLspDiagnosticsInformation NvimTreeDiagnosticInfoFileHL DiagnosticUnderlineInfo
NvimTreeLspDiagnosticsHintText NvimTreeLspDiagnosticsHint NvimTreeDiagnosticHintFileHL DiagnosticUnderlineHint
< <
Diagnostics Folder Text: > Diagnostics Folder Highlight: >
NvimTreeLspDiagnosticsErrorFolderText NvimTreeLspDiagnosticsErrorText NvimTreeDiagnosticErrorFolderHL NvimTreeDiagnosticErrorFileHL
NvimTreeLspDiagnosticsWarningFolderText NvimTreeLspDiagnosticsWarningText NvimTreeDiagnosticWarnFolderHL NvimTreeDiagnosticWarnFileHL
NvimTreeLspDiagnosticsInfoFolderText NvimTreeLspDiagnosticsInfoText NvimTreeDiagnosticInfoFolderHL NvimTreeDiagnosticInfoFileHL
NvimTreeLspDiagnosticsHintFolderText NvimTreeLspDiagnosticsHintText NvimTreeDiagnosticHintFolderHL NvimTreeDiagnosticHintFileHL
<
==============================================================================
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* 9. EVENTS *nvim-tree-events*

View File

@ -1,6 +1,6 @@
local lib = require "nvim-tree.lib" local lib = require "nvim-tree.lib"
local log = require "nvim-tree.log" 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 renderer = require "nvim-tree.renderer"
local view = require "nvim-tree.view" local view = require "nvim-tree.view"
local commands = require "nvim-tree.commands" local commands = require "nvim-tree.commands"
@ -104,12 +104,6 @@ function M.open_on_directory()
actions.root.change_dir.force_dirchange(bufname, true) actions.root.change_dir.force_dirchange(bufname, true)
end end
function M.reset_highlight()
colors.setup()
view.reset_winhl()
renderer.render_hl(view.get_bufnr())
end
function M.place_cursor_on_node() function M.place_cursor_on_node()
local search = vim.fn.searchcount() local search = vim.fn.searchcount()
if search and search.exact_match == 1 then 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)) vim.api.nvim_create_autocmd(name, vim.tbl_extend("force", default_opts, custom_opts))
end end
-- reset highlights when colorscheme is changed -- reset and draw highlights when colorscheme is changed
create_nvim_tree_autocmd("ColorScheme", { callback = M.reset_highlight }) 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 -- prevent new opened file from opening in the same window as nvim-tree
create_nvim_tree_autocmd("BufWipeout", { create_nvim_tree_autocmd("BufWipeout", {
@ -210,7 +209,7 @@ local function setup_autocommands(opts)
-- update opened file buffers -- update opened file buffers
if (filters.config.filter_no_buffer or renderer.config.highlight_opened_files ~= "none") and vim.bo[data.buf].buftype == "" then 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() utils.debounce("Buf:filter_buffer", opts.view.debounce_delay, function()
actions.reloaders.reload_explorer(nil, data.buf) actions.reloaders.reload_explorer()
end) end)
end end
end, end,
@ -386,8 +385,8 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
indent_width = 2, indent_width = 2,
special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" }, special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" },
symlink_destination = true, symlink_destination = true,
highlight_git = false, highlight_git = "none",
highlight_diagnostics = false, highlight_diagnostics = "none",
highlight_opened_files = "none", highlight_opened_files = "none",
highlight_modified = "none", highlight_modified = "none",
highlight_bookmarks = "none", highlight_bookmarks = "none",
@ -641,9 +640,11 @@ local ACCEPTED_STRINGS = {
signcolumn = { "yes", "no", "auto" }, signcolumn = { "yes", "no", "auto" },
}, },
renderer = { renderer = {
highlight_git = { "none", "icon", "name", "all" },
highlight_opened_files = { "none", "icon", "name", "all" }, highlight_opened_files = { "none", "icon", "name", "all" },
highlight_modified = { "none", "icon", "name", "all" }, highlight_modified = { "none", "icon", "name", "all" },
highlight_bookmarks = { "none", "icon", "name", "all" }, highlight_bookmarks = { "none", "icon", "name", "all" },
highlight_diagnostics = { "none", "icon", "name", "all" },
highlight_clipboard = { "none", "icon", "name", "all" }, highlight_clipboard = { "none", "icon", "name", "all" },
icons = { icons = {
git_placement = { "before", "after", "signcolumn" }, git_placement = { "before", "after", "signcolumn" },
@ -786,7 +787,7 @@ function M.setup(conf)
require("nvim-tree.actions").setup(opts) require("nvim-tree.actions").setup(opts)
require("nvim-tree.keymap").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.diagnostics").setup(opts)
require("nvim-tree.explorer").setup(opts) require("nvim-tree.explorer").setup(opts)
require("nvim-tree.git").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.renderer").setup(opts)
require("nvim-tree.live-filter").setup(opts) require("nvim-tree.live-filter").setup(opts)
require("nvim-tree.marks").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.help").setup(opts)
require("nvim-tree.watcher").setup(opts) require("nvim-tree.watcher").setup(opts)
if M.config.renderer.icons.show.file and pcall(require, "nvim-web-devicons") then if M.config.renderer.icons.show.file and pcall(require, "nvim-web-devicons") then

View File

@ -7,8 +7,6 @@ local notify = require "nvim-tree.notify"
local renderer = require "nvim-tree.renderer" local renderer = require "nvim-tree.renderer"
local reloaders = require "nvim-tree.actions.reloaders" 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 find_file = require("nvim-tree.actions.finders.find-file").fn
local M = { local M = {
@ -317,34 +315,23 @@ function M.copy_absolute_path(node)
copy_to_clipboard(content) copy_to_clipboard(content)
end end
--- Clipboard text highlight group and position when highlight_clipboard. ---Node is cut. Will not be copied.
---@param node Node ---@param node Node
---@return HL_POSITION position none when clipboard empty ---@return boolean
---@return string|nil group only when node present in clipboard function M.is_cut(node)
function M.get_highlight(node) return vim.tbl_contains(clipboard.cut, node)
if M.hl_pos == HL_POSITION.none then end
return HL_POSITION.none, nil
end
for _, n in ipairs(clipboard.cut) do ---Node is copied. Will not be cut.
if node == n then ---@param node Node
return M.hl_pos, "NvimTreeCutHL" ---@return boolean
end function M.is_copied(node)
end return vim.tbl_contains(clipboard.copy, node)
for _, n in ipairs(clipboard.copy) do
if node == n then
return M.hl_pos, "NvimTreeCopiedHL"
end
end
return HL_POSITION.none, nil
end end
function M.setup(opts) function M.setup(opts)
M.config.filesystem_watchers = opts.filesystem_watchers M.config.filesystem_watchers = opts.filesystem_watchers
M.config.actions = opts.actions M.config.actions = opts.actions
M.hl_pos = HL_POSITION[opts.renderer.highlight_clipboard] or HL_POSITION.none
end end
return M return M

View File

@ -10,13 +10,12 @@ local M = {}
---@param node Explorer|nil ---@param node Explorer|nil
---@param projects table ---@param projects table
---@param unloaded_bufnr number|nil local function refresh_nodes(node, projects)
local function refresh_nodes(node, projects, unloaded_bufnr)
Iterator.builder({ node }) Iterator.builder({ node })
:applier(function(n) :applier(function(n)
if n.nodes then if n.nodes then
local toplevel = git.get_toplevel(n.cwd or n.link_to or n.absolute_path) 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
end) end)
:recursor(function(n) :recursor(function(n)
@ -43,18 +42,16 @@ function M.reload_node_status(parent_node, projects)
end end
local event_running = false local event_running = false
---@param _ table|nil unused node passed by action function M.reload_explorer()
---@param unloaded_bufnr number|nil optional bufnr recently unloaded via BufUnload event
function M.reload_explorer(_, unloaded_bufnr)
if event_running or not core.get_explorer() or vim.v.exiting ~= vim.NIL then if event_running or not core.get_explorer() or vim.v.exiting ~= vim.NIL then
return return
end end
event_running = true event_running = true
local projects = git.reload() local projects = git.reload()
refresh_nodes(core.get_explorer(), projects, unloaded_bufnr) refresh_nodes(core.get_explorer(), projects)
if view.is_visible() then if view.is_visible() then
renderer.draw(unloaded_bufnr) renderer.draw()
end end
event_running = false event_running = false
end end

View File

@ -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

50
lua/nvim-tree/buffers.lua Normal file
View File

@ -0,0 +1,50 @@
local M = {}
---@type table<string, boolean> 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

View File

@ -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

View File

@ -12,9 +12,10 @@ M.HL_POSITION = {
---Setup options for "*_placement" ---Setup options for "*_placement"
---@enum ICON_PLACEMENT ---@enum ICON_PLACEMENT
M.ICON_PLACEMENT = { M.ICON_PLACEMENT = {
signcolumn = 0, none = 0,
before = 1, signcolumn = 1,
after = 2, before = 2,
after = 3,
} }
return M return M

View File

@ -47,16 +47,15 @@ end
---Check if the given path has no listed buffer ---Check if the given path has no listed buffer
---@param path string Absolute path ---@param path string Absolute path
---@param bufinfo table vim.fn.getbufinfo { buflisted = 1 } ---@param bufinfo table vim.fn.getbufinfo { buflisted = 1 }
---@param unloaded_bufnr number optional bufnr recently unloaded via BufUnload event
---@return boolean ---@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 if not M.config.filter_no_buffer or type(bufinfo) ~= "table" then
return false return false
end end
-- filter files with no open buffer and directories containing no open buffers -- filter files with no open buffer and directories containing no open buffers
for _, b in ipairs(bufinfo) do 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 return false
end end
end end
@ -105,16 +104,13 @@ end
---Prepare arguments for should_filter. This is done prior to should_filter for efficiency reasons. ---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 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 ---@return table
--- git_status: reference --- git_status: reference
--- unloaded_bufnr: copy
--- bufinfo: empty unless no_buffer set: vim.fn.getbufinfo { buflisted = 1 } --- bufinfo: empty unless no_buffer set: vim.fn.getbufinfo { buflisted = 1 }
--- bookmarks: absolute paths to boolean --- bookmarks: absolute paths to boolean
function M.prepare(git_status, unloaded_bufnr) function M.prepare(git_status)
local status = { local status = {
git_status = git_status or {}, git_status = git_status or {},
unloaded_bufnr = unloaded_bufnr,
bufinfo = {}, bufinfo = {},
bookmarks = {}, bookmarks = {},
} }
@ -140,11 +136,7 @@ function M.should_filter(path, status)
return false return false
end end
return git(path, status.git_status) return git(path, status.git_status) or buf(path, status.bufinfo) or dotfile(path) or custom(path) or bookmark(path, status.bookmarks)
or buf(path, status.bufinfo, status.unloaded_bufnr)
or dotfile(path)
or custom(path)
or bookmark(path, status.bookmarks)
end end
function M.setup(opts) function M.setup(opts)

View File

@ -69,8 +69,7 @@ end
---@param node Node ---@param node Node
---@param git_status table ---@param git_status table
---@param unloaded_bufnr number|nil function M.reload(node, git_status)
function M.reload(node, git_status, unloaded_bufnr)
local cwd = node.link_to or node.absolute_path local cwd = node.link_to or node.absolute_path
local handle = vim.loop.fs_scandir(cwd) local handle = vim.loop.fs_scandir(cwd)
if not handle then 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 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 if node.group_next then
node.nodes = { node.group_next } node.nodes = { node.group_next }

View File

@ -41,6 +41,16 @@ local function refactored(opts)
-- 2023/08/26 -- 2023/08/26
utils.move_missing_val(opts, "renderer.icons", "webdev_colors", opts, "renderer.icons.web_devicons.file", "color", true) 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 end
local function deprecated(opts) local function deprecated(opts)

View File

@ -45,7 +45,7 @@ end
---@param node Node|MinimalNode ---@param node Node|MinimalNode
---@return table|nil ---@return table|nil
function M.get_mark(node) function M.get_mark(node)
return NvimTreeMarks[node.absolute_path] return node and NvimTreeMarks[node.absolute_path]
end end
---@return table ---@return table

View File

@ -26,7 +26,8 @@ end
---@param node Node ---@param node Node
---@return boolean ---@return boolean
function M.is_modified(node) function M.is_modified(node)
return M.config.enable return node
and M.config.enable
and M._record[node.absolute_path] and M._record[node.absolute_path]
and (not node.nodes or M.config.show_on_dirs) and (not node.nodes or M.config.show_on_dirs)
and (not node.open or M.config.show_on_open_dirs) and (not node.open or M.config.show_on_open_dirs)

View File

@ -1,136 +1,94 @@
local utils = require "nvim-tree.utils" local appearance = require "nvim-tree.appearance"
local core = require "nvim-tree.core" local core = require "nvim-tree.core"
local live_filter = require "nvim-tree.live-filter"
local notify = require "nvim-tree.notify" 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 pad = require "nvim-tree.renderer.components.padding"
local icons = require "nvim-tree.renderer.components.icons" 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,
},
}
---@class HighlightedString
---@field str string
---@field hl string[]
---@class AddHighlightArgs
---@field group string[]
---@field line number
---@field col_start number
---@field col_end number
---@class Builder
---@field lines string[] includes icons etc.
---@field hl_args AddHighlightArgs[] line highlights
---@field signs string[] line signs
---@field private root_cwd string absolute path
---@field private index number
---@field private depth number
---@field private combined_groups string[] combined group names
---@field private markers boolean[] indent markers
local Builder = {} local Builder = {}
Builder.__index = Builder
local DEFAULT_ROOT_FOLDER_LABEL = ":~:s?$?/..?" ---@return Builder
function Builder:new()
function Builder.new(root_cwd) local o = {
return setmetatable({ root_cwd = core.get_cwd(),
index = 0, index = 0,
depth = 0, depth = 0,
highlights = {}, hl_args = {},
combined_groups = {},
lines = {}, lines = {},
markers = {}, markers = {},
signs = {}, signs = {},
root_cwd = root_cwd, }
}, Builder) setmetatable(o, self)
self.__index = self
return o
end end
function Builder:configure_root_label(root_folder_label) ---Insert ranged highlight groups into self.highlights
self.root_folder_label = root_folder_label or DEFAULT_ROOT_FOLDER_LABEL ---@private
return self ---@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 end
function Builder:configure_trailing_slash(with_trailing) ---@private
self.trailing_slash = with_trailing and "/" or "" function Builder:get_folder_name(node)
return self
end
function Builder:configure_special_files(special_files)
self.special_files = special_files
return self
end
function Builder:configure_picture_map(picture_map)
self.picture_map = picture_map
return self
end
function Builder:configure_filter(filter, prefix)
self.filter_prefix = prefix
self.filter = filter
return self
end
function Builder:configure_opened_file_highlighting(highlight_opened_files)
self.highlight_opened_files = highlight_opened_files
return self
end
function Builder:configure_modified_highlighting(highlight_modified)
self.highlight_modified = highlight_modified
return self
end
function Builder:configure_icon_padding(padding)
self.icon_padding = padding or " "
return self
end
function Builder:configure_git_icons_placement(where)
if where ~= "after" and where ~= "before" and where ~= "signcolumn" then
where = "before" -- default before
end
self.git_placement = where
return self
end
function Builder:configure_diagnostics_icon_placement(where)
if where ~= "after" and where ~= "before" and where ~= "signcolumn" then
where = "before" -- default before
end
self.diagnostics_placement = where
return self
end
function Builder:configure_bookmark_icon_placement(where)
if where ~= "after" and where ~= "before" and where ~= "signcolumn" then
where = "before" -- default before
end
self.bookmarks_placement = where
return self
end
function Builder:configure_modified_placement(where)
if where ~= "after" and where ~= "before" and where ~= "signcolumn" then
where = "after" -- default after
end
self.modified_placement = where
return self
end
function Builder:configure_symlink_destination(show)
self.symlink_destination = show
return self
end
function Builder: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)
local name = node.name local name = node.name
local next = node.group_next local next = node.group_next
while next do while next do
name = name .. "/" .. next.name name = string.format("%s/%s", name, next.name)
next = next.group_next next = next.group_next
end end
if node.group_next and self.group_name_modifier then if node.group_next and type(M.opts.renderer.group_empty) == "function" then
local new_name = self.group_name_modifier(name) local new_name = M.opts.renderer.group_empty(name)
if type(new_name) == "string" then if type(new_name) == "string" then
name = new_name name = new_name
else else
@ -138,16 +96,13 @@ function Builder:_get_folder_name(node)
end end
end end
return name .. self.trailing_slash return string.format("%s%s", name, M.opts.renderer.add_trailing and "/" or "")
end end
---@class HighlightedString ---@private
---@field str string
---@field hl string[]
---@param highlighted_strings HighlightedString[] ---@param highlighted_strings HighlightedString[]
---@return string ---@return string
function Builder:_unwrap_highlighted_strings(highlighted_strings) function Builder:unwrap_highlighted_strings(highlighted_strings)
if not highlighted_strings then if not highlighted_strings then
return "" return ""
end end
@ -156,21 +111,22 @@ function Builder:_unwrap_highlighted_strings(highlighted_strings)
for _, v in ipairs(highlighted_strings) do for _, v in ipairs(highlighted_strings) do
if #v.str > 0 then if #v.str > 0 then
if v.hl and type(v.hl) == "table" 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 end
string = string .. v.str string = string.format("%s%s", string, v.str)
end end
end end
return string return string
end end
---@private
---@param node table ---@param node table
---@return HighlightedString icon ---@return HighlightedString icon
---@return HighlightedString name ---@return HighlightedString name
function Builder:_build_folder(node) function Builder:build_folder(node)
local has_children = #node.nodes ~= 0 or node.has_children local has_children = #node.nodes ~= 0 or node.has_children
local icon, icon_hl = icons.get_folder_icon(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 #icon > 0 and icon_hl == nil then
if node.open then if node.open then
@ -181,12 +137,14 @@ function Builder:_build_folder(node)
end end
local foldername_hl = "NvimTreeFolderName" 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 arrow = icons.i.symlink_arrow
local link_to = utils.path_relative(node.link_to, core.get_cwd()) local link_to = utils.path_relative(node.link_to, self.root_cwd)
foldername = foldername .. arrow .. link_to foldername = string.format("%s%s%s", foldername, arrow, link_to)
foldername_hl = "NvimTreeSymlinkFolderName" 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" foldername_hl = "NvimTreeSpecialFolderName"
elseif node.open then elseif node.open then
foldername_hl = "NvimTreeOpenedFolderName" foldername_hl = "NvimTreeOpenedFolderName"
@ -197,174 +155,56 @@ function Builder:_build_folder(node)
return { str = icon, hl = { icon_hl } }, { str = foldername, hl = { foldername_hl } } return { str = icon, hl = { icon_hl } }, { str = foldername, hl = { foldername_hl } }
end end
---@private
---@param node table ---@param node table
---@return HighlightedString icon ---@return HighlightedString icon
---@return HighlightedString name ---@return HighlightedString name
function Builder:_build_symlink(node) function Builder:build_symlink(node)
local icon = icons.i.symlink local icon = icons.i.symlink
local arrow = icons.i.symlink_arrow local arrow = icons.i.symlink_arrow
local symlink_formatted = node.name local symlink_formatted = node.name
if self.symlink_destination then if M.opts.renderer.symlink_destination then
local link_to = utils.path_relative(node.link_to, core.get_cwd()) local link_to = utils.path_relative(node.link_to, self.root_cwd)
symlink_formatted = symlink_formatted .. arrow .. link_to symlink_formatted = string.format("%s%s%s", symlink_formatted, arrow, link_to)
end end
return { str = icon, hl = { "NvimTreeSymlinkIcon" } }, { str = symlink_formatted, hl = { "NvimTreeSymlink" } } return { str = icon, hl = { "NvimTreeSymlinkIcon" } }, { str = symlink_formatted, hl = { "NvimTreeSymlink" } }
end end
---@param node table ---@private
---@return HighlightedString icon
function Builder:_build_file_icon(node)
local icon, hl_group = icons.get_file_icon(node.name, node.extension)
return { str = icon, hl = { hl_group } }
end
---@param node table ---@param node table
---@return HighlightedString icon ---@return HighlightedString icon
---@return HighlightedString name ---@return HighlightedString name
function Builder:_build_file(node) function Builder:build_file(node)
local icon = self:_build_file_icon(node)
local hl 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" hl = "NvimTreeSpecialFile"
elseif node.executable then elseif node.executable then
hl = "NvimTreeExecFile" hl = "NvimTreeExecFile"
elseif self.picture_map[node.extension] then elseif M.picture_map[node.extension] then
hl = "NvimTreeImageFile" hl = "NvimTreeImageFile"
end end
return icon, { str = node.name, hl = { hl } } local icon, hl_group = icons.get_file_icon(node.name, node.extension)
end return { str = icon, hl = { hl_group } }, { str = node.name, hl = { hl } }
---@param node table
---@return HighlightedString[]|nil icon
function Builder:_get_git_icons(node)
local git_icons = git.get_icons(node)
if git_icons and #git_icons > 0 and self.git_placement == "signcolumn" then
table.insert(self.signs, {
sign = git_icons[1].hl[1],
lnum = self.index + 1,
priority = 1,
})
git_icons = nil
end
return git_icons
end
---@param node table
---@return HighlightedString[]|nil icon
function Builder:_get_diagnostics_icon(node)
local diagnostics_icon = diagnostics.get_icon(node)
if diagnostics_icon and self.diagnostics_placement == "signcolumn" then
table.insert(self.signs, {
sign = diagnostics_icon.hl[1],
lnum = self.index + 1,
priority = 2,
})
diagnostics_icon = nil
end
return diagnostics_icon
end
---@param node table
---@return HighlightedString|nil icon
function Builder:_get_modified_icon(node)
local modified_icon = modified.get_icon(node)
if modified_icon and self.modified_placement == "signcolumn" then
table.insert(self.signs, {
sign = modified_icon.hl[1],
lnum = self.index + 1,
priority = 3,
})
modified_icon = nil
end
return modified_icon
end
---@param node table
---@return HighlightedString[]|nil icon
function Builder:_get_bookmark_icon(node)
local bookmark_icon = bookmarks.get_icon(node)
if bookmark_icon and self.bookmarks_placement == "signcolumn" then
table.insert(self.signs, {
sign = bookmark_icon.hl[1],
lnum = self.index + 1,
priority = 4,
})
bookmark_icon = nil
end
return bookmark_icon
end
---@param node table
---@return string|nil icon_hl
---@return string|nil name_hl
function Builder:_get_highlight_override(node, unloaded_bufnr)
local name_hl, icon_hl
-- git
local git_highlight = git.get_highlight(node)
if git_highlight then
name_hl = git_highlight
end
-- opened file
if self.highlight_opened_files and vim.fn.bufloaded(node.absolute_path) > 0 and vim.fn.bufnr(node.absolute_path) ~= unloaded_bufnr then
if self.highlight_opened_files == "all" or self.highlight_opened_files == "name" then
name_hl = "NvimTreeOpenedFile"
end
if self.highlight_opened_files == "all" or self.highlight_opened_files == "icon" then
icon_hl = "NvimTreeOpenedFileIcon"
end
end
-- modified file
local modified_highlight = modified.get_highlight(node)
if modified_highlight then
if self.highlight_modified == "all" or self.highlight_modified == "name" then
name_hl = modified_highlight
end
if self.highlight_modified == "all" or self.highlight_modified == "icon" then
icon_hl = modified_highlight
end
end
return icon_hl, name_hl
end
---Append optional highlighting to icon or name.
---@param node table
---@param get_hl fun(node: table): HL_POSITION, string
---@param icon_hl string[] icons to append to
---@param name_hl string[] names to append to
function Builder:_append_highlight(node, get_hl, icon_hl, name_hl)
local pos, hl = get_hl(node)
if pos ~= HL_POSITION.none and hl then
if pos == HL_POSITION.all or pos == HL_POSITION.icon then
table.insert(icon_hl, hl)
end
if pos == HL_POSITION.all or pos == HL_POSITION.name then
table.insert(name_hl, hl)
end
end
end end
---@private
---@param indent_markers HighlightedString[] ---@param indent_markers HighlightedString[]
---@param arrows HighlightedString[]|nil ---@param arrows HighlightedString[]|nil
---@param icon HighlightedString ---@param icon HighlightedString
---@param name HighlightedString ---@param name HighlightedString
---@param git_icons HighlightedString[]|nil ---@param node table
---@param diagnostics_icon HighlightedString|nil
---@param modified_icon HighlightedString|nil
---@param bookmark_icon HighlightedString|nil
---@return HighlightedString[] ---@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 added_len = 0
local function add_to_end(t1, t2) local function add_to_end(t1, t2)
if not t2 then
return
end
for _, v in ipairs(t2) do for _, v in ipairs(t2) do
if added_len > 0 then if added_len > 0 then
table.insert(t1, { str = self.icon_padding }) table.insert(t1, { str = M.opts.renderer.icons.padding })
end end
table.insert(t1, v) table.insert(t1, v)
end end
@ -379,78 +219,132 @@ function Builder:_format_line(indent_markers, arrows, icon, name, git_icons, dia
local line = { indent_markers, arrows } local line = { indent_markers, arrows }
add_to_end(line, { icon }) add_to_end(line, { icon })
if git_icons and self.git_placement == "before" then
add_to_end(line, git_icons) for i = #M.decorators, 1, -1 do
end add_to_end(line, M.decorators[i]:icons_before(node))
if modified_icon and self.modified_placement == "before" then
add_to_end(line, { modified_icon })
end
if diagnostics_icon and self.diagnostics_placement == "before" then
add_to_end(line, { diagnostics_icon })
end
if bookmark_icon and self.bookmarks_placement == "before" then
add_to_end(line, { bookmark_icon })
end end
add_to_end(line, { name }) add_to_end(line, { name })
if git_icons and self.git_placement == "after" then for i = #M.decorators, 1, -1 do
add_to_end(line, git_icons) add_to_end(line, M.decorators[i]:icons_after(node))
end
if modified_icon and self.modified_placement == "after" then
add_to_end(line, { modified_icon })
end
if diagnostics_icon and self.diagnostics_placement == "after" then
add_to_end(line, { diagnostics_icon })
end
if bookmark_icon and self.bookmarks_placement == "after" then
add_to_end(line, { bookmark_icon })
end end
log.line("dev", "line = %s", vim.inspect(line))
return line return line
end end
function Builder:_build_line(node, idx, num_children, unloaded_bufnr) ---@private
local copy_paste = require "nvim-tree.actions.fs.copy-paste" ---@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
---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
-- 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
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
-- 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
-- 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 icon_hl_group, name_hl_group
end
---@private
function Builder:build_line(node, idx, num_children)
-- various components -- various components
local indent_markers = pad.get_indent_markers(self.depth, idx, num_children, node, self.markers) local indent_markers = pad.get_indent_markers(self.depth, idx, num_children, node, self.markers)
local arrows = pad.get_arrows(node) 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 -- main components
local is_folder = node.nodes ~= nil local is_folder = node.nodes ~= nil
local is_symlink = node.link_to ~= nil local is_symlink = node.link_to ~= nil
local icon, name local icon, name
if is_folder then if is_folder then
icon, name = self:_build_folder(node) icon, name = self:build_folder(node)
elseif is_symlink then elseif is_symlink then
icon, name = self:_build_symlink(node) icon, name = self:build_symlink(node)
else else
icon, name = self:_build_file(node) icon, name = self:build_file(node)
end end
-- highlight override -- highighting
local icon_hl_override, name_hl_override = self:_get_highlight_override(node, unloaded_bufnr) local icon_hl_group, name_hl_group = self:add_highlights(node)
if icon_hl_override then table.insert(icon.hl, icon_hl_group)
icon.hl = { icon_hl_override } table.insert(name.hl, name_hl_group)
end
if name_hl_override then
name.hl = { name_hl_override }
end
-- extra highighting local line = self:format_line(indent_markers, arrows, icon, name, node)
self:_append_highlight(node, bookmarks.get_highlight, icon.hl, name.hl) table.insert(self.lines, self:unwrap_highlighted_strings(line))
self:_append_highlight(node, diagnostics.get_highlight, icon.hl, name.hl)
self:_append_highlight(node, copy_paste.get_highlight, icon.hl, name.hl)
local line = self:_format_line(indent_markers, arrows, icon, name, git_icons, diagnostics_icon, modified_icon, bookmark_icon)
self:_insert_line(self:_unwrap_highlighted_strings(line))
self.index = self.index + 1 self.index = self.index + 1
@ -458,13 +352,14 @@ function Builder:_build_line(node, idx, num_children, unloaded_bufnr)
if node.open then if node.open then
self.depth = self.depth + 1 self.depth = self.depth + 1
self:build(node, unloaded_bufnr) self:build_lines(node)
self.depth = self.depth - 1 self.depth = self.depth - 1
end end
end end
function Builder:_get_nodes_number(nodes) ---@private
if not self.filter then function Builder:get_nodes_number(nodes)
if not live_filter.filter then
return #nodes return #nodes
end end
@ -477,53 +372,77 @@ function Builder:_get_nodes_number(nodes)
return i return i
end end
function Builder:build(tree, unloaded_bufnr) ---@private
local num_children = self:_get_nodes_number(tree.nodes) 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 local idx = 1
for _, node in ipairs(tree.nodes) do for _, n in ipairs(node.nodes) do
if not node.hidden then if not n.hidden then
self:_build_line(node, idx, num_children, unloaded_bufnr) self:build_signs(n)
self:build_line(n, idx, num_children)
idx = idx + 1 idx = idx + 1
end end
end end
return self
end 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 if type(root_label) == "function" then
local label = root_label(root_cwd) local label = root_label(self.root_cwd)
if type(label) == "string" then if type(label) == "string" then
return label return label
else else
root_label = DEFAULT_ROOT_FOLDER_LABEL return "???"
end end
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 end
function Builder:build_header(show_header) ---@private
if show_header then function Builder:build_header()
local root_name = format_root_name(self.root_cwd, self.root_folder_label) if view.is_root_folder_visible(core.get_cwd()) then
self:_insert_line(root_name) local root_name = self:format_root_name(M.opts.renderer.root_folder_label)
self:_insert_highlight({ "NvimTreeRootFolder" }, 0, string.len(root_name)) table.insert(self.lines, root_name)
self:insert_highlight({ "NvimTreeRootFolder" }, 0, string.len(root_name))
self.index = 1 self.index = 1
end end
if self.filter then if live_filter.filter then
local filter_line = self.filter_prefix .. "/" .. self.filter .. "/" local filter_line = string.format("%s/%s/", M.opts.live_filter.prefix, live_filter.filter)
self:_insert_line(filter_line) table.insert(self.lines, filter_line)
local prefix_length = string.len(self.filter_prefix) local prefix_length = string.len(M.opts.live_filter.prefix)
self:_insert_highlight({ "NvimTreeLiveFilterPrefix" }, 0, prefix_length) self:insert_highlight({ "NvimTreeLiveFilterPrefix" }, 0, prefix_length)
self:_insert_highlight({ "NvimTreeLiveFilterValue" }, prefix_length, string.len(filter_line)) self:insert_highlight({ "NvimTreeLiveFilterValue" }, prefix_length, string.len(filter_line))
self.index = self.index + 1 self.index = self.index + 1
end end
end
---Build all lines with highlights and signs
---@return Builder
function Builder:build()
self:build_header()
self:build_lines()
return self return self
end end
function Builder:unwrap() function Builder.setup(opts)
return self.lines, self.highlights, self.signs 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 end
return Builder return Builder

View File

@ -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

View File

@ -2,18 +2,23 @@ local HL_POSITION = require("nvim-tree.enum").HL_POSITION
local diagnostics = require "nvim-tree.diagnostics" local diagnostics = require "nvim-tree.diagnostics"
local M = { local M = {
HS_FILE = {}, -- highlight strings for the icons
HS_FOLDER = {}, HS_ICON = {},
ICON = {},
hl_pos = HL_POSITION.none, -- 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 ---@param node table
---@return HL_POSITION position none when no status ---@return HL_POSITION position none when no status
---@return string|nil group only when status ---@return string|nil group only when status
function M.get_highlight(node) 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 return HL_POSITION.none, nil
end end
@ -26,7 +31,7 @@ function M.get_highlight(node)
end end
if group then if group then
return M.hl_pos, group return M.HL_POS, group
else else
return HL_POSITION.none, nil return HL_POSITION.none, nil
end end
@ -49,40 +54,38 @@ function M.setup(opts)
} }
if opts.diagnostics.enable and opts.renderer.highlight_diagnostics then 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[opts.renderer.highlight_diagnostics]
M.hl_pos = HL_POSITION.name
end end
M.HS_FILE[vim.diagnostic.severity.ERROR] = "NvimTreeLspDiagnosticsErrorText" M.HG_FILE[vim.diagnostic.severity.ERROR] = "NvimTreeDiagnosticErrorFileHL"
M.HS_FILE[vim.diagnostic.severity.WARN] = "NvimTreeLspDiagnosticsWarningText" M.HG_FILE[vim.diagnostic.severity.WARN] = "NvimTreeDiagnosticWarningFileHL"
M.HS_FILE[vim.diagnostic.severity.INFO] = "NvimTreeLspDiagnosticsInfoText" M.HG_FILE[vim.diagnostic.severity.INFO] = "NvimTreeDiagnosticInfoFileHL"
M.HS_FILE[vim.diagnostic.severity.HINT] = "NvimTreeLspDiagnosticsHintText" M.HG_FILE[vim.diagnostic.severity.HINT] = "NvimTreeDiagnosticHintFileHL"
M.HS_FOLDER[vim.diagnostic.severity.ERROR] = "NvimTreeLspDiagnosticsErrorFolderText" M.HG_FOLDER[vim.diagnostic.severity.ERROR] = "NvimTreeDiagnosticErrorFolderHL"
M.HS_FOLDER[vim.diagnostic.severity.WARN] = "NvimTreeLspDiagnosticsWarningFolderText" M.HG_FOLDER[vim.diagnostic.severity.WARN] = "NvimTreeDiagnosticWarningFolderHL"
M.HS_FOLDER[vim.diagnostic.severity.INFO] = "NvimTreeLspDiagnosticsInfoFolderText" M.HG_FOLDER[vim.diagnostic.severity.INFO] = "NvimTreeDiagnosticInfoFolderHL"
M.HS_FOLDER[vim.diagnostic.severity.HINT] = "NvimTreeLspDiagnosticsHintFolderText" 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, 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, 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, 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, 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] }) vim.fn.sign_define(i.hl[1], { text = i.str, texthl = i.hl[1] })
end end
end end

View File

@ -1,3 +1,5 @@
local appearance = require "nvim-tree.appearance"
local M = {} local M = {}
local utils = require "nvim-tree.utils" local utils = require "nvim-tree.utils"
@ -66,13 +68,12 @@ local function show()
style = "minimal", style = "minimal",
}) })
local ns_id = vim.api.nvim_get_namespaces()["NvimTreeHighlights"] local extmarks = vim.api.nvim_buf_get_extmarks(0, appearance.NS_ID, { line_nr - 1, 0 }, { line_nr - 1, -1 }, { details = 1 })
local extmarks = vim.api.nvim_buf_get_extmarks(0, ns_id, { line_nr - 1, 0 }, { line_nr - 1, -1 }, { details = 1 })
vim.api.nvim_win_call(M.popup_win, function() vim.api.nvim_win_call(M.popup_win, function()
vim.api.nvim_buf_set_lines(0, 0, -1, true, { line }) vim.api.nvim_buf_set_lines(0, 0, -1, true, { line })
for _, extmark in ipairs(extmarks) do for _, extmark in ipairs(extmarks) do
local hl = extmark[4] 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 end
vim.cmd [[ setlocal nowrap cursorline noswapfile nobuflisted buftype=nofile bufhidden=hide ]] vim.cmd [[ setlocal nowrap cursorline noswapfile nobuflisted buftype=nofile bufhidden=hide ]]
end) end)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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<string, string> by porcelain status e.g. "AM"
---@field folder_hl table<string, string> by porcelain status
---@field icons_by_status HighlightedStringGit[] by human status
---@field icons_by_xy table<string, HighlightedStringGit[]> 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<string, string> 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,34 +1,32 @@
local appearance = require "nvim-tree.appearance"
local core = require "nvim-tree.core" local core = require "nvim-tree.core"
local log = require "nvim-tree.log" local log = require "nvim-tree.log"
local view = require "nvim-tree.view" local view = require "nvim-tree.view"
local events = require "nvim-tree.events" local events = require "nvim-tree.events"
local modified = require "nvim-tree.renderer.components.modified"
local _padding = require "nvim-tree.renderer.components.padding" local _padding = require "nvim-tree.renderer.components.padding"
local icon_component = require "nvim-tree.renderer.components.icons" local icon_component = require "nvim-tree.renderer.components.icons"
local full_name = require "nvim-tree.renderer.components.full-name" 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 Builder = require "nvim-tree.renderer.builder"
local live_filter = require "nvim-tree.live-filter"
local bookmarks = require "nvim-tree.renderer.components.bookmarks"
local M = { local M = {
last_highlights = {}, last_hl_args = {},
} }
local SIGN_GROUP = "NvimTreeRendererSigns" local SIGN_GROUP = "NvimTreeRendererSigns"
local namespace_id = vim.api.nvim_create_namespace "NvimTreeHighlights" ---@param bufnr number
---@param lines string[]
local function _draw(bufnr, lines, hl, signs) ---@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_option(bufnr, "modifiable", true)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) 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.api.nvim_buf_set_option(bufnr, "modifiable", false)
vim.fn.sign_unplace(SIGN_GROUP) vim.fn.sign_unplace(SIGN_GROUP)
for _, sign in pairs(signs) do for i, sign_name in pairs(signs) do
vim.fn.sign_place(0, SIGN_GROUP, sign.sign, bufnr, { lnum = sign.lnum, priority = sign.priority }) vim.fn.sign_place(0, SIGN_GROUP, sign_name, bufnr, { lnum = i + 1 })
end end
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 if not bufnr or not vim.api.nvim_buf_is_loaded(bufnr) then
return return
end end
vim.api.nvim_buf_clear_namespace(bufnr, namespace_id, 0, -1) vim.api.nvim_buf_clear_namespace(bufnr, appearance.NS_ID, 0, -1)
for _, data in ipairs(hl or M.last_highlights) do for _, data in ipairs(hl or M.last_hl_args) do
if type(data[1]) == "table" then if type(data[1]) == "table" then
for _, group in ipairs(data[1]) do 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
end end
end end
local picture_map = { function M.draw()
jpg = true,
jpeg = true,
png = true,
gif = true,
webp = true,
jxl = true,
}
function M.draw(unloaded_bufnr)
local bufnr = view.get_bufnr() local bufnr = view.get_bufnr()
if not core.get_explorer() or not bufnr or not vim.api.nvim_buf_is_loaded(bufnr) then if not core.get_explorer() or not bufnr or not vim.api.nvim_buf_is_loaded(bufnr) then
return return
@ -66,30 +55,13 @@ function M.draw(unloaded_bufnr)
local cursor = vim.api.nvim_win_get_cursor(view.get_winnr()) local cursor = vim.api.nvim_win_get_cursor(view.get_winnr())
icon_component.reset_config() icon_component.reset_config()
local lines, hl, signs = Builder.new(core.get_cwd()) local builder = Builder:new():build()
: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) _draw(bufnr, builder.lines, builder.hl_args, builder.signs)
M.last_highlights = hl M.last_hl_args = builder.hl_args
if cursor and #lines >= cursor[1] then if cursor and #builder.lines >= cursor[1] then
vim.api.nvim_win_set_cursor(view.get_winnr(), cursor) vim.api.nvim_win_set_cursor(view.get_winnr(), cursor)
end end
@ -102,15 +74,12 @@ end
function M.setup(opts) function M.setup(opts)
M.config = opts.renderer M.config = opts.renderer
M.config.modified = opts.modified
_padding.setup(opts) _padding.setup(opts)
full_name.setup(opts) full_name.setup(opts)
git.setup(opts)
modified.setup(opts)
diagnostics.setup(opts)
bookmarks.setup(opts)
icon_component.setup(opts) icon_component.setup(opts)
Builder.setup(opts)
end end
return M return M

View File

@ -1,3 +1,4 @@
local appearance = require "nvim-tree.appearance"
local events = require "nvim-tree.events" local events = require "nvim-tree.events"
local utils = require "nvim-tree.utils" local utils = require "nvim-tree.utils"
local log = require "nvim-tree.log" local log = require "nvim-tree.log"
@ -38,19 +39,6 @@ M.View = {
cursorlineopt = "both", cursorlineopt = "both",
colorcolumn = "0", colorcolumn = "0",
wrap = false, 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 vim.opt_local[k] = v
end end
vim.opt.eventignore = eventignore 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 end
---@return table ---@return table
@ -539,13 +530,6 @@ function M.is_root_folder_visible(cwd)
return cwd ~= "/" and not M.View.hide_root_folder return cwd ~= "/" and not M.View.hide_root_folder
end 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) function M.setup(opts)
local options = opts.view or {} local options = opts.view or {}
M.View.centralize_selection = options.centralize_selection M.View.centralize_selection = options.centralize_selection