Compare commits

..

14 Commits

Author SHA1 Message Date
github-actions[bot]
d529a99f88 chore(master): release nvim-tree 1.10.0 (#3021)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-01-13 15:41:35 +11:00
phanium
39bc630816 fix: hijack directory "BufEnter", "BufNewFile" events are nested (#3044)
Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-01-13 15:39:10 +11:00
Lev Yuvenskiy
aae01853dd fix(#3041): use vim.diagnostic.get for updating diagnostics (#3042)
* fix(#3041): use vim.diagnostic.get for updating diagnostics

* fix(#3041): remove unnecessary @type

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-01-13 15:15:48 +11:00
fdgdgerg
68fc4c20f5 feat(api): add node.open.vertical_no_picker, node.open.horizontal_no_picker (#3031)
* test

* add splits with no window pickers

removed the 1 buffer per file limitation

test

test2

* no-picker for splits

* help vertical/horizontal_no_picker

* revert whitespace changes

---------

Co-authored-by: JoeDaBu <joegbu@gmail.com>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-12-22 09:35:48 +11:00
Šimon Mandlík
f7b76cd1a7 fix(#3015): dynamic width no longer truncates on right_align icons (#3022) 2024-12-14 08:54:18 +11:00
Alexander Courtis
c3d9b1779f docs: notify users with a fs.inotify.max_user_watches message on EMFILE event (#3028)
* docs: notify users with a fs.inotify.max_user_watches message on EMFILE event

* type safety for newly created vim.v.event type
2024-12-13 10:39:46 +11:00
ShyRobin
db8d7ac1f5 fix(#3018): error when focusing nvim-tree when in terminal mode (#3019)
fix: Can't re-enter normal mode from terminal mode

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-12-08 12:05:33 +11:00
Jie Liu
6b4be1dc0c fix: view.width functions may return strings (#3020)
* Fix get_size() function when size is a function return string

* update view.width help

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-12-08 11:45:32 +11:00
github-actions[bot]
375e38673b chore(master): release nvim-tree 1.9.0 (#2999)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-12-07 16:04:32 +11:00
Alexander Courtis
7a4ff1a516 feat(#2948): add custom decorators, :help nvim-tree-decorators (#2996)
* feat(#2948): add UserDecorator, proof of concept

* feat(#2948): add UserDecorator, proof of concept

* feat(#2948): add UserDecorator, proof of concept

* feat(#2948): add UserDecorator

* feat(#2948): add UserDecorator

* feat(#2948): add UserDecorator

* feat(#2948): add Decorator node icon override

* feat(#2948): add nvim_tree.api.* node classes

* feat(#2948): extract _meta following nvim pattern

* feat(#2948): extract _meta following nvim pattern

* feat(#2948): add decorator registry and order

* feat(#2948): add decorator registry and order

* feat(#2948): tidy

* feat(#2948): document API

* feat(#2948): document API

* feat(#2948): document API

* feat(#2948): pass api nodes to user decorators

* feat(#2948): document API

* feat(#2948): use renderer.decorators to define order and register

* feat(#2948): tidy decorator args and complete documentation

* feat(#2948): decorator classes specified by prefix rather than suffix

* feat(#2948): improve doc

* feat(#2948): improve doc

* feat(#2948): improve doc

* feat(#2948): additional user decorator safety

* feat(#2948): create nvim_tree.api.decorator.UserDecorator class in API, add :extend

* feat(#2948): improve doc
2024-12-07 16:03:29 +11:00
Alexander Courtis
ca7c4c33ca fix(#3009): nvim < 0.10 apply view options locally (#3010) 2024-11-24 17:00:58 +11:00
Alexander Courtis
1f3ffd6af1 fix(#2954): more efficient LSP updates, increase diagnostics.debounce_delay from 50ms to 500ms (#3007)
* fix(#2954): use LSP diagnostic data deltas from events instead of a full query

* fix(#2954): use LSP diagnostic data deltas from events instead of a full query
2024-11-22 10:12:47 +11:00
devxpain
f7c65e11d6 fix(api): correct argument types in wrap_node and wrap_node_or_nil (#3006)
The `wrap_node` and `wrap_node_or_nil` functions now correctly accept `Node?` to handle nil values, resolving a warning about incorrect argument counts in `api.tree.change_root_to_node()`.
2024-11-18 10:00:19 +11:00
des-b
28eac2801b fix(#2990): Do not check if buffer is buflisted in diagnostics.update() (#2998)
This ensures that LSP diagnostics of files which are not manually opened
by users are rendered by nvim-tree diagnostic indicators.

However when users attach an LSP to nvim-tree this will bring back
flashing as attempted to fix in #2980. Fixing this should probably done
by checking data passed via diagnostic events (DiagnosticChanged and
CocDiagnosticChanged).

Signed-off-by: des-b <66919647+des-b@users.noreply.github.com>
2024-11-11 08:57:06 +11:00
32 changed files with 731 additions and 390 deletions

View File

@@ -1,3 +1,3 @@
{ {
".": "1.8.0" ".": "1.10.0"
} }

View File

@@ -1,5 +1,37 @@
# Changelog # Changelog
## [1.10.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.9.0...nvim-tree-v1.10.0) (2025-01-13)
### Features
* **api:** add node.open.vertical_no_picker, node.open.horizontal_no_picker ([#3031](https://github.com/nvim-tree/nvim-tree.lua/issues/3031)) ([68fc4c2](https://github.com/nvim-tree/nvim-tree.lua/commit/68fc4c20f5803444277022c681785c5edd11916d))
### Bug Fixes
* **#3015:** dynamic width no longer truncates on right_align icons ([#3022](https://github.com/nvim-tree/nvim-tree.lua/issues/3022)) ([f7b76cd](https://github.com/nvim-tree/nvim-tree.lua/commit/f7b76cd1a75615c8d6254fc58bedd2a7304eb7d8))
* **#3018:** error when focusing nvim-tree when in terminal mode ([#3019](https://github.com/nvim-tree/nvim-tree.lua/issues/3019)) ([db8d7ac](https://github.com/nvim-tree/nvim-tree.lua/commit/db8d7ac1f524fc6f808764b29fa695c51e014aa6))
* **#3041:** use vim.diagnostic.get for updating diagnostics ([#3042](https://github.com/nvim-tree/nvim-tree.lua/issues/3042)) ([aae0185](https://github.com/nvim-tree/nvim-tree.lua/commit/aae01853ddbd790d1efd6ff04ff96cf38c02c95f))
* Can't re-enter normal mode from terminal mode ([db8d7ac](https://github.com/nvim-tree/nvim-tree.lua/commit/db8d7ac1f524fc6f808764b29fa695c51e014aa6))
* hijack directory "BufEnter", "BufNewFile" events are nested ([#3044](https://github.com/nvim-tree/nvim-tree.lua/issues/3044)) ([39bc630](https://github.com/nvim-tree/nvim-tree.lua/commit/39bc63081605c1d4b974131ebecaea11e8a8595f))
* view.width functions may return strings ([#3020](https://github.com/nvim-tree/nvim-tree.lua/issues/3020)) ([6b4be1d](https://github.com/nvim-tree/nvim-tree.lua/commit/6b4be1dc0cd4d5d5b8e8b56b510a75016e99746f))
## [1.9.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.8.0...nvim-tree-v1.9.0) (2024-12-07)
### Features
* **#2948:** add custom decorators, :help nvim-tree-decorators ([#2996](https://github.com/nvim-tree/nvim-tree.lua/issues/2996)) ([7a4ff1a](https://github.com/nvim-tree/nvim-tree.lua/commit/7a4ff1a516fe92a5ed6b79d7ce31ea4d8f341a72))
### Bug Fixes
* **#2954:** more efficient LSP updates, increase diagnostics.debounce_delay from 50ms to 500ms ([#3007](https://github.com/nvim-tree/nvim-tree.lua/issues/3007)) ([1f3ffd6](https://github.com/nvim-tree/nvim-tree.lua/commit/1f3ffd6af145af2a4930a61c50f763264922c3fe))
* **#2990:** Do not check if buffer is buflisted in diagnostics.update() ([#2998](https://github.com/nvim-tree/nvim-tree.lua/issues/2998)) ([28eac28](https://github.com/nvim-tree/nvim-tree.lua/commit/28eac2801b201f301449e976d7a9e8cfde053ba3))
* **#3009:** nvim &lt; 0.10 apply view options locally ([#3010](https://github.com/nvim-tree/nvim-tree.lua/issues/3010)) ([ca7c4c3](https://github.com/nvim-tree/nvim-tree.lua/commit/ca7c4c33cac2ad66ec69d45e465379716ef0cc97))
* **api:** correct argument types in `wrap_node` and `wrap_node_or_nil` ([#3006](https://github.com/nvim-tree/nvim-tree.lua/issues/3006)) ([f7c65e1](https://github.com/nvim-tree/nvim-tree.lua/commit/f7c65e11d695a084ca10b93df659bb7e68b71f9f))
## [1.8.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.7.1...nvim-tree-v1.8.0) (2024-11-09) ## [1.8.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.7.1...nvim-tree-v1.8.0) (2024-11-09)

View File

@@ -13,7 +13,7 @@ check: luals
# subtasks # subtasks
# #
luacheck: luacheck:
luacheck -q lua luacheck --codes --quiet lua --exclude-files "**/_meta/**"
# --diagnosis-as-error does not function for workspace, hence we post-process the output # --diagnosis-as-error does not function for workspace, hence we post-process the output
style-check: style-check:

View File

@@ -52,14 +52,16 @@ CONTENTS *nvim-tree*
8.2 Highlight: Overhaul |nvim-tree-highlight-overhaul| 8.2 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. Decorators |nvim-tree-decorators|
12. Netrw |nvim-tree-netrw| 11.1 Decorator Example |nvim-tree-decorator-example|
13. Legacy |nvim-tree-legacy| 12. OS Specific Restrictions |nvim-tree-os-specific|
13.1 Legacy: Opts |nvim-tree-legacy-opts| 13. Netrw |nvim-tree-netrw|
13.2 Legacy: Highlight |nvim-tree-legacy-highlight| 14. Legacy |nvim-tree-legacy|
14. Index |nvim-tree-index| 14.1 Legacy: Opts |nvim-tree-legacy-opts|
14.1 Index: Opts |nvim-tree-index-opts| 14.2 Legacy: Highlight |nvim-tree-legacy-highlight|
14.2 Index: API |nvim-tree-index-api| 15. Index |nvim-tree-index|
15.1 Index: Opts |nvim-tree-index-opts|
15.2 Index: API |nvim-tree-index-api|
============================================================================== ==============================================================================
1. INTRODUCTION *nvim-tree-introduction* 1. INTRODUCTION *nvim-tree-introduction*
@@ -425,6 +427,7 @@ Following is the default configuration. See |nvim-tree-opts| for details. >lua
special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" }, special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" },
hidden_display = "none", hidden_display = "none",
symlink_destination = true, symlink_destination = true,
decorators = { "Git", "Open", "Hidden", "Modified", "Bookmark", "Diagnostics", "Copied", "Cut", },
highlight_git = "none", highlight_git = "none",
highlight_diagnostics = "none", highlight_diagnostics = "none",
highlight_opened_files = "none", highlight_opened_files = "none",
@@ -527,7 +530,7 @@ Following is the default configuration. See |nvim-tree-opts| for details. >lua
enable = false, enable = false,
show_on_dirs = false, show_on_dirs = false,
show_on_open_dirs = true, show_on_open_dirs = true,
debounce_delay = 50, debounce_delay = 500,
severity = { severity = {
min = vim.diagnostic.severity.HINT, min = vim.diagnostic.severity.HINT,
max = vim.diagnostic.severity.ERROR, max = vim.diagnostic.severity.ERROR,
@@ -797,22 +800,22 @@ Width of the window: can be a `%` string, a number representing columns, a
function or a table. function or a table.
A table indicates that the view should be dynamically sized based on the A table indicates that the view should be dynamically sized based on the
longest line. longest line.
Type: `string | number | table | function()` returning a number Type: `string | number | table | fun(): number|string`
Default: `30` Default: `30`
*nvim-tree.view.width.min* *nvim-tree.view.width.min*
Minimum dynamic width. Minimum dynamic width.
Type: `string | number | function()` returning a number Type: `string | number | fun(): number|string`
Default: `30` Default: `30`
*nvim-tree.view.width.max* *nvim-tree.view.width.max*
Maximum dynamic width, -1 for unbounded. Maximum dynamic width, -1 for unbounded.
Type: `string | number | function()` returning a number Type: `string | number | fun(): number|string`
Default: `-1` Default: `-1`
*nvim-tree.view.width.padding* *nvim-tree.view.width.padding*
Extra padding to the right. Extra padding to the right.
Type: `number | function()` returning a number Type: `number | fun(): number|string`
Default: `1` Default: `1`
*nvim-tree.view.float* *nvim-tree.view.float*
@@ -842,9 +845,6 @@ 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, additive:
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.
Type: `boolean`, Default: `false` Type: `boolean`, Default: `false`
@@ -927,6 +927,22 @@ Show a summary of hidden files below the tree using `NvimTreeHiddenDisplay
Whether to show the destination of the symlink. Whether to show the destination of the symlink.
Type: `boolean`, Default: `true` Type: `boolean`, Default: `true`
*nvim-tree.renderer.decorators*
Highlighting and icons for the nodes, in increasing order of precedence.
Uses strings to specify builtin decorators otherwise specify your
`nvim_tree.api.decorator.UserDecorator` class.
Type: `nvim_tree.api.decorator.Name[]`, Default: >lua
{
"Git",
"Open",
"Hidden",
"Modified",
"Bookmark",
"Diagnostics",
"Copied",
"Cut",
}
<
*nvim-tree.renderer.highlight_git* *nvim-tree.renderer.highlight_git*
Enable highlight for git attributes using `NvimTreeGit*HL` highlight groups. Enable highlight for git attributes using `NvimTreeGit*HL` highlight groups.
Requires |nvim-tree.git.enable| Requires |nvim-tree.git.enable|
@@ -996,9 +1012,6 @@ Configuration options for tree indent markers.
*nvim-tree.renderer.icons* *nvim-tree.renderer.icons*
Configuration options for icons. Configuration options for icons.
Icon order and sign column precedence:
git < hidden < modified < bookmarked < diagnostics
`renderer.icons.*_placement` options may be: `renderer.icons.*_placement` options may be:
- `"before"` : before file/folder, after the file/folders icons - `"before"` : before file/folder, after the file/folders icons
- `"after"` : after file/folder - `"after"` : after file/folder
@@ -1272,7 +1285,7 @@ Enable/disable the feature.
*nvim-tree.diagnostics.debounce_delay* *nvim-tree.diagnostics.debounce_delay*
Idle milliseconds between diagnostic event and update. Idle milliseconds between diagnostic event and update.
Type: `number`, Default: `50` (ms) Type: `number`, Default: `500` (ms)
*nvim-tree.diagnostics.show_on_dirs* *nvim-tree.diagnostics.show_on_dirs*
Show diagnostic icons on parent directories. Show diagnostic icons on parent directories.
@@ -2012,9 +2025,19 @@ node.open.no_window_picker({node})
node.open.vertical({node}) *nvim-tree-api.node.open.vertical()* node.open.vertical({node}) *nvim-tree-api.node.open.vertical()*
|nvim-tree-api.node.edit()|, file will be opened in a new vertical split. |nvim-tree-api.node.edit()|, file will be opened in a new vertical split.
*nvim-tree-api.node.open.vertical_no_picker()*
node.open.vertical_no_picker({node})
|nvim-tree-api.node.vertical()|, window picker will never be used as per
|nvim-tree.actions.open_file.window_picker.enable| `false`
node.open.horizontal({node}) *nvim-tree-api.node.open.horizontal()* node.open.horizontal({node}) *nvim-tree-api.node.open.horizontal()*
|nvim-tree-api.node.edit()|, file will be opened in a new horizontal split. |nvim-tree-api.node.edit()|, file will be opened in a new horizontal split.
*nvim-tree-api.node.open.horizontal_no_picker()*
node.open.horizontal_no_picker({node})
|nvim-tree-api.node.horizontal()|, window picker will never be used as per
|nvim-tree.actions.open_file.window_picker.enable| `false`
*nvim-tree-api.node.open.toggle_group_empty()* *nvim-tree-api.node.open.toggle_group_empty()*
node.open.toggle_group_empty({node}) node.open.toggle_group_empty({node})
Toggle |nvim-tree.renderer.group_empty| for a specific folder. Toggle |nvim-tree.renderer.group_empty| for a specific folder.
@@ -2755,7 +2778,90 @@ configurations for different types of prompts.
send all bookmarked to trash during |nvim-tree-api.marks.bulk.trash()| send all bookmarked to trash during |nvim-tree-api.marks.bulk.trash()|
============================================================================== ==============================================================================
11. OS SPECIFIC RESTRICTIONS *nvim-tree-os-specific* 11. DECORATORS *nvim-tree-decorators*
Highlighting and icons for nodes are provided by Decorators. You may provide
your own in addition to the builtin decorators.
Decorators may:
- Add icons
- Set highlight group for the name or icons
- Override node icon
Specify decorators and their precedence via |nvim-tree.renderer.decorators|
e.g. defaults with a user decorator class being overridden only by Cut: >lua
{
"Git",
"Open",
"Hidden",
"Modified",
"Bookmark",
"Diagnostics",
"Copied",
MyDecorator,
"Cut",
}
See `nvim-tree/_meta/api_decorator.lua` for full
`nvim_tree.api.decorator.UserDecorator` class documentation.
<
==============================================================================
11.1. DECORATOR EXAMPLE *nvim-tree-decorator-example*
>lua
---Create your decorator class
---@class (exact) MyDecorator: nvim_tree.api.decorator.UserDecorator
---@field private my_icon nvim_tree.api.HighlightedString
local MyDecorator = require("nvim-tree.api").decorator.UserDecorator:extend()
---Mandatory constructor :new() will be called once per tree render, with no arguments.
function MyDecorator:new()
self.enabled = true
self.highlight_range = "all"
self.icon_placement = "signcolumn"
-- create your icon once, for convenience
self.my_icon = { str = "I", hl = { "MyIcon" } }
-- Define the icon sign only once
-- Only needed if you are using icon_placement = "signcolumn"
self:define_sign(self.my_icon)
end
---Override node icon
---@param node nvim_tree.api.Node
---@return nvim_tree.api.HighlightedString? icon_node
function MyDecorator:icon_node(node)
if node.name == "example" then
return self.my_icon
else
return nil
end
end
---Return one icon for DecoratorIconPlacement
---@param node nvim_tree.api.Node
---@return nvim_tree.api.HighlightedString[]? icons
function MyDecorator:icons(node)
if node.name == "example" then
return { self.my_icon }
else
return nil
end
end
---Exactly one highlight group for DecoratorHighlightRange
---@param node nvim_tree.api.Node
---@return string? highlight_group
function MyDecorator:highlight_group(node)
if node.name == "example" then
return "MyHighlight"
else
return nil
end
end
<
==============================================================================
12. OS SPECIFIC RESTRICTIONS *nvim-tree-os-specific*
Windows WSL and PowerShell Windows WSL and PowerShell
- Trash is synchronized - Trash is synchronized
@@ -2767,7 +2873,7 @@ Windows WSL and PowerShell
issues or disable this feature. issues or disable this feature.
============================================================================== ==============================================================================
12. NETRW *nvim-tree-netrw* 13. NETRW *nvim-tree-netrw*
|netrw| is a standard neovim plugin that is enabled by default. It provides, |netrw| is a standard neovim plugin that is enabled by default. It provides,
amongst other functionality, a file/directory browser. amongst other functionality, a file/directory browser.
@@ -2788,14 +2894,14 @@ keep using |netrw| without its browser features please ensure:
|nvim-tree.hijack_netrw| ` = true` |nvim-tree.hijack_netrw| ` = true`
============================================================================== ==============================================================================
13. LEGACY *nvim-tree-legacy* 14. LEGACY *nvim-tree-legacy*
Breaking refactors have been made however the legacy versions will be silently Breaking refactors have been made however the legacy versions will be silently
migrated and used. migrated and used.
There are no plans to remove this migration. There are no plans to remove this migration.
============================================================================== ==============================================================================
13.1 LEGACY: OPTS *nvim-tree-legacy-opts* 14.1 LEGACY: OPTS *nvim-tree-legacy-opts*
Legacy options are translated to the current, making type and value changes as Legacy options are translated to the current, making type and value changes as
needed. needed.
@@ -2813,7 +2919,7 @@ needed.
`renderer.icons.webdev_colors` |nvim-tree.renderer.icons.web_devicons.file.color| `renderer.icons.webdev_colors` |nvim-tree.renderer.icons.web_devicons.file.color|
============================================================================== ==============================================================================
13.2 LEGACY: HIGHLIGHT *nvim-tree-legacy-highlight* 14.2 LEGACY: HIGHLIGHT *nvim-tree-legacy-highlight*
Legacy highlight group are still obeyed when they are defined and the current Legacy highlight group are still obeyed when they are defined and the current
highlight group is not, hard linking as follows: > highlight group is not, hard linking as follows: >
@@ -2862,10 +2968,10 @@ highlight group is not, hard linking as follows: >
NvimTreeLspDiagnosticsHintFolderText NvimTreeDiagnosticHintFolderHL NvimTreeLspDiagnosticsHintFolderText NvimTreeDiagnosticHintFolderHL
< <
============================================================================== ==============================================================================
14 INDEX *nvim-tree-index* 15 INDEX *nvim-tree-index*
============================================================================== ==============================================================================
14.1 INDEX: OPTS *nvim-tree-index-opts* 15.1 INDEX: OPTS *nvim-tree-index-opts*
|nvim-tree.actions.change_dir| |nvim-tree.actions.change_dir|
|nvim-tree.actions.change_dir.enable| |nvim-tree.actions.change_dir.enable|
@@ -2943,6 +3049,7 @@ highlight group is not, hard linking as follows: >
|nvim-tree.prefer_startup_root| |nvim-tree.prefer_startup_root|
|nvim-tree.reload_on_bufenter| |nvim-tree.reload_on_bufenter|
|nvim-tree.renderer.add_trailing| |nvim-tree.renderer.add_trailing|
|nvim-tree.renderer.decorators|
|nvim-tree.renderer.full_name| |nvim-tree.renderer.full_name|
|nvim-tree.renderer.group_empty| |nvim-tree.renderer.group_empty|
|nvim-tree.renderer.hidden_display| |nvim-tree.renderer.hidden_display|
@@ -3033,7 +3140,7 @@ highlight group is not, hard linking as follows: >
|nvim-tree.view.width.padding| |nvim-tree.view.width.padding|
============================================================================== ==============================================================================
14.2 INDEX: API *nvim-tree-index-api* 15.2 INDEX: API *nvim-tree-index-api*
|nvim-tree-api.commands.get()| |nvim-tree-api.commands.get()|
|nvim-tree-api.config.mappings.default_on_attach()| |nvim-tree-api.config.mappings.default_on_attach()|
@@ -3092,6 +3199,7 @@ highlight group is not, hard linking as follows: >
|nvim-tree-api.node.open.drop()| |nvim-tree-api.node.open.drop()|
|nvim-tree-api.node.open.edit()| |nvim-tree-api.node.open.edit()|
|nvim-tree-api.node.open.horizontal()| |nvim-tree-api.node.open.horizontal()|
|nvim-tree-api.node.open.horizontal_no_picker()|
|nvim-tree-api.node.open.no_window_picker()| |nvim-tree-api.node.open.no_window_picker()|
|nvim-tree-api.node.open.preview()| |nvim-tree-api.node.open.preview()|
|nvim-tree-api.node.open.preview_no_picker()| |nvim-tree-api.node.open.preview_no_picker()|
@@ -3100,6 +3208,7 @@ highlight group is not, hard linking as follows: >
|nvim-tree-api.node.open.tab_drop()| |nvim-tree-api.node.open.tab_drop()|
|nvim-tree-api.node.open.toggle_group_empty()| |nvim-tree-api.node.open.toggle_group_empty()|
|nvim-tree-api.node.open.vertical()| |nvim-tree-api.node.open.vertical()|
|nvim-tree-api.node.open.vertical_no_picker()|
|nvim-tree-api.node.run.cmd()| |nvim-tree-api.node.run.cmd()|
|nvim-tree-api.node.run.system()| |nvim-tree-api.node.run.system()|
|nvim-tree-api.node.show_info_popup()| |nvim-tree-api.node.show_info_popup()|

View File

@@ -190,7 +190,7 @@ local function setup_autocommands(opts)
end end
if opts.hijack_directories.enable then if opts.hijack_directories.enable then
create_nvim_tree_autocmd({ "BufEnter", "BufNewFile" }, { callback = M.open_on_directory }) create_nvim_tree_autocmd({ "BufEnter", "BufNewFile" }, { callback = M.open_on_directory, nested = true })
end end
if opts.view.centralize_selection then if opts.view.centralize_selection then
@@ -199,6 +199,10 @@ local function setup_autocommands(opts)
callback = function() callback = function()
vim.schedule(function() vim.schedule(function()
vim.api.nvim_buf_call(0, function() vim.api.nvim_buf_call(0, function()
local is_term_mode = vim.api.nvim_get_mode().mode == "t"
if is_term_mode then
return
end
vim.cmd([[norm! zz]]) vim.cmd([[norm! zz]])
end) end)
end) end)
@@ -208,16 +212,16 @@ local function setup_autocommands(opts)
if opts.diagnostics.enable then if opts.diagnostics.enable then
create_nvim_tree_autocmd("DiagnosticChanged", { create_nvim_tree_autocmd("DiagnosticChanged", {
callback = function() callback = function(ev)
log.line("diagnostics", "DiagnosticChanged") log.line("diagnostics", "DiagnosticChanged")
require("nvim-tree.diagnostics").update() require("nvim-tree.diagnostics").update_lsp(ev)
end, end,
}) })
create_nvim_tree_autocmd("User", { create_nvim_tree_autocmd("User", {
pattern = "CocDiagnosticChange", pattern = "CocDiagnosticChange",
callback = function() callback = function()
log.line("diagnostics", "CocDiagnosticChange") log.line("diagnostics", "CocDiagnosticChange")
require("nvim-tree.diagnostics").update() require("nvim-tree.diagnostics").update_coc()
end, end,
}) })
end end
@@ -284,6 +288,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" }, special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" },
hidden_display = "none", hidden_display = "none",
symlink_destination = true, symlink_destination = true,
decorators = { "Git", "Open", "Hidden", "Modified", "Bookmark", "Diagnostics", "Copied", "Cut", },
highlight_git = "none", highlight_git = "none",
highlight_diagnostics = "none", highlight_diagnostics = "none",
highlight_opened_files = "none", highlight_opened_files = "none",
@@ -386,7 +391,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
enable = false, enable = false,
show_on_dirs = false, show_on_dirs = false,
show_on_open_dirs = true, show_on_open_dirs = true,
debounce_delay = 50, debounce_delay = 500,
severity = { severity = {
min = vim.diagnostic.severity.HINT, min = vim.diagnostic.severity.HINT,
max = vim.diagnostic.severity.ERROR, max = vim.diagnostic.severity.ERROR,

View File

@@ -0,0 +1,51 @@
---@meta
error("Cannot require a meta file")
--
-- Nodes
--
---Base Node, Abstract
---@class (exact) nvim_tree.api.Node
---@field type "file" | "directory" | "link" uv.fs_stat.result.type
---@field absolute_path string
---@field executable boolean
---@field fs_stat uv.fs_stat.result?
---@field git_status GitNodeStatus?
---@field hidden boolean
---@field name string
---@field parent nvim_tree.api.DirectoryNode?
---@field diag_severity lsp.DiagnosticSeverity?
---File
---@class (exact) nvim_tree.api.FileNode: nvim_tree.api.Node
---@field extension string
---Directory
---@class (exact) nvim_tree.api.DirectoryNode: nvim_tree.api.Node
---@field has_children boolean
---@field nodes nvim_tree.api.Node[]
---@field open boolean
---Root Directory
---@class (exact) nvim_tree.api.RootNode: nvim_tree.api.DirectoryNode
---Link mixin
---@class (exact) nvim_tree.api.LinkNode
---@field link_to string
---@field fs_stat_target uv.fs_stat.result
---File Link
---@class (exact) nvim_tree.api.FileLinkNode: nvim_tree.api.FileNode, nvim_tree.api.LinkNode
---DirectoryLink
---@class (exact) nvim_tree.api.DirectoryLinkNode: nvim_tree.api.DirectoryNode, nvim_tree.api.LinkNode
--
-- Various Types
--
---A string for rendering, with optional highlight groups to apply to it
---@class (exact) nvim_tree.api.HighlightedString
---@field str string
---@field hl string[]

View File

@@ -0,0 +1,54 @@
---@meta
error("Cannot require a meta file")
local nvim_tree = { api = { decorator = {} } }
---Highlight group range as per nvim-tree.renderer.highlight_*
---@alias nvim_tree.api.decorator.HighlightRange "none" | "icon" | "name" | "all"
---Icon position as per renderer.icons.*_placement
---@alias nvim_tree.api.decorator.IconPlacement "none" | "before" | "after" | "signcolumn" | "right_align"
---Names of builtin decorators or your decorator classes. Builtins are ordered lowest to highest priority.
---@alias nvim_tree.api.decorator.Name "Git" | "Opened" | "Hidden" | "Modified" | "Bookmarks" | "Diagnostics" | "Copied" | "Cut" | nvim_tree.api.decorator.UserDecorator
---Custom decorator, see :help nvim-tree-decorators
---
---@class (exact) nvim_tree.api.decorator.UserDecorator
---@field protected enabled boolean
---@field protected highlight_range nvim_tree.api.decorator.HighlightRange
---@field protected icon_placement nvim_tree.api.decorator.IconPlacement
nvim_tree.api.decorator.UserDecorator = {}
---Create your decorator class
---
function nvim_tree.api.decorator.UserDecorator:extend() end
---Abstract: no-args constructor must be implemented and will be called once per tree render.
---Must set all fields.
---
function nvim_tree.api.decorator.UserDecorator:new() end
---Abstract: optionally implement to set the node's icon
---
---@param node nvim_tree.api.Node
---@return nvim_tree.api.HighlightedString? icon_node
function nvim_tree.api.decorator.UserDecorator:icon_node(node) end
---Abstract: optionally implement to provide icons and the highlight groups for your icon_placement.
---
---@param node nvim_tree.api.Node
---@return nvim_tree.api.HighlightedString[]? icons
function nvim_tree.api.decorator.UserDecorator:icons(node) end
---Abstract: optionally implement to provide one highlight group to apply to your highlight_range.
---
---@param node nvim_tree.api.Node
---@return string? highlight_group
function nvim_tree.api.decorator.UserDecorator:highlight_group(node) end
---Define a sign. This should be called in the constructor.
---
---@protected
---@param icon nvim_tree.api.HighlightedString?
function nvim_tree.api.decorator.UserDecorator:define_sign(icon) end

View File

@@ -235,9 +235,8 @@ end
local function get_target_winid(mode) local function get_target_winid(mode)
local target_winid local target_winid
if not M.window_picker.enable or mode == "edit_no_picker" or mode == "preview_no_picker" then if not M.window_picker.enable or string.find(mode, "no_picker") then
target_winid = lib.target_winid target_winid = lib.target_winid
-- first available window -- first available window
if not vim.tbl_contains(vim.api.nvim_tabpage_list_wins(0), target_winid) then if not vim.tbl_contains(vim.api.nvim_tabpage_list_wins(0), target_winid) then
target_winid = first_win_id() target_winid = first_win_id()
@@ -280,6 +279,11 @@ local function open_in_new_window(filename, mode)
return return
end end
local position = string.find(mode, "no_picker")
if position then
mode = string.sub(mode, 0, position - 2)
end
-- non-floating, non-nvim-tree windows -- non-floating, non-nvim-tree windows
local win_ids = vim.tbl_filter(function(id) local win_ids = vim.tbl_filter(function(id)
local config = vim.api.nvim_win_get_config(id) local config = vim.api.nvim_win_get_config(id)

View File

@@ -25,7 +25,7 @@ end
---@param new_tabpage integer ---@param new_tabpage integer
---@return boolean ---@return boolean
local function is_window_event(new_tabpage) local function is_window_event(new_tabpage)
local is_event_scope_window = vim.v.event.scope == "window" or vim.v.event.changed_window local is_event_scope_window = vim.v.event.scope == "window" or vim.v.event.changed_window or false
return is_event_scope_window and new_tabpage == M.current_tab return is_event_scope_window and new_tabpage == M.current_tab
end end

View File

@@ -11,6 +11,7 @@ local notify = require("nvim-tree.notify")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
local FileLinkNode = require("nvim-tree.node.file-link") local FileLinkNode = require("nvim-tree.node.file-link")
local RootNode = require("nvim-tree.node.root") local RootNode = require("nvim-tree.node.root")
local UserDecorator = require("nvim-tree.renderer.decorator.user")
local Api = { local Api = {
tree = {}, tree = {},
@@ -39,6 +40,7 @@ local Api = {
}, },
commands = {}, commands = {},
diagnostics = {}, diagnostics = {},
decorator = {},
} }
---Print error when setup not called. ---Print error when setup not called.
@@ -69,7 +71,7 @@ end
---Inject the node as the first argument if present otherwise do nothing. ---Inject the node as the first argument if present otherwise do nothing.
---@param fn fun(node: Node, ...): any ---@param fn fun(node: Node, ...): any
---@return fun(node: Node, ...): any ---@return fun(node: Node?, ...): any
local function wrap_node(fn) local function wrap_node(fn)
return function(node, ...) return function(node, ...)
node = node or wrap_explorer("get_node_at_cursor")() node = node or wrap_explorer("get_node_at_cursor")()
@@ -80,8 +82,8 @@ local function wrap_node(fn)
end end
---Inject the node or nil as the first argument if absent. ---Inject the node or nil as the first argument if absent.
---@param fn fun(node: Node, ...): any ---@param fn fun(node: Node?, ...): any
---@return fun(node: Node, ...): any ---@return fun(node: Node?, ...): any
local function wrap_node_or_nil(fn) local function wrap_node_or_nil(fn)
return function(node, ...) return function(node, ...)
node = node or wrap_explorer("get_node_at_cursor")() node = node or wrap_explorer("get_node_at_cursor")()
@@ -253,7 +255,9 @@ Api.node.open.tab_drop = wrap_node(open_or_expand_or_dir_up("tab_drop"))
Api.node.open.replace_tree_buffer = wrap_node(open_or_expand_or_dir_up("edit_in_place")) Api.node.open.replace_tree_buffer = wrap_node(open_or_expand_or_dir_up("edit_in_place"))
Api.node.open.no_window_picker = wrap_node(open_or_expand_or_dir_up("edit_no_picker")) Api.node.open.no_window_picker = wrap_node(open_or_expand_or_dir_up("edit_no_picker"))
Api.node.open.vertical = wrap_node(open_or_expand_or_dir_up("vsplit")) Api.node.open.vertical = wrap_node(open_or_expand_or_dir_up("vsplit"))
Api.node.open.vertical_no_picker = wrap_node(open_or_expand_or_dir_up("vsplit_no_picker"))
Api.node.open.horizontal = wrap_node(open_or_expand_or_dir_up("split")) Api.node.open.horizontal = wrap_node(open_or_expand_or_dir_up("split"))
Api.node.open.horizontal_no_picker = wrap_node(open_or_expand_or_dir_up("split_no_picker"))
Api.node.open.tab = wrap_node(open_or_expand_or_dir_up("tabnew")) Api.node.open.tab = wrap_node(open_or_expand_or_dir_up("tabnew"))
Api.node.open.toggle_group_empty = wrap_node(open_or_expand_or_dir_up("toggle_group_empty", true)) Api.node.open.toggle_group_empty = wrap_node(open_or_expand_or_dir_up("toggle_group_empty", true))
Api.node.open.preview = wrap_node(open_or_expand_or_dir_up("preview")) Api.node.open.preview = wrap_node(open_or_expand_or_dir_up("preview"))
@@ -311,4 +315,9 @@ Api.commands.get = wrap(function()
return require("nvim-tree.commands").get() return require("nvim-tree.commands").get()
end) end)
---Create a decorator class by calling :extend()
---See :help nvim-tree-decorators
---@type nvim_tree.api.decorator.UserDecorator
Api.decorator.UserDecorator = UserDecorator --[[@as nvim_tree.api.decorator.UserDecorator]]
return Api return Api

View File

@@ -17,7 +17,7 @@ local COC_SEVERITY_LEVELS = {
} }
---Absolute Node path to LSP severity level ---Absolute Node path to LSP severity level
---@alias NodeSeverities table<string, lsp.DiagnosticSeverity> ---@alias NodeSeverities table<string, vim.diagnostic.Severity>
---@class DiagStatus ---@class DiagStatus
---@field value lsp.DiagnosticSeverity|nil ---@field value lsp.DiagnosticSeverity|nil
@@ -37,33 +37,6 @@ local function uniformize_path(path)
return utils.canonical_path(path:gsub("\\", "/")) return utils.canonical_path(path:gsub("\\", "/"))
end end
---Marshal severities from LSP. Does nothing when LSP disabled.
---@return NodeSeverities
local function from_nvim_lsp()
local buffer_severity = {}
-- is_enabled is not present in all 0.10 builds/releases, see #2781
local is_enabled = false
if vim.fn.has("nvim-0.10") == 1 and type(vim.diagnostic.is_enabled) == "function" then
is_enabled = vim.diagnostic.is_enabled()
elseif type(vim.diagnostic.is_disabled) == "function" then ---@diagnostic disable-line: deprecated
is_enabled = not vim.diagnostic.is_disabled() ---@diagnostic disable-line: deprecated
end
if is_enabled then
for _, diagnostic in ipairs(vim.diagnostic.get(nil, { severity = M.severity })) do
if diagnostic.severity and diagnostic.bufnr and vim.api.nvim_buf_is_valid(diagnostic.bufnr) then
local bufname = uniformize_path(vim.api.nvim_buf_get_name(diagnostic.bufnr))
if not buffer_severity[bufname] or diagnostic.severity < buffer_severity[bufname] then
buffer_severity[bufname] = diagnostic.severity
end
end
end
end
return buffer_severity
end
---Severity is within diagnostics.severity.min, diagnostics.severity.max ---Severity is within diagnostics.severity.min, diagnostics.severity.max
---@param severity lsp.DiagnosticSeverity ---@param severity lsp.DiagnosticSeverity
---@param config table ---@param config table
@@ -135,11 +108,8 @@ local function from_cache(node)
for bufname, severity in pairs(NODE_SEVERITIES) do for bufname, severity in pairs(NODE_SEVERITIES) do
local node_contains_buf = vim.startswith(bufname, nodepath .. "/") local node_contains_buf = vim.startswith(bufname, nodepath .. "/")
if node_contains_buf then if node_contains_buf then
if severity == M.severity.max then if not max_severity or severity < max_severity then
max_severity = severity max_severity = severity
break
else
max_severity = math.min(max_severity or severity, severity)
end end
end end
end end
@@ -147,32 +117,73 @@ local function from_cache(node)
return { value = max_severity, cache_version = NODE_SEVERITIES_VERSION } return { value = max_severity, cache_version = NODE_SEVERITIES_VERSION }
end end
---Fired on DiagnosticChanged and CocDiagnosticChanged events: ---Fired on DiagnosticChanged for a single buffer.
---This will be called on set and reset of diagnostics.
---On disabling LSP, a reset event will be sent for all buffers.
---@param ev table standard event with data.diagnostics populated
function M.update_lsp(ev)
if not M.enable or not ev or not ev.data or not ev.data.diagnostics then
return
end
local profile_event = log.profile_start("DiagnosticChanged event")
local diagnostics = vim.diagnostic.get(ev.buf)
-- use the buffer from the event, as ev.data.diagnostics will be empty on resolved diagnostics
local bufname = uniformize_path(vim.api.nvim_buf_get_name(ev.buf))
---@type vim.diagnostic.Severity?
local new_severity = nil
-- most severe (lowest) severity in user range
for _, diagnostic in ipairs(diagnostics) do
if diagnostic.severity >= M.severity.max and diagnostic.severity <= M.severity.min then
if not new_severity or diagnostic.severity < new_severity then
new_severity = diagnostic.severity
end
end
end
-- record delta and schedule a redraw
if new_severity ~= NODE_SEVERITIES[bufname] then
NODE_SEVERITIES[bufname] = new_severity
NODE_SEVERITIES_VERSION = NODE_SEVERITIES_VERSION + 1
utils.debounce("DiagnosticChanged redraw", M.debounce_delay, function()
local profile_redraw = log.profile_start("DiagnosticChanged redraw")
local explorer = core.get_explorer()
if explorer then
explorer.renderer:draw()
end
log.profile_end(profile_redraw)
end)
end
log.profile_end(profile_event)
end
---Fired on CocDiagnosticChanged events:
---debounced retrieval, cache update, version increment and draw ---debounced retrieval, cache update, version increment and draw
function M.update() function M.update_coc()
if not M.enable then if not M.enable then
return return
end end
utils.debounce("diagnostics", M.debounce_delay, function() utils.debounce("CocDiagnosticChanged update", M.debounce_delay, function()
local profile = log.profile_start("diagnostics update") local profile = log.profile_start("CocDiagnosticChanged update")
if is_using_coc() then NODE_SEVERITIES = from_coc()
NODE_SEVERITIES = from_coc()
else
NODE_SEVERITIES = from_nvim_lsp()
end
NODE_SEVERITIES_VERSION = NODE_SEVERITIES_VERSION + 1 NODE_SEVERITIES_VERSION = NODE_SEVERITIES_VERSION + 1
if log.enabled("diagnostics") then if log.enabled("diagnostics") then
for bufname, severity in pairs(NODE_SEVERITIES) do for bufname, severity in pairs(NODE_SEVERITIES) do
log.line("diagnostics", "Indexing bufname '%s' with severity %d", bufname, severity) log.line("diagnostics", "COC Indexing bufname '%s' with severity %d", bufname, severity)
end end
end end
log.profile_end(profile) log.profile_end(profile)
local bufnr = view.get_bufnr() local bufnr = view.get_bufnr()
local should_draw = bufnr local should_draw = bufnr and vim.api.nvim_buf_is_valid(bufnr) and vim.api.nvim_buf_is_loaded(bufnr)
and vim.api.nvim_buf_is_valid(bufnr)
and vim.api.nvim_buf_is_loaded(bufnr)
and vim.api.nvim_get_option_value("buflisted", { buf = bufnr })
if should_draw then if should_draw then
local explorer = core.get_explorer() local explorer = core.get_explorer()
if explorer then if explorer then

View File

@@ -530,7 +530,7 @@ function Explorer:place_cursor_on_node()
end end
---Api.tree.get_nodes ---Api.tree.get_nodes
---@return Node ---@return nvim_tree.api.Node
function Explorer:get_nodes() function Explorer:get_nodes()
return self:clone() return self:clone()
end end

View File

@@ -73,9 +73,10 @@ function DirectoryLinkNode:highlighted_name()
end end
---Create a sanitized partial copy of a node, populating children recursively. ---Create a sanitized partial copy of a node, populating children recursively.
---@return DirectoryLinkNode cloned ---@param api_nodes table<number, nvim_tree.api.Node>? optional map of uids to api node to populate
function DirectoryLinkNode:clone() ---@return nvim_tree.api.DirectoryLinkNode cloned
local clone = DirectoryNode.clone(self) --[[@as DirectoryLinkNode]] function DirectoryLinkNode:clone(api_nodes)
local clone = DirectoryNode.clone(self, api_nodes) --[[@as nvim_tree.api.DirectoryLinkNode]]
clone.link_to = self.link_to clone.link_to = self.link_to
clone.fs_stat_target = self.fs_stat_target clone.fs_stat_target = self.fs_stat_target

View File

@@ -271,18 +271,20 @@ function DirectoryNode:highlighted_name()
end end
---Create a sanitized partial copy of a node, populating children recursively. ---Create a sanitized partial copy of a node, populating children recursively.
---@return DirectoryNode cloned ---@param api_nodes table<number, nvim_tree.api.Node>? optional map of uids to api node to populate
function DirectoryNode:clone() ---@return nvim_tree.api.DirectoryNode cloned
local clone = Node.clone(self) --[[@as DirectoryNode]] function DirectoryNode:clone(api_nodes)
local clone = Node.clone(self, api_nodes) --[[@as nvim_tree.api.DirectoryNode]]
clone.has_children = self.has_children clone.has_children = self.has_children
clone.group_next = nil
clone.nodes = {} clone.nodes = {}
clone.open = self.open clone.open = self.open
clone.hidden_stats = nil
local clone_child
for _, child in ipairs(self.nodes) do for _, child in ipairs(self.nodes) do
table.insert(clone.nodes, child:clone()) clone_child = child:clone(api_nodes)
clone_child.parent = clone
table.insert(clone.nodes, clone_child)
end end
return clone return clone

View File

@@ -58,9 +58,10 @@ function FileLinkNode:highlighted_name()
end end
---Create a sanitized partial copy of a node ---Create a sanitized partial copy of a node
---@return FileLinkNode cloned ---@param api_nodes table<number, nvim_tree.api.Node>? optional map of uids to api node to populate
function FileLinkNode:clone() ---@return nvim_tree.api.FileLinkNode cloned
local clone = FileNode.clone(self) --[[@as FileLinkNode]] function FileLinkNode:clone(api_nodes)
local clone = FileNode.clone(self, api_nodes) --[[@as nvim_tree.api.FileLinkNode]]
clone.link_to = self.link_to clone.link_to = self.link_to
clone.fs_stat_target = self.fs_stat_target clone.fs_stat_target = self.fs_stat_target

View File

@@ -94,9 +94,10 @@ function FileNode:highlighted_name()
end end
---Create a sanitized partial copy of a node ---Create a sanitized partial copy of a node
---@return FileNode cloned ---@param api_nodes table<number, nvim_tree.api.Node>? optional map of uids to api node to populate
function FileNode:clone() ---@return nvim_tree.api.FileNode cloned
local clone = Node.clone(self) --[[@as FileNode]] function FileNode:clone(api_nodes)
local clone = Node.clone(self, api_nodes) --[[@as nvim_tree.api.FileNode]]
clone.extension = self.extension clone.extension = self.extension

View File

@@ -2,6 +2,7 @@ local Class = require("nvim-tree.classic")
---Abstract Node class. ---Abstract Node class.
---@class (exact) Node: Class ---@class (exact) Node: Class
---@field uid_node number vim.loop.hrtime() at construction time
---@field type "file" | "directory" | "link" uv.fs_stat.result.type ---@field type "file" | "directory" | "link" uv.fs_stat.result.type
---@field explorer Explorer ---@field explorer Explorer
---@field absolute_path string ---@field absolute_path string
@@ -25,6 +26,7 @@ local Node = Class:extend()
---@protected ---@protected
---@param args NodeArgs ---@param args NodeArgs
function Node:new(args) function Node:new(args)
self.uid_node = vim.loop.hrtime()
self.explorer = args.explorer self.explorer = args.explorer
self.absolute_path = args.absolute_path self.absolute_path = args.absolute_path
self.executable = false self.executable = false
@@ -112,21 +114,19 @@ end
---Highlighted name for the node ---Highlighted name for the node
---Empty for base Node ---Empty for base Node
---@return HighlightedString icon ---@return HighlightedString name
function Node:highlighted_name() function Node:highlighted_name()
return self:highlighted_name_empty() return self:highlighted_name_empty()
end end
---Create a sanitized partial copy of a node, populating children recursively. ---Create a sanitized partial copy of a node, populating children recursively.
---@return Node cloned ---@param api_nodes table<number, nvim_tree.api.Node>? optional map of uids to api node to populate
function Node:clone() ---@return nvim_tree.api.Node cloned
---@type Explorer function Node:clone(api_nodes)
local explorer_placeholder = nil ---@type nvim_tree.api.Node
---@type Node
local clone = { local clone = {
uid_node = self.uid_node,
type = self.type, type = self.type,
explorer = explorer_placeholder,
absolute_path = self.absolute_path, absolute_path = self.absolute_path,
executable = self.executable, executable = self.executable,
fs_stat = self.fs_stat, fs_stat = self.fs_stat,
@@ -134,10 +134,13 @@ function Node:clone()
hidden = self.hidden, hidden = self.hidden,
name = self.name, name = self.name,
parent = nil, parent = nil,
diag_status = nil, diag_severity = self.diag_status and self.diag_status.value or nil,
is_dot = self.is_dot,
} }
if api_nodes then
api_nodes[self.uid_node] = clone
end
return clone return clone
end end

View File

@@ -2,7 +2,7 @@ local Class = require("nvim-tree.classic")
---@class (exact) LinkNode: Class ---@class (exact) LinkNode: Class
---@field link_to string ---@field link_to string
---@field protected fs_stat_target uv.fs_stat.result ---@field fs_stat_target uv.fs_stat.result
local LinkNode = Class:extend() local LinkNode = Class:extend()
---@class (exact) LinkNodeArgs: NodeArgs ---@class (exact) LinkNodeArgs: NodeArgs

View File

@@ -22,4 +22,13 @@ function RootNode:destroy()
DirectoryNode.destroy(self) DirectoryNode.destroy(self)
end end
---Create a sanitized partial copy of a node, populating children recursively.
---@param api_nodes table<number, nvim_tree.api.Node>? optional map of uids to api node to populate
---@return nvim_tree.api.RootNode cloned
function RootNode:clone(api_nodes)
local clone = DirectoryNode.clone(self, api_nodes) --[[@as nvim_tree.api.RootNode]]
return clone
end
return RootNode return RootNode

View File

@@ -3,22 +3,35 @@ local utils = require("nvim-tree.utils")
local view = require("nvim-tree.view") local view = require("nvim-tree.view")
local Class = require("nvim-tree.classic") local Class = require("nvim-tree.classic")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
local DecoratorBookmarks = require("nvim-tree.renderer.decorator.bookmarks") local BookmarkDecorator = require("nvim-tree.renderer.decorator.bookmarks")
local DecoratorCopied = require("nvim-tree.renderer.decorator.copied") local CopiedDecorator = require("nvim-tree.renderer.decorator.copied")
local DecoratorCut = require("nvim-tree.renderer.decorator.cut") local CutDecorator = require("nvim-tree.renderer.decorator.cut")
local DecoratorDiagnostics = require("nvim-tree.renderer.decorator.diagnostics") local DiagnosticsDecorator = require("nvim-tree.renderer.decorator.diagnostics")
local DecoratorGit = require("nvim-tree.renderer.decorator.git") local GitDecorator = require("nvim-tree.renderer.decorator.git")
local DecoratorModified = require("nvim-tree.renderer.decorator.modified") local HiddenDecorator = require("nvim-tree.renderer.decorator.hidden")
local DecoratorHidden = require("nvim-tree.renderer.decorator.hidden") local ModifiedDecorator = require("nvim-tree.renderer.decorator.modified")
local DecoratorOpened = require("nvim-tree.renderer.decorator.opened") local OpenDecorator = require("nvim-tree.renderer.decorator.opened")
local UserDecorator = require("nvim-tree.renderer.decorator.user")
local pad = require("nvim-tree.renderer.components.padding") local pad = require("nvim-tree.renderer.components.padding")
---@class (exact) HighlightedString ---@alias HighlightedString nvim_tree.api.HighlightedString
---@field str string
---@field hl string[] -- Builtin Decorators
---@type table<nvim_tree.api.decorator.Name, Decorator>
local BUILTIN_DECORATORS = {
Git = GitDecorator,
Open = OpenDecorator,
Hidden = HiddenDecorator,
Modified = ModifiedDecorator,
Bookmark = BookmarkDecorator,
Diagnostics = DiagnosticsDecorator,
Copied = CopiedDecorator,
Cut = CutDecorator,
}
---@class (exact) AddHighlightArgs ---@class (exact) AddHighlightArgs
---@field group string[] ---@field group string[]
@@ -39,6 +52,7 @@ local pad = require("nvim-tree.renderer.components.padding")
---@field private markers boolean[] indent markers ---@field private markers boolean[] indent markers
---@field private decorators Decorator[] ---@field private decorators Decorator[]
---@field private hidden_display fun(node: Node): string|nil ---@field private hidden_display fun(node: Node): string|nil
---@field private api_nodes table<number, nvim_tree.api.Node>? optional map of uids to api node for user decorators
local Builder = Class:extend() local Builder = Class:extend()
---@class Builder ---@class Builder
@@ -60,18 +74,30 @@ function Builder:new(args)
self.signs = {} self.signs = {}
self.extmarks = {} self.extmarks = {}
self.virtual_lines = {} self.virtual_lines = {}
self.decorators = { self.decorators = {}
-- priority order
DecoratorCut({ explorer = args.explorer }),
DecoratorCopied({ explorer = args.explorer }),
DecoratorDiagnostics({ explorer = args.explorer }),
DecoratorBookmarks({ explorer = args.explorer }),
DecoratorModified({ explorer = args.explorer }),
DecoratorHidden({ explorer = args.explorer }),
DecoratorOpened({ explorer = args.explorer }),
DecoratorGit({ explorer = args.explorer })
}
self.hidden_display = Builder:setup_hidden_display_function(self.explorer.opts) self.hidden_display = Builder:setup_hidden_display_function(self.explorer.opts)
-- instantiate all the builtin and user decorator instances
local builtin, user
for _, d in ipairs(self.explorer.opts.renderer.decorators) do
---@type Decorator
builtin = BUILTIN_DECORATORS[d]
---@type UserDecorator
user = type(d) == "table" and type(d.as) == "function" and d:as(UserDecorator)
if builtin then
table.insert(self.decorators, builtin({ explorer = self.explorer }))
elseif user then
table.insert(self.decorators, user())
-- clone user nodes once
if not self.api_nodes then
self.api_nodes = {}
self.explorer:clone(self.api_nodes)
end
end
end
end end
---Insert ranged highlight groups into self.highlights ---Insert ranged highlight groups into self.highlights
@@ -131,22 +157,25 @@ function Builder:format_line(indent_markers, arrows, icon, name, node)
end end
end end
-- use the api node for user decorators
local api_node = self.api_nodes and self.api_nodes[node.uid_node] --[[@as Node]]
local line = { indent_markers, arrows } local line = { indent_markers, arrows }
add_to_end(line, { icon }) add_to_end(line, { icon })
for i = #self.decorators, 1, -1 do for _, d in ipairs(self.decorators) do
add_to_end(line, self.decorators[i]:icons_before(node)) add_to_end(line, d:icons_before(not d:is(UserDecorator) and node or api_node))
end end
add_to_end(line, { name }) add_to_end(line, { name })
for i = #self.decorators, 1, -1 do for _, d in ipairs(self.decorators) do
add_to_end(line, self.decorators[i]:icons_after(node)) add_to_end(line, d:icons_after(not d:is(UserDecorator) and node or api_node))
end end
local rights = {} local rights = {}
for i = #self.decorators, 1, -1 do for _, d in ipairs(self.decorators) do
add_to_end(rights, self.decorators[i]:icons_right_align(node)) add_to_end(rights, d:icons_right_align(not d:is(UserDecorator) and node or api_node))
end end
if #rights > 0 then if #rights > 0 then
self.extmarks[self.index] = rights self.extmarks[self.index] = rights
@@ -158,10 +187,14 @@ end
---@private ---@private
---@param node Node ---@param node Node
function Builder:build_signs(node) function Builder:build_signs(node)
-- use the api node for user decorators
local api_node = self.api_nodes and self.api_nodes[node.uid_node] --[[@as Node]]
-- first in priority order -- first in priority order
local sign_name local d, sign_name
for _, d in ipairs(self.decorators) do for i = #self.decorators, 1, -1 do
sign_name = d:sign_name(node) d = self.decorators[i]
sign_name = d:sign_name(not d:is(UserDecorator) and node or api_node)
if sign_name then if sign_name then
self.signs[self.index] = sign_name self.signs[self.index] = sign_name
break break
@@ -197,43 +230,50 @@ function Builder:create_combined_group(groups)
return combined_name return combined_name
end end
---Calculate highlight group for icon and name. A combined highlight group will be created ---Calculate decorated icon and name for a node.
---when there is more than one highlight. ---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. ---A highlight group is always calculated and upserted for the case of highlights changing.
---@private ---@private
---@param node Node ---@param node Node
---@return string|nil icon_hl_group ---@return HighlightedString icon
---@return string|nil name_hl_group ---@return HighlightedString name
function Builder:add_highlights(node) function Builder:icon_name_decorated(node)
-- result -- use the api node for user decorators
local icon_hl_group, name_hl_group local api_node = self.api_nodes and self.api_nodes[node.uid_node] --[[@as Node]]
-- calculate all groups -- base case
local icon = node:highlighted_icon()
local name = node:highlighted_name()
-- calculate node icon and all decorated highlight groups
local icon_groups = {} local icon_groups = {}
local name_groups = {} local name_groups = {}
local d, icon, name local hl_icon, hl_name
for i = #self.decorators, 1, -1 do for _, d in ipairs(self.decorators) do
d = self.decorators[i] -- maybe overridde icon
icon, name = d:groups_icon_name(node) icon = d:icon_node((not d:is(UserDecorator) and node or api_node)) or icon
table.insert(icon_groups, icon)
table.insert(name_groups, name) hl_icon, hl_name = d:highlight_group_icon_name((not d:is(UserDecorator) and node or api_node))
table.insert(icon_groups, hl_icon)
table.insert(name_groups, hl_name)
end end
-- one or many icon groups -- add one or many icon groups
if #icon_groups > 1 then if #icon_groups > 1 then
icon_hl_group = self:create_combined_group(icon_groups) table.insert(icon.hl, self:create_combined_group(icon_groups))
else else
icon_hl_group = icon_groups[1] table.insert(icon.hl, icon_groups[1])
end end
-- one or many name groups -- add one or many name groups
if #name_groups > 1 then if #name_groups > 1 then
name_hl_group = self:create_combined_group(name_groups) table.insert(name.hl, self:create_combined_group(name_groups))
else else
name_hl_group = name_groups[1] table.insert(name.hl, name_groups[1])
end end
return icon_hl_group, name_hl_group return icon, name
end end
---Insert node line into self.lines, calling Builder:build_lines for each directory ---Insert node line into self.lines, calling Builder:build_lines for each directory
@@ -246,13 +286,8 @@ function Builder:build_line(node, idx, num_children)
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)
-- main components -- decorated node icon and name
local icon, name = node:highlighted_icon(), node:highlighted_name() local icon, name = self:icon_name_decorated(node)
-- highighting
local icon_hl_group, name_hl_group = self:add_highlights(node)
table.insert(icon.hl, icon_hl_group)
table.insert(name.hl, name_hl_group)
local line = self:format_line(indent_markers, arrows, icon, name, node) local line = self:format_line(indent_markers, arrows, icon, name, node)
table.insert(self.lines, self:unwrap_highlighted_strings(line)) table.insert(self.lines, self:unwrap_highlighted_strings(line))

View File

@@ -1,21 +1,21 @@
local Decorator = require("nvim-tree.renderer.decorator") local Decorator = require("nvim-tree.renderer.decorator")
---@class (exact) DecoratorBookmarks: Decorator ---@class (exact) BookmarkDecorator: Decorator
---@field icon HighlightedString? ---@field private explorer Explorer
local DecoratorBookmarks = Decorator:extend() ---@field private icon HighlightedString?
local BookmarkDecorator = Decorator:extend()
---@class DecoratorBookmarks ---@class BookmarkDecorator
---@overload fun(explorer: DecoratorArgs): DecoratorBookmarks ---@overload fun(args: DecoratorArgs): BookmarkDecorator
---@protected ---@protected
---@param args DecoratorArgs ---@param args DecoratorArgs
function DecoratorBookmarks:new(args) function BookmarkDecorator:new(args)
Decorator.new(self, { self.explorer = args.explorer
explorer = args.explorer,
enabled = true, self.enabled = true
hl_pos = args.explorer.opts.renderer.highlight_bookmarks or "none", self.highlight_range = self.explorer.opts.renderer.highlight_bookmarks or "none"
icon_placement = args.explorer.opts.renderer.icons.bookmarks_placement or "none", self.icon_placement = self.explorer.opts.renderer.icons.bookmarks_placement or "none"
})
if self.explorer.opts.renderer.icons.show.bookmarks then if self.explorer.opts.renderer.icons.show.bookmarks then
self.icon = { self.icon = {
@@ -28,8 +28,8 @@ end
---Bookmark icon: renderer.icons.show.bookmarks and node is marked ---Bookmark icon: renderer.icons.show.bookmarks and node is marked
---@param node Node ---@param node Node
---@return HighlightedString[]|nil icons ---@return HighlightedString[]? icons
function DecoratorBookmarks:calculate_icons(node) function BookmarkDecorator:icons(node)
if self.explorer.marks:get(node) then if self.explorer.marks:get(node) then
return { self.icon } return { self.icon }
end end
@@ -37,11 +37,11 @@ end
---Bookmark highlight: renderer.highlight_bookmarks and node is marked ---Bookmark highlight: renderer.highlight_bookmarks and node is marked
---@param node Node ---@param node Node
---@return string|nil group ---@return string? highlight_group
function DecoratorBookmarks:calculate_highlight(node) function BookmarkDecorator:highlight_group(node)
if self.range ~= "none" and self.explorer.marks:get(node) then if self.highlight_range ~= "none" and self.explorer.marks:get(node) then
return "NvimTreeBookmarkHL" return "NvimTreeBookmarkHL"
end end
end end
return DecoratorBookmarks return BookmarkDecorator

View File

@@ -1,29 +1,29 @@
local Decorator = require("nvim-tree.renderer.decorator") local Decorator = require("nvim-tree.renderer.decorator")
---@class (exact) DecoratorCopied: Decorator ---@class (exact) CopiedDecorator: Decorator
local DecoratorCopied = Decorator:extend() ---@field private explorer Explorer
local CopiedDecorator = Decorator:extend()
---@class DecoratorCopied ---@class CopiedDecorator
---@overload fun(explorer: DecoratorArgs): DecoratorCopied ---@overload fun(args: DecoratorArgs): CopiedDecorator
---@protected ---@protected
---@param args DecoratorArgs ---@param args DecoratorArgs
function DecoratorCopied:new(args) function CopiedDecorator:new(args)
Decorator.new(self, { self.explorer = args.explorer
explorer = args.explorer,
enabled = true, self.enabled = true
hl_pos = args.explorer.opts.renderer.highlight_clipboard or "none", self.highlight_range = self.explorer.opts.renderer.highlight_clipboard or "none"
icon_placement = "none", self.icon_placement = "none"
})
end end
---Copied highlight: renderer.highlight_clipboard and node is copied ---Copied highlight: renderer.highlight_clipboard and node is copied
---@param node Node ---@param node Node
---@return string|nil group ---@return string? highlight_group
function DecoratorCopied:calculate_highlight(node) function CopiedDecorator:highlight_group(node)
if self.range ~= "none" and self.explorer.clipboard:is_copied(node) then if self.highlight_range ~= "none" and self.explorer.clipboard:is_copied(node) then
return "NvimTreeCopiedHL" return "NvimTreeCopiedHL"
end end
end end
return DecoratorCopied return CopiedDecorator

View File

@@ -1,29 +1,29 @@
local Decorator = require("nvim-tree.renderer.decorator") local Decorator = require("nvim-tree.renderer.decorator")
---@class (exact) DecoratorCut: Decorator ---@class (exact) CutDecorator: Decorator
local DecoratorCut = Decorator:extend() ---@field private explorer Explorer
local CutDecorator = Decorator:extend()
---@class DecoratorCut ---@class CutDecorator
---@overload fun(explorer: DecoratorArgs): DecoratorCut ---@overload fun(args: DecoratorArgs): CutDecorator
---@protected ---@protected
---@param args DecoratorArgs ---@param args DecoratorArgs
function DecoratorCut:new(args) function CutDecorator:new(args)
Decorator.new(self, { self.explorer = args.explorer
explorer = args.explorer,
enabled = true, self.enabled = true
hl_pos = args.explorer.opts.renderer.highlight_clipboard or "none", self.highlight_range = self.explorer.opts.renderer.highlight_clipboard or "none"
icon_placement = "none", self.icon_placement = "none"
})
end end
---Cut highlight: renderer.highlight_clipboard and node is cut ---Cut highlight: renderer.highlight_clipboard and node is cut
---@param node Node ---@param node Node
---@return string|nil group ---@return string? highlight_group
function DecoratorCut:calculate_highlight(node) function CutDecorator:highlight_group(node)
if self.range ~= "none" and self.explorer.clipboard:is_cut(node) then if self.highlight_range ~= "none" and self.explorer.clipboard:is_cut(node) then
return "NvimTreeCutHL" return "NvimTreeCutHL"
end end
end end
return DecoratorCut return CutDecorator

View File

@@ -30,58 +30,54 @@ local ICON_KEYS = {
["hint"] = vim.diagnostic.severity.HINT, ["hint"] = vim.diagnostic.severity.HINT,
} }
---@class (exact) DecoratorDiagnostics: Decorator ---@class (exact) DiagnosticsDecorator: Decorator
---@field icons HighlightedString[]? ---@field private explorer Explorer
local DecoratorDiagnostics = Decorator:extend() ---@field private diag_icons HighlightedString[]?
local DiagnosticsDecorator = Decorator:extend()
---@class DecoratorDiagnostics ---@class DiagnosticsDecorator
---@overload fun(explorer: DecoratorArgs): DecoratorDiagnostics ---@overload fun(args: DecoratorArgs): DiagnosticsDecorator
---@protected ---@protected
---@param args DecoratorArgs ---@param args DecoratorArgs
function DecoratorDiagnostics:new(args) function DiagnosticsDecorator:new(args)
Decorator.new(self, { self.explorer = args.explorer
explorer = args.explorer,
enabled = true,
hl_pos = args.explorer.opts.renderer.highlight_diagnostics or "none",
icon_placement = args.explorer.opts.renderer.icons.diagnostics_placement or "none",
})
if not self.enabled then self.enabled = true
return self.highlight_range = self.explorer.opts.renderer.highlight_diagnostics or "none"
end self.icon_placement = self.explorer.opts.renderer.icons.diagnostics_placement or "none"
if self.explorer.opts.renderer.icons.show.diagnostics then if self.explorer.opts.renderer.icons.show.diagnostics then
self.icons = {} self.diag_icons = {}
for name, sev in pairs(ICON_KEYS) do for name, sev in pairs(ICON_KEYS) do
self.icons[sev] = { self.diag_icons[sev] = {
str = self.explorer.opts.diagnostics.icons[name], str = self.explorer.opts.diagnostics.icons[name],
hl = { HG_ICON[sev] }, hl = { HG_ICON[sev] },
} }
self:define_sign(self.icons[sev]) self:define_sign(self.diag_icons[sev])
end end
end end
end end
---Diagnostic icon: diagnostics.enable, renderer.icons.show.diagnostics and node has status ---Diagnostic icon: diagnostics.enable, renderer.icons.show.diagnostics and node has status
---@param node Node ---@param node Node
---@return HighlightedString[]|nil icons ---@return HighlightedString[]? icons
function DecoratorDiagnostics:calculate_icons(node) function DiagnosticsDecorator:icons(node)
if node and self.enabled and self.icons then if node and self.diag_icons then
local diag_status = diagnostics.get_diag_status(node) local diag_status = diagnostics.get_diag_status(node)
local diag_value = diag_status and diag_status.value local diag_value = diag_status and diag_status.value
if diag_value then if diag_value then
return { self.icons[diag_value] } return { self.diag_icons[diag_value] }
end end
end end
end end
---Diagnostic highlight: diagnostics.enable, renderer.highlight_diagnostics and node has status ---Diagnostic highlight: diagnostics.enable, renderer.highlight_diagnostics and node has status
---@param node Node ---@param node Node
---@return string|nil group ---@return string? highlight_group
function DecoratorDiagnostics:calculate_highlight(node) function DiagnosticsDecorator:highlight_group(node)
if not node or not self.enabled or self.range == "none" then if self.highlight_range == "none" then
return nil return nil
end end
@@ -106,4 +102,4 @@ function DecoratorDiagnostics:calculate_highlight(node)
end end
end end
return DecoratorDiagnostics return DiagnosticsDecorator

View File

@@ -3,7 +3,7 @@ local notify = require("nvim-tree.notify")
local Decorator = require("nvim-tree.renderer.decorator") local Decorator = require("nvim-tree.renderer.decorator")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
---@class (exact) GitHighlightedString: HighlightedString ---@class (exact) GitHighlightedString: nvim_tree.api.HighlightedString
---@field ord number decreasing priority ---@field ord number decreasing priority
---@alias GitStatusStrings "deleted" | "ignored" | "renamed" | "staged" | "unmerged" | "unstaged" | "untracked" ---@alias GitStatusStrings "deleted" | "ignored" | "renamed" | "staged" | "unmerged" | "unstaged" | "untracked"
@@ -12,31 +12,31 @@ local DirectoryNode = require("nvim-tree.node.directory")
---@alias GitIconsByXY table<GitXY, GitHighlightedString[]> porcelain status ---@alias GitIconsByXY table<GitXY, GitHighlightedString[]> porcelain status
---@alias GitGlyphsByStatus table<GitStatusStrings, string> from opts ---@alias GitGlyphsByStatus table<GitStatusStrings, string> from opts
---@class (exact) DecoratorGit: Decorator ---@class (exact) GitDecorator: Decorator
---@field file_hl_by_xy table<GitXY, string>? ---@field private explorer Explorer
---@field folder_hl_by_xy table<GitXY, string>? ---@field private file_hl_by_xy table<GitXY, string>?
---@field icons_by_status GitIconsByStatus? ---@field private folder_hl_by_xy table<GitXY, string>?
---@field icons_by_xy GitIconsByXY? ---@field private icons_by_status GitIconsByStatus?
local DecoratorGit = Decorator:extend() ---@field private icons_by_xy GitIconsByXY?
local GitDecorator = Decorator:extend()
---@class DecoratorGit ---@class GitDecorator
---@overload fun(explorer: DecoratorArgs): DecoratorGit ---@overload fun(args: DecoratorArgs): GitDecorator
---@protected ---@protected
---@param args DecoratorArgs ---@param args DecoratorArgs
function DecoratorGit:new(args) function GitDecorator:new(args)
Decorator.new(self, { self.explorer = args.explorer
explorer = args.explorer,
enabled = args.explorer.opts.git.enable, self.enabled = self.explorer.opts.git.enable
hl_pos = args.explorer.opts.renderer.highlight_git or "none", self.highlight_range = self.explorer.opts.renderer.highlight_git or "none"
icon_placement = args.explorer.opts.renderer.icons.git_placement or "none", self.icon_placement = self.explorer.opts.renderer.icons.git_placement or "none"
})
if not self.enabled then if not self.enabled then
return return
end end
if self.range ~= "none" then if self.highlight_range ~= "none" then
self:build_file_folder_hl_by_xy() self:build_file_folder_hl_by_xy()
end end
@@ -51,7 +51,7 @@ function DecoratorGit:new(args)
end end
---@param glyphs GitGlyphsByStatus ---@param glyphs GitGlyphsByStatus
function DecoratorGit:build_icons_by_status(glyphs) function GitDecorator:build_icons_by_status(glyphs)
self.icons_by_status = {} self.icons_by_status = {}
self.icons_by_status.staged = { str = glyphs.staged, hl = { "NvimTreeGitStagedIcon" }, ord = 1 } self.icons_by_status.staged = { str = glyphs.staged, hl = { "NvimTreeGitStagedIcon" }, ord = 1 }
self.icons_by_status.unstaged = { str = glyphs.unstaged, hl = { "NvimTreeGitDirtyIcon" }, ord = 2 } self.icons_by_status.unstaged = { str = glyphs.unstaged, hl = { "NvimTreeGitDirtyIcon" }, ord = 2 }
@@ -63,7 +63,7 @@ function DecoratorGit:build_icons_by_status(glyphs)
end end
---@param icons GitIconsByXY ---@param icons GitIconsByXY
function DecoratorGit:build_icons_by_xy(icons) function GitDecorator:build_icons_by_xy(icons)
self.icons_by_xy = { self.icons_by_xy = {
["M "] = { icons.staged }, ["M "] = { icons.staged },
[" M"] = { icons.unstaged }, [" M"] = { icons.unstaged },
@@ -100,7 +100,7 @@ function DecoratorGit:build_icons_by_xy(icons)
} }
end end
function DecoratorGit:build_file_folder_hl_by_xy() function GitDecorator:build_file_folder_hl_by_xy()
self.file_hl_by_xy = { self.file_hl_by_xy = {
["M "] = "NvimTreeGitFileStagedHL", ["M "] = "NvimTreeGitFileStagedHL",
["C "] = "NvimTreeGitFileStagedHL", ["C "] = "NvimTreeGitFileStagedHL",
@@ -142,9 +142,9 @@ end
---Git icons: git.enable, renderer.icons.show.git and node has status ---Git icons: git.enable, renderer.icons.show.git and node has status
---@param node Node ---@param node Node
---@return HighlightedString[]|nil modified icon ---@return HighlightedString[]? icons
function DecoratorGit:calculate_icons(node) function GitDecorator:icons(node)
if not node or not self.enabled or not self.icons_by_xy then if not self.icons_by_xy then
return nil return nil
end end
@@ -159,7 +159,7 @@ function DecoratorGit:calculate_icons(node)
for _, s in pairs(git_xy) do for _, s in pairs(git_xy) do
local icons = self.icons_by_xy[s] local icons = self.icons_by_xy[s]
if not icons then if not icons then
if self.range == "none" then if self.highlight_range == "none" then
notify.warn(string.format("Unrecognized git state '%s'", git_xy)) notify.warn(string.format("Unrecognized git state '%s'", git_xy))
end end
return nil return nil
@@ -190,12 +190,12 @@ end
---Get the first icon as the sign if appropriate ---Get the first icon as the sign if appropriate
---@param node Node ---@param node Node
---@return string|nil name ---@return string|nil name
function DecoratorGit:sign_name(node) function GitDecorator:sign_name(node)
if self.icon_placement ~= "signcolumn" then if self.icon_placement ~= "signcolumn" then
return return
end end
local icons = self:calculate_icons(node) local icons = self:icons(node)
if icons and #icons > 0 then if icons and #icons > 0 then
return icons[1].hl[1] return icons[1].hl[1]
end end
@@ -203,9 +203,9 @@ end
---Git highlight: git.enable, renderer.highlight_git and node has status ---Git highlight: git.enable, renderer.highlight_git and node has status
---@param node Node ---@param node Node
---@return string|nil group ---@return string? highlight_group
function DecoratorGit:calculate_highlight(node) function GitDecorator:highlight_group(node)
if not node or not self.enabled or self.range == "none" then if self.highlight_range == "none" then
return nil return nil
end end
@@ -221,4 +221,4 @@ function DecoratorGit:calculate_highlight(node)
end end
end end
return DecoratorGit return GitDecorator

View File

@@ -1,22 +1,22 @@
local Decorator = require("nvim-tree.renderer.decorator") local Decorator = require("nvim-tree.renderer.decorator")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
---@class (exact) DecoratorHidden: Decorator ---@class (exact) HiddenDecorator: Decorator
---@field icon HighlightedString? ---@field private explorer Explorer
local DecoratorHidden = Decorator:extend() ---@field private icon HighlightedString?
local HiddenDecorator = Decorator:extend()
---@class DecoratorHidden ---@class HiddenDecorator
---@overload fun(explorer: DecoratorArgs): DecoratorHidden ---@overload fun(args: DecoratorArgs): HiddenDecorator
---@protected ---@protected
---@param args DecoratorArgs ---@param args DecoratorArgs
function DecoratorHidden:new(args) function HiddenDecorator:new(args)
Decorator.new(self, { self.explorer = args.explorer
explorer = args.explorer,
enabled = true, self.enabled = true
hl_pos = args.explorer.opts.renderer.highlight_hidden or "none", self.highlight_range = self.explorer.opts.renderer.highlight_hidden or "none"
icon_placement = args.explorer.opts.renderer.icons.hidden_placement or "none", self.icon_placement = self.explorer.opts.renderer.icons.hidden_placement or "none"
})
if self.explorer.opts.renderer.icons.show.hidden then if self.explorer.opts.renderer.icons.show.hidden then
self.icon = { self.icon = {
@@ -29,18 +29,18 @@ end
---Hidden icon: renderer.icons.show.hidden and node starts with `.` (dotfile). ---Hidden icon: renderer.icons.show.hidden and node starts with `.` (dotfile).
---@param node Node ---@param node Node
---@return HighlightedString[]|nil icons ---@return HighlightedString[]? icons
function DecoratorHidden:calculate_icons(node) function HiddenDecorator:icons(node)
if self.enabled and node:is_dotfile() then if node:is_dotfile() then
return { self.icon } return { self.icon }
end end
end end
---Hidden highlight: renderer.highlight_hidden and node starts with `.` (dotfile). ---Hidden highlight: renderer.highlight_hidden and node starts with `.` (dotfile).
---@param node Node ---@param node Node
---@return string|nil group ---@return string? highlight_group
function DecoratorHidden:calculate_highlight(node) function HiddenDecorator:highlight_group(node)
if not self.enabled or self.range == "none" or not node:is_dotfile() then if self.highlight_range == "none" or not node:is_dotfile() then
return nil return nil
end end
@@ -51,4 +51,4 @@ function DecoratorHidden:calculate_highlight(node)
end end
end end
return DecoratorHidden return HiddenDecorator

View File

@@ -1,48 +1,52 @@
local Class = require("nvim-tree.classic") local Class = require("nvim-tree.classic")
---@alias DecoratorRange "none" | "icon" | "name" | "all"
---@alias DecoratorIconPlacement "none" | "before" | "after" | "signcolumn" | "right_align"
---Abstract Decorator ---Abstract Decorator
---Uses the factory pattern to instantiate child instances.
---@class (exact) Decorator: Class ---@class (exact) Decorator: Class
---@field protected explorer Explorer
---@field protected enabled boolean ---@field protected enabled boolean
---@field protected range DecoratorRange ---@field protected highlight_range nvim_tree.api.decorator.HighlightRange
---@field protected icon_placement DecoratorIconPlacement ---@field protected icon_placement nvim_tree.api.decorator.IconPlacement
local Decorator = Class:extend() local Decorator = Class:extend()
---@class (exact) DecoratorArgs ---@class (exact) DecoratorArgs
---@field explorer Explorer ---@field explorer Explorer
---@class (exact) AbstractDecoratorArgs: DecoratorArgs ---Abstract icon override, optionally implemented
---@field enabled boolean ---@param node Node
---@field hl_pos DecoratorRange ---@return HighlightedString? icon_node
---@field icon_placement DecoratorIconPlacement function Decorator:icon_node(node)
return self:nop(node)
---@protected
---@param args AbstractDecoratorArgs
function Decorator:new(args)
self.explorer = args.explorer
self.enabled = args.enabled
self.range = args.hl_pos
self.icon_placement = args.icon_placement
end end
---Maybe highlight groups ---Abstract icons, optionally implemented
---@protected
---@param node Node ---@param node Node
---@return string|nil icon highlight group ---@return HighlightedString[]? icons
---@return string|nil name highlight group function Decorator:icons(node)
function Decorator:groups_icon_name(node) self:nop(node)
end
---Abstract highlight group, optionally implemented
---@protected
---@param node Node
---@return string? highlight_group
function Decorator:highlight_group(node)
self:nop(node)
end
---Maybe highlight groups for icon and name
---@param node Node
---@return string? icon highlight group
---@return string? name highlight group
function Decorator:highlight_group_icon_name(node)
local icon_hl, name_hl local icon_hl, name_hl
if self.enabled and self.range ~= "none" then if self.enabled and self.highlight_range ~= "none" then
local hl = self:calculate_highlight(node) local hl = self:highlight_group(node)
if self.range == "all" or self.range == "icon" then if self.highlight_range == "all" or self.highlight_range == "icon" then
icon_hl = hl icon_hl = hl
end end
if self.range == "all" or self.range == "name" then if self.highlight_range == "all" or self.highlight_range == "name" then
name_hl = hl name_hl = hl
end end
end end
@@ -52,13 +56,13 @@ end
---Maybe icon sign ---Maybe icon sign
---@param node Node ---@param node Node
---@return string|nil name ---@return string? name
function Decorator:sign_name(node) function Decorator:sign_name(node)
if not self.enabled or self.icon_placement ~= "signcolumn" then if not self.enabled or self.icon_placement ~= "signcolumn" then
return return
end end
local icons = self:calculate_icons(node) local icons = self:icons(node)
if icons and #icons > 0 then if icons and #icons > 0 then
return icons[1].hl[1] return icons[1].hl[1]
end end
@@ -66,56 +70,40 @@ end
---Icons when "before" ---Icons when "before"
---@param node Node ---@param node Node
---@return HighlightedString[]|nil icons ---@return HighlightedString[]? icons
function Decorator:icons_before(node) function Decorator:icons_before(node)
if not self.enabled or self.icon_placement ~= "before" then if not self.enabled or self.icon_placement ~= "before" then
return return
end end
return self:calculate_icons(node) return self:icons(node)
end end
---Icons when "after" ---Icons when "after"
---@param node Node ---@param node Node
---@return HighlightedString[]|nil icons ---@return HighlightedString[]? icons
function Decorator:icons_after(node) function Decorator:icons_after(node)
if not self.enabled or self.icon_placement ~= "after" then if not self.enabled or self.icon_placement ~= "after" then
return return
end end
return self:calculate_icons(node) return self:icons(node)
end end
---Icons when "right_align" ---Icons when "right_align"
---@param node Node ---@param node Node
---@return HighlightedString[]|nil icons ---@return HighlightedString[]? icons
function Decorator:icons_right_align(node) function Decorator:icons_right_align(node)
if not self.enabled or self.icon_placement ~= "right_align" then if not self.enabled or self.icon_placement ~= "right_align" then
return return
end end
return self:calculate_icons(node) return self: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 end
---Define a sign ---Define a sign
---@protected ---@protected
---@param icon HighlightedString|nil ---@param icon HighlightedString?
function Decorator:define_sign(icon) function Decorator:define_sign(icon)
if icon and #icon.hl > 0 then if icon and #icon.hl > 0 then
local name = icon.hl[1] local name = icon.hl[1]

View File

@@ -3,26 +3,22 @@ local buffers = require("nvim-tree.buffers")
local Decorator = require("nvim-tree.renderer.decorator") local Decorator = require("nvim-tree.renderer.decorator")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
---@class (exact) DecoratorModified: Decorator ---@class (exact) ModifiedDecorator: Decorator
---@field icon HighlightedString? ---@field private explorer Explorer
local DecoratorModified = Decorator:extend() ---@field private icon HighlightedString?
local ModifiedDecorator = Decorator:extend()
---@class DecoratorModified ---@class ModifiedDecorator
---@overload fun(explorer: DecoratorArgs): DecoratorModified ---@overload fun(args: DecoratorArgs): ModifiedDecorator
---@protected ---@protected
---@param args DecoratorArgs ---@param args DecoratorArgs
function DecoratorModified:new(args) function ModifiedDecorator:new(args)
Decorator.new(self, { self.explorer = args.explorer
explorer = args.explorer,
enabled = true,
hl_pos = args.explorer.opts.renderer.highlight_modified or "none",
icon_placement = args.explorer.opts.renderer.icons.modified_placement or "none",
})
if not self.enabled then self.enabled = true
return self.highlight_range = self.explorer.opts.renderer.highlight_modified or "none"
end self.icon_placement = self.explorer.opts.renderer.icons.modified_placement or "none"
if self.explorer.opts.renderer.icons.show.modified then if self.explorer.opts.renderer.icons.show.modified then
self.icon = { self.icon = {
@@ -35,18 +31,18 @@ end
---Modified icon: modified.enable, renderer.icons.show.modified and node is modified ---Modified icon: modified.enable, renderer.icons.show.modified and node is modified
---@param node Node ---@param node Node
---@return HighlightedString[]|nil icons ---@return HighlightedString[]? icons
function DecoratorModified:calculate_icons(node) function ModifiedDecorator:icons(node)
if self.enabled and buffers.is_modified(node) then if buffers.is_modified(node) then
return { self.icon } return { self.icon }
end end
end end
---Modified highlight: modified.enable, renderer.highlight_modified and node is modified ---Modified highlight: modified.enable, renderer.highlight_modified and node is modified
---@param node Node ---@param node Node
---@return string|nil group ---@return string? highlight_group
function DecoratorModified:calculate_highlight(node) function ModifiedDecorator:highlight_group(node)
if not self.enabled or self.range == "none" or not buffers.is_modified(node) then if self.highlight_range == "none" or not buffers.is_modified(node) then
return nil return nil
end end
@@ -57,4 +53,4 @@ function DecoratorModified:calculate_highlight(node)
end end
end end
return DecoratorModified return ModifiedDecorator

View File

@@ -2,31 +2,31 @@ local buffers = require("nvim-tree.buffers")
local Decorator = require("nvim-tree.renderer.decorator") local Decorator = require("nvim-tree.renderer.decorator")
---@class (exact) DecoratorOpened: Decorator ---@class (exact) OpenDecorator: Decorator
---@field icon HighlightedString|nil ---@field private explorer Explorer
local DecoratorOpened = Decorator:extend() ---@field private icon HighlightedString|nil
local OpenDecorator = Decorator:extend()
---@class DecoratorOpened ---@class OpenDecorator
---@overload fun(explorer: DecoratorArgs): DecoratorOpened ---@overload fun(args: DecoratorArgs): OpenDecorator
---@protected ---@protected
---@param args DecoratorArgs ---@param args DecoratorArgs
function DecoratorOpened:new(args) function OpenDecorator:new(args)
Decorator.new(self, { self.explorer = args.explorer
explorer = args.explorer,
enabled = true, self.enabled = true
hl_pos = args.explorer.opts.renderer.highlight_opened_files or "none", self.highlight_range = self.explorer.opts.renderer.highlight_opened_files or "none"
icon_placement = "none", self.icon_placement = "none"
})
end end
---Opened highlight: renderer.highlight_opened_files and node has an open buffer ---Opened highlight: renderer.highlight_opened_files and node has an open buffer
---@param node Node ---@param node Node
---@return string|nil group ---@return string? highlight_group
function DecoratorOpened:calculate_highlight(node) function OpenDecorator:highlight_group(node)
if self.range ~= "none" and buffers.is_opened(node) then if self.highlight_range ~= "none" and buffers.is_opened(node) then
return "NvimTreeOpenedHL" return "NvimTreeOpenedHL"
end end
end end
return DecoratorOpened return OpenDecorator

View File

@@ -0,0 +1,7 @@
local Decorator = require("nvim-tree.renderer.decorator")
---Exposed as nvim_tree.api.decorator.UserDecorator
---@class (exact) UserDecorator: Decorator
local UserDecorator = Decorator:extend()
return UserDecorator

View File

@@ -119,7 +119,7 @@ local function get_size(size)
if type(size) == "number" then if type(size) == "number" then
return size return size
elseif type(size) == "function" then elseif type(size) == "function" then
return size() return get_size(size())
end end
local size_as_number = tonumber(size:sub(0, -2)) local size_as_number = tonumber(size:sub(0, -2))
local percent_as_decimal = size_as_number / 100 local percent_as_decimal = size_as_number / 100
@@ -164,8 +164,10 @@ local function set_window_options_and_buffer()
local eventignore = vim.api.nvim_get_option("eventignore") ---@diagnostic disable-line: deprecated local eventignore = vim.api.nvim_get_option("eventignore") ---@diagnostic disable-line: deprecated
vim.api.nvim_set_option("eventignore", "all") ---@diagnostic disable-line: deprecated vim.api.nvim_set_option("eventignore", "all") ---@diagnostic disable-line: deprecated
-- #3009 vim.api.nvim_win_set_option does not set local scope without explicit winid.
-- Revert to opt_local instead of propagating it through for just the 0.10 path.
for k, v in pairs(M.View.winopts) do for k, v in pairs(M.View.winopts) do
vim.api.nvim_win_set_option(0, k, v) ---@diagnostic disable-line: deprecated vim.opt_local[k] = v
end end
vim.api.nvim_set_option("eventignore", eventignore) ---@diagnostic disable-line: deprecated vim.api.nvim_set_option("eventignore", eventignore) ---@diagnostic disable-line: deprecated
@@ -319,8 +321,19 @@ local function grow()
max_width = get_width(M.View.max_width) - padding max_width = get_width(M.View.max_width) - padding
end end
for _, l in pairs(lines) do local ns_id = vim.api.nvim_get_namespaces()["NvimTreeExtmarks"]
for line_nr, l in pairs(lines) do
local count = vim.fn.strchars(l) local count = vim.fn.strchars(l)
-- also add space for right-aligned icons
local extmarks = vim.api.nvim_buf_get_extmarks(M.get_bufnr(), ns_id, { line_nr, 0 }, { line_nr, -1 }, { details = true })
for _, extmark in ipairs(extmarks) do
local virt_texts = extmark[4].virt_text
if virt_texts then
for _, virt_text in ipairs(virt_texts) do
count = count + vim.fn.strchars(virt_text[1])
end
end
end
if resizing_width < count then if resizing_width < count then
resizing_width = count resizing_width = count
end end

View File

@@ -4,6 +4,8 @@ local utils = require("nvim-tree.utils")
local Class = require("nvim-tree.classic") local Class = require("nvim-tree.classic")
local MESSAGE_EMFILE = "fs.inotify.max_user_watches exceeded, see https://github.com/nvim-tree/nvim-tree.lua/wiki/Troubleshooting"
local FS_EVENT_FLAGS = { local FS_EVENT_FLAGS = {
-- inotify or equivalent will be used; fallback to stat has not yet been implemented -- inotify or equivalent will be used; fallback to stat has not yet been implemented
stat = false, stat = false,
@@ -75,6 +77,18 @@ function Event:start()
local event_cb = vim.schedule_wrap(function(err, filename) local event_cb = vim.schedule_wrap(function(err, filename)
if err then if err then
log.line("watcher", "event_cb '%s' '%s' FAIL : %s", self.path, filename, err) log.line("watcher", "event_cb '%s' '%s' FAIL : %s", self.path, filename, err)
-- do nothing if watchers have already been disabled
if not M.config.filesystem_watchers.enable then
return
end
-- EMFILE is catastrophic
if name == "EMFILE" then
M.disable_watchers(MESSAGE_EMFILE)
return
end
local message = string.format("File system watcher failed (%s) for path %s, halting watcher.", err, self.path) local message = string.format("File system watcher failed (%s) for path %s, halting watcher.", err, self.path)
if err == "EPERM" and (utils.is_windows or utils.is_wsl) then if err == "EPERM" and (utils.is_windows or utils.is_wsl) then
-- on directory removal windows will cascade the filesystem events out of order -- on directory removal windows will cascade the filesystem events out of order
@@ -94,7 +108,7 @@ function Event:start()
rc, _, name = self.fs_event:start(self.path, FS_EVENT_FLAGS, event_cb) rc, _, name = self.fs_event:start(self.path, FS_EVENT_FLAGS, event_cb)
if rc ~= 0 then if rc ~= 0 then
if name == "EMFILE" then if name == "EMFILE" then
M.disable_watchers("fs.inotify.max_user_watches exceeded, see https://github.com/nvim-tree/nvim-tree.lua/wiki/Troubleshooting") M.disable_watchers(MESSAGE_EMFILE)
else else
notify.warn(string.format("Could not start the fs_event watcher for path %s : %s", self.path, name)) notify.warn(string.format("Could not start the fs_event watcher for path %s : %s", self.path, name))
end end