Compare commits

..

62 Commits

Author SHA1 Message Date
github-actions[bot]
ca23b325b8 chore(master): release nvim-tree 1.15.0 2025-10-20 00:50:33 +00:00
Eric Wong
64e2192f52 feat: set filter input filetype to NvimTreeFilter (#3207)
feat: add NvimTreeFilter filetype
2025-10-20 00:50:08 +00:00
phanium
e397756d2a fix: prevent NvimTree to be alternate buffer when tab open (#3205)
* fix: prevent NvimTree to be alternate buffer when tab open

* fix: prevent tabnew leave a dangling "[No Name]" buffer

* Update lua/nvim-tree/actions/node/open-file.lua

Co-authored-by: Alexander Courtis <alex@courtis.org>

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-10-08 10:57:48 +11:00
dependabot[bot]
87d096a39c chore(deps): bump leafo/gh-actions-luarocks from 5 to 6 (#3201)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-30 10:52:12 +02:00
dependabot[bot]
01b2e8e5f7 chore(deps): bump leafo/gh-actions-lua from 11 to 12 (#3202)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-30 09:52:10 +02:00
Alexander Courtis
e179ad2f83 refactor(#2942): multi instance: move utils functions to Explorer methods (#3200)
* refactor(#2942): multi instance: move find_node_line to Explorer

* refactor(#2942): multi instance: move get_node_from_path to Explorer

* refactor(#2942): multi instance: move focus_file to Explorer

* refactor(#2942): multi instance: move focus_node_or_parent to Explorer

* refactor(#2942): multi instance: move get_node_from_path to Explorer

* refactor(#2942): multi instance: move find_node to Explorer

* refactor(#2942): multi instance: move get_nodes_by_line to Explorer

* refactor(#2942): multi instance: remove unnecessary focus_file

* refactor(#2942): style
2025-09-08 16:26:42 +10:00
Alexander Courtis
f92cc3a91c chore(#3196): remove unused utils and refactor (#3199)
* chore(#3196): remove unused utils.read_file

* chore(#3196): move utils.move_missing_val to legacy

* chore(#3196): move utils.table_create_missing to legacy
2025-09-08 11:12:31 +10:00
Igor Lacerda
fefa335f1c feat(#1826): add diagnostics.diagnostic_opts: vim.diagnostic.Opts will override diagnostics.severity and diagnostics.icons (#3190)
* feat(#1826): allow using config from vim.diagnostic for icons + severity

* update help default options, add index

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-08-25 13:22:05 +10:00
dependabot[bot]
b70a741f5a chore(deps): bump amannn/action-semantic-pull-request from 6.1.0 to 6.1.1 (#3193)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-22 13:01:24 +02:00
dependabot[bot]
f4fa6ebd3c chore(deps): bump amannn/action-semantic-pull-request from 6.0.1 to 6.1.0 (#3192)
chore(deps): bump amannn/action-semantic-pull-request

Bumps [amannn/action-semantic-pull-request](https://github.com/amannn/action-semantic-pull-request) from 6.0.1 to 6.1.0.
- [Release notes](https://github.com/amannn/action-semantic-pull-request/releases)
- [Changelog](https://github.com/amannn/action-semantic-pull-request/blob/main/CHANGELOG.md)
- [Commits](https://github.com/amannn/action-semantic-pull-request/compare/v6.0.1...v6.1.0)

---
updated-dependencies:
- dependency-name: amannn/action-semantic-pull-request
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-20 13:44:53 +10:00
dependabot[bot]
f0e9951778 chore(deps): bump amannn/action-semantic-pull-request from 5.5.3 to 6.0.1 (#3189)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-14 14:31:09 +02:00
dependabot[bot]
feb901a09e chore(deps): bump actions/checkout from 4 to 5 (#3188)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-13 09:08:14 +02:00
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
github-actions[bot]
6b5b366596 chore(master): release nvim-tree 1.13.0 (#3120)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-14 17:30:22 +10:00
Rami Elwan
ae595611fb feat(#3132): add api.node.expand and api.node.collapse (#3133)
* feat: allow passing node to collapse all

* refactor: use snake case

* feat: handle api legacy calls and update signature

* refactor: make sure open is a boolean

* doc: collapse_all

* Revert "doc: collapse_all"

This reverts commit d243da3e14.

* add api.node.collapse

* add api.node.expand

* add api.node.expand

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-06-14 17:26:58 +10:00
Lucas Mendes
05d8172ebf fix(#3143): actions.open_file.window_picker.exclude applies when not using window picker (#3144)
* fix(#3143): ensure open.no_window_picker respects window_picker.exclude

* fix(#3143): doc

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-06-14 15:35:07 +10:00
Lorentz Lasson
1c733e8c19 chore: use portable shebangs consistently (#3141)
use portable shebangs consistently
2025-06-02 09:21:07 +10:00
Šimon Mandlík
ebcaccda1c fix(#3134): setting one glyph to "" no longer disables others (#3136)
fix: fixes #3134
2025-05-26 13:32:21 +10:00
Šimon Mandlík
cbc3165e08 fix(#2746): background and right aligned icons in floating windows (#3128)
* fix(#2746): fix cursorcolumn and right aligned icons in floating windows

* feat: remove right aligned icons from full name float, show float over right aligned icons

* refactoring: move `extmarks_length` to utils.lua

* fix: decrease `win_width` instead of increasing `text_width` when computing condition for full name float to show

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-05-24 13:19:19 +10:00
Arthur Roos
bd54d1d33c fix(#3117): windows: change file/dir case (#3135)
fix(#3117): allow changing filename's casing

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-05-24 12:52:25 +10:00
Christoph
25d16aab7d fix: "Invalid buffer id" on closing nvim-tree window (#3129)
fix: invalid buffer issue

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-05-18 04:35:59 +00:00
Ross W
e4cd856ebf fix(#3124): fix icon padding for "right_align" placements, notably for dotfiles (#3125)
fix(#3124): prevent empty icons_right_align response from breaking padding
2025-05-18 12:26:18 +10:00
Alexander Courtis
e7d1b7dadc fix(#3122): remove redundant vim.validate (#3123) 2025-05-09 10:00:28 +10:00
Spencer Chunn
ea5097a1e2 feat(#3113): add renderer.icons.folder_arrow_padding (#3114)
* Update padding.lua

* add folder_arrow_padding

* update help docs

* refactor: renderer.icons.padding

renderer.icons.padding -> renderer.icons.padding.icon
renderer.icons.folder_arrow_padding ->
renderer.icons.padding.folder_arrow

* refactor: renderer.icons.padding

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-05-05 11:51:29 +10:00
Alexander Courtis
582ae48c9e chore: fix incorrect @param (#3115) 2025-04-26 12:55:36 +10:00
github-actions[bot]
be5b788f2d chore(master): release nvim-tree 1.12.0 (#3099)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-04-21 12:04:41 +10:00
Alexander Courtis
64bb47f868 ci: simplify luarocks release tag pattern as it was not firing 2025-04-21 08:59:56 +10:00
Devansh Sharma
c24c0470d9 feat: add TreePreOpen event (#3105)
* feat: Add `TreePreOpen` and `TreePreClose` events

* docs: Update docs for `TreePreOpen` and `TreePreClose` events

* chore: remove `TreePreClose` event and update dispatch of `TreePreOpen`

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-04-21 08:39:48 +10:00
Devansh Sharma
3a63717d3d fix: reliably dispatch exactly one TreeOpen and TreeClose events (#3107)
* fix: correctly handle `TreeOpen` and `TreeClose` event dispatch

* fix: lint issues
2025-04-20 09:49:28 +10:00
Alexander Courtis
5bea2b3752 fix(#3101): when renderer.highlight_opened_files = "none" do not reload on BufUnload and BufReadPost (#3102)
* fix(#3101): fix bad reference to renderer.highlight_opened_files during BufUnload and BufReadPost

* fix(#3101): only redraw renderer.highlight_opened_files during BufUnload and BufReadPost

* fix(#3101): only redraw renderer.highlight_opened_files during BufUnload and BufReadPost

* fix(#3101): only redraw renderer.highlight_opened_files during BufUnload and BufReadPost
2025-04-11 12:48:34 +10:00
Šimon Mandlík
c3c1935942 fix: explicitly set border to "none" in full name float (#3094) 2025-04-04 17:29:38 +11:00
Alexander Courtis
44d9b58f11 chore: use builtin EmmyLuaCodeStyle for style checking (#3084)
* chore: sync EmmyLuaCodeStyle settings between .editorconfig and .luarc.json

* chore: lua-language-server 3.11.0 -> 3.13.9

* chore: fix incorrect definition of vim.loop.fs_lstat

* chore: add codestyle-check option to luals-check.sh

* chore: use luals for style check

* chore: use luals for style check

* Revert "chore: use luals for style check"

This reverts commit e5fde80fab.

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check
2025-03-23 12:46:17 +11:00
dependabot[bot]
c09ff35de5 chore(deps): bump leafo/gh-actions-lua from 10 to 11 (#3069)
Bumps [leafo/gh-actions-lua](https://github.com/leafo/gh-actions-lua) from 10 to 11.
- [Release notes](https://github.com/leafo/gh-actions-lua/releases)
- [Commits](https://github.com/leafo/gh-actions-lua/compare/v10...v11)

---
updated-dependencies:
- dependency-name: leafo/gh-actions-lua
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-01 11:07:35 +11:00
github-actions[bot]
6709463b2d chore(master): release nvim-tree 1.11.0 (#3051)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-02-22 12:31:19 +11:00
Šimon Mandlík
b69914325a fix: window picker: hide fillchars: stl and stlnc (#3066)
fix: stl and stlnc fillchars are hidden in window picker

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-02-22 11:32:52 +11:00
Gabriel Crispino
3281f331f7 feat(#1984): add quit_on_open and focus opts to various api.node.open functions (#3054)
* feat: add quit_on_open opt to api.node.open.edit

* fix: fix missing @param annotation

* feat: add focus opt to api.node.open.edit

* fix: fix focus == false behaviour on api.node.open.tab command

* fix: add optional tabpage integer parameter to view.close

if tabpage is not nil, then the function closes the tabpage in this
specific tabpage

* fix: fix quit_on_open == true behaviour on api.node.open.tab command

* fix: add check to not use new opts for certain edit modes

* fix: add docs for new opts

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-02-22 11:09:49 +11:00
Hendrik Ziegler
80523101f0 fix: arithmetic on nil value error on first git project open (#3064)
* fixed error message when opening new git repo

* defensive nil + type check

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-02-10 08:06:02 +11:00
Alexander Courtis
70825f23db fix(#3059): test for presence of new 0.11 API vim.hl.range (#3060) 2025-02-03 15:42:22 +11:00
Alexander Courtis
d05881f65f docs: tidy readme contributing 2025-01-27 11:33:37 +11:00
Gabriel Crispino
fee1da8897 feat(#3037): add API node.buffer.delete, node.buffer.wipe (#3040)
* feat(mappings): add key map to close file buffer

* feat: implement Api.node.buffer.delete

* feat: implement Api.node.buffer.wipe

* refactor: add util fn for common delete ops on bufs

* fix: minor fixes

* refactor: fix lint issues

* fix: undo unintended ApiTreeToggleOpts change

* fix: change error message level to info

* fix: remove unused opts

* refactor: merge delete-buffer and wipe-buffer into single buffer file

* refactor: make wipe and delete fns take a node instead of a file path

* docs: update help with new API commands

* remove refactored utils.lua

* remove unused static setup

* tweak doc

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-01-25 12:47:34 +11:00
Alexander Courtis
db7403243d chore: resolve deprecated in 0.11 (#3053)
* chore: resolve deprecated in 0.11

* chore: resolve deprecated in 0.11

* chore: resolve deprecated in 0.11

* chore: resolve deprecated in 0.11

* chore: resolve deprecated in 0.11

* chore: resolve deprecated in 0.11
2025-01-24 11:57:18 +11:00
𝐍𝐆𝐏𝐎𝐍𝐆
fca0b67c0b fix(#3045): wipe scratch buffers for full name and show info popups (#3050) 2025-01-18 10:28:06 +11:00
53 changed files with 1274 additions and 574 deletions

View File

@@ -7,6 +7,9 @@ end_of_line = lf
[nvim-tree-lua.txt] [nvim-tree-lua.txt]
max_line_length = 78 max_line_length = 78
# keep these in sync with .luarc.json
# .editorconfig is used within nvim, overriding .luarc.json
# .luarc.json is used by style check
[*.lua] [*.lua]
indent_style = space indent_style = space
max_line_length = 140 max_line_length = 140

View File

@@ -20,45 +20,25 @@ jobs:
strategy: strategy:
matrix: matrix:
lua_version: [ 5.1 ] lua_version: [ 5.1 ]
luacheck_version: [ 1.2.0 ]
steps: steps:
- uses: actions/checkout@v4 - name: checkout
uses: actions/checkout@v5
- uses: leafo/gh-actions-lua@v10 - name: install lua ${{ matrix.lua_version }}
uses: leafo/gh-actions-lua@v12
with: with:
luaVersion: ${{ matrix.lua_version }} luaVersion: ${{ matrix.lua_version }}
- uses: leafo/gh-actions-luarocks@v4 - name: install luarocks
uses: leafo/gh-actions-luarocks@v6
- run: luarocks install luacheck 1.1.1 - name: install luacheck ${{ matrix.luacheck_version }}
run: luarocks install luacheck ${{ matrix.luacheck_version }}
- run: make lint - run: make lint
style:
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ matrix.emmy_lua_code_style_version }}-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true
strategy:
matrix:
emmy_lua_code_style_version: [ 1.5.6 ]
steps:
- uses: actions/checkout@v4
- name: install emmy_lua_code_style
run: |
mkdir -p CodeFormat
curl -L "https://github.com/CppCXY/EmmyLuaCodeStyle/releases/download/${{ matrix.emmy_lua_code_style_version }}/linux-x64.tar.gz" | tar zx --directory CodeFormat
- run: echo "CodeFormat/linux-x64/bin" >> "$GITHUB_PATH"
- run: make style
- run: make style-doc
check: check:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -69,26 +49,31 @@ jobs:
strategy: strategy:
matrix: matrix:
nvim_version: [ stable, nightly ] nvim_version: [ stable, nightly ]
luals_version: [ 3.11.0 ] luals_version: [ 3.15.0 ]
env:
VIMRUNTIME: /home/runner/nvim-${{ matrix.nvim_version }}/share/nvim/runtime
steps: steps:
- uses: actions/checkout@v4 - name: checkout
uses: actions/checkout@v5
- uses: rhysd/action-setup-vim@v1 - name: install nvim ${{ matrix.nvim_version }}
uses: rhysd/action-setup-vim@v1
with: with:
neovim: true neovim: true
version: ${{ matrix.nvim_version }} version: ${{ matrix.nvim_version }}
- name: install luals - name: install lua-language-server ${{ matrix.luals_version }}
run: | run: |
mkdir -p luals mkdir -p luals
curl -L "https://github.com/LuaLS/lua-language-server/releases/download/${{ matrix.luals_version }}/lua-language-server-${{ matrix.luals_version }}-linux-x64.tar.gz" | tar zx --directory luals curl -L "https://github.com/LuaLS/lua-language-server/releases/download/${{ matrix.luals_version }}/lua-language-server-${{ matrix.luals_version }}-linux-x64.tar.gz" | tar zx --directory luals
echo "luals/bin" >> "$GITHUB_PATH"
- run: echo "luals/bin" >> "$GITHUB_PATH" - run: make check
- name: make check
env:
VIMRUNTIME: /home/runner/nvim-${{ matrix.nvim_version }}/share/nvim/runtime
run: make check
- run: make help-check - run: make help-check
- run: make style
- run: make style-doc

View File

@@ -1,14 +1,16 @@
name: Luarocks Release name: Luarocks Release
on: on:
push: push:
tags: tags:
- 'v[0-9]+.[0-9]+.[0-9]+' - v*
workflow_dispatch: workflow_dispatch:
jobs: jobs:
luarocks-upload: luarocks-upload:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- name: LuaRocks Upload - name: LuaRocks Upload
uses: nvim-neorocks/luarocks-tag-release@v7 uses: nvim-neorocks/luarocks-tag-release@v7
env: env:

View File

@@ -16,7 +16,7 @@ jobs:
steps: steps:
- uses: google-github-actions/release-please-action@v4 - uses: google-github-actions/release-please-action@v4
id: release id: release
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- name: tag major and minor versions - name: tag major and minor versions
if: ${{ steps.release.outputs.release_created }} if: ${{ steps.release.outputs.release_created }}
run: | run: |

View File

@@ -14,6 +14,6 @@ jobs:
semantic-pr-subject: semantic-pr-subject:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: amannn/action-semantic-pull-request@v5.5.3 - uses: amannn/action-semantic-pull-request@v6.1.1
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,3 +1,3 @@
#!/bin/sh #!/usr/bin/env sh
make make

View File

@@ -1,12 +1,23 @@
{ {
"$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json",
"runtime.version.luals-check-only": "Lua 5.1",
"workspace": { "workspace": {
"library": [ "library": [
"$VIMRUNTIME/lua/vim", "$VIMRUNTIME/lua/vim",
"${3rd}/luv/library" "${3rd}/luv/library"
] ]
}, },
"format": {
"defaultConfig": {
"indent_style": "space",
"max_line_length": "140",
"indent_size": "2",
"continuation_indent": "2",
"quote_style": "double",
"call_arg_parentheses": "always",
"space_before_closure_open_parenthesis": "false",
"align_continuous_similar_call_args": "true"
}
},
"diagnostics": { "diagnostics": {
"libraryFiles": "Disable", "libraryFiles": "Disable",
"globals": [], "globals": [],

View File

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

View File

@@ -1,5 +1,105 @@
# Changelog # Changelog
## [1.15.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.14.0...nvim-tree-v1.15.0) (2025-10-20)
### Features
* **#1826:** add diagnostics.diagnostic_opts: vim.diagnostic.Opts will override diagnostics.severity and diagnostics.icons ([#3190](https://github.com/nvim-tree/nvim-tree.lua/issues/3190)) ([fefa335](https://github.com/nvim-tree/nvim-tree.lua/commit/fefa335f1c8f690eb668a1efd18ee4fc6d64cd3e))
* add NvimTreeFilter filetype ([64e2192](https://github.com/nvim-tree/nvim-tree.lua/commit/64e2192f5250796aa4a7f33c6ad888515af50640))
* set filter input filetype to NvimTreeFilter ([#3207](https://github.com/nvim-tree/nvim-tree.lua/issues/3207)) ([64e2192](https://github.com/nvim-tree/nvim-tree.lua/commit/64e2192f5250796aa4a7f33c6ad888515af50640))
### Bug Fixes
* prevent NvimTree to be alternate buffer when tab open ([#3205](https://github.com/nvim-tree/nvim-tree.lua/issues/3205)) ([e397756](https://github.com/nvim-tree/nvim-tree.lua/commit/e397756d2a79d74314ea4cd3efc41300e91c0ff0))
## [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)
### Features
* **#3113:** add renderer.icons.folder_arrow_padding ([#3114](https://github.com/nvim-tree/nvim-tree.lua/issues/3114)) ([ea5097a](https://github.com/nvim-tree/nvim-tree.lua/commit/ea5097a1e2702b4827cb7380e7fa0bd6da87699c))
* **#3132:** add api.node.expand and api.node.collapse ([#3133](https://github.com/nvim-tree/nvim-tree.lua/issues/3133)) ([ae59561](https://github.com/nvim-tree/nvim-tree.lua/commit/ae595611fb2225f2041996c042aa4e4b8663b41e))
### Bug Fixes
* "Invalid buffer id" on closing nvim-tree window ([#3129](https://github.com/nvim-tree/nvim-tree.lua/issues/3129)) ([25d16aa](https://github.com/nvim-tree/nvim-tree.lua/commit/25d16aab7d29ca940a9feb92e6bb734697417009))
* **#2746:** background and right aligned icons in floating windows ([#3128](https://github.com/nvim-tree/nvim-tree.lua/issues/3128)) ([cbc3165](https://github.com/nvim-tree/nvim-tree.lua/commit/cbc3165e08893bb499da035c6f6f9d1512b57664))
* **#3117:** allow changing filename's casing ([bd54d1d](https://github.com/nvim-tree/nvim-tree.lua/commit/bd54d1d33c20d8630703b9842480291588dbad07))
* **#3117:** windows: change file/dir case ([#3135](https://github.com/nvim-tree/nvim-tree.lua/issues/3135)) ([bd54d1d](https://github.com/nvim-tree/nvim-tree.lua/commit/bd54d1d33c20d8630703b9842480291588dbad07))
* **#3122:** remove redundant vim.validate ([#3123](https://github.com/nvim-tree/nvim-tree.lua/issues/3123)) ([e7d1b7d](https://github.com/nvim-tree/nvim-tree.lua/commit/e7d1b7dadc62fe2eccc17d814354b0a5688621ce))
* **#3124:** fix icon padding for "right_align" placements, notably for dotfiles ([#3125](https://github.com/nvim-tree/nvim-tree.lua/issues/3125)) ([e4cd856](https://github.com/nvim-tree/nvim-tree.lua/commit/e4cd856ebf4fec51db10c69d63e43224b701cbce))
* **#3124:** prevent empty icons_right_align response from breaking padding ([e4cd856](https://github.com/nvim-tree/nvim-tree.lua/commit/e4cd856ebf4fec51db10c69d63e43224b701cbce))
* **#3134:** setting one glyph to "" no longer disables others ([#3136](https://github.com/nvim-tree/nvim-tree.lua/issues/3136)) ([ebcaccd](https://github.com/nvim-tree/nvim-tree.lua/commit/ebcaccda1c575fa19a8087445276e6671e2b9b37))
* **#3143:** actions.open_file.window_picker.exclude applies when not using window picker ([#3144](https://github.com/nvim-tree/nvim-tree.lua/issues/3144)) ([05d8172](https://github.com/nvim-tree/nvim-tree.lua/commit/05d8172ebf9cdb2d140cf25b75625374fbc3df7f))
* fixes [#3134](https://github.com/nvim-tree/nvim-tree.lua/issues/3134) ([ebcaccd](https://github.com/nvim-tree/nvim-tree.lua/commit/ebcaccda1c575fa19a8087445276e6671e2b9b37))
* invalid buffer issue ([25d16aa](https://github.com/nvim-tree/nvim-tree.lua/commit/25d16aab7d29ca940a9feb92e6bb734697417009))
## [1.12.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.11.0...nvim-tree-v1.12.0) (2025-04-20)
### Features
* add TreePreOpen event ([#3105](https://github.com/nvim-tree/nvim-tree.lua/issues/3105)) ([c24c047](https://github.com/nvim-tree/nvim-tree.lua/commit/c24c0470d9de277fbebecd718f33561ed7c90298))
### Bug Fixes
* **#3101:** when renderer.highlight_opened_files = "none" do not reload on BufUnload and BufReadPost ([#3102](https://github.com/nvim-tree/nvim-tree.lua/issues/3102)) ([5bea2b3](https://github.com/nvim-tree/nvim-tree.lua/commit/5bea2b37523a31288e0fcab42f3be5c1bd4516bb))
* explicitly set `border` to `"none"` in full name float ([#3094](https://github.com/nvim-tree/nvim-tree.lua/issues/3094)) ([c3c1935](https://github.com/nvim-tree/nvim-tree.lua/commit/c3c193594213c5e2f89ec5d7729cad805f76b256))
* reliably dispatch exactly one TreeOpen and TreeClose events ([#3107](https://github.com/nvim-tree/nvim-tree.lua/issues/3107)) ([3a63717](https://github.com/nvim-tree/nvim-tree.lua/commit/3a63717d3d332d8f39aaf65be7a0e4c2265af021))
## [1.11.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.10.0...nvim-tree-v1.11.0) (2025-02-22)
### Features
* **#1984:** add quit_on_open and focus opts to various api.node.open functions ([#3054](https://github.com/nvim-tree/nvim-tree.lua/issues/3054)) ([3281f33](https://github.com/nvim-tree/nvim-tree.lua/commit/3281f331f7f0bef13eb00fb2d5a9d28b2f6155a2))
* **#3037:** add API node.buffer.delete, node.buffer.wipe ([#3040](https://github.com/nvim-tree/nvim-tree.lua/issues/3040)) ([fee1da8](https://github.com/nvim-tree/nvim-tree.lua/commit/fee1da88972f5972a8296813f6c00d7598325ebd))
### Bug Fixes
* **#3045:** wipe scratch buffers for full name and show info popups ([#3050](https://github.com/nvim-tree/nvim-tree.lua/issues/3050)) ([fca0b67](https://github.com/nvim-tree/nvim-tree.lua/commit/fca0b67c0b5a31727fb33addc4d9c100736a2894))
* **#3059:** test for presence of new 0.11 API vim.hl.range ([#3060](https://github.com/nvim-tree/nvim-tree.lua/issues/3060)) ([70825f2](https://github.com/nvim-tree/nvim-tree.lua/commit/70825f23db61ecd900c4cfea169bffe931926a9d))
* arithmetic on nil value error on first git project open ([#3064](https://github.com/nvim-tree/nvim-tree.lua/issues/3064)) ([8052310](https://github.com/nvim-tree/nvim-tree.lua/commit/80523101f0ae48b7f1990e907b685a3d79776c01))
* stl and stlnc fillchars are hidden in window picker ([b699143](https://github.com/nvim-tree/nvim-tree.lua/commit/b69914325a945ee5157f0d21047210b42af5776e))
* window picker: hide fillchars: stl and stlnc ([#3066](https://github.com/nvim-tree/nvim-tree.lua/issues/3066)) ([b699143](https://github.com/nvim-tree/nvim-tree.lua/commit/b69914325a945ee5157f0d21047210b42af5776e))
## [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) ## [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)

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.
@@ -12,9 +36,9 @@ Language server: [luals](https://luals.github.io)
Lint: [luacheck](https://github.com/lunarmodules/luacheck/) Lint: [luacheck](https://github.com/lunarmodules/luacheck/)
Style: [EmmyLuaCodeStyle](https://github.com/CppCXY/EmmyLuaCodeStyle): `CodeCheck` Style Fixing: [EmmyLuaCodeStyle](https://github.com/CppCXY/EmmyLuaCodeStyle): `CodeCheck`
nvim-tree.lua migrated from stylua to EmmyLuaCodeStyle ~2024/10. `vim.lsp.buf.format()` may be used as it is the default formatter for luals nvim-tree.lua migrated from stylua to EmmyLuaCodeStyle ~2024/10. `vim.lsp.buf.format()` may be used as it is the default formatter for luals, using an embedded [EmmyLuaCodeStyle](https://github.com/CppCXY/EmmyLuaCodeStyle)
You can install them via you OS package manager e.g. `pacman`, `brew` or other via other package managers such as `cargo` or `luarocks` You can install them via you OS package manager e.g. `pacman`, `brew` or other via other package managers such as `cargo` or `luarocks`
@@ -36,14 +60,14 @@ make lint
## style ## style
1. Runs CodeCheck using `.editorconfig` settings 1. Runs lua language server `codestyle-check` only, using `.luarc.json` settings
1. Runs `scripts/doc-comments.sh` to validate annotated documentation 1. Runs `scripts/doc-comments.sh` to validate annotated documentation
```sh ```sh
make style make style
``` ```
You can automatically fix `CodeCheck` issues via: You can automatically fix style issues using `CodeCheck`:
```sh ```sh
make style-fix make style-fix
@@ -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

@@ -17,7 +17,7 @@ luacheck:
# --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:
CodeFormat check --config .editorconfig --diagnosis-as-error --workspace lua @scripts/luals-check.sh codestyle-check
style-doc: style-doc:
scripts/doc-comments.sh scripts/doc-comments.sh

View File

@@ -162,13 +162,13 @@ nvim-tree exposes a public API. This is non breaking, with additions made as nec
See wiki [Recipes](https://github.com/nvim-tree/nvim-tree.lua/wiki/Recipes) and [Tips](https://github.com/nvim-tree/nvim-tree.lua/wiki/Tips) for ideas and inspiration. See wiki [Recipes](https://github.com/nvim-tree/nvim-tree.lua/wiki/Recipes) and [Tips](https://github.com/nvim-tree/nvim-tree.lua/wiki/Tips) for ideas and inspiration.
Please raise a [feature request](https://github.com/nvim-tree/nvim-tree.lua/issues/new?assignees=&labels=feature+request&template=feature_request.md&title=) if the API is insufficient for your needs. [Contributions](#Contributing) are always welcome. Please raise a [feature request](https://github.com/nvim-tree/nvim-tree.lua/issues/new?assignees=&labels=feature+request&template=feature_request.md&title=) if the API is insufficient for your needs. Contributions are always welcome, see below.
You may also subscribe to events that nvim-tree will dispatch in a variety of situations, see [:help nvim-tree-events](doc/nvim-tree-lua.txt) You may also subscribe to events that nvim-tree will dispatch in a variety of situations, see [:help nvim-tree-events](doc/nvim-tree-lua.txt)
## Contributing ## Contributing
PRs are always welcome. See [wiki](https://github.com/nvim-tree/nvim-tree.lua/wiki/Development) to get started. PRs are always welcome. See [CONTRIBUTING](CONTRIBUTING.md) and [wiki: Development](https://github.com/nvim-tree/nvim-tree.lua/wiki/Development) to get started.
See [bug](https://github.com/nvim-tree/nvim-tree.lua/issues?q=is%3Aissue+is%3Aopen+label%3Abug) and [PR Please](https://github.com/nvim-tree/nvim-tree.lua/issues?q=is%3Aopen+is%3Aissue+label%3A%22PR+please%22) issues if you are looking for some work to get you started. See [bug](https://github.com/nvim-tree/nvim-tree.lua/issues?q=is%3Aissue+is%3Aopen+label%3Abug) and [PR Please](https://github.com/nvim-tree/nvim-tree.lua/issues?q=is%3Aopen+is%3Aissue+label%3A%22PR+please%22) issues if you are looking for some work to get you started.

View File

@@ -206,7 +206,7 @@ Show the mappings: `g?`
`S` Search |nvim-tree-api.tree.search_node()| `S` Search |nvim-tree-api.tree.search_node()|
`u` Rename: Full Path |nvim-tree-api.fs.rename_full()| `u` Rename: Full Path |nvim-tree-api.fs.rename_full()|
`U` Toggle Filter: Hidden |nvim-tree-api.tree.toggle_custom_filter()| `U` Toggle Filter: Hidden |nvim-tree-api.tree.toggle_custom_filter()|
`W` Collapse |nvim-tree-api.tree.collapse_all()| `W` Collapse All |nvim-tree-api.tree.collapse_all()|
`x` Cut |nvim-tree-api.fs.cut()| `x` Cut |nvim-tree-api.fs.cut()|
`y` Copy Name |nvim-tree-api.fs.copy.filename()| `y` Copy Name |nvim-tree-api.fs.copy.filename()|
`Y` Copy Relative Path |nvim-tree-api.fs.copy.relative_path()| `Y` Copy Relative Path |nvim-tree-api.fs.copy.relative_path()|
@@ -341,7 +341,7 @@ See |nvim-tree-highlight| for details.
See |nvim-tree-api.tree.collapse_all()| See |nvim-tree-api.tree.collapse_all()|
Calls: `api.tree.collapse_all(false)` Calls: `api.tree.collapse_all({ keep_buffers = false })`
*:NvimTreeCollapseKeepBuffers* *:NvimTreeCollapseKeepBuffers*
@@ -350,7 +350,7 @@ See |nvim-tree-highlight| for details.
See |nvim-tree-api.tree.collapse_all()| See |nvim-tree-api.tree.collapse_all()|
Calls: `api.tree.collapse_all(true)` Calls: `api.tree.collapse_all({ keep_buffers = true })`
*:NvimTreeHiTest* *:NvimTreeHiTest*
@@ -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,
@@ -462,7 +463,10 @@ Following is the default configuration. See |nvim-tree-opts| for details. >lua
hidden_placement = "after", hidden_placement = "after",
diagnostics_placement = "signcolumn", diagnostics_placement = "signcolumn",
bookmarks_placement = "signcolumn", bookmarks_placement = "signcolumn",
padding = " ", padding = {
icon = " ",
folder_arrow = " ",
},
symlink_arrow = " ➛ ", symlink_arrow = " ➛ ",
show = { show = {
file = true, file = true,
@@ -541,6 +545,7 @@ Following is the default configuration. See |nvim-tree-opts| for details. >lua
warning = "", warning = "",
error = "", error = "",
}, },
diagnostic_opts = false,
}, },
modified = { modified = {
enable = false, enable = false,
@@ -767,6 +772,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.
@@ -1063,12 +1072,16 @@ 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* *nvim-tree.renderer.icons.padding.icon*
Inserted between icon and filename. Inserted between icon and filename.
Type: `string`, Default: `" "` Type: `string`, Default: `" "`
*nvim-tree.renderer.icons.padding.folder_arrow*
Inserted between folder arrow icon and file/folder icon.
Type: `string`, Default: `" "`
*nvim-tree.renderer.icons.symlink_arrow* *nvim-tree.renderer.icons.symlink_arrow*
Used as a separator between symlinks' source and target. Used as a separator between symlinks' source and target.
Type: `string`, Default: `" ➛ "` Type: `string`, Default: `" ➛ "`
@@ -1317,6 +1330,10 @@ Icons for diagnostic severity.
error = "" error = ""
} }
< <
*nvim-tree.diagnostics.diagnostic_opts*
|vim.diagnostic.Opts| overrides |nvim-tree.diagnostics.severity| and
|nvim-tree.diagnostics.icons|
Type: `boolean`, Default: `false`
============================================================================== ==============================================================================
5.9 OPTS: MODIFIED *nvim-tree-opts-modified* 5.9 OPTS: MODIFIED *nvim-tree-opts-modified*
@@ -1373,7 +1390,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`
@@ -1463,7 +1480,8 @@ vim |current-directory| behaviour.
Type: `boolean`, Default: `false` Type: `boolean`, Default: `false`
*nvim-tree.actions.expand_all* *nvim-tree.actions.expand_all*
Configuration for expand_all behaviour. Configuration for |nvim-tree-api.tree.expand_all()| and
|nvim-tree-api.node.expand()|
*nvim-tree.actions.expand_all.max_folder_discovery* *nvim-tree.actions.expand_all.max_folder_discovery*
Limit the number of folders being explored when expanding every folders. Limit the number of folders being explored when expanding every folders.
@@ -1506,17 +1524,13 @@ 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.
*nvim-tree.actions.open_file.window_picker.enable* *nvim-tree.actions.open_file.window_picker.enable*
Enable the feature. If the feature is not enabled, files will open in Enable the feature. If the feature is not enabled, files will open in
window from which you last opened the tree. window from which you last opened the tree, obeying
|nvim-tree.actions.open_file.window_picker.exclude|
Type: `boolean`, Default: `true` Type: `boolean`, Default: `true`
*nvim-tree.actions.open_file.window_picker.picker* *nvim-tree.actions.open_file.window_picker.picker*
@@ -1535,9 +1549,10 @@ Configuration options for opening a file from nvim-tree.
Type: `string`, Default: `"ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"` Type: `string`, Default: `"ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"`
*nvim-tree.actions.open_file.window_picker.exclude* *nvim-tree.actions.open_file.window_picker.exclude*
Table of buffer option names mapped to a list of option values that Table of buffer option names mapped to a list of option values.
indicates to the picker that the buffer's window should not be Windows containing matching buffers will not be:
selectable. - available when using a window picker
- selected when not using a window picker
Type: `table`, Default: >lua Type: `table`, Default: >lua
{ {
filetype = { filetype = {
@@ -1822,17 +1837,26 @@ tree.find_file({opts}) *nvim-tree-api.tree.find_file()*
tree.search_node() *nvim-tree-api.tree.search_node()* tree.search_node() *nvim-tree-api.tree.search_node()*
Open the search dialogue as per the search_node action. Open the search dialogue as per the search_node action.
tree.collapse_all({keep_buffers}) *nvim-tree-api.tree.collapse_all()* tree.collapse_all({opts}) *nvim-tree-api.tree.collapse_all()*
Collapse the tree. Collapse the tree.
Parameters: ~ Parameters: ~
• {opts} (table) optional parameters
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()
@@ -1894,7 +1918,7 @@ tree.winid({opts}) *nvim-tree-api.tree.winid()*
• {opts} (table) optional parameters • {opts} (table) optional parameters
Options: ~ Options: ~
• {tabpage} (number|nil) tabpage, 0 or nil for current, default nil • {tabpage} (number|nil) tabpage, 0 or nil for current, default nil
Return: ~ Return: ~
(number) winid or nil if tree is not visible (number) winid or nil if tree is not visible
@@ -2007,43 +2031,99 @@ fs.print_clipboard() *nvim-tree-api.fs.print_clipboard()*
Parameters: ~ Parameters: ~
• {node} (Node|nil) file or folder • {node} (Node|nil) file or folder
node.open.edit({node}) *nvim-tree-api.node.open.edit()* node.open.edit({node}, {opts}) *nvim-tree-api.node.open.edit()*
File: open as per |nvim-tree.actions.open_file| File: open as per |nvim-tree.actions.open_file|
Folder: expand or collapse Folder: expand or collapse
Root: change directory up Root: change directory up
Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {quit_on_open} (boolean) quits the tree when opening the file
• {focus} (boolean) keep focus in the tree when opening the file
*nvim-tree-api.node.open.replace_tree_buffer()* *nvim-tree-api.node.open.replace_tree_buffer()*
node.open.replace_tree_buffer({node}) node.open.replace_tree_buffer({node})
|nvim-tree-api.node.edit()|, file will be opened in place: in the |nvim-tree-api.node.edit()|, file will be opened in place: in the
nvim-tree window. nvim-tree window.
*nvim-tree-api.node.open.no_window_picker()* *nvim-tree-api.node.open.no_window_picker()*
node.open.no_window_picker({node}) node.open.no_window_picker({node}, {opts})
|nvim-tree-api.node.edit()|, window picker will never be used as per |nvim-tree-api.node.edit()|, window picker will never be used as per
|nvim-tree.actions.open_file.window_picker.enable| `false` |nvim-tree.actions.open_file.window_picker.enable| `false`
node.open.vertical({node}) *nvim-tree-api.node.open.vertical()* Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {quit_on_open} (boolean) quits the tree when opening the file
• {focus} (boolean) keep focus in the tree when opening the file
node.open.vertical({node}, {opts}) *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.
Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {quit_on_open} (boolean) quits the tree when opening the file
• {focus} (boolean) keep focus in the tree when opening the file
*nvim-tree-api.node.open.vertical_no_picker()* *nvim-tree-api.node.open.vertical_no_picker()*
node.open.vertical_no_picker({node}) node.open.vertical_no_picker({node}, {opts})
|nvim-tree-api.node.vertical()|, window picker will never be used as per |nvim-tree-api.node.vertical()|, window picker will never be used as per
|nvim-tree.actions.open_file.window_picker.enable| `false` |nvim-tree.actions.open_file.window_picker.enable| `false`
node.open.horizontal({node}) *nvim-tree-api.node.open.horizontal()* Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {quit_on_open} (boolean) quits the tree when opening the file
• {focus} (boolean) keep focus in the tree when opening the file
node.open.horizontal({node}, {opts}) *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.
Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {quit_on_open} (boolean) quits the tree when opening the file
• {focus} (boolean) keep focus in the tree when opening the file
*nvim-tree-api.node.open.horizontal_no_picker()* *nvim-tree-api.node.open.horizontal_no_picker()*
node.open.horizontal_no_picker({node}) node.open.horizontal_no_picker({node}, {opts})
|nvim-tree-api.node.horizontal()|, window picker will never be used as per |nvim-tree-api.node.horizontal()|, window picker will never be used as per
|nvim-tree.actions.open_file.window_picker.enable| `false` |nvim-tree.actions.open_file.window_picker.enable| `false`
Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {quit_on_open} (boolean) quits the tree when opening the file
• {focus} (boolean) keep focus in the tree when opening the file
*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}, {opts})
Toggle |nvim-tree.renderer.group_empty| for a specific folder. Toggle |nvim-tree.renderer.group_empty| for a specific folder.
Does nothing on files. Does nothing on files.
Needs |nvim-tree.renderer.group_empty| set. Needs |nvim-tree.renderer.group_empty| set.
Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {quit_on_open} (boolean) quits the tree when opening the file
• {focus} (boolean) keep focus in the tree when opening the file
node.open.drop({node}) *nvim-tree-api.node.open.drop()* node.open.drop({node}) *nvim-tree-api.node.open.drop()*
Switch to window with selected file if it exists. Switch to window with selected file if it exists.
Open file otherwise. Open file otherwise.
@@ -2053,9 +2133,17 @@ node.open.drop({node}) *nvim-tree-api.node.open.drop()*
Folder: expand or collapse Folder: expand or collapse
Root: change directory up Root: change directory up
node.open.tab({node}) *nvim-tree-api.node.open.tab()* node.open.tab({node}, {opts}) *nvim-tree-api.node.open.tab()*
|nvim-tree-api.node.edit()|, file will be opened in a new tab. |nvim-tree-api.node.edit()|, file will be opened in a new tab.
Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {quit_on_open} (boolean) quits the tree when opening the file
• {focus} (boolean) keep focus in the tree when opening the file
*nvim-tree-api.node.open.tab_drop()* *nvim-tree-api.node.open.tab_drop()*
node.open.tab_drop({node}) node.open.tab_drop({node})
Switch to tab containing window with selected file if it exists. Switch to tab containing window with selected file if it exists.
@@ -2065,15 +2153,31 @@ node.open.tab_drop({node})
Folder: expand or collapse Folder: expand or collapse
Root: change directory up Root: change directory up
node.open.preview({node}) *nvim-tree-api.node.open.preview()* node.open.preview({node}, {opts}) *nvim-tree-api.node.open.preview()*
|nvim-tree-api.node.edit()|, file buffer will have |bufhidden| set to `delete`. |nvim-tree-api.node.edit()|, file buffer will have |bufhidden| set to `delete`.
Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {quit_on_open} (boolean) quits the tree when opening the file
• {focus} (boolean) keep focus in the tree when opening the file
*nvim-tree-api.node.open.preview_no_picker()* *nvim-tree-api.node.open.preview_no_picker()*
node.open.preview_no_picker({node}) node.open.preview_no_picker({node}, {opts})
|nvim-tree-api.node.edit()|, file buffer will have |bufhidden| set to `delete`. |nvim-tree-api.node.edit()|, file buffer will have |bufhidden| set to `delete`.
window picker will never be used as per window picker will never be used as per
|nvim-tree.actions.open_file.window_picker.enable| `false` |nvim-tree.actions.open_file.window_picker.enable| `false`
Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {quit_on_open} (boolean) quits the tree when opening the file
• {focus} (boolean) keep focus in the tree when opening the file
node.navigate.git.next({node}) *nvim-tree-api.node.navigate.git.next()* node.navigate.git.next({node}) *nvim-tree-api.node.navigate.git.next()*
Navigate to the next item showing git status. Navigate to the next item showing git status.
@@ -2164,6 +2268,51 @@ node.run.cmd({node}) *nvim-tree-api.node.run.cmd()*
node.run.system({node}) *nvim-tree-api.node.run.system()* node.run.system({node}) *nvim-tree-api.node.run.system()*
Execute |nvim-tree.system_open| Execute |nvim-tree.system_open|
node.buffer.delete({node}, {opts}) *nvim-tree-api.node.buffer.delete()*
Deletes node's related buffer, if one exists.
Executes |:bdelete| or |:bdelete|!
Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {force} (boolean) delete even if buffer is modified, default false
node.buffer.wipe({node}, {opts}) *nvim-tree-api.node.buffer.wipe()*
Wipes node's related buffer, if one exists.
Executes |:bwipe| or |:bwipe|!
Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {force} (boolean) wipe even if buffer is modified, default false
node.expand({node}, {opts}) *nvim-tree-api.node.expand()*
Recursively expand all nodes under a directory or a file's parent
directory.
Parameters: ~
• {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()*
Collapse the tree under a directory or a file's parent directory.
Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {keep_buffers} (boolean) do not collapse nodes with open buffers.
============================================================================== ==============================================================================
6.4 API GIT *nvim-tree-api.git* 6.4 API GIT *nvim-tree-api.git*
@@ -2190,6 +2339,7 @@ events.Event *nvim-tree-api.events.Event*
live_filter.start() *nvim-tree-api.live_filter.start()* live_filter.start() *nvim-tree-api.live_filter.start()*
Enter |nvim-tree.live_filter| mode. Enter |nvim-tree.live_filter| mode.
Opens an input window with |filetype| `"NvimTreeFilter"`
live_filter.clear() *nvim-tree-api.live_filter.clear()* live_filter.clear() *nvim-tree-api.live_filter.clear()*
Exit |nvim-tree.live_filter| mode. Exit |nvim-tree.live_filter| mode.
@@ -2418,7 +2568,7 @@ You are encouraged to copy these to your own |nvim-tree.on_attach| function. >lu
vim.keymap.set("n", "S", api.tree.search_node, opts("Search")) vim.keymap.set("n", "S", api.tree.search_node, opts("Search"))
vim.keymap.set("n", "u", api.fs.rename_full, opts("Rename: Full Path")) vim.keymap.set("n", "u", api.fs.rename_full, opts("Rename: Full Path"))
vim.keymap.set("n", "U", api.tree.toggle_custom_filter, opts("Toggle Filter: Hidden")) vim.keymap.set("n", "U", api.tree.toggle_custom_filter, opts("Toggle Filter: Hidden"))
vim.keymap.set("n", "W", api.tree.collapse_all, opts("Collapse")) vim.keymap.set("n", "W", api.tree.collapse_all, opts("Collapse All"))
vim.keymap.set("n", "x", api.fs.cut, opts("Cut")) vim.keymap.set("n", "x", api.fs.cut, opts("Cut"))
vim.keymap.set("n", "y", api.fs.copy.filename, opts("Copy Name")) vim.keymap.set("n", "y", api.fs.copy.filename, opts("Copy Name"))
vim.keymap.set("n", "Y", api.fs.copy.relative_path, opts("Copy Relative Path")) vim.keymap.set("n", "Y", api.fs.copy.relative_path, opts("Copy Relative Path"))
@@ -2667,13 +2817,21 @@ e.g. handler for node renamed: >lua
|nvim_tree_events_kind| |nvim_tree_events_kind|
- Event.Ready - Event.Ready
When NvimTree has been initialized When NvimTree has been initialized.
• Note: Handler takes no parameter.
- Event.TreePreOpen
Invoked before the window and buffer for NvimTree are created
or opened. Before |Event.TreeOpen| event.
• Note: Handler takes no parameter. • Note: Handler takes no parameter.
- Event.TreeOpen - Event.TreeOpen
Invoked after the NvimTree is opened.
• Note: Handler takes no parameter. • Note: Handler takes no parameter.
- Event.TreeClose - Event.TreeClose
Invoked after the NvimTree is closed, but before the window is
closed. Dispatched on |WinClosed| event for NvimTree window.
• Note: Handler takes no parameter. • Note: Handler takes no parameter.
- Event.Resize - When NvimTree is resized. - Event.Resize - When NvimTree is resized.
@@ -2788,43 +2946,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
@@ -2832,33 +3018,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*
@@ -2868,9 +3056,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*
@@ -2917,6 +3102,7 @@ needed.
`sort_by` |nvim-tree.sort.sorter| `sort_by` |nvim-tree.sort.sorter|
`git.ignore` |nvim-tree.filters.git_ignored| `git.ignore` |nvim-tree.filters.git_ignored|
`renderer.icons.webdev_colors` |nvim-tree.renderer.icons.web_devicons.file.color| `renderer.icons.webdev_colors` |nvim-tree.renderer.icons.web_devicons.file.color|
`renderer.icons.padding` |nvim-tree.renderer.icons.padding.icon|
============================================================================== ==============================================================================
14.2 LEGACY: HIGHLIGHT *nvim-tree-legacy-highlight* 14.2 LEGACY: HIGHLIGHT *nvim-tree-legacy-highlight*
@@ -2995,6 +3181,7 @@ highlight group is not, hard linking as follows: >
|nvim-tree.actions.use_system_clipboard| |nvim-tree.actions.use_system_clipboard|
|nvim-tree.auto_reload_on_write| |nvim-tree.auto_reload_on_write|
|nvim-tree.diagnostics.debounce_delay| |nvim-tree.diagnostics.debounce_delay|
|nvim-tree.diagnostics.diagnostic_opts|
|nvim-tree.diagnostics.enable| |nvim-tree.diagnostics.enable|
|nvim-tree.diagnostics.icons| |nvim-tree.diagnostics.icons|
|nvim-tree.diagnostics.severity| |nvim-tree.diagnostics.severity|
@@ -3004,7 +3191,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|
@@ -3073,7 +3259,8 @@ highlight group is not, hard linking as follows: >
|nvim-tree.renderer.icons.glyphs.symlink| |nvim-tree.renderer.icons.glyphs.symlink|
|nvim-tree.renderer.icons.hidden_placement| |nvim-tree.renderer.icons.hidden_placement|
|nvim-tree.renderer.icons.modified_placement| |nvim-tree.renderer.icons.modified_placement|
|nvim-tree.renderer.icons.padding| |nvim-tree.renderer.icons.padding.folder_arrow|
|nvim-tree.renderer.icons.padding.icon|
|nvim-tree.renderer.icons.show| |nvim-tree.renderer.icons.show|
|nvim-tree.renderer.icons.show.bookmarks| |nvim-tree.renderer.icons.show.bookmarks|
|nvim-tree.renderer.icons.show.diagnostics| |nvim-tree.renderer.icons.show.diagnostics|
@@ -3124,6 +3311,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|
@@ -3178,6 +3366,10 @@ highlight group is not, hard linking as follows: >
|nvim-tree-api.marks.navigate.prev()| |nvim-tree-api.marks.navigate.prev()|
|nvim-tree-api.marks.navigate.select()| |nvim-tree-api.marks.navigate.select()|
|nvim-tree-api.marks.toggle()| |nvim-tree-api.marks.toggle()|
|nvim-tree-api.node.buffer.delete()|
|nvim-tree-api.node.buffer.wipe()|
|nvim-tree-api.node.collapse()|
|nvim-tree-api.node.expand()|
|nvim-tree-api.node.navigate.diagnostics.next()| |nvim-tree-api.node.navigate.diagnostics.next()|
|nvim-tree-api.node.navigate.diagnostics.next_recursive()| |nvim-tree-api.node.navigate.diagnostics.next_recursive()|
|nvim-tree-api.node.navigate.diagnostics.prev()| |nvim-tree-api.node.navigate.diagnostics.prev()|

View File

@@ -236,6 +236,20 @@ local function setup_autocommands(opts)
end, end,
}) })
end end
-- Handles event dispatch when tree is closed by `:q`
create_nvim_tree_autocmd("WinClosed", {
pattern = "*",
---@param ev vim.api.keyset.create_autocmd.callback_args
callback = function(ev)
if not vim.api.nvim_buf_is_valid(ev.buf) then
return
end
if vim.api.nvim_get_option_value("filetype", { buf = ev.buf }) == "NvimTree" then
require("nvim-tree.events")._dispatch_on_tree_close()
end
end,
})
end end
local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
@@ -259,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,
@@ -323,7 +338,10 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
hidden_placement = "after", hidden_placement = "after",
diagnostics_placement = "signcolumn", diagnostics_placement = "signcolumn",
bookmarks_placement = "signcolumn", bookmarks_placement = "signcolumn",
padding = " ", padding = {
icon = " ",
folder_arrow = " ",
},
symlink_arrow = "", symlink_arrow = "",
show = { show = {
file = true, file = true,
@@ -402,6 +420,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
warning = "", warning = "",
error = "", error = "",
}, },
diagnostic_opts = false,
}, },
modified = { modified = {
enable = false, enable = false,

View File

@@ -32,7 +32,7 @@ function M.fn(path)
local profile = log.profile_start("find file %s", path_real) local profile = log.profile_start("find file %s", path_real)
-- refresh the contents of all parents, expanding groups as needed -- refresh the contents of all parents, expanding groups as needed
if utils.get_node_from_path(path_real) == nil then if explorer:get_node_from_path(path_real) == nil then
explorer:refresh_parent_nodes_for_path(vim.fn.fnamemodify(path_real, ":h")) explorer:refresh_parent_nodes_for_path(vim.fn.fnamemodify(path_real, ":h"))
end end

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

@@ -1,4 +1,3 @@
local utils = require("nvim-tree.utils")
local view = require("nvim-tree.view") local view = require("nvim-tree.view")
local core = require("nvim-tree.core") local core = require("nvim-tree.core")
local diagnostics = require("nvim-tree.diagnostics") local diagnostics = require("nvim-tree.diagnostics")
@@ -36,7 +35,7 @@ end
---@param skip_gitignored boolean? default false ---@param skip_gitignored boolean? default false
local function move(explorer, where, what, skip_gitignored) local function move(explorer, where, what, skip_gitignored)
local first_node_line = core.get_nodes_starting_line() local first_node_line = core.get_nodes_starting_line()
local nodes_by_line = utils.get_nodes_by_line(explorer.nodes, first_node_line) local nodes_by_line = explorer:get_nodes_by_line(first_node_line)
local iter_start, iter_end, iter_step, cur, first, nex local iter_start, iter_end, iter_step, cur, first, nex
local cursor = explorer:get_cursor_position() local cursor = explorer:get_cursor_position()
@@ -191,7 +190,7 @@ local function move_prev_recursive(explorer, what, skip_gitignored)
if node_init.name == ".." then -- root node if node_init.name == ".." then -- root node
view.set_cursor({ 1, 0 }) -- move to root node (position 1) view.set_cursor({ 1, 0 }) -- move to root node (position 1)
else else
local node_init_line = utils.find_node_line(node_init) local node_init_line = explorer:find_node_line(node_init)
if node_init_line < 0 then if node_init_line < 0 then
return return
end end

View File

@@ -1,6 +1,4 @@
local view = require("nvim-tree.view") local view = require("nvim-tree.view")
local utils = require("nvim-tree.utils")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
local M = {} local M = {}
@@ -29,7 +27,7 @@ function M.fn(should_close)
return return
end end
local _, line = utils.find_node(parent.explorer.nodes, function(n) local _, line = parent.explorer:find_node(function(n)
return n.absolute_path == parent.absolute_path return n.absolute_path == parent.absolute_path
end) end)

View File

@@ -1,4 +1,3 @@
local utils = require("nvim-tree.utils")
local core = require("nvim-tree.core") local core = require("nvim-tree.core")
local Iterator = require("nvim-tree.iterators.node-iterator") local Iterator = require("nvim-tree.iterators.node-iterator")
@@ -12,9 +11,14 @@ function M.fn(direction)
return return
end end
local explorer = core.get_explorer()
if not explorer then
return
end
local first, last, next, prev = nil, nil, nil, nil local first, last, next, prev = nil, nil, nil, nil
local found = false local found = false
local parent = node.parent or core.get_explorer() local parent = node.parent or explorer
Iterator.builder(parent and parent.nodes or {}) Iterator.builder(parent and parent.nodes or {})
:recursor(function() :recursor(function()
return nil return nil
@@ -45,7 +49,7 @@ function M.fn(direction)
end end
if target_node then if target_node then
utils.focus_file(target_node.absolute_path) explorer:focus_node_or_parent(target_node)
end end
end end
end end

View File

@@ -0,0 +1,58 @@
-- Copyright 2019 Yazdani Kiyan under MIT License
local notify = require("nvim-tree.notify")
local M = {}
---@param node Node
---@param opts ApiNodeDeleteWipeBufferOpts|nil
---@return nil
function M.delete(node, opts)
M.delete_buffer("delete", node.absolute_path, opts)
end
---@param node Node
---@param opts ApiNodeDeleteWipeBufferOpts|nil
---@return nil
function M.wipe(node, opts)
M.delete_buffer("wipe", node.absolute_path, opts)
end
---@alias ApiNodeDeleteWipeBufferMode '"delete"'|'"wipe"'
---@param mode ApiNodeDeleteWipeBufferMode
---@param filename string
---@param opts ApiNodeDeleteWipeBufferOpts|nil
---@return nil
function M.delete_buffer(mode, filename, opts)
if type(mode) ~= "string" then
mode = "delete"
end
local buf_fn = vim.cmd.bdelete
if mode == "wipe" then
buf_fn = vim.cmd.bwipe
end
opts = opts or { force = false }
local notify_node = notify.render_path(filename)
-- check if buffer for file at cursor exists and if it is loaded
local bufnr_at_filename = vim.fn.bufnr(filename)
if bufnr_at_filename == -1 or vim.fn.getbufinfo(bufnr_at_filename)[1].loaded == 0 then
notify.info("No loaded buffer coincides with " .. notify_node)
return
end
local force = opts.force
-- check if buffer is modified
local buf_modified = vim.fn.getbufinfo(bufnr_at_filename)[1].changed
if not force and buf_modified == 1 then
notify.error("Buffer for file " .. notify_node .. " is modified")
return
end
buf_fn({ filename, bang = force })
end
return M

View File

@@ -50,13 +50,16 @@ local function setup_window(node)
file_path = node.absolute_path, file_path = node.absolute_path,
} }
local bufnr = vim.api.nvim_create_buf(false, true) local bufnr = vim.api.nvim_create_buf(false, true)
vim.bo[bufnr].bufhidden = "wipe"
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
vim.api.nvim_win_set_buf(winnr, bufnr) vim.api.nvim_win_set_buf(winnr, bufnr)
end 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

@@ -4,6 +4,7 @@ M.file_popup = require("nvim-tree.actions.node.file-popup")
M.open_file = require("nvim-tree.actions.node.open-file") M.open_file = require("nvim-tree.actions.node.open-file")
M.run_command = require("nvim-tree.actions.node.run-command") M.run_command = require("nvim-tree.actions.node.run-command")
M.system_open = require("nvim-tree.actions.node.system-open") M.system_open = require("nvim-tree.actions.node.system-open")
M.buffer = require("nvim-tree.actions.node.buffer")
function M.setup(opts) function M.setup(opts)
require("nvim-tree.actions.node.system-open").setup(opts) require("nvim-tree.actions.node.system-open").setup(opts)

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,21 +40,15 @@ 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
---Find the first window in the tab that is not NvimTree.
---@return integer -1 if none available
local function first_win_id()
local selectable = usable_win_ids()
if #selectable > 0 then
return selectable[1]
else
return -1
end
end
---Get user to pick a window in the tab that is not NvimTree. ---Get user to pick a window in the tab that is not NvimTree.
---@return integer|nil -- If a valid window was picked, return its id. If an ---@return integer|nil -- If a valid window was picked, return its id. If an
--- invalid window was picked / user canceled, return nil. If there are --- invalid window was picked / user canceled, return nil. If there are
@@ -80,6 +75,14 @@ local function pick_win_id()
local win_map = {} local win_map = {}
local laststatus = vim.o.laststatus local laststatus = vim.o.laststatus
vim.o.laststatus = 2 vim.o.laststatus = 2
local fillchars = vim.opt.fillchars:get()
local stl = fillchars.stl
local stlnc = fillchars.stlnc
fillchars.stl = nil
fillchars.stlnc = nil
vim.opt.fillchars = fillchars
fillchars.stl = stl
fillchars.stlnc = stlnc
local tabpage = vim.api.nvim_get_current_tabpage() local tabpage = vim.api.nvim_get_current_tabpage()
local win_ids = vim.api.nvim_tabpage_list_wins(tabpage) local win_ids = vim.api.nvim_tabpage_list_wins(tabpage)
@@ -179,6 +182,7 @@ local function pick_win_id()
end end
vim.o.laststatus = laststatus vim.o.laststatus = laststatus
vim.opt.fillchars = fillchars
if not vim.tbl_contains(vim.split(M.window_picker.chars, ""), resp) then if not vim.tbl_contains(vim.split(M.window_picker.chars, ""), resp) then
return return
@@ -194,7 +198,15 @@ local function open_file_in_tab(filename)
if M.relative_path then if M.relative_path then
filename = utils.path_relative(filename, vim.fn.getcwd()) filename = utils.path_relative(filename, vim.fn.getcwd())
end end
vim.cmd("tabe " .. vim.fn.fnameescape(filename)) vim.cmd.tabnew()
vim.bo.bufhidden = "wipe"
-- Following vim.fn.tabnew the # buffer may be set to the tree buffer. There is no way to clear the # buffer via vim.fn.setreg as it requires a valid buffer. Clear # by setting it to a new temporary scratch buffer.
if utils.is_nvim_tree_buf(vim.fn.bufnr("#")) then
local tmpbuf = vim.api.nvim_create_buf(false, true)
vim.fn.setreg("#", tmpbuf)
vim.api.nvim_buf_delete(tmpbuf, { force = true })
end
vim.cmd.edit(vim.fn.fnameescape(filename))
end end
local function drop(filename) local function drop(filename)
@@ -237,9 +249,14 @@ local function get_target_winid(mode)
local target_winid local target_winid
if not M.window_picker.enable or string.find(mode, "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 local usable_wins = usable_win_ids()
if not vim.tbl_contains(vim.api.nvim_tabpage_list_wins(0), target_winid) then -- first available usable window
target_winid = first_win_id() if not vim.tbl_contains(usable_wins, target_winid) then
if #usable_wins > 0 then
target_winid = usable_wins[1]
else
target_winid = -1
end
end end
else else
-- pick a window -- pick a window

View File

@@ -2,6 +2,7 @@ local utils = require("nvim-tree.utils")
local core = require("nvim-tree.core") local core = require("nvim-tree.core")
local Iterator = require("nvim-tree.iterators.node-iterator") local Iterator = require("nvim-tree.iterators.node-iterator")
local FileNode = require("nvim-tree.node.file")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
local M = {} local M = {}
@@ -23,26 +24,30 @@ local function buf_match()
end end
end end
---@param keep_buffers boolean ---Collapse a node, root if nil
function M.fn(keep_buffers) ---@param node Node?
---@param opts ApiCollapseOpts
local function collapse(node, opts)
local explorer = core.get_explorer() local explorer = core.get_explorer()
if not explorer then if not explorer then
return return
end end
local node = explorer:get_node_at_cursor() node = node or explorer
if not node then
local node_at_cursor = explorer:get_node_at_cursor()
if not node_at_cursor then
return return
end end
local matches = buf_match() local matches = buf_match()
Iterator.builder(explorer.nodes) Iterator.builder({ node:is(FileNode) and node.parent or node:as(DirectoryNode) })
:hidden() :hidden()
:applier(function(n) :applier(function(n)
local dir = n:as(DirectoryNode) local dir = n:as(DirectoryNode)
if dir then if dir then
dir.open = keep_buffers and matches(dir.absolute_path) dir.open = opts.keep_buffers == true and matches(dir.absolute_path)
end end
end) end)
:recursor(function(n) :recursor(function(n)
@@ -51,7 +56,26 @@ function M.fn(keep_buffers)
:iterate() :iterate()
explorer.renderer:draw() explorer.renderer:draw()
utils.focus_node_or_parent(node) explorer:focus_node_or_parent(node_at_cursor)
end
---@param opts ApiCollapseOpts|boolean|nil legacy -> opts.keep_buffers
function M.all(opts)
-- legacy arguments
if type(opts) == "boolean" then
opts = {
keep_buffers = opts,
}
end
collapse(nil, opts or {})
end
---@param node Node
---@param opts ApiCollapseOpts?
function M.node(node, opts)
collapse(node, opts or {})
end end
return M return M

View File

@@ -1,95 +0,0 @@
local core = require("nvim-tree.core")
local Iterator = require("nvim-tree.iterators.node-iterator")
local notify = require("nvim-tree.notify")
local DirectoryNode = require("nvim-tree.node.directory")
local M = {}
---@param list string[]
---@return table
local function to_lookup_table(list)
local table = {}
for _, element in ipairs(list) do
table[element] = true
end
return table
end
---@param node DirectoryNode
local function expand(node)
node = node:last_group_node()
node.open = true
if #node.nodes == 0 then
core.get_explorer():expand(node)
end
end
---@param expansion_count integer
---@param node Node
---@return boolean
local function should_expand(expansion_count, node)
local dir = node:as(DirectoryNode)
if not dir then
return false
end
local should_halt = expansion_count >= M.MAX_FOLDER_DISCOVERY
local should_exclude = M.EXCLUDE[dir.name]
return not should_halt and not dir.open and not should_exclude
end
local function gen_iterator()
local expansion_count = 0
return function(parent)
if parent.parent and parent.nodes and not parent.open then
expansion_count = expansion_count + 1
expand(parent)
end
Iterator.builder(parent.nodes)
:hidden()
:applier(function(node)
if should_expand(expansion_count, node) then
expansion_count = expansion_count + 1
node = node:as(DirectoryNode)
if node then
expand(node)
end
end
end)
:recursor(function(node)
return expansion_count < M.MAX_FOLDER_DISCOVERY and (node.group_next and { node.group_next } or (node.open and node.nodes))
end)
:iterate()
if expansion_count >= M.MAX_FOLDER_DISCOVERY then
return true
end
end
end
---Expand the directory node or the root
---@param node Node
function M.fn(node)
local explorer = core.get_explorer()
local parent = node:as(DirectoryNode) or explorer
if not parent then
return
end
if gen_iterator()(parent) then
notify.warn("expansion iteration was halted after " .. M.MAX_FOLDER_DISCOVERY .. " discovered folders")
end
if explorer then
explorer.renderer:draw()
end
end
function M.setup(opts)
M.MAX_FOLDER_DISCOVERY = opts.actions.expand_all.max_folder_discovery
M.EXCLUDE = to_lookup_table(opts.actions.expand_all.exclude)
end
return M

View File

@@ -0,0 +1,166 @@
local core = require("nvim-tree.core")
local Iterator = require("nvim-tree.iterators.node-iterator")
local notify = require("nvim-tree.notify")
local FileNode = require("nvim-tree.node.file")
local DirectoryNode = require("nvim-tree.node.directory")
local M = {}
---@param list string[]
---@return table
local function to_lookup_table(list)
local table = {}
for _, element in ipairs(list) do
table[element] = true
end
return table
end
---@param node DirectoryNode
local function expand(node)
node = node:last_group_node()
node.open = true
if #node.nodes == 0 then
core.get_explorer():expand(node)
end
end
---@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
---@return boolean
local function descend_until_empty(_, node)
local dir = node:as(DirectoryNode)
if not dir then
return false
end
local should_exclude = M.EXCLUDE[dir.name]
return not should_exclude
end
---@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
return function(parent)
if parent.parent and parent.nodes and not parent.open then
expansion_count = expansion_count + 1
expand(parent)
end
Iterator.builder(parent.nodes)
:hidden()
:applier(function(node)
if should_expand(expansion_count, node, should_descend) then
expansion_count = expansion_count + 1
node = node:as(DirectoryNode)
if node then
expand(node)
end
end
end)
:recursor(function(node)
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)
:iterate()
end
end
---@param node Node?
---@param expand_opts ApiTreeExpandOpts?
local function expand_node(node, expand_opts)
if not node then
return
end
local descend_until = limit_folder_discovery((expand_opts and expand_opts.expand_until) or descend_until_empty)
gen_iterator(descend_until)(node)
local explorer = core.get_explorer()
if explorer then
explorer.renderer:draw()
end
end
---Expand the directory node or the root
---@param node Node
---@param expand_opts ApiTreeExpandOpts?
function M.all(node, expand_opts)
expand_node(node and node:as(DirectoryNode) or core.get_explorer(), expand_opts)
end
---Expand the directory node or parent node
---@param node Node
---@param expand_opts ApiTreeExpandOpts?
function M.node(node, expand_opts)
if not node then
return
end
expand_node(node:is(FileNode) and node.parent or node:as(DirectoryNode), expand_opts)
end
function M.setup(opts)
M.MAX_FOLDER_DISCOVERY = opts.actions.expand_all.max_folder_discovery
M.EXCLUDE = to_lookup_table(opts.actions.expand_all.exclude)
end
return M

View File

@@ -1,10 +1,10 @@
local M = {} local M = {}
M.collapse_all = require("nvim-tree.actions.tree.modifiers.collapse-all") M.collapse = require("nvim-tree.actions.tree.modifiers.collapse")
M.expand_all = require("nvim-tree.actions.tree.modifiers.expand-all") M.expand = require("nvim-tree.actions.tree.modifiers.expand")
function M.setup(opts) function M.setup(opts)
M.expand_all.setup(opts) M.expand.setup(opts)
end end
return M return M

View File

@@ -24,6 +24,7 @@ local Api = {
}, },
run = {}, run = {},
open = {}, open = {},
buffer = {},
}, },
events = {}, events = {},
marks = { marks = {
@@ -181,8 +182,16 @@ Api.tree.get_nodes = wrap_explorer("get_nodes")
Api.tree.find_file = wrap(actions.tree.find_file.fn) Api.tree.find_file = wrap(actions.tree.find_file.fn)
Api.tree.search_node = wrap(actions.finders.search_node.fn) Api.tree.search_node = wrap(actions.finders.search_node.fn)
Api.tree.collapse_all = wrap(actions.tree.modifiers.collapse_all.fn)
Api.tree.expand_all = wrap_node(actions.tree.modifiers.expand_all.fn) ---@class ApiCollapseOpts
---@field keep_buffers boolean|nil default false
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.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")
Api.tree.toggle_git_clean_filter = wrap_explorer_member_args("filters", "toggle", "git_clean") Api.tree.toggle_git_clean_filter = wrap_explorer_member_args("filters", "toggle", "git_clean")
@@ -221,21 +230,46 @@ Api.fs.copy.absolute_path = wrap_node(wrap_explorer_member("clipboard", "copy_ab
Api.fs.copy.filename = wrap_node(wrap_explorer_member("clipboard", "copy_filename")) Api.fs.copy.filename = wrap_node(wrap_explorer_member("clipboard", "copy_filename"))
Api.fs.copy.basename = wrap_node(wrap_explorer_member("clipboard", "copy_basename")) Api.fs.copy.basename = wrap_node(wrap_explorer_member("clipboard", "copy_basename"))
Api.fs.copy.relative_path = wrap_node(wrap_explorer_member("clipboard", "copy_path")) Api.fs.copy.relative_path = wrap_node(wrap_explorer_member("clipboard", "copy_path"))
---
---@class NodeEditOpts
---@field quit_on_open boolean|nil default false
---@field focus boolean|nil default true
---@param mode string ---@param mode string
---@param node Node ---@param node Node
local function edit(mode, node) ---@param edit_opts NodeEditOpts?
local function edit(mode, node, edit_opts)
local file_link = node:as(FileLinkNode) local file_link = node:as(FileLinkNode)
local path = file_link and file_link.link_to or node.absolute_path local path = file_link and file_link.link_to or node.absolute_path
local cur_tabpage = vim.api.nvim_get_current_tabpage()
actions.node.open_file.fn(mode, path) actions.node.open_file.fn(mode, path)
edit_opts = edit_opts or {}
local mode_unsupported_quit_on_open = mode == "drop" or mode == "tab_drop" or mode == "edit_in_place"
if not mode_unsupported_quit_on_open and edit_opts.quit_on_open then
view.close(cur_tabpage)
end
local mode_unsupported_focus = mode == "drop" or mode == "tab_drop" or mode == "edit_in_place"
local focus = edit_opts.focus == nil or edit_opts.focus == true
if not mode_unsupported_focus and not focus then
-- if mode == "tabnew" a new tab will be opened and we need to focus back to the previous tab
if mode == "tabnew" then
vim.cmd(":tabprev")
end
view.focus()
end
end end
---@param mode string ---@param mode string
---@param toggle_group boolean? ---@param toggle_group boolean?
---@return fun(node: Node) ---@return fun(node: Node, edit_opts: NodeEditOpts?)
local function open_or_expand_or_dir_up(mode, toggle_group) local function open_or_expand_or_dir_up(mode, toggle_group)
---@param node Node ---@param node Node
return function(node) ---@param edit_opts NodeEditOpts?
return function(node, edit_opts)
local root = node:as(RootNode) local root = node:as(RootNode)
local dir = node:as(DirectoryNode) local dir = node:as(DirectoryNode)
@@ -244,7 +278,7 @@ local function open_or_expand_or_dir_up(mode, toggle_group)
elseif dir then elseif dir then
dir:expand_or_collapse(toggle_group) dir:expand_or_collapse(toggle_group)
elseif not toggle_group then elseif not toggle_group then
edit(mode, node) edit(mode, node, edit_opts)
end end
end end
end end
@@ -286,6 +320,19 @@ Api.node.navigate.diagnostics.prev_recursive = wrap_node(actions.moves.item.fn({
Api.node.navigate.opened.next = wrap_node(actions.moves.item.fn({ where = "next", what = "opened" })) Api.node.navigate.opened.next = wrap_node(actions.moves.item.fn({ where = "next", what = "opened" }))
Api.node.navigate.opened.prev = wrap_node(actions.moves.item.fn({ where = "prev", what = "opened" })) Api.node.navigate.opened.prev = wrap_node(actions.moves.item.fn({ where = "prev", what = "opened" }))
Api.node.expand = wrap_node(actions.tree.modifiers.expand.node)
Api.node.collapse = wrap_node(actions.tree.modifiers.collapse.node)
---@class ApiNodeDeleteWipeBufferOpts
---@field force boolean|nil default false
Api.node.buffer.delete = wrap_node(function(node, opts)
actions.node.buffer.delete(node, opts)
end)
Api.node.buffer.wipe = wrap_node(function(node, opts)
actions.node.buffer.wipe(node, opts)
end)
Api.git.reload = wrap_explorer("reload_git") Api.git.reload = wrap_explorer("reload_git")
Api.events.subscribe = events.subscribe Api.events.subscribe = events.subscribe

View File

@@ -5,6 +5,8 @@ local Class = require("nvim-tree.classic")
-- others with name and links less than this arbitrary value are short -- others with name and links less than this arbitrary value are short
local SHORT_LEN = 50 local SHORT_LEN = 50
local namespace_hi_test_id = vim.api.nvim_create_namespace("NvimTreeHiTest")
---@class (exact) HighlightDisplay: Class for :NvimTreeHiTest ---@class (exact) HighlightDisplay: Class for :NvimTreeHiTest
---@field group string nvim-tree highlight group name ---@field group string nvim-tree highlight group name
---@field links string link chain to a concretely defined group ---@field links string link chain to a concretely defined group
@@ -52,7 +54,12 @@ function HighlightDisplay:render(bufnr, fmt, l)
local text = string.format(fmt, self.group, self.links, self.def) local text = string.format(fmt, self.group, self.links, self.def)
vim.api.nvim_buf_set_lines(bufnr, l, -1, true, { text }) vim.api.nvim_buf_set_lines(bufnr, l, -1, true, { text })
vim.api.nvim_buf_add_highlight(bufnr, -1, self.group, l, 0, #self.group)
if vim.fn.has("nvim-0.11") == 1 and vim.hl and vim.hl.range then
vim.hl.range(bufnr, namespace_hi_test_id, self.group, { l, 0 }, { l, #self.group, }, {})
else
vim.api.nvim_buf_add_highlight(bufnr, -1, self.group, l, 0, #self.group) ---@diagnostic disable-line: deprecated
end
return l + 1 return l + 1
end end

View File

@@ -231,7 +231,10 @@ end
function M.setup(opts) function M.setup(opts)
M.enable = opts.diagnostics.enable M.enable = opts.diagnostics.enable
M.debounce_delay = opts.diagnostics.debounce_delay M.debounce_delay = opts.diagnostics.debounce_delay
M.severity = opts.diagnostics.severity M.severity = opts.diagnostics.diagnostic_opts and {
min = vim.diagnostic.severity.HINT,
max = vim.diagnostic.severity.ERROR
} or opts.diagnostics.severity
if M.enable then if M.enable then
log.line("diagnostics", "setup") log.line("diagnostics", "setup")

View File

@@ -8,6 +8,7 @@ M.Event = {
Ready = "Ready", Ready = "Ready",
WillRenameNode = "WillRenameNode", WillRenameNode = "WillRenameNode",
NodeRenamed = "NodeRenamed", NodeRenamed = "NodeRenamed",
TreePreOpen = "TreePreOpen",
TreeOpen = "TreeOpen", TreeOpen = "TreeOpen",
TreeClose = "TreeClose", TreeClose = "TreeClose",
WillCreateFile = "WillCreateFile", WillCreateFile = "WillCreateFile",
@@ -91,6 +92,11 @@ function M._dispatch_folder_removed(folder_name)
dispatch(M.Event.FolderRemoved, { folder_name = folder_name }) dispatch(M.Event.FolderRemoved, { folder_name = folder_name })
end end
--@private
function M._dispatch_on_tree_pre_open()
dispatch(M.Event.TreePreOpen, nil)
end
--@private --@private
function M._dispatch_on_tree_open() function M._dispatch_on_tree_open()
dispatch(M.Event.TreeOpen, nil) dispatch(M.Event.TreeOpen, nil)

View File

@@ -280,7 +280,7 @@ function Filters:toggle(type)
local node = self.explorer:get_node_at_cursor() local node = self.explorer:get_node_at_cursor()
self.explorer:reload_explorer() self.explorer:reload_explorer()
if node then if node then
utils.focus_node_or_parent(node) self.explorer:focus_node_or_parent(node)
end end
end end

View File

@@ -101,10 +101,19 @@ function Explorer:create_autocmds()
vim.api.nvim_create_autocmd("BufReadPost", { vim.api.nvim_create_autocmd("BufReadPost", {
group = self.augroup_id, group = self.augroup_id,
callback = function(data) callback = function(data)
if (self.filters.state.no_buffer or self.opts.highlight_opened_files ~= "none") and vim.bo[data.buf].buftype == "" then -- only handle normal files
if vim.bo[data.buf].buftype ~= "" then
return
end
if self.filters.state.no_buffer then
-- full reload is required to update the filter state
utils.debounce("Buf:filter_buffer_" .. self.uid_explorer, self.opts.view.debounce_delay, function() utils.debounce("Buf:filter_buffer_" .. self.uid_explorer, self.opts.view.debounce_delay, function()
self:reload_explorer() self:reload_explorer()
end) end)
elseif self.opts.renderer.highlight_opened_files ~= "none" then
-- draw to update opened highlight
self.renderer:draw()
end end
end, end,
}) })
@@ -113,10 +122,21 @@ function Explorer:create_autocmds()
vim.api.nvim_create_autocmd("BufUnload", { vim.api.nvim_create_autocmd("BufUnload", {
group = self.augroup_id, group = self.augroup_id,
callback = function(data) callback = function(data)
if (self.filters.state.no_buffer or self.opts.highlight_opened_files ~= "none") and vim.bo[data.buf].buftype == "" then -- only handle normal files
if vim.bo[data.buf].buftype ~= "" then
return
end
if self.filters.state.no_buffer then
-- full reload is required to update the filter state
utils.debounce("Buf:filter_buffer_" .. self.uid_explorer, self.opts.view.debounce_delay, function() utils.debounce("Buf:filter_buffer_" .. self.uid_explorer, self.opts.view.debounce_delay, function()
self:reload_explorer() self:reload_explorer()
end) end)
elseif self.opts.renderer.highlight_opened_files ~= "none" then
-- draw to update opened highlight; must be delayed as the buffer is still loaded during BufUnload
vim.schedule(function()
self.renderer:draw()
end)
end end
end, end,
}) })
@@ -214,8 +234,9 @@ function Explorer:reload(node, project)
end end
local abs = utils.path_join({ cwd, name }) local abs = utils.path_join({ cwd, name })
---@type uv.fs_stat.result|nil
local stat = vim.loop.fs_lstat(abs) -- path incorrectly specified as an integer
local stat = vim.loop.fs_lstat(abs) ---@diagnostic disable-line param-type-mismatch
local filter_reason = self.filters:should_filter_as_reason(abs, stat, filter_status) local filter_reason = self.filters:should_filter_as_reason(abs, stat, filter_status)
if filter_reason == FILTER_REASON.none then if filter_reason == FILTER_REASON.none then
@@ -373,8 +394,9 @@ function Explorer:populate_children(handle, cwd, node, project, parent)
if Watcher.is_fs_event_capable(abs) then if Watcher.is_fs_event_capable(abs) then
local profile = log.profile_start("populate_children %s", abs) local profile = log.profile_start("populate_children %s", abs)
---@type uv.fs_stat.result|nil -- path incorrectly specified as an integer
local stat = vim.loop.fs_lstat(abs) local stat = vim.loop.fs_lstat(abs) ---@diagnostic disable-line param-type-mismatch
local filter_reason = parent.filters:should_filter_as_reason(abs, stat, filter_status) local filter_reason = parent.filters:should_filter_as_reason(abs, stat, filter_status)
if filter_reason == FILTER_REASON.none and not nodes_by_path[abs] then if filter_reason == FILTER_REASON.none and not nodes_by_path[abs] then
local child = node_factory.create({ local child = node_factory.create({
@@ -389,9 +411,9 @@ function Explorer:populate_children(handle, cwd, node, project, parent)
nodes_by_path[child.absolute_path] = true nodes_by_path[child.absolute_path] = true
child:update_git_status(node_ignored, project) child:update_git_status(node_ignored, project)
end end
else elseif node.hidden_stats then
for reason, value in pairs(FILTER_REASON) do for reason, value in pairs(FILTER_REASON) do
if filter_reason == value then if filter_reason == value and type(node.hidden_stats[reason]) == "number" then
node.hidden_stats[reason] = node.hidden_stats[reason] + 1 node.hidden_stats[reason] = node.hidden_stats[reason] + 1
end end
end end
@@ -505,7 +527,7 @@ function Explorer:get_node_at_cursor()
return self return self
end end
return utils.get_nodes_by_line(self.nodes, core.get_nodes_starting_line())[cursor[1]] return self:get_nodes_by_line(core.get_nodes_starting_line())[cursor[1]]
end end
function Explorer:place_cursor_on_node() function Explorer:place_cursor_on_node()
@@ -529,6 +551,114 @@ function Explorer:place_cursor_on_node()
end end
end end
-- Find the line number of a node.
---@param node Node?
---@return integer -1 not found
function Explorer:find_node_line(node)
if not node then
return -1
end
local first_node_line = core.get_nodes_starting_line()
local nodes_by_line = self:get_nodes_by_line(first_node_line)
local iter_start, iter_end = first_node_line, #nodes_by_line
for line = iter_start, iter_end, 1 do
if nodes_by_line[line] == node then
return line
end
end
return -1
end
-- get the node in the tree state depending on the absolute path of the node
-- (grouped or hidden too)
---@param path string
---@return Node|nil
---@return number|nil
function Explorer:get_node_from_path(path)
if self.absolute_path == path then
return self
end
return Iterator.builder(self.nodes)
:hidden()
:matcher(function(node)
return node.absolute_path == path or node.link_to == path
end)
:recursor(function(node)
if node.group_next then
return { node.group_next }
end
if node.nodes then
return node.nodes
end
end)
:iterate()
end
---Focus node passed as parameter if visible, otherwise focus first visible parent.
---If none of the parents is visible focus root.
---If node is nil do nothing.
---@param node Node? node to focus
function Explorer:focus_node_or_parent(node)
while node do
local found_node, i = self:find_node(function(node_)
return node_.absolute_path == node.absolute_path
end)
if found_node or node.parent == nil then
view.set_cursor({ i + 1, 1 })
break
end
node = node.parent
end
end
--- Get the node and index of the node from the tree that matches the predicate.
--- The explored nodes are those displayed on the view.
---@param fn fun(node: Node): boolean
---@return table|nil
---@return number
function Explorer:find_node(fn)
local node, i = Iterator.builder(self.nodes)
:matcher(fn)
:recursor(function(node)
return node.group_next and { node.group_next } or (node.open and #node.nodes > 0 and node.nodes)
end)
:iterate()
i = view.is_root_folder_visible() and i or i - 1
if node and node.explorer.live_filter.filter then
i = i + 1
end
return node, i
end
--- Return visible nodes indexed by line
---@param line_start number
---@return table
function Explorer:get_nodes_by_line(line_start)
local nodes_by_line = {}
local line = line_start
Iterator.builder(self.nodes)
:applier(function(node)
if node.group_next then
return
end
nodes_by_line[line] = node
line = line + 1
end)
:recursor(function(node)
return node.group_next and { node.group_next } or (node.open and #node.nodes > 0 and node.nodes)
end)
:iterate()
return nodes_by_line
end
---Api.tree.get_nodes ---Api.tree.get_nodes
---@return nvim_tree.api.Node ---@return nvim_tree.api.Node
function Explorer:get_nodes() function Explorer:get_nodes()

View File

@@ -188,8 +188,10 @@ local function create_overlay(self)
if vim.fn.has("nvim-0.10") == 1 then if vim.fn.has("nvim-0.10") == 1 then
vim.api.nvim_set_option_value("modifiable", true, { buf = overlay_bufnr }) vim.api.nvim_set_option_value("modifiable", true, { buf = overlay_bufnr })
vim.api.nvim_set_option_value("filetype", "NvimTreeFilter", { buf = overlay_bufnr })
else else
vim.api.nvim_buf_set_option(overlay_bufnr, "modifiable", true) ---@diagnostic disable-line: deprecated vim.api.nvim_buf_set_option(overlay_bufnr, "modifiable", true) ---@diagnostic disable-line: deprecated
vim.api.nvim_buf_set_option(overlay_bufnr, "filetype", "NvimTreeFilter") ---@diagnostic disable-line: deprecated
end end
vim.api.nvim_buf_set_lines(overlay_bufnr, 0, -1, false, { self.filter }) vim.api.nvim_buf_set_lines(overlay_bufnr, 0, -1, false, { self.filter })
@@ -220,9 +222,9 @@ function LiveFilter:clear_filter()
self.explorer.renderer:draw() self.explorer.renderer:draw()
if node then if node then
utils.focus_file(node.absolute_path) self.explorer:focus_node_or_parent(node)
elseif last_node then elseif last_node then
utils.focus_file(last_node.absolute_path) self.explorer:focus_node_or_parent(last_node)
end end
end end

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")
@@ -232,7 +233,13 @@ local function reload_tree_at(toplevel)
end end
log.line("watcher", "git event executing '%s'", toplevel) log.line("watcher", "git event executing '%s'", toplevel)
local root_node = utils.get_node_from_path(toplevel)
local explorer = require("nvim-tree.core").get_explorer()
if not explorer then
return nil
end
local root_node = explorer:get_node_from_path(toplevel)
if not root_node then if not root_node then
return return
end end
@@ -251,7 +258,7 @@ local function reload_tree_at(toplevel)
end) end)
:iterate() :iterate()
root_node.explorer.renderer:draw() explorer.renderer:draw()
end) end)
end end

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

@@ -11,6 +11,8 @@ local WIN_HL = table.concat({
"CursorLine:NvimTreeCursorLine", "CursorLine:NvimTreeCursorLine",
}, ",") }, ",")
local namespace_help_id = vim.api.nvim_create_namespace("NvimTreeHelp")
local M = { local M = {
config = {}, config = {},
@@ -82,8 +84,8 @@ end
--- Compute all lines for the buffer --- Compute all lines for the buffer
---@param map table keymap.get_keymap ---@param map table keymap.get_keymap
---@return table strings of text ---@return string[] lines of text
---@return table arrays of arguments 3-6 for nvim_buf_add_highlight() ---@return HighlightRangeArgs[] hl_range_args for lines
---@return number maximum length of text ---@return number maximum length of text
local function compute(map) local function compute(map)
local head_lhs = "nvim-tree mappings" local head_lhs = "nvim-tree mappings"
@@ -130,10 +132,10 @@ local function compute(map)
local width = #lines[1] local width = #lines[1]
-- header highlight, assume one character keys -- header highlight, assume one character keys
local hl = { local hl_range_args = {
{ "NvimTreeFolderName", 0, 0, #head_lhs }, { higroup = "NvimTreeFolderName", start = { 0, 0, }, finish = { 0, #head_lhs, }, },
{ "NvimTreeFolderName", 0, width - 1, width }, { higroup = "NvimTreeFolderName", start = { 0, width - 1, }, finish = { 0, width, }, },
{ "NvimTreeFolderName", 1, width - 1, width }, { higroup = "NvimTreeFolderName", start = { 1, width - 1, }, finish = { 1, width, }, },
} }
-- mappings, left padded 1 -- mappings, left padded 1
@@ -145,10 +147,10 @@ local function compute(map)
width = math.max(#line, width) width = math.max(#line, width)
-- highlight lhs -- highlight lhs
table.insert(hl, { "NvimTreeFolderName", i + 1, 1, #l.lhs + 1 }) table.insert(hl_range_args, { higroup = "NvimTreeFolderName", start = { i + 1, 1, }, finish = { i + 1, #l.lhs + 1, }, })
end end
return lines, hl, width return lines, hl_range_args, width
end end
--- close the window and delete the buffer, if they exist --- close the window and delete the buffer, if they exist
@@ -172,7 +174,7 @@ local function open()
local map = keymap.get_keymap() local map = keymap.get_keymap()
-- text and highlight -- text and highlight
local lines, hl, width = compute(map) local lines, hl_range_args, width = compute(map)
-- create the buffer -- create the buffer
M.bufnr = vim.api.nvim_create_buf(false, true) M.bufnr = vim.api.nvim_create_buf(false, true)
@@ -187,8 +189,12 @@ local function open()
end end
-- highlight it -- highlight it
for _, h in ipairs(hl) do for _, args in ipairs(hl_range_args) do
vim.api.nvim_buf_add_highlight(M.bufnr, -1, h[1], h[2], h[3], h[4]) if vim.fn.has("nvim-0.11") == 1 and vim.hl and vim.hl.range then
vim.hl.range(M.bufnr, namespace_help_id, args.higroup, args.start, args.finish, {})
else
vim.api.nvim_buf_add_highlight(M.bufnr, -1, args.higroup, args.start[1], args.start[2], args.finish[2]) ---@diagnostic disable-line: deprecated
end
end end
-- open a very restricted window -- open a very restricted window

View File

@@ -96,7 +96,7 @@ function M.default_on_attach(bufnr)
vim.keymap.set("n", "S", api.tree.search_node, opts("Search")) vim.keymap.set("n", "S", api.tree.search_node, opts("Search"))
vim.keymap.set("n", "u", api.fs.rename_full, opts("Rename: Full Path")) vim.keymap.set("n", "u", api.fs.rename_full, opts("Rename: Full Path"))
vim.keymap.set("n", "U", api.tree.toggle_custom_filter, opts("Toggle Filter: Hidden")) vim.keymap.set("n", "U", api.tree.toggle_custom_filter, opts("Toggle Filter: Hidden"))
vim.keymap.set("n", "W", api.tree.collapse_all, opts("Collapse")) vim.keymap.set("n", "W", api.tree.collapse_all, opts("Collapse All"))
vim.keymap.set("n", "x", api.fs.cut, opts("Cut")) vim.keymap.set("n", "x", api.fs.cut, opts("Cut"))
vim.keymap.set("n", "y", api.fs.copy.filename, opts("Copy Name")) vim.keymap.set("n", "y", api.fs.copy.filename, opts("Copy Name"))
vim.keymap.set("n", "Y", api.fs.copy.relative_path, opts("Copy Relative Path")) vim.keymap.set("n", "Y", api.fs.copy.relative_path, opts("Copy Relative Path"))

View File

@@ -1,27 +1,74 @@
local utils = require("nvim-tree.utils")
local notify = require("nvim-tree.notify") local notify = require("nvim-tree.notify")
local M = {} local M = {}
--- Create empty sub-tables if not present
---@param tbl table to create empty inside of
---@param path string dot separated string of sub-tables
---@return table deepest sub-table
local function create(tbl, path)
local t = tbl
for s in string.gmatch(path, "([^%.]+)%.*") do
if t[s] == nil then
t[s] = {}
end
t = t[s]
end
return t
end
--- Move a value from src to dst if value is nil on dst.
--- Remove value from src
---@param src table to copy from
---@param src_path string dot separated string of sub-tables
---@param src_pos string value pos
---@param dst table to copy to
---@param dst_path string dot separated string of sub-tables, created when missing
---@param dst_pos string value pos
---@param remove boolean
local function move(src, src_path, src_pos, dst, dst_path, dst_pos, remove)
for pos in string.gmatch(src_path, "([^%.]+)%.*") do
if src[pos] and type(src[pos]) == "table" then
src = src[pos]
else
return
end
end
local src_val = src[src_pos]
if src_val == nil then
return
end
dst = create(dst, dst_path)
if dst[dst_pos] == nil then
dst[dst_pos] = src_val
end
if remove then
src[src_pos] = nil
end
end
-- silently move, please add to help nvim-tree-legacy-opts -- silently move, please add to help nvim-tree-legacy-opts
local function refactored(opts) local function refactored(opts)
-- 2022/06/20 -- 2022/06/20
utils.move_missing_val(opts, "update_focused_file", "update_cwd", opts, "update_focused_file", "update_root", true) move(opts, "update_focused_file", "update_cwd", opts, "update_focused_file", "update_root", true)
utils.move_missing_val(opts, "", "update_cwd", opts, "", "sync_root_with_cwd", true) move(opts, "", "update_cwd", opts, "", "sync_root_with_cwd", true)
-- 2022/11/07 -- 2022/11/07
utils.move_missing_val(opts, "", "open_on_tab", opts, "tab.sync", "open", false) move(opts, "", "open_on_tab", opts, "tab.sync", "open", false)
utils.move_missing_val(opts, "", "open_on_tab", opts, "tab.sync", "close", true) move(opts, "", "open_on_tab", opts, "tab.sync", "close", true)
utils.move_missing_val(opts, "", "ignore_buf_on_tab_change", opts, "tab.sync", "ignore", true) move(opts, "", "ignore_buf_on_tab_change", opts, "tab.sync", "ignore", true)
-- 2022/11/22 -- 2022/11/22
utils.move_missing_val(opts, "renderer", "root_folder_modifier", opts, "renderer", "root_folder_label", true) move(opts, "renderer", "root_folder_modifier", opts, "renderer", "root_folder_label", true)
-- 2023/01/01 -- 2023/01/01
utils.move_missing_val(opts, "update_focused_file", "debounce_delay", opts, "view", "debounce_delay", true) move(opts, "update_focused_file", "debounce_delay", opts, "view", "debounce_delay", true)
-- 2023/01/08 -- 2023/01/08
utils.move_missing_val(opts, "trash", "require_confirm", opts, "ui.confirm", "trash", true) move(opts, "trash", "require_confirm", opts, "ui.confirm", "trash", true)
-- 2023/01/15 -- 2023/01/15
if type(opts.view) == "table" and opts.view.adaptive_size ~= nil then if type(opts.view) == "table" and opts.view.adaptive_size ~= nil then
@@ -35,13 +82,13 @@ local function refactored(opts)
end end
-- 2023/07/15 -- 2023/07/15
utils.move_missing_val(opts, "", "sort_by", opts, "sort", "sorter", true) move(opts, "", "sort_by", opts, "sort", "sorter", true)
-- 2023/07/16 -- 2023/07/16
utils.move_missing_val(opts, "git", "ignore", opts, "filters", "git_ignored", true) move(opts, "git", "ignore", opts, "filters", "git_ignored", true)
-- 2023/08/26 -- 2023/08/26
utils.move_missing_val(opts, "renderer.icons", "webdev_colors", opts, "renderer.icons.web_devicons.file", "color", true) move(opts, "renderer.icons", "webdev_colors", opts, "renderer.icons.web_devicons.file", "color", true)
-- 2023/10/08 -- 2023/10/08
if type(opts.renderer) == "table" and type(opts.renderer.highlight_diagnostics) == "boolean" then if type(opts.renderer) == "table" and type(opts.renderer.highlight_diagnostics) == "boolean" then
@@ -59,7 +106,14 @@ local function refactored(opts)
opts.update_focused_file.update_root = { enable = opts.update_focused_file.update_root } opts.update_focused_file.update_root = { enable = opts.update_focused_file.update_root }
end end
end end
utils.move_missing_val(opts, "update_focused_file", "ignore_list", opts, "update_focused_file.update_root", "ignore_list", true) move(opts, "update_focused_file", "ignore_list", opts, "update_focused_file.update_root", "ignore_list", true)
-- 2025/04/30
if opts.renderer and opts.renderer.icons and type(opts.renderer.icons.padding) == "string" then
local icons_padding = opts.renderer.icons.padding
opts.renderer.icons.padding = {}
opts.renderer.icons.padding.icon = icons_padding
end
end end
local function deprecated(opts) local function deprecated(opts)

View File

@@ -1,6 +1,5 @@
local view = require("nvim-tree.view") local view = require("nvim-tree.view")
local core = require("nvim-tree.core") local core = require("nvim-tree.core")
local events = require("nvim-tree.events")
local notify = require("nvim-tree.notify") local notify = require("nvim-tree.notify")
---@class LibOpenOpts ---@class LibOpenOpts
@@ -130,7 +129,6 @@ function M.open(opts)
open_view_and_draw() open_view_and_draw()
end end
view.restore_tab_state() view.restore_tab_state()
events._dispatch_on_tree_open()
end end
function M.setup(opts) function M.setup(opts)

View File

@@ -227,9 +227,9 @@ function Marks:navigate(up)
end end
if up then if up then
utils.focus_node_or_parent(prev or last) self.explorer:focus_node_or_parent(prev or last)
else else
utils.focus_node_or_parent(next or first) self.explorer:focus_node_or_parent(next or first)
end end
end end
@@ -263,7 +263,7 @@ function Marks:navigate_select()
if node and not node:is(DirectoryNode) and not utils.get_win_buf_from_path(node.absolute_path) then if node and not node:is(DirectoryNode) and not utils.get_win_buf_from_path(node.absolute_path) then
open_file.fn("edit", node.absolute_path) open_file.fn("edit", node.absolute_path)
elseif node then elseif node then
utils.focus_file(node.absolute_path) self.explorer:focus_node_or_parent(node)
end end
end) end)
end end

View File

@@ -33,15 +33,9 @@ local BUILTIN_DECORATORS = {
Cut = CutDecorator, Cut = CutDecorator,
} }
---@class (exact) AddHighlightArgs
---@field group string[]
---@field line number
---@field col_start number
---@field col_end number
---@class (exact) Builder ---@class (exact) Builder
---@field lines string[] includes icons etc. ---@field lines string[] includes icons etc.
---@field hl_args AddHighlightArgs[] line highlights ---@field hl_range_args HighlightRangeArgs[] highlights for lines
---@field signs string[] line signs ---@field signs string[] line signs
---@field extmarks table[] extra marks for right icon placement ---@field extmarks table[] extra marks for right icon placement
---@field virtual_lines table[] virtual lines for hidden count display ---@field virtual_lines table[] virtual lines for hidden count display
@@ -67,7 +61,7 @@ function Builder:new(args)
self.explorer = args.explorer self.explorer = args.explorer
self.index = 0 self.index = 0
self.depth = 0 self.depth = 0
self.hl_args = {} self.hl_range_args = {}
self.combined_groups = {} self.combined_groups = {}
self.lines = {} self.lines = {}
self.markers = {} self.markers = {}
@@ -106,7 +100,9 @@ end
---@param start number ---@param start number
---@param end_ number|nil ---@param end_ number|nil
function Builder:insert_highlight(groups, start, end_) function Builder:insert_highlight(groups, start, end_)
table.insert(self.hl_args, { groups, self.index, start, end_ or -1 }) for _, higroup in ipairs(groups) do
table.insert(self.hl_range_args, { higroup = higroup, start = { self.index, start, }, finish = { self.index, end_ or -1, } })
end
end end
---@private ---@private
@@ -139,12 +135,12 @@ end
function Builder:format_line(indent_markers, arrows, icon, name, node) function Builder:format_line(indent_markers, arrows, icon, name, node)
local added_len = 0 local added_len = 0
local function add_to_end(t1, t2) local function add_to_end(t1, t2)
if not t2 then if not t2 or vim.tbl_isempty(t2) then
return return
end end
for _, v in ipairs(t2) do for _, v in ipairs(t2) do
if added_len > 0 then if added_len > 0 then
table.insert(t1, { str = self.explorer.opts.renderer.icons.padding }) table.insert(t1, { str = self.explorer.opts.renderer.icons.padding.icon })
end end
table.insert(t1, v) table.insert(t1, v)
end end

View File

@@ -1,6 +1,7 @@
local M = {} local M = {}
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local view = require("nvim-tree.view")
local function hide(win) local function hide(win)
if win then if win then
@@ -32,7 +33,7 @@ local function effective_win_width()
return win_width - win_info[1].textoff return win_width - win_info[1].textoff
end end
local function show() local function show(opts)
local line_nr = vim.api.nvim_win_get_cursor(0)[1] local line_nr = vim.api.nvim_win_get_cursor(0)[1]
if vim.wo.wrap then if vim.wo.wrap then
return return
@@ -52,6 +53,11 @@ local function show()
local text_width = vim.fn.strdisplaywidth(vim.fn.substitute(line, "[^[:print:]]*$", "", "g")) local text_width = vim.fn.strdisplaywidth(vim.fn.substitute(line, "[^[:print:]]*$", "", "g"))
local win_width = effective_win_width() local win_width = effective_win_width()
-- windows width reduced by right aligned icons
local icon_ns_id = vim.api.nvim_get_namespaces()["NvimTreeExtmarks"]
local icon_extmarks = vim.api.nvim_buf_get_extmarks(0, icon_ns_id, { line_nr - 1, 0 }, { line_nr - 1, -1 }, { details = true })
win_width = win_width - utils.extmarks_length(icon_extmarks)
if text_width < win_width then if text_width < win_width then
return return
end end
@@ -64,7 +70,9 @@ local function show()
height = 1, height = 1,
noautocmd = true, noautocmd = true,
style = "minimal", style = "minimal",
border = "none"
}) })
vim.wo[M.popup_win].winhl = view.View.winopts.winhl
local ns_id = vim.api.nvim_get_namespaces()["NvimTreeHighlights"] local ns_id = vim.api.nvim_get_namespaces()["NvimTreeHighlights"]
local extmarks = vim.api.nvim_buf_get_extmarks(0, ns_id, { line_nr - 1, 0 }, { line_nr - 1, -1 }, { details = true }) local extmarks = vim.api.nvim_buf_get_extmarks(0, ns_id, { line_nr - 1, 0 }, { line_nr - 1, -1 }, { details = true })
@@ -79,9 +87,18 @@ local function show()
---@type vim.api.keyset.extmark_details ---@type vim.api.keyset.extmark_details
local details = extmark[4] local details = extmark[4]
vim.api.nvim_buf_add_highlight(0, ns_id, details.hl_group, 0, col, details.end_col) if type(details) == "table" then
if vim.fn.has("nvim-0.11") == 1 and vim.hl and vim.hl.range then
vim.hl.range(0, ns_id, details.hl_group, { 0, col }, { 0, details.end_col, }, {})
else
vim.api.nvim_buf_add_highlight(0, ns_id, details.hl_group, 0, col, details.end_col) ---@diagnostic disable-line: deprecated
end
end
end
vim.cmd([[ setlocal nowrap noswapfile nobuflisted buftype=nofile bufhidden=wipe ]])
if opts.view.cursorline then
vim.cmd([[ setlocal cursorline cursorlineopt=both ]])
end end
vim.cmd([[ setlocal nowrap cursorline noswapfile nobuflisted buftype=nofile bufhidden=hide ]])
end) end)
end end
@@ -107,7 +124,7 @@ M.setup = function(opts)
pattern = { "NvimTree_*" }, pattern = { "NvimTree_*" },
callback = function() callback = function()
if utils.is_nvim_tree_buf(0) then if utils.is_nvim_tree_buf(0) then
show() show(opts)
end end
end, end,
}) })

View File

@@ -95,15 +95,15 @@ function M.get_arrows(node)
local dir = node:as(DirectoryNode) local dir = node:as(DirectoryNode)
if dir then if dir then
if dir.open then if dir.open then
str = M.config.icons.glyphs.folder["arrow_open"] .. " " str = M.config.icons.glyphs.folder["arrow_open"] .. M.config.icons.padding.folder_arrow
hl = "NvimTreeFolderArrowOpen" hl = "NvimTreeFolderArrowOpen"
else else
str = M.config.icons.glyphs.folder["arrow_closed"] .. " " str = M.config.icons.glyphs.folder["arrow_closed"] .. M.config.icons.padding.folder_arrow
end end
elseif M.config.indent_markers.enable then elseif M.config.indent_markers.enable then
str = "" str = ""
else else
str = " " str = " " .. string.rep(" ", #M.config.icons.padding.folder_arrow)
end end
return { str = str, hl = { hl } } return { str = str, hl = { hl } }

View File

@@ -41,17 +41,29 @@ local DiagnosticsDecorator = Decorator:extend()
---@protected ---@protected
---@param args DecoratorArgs ---@param args DecoratorArgs
function DiagnosticsDecorator:new(args) function DiagnosticsDecorator:new(args)
self.explorer = args.explorer self.explorer = args.explorer
self.enabled = true self.enabled = true
self.highlight_range = self.explorer.opts.renderer.highlight_diagnostics or "none" self.highlight_range = self.explorer.opts.renderer.highlight_diagnostics or "none"
self.icon_placement = self.explorer.opts.renderer.icons.diagnostics_placement or "none" self.icon_placement = self.explorer.opts.renderer.icons.diagnostics_placement or "none"
local vim_diagnostic_icons = {}
if self.explorer.opts.diagnostics.diagnostic_opts then
local vim_diagnostic_config = vim.diagnostic.config() or {}
local signs = vim_diagnostic_config.signs or {}
if type(signs) == "function" then
signs = signs(0, 0)
end
vim_diagnostic_icons = (type(signs) == "table" and signs.text) or {}
end
if self.explorer.opts.renderer.icons.show.diagnostics then if self.explorer.opts.renderer.icons.show.diagnostics then
self.diag_icons = {} self.diag_icons = {}
for name, sev in pairs(ICON_KEYS) do for name, sev in pairs(ICON_KEYS) do
self.diag_icons[sev] = { self.diag_icons[sev] = {
str = self.explorer.opts.diagnostics.icons[name], str = vim_diagnostic_icons[sev] or self.explorer.opts.diagnostics.icons[name],
hl = { HG_ICON[sev] }, hl = { HG_ICON[sev] },
} }
self:define_sign(self.diag_icons[sev]) self:define_sign(self.diag_icons[sev])

View File

@@ -62,7 +62,7 @@ function GitDecorator:build_icons_by_status(glyphs)
self.icons_by_status.ignored = { str = glyphs.ignored, hl = { "NvimTreeGitIgnoredIcon" }, ord = 7 } self.icons_by_status.ignored = { str = glyphs.ignored, hl = { "NvimTreeGitIgnoredIcon" }, ord = 7 }
end end
---@param icons GitIconsByXY ---@param icons GitIconsByStatus
function GitDecorator: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 },
@@ -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

@@ -112,9 +112,8 @@ function Decorator:define_sign(icon)
vim.fn.sign_undefine(name) vim.fn.sign_undefine(name)
end end
-- don't use sign if not defined -- don't render sign if empty
if #icon.str < 1 then if #icon.str < 1 then
self.icon_placement = "none"
return return
end end

View File

@@ -11,6 +11,8 @@ local namespace_highlights_id = vim.api.nvim_create_namespace("NvimTreeHighlight
local namespace_extmarks_id = vim.api.nvim_create_namespace("NvimTreeExtmarks") local namespace_extmarks_id = vim.api.nvim_create_namespace("NvimTreeExtmarks")
local namespace_virtual_lines_id = vim.api.nvim_create_namespace("NvimTreeVirtualLines") local namespace_virtual_lines_id = vim.api.nvim_create_namespace("NvimTreeVirtualLines")
---@alias HighlightRangeArgs { higroup:string, start:integer[], finish:integer[] } named arguments for vim.hl.range
---@class (exact) Renderer: Class ---@class (exact) Renderer: Class
---@field explorer Explorer ---@field explorer Explorer
local Renderer = Class:extend() local Renderer = Class:extend()
@@ -30,11 +32,11 @@ end
---@private ---@private
---@param bufnr number ---@param bufnr number
---@param lines string[] ---@param lines string[]
---@param hl_args AddHighlightArgs[] ---@param hl_range_args HighlightRangeArgs[]
---@param signs string[] ---@param signs string[]
---@param extmarks table[] extra marks for right icon placement ---@param extmarks table[] extra marks for right icon placement
---@param virtual_lines table[] virtual lines for hidden count display ---@param virtual_lines table[] virtual lines for hidden count display
function Renderer:_draw(bufnr, lines, hl_args, signs, extmarks, virtual_lines) function Renderer:_draw(bufnr, lines, hl_range_args, signs, extmarks, virtual_lines)
if vim.fn.has("nvim-0.10") == 1 then if vim.fn.has("nvim-0.10") == 1 then
vim.api.nvim_set_option_value("modifiable", true, { buf = bufnr }) vim.api.nvim_set_option_value("modifiable", true, { buf = bufnr })
else else
@@ -42,7 +44,7 @@ function Renderer:_draw(bufnr, lines, hl_args, signs, extmarks, virtual_lines)
end end
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
self:render_hl(bufnr, hl_args) self:render_hl(bufnr, hl_range_args)
if vim.fn.has("nvim-0.10") == 1 then if vim.fn.has("nvim-0.10") == 1 then
vim.api.nvim_set_option_value("modifiable", false, { buf = bufnr }) vim.api.nvim_set_option_value("modifiable", false, { buf = bufnr })
@@ -77,16 +79,18 @@ function Renderer:_draw(bufnr, lines, hl_args, signs, extmarks, virtual_lines)
end end
---@private ---@private
function Renderer:render_hl(bufnr, hl) ---@param bufnr integer
---@param hl_range_args HighlightRangeArgs[]
function Renderer:render_hl(bufnr, hl_range_args)
if not bufnr or not vim.api.nvim_buf_is_loaded(bufnr) then if not bufnr or not vim.api.nvim_buf_is_loaded(bufnr) then
return return
end end
vim.api.nvim_buf_clear_namespace(bufnr, namespace_highlights_id, 0, -1) vim.api.nvim_buf_clear_namespace(bufnr, namespace_highlights_id, 0, -1)
for _, data in ipairs(hl) do for _, args in ipairs(hl_range_args) do
if type(data[1]) == "table" then if vim.fn.has("nvim-0.11") == 1 and vim.hl and vim.hl.range then
for _, group in ipairs(data[1]) do vim.hl.range(bufnr, namespace_highlights_id, args.higroup, args.start, args.finish, {})
vim.api.nvim_buf_add_highlight(bufnr, namespace_highlights_id, group, data[2], data[3], data[4]) else
end vim.api.nvim_buf_add_highlight(bufnr, namespace_highlights_id, args.higroup, args.start[1], args.start[2], args.finish[2]) ---@diagnostic disable-line: deprecated
end end
end end
end end
@@ -103,7 +107,7 @@ function Renderer:draw()
local builder = Builder(self.explorer):build() local builder = Builder(self.explorer):build()
self:_draw(bufnr, builder.lines, builder.hl_args, builder.signs, builder.extmarks, builder.virtual_lines) self:_draw(bufnr, builder.lines, builder.hl_range_args, builder.signs, builder.extmarks, builder.virtual_lines)
if cursor and #builder.lines >= cursor[1] then if cursor and #builder.lines >= cursor[1] then
vim.api.nvim_win_set_cursor(view.get_winnr() or 0, cursor) vim.api.nvim_win_set_cursor(view.get_winnr() or 0, cursor)

View File

@@ -1,6 +1,3 @@
local Iterator = require("nvim-tree.iterators.node-iterator")
local notify = require("nvim-tree.notify")
local M = { local M = {
debouncers = {}, debouncers = {},
} }
@@ -18,22 +15,6 @@ function M.str_find(haystack, needle)
return vim.fn.stridx(haystack, needle) ~= -1 return vim.fn.stridx(haystack, needle) ~= -1
end end
---@param path string
---@return string|uv.uv_fs_t
function M.read_file(path)
local fd = vim.loop.fs_open(path, "r", 438)
if not fd then
return ""
end
local stat = vim.loop.fs_fstat(fd)
if not stat then
return ""
end
local data = vim.loop.fs_read(fd, stat.size, 0)
vim.loop.fs_close(fd)
return data or ""
end
local path_separator = package.config:sub(1, 1) local path_separator = package.config:sub(1, 1)
---@param paths string[] ---@param paths string[]
---@return string ---@return string
@@ -131,79 +112,19 @@ end
M.path_separator = path_separator M.path_separator = path_separator
--- Get the node and index of the node from the tree that matches the predicate. ---@param extmarks vim.api.keyset.get_extmark_item[] as per vim.api.nvim_buf_get_extmarks
--- The explored nodes are those displayed on the view.
---@param nodes Node[]
---@param fn fun(node: Node): boolean
---@return table|nil
---@return number ---@return number
function M.find_node(nodes, fn) function M.extmarks_length(extmarks)
local node, i = Iterator.builder(nodes) local length = 0
:matcher(fn) for _, extmark in ipairs(extmarks) do
:recursor(function(node) local details = extmark[4]
return node.group_next and { node.group_next } or (node.open and #node.nodes > 0 and node.nodes) if details and details.virt_text then
end) for _, text in ipairs(details.virt_text) do
:iterate() length = length + vim.fn.strchars(text[1])
i = require("nvim-tree.view").is_root_folder_visible() and i or i - 1 end
if node and node.explorer.live_filter.filter then
i = i + 1
end
return node, i
end
-- Find the line number of a node.
-- Return -1 is node is nil or not found.
---@param node Node?
---@return integer
function M.find_node_line(node)
if not node then
return -1
end
local first_node_line = require("nvim-tree.core").get_nodes_starting_line()
local nodes_by_line = M.get_nodes_by_line(require("nvim-tree.core").get_explorer().nodes, first_node_line)
local iter_start, iter_end = first_node_line, #nodes_by_line
for line = iter_start, iter_end, 1 do
if nodes_by_line[line] == node then
return line
end end
end end
return length
return -1
end
-- get the node in the tree state depending on the absolute path of the node
-- (grouped or hidden too)
---@param path string
---@return Node|nil
---@return number|nil
function M.get_node_from_path(path)
local explorer = require("nvim-tree.core").get_explorer()
-- tree may not yet be loaded
if not explorer then
return
end
if explorer.absolute_path == path then
return explorer
end
return Iterator.builder(explorer.nodes)
:hidden()
:matcher(function(node)
return node.absolute_path == path or node.link_to == path
end)
:recursor(function(node)
if node.group_next then
return { node.group_next }
end
if node.nodes then
return node.nodes
end
end)
:iterate()
end end
M.default_format_hidden_count = function(hidden_count, simple) M.default_format_hidden_count = function(hidden_count, simple)
@@ -226,30 +147,6 @@ M.default_format_hidden_count = function(hidden_count, simple)
return nil return nil
end end
--- Return visible nodes indexed by line
---@param nodes_all Node[]
---@param line_start number
---@return table
function M.get_nodes_by_line(nodes_all, line_start)
local nodes_by_line = {}
local line = line_start
Iterator.builder(nodes_all)
:applier(function(node)
if node.group_next then
return
end
nodes_by_line[line] = node
line = line + 1
end)
:recursor(function(node)
return node.group_next and { node.group_next } or (node.open and #node.nodes > 0 and node.nodes)
end)
:iterate()
return nodes_by_line
end
function M.rename_loaded_buffers(old_path, new_path) function M.rename_loaded_buffers(old_path, new_path)
-- delete new if it exists -- delete new if it exists
for _, buf in pairs(vim.api.nvim_list_bufs()) do for _, buf in pairs(vim.api.nvim_list_bufs()) do
@@ -289,11 +186,51 @@ function M.rename_loaded_buffers(old_path, new_path)
end end
end end
local is_windows_drive = function(path)
return (M.is_windows) and (path:match("^%a:\\$") ~= nil)
end
---@param path string path to file or directory ---@param path string path to file or directory
---@return boolean ---@return boolean
function M.file_exists(path) function M.file_exists(path)
local _, error = vim.loop.fs_stat(path) if not (M.is_windows or M.is_wsl) then
return error == nil local _, error = vim.loop.fs_stat(path)
return error == nil
end
-- Windows is case-insensetive, but case-preserving
-- If a file's name is being changed into itself
-- with different casing, windows will falsely
-- report that file is already existing, so a hand-rolled
-- implementation of checking for existance is needed.
-- Same holds for WSL, since it can sometimes
-- access Windows files directly.
-- For more details see (#3117).
if is_windows_drive(path) then
return vim.fn.isdirectory(path) == 1
end
local parent = vim.fn.fnamemodify(path, ":h")
local filename = vim.fn.fnamemodify(path, ":t")
local handle = vim.loop.fs_scandir(parent)
if not handle then
-- File can not exist if its parent directory does not exist
return false
end
while true do
local name, _ = vim.loop.fs_scandir_next(handle)
if not name then
break
end
if name == filename then
return true
end
end
return false
end end
---@param path string ---@param path string
@@ -323,68 +260,6 @@ function M.escape_special_chars(path)
return M.is_windows and escape_special_char_for_windows(path) or path return M.is_windows and escape_special_char_for_windows(path) or path
end end
--- Create empty sub-tables if not present
---@param tbl table to create empty inside of
---@param path string dot separated string of sub-tables
---@return table deepest sub-table
function M.table_create_missing(tbl, path)
local t = tbl
for s in string.gmatch(path, "([^%.]+)%.*") do
if t[s] == nil then
t[s] = {}
end
t = t[s]
end
return t
end
--- Move a value from src to dst if value is nil on dst.
--- Remove value from src
---@param src table to copy from
---@param src_path string dot separated string of sub-tables
---@param src_pos string value pos
---@param dst table to copy to
---@param dst_path string dot separated string of sub-tables, created when missing
---@param dst_pos string value pos
---@param remove boolean
function M.move_missing_val(src, src_path, src_pos, dst, dst_path, dst_pos, remove)
local ok, err = pcall(vim.validate, {
src = { src, "table" },
src_path = { src_path, "string" },
src_pos = { src_pos, "string" },
dst = { dst, "table" },
dst_path = { dst_path, "string" },
dst_pos = { dst_pos, "string" },
remove = { remove, "boolean" },
})
if not ok then
notify.warn("move_missing_val: " .. (err or "invalid arguments"))
return
end
for pos in string.gmatch(src_path, "([^%.]+)%.*") do
if src[pos] and type(src[pos]) == "table" then
src = src[pos]
else
return
end
end
local src_val = src[src_pos]
if src_val == nil then
return
end
dst = M.table_create_missing(dst, dst_path)
if dst[dst_pos] == nil then
dst[dst_pos] = src_val
end
if remove then
src[src_pos] = nil
end
end
local function round(value) local function round(value)
-- Amount of digits to round to after floating point. -- Amount of digits to round to after floating point.
local digits = 2 local digits = 2
@@ -493,38 +368,6 @@ function M.debounce(context, timeout, callback)
end) end)
end end
function M.focus_file(path)
local _, i = M.find_node(require("nvim-tree.core").get_explorer().nodes, function(node)
return node.absolute_path == path
end)
require("nvim-tree.view").set_cursor({ i + 1, 1 })
end
---Focus node passed as parameter if visible, otherwise focus first visible parent.
---If none of the parents is visible focus root.
---If node is nil do nothing.
---@param node Node? node to focus
function M.focus_node_or_parent(node)
local explorer = require("nvim-tree.core").get_explorer()
if explorer == nil then
return
end
while node do
local found_node, i = M.find_node(explorer.nodes, function(node_)
return node_.absolute_path == node.absolute_path
end)
if found_node or node.parent == nil then
require("nvim-tree.view").set_cursor({ i + 1, 1 })
break
end
node = node.parent
end
end
---@param path string ---@param path string
---@return integer|nil ---@return integer|nil
---@return integer|nil ---@return integer|nil

View File

@@ -254,7 +254,6 @@ local function close(tabpage)
return return
end end
end end
events._dispatch_on_tree_close()
return return
end end
end end
@@ -270,9 +269,12 @@ function M.close_all_tabs()
end end
end end
function M.close() ---@param tabpage integer|nil
function M.close(tabpage)
if M.View.tab.sync.close then if M.View.tab.sync.close then
M.close_all_tabs() M.close_all_tabs()
elseif tabpage then
close(tabpage)
else else
M.close_this_tab_only() M.close_this_tab_only()
end end
@@ -286,6 +288,7 @@ function M.open(options)
local profile = log.profile_start("view open") local profile = log.profile_start("view open")
events._dispatch_on_tree_pre_open()
create_buffer() create_buffer()
open_window() open_window()
M.resize() M.resize()
@@ -326,14 +329,7 @@ local function grow()
local count = vim.fn.strchars(l) local count = vim.fn.strchars(l)
-- also add space for right-aligned icons -- 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 }) 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 count = count + utils.extmarks_length(extmarks)
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
@@ -411,6 +407,7 @@ end
---@param opts OpenInWinOpts|nil ---@param opts OpenInWinOpts|nil
function M.open_in_win(opts) function M.open_in_win(opts)
opts = opts or { hijack_current_buf = true, resize = true } opts = opts or { hijack_current_buf = true, resize = true }
events._dispatch_on_tree_pre_open()
if opts.winid and vim.api.nvim_win_is_valid(opts.winid) then if opts.winid and vim.api.nvim_win_is_valid(opts.winid) then
vim.api.nvim_set_current_win(opts.winid) vim.api.nvim_set_current_win(opts.winid)
end end
@@ -422,6 +419,7 @@ function M.open_in_win(opts)
M.reposition_window() M.reposition_window()
M.resize() M.resize()
end end
events._dispatch_on_tree_open()
end end
function M.abandon_current_window() function M.abandon_current_window()
@@ -626,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

View File

@@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
out=$(grep -nr "^--- @" lua) out=$(grep -nr "^--- @" lua)

View File

@@ -1,4 +1,4 @@
#!/bin/sh #!/usr/bin/env sh
# run after changing nvim-tree.lua DEFAULT_OPTS or keymap.lua M.default_on_attach # run after changing nvim-tree.lua DEFAULT_OPTS or keymap.lua M.default_on_attach
# scrapes and updates nvim-tree-lua.txt # scrapes and updates nvim-tree-lua.txt

View File

@@ -1,9 +1,11 @@
#!/bin/sh #!/usr/bin/env sh
# Performs a lua-language-server check on all files. # Performs a lua-language-server check on all files.
# luals-out/check.json will be produced on any issues, returning 1. # luals-out/check.json will be produced on any issues, returning 1.
# Outputs only check.json to stdout, all other messages to stderr, to allow jq etc. # Outputs only check.json to stdout, all other messages to stderr, to allow jq etc.
# $VIMRUNTIME specifies neovim runtime path, defaults to "/usr/share/nvim/runtime" if unset. # $VIMRUNTIME specifies neovim runtime path, defaults to "/usr/share/nvim/runtime" if unset.
#
# Call with codestyle-check param to enable only codestyle-check
if [ -z "${VIMRUNTIME}" ]; then if [ -z "${VIMRUNTIME}" ]; then
export VIMRUNTIME="/usr/share/nvim/runtime" export VIMRUNTIME="/usr/share/nvim/runtime"
@@ -17,11 +19,24 @@ FILE_LUARC="${DIR_OUT}/luarc.json"
rm -rf "${DIR_OUT}" rm -rf "${DIR_OUT}"
mkdir "${DIR_OUT}" mkdir "${DIR_OUT}"
# Uncomment runtime.version for strict neovim baseline 5.1 case "${1}" in
# It is not set normally, to prevent luals loading 5.1 and 5.x, resulting in both versions being chosen on vim.lsp.buf.definition() "codestyle-check")
cat "${PWD}/.luarc.json" | sed -E 's/.luals-check-only//g' > "${FILE_LUARC}" jq \
'.diagnostics.neededFileStatus[] = "None" | .diagnostics.neededFileStatus."codestyle-check" = "Any"' \
"${PWD}/.luarc.json" > "${FILE_LUARC}"
# execute inside lua to prevent luals itself from being checked ;;
*)
# Add runtime.version for strict neovim baseline 5.1
# It is not set normally, to prevent luals loading 5.1 and 5.x, resulting in both versions being chosen on vim.lsp.buf.definition
jq \
'."runtime.version" = "Lua 5.1"' \
"${PWD}/.luarc.json" > "${FILE_LUARC}"
;;
esac
# execute inside lua directory to prevent luals itself from being checked
OUT=$(lua-language-server --check="${DIR_SRC}" --configpath="${FILE_LUARC}" --checklevel=Information --logpath="${DIR_OUT}" --loglevel=error) OUT=$(lua-language-server --check="${DIR_SRC}" --configpath="${FILE_LUARC}" --checklevel=Information --logpath="${DIR_OUT}" --loglevel=error)
RC=$? RC=$?