Compare commits

...

21 Commits
v1.13.0 ... v1

Author SHA1 Message Date
github-actions[bot]
321bc61580 chore(master): release nvim-tree 1.14.0 (#3146)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-12 10:43:00 +10:00
Alexander Courtis
442513648c perf(#3171): cache toplevel for untracked (#3185) 2025-08-12 10:34:24 +10:00
Alexander Courtis
000ca6bcdd docs: CONTRIBUTING.md: add diagnostics and backward compatibility 2025-08-11 14:22:46 +10:00
Kasper Kondzielski
1b876db049 feat(#2789): add optional function expand_until to api.tree.expand_all and api.node.expand (#3166)
* feat: Allow to expand nodes until certain condition is met

* Fix warnings

* Restore original position of edit function

* Rename field to match the api method name

* Rename ApiTreeExpandAllOpts to ApiTreeExpandOpts

* Remove toggle_descend_until

* Remove redundant empty line

* Update :help for changed methods

* Fix partial expansion of grouped nodes

* Fix lint error

* Fix linting error

* Fix incorrect open/close indicator state

* Update docs

* Rename descend_until option to expand_until

* Always check directory expansion limit

* Fix linter errors

* Ignore unused param warning

* Apply suggestions from code review

* simplify MAX_FOLDER_DISCOVERY warning

* fix bad comment whitespace

---------

Co-authored-by: ghostbuster91 <ghostbuster91@users.noreply.github.com>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-08-11 13:50:26 +10:00
Julien Vincent
0a52012d61 feat(#2685): highlight git new tracked with NvimTreeGitFileNewHL (#3176)
Highlight new, tracked files in working copy

Currently nvim-tree doesn't highlight new, tracked files in the working
copy/directory.

This change assigns the `NvimTreeGitFileNewHL` highlight to the `" A"`
git porcelain status marker.

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-08-10 15:00:38 +10:00
Alexander Courtis
a4699c0904 revert(#3180, #3177): resolve live filter failures (#3183)
* Revert "fix(#3172): live filter exception (#3173)"

This reverts commit 0a7fcdf3f8.

* Revert "refactor(#2826): move view to instanced window class (#3153)"

This reverts commit 0a06f65bf0.

* feat(#3157): add view.cursorlineopt
2025-08-10 14:45:25 +10:00
Alexander Courtis
9b289abd69 revert(#3180, #3177): invalid group or tabpage (#3181)
* Revert "feat(#2826): allow only one window with nvim-tree buffer per tab (#3174)"

This reverts commit dd2364d680.

* Revert "refactor(#2826): View tracks winids and bufnrs via events, unused for now (#3170)"

This reverts commit 65bae44922.

* Revert "refactor(#2826): remove view debug/cc, enable new codepaths for get_winid and get_bufnr (#3169)"

This reverts commit a9156c0139.
2025-08-10 11:18:31 +10:00
Alexander Courtis
dd2364d680 feat(#2826): allow only one window with nvim-tree buffer per tab (#3174)
* feat(#2826): allow only one window with nvim-tree buffer per tab

* feat(#2826): remove globals.BUFNR_BY_TABID

* Revert "feat(#2826): remove globals.BUFNR_BY_TABID"

This reverts commit 2651f9b34a.

* feat(#2826): remove unused View.winid_by_tabid

* feat(#2826): add feature gate experimental.close_other_windows_in_tab
2025-08-05 15:29:25 +10:00
Tomasz N
9a05b9e9f9 perf(#3171): use vim.system() instead of vim.fn.system() to execute git toplevel (#3175)
* fix(#3171): use vim.system() to determine git toplevel

* Don't use vim.trim

* Ensure sdtout is a string

* Keep Nvim 0.9 compatibility

* Use vim.system to query git config for status.showUntrackedFiles too
2025-08-05 13:23:51 +10:00
Alexander Courtis
0a7fcdf3f8 fix(#3172): live filter exception (#3173) 2025-08-04 11:51:42 +10:00
Alexander Courtis
65bae44922 refactor(#2826): View tracks winids and bufnrs via events, unused for now (#3170)
* refactor(#2826): add View.bufnr and use for all public methods

* use View.bufnr for all internal methods

* revert View.bufnr, add buffer local autocommands

* View revert to globals.BUFNR_BY_TABID until bufnr and winid are tracked for the view

* View track winids and bufnrs via events
2025-07-29 14:19:24 +10:00
Alexander Courtis
a9156c0139 refactor(#2826): remove view debug/cc, enable new codepaths for get_winid and get_bufnr (#3169)
* refactor(#2826): fuller error messages

* refactor(#2826): winnr->winid in view/globals, remove redundant get_winid and get_bufnr calls

* refactor(#2826): winnr->winid consistently

* refactor(#2826): consistent use of buffer registry, tidy, add todos

* refactor(#2826): remove unnecessary view members float, hide_root_folder; use explorer opts

* refactor(#2826): remove unused view members centralize_selection and preserve_window_proportions

* refactor(#2826): remove unused view member height

* refactor(#2826): temporarily reuse BUFNR_PER_TAB in view constructor

* refactor(#2826): get_winid returns new after consistency check

* refactor(#2826): globals.TABPAGES -> WINID_PER_TAB

* refactor(#2826): consistent naming of tabid

* refactor(#2826): more consistency checking

* refactor(#2826): more consistency checking

* refactor(#2826): move global CURSORS to view member

* Revert "refactor(#2826): move global CURSORS to view member"

This reverts commit d84dfad1c3.

* refactor(#2826): move global CURSORS to view member

* refactor(#2826): consistency check returns new

* refactor(#2826): remove consistency checks, enabling new path for view get_winid and get_bufnr

* refactor(#2826): restore CURSORS global
2025-07-28 12:44:17 +10:00
alexfinger21
10db6943cb fix(#3077): deleting a directory containing symlinked directory will delete the contents of the linked directory (#3168)
* fix(#3077) deleting a directory containing symlink file will delete all content inside the symlink

* fix(#3077): add diagnostic override TODO

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-07-28 09:42:38 +10:00
Tomasz N
543ed3cac2 fix(picker): exclude full_name window id from the choice (#3165)
Problem: `full_name` window from is considered as usable by picker
Solution: exclude its ID (also true for nil values)

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-07-21 13:22:56 +10:00
Alexander Courtis
2a386fe567 ci: luals_version 3.13.9 -> 3.15.0 (#3167) 2025-07-21 13:22:07 +10:00
Alexander Courtis
b0b49552c9 docs: polish example decorator (#3160) 2025-06-21 10:57:48 +10:00
Alexander Courtis
8eb5e0bfd1 feat(#3157): add view.cursorlineopt (#3158)
fix(#3157): add view.cursorlineopt
2025-06-20 13:46:38 +10:00
Alexander Courtis
0a06f65bf0 refactor(#2826): move view to instanced window class (#3153)
* refactor(#2826): singleton View class, WIP

* refactor(#2826): singleton View class, WIP

* refactor(#2826): singleton View class, WIP

* refactor(#2826): singleton View class, WIP

* refactor(#2826): singleton View class, WIP

* refactor(#2826): singleton View class, WIP

* refactor(#2826): singleton View class, WIP

* refactor(#2826): singleton View class

* refactor(#2826): View is an Explorer member

* refactor(#2826): move autocmds to Explorer

* refactor(#2826): API uses Explorer's View

* refactor(#2826): move View into Explorer package

* refactor(#2826): retain necessary view globals

* refactor(#2826): move all winhl to appearance constants

* refactor(#2826): add lifecycle logging to all Explorer members

* refactor(#2826): fix bad cherry-pick

* refactor(#2826): better enumerate_options function

* refactor(#2826): add View.tab_line for debugging

* refactor(#2826): default lifecycle log off

* refactor(#2826): add experimental.multi_instance_debug, split globals out of view, move diagnostics to its own module

* refactor(#2826): instrument View:get_winnr

* refactor(#2826): instrument View:setup_tabpage

* refactor(#2826): instrument View:set_current_win, View:prevent_buffer_override

* refactor(#2826): instrument View:get_bufnr

* refactor(#2826): track member bufnr -> winid with global

* refactor(#2826): tidy experiment names and logs

* vim: nvim-tree: track bufnr via buffer-update channel

* vim: nvim-tree: more logging

* vim: nvim-tree: revert: track bufnr via buffer-update channel

* refactor(#2826): notify error on view winid and bufnr mismatches

* refactor(#2826): notify error on view winid and bufnr mismatches

* refactor(#2826): explorer init logging
2025-06-19 15:45:55 +10:00
Yavorski
d54a1875a9 fix: invalid window id for popup info window (#3147) 2025-06-17 16:59:28 +10:00
Garry Filakhtov
aa087788d7 docs: fix renderer.icons.bookmarks_placement parameter, misspelling (#3150)
Fix minor documentation issues

Add a missing double quotes around the default value for
`nvim-tree.renderer.icons.bookmarks_placement` config value and fix
spelling of `bookmarked`.
2025-06-17 10:23:34 +10:00
phanium
d87b41ca53 fix: window picker ignore hidden window (#3145) 2025-06-15 15:04:47 +10:00
15 changed files with 283 additions and 74 deletions

View File

@@ -49,7 +49,7 @@ jobs:
strategy: strategy:
matrix: matrix:
nvim_version: [ stable, nightly ] nvim_version: [ stable, nightly ]
luals_version: [ 3.13.9 ] luals_version: [ 3.15.0 ]
env: env:
VIMRUNTIME: /home/runner/nvim-${{ matrix.nvim_version }}/share/nvim/runtime VIMRUNTIME: /home/runner/nvim-${{ matrix.nvim_version }}/share/nvim/runtime

View File

@@ -1,3 +1,3 @@
{ {
".": "1.13.0" ".": "1.14.0"
} }

View File

@@ -1,5 +1,37 @@
# Changelog # Changelog
## [1.14.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.13.0...nvim-tree-v1.14.0) (2025-08-12)
### Features
* **#2685:** highlight git new tracked with NvimTreeGitFileNewHL ([#3176](https://github.com/nvim-tree/nvim-tree.lua/issues/3176)) ([0a52012](https://github.com/nvim-tree/nvim-tree.lua/commit/0a52012d611f3c1492b8d2aba363fabf734de91d))
* **#2789:** add optional function expand_until to api.tree.expand_all and api.node.expand ([#3166](https://github.com/nvim-tree/nvim-tree.lua/issues/3166)) ([1b876db](https://github.com/nvim-tree/nvim-tree.lua/commit/1b876db04903b93c78c97fd3f3dd85d59eeef5ff))
* **#2826:** allow only one window with nvim-tree buffer per tab ([#3174](https://github.com/nvim-tree/nvim-tree.lua/issues/3174)) ([dd2364d](https://github.com/nvim-tree/nvim-tree.lua/commit/dd2364d6802f7f57a98acb8b545ed484c6697626))
* **#3157:** add view.cursorlineopt ([#3158](https://github.com/nvim-tree/nvim-tree.lua/issues/3158)) ([8eb5e0b](https://github.com/nvim-tree/nvim-tree.lua/commit/8eb5e0bfd1c4da6efc03ab0c1ccf463dbaae831e))
### Bug Fixes
* **#3077:** deleting a directory containing symlinked directory will delete the contents of the linked directory ([#3168](https://github.com/nvim-tree/nvim-tree.lua/issues/3168)) ([10db694](https://github.com/nvim-tree/nvim-tree.lua/commit/10db6943cb40625941a35235eeb385ffdfbf827a))
* **#3157:** add view.cursorlineopt ([8eb5e0b](https://github.com/nvim-tree/nvim-tree.lua/commit/8eb5e0bfd1c4da6efc03ab0c1ccf463dbaae831e))
* **#3172:** live filter exception ([#3173](https://github.com/nvim-tree/nvim-tree.lua/issues/3173)) ([0a7fcdf](https://github.com/nvim-tree/nvim-tree.lua/commit/0a7fcdf3f8ba208f4260988a198c77ec11748339))
* invalid window id for popup info window ([#3147](https://github.com/nvim-tree/nvim-tree.lua/issues/3147)) ([d54a187](https://github.com/nvim-tree/nvim-tree.lua/commit/d54a1875a91e1a705795ea26074795210b92ce7f))
* **picker:** exclude full_name window id from the choice ([#3165](https://github.com/nvim-tree/nvim-tree.lua/issues/3165)) ([543ed3c](https://github.com/nvim-tree/nvim-tree.lua/commit/543ed3cac212dc3993ef9f042f6c0812e34ddd43))
* window picker ignore hidden window ([#3145](https://github.com/nvim-tree/nvim-tree.lua/issues/3145)) ([d87b41c](https://github.com/nvim-tree/nvim-tree.lua/commit/d87b41ca537e2131622d48a6c25ccf2fbe0e5d62))
### Performance Improvements
* **#3171:** cache toplevel for untracked ([#3185](https://github.com/nvim-tree/nvim-tree.lua/issues/3185)) ([4425136](https://github.com/nvim-tree/nvim-tree.lua/commit/442513648c6936e754c3308a1c58591a399493e5))
* **#3171:** use vim.system() instead of vim.fn.system() to execute git toplevel ([#3175](https://github.com/nvim-tree/nvim-tree.lua/issues/3175)) ([9a05b9e](https://github.com/nvim-tree/nvim-tree.lua/commit/9a05b9e9f928856ca23dbf876fab372003180c3f))
### Reverts
* **#3180, #3177:** invalid group or tabpage ([#3181](https://github.com/nvim-tree/nvim-tree.lua/issues/3181)) ([9b289ab](https://github.com/nvim-tree/nvim-tree.lua/commit/9b289abd6998e30fd24cbc9919e0b0cbed6364ce))
* **#3180, #3177:** resolve live filter failures ([#3183](https://github.com/nvim-tree/nvim-tree.lua/issues/3183)) ([a4699c0](https://github.com/nvim-tree/nvim-tree.lua/commit/a4699c0904103e7767334f6da05f5c2ea5514845))
## [1.13.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.12.0...nvim-tree-v1.13.0) (2025-06-14) ## [1.13.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.12.0...nvim-tree-v1.13.0) (2025-06-14)

View File

@@ -4,6 +4,30 @@ Thank you for contributing.
See [wiki: Development](https://github.com/nvim-tree/nvim-tree.lua/wiki/Development) for environment setup, tips and tools. See [wiki: Development](https://github.com/nvim-tree/nvim-tree.lua/wiki/Development) for environment setup, tips and tools.
<!--
https://github.com/jonschlinkert/markdown-toc
markdown-toc --maxdepth=2 -i CONTRIBUTING.md
-->
<!-- toc -->
- [Tools](#tools)
- [Quality](#quality)
* [lint](#lint)
* [style](#style)
* [check](#check)
- [Diagnostics](#diagnostics)
- [Backwards Compatibility](#backwards-compatibility)
- [Adding New Actions](#adding-new-actions)
- [Documentation](#documentation)
* [Opts](#opts)
* [API](#api)
- [Windows](#windows)
- [Pull Request](#pull-request)
* [Subject](#subject)
<!-- tocstop -->
# Tools # Tools
Following are used during CI and strongly recommended during local development. Following are used during CI and strongly recommended during local development.
@@ -72,6 +96,30 @@ curl -L "https://github.com/LuaLS/lua-language-server/releases/download/3.9.1/lu
PATH="luals/bin:${PATH}" make check PATH="luals/bin:${PATH}" make check
``` ```
# Diagnostics
Diagnostics issues may not be suppressed. See [luals](https://luals.github.io) documentation for details on how to structure the code and comments.
Suppressions are permitted only in the following cases:
- Backwards compatibility shims
- neovim API metadata incorrect, awaiting upstream fix
- classic class framework
# Backwards Compatibility
Whenever new neovim API is introduced, please ensure that it is available in older versions. See `:help deprecated.txt` and `$VIMRUNTIME/lua/vim/_meta/api.lua`
See `nvim-tree.setup` for the oldest supported version of neovim. If the API is not availble in that version, a backwards compatibility shim must be used e.g.
```lua
if vim.fn.has("nvim-0.10") == 1 then
modified = vim.api.nvim_get_option_value("modified", { buf = target_bufid })
else
modified = vim.api.nvim_buf_get_option(target_bufid, "modified") ---@diagnostic disable-line: deprecated
end
```
# Adding New Actions # Adding New Actions
To add a new action, add a file in `actions/name-of-the-action.lua`. You should export a `setup` function if some configuration is needed. To add a new action, add a file in `actions/name-of-the-action.lua`. You should export a `setup` function if some configuration is needed.

View File

@@ -398,6 +398,7 @@ Following is the default configuration. See |nvim-tree-opts| for details. >lua
view = { view = {
centralize_selection = false, centralize_selection = false,
cursorline = true, cursorline = true,
cursorlineopt = "both",
debounce_delay = 15, debounce_delay = 15,
side = "left", side = "left",
preserve_window_proportions = false, preserve_window_proportions = false,
@@ -770,6 +771,10 @@ initially centralized, see |zz|.
Enable |cursorline| in the tree window. Enable |cursorline| in the tree window.
Type: `boolean`, Default: `true` Type: `boolean`, Default: `true`
*nvim-tree.view.cursorlineopt*
Set |cursorlineopt| in the tree window.
Type: `string`, Default: `"both"`
*nvim-tree.view.debounce_delay* *nvim-tree.view.debounce_delay*
Idle milliseconds before some reload / refresh operations. Idle milliseconds before some reload / refresh operations.
Increase if you experience performance issues around screen refresh. Increase if you experience performance issues around screen refresh.
@@ -1066,7 +1071,7 @@ Configuration options for icons.
*nvim-tree.renderer.icons.bookmarks_placement* *nvim-tree.renderer.icons.bookmarks_placement*
Bookmark icon placement. Bookmark icon placement.
Type: `string`, Default: `signcolumn` Type: `string`, Default: `"signcolumn"`
*nvim-tree.renderer.icons.padding.icon* *nvim-tree.renderer.icons.padding.icon*
Inserted between icon and filename. Inserted between icon and filename.
@@ -1380,7 +1385,7 @@ delete/wipe. A reload or filesystem event will result in an update.
Type: `boolean`, Default: `false` Type: `boolean`, Default: `false`
*nvim-tree.filters.no_bookmark* *nvim-tree.filters.no_bookmark*
Do not show files that are not bookarked. Do not show files that are not bookmarked.
Toggle via |nvim-tree-api.tree.toggle_no_bookmark_filter()|, default `M` Toggle via |nvim-tree-api.tree.toggle_no_bookmark_filter()|, default `M`
Enabling this is not useful as there is no means yet to persist bookmarks. Enabling this is not useful as there is no means yet to persist bookmarks.
Type: `boolean`, Default: `false` Type: `boolean`, Default: `false`
@@ -1514,11 +1519,6 @@ Configuration options for opening a file from nvim-tree.
Resizes the tree when opening a file. Resizes the tree when opening a file.
Type: `boolean`, Default: `true` Type: `boolean`, Default: `true`
*nvim-tree.experimental.actions.open_file.relative_path*
Buffers opened by nvim-tree will use with relative paths instead of
absolute.
Type: `boolean`, Default: `true`
*nvim-tree.actions.open_file.window_picker* *nvim-tree.actions.open_file.window_picker*
Window picker configuration. Window picker configuration.
@@ -1841,11 +1841,17 @@ tree.collapse_all({opts}) *nvim-tree-api.tree.collapse_all()*
Options: ~ Options: ~
• {keep_buffers} (boolean) do not collapse nodes with open buffers. • {keep_buffers} (boolean) do not collapse nodes with open buffers.
tree.expand_all({node}) *nvim-tree-api.tree.expand_all()* tree.expand_all({node}, {opts}) *nvim-tree-api.tree.expand_all()*
Recursively expand all nodes under the tree root or specified folder. Recursively expand all nodes under the tree root or specified folder.
Parameters: ~ Parameters: ~
• {node} (Node|nil) folder • {node} (Node|nil) folder
• {opts} (ApiTreeExpandOpts) optional parameters
Options: ~
• {expand_until} ((fun(expansion_count: integer, node: Node?): boolean)?)
Return true if {node} should be expanded.
{expansion_count} is the total number of folders expanded.
*nvim-tree-api.tree.toggle_enable_filters()* *nvim-tree-api.tree.toggle_enable_filters()*
tree.toggle_enable_filters() tree.toggle_enable_filters()
@@ -2279,12 +2285,18 @@ node.buffer.wipe({node}, {opts}) *nvim-tree-api.node.buffer.wipe()*
Options: ~ Options: ~
• {force} (boolean) wipe even if buffer is modified, default false • {force} (boolean) wipe even if buffer is modified, default false
node.expand({node}) *nvim-tree-api.node.expand()* node.expand({node}, {opts}) *nvim-tree-api.node.expand()*
Recursively expand all nodes under a directory or a file's parent Recursively expand all nodes under a directory or a file's parent
directory. directory.
Parameters: ~ Parameters: ~
• {node} (Node|nil) file or folder • {node} (Node|nil) file or folder
• {opts} (ApiTreeExpandOpts) optional parameters
Options: ~
• {expand_until} ((fun(expansion_count: integer, node: Node?): boolean)?)
Return true if {node} should be expanded.
{expansion_count} is the total number of folders expanded.
node.collapse({node}, {opts}) *nvim-tree-api.node.collapse()* node.collapse({node}, {opts}) *nvim-tree-api.node.collapse()*
Collapse the tree under a directory or a file's parent directory. Collapse the tree under a directory or a file's parent directory.
@@ -2928,43 +2940,71 @@ Decorators may:
- Set highlight group for the name or icons - Set highlight group for the name or icons
- Override node icon - Override node icon
Specify decorators and their precedence via |nvim-tree.renderer.decorators| Create a `nvim_tree.api.decorator.UserDecorator` class and register it with
e.g. defaults with a user decorator class being overridden only by Cut: >lua precedence via |nvim-tree.renderer.decorators|
{
"Git", See |nvim-tree-decorator-example|
"Open",
"Hidden", See `nvim-tree/_meta/api_decorator.lua` for full class documentation.
"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* 11.1. DECORATOR EXAMPLE *nvim-tree-decorator-example*
A decorator class for nodes named "example", overridind all builtin decorators
except for Cut.
- Highlights node name with `IncSearch`
- Creates two icons `"1"` and `"2"` placed after the node name, highlighted with
`DiffAdd` and `DiffText`
- Replaces the node icon with `"N"`, highlighted with `Error `
Create a class file `~/.config/nvim/lua/my-decorator.lua`
Require and register it during |nvim-tree-setup|:
>lua
local MyDecorator = require("my-decorator")
require("nvim-tree").setup({
renderer = {
decorators = {
"Git",
"Open",
"Hidden",
"Modified",
"Bookmark",
"Diagnostics",
"Copied",
MyDecorator,
"Cut",
},
},
})
<
Contents of `my-decorator.lua`:
>lua >lua
---Create your decorator class
---@class (exact) MyDecorator: nvim_tree.api.decorator.UserDecorator ---@class (exact) MyDecorator: nvim_tree.api.decorator.UserDecorator
---@field private my_icon nvim_tree.api.HighlightedString ---@field private my_icon1 nvim_tree.api.HighlightedString
---@field private my_icon2 nvim_tree.api.HighlightedString
---@field private my_icon_node nvim_tree.api.HighlightedString
---@field private my_highlight_group string
local MyDecorator = require("nvim-tree.api").decorator.UserDecorator:extend() local MyDecorator = require("nvim-tree.api").decorator.UserDecorator:extend()
---Mandatory constructor :new() will be called once per tree render, with no arguments. ---Mandatory constructor :new() will be called once per tree render, with no arguments.
function MyDecorator:new() function MyDecorator:new()
self.enabled = true self.enabled = true
self.highlight_range = "all" self.highlight_range = "name"
self.icon_placement = "signcolumn" self.icon_placement = "after"
-- create your icon once, for convenience -- create your icons and highlights once, applied to every node
self.my_icon = { str = "I", hl = { "MyIcon" } } self.my_icon1 = { str = "1", hl = { "DiffAdd" } }
self.my_icon2 = { str = "2", hl = { "DiffText" } }
self.my_icon_node = { str = "N", hl = { "Error" } }
self.my_highlight_group = "IncSearch"
-- Define the icon sign only once -- Define the icon signs only once
-- Only needed if you are using icon_placement = "signcolumn" -- Only needed if you are using icon_placement = "signcolumn"
self:define_sign(self.my_icon) -- self:define_sign(self.my_icon1)
-- self:define_sign(self.my_icon2)
end end
---Override node icon ---Override node icon
@@ -2972,33 +3012,35 @@ See `nvim-tree/_meta/api_decorator.lua` for full
---@return nvim_tree.api.HighlightedString? icon_node ---@return nvim_tree.api.HighlightedString? icon_node
function MyDecorator:icon_node(node) function MyDecorator:icon_node(node)
if node.name == "example" then if node.name == "example" then
return self.my_icon return self.my_icon_node
else else
return nil return nil
end end
end end
---Return one icon for DecoratorIconPlacement ---Return two icons for DecoratorIconPlacement "after"
---@param node nvim_tree.api.Node ---@param node nvim_tree.api.Node
---@return nvim_tree.api.HighlightedString[]? icons ---@return nvim_tree.api.HighlightedString[]? icons
function MyDecorator:icons(node) function MyDecorator:icons(node)
if node.name == "example" then if node.name == "example" then
return { self.my_icon } return { self.my_icon1, self.my_icon2, }
else else
return nil return nil
end end
end end
---Exactly one highlight group for DecoratorHighlightRange ---Exactly one highlight group for DecoratorHighlightRange "name"
---@param node nvim_tree.api.Node ---@param node nvim_tree.api.Node
---@return string? highlight_group ---@return string? highlight_group
function MyDecorator:highlight_group(node) function MyDecorator:highlight_group(node)
if node.name == "example" then if node.name == "example" then
return "MyHighlight" return self.my_highlight_group
else else
return nil return nil
end end
end end
return MyDecorator
< <
============================================================================== ==============================================================================
12. OS SPECIFIC RESTRICTIONS *nvim-tree-os-specific* 12. OS SPECIFIC RESTRICTIONS *nvim-tree-os-specific*
@@ -3008,9 +3050,6 @@ Windows WSL and PowerShell
- Executable file detection is disabled as this is non-performant and can - Executable file detection is disabled as this is non-performant and can
freeze nvim freeze nvim
- Some filesystem watcher error related to permissions will not be reported - Some filesystem watcher error related to permissions will not be reported
- Some users have reported unspecified issues with
|nvim-tree.experimental.actions.open_file.relative_path|. Please report any
issues or disable this feature.
============================================================================== ==============================================================================
13. NETRW *nvim-tree-netrw* 13. NETRW *nvim-tree-netrw*
@@ -3145,7 +3184,6 @@ highlight group is not, hard linking as follows: >
|nvim-tree.diagnostics.show_on_open_dirs| |nvim-tree.diagnostics.show_on_open_dirs|
|nvim-tree.disable_netrw| |nvim-tree.disable_netrw|
|nvim-tree.experimental| |nvim-tree.experimental|
|nvim-tree.experimental.actions.open_file.relative_path|
|nvim-tree.filesystem_watchers.debounce_delay| |nvim-tree.filesystem_watchers.debounce_delay|
|nvim-tree.filesystem_watchers.enable| |nvim-tree.filesystem_watchers.enable|
|nvim-tree.filesystem_watchers.ignore_dirs| |nvim-tree.filesystem_watchers.ignore_dirs|
@@ -3266,6 +3304,7 @@ highlight group is not, hard linking as follows: >
|nvim-tree.update_focused_file.update_root.ignore_list| |nvim-tree.update_focused_file.update_root.ignore_list|
|nvim-tree.view.centralize_selection| |nvim-tree.view.centralize_selection|
|nvim-tree.view.cursorline| |nvim-tree.view.cursorline|
|nvim-tree.view.cursorlineopt|
|nvim-tree.view.debounce_delay| |nvim-tree.view.debounce_delay|
|nvim-tree.view.float| |nvim-tree.view.float|
|nvim-tree.view.float.enable| |nvim-tree.view.float.enable|

View File

@@ -273,6 +273,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
view = { view = {
centralize_selection = false, centralize_selection = false,
cursorline = true, cursorline = true,
cursorlineopt = "both",
debounce_delay = 15, debounce_delay = 15,
side = "left", side = "left",
preserve_window_proportions = false, preserve_window_proportions = false,

View File

@@ -69,9 +69,16 @@ local function remove_dir(cwd)
-- Type must come from fs_stat and not fs_scandir_next to maintain sshfs compatibility -- Type must come from fs_stat and not fs_scandir_next to maintain sshfs compatibility
local stat = vim.loop.fs_stat(new_cwd) local stat = vim.loop.fs_stat(new_cwd)
local type = stat and stat.type or nil -- TODO remove once 0.12 is the minimum neovim version
-- path incorrectly specified as an integer, fixed upstream for neovim 0.12 https://github.com/neovim/neovim/pull/33872
---@diagnostic disable-next-line: param-type-mismatch
local lstat = vim.loop.fs_lstat(new_cwd)
if type == "directory" then local type = stat and stat.type or nil
-- Checks if file is a link file to ensure deletion of the symlink instead of the file it points to
local ltype = lstat and lstat.type or nil
if type == "directory" and ltype ~= "link" then
local success = remove_dir(new_cwd) local success = remove_dir(new_cwd)
if not success then if not success then
return false return false

View File

@@ -57,7 +57,9 @@ end
function M.close_popup() function M.close_popup()
if current_popup ~= nil then if current_popup ~= nil then
vim.api.nvim_win_close(current_popup.winnr, true) if vim.api.nvim_win_is_valid(current_popup.winnr) then
vim.api.nvim_win_close(current_popup.winnr, true)
end
vim.cmd("augroup NvimTreeRemoveFilePopup | au! CursorMoved | augroup END") vim.cmd("augroup NvimTreeRemoveFilePopup | au! CursorMoved | augroup END")
current_popup = nil current_popup = nil

View File

@@ -2,6 +2,7 @@
local lib = require("nvim-tree.lib") local lib = require("nvim-tree.lib")
local notify = require("nvim-tree.notify") local notify = require("nvim-tree.notify")
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local full_name = require("nvim-tree.renderer.components.full-name")
local view = require("nvim-tree.view") local view = require("nvim-tree.view")
local M = {} local M = {}
@@ -39,7 +40,12 @@ local function usable_win_ids()
end end
local win_config = vim.api.nvim_win_get_config(id) local win_config = vim.api.nvim_win_get_config(id)
return id ~= tree_winid and win_config.focusable and not win_config.external or false return id ~= tree_winid
and id ~= full_name.popup_win
and win_config.focusable
and not win_config.hide
and not win_config.external
or false
end, win_ids) end, win_ids)
end end

View File

@@ -27,20 +27,66 @@ local function expand(node)
end end
end end
---@param expansion_count integer ---@param should_descend fun(expansion_count: integer, node: Node): boolean
---@return fun(expansion_count: integer, node: Node): boolean
local function limit_folder_discovery(should_descend)
return function(expansion_count, node)
local should_halt = expansion_count >= M.MAX_FOLDER_DISCOVERY
if should_halt then
notify.warn("expansion iteration was halted after " .. M.MAX_FOLDER_DISCOVERY .. " discovered folders")
return false
end
return should_descend(expansion_count, node)
end
end
---@param _ integer expansion_count
---@param node Node ---@param node Node
---@return boolean ---@return boolean
local function should_expand(expansion_count, node) local function descend_until_empty(_, node)
local dir = node:as(DirectoryNode) local dir = node:as(DirectoryNode)
if not dir then if not dir then
return false return false
end end
local should_halt = expansion_count >= M.MAX_FOLDER_DISCOVERY
local should_exclude = M.EXCLUDE[dir.name] local should_exclude = M.EXCLUDE[dir.name]
return not should_halt and not dir.open and not should_exclude return not should_exclude
end end
local function gen_iterator() ---@param expansion_count integer
---@param node Node
---@param should_descend fun(expansion_count: integer, node: Node): boolean
---@return boolean
local function should_expand(expansion_count, node, should_descend)
local dir = node:as(DirectoryNode)
if not dir then
return false
end
if not dir.open and should_descend(expansion_count, node) then
if #node.nodes == 0 then
core.get_explorer():expand(dir) -- populate node.group_next
end
if dir.group_next then
local expand_next = should_expand(expansion_count, dir.group_next, should_descend)
if expand_next then
dir.open = true
end
return expand_next
else
return true
end
end
return false
end
---@param should_descend fun(expansion_count: integer, node: Node): boolean
---@return fun(node): any
local function gen_iterator(should_descend)
local expansion_count = 0 local expansion_count = 0
return function(parent) return function(parent)
@@ -52,7 +98,7 @@ local function gen_iterator()
Iterator.builder(parent.nodes) Iterator.builder(parent.nodes)
:hidden() :hidden()
:applier(function(node) :applier(function(node)
if should_expand(expansion_count, node) then if should_expand(expansion_count, node, should_descend) then
expansion_count = expansion_count + 1 expansion_count = expansion_count + 1
node = node:as(DirectoryNode) node = node:as(DirectoryNode)
if node then if node then
@@ -61,25 +107,32 @@ local function gen_iterator()
end end
end) end)
:recursor(function(node) :recursor(function(node)
return expansion_count < M.MAX_FOLDER_DISCOVERY and (node.group_next and { node.group_next } or (node.open and node.nodes)) if not should_descend(expansion_count, node) then
return nil
end
if node.group_next then
return { node.group_next }
end
if node.open and node.nodes then
return node.nodes
end
return nil
end) end)
:iterate() :iterate()
if expansion_count >= M.MAX_FOLDER_DISCOVERY then
return true
end
end end
end end
---@param node Node? ---@param node Node?
local function expand_node(node) ---@param expand_opts ApiTreeExpandOpts?
local function expand_node(node, expand_opts)
if not node then if not node then
return return
end end
local descend_until = limit_folder_discovery((expand_opts and expand_opts.expand_until) or descend_until_empty)
if gen_iterator()(node) then gen_iterator(descend_until)(node)
notify.warn("expansion iteration was halted after " .. M.MAX_FOLDER_DISCOVERY .. " discovered folders")
end
local explorer = core.get_explorer() local explorer = core.get_explorer()
if explorer then if explorer then
@@ -89,18 +142,20 @@ end
---Expand the directory node or the root ---Expand the directory node or the root
---@param node Node ---@param node Node
function M.all(node) ---@param expand_opts ApiTreeExpandOpts?
expand_node(node and node:as(DirectoryNode) or core.get_explorer()) function M.all(node, expand_opts)
expand_node(node and node:as(DirectoryNode) or core.get_explorer(), expand_opts)
end end
---Expand the directory node or parent node ---Expand the directory node or parent node
---@param node Node ---@param node Node
function M.node(node) ---@param expand_opts ApiTreeExpandOpts?
function M.node(node, expand_opts)
if not node then if not node then
return return
end end
expand_node(node:is(FileNode) and node.parent or node:as(DirectoryNode)) expand_node(node:is(FileNode) and node.parent or node:as(DirectoryNode), expand_opts)
end end
function M.setup(opts) function M.setup(opts)

View File

@@ -187,6 +187,10 @@ Api.tree.search_node = wrap(actions.finders.search_node.fn)
---@field keep_buffers boolean|nil default false ---@field keep_buffers boolean|nil default false
Api.tree.collapse_all = wrap(actions.tree.modifiers.collapse.all) Api.tree.collapse_all = wrap(actions.tree.modifiers.collapse.all)
---@class ApiTreeExpandOpts
---@field expand_until (fun(expansion_count: integer, node: Node): boolean)|nil
Api.tree.expand_all = wrap_node(actions.tree.modifiers.expand.all) Api.tree.expand_all = wrap_node(actions.tree.modifiers.expand.all)
Api.tree.toggle_enable_filters = wrap_explorer_member("filters", "toggle") Api.tree.toggle_enable_filters = wrap_explorer_member("filters", "toggle")
Api.tree.toggle_gitignore_filter = wrap_explorer_member_args("filters", "toggle", "git_ignored") Api.tree.toggle_gitignore_filter = wrap_explorer_member_args("filters", "toggle", "git_ignored")

View File

@@ -193,9 +193,10 @@ function M.get_toplevel(path)
end end
end end
-- attempt to fetch toplevel -- attempt to fetch toplevel, cache if untracked
local toplevel, git_dir = git_utils.get_toplevel(path) local toplevel, git_dir = git_utils.get_toplevel(path)
if not toplevel or not git_dir then if not toplevel or not git_dir then
M._toplevels_by_path[path] = false
return nil return nil
end end
local toplevel_norm = vim.fn.fnamemodify(toplevel, ":p") local toplevel_norm = vim.fn.fnamemodify(toplevel, ":p")

View File

@@ -5,6 +5,19 @@ local M = {
use_cygpath = false, use_cygpath = false,
} }
--- Execute system command
---@param cmd string[]
---@return string stdout
---@return integer exit code
local function system(cmd)
if vim.fn.has("nvim-0.10") == 1 then
local obj = vim.system(cmd):wait()
return obj.stdout or "", obj.code
else
return vim.fn.system(cmd), vim.v.shell_error
end
end
--- Retrieve the git toplevel directory --- Retrieve the git toplevel directory
---@param cwd string path ---@param cwd string path
---@return string|nil toplevel absolute path ---@return string|nil toplevel absolute path
@@ -16,12 +29,12 @@ function M.get_toplevel(cwd)
local cmd = { "git", "-C", cwd, "rev-parse", "--show-toplevel", "--absolute-git-dir" } local cmd = { "git", "-C", cwd, "rev-parse", "--show-toplevel", "--absolute-git-dir" }
log.line("git", "%s", table.concat(cmd, " ")) log.line("git", "%s", table.concat(cmd, " "))
local out = vim.fn.system(cmd) local out, exitCode = system(cmd)
log.raw("git", out) log.raw("git", out)
log.profile_end(profile) log.profile_end(profile)
if vim.v.shell_error ~= 0 or not out or #out == 0 or out:match("fatal") then if exitCode ~= 0 or not out or #out == 0 or out:match("fatal") then
return nil, nil return nil, nil
end end
@@ -73,7 +86,7 @@ function M.should_show_untracked(cwd)
local cmd = { "git", "-C", cwd, "config", "status.showUntrackedFiles" } local cmd = { "git", "-C", cwd, "config", "status.showUntrackedFiles" }
log.line("git", table.concat(cmd, " ")) log.line("git", table.concat(cmd, " "))
local has_untracked = vim.fn.system(cmd) local has_untracked = system(cmd)
log.raw("git", has_untracked) log.raw("git", has_untracked)
log.profile_end(profile) log.profile_end(profile)

View File

@@ -131,7 +131,7 @@ function GitDecorator:build_file_folder_hl_by_xy()
["RM"] = "NvimTreeGitFileRenamedHL", ["RM"] = "NvimTreeGitFileRenamedHL",
[" R"] = "NvimTreeGitFileRenamedHL", [" R"] = "NvimTreeGitFileRenamedHL",
["!!"] = "NvimTreeGitFileIgnoredHL", ["!!"] = "NvimTreeGitFileIgnoredHL",
[" A"] = "none", [" A"] = "NvimTreeGitFileNewHL",
} }
self.folder_hl_by_xy = {} self.folder_hl_by_xy = {}

View File

@@ -624,6 +624,7 @@ function M.setup(opts)
M.View.tab = opts.tab M.View.tab = opts.tab
M.View.preserve_window_proportions = options.preserve_window_proportions M.View.preserve_window_proportions = options.preserve_window_proportions
M.View.winopts.cursorline = options.cursorline M.View.winopts.cursorline = options.cursorline
M.View.winopts.cursorlineopt = options.cursorlineopt
M.View.winopts.number = options.number M.View.winopts.number = options.number
M.View.winopts.relativenumber = options.relativenumber M.View.winopts.relativenumber = options.relativenumber
M.View.winopts.signcolumn = options.signcolumn M.View.winopts.signcolumn = options.signcolumn