Compare commits

...

480 Commits

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

* Fix warnings

* Restore original position of edit function

* Rename field to match the api method name

* Rename ApiTreeExpandAllOpts to ApiTreeExpandOpts

* Remove toggle_descend_until

* Remove redundant empty line

* Update :help for changed methods

* Fix partial expansion of grouped nodes

* Fix lint error

* Fix linting error

* Fix incorrect open/close indicator state

* Update docs

* Rename descend_until option to expand_until

* Always check directory expansion limit

* Fix linter errors

* Ignore unused param warning

* Apply suggestions from code review

* simplify MAX_FOLDER_DISCOVERY warning

* fix bad comment whitespace

---------

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

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

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

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

This reverts commit 0a7fcdf3f8.

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

This reverts commit 0a06f65bf0.

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

This reverts commit dd2364d680.

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

This reverts commit 65bae44922.

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

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

* feat(#2826): remove globals.BUFNR_BY_TABID

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

This reverts commit 2651f9b34a.

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

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

* Don't use vim.trim

* Ensure sdtout is a string

* Keep Nvim 0.9 compatibility

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

* use View.bufnr for all internal methods

* revert View.bufnr, add buffer local autocommands

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

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

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

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

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

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

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

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

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

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

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

* refactor(#2826): consistent naming of tabid

* refactor(#2826): more consistency checking

* refactor(#2826): more consistency checking

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

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

This reverts commit d84dfad1c3.

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

* refactor(#2826): consistency check returns new

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

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

* fix(#3077): add diagnostic override TODO

---------

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

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

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

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

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

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

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

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

* refactor(#2826): singleton View class

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

* refactor(#2826): move autocmds to Explorer

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

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

* refactor(#2826): retain necessary view globals

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

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

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

* refactor(#2826): better enumerate_options function

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

* refactor(#2826): default lifecycle log off

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

* refactor(#2826): instrument View:get_winnr

* refactor(#2826): instrument View:setup_tabpage

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

* refactor(#2826): instrument View:get_bufnr

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

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

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

* vim: nvim-tree: more logging

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

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

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

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

Add a missing double quotes around the default value for
`nvim-tree.renderer.icons.bookmarks_placement` config value and fix
spelling of `bookmarked`.
2025-06-17 10:23:34 +10:00
phanium
d87b41ca53 fix: window picker ignore hidden window (#3145) 2025-06-15 15:04:47 +10:00
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
github-actions[bot]
d529a99f88 chore(master): release nvim-tree 1.10.0 (#3021)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-01-13 15:41:35 +11:00
phanium
39bc630816 fix: hijack directory "BufEnter", "BufNewFile" events are nested (#3044)
Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-01-13 15:39:10 +11:00
Lev Yuvenskiy
aae01853dd fix(#3041): use vim.diagnostic.get for updating diagnostics (#3042)
* fix(#3041): use vim.diagnostic.get for updating diagnostics

* fix(#3041): remove unnecessary @type

---------

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

* add splits with no window pickers

removed the 1 buffer per file limitation

test

test2

* no-picker for splits

* help vertical/horizontal_no_picker

* revert whitespace changes

---------

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

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

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

* update view.width help

---------

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

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

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

* feat(#2948): add UserDecorator

* feat(#2948): add UserDecorator

* feat(#2948): add UserDecorator

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

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

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

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

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

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

* feat(#2948): tidy

* feat(#2948): document API

* feat(#2948): document API

* feat(#2948): document API

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

* feat(#2948): document API

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

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

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

* feat(#2948): improve doc

* feat(#2948): improve doc

* feat(#2948): improve doc

* feat(#2948): additional user decorator safety

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

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

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

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

Signed-off-by: des-b <66919647+des-b@users.noreply.github.com>
2024-11-11 08:57:06 +11:00
github-actions[bot]
c7639482a1 chore(master): release nvim-tree 1.8.0 (#2943)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-09 14:46:26 +11:00
Alexander Courtis
2ee1c5e17f feat(#2819): add actions.open_file.relative_path, default enabled, following successful experiment (#2995) 2024-11-09 14:44:59 +11:00
Alexander Courtis
3fc8de198c chore: migrate to classic (#2991)
* add classic, migrating nodes classes

* add mixins to classic

* typechecked optargs constructors for nodes

* typechecked optargs constructors for watcher and event

* luacheck

* typechecked optargs constructors for GitRunner

* typechecked optargs constructors for Sorter

* typechecked optargs constructors for decorators, WIP

* typechecked optargs constructors for decorators, WIP

* typechecked optargs constructors for decorators

* remove class

* replace enums with named maps

* Renderer and Builder use classic, tidy opts

* LiveFilter uses classic, tidy opts

* Filter uses classic, tidy opts

* add FilterTypes named map

* move toggles into filters

* Marks uses classic, tidy opts

* Sorter uses classic, tidy opts

* Clipboard uses classic, tidy opts

* use supers for node methods

* HighlightDisplay uses classic

* protected :new

* Watcher tidy

* Revert "use supers for node methods"

This reverts commit 9fc7a866ec.

* Watcher tidy

* format

* format

* Filters private methods

* format

* Sorter type safety

* Sorter type safety

* Sorter type safety

* Sorter type safety

* Sorter type safety

* Sorter type safety

* tidy Runner

* tidy hi-test name
2024-11-09 14:14:04 +11:00
Alexander Courtis
610a1c189b chore: resolve undefined-field warnings, fix link git statuses, rewrite devicons (#2968)
* add todo

* refactor(#2886): multi instance: node class refactoring: extract links, *_git_status (#2944)

* extract DirectoryLinkNode and FileLinkNode, move Node methods to children

* temporarily move DirectoryNode methods into BaseNode for easier reviewing

* move mostly unchanged DirectoryNode methods back to BaseNode

* tidy

* git.git_status_file takes an array

* update git status of links

* luacheck hack

* safer git_status_dir

* refactor(#2886): multi instance: node class refactoring: DirectoryNode:expand_or_collapse (#2957)

move expand_or_collapse to DirectoryNode

* refactor(#2886): multi instance: node group functions refactoring (#2959)

* move last_group_node to DirectoryNode

* move add BaseNode:as and more doc

* revert parameter name changes

* revert parameter name changes

* add Class

* move group methods into DN

* tidy group methods

* tidy group methods

* tidy group methods

* tidy group methods

* parent is DirectoryNode

* tidy expand all

* BaseNode -> Node

* move watcher to DirectoryNode

* last_group_node is DirectoryNode only

* simplify create-file

* simplify parent

* simplify collapse-all

* simplify live-filter

* style

* move lib.get_cursor_position to Explorer

* move lib.get_node_at_cursor to Explorer

* move lib.get_nodes to Explorer

* move place_cursor_on_node to Explorer

* resolve resource leak in purge_all_state

* move many autocommands into Explorer

* post merge tidy

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* Revert "chore: resolve undefined-field"

This reverts commit be546ff18d41f28466b065c857e1e041659bd2c8.

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* Revert "chore: resolve undefined-field"

This reverts commit e82db1c44d.

* chore: resolve undefined-field

* chore: class new is now generic

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* Revert "chore: resolve undefined-field"

This reverts commit 0e9b844d22.

* move icon builders into node classes

* move icon builders into node classes

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* move folder specifics from icons to Directory

* move folder specifics from icons to Directory

* move folder specifics from icons to Directory

* move folder specifics from icons to Directory

* move file specifics from icons to File

* clean up sorters

* chore: resolve undefined-field

* tidy hl icon name

* file devicon uses library to fall back

* file devicon uses library to fall back

* file devicon uses library to fall back
2024-11-03 14:06:12 +11:00
Jie Liu
c22124b374 fix(#2981): windows: root changed when navigating with LSP (#2982)
* fix #2981: nvim-tree root changed when navigating with LSP

* add error checks
2024-11-03 13:52:20 +11:00
Alexander Courtis
2156bc08c9 fix: symlink file icons rendered when renderer.icons.show.file = false, folder.symlink* was incorrectly rendered as folder.default|open (#2983)
* fix: folder.symlink* was incorrectly rendered as folder.default|open

* fix: symlink file icons rendered when renderer.icons.show.file = false
2024-11-03 12:10:00 +11:00
des-b
82ab19ebf7 fix(#2954): resolve occasional tree flashing on diagnostics, set tree buffer options in deterministic order (#2980)
* fix(#2954): set buffer options in deterministic order

This ensures related autocmd's (e.g. on FileType) will be called in a
similar environment.

* fix(#2954): redraw only for diagnostics if source buffer is 'buflisted'

is_buf_valid has been inlined since it is only used for diagnostics
and its name is misleading.
2024-11-02 12:07:42 +11:00
Alexander Courtis
120ba58254 fix(#2978): grouped folder not showing closed icon (#2979) 2024-10-29 11:07:48 +11:00
Alexander Courtis
00dff482f9 fix(#2976): use vim.loop to preserve neovim 0.9 compatibility (#2977) 2024-10-29 08:01:52 +11:00
Alexander Courtis
8f974879a0 chore: luals runtime.version only set during check, to prevent lua version ambuguity at dev time (#2975)
* chore: luals runtime.version only set during check, to prevent lua version ambuguity at dev time

* inject lua 5.1 check failure

* Revert "inject lua 5.1 check failure"

This reverts commit eed966dc7b.
2024-10-28 11:57:53 +11:00
cpp_programmer
14039337a5 fix(#2969): After a rename, the node loses selection (#2974)
Co-authored-by: Lucian Ion <lucian.ion.2005@gmail.com>
2024-10-27 17:26:47 +11:00
Alexander Courtis
e4bc05b415 doc: remove outdated warning from actions.change_dir.global 2024-10-27 10:48:17 +11:00
Alexander Courtis
3cddd28177 doc: add windows specifics to CONTRIBUTING 2024-10-27 10:32:41 +11:00
Alexander Courtis
6e5a204ca6 fix(#2972): error on :colorscheme (#2973) 2024-10-27 10:18:21 +11:00
Alexander Courtis
f3efc25e56 refactor(#2941): move lib methods to explorer (#2964)
* add todo

* refactor(#2886): multi instance: node class refactoring: extract links, *_git_status (#2944)

* extract DirectoryLinkNode and FileLinkNode, move Node methods to children

* temporarily move DirectoryNode methods into BaseNode for easier reviewing

* move mostly unchanged DirectoryNode methods back to BaseNode

* tidy

* git.git_status_file takes an array

* update git status of links

* luacheck hack

* safer git_status_dir

* refactor(#2886): multi instance: node class refactoring: DirectoryNode:expand_or_collapse (#2957)

move expand_or_collapse to DirectoryNode

* refactor(#2886): multi instance: node group functions refactoring (#2959)

* move last_group_node to DirectoryNode

* move add BaseNode:as and more doc

* revert parameter name changes

* revert parameter name changes

* add Class

* move group methods into DN

* tidy group methods

* tidy group methods

* tidy group methods

* tidy group methods

* parent is DirectoryNode

* tidy expand all

* BaseNode -> Node

* move watcher to DirectoryNode

* last_group_node is DirectoryNode only

* simplify create-file

* simplify parent

* simplify collapse-all

* simplify live-filter

* style

* move lib.get_cursor_position to Explorer

* move lib.get_node_at_cursor to Explorer

* move lib.get_nodes to Explorer

* move place_cursor_on_node to Explorer

* resolve resource leak in purge_all_state

* move many autocommands into Explorer

* post merge tidy
2024-10-27 09:03:26 +11:00
Alexander Courtis
8760d76c1d chore: enable missing-local-export-doc 2024-10-25 14:35:48 +11:00
Alexander Courtis
077af9f990 chore: enable incomplete-signature-doc, format nvt-min.lua, assorted formatting tidies (#2967)
* chore: luacheckrc uses table

* chore: format nvt-min.lua

* chore: complete lua doc

* chore: complete lua doc

* chore: complete lua doc

* chore: complete lua doc

* chore: complete lua doc

* chore: enable incomplete-signature-doc

* chore: enable incomplete-signature-doc

* chore: complete lua doc

* chore: complete lua doc
2024-10-25 14:25:30 +11:00
Alexander Courtis
68be6df2fc refactor(#2886): multi instance: node class refactoring (#2950)
* add todo

* refactor(#2886): multi instance: node class refactoring: extract links, *_git_status (#2944)

* extract DirectoryLinkNode and FileLinkNode, move Node methods to children

* temporarily move DirectoryNode methods into BaseNode for easier reviewing

* move mostly unchanged DirectoryNode methods back to BaseNode

* tidy

* git.git_status_file takes an array

* update git status of links

* luacheck hack

* safer git_status_dir

* refactor(#2886): multi instance: node class refactoring: DirectoryNode:expand_or_collapse (#2957)

move expand_or_collapse to DirectoryNode

* refactor(#2886): multi instance: node group functions refactoring (#2959)

* move last_group_node to DirectoryNode

* move add BaseNode:as and more doc

* revert parameter name changes

* revert parameter name changes

* add Class

* move group methods into DN

* tidy group methods

* tidy group methods

* tidy group methods

* tidy group methods

* parent is DirectoryNode

* tidy expand all

* BaseNode -> Node

* move watcher to DirectoryNode

* last_group_node is DirectoryNode only

* simplify create-file

* simplify parent

* simplify collapse-all

* simplify live-filter

* style

* more type safety
2024-10-25 12:24:59 +11:00
Jie Liu
63c7ad9037 fix(#2961): windows: escape brackets and parentheses when opening file (#2962)
* Revert "fix(#2862): windows path replaces backslashes with forward slashes (#2903)"

This reverts commit 45a93d9979.

* fix the case when '()' and '[]' are both in file path

* remove debug messages

* remove unnecessary comments

* add is_windows feature flag when normalizing path

* add is_windows flag for filename change

* Revert "add is_windows flag for filename change"

This reverts commit ada77cb7e9.

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-10-25 11:11:21 +11:00
Alexander Courtis
9b82ff9bba chore: fix lib prompt doc for neovim nightly (#2966) 2024-10-25 11:10:07 +11:00
Alexander Courtis
2a268f631d doc: help: syntax highlighting for lua and vimscript 2024-10-18 18:29:19 +11:00
Alexander Courtis
f5f6789299 fix(#2947): root is never a dotfile, so that it doesn't propagate to children (#2958) 2024-10-14 18:56:43 +11:00
Alexander Courtis
ce09bfb95f chore: TODO issue links 2024-10-14 10:47:41 +11:00
Alexander Courtis
0fede9f813 chore: nvt-min.lua: ensure only one instance of lua-language-server runs (#2956) 2024-10-14 10:24:15 +11:00
Alexander Courtis
1c9553a19f fix(#2951): highlights incorrect following cancelled pick (#2952) 2024-10-12 15:54:12 +11:00
Alexander Courtis
ca0904e4c5 chore: add utils.enumerate_options (#2953) 2024-10-12 15:53:23 +11:00
Alexander Courtis
5ad87620ec fix(#2945): stack overflow on api.git.reload or fugitive event with watchers disabled (#2949)
* Reapply "refactor(#2871, #2886): multi instance: node classes created (#2916)"

This reverts commit 50e919426a.

* fix(#2945): stack overflow on api.git.reload or fugitive event
2024-10-11 13:47:01 +11:00
Alexander Courtis
50e919426a Revert "refactor(#2871, #2886): multi instance: node classes created (#2916)"
This reverts commit 38aac09151.
2024-10-08 18:07:47 +11:00
Alexander Courtis
010ae0365a feat(#2938): add default filesystem_watchers.ignore_dirs = { "/.ccls-cache", "/build", "/node_modules", "/target", } (#2940)
* feat(#2938): filesystem_watchers.ignore_dirs defaults to { node_modules } to resolve pathalogical issues

* feat(#2938): more filesystem_watchers.ignore_dirs defaults to to resolve pathalogical issues

* feat(#2938): more filesystem_watchers.ignore_dirs defaults to to resolve pathalogical issues
2024-10-07 15:25:24 +11:00
Alexander Courtis
38aac09151 refactor(#2871, #2886): multi instance: node classes created (#2916)
* refactor(#2875): multi instance renderer

* refactor(#2875): multi instance renderer

* refactor(#2875): multi instance renderer

* refactor(#2875): multi instance renderer

* node classes and constructors

* node methods

* refactor(#2875): multi instance renderer

* node classes and constructors

* explorer is a directory node

* extract methods from explore_node

* extract methods from explore_node

* extract methods from explore_node

* extract methods from lib

* use .. name for root node for compatibility

* use node.explorer

* extract node factory, remove unused code

* factories for all nodes, add RootNode

* factories for all nodes, add RootNode

* use factory pattern for decorators

* note regression and commit

* fix dir git status regression

* destroy nodes, not explorer

* add BaseNode:is

* revert changes to create-file, handle in #2924

* extract methods from explorer

* extract methods from explorer

* extract methods from explorer

* use Node everywhere in luadoc

* extract methods from lib

* extract methods from lib

* lint

* remove unused code

* don't call methods on fake root node

* get_node_at_cursor returns explorer (root) node instead of { name = '..' }

* remove unused inject_node

* refactor(#2875): multi instance renderer

* refactor(#2875): multi instance renderer

* refactor(#2875): multi instance renderer

* extract methods from lib

* node factory uses stat only

* temporary DirectoryNode casting until method extraction into child classes

* lua-language-server 3.10.5 -> 3.11.0

* explicitly call Explorer constructor

* normalise explorer RootNode new call, tidy annotations
2024-10-07 13:46:56 +11:00
Alexander Courtis
c9104a5d07 chore: style: align_continuous_similar_call_args (#2937)
* chore: style: align_continuous_similar_call_args

* chore: style: align_continuous_similar_call_args

* chore: style: align_continuous_similar_call_args

* chore: style: align_continuous_similar_call_args

* chore: style: consistent use of double quotes
2024-09-30 15:34:01 +10:00
github-actions[bot]
4a9e82d10a chore(master): release nvim-tree 1.7.1 (#2921)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-09-30 11:26:35 +10:00
Alexander Courtis
4520c0355c fix(#2930): empty groups expanded on reload (#2935) 2024-09-30 11:21:27 +10:00
Alexander Courtis
1ae1c33ce1 chore(#2931): stylua -> EmmyLuaCodeStyle (#2932)
* stylua -> EmmyLuaCodeStyle: config and doc

* stylua -> EmmyLuaCodeStyle: CI

* stylua -> EmmyLuaCodeStyle: CI

* stylua -> EmmyLuaCodeStyle: CI

* stylua -> EmmyLuaCodeStyle: CI

* stylua -> EmmyLuaCodeStyle: CI

* stylua -> EmmyLuaCodeStyle

* stylua -> EmmyLuaCodeStyle: call_arg_parentheses = always

* stylua -> EmmyLuaCodeStyle

* stylua -> EmmyLuaCodeStyle
2024-09-29 14:05:52 +10:00
Eric 李
9650e735ba fix(#2794): sshfs compatibility (#2922)
* Revert "revert(#2794): sshfs compatibility (#2920)"

This reverts commit 8405ecfbd6.

Fix for symlinks is simple

* fix sshfs compatibility with symlinks

* add suggestions

* revert variable name change to ease multi-instance feature branch conflicts

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-09-29 09:21:02 +10:00
Lin Tinusgrag
59a8a6ae5e fix: invalid explorer on open (#2927)
* fix: use of possibly stale value

The return value of `core.get_explorer()` could be changed by `core.init(..)`.

* fix style

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-09-25 19:27:25 +10:00
Lucas Reyna Córdoba
0429f286b3 fix(#2928): nil explorer in parent move action (#2929) 2024-09-25 19:23:57 +10:00
Alexander Courtis
8405ecfbd6 revert(#2794): sshfs compatibility (#2920)
* refactor(#2875): multi instance renderer: remove unused code

* Revert "fix(#2794): sshfs compatibility (#2893)"

This reverts commit 2d6e64dd8c.
2024-09-22 15:23:42 +10:00
Alexander Courtis
0ae9ad4ded refactor(#2875): multi instance renderer: remove unused code (#2919) 2024-09-22 15:22:06 +10:00
github-actions[bot]
e7cdecc636 chore(master): release nvim-tree 1.7.0 (#2910)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-09-21 12:12:45 +10:00
Alexander Courtis
b18ce8be8f fix(#2917): fix root copy paths: Y, ge, gy, y (#2918) 2024-09-21 12:05:16 +10:00
Alexander Courtis
03ae60313b refactor(#2875): multi instance renderer (#2900)
* refactor(#2875): multi instance renderer

* refactor(#2875): multi instance renderer

* refactor(#2875): multi instance renderer

* refactor(#2875): multi instance renderer

* refactor(#2875): deal with some cyclic require

* refactor(#2875): multi instance renderer

* refactor(#2875): multi instance renderer

* refactor(#2875): multi instance renderer

* refactor(#2875): multi instance renderer

* refactor(#2875): multi instance renderer

* refactor(#2875): multi instance renderer

* refactor(#2875): multi instance renderer

* refactor(#2875): multi instance renderer

* refactor(#2875): multi instance renderer

* refactor(#2875): multi instance renderer

* refactor(#2875): multi instance renderer
2024-09-21 10:41:35 +10:00
Zifan Zhu
45a93d9979 fix(#2862): windows path replaces backslashes with forward slashes (#2903)
* Fix Winodws path issue by replacing backslashes with forward slashes

* Fix #2862 (handle all filename-related tasks)

* fix type mismatch

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-09-15 12:23:37 +10:00
Kyle Beede
bd4881660b fix: safely close last tree window (#2913)
fix: safely close tree window with pcall and debug logging

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-09-14 16:34:36 +10:00
Alexander Courtis
cd9c6db77f refactor(#2882, #2883): multi instance explore, reloaders (#2897)
* refactor(#2883): multi instance explore

* refactor(#2882): multi instance reloaders

* style
2024-09-14 15:35:31 +10:00
Alexander Courtis
03f737e574 feat(#2430): use vim.ui.open as default system_open, for neovim 0.10+ (#2912)
* feat(#2430): use vim.ui.open as default system_open, for neovim 0.10+

* feat(#2430): use vim.ui.open as default system_open, for neovim 0.10+
2024-09-14 15:15:44 +10:00
Ian Homer
a4dd5ad5c8 fix(#2906): resource leak on populate children (#2907)
Don't collect reason statistics for reason none

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-09-14 13:39:59 +10:00
Alexander Courtis
b652dbd0e0 feat: help closes on <Esc> and api.tree.toggle_help mappings (#2909)
* feat: help closes on <Esc> and api.tree.toggle_help mappings

* feat: help closes on <Esc> and api.tree.toggle_help mappings
2024-09-14 12:24:07 +10:00
github-actions[bot]
d41b4ca013 chore(master): release nvim-tree 1.6.1 (#2877)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-09-09 10:41:48 +10:00
Eric 李
2d6e64dd8c fix(#2794): sshfs compatibility (#2893)
* add type fallback for nil types

* add PR suggestions

* Update lua/nvim-tree/explorer/explore.lua

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

* use type from fs_stat for sshfs compatibility

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-09-09 10:31:55 +10:00
Alexander Courtis
cb57691536 docs: clarify node parameters in API, use of function {rhs} in on_attach (#2899)
* docs: specify node parameters for all API

* docs: clarify need to use a custom functions in on_attach
2024-09-08 15:11:02 +10:00
Alexander Courtis
70d7377c3f chore: lua-language-server 3.9.1 -> 3.10.5 (#2898) 2024-09-08 14:40:17 +10:00
Alexander Courtis
ea55ef1203 refactor(#2837): multi instance reload (#2885)
* refactor(#2837): multi instance reload

* refactor(#2837): multi instance reload
2024-09-01 15:50:03 +10:00
Alexander Courtis
43c3c36c7a doc(#2891): remove unused option hidden.enable 2024-09-01 14:52:25 +10:00
Alexander Courtis
d43ab67d0e fix(#2879): remove unnecessary tree window width setting to prevent unnecessary :wincmd = (#2881) 2024-08-25 13:23:28 +10:00
Alexander Courtis
6fbcb5a892 refactor(#2831): multi instance clipboard (#2869)
* refactor(#2831): multi instance clipboard

* refactor(#2831): multi instance clipboard

* refactor(#2831): multi instance clipboard
2024-08-25 12:49:46 +10:00
Alexander Courtis
e962e97cab refactor(#2830): multi instance marks (#2873)
* refactor(#2830): multi instance marks

* refactor(#2830): multi instance marks

* refactor(#2830): multi instance marks
2024-08-25 12:32:09 +10:00
Alexander Courtis
42340952af fix(#2878): nowrapscan prevents move from root (#2880)
* fix(#2878): nowrapscan prevents move from root

* fix(#2878): nowrapscan prevents move from root
2024-08-25 12:21:17 +10:00
Alexander Courtis
210478677c fix(#2868): windows: do not visit unenumerable directories such as Application Data (#2874) 2024-08-24 13:09:12 +10:00
github-actions[bot]
ad0b95dee5 chore(master): release nvim-tree 1.6.0 (#2845)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-08-10 12:26:42 +10:00
Michael Härtl
466fbed3e4 fix(#2859): make sure window still exists when restoring options (#2863)
Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-08-10 12:21:48 +10:00
Mateusz Russak
15942df2bb refactor(#2827): multi instance nvim-tree.live-filter (#2849)
* feat(#2827): Multi Instance: Refactor: nvim-tree.live-filter

* refactor: all usages going through the explorer

* fix: api and filtration

* fix: style

* Update lua/nvim-tree/api.lua

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

* docs: add missing live filter luadocs

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-08-10 12:02:13 +10:00
Everton Jr.
e25eb7fa83 feat(#2225): add renderer.hidden_display to show a summary of hidden files below the tree (#2856)
* feat(icon_placement): Allow right_align icon_placemente for decorator using ext_marks nvim api

* feat(icon_placement): Allow right_align icon_placemente for decorator using ext_marks nvim api

feat(icon_placement): Allow right_align icon_placemente for decorator using ext_marks nvim api

* feat(icon_placement): consolidate doc

* fix: extra namespace added to avoid colision between right_align and full_name features

* feat(hidden_display): Allow fine grained rendering of hidden files in
a folder

* feat(hidden_display): update defaults in Builder to allow rendering

* feat(hidden_display): Rename opts function name for the feature

* feat(#2349): add "right_align" option for renderer.icons.*_placement (#2846)

* feat(icon_placement): Allow right_align icon_placemente for decorator using ext_marks nvim api

* feat(icon_placement): Allow right_align icon_placemente for decorator using ext_marks nvim api

feat(icon_placement): Allow right_align icon_placemente for decorator using ext_marks nvim api

* feat(icon_placement): consolidate doc

* fix: extra namespace added to avoid colision between right_align and full_name features

* style: rename namespace_id

---------

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

* docs: update docs

* feat(hidden_display): Simplification and better performance by not sorting and grouping virtual lines

* Update doc/nvim-tree-lua.txt

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

* style: hidden_stats is better

* docs: change to hidden_stats

* add separate namespace for virtual lines

* help: add highlight group

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-08-10 11:36:30 +10:00
Everton Jr.
48d0e82f94 feat(#2349): add "right_align" option for renderer.icons.*_placement (#2846)
* feat(icon_placement): Allow right_align icon_placemente for decorator using ext_marks nvim api

* feat(icon_placement): Allow right_align icon_placemente for decorator using ext_marks nvim api

feat(icon_placement): Allow right_align icon_placemente for decorator using ext_marks nvim api

* feat(icon_placement): consolidate doc

* fix: extra namespace added to avoid colision between right_align and full_name features

* style: rename namespace_id

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-07-28 13:26:22 +10:00
Mateusz Russak
82ba116bbd refactor(#2829): multi instance nvim-tree.explorer.sorters (#2835)
* refactor: multi instance nvim-tree.explorer.sorters

* fix: linter errors

* fix: style

* fix: according to code review

* chore: removed comment

* fix: missing cfg params in sorters

* tidy following rebase

* tidy following rebase

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-07-27 13:54:40 +10:00
Mateusz Russak
908478a0e0 refactor(#2828): multi instance nvim-tree.explorer.filters (#2841)
* refactor(#2828): multi instance nvim-tree.explorer.filters

* fix: style

* fix: apply suggestions from code review

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

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-07-27 13:29:27 +10:00
Mateusz Russak
1aa9852cad docs: removed entry about macos rename (#2848) 2024-07-27 12:58:02 +10:00
Alexander Courtis
4e396b2624 refactor(#2830): multi instance nvim-tree.marks (#2838)
refactor(#2380): multi instance nvim-tree.marks
2024-07-21 16:12:42 +10:00
Everton Jr.
48a9290757 feat: add renderer.highlight_hidden, renderer.icons.show.hidden and renderer.icons.hidden_placement for dotfile icons/highlights (#2840)
* feat(hidden_decorator): Allow hidden (dotfiles) to be highlighted, both icon and name (this not related to git highlights).

Better defaults

squashed

docs(hidden)

docs(hidden)

docs(hidden)

* fix(typo): small typo on hl groups

* feat(hidden_dotfile_highlight): make a file that has a dotfile parent be also a dotfile

* docs: update docs on hidden highlight

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-07-21 16:00:34 +10:00
Alexander Courtis
b2640685a8 Revert "feat(#2349): add "right_align" option for renderer.icons.*_placement (#2839)"
This reverts commit 1d629a5d3f.
2024-07-21 15:22:24 +10:00
Everton Jr.
1d629a5d3f feat(#2349): add "right_align" option for renderer.icons.*_placement (#2839)
* feat(icon_placement): Allow right_align icon_placemente for decorator using ext_marks nvim api

* feat(icon_placement): Allow right_align icon_placemente for decorator using ext_marks nvim api

feat(icon_placement): Allow right_align icon_placemente for decorator using ext_marks nvim api

* feat(icon_placement): consolidate doc

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-07-21 14:49:10 +10:00
github-actions[bot]
f9ff00bc06 chore(master): release nvim-tree 1.5.0 (#2810)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-07-11 16:17:05 +10:00
Samuel Durante
abfd1d1b67 fix(#2813): macos: enable file renaming with changed capitalization (#2814)
* fix(#2813): enable file renaming in `nvim-tree` with changed capitalization

* fix(#2813): check if is macos

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-07-11 16:15:03 +10:00
Vladimir Levin
2ede0de67b feat(#2598): add api.tree.resize (#2811)
* feat(#2598): Implemented API `tree.resize`

* rely on  when resize

* Fix docs

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-07-11 15:15:40 +10:00
Epheien
12a9a995a4 fix(#2819): experimental.actions.open_file.relative_path issue following change directory (#2820)
fix issue with the description of epheien in #2819

Co-authored-by: eph <eph@MacBook-Pro.local>
2024-07-07 15:53:04 +10:00
dependabot[bot]
d1957d3472 chore(deps): bump nvim-neorocks/luarocks-tag-release from 5 to 7 (#2808)
Bumps [nvim-neorocks/luarocks-tag-release](https://github.com/nvim-neorocks/luarocks-tag-release) from 5 to 7.
- [Release notes](https://github.com/nvim-neorocks/luarocks-tag-release/releases)
- [Changelog](https://github.com/nvim-neorocks/luarocks-tag-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nvim-neorocks/luarocks-tag-release/compare/v5...v7)

---
updated-dependencies:
- dependency-name: nvim-neorocks/luarocks-tag-release
  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>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-07-07 12:55:47 +10:00
Epheien
869c064721 feat(#2127): add experimental.actions.open_file.relative_path to open files with a relative path rather than absolute (#2805)
* temp workaround for issue #2803

* fix #2127 and #2803

* chore(#2127): read the configuration correctly

* feat(#2127): add help

* feat(#2127): normalise relative_path in config hierarchy

* feat(#2127): update help

---------

Co-authored-by: eph <eph@MacBook-Pro.local>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-07-07 12:51:43 +10:00
dependabot[bot]
74e94625b1 chore(deps): bump amannn/action-semantic-pull-request from 5.5.2 to 5.5.3 (#2812)
chore(deps): bump amannn/action-semantic-pull-request

Bumps [amannn/action-semantic-pull-request](https://github.com/amannn/action-semantic-pull-request) from 5.5.2 to 5.5.3.
- [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/v5.5.2...v5.5.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-07 11:23:00 +10:00
Alexander Courtis
8b2c5c678b feat(#2799): filesystem_watchers.ignore_dirs and git.disable_for_dirs may be functions (#2800)
feat(#2799): filesystem_watchers.ignore_dirs and git.disable_for_dirs may be functions
2024-06-23 11:44:45 +10:00
github-actions[bot]
2086e564c4 chore(master): release nvim-tree 1.4.0 (#2785)
* chore(master): release nvim-tree 1.4.0

* add neovim minimum version 0.9 notice

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-06-09 12:43:06 +10:00
Alexander Courtis
1cac8005df chore: release 1.4.0
Release-As: 1.4.0
2024-06-09 12:30:45 +10:00
Alexander Courtis
8704b6f7fc chore(#2787): minimum nvim version 0.9, replace 0.10 deprecated, enable deprecated warnings (#2788)
* refactor(#2787): replace deprecated

* refactor(#2787): enable deprecated checks

* refactor(#2787): replace deprecated

* refactor(#2787): replace deprecated

* refactor(#2787): replace deprecated

* refactor(#2787): replace deprecated

* refactor(#2787): use inline deprecation disabling

* refactor(#2787): replace deprecated

* refactor(#2787): replace deprecated

* refactor(#2787): replace deprecated

* refactor(#2787): replace deprecated

* refactor(#2787): replace deprecated

* refactor(#2787): replace deprecated

* refactor(#2787): replace deprecated

* refactor(#2787): replace deprecated

* refactor(#2787): replace deprecated

* refactor(#2787): deprecated are now warnings

* refactor(#2787): 0.9 is the minimum supported version

* Revert "refactor(#2787): replace deprecated"

This reverts commit b6b4c32fcb.

* refactor(#2787): suppress deprecated until 0.11

* refactor(#2787): minimum nvim version 0.8 -> 0.9

* refactor(#2787): reset globals

* refactor(#2787): explicitly check for vim.diagnostic.is_enabled function presence
2024-06-09 12:24:35 +10:00
Alexander Courtis
26632f496e chore(#2731): neovim luadoc 0.10 compliance (#2786)
* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings, type gymnastics

* refactor(#2731): resolve warnings, type gymnastics

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

* refactor(#2731): handle cwd unavailable when opening

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings, type gymnastics

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

* refactor(#2731): style

* refactor(#2731): add _meta library, explicit check disables

* refactor(#2731): add lua-language-server manual install instructions

* refactor(#2731): resolve warnings

* refactor(#2731): explicitly set all diagnostics, reduce deprecated to hint

* Revert "refactor(#2731): resolve warnings"

This reverts commit 9c0526b7b0.

* Revert "refactor(#2731): resolve warnings"

This reverts commit f534fbc606.

* refactor(#2731): handle directory unavailable when deleting

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

* refactor(#2731): handle directory unavailable when creating explorer

* refactor(#2731): add all nvim lua libraries

* refactor(#2731): resolve warnings

* refactor(#2731): remove vim global

* refactor(#2731): disable deprecated until we have a 0.9->0.10 story
2024-06-01 15:24:03 +10:00
Alexander Courtis
5a87ffe35c ci: release tags vMAJOR.MINOR.PATCH (#2772)
* ci: release tags vMAJOR.MINOR.PATCH

* ci: tidy luarocks release naming
2024-05-28 16:11:06 +10:00
Alexander Courtis
517e4fbb9e revert(#2781): "refactor: replace deprecated use of vim.diagnostic.is_disabled()" (#2784)
Revert "refactor: replace deprecated use of vim.diagnostic.is_disabled()  (#2781)"

This reverts commit 4215f33da5.
2024-05-26 11:34:03 +10:00
Alexander Courtis
4c8ddee453 ci: add lua-language-server 3.9.1 (#2782)
* add lua-language-server 3.9.1

* remove lua-language-server 3.7.3
2024-05-25 15:42:38 +10:00
Zachary Rizer
4215f33da5 refactor: replace deprecated use of vim.diagnostic.is_disabled() (#2781)
* Deprecation fix

* Deprecation fix

* remove unnecessary assignment

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-05-25 14:42:34 +10:00
github-actions[bot]
2bc725a3eb chore(master): release nvim-tree 1.3.3 (#2776)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-05-14 10:44:50 +10:00
Jacob Kania
340d3a9795 fix: nil access exception with git integration when changing branches (#2774)
Fix nil access exception appearing when changing branches
2024-05-14 10:31:56 +10:00
dependabot[bot]
edd4e25fd4 chore(deps): bump actions/checkout from 3 to 4 (#2773)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 12:14:07 +03:00
github-actions[bot]
78c4c083ed chore(master): release nvim-tree 1.3.2 (#2771)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-05-12 14:45:55 +10:00
Andrew Plaza
acffab931a ci: luarocks releases (#2764)
* add luarocks upload

* refactor

* restrict to full semver versions

* tweak luarocks descriptions

* remove test release following successful run

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-05-12 14:37:49 +10:00
Alexander Courtis
64f61e4c91 fix(#925): handle newlines in file names (#2754) 2024-05-04 13:51:13 +10:00
Alexander Courtis
347e1eb352 fix(#2758): use nvim-webdevicons default file icon, not renderer.icons.glyphs.default, as per :help (#2759)
fix(#2758): use nvim-webdevicons default for default files
2024-04-30 11:32:51 +10:00
github-actions[bot]
76db7ed0da chore(master): release nvim-tree 1.3.1 (#2736)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-04-30 11:12:07 +10:00
dependabot[bot]
5a18b98274 chore(deps): bump amannn/action-semantic-pull-request from 5.5.0 to 5.5.2 (#2756)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-25 11:35:54 +03:00
dependabot[bot]
62008e5cf2 chore(deps): bump amannn/action-semantic-pull-request from 5.4.0 to 5.5.0 (#2755)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-24 11:14:32 +03:00
Alexander Courtis
ae8e46e8fa chore: add plugin manager requirements to bug template (#2752) 2024-04-21 12:58:49 +10:00
Alexander Courtis
81eb8d5192 fix(#2733): escape trash path (#2735)
* fix(#2733): escape trash path

* fix(#2733): escape trash path

* fix(#2733): escape trash path
2024-04-06 12:28:41 +11:00
Yida Zhang
d8d3a1590a fix(#2535): TextYankPost event sends vim.v.event (#2734)
* fix TextYankPost event

* Update lua/nvim-tree/actions/fs/copy-paste.lua

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

* fix format string

* style

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-03-31 17:24:47 +11:00
github-actions[bot]
ddd1d6eb21 chore(master): release nvim-tree 1.3.0 (#2725)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-03-30 17:53:21 +11:00
Alexander Courtis
0aca0920f4 fix(#2658): change SpellCap groups to reduce confusion: ExecFile->Question, ImageFile->Question, SpecialFile->Title, Symlink->Underlined; add all other highlight groups to :NvimTreeHiTest (#2732)
* fix(#2658): add all highlight groups to :NvimTreeHiTest

* fix(#2658): add all highlight groups to :NvimTreeHiTest

* fix(#2658): change SpellCap groups: ExecFile->Question, ImageFile->Question, SpecialFile->Title, Symlink->Underlined
2024-03-30 17:47:30 +11:00
Alexander Courtis
308f2fcec2 docs: retire matrix (#2730) 2024-03-30 14:09:08 +11:00
remvn
2d97059661 fix: bookmark filter shows marked directory children (#2719)
* fix: bookmark filter include marked-directory's children

* fix(perf): add path_type to filter functions

* fix: replace undefined type

* fix: correct Node.fs_stat type

* fix: file info popup check fs_stat not nil

* refactor: add stat to should_filter, Node constructor

* perf: early return if bookmark is empty

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-03-30 13:21:55 +11:00
Alexander Courtis
e508bdc418 docs: contributing: PR subject (#2726)
* docs: contributing: PR subject

* docs: contributing: PR subject
2024-03-26 12:23:48 +11:00
Ava Harris
e20966ae55 feat: add update_focused_file.exclude (#2673)
* Add update_focused_file.exclude and move update_focused_file.ignore_list to update_focused_file.update_root.ignore_list

* Pass ci checks

* Add config migration for update_root and ignore_list

* Missed one mention of update root in find_file.lua

* Update migration code

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

* make docs consistent

* match on filename instead of entire path

* exclude as a function

* fix docs

* default exclude value

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-03-25 15:41:05 +11:00
github-actions[bot]
85c502e907 chore(master): release nvim-tree 1.2.0 (#2713)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-03-24 13:25:16 +11:00
gegoune
b1bbd4f7dc ci(workflows): include matrices in concurrency groups (#2715) 2024-03-24 12:44:16 +11:00
Denys Lytviak
f7c09bd72e feat: add api.tree.toggle_enable_filters (#2706)
* feat: toggle filters

* naming refactoring

* change name to enable

* fix default opt

* fix api name

* update doc

* remove default keybinding, toggle live filter

* add API doc

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-03-16 12:35:13 +11:00
github-actions[bot]
707b24af91 chore(master): release nvim-tree 1.1.1 (#2709)
* chore(master): release nvim-tree 1.1.1

* doc: remove duplicate bug fix entry

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-03-15 12:26:28 +11:00
remvn
76b98109f6 fix: bookmark filter should include parent directory (#2704)
* fix: bookmark filter should include parent directory

* fix: dont match mark's siblings

* fix: match mark from the start

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-03-15 12:21:04 +11:00
Asman Umbetov
cfea5bd080 fix(#2395): marks.bulk.move defaults to directory at cursor (#2688)
* fix(#2395): marks.bulk.move defaults to directory at cursor

* fix(#2395): adds check if node_at_cursor.parent is nil

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-03-15 12:03:06 +11:00
Alexander Courtis
1fd9c98960 fix(#2705): change NvimTreeWindowPicker cterm background from Cyan to more visible DarkBlue (#2708)
fix(#2705): change NvimTreeWindowPicker from Cyan to the more visible DarkBlue
2024-03-15 10:58:40 +11:00
github-actions[bot]
164f11db4f chore(master): release nvim-tree 1.1.0 (#2691)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-03-14 12:10:17 +02:00
Rami Elwan
8f2a50f1cd feat: add api.fs.copy.basename, default mapping ge (#2698)
* feat: add copy basename

* fix: change keymap for copy basename

* fix: use double quotes

* fix: add missing help

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-03-14 16:57:46 +11:00
gegoune
c64becf80c ci: set concurrency and trigger ci on master (#2701) 2024-03-14 07:36:22 +02:00
Alexander Courtis
3c4267eb50 fix(#2695): git toplevel guard against missing paths (#2696) 2024-03-14 16:23:58 +11:00
DB
041dbd18f4 fix: searchcount exception on invalid search regex (#2693)
* fix: wrap searchcount in pcall to avoid error

* fix: searchcount in pcall

---------

Co-authored-by: xVermillionx <xVermillionx@notvalid>
2024-03-09 13:27:23 +11:00
Mohamed Arish
efafd73efa feat(#2630): file renames can now create directories (#2657)
* Added creating of directories when renaming files

* Style check error? fixed

* Forgot, I added back the line of code that creates a directory named as the file and forgot to remove it

* Added a check for file already exists and also switched the is_error values as they were mismatched

* I don't know how but this somehow fixed the creation of a directory?
2024-03-03 12:25:17 +11:00
Alexander Courtis
d52fdeb0a3 docs: add help indexes (#2684)
* docs: add nvim-tree-index-api

* docs: add nvim-tree-index-opts

* docs: add nvim-tree-index-api

* docs: sort indices

* docs: less verbose

* docs: use dictionary sort for indices
2024-02-24 17:53:25 +11:00
Alexander Courtis
7efaa339d3 docs: :help nvim-tree-legacy (#2679)
doc: add help nvim-tree-legacy
2024-02-24 15:50:10 +11:00
Alexander Courtis
030defdb65 docs: add highlight examples to quickstart, document pre-overhaul SpellCap groups (#2680)
* docs: add highlight examples to quickstart, document pre-overhaul SpellLocal groups

* docs: add highlight examples to quickstart

* docs: document pre-overhaul SpellCap groups

* docs: document pre-overhaul SpellCap groups
2024-02-20 13:24:37 +11:00
github-actions[bot]
d35a8d5ec6 chore(master): release nvim-tree 1.0.0 (#2670)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-02-18 17:28:24 +11:00
Alexander Courtis
d16246a757 chore: release 1.0.0 (#2678)
Release-As: 1.0.0
2024-02-18 17:24:05 +11:00
gegoune
863cf832ce ci: triggers, nvim stable version & env vars (#2671) 2024-02-12 09:49:39 +01:00
dependabot[bot]
c7d4650c38 chore(deps): bump JohnnyMorganz/stylua-action from 3 to 4 (#2672)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-12 09:48:00 +01:00
darcy
4a87b8b46b feat(#2654): filters.custom may be a function (#2655)
* feat(#2654): add `binaries` field to `filters`

* feat(#2648): allow functions in `filters.custom`

* ci: fix: stylua check

* ci: fix: add new keybind and config to docs

* fix: replace os-specific binary filter with `vim.fn.executable`

* fix: remove function and mapping for `binaries` filter

* fix: add `node` parameter to custom filter function

* fix: update doc for custom filter function signature

* fix: add custom filter to `ACCEPTED_TYPES`

* fix: accept single function for custom filter

* fix: change custom filter on `ACCEPTED_TYPES`

* fix: revert to using `path` for custom filter function

* fix: use `function` type for custom filter

* fix: type for custom filter in help

* fix: custom filter single function no longer mutates `M.config.filter_custom`

* fix: remove dead `if` statement in custom filter

* fix: separate custom filter function from `M.ignore_list`

* doc nit

---------

Co-authored-by: darcy <44690813+darccyy@users.noreply.github.com>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-02-11 17:18:40 +11:00
github-actions[bot]
2dbe4ea2b5 chore(master): release nvim-tree 0.100.0 (#2635)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-02-11 15:49:27 +11:00
Alexander Courtis
39e6fef85a fix(#2415): highlight help header and mappings (#2669) 2024-02-11 15:40:21 +11:00
Alexander Courtis
b278fc25ae feat(#2415): add :NvimTreeHiTest (#2664)
* feat(#2415): add :NvimTreeHiTest

* feat(#2415): split out appearance diagnostics
2024-02-11 14:41:40 +11:00
Zeta
8cbb1db8e9 feat: add node.open.toggle_group_empty, default mapping L (#2647)
* feat: ungrouping empty directories

* add a new api to toggle empty folders

* solve comments

* solve comments

* update help

---------

Co-authored-by: juefei yan <juefeiyan@juefeis-MacBook-Air.local>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-02-04 16:48:56 +11:00
Confidenceman02
f39f7b6fcd fix(#2415): nvim 0.8 highlight overhaul support, limited to only show highest highlight precedence (#2642)
* fix: Add support for get_hl_defs in nvim 0.8

nvim-tree is using `nvim_get_hl` which was introduced in nvim 0.9 to
replace the unstable `get_hl_defs` in the following [commit](https://github.com/neovim/neovim/pull/22693/files).

Unfortunately this raises an error in 0.8 nvim versions due to the
function not existing.

```
Failed to run `config` for nvim-tree.lua
...are/nvim/lazy/nvim-tree.lua/lua/nvim-tree/appearance.lua:199: attempt to call field 'nvim_get_hl' (a nil value)
stacktrace:
  - ~/.config/nvim/lua/confidenceman02/plugins/nvim-tree.lua:14 _in_ **config**
  - ~/.config/nvim/lua/confidenceman02/lazy.lua:14
```

- Fall back to get_hl_defs when detecting 0.8
- Set the 'link' property to nil to emulate `link = false` in
  `builder.lua`

* fix(#2415): nvim 0.8 highlight overhaul support, limited to only show highest highlight precedence

---------

Co-authored-by: Jaime Terreu <jaime@terreu.com>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-01-30 10:37:32 +11:00
Alexander Courtis
e9ac136a3a fix(#2415): NvimTreeIndentMarker highlight group: FileIcon->FolderIcon (#2656)
fix(#2415): fix NvimTreeIndentMarker highlight group: FileIcon->FolderIcon
2024-01-29 13:28:20 +11:00
Alexander Courtis
d9cb432d2c fix(#2415): disambiguate highlight groups, see :help nvim-tree-highlight-overhaul (#2639)
* fix(#2415): disambiguate highlight groups, see :help nvim-tree-highlight-overhaul

* fix(#2415): disambiguate highlight groups, see :help nvim-tree-highlight-overhaul

* fix(#2415): disambiguate highlight groups, see :help nvim-tree-highlight-overhaul

* fix(#2415): disambiguate highlight groups, see :help nvim-tree-highlight-overhaul

* fix(#2415): disambiguate highlight groups, see :help nvim-tree-highlight-overhaul

* fix(#2415): disambiguate highlight groups, see :help nvim-tree-highlight-overhaul

* fix(#2415): disambiguate highlight groups, see :help nvim-tree-highlight-overhaul
2024-01-29 12:43:02 +11:00
Alexander Courtis
fbee8a69a4 fix(#2643): correctly apply linked highlight groups in tree window (#2653)
* fix(#2643): correctly apply linked highlight groups in tree window

* fix(#2643): recreate and apply combined highlight groups on colorscheme change
2024-01-29 12:42:19 +11:00
Alexander Courtis
7bdb220d0f fix(#2637): show buffer modified icons and highlights (#2638) 2024-01-21 17:24:43 +11:00
Danila Usachev
48b1d8638f fix(#2632): occasional error stack when locating nvim-tree window (#2633)
fix: passing nil as window handle in view.get_winnr

Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-01-21 15:54:27 +11:00
Tomasz N
75ff64e666 fix: bad column offset when using full_name (#2629)
Co-authored-by: __ <__@__>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-01-21 15:37:46 +11:00
Kevin Ko
74525ac047 fix: allow highlight overrides for DEFAULT_DEFS: NvimTreeFolderIcon, NvimTreeWindowPicker (#2636) 2024-01-21 10:32:28 +11:00
Alexander Courtis
e9c5abe073 feat(#2415): colour and highlight overhaul, see :help nvim-tree-highlight-overhaul (#2455)
* feat(#2415): granular highlight_diagnostics, normalise groups (#2454)

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

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

* docs: update CONTRIBUTING.md (#2485)

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

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

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

* feat(#2415): create DecoratorDiagnostics

* feat(#2415): create DecoratorGit

* feat(#2415): create DecoratorGit

* add DecoratorCopied DecoratorCut

* add DecoratorOpened

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

* Add `renderer.highlight_git` to accepted strings

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

* simplify builder signs

* decorators take care of themselves and are priority ordered

* simplify builder hl groups

* refactor builder for icon arrays

* builder use decorators generically

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

* fix(#2415): harden unicode signs

* Decorator tidy

* normalise git sign creation and tidy

* tidy builder

* NvimTreeBookmarkIcon

* tidy HL doc

* tidy HL doc

* tidy HL doc

* tidy builder doc

* standardise on '---@param'

* DiagnosticWarning -> DiagnosticWarn

* annotate decorators

* limit to two highlight groups for line rendering

* style

* apply #2519

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

* feat(#2415): create combined highlight groups

* feat(#2415): create combined highlight groups

* feat(#2415): create combined highlight groups

* ci: allow workflow_dispatch (#2620)

* one and only one hl namespace, required winhl removal

* small tidies

* colors.lua -> appearance.lua

* full-name uses one and only namespace

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

* gut builder (#2622)

collapse Builder

* fix group_empty function check

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

---------

Co-authored-by: Akmadan23 <azadahmadi@mailo.com>
2024-01-20 16:12:13 +11:00
Alexander Courtis
f24afa2cef fix(#2624): open file from docked floating window (#2627) 2024-01-14 11:08:15 +11:00
dependabot[bot]
b8c3a23e76 chore(deps): bump actions/checkout from 3 to 4 (#2623)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 10:09:04 +01:00
Alexander Courtis
9f6d3fac82 ci: fix: run on push release-please--branches--master--components--nvim-tree (#2621) 2024-01-08 11:24:19 +11:00
Alexander Courtis
78a5836092 ci: allow workflow_dispatch (#2620) 2024-01-07 10:20:23 +11:00
Alexander Courtis
86810e5c0b ci: run on push release-please--branches--master--components--nvim-tree (#2619) 2024-01-07 09:57:13 +11:00
Antonin Godard
5d13cc8205 feat(#1389): api: recursive node navigation for git and diagnostics (#2525)
* feat(#1389): add next recursive for git and diag moves

The recurse opt can be used to directly go to the next item showing
git/diagnostic status recursively.

Signed-off-by: Antonin Godard <antoningodard@pm.me>

* refactor: status logic in single function

Rename get_status to status_is_valid.

Use status_is_valid function in multiple place to avoid duplicating
code.

Signed-off-by: Antonin Godard <antoningodard@pm.me>

* feat(#1389): add prev recursive for git and diag moves

Signed-off-by: Antonin Godard <antoningodard@pm.me>

* fix(#1389): next recursive: take root node into account

The root node cannot have a status. Previously if moving from the root
node, status_is_valid was trying to fetch the status from it and errored.

Signed-off-by: Antonin Godard <antoningodard@pm.me>

* fix(#1389): doc: remove show_on_open_dirs limitation

Signed-off-by: Antonin Godard <antoningodard@pm.me>

* feat(#1389): move find_node_line to utils

Signed-off-by: Antonin Godard <antoningodard@pm.me>

* feat(#1389): doc: note recursive moves are to files only, tidy

---------

Signed-off-by: Antonin Godard <antoningodard@pm.me>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-01-07 09:08:58 +11:00
Alexander Courtis
6a99f5af78 ci: lua language server and Makefile (#2546)
* ci: add lls-check

* ci: add lls-check to ci.yml

* ci: download lua-language-server binary

* ci: download lua-language-server binary

* ci: dummy failure to test

* Revert "ci: dummy failure to test"

This reverts commit 2bc43bad430209e32a5049a16c56710c4f6e2f7b.

* ci: ignore lls-out

* ci: better name

* ci: shellcheck nits

* ci: add luals libs and tidy

* ci: tidy

* ci: add neovim 0.9.4

* ci: add ci neovim 0.9.4 to lib path

* ci: dummy failure to test

* Revert "ci: dummy failure to test"

This reverts commit 45987335d81ec65fecc6636b339671a9a9fcdd97.

* Revert "ci: add ci neovim 0.9.4 to lib path"

This reverts commit 4f397d6ea8bbdf6e808f9dc9db5ecbae291d8cd4.

* Revert "ci: add neovim 0.9.4"

This reverts commit 46fd1b368d27a1892b55381691723db3b30a7527.

* ci: action downloads and installs luals

* ci: remove workspaces from luals

* ci: consistent script naming

* ci: add quality to contributing

* ci: consistent script naming

* ci: add lsp to diagnostics

* ci: temporary find to enumerate home

* ci: add VIMRUNTIME for lls

* ci: temporary find to enumerate home

* ci: temporary find to enumerate home

* ci: remove temporary find to enumerate home

* ci: correct VIMRUNTIME

* ci: add ${3rd}/luv/library

* ci: note VIMRUNTIME override

* ci: add Makefile

* ci: add Makefile

* ci: add Makefile

* ci: add Makefile

* ci: document checks and fixes

* ci: add help check

* ci: add help check

* ci: dummy help failure

* Revert "ci: dummy help failure"

This reverts commit c50cceaa4a.

* ci: document checks and fixes

* ci: document checks and fixes

* ci: matrix nvim version

* ci: matrix nvim version

* Revert "ci: matrix nvim version"

This reverts commit fcef6a11e9.

* Revert "ci: matrix nvim version"

This reverts commit a8cb50d39d.

* ci: matrix nvim version from env

* ci: matrix nvim version from env

* ci: matrix nvim version from env

* ci: matrix nvim version

* ci: matrix nvim version

* ci: matrix per job

* ci: matrix per job

* ci: many lua versions

* ci: move doc to style

* ci: tidy ci and contributing
2024-01-06 13:18:52 +11:00
github-actions[bot]
f1b3e6a7eb release nvim-tree 0.99.0 (#2587)
* chore(master): release nvim-tree 0.99.0

* doc: tidy changelog

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-01-01 12:46:40 +11:00
Alexander Courtis
b6b86e1f3e chore: release 0.99.0
Release-As: 0.99.0
2024-01-01 12:40:29 +11:00
Alexander Courtis
fac4900bd1 fix(#2609): help toggle (#2611) 2024-01-01 12:36:57 +11:00
Azad
f779abaf2a refactor: improve API readability and tidy actions submodules (#2593)
* refactor: improve API readability, tidy actions modules

* Apply requested changes

* `actions/reloaders/reloaders.lua` -> `actions/reloaders.lua`

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-12-31 15:52:27 +11:00
Devansh Sharma
dc839a72a6 feat: add kind param to vim.ui.select function calls (#2602)
* feat: add kind param to vim.ui.select function calls

* feat: add kind param to prompts for bookmark actions

* docs: add section for prompts

* docs: add section for prompts

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-12-31 15:37:16 +11:00
geril2207
02ae52357b fix: hijack_cursor on update focused file and vim search (#2600)
refactor: hijack_cursor search skip
2023-12-31 14:40:58 +11:00
Max
96a783fbd6 fix(#2519): Diagnostics Not Updated When Tree Not Visible (#2597)
* fix(#2519): diagnostics overhaul

Signed-off-by: iusmac <iusico.maxim@libero.it>

* fix: Properly filter diagnostics from coc

Also, while we're at it, refactor the lsp function for consistency.
There should be no functional change, just cosmetic.

Signed-off-by: iusmac <iusico.maxim@libero.it>

* Assign diagnostic version per node to reduce overhead

Signed-off-by: iusmac <iusico.maxim@libero.it>

* Require renderer once

Signed-off-by: iusmac <iusico.maxim@libero.it>

* Revert "Require renderer once"

Causes circular requires after the previous commit.

This reverts commit 7413041630.

* Rename `buffer_severity_dict` to `BUFFER_SEVERITY`

Signed-off-by: iusmac <iusico.maxim@libero.it>

* Log diagnostics update properly

Signed-off-by: iusmac <iusico.maxim@libero.it>

* Implement error handling for coc.nvim

Signed-off-by: iusmac <iusico.maxim@libero.it>

* CI style fixes

Signed-off-by: iusmac <iusico.maxim@libero.it>

* Capture `Keyboard interrupt` when handling coc exceptions

Signed-off-by: iusmac <iusico.maxim@libero.it>

* add more doc

---------

Signed-off-by: iusmac <iusico.maxim@libero.it>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-12-30 14:30:07 +11:00
Azad
50f30bcd8c feat: add option to skip gitignored files on git navigation (#2583)
* feat: add option to skip gitignored files on git navigation

* Add API bindings

* stylua: ignore
2023-12-19 11:29:01 +01:00
Alexander Courtis
8f92e1edd3 feat(#1850): add "no bookmark" filter (#2571)
* feat(#1850): add no bookmark filter

* feat(#1850): add no bookmark filter - style
2023-12-19 16:18:24 +11:00
gegoune
141c0f97c3 chore: first release (#2588) 2023-12-11 01:36:12 +01:00
Azad
34780aca5b refactor: take single opts param on node navigation (#2584)
* refactor: take single `opts` param on node navigation

* `MoveOpts` -> `NavigationItemOpts`
2023-12-10 23:44:36 +01:00
gegoune
4891d6cec3 ci: fix release-please manifest (#2586)
Release-As: 0.9.0
2023-12-10 11:39:39 +01:00
gegoune
90cff8e468 ci: configure release-please
Release-As: v0.9.0
2023-12-10 11:28:25 +01:00
Alexander Courtis
0a7c24b675 fix(#2568): hijack_cursor positions at top grouped node (#2569)
Co-authored-by: Azad <49314270+Akmadan23@users.noreply.github.com>
2023-12-10 00:13:22 +01:00
Alexander Courtis
27e66c2ea8 refactor(#1645): remove unused open_replacing_current_buffer (#2570) 2023-12-09 12:18:57 +11:00
Azad
2fed5e1010 ci: add style check for doc comments (#2575)
Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-12-09 12:06:06 +11:00
Azad
13f967f8e7 chore: add type annotations and resolve LSP warnings (#2555)
* chore: add type annotations to (almost) all functions

* stylua

* Add classes for symlink nodes

* Replace deprecated `@vararg`

* Move node classes to `node` module

* Fix `Symlink*` classes

* add vim and libuv runtime for luals, qualify libuv types

* add scripts/luals-check, not quite ready for CI

* additional nil checks for git/init.lua and git/runner.lua

* additional nil checks for nvim-tree.lua

* wrap vim.cmd-as-a-function calls inside functions

* vim.tbl_filter predicate returns booleans

* Revert "add scripts/luals-check, not quite ready for CI"

This reverts commit c70229cad9.

* Add `MinimalNode` class in `marks` module

* Fix various LSP warnings

* stylua

* Fix `Explorer` class, update related annotations and add necessary checks

* Add missing annotations to `live-filter`

* Add temporary aliases for `uv.*` types

* Resolve remaining LSP warnings

* Revert changes not related to internal types

* Minor adjustments

* Update doc comments style

* Minor adjustments (pt. 2)

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-12-09 11:34:35 +11:00
dependabot[bot]
7d1760f892 chore(deps): bump google-github-actions/release-please-action from 3 to 4 (#2573)
chore(deps): bump google-github-actions/release-please-action

Bumps [google-github-actions/release-please-action](https://github.com/google-github-actions/release-please-action) from 3 to 4.
- [Release notes](https://github.com/google-github-actions/release-please-action/releases)
- [Changelog](https://github.com/google-github-actions/release-please-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google-github-actions/release-please-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: google-github-actions/release-please-action
  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>
2023-12-05 10:39:40 +11:00
Alexander Courtis
7e5c673180 docs(#285): clarify api.fs.create directory creation (#2572)
docs(#285): clarify api.fs.create directory creation
2023-12-04 11:32:03 +11:00
Azad
05f55c1fd6 chore: remove TreeExplorer global variable (#2561) 2023-11-28 10:39:52 +01:00
Alexander Courtis
d5cc938ab0 refactor: api and command focus call tree.open(), soft deprecate tree.focus() 2023-11-27 14:24:55 +02:00
kezhenxu94
5e4475d8bf fix: harden tree root cwd fetch (#2557)
Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-11-26 09:39:19 +11:00
Azad
5231562caf feat(log): add node inspection function (#2541)
* feat: add `api.tree.inspect_node_under_cursor`

* Add documentation

* Revert "feat: add `api.tree.inspect_node_under_cursor`"

This reverts commit 784ee91cc6.

* Revert "Add documentation"

This reverts commit 6dc396d0a5.

* feat(log): add node inspection function

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-11-25 21:45:59 +01:00
Alexander Courtis
db796fc74e docs: complete API calls for commands (#2556)
doc: complete API calls for commands
2023-11-25 13:21:34 +11:00
dependabot[bot]
fa00b57873 chore(deps): bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-22 22:02:04 +02:00
John Hui
8c534822a7 feat(#2544): add api.tree.winid (#2545)
* feat(#2544): add API for querying win ID, api.tree.winid()

* Document winid() opts

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

* Fix winid() docs

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

* Handle case where tabpage = 0

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-11-21 16:04:43 +11:00
Alexander Courtis
28cf0cd678 ci: pin versions, use luarocks for luacheck (#2543)
Co-authored-by: Azad <49314270+Akmadan23@users.noreply.github.com>
2023-11-21 10:43:31 +11:00
Azad
fb89297347 fix(#2468): always apply filters to subdirectories (#2537)
Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-11-20 11:43:35 +01:00
David Karlsson
b67a773609 docs: update quick start example (#2540)
update sort configuration key
2023-11-20 11:21:56 +11:00
Alexander Courtis
46e1f776f0 fix(#2516): diagnostics icon highlight group matches the documentation: NvimTreeLspDiagnosticsInfo -> Information (#2518) 2023-11-19 15:31:52 +11:00
Cristi
8f9169a059 fix: git highlight for new staged files (#2534)
Co-authored-by: Cristian Toma <cristian.toma@vivre.eu>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-11-19 14:47:10 +11:00
Matt W
633811c53d fix: harden git status updates (#2533)
* fix for nil status error messages

* simplify logic

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-11-19 14:43:36 +11:00
Alfonso Ros
52a1c99bf0 feat(#2530): keep alt filename on node.open.replace_tree_buffer (#2531)
Co-authored-by: Alfonso Ros <alfonso.ros@apex.ai>
2023-11-19 14:38:55 +11:00
geril2207
80cfeadf17 fix(#2523): live filter overlay width calculation (#2524)
* fix: live filter overlay width calculation

* refactor: simplify calculate_width return if not wininfo

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-11-13 09:49:14 +11:00
Azad
874ae6e944 fix: reload tree on BufEnter if cwd is different (#2527)
Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-11-12 13:14:32 +11:00
geril2207
74ddb8f2bb fix: cleanup live filter scratch buffers (#2522) 2023-11-12 12:53:24 +11:00
Azad
a2aaf8b430 feat(#2515): add option to change grouped folders name with custom function (#2521)
* Add option to change grouped folders name with custom function

* Fix docs

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-11-09 11:20:29 +01:00
geril2207
4ee6366ff1 fix(#2512): file creation in empty folder without root_folder_label (#2514)
Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-11-08 09:01:28 +11:00
Azad
0a99c4a23e feat: allow cycling on git/diagnostic/opened files navigation (#2506)
* feat: allow cycling on git/diagnostic/opened files navigation

* luacheck

* Remove useless nil check

* Cycle only if `wrapscan` is enabled

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-11-07 00:39:08 +01:00
dependabot[bot]
c763861afb chore(deps): bump amannn/action-semantic-pull-request
Bumps [amannn/action-semantic-pull-request](https://github.com/amannn/action-semantic-pull-request) from 5.3.0 to 5.4.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/v5.3.0...v5.4.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-06 12:42:04 +02:00
Dongyomi
20a0707e0f fix(#2507): icon in message after rename-file (#2510)
* fix(#2507): icon in message after rename-file

* fix(#2507): icon in message after rename-file

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-11-06 10:38:16 +11:00
geril2207
24bb0ed806 fix: error when deleting opened file from floating window (#2503) 2023-11-06 09:35:51 +11:00
Tomasz N
7e3c0bee7b feat: renderer.full_name includes root node (#2502)
* Do not exclude root node from `full_name`

* fix range

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-10-30 13:17:33 +11:00
Azad
7630cf4a92 fix(#2495): skip API action if node == nil (#2499)
* fix(#2495): skip action if node == nil

* simplify

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-10-30 12:46:21 +11:00
Alexander Courtis
df38f1f30d docs: add (disabled) diagnostics config to bug report template (#2484)
* docs: add (disabled) diagnostics config to bug report template

* Revert "docs: add (disabled) diagnostics config to bug report template"

This reverts commit aa0b9aa9b8.

* docs: add (disabled) diagnostics config to bug report template
2023-10-30 12:44:25 +11:00
Azad
c1568568b3 feat(#2498): delete, trash prompts default N, added ui.confirm.default_yes option to override this behaviour (#2500)
Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-10-30 12:28:29 +11:00
rei
7c5c074354 fix(#2467): remove newline in git paths when using cygwin_support (#2478)
* fix(#2467): remove newline in git paths

* fix: info size suffix and formatting (#2492)

- Now there is a whitespace between value and unit.
- Now values >= 1024 YiB are shown in YiB instead of B.
- To reuse same code a new local function was added: round().

* feat(#2312): fire `TextYankPost` event on path copy (#2489)

* feat(#2312): fire `TextYankPost` event on path copy

* stylua

* Bug fix

---------

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

* feat: mapping and options to sort entries in help window (#2482)

* feat: add option to sort entries in help window

* stylua

* Add keymap to toggle sorting methods

* Bug fix

---------

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

* fix(#2467): remove newline in git paths

* fix(#2467): change cygpath calls to array format
To avoid shell compatibility issues in msys2 environment on Windows

* stylua nit

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
Co-authored-by: Andrew Voynov <37143421+Andrew15-5@users.noreply.github.com>
Co-authored-by: Azad <49314270+Akmadan23@users.noreply.github.com>
2023-10-30 11:39:32 +11:00
Azad
78a9ca5ed6 feat: mapping and options to sort entries in help window (#2482)
* feat: add option to sort entries in help window

* stylua

* Add keymap to toggle sorting methods

* Bug fix

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-10-23 08:57:49 +11:00
Azad
c2194e940c feat(#2312): fire TextYankPost event on path copy (#2489)
* feat(#2312): fire `TextYankPost` event on path copy

* stylua

* Bug fix

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-10-23 08:56:06 +11:00
Andrew Voynov
a31dfada1d fix: info size suffix and formatting (#2492)
- Now there is a whitespace between value and unit.
- Now values >= 1024 YiB are shown in YiB instead of B.
- To reuse same code a new local function was added: round().
2023-10-23 08:54:37 +11:00
Andrew Voynov
83b699533b feat: use IEC binary size prefixes (#2483)
* Added binary (IEC) prefixes

* Added missing binary prefixes
2023-10-22 08:58:45 +11:00
Alexander Courtis
db8145c27d fix(#2459): disable cygwin git support by default, see :help nvim-tree.git.cygwin_support to enable (#2486) 2023-10-21 16:34:34 +11:00
Alexander Courtis
8b4dbc57e4 docs: update CONTRIBUTING.md (#2485) 2023-10-21 13:54:25 +11:00
Alexander Courtis
40b9b887d0 fix(#2473): remove problematic <S-Tab> default mapping (#2475) 2023-10-17 10:00:56 +11:00
umlx5h
aaee4cd896 feat: api.node.open.preview_no_picker with default mapping <S-Tab> (#2464)
* feat: add preview with no window picker action

* feat: preview_no_window_picker -> preview_no_picker

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-10-15 14:04:04 +11:00
Benoit Charles
0882354517 feat(#2148): api.fs.rename_full (#2461)
* feat(#2148): add rename_full in API

* feat(#2148): add default mapping 'u' for rename_full

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-10-15 13:40:40 +11:00
Alexander Courtis
57078f9240 docs: add team and windows variant/notes to bug report (#2470) 2023-10-15 13:12:33 +11:00
Azad
4054fc4be3 refactor: format tables line by line for better readability (#2456)
* Format tables line by line for better readability

* Forgot a comma

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-10-14 18:07:28 +11:00
Alexander Courtis
e64a498a5e feat: notify [NvimTree] prefix is multiline only if the message is multiline (#2453)
* feat: notify [NvimTree] prefix is multiline only if the message is multiline

* feat: notify [NvimTree] prefix is multiline only if the message is multiline
2023-10-14 18:03:13 +11:00
Alexander Courtis
53b0bcaada chore: stylua column width 120 -> 140 (#2448)
* chore: stylua column width 120 -> 140

* chore: stylua column width 120 -> 140, tidy

* Revert "chore: stylua column width 120 -> 140, tidy"

This reverts commit 8a0524d6bd.

* chore: stylua column width 120 -> 140, tidy watcher.lua

* chore: stylua column width 120 -> 140, tidy diagnostics.lua

* chore: stylua column width 120 -> 140, tidy git.lua

* chore: stylua column width 120 -> 140, tidy open-file.lua

* chore: stylua column width 120 -> 140, tidy system-open.lua

* chore: stylua column width 120 -> 140, tidy runner.lua
2023-10-08 11:40:58 +11:00
zootedb0t
94e572e141 fix(#2450): apply NvimTreeImageFile for webp and jxl files (#2451) 2023-10-08 11:37:20 +11:00
Azad
85abe29396 feat: use virtual title in notifications if title is not supported (#2439)
* feat: use virtual title in notifications if title is not supported

* Fix boolean expressions

* Replace `pcall` with `package.loaded`

* Detect title support before sending notification

* Prevent `title_support` from being nil after evaluation

* temporary stylua suppression

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-10-07 12:40:39 +11:00
Alexander Courtis
d8e495b235 fix(#2154): selection incorrect after find-file when renderer.group_empty (#2437) 2023-10-07 12:24:19 +11:00
Bram Reyniers
e153d9f599 fix(#2440): view.width.padding may be a number or function returning a number (#2442)
* fix validation view.width.padding

* fix docs for view.width.padding

* fix docs for view.width.padding

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-10-07 12:21:13 +11:00
Alexander Courtis
7dcda5d3b6 doc(#2440): view.width.padding may only be a string (#2441) 2023-10-02 14:38:11 +11:00
Azad
113e0950c8 feat: split startup warning messages into multiple lines (#2436) 2023-10-01 12:51:49 +11:00
dependabot[bot]
934469b9b6 chore(deps): bump amannn/action-semantic-pull-request from 5.2.0 to 5.3.0 (#2435)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-26 10:21:35 +02:00
Azad
ce3495bd4c fix: schedule notifications to avoid UI freeze on startup (#2432)
* fix: schedule notifications to avoid UI freeze on startup

* stylua
2023-09-26 08:41:23 +10:00
Alexander Courtis
07eb5b4059 docs: notify deprecated hide_root_folder (#2431) 2023-09-24 15:15:19 +10:00
Alexander Courtis
d49a284236 feat(#2411): add renderer.highlight_bookmarks, renderer.icons.bookmarks_placement (#2412)
* feat(#1079): add highlight NvimTreeCopiedText and NvimTreeCutText

* feat(#1079): add highlight NvimTreeCopiedText and NvimTreeCutText

* feat(#1079): node may not be present in copy and cut

* feat(#2411): bookmark highlight and icon placement

* feat(#1079): add renderer.highlight_clipboard

* feat(#1079): add renderer.highlight_clipboard

* feat(#2411): bookmark highlight and icon placement

* feat(#2411): bookmark highlight and icon placement

* style

* feat(#2411): bookmark highlight and icon placement

* feat(#2411): bookmark highlight and icon placement

* feat(#2411): bookmark highlight and icon placement

* feat(#2411): bookmark highlight and icon placement
2023-09-24 15:07:02 +10:00
Azad
ea147418e0 feat: validate all option types (#2414)
* refactor: follow config structure for `ACCEPTED_TYPES`

* Bug fix

* Fix check for default values

* Reduce error notifications verbosity

* Address issues introduced previously

* stylua

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-09-23 14:56:17 +10:00
Azad
914a6868cb docs: add missing quotes (#2424) 2023-09-23 12:06:48 +10:00
Alexander Courtis
a3aa3b47ea feat(#1079): add renderer.highlight_clipboard default name, defaults to undercurls (#2410)
* feat(#1079): add highlight NvimTreeCopiedText and NvimTreeCutText

* feat(#1079): add highlight NvimTreeCopiedText and NvimTreeCutText

* feat(#1079): node may not be present in copy and cut

* feat(#1079): add renderer.highlight_clipboard

* feat(#1079): renderer.highlight_clipboard takes options, style cut/copy HL

* feat(#1079): renderer.highlight_clipboard takes options, style cut/copy HL

* feat(#1079): use an enum for highlight position

* feat(#1079): diagnostics uses _append_highlight
2023-09-17 16:08:04 +10:00
Azad
f742b86852 fix: add legacy options safety 2023-09-17 11:08:57 +10:00
pr4th4m
7f7665a17b feat: api.marks.bulk.trash (#2391)
* Feature: Bulk trash api

* Update docs

* Follow documentation syntax

* Remove unnecessary refresh

* doc spacing

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-09-16 15:10:36 +10:00
Alexander Courtis
94c7c810af Revert "fix: ensure startup warnings are visible with a multiline message, to work around https://github.com/neovim/neovim/issues/17832 planned for fix in nvim 0.10 (#2387)"
This reverts commit 807dc05156.
2023-09-10 10:38:49 +10:00
Alexander Courtis
b7f6600bc2 feat(#2197): git and diagnostics folder highlight groups (#2409)
* feat(#2197): add git folder highlight groups

* feat(#2197): add diagnostics folder highlight groups
2023-09-09 15:13:14 +10:00
Alexander Courtis
8f48426c88 feat(#2316): add NvimTreeFolderArrowClosed NvimTreeFolderArrowOpen (#2408) 2023-09-09 14:21:25 +10:00
Alexander Courtis
33c3bc562b feat(#2398): add NvimTreeOpenedFileIcon (#2407) 2023-09-09 13:31:40 +10:00
Tillman Jex
b856d0a0c3 fix(#2392): bookmarks icon placement when group_empty (#2402)
* fix: marks now align with nodes when parent nodes are empty and group_empty option
enabled

* run stylua

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-09-09 11:27:15 +10:00
Azad
51f02366de feat: validate some option string values (#2404)
* Add check for accepted strings in user opts

* option failures point to :help nvim-tree-opts

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-09-09 11:07:06 +10:00
dependabot[bot]
ec33d4befa chore(deps): bump actions/checkout from 3 to 4 (#2403) 2023-09-05 12:45:39 +02:00
Alexander Courtis
5897b3622f fix(#2386): kill git zombies (#2401) 2023-09-03 17:26:05 +10:00
Alexander Courtis
277632fbd9 docs: enhance quickstart, document git timeouts (#2400)
* doc: clarify git and remove bookmarks

* doc: break up quickstart
2023-09-03 16:36:37 +10:00
Alexander Courtis
a2b6e5ad2a docs: help sections and tidy (#2399)
* doc: add nvim-tree-options, sort root level options

* doc: reorder default options, add sections

* doc: collapse opts spacing and tidy

* doc: tidy highlight groups
2023-09-03 15:52:33 +10:00
Alexander Courtis
323f65cb9c feat(#1917): add diagnostic highlighting and icon placement (#2396)
* feat(#1917): add renderer.highlight_diagnostics

* feat(#1917): add renderer.highlight_diagnostics

* feat(#1917): add enderer.icons.diagnostics_placement

* feat(#1917): add renderer.icons.show.diagnostics

* feat(#1917): document highlight overrides
2023-09-03 12:29:33 +10:00
Alexander Courtis
28c3980b25 fix(#2382): git watcher handles worktrees and submodules, via --absolute-git-dir when it is available (#2389)
* fix(#2382): use --absolute-git-dir when available

* fix(#2382): use --absolute-git-dir when available

* fix(#2382): rename private git members, destroy git watchers on purge

* fix(#2382): consistent naming of toplevel

* fix(#2382): more doc and safety

* fix(#2382): consistent naming of toplevel

* fix(#2382): consistent naming of toplevel
2023-09-02 12:05:34 +10:00
Danie-1
00741206c2 fix: expand and collapse whole folder groups (#2380)
* fix: expand and collapse whole folder groups

* refactor: rename some usages of `next`

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-08-26 14:44:38 +10:00
Antonio Sarosi
b144b33390 feat(#2369): add full renderer.icons.web_devicons options for file and folder (#2375)
* Add `webdev_colors_folder` option

* Check if `M.devicons` exists

* Refactor `get_folder_icon`

* Add configuration options for both files and folders

* web_devicons.*.enabled -> enable

* silent migration: renderer.icons.webdev_colors -> renderer.icons.web_devicons.file.color

* silent migration: renderer.icons.webdev_colors -> renderer.icons.web_devicons.file.color

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-08-26 14:42:44 +10:00
Azad
d11d701857 feat(#2364): add option to sort files first (#2366)
* feat(#2364): add option to show files first

* Refactor `folders_or_files_first` function

* Improve readability

* Remove `fallback` from `folders_or_files_first` function

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-08-26 14:15:56 +10:00
Alexander Courtis
807dc05156 fix: ensure startup warnings are visible with a multiline message, to work around https://github.com/neovim/neovim/issues/17832 planned for fix in nvim 0.10 (#2387) 2023-08-26 13:40:25 +10:00
Mohamed Arish
920868dba1 fix(#2370): Better "y/N" prompts (#2377)
* Changed the default y/n prompt to default to N in most cases(all delete cases) and made it so that the copy paste same name conflict defaults to R(ename)

* Removed all No conditions as they are not used and not needed

* Made item_short into lowercase and also fixed prompts in dressing.nvim and telescope-select.nvim

* Fixed the exception which occurs on pressing esc in the prompt and also made rename to be blank or r.*/R.*
2023-08-20 17:34:14 +10:00
davisthedev
7c4c7e4e98 fix(#2352): windows: escape special filename characters on edit (#2374)
* Fix escape special characters on windows

fixes #2362

* use utils for windows check

* Add function to escape special chars on windows

* Change escape string function to use and/or

* Add nil check in escape special chars function

---------

Co-authored-by: Davis Sanders <dsanders@smartlink.city>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-08-20 13:27:33 +10:00
Alexander Courtis
4e36850811 fix(#2301): various git folder status fixes (#2373)
* fix(#2301): reloader handles grouped

* fix(#2301): explore uses correct git project for grouped

* fix(#2301): update parent status correctly across repositories

* fix(#2301): missing require
2023-08-20 12:53:41 +10:00
Devansh Sharma
dea82ae207 docs: fix typo in API Node section (#2381) 2023-08-18 23:54:16 +02:00
Alexander Courtis
18c7a31198 chore: remove legacy view.mappings.list (#2371) 2023-08-14 17:41:55 +10:00
Alexander Courtis
ace64228ad feat(#2305): find file refreshes up the tree when node is not present (#2358) 2023-08-14 11:08:16 +10:00
Alexander Courtis
116b88564f chore: use stdpath "log" rather than "cache" (#2372) 2023-08-14 11:03:38 +10:00
Alexander Courtis
0a54dcb76b fix: trash.cmd defaults to 'trash' on macos and windows, document option (#2336)
* fix: trash.cmd defaults to 'trash' on macos

* fix: macOS and windows default trash commands, allow trash on all OS

* fix: windows default trash command doc

* fix: trash.cmd message
2023-08-13 12:18:06 +10:00
Alexander Courtis
904f95cd9d fix: prompt uses first character of response - allow "yy" (#2357) 2023-08-06 13:26:10 +10:00
xundaoxd
0042886db0 select yes default when select_prompts = false in lib.prompt (#2337)
Co-authored-by: yinchaogao <yinchaogao@deeproute.ai>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-08-06 11:14:14 +10:00
Alexander Courtis
6c3ddcbc10 doc: sort.sort_folders_first -> sort.folders_first (#2355) 2023-08-06 10:50:51 +10:00
Danila Usachev
0a89dcb464 fix(#2343): tree is now correctly abandoned upon an in-place open with eject=false (#2344) 2023-08-01 10:53:58 +10:00
Danila Usachev
4bd30f0137 feat: add actions.open_file.eject (#2341)
* feat: added prevent_buffer_override option to allow in-place opens by :e

* Moved option check inside the callback

* Renamed option to eject
2023-07-29 17:45:03 +10:00
linrongbin16
75c05742bc feat(trash): add synchronized trash support for windows (#2335)
* feat(trash): support 'trash' on Windows

* feat(trash): need sync wait on Windows to avoid switch to other app from nvim process

* doc: remove 'Only available for UNIX'

* doc(trash): highlight 'Trash' on Windows is syncrhonized

* doc(trash): highlight 'trash' on Windows is synchronized

* doc(trash): remove dot

* fix(trash): check for unix and windows

* fix(trash): comment

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-07-29 17:34:47 +10:00
Alexander Courtis
c1466f991a fix(#2327): set parent folder git ignore status following file update (#2328)
* fix(#1931): do not execute git status in git ignored directories

* fix(#1931): reload.refresh_node is always asynchronous

* fix(#2327): set parent folder ignore status following file update
2023-07-29 16:28:19 +10:00
Alexander Courtis
273c1700eb fix(#1931): do not execute git status in git ignored directories (#2326)
* fix(#1931): do not execute git status in git ignored directories

* fix(#1931): reload.refresh_node is always asynchronous
2023-07-23 17:12:49 +10:00
Stefano Stoduto
3b62c6bf2c feat(event): add TreeRendered (#2324)
* add TreeRendered event

* pass bufnr and winnr to TreeRendered event

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-07-16 17:22:45 +10:00
Alexander Courtis
697bfaccac feat: add filters.git_ignored (prev git.ignore), apply "Toggle Filter:" prefix to mapping descriptions (#2325) 2023-07-16 13:26:03 +10:00
Zach Himsel
a6daf50b9d feat: support custom $GIT_DIR (#2263)
* feat: Watch $GIT_DIR for git changes, if set

While rarely used, it's possible to set the $GIT_DIR environment
variable to instruct git to use a directory other than `.git`.

This checks if that environment variable is set; if it is, the plugin
will watch that directory. If it's not set, it'll fall back to the
default `.git` directory.

* fix: Don't create two watchers for $GIT_DIR

This will ignore a path for watching if EITHER it's '.git', or the value
of $GIT_DIR (if it's set).

If $GIT_DIR is not set, the vim.env object returns `nil`, which will
never match `path`.

* fix: Attempt to make a relative $GIT_DIR absolute
2023-07-16 12:44:21 +10:00
Alexander Courtis
ef305a888b feat(#2313): sort_by -> sort.sorter, add sort.folders_first default true (#2314)
* feat(#2313): add sort_folders_first, default true

* feat(#2313): add sort.sorter, sort.folders_firs
2023-07-15 15:20:22 +10:00
Cyber Oliveira
a708bd2413 feat: add sort_by "suffix" (#2307)
* feat: adds new type of sorting based on the filename's suffix

* chore(syntax): using string colon methods

* fix(regex): use alphanumeric match for extensions

* feat: adds new type of sorting based on the filename's suffix

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-07-09 13:49:41 +10:00
Cyber Oliveira
04b2c1e08c fix: sort_by "extension" falls back to name (#2306)
* fix(extension/sorter): fallbacks to C.name when both exts are the same or nil

* fix(nil): files with no extension

* fix(nil): files with no extension

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-07-09 13:12:49 +10:00
Cyber Oliveira
3d2fd90b28 feat: add sort_by "filetype" (#2302)
* feat: adds new type of sorting based on vim's filetype detection

* fix(ft/sorter): fallbacks to C.name when both ft's are nil or equal

* feat: adds new type of sorting based on vim's filetype detection

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-07-09 12:31:06 +10:00
Alexander Courtis
4af572246c fix(#1989): cut/paste over open buffer (#2279)
* fix(#1989): cut/paste overwrite deletes destination buffer

* fix(#1989): cut/paste overwrite deletes destination buffer
2023-07-02 17:13:50 +10:00
Alexander Courtis
d17389ce53 fix(#2301): do not show git status on grouped dirs when show_on_open_dirs (#2303) 2023-07-02 16:00:27 +10:00
Alexander Courtis
1fe32286db fix(#2293): remove unnecessary git status during find file (#2294) 2023-07-01 11:28:20 +10:00
Asror
3cc698b35b feat(#2270): add notify.absolute_path - show file or absolute path (default) names with notifications (#2286)
Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-06-25 13:06:21 +10:00
Alexander Courtis
7aff29d755 feat(#2277): skip overwrite prompt when copy/cut paste into same directory (#2278) 2023-06-25 11:37:55 +10:00
Svetlozar Iliev
c3c6544ee0 feat(event): add WillCreateFile, WillRemoveFile (#2273)
node. These are mostly going to be useful for implementing lsp file
operation actions.

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-06-19 10:00:55 +10:00
Alexander Courtis
85ece277bc docs: clarify need for clean room replicator on bug report form 2023-06-19 09:57:06 +10:00
Alexander Courtis
bdceaf5096 feat(#1804): add api.marks.bulk.delete with default bd mapping (#2276) 2023-06-18 14:09:11 +10:00
Alexander Courtis
d4f6d33496 fix(#2240): disable watchers following EMFILE (#2268)
* fix(#2240): disable watchers following EMFILE

* fix(#2240): disable watchers following EMFILE
2023-06-18 11:42:48 +10:00
Azad
f873625d06 fix: focus visible parent on collapse all (#2261)
Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-06-12 16:05:06 +10:00
Anton Kriese
f5804ce94e fix: fs_watcher not started for .git folders (windows) (#2265)
This fixes the issue described in #2243, where the .git folder's path
ends on a weird character produced by `cygpath` when the newline
character is fed into it (when using shell=powershell)
2023-06-12 15:44:52 +10:00
Felix Kästner
e0c7eb5044 docs: fix typo in netrw section (#2264) 2023-06-11 16:54:31 +02:00
Alexander Courtis
034511714b fix(#1545): dispatch Event.Resize on all window resizes, requires nvim 0.9+ (#2238) 2023-06-10 16:58:29 +10:00
Steven Stallion
8d82c4dbe1 feat: support vim.diagnostic.is_disabled() (#2232)
Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-06-10 16:10:28 +10:00
Azad
576d4c1b03 refactor: move reload function into utils module (#2247)
* refactor: move `reload` function into `utils` module

* docs: add annotations to `utils.focus_node_or_parent`

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-06-10 16:02:02 +10:00
Sander
f5d970d450 docs: reformat quickstart readme example (#2258) 2023-06-05 09:10:57 +10:00
Alexander Courtis
63061404f4 feat(#1837): add git.disable_for_dirs (#2239)
* feat(##1837): add git.disabled_dirs

* feat(#1837): add git.disable_for_dirs

* feat(#1837): note disable_for_dirs evaluation
2023-06-04 14:29:04 +10:00
Alexander Courtis
73ab6651db docs: add on_attach to quickstart (#2256) 2023-06-04 14:09:49 +10:00
Alexander Courtis
58d1014324 docs: add on_attach to quickstart (#2236) 2023-06-04 14:02:32 +10:00
Alexander Courtis
e2a4c9d09d docs: clarify git icon positions (#2235) 2023-05-27 16:23:33 +10:00
Alexander Courtis
164eb10cbd fix(#2104): remove experimental.git.async, always used (#2234) 2023-05-27 15:11:29 +10:00
Azad
d5d6950a0d fix(#1785): retain focused node on filter toggles (#2202)
* feat(live-filter): focus selected node after clear

* fix(#1785): retain focused node on filter toggle

* fix(#1785): apply requested changes

* fix(live-filter): focus last focused node when cleared on prompt

* refactor: store last focused node in `view` module

* refactor: store last focused node in view module

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-05-27 13:47:36 +10:00
Alexander Courtis
9ef6c3cd88 docs: add nvim-tree-netrw (#2222)
* docs: add nvim-tree-netrw

* docs: add nvim-tree-netrw
2023-05-24 12:09:23 +10:00
Alexander Courtis
b1e074d2b5 feat: add winid parameter for api.tree.open, toggle, find_file (#2213)
* feat: add winid parameter for api.tree.open, toggle, find_file

* feat: add winid parameter for api.tree.open, toggle, find_file
2023-05-21 17:37:22 +10:00
DoctorKnowsBetter
736c7ff590 feat: add NvimTreeSymlinkIcon (#2198)
* #2193 Add the ability to change the icon color for "symlink_formatted"

* Remove defaults and update doc #2198

---------

Co-authored-by: DoctorKnowsBetter <you@example.com>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-05-15 10:54:36 +10:00
Alexander Courtis
b6574056b5 fix(#2191): edit: disregard floating windows (#2212) 2023-05-15 10:38:11 +10:00
Alexander Courtis
89816ace70 fix(#2191): edit: disregard floating windows (#2209) 2023-05-14 11:30:01 +10:00
MoonFruit
498e8793bb fix: nerd font 3 bookmark icon (#2203) 2023-05-13 15:21:08 +10:00
kang
270c95556c fix: type annotations for utils.is_nvim_tree_buf (#2180)
Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-05-06 15:37:12 +10:00
James Tan
98b76ff0a2 feat: add NvimTreeNormalFloat (#2167)
* feat: add `NormalFloat` hl for floating windows

* fix: follow default floatwin bg color

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-05-06 15:33:17 +10:00
dependabot[bot]
53295de04d chore(deps): bump JohnnyMorganz/stylua-action from 2 to 3 (#2181) 2023-05-03 13:06:40 +02:00
gegoune
0df384b6c0 feat(api): add node.open.drop() (#2164)
Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-04-30 09:35:25 +02:00
Alexander Courtis
9c60947926 fix(#2175): check number of actions.open_file.window_picker.chars before picking window (#2177) 2023-04-30 15:56:46 +10:00
Zhanibek Adilbekov
d8b154c5f0 fix(#2154): find_file doesn't work when group_empty option is enabled (#2100)
Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-04-29 17:03:20 +10:00
Alexander Courtis
74996b8626 docs: clarify on_attach example, quick start (#2165)
* doc: more on_attach examples

* doc: human readable mappings help

* doc: human readable mappings help

* doc: more on_attach examples

* doc: enhance quick start

* doc: enhance quick start

* doc: enhance quick start

* doc: enhance quick start

* doc: enhance quick start

* doc: enhance quick start
2023-04-29 17:00:40 +10:00
Alexander Courtis
1f5bbc1efd Revert "fix(#1976): support non-standard $GIT_DIR (#2012)"
This reverts commit 517dee64c1.
2023-04-29 16:42:51 +10:00
Alexander Courtis
bb375fb203 build: explicit lua lsp settings (#2166) 2023-04-24 13:03:59 +10:00
baahrens
061a05bfd9 fix(#2132): focus file after rename and paste (#2151)
* feat: Focus file after creation

* feat: Focus file after pasting

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-04-23 13:59:16 +10:00
Alexander Courtis
71e1c5bdc2 test: disable netrw for clean room (#2163) 2023-04-23 13:33:13 +10:00
Alexander Courtis
13c714681f fix(#2157): use stdpath cache for nvim-tree-on-attach.lua (#2159)
* fix(#2157): use stdpath cache for my_on_attach.lua

* fix(#2157): my_on_attach.lua -> nvim-tree-on-attach.lua
2023-04-23 13:09:49 +10:00
hinell
d68b00a63e feat(api): Add new node selection action based on tab :drop command (#2161)
Co-authored-by: gegoune <dev@clog.rocks>
2023-04-23 02:41:21 +03:00
Alexander Courtis
967865cdb7 doc: update matrix link 2023-04-22 16:35:02 +10:00
Damien Mehala
517dee64c1 fix(#1976): support non-standard $GIT_DIR (#2012)
* Improve $GIT_DIR handling

- Retrieve $GIT_DIR using `git rev-parse --absolute-git-dir`
- Move $GIT_DIR ignore in the project exploration part

Resolves #1976

* Code review

- move norm_path to utils.lua
- fix comment #2012

* add comments for utils.norm_path

* get_git_directory use norm_path from utils

* watcher improvements

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-04-22 13:26:43 +10:00
Alexander Courtis
f8bb6b4c76 feat(#1974): enable experimental.git.async by default, see https://github.com/nvim-tree/nvim-tree.lua/issues/2104 (#2158) 2023-04-22 13:22:30 +10:00
Alexander Courtis
0db85a7024 doc: add test case requirement to CONTRIBUTING 2023-04-18 17:11:24 +10:00
Alexander Courtis
a774fa186c feat(api): add tree.is_visible, tree.is_tree_buf (#2150)
* feat(api): add api.tree.is_visible

* feat(api): add api.tree.is_tree_buf
2023-04-18 16:51:57 +10:00
Alexander Courtis
5b5373254f fix: help window header is minimum width (#2149)
* help: window header is minimum width

* help: window header is minimum width
2023-04-18 15:30:44 +10:00
Alexander Courtis
e99616bebe doc: tidy quick start, enhance on_attach 2023-04-18 14:38:57 +10:00
Alexander Courtis
5cc18d4f2f update hero shots 2023-04-18 13:24:22 +10:00
Alexander Courtis
5aa318c159 feat: deprecate view.mappings, see https://github.com/nvim-tree/nvim-tree.lua/wiki/Migrating-To-on_attach (#2143) 2023-04-18 10:17:22 +10:00
Anshuman Medhi
8f392fa763 fix(#2126): custom window pickers may create windows (#2140)
call nvim_list_wins again after the picker is run
2023-04-16 15:58:10 +10:00
John Fred Fadrigalan
a8a4834e1a fix(#2139): API functions not passing arguments (#2141) 2023-04-16 09:27:11 +10:00
Alexander Courtis
6ad5c26f4d feat(#2079): sort_by may return predefined sort (#2123)
* feat(#2079): prefactor

* feat(#2079): sort_by may return a predefined string
2023-04-15 15:53:40 +10:00
Alexander Courtis
56cdb5827d fix(#1950): disable most API until nvim-tree setup has been called (#2125)
* fix(#1950): disable most API until nvim-tree setup has been called

* fix(#1950): disable most API until nvim-tree setup has been called
2023-04-15 15:52:07 +10:00
Eden Lentz
f3dbddf8b3 feat(renderer): add NvimTreeSymlinkFolderName (#2000)
* Added FolderSymlink color that is applied in builder.lua

* changed highlight names and links, changed folder build function

* remove NvimTreeSymlinkFolderName

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-04-15 15:24:50 +10:00
Kasper Kondzielski
68f485b454 fix(#1697): remove notify plugin auto-detection (#2135)
* feat: Remove nvim-notify auto-detection

* feat: Remove nvim-notify auto-detection: stylua

---------

Co-authored-by: ghostbuster91 <ghostbuster91@users.noreply.github.com>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-04-15 15:18:05 +10:00
Alexander Courtis
0a144ba50a fix(#2109): floating help window (#2120)
* fix(#2109): floating help window

* fix(#2109): floating help window

* fix(#2109): floating help window

* fix(#2109): floating help window

* fix(#2109): floating help window

* fix(#2109): floating help window

* fix(#2109): floating help window

* fix(#2109): floating help window

* help float no border

Co-authored-by: gegoune <69750637+gegoune@users.noreply.github.com>

* Update lua/nvim-tree/help.lua

Co-authored-by: gegoune <69750637+gegoune@users.noreply.github.com>

* Update lua/nvim-tree/help.lua

Co-authored-by: gegoune <69750637+gegoune@users.noreply.github.com>

* Update lua/nvim-tree/help.lua

Co-authored-by: gegoune <69750637+gegoune@users.noreply.github.com>

* Update lua/nvim-tree/help.lua

Co-authored-by: gegoune <69750637+gegoune@users.noreply.github.com>

* fix(#2109): floating help window

* fix(#2109): floating help window

---------

Co-authored-by: gegoune <69750637+gegoune@users.noreply.github.com>
2023-04-15 14:59:30 +10:00
Alexander Courtis
b601b5aa25 fix(#2133): harden hide_root_folder deprecation (#2134) 2023-04-12 16:33:14 +10:00
Alexander Courtis
e16083cb01 deprecated(#1808): hide_root_folder (#2124) 2023-04-12 16:02:48 +10:00
Alexander Courtis
48d53a5934 feat(#1669): remove deprecated open_on_setup mechanisms (#2122) 2023-04-11 14:53:27 +10:00
Alexander Courtis
920d5c8f7e fix(#2114): remove deprecated non-API from lib, events (#2121) 2023-04-11 12:11:38 +10:00
Alexander Courtis
086bf310bd docs: complete API (#2115)
* doc: correct api.tree.expand_all

* doc: add Event.WillRenameNode

* doc: api git, events, live_filter, marks

* doc: api fs

* doc: api tidy

* doc: api index

* doc: api tidy

* doc: api index
2023-04-09 14:11:03 +10:00
Alexander Courtis
d1410cb089 docs: :help for api.node (#2106) 2023-04-04 16:32:32 +10:00
dependabot[bot]
94e3b09900 chore(deps): bump amannn/action-semantic-pull-request (#2107) 2023-04-03 13:18:42 +03:00
Alexander Courtis
0ef3d4613f feat(#1974): experimental.git.async see https://github.com/nvim-tree/nvim-tree.lua/issues/2104 (#2094)
* async git watcher reload; callback hell for now

* async git watcher reload; revert unnecessary extractions

* async git watcher reload; callback and non-callback functions are required for sync codepaths that loop

* async git watcher reload

* async git watcher reload

* feat(#1974): experimental.git.async

* feat(#1974): experimental.git.async
2023-04-03 16:20:52 +10:00
gegoune
7ad1c204c4 fix: api.node.open.preview should toggle directories (#2099) 2023-04-03 09:20:23 +03:00
Alexander Courtis
0c9bfe7225 feat(#2092): add api.node.navigate.open.next, prev (#2093)
* feat(#2092): add api.node.navigate.open.next, prev

* feat(#2092): add api.node.navigate.listed.next, prev

* feat(#2092): add api.node.navigate.opened.next, prev

* feat(#2092): add api.node.navigate.opened.next, prev
2023-04-03 14:23:03 +10:00
gegoune
7cd722ff3a ci: ensure PR subjects follow semantic commit spec (#2096) 2023-04-03 14:21:52 +10:00
Alexander Courtis
3e9509ec1b fix(#2088): actions change dir enable false does not update tree root (#2095)
* fix(#2088): update tree root when actions.change_dir disabled

* fix(#2088): update tree root when actions.change_dir disabled
2023-04-03 13:07:11 +10:00
Alexander Courtis
45400cd7e0 feat(api): add api.commands.get (#2083)
* feat(commands): add descriptions

* Update lua/nvim-tree.lua

Co-authored-by: gegoune <69750637+gegoune@users.noreply.github.com>

* feat(commands): add descriptions, extract to commands.lua

* feat(commands): add descriptions, add api.get_commands

* feat(commands): add descriptions, api.get_commands -> api.commands.get

---------

Co-authored-by: gegoune <69750637+gegoune@users.noreply.github.com>
2023-03-28 10:52:48 +11:00
Pholawat
a38f9a55a4 fix(#2086): "Rename: Omit Filename" not removing file name (#2087) 2023-03-27 10:52:09 +11:00
Alexander Courtis
6319ad9405 fix(#2078): :NvimTreeFindFile focuses tree (#2080) 2023-03-26 15:34:26 +11:00
Alexander Courtis
886d852f6e fix(#1858): deprecation warning for config.mappings.active and default (#2084) 2023-03-26 15:29:16 +11:00
Alexander Courtis
6515a1e1a9 fix(#2081): do not change root for inexistent files (#2082) 2023-03-26 15:05:41 +11:00
CsYakamoz
874b7be5d0 fix(diagnostics): coc obey diagnostics.severity (#2072)
`diagnostics.severity` option do not work with coc
2023-03-25 16:06:21 +11:00
EliasGill
31d8e24460 fix(#2057): update focused file on new terminal (#2060)
* Fixing #2057

* Update change-dir.lua

* Fixing error in force_dir_change
2023-03-25 14:50:22 +11:00
Zhizhen He
aa9971768a doc: spelling (#2070) 2023-03-21 16:39:45 +11:00
Gaétan Lepage
a50723e35f fix(doc): duplicate help tag in README (#2066) 2023-03-20 13:52:53 +02:00
Alexander Courtis
4f036342f1 feat(api): add api.config.mappings.get_keymap and get_keymap_default (#2056)
* feat(api): add api.config.mappings.get_keymap and get_keymap_default

* feat(api): add api.config.mappings.get_keymap and get_keymap_default
2023-03-20 11:23:45 +11:00
Alexander Courtis
1d79a64a88 doc: README installation points to wiki for plugin manager instructions (#2055) 2023-03-14 10:06:47 +11:00
Alexander Courtis
fe980baa94 feat(api): api.tree.find_file feature parity with open/toggle, convert all commands to API, document commands (#2039)
* fix(#1212): API find file feature parity

* fix(#2011): API find file feature parity

* fix(#2011): API find file feature parity

* fix(#2011): API find file feature parity

* fix(#2011): API find file feature parity

* fix(#2011): API find file feature parity

* fix(#2011): API find file feature parity

* fix(#2011): API find file feature parity
2023-03-13 16:53:20 +11:00
Alexander Courtis
f0a1c6ae2e fix(#1858): fire TreeAttachedPost event following on_attach call 2023-03-13 14:48:01 +11:00
gegoune
1830e5e8a4 feat(git): add TM git status (#2045)
* feat(git): add `TM` git status

Potential fix for #2043.

* fix: show as staged as well
2023-03-13 14:00:32 +11:00
Alexander Courtis
6e4b3b1868 doc: clarify setup() expense and purpose 2023-03-13 12:15:36 +11:00
Alexander Courtis
851ed88f29 doc: bug report points users to wiki for nvt-min.lua replication 2023-03-13 11:45:30 +11:00
Alexander Courtis
6001523c0a doc: bug report points users to wiki for nvt-min.lua replication (#2051) 2023-03-13 11:44:00 +11:00
Alexander Courtis
bbb6d48910 feat(api): add api.config.mappings.default_on_attach (#2037) 2023-03-06 10:45:58 +11:00
Alexander Courtis
1b453441f4 doc: case sensitive rename is not possible on macOS with case insensitive file systems 2023-03-04 15:02:01 +11:00
Jeremy Goh
6117582578 fix: typo on clipboard notify (#2032) 2023-03-04 13:51:09 +11:00
Alexander Courtis
362ecbeed6 fix(#2024): revert removal of deprecated nvim-tree.config nvim_tree_callback 2023-02-28 09:10:13 +11:00
Alexander Courtis
59bcb01d3b fix(#2024): help handles empty mapping description 2023-02-27 20:28:10 +11:00
Alexander Courtis
74959750f7 feat: automated migration from view.mappings.list to on_attach, see https://github.com/nvim-tree/nvim-tree.lua/wiki/Migrating-To-on_attach (#1579)
* chore(mappings): migrate legacy mappings under the hood

* chore(mappings): POC for help and :help on_attach keymaps

* chore(mappings): POC for help and :help on_attach keymaps

* chore(mappings): add desc to all mappings, show in help, reformat help

* chore(mappings): add desc to all mappings

* chore(mappings): add desc to all mappings

* chore(mappings): escape help keys

* chore(mappings): migrate legacy mappings under the hood: map keymap to legacy mappings

* chore(mappings): migrate legacy mappings under the hood: remove dispatch

* Revert "chore(mappings): migrate legacy mappings under the hood: remove dispatch"

This reverts commit f6f439ba59.

* chore(mappings): migrate legacy mappings under the hood: pass node to action_cb

* chore(mappings): migrate legacy mappings under the hood: remove dispatch

* chore(mappings): migrate legacy mappings under the hood: replace mappigns with keymaps in help

* chore(mappings): generate on_attach from user's legacy mappings

* chore(mappings): generate on_attach from user's legacy mappings

* chore(mappings): merge cleanup

* chore(mappings): use default mappings when on_attach not present, log legacy migration

* on_attach is default or user only, legacy and generation includes defaults (#1777)

* chore(mappings): remove mappings via vim.keymap.del instead of filtering mappings, to allow for multiple ways of specifying a key

* doc: specify that the terminal emulator must be configured to use the patched font

* feat(renderer): add NvimTreeOpenedFolderIcon NvimTreeClosedFolderIcon (#1768)

* feat: Add highlight group for opened folder

closes #1674

* docs: Add NvimTreeOpenedFolderIcon default

* feat: Add NvimTreeClosedFolderIcon highlight group

Defaults to NvimTreeFolderIcon

* feat: add diagnostics.show_on_open_dirs git.show_on_open_dirs (#1778)

* feat(diagnostics): only show diagnostic on closed folder

* feat(git): only show git icon on closed folder

* docs: Update feature_request.md (#1788)

* Update feature_request.md

Closes #1654

* Update feature_request.md

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

* 1786 git next prev land on dirs (#1787)

* Filtered dir with git status that are open when show_on_open_dir is false

* refactored for single source of truth of existence of git status on a node

Putting `has_git_status()` in `explorer.common` because that's where node.status is constructed
Or at least I think that's where it's constructed

* 1786 semantic nit

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

* fix(git): git rename not showing up for the renamed file (#1783)

* fixed git rename not showing up for the renamed file

* considered " -> " being a part of the filename

Fixed -> pattern to escape -
Fixed "\"" and "\\" in filename

* using string.find(, , true) to match plain ->

* Using -z and removed unnecessary logic

* feat(view): always enable cursorline, users may change this behaviour via Event.TreeOpen (#1814)

* Update view.lua

* set cursorline to true

* feat(event): dispatch Event.NodeRenamed on cut-paste (#1817)

* feat(view): add filters.git_clean, filters.no_buffer (#1784)

* feat(view): add filters.git_clean

* feat(view): add filters.git_clean

* feat(view): add filters.no_buffer

* feat(view): filters.no_buffer misses unloaded, handles buffer in/out

* feat(view): filters.no_buffer matches directories specifically

* feat(view): filters.no_buffer clarify targets

* feat: add placeholder filters.diagnostics_ok, refactor filters

* feat(view): remove placeholder filters.diagnostics_ok

* doc: consolidate and clarify :help examples

* doc: format help

* feat: paste and create always target closed folder, remove create_in_closed_folder (#1802)

* Fix default for file creation in closed directories

* Make paste in closed directories consistent with create

* doc: clarify create_in_closed_folder

* Remove create_in_closed_folder option

* doc: clarify create_in_closed_folder removal message (whoops)

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

* on_attach is user's or default, nothing else; legacy generated on_attach includes defaults

Co-authored-by: baahrens <bahrens@compeon.de>
Co-authored-by: Richard Li <38484873+chomosuke@users.noreply.github.com>
Co-authored-by: gegoune <69750637+gegoune@users.noreply.github.com>
Co-authored-by: rishabhjain9191 <rishabh.jain9191@gmail.com>
Co-authored-by: Anton <14187674+antosha417@users.noreply.github.com>
Co-authored-by: Eric Haynes <ehaynes99@gmail.com>

* on_attach_default hardcoded

* format default_on_attach

* source default on_attach directly

* remove human mappings help

* simplified on_attach generation

* simplified on_attach generation

* generate default on_attach

* generate default on_attach

* split out keymap_legacy

* add recently introduced mappings

* legacy api.config.mappings.active and default

* legacy api.config.mappings.active and default

* on_attach help and readme

* legacy generate handles action = ""

* legacy generate handles action =

* legacy generate gives defaults when no user mappings

* legacy generate handles action = ""

* legacy generate api handles overrides

* legacy generate handles subsequent setup, on_attach retains deep copies of legacy config

* add wiki link to generated on_attach

* add opts helper function for on_attach, prefixing 'nvim-tree: '

---------

Co-authored-by: kiyan <yazdani.kiyan@protonmail.com>
Co-authored-by: baahrens <bahrens@compeon.de>
Co-authored-by: Richard Li <38484873+chomosuke@users.noreply.github.com>
Co-authored-by: gegoune <69750637+gegoune@users.noreply.github.com>
Co-authored-by: rishabhjain9191 <rishabh.jain9191@gmail.com>
Co-authored-by: Anton <14187674+antosha417@users.noreply.github.com>
Co-authored-by: Eric Haynes <ehaynes99@gmail.com>
2023-02-27 14:19:50 +11:00
Alexander Courtis
9c97e6449b fix(#1961): stop unnecessary find file refreshes, avoid find file refresh cycles (#2010) 2023-02-21 10:34:01 +11:00
Kai Ting
66c15afd13 fix(#2004): relative path detection handles regex magic (#2005)
* fix(#2004): Change path_relative to use string find and substring to avoid using regex.

- This removed the original gsub with unintentional captures in path_to_matching_str
- The original regex based code captures create a limit where input path cannot
  have more than 32 special charactors ( `.`  , `_` or `-`)

* style nit

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-02-20 10:15:42 +11:00
Alexander Courtis
04f99f14b5 fix(#2003): obey user split command for modified buffers when hidden not set (#2008) 2023-02-20 10:04:55 +11:00
Alexander Courtis
bcb969c869 doc(#1997): clarify renderer.highlight_ and correct highlight group names (#2007) 2023-02-19 11:24:35 +11:00
Alexander Courtis
08a0aa1a3b Revert "fix(#1961): cycle detection on refresh, preventing infinite loop (#1996)"
This reverts commit 4222bb875d.
2023-02-15 09:24:12 +11:00
Alexander Courtis
4222bb875d fix(#1961): cycle detection on refresh, preventing infinite loop (#1996)
* #1961 diagnostic logging refresh_nodes_for_path

* #1961 add cycle detection to refresh_nodes_for_path

* #1961 escape special characters on when path matching during refresh

* #1961 escape special characters on when path matching during refresh
2023-02-14 15:08:41 +11:00
Alexander Courtis
8b8d457e07 fix(#1993): always fire TreeOpen event (#1994) 2023-02-13 14:57:01 +11:00
Alexander Courtis
ba1778e061 fix(#1923): handle empty git icons (#1987) 2023-02-13 10:32:02 +11:00
Alexander Courtis
36e29c3a95 fix(#1970): disable git integration after 5 timeouts (#1990)
* fix(#1970): additional log function gating for efficiency when not logging

* fix(#1970): additional log function gating for efficiency when not logging

* fix(#1970): disable git integration after 10 timeouts

* fix(#1970): disable git integration after 10 timeouts

* fix(#1970): disable git integration after 10 timeouts

* fix(#1970): cleanly kill timed out git processes

* fix(#1970): revert git kill, to be completed via #1974 experiment

* fix(#1970): revert git kill, to be completed via #1974 experiment
2023-02-12 14:28:48 +11:00
Alexander Courtis
b712b82b0c fix(#1961): harden profiling functions (#1986) 2023-02-11 17:05:01 +11:00
Alexander Courtis
02fdc262eb fix(#1970): additional log function gating for efficiency when not logging (#1971)
* fix(#1970): additional log function gating for efficiency when not logging

* fix(#1970): additional log function gating for efficiency when not logging
2023-02-06 10:23:20 +11:00
Alexander Courtis
59e65d88db doc: README: broadcast Open At Startup change 2023-02-05 12:39:14 +11:00
Alexander Courtis
7eb33d2a6d fix(#1831): remove instrumentation (#1969) 2023-02-04 16:57:05 +11:00
Alexander Courtis
e0166d1469 fix(#1831): remove instrumentation (#1968) 2023-02-04 16:54:36 +11:00
Alexander Courtis
8505b6ecd8 fix(#1923): handle empty git icons (#1952)
* 1923 skip empty git icons

* 1923 skip empty git icons
2023-02-04 16:27:27 +11:00
Alexander Courtis
215b29bfad feat(api): api.tree.open feature parity with api.tree.toggle (#1955) 2023-01-31 12:27:10 +11:00
Alexander Courtis
f3b73725c5 stylua: nit 2023-01-30 11:51:30 +11:00
Alexander Courtis
9fcd50d3e1 doc: clarify open/toggle defaults, more robust legacy argument handling 2023-01-30 11:49:21 +11:00
Alexander Courtis
fb775b3353 feat(view): deprecate open_on_setup.* in favour of https://github.com/nvim-tree/nvim-tree.lua/wiki/Open-At-Startup (#1951) 2023-01-30 10:10:41 +11:00
Adam Karim
e14989c0ea Remove what appears to have been a debug message. (#1949) 2023-01-28 12:51:28 +01:00
Alexander Courtis
8567841b87 fix(#1946): only change vim's global cwd on startup when opening the tree (#1947) 2023-01-28 14:48:05 +11:00
Peter van der Meulen
e05ed6a60f feat(view): add view.width.padding (#1941)
* fix: variable width accounts for sign/number columns

* Add dynamic sizing padding options

with https://github.com/neovim/neovim/pull/20621 merged in it is now
possible to fully customize the status-column in nvim (the column on the
left containing line-numbers, fold info, signs and borders).

A fair few cool implementations have popped up like:
- https://github.com/CKolkey/config/blob/master/nvim/after/plugin/statuscolumn.lua
- https://github.com/luukvbaal/statuscol.nvim
- and my own personal one (based on CKolkey's fantastic work) https://git.hendrikpeter.net/hendrikpeter/pico-vim/-/blob/main/lua/peva/status_column.lua

The problem with nvim-tree however is that dynamic sizing doesn't take
the custom size of a status column into account and the end of file
names get clipped off. This little patch should fix that (and give some
examples to help other status_column modders get started).

Thanks for looking at this and thanks for making this amazing plugin,
I've been using it for a while and I really like it!

* allow padding function, update docs, rollback readme

* typo in example setup

* help formatting

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-01-28 14:07:14 +11:00
tummetott
55028e30d7 fix(#1942): nvim-tree window options do not trigger OptionSet event (#1945) 2023-01-28 13:31:27 +11:00
Alexander Courtis
9e87ee2d6e fix(#1940): NvimTreeFindFileToggle focus tree 2023-01-26 10:38:47 +11:00
Joanjajas
7944e479c1 doc: fix incorrect tag (#1938) 2023-01-24 12:55:59 +11:00
Alexander Courtis
f1c2d6d372 feat(api): api.tree.open/toggle: add current_window option (#1935)
* feat(api): api.tree.open: add current_window option

* feat(api): api.tree.toggle: add current_window option

* feat(api): api.tree.toggle: add current_window option

* doc: api.tree.*

* doc: api.tree.*
2023-01-24 08:30:49 +11:00
yioneko
16f2806d59 fix: remove redundant file existence check in create file operation (#1936) 2023-01-23 15:12:45 +11:00
ramezgerges
96506fee49 feat(view): add view.width.min/max replacing adaptive_size, allowing upper bound (#1915)
* feat: max_width for adaptive_size

* view grow calculates size correctly based on sign column visibility

* limit width to a minimum of 20

* adaptive_size -> min/max table

* harden view size calculations against bad user input

* style

* add back an extra column of padding to adaptive resizing

* back out: limit width to a minimum of 20

* revert unnecessary change

* backout: view grow calculates size correctly based on sign column visibility

* remove adaptive_size from help

* backout unnecessary change M.View.config

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-01-21 15:21:18 +11:00
Alexander Courtis
13adc94e8e doc: clarify system_open, specifying defaults 2023-01-21 11:54:24 +11:00
Alexander Courtis
e8a89db1bb doc: move vinegar-style to wiki 2023-01-17 12:02:42 +11:00
Alexander Courtis
1b13a49f91 fix(#1916): suppress EPERM watcher failures on windows (#1919) 2023-01-16 13:00:57 +11:00
Alexander Courtis
1f0fc8d6e8 feat(event): add au, global: NvimTreeRequired, NvimTreeSetup (#1912)
* feat(even): add autocommands NvimTreeRequired, NvimTreeSetup

* feat(event): add vim.g.NvimTreeRequired, vim.g.NvimTreeSetup
2023-01-15 10:12:50 +11:00
gegoune
3ce0a8e770 feat(git): support DA state, fix(#1822): test directory capable of watching before presenting it (#1905)
* fix(#1822): test directory capable of watching before presenting it (#1901)

* feat(git): support `DA` state

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-01-10 13:32:53 +11:00
Telman Babayev
ccb6d8a518 feat: add ui.confirm.remove and ui.confirm.trash, deprecate trash.require_confirm (#1887)
* Implement turning off y/n prompt for file deletion

* Refactor different prompt configs to single ui table

* Remove unused fields

* add ui.confirm doc, format/tidy

* trash.require_confirm -> ui.confirm.trash

* Fix nil value trash field

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-01-08 17:43:01 +11:00
gegoune
5b554a9e2d ci: fix release-please workflow (#1899) 2023-01-08 02:38:01 +01:00
dependabot[bot]
3a42468a58 chore(deps): bump JohnnyMorganz/stylua-action from 1 to 2 (#1897) 2023-01-08 02:16:23 +01:00
dependabot[bot]
f2ee30998e chore(deps): bump actions/checkout from 2 to 3 (#1898)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-07 11:31:28 +01:00
gegoune
92a0802b88 ci: add release-please workflow (#1892)
Closes https://github.com/nvim-tree/nvim-tree.lua/issues/1881
2023-01-07 11:29:21 +01:00
Alexander Courtis
f43f3110a5 feat(event): add TreeAttachedPost (#1877)
* feat(event): add OnAttachPost

* feat(event): add TreeAttachPost

* feat(event): add TreeAttachPost

* feat(event): TreeAttachedPost fired after all mappings created, not just on_attach
2023-01-07 11:50:41 +11:00
Alexander Courtis
bac962caf4 feat(api): add api.config.mappings.active, api.config.mappings.default (#1876)
* feat(api): add config.mappings.current and config.mappings.default

* feat(api): add config.mappings.current and config.mappings.default

* feat(api): add config.mappings.current and config.mappings.default
2023-01-03 13:13:49 +11:00
Richard Li
cdbd7daf29 fix(#1878): nvim frozen on no name buffer when modified.enable (#1879) 2023-01-03 13:11:14 +11:00
Richard Li
951b6e7e55 fix(#1836): add view.debounce_delay (#1871)
* fix(#1836):add view.debounce_delay to avoid some unnecessary explorer reloads

* fixed BufReadPost & BufUnload nil pointer

* update_focused_file.debouce_delay to view.debounce_delay

* changed docs to be more accurate

* added debounce on modified update

* Using same event for filter buffer

* removed unused View.debounce_delay

* changed docs to be more accurate

Co-authored-by: doot <gugegby@gmail.com>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-01-02 07:32:56 +11:00
Alexander Courtis
e322fbb80b chore: remove diagnostic suppressions after tidying nits 2023-01-01 13:27:05 +11:00
Alexander Courtis
9d27c9ebea doc: add recipes and tips links to API 2023-01-01 08:32:18 +11:00
Alexander Courtis
013b4982d9 doc: add recipes and tips links to API 2023-01-01 08:31:47 +11:00
Richard Li
dcc344cc72 feat(view): indicate modified buffers (#1835)
* Outlined new options

* highlight_modified is highlight_opened_files

* prototype with autocmd

* moved modified into glyphs

* show_on_dirs and show_on_open_dirs

* icon placement before & after

* _get_filename_offset

* fixed :wq doesn't update modified indicator

* highlight_modified, signcolumn modified_placement

Refactored to make everything use HighlightedString to remove all the complex `insert_highlight` calculation.
Not tested.

* updated doc to match the reality of no multi char for glyphs.modified

* fixed git signcolumn doesn't show

* fixed highlight_modified gets replaced by highlight_opened_files

* fixed renderer.icons.show.modified = false crash

* updated doc to reflect empty icon not breaking rendering

* removed debounce_delay to implement in a later PR

* doc nit: order placement

* change modified dirs default to be consistent with git

* illegal git & modified placement changed to default

* don't assume icon exist

* nit remove comment

* Noted in doc that glyphs can't have more than 2 characters if in signcolumn

* Don't sign_define if placement isn't signcolumn

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-12-31 15:54:04 +11:00
youkwhd
9ad93b6ac0 feat(view): add view.cursorline (#1859)
* feat(#1814): added cursorline config to DEFAULT_OPTS

Extends #1814

Currently, the config cursorline is set to `true` by default.

This behaviour can only be changed by a hacky way of listening to an event, as @alex-courtis mentioned that: "The user can change this default if they want via event".

This PR generalizes the configuration to be easier to config via the function `setup()`.

* doc: add cursorline

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-12-31 15:27:31 +11:00
Alexander Courtis
3c4958ab3d fix(#1831): remove windows executable functionality due to occasional vim freeze and performance concerns (#1868)
* #1831 exploratory testing: disable file executable checks

* fix(#1831): remove windows executable functionality
2022-12-31 12:34:55 +11:00
Alexander Courtis
9e4c39572f fix(#1833): do not find file when view is not visible on the current tab (#1845) 2022-12-23 12:47:39 +11:00
Alexander Courtis
fee6801393 fix(diagnostics): do not show on file/dir with same prefix 2022-12-23 12:44:30 +11:00
Richard Li
3000797e53 fix(diagnostics): do not show on file/dir with same prefix (#1832)
* fix diagnostics showing up on file/dir with same prefix

* using fnamemodify instead of gsub
2022-12-23 12:36:23 +11:00
Alexander Courtis
e14c2895b4 fix(#1841): do not refresh on buffer events when not a file buffer (#1843)
> Haven't had time to dig into #1841

No worries at all; there's no hurry.
2022-12-19 17:18:27 +11:00
Alexander Courtis
4fc74ca321 fix(#1841): do not refresh on buffer events when highlight_opened_files is none 2022-12-19 15:45:07 +11:00
Alexander Courtis
547db6e929 fix(#1831): remove error messages that were previously unreachable and add no value 2022-12-19 15:11:12 +11:00
Alexander Courtis
d949af7245 fix(#1804): do not refresh watched nodes that have been destroyed (deleted) 2022-12-19 14:36:42 +11:00
Ross Wilson
e0cfbbb93d fix(copy-paste): fix message on clipboard clear (#1838)
* Clear clipboard function was calling utils to
  notify when the clipboard was cleared
* This produced a nil value error
* Replaced with notify
2022-12-19 09:59:38 +11:00
Richard Li
29788cc32a fix(git): git folder fixes and improvements (#1809)
* coding style

* outlined git.show_on_open_dirs behavior

* show some icon on opendir even if show_on_open_dir=false

and show all children's status on parent

* fixed renamed icon not showing

* sorted icons

* removed DU from deleted as file will show up in tree

* fixed update_git_status in reloaders not tested

* fixed Api.git.reload()

Tested update_git_status in reloaders.lua

* sort icon only if not git signcolumn

* fixed crashing when root dir isn't git dir

* made git.show_on_dirs doc more concise

* git_statuses -> git_status for consistency

* explorer/common.lua -> explorer/node.lua

* fixed #1784 conflict

* don't order icons

* Revert "don't order icons"

This reverts commit 23f6276ef7.
2022-12-17 17:05:33 +11:00
Alexander Courtis
89c79cb33b fix(#1831): improve fs_scandir error handling, add profiling 2022-12-17 16:59:09 +11:00
Alexander Courtis
87409bb4af fix(#1815): don't schedule find_file calls, debounce update_focused_file with 15ms default (#1828)
* Revert "Revert "fix(#1815): don't schedule find_file calls, debounce update_focused_file with 15ms default (#1820)""

This reverts commit a8d26bb088.

* fix(#1723): find_file for externally created new file results in folder unable to be opened

* fix(#1723): find_file for externally created new file results in folder unable to be opened
2022-12-16 15:35:09 +11:00
David Sierra DiazGranados
d85b6718ce feat(picker): allow custom function actions.open_file.window_picker.picker (#1782)
* feat: allow passing a custom function as a window picker

WIP

* fix: move logic expression to if statement

If `M.window_picker.custom_function()` returns `nil` then `pick_win_id()`
will run (the or part). We don't want that. More verbose, but better.

* feat(open): add window_picker.picker

* feat(open): add window_picker.picker

* style nit

* feat(open): add window_picker.picker

* docs: add window_picker.picker documentation

* docs: add window_picker.picker documentation

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-12-16 15:14:33 +11:00
Richard Li
18272f8df3 fix(view): refresh opened files highlight on buffer read, unload (#1827) 2022-12-16 14:36:00 +11:00
Anton
e8ea62c198 fix(#1824): Don't modify jumplist when edit_in_place. (#1825) 2022-12-16 14:08:27 +11:00
Alexander Courtis
cdb40dc42e neovim requirement 0.7.0 -> 0.8.0, remove WinSeparator/VertSplit compatibility shims 2022-12-16 13:45:16 +11:00
131 changed files with 13677 additions and 6401 deletions

View File

@@ -4,6 +4,21 @@ root = true
insert_final_newline = true insert_final_newline = true
end_of_line = lf end_of_line = lf
[nvim-tree-lua.txt]
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
indent_size = 2 indent_size = 2
# EmmyLuaCodeStyle specific, see
# https://github.com/CppCXY/EmmyLuaCodeStyle/blob/master/lua.template.editorconfig
continuation_indent = 2
quote_style = double
call_arg_parentheses = always
space_before_closure_open_parenthesis = false
align_continuous_similar_call_args = true

View File

@@ -13,6 +13,8 @@ body:
* ensure that nvim-tree is updated to the latest version * ensure that nvim-tree is updated to the latest version
If you are experiencing performance issues, please [enable profiling](https://github.com/nvim-tree/nvim-tree.lua#performance-issues) and attach the logs. If you are experiencing performance issues, please [enable profiling](https://github.com/nvim-tree/nvim-tree.lua#performance-issues) and attach the logs.
Please note that nvim-tree team members do not have access to nor expertise with Windows. You will need to be an active participant during resolution.
- type: textarea - type: textarea
attributes: attributes:
label: "Description" label: "Description"
@@ -36,6 +38,12 @@ body:
placeholder: "Linux 5.16.11-arch1-1, macOS 11.5, Windows 10" placeholder: "Linux 5.16.11-arch1-1, macOS 11.5, Windows 10"
validations: validations:
required: true required: true
- type: input
attributes:
label: "Windows variant"
placeholder: "WSL, PowerShell, cygwin, msys"
validations:
required: false
- type: input - type: input
attributes: attributes:
label: "nvim-tree version" label: "nvim-tree version"
@@ -46,14 +54,14 @@ body:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: "Minimal config" label: "Clean room replication"
description: "Minimal(!) configuration necessary to reproduce the issue. description: "Minimal(!) configuration necessary to reproduce the issue.
(Right click) save [nvt-min.lua](https://raw.githubusercontent.com/nvim-tree/nvim-tree.lua/master/.github/ISSUE_TEMPLATE/nvt-min.lua) to `/tmp` and run using `nvim -nu /tmp/nvt-min.lua` If not provided it is very unlikely that the nvim-tree team will be able to address your issue.
If _absolutely_ necessary, add plugins and modify the nvim-tree setup at the indicated lines. See [wiki: Clean Room Replication](https://github.com/nvim-tree/nvim-tree.lua/wiki/Troubleshooting#clean-room-replication) for instructions and paste the contents of your `/tmp/nvt-min.lua` here.
Paste the contents of your `/tmp/nvt-min.lua` here." Please do NOT post a configuration that uses other plugin managers such as lazy, see [wiki: Lazy Loading](https://github.com/nvim-tree/nvim-tree.lua/wiki/Installation#lazy-loading)"
render: lua render: lua
validations: validations:
required: true required: true

View File

@@ -1,9 +1,12 @@
vim.cmd [[set runtimepath=$VIMRUNTIME]] vim.g.loaded_netrw = 1
vim.cmd [[set packpath=/tmp/nvt-min/site]] vim.g.loaded_netrwPlugin = 1
vim.cmd([[set runtimepath=$VIMRUNTIME]])
vim.cmd([[set packpath=/tmp/nvt-min/site]])
local package_root = "/tmp/nvt-min/site/pack" local package_root = "/tmp/nvt-min/site/pack"
local install_path = package_root .. "/packer/start/packer.nvim" local install_path = package_root .. "/packer/start/packer.nvim"
local function load_plugins() local function load_plugins()
require("packer").startup { require("packer").startup({
{ {
"wbthomason/packer.nvim", "wbthomason/packer.nvim",
"nvim-tree/nvim-tree.lua", "nvim-tree/nvim-tree.lua",
@@ -15,20 +18,34 @@ local function load_plugins()
compile_path = install_path .. "/plugin/packer_compiled.lua", compile_path = install_path .. "/plugin/packer_compiled.lua",
display = { non_interactive = true }, display = { non_interactive = true },
}, },
} })
end end
if vim.fn.isdirectory(install_path) == 0 then if vim.fn.isdirectory(install_path) == 0 then
print "Installing nvim-tree and dependencies." print("Installing nvim-tree and dependencies.")
vim.fn.system { "git", "clone", "--depth=1", "https://github.com/wbthomason/packer.nvim", install_path } vim.fn.system({ "git", "clone", "--depth=1", "https://github.com/wbthomason/packer.nvim", install_path })
end end
load_plugins() load_plugins()
require("packer").sync() require("packer").sync()
vim.cmd [[autocmd User PackerComplete ++once echo "Ready!" | lua setup()]] vim.cmd([[autocmd User PackerComplete ++once echo "Ready!" | lua setup()]])
vim.opt.termguicolors = true vim.opt.termguicolors = true
vim.opt.cursorline = true vim.opt.cursorline = true
-- MODIFY NVIM-TREE SETTINGS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE -- MODIFY NVIM-TREE SETTINGS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE
_G.setup = function() _G.setup = function()
require("nvim-tree").setup {} require("nvim-tree").setup({})
end end
-- UNCOMMENT this block for diagnostics issues, substituting pattern and cmd as appropriate.
-- Requires diagnostics.enable = true in setup.
--[[
vim.api.nvim_create_autocmd("FileType", {
pattern = "lua",
callback = function()
vim.lsp.start {
name = "my-luals",
cmd = { "lua-language-server" },
root_dir = vim.loop.cwd(),
}
end,
})
]]

8
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
reviewers:
- "gegoune"

View File

@@ -2,34 +2,78 @@ name: CI
on: on:
pull_request: pull_request:
branches:
- '*'
push: push:
branches: branches: [master]
- master workflow_dispatch:
permissions:
contents: read
jobs: jobs:
luacheck: lint:
name: luacheck
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Prepare concurrency:
run: | group: ${{ github.workflow }}-${{ matrix.lua_version }}-${{ github.head_ref || github.ref_name }}
sudo apt-get update cancel-in-progress: true
sudo add-apt-repository universe
sudo apt install luarocks -y strategy:
sudo luarocks install luacheck matrix:
- name: Run luacheck lua_version: [ 5.1 ]
run: luacheck . luacheck_version: [ 1.2.0 ]
stylua:
name: stylua
runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - name: checkout
- uses: JohnnyMorganz/stylua-action@v1 uses: actions/checkout@v4
- name: install lua ${{ matrix.lua_version }}
uses: leafo/gh-actions-lua@v11
with: with:
token: ${{ secrets.GITHUB_TOKEN }} luaVersion: ${{ matrix.lua_version }}
version: 0.15.1
args: --color always --check lua/ - name: install luarocks
uses: leafo/gh-actions-luarocks@v5
- name: install luacheck ${{ matrix.luacheck_version }}
run: luarocks install luacheck ${{ matrix.luacheck_version }}
- run: make lint
check:
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ matrix.nvim_version }}-${{ matrix.luals_version }}-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true
strategy:
matrix:
nvim_version: [ stable, nightly ]
luals_version: [ 3.15.0 ]
env:
VIMRUNTIME: /home/runner/nvim-${{ matrix.nvim_version }}/share/nvim/runtime
steps:
- name: checkout
uses: actions/checkout@v4
- name: install nvim ${{ matrix.nvim_version }}
uses: rhysd/action-setup-vim@v1
with:
neovim: true
version: ${{ matrix.nvim_version }}
- name: install lua-language-server ${{ matrix.luals_version }}
run: |
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
echo "luals/bin" >> "$GITHUB_PATH"
- run: make check
- run: make help-check
- run: make style
- run: make style-doc

39
.github/workflows/luarocks-release.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
name: Luarocks Release
on:
push:
tags:
- v*
workflow_dispatch:
jobs:
luarocks-upload:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: LuaRocks Upload
uses: nvim-neorocks/luarocks-tag-release@v7
env:
LUAROCKS_API_KEY: ${{ secrets.LUAROCKS_API_KEY }}
with:
summary: A File Explorer For Neovim
detailed_description: |
Automatic updates
File type icons
Git integration
Diagnostics integration - LSP and COC
(Live) filtering
Cut, copy, paste, rename, delete, create etc.
Highly customisable
Rich API
license: "GPL-3.0"
labels: neovim
dependencies: |
nvim-web-devicons

37
.github/workflows/release-please.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
on:
push:
branches:
- master
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true
name: release-please
permissions:
contents: write
pull-requests: write
jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: google-github-actions/release-please-action@v4
id: release
- uses: actions/checkout@v4
- name: tag major and minor versions
if: ${{ steps.release.outputs.release_created }}
run: |
git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
git remote add gh-token "https://${{ secrets.GITHUB_TOKEN }}@github.com/google-github-actions/release-please-action.git"
git tag -d v${{ steps.release.outputs.major }} || true
git tag -d v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} || true
git tag -d v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}.${{ steps.release.outputs.patch }} || true
git push origin :v${{ steps.release.outputs.major }} || true
git push origin :v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} || true
git push origin :v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}.${{ steps.release.outputs.patch }} || true
git tag -a v${{ steps.release.outputs.major }} -m "Release v${{ steps.release.outputs.major }}"
git tag -a v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} -m "Release v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}"
git tag -a v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}.${{ steps.release.outputs.patch }} -m "Release v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}.${{ steps.release.outputs.patch }}"
git push origin v${{ steps.release.outputs.major }}
git push origin v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}
git push origin v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}.${{ steps.release.outputs.patch }}

View File

@@ -0,0 +1,19 @@
name: Semantic Pull Request Subject
on:
pull_request:
types:
- opened
- reopened
- edited
- synchronize
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref }}
cancel-in-progress: true
jobs:
semantic-pr-subject:
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v5.5.3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/luals-out/
/luals/
# backup vim files
*~

View File

@@ -1,4 +1,3 @@
#!/usr/bin/env bash #!/usr/bin/env sh
stylua . --check || exit 1 make
luacheck . || exit 1

View File

@@ -1,14 +1,15 @@
-- vim: ft=lua tw=80 local M = {}
-- Don't report unused self arguments of methods. -- Don't report unused self arguments of methods.
self = false M.self = false
ignore = { M.ignore = {
"631", -- max_line_length "631", -- max_line_length
} }
-- Global objects defined by the C code -- Global objects defined by the C code
globals = { M.globals = {
"vim", "vim",
"TreeExplorer"
} }
return M

View File

@@ -1,19 +1,89 @@
{ {
"$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" : "Lua 5.1", "workspace": {
"diagnostics": { "library": [
"globals": [ "$VIMRUNTIME/lua/vim",
"vim" "${3rd}/luv/library"
], ]
"disable": [ },
"cast-local-type", "format": {
"lowercase-global", "defaultConfig": {
"missing-parameter", "indent_style": "space",
"missing-return", "max_line_length": "140",
"missing-return-value", "indent_size": "2",
"need-check-nil", "continuation_indent": "2",
"param-type-mismatch" "quote_style": "double",
] "call_arg_parentheses": "always",
} "space_before_closure_open_parenthesis": "false",
"align_continuous_similar_call_args": "true"
}
},
"diagnostics": {
"libraryFiles": "Disable",
"globals": [],
"neededFileStatus": {
"ambiguity-1": "Any",
"assign-type-mismatch": "Any",
"await-in-sync": "Any",
"cast-local-type": "Any",
"cast-type-mismatch": "Any",
"circle-doc-class": "Any",
"close-non-object": "Any",
"code-after-break": "Any",
"codestyle-check": "None",
"count-down-loop": "Any",
"deprecated": "Any",
"different-requires": "Any",
"discard-returns": "Any",
"doc-field-no-class": "Any",
"duplicate-doc-alias": "Any",
"duplicate-doc-field": "Any",
"duplicate-doc-param": "Any",
"duplicate-index": "Any",
"duplicate-set-field": "Any",
"empty-block": "Any",
"global-element": "Any",
"global-in-nil-env": "Any",
"incomplete-signature-doc": "Any",
"inject-field": "Any",
"invisible": "Any",
"lowercase-global": "Any",
"missing-fields": "Any",
"missing-global-doc": "Any",
"missing-local-export-doc": "Any",
"missing-parameter": "Any",
"missing-return": "Any",
"missing-return-value": "Any",
"name-style-check": "None",
"need-check-nil": "Any",
"newfield-call": "Any",
"newline-call": "Any",
"no-unknown": "None",
"not-yieldable": "Any",
"param-type-mismatch": "Any",
"redefined-local": "Any",
"redundant-parameter": "Any",
"redundant-return": "Any",
"redundant-return-value": "Any",
"redundant-value": "Any",
"return-type-mismatch": "Any",
"spell-check": "None",
"trailing-space": "Any",
"unbalanced-assignments": "Any",
"undefined-doc-class": "Any",
"undefined-doc-name": "Any",
"undefined-doc-param": "Any",
"undefined-env-child": "Any",
"undefined-field": "None",
"undefined-global": "Any",
"unknown-cast-variable": "Any",
"unknown-diag-code": "Any",
"unknown-operator": "Any",
"unreachable-code": "Any",
"unused-function": "Any",
"unused-label": "Any",
"unused-local": "Any",
"unused-vararg": "Any"
}
}
} }

View File

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

View File

@@ -1,6 +0,0 @@
column_width = 120
line_endings = "Unix"
indent_type = "Spaces"
indent_width = 2
quote_style = "AutoPreferDouble"
call_parentheses = "None"

351
CHANGELOG.md Normal file
View File

@@ -0,0 +1,351 @@
# Changelog
## [1.14.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.13.0...nvim-tree-v1.14.0) (2025-08-12)
### Features
* **#2685:** highlight git new tracked with NvimTreeGitFileNewHL ([#3176](https://github.com/nvim-tree/nvim-tree.lua/issues/3176)) ([0a52012](https://github.com/nvim-tree/nvim-tree.lua/commit/0a52012d611f3c1492b8d2aba363fabf734de91d))
* **#2789:** add optional function expand_until to api.tree.expand_all and api.node.expand ([#3166](https://github.com/nvim-tree/nvim-tree.lua/issues/3166)) ([1b876db](https://github.com/nvim-tree/nvim-tree.lua/commit/1b876db04903b93c78c97fd3f3dd85d59eeef5ff))
* **#2826:** allow only one window with nvim-tree buffer per tab ([#3174](https://github.com/nvim-tree/nvim-tree.lua/issues/3174)) ([dd2364d](https://github.com/nvim-tree/nvim-tree.lua/commit/dd2364d6802f7f57a98acb8b545ed484c6697626))
* **#3157:** add view.cursorlineopt ([#3158](https://github.com/nvim-tree/nvim-tree.lua/issues/3158)) ([8eb5e0b](https://github.com/nvim-tree/nvim-tree.lua/commit/8eb5e0bfd1c4da6efc03ab0c1ccf463dbaae831e))
### Bug Fixes
* **#3077:** deleting a directory containing symlinked directory will delete the contents of the linked directory ([#3168](https://github.com/nvim-tree/nvim-tree.lua/issues/3168)) ([10db694](https://github.com/nvim-tree/nvim-tree.lua/commit/10db6943cb40625941a35235eeb385ffdfbf827a))
* **#3157:** add view.cursorlineopt ([8eb5e0b](https://github.com/nvim-tree/nvim-tree.lua/commit/8eb5e0bfd1c4da6efc03ab0c1ccf463dbaae831e))
* **#3172:** live filter exception ([#3173](https://github.com/nvim-tree/nvim-tree.lua/issues/3173)) ([0a7fcdf](https://github.com/nvim-tree/nvim-tree.lua/commit/0a7fcdf3f8ba208f4260988a198c77ec11748339))
* invalid window id for popup info window ([#3147](https://github.com/nvim-tree/nvim-tree.lua/issues/3147)) ([d54a187](https://github.com/nvim-tree/nvim-tree.lua/commit/d54a1875a91e1a705795ea26074795210b92ce7f))
* **picker:** exclude full_name window id from the choice ([#3165](https://github.com/nvim-tree/nvim-tree.lua/issues/3165)) ([543ed3c](https://github.com/nvim-tree/nvim-tree.lua/commit/543ed3cac212dc3993ef9f042f6c0812e34ddd43))
* window picker ignore hidden window ([#3145](https://github.com/nvim-tree/nvim-tree.lua/issues/3145)) ([d87b41c](https://github.com/nvim-tree/nvim-tree.lua/commit/d87b41ca537e2131622d48a6c25ccf2fbe0e5d62))
### Performance Improvements
* **#3171:** cache toplevel for untracked ([#3185](https://github.com/nvim-tree/nvim-tree.lua/issues/3185)) ([4425136](https://github.com/nvim-tree/nvim-tree.lua/commit/442513648c6936e754c3308a1c58591a399493e5))
* **#3171:** use vim.system() instead of vim.fn.system() to execute git toplevel ([#3175](https://github.com/nvim-tree/nvim-tree.lua/issues/3175)) ([9a05b9e](https://github.com/nvim-tree/nvim-tree.lua/commit/9a05b9e9f928856ca23dbf876fab372003180c3f))
### Reverts
* **#3180, #3177:** invalid group or tabpage ([#3181](https://github.com/nvim-tree/nvim-tree.lua/issues/3181)) ([9b289ab](https://github.com/nvim-tree/nvim-tree.lua/commit/9b289abd6998e30fd24cbc9919e0b0cbed6364ce))
* **#3180, #3177:** resolve live filter failures ([#3183](https://github.com/nvim-tree/nvim-tree.lua/issues/3183)) ([a4699c0](https://github.com/nvim-tree/nvim-tree.lua/commit/a4699c0904103e7767334f6da05f5c2ea5514845))
## [1.13.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.12.0...nvim-tree-v1.13.0) (2025-06-14)
### 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)
### Features
* **api:** add node.open.vertical_no_picker, node.open.horizontal_no_picker ([#3031](https://github.com/nvim-tree/nvim-tree.lua/issues/3031)) ([68fc4c2](https://github.com/nvim-tree/nvim-tree.lua/commit/68fc4c20f5803444277022c681785c5edd11916d))
### Bug Fixes
* **#3015:** dynamic width no longer truncates on right_align icons ([#3022](https://github.com/nvim-tree/nvim-tree.lua/issues/3022)) ([f7b76cd](https://github.com/nvim-tree/nvim-tree.lua/commit/f7b76cd1a75615c8d6254fc58bedd2a7304eb7d8))
* **#3018:** error when focusing nvim-tree when in terminal mode ([#3019](https://github.com/nvim-tree/nvim-tree.lua/issues/3019)) ([db8d7ac](https://github.com/nvim-tree/nvim-tree.lua/commit/db8d7ac1f524fc6f808764b29fa695c51e014aa6))
* **#3041:** use vim.diagnostic.get for updating diagnostics ([#3042](https://github.com/nvim-tree/nvim-tree.lua/issues/3042)) ([aae0185](https://github.com/nvim-tree/nvim-tree.lua/commit/aae01853ddbd790d1efd6ff04ff96cf38c02c95f))
* Can't re-enter normal mode from terminal mode ([db8d7ac](https://github.com/nvim-tree/nvim-tree.lua/commit/db8d7ac1f524fc6f808764b29fa695c51e014aa6))
* hijack directory "BufEnter", "BufNewFile" events are nested ([#3044](https://github.com/nvim-tree/nvim-tree.lua/issues/3044)) ([39bc630](https://github.com/nvim-tree/nvim-tree.lua/commit/39bc63081605c1d4b974131ebecaea11e8a8595f))
* view.width functions may return strings ([#3020](https://github.com/nvim-tree/nvim-tree.lua/issues/3020)) ([6b4be1d](https://github.com/nvim-tree/nvim-tree.lua/commit/6b4be1dc0cd4d5d5b8e8b56b510a75016e99746f))
## [1.9.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.8.0...nvim-tree-v1.9.0) (2024-12-07)
### Features
* **#2948:** add custom decorators, :help nvim-tree-decorators ([#2996](https://github.com/nvim-tree/nvim-tree.lua/issues/2996)) ([7a4ff1a](https://github.com/nvim-tree/nvim-tree.lua/commit/7a4ff1a516fe92a5ed6b79d7ce31ea4d8f341a72))
### Bug Fixes
* **#2954:** more efficient LSP updates, increase diagnostics.debounce_delay from 50ms to 500ms ([#3007](https://github.com/nvim-tree/nvim-tree.lua/issues/3007)) ([1f3ffd6](https://github.com/nvim-tree/nvim-tree.lua/commit/1f3ffd6af145af2a4930a61c50f763264922c3fe))
* **#2990:** Do not check if buffer is buflisted in diagnostics.update() ([#2998](https://github.com/nvim-tree/nvim-tree.lua/issues/2998)) ([28eac28](https://github.com/nvim-tree/nvim-tree.lua/commit/28eac2801b201f301449e976d7a9e8cfde053ba3))
* **#3009:** nvim &lt; 0.10 apply view options locally ([#3010](https://github.com/nvim-tree/nvim-tree.lua/issues/3010)) ([ca7c4c3](https://github.com/nvim-tree/nvim-tree.lua/commit/ca7c4c33cac2ad66ec69d45e465379716ef0cc97))
* **api:** correct argument types in `wrap_node` and `wrap_node_or_nil` ([#3006](https://github.com/nvim-tree/nvim-tree.lua/issues/3006)) ([f7c65e1](https://github.com/nvim-tree/nvim-tree.lua/commit/f7c65e11d695a084ca10b93df659bb7e68b71f9f))
## [1.8.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.7.1...nvim-tree-v1.8.0) (2024-11-09)
### Features
* **#2819:** add actions.open_file.relative_path, default enabled, following successful experiment ([#2995](https://github.com/nvim-tree/nvim-tree.lua/issues/2995)) ([2ee1c5e](https://github.com/nvim-tree/nvim-tree.lua/commit/2ee1c5e17fdfbf5013af31b1410e4a5f28f4cadd))
* **#2938:** add default filesystem_watchers.ignore_dirs = { "/.ccls-cache", "/build", "/node_modules", "/target", } ([#2940](https://github.com/nvim-tree/nvim-tree.lua/issues/2940)) ([010ae03](https://github.com/nvim-tree/nvim-tree.lua/commit/010ae0365aafd6275c478d932515d2e8e897b7bb))
### Bug Fixes
* **#2945:** stack overflow on api.git.reload or fugitive event with watchers disabled ([#2949](https://github.com/nvim-tree/nvim-tree.lua/issues/2949)) ([5ad8762](https://github.com/nvim-tree/nvim-tree.lua/commit/5ad87620ec9d1190d15c88171a3f0122bc16b0fe))
* **#2947:** root is never a dotfile, so that it doesn't propagate to children ([#2958](https://github.com/nvim-tree/nvim-tree.lua/issues/2958)) ([f5f6789](https://github.com/nvim-tree/nvim-tree.lua/commit/f5f67892996b280ae78b1b0a2d07c4fa29ae0905))
* **#2951:** highlights incorrect following cancelled pick ([#2952](https://github.com/nvim-tree/nvim-tree.lua/issues/2952)) ([1c9553a](https://github.com/nvim-tree/nvim-tree.lua/commit/1c9553a19f70df3dcb171546a3d5e034531ef093))
* **#2954:** resolve occasional tree flashing on diagnostics, set tree buffer options in deterministic order ([#2980](https://github.com/nvim-tree/nvim-tree.lua/issues/2980)) ([82ab19e](https://github.com/nvim-tree/nvim-tree.lua/commit/82ab19ebf79c1839d7351f2fed213d1af13a598e))
* **#2961:** windows: escape brackets and parentheses when opening file ([#2962](https://github.com/nvim-tree/nvim-tree.lua/issues/2962)) ([63c7ad9](https://github.com/nvim-tree/nvim-tree.lua/commit/63c7ad9037fb7334682dd0b3a177cee25c5c8a0f))
* **#2969:** After a rename, the node loses selection ([#2974](https://github.com/nvim-tree/nvim-tree.lua/issues/2974)) ([1403933](https://github.com/nvim-tree/nvim-tree.lua/commit/14039337a563f4efd72831888f332a15585f0ea1))
* **#2972:** error on :colorscheme ([#2973](https://github.com/nvim-tree/nvim-tree.lua/issues/2973)) ([6e5a204](https://github.com/nvim-tree/nvim-tree.lua/commit/6e5a204ca659bb8f2a564df75df2739edec03cb0))
* **#2976:** use vim.loop to preserve neovim 0.9 compatibility ([#2977](https://github.com/nvim-tree/nvim-tree.lua/issues/2977)) ([00dff48](https://github.com/nvim-tree/nvim-tree.lua/commit/00dff482f9a8fb806a54fd980359adc6cd45d435))
* **#2978:** grouped folder not showing closed icon ([#2979](https://github.com/nvim-tree/nvim-tree.lua/issues/2979)) ([120ba58](https://github.com/nvim-tree/nvim-tree.lua/commit/120ba58254835d412bbc91cffe847e9be835fadd))
* **#2981:** windows: root changed when navigating with LSP ([#2982](https://github.com/nvim-tree/nvim-tree.lua/issues/2982)) ([c22124b](https://github.com/nvim-tree/nvim-tree.lua/commit/c22124b37409bee6d1a0da77f4f3a1526f7a204d))
* symlink file icons rendered when renderer.icons.show.file = false, folder.symlink* was incorrectly rendered as folder.default|open ([#2983](https://github.com/nvim-tree/nvim-tree.lua/issues/2983)) ([2156bc0](https://github.com/nvim-tree/nvim-tree.lua/commit/2156bc08c982d3c4b4cfc2b8fd7faeff58a88e10))
## [1.7.1](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.7.0...nvim-tree-v1.7.1) (2024-09-30)
### Bug Fixes
* **#2794:** sshfs compatibility ([#2922](https://github.com/nvim-tree/nvim-tree.lua/issues/2922)) ([9650e73](https://github.com/nvim-tree/nvim-tree.lua/commit/9650e735baad0d39505f4cb4867a60f02858536a))
* **#2928:** nil explorer in parent move action ([#2929](https://github.com/nvim-tree/nvim-tree.lua/issues/2929)) ([0429f28](https://github.com/nvim-tree/nvim-tree.lua/commit/0429f286b350c65118d66b646775bf187936fa47))
* **#2930:** empty groups expanded on reload ([#2935](https://github.com/nvim-tree/nvim-tree.lua/issues/2935)) ([4520c03](https://github.com/nvim-tree/nvim-tree.lua/commit/4520c0355cc561830ee2cf90dc37a2a75abf7995))
* invalid explorer on open ([#2927](https://github.com/nvim-tree/nvim-tree.lua/issues/2927)) ([59a8a6a](https://github.com/nvim-tree/nvim-tree.lua/commit/59a8a6ae5e9d3eae99d08ab655d12fd51d5d17f3))
### Reverts
* **#2794:** sshfs compatibility ([#2920](https://github.com/nvim-tree/nvim-tree.lua/issues/2920)) ([8405ecf](https://github.com/nvim-tree/nvim-tree.lua/commit/8405ecfbd6bb08a94ffc9c68fef211eea56e8a3b))
## [1.7.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.6.1...nvim-tree-v1.7.0) (2024-09-21)
### Features
* **#2430:** use vim.ui.open as default system_open, for neovim 0.10+ ([#2912](https://github.com/nvim-tree/nvim-tree.lua/issues/2912)) ([03f737e](https://github.com/nvim-tree/nvim-tree.lua/commit/03f737e5744a2b3ebb4b086f7636a3399224ec0c))
* help closes on &lt;Esc&gt; and api.tree.toggle_help mappings ([#2909](https://github.com/nvim-tree/nvim-tree.lua/issues/2909)) ([b652dbd](https://github.com/nvim-tree/nvim-tree.lua/commit/b652dbd0e0489c5fbb81fbededf0d99029cd2f38))
### Bug Fixes
* **#2862:** windows path replaces backslashes with forward slashes ([#2903](https://github.com/nvim-tree/nvim-tree.lua/issues/2903)) ([45a93d9](https://github.com/nvim-tree/nvim-tree.lua/commit/45a93d99794fff3064141d5b3a50db98ce352697))
* **#2906:** resource leak on populate children ([#2907](https://github.com/nvim-tree/nvim-tree.lua/issues/2907)) ([a4dd5ad](https://github.com/nvim-tree/nvim-tree.lua/commit/a4dd5ad5c8f9349142291d24e0e6466995594b9a))
* **#2917:** fix root copy paths: Y, ge, gy, y ([#2918](https://github.com/nvim-tree/nvim-tree.lua/issues/2918)) ([b18ce8b](https://github.com/nvim-tree/nvim-tree.lua/commit/b18ce8be8f162eee0bc37addcfe17d7d019fcec7))
* safely close last tree window ([#2913](https://github.com/nvim-tree/nvim-tree.lua/issues/2913)) ([bd48816](https://github.com/nvim-tree/nvim-tree.lua/commit/bd4881660bf0ddfa6acb21259f856ba3dcb26a93))
* safely close tree window with pcall and debug logging ([bd48816](https://github.com/nvim-tree/nvim-tree.lua/commit/bd4881660bf0ddfa6acb21259f856ba3dcb26a93))
## [1.6.1](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.6.0...nvim-tree-v1.6.1) (2024-09-09)
### Bug Fixes
* **#2794:** sshfs compatibility ([#2893](https://github.com/nvim-tree/nvim-tree.lua/issues/2893)) ([2d6e64d](https://github.com/nvim-tree/nvim-tree.lua/commit/2d6e64dd8c45a86f312552b7a47eef2c8623a25c))
* **#2868:** windows: do not visit unenumerable directories such as Application Data ([#2874](https://github.com/nvim-tree/nvim-tree.lua/issues/2874)) ([2104786](https://github.com/nvim-tree/nvim-tree.lua/commit/210478677cb9d672c4265deb0e9b59d58b675bd4))
* **#2878:** nowrapscan prevents move from root ([#2880](https://github.com/nvim-tree/nvim-tree.lua/issues/2880)) ([4234095](https://github.com/nvim-tree/nvim-tree.lua/commit/42340952af598a08ab80579d067b6da72a9e6d29))
* **#2879:** remove unnecessary tree window width setting to prevent unnecessary :wincmd = ([#2881](https://github.com/nvim-tree/nvim-tree.lua/issues/2881)) ([d43ab67](https://github.com/nvim-tree/nvim-tree.lua/commit/d43ab67d0eb4317961c5e9d15fffe908519debe0))
## [1.6.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.5.0...nvim-tree-v1.6.0) (2024-08-10)
### Features
* **#2225:** add renderer.hidden_display to show a summary of hidden files below the tree ([#2856](https://github.com/nvim-tree/nvim-tree.lua/issues/2856)) ([e25eb7f](https://github.com/nvim-tree/nvim-tree.lua/commit/e25eb7fa83f7614bb23d762e91d2de44fcd7103b))
* **#2349:** add "right_align" option for renderer.icons.*_placement ([#2839](https://github.com/nvim-tree/nvim-tree.lua/issues/2839)) ([1d629a5](https://github.com/nvim-tree/nvim-tree.lua/commit/1d629a5d3f7d83d516494c221a2cfc079f43bc47))
* **#2349:** add "right_align" option for renderer.icons.*_placement ([#2846](https://github.com/nvim-tree/nvim-tree.lua/issues/2846)) ([48d0e82](https://github.com/nvim-tree/nvim-tree.lua/commit/48d0e82f9434691cc50d970898142a8c084a49d6))
* add renderer.highlight_hidden, renderer.icons.show.hidden and renderer.icons.hidden_placement for dotfile icons/highlights ([#2840](https://github.com/nvim-tree/nvim-tree.lua/issues/2840)) ([48a9290](https://github.com/nvim-tree/nvim-tree.lua/commit/48a92907575df1dbd7242975a04e98169cb3a115))
### Bug Fixes
* **#2859:** make sure window still exists when restoring options ([#2863](https://github.com/nvim-tree/nvim-tree.lua/issues/2863)) ([466fbed](https://github.com/nvim-tree/nvim-tree.lua/commit/466fbed3e4b61fcc23a48fe99de7bfa264a9fee8))
## [1.5.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.4.0...nvim-tree-v1.5.0) (2024-07-11)
### Features
* **#2127:** add experimental.actions.open_file.relative_path to open files with a relative path rather than absolute ([#2805](https://github.com/nvim-tree/nvim-tree.lua/issues/2805)) ([869c064](https://github.com/nvim-tree/nvim-tree.lua/commit/869c064721a6c2091f22c3541e8f0ff958361771))
* **#2598:** add api.tree.resize ([#2811](https://github.com/nvim-tree/nvim-tree.lua/issues/2811)) ([2ede0de](https://github.com/nvim-tree/nvim-tree.lua/commit/2ede0de67b47e89e2b4cb488ea3f58b8f5a8c90a))
* **#2799:** `filesystem_watchers.ignore_dirs` and `git.disable_for_dirs` may be functions ([#2800](https://github.com/nvim-tree/nvim-tree.lua/issues/2800)) ([8b2c5c6](https://github.com/nvim-tree/nvim-tree.lua/commit/8b2c5c678be4b49dff6a2df794877000113fd77b))
* **#2799:** filesystem_watchers.ignore_dirs and git.disable_for_dirs may be functions ([8b2c5c6](https://github.com/nvim-tree/nvim-tree.lua/commit/8b2c5c678be4b49dff6a2df794877000113fd77b))
### Bug Fixes
* **#2813:** macos: enable file renaming with changed capitalization ([#2814](https://github.com/nvim-tree/nvim-tree.lua/issues/2814)) ([abfd1d1](https://github.com/nvim-tree/nvim-tree.lua/commit/abfd1d1b6772540364743531cc0331e08a0027a9))
* **#2819:** experimental.actions.open_file.relative_path issue following change directory ([#2820](https://github.com/nvim-tree/nvim-tree.lua/issues/2820)) ([12a9a99](https://github.com/nvim-tree/nvim-tree.lua/commit/12a9a995a455d2c2466e47140663275365a5d2fc))
## [1.4.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.3.3...nvim-tree-v1.4.0) (2024-06-09)
### Notice
* Neovim 0.9 is now the minimum supported version; please upgrade to neovim release version 0.9 or 0.10.
### Reverts
* **#2781:** "refactor: replace deprecated use of vim.diagnostic.is_disabled()" ([#2784](https://github.com/nvim-tree/nvim-tree.lua/issues/2784)) ([517e4fb](https://github.com/nvim-tree/nvim-tree.lua/commit/517e4fbb9ef3c0986da7047f44b4b91a2400f93c))
### Miscellaneous Chores
* release 1.4.0 ([1cac800](https://github.com/nvim-tree/nvim-tree.lua/commit/1cac8005df6da484c97499247754afa59fef92db))
## [1.3.3](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.3.2...nvim-tree-v1.3.3) (2024-05-14)
### Bug Fixes
* nil access exception with git integration when changing branches ([#2774](https://github.com/nvim-tree/nvim-tree.lua/issues/2774)) ([340d3a9](https://github.com/nvim-tree/nvim-tree.lua/commit/340d3a9795e06bdd1814228de398cd510f9bfbb0))
## [1.3.2](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.3.1...nvim-tree-v1.3.2) (2024-05-12)
### Bug Fixes
* **#2758:** use nvim-webdevicons default file icon, not renderer.icons.glyphs.default, as per :help ([#2759](https://github.com/nvim-tree/nvim-tree.lua/issues/2759)) ([347e1eb](https://github.com/nvim-tree/nvim-tree.lua/commit/347e1eb35264677f66a79466bb5e3d111968e12c))
* **#2758:** use nvim-webdevicons default for default files ([347e1eb](https://github.com/nvim-tree/nvim-tree.lua/commit/347e1eb35264677f66a79466bb5e3d111968e12c))
* **#925:** handle newlines in file names ([#2754](https://github.com/nvim-tree/nvim-tree.lua/issues/2754)) ([64f61e4](https://github.com/nvim-tree/nvim-tree.lua/commit/64f61e4c913047a045ff90bd188dd3b54ee443cf))
## [1.3.1](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.3.0...nvim-tree-v1.3.1) (2024-04-25)
### Bug Fixes
* **#2535:** TextYankPost event sends vim.v.event ([#2734](https://github.com/nvim-tree/nvim-tree.lua/issues/2734)) ([d8d3a15](https://github.com/nvim-tree/nvim-tree.lua/commit/d8d3a1590a05b2d8b5eb26e2ed1c6052b1b47a77))
* **#2733:** escape trash path ([#2735](https://github.com/nvim-tree/nvim-tree.lua/issues/2735)) ([81eb8d5](https://github.com/nvim-tree/nvim-tree.lua/commit/81eb8d519233c105f30dc0a278607e62b20502fd))
## [1.3.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.2.0...nvim-tree-v1.3.0) (2024-03-30)
### Features
* add update_focused_file.exclude ([#2673](https://github.com/nvim-tree/nvim-tree.lua/issues/2673)) ([e20966a](https://github.com/nvim-tree/nvim-tree.lua/commit/e20966ae558524f8d6f93dc37f5d2a8605f893e2))
### Bug Fixes
* **#2658:** change SpellCap groups to reduce confusion: ExecFile-&gt;Question, ImageFile->Question, SpecialFile->Title, Symlink->Underlined; add all other highlight groups to :NvimTreeHiTest ([#2732](https://github.com/nvim-tree/nvim-tree.lua/issues/2732)) ([0aca092](https://github.com/nvim-tree/nvim-tree.lua/commit/0aca0920f44b12a8383134bcb52da9faec123608))
* bookmark filter shows marked directory children ([#2719](https://github.com/nvim-tree/nvim-tree.lua/issues/2719)) ([2d97059](https://github.com/nvim-tree/nvim-tree.lua/commit/2d97059661c83787372c8c003e743c984ba3ac50))
## [1.2.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.1.1...nvim-tree-v1.2.0) (2024-03-24)
### Features
* add api.tree.toggle_enable_filters ([#2706](https://github.com/nvim-tree/nvim-tree.lua/issues/2706)) ([f7c09bd](https://github.com/nvim-tree/nvim-tree.lua/commit/f7c09bd72e50e1795bd3afb9e2a2b157b4bfb3c3))
## [1.1.1](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.1.0...nvim-tree-v1.1.1) (2024-03-15)
### Bug Fixes
* **#2395:** marks.bulk.move defaults to directory at cursor ([#2688](https://github.com/nvim-tree/nvim-tree.lua/issues/2688)) ([cfea5bd](https://github.com/nvim-tree/nvim-tree.lua/commit/cfea5bd0806aab41bef6014c6cf5a510910ddbdb))
* **#2705:** change NvimTreeWindowPicker cterm background from Cyan to more visible DarkBlue ([#2708](https://github.com/nvim-tree/nvim-tree.lua/issues/2708)) ([1fd9c98](https://github.com/nvim-tree/nvim-tree.lua/commit/1fd9c98960463d2d5d400916c0633b2df016941d))
* bookmark filter should include parent directory ([#2704](https://github.com/nvim-tree/nvim-tree.lua/issues/2704)) ([76b9810](https://github.com/nvim-tree/nvim-tree.lua/commit/76b98109f62caa12b2f1dff472060b2233ea2e90))
## [1.1.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.0.0...nvim-tree-v1.1.0) (2024-03-14)
### Features
* **#2630:** file renames can now create directories ([#2657](https://github.com/nvim-tree/nvim-tree.lua/issues/2657)) ([efafd73](https://github.com/nvim-tree/nvim-tree.lua/commit/efafd73efa9bc8c26282aed563ba0f01c7465b06))
* add api.fs.copy.basename, default mapping ge ([#2698](https://github.com/nvim-tree/nvim-tree.lua/issues/2698)) ([8f2a50f](https://github.com/nvim-tree/nvim-tree.lua/commit/8f2a50f1cd0c64003042364cf317c8788eaa6c8c))
### Bug Fixes
* **#2695:** git toplevel guard against missing paths ([#2696](https://github.com/nvim-tree/nvim-tree.lua/issues/2696)) ([3c4267e](https://github.com/nvim-tree/nvim-tree.lua/commit/3c4267eb5045fa86b67fe40c0c63d31efc801e77))
* searchcount exception on invalid search regex ([#2693](https://github.com/nvim-tree/nvim-tree.lua/issues/2693)) ([041dbd1](https://github.com/nvim-tree/nvim-tree.lua/commit/041dbd18f440207ad161503a384e7c82d575db66))
## [1.0.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v0.100.0...nvim-tree-v1.0.0) (2024-02-18)
### Features
* **#2654:** filters.custom may be a function ([#2655](https://github.com/nvim-tree/nvim-tree.lua/issues/2655)) ([4a87b8b](https://github.com/nvim-tree/nvim-tree.lua/commit/4a87b8b46b4a30107971871df3cb7f4c30fdd5d0))
### Miscellaneous Chores
* release 1.0.0 ([#2678](https://github.com/nvim-tree/nvim-tree.lua/issues/2678)) ([d16246a](https://github.com/nvim-tree/nvim-tree.lua/commit/d16246a7575538f77e9246520449b99333c469f7))
## [0.100.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v0.99.0...nvim-tree-v0.100.0) (2024-02-11)
### Features
* **#1389:** api: recursive node navigation for git and diagnostics ([#2525](https://github.com/nvim-tree/nvim-tree.lua/issues/2525)) ([5d13cc8](https://github.com/nvim-tree/nvim-tree.lua/commit/5d13cc8205bce4963866f73c50f6fdc18a515ffe))
* **#2415:** add :NvimTreeHiTest ([#2664](https://github.com/nvim-tree/nvim-tree.lua/issues/2664)) ([b278fc2](https://github.com/nvim-tree/nvim-tree.lua/commit/b278fc25ae0fc95e4808eb5618f07fc2522fd2b3))
* **#2415:** colour and highlight overhaul, see :help nvim-tree-highlight-overhaul ([#2455](https://github.com/nvim-tree/nvim-tree.lua/issues/2455)) ([e9c5abe](https://github.com/nvim-tree/nvim-tree.lua/commit/e9c5abe073a973f54d3ca10bfe30f253569f4405))
* add node.open.toggle_group_empty, default mapping L ([#2647](https://github.com/nvim-tree/nvim-tree.lua/issues/2647)) ([8cbb1db](https://github.com/nvim-tree/nvim-tree.lua/commit/8cbb1db8e90b62fc56f379992e622e9f919792ce))
### Bug Fixes
* **#2415:** disambiguate highlight groups, see :help nvim-tree-highlight-overhaul ([#2639](https://github.com/nvim-tree/nvim-tree.lua/issues/2639)) ([d9cb432](https://github.com/nvim-tree/nvim-tree.lua/commit/d9cb432d2c8d8fa9267ddbd7535d76fe4df89360))
* **#2415:** fix NvimTreeIndentMarker highlight group: FileIcon-&gt;FolderIcon ([e9ac136](https://github.com/nvim-tree/nvim-tree.lua/commit/e9ac136a3ab996aa8e4253253521dcf2cb66b81b))
* **#2415:** highlight help header and mappings ([#2669](https://github.com/nvim-tree/nvim-tree.lua/issues/2669)) ([39e6fef](https://github.com/nvim-tree/nvim-tree.lua/commit/39e6fef85ac3bb29532b877aa7c9c34911c661af))
* **#2415:** nvim 0.8 highlight overhaul support, limited to only show highest highlight precedence ([#2642](https://github.com/nvim-tree/nvim-tree.lua/issues/2642)) ([f39f7b6](https://github.com/nvim-tree/nvim-tree.lua/commit/f39f7b6fcd3865ac2146de4cb4045286308f2935))
* **#2415:** NvimTreeIndentMarker highlight group: FileIcon-&gt;FolderIcon ([#2656](https://github.com/nvim-tree/nvim-tree.lua/issues/2656)) ([e9ac136](https://github.com/nvim-tree/nvim-tree.lua/commit/e9ac136a3ab996aa8e4253253521dcf2cb66b81b))
* **#2624:** open file from docked floating window ([#2627](https://github.com/nvim-tree/nvim-tree.lua/issues/2627)) ([f24afa2](https://github.com/nvim-tree/nvim-tree.lua/commit/f24afa2cef551122b8bd53bb2e4a7df42343ce2e))
* **#2632:** occasional error stack when locating nvim-tree window ([#2633](https://github.com/nvim-tree/nvim-tree.lua/issues/2633)) ([48b1d86](https://github.com/nvim-tree/nvim-tree.lua/commit/48b1d8638fa3726236ae22e0e48a74ac8ea6592a))
* **#2637:** show buffer modified icons and highlights ([#2638](https://github.com/nvim-tree/nvim-tree.lua/issues/2638)) ([7bdb220](https://github.com/nvim-tree/nvim-tree.lua/commit/7bdb220d0fe604a77361e92cdbc7af1b8a412126))
* **#2643:** correctly apply linked highlight groups in tree window ([#2653](https://github.com/nvim-tree/nvim-tree.lua/issues/2653)) ([fbee8a6](https://github.com/nvim-tree/nvim-tree.lua/commit/fbee8a69a46f558d29ab84e96301425b0501c668))
* allow highlight overrides for DEFAULT_DEFS: NvimTreeFolderIcon, NvimTreeWindowPicker ([#2636](https://github.com/nvim-tree/nvim-tree.lua/issues/2636)) ([74525ac](https://github.com/nvim-tree/nvim-tree.lua/commit/74525ac04760bf0d9fec2bf51474d2b05f36048e))
* bad column offset when using full_name ([#2629](https://github.com/nvim-tree/nvim-tree.lua/issues/2629)) ([75ff64e](https://github.com/nvim-tree/nvim-tree.lua/commit/75ff64e6663fc3b23c72dca32b2f838acefe7c8a))
* passing nil as window handle in view.get_winnr ([48b1d86](https://github.com/nvim-tree/nvim-tree.lua/commit/48b1d8638fa3726236ae22e0e48a74ac8ea6592a))
## 0.99.0 (2024-01-01)
### Features
* **#1850:** add "no bookmark" filter ([#2571](https://github.com/nvim-tree/nvim-tree.lua/issues/2571)) ([8f92e1e](https://github.com/nvim-tree/nvim-tree.lua/commit/8f92e1edd399f839a23776dcc6eee4ba18030370))
* add kind param to vim.ui.select function calls ([#2602](https://github.com/nvim-tree/nvim-tree.lua/issues/2602)) ([dc839a7](https://github.com/nvim-tree/nvim-tree.lua/commit/dc839a72a6496ce22ebd3dd959115cf97c1b20a0))
* add option to skip gitignored files on git navigation ([#2583](https://github.com/nvim-tree/nvim-tree.lua/issues/2583)) ([50f30bc](https://github.com/nvim-tree/nvim-tree.lua/commit/50f30bcd8c62ac4a83d133d738f268279f2c2ce2))
### Bug Fixes
* **#2519:** Diagnostics Not Updated When Tree Not Visible ([#2597](https://github.com/nvim-tree/nvim-tree.lua/issues/2597)) ([96a783f](https://github.com/nvim-tree/nvim-tree.lua/commit/96a783fbd606a458bcce2ef8041240a8b94510ce))
* **#2609:** help toggle ([#2611](https://github.com/nvim-tree/nvim-tree.lua/issues/2611)) ([fac4900](https://github.com/nvim-tree/nvim-tree.lua/commit/fac4900bd18a9fa15be3d104645d9bdef7b3dcec))
* hijack_cursor on update focused file and vim search ([#2600](https://github.com/nvim-tree/nvim-tree.lua/issues/2600)) ([02ae523](https://github.com/nvim-tree/nvim-tree.lua/commit/02ae52357ba4da77a4c120390791584a81d15340))

View File

@@ -2,32 +2,177 @@
Thank you for contributing. Thank you for contributing.
## Styling and formatting See [wiki: Development](https://github.com/nvim-tree/nvim-tree.lua/wiki/Development) for environment setup, tips and tools.
Code is formatted using luacheck, and linted using stylua. <!--
You can install these with: https://github.com/jonschlinkert/markdown-toc
markdown-toc --maxdepth=2 -i CONTRIBUTING.md
-->
```bash <!-- toc -->
luarocks install luacheck
cargo install stylua - [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
Following are used during CI and strongly recommended during local development.
Language server: [luals](https://luals.github.io)
Lint: [luacheck](https://github.com/lunarmodules/luacheck/)
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, 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`
# Quality
The following quality checks are mandatory and are performed during CI. They run on the entire `lua` directory and return 1 on any failure.
You can run them all via `make` or `make all`
You can setup git hooks to run all checks by running `scripts/setup-hooks.sh`
## lint
1. Runs luacheck quietly using `.luacheck` settings
```sh
make lint
``` ```
You can setup the git hooks by running `scripts/setup-hooks.sh`. ## style
## Adding new actions 1. Runs lua language server `codestyle-check` only, using `.luarc.json` settings
1. Runs `scripts/doc-comments.sh` to validate annotated documentation
```sh
make style
```
You can automatically fix style issues using `CodeCheck`:
```sh
make style-fix
```
## check
1. Runs the checks that the LSP lua language server runs inside nvim using `.luarc.json` via `scripts/luals-check.sh`
```sh
make check
```
Assumes `$VIMRUNTIME` is `/usr/share/nvim/runtime`. Adjust as necessary e.g.
```sh
VIMRUNTIME="/my/path/to/runtime" make check
```
If `lua-language-server` is not available or `--check` doesn't function (e.g. Arch Linux 3.9.1-1) you can manually install it as per `ci.yml` e.g.
```sh
mkdir luals
curl -L "https://github.com/LuaLS/lua-language-server/releases/download/3.9.1/lua-language-server-3.9.1-linux-x64.tar.gz" | tar zx --directory luals
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
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.
Once you did, you should run the `scripts/update-help.sh`.
## Documentation Once you did, you should run `make help-update`
# Documentation
## Opts
When adding new options, you should declare the defaults in the main `nvim-tree.lua` file. When adding new options, you should declare the defaults in the main `nvim-tree.lua` file.
Once you did, you should run the `scripts/update-help.sh`.
Documentation for options should also be added, see how this is done after `nvim-tree.disable_netrw` in the `nvim-tree-lua.txt` file. Documentation for options should also be added to `nvim-tree-opts` in `doc/nvim-tree-lua.txt`
## Pull Request ## API
Please reference any issues in the description e.g. "resolves #1234". When adding or changing API please update :help nvim-tree-api
# Windows
Please note that nvim-tree team members do not have access to nor expertise with Windows.
You will need to be an active participant during development and raise a PR to resolve any issues that may arise.
Please ensure that windows specific features and fixes are behind the appropriate feature flag, see [wiki: OS Feature Flags](https://github.com/nvim-tree/nvim-tree.lua/wiki/Development#os-feature-flags)
# Pull Request
Please reference any issues in the description e.g. "resolves #1234", which will be closed upon merge.
Please check "allow edits by maintainers" to allow nvim-tree developers to make small changes such as documentation tweaks. Please check "allow edits by maintainers" to allow nvim-tree developers to make small changes such as documentation tweaks.
## Subject
The merge commit message will be the subject of the PR.
A [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0) subject will be validated by the Semantic Pull Request Subject CI job. Reference the issue to be used in the release notes e.g.
`fix(#2395): marks.bulk.move defaults to directory at cursor`
Available types:
* feat: A new feature
* fix: A bug fix
* docs: Documentation only changes
* style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
* refactor: A code change that neither fixes a bug nor adds a feature
* perf: A code change that improves performance
* test: Adding missing tests or correcting existing tests
* build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
* ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
* chore: Other changes that don't modify src or test files
* revert: Reverts a previous commit
If in doubt, look at previous commits.
See also [The Conventional Commits ultimate cheatsheet](https://gist.github.com/gabrielecanepa/fa6cca1a8ae96f77896fe70ddee65527)

48
Makefile Normal file
View File

@@ -0,0 +1,48 @@
all: lint style check
#
# mandatory checks
#
lint: luacheck
style: style-check style-doc
check: luals
#
# subtasks
#
luacheck:
luacheck --codes --quiet lua --exclude-files "**/_meta/**"
# --diagnosis-as-error does not function for workspace, hence we post-process the output
style-check:
@scripts/luals-check.sh codestyle-check
style-doc:
scripts/doc-comments.sh
luals:
@scripts/luals-check.sh
#
# fixes
#
style-fix:
CodeFormat format --config .editorconfig --workspace lua
#
# utility
#
help-update:
scripts/help-update.sh
#
# CI
#
help-check: help-update
git diff --exit-code doc/nvim-tree-lua.txt
.PHONY: all lint style check luacheck style-check style-doc luals style-fix help-update help-check

155
README.md
View File

@@ -2,8 +2,8 @@
[![CI](https://github.com/nvim-tree/nvim-tree.lua/actions/workflows/ci.yml/badge.svg)](https://github.com/nvim-tree/nvim-tree.lua/actions/workflows/ci.yml) [![CI](https://github.com/nvim-tree/nvim-tree.lua/actions/workflows/ci.yml/badge.svg)](https://github.com/nvim-tree/nvim-tree.lua/actions/workflows/ci.yml)
<img align="left" width="149" height="484" src="https://user-images.githubusercontent.com/17254073/195207026-f3434ba1-dc86-4c48-8ab3-b2efc3b85227.png"> <img align="left" width="199" height="598" src="https://user-images.githubusercontent.com/1505378/232662694-8dc494e0-24da-497a-8541-29344293378c.png">
<img align="left" width="149" height="484" src="https://user-images.githubusercontent.com/17254073/195207023-7b709e35-7f10-416b-aafb-5bb61268c7d3.png"> <img align="left" width="199" height="598" src="https://user-images.githubusercontent.com/1505378/232662698-2f321315-c67a-486b-85d8-8c391de52392.png">
Automatic updates Automatic updates
@@ -24,47 +24,38 @@
Take a look at the [wiki](https://github.com/nvim-tree/nvim-tree.lua/wiki) for Showcases, Tips, Recipes and more. Take a look at the [wiki](https://github.com/nvim-tree/nvim-tree.lua/wiki) for Showcases, Tips, Recipes and more.
[Join us on matrix](https://matrix.to/#/#nvim-tree:matrix.org) Questions and general support: [Discussions](https://github.com/nvim-tree/nvim-tree.lua/discussions)
## Requirements ## Requirements
[neovim >=0.7.0](https://github.com/neovim/neovim/wiki/Installing-Neovim) [neovim >=0.9.0](https://github.com/neovim/neovim/wiki/Installing-Neovim)
[nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons) is optional and used to display file icons. It requires a [patched font](https://www.nerdfonts.com/). Your terminal emulator must be configured to use that font, usually "Hack Nerd Font" [nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons) is optional and used to display file icons. It requires a [patched font](https://www.nerdfonts.com/). Your terminal emulator must be configured to use that font, usually "Hack Nerd Font"
## Install ## Install
Install with [vim-plug](https://github.com/junegunn/vim-plug): Please install via your preferred package manager. See [Installation](https://github.com/nvim-tree/nvim-tree.lua/wiki/Installation) for specific package manager instructions.
```vim `nvim-tree/nvim-tree.lua`
Plug 'nvim-tree/nvim-web-devicons' " optional, for file icons
Plug 'nvim-tree/nvim-tree.lua'
```
or with [packer](https://github.com/wbthomason/packer.nvim): Major or minor versions may be specified via tags: `v<MAJOR>` e.g. `v1` or `v<MAJOR>.<MINOR>` e.g. `v1.23`
`nvim-tree/nvim-web-devicons` optional, for file icons
Disabling [netrw](https://neovim.io/doc/user/pi_netrw.html) is strongly advised, see [:help nvim-tree-netrw](doc/nvim-tree-lua.txt)
## Quick Start
### Setup
Setup the plugin in your `init.lua`
```lua ```lua
use { -- disable netrw at the very start of your init.lua
'nvim-tree/nvim-tree.lua',
requires = {
'nvim-tree/nvim-web-devicons', -- optional, for file icons
},
tag = 'nightly' -- optional, updated every week. (see issue #1193)
}
```
## Setup
Setup should be run in a lua file or in a lua heredoc [:help lua-heredoc](https://neovim.io/doc/user/lua.html) if using in a vim file.
```lua
-- examples for your init.lua
-- disable netrw at the very start of your init.lua (strongly advised)
vim.g.loaded_netrw = 1 vim.g.loaded_netrw = 1
vim.g.loaded_netrwPlugin = 1 vim.g.loaded_netrwPlugin = 1
-- set termguicolors to enable highlight groups -- optionally enable 24-bit colour
vim.opt.termguicolors = true vim.opt.termguicolors = true
-- empty setup using defaults -- empty setup using defaults
@@ -72,14 +63,11 @@ require("nvim-tree").setup()
-- OR setup with some options -- OR setup with some options
require("nvim-tree").setup({ require("nvim-tree").setup({
sort_by = "case_sensitive", sort = {
sorter = "case_sensitive",
},
view = { view = {
adaptive_size = true, width = 30,
mappings = {
list = {
{ key = "u", action = "dir_up" },
},
},
}, },
renderer = { renderer = {
group_empty = true, group_empty = true,
@@ -90,9 +78,56 @@ require("nvim-tree").setup({
}) })
``` ```
For complete list of available configuration options see [:help nvim-tree-setup](doc/nvim-tree-lua.txt) ### Help
Each option is documented in `:help nvim-tree.OPTION_NAME`. Nested options can be accessed by appending `.`, for example [:help nvim-tree.view.mappings](doc/nvim-tree-lua.txt) Open the tree: `:NvimTreeOpen`
Show the mappings: `g?`
### Custom Mappings
[:help nvim-tree-mappings-default](doc/nvim-tree-lua.txt) are applied by default however you may customise via |nvim-tree.on_attach| e.g.
```lua
local function my_on_attach(bufnr)
local api = require "nvim-tree.api"
local function opts(desc)
return { desc = "nvim-tree: " .. desc, buffer = bufnr, noremap = true, silent = true, nowait = true }
end
-- default mappings
api.config.mappings.default_on_attach(bufnr)
-- custom mappings
vim.keymap.set('n', '<C-t>', api.tree.change_root_to_parent, opts('Up'))
vim.keymap.set('n', '?', api.tree.toggle_help, opts('Help'))
end
-- pass to setup along with your other options
require("nvim-tree").setup {
---
on_attach = my_on_attach,
---
}
```
### Highlight
Run `:NvimTreeHiTest` to show all the highlights that nvim-tree uses.
They can be customised before or after setup is called and will be immediately
applied at runtime. e.g.
```lua
vim.cmd([[
:hi NvimTreeExecFile guifg=#ffa0a0
:hi NvimTreeSpecialFile guifg=#ff80ff gui=underline
:hi NvimTreeSymlink guifg=Yellow gui=italic
:hi link NvimTreeImageFile Title
]])
```
See [:help nvim-tree-highlight](doc/nvim-tree-lua.txt) for details.
## Commands ## Commands
@@ -108,12 +143,6 @@ Basic commands:
`:NvimTreeCollapse` Collapses the nvim-tree recursively. `:NvimTreeCollapse` Collapses the nvim-tree recursively.
## Mappings
nvim-tree comes with number of mappings; for default mappings please see [:help nvim-tree-default-mappings](doc/nvim-tree-lua.txt), for way of configuring mappings see [:help nvim-tree-mappings](doc/nvim-tree-lua.txt)
`g?` toggles help, showing all the mappings and their actions.
## Roadmap ## Roadmap
nvim-tree is stable and new major features will not be added. The focus is on existing user experience. nvim-tree is stable and new major features will not be added. The focus is on existing user experience.
@@ -129,47 +158,29 @@ Development is focused on:
## API ## API
nvim-tree exposes a public API. This is non breaking, with additions made as necessary. nvim-tree exposes a public API. This is non breaking, with additions made as necessary. See [:help nvim-tree-api](doc/nvim-tree-lua.txt)
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 insufficent for your needs. [Contributions](#Contributing) are always welcome. 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.
[:help nvim-tree-api](doc/nvim-tree-lua.txt) 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.
### Events 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)
Users may subscribe to events that nvim-tree will dispatch in a variety of situations.
[:help nvim-tree-events](doc/nvim-tree-lua.txt)
### Actions
Custom actions may be mapped which can invoke API or perform your own actions.
[:help nvim-tree-mappings](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.
### Help Wanted
Developers with the following environments:
* Apple macOS
* Windows
* WSL
* msys
* powershell
Help triaging, diagnosing and fixing issues specific to those environments is needed, as the nvim-tree developers do not have access to or expertise in these environments.
Let us know you're interested by commenting on issues and raising PRs.
## Screenshots ## Screenshots
See [Showcases](https://github.com/nvim-tree/nvim-tree.lua/wiki/Showcases) wiki page for examples of user's configurations with sources. See [Showcases](https://github.com/nvim-tree/nvim-tree.lua/wiki/Showcases) wiki page for examples of user's configurations with sources.
Please add your own! Please add your own!
## Team
* [@alex-courtis](https://github.com/alex-courtis) Arch Linux
* [@gegoune](https://github.com/gegoune) macOS
* [@Akmadan23](https://github.com/Akmadan23) Linux
* [@dependabot[bot]](https://github.com/apps/dependabot) Ubuntu Linux

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -1,131 +0,0 @@
local view = require "nvim-tree.view"
local lib = require "nvim-tree.lib"
local M = {}
local Actions = {
close = view.close,
-- Tree modifiers
collapse_all = require("nvim-tree.actions.tree-modifiers.collapse-all").fn,
expand_all = require("nvim-tree.actions.tree-modifiers.expand-all").fn,
toggle_dotfiles = require("nvim-tree.actions.tree-modifiers.toggles").dotfiles,
toggle_custom = require("nvim-tree.actions.tree-modifiers.toggles").custom,
toggle_git_ignored = require("nvim-tree.actions.tree-modifiers.toggles").git_ignored,
toggle_git_clean = require("nvim-tree.actions.tree-modifiers.toggles").git_clean,
toggle_no_buffer = require("nvim-tree.actions.tree-modifiers.toggles").no_buffer,
-- Filesystem operations
copy_absolute_path = require("nvim-tree.actions.fs.copy-paste").copy_absolute_path,
copy_name = require("nvim-tree.actions.fs.copy-paste").copy_filename,
copy_path = require("nvim-tree.actions.fs.copy-paste").copy_path,
copy = require("nvim-tree.actions.fs.copy-paste").copy,
create = require("nvim-tree.actions.fs.create-file").fn,
cut = require("nvim-tree.actions.fs.copy-paste").cut,
full_rename = require("nvim-tree.actions.fs.rename-file").fn ":p",
paste = require("nvim-tree.actions.fs.copy-paste").paste,
trash = require("nvim-tree.actions.fs.trash").fn,
remove = require("nvim-tree.actions.fs.remove-file").fn,
rename = require("nvim-tree.actions.fs.rename-file").fn ":t",
rename_basename = require("nvim-tree.actions.fs.rename-file").fn ":t:r",
-- Movements in tree
close_node = require("nvim-tree.actions.moves.parent").fn(true),
first_sibling = require("nvim-tree.actions.moves.sibling").fn "first",
last_sibling = require("nvim-tree.actions.moves.sibling").fn "last",
next_diag_item = require("nvim-tree.actions.moves.item").fn("next", "diag"),
next_git_item = require("nvim-tree.actions.moves.item").fn("next", "git"),
next_sibling = require("nvim-tree.actions.moves.sibling").fn "next",
parent_node = require("nvim-tree.actions.moves.parent").fn(false),
prev_diag_item = require("nvim-tree.actions.moves.item").fn("prev", "diag"),
prev_git_item = require("nvim-tree.actions.moves.item").fn("prev", "git"),
prev_sibling = require("nvim-tree.actions.moves.sibling").fn "prev",
-- Other types
refresh = require("nvim-tree.actions.reloaders.reloaders").reload_explorer,
dir_up = require("nvim-tree.actions.root.dir-up").fn,
search_node = require("nvim-tree.actions.finders.search-node").fn,
run_file_command = require("nvim-tree.actions.node.run-command").run_file_command,
toggle_file_info = require("nvim-tree.actions.node.file-popup").toggle_file_info,
system_open = require("nvim-tree.actions.node.system-open").fn,
toggle_mark = require("nvim-tree.marks").toggle_mark,
bulk_move = require("nvim-tree.marks.bulk-move").bulk_move,
}
local function handle_action_on_help_ui(action)
if action == "close" or action == "toggle_help" then
require("nvim-tree.actions.tree-modifiers.toggles").help()
end
end
local function handle_filter_actions(action)
if action == "live_filter" then
require("nvim-tree.live-filter").start_filtering()
elseif action == "clear_live_filter" then
require("nvim-tree.live-filter").clear_filter()
end
end
local function change_dir_action(node)
if node.name == ".." then
require("nvim-tree.actions.root.change-dir").fn ".."
elseif node.nodes ~= nil then
require("nvim-tree.actions.root.change-dir").fn(lib.get_last_group_node(node).absolute_path)
end
end
local function open_file(action, node)
local path = node.absolute_path
if node.link_to and not node.nodes then
path = node.link_to
end
require("nvim-tree.actions.node.open-file").fn(action, path)
end
local function handle_tree_actions(action)
local node = lib.get_node_at_cursor()
if not node then
return
end
local custom_function = M.custom_keypress_funcs[action]
local defined_action = Actions[action]
if type(custom_function) == "function" then
return custom_function(node)
elseif defined_action then
return defined_action(node)
end
local is_parent = node.name == ".."
if action == "preview" and is_parent then
return
end
if action == "cd" or is_parent then
return change_dir_action(node)
end
if node.nodes then
lib.expand_or_collapse(node)
else
open_file(action, node)
end
end
function M.dispatch(action)
if view.is_help_ui() or action == "toggle_help" then
handle_action_on_help_ui(action)
elseif action == "live_filter" or action == "clear_live_filter" then
handle_filter_actions(action)
else
handle_tree_actions(action)
end
end
function M.setup(custom_keypress_funcs)
M.custom_keypress_funcs = custom_keypress_funcs
end
return M

View File

@@ -1,37 +1,40 @@
local log = require "nvim-tree.log" local log = require("nvim-tree.log")
local view = require "nvim-tree.view" local view = require("nvim-tree.view")
local utils = require "nvim-tree.utils" local utils = require("nvim-tree.utils")
local renderer = require "nvim-tree.renderer" local core = require("nvim-tree.core")
local core = require "nvim-tree.core"
local reload = require "nvim-tree.explorer.reload" local DirectoryNode = require("nvim-tree.node.directory")
local Iterator = require "nvim-tree.iterators.node-iterator" local Iterator = require("nvim-tree.iterators.node-iterator")
local M = {} local M = {}
local running = {} local running = {}
---Find a path in the tree, expand it and focus it ---Find a path in the tree, expand it and focus it
---@param fname string full path ---@param path string relative or absolute
function M.fn(fname) function M.fn(path)
if not core.get_explorer() then local explorer = core.get_explorer()
if not explorer or not view.is_visible() then
return return
end end
-- always match against the real path -- always match against the real path
local fname_real = vim.loop.fs_realpath(fname) local path_real = vim.loop.fs_realpath(path)
if not fname_real then if not path_real then
return return
end end
if running[fname_real] then if running[path_real] then
return return
end end
running[fname_real] = true running[path_real] = true
local ps = log.profile_start("find file %s", fname_real) local profile = log.profile_start("find file %s", path_real)
-- we cannot wait for watchers -- refresh the contents of all parents, expanding groups as needed
reload.refresh_nodes_for_path(vim.fn.fnamemodify(fname_real, ":h")) if utils.get_node_from_path(path_real) == nil then
explorer:refresh_parent_nodes_for_path(vim.fn.fnamemodify(path_real, ":h"))
end
local line = core.get_nodes_starting_line() local line = core.get_nodes_starting_line()
@@ -39,39 +42,56 @@ function M.fn(fname)
local found = Iterator.builder(core.get_explorer().nodes) local found = Iterator.builder(core.get_explorer().nodes)
:matcher(function(node) :matcher(function(node)
return node.absolute_path == fname_real or node.link_to == fname_real return node.absolute_path == path_real or node.link_to == path_real
end) end)
:applier(function(node) :applier(function(node)
line = line + 1 local incremented_line = false
if not node.group_next then
line = line + 1
incremented_line = true
end
if vim.tbl_contains(absolute_paths_searched, node.absolute_path) then if vim.tbl_contains(absolute_paths_searched, node.absolute_path) then
return return
end end
table.insert(absolute_paths_searched, node.absolute_path) table.insert(absolute_paths_searched, node.absolute_path)
local abs_match = vim.startswith(fname_real, node.absolute_path .. utils.path_separator) local abs_match = vim.startswith(path_real, node.absolute_path .. utils.path_separator)
local link_match = node.link_to and vim.startswith(fname_real, node.link_to .. utils.path_separator) local link_match = node.link_to and vim.startswith(path_real, node.link_to .. utils.path_separator)
if abs_match or link_match then if abs_match or link_match then
node.open = true local dir = node:as(DirectoryNode)
if #node.nodes == 0 then if dir then
core.get_explorer():expand(node) if not dir.group_next then
dir.open = true
end
if #dir.nodes == 0 then
core.get_explorer():expand(dir)
if dir.group_next and incremented_line then
line = line - 1
end
end
end end
end end
end) end)
:recursor(function(node) :recursor(function(node)
return node.open and node.nodes node = node and node:as(DirectoryNode)
if node then
return node.group_next and { node.group_next } or (node.open and #node.nodes > 0 and node.nodes)
else
return nil
end
end) end)
:iterate() :iterate()
if found and view.is_visible() then if found and view.is_visible() then
renderer.draw() explorer.renderer:draw()
view.set_cursor { line, 0 } view.set_cursor({ line, 0 })
end end
running[fname_real] = false running[path_real] = false
log.profile_end(ps, "find file %s", fname_real) log.profile_end(profile)
end end
return M return M

View File

@@ -0,0 +1,6 @@
local M = {}
M.find_file = require("nvim-tree.actions.finders.find-file")
M.search_node = require("nvim-tree.actions.finders.search-node")
return M

View File

@@ -1,20 +1,29 @@
local core = require "nvim-tree.core" local core = require("nvim-tree.core")
local filters = require "nvim-tree.explorer.filters"
local find_file = require("nvim-tree.actions.finders.find-file").fn local find_file = require("nvim-tree.actions.finders.find-file").fn
local M = {} local M = {}
---@param search_dir string|nil
---@param input_path string
---@return string|nil
local function search(search_dir, input_path) local function search(search_dir, input_path)
local realpaths_searched = {} local realpaths_searched = {}
local explorer = core.get_explorer()
if not explorer then
return
end
if not search_dir then if not search_dir then
return return
end end
---@param dir string
---@return string|nil
local function iter(dir) local function iter(dir)
local realpath, path, name, stat, handle, _ local realpath, path, name, stat, handle, _
local filter_status = filters.prepare() local filter_status = explorer.filters:prepare()
handle, _ = vim.loop.fs_scandir(dir) handle, _ = vim.loop.fs_scandir(dir)
if not handle then if not handle then
@@ -31,12 +40,13 @@ local function search(search_dir, input_path)
while name do while name do
path = dir .. "/" .. name path = dir .. "/" .. name
---@type uv.fs_stat.result|nil
stat, _ = vim.loop.fs_stat(path) stat, _ = vim.loop.fs_stat(path)
if not stat then if not stat then
break break
end end
if not filters.should_filter(path, filter_status) then if not explorer.filters:should_filter(path, stat, filter_status) then
if string.find(path, "/" .. input_path .. "$") then if string.find(path, "/" .. input_path .. "$") then
return path return path
end end
@@ -63,8 +73,15 @@ function M.fn()
-- temporarily set &path -- temporarily set &path
local bufnr = vim.api.nvim_get_current_buf() local bufnr = vim.api.nvim_get_current_buf()
local path_existed, path_opt = pcall(vim.api.nvim_buf_get_option, bufnr, "path")
vim.api.nvim_buf_set_option(bufnr, "path", core.get_cwd() .. "/**") local path_existed, path_opt
if vim.fn.has("nvim-0.10") == 1 then
path_existed, path_opt = pcall(vim.api.nvim_get_option_value, "path", { buf = bufnr })
vim.api.nvim_set_option_value("path", core.get_cwd() .. "/**", { buf = bufnr })
else
path_existed, path_opt = pcall(vim.api.nvim_buf_get_option, bufnr, "path") ---@diagnostic disable-line: deprecated
vim.api.nvim_buf_set_option(bufnr, "path", core.get_cwd() .. "/**") ---@diagnostic disable-line: deprecated
end
vim.ui.input({ prompt = "Search: ", completion = "file_in_path" }, function(input_path) vim.ui.input({ prompt = "Search: ", completion = "file_in_path" }, function(input_path)
if not input_path or input_path == "" then if not input_path or input_path == "" then
@@ -72,9 +89,17 @@ function M.fn()
end end
-- reset &path -- reset &path
if path_existed then if path_existed then
vim.api.nvim_buf_set_option(bufnr, "path", path_opt) if vim.fn.has("nvim-0.10") == 1 then
vim.api.nvim_set_option_value("path", path_opt, { buf = bufnr })
else
vim.api.nvim_buf_set_option(bufnr, "path", path_opt) ---@diagnostic disable-line: deprecated
end
else else
vim.api.nvim_buf_set_option(bufnr, "path", nil) if vim.fn.has("nvim-0.10") == 1 then
vim.api.nvim_set_option_value("path", nil, { buf = bufnr })
else
vim.api.nvim_buf_set_option(bufnr, "path", nil) ---@diagnostic disable-line: deprecated
end
end end
-- strip trailing slash -- strip trailing slash

View File

@@ -0,0 +1,391 @@
local lib = require("nvim-tree.lib")
local log = require("nvim-tree.log")
local utils = require("nvim-tree.utils")
local core = require("nvim-tree.core")
local events = require("nvim-tree.events")
local notify = require("nvim-tree.notify")
local find_file = require("nvim-tree.actions.finders.find-file").fn
local Class = require("nvim-tree.classic")
local DirectoryNode = require("nvim-tree.node.directory")
---@alias ClipboardAction "copy" | "cut"
---@alias ClipboardData table<ClipboardAction, Node[]>
---@alias ClipboardActionFn fun(source: string, dest: string): boolean, string?
---@class (exact) Clipboard: Class
---@field private explorer Explorer
---@field private data ClipboardData
---@field private clipboard_name string
---@field private reg string
local Clipboard = Class:extend()
---@class Clipboard
---@overload fun(args: ClipboardArgs): Clipboard
---@class (exact) ClipboardArgs
---@field explorer Explorer
---@protected
---@param args ClipboardArgs
function Clipboard:new(args)
self.explorer = args.explorer
self.data = {
copy = {},
cut = {},
}
self.clipboard_name = self.explorer.opts.actions.use_system_clipboard and "system" or "neovim"
self.reg = self.explorer.opts.actions.use_system_clipboard and "+" or "1"
end
---@param source string
---@param destination string
---@return boolean
---@return string|nil
local function do_copy(source, destination)
local source_stats, err = vim.loop.fs_stat(source)
if not source_stats then
log.line("copy_paste", "do_copy fs_stat '%s' failed '%s'", source, err)
return false, err
end
log.line("copy_paste", "do_copy %s '%s' -> '%s'", source_stats.type, source, destination)
if source == destination then
log.line("copy_paste", "do_copy source and destination are the same, exiting early")
return true
end
if source_stats.type == "file" then
local success
success, err = vim.loop.fs_copyfile(source, destination)
if not success then
log.line("copy_paste", "do_copy fs_copyfile failed '%s'", err)
return false, err
end
return true
elseif source_stats.type == "directory" then
local handle
handle, err = vim.loop.fs_scandir(source)
if type(handle) == "string" then
return false, handle
elseif not handle then
log.line("copy_paste", "do_copy fs_scandir '%s' failed '%s'", source, err)
return false, err
end
local success
success, err = vim.loop.fs_mkdir(destination, source_stats.mode)
if not success then
log.line("copy_paste", "do_copy fs_mkdir '%s' failed '%s'", destination, err)
return false, err
end
while true do
local name, _ = vim.loop.fs_scandir_next(handle)
if not name then
break
end
local new_name = utils.path_join({ source, name })
local new_destination = utils.path_join({ destination, name })
success, err = do_copy(new_name, new_destination)
if not success then
return false, err
end
end
else
err = string.format("'%s' illegal file type '%s'", source, source_stats.type)
log.line("copy_paste", "do_copy %s", err)
return false, err
end
return true
end
---@param source string
---@param dest string
---@param action ClipboardAction
---@param action_fn ClipboardActionFn
---@return boolean|nil -- success
---@return string|nil -- error message
local function do_single_paste(source, dest, action, action_fn)
local notify_source = notify.render_path(source)
log.line("copy_paste", "do_single_paste '%s' -> '%s'", source, dest)
local dest_stats, err, err_name = vim.loop.fs_stat(dest)
if not dest_stats and err_name ~= "ENOENT" then
notify.error("Could not " .. action .. " " .. notify_source .. " - " .. (err or "???"))
return false, err
end
local function on_process()
local success, error = action_fn(source, dest)
if not success then
notify.error("Could not " .. action .. " " .. notify_source .. " - " .. (error or "???"))
return false, error
end
find_file(utils.path_remove_trailing(dest))
end
if dest_stats then
local input_opts = {
prompt = "Rename to ",
default = dest,
completion = "dir",
}
if source == dest then
vim.ui.input(input_opts, function(new_dest)
utils.clear_prompt()
if new_dest then
do_single_paste(source, new_dest, action, action_fn)
end
end)
else
local prompt_select = "Overwrite " .. dest .. " ?"
local prompt_input = prompt_select .. " R(ename)/y/n: "
lib.prompt(prompt_input, prompt_select, { "", "y", "n" }, { "Rename", "Yes", "No" }, "nvimtree_overwrite_rename", function(item_short)
utils.clear_prompt()
if item_short == "y" then
on_process()
elseif item_short == "" or item_short == "r" then
vim.ui.input(input_opts, function(new_dest)
utils.clear_prompt()
if new_dest then
do_single_paste(source, new_dest, action, action_fn)
end
end)
end
end)
end
else
on_process()
end
end
---@param node Node
---@param clip ClipboardData
local function toggle(node, clip)
if node.name == ".." then
return
end
local notify_node = notify.render_path(node.absolute_path)
if utils.array_remove(clip, node) then
notify.info(notify_node .. " removed from clipboard.")
return
end
table.insert(clip, node)
notify.info(notify_node .. " added to clipboard.")
end
---Clear copied and cut
function Clipboard:clear_clipboard()
self.data.copy = {}
self.data.cut = {}
notify.info("Clipboard has been emptied.")
self.explorer.renderer:draw()
end
---Copy one node
---@param node Node
function Clipboard:copy(node)
utils.array_remove(self.data.cut, node)
toggle(node, self.data.copy)
self.explorer.renderer:draw()
end
---Cut one node
---@param node Node
function Clipboard:cut(node)
utils.array_remove(self.data.copy, node)
toggle(node, self.data.cut)
self.explorer.renderer:draw()
end
---Paste cut or cop
---@private
---@param node Node
---@param action ClipboardAction
---@param action_fn ClipboardActionFn
function Clipboard:do_paste(node, action, action_fn)
if node.name == ".." then
node = self.explorer
else
local dir = node:as(DirectoryNode)
if dir then
node = dir:last_group_node()
end
end
local clip = self.data[action]
if #clip == 0 then
return
end
local destination = node.absolute_path
local stats, err, err_name = vim.loop.fs_stat(destination)
if not stats and err_name ~= "ENOENT" then
log.line("copy_paste", "do_paste fs_stat '%s' failed '%s'", destination, err)
notify.error("Could not " .. action .. " " .. notify.render_path(destination) .. " - " .. (err or "???"))
return
end
local is_dir = stats and stats.type == "directory"
if not is_dir then
destination = vim.fn.fnamemodify(destination, ":p:h")
end
for _, _node in ipairs(clip) do
local dest = utils.path_join({ destination, _node.name })
do_single_paste(_node.absolute_path, dest, action, action_fn)
end
self.data[action] = {}
if not self.explorer.opts.filesystem_watchers.enable then
self.explorer:reload_explorer()
end
end
---@param source string
---@param destination string
---@return boolean
---@return string?
local function do_cut(source, destination)
log.line("copy_paste", "do_cut '%s' -> '%s'", source, destination)
if source == destination then
log.line("copy_paste", "do_cut source and destination are the same, exiting early")
return true
end
events._dispatch_will_rename_node(source, destination)
local success, errmsg = vim.loop.fs_rename(source, destination)
if not success then
log.line("copy_paste", "do_cut fs_rename failed '%s'", errmsg)
return false, errmsg
end
utils.rename_loaded_buffers(source, destination)
events._dispatch_node_renamed(source, destination)
return true
end
---Paste cut (if present) or copy (if present)
---@param node Node
function Clipboard:paste(node)
if self.data.cut[1] ~= nil then
self:do_paste(node, "cut", do_cut)
elseif self.data.copy[1] ~= nil then
self:do_paste(node, "copy", do_copy)
end
end
function Clipboard:print_clipboard()
local content = {}
if #self.data.cut > 0 then
table.insert(content, "Cut")
for _, node in pairs(self.data.cut) do
table.insert(content, " * " .. (notify.render_path(node.absolute_path)))
end
end
if #self.data.copy > 0 then
table.insert(content, "Copy")
for _, node in pairs(self.data.copy) do
table.insert(content, " * " .. (notify.render_path(node.absolute_path)))
end
end
notify.info(table.concat(content, "\n") .. "\n")
end
---@param content string
function Clipboard:copy_to_reg(content)
-- manually firing TextYankPost does not set vim.v.event
-- workaround: create a scratch buffer with the clipboard contents and send a yank command
local temp_buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_text(temp_buf, 0, 0, 0, 0, { content })
vim.api.nvim_buf_call(temp_buf, function()
vim.cmd(string.format('normal! "%sy$', self.reg))
end)
vim.api.nvim_buf_delete(temp_buf, {})
notify.info(string.format("Copied %s to %s clipboard!", content, self.clipboard_name))
end
---@param node Node
function Clipboard:copy_filename(node)
if node.name == ".." then
-- root
self:copy_to_reg(vim.fn.fnamemodify(self.explorer.absolute_path, ":t"))
else
-- node
self:copy_to_reg(node.name)
end
end
---@param node Node
function Clipboard:copy_basename(node)
if node.name == ".." then
-- root
self:copy_to_reg(vim.fn.fnamemodify(self.explorer.absolute_path, ":t:r"))
else
-- node
self:copy_to_reg(vim.fn.fnamemodify(node.name, ":r"))
end
end
---@param node Node
function Clipboard:copy_path(node)
if node.name == ".." then
-- root
self:copy_to_reg(utils.path_add_trailing(""))
else
-- node
local absolute_path = node.absolute_path
local cwd = core.get_cwd()
if cwd == nil then
return
end
local relative_path = utils.path_relative(absolute_path, cwd)
if node:is(DirectoryNode) then
self:copy_to_reg(utils.path_add_trailing(relative_path))
else
self:copy_to_reg(relative_path)
end
end
end
---@param node Node
function Clipboard:copy_absolute_path(node)
if node.name == ".." then
node = self.explorer
end
local absolute_path = node.absolute_path
local content = node.nodes ~= nil and utils.path_add_trailing(absolute_path) or absolute_path
self:copy_to_reg(content)
end
---Node is cut. Will not be copied.
---@param node Node
---@return boolean
function Clipboard:is_cut(node)
return vim.tbl_contains(self.data.cut, node)
end
---Node is copied. Will not be cut.
---@param node Node
---@return boolean
function Clipboard:is_copied(node)
return vim.tbl_contains(self.data.copy, node)
end
return Clipboard

View File

@@ -1,258 +0,0 @@
local lib = require "nvim-tree.lib"
local log = require "nvim-tree.log"
local utils = require "nvim-tree.utils"
local core = require "nvim-tree.core"
local events = require "nvim-tree.events"
local notify = require "nvim-tree.notify"
local M = {}
local clipboard = {
move = {},
copy = {},
}
local function do_copy(source, destination)
local source_stats, handle
local success, errmsg
source_stats, errmsg = vim.loop.fs_stat(source)
if not source_stats then
log.line("copy_paste", "do_copy fs_stat '%s' failed '%s'", source, errmsg)
return false, errmsg
end
log.line("copy_paste", "do_copy %s '%s' -> '%s'", source_stats.type, source, destination)
if source == destination then
log.line("copy_paste", "do_copy source and destination are the same, exiting early")
return true
end
if source_stats.type == "file" then
success, errmsg = vim.loop.fs_copyfile(source, destination)
if not success then
log.line("copy_paste", "do_copy fs_copyfile failed '%s'", errmsg)
return false, errmsg
end
return true
elseif source_stats.type == "directory" then
handle, errmsg = vim.loop.fs_scandir(source)
if type(handle) == "string" then
return false, handle
elseif not handle then
log.line("copy_paste", "do_copy fs_scandir '%s' failed '%s'", source, errmsg)
return false, errmsg
end
success, errmsg = vim.loop.fs_mkdir(destination, source_stats.mode)
if not success then
log.line("copy_paste", "do_copy fs_mkdir '%s' failed '%s'", destination, errmsg)
return false, errmsg
end
while true do
local name, _ = vim.loop.fs_scandir_next(handle)
if not name then
break
end
local new_name = utils.path_join { source, name }
local new_destination = utils.path_join { destination, name }
success, errmsg = do_copy(new_name, new_destination)
if not success then
return false, errmsg
end
end
else
errmsg = string.format("'%s' illegal file type '%s'", source, source_stats.type)
log.line("copy_paste", "do_copy %s", errmsg)
return false, errmsg
end
return true
end
local function do_single_paste(source, dest, action_type, action_fn)
local dest_stats
local success, errmsg, errcode
log.line("copy_paste", "do_single_paste '%s' -> '%s'", source, dest)
dest_stats, errmsg, errcode = vim.loop.fs_stat(dest)
if not dest_stats and errcode ~= "ENOENT" then
notify.error("Could not " .. action_type .. " " .. source .. " - " .. (errmsg or "???"))
return false, errmsg
end
local function on_process()
success, errmsg = action_fn(source, dest)
if not success then
notify.error("Could not " .. action_type .. " " .. source .. " - " .. (errmsg or "???"))
return false, errmsg
end
end
if dest_stats then
local prompt_select = "Overwrite " .. dest .. " ?"
local prompt_input = prompt_select .. " y/n/r(ename): "
lib.prompt(prompt_input, prompt_select, { "y", "n", "r" }, { "Yes", "No", "Rename" }, function(item_short)
utils.clear_prompt()
if item_short == "y" then
on_process()
elseif item_short == "r" then
vim.ui.input({ prompt = "Rename to ", default = dest, completion = "dir" }, function(new_dest)
utils.clear_prompt()
if new_dest then
do_single_paste(source, new_dest, action_type, action_fn)
end
end)
end
end)
else
on_process()
end
end
local function add_to_clipboard(node, clip)
if node.name == ".." then
return
end
for idx, _node in ipairs(clip) do
if _node.absolute_path == node.absolute_path then
table.remove(clip, idx)
return notify.info(node.absolute_path .. " removed to clipboard.")
end
end
table.insert(clip, node)
notify.info(node.absolute_path .. " added to clipboard.")
end
function M.clear_clipboard()
clipboard.move = {}
clipboard.copy = {}
utils.notify.info "Clipboard has been emptied."
end
function M.copy(node)
add_to_clipboard(node, clipboard.copy)
end
function M.cut(node)
add_to_clipboard(node, clipboard.move)
end
local function do_paste(node, action_type, action_fn)
node = lib.get_last_group_node(node)
if node.name == ".." then
node = core.get_explorer()
end
local clip = clipboard[action_type]
if #clip == 0 then
return
end
local destination = node.absolute_path
local stats, errmsg, errcode = vim.loop.fs_stat(destination)
if not stats and errcode ~= "ENOENT" then
log.line("copy_paste", "do_paste fs_stat '%s' failed '%s'", destination, errmsg)
notify.error("Could not " .. action_type .. " " .. destination .. " - " .. (errmsg or "???"))
return
end
local is_dir = stats and stats.type == "directory"
if not is_dir then
destination = vim.fn.fnamemodify(destination, ":p:h")
end
for _, _node in ipairs(clip) do
local dest = utils.path_join { destination, _node.name }
do_single_paste(_node.absolute_path, dest, action_type, action_fn)
end
clipboard[action_type] = {}
if M.enable_reload then
return require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
end
end
local function do_cut(source, destination)
log.line("copy_paste", "do_cut '%s' -> '%s'", source, destination)
if source == destination then
log.line("copy_paste", "do_cut source and destination are the same, exiting early")
return true
end
events._dispatch_will_rename_node(source, destination)
local success, errmsg = vim.loop.fs_rename(source, destination)
if not success then
log.line("copy_paste", "do_cut fs_rename failed '%s'", errmsg)
return false, errmsg
end
utils.rename_loaded_buffers(source, destination)
events._dispatch_node_renamed(source, destination)
return true
end
function M.paste(node)
if clipboard.move[1] ~= nil then
return do_paste(node, "move", do_cut)
end
return do_paste(node, "copy", do_copy)
end
function M.print_clipboard()
local content = {}
if #clipboard.move > 0 then
table.insert(content, "Cut")
for _, item in pairs(clipboard.move) do
table.insert(content, " * " .. item.absolute_path)
end
end
if #clipboard.copy > 0 then
table.insert(content, "Copy")
for _, item in pairs(clipboard.copy) do
table.insert(content, " * " .. item.absolute_path)
end
end
return notify.info(table.concat(content, "\n") .. "\n")
end
local function copy_to_clipboard(content)
if M.use_system_clipboard == true then
vim.fn.setreg("+", content)
vim.fn.setreg('"', content)
return notify.info(string.format("Copied %s to system clipboard!", content))
else
vim.fn.setreg('"', content)
vim.fn.setreg("1", content)
return notify.info(string.format("Copied %s to neovim clipboard!", content))
end
end
function M.copy_filename(node)
return copy_to_clipboard(node.name)
end
function M.copy_path(node)
local absolute_path = node.absolute_path
local relative_path = utils.path_relative(absolute_path, core.get_cwd())
local content = node.nodes ~= nil and utils.path_add_trailing(relative_path) or relative_path
return copy_to_clipboard(content)
end
function M.copy_absolute_path(node)
local absolute_path = node.absolute_path
local content = node.nodes ~= nil and utils.path_add_trailing(absolute_path) or absolute_path
return copy_to_clipboard(content)
end
function M.setup(opts)
M.use_system_clipboard = opts.actions.use_system_clipboard
M.enable_reload = not opts.filesystem_watchers.enable
end
return M

View File

@@ -1,38 +1,29 @@
local utils = require "nvim-tree.utils" local utils = require("nvim-tree.utils")
local events = require "nvim-tree.events" local events = require("nvim-tree.events")
local lib = require "nvim-tree.lib" local core = require("nvim-tree.core")
local core = require "nvim-tree.core" local notify = require("nvim-tree.notify")
local notify = require "nvim-tree.notify"
local find_file = require("nvim-tree.actions.finders.find-file").fn local find_file = require("nvim-tree.actions.finders.find-file").fn
local FileNode = require("nvim-tree.node.file")
local DirectoryNode = require("nvim-tree.node.directory")
local M = {} local M = {}
---@param file string
local function create_and_notify(file) local function create_and_notify(file)
events._dispatch_will_create_file(file)
local ok, fd = pcall(vim.loop.fs_open, file, "w", 420) local ok, fd = pcall(vim.loop.fs_open, file, "w", 420)
if not ok then if not ok or type(fd) ~= "number" then
notify.error("Couldn't create file " .. file) notify.error("Couldn't create file " .. notify.render_path(file))
return return
end end
vim.loop.fs_close(fd) vim.loop.fs_close(fd)
events._dispatch_file_created(file) events._dispatch_file_created(file)
end end
local function create_file(file) ---@param iter function iterable
if utils.file_exists(file) then ---@return integer
local prompt_select = "Overwrite " .. file .. " ?"
local prompt_input = prompt_select .. " y/n: "
lib.prompt(prompt_input, prompt_select, { "y", "n" }, { "Yes", "No" }, function(item_short)
utils.clear_prompt()
if item_short == "y" then
create_and_notify(file)
end
end)
else
create_and_notify(file)
end
end
local function get_num_nodes(iter) local function get_num_nodes(iter)
local i = 0 local i = 0
for _ in iter do for _ in iter do
@@ -41,27 +32,27 @@ local function get_num_nodes(iter)
return i return i
end end
local function get_containing_folder(node) ---@param node Node?
if node.nodes ~= nil then
return utils.path_add_trailing(node.absolute_path)
end
local node_name_size = #(node.name or "")
return node.absolute_path:sub(0, -node_name_size - 1)
end
function M.fn(node) function M.fn(node)
node = node and lib.get_last_group_node(node) node = node or core.get_explorer()
if not node or node.name == ".." then if not node then
node = { return
absolute_path = core.get_cwd(),
nodes = core.get_explorer().nodes,
open = true,
}
end end
local containing_folder = get_containing_folder(node) local dir = node:is(FileNode) and node.parent or node:as(DirectoryNode)
if not dir then
return
end
local input_opts = { prompt = "Create file ", default = containing_folder, completion = "file" } dir = dir:last_group_node()
local containing_folder = utils.path_add_trailing(dir.absolute_path)
local input_opts = {
prompt = "Create file ",
default = containing_folder,
completion = "file",
}
vim.ui.input(input_opts, function(new_file_path) vim.ui.input(input_opts, function(new_file_path)
utils.clear_prompt() utils.clear_prompt()
@@ -70,7 +61,7 @@ function M.fn(node)
end end
if utils.file_exists(new_file_path) then if utils.file_exists(new_file_path) then
notify.warn "Cannot create: file already exists" notify.warn("Cannot create: file already exists")
return return
end end
@@ -85,17 +76,17 @@ function M.fn(node)
for path in utils.path_split(new_file_path) do for path in utils.path_split(new_file_path) do
idx = idx + 1 idx = idx + 1
local p = utils.path_remove_trailing(path) local p = utils.path_remove_trailing(path)
if #path_to_create == 0 and vim.fn.has "win32" == 1 then if #path_to_create == 0 and vim.fn.has("win32") == 1 then
path_to_create = utils.path_join { p, path_to_create } path_to_create = utils.path_join({ p, path_to_create })
else else
path_to_create = utils.path_join { path_to_create, p } path_to_create = utils.path_join({ path_to_create, p })
end end
if is_last_path_file and idx == num_nodes then if is_last_path_file and idx == num_nodes then
create_file(path_to_create) create_and_notify(path_to_create)
elseif not utils.file_exists(path_to_create) then elseif not utils.file_exists(path_to_create) then
local success = vim.loop.fs_mkdir(path_to_create, 493) local success = vim.loop.fs_mkdir(path_to_create, 493)
if not success then if not success then
notify.error("Could not create folder " .. path_to_create) notify.error("Could not create folder " .. notify.render_path(path_to_create))
is_error = true is_error = true
break break
end end
@@ -103,7 +94,7 @@ function M.fn(node)
end end
end end
if not is_error then if not is_error then
notify.info(new_file_path .. " was properly created") notify.info(notify.render_path(new_file_path) .. " was properly created")
end end
-- synchronously refreshes as we can't wait for the watchers -- synchronously refreshes as we can't wait for the watchers
@@ -111,8 +102,4 @@ function M.fn(node)
end) end)
end end
function M.setup(opts)
M.enable_reload = not opts.filesystem_watchers.enable
end
return M return M

View File

@@ -0,0 +1,14 @@
local M = {}
M.create_file = require("nvim-tree.actions.fs.create-file")
M.remove_file = require("nvim-tree.actions.fs.remove-file")
M.rename_file = require("nvim-tree.actions.fs.rename-file")
M.trash = require("nvim-tree.actions.fs.trash")
function M.setup(opts)
M.remove_file.setup(opts)
M.rename_file.setup(opts)
M.trash.setup(opts)
end
return M

View File

@@ -1,13 +1,23 @@
local utils = require "nvim-tree.utils" local core = require("nvim-tree.core")
local events = require "nvim-tree.events" local utils = require("nvim-tree.utils")
local view = require "nvim-tree.view" local events = require("nvim-tree.events")
local lib = require "nvim-tree.lib" local view = require("nvim-tree.view")
local notify = require "nvim-tree.notify" local lib = require("nvim-tree.lib")
local notify = require("nvim-tree.notify")
local M = {} local DirectoryLinkNode = require("nvim-tree.node.directory-link")
local DirectoryNode = require("nvim-tree.node.directory")
local M = {
config = {},
}
---@param windows integer[]
local function close_windows(windows) local function close_windows(windows)
if view.View.float.enable and #vim.api.nvim_list_wins() == 1 then -- Prevent from closing when the win count equals 1 or 2,
-- where the win to remove could be the last opened.
-- For details see #2503.
if view.View.float.enable and #vim.api.nvim_list_wins() < 3 then
return return
end end
@@ -18,20 +28,21 @@ local function close_windows(windows)
end end
end end
---@param absolute_path string
local function clear_buffer(absolute_path) local function clear_buffer(absolute_path)
local bufs = vim.fn.getbufinfo { bufloaded = 1, buflisted = 1 } local bufs = vim.fn.getbufinfo({ bufloaded = 1, buflisted = 1 })
for _, buf in pairs(bufs) do for _, buf in pairs(bufs) do
if buf.name == absolute_path then if buf.name == absolute_path then
local tree_winnr = vim.api.nvim_get_current_win()
if buf.hidden == 0 and (#bufs > 1 or view.View.float.enable) then if buf.hidden == 0 and (#bufs > 1 or view.View.float.enable) then
local winnr = vim.api.nvim_get_current_win()
vim.api.nvim_set_current_win(buf.windows[1]) vim.api.nvim_set_current_win(buf.windows[1])
vim.cmd ":bn" vim.cmd(":bn")
if not view.View.float.enable then
vim.api.nvim_set_current_win(winnr)
end
end end
vim.api.nvim_buf_delete(buf.bufnr, { force = true }) vim.api.nvim_buf_delete(buf.bufnr, { force = true })
if M.close_window then if not view.View.float.quit_on_focus_loss then
vim.api.nvim_set_current_win(tree_winnr)
end
if M.config.actions.remove_file.close_window then
close_windows(buf.windows) close_windows(buf.windows)
end end
return return
@@ -39,20 +50,35 @@ local function clear_buffer(absolute_path)
end end
end end
---@param cwd string
---@return boolean|nil
local function remove_dir(cwd) local function remove_dir(cwd)
local handle = vim.loop.fs_scandir(cwd) local handle, err = vim.loop.fs_scandir(cwd)
if type(handle) == "string" then if not handle then
return notify.error(handle) notify.error(err)
return
end end
while true do while true do
local name, t = vim.loop.fs_scandir_next(handle) local name, _ = vim.loop.fs_scandir_next(handle)
if not name then if not name then
break break
end end
local new_cwd = utils.path_join { cwd, name } local new_cwd = utils.path_join({ cwd, name })
if t == "directory" then
-- Type must come from fs_stat and not fs_scandir_next to maintain sshfs compatibility
local stat = vim.loop.fs_stat(new_cwd)
-- 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)
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
@@ -69,40 +95,73 @@ local function remove_dir(cwd)
return vim.loop.fs_rmdir(cwd) return vim.loop.fs_rmdir(cwd)
end end
--- Remove a node, notify errors, dispatch events
---@param node Node
function M.remove(node)
local notify_node = notify.render_path(node.absolute_path)
if node:is(DirectoryNode) and not node:is(DirectoryLinkNode) then
local success = remove_dir(node.absolute_path)
if not success then
notify.error("Could not remove " .. notify_node)
return
end
events._dispatch_folder_removed(node.absolute_path)
else
events._dispatch_will_remove_file(node.absolute_path)
local success = vim.loop.fs_unlink(node.absolute_path)
if not success then
notify.error("Could not remove " .. notify_node)
return
end
events._dispatch_file_removed(node.absolute_path)
clear_buffer(node.absolute_path)
end
notify.info(notify_node .. " was properly removed.")
end
---@param node Node
function M.fn(node) function M.fn(node)
if node.name == ".." then if node.name == ".." then
return return
end end
local prompt_select = "Remove " .. node.name .. " ?"
local prompt_input = prompt_select .. " y/n: " local function do_remove()
lib.prompt(prompt_input, prompt_select, { "y", "n" }, { "Yes", "No" }, function(item_short) M.remove(node)
utils.clear_prompt() local explorer = core.get_explorer()
if item_short == "y" then if not M.config.filesystem_watchers.enable and explorer then
if node.nodes ~= nil and not node.link_to then explorer:reload_explorer()
local success = remove_dir(node.absolute_path)
if not success then
return notify.error("Could not remove " .. node.name)
end
events._dispatch_folder_removed(node.absolute_path)
else
local success = vim.loop.fs_unlink(node.absolute_path)
if not success then
return notify.error("Could not remove " .. node.name)
end
events._dispatch_file_removed(node.absolute_path)
clear_buffer(node.absolute_path)
end
notify.info(node.absolute_path .. " was properly removed.")
if M.enable_reload then
require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
end
end end
end) end
if M.config.ui.confirm.remove then
local prompt_select = "Remove " .. node.name .. "?"
local prompt_input, items_short, items_long
if M.config.ui.confirm.default_yes then
prompt_input = prompt_select .. " Y/n: "
items_short = { "", "n" }
items_long = { "Yes", "No" }
else
prompt_input = prompt_select .. " y/N: "
items_short = { "", "y" }
items_long = { "No", "Yes" }
end
lib.prompt(prompt_input, prompt_select, items_short, items_long, "nvimtree_remove", function(item_short)
utils.clear_prompt()
if item_short == "y" or item_short == (M.config.ui.confirm.default_yes and "") then
do_remove()
end
end)
else
do_remove()
end
end end
function M.setup(opts) function M.setup(opts)
M.enable_reload = not opts.filesystem_watchers.enable M.config.ui = opts.ui
M.close_window = opts.actions.remove_file.close_window M.config.actions = opts.actions
M.config.filesystem_watchers = opts.filesystem_watchers
end end
return M return M

View File

@@ -1,12 +1,29 @@
local lib = require "nvim-tree.lib" local core = require("nvim-tree.core")
local utils = require "nvim-tree.utils" local utils = require("nvim-tree.utils")
local events = require "nvim-tree.events" local events = require("nvim-tree.events")
local notify = require "nvim-tree.notify" local notify = require("nvim-tree.notify")
local M = {} local find_file = require("nvim-tree.actions.finders.find-file").fn
local DirectoryNode = require("nvim-tree.node.directory")
local M = {
config = {},
}
---@param iter function iterable
---@return integer
local function get_num_nodes(iter)
local i = 0
for _ in iter do
i = i + 1
end
return i
end
local ALLOWED_MODIFIERS = { local ALLOWED_MODIFIERS = {
[":p"] = true, [":p"] = true,
[":p:h"] = true,
[":t"] = true, [":t"] = true,
[":t:r"] = true, [":t:r"] = true,
} }
@@ -15,28 +32,87 @@ local function err_fmt(from, to, reason)
return string.format("Cannot rename %s -> %s: %s", from, to, reason) return string.format("Cannot rename %s -> %s: %s", from, to, reason)
end end
local function rename_file_exists(node, to)
if not utils.is_macos then
return utils.file_exists(to)
end
if string.lower(node) == string.lower(to) then
return false
end
return utils.file_exists(to)
end
---@param node Node
---@param to string
function M.rename(node, to) function M.rename(node, to)
if utils.file_exists(to) then local notify_from = notify.render_path(node.absolute_path)
notify.warn(err_fmt(node.absolute_path, to, "file already exists")) local notify_to = notify.render_path(to)
if rename_file_exists(notify_from, notify_to) then
notify.warn(err_fmt(notify_from, notify_to, "file already exists"))
return return
end end
events._dispatch_will_rename_node(node.absolute_path, to) -- create a folder for each path element if the folder does not exist
local success, err = vim.loop.fs_rename(node.absolute_path, to) local idx = 0
if not success then local path_to_create = ""
return notify.warn(err_fmt(node.absolute_path, to, err))
local num_nodes = get_num_nodes(utils.path_split(utils.path_remove_trailing(to)))
local is_error = false
for path in utils.path_split(to) do
idx = idx + 1
local p = utils.path_remove_trailing(path)
if #path_to_create == 0 and vim.fn.has("win32") == 1 then
path_to_create = utils.path_join({ p, path_to_create })
else
path_to_create = utils.path_join({ path_to_create, p })
end
if idx == num_nodes then
events._dispatch_will_rename_node(node.absolute_path, to)
local success, err = vim.loop.fs_rename(node.absolute_path, to)
if not success then
notify.warn(err_fmt(notify_from, notify_to, err))
return
end
elseif not rename_file_exists(notify_from, path_to_create) then
local success = vim.loop.fs_mkdir(path_to_create, 493)
if not success then
notify.error("Could not create folder " .. notify.render_path(path_to_create))
is_error = true
break
end
is_error = false
end
end
if not is_error then
notify.info(string.format("%s -> %s", notify_from, notify_to))
utils.rename_loaded_buffers(node.absolute_path, to)
events._dispatch_node_renamed(node.absolute_path, to)
end end
notify.info(node.absolute_path .. "" .. to)
utils.rename_loaded_buffers(node.absolute_path, to)
events._dispatch_node_renamed(node.absolute_path, to)
end end
---@param default_modifier string|nil
---@return fun(node: Node, modifier: string)
function M.fn(default_modifier) function M.fn(default_modifier)
default_modifier = default_modifier or ":t" default_modifier = default_modifier or ":t"
return function(node, modifier) return function(node, modifier)
local explorer = core.get_explorer()
if not explorer then
return
end
if type(node) ~= "table" then if type(node) ~= "table" then
node = lib.get_node_at_cursor() node = explorer:get_node_at_cursor()
end
if not node then
return
end end
if type(modifier) ~= "string" then if type(modifier) ~= "string" then
@@ -45,12 +121,14 @@ function M.fn(default_modifier)
-- support for only specific modifiers have been implemented -- support for only specific modifiers have been implemented
if not ALLOWED_MODIFIERS[modifier] then if not ALLOWED_MODIFIERS[modifier] then
return notify.warn( notify.warn("Modifier " .. vim.inspect(modifier) .. " is not in allowed list : " .. table.concat(ALLOWED_MODIFIERS, ","))
"Modifier " .. vim.inspect(modifier) .. " is not in allowed list : " .. table.concat(ALLOWED_MODIFIERS, ",") return
)
end end
node = lib.get_last_group_node(node) local dir = node:as(DirectoryNode)
if dir then
node = dir:last_group_node()
end
if node.name == ".." then if node.name == ".." then
return return
end end
@@ -68,8 +146,15 @@ function M.fn(default_modifier)
local extension = vim.fn.fnamemodify(node.name, ":e") local extension = vim.fn.fnamemodify(node.name, ":e")
append = extension:len() == 0 and "" or "." .. extension append = extension:len() == 0 and "" or "." .. extension
end end
if modifier == ":p:h" then
default_path = default_path .. "/"
end
local input_opts = { prompt = "Rename to ", default = default_path, completion = "file" } local input_opts = {
prompt = "Rename to ",
default = default_path,
completion = "file",
}
vim.ui.input(input_opts, function(new_file_path) vim.ui.input(input_opts, function(new_file_path)
utils.clear_prompt() utils.clear_prompt()
@@ -77,16 +162,20 @@ function M.fn(default_modifier)
return return
end end
M.rename(node, prepend .. new_file_path .. append) local full_new_path = prepend .. new_file_path .. append
if M.enable_reload then
require("nvim-tree.actions.reloaders.reloaders").reload_explorer() M.rename(node, full_new_path)
if not M.config.filesystem_watchers.enable then
explorer:reload_explorer()
end end
find_file(utils.path_remove_trailing(full_new_path))
end) end)
end end
end end
function M.setup(opts) function M.setup(opts)
M.enable_reload = not opts.filesystem_watchers.enable M.config.filesystem_watchers = opts.filesystem_watchers
end end
return M return M

View File

@@ -1,25 +1,26 @@
local lib = require "nvim-tree.lib" local core = require("nvim-tree.core")
local notify = require "nvim-tree.notify" local lib = require("nvim-tree.lib")
local notify = require("nvim-tree.notify")
local DirectoryLinkNode = require("nvim-tree.node.directory-link")
local DirectoryNode = require("nvim-tree.node.directory")
local M = { local M = {
config = { config = {},
is_windows = vim.fn.has "win32" == 1 or vim.fn.has "win32unix" == 1,
is_macos = vim.fn.has "mac" == 1 or vim.fn.has "macunix" == 1,
is_unix = vim.fn.has "unix" == 1,
},
} }
local utils = require "nvim-tree.utils" local utils = require("nvim-tree.utils")
local events = require "nvim-tree.events" local events = require("nvim-tree.events")
---@param absolute_path string
local function clear_buffer(absolute_path) local function clear_buffer(absolute_path)
local bufs = vim.fn.getbufinfo { bufloaded = 1, buflisted = 1 } local bufs = vim.fn.getbufinfo({ bufloaded = 1, buflisted = 1 })
for _, buf in pairs(bufs) do for _, buf in pairs(bufs) do
if buf.name == absolute_path then if buf.name == absolute_path then
if buf.hidden == 0 and #bufs > 1 then if buf.hidden == 0 and #bufs > 1 then
local winnr = vim.api.nvim_get_current_win() local winnr = vim.api.nvim_get_current_win()
vim.api.nvim_set_current_win(buf.windows[1]) vim.api.nvim_set_current_win(buf.windows[1])
vim.cmd ":bn" vim.cmd(":bn")
vim.api.nvim_set_current_win(winnr) vim.api.nvim_set_current_win(winnr)
end end
vim.api.nvim_buf_delete(buf.bufnr, {}) vim.api.nvim_buf_delete(buf.bufnr, {})
@@ -28,27 +29,11 @@ local function clear_buffer(absolute_path)
end end
end end
function M.fn(node) ---@param node Node
if node.name == ".." then function M.remove(node)
return
end
-- configs
if M.config.is_unix then
if M.config.trash.cmd == nil then
M.config.trash.cmd = "trash"
end
if M.config.trash.require_confirm == nil then
M.config.trash.require_confirm = true
end
else
notify.warn "Trash is currently a UNIX only feature!"
return
end
local binary = M.config.trash.cmd:gsub(" .*$", "") local binary = M.config.trash.cmd:gsub(" .*$", "")
if vim.fn.executable(binary) == 0 then if vim.fn.executable(binary) == 0 then
notify.warn(binary .. " is not executable.") notify.warn(string.format("trash.cmd '%s' is not an executable.", M.config.trash.cmd))
return return
end end
@@ -59,46 +44,73 @@ function M.fn(node)
-- trashes a path (file or folder) -- trashes a path (file or folder)
local function trash_path(on_exit) local function trash_path(on_exit)
vim.fn.jobstart(M.config.trash.cmd .. ' "' .. node.absolute_path .. '"', { local need_sync_wait = utils.is_windows
detach = true, local job = vim.fn.jobstart(M.config.trash.cmd .. " " .. vim.fn.shellescape(node.absolute_path), {
detach = not need_sync_wait,
on_exit = on_exit, on_exit = on_exit,
on_stderr = on_stderr, on_stderr = on_stderr,
}) })
end if need_sync_wait then
vim.fn.jobwait({ job })
local function do_trash()
if node.nodes ~= nil and not node.link_to then
trash_path(function(_, rc)
if rc ~= 0 then
notify.warn("trash failed: " .. err_msg .. "; please see :help nvim-tree.trash")
return
end
events._dispatch_folder_removed(node.absolute_path)
if M.enable_reload then
require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
end
end)
else
trash_path(function(_, rc)
if rc ~= 0 then
notify.warn("trash failed: " .. err_msg .. "; please see :help nvim-tree.trash")
return
end
events._dispatch_file_removed(node.absolute_path)
clear_buffer(node.absolute_path)
if M.enable_reload then
require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
end
end)
end end
end end
if M.config.trash.require_confirm then local explorer = core.get_explorer()
local prompt_select = "Trash " .. node.name .. " ?"
local prompt_input = prompt_select .. " y/n: " if node:is(DirectoryNode) and not node:is(DirectoryLinkNode) then
lib.prompt(prompt_input, prompt_select, { "y", "n" }, { "Yes", "No" }, function(item_short) trash_path(function(_, rc)
if rc ~= 0 then
notify.warn("trash failed: " .. err_msg .. "; please see :help nvim-tree.trash")
return
end
events._dispatch_folder_removed(node.absolute_path)
if not M.config.filesystem_watchers.enable and explorer then
explorer:reload_explorer()
end
end)
else
events._dispatch_will_remove_file(node.absolute_path)
trash_path(function(_, rc)
if rc ~= 0 then
notify.warn("trash failed: " .. err_msg .. "; please see :help nvim-tree.trash")
return
end
events._dispatch_file_removed(node.absolute_path)
clear_buffer(node.absolute_path)
if not M.config.filesystem_watchers.enable and explorer then
explorer:reload_explorer()
end
end)
end
end
---@param node Node
function M.fn(node)
if node.name == ".." then
return
end
local function do_trash()
M.remove(node)
end
if M.config.ui.confirm.trash then
local prompt_select = "Trash " .. node.name .. "?"
local prompt_input, items_short, items_long
if M.config.ui.confirm.default_yes then
prompt_input = prompt_select .. " Y/n: "
items_short = { "", "n" }
items_long = { "Yes", "No" }
else
prompt_input = prompt_select .. " y/N: "
items_short = { "", "y" }
items_long = { "No", "Yes" }
end
lib.prompt(prompt_input, prompt_select, items_short, items_long, "nvimtree_trash", function(item_short)
utils.clear_prompt() utils.clear_prompt()
if item_short == "y" then if item_short == "y" or item_short == (M.config.ui.confirm.default_yes and "") then
do_trash() do_trash()
end end
end) end)
@@ -108,8 +120,9 @@ function M.fn(node)
end end
function M.setup(opts) function M.setup(opts)
M.config.trash = opts.trash or {} M.config.ui = opts.ui
M.enable_reload = not opts.filesystem_watchers.enable M.config.trash = opts.trash
M.config.filesystem_watchers = opts.filesystem_watchers
end end
return M return M

View File

@@ -1,432 +1,17 @@
-- @deprecated: new implementation in nvim-tree.keymap. Please do not edit this file. local M = {}
local log = require "nvim-tree.log" M.finders = require("nvim-tree.actions.finders")
local view = require "nvim-tree.view" M.fs = require("nvim-tree.actions.fs")
local notify = require "nvim-tree.notify" M.moves = require("nvim-tree.actions.moves")
M.node = require("nvim-tree.actions.node")
-- BEGIN_DEFAULT_MAPPINGS M.root = require("nvim-tree.actions.root")
local DEFAULT_MAPPINGS = { M.tree = require("nvim-tree.actions.tree")
{
key = { "<CR>", "o", "<2-LeftMouse>" },
action = "edit",
desc = "open a file or folder; root will cd to the above directory",
},
{
key = "<C-e>",
action = "edit_in_place",
desc = "edit the file in place, effectively replacing the tree explorer",
},
{
key = "O",
action = "edit_no_picker",
desc = "same as (edit) with no window picker",
},
{
key = { "<C-]>", "<2-RightMouse>" },
action = "cd",
desc = "cd in the directory under the cursor",
},
{
key = "<C-v>",
action = "vsplit",
desc = "open the file in a vertical split",
},
{
key = "<C-x>",
action = "split",
desc = "open the file in a horizontal split",
},
{
key = "<C-t>",
action = "tabnew",
desc = "open the file in a new tab",
},
{
key = "<",
action = "prev_sibling",
desc = "navigate to the previous sibling of current file/directory",
},
{
key = ">",
action = "next_sibling",
desc = "navigate to the next sibling of current file/directory",
},
{
key = "P",
action = "parent_node",
desc = "move cursor to the parent directory",
},
{
key = "<BS>",
action = "close_node",
desc = "close current opened directory or parent",
},
{
key = "<Tab>",
action = "preview",
desc = "open the file as a preview (keeps the cursor in the tree)",
},
{
key = "K",
action = "first_sibling",
desc = "navigate to the first sibling of current file/directory",
},
{
key = "J",
action = "last_sibling",
desc = "navigate to the last sibling of current file/directory",
},
{
key = "C",
action = "toggle_git_clean",
desc = "toggle visibility of git clean via |filters.git_clean| option",
},
{
key = "I",
action = "toggle_git_ignored",
desc = "toggle visibility of files/folders hidden via |git.ignore| option",
},
{
key = "H",
action = "toggle_dotfiles",
desc = "toggle visibility of dotfiles via |filters.dotfiles| option",
},
{
key = "B",
action = "toggle_no_buffer",
desc = "toggle visibility of files/folders hidden via |filters.no_buffer| option",
},
{
key = "U",
action = "toggle_custom",
desc = "toggle visibility of files/folders hidden via |filters.custom| option",
},
{
key = "R",
action = "refresh",
desc = "refresh the tree",
},
{
key = "a",
action = "create",
desc = "add a file; leaving a trailing `/` will add a directory",
},
{
key = "d",
action = "remove",
desc = "delete a file (will prompt for confirmation)",
},
{
key = "D",
action = "trash",
desc = "trash a file via |trash| option",
},
{
key = "r",
action = "rename",
desc = "rename a file",
},
{
key = "<C-r>",
action = "full_rename",
desc = "rename a file and omit the filename on input",
},
{
key = "e",
action = "rename_basename",
desc = "rename a file with filename-modifiers ':t:r' without changing extension",
},
{
key = "x",
action = "cut",
desc = "add/remove file/directory to cut clipboard",
},
{
key = "c",
action = "copy",
desc = "add/remove file/directory to copy clipboard",
},
{
key = "p",
action = "paste",
desc = "paste from clipboard; cut clipboard has precedence over copy; will prompt for confirmation",
},
{
key = "y",
action = "copy_name",
desc = "copy name to system clipboard",
},
{
key = "Y",
action = "copy_path",
desc = "copy relative path to system clipboard",
},
{
key = "gy",
action = "copy_absolute_path",
desc = "copy absolute path to system clipboard",
},
{
key = "[e",
action = "prev_diag_item",
desc = "go to next diagnostic item",
},
{
key = "[c",
action = "prev_git_item",
desc = "go to next git item",
},
{
key = "]e",
action = "next_diag_item",
desc = "go to prev diagnostic item",
},
{
key = "]c",
action = "next_git_item",
desc = "go to prev git item",
},
{
key = "-",
action = "dir_up",
desc = "navigate up to the parent directory of the current file/directory",
},
{
key = "s",
action = "system_open",
desc = "open a file with default system application or a folder with default file manager, using |system_open| option",
},
{
key = "f",
action = "live_filter",
desc = "live filter nodes dynamically based on regex matching.",
},
{
key = "F",
action = "clear_live_filter",
desc = "clear live filter",
},
{
key = "q",
action = "close",
desc = "close tree window",
},
{
key = "W",
action = "collapse_all",
desc = "collapse the whole tree",
},
{
key = "E",
action = "expand_all",
desc = "expand the whole tree, stopping after expanding |actions.expand_all.max_folder_discovery| folders; this might hang neovim for a while if running on a big folder",
},
{
key = "S",
action = "search_node",
desc = "prompt the user to enter a path and then expands the tree to match the path",
},
{
key = ".",
action = "run_file_command",
desc = "enter vim command mode with the file the cursor is on",
},
{
key = "<C-k>",
action = "toggle_file_info",
desc = "toggle a popup with file infos about the file under the cursor",
},
{
key = "g?",
action = "toggle_help",
desc = "toggle help",
},
{
key = "m",
action = "toggle_mark",
desc = "Toggle node in bookmarks",
},
{
key = "bmv",
action = "bulk_move",
desc = "Move all bookmarked nodes into specified location",
},
}
-- END_DEFAULT_MAPPINGS
local M = {
mappings = {},
custom_keypress_funcs = {},
}
local function set_map_for(bufnr)
local opts = { noremap = true, silent = true, nowait = true, buffer = bufnr }
return function(mode, rhs)
return function(lhs)
vim.keymap.set(mode or "n", lhs, rhs, opts)
end
end
end
local function run_dispatch(action)
return function()
require("nvim-tree.actions.dispatch").dispatch(action)
end
end
function M.apply_mappings(bufnr)
local setter_for = set_map_for(bufnr)
for _, b in pairs(M.mappings) do
local rhs = b.cb or run_dispatch(b.action)
if rhs then
local setter = setter_for(b.mode, rhs)
local keys = type(b.key) == "table" and b.key or { b.key }
for _, key in pairs(keys) do
setter(key)
end
end
end
end
local function merge_mappings(user_mappings)
if #user_mappings == 0 then
return M.mappings
end
local function is_empty(s)
return s == ""
end
local user_keys = {}
local removed_keys = {}
-- remove default mappings if action is a empty string
for _, map in pairs(user_mappings) do
if type(map.key) == "table" then
for _, key in pairs(map.key) do
table.insert(user_keys, key)
if is_empty(map.action) then
table.insert(removed_keys, key)
end
end
else
table.insert(user_keys, map.key)
if is_empty(map.action) then
table.insert(removed_keys, map.key)
end
end
if map.action and type(map.action_cb) == "function" then
if not is_empty(map.action) then
M.custom_keypress_funcs[map.action] = map.action_cb
else
notify.warn "action can't be empty if action_cb provided"
end
end
end
local default_map = vim.tbl_filter(function(map)
if type(map.key) == "table" then
local filtered_keys = {}
for _, key in pairs(map.key) do
if not vim.tbl_contains(user_keys, key) and not vim.tbl_contains(removed_keys, key) then
table.insert(filtered_keys, key)
end
end
map.key = filtered_keys
return not vim.tbl_isempty(map.key)
else
return not vim.tbl_contains(user_keys, map.key) and not vim.tbl_contains(removed_keys, map.key)
end
end, M.mappings)
local user_map = vim.tbl_filter(function(map)
return not is_empty(map.action)
end, user_mappings)
return vim.fn.extend(default_map, user_map)
end
local function copy_mappings(user_mappings)
if #user_mappings == 0 then
return M.mappings
end
for _, map in pairs(user_mappings) do
if map.action and type(map.action_cb) == "function" then
M.custom_keypress_funcs[map.action] = map.action_cb
end
end
return user_mappings
end
local function cleanup_existing_mappings()
local bufnr = view.get_bufnr()
if bufnr == nil or not vim.api.nvim_buf_is_valid(bufnr) then
return
end
for _, b in pairs(M.mappings) do
local keys = type(b.key) == "table" and b.key or { b.key }
for _, key in pairs(keys) do
vim.keymap.del(b.mode or "n", key, { buffer = bufnr })
end
end
end
local function filter_mappings(mappings, keys)
if type(keys) == "boolean" and keys then
return {}
elseif type(keys) == "table" then
return vim.tbl_filter(function(m)
if type(m.key) == "table" then
m.key = vim.tbl_filter(function(k)
return not vim.tbl_contains(keys, k)
end, m.key)
return #m.key > 0
else
return not vim.tbl_contains(keys, m.key)
end
end, vim.deepcopy(mappings))
else
return vim.deepcopy(mappings)
end
end
local DEFAULT_MAPPING_CONFIG = {
custom_only = false,
list = {},
}
function M.setup(opts) function M.setup(opts)
require("nvim-tree.actions.fs.trash").setup(opts) M.fs.setup(opts)
require("nvim-tree.actions.node.system-open").setup(opts) M.node.setup(opts)
require("nvim-tree.actions.node.file-popup").setup(opts) M.root.setup(opts)
require("nvim-tree.actions.node.open-file").setup(opts) M.tree.setup(opts)
require("nvim-tree.actions.root.change-dir").setup(opts)
require("nvim-tree.actions.fs.create-file").setup(opts)
require("nvim-tree.actions.fs.rename-file").setup(opts)
require("nvim-tree.actions.fs.remove-file").setup(opts)
require("nvim-tree.actions.fs.copy-paste").setup(opts)
require("nvim-tree.actions.tree-modifiers.expand-all").setup(opts)
cleanup_existing_mappings()
M.mappings = filter_mappings(DEFAULT_MAPPINGS, opts.remove_keymaps)
local user_map_config = (opts.view or {}).mappings or {}
local options = vim.tbl_deep_extend("force", DEFAULT_MAPPING_CONFIG, user_map_config)
if options.custom_only then
M.mappings = copy_mappings(options.list)
else
M.mappings = merge_mappings(options.list)
end
require("nvim-tree.actions.dispatch").setup(M.custom_keypress_funcs)
log.line("config", "active mappings")
log.raw("config", "%s\n", vim.inspect(M.mappings))
end end
return M return M

View File

@@ -0,0 +1,7 @@
local M = {}
M.item = require("nvim-tree.actions.moves.item")
M.parent = require("nvim-tree.actions.moves.parent")
M.sibling = require("nvim-tree.actions.moves.sibling")
return M

View File

@@ -1,54 +1,245 @@
local utils = require "nvim-tree.utils" 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 lib = require "nvim-tree.lib" local diagnostics = require("nvim-tree.diagnostics")
local explorer_common = require "nvim-tree.explorer.common"
local FileNode = require("nvim-tree.node.file")
local DirectoryNode = require("nvim-tree.node.directory")
local M = {} local M = {}
local MAX_DEPTH = 100
function M.fn(where, what) ---Return the status of the node or nil if no status, depending on the type of
return function() ---status.
local node_cur = lib.get_node_at_cursor() ---@param node Node to inspect
local nodes_by_line = utils.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line()) ---@param what string? type of status
---@param skip_gitignored boolean? default false
---@return boolean
local function status_is_valid(node, what, skip_gitignored)
if what == "git" then
local git_xy = node:get_git_xy()
return git_xy ~= nil and (not skip_gitignored or git_xy[1] ~= "!!")
elseif what == "diag" then
local diag_status = diagnostics.get_diag_status(node)
return diag_status ~= nil and diag_status.value ~= nil
elseif what == "opened" then
return vim.fn.bufloaded(node.absolute_path) ~= 0
end
local cur, first, prev, nex = nil, nil, nil, nil return false
for line, node in pairs(nodes_by_line) do end
local valid = false
if what == "git" then
valid = explorer_common.shows_git_status(node)
elseif what == "diag" then
valid = node.diag_status ~= nil
end
if not first and valid then ---Move to the next node that has a valid status. If none found, don't move.
first = line ---@param explorer Explorer
end ---@param where string? where to move (forwards or backwards)
---@param what string? type of status
---@param skip_gitignored boolean? default false
local function move(explorer, where, what, skip_gitignored)
local first_node_line = core.get_nodes_starting_line()
local nodes_by_line = utils.get_nodes_by_line(explorer.nodes, first_node_line)
local iter_start, iter_end, iter_step, cur, first, nex
if node == node_cur then local cursor = explorer:get_cursor_position()
cur = line if cursor and cursor[1] < first_node_line then
elseif valid then cur = cursor[1]
if not cur then end
prev = line
end if where == "next" then
if cur and not nex then iter_start, iter_end, iter_step = first_node_line, #nodes_by_line, 1
nex = line elseif where == "prev" then
break iter_start, iter_end, iter_step = #nodes_by_line, first_node_line, -1
end end
end
for line = iter_start, iter_end, iter_step do
local node = nodes_by_line[line]
local valid = status_is_valid(node, what, skip_gitignored)
if not first and valid then
first = line
end end
if where == "prev" then if cursor and line == cursor[1] then
if prev then cur = line
view.set_cursor { prev, 0 } elseif valid and cur then
nex = line
break
end
end
if nex then
view.set_cursor({ nex, 0 })
elseif vim.o.wrapscan and first then
view.set_cursor({ first, 0 })
end
end
---@param node DirectoryNode
local function expand_node(node)
if not node.open then
-- Expand the node.
-- Should never collapse since we checked open.
node:expand_or_collapse(false)
end
end
--- Move to the next node recursively.
---@param explorer Explorer
---@param what string? type of status
---@param skip_gitignored? boolean default false
local function move_next_recursive(explorer, what, skip_gitignored)
-- If the current node:
-- * is a directory
-- * and is not the root node
-- * and has a git/diag status
-- * and is not opened
-- expand it.
local node_init = explorer:get_node_at_cursor()
if not node_init then
return
end
local valid = false
if node_init.name ~= ".." then -- root node cannot have a status
valid = status_is_valid(node_init, what, skip_gitignored)
end
local node_dir = node_init:as(DirectoryNode)
if node_dir and valid and not node_dir.open then
node_dir:expand_or_collapse(false)
end
move(explorer, "next", what, skip_gitignored)
local node_cur = explorer:get_node_at_cursor()
if not node_cur then
return
end
-- If we haven't moved at all at this point, return.
if node_init == node_cur then
return
end
-- i is used to limit iterations.
local i = 0
local dir_cur = node_cur:as(DirectoryNode)
while dir_cur and i < MAX_DEPTH do
expand_node(dir_cur)
move(explorer, "next", what, skip_gitignored)
-- Save current node.
node_cur = explorer:get_node_at_cursor()
dir_cur = node_cur and node_cur:as(DirectoryNode)
i = i + 1
end
end
--- Move to the previous node recursively.
---
--- move_prev_recursive:
---
--- 1) Save current as node_init.
-- 2) Call a non-recursive prev.
--- 3) If current node is node_init's parent, call move_prev_recursive.
--- 4) Else:
--- 4.1) If current node is nil, is node_init (we didn't move), or is a file, return.
--- 4.2) The current file is a directory, expand it.
--- 4.3) Find node_init in current window, and move to it (if not found, return).
--- If node_init is the root node (name = ".."), directly move to position 1.
--- 4.4) Call a non-recursive prev.
--- 4.5) Save the current node and start back from 4.1.
---
---@param explorer Explorer
---@param what string? type of status
---@param skip_gitignored boolean? default false
local function move_prev_recursive(explorer, what, skip_gitignored)
local node_init, node_cur
-- 1)
node_init = explorer:get_node_at_cursor()
if node_init == nil then
return
end
-- 2)
move(explorer, "prev", what, skip_gitignored)
node_cur = explorer:get_node_at_cursor()
if node_cur == node_init.parent then
-- 3)
move_prev_recursive(explorer, what, skip_gitignored)
else
-- i is used to limit iterations.
local i = 0
while i < MAX_DEPTH do
-- 4.1)
if
node_cur == nil
or node_cur == node_init -- we didn't move
or node_cur:is(FileNode) -- node is a file
then
return
end end
else
if cur then -- 4.2)
if nex then local node_dir = node_cur:as(DirectoryNode)
view.set_cursor { nex, 0 } if node_dir then
expand_node(node_dir)
end
-- 4.3)
if node_init.name == ".." then -- root node
view.set_cursor({ 1, 0 }) -- move to root node (position 1)
else
local node_init_line = utils.find_node_line(node_init)
if node_init_line < 0 then
return
end end
elseif first then view.set_cursor({ node_init_line, 0 })
view.set_cursor { first, 0 }
end end
-- 4.4)
move(explorer, "prev", what, skip_gitignored)
-- 4.5)
node_cur = explorer:get_node_at_cursor()
i = i + 1
end
end
end
---@class NavigationItemOpts
---@field where string?
---@field what string?
---@field skip_gitignored boolean?
---@field recurse boolean?
---@param opts NavigationItemOpts
---@return fun()
function M.fn(opts)
return function()
local explorer = core.get_explorer()
if not explorer then
return
end
local recurse = false
-- recurse only valid for git and diag moves.
if (opts.what == "git" or opts.what == "diag") and opts.recurse ~= nil then
recurse = opts.recurse
end
if not recurse then
move(explorer, opts.where, opts.what, opts.skip_gitignored)
return
end
if opts.where == "next" then
move_next_recursive(explorer, opts.what, opts.skip_gitignored)
elseif opts.where == "prev" then
move_prev_recursive(explorer, opts.what, opts.skip_gitignored)
end end
end end
end end

View File

@@ -1,39 +1,42 @@
local renderer = require "nvim-tree.renderer" local view = require("nvim-tree.view")
local view = require "nvim-tree.view" local utils = require("nvim-tree.utils")
local utils = require "nvim-tree.utils"
local core = require "nvim-tree.core" local DirectoryNode = require("nvim-tree.node.directory")
local M = {} local M = {}
---@param should_close boolean|nil
---@return fun(node: Node): boolean|nil
function M.fn(should_close) function M.fn(should_close)
should_close = should_close or false should_close = should_close or false
---@param node Node
return function(node) return function(node)
if should_close and node.open then local dir = node:as(DirectoryNode)
node.open = false if dir then
return renderer.draw() dir = dir:last_group_node()
end if should_close and dir.open then
dir.open = false
local parent = node.parent dir.explorer.renderer:draw()
return
if renderer.config.group_empty and parent then
while parent.parent and parent.parent.group_next do
parent = parent.parent
end end
end end
local parent = (node:get_parent_of_group() or node).parent
if not parent or not parent.parent then if not parent or not parent.parent then
return view.set_cursor { 1, 0 } view.set_cursor({ 1, 0 })
return
end end
local _, line = utils.find_node(core.get_explorer().nodes, function(n) local _, line = utils.find_node(parent.explorer.nodes, function(n)
return n.absolute_path == parent.absolute_path return n.absolute_path == parent.absolute_path
end) end)
view.set_cursor { line + 1, 0 } view.set_cursor({ line + 1, 0 })
if should_close then if should_close then
parent.open = false parent.open = false
renderer.draw() parent.explorer.renderer:draw()
end end
end end
end end

View File

@@ -1,9 +1,11 @@
local utils = require "nvim-tree.utils" 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 M = {} local M = {}
---@param direction string
---@return fun(node: Node): nil
function M.fn(direction) function M.fn(direction)
return function(node) return function(node)
if node.name == ".." or not direction then if node.name == ".." or not direction then
@@ -13,7 +15,7 @@ function M.fn(direction)
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 core.get_explorer()
Iterator.builder(parent.nodes) Iterator.builder(parent and parent.nodes or {})
:recursor(function() :recursor(function()
return nil return nil
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

@@ -1,9 +1,19 @@
local utils = require "nvim-tree.utils" local utils = require("nvim-tree.utils")
local M = {} local M = {}
---@param node Node
---@return table
local function get_formatted_lines(node) local function get_formatted_lines(node)
local stats = node.fs_stat local stats = node.fs_stat
if stats == nil then
return {
"",
" Can't retrieve file information",
"",
}
end
local fpath = " fullpath: " .. node.absolute_path local fpath = " fullpath: " .. node.absolute_path
local created_at = " created: " .. os.date("%x %X", stats.birthtime.sec) local created_at = " created: " .. os.date("%x %X", stats.birthtime.sec)
local modified_at = " modified: " .. os.date("%x %X", stats.mtime.sec) local modified_at = " modified: " .. os.date("%x %X", stats.mtime.sec)
@@ -21,6 +31,7 @@ end
local current_popup = nil local current_popup = nil
---@param node Node
local function setup_window(node) local function setup_window(node)
local lines = get_formatted_lines(node) local lines = get_formatted_lines(node)
@@ -39,19 +50,23 @@ 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, { force = true }) if vim.api.nvim_win_is_valid(current_popup.winnr) then
vim.cmd "augroup NvimTreeRemoveFilePopup | au! CursorMoved | augroup END" vim.api.nvim_win_close(current_popup.winnr, true)
end
vim.cmd("augroup NvimTreeRemoveFilePopup | au! CursorMoved | augroup END")
current_popup = nil current_popup = nil
end end
end end
---@param node Node
function M.toggle_file_info(node) function M.toggle_file_info(node)
if node.name == ".." then if node.name == ".." then
return return

View File

@@ -0,0 +1,15 @@
local M = {}
M.file_popup = require("nvim-tree.actions.node.file-popup")
M.open_file = require("nvim-tree.actions.node.open-file")
M.run_command = require("nvim-tree.actions.node.run-command")
M.system_open = require("nvim-tree.actions.node.system-open")
M.buffer = require("nvim-tree.actions.node.buffer")
function M.setup(opts)
require("nvim-tree.actions.node.system-open").setup(opts)
require("nvim-tree.actions.node.file-popup").setup(opts)
require("nvim-tree.actions.node.open-file").setup(opts)
end
return M

View File

@@ -1,10 +1,14 @@
-- Copyright 2019 Yazdani Kiyan under MIT License -- Copyright 2019 Yazdani Kiyan under MIT License
local lib = require "nvim-tree.lib" local lib = require("nvim-tree.lib")
local utils = require "nvim-tree.utils" local notify = require("nvim-tree.notify")
local view = require "nvim-tree.view" local utils = require("nvim-tree.utils")
local full_name = require("nvim-tree.renderer.components.full-name")
local view = require("nvim-tree.view")
local M = {} local M = {}
---Get single char from user input
---@return string
local function get_user_input_char() local function get_user_input_char()
local c = vim.fn.getchar() local c = vim.fn.getchar()
while type(c) ~= "number" do while type(c) ~= "number" do
@@ -23,28 +27,28 @@ local function usable_win_ids()
return vim.tbl_filter(function(id) return vim.tbl_filter(function(id)
local bufid = vim.api.nvim_win_get_buf(id) local bufid = vim.api.nvim_win_get_buf(id)
for option, v in pairs(M.window_picker.exclude) do for option, v in pairs(M.window_picker.exclude) do
local ok, option_value = pcall(vim.api.nvim_buf_get_option, bufid, option) local ok, option_value
if vim.fn.has("nvim-0.10") == 1 then
ok, option_value = pcall(vim.api.nvim_get_option_value, option, { buf = bufid })
else
ok, option_value = pcall(vim.api.nvim_buf_get_option, bufid, option) ---@diagnostic disable-line: deprecated
end
if ok and vim.tbl_contains(v, option_value) then if ok and vim.tbl_contains(v, option_value) then
return false return false
end end
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 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
@@ -60,11 +64,25 @@ local function pick_win_id()
return selectable[1] return selectable[1]
end end
if #M.window_picker.chars < #selectable then
notify.error(string.format("More windows (%d) than actions.open_file.window_picker.chars (%d).", #selectable, #M.window_picker.chars))
return nil
end
local i = 1 local i = 1
local win_opts = {} local win_opts_selectable = {}
local win_opts_unselectable = {}
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)
@@ -75,33 +93,53 @@ local function pick_win_id()
if laststatus == 3 then if laststatus == 3 then
for _, win_id in ipairs(not_selectable) do for _, win_id in ipairs(not_selectable) do
local ok_status, statusline = pcall(vim.api.nvim_win_get_option, win_id, "statusline") local ok_status, statusline
local ok_hl, winhl = pcall(vim.api.nvim_win_get_option, win_id, "winhl")
win_opts[win_id] = { if vim.fn.has("nvim-0.10") == 1 then
ok_status, statusline = pcall(vim.api.nvim_get_option_value, "statusline", { win = win_id })
else
ok_status, statusline = pcall(vim.api.nvim_win_get_option, win_id, "statusline") ---@diagnostic disable-line: deprecated
end
win_opts_unselectable[win_id] = {
statusline = ok_status and statusline or "", statusline = ok_status and statusline or "",
winhl = ok_hl and winhl or "",
} }
-- Clear statusline for windows not selectable -- Clear statusline for windows not selectable
vim.api.nvim_win_set_option(win_id, "statusline", " ") if vim.fn.has("nvim-0.10") == 1 then
vim.api.nvim_set_option_value("statusline", " ", { win = win_id })
else
vim.api.nvim_win_set_option(win_id, "statusline", " ") ---@diagnostic disable-line: deprecated
end
end end
end end
-- Setup UI -- Setup UI
for _, id in ipairs(selectable) do for _, id in ipairs(selectable) do
local char = M.window_picker.chars:sub(i, i) local char = M.window_picker.chars:sub(i, i)
local ok_status, statusline = pcall(vim.api.nvim_win_get_option, id, "statusline")
local ok_hl, winhl = pcall(vim.api.nvim_win_get_option, id, "winhl")
win_opts[id] = { local ok_status, statusline, ok_hl, winhl
if vim.fn.has("nvim-0.10") == 1 then
ok_status, statusline = pcall(vim.api.nvim_get_option_value, "statusline", { win = id })
ok_hl, winhl = pcall(vim.api.nvim_get_option_value, "winhl", { win = id })
else
ok_status, statusline = pcall(vim.api.nvim_win_get_option, id, "statusline") ---@diagnostic disable-line: deprecated
ok_hl, winhl = pcall(vim.api.nvim_win_get_option, id, "winhl") ---@diagnostic disable-line: deprecated
end
win_opts_selectable[id] = {
statusline = ok_status and statusline or "", statusline = ok_status and statusline or "",
winhl = ok_hl and winhl or "", winhl = ok_hl and winhl or "",
} }
win_map[char] = id win_map[char] = id
vim.api.nvim_win_set_option(id, "statusline", "%=" .. char .. "%=") if vim.fn.has("nvim-0.10") == 1 then
vim.api.nvim_win_set_option(id, "winhl", "StatusLine:NvimTreeWindowPicker,StatusLineNC:NvimTreeWindowPicker") vim.api.nvim_set_option_value("statusline", "%=" .. char .. "%=", { win = id })
vim.api.nvim_set_option_value("winhl", "StatusLine:NvimTreeWindowPicker,StatusLineNC:NvimTreeWindowPicker", { win = id })
else
vim.api.nvim_win_set_option(id, "statusline", "%=" .. char .. "%=") ---@diagnostic disable-line: deprecated
vim.api.nvim_win_set_option(id, "winhl", "StatusLine:NvimTreeWindowPicker,StatusLineNC:NvimTreeWindowPicker") ---@diagnostic disable-line: deprecated
end
i = i + 1 i = i + 1
if i > #M.window_picker.chars then if i > #M.window_picker.chars then
@@ -109,9 +147,9 @@ local function pick_win_id()
end end
end end
vim.cmd "redraw" vim.cmd("redraw")
if vim.opt.cmdheight._value ~= 0 then if vim.opt.cmdheight._value ~= 0 then
print "Pick window: " print("Pick window: ")
end end
local _, resp = pcall(get_user_input_char) local _, resp = pcall(get_user_input_char)
resp = (resp or ""):upper() resp = (resp or ""):upper()
@@ -119,20 +157,32 @@ local function pick_win_id()
-- Restore window options -- Restore window options
for _, id in ipairs(selectable) do for _, id in ipairs(selectable) do
for opt, value in pairs(win_opts[id]) do for opt, value in pairs(win_opts_selectable[id]) do
vim.api.nvim_win_set_option(id, opt, value) if vim.fn.has("nvim-0.10") == 1 then
vim.api.nvim_set_option_value(opt, value, { win = id })
else
vim.api.nvim_win_set_option(id, opt, value) ---@diagnostic disable-line: deprecated
end
end end
end end
if laststatus == 3 then if laststatus == 3 then
for _, id in ipairs(not_selectable) do for _, id in ipairs(not_selectable) do
for opt, value in pairs(win_opts[id]) do -- Ensure window still exists at this point
vim.api.nvim_win_set_option(id, opt, value) if vim.api.nvim_win_is_valid(id) then
for opt, value in pairs(win_opts_unselectable[id]) do
if vim.fn.has("nvim-0.10") == 1 then
vim.api.nvim_set_option_value(opt, value, { win = id })
else
vim.api.nvim_win_set_option(id, opt, value) ---@diagnostic disable-line: deprecated
end
end
end end
end end
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
@@ -145,9 +195,32 @@ local function open_file_in_tab(filename)
if M.quit_on_open then if M.quit_on_open then
view.close() view.close()
end end
if M.relative_path then
filename = utils.path_relative(filename, vim.fn.getcwd())
end
vim.cmd("tabe " .. vim.fn.fnameescape(filename)) vim.cmd("tabe " .. vim.fn.fnameescape(filename))
end end
local function drop(filename)
if M.quit_on_open then
view.close()
end
if M.relative_path then
filename = utils.path_relative(filename, vim.fn.getcwd())
end
vim.cmd("drop " .. vim.fn.fnameescape(filename))
end
local function tab_drop(filename)
if M.quit_on_open then
view.close()
end
if M.relative_path then
filename = utils.path_relative(filename, vim.fn.getcwd())
end
vim.cmd("tab :drop " .. vim.fn.fnameescape(filename))
end
local function on_preview(buf_loaded) local function on_preview(buf_loaded)
if not buf_loaded then if not buf_loaded then
vim.bo.bufhidden = "delete" vim.bo.bufhidden = "delete"
@@ -164,18 +237,26 @@ local function on_preview(buf_loaded)
view.focus() view.focus()
end end
local function get_target_winid(mode, win_ids) local function get_target_winid(mode)
local target_winid local target_winid
if not M.window_picker.enable or mode == "edit_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
local usable_wins = usable_win_ids()
-- first available window -- first available usable window
if not vim.tbl_contains(win_ids, target_winid) then if not vim.tbl_contains(usable_wins, target_winid) then
target_winid = first_win_id() 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
target_winid = pick_win_id() if type(M.window_picker.picker) == "function" then
target_winid = M.window_picker.picker()
else
target_winid = pick_win_id()
end
if target_winid == nil then if target_winid == nil then
-- pick failed/cancelled -- pick failed/cancelled
return return
@@ -197,20 +278,32 @@ local function set_current_win_no_autocmd(winid, autocmd)
vim.opt.eventignore = eventignore vim.opt.eventignore = eventignore
end end
local function open_in_new_window(filename, mode, win_ids) local function open_in_new_window(filename, mode)
if type(mode) ~= "string" then if type(mode) ~= "string" then
mode = "" mode = ""
end end
local target_winid = get_target_winid(mode, win_ids) local target_winid = get_target_winid(mode)
if not target_winid then if not target_winid then
return return
end end
local create_new_window = #vim.api.nvim_list_wins() == 1 local position = string.find(mode, "no_picker")
if position then
mode = string.sub(mode, 0, position - 2)
end
-- non-floating, non-nvim-tree windows
local win_ids = vim.tbl_filter(function(id)
local config = vim.api.nvim_win_get_config(id)
local bufnr = vim.api.nvim_win_get_buf(id)
return config and config.relative == "" or utils.is_nvim_tree_buf(bufnr)
end, vim.api.nvim_list_wins())
local create_new_window = #win_ids == 1 -- This implies that the nvim-tree window is the only one
local new_window_side = (view.View.side == "right") and "aboveleft" or "belowright" local new_window_side = (view.View.side == "right") and "aboveleft" or "belowright"
-- Target is invalid or window does not exist in current tabpage: create new window -- Target is invalid: create new window
if not vim.tbl_contains(win_ids, target_winid) then if not vim.tbl_contains(win_ids, target_winid) then
vim.cmd(new_window_side .. " vsplit") vim.cmd(new_window_side .. " vsplit")
target_winid = vim.api.nvim_get_current_win() target_winid = vim.api.nvim_get_current_win()
@@ -218,30 +311,29 @@ local function open_in_new_window(filename, mode, win_ids)
-- No need to split, as we created a new window. -- No need to split, as we created a new window.
create_new_window = false create_new_window = false
if mode:match "split$" then if mode:match("split$") then
mode = "edit" mode = "edit"
end end
elseif not vim.o.hidden then elseif not vim.o.hidden then
-- If `hidden` is not enabled, check if buffer in target window is -- If `hidden` is not enabled, check if buffer in target window is
-- modified, and create new split if it is. -- modified, and create new split if it is.
local target_bufid = vim.api.nvim_win_get_buf(target_winid) local target_bufid = vim.api.nvim_win_get_buf(target_winid)
if vim.api.nvim_buf_get_option(target_bufid, "modified") then
mode = "vsplit" local modified
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
if modified then
if not mode:match("split$") then
mode = "vsplit"
end
end end
end end
local fname = vim.fn.fnameescape(filename) if (mode == "preview" or mode == "preview_no_picker") and view.View.float.enable then
local cmd
if create_new_window then
cmd = string.format("%s vsplit %s", new_window_side, fname)
elseif mode:match "split$" then
cmd = string.format("%s %s", mode, fname)
else
cmd = string.format("edit %s", fname)
end
if mode == "preview" and view.View.float.enable then
-- ignore "WinLeave" autocmd on preview -- ignore "WinLeave" autocmd on preview
-- because the registered "WinLeave" -- because the registered "WinLeave"
-- will kill the floating window immediately -- will kill the floating window immediately
@@ -250,7 +342,24 @@ local function open_in_new_window(filename, mode, win_ids)
set_current_win_no_autocmd(target_winid, { "BufEnter" }) set_current_win_no_autocmd(target_winid, { "BufEnter" })
end end
pcall(vim.cmd, cmd) local fname
if M.relative_path then
fname = utils.escape_special_chars(vim.fn.fnameescape(utils.path_relative(filename, vim.fn.getcwd())))
else
fname = utils.escape_special_chars(vim.fn.fnameescape(filename))
end
local command
if create_new_window then
-- generated from vim.api.nvim_parse_cmd("belowright vsplit foo", {})
command = { cmd = "vsplit", mods = { split = new_window_side }, args = { fname } }
elseif mode:match("split$") then
command = { cmd = mode, args = { fname } }
else
command = { cmd = "edit", args = { fname } }
end
pcall(vim.api.nvim_cmd, command, { output = false })
lib.set_target_win() lib.set_target_win()
end end
@@ -265,9 +374,15 @@ end
local function edit_in_current_buf(filename) local function edit_in_current_buf(filename)
require("nvim-tree.view").abandon_current_window() require("nvim-tree.view").abandon_current_window()
vim.cmd("edit " .. vim.fn.fnameescape(filename)) if M.relative_path then
filename = utils.path_relative(filename, vim.fn.getcwd())
end
vim.cmd("keepalt keepjumps edit " .. vim.fn.fnameescape(filename))
end end
---@param mode string
---@param filename string
---@return nil
function M.fn(mode, filename) function M.fn(mode, filename)
if type(mode) ~= "string" then if type(mode) ~= "string" then
mode = "" mode = ""
@@ -277,21 +392,27 @@ function M.fn(mode, filename)
return open_file_in_tab(filename) return open_file_in_tab(filename)
end end
if mode == "drop" then
return drop(filename)
end
if mode == "tab_drop" then
return tab_drop(filename)
end
if mode == "edit_in_place" then if mode == "edit_in_place" then
return edit_in_current_buf(filename) return edit_in_current_buf(filename)
end end
local tabpage = vim.api.nvim_get_current_tabpage()
local win_ids = vim.api.nvim_tabpage_list_wins(tabpage)
local buf_loaded = is_already_loaded(filename) local buf_loaded = is_already_loaded(filename)
local found_win = utils.get_win_buf_from_path(filename) local found_win = utils.get_win_buf_from_path(filename)
if found_win and mode == "preview" then if found_win and (mode == "preview" or mode == "preview_no_picker") then
return return
end end
if not found_win then if not found_win then
open_in_new_window(filename, mode, win_ids) open_in_new_window(filename, mode)
else else
vim.api.nvim_set_current_win(found_win) vim.api.nvim_set_current_win(found_win)
vim.bo.bufhidden = "" vim.bo.bufhidden = ""
@@ -301,7 +422,7 @@ function M.fn(mode, filename)
view.resize() view.resize()
end end
if mode == "preview" then if mode == "preview" or mode == "preview_no_picker" then
return on_preview(buf_loaded) return on_preview(buf_loaded)
end end
@@ -313,6 +434,7 @@ end
function M.setup(opts) function M.setup(opts)
M.quit_on_open = opts.actions.open_file.quit_on_open M.quit_on_open = opts.actions.open_file.quit_on_open
M.resize_window = opts.actions.open_file.resize_window M.resize_window = opts.actions.open_file.resize_window
M.relative_path = opts.actions.open_file.relative_path
if opts.actions.open_file.window_picker.chars then if opts.actions.open_file.window_picker.chars then
opts.actions.open_file.window_picker.chars = tostring(opts.actions.open_file.window_picker.chars):upper() opts.actions.open_file.window_picker.chars = tostring(opts.actions.open_file.window_picker.chars):upper()
end end

View File

@@ -1,19 +1,23 @@
local utils = require "nvim-tree.utils" local utils = require("nvim-tree.utils")
local core = require "nvim-tree.core" local core = require("nvim-tree.core")
local M = {} local M = {}
---Retrieves the absolute path to the node. ---Retrieves the absolute path to the node.
---Safely handles the node representing the current directory ---Safely handles the node representing the current directory
---(the topmost node in the nvim-tree window) ---(the topmost node in the nvim-tree window)
---@param node Node
---@return string
local function get_node_path(node) local function get_node_path(node)
if node.name == ".." then local cwd = core.get_cwd()
return utils.path_remove_trailing(core.get_cwd()) if node.name == ".." and cwd then
return utils.path_remove_trailing(cwd)
else else
return node.absolute_path return node.absolute_path
end end
end end
---@param node Node
function M.run_file_command(node) function M.run_file_command(node)
local node_path = get_node_path(node) local node_path = get_node_path(node)
vim.api.nvim_input(": " .. node_path .. "<Home>") vim.api.nvim_input(": " .. node_path .. "<Home>")

View File

@@ -1,16 +1,12 @@
local notify = require "nvim-tree.notify" local notify = require("nvim-tree.notify")
local utils = require("nvim-tree.utils")
local M = { local M = {}
config = {
is_windows = vim.fn.has "win32" == 1 or vim.fn.has "win32unix" == 1,
is_macos = vim.fn.has "mac" == 1 or vim.fn.has "macunix" == 1,
is_unix = vim.fn.has "unix" == 1,
},
}
function M.fn(node) ---@param node Node
local function user(node)
if #M.config.system_open.cmd == 0 then if #M.config.system_open.cmd == 0 then
require("nvim-tree.utils").notify.warn "Cannot open file with system application. Unrecognized platform." require("nvim-tree.utils").notify.warn("Cannot open file with system application. Unrecognized platform.")
return return
end end
@@ -21,18 +17,22 @@ function M.fn(node)
stderr = vim.loop.new_pipe(false), stderr = vim.loop.new_pipe(false),
} }
table.insert(process.args, node.link_to or node.absolute_path) table.insert(process.args, node.link_to or node.absolute_path)
process.handle, process.pid = vim.loop.spawn(
process.cmd, local opts = {
{ args = process.args, stdio = { nil, nil, process.stderr }, detached = true }, args = process.args,
function(code) stdio = { nil, nil, process.stderr },
process.stderr:read_stop() detached = true,
process.stderr:close() }
process.handle:close()
if code ~= 0 then process.handle, process.pid = vim.loop.spawn(process.cmd, opts, function(code)
notify.warn(string.format("system_open failed with return code %d: %s", code, process.errors)) process.stderr:read_stop()
end process.stderr:close()
process.handle:close()
if code ~= 0 then
notify.warn(string.format("system_open failed with return code %d: %s", code, process.errors))
end end
) end)
table.remove(process.args) table.remove(process.args)
if not process.handle then if not process.handle then
notify.warn(string.format("system_open failed to spawn command '%s': %s", process.cmd, process.pid)) notify.warn(string.format("system_open failed to spawn command '%s': %s", process.cmd, process.pid))
@@ -49,19 +49,41 @@ function M.fn(node)
vim.loop.unref(process.handle) vim.loop.unref(process.handle)
end end
---@param node Node
local function native(node)
local _, err = vim.ui.open(node.link_to or node.absolute_path)
-- err only provided on opener executable not found hence logging path is not useful
if err then
notify.warn(err)
end
end
---@param node Node
function M.fn(node)
M.open(node)
end
-- TODO #2430 always use native once 0.10 is the minimum neovim version
function M.setup(opts) function M.setup(opts)
M.config = {}
M.config.system_open = opts.system_open or {} M.config.system_open = opts.system_open or {}
if #M.config.system_open.cmd == 0 then if vim.fn.has("nvim-0.10") == 1 and #M.config.system_open.cmd == 0 then
if M.config.is_windows then M.open = native
M.config.system_open = { else
cmd = "cmd", M.open = user
args = { "/c", "start", '""' }, if #M.config.system_open.cmd == 0 then
} if utils.is_windows then
elseif M.config.is_macos then M.config.system_open = {
M.config.system_open.cmd = "open" cmd = "cmd",
elseif M.config.is_unix then args = { "/c", "start", '""' },
M.config.system_open.cmd = "xdg-open" }
elseif utils.is_macos then
M.config.system_open.cmd = "open"
elseif utils.is_unix then
M.config.system_open.cmd = "xdg-open"
end
end end
end end
end end

View File

@@ -1,64 +0,0 @@
local git = require "nvim-tree.git"
local view = require "nvim-tree.view"
local renderer = require "nvim-tree.renderer"
local explorer_module = require "nvim-tree.explorer"
local core = require "nvim-tree.core"
local M = {}
local function refresh_nodes(node, projects, unloaded_bufnr)
local cwd = node.cwd or node.link_to or node.absolute_path
local project_root = git.get_project_root(cwd)
explorer_module.reload(node, projects[project_root] or {}, unloaded_bufnr)
for _, _node in ipairs(node.nodes) do
if _node.nodes and _node.open then
refresh_nodes(_node, projects, unloaded_bufnr)
end
end
end
function M.reload_node_status(parent_node, projects)
local project_root = git.get_project_root(parent_node.absolute_path)
local status = projects[project_root] or {}
for _, node in ipairs(parent_node.nodes) do
if node.nodes then
node.git_status = status.dirs and status.dirs[node.absolute_path]
else
node.git_status = status.files and status.files[node.absolute_path]
end
if node.nodes and #node.nodes > 0 then
M.reload_node_status(node, projects)
end
end
end
local event_running = false
---@param _ table unused node passed by action
---@param unloaded_bufnr number optional bufnr recently unloaded via BufUnload event
function M.reload_explorer(_, unloaded_bufnr)
if event_running or not core.get_explorer() or vim.v.exiting ~= vim.NIL then
return
end
event_running = true
local projects = git.reload()
refresh_nodes(core.get_explorer(), projects, unloaded_bufnr)
if view.is_visible() then
renderer.draw()
end
event_running = false
end
function M.reload_git()
if not core.get_explorer() or not git.config.enable or event_running then
return
end
event_running = true
local projects = git.reload()
M.reload_node_status(core.get_explorer(), projects)
renderer.draw()
event_running = false
end
return M

View File

@@ -1,14 +1,20 @@
local log = require "nvim-tree.log" local log = require("nvim-tree.log")
local utils = require "nvim-tree.utils" local utils = require("nvim-tree.utils")
local core = require "nvim-tree.core" local core = require("nvim-tree.core")
local M = { local M = {
current_tab = vim.api.nvim_get_current_tabpage(), current_tab = vim.api.nvim_get_current_tabpage(),
} }
---@param name string
---@return string|nil
local function clean_input_cwd(name) local function clean_input_cwd(name)
name = vim.fn.fnameescape(name) name = vim.fn.fnameescape(name)
local root_parent_cwd = vim.fn.fnamemodify(utils.path_remove_trailing(core.get_cwd()), ":h") local cwd = core.get_cwd()
if cwd == nil then
return
end
local root_parent_cwd = vim.fn.fnamemodify(utils.path_remove_trailing(cwd), ":h")
if name == ".." and root_parent_cwd then if name == ".." and root_parent_cwd then
return vim.fn.expand(root_parent_cwd) return vim.fn.expand(root_parent_cwd)
else else
@@ -16,17 +22,23 @@ local function clean_input_cwd(name)
end end
end end
---@param new_tabpage integer
---@return boolean
local function is_window_event(new_tabpage) local function is_window_event(new_tabpage)
local is_event_scope_window = vim.v.event.scope == "window" or vim.v.event.changed_window local is_event_scope_window = vim.v.event.scope == "window" or vim.v.event.changed_window or false
return is_event_scope_window and new_tabpage == M.current_tab return is_event_scope_window and new_tabpage == M.current_tab
end end
---@param foldername string
---@return boolean
local function prevent_cwd_change(foldername) local function prevent_cwd_change(foldername)
local is_same_cwd = foldername == core.get_cwd() local is_same_cwd = foldername == core.get_cwd()
local is_restricted_above = M.options.restrict_above_cwd and foldername < vim.fn.getcwd(-1, -1) local is_restricted_above = M.options.restrict_above_cwd and foldername < vim.fn.getcwd(-1, -1)
return is_same_cwd or is_restricted_above return is_same_cwd or is_restricted_above
end end
---@param input_cwd string
---@param with_open boolean|nil
function M.fn(input_cwd, with_open) function M.fn(input_cwd, with_open)
if not core.get_explorer() then if not core.get_explorer() then
return return
@@ -38,7 +50,7 @@ function M.fn(input_cwd, with_open)
end end
local foldername = clean_input_cwd(input_cwd) local foldername = clean_input_cwd(input_cwd)
if prevent_cwd_change(foldername) then if foldername == nil or prevent_cwd_change(foldername) then
return return
end end
@@ -46,33 +58,43 @@ function M.fn(input_cwd, with_open)
M.force_dirchange(foldername, with_open) M.force_dirchange(foldername, with_open)
end end
---@param global boolean
---@param path string
local function cd(global, path) local function cd(global, path)
vim.cmd((global and "cd " or "lcd ") .. vim.fn.fnameescape(path)) vim.cmd((global and "cd " or "lcd ") .. vim.fn.fnameescape(path))
end end
---@return boolean
local function should_change_dir() local function should_change_dir()
return M.options.enable and vim.tbl_isempty(vim.v.event) return M.options.enable and vim.tbl_isempty(vim.v.event)
end end
---@param f function
---@return fun(foldername: string, should_open_view: boolean|nil)
local function add_profiling_to(f) local function add_profiling_to(f)
return function(foldername, should_open_view) return function(foldername, should_open_view)
local ps = log.profile_start("change dir %s", foldername) local profile = log.profile_start("change dir %s", foldername)
f(foldername, should_open_view) f(foldername, should_open_view)
log.profile_end(ps, "change dir %s", foldername) log.profile_end(profile)
end end
end end
M.force_dirchange = add_profiling_to(function(foldername, should_open_view) M.force_dirchange = add_profiling_to(function(foldername, should_open_view)
if should_change_dir() then local valid_dir = vim.fn.isdirectory(foldername) == 1 -- prevent problems on non existing dirs
cd(M.options.global, foldername) if valid_dir then
if should_change_dir() then
cd(M.options.global, foldername)
end
core.init(foldername)
end end
core.init(foldername)
if should_open_view then if should_open_view then
require("nvim-tree.lib").open() require("nvim-tree.lib").open()
else else
require("nvim-tree.renderer").draw() local explorer = core.get_explorer()
if explorer then
explorer.renderer:draw()
end
end end
end) end)

View File

@@ -1,15 +1,21 @@
local utils = require "nvim-tree.utils" local utils = require("nvim-tree.utils")
local core = require "nvim-tree.core" local core = require("nvim-tree.core")
local M = {} local M = {}
---@param node Node
function M.fn(node) function M.fn(node)
if not node or node.name == ".." then if not node or node.name == ".." then
return require("nvim-tree.actions.root.change-dir").fn ".." require("nvim-tree.actions.root.change-dir").fn("..")
else else
local newdir = vim.fn.fnamemodify(utils.path_remove_trailing(core.get_cwd()), ":h") local cwd = core.get_cwd()
if cwd == nil then
return
end
local newdir = vim.fn.fnamemodify(utils.path_remove_trailing(cwd), ":h")
require("nvim-tree.actions.root.change-dir").fn(newdir) require("nvim-tree.actions.root.change-dir").fn(newdir)
return require("nvim-tree.actions.finders.find-file").fn(node.absolute_path) require("nvim-tree.actions.finders.find-file").fn(node.absolute_path)
end end
end end

View File

@@ -0,0 +1,10 @@
local M = {}
M.change_dir = require("nvim-tree.actions.root.change-dir")
M.dir_up = require("nvim-tree.actions.root.dir-up")
function M.setup(opts)
M.change_dir.setup(opts)
end
return M

View File

@@ -1,46 +0,0 @@
local renderer = require "nvim-tree.renderer"
local utils = require "nvim-tree.utils"
local core = require "nvim-tree.core"
local Iterator = require "nvim-tree.iterators.node-iterator"
local M = {}
local function buf_match()
local buffer_paths = vim.tbl_map(function(buffer)
return vim.api.nvim_buf_get_name(buffer)
end, vim.api.nvim_list_bufs())
return function(path)
for _, buffer_path in ipairs(buffer_paths) do
local matches = utils.str_find(buffer_path, path)
if matches then
return true
end
end
return false
end
end
function M.fn(keep_buffers)
if not core.get_explorer() then
return
end
local matches = buf_match()
Iterator.builder(core.get_explorer().nodes)
:hidden()
:applier(function(node)
if node.nodes ~= nil then
node.open = keep_buffers == true and matches(node.absolute_path)
end
end)
:recursor(function(n)
return n.nodes
end)
:iterate()
renderer.draw()
end
return M

View File

@@ -1,71 +0,0 @@
local core = require "nvim-tree.core"
local renderer = require "nvim-tree.renderer"
local Iterator = require "nvim-tree.iterators.node-iterator"
local notify = require "nvim-tree.notify"
local M = {}
local function to_lookup_table(list)
local table = {}
for _, element in ipairs(list) do
table[element] = true
end
return table
end
local function expand(node)
node.open = true
if #node.nodes == 0 then
core.get_explorer():expand(node)
end
end
local function should_expand(expansion_count, node)
local should_halt = expansion_count >= M.MAX_FOLDER_DISCOVERY
local should_exclude = M.EXCLUDE[node.name]
return not should_halt and node.nodes and not node.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
expand(node)
end
end)
:recursor(function(node)
return expansion_count < M.MAX_FOLDER_DISCOVERY and node.open and node.nodes
end)
:iterate()
if expansion_count >= M.MAX_FOLDER_DISCOVERY then
return true
end
end
end
function M.fn(base_node)
local node = base_node.nodes and base_node or core.get_explorer()
if gen_iterator()(node) then
notify.warn("expansion iteration was halted after " .. M.MAX_FOLDER_DISCOVERY .. " discovered folders")
end
renderer.draw()
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,38 +0,0 @@
local view = require "nvim-tree.view"
local filters = require "nvim-tree.explorer.filters"
local renderer = require "nvim-tree.renderer"
local reloaders = require "nvim-tree.actions.reloaders.reloaders"
local M = {}
function M.custom()
filters.config.filter_custom = not filters.config.filter_custom
return reloaders.reload_explorer()
end
function M.git_ignored()
filters.config.filter_git_ignored = not filters.config.filter_git_ignored
return reloaders.reload_explorer()
end
function M.git_clean()
filters.config.filter_git_clean = not filters.config.filter_git_clean
return reloaders.reload_explorer()
end
function M.no_buffer()
filters.config.filter_no_buffer = not filters.config.filter_no_buffer
return reloaders.reload_explorer()
end
function M.dotfiles()
filters.config.filter_dotfiles = not filters.config.filter_dotfiles
return reloaders.reload_explorer()
end
function M.help()
view.toggle_help()
renderer.draw()
end
return M

View File

@@ -0,0 +1,71 @@
local core = require("nvim-tree.core")
local lib = require("nvim-tree.lib")
local view = require("nvim-tree.view")
local finders_find_file = require("nvim-tree.actions.finders.find-file")
local M = {}
--- Find file or buffer
---@param opts ApiTreeFindFileOpts|nil|boolean legacy -> opts.buf
function M.fn(opts)
-- legacy arguments
if type(opts) == "string" then
opts = {
buf = opts,
}
end
opts = opts or {}
-- do nothing if closed and open not requested
if not opts.open and not core.get_explorer() then
return
end
local bufnr, path
-- (optional) buffer number and path
local opts_buf = opts.buf
if type(opts_buf) == "nil" then
bufnr = vim.api.nvim_get_current_buf()
path = vim.api.nvim_buf_get_name(bufnr)
elseif type(opts_buf) == "number" then
if not vim.api.nvim_buf_is_valid(opts_buf) then
return
end
bufnr = opts_buf
path = vim.api.nvim_buf_get_name(bufnr)
elseif type(opts_buf) == "string" then
bufnr = nil
path = tostring(opts_buf)
else
return
end
if view.is_visible() then
-- focus
if opts.focus then
lib.set_target_win()
view.focus()
end
elseif opts.open then
-- open
lib.open({ current_window = opts.current_window, winid = opts.winid })
if not opts.focus then
vim.cmd("noautocmd wincmd p")
end
end
-- update root
if opts.update_root or M.config.update_focused_file.update_root.enable then
require("nvim-tree").change_root(path, bufnr)
end
-- find
finders_find_file.fn(path)
end
function M.setup(opts)
M.config = opts or {}
end
return M

View File

@@ -0,0 +1,17 @@
local M = {}
M.find_file = require("nvim-tree.actions.tree.find-file")
M.modifiers = require("nvim-tree.actions.tree.modifiers")
M.open = require("nvim-tree.actions.tree.open")
M.toggle = require("nvim-tree.actions.tree.toggle")
M.resize = require("nvim-tree.actions.tree.resize")
function M.setup(opts)
M.find_file.setup(opts)
M.modifiers.setup(opts)
M.open.setup(opts)
M.toggle.setup(opts)
M.resize.setup(opts)
end
return M

View File

@@ -0,0 +1,81 @@
local utils = require("nvim-tree.utils")
local core = require("nvim-tree.core")
local Iterator = require("nvim-tree.iterators.node-iterator")
local FileNode = require("nvim-tree.node.file")
local DirectoryNode = require("nvim-tree.node.directory")
local M = {}
---@return fun(path: string): boolean
local function buf_match()
local buffer_paths = vim.tbl_map(function(buffer)
return vim.api.nvim_buf_get_name(buffer)
end, vim.api.nvim_list_bufs())
return function(path)
for _, buffer_path in ipairs(buffer_paths) do
local matches = utils.str_find(buffer_path, path)
if matches then
return true
end
end
return false
end
end
---Collapse a node, root if nil
---@param node Node?
---@param opts ApiCollapseOpts
local function collapse(node, opts)
local explorer = core.get_explorer()
if not explorer then
return
end
node = node or explorer
local node_at_cursor = explorer:get_node_at_cursor()
if not node_at_cursor then
return
end
local matches = buf_match()
Iterator.builder({ node:is(FileNode) and node.parent or node:as(DirectoryNode) })
:hidden()
:applier(function(n)
local dir = n:as(DirectoryNode)
if dir then
dir.open = opts.keep_buffers == true and matches(dir.absolute_path)
end
end)
:recursor(function(n)
return n.group_next and { n.group_next } or n.nodes
end)
:iterate()
explorer.renderer:draw()
utils.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
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

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

View File

@@ -0,0 +1,55 @@
local lib = require("nvim-tree.lib")
local view = require("nvim-tree.view")
local finders_find_file = require("nvim-tree.actions.finders.find-file")
local M = {}
---Open the tree, focusing if already open.
---@param opts ApiTreeOpenOpts|nil|string legacy -> opts.path
function M.fn(opts)
-- legacy arguments
if type(opts) == "string" then
opts = {
path = opts,
}
end
opts = opts or {}
local previous_buf = vim.api.nvim_get_current_buf()
local previous_path = vim.api.nvim_buf_get_name(previous_buf)
-- sanitise path
if type(opts.path) ~= "string" or vim.fn.isdirectory(opts.path) == 0 then
opts.path = nil
end
if view.is_visible() then
-- focus
lib.set_target_win()
view.focus()
else
-- open
lib.open({
path = opts.path,
current_window = opts.current_window,
winid = opts.winid,
})
end
-- find file
if M.config.update_focused_file.enable or opts.find_file then
-- update root
if opts.update_root then
require("nvim-tree").change_root(previous_path, previous_buf)
end
-- find
finders_find_file.fn(previous_path)
end
end
function M.setup(opts)
M.config = opts or {}
end
return M

View File

@@ -0,0 +1,51 @@
local view = require("nvim-tree.view")
local M = {}
---Resize the tree, persisting the new size.
---@param opts ApiTreeResizeOpts|nil
function M.fn(opts)
if opts == nil then
-- reset to config values
view.configure_width()
view.resize()
return
end
local options = opts or {}
local width_cfg = options.width
if width_cfg ~= nil then
view.configure_width(width_cfg)
view.resize()
return
end
if not view.is_width_determined() then
-- {absolute} and {relative} do nothing when {width} is a function.
return
end
local absolute = options.absolute
if type(absolute) == "number" then
view.resize(absolute)
return
end
local relative = options.relative
if type(relative) == "number" then
local relative_size = tostring(relative)
if relative > 0 then
relative_size = "+" .. relative_size
end
view.resize(relative_size)
return
end
end
function M.setup(opts)
M.config = opts or {}
end
return M

View File

@@ -0,0 +1,76 @@
local lib = require("nvim-tree.lib")
local view = require("nvim-tree.view")
local finders_find_file = require("nvim-tree.actions.finders.find-file")
local M = {}
---Toggle the tree.
---@param opts ApiTreeToggleOpts|nil|boolean legacy -> opts.find_file
---@param no_focus string|nil legacy -> opts.focus
---@param cwd boolean|nil legacy -> opts.path
---@param bang boolean|nil legacy -> opts.update_root
function M.fn(opts, no_focus, cwd, bang)
-- legacy arguments
if type(opts) == "boolean" then
opts = {
find_file = opts,
}
if type(cwd) == "string" then
opts.path = cwd
end
if type(no_focus) == "boolean" then
opts.focus = not no_focus
end
if type(bang) == "boolean" then
opts.update_root = bang
end
end
opts = opts or {}
-- defaults
if opts.focus == nil then
opts.focus = true
end
local previous_buf = vim.api.nvim_get_current_buf()
local previous_path = vim.api.nvim_buf_get_name(previous_buf)
-- sanitise path
if type(opts.path) ~= "string" or vim.fn.isdirectory(opts.path) == 0 then
opts.path = nil
end
if view.is_visible() then
-- close
view.close()
else
-- open
lib.open({
path = opts.path,
current_window = opts.current_window,
winid = opts.winid,
})
-- find file
if M.config.update_focused_file.enable or opts.find_file then
-- update root
if opts.update_root then
require("nvim-tree").change_root(previous_path, previous_buf)
end
-- find
finders_find_file.fn(previous_path)
end
-- restore focus
if not opts.focus then
vim.cmd("noautocmd wincmd p")
end
end
end
function M.setup(opts)
M.config = opts or {}
end
return M

View File

@@ -1,130 +1,370 @@
local core = require("nvim-tree.core")
local view = require("nvim-tree.view")
local utils = require("nvim-tree.utils")
local actions = require("nvim-tree.actions")
local appearance_hi_test = require("nvim-tree.appearance.hi-test")
local events = require("nvim-tree.events")
local help = require("nvim-tree.help")
local keymap = require("nvim-tree.keymap")
local notify = require("nvim-tree.notify")
local DirectoryNode = require("nvim-tree.node.directory")
local FileLinkNode = require("nvim-tree.node.file-link")
local RootNode = require("nvim-tree.node.root")
local UserDecorator = require("nvim-tree.renderer.decorator.user")
local Api = { local Api = {
tree = {}, tree = {},
node = { navigate = { sibling = {}, git = {}, diagnostics = {} }, run = {}, open = {} }, node = {
navigate = {
sibling = {},
git = {},
diagnostics = {},
opened = {},
},
run = {},
open = {},
buffer = {},
},
events = {}, events = {},
marks = { bulk = {}, navigate = {} }, marks = {
fs = { copy = {} }, bulk = {},
navigate = {},
},
fs = {
copy = {},
},
git = {}, git = {},
live_filter = {}, live_filter = {},
config = {
mappings = {},
},
commands = {},
diagnostics = {},
decorator = {},
} }
local function inject_node(f) ---Print error when setup not called.
return function(node, ...) ---@param fn fun(...): any
node = node or require("nvim-tree.lib").get_node_at_cursor() ---@return fun(...): any
f(node, ...) local function wrap(fn)
end return function(...)
end if vim.g.NvimTreeSetup == 1 then
return fn(...)
Api.tree.open = require("nvim-tree").open
Api.tree.toggle = require("nvim-tree").toggle
Api.tree.close = require("nvim-tree.view").close
Api.tree.close_in_this_tab = require("nvim-tree.view").close_this_tab_only
Api.tree.close_in_all_tabs = require("nvim-tree.view").close_all_tabs
Api.tree.focus = require("nvim-tree").focus
Api.tree.reload = require("nvim-tree.actions.reloaders.reloaders").reload_explorer
Api.tree.change_root = require("nvim-tree").change_dir
Api.tree.change_root_to_node = inject_node(function(node)
if node.name == ".." then
require("nvim-tree.actions.root.change-dir").fn ".."
elseif node.nodes ~= nil then
require("nvim-tree.actions.root.change-dir").fn(require("nvim-tree.lib").get_last_group_node(node).absolute_path)
end
end)
Api.tree.change_root_to_parent = inject_node(require("nvim-tree.actions.root.dir-up").fn)
Api.tree.get_node_under_cursor = require("nvim-tree.lib").get_node_at_cursor
Api.tree.get_nodes = require("nvim-tree.lib").get_nodes
Api.tree.find_file = require("nvim-tree.actions.finders.find-file").fn
Api.tree.search_node = require("nvim-tree.actions.finders.search-node").fn
Api.tree.collapse_all = require("nvim-tree.actions.tree-modifiers.collapse-all").fn
Api.tree.expand_all = inject_node(require("nvim-tree.actions.tree-modifiers.expand-all").fn)
Api.tree.toggle_gitignore_filter = require("nvim-tree.actions.tree-modifiers.toggles").git_ignored
Api.tree.toggle_git_clean_filter = require("nvim-tree.actions.tree-modifiers.toggles").git_clean
Api.tree.toggle_no_buffer_filter = require("nvim-tree.actions.tree-modifiers.toggles").no_buffer
Api.tree.toggle_custom_filter = require("nvim-tree.actions.tree-modifiers.toggles").custom
Api.tree.toggle_hidden_filter = require("nvim-tree.actions.tree-modifiers.toggles").dotfiles
Api.tree.toggle_help = require("nvim-tree.actions.tree-modifiers.toggles").help
Api.fs.create = inject_node(require("nvim-tree.actions.fs.create-file").fn)
Api.fs.remove = inject_node(require("nvim-tree.actions.fs.remove-file").fn)
Api.fs.trash = inject_node(require("nvim-tree.actions.fs.trash").fn)
Api.fs.rename_node = inject_node(require("nvim-tree.actions.fs.rename-file").fn ":t")
Api.fs.rename = inject_node(require("nvim-tree.actions.fs.rename-file").fn ":t")
Api.fs.rename_sub = inject_node(require("nvim-tree.actions.fs.rename-file").fn ":p")
Api.fs.rename_basename = inject_node(require("nvim-tree.actions.fs.rename-file").fn ":t:r")
Api.fs.cut = inject_node(require("nvim-tree.actions.fs.copy-paste").cut)
Api.fs.paste = inject_node(require("nvim-tree.actions.fs.copy-paste").paste)
Api.fs.clear_clipboard = require("nvim-tree.actions.fs.copy-paste").clear_clipboard
Api.fs.print_clipboard = require("nvim-tree.actions.fs.copy-paste").print_clipboard
Api.fs.copy.node = inject_node(require("nvim-tree.actions.fs.copy-paste").copy)
Api.fs.copy.absolute_path = inject_node(require("nvim-tree.actions.fs.copy-paste").copy_absolute_path)
Api.fs.copy.filename = inject_node(require("nvim-tree.actions.fs.copy-paste").copy_filename)
Api.fs.copy.relative_path = inject_node(require("nvim-tree.actions.fs.copy-paste").copy_path)
local function edit(mode, node)
local path = node.absolute_path
if node.link_to and not node.nodes then
path = node.link_to
end
require("nvim-tree.actions.node.open-file").fn(mode, path)
end
local function open_or_expand_or_dir_up(mode)
return function(node)
if node.name == ".." then
require("nvim-tree.actions.root.change-dir").fn ".."
elseif node.nodes then
require("nvim-tree.lib").expand_or_collapse(node)
else else
edit(mode, node) notify.error("nvim-tree setup not called")
end end
end end
end end
local function open_preview(node) ---Invoke a method on the singleton explorer.
if node.nodes or node.name == ".." then ---Print error when setup not called.
return ---@param explorer_method string explorer method name
end ---@return fun(...): any
local function wrap_explorer(explorer_method)
edit("preview", node) return wrap(function(...)
local explorer = core.get_explorer()
if explorer then
return explorer[explorer_method](explorer, ...)
end
end)
end end
Api.node.open.edit = inject_node(open_or_expand_or_dir_up "edit") ---Inject the node as the first argument if present otherwise do nothing.
Api.node.open.replace_tree_buffer = inject_node(open_or_expand_or_dir_up "edit_in_place") ---@param fn fun(node: Node, ...): any
Api.node.open.no_window_picker = inject_node(open_or_expand_or_dir_up "edit_no_picker") ---@return fun(node: Node?, ...): any
Api.node.open.vertical = inject_node(open_or_expand_or_dir_up "vsplit") local function wrap_node(fn)
Api.node.open.horizontal = inject_node(open_or_expand_or_dir_up "split") return function(node, ...)
Api.node.open.tab = inject_node(open_or_expand_or_dir_up "tabnew") node = node or wrap_explorer("get_node_at_cursor")()
Api.node.open.preview = inject_node(open_preview) if node then
return fn(node, ...)
end
end
end
Api.node.show_info_popup = inject_node(require("nvim-tree.actions.node.file-popup").toggle_file_info) ---Inject the node or nil as the first argument if absent.
Api.node.run.cmd = inject_node(require("nvim-tree.actions.node.run-command").run_file_command) ---@param fn fun(node: Node?, ...): any
Api.node.run.system = inject_node(require("nvim-tree.actions.node.system-open").fn) ---@return fun(node: Node?, ...): any
Api.node.navigate.sibling.next = inject_node(require("nvim-tree.actions.moves.sibling").fn "next") local function wrap_node_or_nil(fn)
Api.node.navigate.sibling.prev = inject_node(require("nvim-tree.actions.moves.sibling").fn "prev") return function(node, ...)
Api.node.navigate.sibling.first = inject_node(require("nvim-tree.actions.moves.sibling").fn "first") node = node or wrap_explorer("get_node_at_cursor")()
Api.node.navigate.sibling.last = inject_node(require("nvim-tree.actions.moves.sibling").fn "last") return fn(node, ...)
Api.node.navigate.parent = inject_node(require("nvim-tree.actions.moves.parent").fn(false)) end
Api.node.navigate.parent_close = inject_node(require("nvim-tree.actions.moves.parent").fn(true)) end
Api.node.navigate.git.next = inject_node(require("nvim-tree.actions.moves.item").fn("next", "git"))
Api.node.navigate.git.prev = inject_node(require("nvim-tree.actions.moves.item").fn("prev", "git"))
Api.node.navigate.diagnostics.next = inject_node(require("nvim-tree.actions.moves.item").fn("next", "diag"))
Api.node.navigate.diagnostics.prev = inject_node(require("nvim-tree.actions.moves.item").fn("prev", "diag"))
Api.git.reload = require("nvim-tree.actions.reloaders.reloaders").reload_git ---Invoke a member's method on the singleton explorer.
---Print error when setup not called.
---@param explorer_member string explorer member name
---@param member_method string method name to invoke on member
---@param ... any passed to method
---@return fun(...): any
local function wrap_explorer_member_args(explorer_member, member_method, ...)
local method_args = ...
return wrap(function(...)
local explorer = core.get_explorer()
if explorer then
return explorer[explorer_member][member_method](explorer[explorer_member], method_args, ...)
end
end)
end
Api.events.subscribe = require("nvim-tree.events").subscribe ---Invoke a member's method on the singleton explorer.
Api.events.Event = require("nvim-tree.events").Event ---Print error when setup not called.
---@param explorer_member string explorer member name
---@param member_method string method name to invoke on member
---@return fun(...): any
local function wrap_explorer_member(explorer_member, member_method)
return wrap(function(...)
local explorer = core.get_explorer()
if explorer then
return explorer[explorer_member][member_method](explorer[explorer_member], ...)
end
end)
end
Api.live_filter.start = require("nvim-tree.live-filter").start_filtering ---@class ApiTreeOpenOpts
Api.live_filter.clear = require("nvim-tree.live-filter").clear_filter ---@field path string|nil path
---@field current_window boolean|nil default false
---@field winid number|nil
---@field find_file boolean|nil default false
---@field update_root boolean|nil default false
Api.marks.get = inject_node(require("nvim-tree.marks").get_mark) Api.tree.open = wrap(actions.tree.open.fn)
Api.marks.list = require("nvim-tree.marks").get_marks Api.tree.focus = Api.tree.open
Api.marks.toggle = inject_node(require("nvim-tree.marks").toggle_mark)
Api.marks.clear = require("nvim-tree.marks").clear_marks ---@class ApiTreeToggleOpts
Api.marks.bulk.move = require("nvim-tree.marks.bulk-move").bulk_move ---@field path string|nil
Api.marks.navigate.next = require("nvim-tree.marks.navigation").next ---@field current_window boolean|nil default false
Api.marks.navigate.prev = require("nvim-tree.marks.navigation").prev ---@field winid number|nil
Api.marks.navigate.select = require("nvim-tree.marks.navigation").select ---@field find_file boolean|nil default false
---@field update_root boolean|nil default false
---@field focus boolean|nil default true
Api.tree.toggle = wrap(actions.tree.toggle.fn)
Api.tree.close = wrap(view.close)
Api.tree.close_in_this_tab = wrap(view.close_this_tab_only)
Api.tree.close_in_all_tabs = wrap(view.close_all_tabs)
Api.tree.reload = wrap_explorer("reload_explorer")
---@class ApiTreeResizeOpts
---@field width string|function|number|table|nil
---@field absolute number|nil
---@field relative number|nil
Api.tree.resize = wrap(actions.tree.resize.fn)
Api.tree.change_root = wrap(function(...)
require("nvim-tree").change_dir(...)
end)
Api.tree.change_root_to_node = wrap_node(function(node)
if node.name == ".." or node:is(RootNode) then
actions.root.change_dir.fn("..")
else
node = node:as(DirectoryNode)
if node then
actions.root.change_dir.fn(node:last_group_node().absolute_path)
end
end
end)
Api.tree.change_root_to_parent = wrap_node(actions.root.dir_up.fn)
Api.tree.get_node_under_cursor = wrap_explorer("get_node_at_cursor")
Api.tree.get_nodes = wrap_explorer("get_nodes")
---@class ApiTreeFindFileOpts
---@field buf string|number|nil
---@field open boolean|nil default false
---@field current_window boolean|nil default false
---@field winid number|nil
---@field update_root boolean|nil default false
---@field focus boolean|nil default false
Api.tree.find_file = wrap(actions.tree.find_file.fn)
Api.tree.search_node = wrap(actions.finders.search_node.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_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_no_buffer_filter = wrap_explorer_member_args("filters", "toggle", "no_buffer")
Api.tree.toggle_custom_filter = wrap_explorer_member_args("filters", "toggle", "custom")
Api.tree.toggle_hidden_filter = wrap_explorer_member_args("filters", "toggle", "dotfiles")
Api.tree.toggle_no_bookmark_filter = wrap_explorer_member_args("filters", "toggle", "no_bookmark")
Api.tree.toggle_help = wrap(help.toggle)
Api.tree.is_tree_buf = wrap(utils.is_nvim_tree_buf)
---@class ApiTreeIsVisibleOpts
---@field tabpage number|nil
---@field any_tabpage boolean|nil default false
Api.tree.is_visible = wrap(view.is_visible)
---@class ApiTreeWinIdOpts
---@field tabpage number|nil default nil
Api.tree.winid = wrap(view.winid)
Api.fs.create = wrap_node_or_nil(actions.fs.create_file.fn)
Api.fs.remove = wrap_node(actions.fs.remove_file.fn)
Api.fs.trash = wrap_node(actions.fs.trash.fn)
Api.fs.rename_node = wrap_node(actions.fs.rename_file.fn(":t"))
Api.fs.rename = wrap_node(actions.fs.rename_file.fn(":t"))
Api.fs.rename_sub = wrap_node(actions.fs.rename_file.fn(":p:h"))
Api.fs.rename_basename = wrap_node(actions.fs.rename_file.fn(":t:r"))
Api.fs.rename_full = wrap_node(actions.fs.rename_file.fn(":p"))
Api.fs.cut = wrap_node(wrap_explorer_member("clipboard", "cut"))
Api.fs.paste = wrap_node(wrap_explorer_member("clipboard", "paste"))
Api.fs.clear_clipboard = wrap_explorer_member("clipboard", "clear_clipboard")
Api.fs.print_clipboard = wrap_explorer_member("clipboard", "print_clipboard")
Api.fs.copy.node = wrap_node(wrap_explorer_member("clipboard", "copy"))
Api.fs.copy.absolute_path = wrap_node(wrap_explorer_member("clipboard", "copy_absolute_path"))
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.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 node Node
---@param edit_opts NodeEditOpts?
local function edit(mode, node, edit_opts)
local file_link = node:as(FileLinkNode)
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)
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
---@param mode string
---@param toggle_group boolean?
---@return fun(node: Node, edit_opts: NodeEditOpts?)
local function open_or_expand_or_dir_up(mode, toggle_group)
---@param node Node
---@param edit_opts NodeEditOpts?
return function(node, edit_opts)
local root = node:as(RootNode)
local dir = node:as(DirectoryNode)
if root or node.name == ".." then
actions.root.change_dir.fn("..")
elseif dir then
dir:expand_or_collapse(toggle_group)
elseif not toggle_group then
edit(mode, node, edit_opts)
end
end
end
Api.node.open.edit = wrap_node(open_or_expand_or_dir_up("edit"))
Api.node.open.drop = wrap_node(open_or_expand_or_dir_up("drop"))
Api.node.open.tab_drop = wrap_node(open_or_expand_or_dir_up("tab_drop"))
Api.node.open.replace_tree_buffer = wrap_node(open_or_expand_or_dir_up("edit_in_place"))
Api.node.open.no_window_picker = wrap_node(open_or_expand_or_dir_up("edit_no_picker"))
Api.node.open.vertical = wrap_node(open_or_expand_or_dir_up("vsplit"))
Api.node.open.vertical_no_picker = wrap_node(open_or_expand_or_dir_up("vsplit_no_picker"))
Api.node.open.horizontal = wrap_node(open_or_expand_or_dir_up("split"))
Api.node.open.horizontal_no_picker = wrap_node(open_or_expand_or_dir_up("split_no_picker"))
Api.node.open.tab = wrap_node(open_or_expand_or_dir_up("tabnew"))
Api.node.open.toggle_group_empty = wrap_node(open_or_expand_or_dir_up("toggle_group_empty", true))
Api.node.open.preview = wrap_node(open_or_expand_or_dir_up("preview"))
Api.node.open.preview_no_picker = wrap_node(open_or_expand_or_dir_up("preview_no_picker"))
Api.node.show_info_popup = wrap_node(actions.node.file_popup.toggle_file_info)
Api.node.run.cmd = wrap_node(actions.node.run_command.run_file_command)
Api.node.run.system = wrap_node(actions.node.system_open.fn)
Api.node.navigate.sibling.next = wrap_node(actions.moves.sibling.fn("next"))
Api.node.navigate.sibling.prev = wrap_node(actions.moves.sibling.fn("prev"))
Api.node.navigate.sibling.first = wrap_node(actions.moves.sibling.fn("first"))
Api.node.navigate.sibling.last = wrap_node(actions.moves.sibling.fn("last"))
Api.node.navigate.parent = wrap_node(actions.moves.parent.fn(false))
Api.node.navigate.parent_close = wrap_node(actions.moves.parent.fn(true))
Api.node.navigate.git.next = wrap_node(actions.moves.item.fn({ where = "next", what = "git" }))
Api.node.navigate.git.next_skip_gitignored = wrap_node(actions.moves.item.fn({ where = "next", what = "git", skip_gitignored = true }))
Api.node.navigate.git.next_recursive = wrap_node(actions.moves.item.fn({ where = "next", what = "git", recurse = true }))
Api.node.navigate.git.prev = wrap_node(actions.moves.item.fn({ where = "prev", what = "git" }))
Api.node.navigate.git.prev_skip_gitignored = wrap_node(actions.moves.item.fn({ where = "prev", what = "git", skip_gitignored = true }))
Api.node.navigate.git.prev_recursive = wrap_node(actions.moves.item.fn({ where = "prev", what = "git", recurse = true }))
Api.node.navigate.diagnostics.next = wrap_node(actions.moves.item.fn({ where = "next", what = "diag" }))
Api.node.navigate.diagnostics.next_recursive = wrap_node(actions.moves.item.fn({ where = "next", what = "diag", recurse = true }))
Api.node.navigate.diagnostics.prev = wrap_node(actions.moves.item.fn({ where = "prev", what = "diag" }))
Api.node.navigate.diagnostics.prev_recursive = wrap_node(actions.moves.item.fn({ where = "prev", what = "diag", recurse = true }))
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.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.events.subscribe = events.subscribe
Api.events.Event = events.Event
Api.live_filter.start = wrap_explorer_member("live_filter", "start_filtering")
Api.live_filter.clear = wrap_explorer_member("live_filter", "clear_filter")
Api.marks.get = wrap_node(wrap_explorer_member("marks", "get"))
Api.marks.list = wrap_explorer_member("marks", "list")
Api.marks.toggle = wrap_node(wrap_explorer_member("marks", "toggle"))
Api.marks.clear = wrap_explorer_member("marks", "clear")
Api.marks.bulk.delete = wrap_explorer_member("marks", "bulk_delete")
Api.marks.bulk.trash = wrap_explorer_member("marks", "bulk_trash")
Api.marks.bulk.move = wrap_explorer_member("marks", "bulk_move")
Api.marks.navigate.next = wrap_explorer_member("marks", "navigate_next")
Api.marks.navigate.prev = wrap_explorer_member("marks", "navigate_prev")
Api.marks.navigate.select = wrap_explorer_member("marks", "navigate_select")
Api.config.mappings.get_keymap = wrap(keymap.get_keymap)
Api.config.mappings.get_keymap_default = wrap(keymap.get_keymap_default)
Api.config.mappings.default_on_attach = keymap.default_on_attach
Api.diagnostics.hi_test = wrap(appearance_hi_test)
Api.commands.get = wrap(function()
return require("nvim-tree.commands").get()
end)
---Create a decorator class by calling :extend()
---See :help nvim-tree-decorators
---@type nvim_tree.api.decorator.UserDecorator
Api.decorator.UserDecorator = UserDecorator --[[@as nvim_tree.api.decorator.UserDecorator]]
return Api return Api

View File

@@ -0,0 +1,147 @@
local appearance = require("nvim-tree.appearance")
local Class = require("nvim-tree.classic")
-- others with name and links less than this arbitrary value are short
local SHORT_LEN = 50
local namespace_hi_test_id = vim.api.nvim_create_namespace("NvimTreeHiTest")
---@class (exact) HighlightDisplay: Class for :NvimTreeHiTest
---@field group string nvim-tree highlight group name
---@field links string link chain to a concretely defined group
---@field def string :hi concrete definition after following any links
local HighlightDisplay = Class:extend()
---@class HighlightDisplay
---@overload fun(args: HighlightDisplayArgs): HighlightDisplay
---@class (exact) HighlightDisplayArgs
---@field group string nvim-tree highlight group name
---@protected
---@param args HighlightDisplayArgs
function HighlightDisplay:new(args)
self.group = args.group
local concrete = self.group
-- maybe follow links
local links = {}
local link = vim.api.nvim_get_hl(0, { name = self.group }).link
while link do
table.insert(links, link)
concrete = link
link = vim.api.nvim_get_hl(0, { name = link }).link
end
self.links = table.concat(links, " ")
-- concrete definition
local ok, res = pcall(vim.api.nvim_cmd, { cmd = "highlight", args = { concrete } }, { output = true })
if ok and type(res) == "string" then
self.def = res:gsub(".*xxx *", "")
else
self.def = ""
end
end
---Render one group.
---@param bufnr number to render in
---@param fmt string format string for group, links, def
---@param l number line number to render at
---@return number l next line number
function HighlightDisplay:render(bufnr, fmt, l)
local text = string.format(fmt, self.group, self.links, self.def)
vim.api.nvim_buf_set_lines(bufnr, l, -1, true, { text })
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
end
---Render many groups.
---@param header string before with underline line
---@param displays HighlightDisplay[] highlight group
---@param bufnr number to render in
---@param l number line number to start at
---@return number l next line number
local function render_displays(header, displays, bufnr, l)
local max_group_len = 0
local max_links_len = 0
-- build all highlight groups, using name only
for _, display in ipairs(displays) do
max_group_len = math.max(max_group_len, #display.group)
max_links_len = math.max(max_links_len, #display.links)
end
-- header
vim.api.nvim_buf_set_lines(bufnr, l, -1, true, { header, (header:gsub(".", "-")) })
l = l + 2
-- render and highlight
local fmt = string.format("%%-%d.%ds %%-%d.%ds %%s", max_group_len, max_group_len, max_links_len, max_links_len)
for _, display in ipairs(displays) do
l = display:render(bufnr, fmt, l)
end
return l
end
---Run a test similar to :so $VIMRUNTIME/syntax/hitest.vim
---Display all nvim-tree and neovim highlight groups, their link chain and actual definition
return function()
-- create a buffer
local bufnr = vim.api.nvim_create_buf(false, true)
local l = 0
-- nvim-tree groups, ordered
local displays = {}
for _, highlight_group in ipairs(appearance.HIGHLIGHT_GROUPS) do
local display = HighlightDisplay({ group = highlight_group.group })
table.insert(displays, display)
end
l = render_displays("nvim-tree", displays, bufnr, l)
vim.api.nvim_buf_set_lines(bufnr, l, -1, true, { "" })
l = l + 1
-- built in groups, ordered opaquely by nvim
local displays_short, displays_long = {}, {}
local ok, out = pcall(vim.api.nvim_cmd, { cmd = "highlight" }, { output = true })
if ok then
for group in string.gmatch(out, "(%w*)%s+xxx") do
if group:find("NvimTree", 1, true) ~= 1 then
local display = HighlightDisplay({ group = group })
if #display.group + #display.links > SHORT_LEN then
table.insert(displays_long, display)
else
table.insert(displays_short, display)
end
end
end
end
-- short ones first
l = render_displays("other, short", displays_short, bufnr, l)
vim.api.nvim_buf_set_lines(bufnr, l, -1, true, { "" })
l = l + 1
-- long
render_displays("other, long", displays_long, bufnr, l)
-- finalise and focus the buffer
if vim.fn.has("nvim-0.10") == 1 then
vim.api.nvim_set_option_value("modifiable", false, { buf = bufnr })
else
vim.api.nvim_buf_set_option(bufnr, "modifiable", false) ---@diagnostic disable-line: deprecated
end
vim.cmd.buffer(bufnr)
end

View File

@@ -0,0 +1,210 @@
local M = {}
---@class HighlightGroup
---@field group string
---@field link string|nil
---@field def string|nil
---@type HighlightGroup[]
-- All highlight groups: linked or directly defined.
-- Please add new groups to help and preserve order.
-- Please avoid directly defined groups to preserve accessibility for TUI.
M.HIGHLIGHT_GROUPS = {
-- Standard
{ group = "NvimTreeNormal", link = "Normal" },
{ group = "NvimTreeNormalFloat", link = "NormalFloat" },
{ group = "NvimTreeNormalFloatBorder", link = "FloatBorder" },
{ group = "NvimTreeNormalNC", link = "NvimTreeNormal" },
{ group = "NvimTreeLineNr", link = "LineNr" },
{ group = "NvimTreeWinSeparator", link = "WinSeparator" },
{ group = "NvimTreeEndOfBuffer", link = "EndOfBuffer" },
{ group = "NvimTreePopup", link = "Normal" },
{ group = "NvimTreeSignColumn", link = "NvimTreeNormal" },
{ group = "NvimTreeCursorColumn", link = "CursorColumn" },
{ group = "NvimTreeCursorLine", link = "CursorLine" },
{ group = "NvimTreeCursorLineNr", link = "CursorLineNr" },
{ group = "NvimTreeStatusLine", link = "StatusLine" },
{ group = "NvimTreeStatusLineNC", link = "StatusLineNC" },
-- File Text
{ group = "NvimTreeExecFile", link = "Question" },
{ group = "NvimTreeImageFile", link = "Question" },
{ group = "NvimTreeSpecialFile", link = "Title" },
{ group = "NvimTreeSymlink", link = "Underlined" },
-- Folder Text
{ group = "NvimTreeRootFolder", link = "Title" },
{ group = "NvimTreeFolderName", link = "Directory" },
{ group = "NvimTreeEmptyFolderName", link = "Directory" },
{ group = "NvimTreeOpenedFolderName", link = "Directory" },
{ group = "NvimTreeSymlinkFolderName", link = "Directory" },
-- File Icons
{ group = "NvimTreeFileIcon", link = "NvimTreeNormal" },
{ group = "NvimTreeSymlinkIcon", link = "NvimTreeNormal" },
-- Folder Icons
{ group = "NvimTreeFolderIcon", def = "guifg=#8094b4 ctermfg=Blue" },
{ group = "NvimTreeOpenedFolderIcon", link = "NvimTreeFolderIcon" },
{ group = "NvimTreeClosedFolderIcon", link = "NvimTreeFolderIcon" },
{ group = "NvimTreeFolderArrowClosed", link = "NvimTreeIndentMarker" },
{ group = "NvimTreeFolderArrowOpen", link = "NvimTreeIndentMarker" },
-- Indent
{ group = "NvimTreeIndentMarker", link = "NvimTreeFolderIcon" },
-- Picker
{ group = "NvimTreeWindowPicker", def = "guifg=#ededed guibg=#4493c8 gui=bold ctermfg=White ctermbg=DarkBlue" },
-- LiveFilter
{ group = "NvimTreeLiveFilterPrefix", link = "PreProc" },
{ group = "NvimTreeLiveFilterValue", link = "ModeMsg" },
-- Clipboard
{ group = "NvimTreeCutHL", link = "SpellBad" },
{ group = "NvimTreeCopiedHL", link = "SpellRare" },
-- Bookmark
{ group = "NvimTreeBookmarkIcon", link = "NvimTreeFolderIcon" },
{ group = "NvimTreeBookmarkHL", link = "SpellLocal" },
-- Modified
{ group = "NvimTreeModifiedIcon", link = "Type" },
{ group = "NvimTreeModifiedFileHL", link = "NvimTreeModifiedIcon" },
{ group = "NvimTreeModifiedFolderHL", link = "NvimTreeModifiedFileHL" },
-- Hidden
{ group = "NvimTreeHiddenIcon", link = "Conceal" },
{ group = "NvimTreeHiddenFileHL", link = "NvimTreeHiddenIcon" },
{ group = "NvimTreeHiddenFolderHL", link = "NvimTreeHiddenFileHL" },
-- Hidden Display
{ group = "NvimTreeHiddenDisplay", link = "Conceal" },
-- Opened
{ group = "NvimTreeOpenedHL", link = "Special" },
-- Git Icon
{ group = "NvimTreeGitDeletedIcon", link = "Statement" },
{ group = "NvimTreeGitDirtyIcon", link = "Statement" },
{ group = "NvimTreeGitIgnoredIcon", link = "Comment" },
{ group = "NvimTreeGitMergeIcon", link = "Constant" },
{ group = "NvimTreeGitNewIcon", link = "PreProc" },
{ group = "NvimTreeGitRenamedIcon", link = "PreProc" },
{ group = "NvimTreeGitStagedIcon", link = "Constant" },
-- Git File Highlight
{ group = "NvimTreeGitFileDeletedHL", link = "NvimTreeGitDeletedIcon" },
{ group = "NvimTreeGitFileDirtyHL", link = "NvimTreeGitDirtyIcon" },
{ group = "NvimTreeGitFileIgnoredHL", link = "NvimTreeGitIgnoredIcon" },
{ group = "NvimTreeGitFileMergeHL", link = "NvimTreeGitMergeIcon" },
{ group = "NvimTreeGitFileNewHL", link = "NvimTreeGitNewIcon" },
{ group = "NvimTreeGitFileRenamedHL", link = "NvimTreeGitRenamedIcon" },
{ group = "NvimTreeGitFileStagedHL", link = "NvimTreeGitStagedIcon" },
-- Git Folder Highlight
{ group = "NvimTreeGitFolderDeletedHL", link = "NvimTreeGitFileDeletedHL" },
{ group = "NvimTreeGitFolderDirtyHL", link = "NvimTreeGitFileDirtyHL" },
{ group = "NvimTreeGitFolderIgnoredHL", link = "NvimTreeGitFileIgnoredHL" },
{ group = "NvimTreeGitFolderMergeHL", link = "NvimTreeGitFileMergeHL" },
{ group = "NvimTreeGitFolderNewHL", link = "NvimTreeGitFileNewHL" },
{ group = "NvimTreeGitFolderRenamedHL", link = "NvimTreeGitFileRenamedHL" },
{ group = "NvimTreeGitFolderStagedHL", link = "NvimTreeGitFileStagedHL" },
-- Diagnostics Icon
{ group = "NvimTreeDiagnosticErrorIcon", link = "DiagnosticError" },
{ group = "NvimTreeDiagnosticWarnIcon", link = "DiagnosticWarn" },
{ group = "NvimTreeDiagnosticInfoIcon", link = "DiagnosticInfo" },
{ group = "NvimTreeDiagnosticHintIcon", link = "DiagnosticHint" },
-- Diagnostics File Highlight
{ group = "NvimTreeDiagnosticErrorFileHL", link = "DiagnosticUnderlineError" },
{ group = "NvimTreeDiagnosticWarnFileHL", link = "DiagnosticUnderlineWarn" },
{ group = "NvimTreeDiagnosticInfoFileHL", link = "DiagnosticUnderlineInfo" },
{ group = "NvimTreeDiagnosticHintFileHL", link = "DiagnosticUnderlineHint" },
-- Diagnostics Folder Highlight
{ group = "NvimTreeDiagnosticErrorFolderHL", link = "NvimTreeDiagnosticErrorFileHL" },
{ group = "NvimTreeDiagnosticWarnFolderHL", link = "NvimTreeDiagnosticWarnFileHL" },
{ group = "NvimTreeDiagnosticInfoFolderHL", link = "NvimTreeDiagnosticInfoFileHL" },
{ group = "NvimTreeDiagnosticHintFolderHL", link = "NvimTreeDiagnosticHintFileHL" },
}
-- nvim-tree highlight groups to legacy
M.LEGACY_LINKS = {
NvimTreeModifiedIcon = "NvimTreeModifiedFile",
NvimTreeOpenedHL = "NvimTreeOpenedFile",
NvimTreeBookmarkIcon = "NvimTreeBookmark",
NvimTreeGitDeletedIcon = "NvimTreeGitDeleted",
NvimTreeGitDirtyIcon = "NvimTreeGitDirty",
NvimTreeGitIgnoredIcon = "NvimTreeGitIgnored",
NvimTreeGitMergeIcon = "NvimTreeGitMerge",
NvimTreeGitNewIcon = "NvimTreeGitNew",
NvimTreeGitRenamedIcon = "NvimTreeGitRenamed",
NvimTreeGitStagedIcon = "NvimTreeGitStaged",
NvimTreeGitFileDeletedHL = "NvimTreeFileDeleted",
NvimTreeGitFileDirtyHL = "NvimTreeFileDirty",
NvimTreeGitFileIgnoredHL = "NvimTreeFileIgnored",
NvimTreeGitFileMergeHL = "NvimTreeFileMerge",
NvimTreeGitFileNewHL = "NvimTreeFileNew",
NvimTreeGitFileRenamedHL = "NvimTreeFileRenamed",
NvimTreeGitFileStagedHL = "NvimTreeFileStaged",
NvimTreeGitFolderDeletedHL = "NvimTreeFolderDeleted",
NvimTreeGitFolderDirtyHL = "NvimTreeFolderDirty",
NvimTreeGitFolderIgnoredHL = "NvimTreeFolderIgnored",
NvimTreeGitFolderMergeHL = "NvimTreeFolderMerge",
NvimTreeGitFolderNewHL = "NvimTreeFolderNew",
NvimTreeGitFolderRenamedHL = "NvimTreeFolderRenamed",
NvimTreeGitFolderStagedHL = "NvimTreeFolderStaged",
NvimTreeDiagnosticErrorIcon = "NvimTreeLspDiagnosticsError",
NvimTreeDiagnosticWarnIcon = "NvimTreeLspDiagnosticsWarning",
NvimTreeDiagnosticInfoIcon = "NvimTreeLspDiagnosticsInformation",
NvimTreeDiagnosticHintIcon = "NvimTreeLspDiagnosticsHint",
NvimTreeDiagnosticErrorFileHL = "NvimTreeLspDiagnosticsErrorText",
NvimTreeDiagnosticWarnFileHL = "NvimTreeLspDiagnosticsWarningText",
NvimTreeDiagnosticInfoFileHL = "NvimTreeLspDiagnosticsInformationText",
NvimTreeDiagnosticHintFileHL = "NvimTreeLspDiagnosticsHintText",
NvimTreeDiagnosticErrorFolderHL = "NvimTreeLspDiagnosticsErrorFolderText",
NvimTreeDiagnosticWarnFolderHL = "NvimTreeLspDiagnosticsWarningFolderText",
NvimTreeDiagnosticInfoFolderHL = "NvimTreeLspDiagnosticsInformationFolderText",
NvimTreeDiagnosticHintFolderHL = "NvimTreeLspDiagnosticsHintFolderText",
}
function M.setup()
-- non-linked
for _, g in ipairs(M.HIGHLIGHT_GROUPS) do
if g.def then
vim.api.nvim_command("hi def " .. g.group .. " " .. g.def)
end
end
-- hard link override when legacy only is present
for from, to in pairs(M.LEGACY_LINKS) do
local hl_from = vim.api.nvim_get_hl(0, { name = from })
local hl_to = vim.api.nvim_get_hl(0, { name = to })
if vim.tbl_isempty(hl_from) and not vim.tbl_isempty(hl_to) then
vim.api.nvim_command("hi link " .. from .. " " .. to)
end
end
-- default links
for _, g in ipairs(M.HIGHLIGHT_GROUPS) do
if g.link then
vim.api.nvim_command("hi def link " .. g.group .. " " .. g.link)
end
end
end
return M

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

@@ -0,0 +1,65 @@
local DirectoryNode = require("nvim-tree.node.directory")
local M = {}
---@type table<string, boolean> record of which file is modified
M._modified = {}
---refresh M._modified
function M.reload_modified()
M._modified = {}
local bufs = vim.fn.getbufinfo({ bufmodified = 1, buflisted = 1 })
for _, buf in pairs(bufs) do
local path = buf.name
if path ~= "" then -- not a [No Name] buffer
-- mark all the parent as modified as well
while M._modified[path] ~= true do
-- no need to keep going if already recorded
-- This also prevents an infinite loop
M._modified[path] = true
path = vim.fn.fnamemodify(path, ":h")
end
end
end
end
---@param node Node
---@return boolean
function M.is_modified(node)
if not M.config.modified.enable then
return false
end
if not M._modified[node.absolute_path] then
return false
end
local dir = node:as(DirectoryNode)
if dir then
if not M.config.modified.show_on_dirs then
return false
end
if dir.open and not M.config.modified.show_on_open_dirs then
return false
end
end
return true
end
---A buffer exists for the node's absolute path
---@param node Node
---@return boolean
function M.is_opened(node)
return node and vim.fn.bufloaded(node.absolute_path) > 0
end
---@param opts table
function M.setup(opts)
M.config = {
modified = opts.modified,
}
end
return M

91
lua/nvim-tree/classic.lua Normal file
View File

@@ -0,0 +1,91 @@
--
-- classic
--
-- Copyright (c) 2014, rxi
--
-- This module is free software; you can redistribute it and/or modify it under
-- the terms of the MIT license. See LICENSE for details.
--
-- https://github.com/rxi/classic
--
---@class (exact) Class
---@field super Class
---@field private implements table<Class, boolean>
local Class = {}
Class.__index = Class ---@diagnostic disable-line: inject-field
---Default constructor
---@protected
function Class:new(...) --luacheck: ignore 212
end
---Extend a class, setting .super
function Class:extend()
local cls = {}
for k, v in pairs(self) do
if k:find("__") == 1 then
cls[k] = v
end
end
cls.__index = cls
cls.super = self
setmetatable(cls, self)
return cls
end
---Implement the functions of a mixin
---Add the mixin to .implements
---@param mixin Class
function Class:implement(mixin)
if not rawget(self, "implements") then
-- set on the class itself instead of parents
rawset(self, "implements", {})
end
self.implements[mixin] = true
for k, v in pairs(mixin) do
if self[k] == nil and type(v) == "function" then
self[k] = v
end
end
end
---Object is an instance of class or implements a mixin
---@generic T
---@param class T
---@return boolean
function Class:is(class)
local mt = getmetatable(self)
while mt do
if mt == class then
return true
end
if mt.implements and mt.implements[class] then
return true
end
mt = getmetatable(mt)
end
return false
end
---Return object if :is otherwise nil
---@generic T
---@param class T
---@return T|nil
function Class:as(class)
return self:is(class) and self or nil
end
---Constructor to create instance, call :new and return
function Class:__call(...)
local obj = setmetatable({}, self)
obj:new(...)
return obj
end
-- avoid unused param warnings in abstract methods
---@param ... any
function Class:nop(...) --luacheck: ignore 212
end
return Class

View File

@@ -1,105 +0,0 @@
local M = {}
local function get_color_from_hl(hl_name, fallback)
local id = vim.api.nvim_get_hl_id_by_name(hl_name)
if not id then
return fallback
end
local foreground = vim.fn.synIDattr(vim.fn.synIDtrans(id), "fg")
if not foreground or foreground == "" then
return fallback
end
return foreground
end
local function get_colors()
return {
red = vim.g.terminal_color_1 or get_color_from_hl("Keyword", "Red"),
green = vim.g.terminal_color_2 or get_color_from_hl("Character", "Green"),
yellow = vim.g.terminal_color_3 or get_color_from_hl("PreProc", "Yellow"),
blue = vim.g.terminal_color_4 or get_color_from_hl("Include", "Blue"),
purple = vim.g.terminal_color_5 or get_color_from_hl("Define", "Purple"),
cyan = vim.g.terminal_color_6 or get_color_from_hl("Conditional", "Cyan"),
dark_red = vim.g.terminal_color_9 or get_color_from_hl("Keyword", "DarkRed"),
orange = vim.g.terminal_color_11 or get_color_from_hl("Number", "Orange"),
}
end
local function get_hl_groups()
local colors = get_colors()
return {
IndentMarker = { fg = "#8094b4" },
Symlink = { gui = "bold", fg = colors.cyan },
FolderIcon = { fg = "#8094b4" },
RootFolder = { fg = colors.purple },
ExecFile = { gui = "bold", fg = colors.green },
SpecialFile = { gui = "bold,underline", fg = colors.yellow },
ImageFile = { gui = "bold", fg = colors.purple },
OpenedFile = { gui = "bold", fg = colors.green },
GitDirty = { fg = colors.dark_red },
GitDeleted = { fg = colors.dark_red },
GitStaged = { fg = colors.green },
GitMerge = { fg = colors.orange },
GitRenamed = { fg = colors.purple },
GitNew = { fg = colors.yellow },
WindowPicker = { gui = "bold", fg = "#ededed", bg = "#4493c8" },
LiveFilterPrefix = { gui = "bold", fg = colors.purple },
LiveFilterValue = { gui = "bold", fg = "#fff" },
Bookmark = { fg = colors.green },
}
end
local function get_links()
return {
FolderName = "Directory",
EmptyFolderName = "Directory",
OpenedFolderName = "Directory",
OpenedFolderIcon = "NvimTreeFolderIcon",
ClosedFolderIcon = "NvimTreeFolderIcon",
Normal = "Normal",
NormalNC = "NvimTreeNormal",
EndOfBuffer = "EndOfBuffer",
CursorLineNr = "CursorLineNr",
LineNr = "LineNr",
CursorLine = "CursorLine",
VertSplit = "VertSplit",
WinSeparator = "NvimTreeVertSplit",
CursorColumn = "CursorColumn",
FileDirty = "NvimTreeGitDirty",
FileNew = "NvimTreeGitNew",
FileRenamed = "NvimTreeGitRenamed",
FileMerge = "NvimTreeGitMerge",
FileStaged = "NvimTreeGitStaged",
FileDeleted = "NvimTreeGitDeleted",
FileIgnored = "NvimTreeGitIgnored",
Popup = "Normal",
GitIgnored = "Comment",
StatusLine = "StatusLine",
StatusLineNC = "StatusLineNC",
SignColumn = "NvimTreeNormal",
}
end
function M.setup()
local higlight_groups = get_hl_groups()
for k, d in pairs(higlight_groups) do
local gui = d.gui and " gui=" .. d.gui or ""
local fg = d.fg and " guifg=" .. d.fg or ""
local bg = d.bg and " guibg=" .. d.bg or ""
vim.api.nvim_command("hi def NvimTree" .. k .. gui .. fg .. bg)
end
local links = get_links()
for k, d in pairs(links) do
vim.api.nvim_command("hi def link NvimTree" .. k .. " " .. d)
end
end
return M

157
lua/nvim-tree/commands.lua Normal file
View File

@@ -0,0 +1,157 @@
local api = require("nvim-tree.api")
local view = require("nvim-tree.view")
local M = {}
local CMDS = {
{
name = "NvimTreeOpen",
opts = {
desc = "nvim-tree: open",
nargs = "?",
complete = "dir",
},
command = function(c)
api.tree.open({ path = c.args })
end,
},
{
name = "NvimTreeClose",
opts = {
desc = "nvim-tree: close",
bar = true,
},
command = function()
api.tree.close()
end,
},
{
name = "NvimTreeToggle",
opts = {
desc = "nvim-tree: toggle",
nargs = "?",
complete = "dir",
},
command = function(c)
api.tree.toggle({
find_file = false,
focus = true,
path = c.args,
update_root = false,
})
end,
},
{
name = "NvimTreeFocus",
opts = {
desc = "nvim-tree: focus",
bar = true,
},
command = function()
api.tree.open()
end,
},
{
name = "NvimTreeRefresh",
opts = {
desc = "nvim-tree: refresh",
bar = true,
},
command = function()
api.tree.reload()
end,
},
{
name = "NvimTreeClipboard",
opts = {
desc = "nvim-tree: print clipboard",
bar = true,
},
command = function()
api.fs.print_clipboard()
end,
},
{
name = "NvimTreeFindFile",
opts = {
desc = "nvim-tree: find file",
bang = true,
bar = true,
},
command = function(c)
api.tree.find_file({
open = true,
focus = true,
update_root = c.bang,
})
end,
},
{
name = "NvimTreeFindFileToggle",
opts = {
desc = "nvim-tree: find file, toggle",
bang = true,
nargs = "?",
complete = "dir",
},
command = function(c)
api.tree.toggle({
find_file = true,
focus = true,
path = c.args,
update_root = c.bang,
})
end,
},
{
name = "NvimTreeResize",
opts = {
desc = "nvim-tree: resize",
nargs = 1,
bar = true,
},
command = function(c)
view.resize(c.args)
end,
},
{
name = "NvimTreeCollapse",
opts = {
desc = "nvim-tree: collapse",
bar = true,
},
command = function()
api.tree.collapse_all(false)
end,
},
{
name = "NvimTreeCollapseKeepBuffers",
opts = {
desc = "nvim-tree: collapse, keep directories open",
bar = true,
},
command = function()
api.tree.collapse_all(true)
end,
},
{
name = "NvimTreeHiTest",
opts = {
desc = "nvim-tree: highlight test",
},
command = api.diagnostics.hi_test,
},
}
function M.get()
return vim.deepcopy(CMDS)
end
function M.setup()
for _, cmd in ipairs(CMDS) do
local opts = vim.tbl_extend("force", cmd.opts, { force = true })
vim.api.nvim_create_user_command(cmd.name, cmd.command, opts)
end
end
return M

View File

@@ -1,10 +0,0 @@
-- INFO: DEPRECATED FILE, DO NOT ADD ANYTHING IN THERE
-- keeping to avoid breaking user configs. Will remove during a weekend.
local M = {}
-- TODO: remove this once the cb property is not supported in mappings
function M.nvim_tree_callback(callback_name)
return string.format("<cmd>lua require'nvim-tree.actions.dispatch'.dispatch('%s')<CR>", callback_name)
end
return M

View File

@@ -1,43 +1,64 @@
local events = require "nvim-tree.events" local events = require("nvim-tree.events")
local explorer = require "nvim-tree.explorer" local notify = require("nvim-tree.notify")
local live_filter = require "nvim-tree.live-filter" local view = require("nvim-tree.view")
local view = require "nvim-tree.view" local log = require("nvim-tree.log")
local log = require "nvim-tree.log"
local M = {} local M = {}
TreeExplorer = nil ---@type Explorer|nil
local TreeExplorer = nil
local first_init_done = false local first_init_done = false
---@param foldername string
function M.init(foldername) function M.init(foldername)
local pn = string.format("core init %s", foldername) local profile = log.profile_start("core init %s", foldername)
local ps = log.profile_start(pn)
if TreeExplorer then if TreeExplorer then
TreeExplorer:destroy() TreeExplorer:destroy()
end end
TreeExplorer = explorer.Explorer.new(foldername)
local err, path
if foldername then
path, err = vim.loop.fs_realpath(foldername)
else
path, err = vim.loop.cwd()
end
if path then
TreeExplorer = require("nvim-tree.explorer")({ path = path })
else
notify.error(err)
TreeExplorer = nil
end
if not first_init_done then if not first_init_done then
events._dispatch_ready() events._dispatch_ready()
first_init_done = true first_init_done = true
end end
log.profile_end(ps, pn) log.profile_end(profile)
end end
---@return Explorer|nil
function M.get_explorer() function M.get_explorer()
return TreeExplorer return TreeExplorer
end end
function M.get_cwd() function M.reset_explorer()
return TreeExplorer.absolute_path TreeExplorer = nil
end end
---@return string|nil
function M.get_cwd()
return TreeExplorer and TreeExplorer.absolute_path
end
---@return integer
function M.get_nodes_starting_line() function M.get_nodes_starting_line()
local offset = 1 local offset = 1
if view.is_root_folder_visible(M.get_cwd()) then if view.is_root_folder_visible(M.get_cwd()) then
offset = offset + 1 offset = offset + 1
end end
if live_filter.filter then if TreeExplorer and TreeExplorer.live_filter.filter then
return offset + 1 return offset + 1
end end
return offset return offset

View File

@@ -1,141 +1,232 @@
local utils = require "nvim-tree.utils" local core = require("nvim-tree.core")
local view = require "nvim-tree.view" local utils = require("nvim-tree.utils")
local core = require "nvim-tree.core" local view = require("nvim-tree.view")
local log = require "nvim-tree.log" local log = require("nvim-tree.log")
local DirectoryNode = require("nvim-tree.node.directory")
local M = {} local M = {}
local GROUP = "NvimTreeDiagnosticSigns" ---COC severity level strings to LSP severity levels
---@enum COC_SEVERITY_LEVELS
local severity_levels = { Error = 1, Warning = 2, Information = 3, Hint = 4 } local COC_SEVERITY_LEVELS = {
local sign_names = { Error = 1,
{ "NvimTreeSignError", "NvimTreeLspDiagnosticsError" }, Warning = 2,
{ "NvimTreeSignWarning", "NvimTreeLspDiagnosticsWarning" }, Information = 3,
{ "NvimTreeSignInformation", "NvimTreeLspDiagnosticsInformation" }, Hint = 4,
{ "NvimTreeSignHint", "NvimTreeLspDiagnosticsHint" },
} }
local function add_sign(linenr, severity) ---Absolute Node path to LSP severity level
local buf = view.get_bufnr() ---@alias NodeSeverities table<string, vim.diagnostic.Severity>
if not vim.api.nvim_buf_is_valid(buf) or not vim.api.nvim_buf_is_loaded(buf) then
return ---@class DiagStatus
end ---@field value lsp.DiagnosticSeverity|nil
local sign_name = sign_names[severity][1] ---@field cache_version integer
vim.fn.sign_place(0, GROUP, sign_name, buf, { lnum = linenr, priority = 2 })
--- The buffer-severity mappings derived during the last diagnostic list update.
---@type NodeSeverities
local NODE_SEVERITIES = {}
---The cache version number of the buffer-severity mappings.
---@type integer
local NODE_SEVERITIES_VERSION = 0
---@param path string
---@return string
local function uniformize_path(path)
return utils.canonical_path(path:gsub("\\", "/"))
end end
local function from_nvim_lsp() ---Severity is within diagnostics.severity.min, diagnostics.severity.max
local buffer_severity = {} ---@param severity lsp.DiagnosticSeverity
---@param config table
for _, diagnostic in ipairs(vim.diagnostic.get(nil, { severity = M.severity })) do ---@return boolean
local buf = diagnostic.bufnr local function is_severity_in_range(severity, config)
if vim.api.nvim_buf_is_valid(buf) then return config.max <= severity and severity <= config.min
local bufname = vim.api.nvim_buf_get_name(buf)
local lowest_severity = buffer_severity[bufname]
if not lowest_severity or diagnostic.severity < lowest_severity then
buffer_severity[bufname] = diagnostic.severity
end
end
end
return buffer_severity
end end
local function from_coc() ---Handle any COC exceptions, preventing any propagation
if vim.g.coc_service_initialized ~= 1 then ---@param err string
return {} local function handle_coc_exception(err)
log.line("diagnostics", "handle_coc_exception: %s", vim.inspect(err))
local notify = true
-- avoid distractions on interrupts (CTRL-C)
if err:find("Vim:Interrupt") or err:find("Keyboard interrupt") then
notify = false
end end
local diagnostic_list = vim.fn.CocAction "diagnosticList" if notify then
if type(diagnostic_list) ~= "table" or vim.tbl_isempty(diagnostic_list) then require("nvim-tree.notify").error("Diagnostics update from coc.nvim failed. " .. vim.inspect(err))
return {}
end end
local buffer_severity = {}
local diagnostics = {}
for _, diagnostic in ipairs(diagnostic_list) do
local bufname = diagnostic.file
local severity = severity_levels[diagnostic.severity]
local severity_list = diagnostics[bufname] or {}
table.insert(severity_list, severity)
diagnostics[bufname] = severity_list
end
for bufname, severity_list in pairs(diagnostics) do
if not buffer_severity[bufname] then
local severity = math.min(unpack(severity_list))
buffer_severity[bufname] = severity
end
end
return buffer_severity
end end
---COC service initialized
---@return boolean
local function is_using_coc() local function is_using_coc()
return vim.g.coc_service_initialized == 1 return vim.g.coc_service_initialized == 1
end end
function M.clear() ---Marshal severities from COC. Does nothing when COC service not started.
if not M.enable or not view.is_buf_valid(view.get_bufnr()) then ---@return NodeSeverities
return local function from_coc()
if not is_using_coc() then
return {}
end end
vim.fn.sign_unplace(GROUP) local ok, diagnostic_list = xpcall(function()
return vim.fn.CocAction("diagnosticList")
end, handle_coc_exception)
if not ok or type(diagnostic_list) ~= "table" or vim.tbl_isempty(diagnostic_list) then
return {}
end
local buffer_severity = {}
for _, diagnostic in ipairs(diagnostic_list) do
local bufname = uniformize_path(diagnostic.file)
local coc_severity = COC_SEVERITY_LEVELS[diagnostic.severity]
local highest_severity = buffer_severity[bufname] or coc_severity
if is_severity_in_range(highest_severity, M.severity) then
buffer_severity[bufname] = math.min(highest_severity, coc_severity)
end
end
return buffer_severity
end end
function M.update() ---Maybe retrieve severity level from the cache
if not M.enable or not core.get_explorer() or not view.is_buf_valid(view.get_bufnr()) then ---@param node Node
return ---@return DiagStatus
end local function from_cache(node)
utils.debounce("diagnostics", M.debounce_delay, function() local nodepath = uniformize_path(node.absolute_path)
local ps = log.profile_start "diagnostics update" local max_severity = nil
log.line("diagnostics", "update") if not node:is(DirectoryNode) then
-- direct cache hit for files
local buffer_severity max_severity = NODE_SEVERITIES[nodepath]
if is_using_coc() then else
buffer_severity = from_coc() -- dirs should be searched in the list of cached buffer names by prefix
else for bufname, severity in pairs(NODE_SEVERITIES) do
buffer_severity = from_nvim_lsp() local node_contains_buf = vim.startswith(bufname, nodepath .. "/")
end if node_contains_buf then
if not max_severity or severity < max_severity then
M.clear() max_severity = severity
local nodes_by_line = utils.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line())
for _, node in pairs(nodes_by_line) do
node.diag_status = nil
end
for bufname, severity in pairs(buffer_severity) do
local bufpath = utils.canonical_path(bufname)
log.line("diagnostics", " bufpath '%s' severity %d", bufpath, severity)
if 0 < severity and severity < 5 then
for line, node in pairs(nodes_by_line) do
local nodepath = utils.canonical_path(node.absolute_path)
log.line("diagnostics", " %d checking nodepath '%s'", line, nodepath)
if M.show_on_dirs and vim.startswith(bufpath, nodepath) and (not node.open or M.show_on_open_dirs) then
log.line("diagnostics", " matched fold node '%s'", node.absolute_path)
node.diag_status = severity
add_sign(line, severity)
elseif nodepath == bufpath then
log.line("diagnostics", " matched file node '%s'", node.absolute_path)
node.diag_status = severity
add_sign(line, severity)
end
end end
end end
end end
log.profile_end(ps, "diagnostics update") end
return { value = max_severity, cache_version = NODE_SEVERITIES_VERSION }
end
---Fired on DiagnosticChanged for a single buffer.
---This will be called on set and reset of diagnostics.
---On disabling LSP, a reset event will be sent for all buffers.
---@param ev table standard event with data.diagnostics populated
function M.update_lsp(ev)
if not M.enable or not ev or not ev.data or not ev.data.diagnostics then
return
end
local profile_event = log.profile_start("DiagnosticChanged event")
local diagnostics = vim.diagnostic.get(ev.buf)
-- use the buffer from the event, as ev.data.diagnostics will be empty on resolved diagnostics
local bufname = uniformize_path(vim.api.nvim_buf_get_name(ev.buf))
---@type vim.diagnostic.Severity?
local new_severity = nil
-- most severe (lowest) severity in user range
for _, diagnostic in ipairs(diagnostics) do
if diagnostic.severity >= M.severity.max and diagnostic.severity <= M.severity.min then
if not new_severity or diagnostic.severity < new_severity then
new_severity = diagnostic.severity
end
end
end
-- record delta and schedule a redraw
if new_severity ~= NODE_SEVERITIES[bufname] then
NODE_SEVERITIES[bufname] = new_severity
NODE_SEVERITIES_VERSION = NODE_SEVERITIES_VERSION + 1
utils.debounce("DiagnosticChanged redraw", M.debounce_delay, function()
local profile_redraw = log.profile_start("DiagnosticChanged redraw")
local explorer = core.get_explorer()
if explorer then
explorer.renderer:draw()
end
log.profile_end(profile_redraw)
end)
end
log.profile_end(profile_event)
end
---Fired on CocDiagnosticChanged events:
---debounced retrieval, cache update, version increment and draw
function M.update_coc()
if not M.enable then
return
end
utils.debounce("CocDiagnosticChanged update", M.debounce_delay, function()
local profile = log.profile_start("CocDiagnosticChanged update")
NODE_SEVERITIES = from_coc()
NODE_SEVERITIES_VERSION = NODE_SEVERITIES_VERSION + 1
if log.enabled("diagnostics") then
for bufname, severity in pairs(NODE_SEVERITIES) do
log.line("diagnostics", "COC Indexing bufname '%s' with severity %d", bufname, severity)
end
end
log.profile_end(profile)
local bufnr = view.get_bufnr()
local should_draw = bufnr and vim.api.nvim_buf_is_valid(bufnr) and vim.api.nvim_buf_is_loaded(bufnr)
if should_draw then
local explorer = core.get_explorer()
if explorer then
explorer.renderer:draw()
end
end
end) end)
end end
local links = { ---Maybe retrieve diagnostic status for a node.
NvimTreeLspDiagnosticsError = "DiagnosticError", ---Returns cached value when node's version matches.
NvimTreeLspDiagnosticsWarning = "DiagnosticWarn", ---@param node Node
NvimTreeLspDiagnosticsInformation = "DiagnosticInfo", ---@return DiagStatus|nil
NvimTreeLspDiagnosticsHint = "DiagnosticHint", function M.get_diag_status(node)
} if not M.enable then
return nil
end
-- dir but we shouldn't show on dirs at all
if node:is(DirectoryNode) and not M.show_on_dirs then
return nil
end
-- here, we do a lazy update of the diagnostic status carried by the node.
-- This is by design, as diagnostics and nodes live in completely separate
-- worlds, and this module is the link between the two
if not node.diag_status or node.diag_status.cache_version < NODE_SEVERITIES_VERSION then
node.diag_status = from_cache(node)
end
local dir = node:as(DirectoryNode)
-- file
if not dir then
return node.diag_status
end
-- dir is closed or we should show on open_dirs
if not dir.open or M.show_on_open_dirs then
return node.diag_status
end
return nil
end
function M.setup(opts) function M.setup(opts)
M.enable = opts.diagnostics.enable M.enable = opts.diagnostics.enable
@@ -148,14 +239,6 @@ function M.setup(opts)
M.show_on_dirs = opts.diagnostics.show_on_dirs M.show_on_dirs = opts.diagnostics.show_on_dirs
M.show_on_open_dirs = opts.diagnostics.show_on_open_dirs M.show_on_open_dirs = opts.diagnostics.show_on_open_dirs
vim.fn.sign_define(sign_names[1][1], { text = opts.diagnostics.icons.error, texthl = sign_names[1][2] })
vim.fn.sign_define(sign_names[2][1], { text = opts.diagnostics.icons.warning, texthl = sign_names[2][2] })
vim.fn.sign_define(sign_names[3][1], { text = opts.diagnostics.icons.info, texthl = sign_names[3][2] })
vim.fn.sign_define(sign_names[4][1], { text = opts.diagnostics.icons.hint, texthl = sign_names[4][2] })
for lhs, rhs in pairs(links) do
vim.cmd("hi def link " .. lhs .. " " .. rhs)
end
end end
return M return M

14
lua/nvim-tree/enum.lua Normal file
View File

@@ -0,0 +1,14 @@
local M = {}
---Reason for filter in filter.lua
---@enum FILTER_REASON
M.FILTER_REASON = {
none = 0, -- It's not filtered
git = 1,
buf = 2,
dotfile = 4,
custom = 8,
bookmark = 16,
}
return M

View File

@@ -1,4 +1,4 @@
local notify = require "nvim-tree.notify" local notify = require("nvim-tree.notify")
local M = {} local M = {}
@@ -8,25 +8,36 @@ 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",
FileCreated = "FileCreated", FileCreated = "FileCreated",
WillRemoveFile = "WillRemoveFile",
FileRemoved = "FileRemoved", FileRemoved = "FileRemoved",
FolderCreated = "FolderCreated", FolderCreated = "FolderCreated",
FolderRemoved = "FolderRemoved", FolderRemoved = "FolderRemoved",
Resize = "Resize", Resize = "Resize",
TreeAttachedPost = "TreeAttachedPost",
TreeRendered = "TreeRendered",
} }
---@param event_name string
---@return table
local function get_handlers(event_name) local function get_handlers(event_name)
return global_handlers[event_name] or {} return global_handlers[event_name] or {}
end end
---@param event_name string
---@param handler function
function M.subscribe(event_name, handler) function M.subscribe(event_name, handler)
local handlers = get_handlers(event_name) local handlers = get_handlers(event_name)
table.insert(handlers, handler) table.insert(handlers, handler)
global_handlers[event_name] = handlers global_handlers[event_name] = handlers
end end
---@param event_name string
---@param payload table|nil
local function dispatch(event_name, payload) local function dispatch(event_name, payload)
for _, handler in pairs(get_handlers(event_name)) do for _, handler in pairs(get_handlers(event_name)) do
local success, error = pcall(handler, payload) local success, error = pcall(handler, payload)
@@ -51,11 +62,21 @@ function M._dispatch_node_renamed(old_name, new_name)
dispatch(M.Event.NodeRenamed, { old_name = old_name, new_name = new_name }) dispatch(M.Event.NodeRenamed, { old_name = old_name, new_name = new_name })
end end
--@private
function M._dispatch_will_remove_file(fname)
dispatch(M.Event.WillRemoveFile, { fname = fname })
end
--@private --@private
function M._dispatch_file_removed(fname) function M._dispatch_file_removed(fname)
dispatch(M.Event.FileRemoved, { fname = fname }) dispatch(M.Event.FileRemoved, { fname = fname })
end end
--@private
function M._dispatch_will_create_file(fname)
dispatch(M.Event.WillCreateFile, { fname = fname })
end
--@private --@private
function M._dispatch_file_created(fname) function M._dispatch_file_created(fname)
dispatch(M.Event.FileCreated, { fname = fname }) dispatch(M.Event.FileCreated, { fname = fname })
@@ -71,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)
@@ -86,49 +112,14 @@ function M._dispatch_on_tree_resize(size)
dispatch(M.Event.Resize, size) dispatch(M.Event.Resize, size)
end end
--- @deprecated --@private
function M.on_nvim_tree_ready(handler) function M._dispatch_tree_attached_post(buf)
M.subscribe(M.Event.Ready, handler) dispatch(M.Event.TreeAttachedPost, buf)
end end
--- @deprecated --@private
function M.on_node_renamed(handler) function M._dispatch_on_tree_rendered(bufnr, winnr)
M.subscribe(M.Event.NodeRenamed, handler) dispatch(M.Event.TreeRendered, { bufnr = bufnr, winnr = winnr })
end
--- @deprecated
function M.on_file_created(handler)
M.subscribe(M.Event.FileCreated, handler)
end
--- @deprecated
function M.on_file_removed(handler)
M.subscribe(M.Event.FileRemoved, handler)
end
--- @deprecated
function M.on_folder_created(handler)
M.subscribe(M.Event.FolderCreated, handler)
end
--- @deprecated
function M.on_folder_removed(handler)
M.subscribe(M.Event.FolderRemoved, handler)
end
--- @deprecated
function M.on_tree_open(handler)
M.subscribe(M.Event.TreeOpen, handler)
end
--- @deprecated
function M.on_tree_close(handler)
M.subscribe(M.Event.TreeClose, handler)
end
--- @deprecated
function M.on_tree_resize(handler)
M.subscribe(M.Event.Resize, handler)
end end
return M return M

View File

@@ -1,74 +0,0 @@
local M = {}
local function get_dir_git_status(parent_ignored, status, absolute_path)
if parent_ignored then
return "!!"
end
local file_status = status.files and status.files[absolute_path]
if file_status then
return file_status
end
return status.dirs and status.dirs[absolute_path]
end
local function get_git_status(parent_ignored, status, absolute_path)
return parent_ignored and "!!" or status.files and status.files[absolute_path]
end
function M.has_one_child_folder(node)
return #node.nodes == 1 and node.nodes[1].nodes and vim.loop.fs_access(node.nodes[1].absolute_path, "R")
end
function M.update_git_status(node, parent_ignored, status)
-- status of the node's absolute path
if node.nodes then
node.git_status = get_dir_git_status(parent_ignored, status, node.absolute_path)
else
node.git_status = get_git_status(parent_ignored, status, node.absolute_path)
end
-- status of the link target, if the link itself is not dirty
if node.link_to and not node.git_status then
if node.nodes then
node.git_status = get_dir_git_status(parent_ignored, status, node.link_to)
else
node.git_status = get_git_status(parent_ignored, status, node.link_to)
end
end
end
function M.shows_git_status(node)
if not node.git_status then
-- status doesn't exist
return false
elseif not node.nodes then
-- status exist and is a file
return true
elseif not node.open then
-- status exist, is a closed dir
return M.config.git.show_on_dirs
else
-- status exist, is a open dir
return M.config.git.show_on_dirs and M.config.git.show_on_open_dirs
end
end
function M.node_destroy(node)
if not node then
return
end
if node.watcher then
node.watcher:destroy()
end
end
function M.setup(opts)
M.config = {
git = opts.git,
}
end
return M

View File

@@ -1,92 +0,0 @@
local utils = require "nvim-tree.utils"
local builders = require "nvim-tree.explorer.node-builders"
local common = require "nvim-tree.explorer.common"
local sorters = require "nvim-tree.explorer.sorters"
local filters = require "nvim-tree.explorer.filters"
local live_filter = require "nvim-tree.live-filter"
local notify = require "nvim-tree.notify"
local log = require "nvim-tree.log"
local M = {}
local function get_type_from(type_, cwd)
return type_ or (vim.loop.fs_stat(cwd) or {}).type
end
local function populate_children(handle, cwd, node, git_status)
local node_ignored = node.git_status == "!!"
local nodes_by_path = utils.bool_record(node.nodes, "absolute_path")
local filter_status = filters.prepare(git_status)
while true do
local name, t = vim.loop.fs_scandir_next(handle)
if not name then
break
end
local abs = utils.path_join { cwd, name }
t = get_type_from(t, abs)
if not filters.should_filter(abs, filter_status) and not nodes_by_path[abs] then
local child = nil
if t == "directory" and vim.loop.fs_access(abs, "R") then
child = builders.folder(node, abs, name)
elseif t == "file" then
child = builders.file(node, abs, name)
elseif t == "link" then
local link = builders.link(node, abs, name)
if link.link_to ~= nil then
child = link
end
end
if child then
table.insert(node.nodes, child)
nodes_by_path[child.absolute_path] = true
common.update_git_status(child, node_ignored, git_status)
end
end
end
end
local function get_dir_handle(cwd)
local handle = vim.loop.fs_scandir(cwd)
if type(handle) == "string" then
notify.error(handle)
return
end
return handle
end
function M.explore(node, status)
local cwd = node.link_to or node.absolute_path
local handle = get_dir_handle(cwd)
if not handle then
return
end
local pn = string.format("explore init %s", node.absolute_path)
local ps = log.profile_start(pn)
populate_children(handle, cwd, node, status)
local is_root = not node.parent
local child_folder_only = common.has_one_child_folder(node) and node.nodes[1]
if M.config.group_empty and not is_root and child_folder_only then
node.group_next = child_folder_only
local ns = M.explore(child_folder_only, status)
node.nodes = ns or {}
log.profile_end(ps, pn)
return ns
end
sorters.merge_sort(node.nodes, sorters.node_comparator)
live_filter.apply_filter(node)
log.profile_end(ps, pn)
return node.nodes
end
function M.setup(opts)
M.config = opts.renderer
end
return M

View File

@@ -1,12 +1,60 @@
local utils = require "nvim-tree.utils" local utils = require("nvim-tree.utils")
local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON
local M = { local Class = require("nvim-tree.classic")
ignore_list = {},
exclude_list = {},
}
local function is_excluded(path) ---@alias FilterType "custom" | "dotfiles" | "git_ignored" | "git_clean" | "no_buffer" | "no_bookmark"
for _, node in ipairs(M.exclude_list) do
---@class (exact) Filters: Class
---@field enabled boolean
---@field state table<FilterType, boolean>
---@field private explorer Explorer
---@field private exclude_list string[] filters.exclude
---@field private ignore_list table<string, boolean> filters.custom string table
---@field private custom_function (fun(absolute_path: string): boolean)|nil filters.custom function
local Filters = Class:extend()
---@class Filters
---@overload fun(args: FiltersArgs): Filters
---@class (exact) FiltersArgs
---@field explorer Explorer
---@protected
---@param args FiltersArgs
function Filters:new(args)
self.explorer = args.explorer
self.ignore_list = {}
self.exclude_list = self.explorer.opts.filters.exclude
self.custom_function = nil
self.enabled = self.explorer.opts.filters.enable
self.state = {
custom = true,
dotfiles = self.explorer.opts.filters.dotfiles,
git_ignored = self.explorer.opts.filters.git_ignored,
git_clean = self.explorer.opts.filters.git_clean,
no_buffer = self.explorer.opts.filters.no_buffer,
no_bookmark = self.explorer.opts.filters.no_bookmark,
}
local custom_filter = self.explorer.opts.filters.custom
if type(custom_filter) == "function" then
self.custom_function = custom_filter
else
if custom_filter and #custom_filter > 0 then
for _, filter_name in pairs(custom_filter) do
self.ignore_list[filter_name] = true
end
end
end
end
---@private
---@param path string
---@return boolean
function Filters:is_excluded(path)
for _, node in ipairs(self.exclude_list) do
if path:match(node) then if path:match(node) then
return true return true
end end
@@ -15,24 +63,27 @@ local function is_excluded(path)
end end
---Check if the given path is git clean/ignored ---Check if the given path is git clean/ignored
---@private
---@param path string Absolute path ---@param path string Absolute path
---@param git_status table from prepare ---@param project GitProject from prepare
---@return boolean ---@return boolean
local function git(path, git_status) function Filters:git(path, project)
if type(git_status) ~= "table" or type(git_status.files) ~= "table" or type(git_status.dirs) ~= "table" then if type(project) ~= "table" or type(project.files) ~= "table" or type(project.dirs) ~= "table" then
return false return false
end end
-- default status to clean -- default status to clean
local status = git_status.files[path] or git_status.dirs[path] or " " local xy = project.files[path]
xy = xy or project.dirs.direct[path] and project.dirs.direct[path][1]
xy = xy or project.dirs.indirect[path] and project.dirs.indirect[path][1]
-- filter ignored; overrides clean as they are effectively dirty -- filter ignored; overrides clean as they are effectively dirty
if M.config.filter_git_ignored and status == "!!" then if self.state.git_ignored and xy == "!!" then
return true return true
end end
-- filter clean -- filter clean
if M.config.filter_git_clean and status == " " then if self.state.git_clean and not xy then
return true return true
end end
@@ -40,18 +91,18 @@ local function git(path, git_status)
end end
---Check if the given path has no listed buffer ---Check if the given path has no listed buffer
---@private
---@param path string Absolute path ---@param path string Absolute path
---@param bufinfo table vim.fn.getbufinfo { buflisted = 1 } ---@param bufinfo table vim.fn.getbufinfo { buflisted = 1 }
---@param unloaded_bufnr number optional bufnr recently unloaded via BufUnload event
---@return boolean ---@return boolean
local function buf(path, bufinfo, unloaded_bufnr) function Filters:buf(path, bufinfo)
if not M.config.filter_no_buffer or type(bufinfo) ~= "table" then if not self.state.no_buffer or type(bufinfo) ~= "table" then
return false return false
end end
-- filter files with no open buffer and directories containing no open buffers -- filter files with no open buffer and directories containing no open buffers
for _, b in ipairs(bufinfo) do for _, b in ipairs(bufinfo) do
if b.name == path or b.name:find(path .. "/", 1, true) and b.bufnr ~= unloaded_bufnr then if b.name == path or b.name:find(path .. "/", 1, true) then
return false return false
end end
end end
@@ -59,28 +110,78 @@ local function buf(path, bufinfo, unloaded_bufnr)
return true return true
end end
local function dotfile(path) ---@private
return M.config.filter_dotfiles and utils.path_basename(path):sub(1, 1) == "." ---@param path string
---@return boolean
function Filters:dotfile(path)
return self.state.dotfiles and utils.path_basename(path):sub(1, 1) == "."
end end
local function custom(path) ---Bookmark is present
if not M.config.filter_custom then ---@private
---@param path string
---@param path_type string|nil filetype of path
---@param bookmarks table<string, string|nil> path, filetype table of bookmarked files
---@return boolean
function Filters:bookmark(path, path_type, bookmarks)
if not self.state.no_bookmark then
return false
end
-- if bookmark is empty, we should see a empty filetree
if next(bookmarks) == nil then
return true
end
local mark_parent = utils.path_add_trailing(path)
for mark, mark_type in pairs(bookmarks) do
if path == mark then
return false
end
if path_type == "directory" then
-- check if path is mark's parent
if vim.fn.stridx(mark, mark_parent) == 0 then
return false
end
end
if mark_type == "directory" then
-- check if mark is path's parent
local path_parent = utils.path_add_trailing(mark)
if vim.fn.stridx(path, path_parent) == 0 then
return false
end
end
end
return true
end
---@private
---@param path string
---@return boolean
function Filters:custom(path)
if not self.state.custom then
return false return false
end end
local basename = utils.path_basename(path) local basename = utils.path_basename(path)
-- filter user's custom function
if self.custom_function and self.custom_function(path) then
return true
end
-- filter custom regexes -- filter custom regexes
local relpath = utils.path_relative(path, vim.loop.cwd()) local relpath = utils.path_relative(path, vim.loop.cwd())
for pat, _ in pairs(M.ignore_list) do for pat, _ in pairs(self.ignore_list) do
if vim.fn.match(relpath, pat) ~= -1 or vim.fn.match(basename, pat) ~= -1 then if vim.fn.match(relpath, pat) ~= -1 or vim.fn.match(basename, pat) ~= -1 then
return true return true
end end
end end
local idx = path:match ".+()%.[^.]+$" local idx = path:match(".+()%.[^.]+$")
if idx then if idx then
if M.ignore_list["*" .. string.sub(path, idx)] == true then if self.ignore_list["*" .. string.sub(path, idx)] == true then
return true return true
end end
end end
@@ -89,21 +190,27 @@ local function custom(path)
end end
---Prepare arguments for should_filter. This is done prior to should_filter for efficiency reasons. ---Prepare arguments for should_filter. This is done prior to should_filter for efficiency reasons.
---@param git_status table results of git.load_project_status(...) ---@param project GitProject? optional results of git.load_projects(...)
---@param unloaded_bufnr number optional bufnr recently unloaded via BufUnload event
---@return table ---@return table
--- git_status: reference --- project: reference
--- unloaded_bufnr: copy
--- bufinfo: empty unless no_buffer set: vim.fn.getbufinfo { buflisted = 1 } --- bufinfo: empty unless no_buffer set: vim.fn.getbufinfo { buflisted = 1 }
function M.prepare(git_status, unloaded_bufnr) --- bookmarks: absolute paths to boolean
function Filters:prepare(project)
local status = { local status = {
git_status = git_status or {}, project = project or {},
unloaded_bufnr = unloaded_bufnr,
bufinfo = {}, bufinfo = {},
bookmarks = {},
} }
if M.config.filter_no_buffer then if self.state.no_buffer then
status.bufinfo = vim.fn.getbufinfo { buflisted = 1 } status.bufinfo = vim.fn.getbufinfo({ buflisted = 1 })
end
local explorer = require("nvim-tree.core").get_explorer()
if explorer then
for _, node in pairs(explorer.marks:list()) do
status.bookmarks[node.absolute_path] = node.type
end
end end
return status return status
@@ -111,38 +218,70 @@ end
---Check if the given path should be filtered. ---Check if the given path should be filtered.
---@param path string Absolute path ---@param path string Absolute path
---@param fs_stat uv.fs_stat.result|nil fs_stat of file
---@param status table from prepare ---@param status table from prepare
---@return boolean ---@return boolean
function M.should_filter(path, status) function Filters:should_filter(path, fs_stat, status)
-- exclusions override all filters if not self.enabled then
if is_excluded(path) then
return false return false
end end
return git(path, status.git_status) -- exclusions override all filters
or buf(path, status.bufinfo, status.unloaded_bufnr) if self:is_excluded(path) then
or dotfile(path) return false
or custom(path) end
return self:git(path, status.project)
or self:buf(path, status.bufinfo)
or self:dotfile(path)
or self:custom(path)
or self:bookmark(path, fs_stat and fs_stat.type, status.bookmarks)
end end
function M.setup(opts) --- Check if the given path should be filtered, and provide the reason why it was
M.config = { ---@param path string Absolute path
filter_custom = true, ---@param fs_stat uv.fs_stat.result|nil fs_stat of file
filter_dotfiles = opts.filters.dotfiles, ---@param status table from prepare
filter_git_ignored = opts.git.ignore, ---@return FILTER_REASON
filter_git_clean = opts.filters.git_clean, function Filters:should_filter_as_reason(path, fs_stat, status)
filter_no_buffer = opts.filters.no_buffer, if not self.enabled then
} return FILTER_REASON.none
end
M.ignore_list = {} if self:is_excluded(path) then
M.exclude_list = opts.filters.exclude return FILTER_REASON.none
end
local custom_filter = opts.filters.custom if self:git(path, status.project) then
if custom_filter and #custom_filter > 0 then return FILTER_REASON.git
for _, filter_name in pairs(custom_filter) do elseif self:buf(path, status.bufinfo) then
M.ignore_list[filter_name] = true return FILTER_REASON.buf
end elseif self:dotfile(path) then
return FILTER_REASON.dotfile
elseif self:custom(path) then
return FILTER_REASON.custom
elseif self:bookmark(path, fs_stat and fs_stat.type, status.bookmarks) then
return FILTER_REASON.bookmark
else
return FILTER_REASON.none
end end
end end
return M ---Toggle a type and refresh
---@private
---@param type FilterType? nil to disable all
function Filters:toggle(type)
if not type or self.state[type] == nil then
self.enabled = not self.enabled
else
self.state[type] = not self.state[type]
end
local node = self.explorer:get_node_at_cursor()
self.explorer:reload_explorer()
if node then
utils.focus_node_or_parent(node)
end
end
return Filters

View File

@@ -1,58 +1,564 @@
local git = require "nvim-tree.git" local appearance = require("nvim-tree.appearance")
local watch = require "nvim-tree.explorer.watch" local buffers = require("nvim-tree.buffers")
local common = require "nvim-tree.explorer.common" local core = require("nvim-tree.core")
local git = require("nvim-tree.git")
local log = require("nvim-tree.log")
local utils = require("nvim-tree.utils")
local view = require("nvim-tree.view")
local node_factory = require("nvim-tree.node.factory")
local M = {} local DirectoryNode = require("nvim-tree.node.directory")
local RootNode = require("nvim-tree.node.root")
local Watcher = require("nvim-tree.watcher")
M.explore = require("nvim-tree.explorer.explore").explore local Iterator = require("nvim-tree.iterators.node-iterator")
M.reload = require("nvim-tree.explorer.reload").reload local NodeIterator = require("nvim-tree.iterators.node-iterator")
local Explorer = {} local Filters = require("nvim-tree.explorer.filters")
Explorer.__index = Explorer local Marks = require("nvim-tree.marks")
local LiveFilter = require("nvim-tree.explorer.live-filter")
local Sorter = require("nvim-tree.explorer.sorter")
local Clipboard = require("nvim-tree.actions.fs.clipboard")
local Renderer = require("nvim-tree.renderer")
function Explorer.new(cwd) local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON
cwd = vim.loop.fs_realpath(cwd or vim.loop.cwd())
local explorer = setmetatable({ local config
absolute_path = cwd,
nodes = {}, ---@class (exact) Explorer: RootNode
open = true, ---@field uid_explorer number vim.loop.hrtime() at construction time
}, Explorer) ---@field opts table user options
explorer.watcher = watch.create_watcher(explorer) ---@field augroup_id integer
explorer:_load(explorer) ---@field renderer Renderer
return explorer ---@field filters Filters
---@field live_filter LiveFilter
---@field sorters Sorter
---@field marks Marks
---@field clipboard Clipboard
local Explorer = RootNode:extend()
---@class Explorer
---@overload fun(args: ExplorerArgs): Explorer
---@class (exact) ExplorerArgs
---@field path string
---@protected
---@param args ExplorerArgs
function Explorer:new(args)
Explorer.super.new(self, {
explorer = self,
absolute_path = args.path,
name = "..",
})
self.uid_explorer = vim.loop.hrtime()
self.augroup_id = vim.api.nvim_create_augroup("NvimTree_Explorer_" .. self.uid_explorer, {})
self.open = true
self.opts = config
self.sorters = Sorter({ explorer = self })
self.renderer = Renderer({ explorer = self })
self.filters = Filters({ explorer = self })
self.live_filter = LiveFilter({ explorer = self })
self.marks = Marks({ explorer = self })
self.clipboard = Clipboard({ explorer = self })
self:create_autocmds()
self:_load(self)
end end
function Explorer:_load(node) function Explorer:destroy()
local cwd = node.link_to or node.absolute_path log.line("dev", "Explorer:destroy")
local git_statuses = git.load_project_status(cwd)
M.explore(node, git_statuses) vim.api.nvim_del_augroup_by_id(self.augroup_id)
RootNode.destroy(self)
end end
function Explorer:create_autocmds()
-- reset and draw (highlights) when colorscheme is changed
vim.api.nvim_create_autocmd("ColorScheme", {
group = self.augroup_id,
callback = function()
appearance.setup()
view.reset_winhl()
self.renderer:draw()
end,
})
vim.api.nvim_create_autocmd("BufWritePost", {
group = self.augroup_id,
callback = function()
if self.opts.auto_reload_on_write and not self.opts.filesystem_watchers.enable then
self:reload_explorer()
end
end,
})
vim.api.nvim_create_autocmd("BufReadPost", {
group = self.augroup_id,
callback = function(data)
-- 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()
self:reload_explorer()
end)
elseif self.opts.renderer.highlight_opened_files ~= "none" then
-- draw to update opened highlight
self.renderer:draw()
end
end,
})
-- update opened file buffers
vim.api.nvim_create_autocmd("BufUnload", {
group = self.augroup_id,
callback = function(data)
-- 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()
self:reload_explorer()
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,
})
vim.api.nvim_create_autocmd("BufEnter", {
group = self.augroup_id,
pattern = "NvimTree_*",
callback = function()
if utils.is_nvim_tree_buf(0) then
if vim.fn.getcwd() ~= core.get_cwd() or (self.opts.reload_on_bufenter and not self.opts.filesystem_watchers.enable) then
self:reload_explorer()
end
end
end,
})
vim.api.nvim_create_autocmd("User", {
group = self.augroup_id,
pattern = { "FugitiveChanged", "NeogitStatusRefreshed" },
callback = function()
if not self.opts.filesystem_watchers.enable and self.opts.git.enable then
self:reload_git()
end
end,
})
if self.opts.hijack_cursor then
vim.api.nvim_create_autocmd("CursorMoved", {
group = self.augroup_id,
pattern = "NvimTree_*",
callback = function()
if utils.is_nvim_tree_buf(0) then
self:place_cursor_on_node()
end
end,
})
end
if self.opts.modified.enable then
vim.api.nvim_create_autocmd({ "BufModifiedSet", "BufWritePost" }, {
group = self.augroup_id,
callback = function()
utils.debounce("Buf:modified_" .. self.uid_explorer, self.opts.view.debounce_delay, function()
buffers.reload_modified()
self:reload_explorer()
end)
end,
})
end
end
---@param node DirectoryNode
function Explorer:expand(node) function Explorer:expand(node)
self:_load(node) self:_load(node)
end end
function Explorer:destroy() ---@param node DirectoryNode
local function iterate(node) ---@param project GitProject?
common.node_destroy(node) ---@return Node[]?
if node.nodes then function Explorer:reload(node, project)
for _, child in pairs(node.nodes) do local cwd = node.link_to or node.absolute_path
iterate(child) local handle = vim.loop.fs_scandir(cwd)
if not handle then
return
end
local profile = log.profile_start("reload %s", node.absolute_path)
local filter_status = self.filters:prepare(project)
if node.group_next then
node.nodes = { node.group_next }
node.group_next = nil
end
local remain_childs = {}
local node_ignored = node:is_git_ignored()
---@type table<string, Node>
local nodes_by_path = utils.key_by(node.nodes, "absolute_path")
-- To reset we must 'zero' everything that we use
node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, {
git = 0,
buf = 0,
dotfile = 0,
custom = 0,
bookmark = 0,
})
while true do
local name, _ = vim.loop.fs_scandir_next(handle)
if not name then
break
end
local abs = utils.path_join({ cwd, name })
-- 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)
if filter_reason == FILTER_REASON.none then
remain_childs[abs] = true
-- Recreate node if type changes.
if nodes_by_path[abs] then
local n = nodes_by_path[abs]
if not stat or n.type ~= stat.type then
utils.array_remove(node.nodes, n)
n:destroy()
nodes_by_path[abs] = nil
end
end
if not nodes_by_path[abs] then
local new_child = node_factory.create({
explorer = self,
parent = node,
absolute_path = abs,
name = name,
fs_stat = stat
})
if new_child then
table.insert(node.nodes, new_child)
nodes_by_path[abs] = new_child
end
else
local n = nodes_by_path[abs]
if n then
n.executable = utils.is_executable(abs) or false
n.fs_stat = stat
end
end
else
for reason, value in pairs(FILTER_REASON) do
if filter_reason == value then
node.hidden_stats[reason] = node.hidden_stats[reason] + 1
end
end end
end end
end end
iterate(self)
node.nodes = vim.tbl_map(
self:update_git_statuses(nodes_by_path, node_ignored, project),
vim.tbl_filter(function(n)
if remain_childs[n.absolute_path] then
return remain_childs[n.absolute_path]
else
n:destroy()
return false
end
end, node.nodes)
)
local single_child = node:single_child_directory()
if config.renderer.group_empty and node.parent and single_child then
node.group_next = single_child
local ns = self:reload(single_child, project)
node.nodes = ns or {}
log.profile_end(profile)
return ns
end
self.sorters:sort(node.nodes)
self.live_filter:apply_filter(node)
log.profile_end(profile)
return node.nodes
end end
function M.setup(opts) ---Refresh contents of all nodes to a path: actual directory and links.
require("nvim-tree.explorer.common").setup(opts) ---Groups will be expanded if needed.
require("nvim-tree.explorer.explore").setup(opts) ---@param path string absolute path
require("nvim-tree.explorer.filters").setup(opts) function Explorer:refresh_parent_nodes_for_path(path)
require("nvim-tree.explorer.sorters").setup(opts) local profile = log.profile_start("refresh_parent_nodes_for_path %s", path)
require("nvim-tree.explorer.reload").setup(opts)
require("nvim-tree.explorer.watch").setup(opts) -- collect parent nodes from the top down
local parent_nodes = {}
NodeIterator.builder({ self })
:recursor(function(node)
return node.nodes
end)
:applier(function(node)
local abs_contains = node.absolute_path and path:find(node.absolute_path, 1, true) == 1
local link_contains = node.link_to and path:find(node.link_to, 1, true) == 1
if abs_contains or link_contains then
table.insert(parent_nodes, node)
end
end)
:iterate()
-- refresh in order; this will expand groups as needed
for _, node in ipairs(parent_nodes) do
local toplevel = git.get_toplevel(node.absolute_path)
local project = git.get_project(toplevel) or {}
self:reload(node, project)
git.update_parent_projects(node, project, toplevel)
end
log.profile_end(profile)
end end
M.Explorer = Explorer ---@private
---@param node DirectoryNode
function Explorer:_load(node)
local cwd = node.link_to or node.absolute_path
local project = git.load_project(cwd)
self:explore(node, project, self)
end
return M ---@private
---@param nodes_by_path Node[]
---@param node_ignored boolean
---@param project GitProject?
---@return fun(node: Node): Node
function Explorer:update_git_statuses(nodes_by_path, node_ignored, project)
return function(node)
if nodes_by_path[node.absolute_path] then
node:update_git_status(node_ignored, project)
end
return node
end
end
---@private
---@param handle uv.uv_fs_t
---@param cwd string
---@param node DirectoryNode
---@param project GitProject
---@param parent Explorer
function Explorer:populate_children(handle, cwd, node, project, parent)
local node_ignored = node:is_git_ignored()
local nodes_by_path = utils.bool_record(node.nodes, "absolute_path")
local filter_status = parent.filters:prepare(project)
node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, {
git = 0,
buf = 0,
dotfile = 0,
custom = 0,
bookmark = 0,
})
while true do
local name, _ = vim.loop.fs_scandir_next(handle)
if not name then
break
end
local abs = utils.path_join({ cwd, name })
if Watcher.is_fs_event_capable(abs) then
local profile = log.profile_start("populate_children %s", abs)
-- path incorrectly specified as an integer
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)
if filter_reason == FILTER_REASON.none and not nodes_by_path[abs] then
local child = node_factory.create({
explorer = self,
parent = node,
absolute_path = abs,
name = name,
fs_stat = stat
})
if child then
table.insert(node.nodes, child)
nodes_by_path[child.absolute_path] = true
child:update_git_status(node_ignored, project)
end
elseif node.hidden_stats then
for reason, value in pairs(FILTER_REASON) do
if filter_reason == value and type(node.hidden_stats[reason]) == "number" then
node.hidden_stats[reason] = node.hidden_stats[reason] + 1
end
end
end
log.profile_end(profile)
end
end
end
---@private
---@param node DirectoryNode
---@param project GitProject
---@param parent Explorer
---@return Node[]|nil
function Explorer:explore(node, project, parent)
local cwd = node.link_to or node.absolute_path
local handle = vim.loop.fs_scandir(cwd)
if not handle then
return
end
local profile = log.profile_start("explore %s", node.absolute_path)
self:populate_children(handle, cwd, node, project, parent)
local is_root = not node.parent
local single_child = node:single_child_directory()
if config.renderer.group_empty and not is_root and single_child then
local child_cwd = single_child.link_to or single_child.absolute_path
local child_project = git.load_project(child_cwd)
node.group_next = single_child
local ns = self:explore(single_child, child_project, parent)
node.nodes = ns or {}
log.profile_end(profile)
return ns
end
parent.sorters:sort(node.nodes)
parent.live_filter:apply_filter(node)
log.profile_end(profile)
return node.nodes
end
---@private
---@param projects GitProject[]
function Explorer:refresh_nodes(projects)
Iterator.builder({ self })
:applier(function(n)
local dir = n:as(DirectoryNode)
if dir then
local toplevel = git.get_toplevel(dir.cwd or dir.link_to or dir.absolute_path)
self:reload(dir, projects[toplevel] or {})
end
end)
:recursor(function(n)
return n.group_next and { n.group_next } or (n.open and n.nodes)
end)
:iterate()
end
local event_running = false
function Explorer:reload_explorer()
if event_running or vim.v.exiting ~= vim.NIL then
return
end
event_running = true
local projects = git.reload_all_projects()
self:refresh_nodes(projects)
if view.is_visible() then
self.renderer:draw()
end
event_running = false
end
function Explorer:reload_git()
if not git.config.git.enable or event_running then
return
end
event_running = true
local projects = git.reload_all_projects()
git.reload_node_status(self, projects)
self.renderer:draw()
event_running = false
end
---Cursor position as per vim.api.nvim_win_get_cursor
---nil on no explorer or invalid view win
---@return integer[]|nil
function Explorer:get_cursor_position()
local winnr = view.get_winnr()
if not winnr or not vim.api.nvim_win_is_valid(winnr) then
return
end
return vim.api.nvim_win_get_cursor(winnr)
end
---@return Node|nil
function Explorer:get_node_at_cursor()
local cursor = self:get_cursor_position()
if not cursor then
return
end
if cursor[1] == 1 and view.is_root_folder_visible(core.get_cwd()) then
return self
end
return utils.get_nodes_by_line(self.nodes, core.get_nodes_starting_line())[cursor[1]]
end
function Explorer:place_cursor_on_node()
local ok, search = pcall(vim.fn.searchcount)
if ok and search and search.exact_match == 1 then
return
end
local node = self:get_node_at_cursor()
if not node or node.name == ".." then
return
end
node = node:get_parent_of_group() or node
local line = vim.api.nvim_get_current_line()
local cursor = vim.api.nvim_win_get_cursor(0)
local idx = vim.fn.stridx(line, node.name)
if idx >= 0 then
vim.api.nvim_win_set_cursor(0, { cursor[1], idx })
end
end
---Api.tree.get_nodes
---@return nvim_tree.api.Node
function Explorer:get_nodes()
return self:clone()
end
function Explorer:setup(opts)
config = opts
end
return Explorer

View File

@@ -0,0 +1,229 @@
local view = require("nvim-tree.view")
local utils = require("nvim-tree.utils")
local Class = require("nvim-tree.classic")
local Iterator = require("nvim-tree.iterators.node-iterator")
local DirectoryNode = require("nvim-tree.node.directory")
---@class (exact) LiveFilter: Class
---@field explorer Explorer
---@field prefix string
---@field always_show_folders boolean
---@field filter string
local LiveFilter = Class:extend()
---@class LiveFilter
---@overload fun(args: LiveFilterArgs): LiveFilter
---@class (exact) LiveFilterArgs
---@field explorer Explorer
---@protected
---@param args LiveFilterArgs
function LiveFilter:new(args)
self.explorer = args.explorer
self.prefix = self.explorer.opts.live_filter.prefix
self.always_show_folders = self.explorer.opts.live_filter.always_show_folders
self.filter = nil
end
---@param node_ Node?
local function reset_filter(self, node_)
node_ = node_ or self.explorer
if node_ == nil then
return
end
local dir_ = node_:as(DirectoryNode)
if dir_ then
dir_.hidden_stats = vim.tbl_deep_extend("force", dir_.hidden_stats or {}, { live_filter = 0, })
end
Iterator.builder(node_.nodes)
:hidden()
:applier(function(node)
node.hidden = false
local dir = node:as(DirectoryNode)
if dir then
dir.hidden_stats = vim.tbl_deep_extend("force", dir.hidden_stats or {}, { live_filter = 0, })
end
end)
:iterate()
end
local overlay_bufnr = 0
local overlay_winnr = 0
local function remove_overlay(self)
if view.View.float.enable and view.View.float.quit_on_focus_loss then
-- return to normal nvim-tree float behaviour when filter window is closed
vim.api.nvim_create_autocmd("WinLeave", {
pattern = "NvimTree_*",
group = vim.api.nvim_create_augroup("NvimTree", { clear = false }),
callback = function()
if utils.is_nvim_tree_buf(0) then
view.close()
end
end,
})
end
vim.api.nvim_win_close(overlay_winnr, true)
vim.api.nvim_buf_delete(overlay_bufnr, { force = true })
overlay_bufnr = 0
overlay_winnr = 0
if self.filter == "" then
self:clear_filter()
end
end
---@param node Node
---@return boolean
local function matches(self, node)
if not self.explorer.filters.enabled then
return true
end
local path = node.absolute_path
local name = vim.fn.fnamemodify(path, ":t")
return vim.regex(self.filter):match_str(name) ~= nil
end
---@param node_ DirectoryNode?
function LiveFilter:apply_filter(node_)
if not self.filter or self.filter == "" then
reset_filter(self, node_)
return
end
-- this iterator cannot yet be refactored with the Iterator module
-- since the node mapper is based on its children
local function iterate(node)
local filtered_nodes = 0
local nodes = node.group_next and { node.group_next } or node.nodes
node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, {
live_filter = 0,
})
if nodes then
for _, n in pairs(nodes) do
iterate(n)
if n.hidden then
filtered_nodes = filtered_nodes + 1
end
end
end
node.hidden_stats.live_filter = filtered_nodes
local has_nodes = nodes and (self.always_show_folders or #nodes > filtered_nodes)
local ok, is_match = pcall(matches, self, node)
node.hidden = not (has_nodes or (ok and is_match))
end
iterate(node_ or self.explorer)
end
local function record_char(self)
vim.schedule(function()
self.filter = vim.api.nvim_buf_get_lines(overlay_bufnr, 0, -1, false)[1]
self:apply_filter()
self.explorer.renderer:draw()
end)
end
local function configure_buffer_overlay(self)
overlay_bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_attach(overlay_bufnr, true, {
on_lines = function()
return record_char(self)
end,
})
vim.api.nvim_create_autocmd("InsertLeave", {
callback = function()
return remove_overlay(self)
end,
once = true,
})
vim.api.nvim_buf_set_keymap(overlay_bufnr, "i", "<CR>", "<cmd>stopinsert<CR>", {})
end
---@return integer
local function calculate_overlay_win_width(self)
local wininfo = vim.fn.getwininfo(view.get_winnr())[1]
if wininfo then
return wininfo.width - wininfo.textoff - #self.prefix
end
return 20
end
local function create_overlay(self)
if view.View.float.enable then
-- don't close nvim-tree float when focus is changed to filter window
vim.api.nvim_clear_autocmds({
event = "WinLeave",
pattern = "NvimTree_*",
group = vim.api.nvim_create_augroup("NvimTree", { clear = false }),
})
end
configure_buffer_overlay(self)
overlay_winnr = vim.api.nvim_open_win(overlay_bufnr, true, {
col = 1,
row = 0,
relative = "cursor",
width = calculate_overlay_win_width(self),
height = 1,
border = "none",
style = "minimal",
})
if vim.fn.has("nvim-0.10") == 1 then
vim.api.nvim_set_option_value("modifiable", true, { buf = overlay_bufnr })
else
vim.api.nvim_buf_set_option(overlay_bufnr, "modifiable", true) ---@diagnostic disable-line: deprecated
end
vim.api.nvim_buf_set_lines(overlay_bufnr, 0, -1, false, { self.filter })
vim.cmd("startinsert")
vim.api.nvim_win_set_cursor(overlay_winnr, { 1, #self.filter + 1 })
end
function LiveFilter:start_filtering()
view.View.live_filter.prev_focused_node = self.explorer:get_node_at_cursor()
self.filter = self.filter or ""
self.explorer.renderer:draw()
local row = require("nvim-tree.core").get_nodes_starting_line() - 1
local col = #self.prefix > 0 and #self.prefix - 1 or 1
view.set_cursor({ row, col })
-- needs scheduling to let the cursor move before initializing the window
vim.schedule(function()
return create_overlay(self)
end)
end
function LiveFilter:clear_filter()
local node = self.explorer:get_node_at_cursor()
local last_node = view.View.live_filter.prev_focused_node
self.filter = nil
reset_filter(self)
self.explorer.renderer:draw()
if node then
utils.focus_file(node.absolute_path)
elseif last_node then
utils.focus_file(last_node.absolute_path)
end
end
return LiveFilter

View File

@@ -1,100 +0,0 @@
local utils = require "nvim-tree.utils"
local watch = require "nvim-tree.explorer.watch"
local M = {
is_windows = vim.fn.has "win32" == 1,
is_wsl = vim.fn.has "wsl" == 1,
}
function M.folder(parent, absolute_path, name)
local handle = vim.loop.fs_scandir(absolute_path)
local has_children = handle and vim.loop.fs_scandir_next(handle) ~= nil
local node = {
type = "directory",
absolute_path = absolute_path,
fs_stat = vim.loop.fs_stat(absolute_path),
group_next = nil, -- If node is grouped, this points to the next child dir/link node
has_children = has_children,
name = name,
nodes = {},
open = false,
parent = parent,
}
node.watcher = watch.create_watcher(node)
return node
end
function M.is_executable(parent, absolute_path, ext)
if M.is_windows then
return utils.is_windows_exe(ext)
elseif M.is_wsl then
if parent.is_wsl_windows_fs_path == nil then
-- Evaluate lazily when needed and do so only once for each parent
-- as 'wslpath' calls can get expensive in highly populated directories.
parent.is_wsl_windows_fs_path = utils.is_wsl_windows_fs_path(absolute_path)
end
if parent.is_wsl_windows_fs_path then
return utils.is_wsl_windows_fs_exe(ext)
end
end
return vim.loop.fs_access(absolute_path, "X")
end
function M.file(parent, absolute_path, name)
local ext = string.match(name, ".?[^.]+%.(.*)") or ""
return {
type = "file",
absolute_path = absolute_path,
executable = M.is_executable(parent, absolute_path, ext),
extension = ext,
fs_stat = vim.loop.fs_stat(absolute_path),
name = name,
parent = parent,
}
end
-- TODO-INFO: sometimes fs_realpath returns nil
-- I expect this be a bug in glibc, because it fails to retrieve the path for some
-- links (for instance libr2.so in /usr/lib) and thus even with a C program realpath fails
-- when it has no real reason to. Maybe there is a reason, but errno is definitely wrong.
-- So we need to check for link_to ~= nil when adding new links to the main tree
function M.link(parent, absolute_path, name)
--- I dont know if this is needed, because in my understanding, there isn't hard links in windows, but just to be sure i changed it.
local link_to = vim.loop.fs_realpath(absolute_path)
local open, nodes, has_children
local is_dir_link = (link_to ~= nil) and vim.loop.fs_stat(link_to).type == "directory"
if is_dir_link then
local handle = vim.loop.fs_scandir(link_to)
has_children = handle and vim.loop.fs_scandir_next(handle) ~= nil
open = false
nodes = {}
end
local node = {
type = "link",
absolute_path = absolute_path,
fs_stat = vim.loop.fs_stat(absolute_path),
group_next = nil, -- If node is grouped, this points to the next child dir/link node
has_children = has_children,
link_to = link_to,
name = name,
nodes = nodes,
open = open,
parent = parent,
}
if is_dir_link then
node.watcher = watch.create_watcher(node)
end
return node
end
return M

View File

@@ -1,197 +0,0 @@
local utils = require "nvim-tree.utils"
local builders = require "nvim-tree.explorer.node-builders"
local common = require "nvim-tree.explorer.common"
local filters = require "nvim-tree.explorer.filters"
local sorters = require "nvim-tree.explorer.sorters"
local live_filter = require "nvim-tree.live-filter"
local notify = require "nvim-tree.notify"
local git = require "nvim-tree.git"
local log = require "nvim-tree.log"
local NodeIterator = require "nvim-tree.iterators.node-iterator"
local M = {}
local function update_status(nodes_by_path, node_ignored, status)
return function(node)
if nodes_by_path[node.absolute_path] then
common.update_git_status(node, node_ignored, status)
end
return node
end
end
local function reload_and_get_git_project(path)
local project_root = git.get_project_root(path)
git.reload_project(project_root, path)
return project_root, git.get_project(project_root) or {}
end
local function update_parent_statuses(node, project, root)
while project and node and node.absolute_path ~= root do
common.update_git_status(node, false, project)
node = node.parent
end
end
function M.reload(node, git_status, unloaded_bufnr)
local cwd = node.link_to or node.absolute_path
local handle = vim.loop.fs_scandir(cwd)
if type(handle) == "string" then
notify.error(handle)
return
end
local ps = log.profile_start("reload %s", node.absolute_path)
local filter_status = filters.prepare(git_status, unloaded_bufnr)
if node.group_next then
node.nodes = { node.group_next }
node.group_next = nil
end
local child_names = {}
local node_ignored = node.git_status == "!!"
local nodes_by_path = utils.key_by(node.nodes, "absolute_path")
while true do
local ok, name, t = pcall(vim.loop.fs_scandir_next, handle)
if not ok or not name then
break
end
local stat
local function fs_stat_cached(path)
if stat ~= nil then
return stat
end
stat = vim.loop.fs_stat(path)
return stat
end
local abs = utils.path_join { cwd, name }
t = t or (fs_stat_cached(abs) or {}).type
if not filters.should_filter(abs, filter_status) then
child_names[abs] = true
-- Recreate node if type changes.
if nodes_by_path[abs] then
local n = nodes_by_path[abs]
if n.type ~= t then
utils.array_remove(node.nodes, n)
common.node_destroy(n)
nodes_by_path[abs] = nil
end
end
if not nodes_by_path[abs] then
if t == "directory" and vim.loop.fs_access(abs, "R") then
local folder = builders.folder(node, abs, name)
nodes_by_path[abs] = folder
table.insert(node.nodes, folder)
elseif t == "file" then
local file = builders.file(node, abs, name)
nodes_by_path[abs] = file
table.insert(node.nodes, file)
elseif t == "link" then
local link = builders.link(node, abs, name)
if link.link_to ~= nil then
nodes_by_path[abs] = link
table.insert(node.nodes, link)
end
end
else
local n = nodes_by_path[abs]
if n then
n.executable = builders.is_executable(n.parent, abs, n.extension or "")
n.fs_stat = fs_stat_cached(abs)
end
end
end
end
node.nodes = vim.tbl_map(
update_status(nodes_by_path, node_ignored, git_status),
vim.tbl_filter(function(n)
if child_names[n.absolute_path] then
return child_names[n.absolute_path]
else
common.node_destroy(n)
return nil
end
end, node.nodes)
)
local is_root = not node.parent
local child_folder_only = common.has_one_child_folder(node) and node.nodes[1]
if M.config.group_empty and not is_root and child_folder_only then
node.group_next = child_folder_only
local ns = M.reload(child_folder_only, git_status)
node.nodes = ns or {}
log.profile_end(ps, "reload %s", node.absolute_path)
return ns
end
sorters.merge_sort(node.nodes, sorters.node_comparator)
live_filter.apply_filter(node)
log.profile_end(ps, "reload %s", node.absolute_path)
return node.nodes
end
---Refresh contents and git status for a single node
---@param node table
function M.refresh_node(node)
if type(node) ~= "table" then
return
end
local parent_node = utils.get_parent_of_group(node)
local project_root, project = reload_and_get_git_project(node.absolute_path)
require("nvim-tree.explorer.reload").reload(parent_node, project)
update_parent_statuses(parent_node, project, project_root)
end
---Refresh contents and git status for all nodes to a path: actual directory and links
---@param path string absolute path
function M.refresh_nodes_for_path(path)
local explorer = require("nvim-tree.core").get_explorer()
if not explorer then
return
end
local pn = string.format("refresh_nodes_for_path %s", path)
local ps = log.profile_start(pn)
NodeIterator.builder({ explorer })
:hidden()
:recursor(function(node)
if node.group_next then
return { node.group_next }
end
if node.nodes then
return node.nodes
end
end)
:applier(function(node)
local abs_contains = node.absolute_path and path:match("^" .. node.absolute_path)
local link_contains = node.link_to and path:match("^" .. node.link_to)
if abs_contains or link_contains then
M.refresh_node(node)
end
end)
:iterate()
log.profile_end(ps, pn)
end
function M.setup(opts)
M.config = opts.renderer
end
return M

View File

@@ -0,0 +1,331 @@
local Class = require("nvim-tree.classic")
local DirectoryNode = require("nvim-tree.node.directory")
---@alias SorterType "name" | "case_sensitive" | "modification_time" | "extension" | "suffix" | "filetype"
---@alias SorterComparator fun(self: Sorter, a: Node, b: Node): boolean?
---@alias SorterUser fun(nodes: Node[]): SorterType?
---@class (exact) Sorter: Class
---@field private explorer Explorer
local Sorter = Class:extend()
---@class Sorter
---@overload fun(args: SorterArgs): Sorter
---@class (exact) SorterArgs
---@field explorer Explorer
---@protected
---@param args SorterArgs
function Sorter:new(args)
self.explorer = args.explorer
end
---Create a shallow copy of a portion of a list.
---@param t table
---@param first integer First index, inclusive
---@param last integer Last index, inclusive
---@return table
local function tbl_slice(t, first, last)
local slice = {}
for i = first, last or #t, 1 do
table.insert(slice, t[i])
end
return slice
end
---Evaluate folders_first and sort.files_first returning nil when no order is necessary
---@private
---@type SorterComparator
function Sorter:folders_or_files_first(a, b)
if not (self.explorer.opts.sort.folders_first or self.explorer.opts.sort.files_first) then
return nil
end
if not a:is(DirectoryNode) and b:is(DirectoryNode) then
-- file <> folder
return self.explorer.opts.sort.files_first
elseif a:is(DirectoryNode) and not b:is(DirectoryNode) then
-- folder <> file
return not self.explorer.opts.sort.files_first
end
return nil
end
---@private
---@param t Node[]
---@param first number
---@param mid number
---@param last number
---@param comparator SorterComparator
function Sorter:merge(t, first, mid, last, comparator)
local n1 = mid - first + 1
local n2 = last - mid
local ls = tbl_slice(t, first, mid)
local rs = tbl_slice(t, mid + 1, last)
local i = 1
local j = 1
local k = first
while i <= n1 and j <= n2 do
if comparator(self, ls[i], rs[j]) then
t[k] = ls[i]
i = i + 1
else
t[k] = rs[j]
j = j + 1
end
k = k + 1
end
while i <= n1 do
t[k] = ls[i]
i = i + 1
k = k + 1
end
while j <= n2 do
t[k] = rs[j]
j = j + 1
k = k + 1
end
end
---@private
---@param t Node[]
---@param first number
---@param last number
---@param comparator SorterComparator
function Sorter:split_merge(t, first, last, comparator)
if (last - first) < 1 then
return
end
local mid = math.floor((first + last) / 2)
self:split_merge(t, first, mid, comparator)
self:split_merge(t, mid + 1, last, comparator)
self:merge(t, first, mid, last, comparator)
end
---Perform a merge sort using sorter option.
---@param t Node[]
function Sorter:sort(t)
if self[self.explorer.opts.sort.sorter] then
self:split_merge(t, 1, #t, self[self.explorer.opts.sort.sorter])
elseif type(self.explorer.opts.sort.sorter) == "function" then
local t_user = {}
local origin_index = {}
for _, n in ipairs(t) do
table.insert(t_user, {
absolute_path = n.absolute_path,
executable = n.executable,
extension = n.extension,
filetype = vim.filetype.match({ filename = n.name }),
link_to = n.link_to,
name = n.name,
type = n.type,
})
table.insert(origin_index, n)
end
-- user may return a SorterType
local ret = self.explorer.opts.sort.sorter(t_user)
if self[ret] then
self:split_merge(t, 1, #t, self[ret])
return
end
-- do merge sort for prevent memory exceed
local user_index = {}
for i, v in ipairs(t_user) do
if type(v.absolute_path) == "string" and user_index[v.absolute_path] == nil then
user_index[v.absolute_path] = i
end
end
-- if missing value found, then using origin_index
local mini_comparator = function(_, a, b)
local a_index = user_index[a.absolute_path] or origin_index[a.absolute_path]
local b_index = user_index[b.absolute_path] or origin_index[b.absolute_path]
if type(a_index) == "number" and type(b_index) == "number" then
return a_index <= b_index
end
return (a_index or 0) <= (b_index or 0)
end
self:split_merge(t, 1, #t, mini_comparator) -- sort by user order
end
end
---@private
---@param a Node
---@param b Node
---@param ignore_case boolean
---@return boolean
function Sorter:name_case(a, b, ignore_case)
if not (a and b) then
return true
end
local early_return = self:folders_or_files_first(a, b)
if early_return ~= nil then
return early_return
end
if ignore_case then
return a.name:lower() <= b.name:lower()
else
return a.name <= b.name
end
end
---@private
---@type SorterComparator
function Sorter:case_sensitive(a, b)
return self:name_case(a, b, false)
end
---@private
---@type SorterComparator
function Sorter:name(a, b)
return self:name_case(a, b, true)
end
---@private
---@type SorterComparator
function Sorter:modification_time(a, b)
if not (a and b) then
return true
end
local early_return = self:folders_or_files_first(a, b)
if early_return ~= nil then
return early_return
end
local last_modified_a = 0
local last_modified_b = 0
if a.fs_stat ~= nil then
last_modified_a = a.fs_stat.mtime.sec
end
if b.fs_stat ~= nil then
last_modified_b = b.fs_stat.mtime.sec
end
return last_modified_b <= last_modified_a
end
---@private
---@type SorterComparator
function Sorter:suffix(a, b)
if not (a and b) then
return true
end
-- directories go first
local early_return = self:folders_or_files_first(a, b)
if early_return ~= nil then
return early_return
elseif a.nodes and b.nodes then
return self:name(a, b)
end
-- dotfiles go second
if a.name:sub(1, 1) == "." and b.name:sub(1, 1) ~= "." then
return true
elseif a.name:sub(1, 1) ~= "." and b.name:sub(1, 1) == "." then
return false
elseif a.name:sub(1, 1) == "." and b.name:sub(1, 1) == "." then
return self:name(a, b)
end
-- unsuffixed go third
local a_suffix_ndx = a.name:find("%.%w+$")
local b_suffix_ndx = b.name:find("%.%w+$")
if not a_suffix_ndx and b_suffix_ndx then
return true
elseif a_suffix_ndx and not b_suffix_ndx then
return false
elseif not (a_suffix_ndx and b_suffix_ndx) then
return self:name(a, b)
end
-- finally, compare by suffixes
local a_suffix = a.name:sub(a_suffix_ndx)
local b_suffix = b.name:sub(b_suffix_ndx)
if a_suffix and not b_suffix then
return true
elseif not a_suffix and b_suffix then
return false
elseif a_suffix:lower() == b_suffix:lower() then
return self:name(a, b)
end
return a_suffix:lower() < b_suffix:lower()
end
---@private
---@type SorterComparator
function Sorter:extension(a, b)
if not (a and b) then
return true
end
local early_return = self:folders_or_files_first(a, b)
if early_return ~= nil then
return early_return
end
if a.extension and not b.extension then
return true
elseif not a.extension and b.extension then
return false
end
local a_ext = (a.extension or ""):lower()
local b_ext = (b.extension or ""):lower()
if a_ext == b_ext then
return self:name(a, b)
end
return a_ext < b_ext
end
---@private
---@type SorterComparator
function Sorter:filetype(a, b)
local a_ft = vim.filetype.match({ filename = a.name })
local b_ft = vim.filetype.match({ filename = b.name })
-- directories first
local early_return = self:folders_or_files_first(a, b)
if early_return ~= nil then
return early_return
end
-- one is nil, the other wins
if a_ft and not b_ft then
return true
elseif not a_ft and b_ft then
return false
end
-- same filetype or both nil, sort by name
if a_ft == b_ft then
return self:name(a, b)
end
return a_ft < b_ft
end
return Sorter

View File

@@ -1,205 +0,0 @@
local M = {
sort_by = nil,
node_comparator = nil,
}
---Create a shallow copy of a portion of a list.
---@param t table
---@param first integer First index, inclusive
---@param last integer Last index, inclusive
---@return table
local function tbl_slice(t, first, last)
local slice = {}
for i = first, last or #t, 1 do
table.insert(slice, t[i])
end
return slice
end
local function merge(t, first, mid, last, comparator)
local n1 = mid - first + 1
local n2 = last - mid
local ls = tbl_slice(t, first, mid)
local rs = tbl_slice(t, mid + 1, last)
local i = 1
local j = 1
local k = first
while i <= n1 and j <= n2 do
if comparator(ls[i], rs[j]) then
t[k] = ls[i]
i = i + 1
else
t[k] = rs[j]
j = j + 1
end
k = k + 1
end
while i <= n1 do
t[k] = ls[i]
i = i + 1
k = k + 1
end
while j <= n2 do
t[k] = rs[j]
j = j + 1
k = k + 1
end
end
local function split_merge(t, first, last, comparator)
if (last - first) < 1 then
return
end
local mid = math.floor((first + last) / 2)
split_merge(t, first, mid, comparator)
split_merge(t, mid + 1, last, comparator)
merge(t, first, mid, last, comparator)
end
---Perform a merge sort on a given list.
---@param t any[]
---@param comparator function|nil
function M.merge_sort(t, comparator)
if type(M.sort_by) == "function" then
local t_user = {}
local origin_index = {}
for _, n in ipairs(t) do
table.insert(t_user, {
absolute_path = n.absolute_path,
executable = n.executable,
extension = n.extension,
link_to = n.link_to,
name = n.name,
type = n.type,
})
table.insert(origin_index, n)
end
M.sort_by(t_user)
-- do merge sort for prevent memory exceed
local user_index = {}
for i, v in ipairs(t_user) do
if type(v.absolute_path) == "string" and user_index[v.absolute_path] == nil then
user_index[v.absolute_path] = i
end
end
-- if missing value found, then using origin_index
local mini_comparator = function(a, b)
local a_index = user_index[a.absolute_path] or origin_index[a.absolute_path]
local b_index = user_index[b.absolute_path] or origin_index[b.absolute_path]
if type(a_index) == "number" and type(b_index) == "number" then
return a_index <= b_index
end
return (a_index or 0) <= (b_index or 0)
end
split_merge(t, 1, #t, mini_comparator) -- sort by user order
else
if not comparator then
comparator = function(left, right)
return left < right
end
end
split_merge(t, 1, #t, comparator)
end
end
local function node_comparator_name_ignorecase_or_not(a, b, ignorecase)
if not (a and b) then
return true
end
if a.nodes and not b.nodes then
return true
elseif not a.nodes and b.nodes then
return false
end
if ignorecase then
return a.name:lower() <= b.name:lower()
else
return a.name <= b.name
end
end
function M.node_comparator_name_case_sensisive(a, b)
return node_comparator_name_ignorecase_or_not(a, b, false)
end
function M.node_comparator_name_ignorecase(a, b)
return node_comparator_name_ignorecase_or_not(a, b, true)
end
function M.node_comparator_modification_time(a, b)
if not (a and b) then
return true
end
if a.nodes and not b.nodes then
return true
elseif not a.nodes and b.nodes then
return false
end
local last_modified_a = 0
local last_modified_b = 0
if a.fs_stat ~= nil then
last_modified_a = a.fs_stat.mtime.sec
end
if b.fs_stat ~= nil then
last_modified_b = b.fs_stat.mtime.sec
end
return last_modified_b <= last_modified_a
end
function M.node_comparator_extension(a, b)
if not (a and b) then
return true
end
if a.nodes and not b.nodes then
return true
elseif not a.nodes and b.nodes then
return false
end
if not (a.extension and b.extension) then
return true
end
if a.extension and not b.extension then
return true
elseif not a.extension and b.extension then
return false
end
return a.extension:lower() <= b.extension:lower()
end
function M.setup(opts)
M.sort_by = opts.sort_by
if M.sort_by and type(M.sort_by) == "function" then
M.node_comparator = M.sort_by
elseif M.sort_by == "modification_time" then
M.node_comparator = M.node_comparator_modification_time
elseif M.sort_by == "case_sensitive" then
M.node_comparator = M.node_comparator_name_case_sensisive
elseif M.sort_by == "extension" then
M.node_comparator = M.node_comparator_extension
else
M.node_comparator = M.node_comparator_name_ignorecase
end
end
return M

View File

@@ -1,11 +1,27 @@
local log = require "nvim-tree.log" local log = require("nvim-tree.log")
local utils = require "nvim-tree.utils" local git = require("nvim-tree.git")
local utils = require("nvim-tree.utils")
local Watcher = require("nvim-tree.watcher").Watcher local Watcher = require("nvim-tree.watcher").Watcher
local M = {} local M = {
config = {},
uid = 0,
}
---@param path string
---@return boolean
local function is_git(path) local function is_git(path)
return vim.fn.fnamemodify(path, ":t") == ".git" -- If $GIT_DIR is set, consider its value to be equivalent to '.git'.
-- Expand $GIT_DIR (and `path`) to a full path (see :help filename-modifiers), since
-- it's possible to set it to a relative path. We want to make our best
-- effort to expand that to a valid absolute path.
if vim.fn.fnamemodify(path, ":p") == vim.fn.fnamemodify(vim.env.GIT_DIR, ":p") then
return true
elseif vim.fn.fnamemodify(path, ":t") == ".git" then
return true
else
return false
end
end end
local IGNORED_PATHS = { local IGNORED_PATHS = {
@@ -16,6 +32,8 @@ local IGNORED_PATHS = {
"/dev", "/dev",
} }
---@param path string
---@return boolean
local function is_folder_ignored(path) local function is_folder_ignored(path)
for _, folder in ipairs(IGNORED_PATHS) do for _, folder in ipairs(IGNORED_PATHS) do
if vim.startswith(path, folder) then if vim.startswith(path, folder) then
@@ -23,54 +41,59 @@ local function is_folder_ignored(path)
end end
end end
for _, ignore_dir in ipairs(M.ignore_dirs) do if type(M.config.filesystem_watchers.ignore_dirs) == "table" then
if vim.fn.match(path, ignore_dir) ~= -1 then for _, ignore_dir in ipairs(M.config.filesystem_watchers.ignore_dirs) do
return true if vim.fn.match(path, ignore_dir) ~= -1 then
return true
end
end end
elseif type(M.config.filesystem_watchers.ignore_dirs) == "function" then
return M.config.filesystem_watchers.ignore_dirs(path)
end end
return false return false
end end
---@param node DirectoryNode
---@return Watcher|nil
function M.create_watcher(node) function M.create_watcher(node)
if not M.enabled or type(node) ~= "table" then if not M.config.filesystem_watchers.enable or type(node) ~= "table" then
return nil return nil
end end
local path local path = node.link_to or node.absolute_path
if node.type == "link" then
path = node.link_to
else
path = node.absolute_path
end
if is_git(path) or is_folder_ignored(path) then if is_git(path) or is_folder_ignored(path) then
return nil return nil
end end
---@param watcher Watcher
local function callback(watcher) local function callback(watcher)
log.line("watcher", "node event scheduled refresh %s", watcher.context) log.line("watcher", "node event scheduled refresh %s", watcher.data.context)
utils.debounce(watcher.context, M.debounce_delay, function() utils.debounce(watcher.data.context, M.config.filesystem_watchers.debounce_delay, function()
if watcher.destroyed then
return
end
if node.link_to then if node.link_to then
log.line("watcher", "node event executing refresh '%s' -> '%s'", node.link_to, node.absolute_path) log.line("watcher", "node event executing refresh '%s' -> '%s'", node.link_to, node.absolute_path)
else else
log.line("watcher", "node event executing refresh '%s'", node.absolute_path) log.line("watcher", "node event executing refresh '%s'", node.absolute_path)
end end
require("nvim-tree.explorer.reload").refresh_node(node) git.refresh_dir(node)
require("nvim-tree.renderer").draw()
end) end)
end end
M.uid = M.uid + 1 M.uid = M.uid + 1
return Watcher:new(path, nil, callback, { return Watcher:create({
context = "explorer:watch:" .. path .. ":" .. M.uid, path = path,
callback = callback,
data = {
context = "explorer:watch:" .. path .. ":" .. M.uid
}
}) })
end end
function M.setup(opts) function M.setup(opts)
M.enabled = opts.filesystem_watchers.enable M.config.filesystem_watchers = opts.filesystem_watchers
M.debounce_delay = opts.filesystem_watchers.debounce_delay
M.ignore_dirs = opts.filesystem_watchers.ignore_dirs
M.uid = 0 M.uid = 0
end end

View File

@@ -1,181 +1,411 @@
local log = require "nvim-tree.log" local log = require("nvim-tree.log")
local utils = require "nvim-tree.utils" local utils = require("nvim-tree.utils")
local git_utils = require "nvim-tree.git.utils" local git_utils = require("nvim-tree.git.utils")
local Runner = require "nvim-tree.git.runner"
local GitRunner = require("nvim-tree.git.runner")
local Watcher = require("nvim-tree.watcher").Watcher local Watcher = require("nvim-tree.watcher").Watcher
local Iterator = require "nvim-tree.iterators.node-iterator" local Iterator = require("nvim-tree.iterators.node-iterator")
local DirectoryNode = require("nvim-tree.node.directory")
---Git short format status xy
---@alias GitXY string
-- Git short-format status
---@alias GitPathXY table<string, GitXY>
-- Git short-format statuses
---@alias GitPathXYs table<string, GitXY[]>
---Git short-format statuses for a single node
---@class (exact) GitNodeStatus
---@field file GitXY?
---@field dir table<"direct" | "indirect", GitXY[]>?
---Git state for an entire repo
---@class (exact) GitProject
---@field files GitProjectFiles?
---@field dirs GitProjectDirs?
---@field watcher Watcher?
---@alias GitProjectFiles GitPathXY
---@alias GitProjectDirs table<"direct" | "indirect", GitPathXYs>
local M = { local M = {
config = {}, config = {},
projects = {},
cwd_to_project_root = {}, ---all projects keyed by toplevel
---@type table<string, GitProject>
_projects_by_toplevel = {},
---index of paths inside toplevels, false when not inside a project
---@type table<string, string|false>
_toplevels_by_path = {},
-- git dirs by toplevel
---@type table<string, string>
_git_dirs_by_toplevel = {},
} }
-- Files under .git that should result in a reload when changed. -- Files under .git that should result in a reload when changed.
-- Utilities (like watchman) can also write to this directory (often) and aren't useful for us. -- Utilities (like watchman) can also write to this directory (often) and aren't useful for us.
local WATCHED_FILES = { local WATCHED_FILES = {
"FETCH_HEAD", -- remote ref "FETCH_HEAD", -- remote ref
"HEAD", -- local ref "HEAD", -- local ref
"HEAD.lock", -- HEAD will not always be updated e.g. revert "HEAD.lock", -- HEAD will not always be updated e.g. revert
"config", -- user config "config", -- user config
"index", -- staging area "index", -- staging area
} }
function M.reload() ---@param toplevel string|nil
if not M.config.git.enable then ---@param path string|nil
return {} ---@param project GitProject
end ---@param project_files GitProjectFiles?
local function reload_git_project(toplevel, path, project, project_files)
for project_root in pairs(M.projects) do
M.reload_project(project_root)
end
return M.projects
end
function M.reload_project(project_root, path)
local project = M.projects[project_root]
if not project or not M.config.git.enable then
return
end
if path and path:find(project_root, 1, true) ~= 1 then
return
end
local git_status = Runner.run {
project_root = project_root,
path = path,
list_untracked = git_utils.should_show_untracked(project_root),
list_ignored = true,
timeout = M.config.git.timeout,
}
if path then if path then
for p in pairs(project.files) do for p in pairs(project.files) do
if p:find(path, 1, true) == 1 then if p:find(path, 1, true) == 1 then
project.files[p] = nil project.files[p] = nil
end end
end end
project.files = vim.tbl_deep_extend("force", project.files, git_status) project.files = vim.tbl_deep_extend("force", project.files, project_files)
else else
project.files = git_status project.files = project_files or {}
end end
project.dirs = git_utils.file_status_to_dir_status(project.files, project_root) project.dirs = git_utils.project_files_to_project_dirs(project.files, toplevel)
end end
function M.get_project(project_root) --- Is this path in a known ignored directory?
return M.projects[project_root] ---@param path string
---@param project GitProject
---@return boolean
local function path_ignored_in_project(path, project)
if not path or not project then
return false
end
if project.files then
for p, xy in pairs(project.files) do
if xy == "!!" and vim.startswith(path, p) then
return true
end
end
end
return false
end end
function M.get_project_root(cwd) ---@return GitProject[] maybe empty
function M.reload_all_projects()
if not M.config.git.enable then
return {}
end
for toplevel in pairs(M._projects_by_toplevel) do
M.reload_project(toplevel)
end
return M._projects_by_toplevel
end
--- Reload one project. Does nothing when no project or path is ignored
---@param toplevel string?
---@param path string? optional path to update only
---@param callback function?
function M.reload_project(toplevel, path, callback)
local project = M._projects_by_toplevel[toplevel] --[[@as GitProject]]
if not toplevel or not project or not M.config.git.enable then
if callback then
callback()
end
return
end
if path and (path:find(toplevel, 1, true) ~= 1 or path_ignored_in_project(path, project)) then
if callback then
callback()
end
return
end
---@type GitRunnerArgs
local args = {
toplevel = toplevel,
path = path,
list_untracked = git_utils.should_show_untracked(toplevel),
list_ignored = true,
timeout = M.config.git.timeout,
}
if callback then
---@param path_xy GitPathXY
args.callback = function(path_xy)
reload_git_project(toplevel, path, project, path_xy)
callback()
end
GitRunner:run(args)
else
-- TODO #1974 use callback once async/await is available
reload_git_project(toplevel, path, project, GitRunner:run(args))
end
end
--- Retrieve a known project
---@param toplevel string?
---@return GitProject? project
function M.get_project(toplevel)
return M._projects_by_toplevel[toplevel]
end
--- Retrieve the toplevel for a path. nil on:
--- git disabled
--- not part of a project
--- not a directory
--- path in git.disable_for_dirs
---@param path string absolute
---@return string|nil
function M.get_toplevel(path)
if not path then
return nil
end
if not M.config.git.enable then if not M.config.git.enable then
return nil return nil
end end
if M.cwd_to_project_root[cwd] then local tl = M._toplevels_by_path[path]
return M.cwd_to_project_root[cwd] if tl then
end return tl
elseif tl == false then
if M.cwd_to_project_root[cwd] == false then
return nil return nil
end end
local stat, _ = vim.loop.fs_stat(cwd) local stat, _ = vim.loop.fs_stat(path)
if not stat or stat.type ~= "directory" then if not stat or stat.type ~= "directory" then
return nil return nil
end end
M.cwd_to_project_root[cwd] = git_utils.get_toplevel(cwd) -- short-circuit any known ignored paths
return M.cwd_to_project_root[cwd] for root, project in pairs(M._projects_by_toplevel) do
if project and path_ignored_in_project(path, project) then
M._toplevels_by_path[path] = root
return root
end
end
-- attempt to fetch toplevel, cache if untracked
local toplevel, git_dir = git_utils.get_toplevel(path)
if not toplevel or not git_dir then
M._toplevels_by_path[path] = false
return nil
end
local toplevel_norm = vim.fn.fnamemodify(toplevel, ":p")
-- ignore disabled paths
if type(M.config.git.disable_for_dirs) == "table" then
for _, disabled_for_dir in ipairs(M.config.git.disable_for_dirs) do
local disabled_norm = vim.fn.fnamemodify(disabled_for_dir, ":p")
if toplevel_norm == disabled_norm then
return nil
end
end
elseif type(M.config.git.disable_for_dirs) == "function" then
if M.config.git.disable_for_dirs(toplevel_norm) then
return nil
end
end
M._toplevels_by_path[path] = toplevel
M._git_dirs_by_toplevel[toplevel] = git_dir
toplevel = M._toplevels_by_path[path]
if toplevel == false then
return nil
else
return toplevel
end
end end
local function reload_tree_at(project_root) local function reload_tree_at(toplevel)
if not M.config.git.enable then if not M.config.git.enable or not toplevel then
return nil return nil
end end
log.line("watcher", "git event executing '%s'", project_root) log.line("watcher", "git event executing '%s'", toplevel)
local root_node = utils.get_node_from_path(project_root) local root_node = utils.get_node_from_path(toplevel)
if not root_node then if not root_node then
return return
end end
M.reload_project(project_root) M.reload_project(toplevel, nil, function()
local project = M.get_project(project_root) local project = M.get_project(toplevel)
local project_files = project.files and project.files or {} Iterator.builder(root_node.nodes)
local project_dirs = project.dirs and project.dirs or {} :hidden()
:applier(function(node)
local parent_ignored = node.parent and node.parent:is_git_ignored() or false
node:update_git_status(parent_ignored, project)
end)
:recursor(function(node)
return node.nodes and #node.nodes > 0 and node.nodes
end)
:iterate()
Iterator.builder(root_node.nodes) root_node.explorer.renderer:draw()
:hidden() end)
:applier(function(node)
local parent_ignored = node.parent.git_status == "!!"
node.git_status = project_dirs[node.absolute_path] or project_files[node.absolute_path]
if not node.git_status and parent_ignored then
node.git_status = "!!"
end
end)
:recursor(function(node)
return node.nodes and #node.nodes > 0 and node.nodes
end)
:iterate()
require("nvim-tree.renderer").draw()
end end
function M.load_project_status(cwd) --- Load the project status for a path. Does nothing when no toplevel for path.
--- Only fetches project status when unknown, otherwise returns existing.
---@param path string absolute
---@return GitProject maybe empty
function M.load_project(path)
if not M.config.git.enable then if not M.config.git.enable then
return {} return {}
end end
local project_root = M.get_project_root(cwd) local toplevel = M.get_toplevel(path)
if not project_root then if not toplevel then
M.cwd_to_project_root[cwd] = false M._toplevels_by_path[path] = false
return {} return {}
end end
local status = M.projects[project_root] local project = M._projects_by_toplevel[toplevel]
if status then if project then
return status return project
end end
local git_status = Runner.run { local path_xys = GitRunner:run({
project_root = project_root, toplevel = toplevel,
list_untracked = git_utils.should_show_untracked(project_root), list_untracked = git_utils.should_show_untracked(toplevel),
list_ignored = true, list_ignored = true,
timeout = M.config.git.timeout, timeout = M.config.git.timeout,
} })
local watcher = nil local watcher = nil
if M.config.filesystem_watchers.enable then if M.config.filesystem_watchers.enable then
log.line("watcher", "git start") log.line("watcher", "git start")
---@param w Watcher
local callback = function(w) local callback = function(w)
log.line("watcher", "git event scheduled '%s'", w.project_root) log.line("watcher", "git event scheduled '%s'", w.data.toplevel)
utils.debounce("git:watcher:" .. w.project_root, M.config.filesystem_watchers.debounce_delay, function() utils.debounce("git:watcher:" .. w.data.toplevel, M.config.filesystem_watchers.debounce_delay, function()
reload_tree_at(w.project_root) if w.destroyed then
return
end
reload_tree_at(w.data.toplevel)
end) end)
end end
watcher = Watcher:new(utils.path_join { project_root, ".git" }, WATCHED_FILES, callback, { local git_dir = vim.env.GIT_DIR or M._git_dirs_by_toplevel[toplevel] or utils.path_join({ toplevel, ".git" })
project_root = project_root, watcher = Watcher:create({
path = git_dir,
files = WATCHED_FILES,
callback = callback,
data = {
toplevel = toplevel,
}
}) })
end end
M.projects[project_root] = { if path_xys then
files = git_status, M._projects_by_toplevel[toplevel] = {
dirs = git_utils.file_status_to_dir_status(git_status, project_root), files = path_xys,
watcher = watcher, dirs = git_utils.project_files_to_project_dirs(path_xys, toplevel),
} watcher = watcher,
return M.projects[project_root] }
return M._projects_by_toplevel[toplevel]
else
M._toplevels_by_path[path] = false
return {}
end
end
---@param dir DirectoryNode
---@param project GitProject?
---@param root string?
function M.update_parent_projects(dir, project, root)
while project and dir do
-- step up to the containing project
if dir.absolute_path == root then
-- stop at the top of the tree
if not dir.parent then
break
end
root = M.get_toplevel(dir.parent.absolute_path)
-- stop when no more projects
if not root then
break
end
-- update the containing project
project = M.get_project(root)
M.reload_project(root, dir.absolute_path, nil)
end
-- update status
dir:update_git_status(dir.parent and dir.parent:is_git_ignored() or false, project)
-- maybe parent
dir = dir.parent
end
end
---Refresh contents and git status for a single directory
---@param dir DirectoryNode
function M.refresh_dir(dir)
local node = dir:get_parent_of_group() or dir
local toplevel = M.get_toplevel(dir.absolute_path)
M.reload_project(toplevel, dir.absolute_path, function()
local project = M.get_project(toplevel) or {}
dir.explorer:reload(node, project)
M.update_parent_projects(dir, project, toplevel)
dir.explorer.renderer:draw()
end)
end
---@param dir DirectoryNode?
---@param projects GitProject[]
function M.reload_node_status(dir, projects)
dir = dir and dir:as(DirectoryNode)
if not dir or #dir.nodes == 0 then
return
end
local toplevel = M.get_toplevel(dir.absolute_path)
local project = projects[toplevel] or {}
for _, node in ipairs(dir.nodes) do
node:update_git_status(dir:is_git_ignored(), project)
M.reload_node_status(node:as(DirectoryNode), projects)
end
end end
function M.purge_state() function M.purge_state()
log.line("git", "purge_state") log.line("git", "purge_state")
M.projects = {}
M.cwd_to_project_root = {} for _, project in pairs(M._projects_by_toplevel) do
if project.watcher then
project.watcher:destroy()
end
end
M._projects_by_toplevel = {}
M._toplevels_by_path = {}
M._git_dirs_by_toplevel = {}
end
--- Disable git integration permanently
function M.disable_git_integration()
log.line("git", "disabling git integration")
M.purge_state()
M.config.git.enable = false
end end
function M.setup(opts) function M.setup(opts)

View File

@@ -1,25 +1,75 @@
local log = require "nvim-tree.log" local log = require("nvim-tree.log")
local utils = require "nvim-tree.utils" local utils = require("nvim-tree.utils")
local notify = require("nvim-tree.notify")
local Runner = {} local Class = require("nvim-tree.classic")
Runner.__index = Runner
---@class (exact) GitRunner: Class
---@field private toplevel string absolute path
---@field private path string? absolute path
---@field private list_untracked boolean
---@field private list_ignored boolean
---@field private timeout integer
---@field private callback fun(path_xy: GitPathXY)?
---@field private path_xy GitPathXY
---@field private rc integer? -- -1 indicates timeout
local GitRunner = Class:extend()
---@class GitRunner
---@overload fun(args: GitRunnerArgs): GitRunner
---@class (exact) GitRunnerArgs
---@field toplevel string absolute path
---@field path string? absolute path
---@field list_untracked boolean
---@field list_ignored boolean
---@field timeout integer
---@field callback fun(path_xy: GitPathXY)?
local timeouts = 0
local MAX_TIMEOUTS = 5
---@protected
---@param args GitRunnerArgs
function GitRunner:new(args)
self.toplevel = args.toplevel
self.path = args.path
self.list_untracked = args.list_untracked
self.list_ignored = args.list_ignored
self.timeout = args.timeout
self.callback = args.callback
self.path_xy = {}
self.rc = nil
end
---@private
---@param status string
---@param path string|nil
function GitRunner:parse_status_output(status, path)
if not path then
return
end
function Runner:_parse_status_output(status, path)
-- replacing slashes if on windows -- replacing slashes if on windows
if vim.fn.has "win32" == 1 then if vim.fn.has("win32") == 1 then
path = path:gsub("/", "\\") path = path:gsub("/", "\\")
end end
if #status > 0 and #path > 0 then if #status > 0 and #path > 0 then
self.output[utils.path_remove_trailing(utils.path_join { self.project_root, path })] = status self.path_xy[utils.path_remove_trailing(utils.path_join({ self.toplevel, path }))] = status
end end
end end
function Runner:_handle_incoming_data(prev_output, incoming) ---@private
---@param prev_output string
---@param incoming string
---@return string
function GitRunner:handle_incoming_data(prev_output, incoming)
if incoming and utils.str_find(incoming, "\n") then if incoming and utils.str_find(incoming, "\n") then
local prev = prev_output .. incoming local prev = prev_output .. incoming
local i = 1 local i = 1
local skip_next_line = false local skip_next_line = false
for line in prev:gmatch "[^\n]*\n" do for line in prev:gmatch("[^\n]*\n") do
if skip_next_line then if skip_next_line then
skip_next_line = false skip_next_line = false
else else
@@ -29,7 +79,7 @@ function Runner:_handle_incoming_data(prev_output, incoming)
-- skip next line if it is a rename entry -- skip next line if it is a rename entry
skip_next_line = true skip_next_line = true
end end
self:_parse_status_output(status, path) self:parse_status_output(status, path)
end end
i = i + #line i = i + #line
end end
@@ -41,39 +91,54 @@ function Runner:_handle_incoming_data(prev_output, incoming)
return prev_output .. incoming return prev_output .. incoming
end end
for line in prev_output:gmatch "[^\n]*\n" do for line in prev_output:gmatch("[^\n]*\n") do
self._parse_status_output(line) self:parse_status_output(line)
end end
return nil return ""
end end
function Runner:_getopts(stdout_handle, stderr_handle) ---@private
---@param stdout_handle uv.uv_pipe_t
---@param stderr_handle uv.uv_pipe_t
---@return uv.spawn.options
function GitRunner:get_spawn_options(stdout_handle, stderr_handle)
local untracked = self.list_untracked and "-u" or nil local untracked = self.list_untracked and "-u" or nil
local ignored = (self.list_untracked and self.list_ignored) and "--ignored=matching" or "--ignored=no" local ignored = (self.list_untracked and self.list_ignored) and "--ignored=matching" or "--ignored=no"
return { return {
args = { "--no-optional-locks", "status", "--porcelain=v1", "-z", ignored, untracked, self.path }, args = { "--no-optional-locks", "status", "--porcelain=v1", "-z", ignored, untracked, self.path },
cwd = self.project_root, cwd = self.toplevel,
stdio = { nil, stdout_handle, stderr_handle }, stdio = { nil, stdout_handle, stderr_handle },
} }
end end
function Runner:_log_raw_output(output) ---@private
if output and type(output) == "string" then ---@param output string
function GitRunner:log_raw_output(output)
if log.enabled("git") and output and type(output) == "string" then
log.raw("git", "%s", output) log.raw("git", "%s", output)
log.line("git", "done") log.line("git", "done")
end end
end end
function Runner:_run_git_job() ---@private
---@param callback function|nil
function GitRunner:run_git_job(callback)
local handle, pid local handle, pid
local stdout = vim.loop.new_pipe(false) local stdout = vim.loop.new_pipe(false)
local stderr = vim.loop.new_pipe(false) local stderr = vim.loop.new_pipe(false)
local timer = vim.loop.new_timer() local timer = vim.loop.new_timer()
if stdout == nil or stderr == nil or timer == nil then
return
end
local function on_finish(rc) local function on_finish(rc)
self.rc = rc or 0 self.rc = rc or 0
if timer:is_closing() or stdout:is_closing() or stderr:is_closing() or (handle and handle:is_closing()) then if timer:is_closing() or stdout:is_closing() or stderr:is_closing() or (handle and handle:is_closing()) then
if callback then
callback()
end
return return
end end
timer:stop() timer:stop()
@@ -82,20 +147,26 @@ function Runner:_run_git_job()
stderr:read_stop() stderr:read_stop()
stdout:close() stdout:close()
stderr:close() stderr:close()
if handle then
-- don't close the handle when killing as it will leave a zombie
if rc == -1 then
pcall(vim.loop.kill, pid, "sigkill")
elseif handle then
handle:close() handle:close()
end end
pcall(vim.loop.kill, pid) if callback then
callback()
end
end end
local opts = self:_getopts(stdout, stderr) local spawn_options = self:get_spawn_options(stdout, stderr)
log.line("git", "running job with timeout %dms", self.timeout) log.line("git", "running job with timeout %dms", self.timeout)
log.line("git", "git %s", table.concat(utils.array_remove_nils(opts.args), " ")) log.line("git", "git %s", table.concat(utils.array_remove_nils(spawn_options.args), " "))
handle, pid = vim.loop.spawn( handle, pid = vim.loop.spawn(
"git", "git",
opts, spawn_options,
vim.schedule_wrap(function(rc) vim.schedule_wrap(function(rc)
on_finish(rc) on_finish(rc)
end) end)
@@ -117,19 +188,20 @@ function Runner:_run_git_job()
if data then if data then
data = data:gsub("%z", "\n") data = data:gsub("%z", "\n")
end end
self:_log_raw_output(data) self:log_raw_output(data)
output_leftover = self:_handle_incoming_data(output_leftover, data) output_leftover = self:handle_incoming_data(output_leftover, data)
end end
local function manage_stderr(_, data) local function manage_stderr(_, data)
self:_log_raw_output(data) self:log_raw_output(data)
end end
vim.loop.read_start(stdout, vim.schedule_wrap(manage_stdout)) vim.loop.read_start(stdout, vim.schedule_wrap(manage_stdout))
vim.loop.read_start(stderr, vim.schedule_wrap(manage_stderr)) vim.loop.read_start(stderr, vim.schedule_wrap(manage_stderr))
end end
function Runner:_wait() ---@private
function GitRunner:wait()
local function is_done() local function is_done()
return self.rc ~= nil return self.rc ~= nil
end end
@@ -138,34 +210,64 @@ function Runner:_wait()
end end
end end
-- This module runs a git process, which will be killed if it takes more than timeout which defaults to 400ms ---@private
function Runner.run(opts) function GitRunner:finalise()
local ps = log.profile_start("git job %s %s", opts.project_root, opts.path)
local self = setmetatable({
project_root = opts.project_root,
path = opts.path,
list_untracked = opts.list_untracked,
list_ignored = opts.list_ignored,
timeout = opts.timeout or 400,
output = {},
rc = nil, -- -1 indicates timeout
}, Runner)
self:_run_git_job()
self:_wait()
log.profile_end(ps, "git job %s %s", opts.project_root, opts.path)
if self.rc == -1 then if self.rc == -1 then
log.line("git", "job timed out %s %s", opts.project_root, opts.path) log.line("git", "job timed out %s %s", self.toplevel, self.path)
timeouts = timeouts + 1
if timeouts == MAX_TIMEOUTS then
notify.warn(string.format("%d git jobs have timed out after git.timeout %dms, disabling git integration.", timeouts,
self.timeout))
require("nvim-tree.git").disable_git_integration()
end
elseif self.rc ~= 0 then elseif self.rc ~= 0 then
log.line("git", "job fail rc %d %s %s", self.rc, opts.project_root, opts.path) log.line("git", "job fail rc %d %s %s", self.rc, self.toplevel, self.path)
else else
log.line("git", "job success %s %s", opts.project_root, opts.path) log.line("git", "job success %s %s", self.toplevel, self.path)
end end
return self.output
end end
return Runner ---Return nil when callback present
---@private
---@return GitPathXY?
function GitRunner:execute()
local async = self.callback ~= nil
local profile = log.profile_start("git %s job %s %s", async and "async" or "sync", self.toplevel, self.path)
if async and self.callback then
-- async, always call back
self:run_git_job(function()
log.profile_end(profile)
self:finalise()
self.callback(self.path_xy)
end)
else
-- sync, maybe call back
self:run_git_job()
self:wait()
log.profile_end(profile)
self:finalise()
if self.callback then
self.callback(self.path_xy)
else
return self.path_xy
end
end
end
---Static method to run a git process, which will be killed if it takes more than timeout
---Return nil when callback present
---@param args GitRunnerArgs
---@return GitPathXY?
function GitRunner:run(args)
local runner = GitRunner(args)
return runner:execute()
end
return GitRunner

View File

@@ -1,78 +1,204 @@
local M = {} local log = require("nvim-tree.log")
local log = require "nvim-tree.log" local utils = require("nvim-tree.utils")
local has_cygpath = vim.fn.executable "cygpath" == 1 local M = {
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
---@param cwd string path
---@return string|nil toplevel absolute path
---@return string|nil git_dir absolute path
function M.get_toplevel(cwd) function M.get_toplevel(cwd)
local ps = log.profile_start("git toplevel %s", cwd) local profile = log.profile_start("git toplevel git_dir %s", cwd)
local cmd = { "git", "-C", cwd, "rev-parse", "--show-toplevel" } -- both paths are absolute
log.line("git", "%s", vim.inspect(cmd)) local cmd = { "git", "-C", cwd, "rev-parse", "--show-toplevel", "--absolute-git-dir" }
log.line("git", "%s", table.concat(cmd, " "))
local toplevel = vim.fn.system(cmd) local out, exitCode = system(cmd)
log.raw("git", toplevel) log.raw("git", out)
log.profile_end(ps, "git toplevel %s", cwd) log.profile_end(profile)
if vim.v.shell_error ~= 0 or not toplevel or #toplevel == 0 or toplevel:match "fatal" then if exitCode ~= 0 or not out or #out == 0 or out:match("fatal") then
return nil return nil, nil
end
local toplevel, git_dir = out:match("([^\n]+)\n+([^\n]+)")
if not toplevel then
return nil, nil
end
if not git_dir then
git_dir = utils.path_join({ toplevel, ".git" })
end end
-- git always returns path with forward slashes -- git always returns path with forward slashes
if vim.fn.has "win32" == 1 then if vim.fn.has("win32") == 1 then
-- msys2 git support -- msys2 git support
if has_cygpath then -- cygpath calls must in array format to avoid shell compatibility issues
toplevel = vim.fn.system("cygpath -w " .. vim.fn.shellescape(toplevel)) if M.use_cygpath then
toplevel = vim.fn.system({ "cygpath", "-w", toplevel })
if vim.v.shell_error ~= 0 then if vim.v.shell_error ~= 0 then
return nil return nil, nil
end end
-- remove trailing newline(\n) character added by vim.fn.system
toplevel = toplevel:gsub("\n", "")
git_dir = vim.fn.system({ "cygpath", "-w", git_dir })
if vim.v.shell_error ~= 0 then
return nil, nil
end
-- remove trailing newline(\n) character added by vim.fn.system
git_dir = git_dir:gsub("\n", "")
end end
toplevel = toplevel:gsub("/", "\\") toplevel = toplevel:gsub("/", "\\")
git_dir = git_dir:gsub("/", "\\")
end end
-- remove newline return toplevel, git_dir
return toplevel:sub(0, -2)
end end
---@type table<string, boolean>
local untracked = {} local untracked = {}
---@param cwd string
---@return boolean
function M.should_show_untracked(cwd) function M.should_show_untracked(cwd)
if untracked[cwd] ~= nil then if untracked[cwd] ~= nil then
return untracked[cwd] return untracked[cwd]
end end
local ps = log.profile_start("git untracked %s", cwd) local profile = log.profile_start("git untracked %s", cwd)
local cmd = { "git", "-C", cwd, "config", "status.showUntrackedFiles" } local cmd = { "git", "-C", cwd, "config", "status.showUntrackedFiles" }
log.line("git", vim.inspect(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(ps, "git untracked %s", cwd) log.profile_end(profile)
untracked[cwd] = vim.trim(has_untracked) ~= "no" untracked[cwd] = vim.trim(has_untracked) ~= "no"
return untracked[cwd] return untracked[cwd]
end end
function M.file_status_to_dir_status(status, cwd) ---@param t table<string|integer, boolean>?
local dirs = {} ---@param k string|integer
for p, s in pairs(status) do ---@return table
local function nil_insert(t, k)
t = t or {}
t[k] = true
return t
end
---@param project_files GitProjectFiles
---@param cwd string|nil
---@return GitProjectDirs
function M.project_files_to_project_dirs(project_files, cwd)
---@type GitProjectDirs
local project_dirs = {}
project_dirs.direct = {}
for p, s in pairs(project_files) do
if s ~= "!!" then if s ~= "!!" then
local modified = vim.fn.fnamemodify(p, ":h") local modified = vim.fn.fnamemodify(p, ":h")
dirs[modified] = s project_dirs.direct[modified] = nil_insert(project_dirs.direct[modified], s)
end end
end end
for dirname, s in pairs(dirs) do project_dirs.indirect = {}
local modified = dirname for dirname, statuses in pairs(project_dirs.direct) do
while modified ~= cwd and modified ~= "/" do for s, _ in pairs(statuses) do
modified = vim.fn.fnamemodify(modified, ":h") local modified = dirname
dirs[modified] = s while modified ~= cwd and modified ~= "/" do
modified = vim.fn.fnamemodify(modified, ":h")
project_dirs.indirect[modified] = nil_insert(project_dirs.indirect[modified], s)
end
end end
end end
return dirs for _, d in pairs(project_dirs) do
for dirname, statuses in pairs(d) do
local new_statuses = {}
for s, _ in pairs(statuses) do
table.insert(new_statuses, s)
end
d[dirname] = new_statuses
end
end
return project_dirs
end
---Git file status for an absolute path
---@param parent_ignored boolean
---@param project GitProject?
---@param path string
---@param path_fallback string? alternative file path when no other file status
---@return GitNodeStatus
function M.git_status_file(parent_ignored, project, path, path_fallback)
---@type GitNodeStatus
local ns
if parent_ignored then
ns = {
file = "!!"
}
elseif project and project.files then
ns = {
file = project.files[path] or project.files[path_fallback]
}
else
ns = {}
end
return ns
end
---Git file and directory status for an absolute path
---@param parent_ignored boolean
---@param project GitProject?
---@param path string
---@param path_fallback string? alternative file path when no other file status
---@return GitNodeStatus?
function M.git_status_dir(parent_ignored, project, path, path_fallback)
---@type GitNodeStatus?
local ns
if parent_ignored then
ns = {
file = "!!"
}
elseif project then
ns = {
file = project.files and (project.files[path] or project.files[path_fallback]),
dir = project.dirs and {
direct = project.dirs.direct and project.dirs.direct[path],
indirect = project.dirs.indirect and project.dirs.indirect[path],
},
}
end
return ns
end
function M.setup(opts)
if opts.git.cygwin_support then
M.use_cygpath = vim.fn.executable("cygpath") == 1
end
end end
return M return M

268
lua/nvim-tree/help.lua Normal file
View File

@@ -0,0 +1,268 @@
local keymap = require("nvim-tree.keymap")
local api = {} -- circular dependency
local PAT_MOUSE = "^<.*Mouse"
local PAT_CTRL = "^<C%-"
local PAT_SPECIAL = "^<.+"
local WIN_HL = table.concat({
"NormalFloat:NvimTreeNormalFloat",
"WinSeparator:NvimTreeWinSeparator",
"CursorLine:NvimTreeCursorLine",
}, ",")
local namespace_help_id = vim.api.nvim_create_namespace("NvimTreeHelp")
local M = {
config = {},
-- one and only buf/win
bufnr = nil,
winnr = nil,
}
--- Shorten and normalise a vim command lhs
---@param lhs string
---@return string
local function tidy_lhs(lhs)
-- nvim_buf_get_keymap replaces leading "<" with "<lt>" e.g. "<lt>CTRL-v>"
lhs = lhs:gsub("^<lt>", "<")
-- shorten ctrls
if lhs:lower():match("^<ctrl%-") then
lhs = lhs:lower():gsub("^<ctrl%-", "<C%-")
end
-- uppercase ctrls
if lhs:lower():match("^<c%-") then
lhs = lhs:upper()
end
-- space is not escaped
lhs = lhs:gsub(" ", "<Space>")
return lhs
end
--- Remove prefix 'nvim-tree: '
--- Hardcoded to keep default_on_attach simple
---@param desc string
---@return string
local function tidy_desc(desc)
return desc and desc:gsub("^nvim%-tree: ", "") or ""
end
--- sort vim command lhs roughly as per :help index
---@param a string
---@param b string
---@return boolean
local function sort_lhs(a, b)
-- mouse first
if a:match(PAT_MOUSE) and not b:match(PAT_MOUSE) then
return true
elseif not a:match(PAT_MOUSE) and b:match(PAT_MOUSE) then
return false
end
-- ctrl next
if a:match(PAT_CTRL) and not b:match(PAT_CTRL) then
return true
elseif not a:match(PAT_CTRL) and b:match(PAT_CTRL) then
return false
end
-- special next
if a:match(PAT_SPECIAL) and not b:match(PAT_SPECIAL) then
return true
elseif not a:match(PAT_SPECIAL) and b:match(PAT_SPECIAL) then
return false
end
-- remainder alpha
return a:gsub("[^a-zA-Z]", "") < b:gsub("[^a-zA-Z]", "")
end
--- Compute all lines for the buffer
---@param map table keymap.get_keymap
---@return string[] lines of text
---@return HighlightRangeArgs[] hl_range_args for lines
---@return number maximum length of text
local function compute(map)
local head_lhs = "nvim-tree mappings"
local head_rhs1 = "exit: q"
local head_rhs2 = string.format("sort by %s: s", M.config.sort_by == "key" and "description" or "keymap")
-- formatted lhs and desc from active keymap
local mappings = vim.tbl_map(function(m)
return { lhs = tidy_lhs(m.lhs), desc = tidy_desc(m.desc) }
end, map)
-- sorter function for mappings
local sort_fn
if M.config.sort_by == "desc" then
sort_fn = function(a, b)
return a.desc:lower() < b.desc:lower()
end
else
-- by default sort roughly by lhs
sort_fn = function(a, b)
return sort_lhs(a.lhs, b.lhs)
end
end
table.sort(mappings, sort_fn)
-- longest lhs and description
local max_lhs = 0
local max_desc = 0
for _, l in pairs(mappings) do
max_lhs = math.max(#l.lhs, max_lhs)
max_desc = math.max(#l.desc, max_desc)
end
-- increase desc if lines are shorter than the header
max_desc = math.max(max_desc, #head_lhs + #head_rhs1 - max_lhs)
-- header text, not padded
local lines = {
head_lhs .. string.rep(" ", max_desc + max_lhs - #head_lhs - #head_rhs1 + 2) .. head_rhs1,
string.rep(" ", max_desc + max_lhs - #head_rhs2 + 2) .. head_rhs2,
}
local width = #lines[1]
-- header highlight, assume one character keys
local hl_range_args = {
{ higroup = "NvimTreeFolderName", start = { 0, 0, }, finish = { 0, #head_lhs, }, },
{ higroup = "NvimTreeFolderName", start = { 0, width - 1, }, finish = { 0, width, }, },
{ higroup = "NvimTreeFolderName", start = { 1, width - 1, }, finish = { 1, width, }, },
}
-- mappings, left padded 1
local fmt = string.format(" %%-%ds %%-%ds", max_lhs, max_desc)
for i, l in ipairs(mappings) do
-- format in left aligned columns
local line = string.format(fmt, l.lhs, l.desc)
table.insert(lines, line)
width = math.max(#line, width)
-- highlight lhs
table.insert(hl_range_args, { higroup = "NvimTreeFolderName", start = { i + 1, 1, }, finish = { i + 1, #l.lhs + 1, }, })
end
return lines, hl_range_args, width
end
--- close the window and delete the buffer, if they exist
local function close()
if M.winnr then
vim.api.nvim_win_close(M.winnr, true)
M.winnr = nil
end
if M.bufnr then
vim.api.nvim_buf_delete(M.bufnr, { force = true })
M.bufnr = nil
end
end
--- open a new window and buffer
local function open()
-- close existing, shouldn't be necessary
close()
-- fetch all mappings
local map = keymap.get_keymap()
-- text and highlight
local lines, hl_range_args, width = compute(map)
-- create the buffer
M.bufnr = vim.api.nvim_create_buf(false, true)
-- populate it
vim.api.nvim_buf_set_lines(M.bufnr, 0, -1, false, lines)
if vim.fn.has("nvim-0.10") == 1 then
vim.api.nvim_set_option_value("modifiable", false, { buf = M.bufnr })
else
vim.api.nvim_buf_set_option(M.bufnr, "modifiable", false) ---@diagnostic disable-line: deprecated
end
-- highlight it
for _, args in ipairs(hl_range_args) do
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
-- open a very restricted window
M.winnr = vim.api.nvim_open_win(M.bufnr, true, {
relative = "editor",
border = "single",
width = width,
height = #lines,
row = 1,
col = 0,
style = "minimal",
noautocmd = true,
})
-- style it a bit like the tree
vim.wo[M.winnr].winhl = WIN_HL
vim.wo[M.winnr].cursorline = M.config.cursorline
local function toggle_sort()
M.config.sort_by = (M.config.sort_by == "desc") and "key" or "desc"
open()
end
-- hardcoded
local help_keymaps = {
q = { fn = close, desc = "nvim-tree: exit help" },
["<Esc>"] = { fn = close, desc = "nvim-tree: exit help" }, -- hidden
s = { fn = toggle_sort, desc = "nvim-tree: toggle sorting method" },
}
-- api help binding closes
for _, m in ipairs(map) do
if m.callback == api.tree.toggle_help then
help_keymaps[m.lhs] = { fn = close, desc = "nvim-tree: exit help" }
end
end
for k, v in pairs(help_keymaps) do
vim.keymap.set("n", k, v.fn, {
desc = v.desc,
buffer = M.bufnr,
noremap = true,
silent = true,
nowait = true,
})
end
-- close window and delete buffer on leave
vim.api.nvim_create_autocmd({ "BufLeave", "WinLeave" }, {
buffer = M.bufnr,
once = true,
callback = close,
})
end
function M.toggle()
if M.winnr or M.bufnr then
close()
else
open()
end
end
function M.setup(opts)
M.config.cursorline = opts.view.cursorline
M.config.sort_by = opts.help.sort_by
api = require("nvim-tree.api")
end
return M

View File

@@ -1,6 +1,9 @@
---@class NodeIterator
local NodeIterator = {} local NodeIterator = {}
NodeIterator.__index = NodeIterator NodeIterator.__index = NodeIterator
---@param nodes Node[]
---@return NodeIterator
function NodeIterator.builder(nodes) function NodeIterator.builder(nodes)
return setmetatable({ return setmetatable({
nodes = nodes, nodes = nodes,
@@ -15,6 +18,7 @@ function NodeIterator.builder(nodes)
}, NodeIterator) }, NodeIterator)
end end
---@return NodeIterator
function NodeIterator:hidden() function NodeIterator:hidden()
self._filter_hidden = function(_) self._filter_hidden = function(_)
return true return true
@@ -22,27 +26,37 @@ function NodeIterator:hidden()
return self return self
end end
---@param f fun(node: Node): boolean
---@return NodeIterator
function NodeIterator:matcher(f) function NodeIterator:matcher(f)
self._match = f self._match = f
return self return self
end end
---@param f fun(node: Node, i: number)
---@return NodeIterator
function NodeIterator:applier(f) function NodeIterator:applier(f)
self._apply_fn_on_node = f self._apply_fn_on_node = f
return self return self
end end
---@param f fun(node: Node): any
---@return NodeIterator
function NodeIterator:recursor(f) function NodeIterator:recursor(f)
self._recurse_with = f self._recurse_with = f
return self return self
end end
---@return Node|nil
---@return number|nil
function NodeIterator:iterate() function NodeIterator:iterate()
local iteration_count = 0 local iteration_count = 0
local function iter(nodes) local function iter(nodes)
for _, node in ipairs(nodes) do for _, node in ipairs(nodes) do
if self._filter_hidden(node) then if self._filter_hidden(node) then
iteration_count = iteration_count + 1 if not node.group_next then
iteration_count = iteration_count + 1
end
if self._match(node) then if self._match(node) then
return node, iteration_count return node, iteration_count
end end

View File

@@ -1,290 +1,116 @@
local Api = require "nvim-tree.api"
local M = {} local M = {}
local DEFAULT_KEYMAPS = { --- Apply mappings to a scratch buffer and return buffer local mappings
{ ---@param fn fun(bufnr: integer) on_attach or default_on_attach
key = { "<CR>", "o", "<2-LeftMouse>" }, ---@return table as per vim.api.nvim_buf_get_keymap
callback = Api.node.open.edit, local function generate_keymap(fn)
desc = "open a file or folder; root will cd to the above directory", -- create an unlisted scratch buffer
}, local scratch_bufnr = vim.api.nvim_create_buf(false, true)
{
key = "<C-e>",
callback = Api.node.open.replace_tree_buffer,
desc = "edit the file in place, effectively replacing the tree explorer",
},
{
key = "O",
callback = Api.node.open.no_window_picker,
desc = "same as (edit) with no window picker",
},
{
key = { "<C-]>", "<2-RightMouse>" },
callback = Api.tree.change_root_to_node,
desc = "cd in the directory under the cursor",
},
{
key = "<C-v>",
callback = Api.node.open.vertical,
desc = "open the file in a vertical split",
},
{
key = "<C-x>",
callback = Api.node.open.horizontal,
desc = "open the file in a horizontal split",
},
{
key = "<C-t>",
callback = Api.node.open.tab,
desc = "open the file in a new tab",
},
{
key = "<",
callback = Api.node.navigate.sibling.prev,
desc = "navigate to the previous sibling of current file/directory",
},
{
key = ">",
callback = Api.node.navigate.sibling.next,
desc = "navigate to the next sibling of current file/directory",
},
{
key = "P",
callback = Api.node.navigate.parent,
desc = "move cursor to the parent directory",
},
{
key = "<BS>",
callback = Api.node.navigate.parent_close,
desc = "close current opened directory or parent",
},
{
key = "<Tab>",
callback = Api.node.open.preview,
desc = "open the file as a preview (keeps the cursor in the tree)",
},
{
key = "K",
callback = Api.node.navigate.sibling.first,
desc = "navigate to the first sibling of current file/directory",
},
{
key = "J",
callback = Api.node.navigate.sibling.last,
desc = "navigate to the last sibling of current file/directory",
},
{
key = "I",
callback = Api.tree.toggle_gitignore_filter,
desc = "toggle visibility of files/folders hidden via |git.ignore| option",
},
{
key = "H",
callback = Api.tree.toggle_hidden_filter,
desc = "toggle visibility of dotfiles via |filters.dotfiles| option",
},
{
key = "U",
callback = Api.tree.toggle_custom_filter,
desc = "toggle visibility of files/folders hidden via |filters.custom| option",
},
{
key = "R",
callback = Api.tree.reload,
desc = "refresh the tree",
},
{
key = "a",
callback = Api.fs.create,
desc = "add a file; leaving a trailing `/` will add a directory",
},
{
key = "d",
callback = Api.fs.remove,
desc = "delete a file (will prompt for confirmation)",
},
{
key = "D",
callback = Api.fs.trash,
desc = "trash a file via |trash| option",
},
{
key = "r",
callback = Api.fs.rename,
desc = "rename a file",
},
{
key = "<C-r>",
callback = Api.fs.rename_sub,
desc = "rename a file and omit the filename on input",
},
{
key = "x",
callback = Api.fs.cut,
desc = "add/remove file/directory to cut clipboard",
},
{
key = "c",
callback = Api.fs.copy.node,
desc = "add/remove file/directory to copy clipboard",
},
{
key = "p",
callback = Api.fs.paste,
desc = "paste from clipboard; cut clipboard has precedence over copy; will prompt for confirmation",
},
{
key = "y",
callback = Api.fs.copy.filename,
desc = "copy name to system clipboard",
},
{
key = "Y",
callback = Api.fs.copy.relative_path,
desc = "copy relative path to system clipboard",
},
{
key = "gy",
callback = Api.fs.copy.absolute_path,
desc = "copy absolute path to system clipboard",
},
{
key = "]e",
callback = Api.node.navigate.diagnostics.next,
desc = "go to next diagnostic item",
},
{
key = "]c",
callback = Api.node.navigate.git.next,
desc = "go to next git item",
},
{
key = "[e",
callback = Api.node.navigate.diagnostics.prev,
desc = "go to prev diagnostic item",
},
{
key = "[c",
callback = Api.node.navigate.git.prev,
desc = "go to prev git item",
},
{
key = "-",
callback = Api.tree.change_root_to_parent,
desc = "navigate up to the parent directory of the current file/directory",
},
{
key = "s",
callback = Api.node.run.system,
desc = "open a file with default system application or a folder with default file manager, using |system_open| option",
},
{
key = "f",
callback = Api.live_filter.start,
desc = "live filter nodes dynamically based on regex matching.",
},
{
key = "F",
callback = Api.live_filter.clear,
desc = "clear live filter",
},
{
key = "q",
callback = Api.tree.close,
desc = "close tree window",
},
{
key = "W",
callback = Api.tree.collapse_all,
desc = "collapse the whole tree",
},
{
key = "E",
callback = Api.tree.expand_all,
desc = "expand the whole tree, stopping after expanding |callbacks.expand_all.max_folder_discovery| folders; this might hang neovim for a while if running on a big folder",
},
{
key = "S",
callback = Api.tree.search_node,
desc = "prompt the user to enter a path and then expands the tree to match the path",
},
{
key = ".",
callback = Api.node.run.cmd,
desc = "enter vim command mode with the file the cursor is on",
},
{
key = "<C-k>",
callback = Api.node.show_info_popup,
desc = "toggle a popup with file infos about the file under the cursor",
},
{
key = "g?",
callback = Api.tree.toggle_help,
desc = "toggle help",
},
{
key = "m",
callback = Api.marks.toggle,
desc = "Toggle node in bookmarks",
},
{
key = "bmv",
callback = Api.marks.bulk.move,
desc = "Move all bookmarked nodes into specified location",
},
}
function M.set_keymaps(bufnr) -- apply mappings
local opts = { noremap = true, silent = true, nowait = true, buffer = bufnr } fn(scratch_bufnr)
for _, km in ipairs(M.keymaps) do
local keys = type(km.key) == "table" and km.key or { km.key } -- retrieve all
for _, key in ipairs(keys) do local keymap = vim.api.nvim_buf_get_keymap(scratch_bufnr, "")
vim.keymap.set("n", key, km.callback, opts)
end -- delete the scratch buffer
end vim.api.nvim_buf_delete(scratch_bufnr, { force = true })
return keymap
end end
local function filter_default_mappings(keys_to_disable) ---@return table
local new_map = {} function M.get_keymap()
for _, m in pairs(DEFAULT_KEYMAPS) do return generate_keymap(M.on_attach)
local keys = type(m.key) == "table" and m.key or { m.key }
local reminding_keys = {}
for _, key in pairs(keys) do
local found = false
for _, key_to_disable in pairs(keys_to_disable) do
if key_to_disable == key then
found = true
break
end
end
if not found then
table.insert(reminding_keys, key)
end
end
if #reminding_keys > 0 then
local map = vim.deepcopy(m)
map.key = reminding_keys
table.insert(new_map, map)
end
end
return new_map
end end
local function get_keymaps(keys_to_disable) ---@return table
if keys_to_disable == true then function M.get_keymap_default()
return {} return generate_keymap(M.default_on_attach)
end
---@param bufnr integer
function M.default_on_attach(bufnr)
local api = require("nvim-tree.api")
local function opts(desc)
return {
desc = "nvim-tree: " .. desc,
buffer = bufnr,
noremap = true,
silent = true,
nowait = true,
}
end end
if type(keys_to_disable) == "table" and #keys_to_disable > 0 then -- BEGIN_DEFAULT_ON_ATTACH
return filter_default_mappings(keys_to_disable) vim.keymap.set("n", "<C-]>", api.tree.change_root_to_node, opts("CD"))
end vim.keymap.set("n", "<C-e>", api.node.open.replace_tree_buffer, opts("Open: In Place"))
vim.keymap.set("n", "<C-k>", api.node.show_info_popup, opts("Info"))
return DEFAULT_KEYMAPS vim.keymap.set("n", "<C-r>", api.fs.rename_sub, opts("Rename: Omit Filename"))
vim.keymap.set("n", "<C-t>", api.node.open.tab, opts("Open: New Tab"))
vim.keymap.set("n", "<C-v>", api.node.open.vertical, opts("Open: Vertical Split"))
vim.keymap.set("n", "<C-x>", api.node.open.horizontal, opts("Open: Horizontal Split"))
vim.keymap.set("n", "<BS>", api.node.navigate.parent_close, opts("Close Directory"))
vim.keymap.set("n", "<CR>", api.node.open.edit, opts("Open"))
vim.keymap.set("n", "<Tab>", api.node.open.preview, opts("Open Preview"))
vim.keymap.set("n", ">", api.node.navigate.sibling.next, opts("Next Sibling"))
vim.keymap.set("n", "<", api.node.navigate.sibling.prev, opts("Previous Sibling"))
vim.keymap.set("n", ".", api.node.run.cmd, opts("Run Command"))
vim.keymap.set("n", "-", api.tree.change_root_to_parent, opts("Up"))
vim.keymap.set("n", "a", api.fs.create, opts("Create File Or Directory"))
vim.keymap.set("n", "bd", api.marks.bulk.delete, opts("Delete Bookmarked"))
vim.keymap.set("n", "bt", api.marks.bulk.trash, opts("Trash Bookmarked"))
vim.keymap.set("n", "bmv", api.marks.bulk.move, opts("Move Bookmarked"))
vim.keymap.set("n", "B", api.tree.toggle_no_buffer_filter, opts("Toggle Filter: No Buffer"))
vim.keymap.set("n", "c", api.fs.copy.node, opts("Copy"))
vim.keymap.set("n", "C", api.tree.toggle_git_clean_filter, opts("Toggle Filter: Git Clean"))
vim.keymap.set("n", "[c", api.node.navigate.git.prev, opts("Prev Git"))
vim.keymap.set("n", "]c", api.node.navigate.git.next, opts("Next Git"))
vim.keymap.set("n", "d", api.fs.remove, opts("Delete"))
vim.keymap.set("n", "D", api.fs.trash, opts("Trash"))
vim.keymap.set("n", "E", api.tree.expand_all, opts("Expand All"))
vim.keymap.set("n", "e", api.fs.rename_basename, opts("Rename: Basename"))
vim.keymap.set("n", "]e", api.node.navigate.diagnostics.next, opts("Next Diagnostic"))
vim.keymap.set("n", "[e", api.node.navigate.diagnostics.prev, opts("Prev Diagnostic"))
vim.keymap.set("n", "F", api.live_filter.clear, opts("Live Filter: Clear"))
vim.keymap.set("n", "f", api.live_filter.start, opts("Live Filter: Start"))
vim.keymap.set("n", "g?", api.tree.toggle_help, opts("Help"))
vim.keymap.set("n", "gy", api.fs.copy.absolute_path, opts("Copy Absolute Path"))
vim.keymap.set("n", "ge", api.fs.copy.basename, opts("Copy Basename"))
vim.keymap.set("n", "H", api.tree.toggle_hidden_filter, opts("Toggle Filter: Dotfiles"))
vim.keymap.set("n", "I", api.tree.toggle_gitignore_filter, opts("Toggle Filter: Git Ignore"))
vim.keymap.set("n", "J", api.node.navigate.sibling.last, opts("Last Sibling"))
vim.keymap.set("n", "K", api.node.navigate.sibling.first, opts("First Sibling"))
vim.keymap.set("n", "L", api.node.open.toggle_group_empty, opts("Toggle Group Empty"))
vim.keymap.set("n", "M", api.tree.toggle_no_bookmark_filter, opts("Toggle Filter: No Bookmark"))
vim.keymap.set("n", "m", api.marks.toggle, opts("Toggle Bookmark"))
vim.keymap.set("n", "o", api.node.open.edit, opts("Open"))
vim.keymap.set("n", "O", api.node.open.no_window_picker, opts("Open: No Window Picker"))
vim.keymap.set("n", "p", api.fs.paste, opts("Paste"))
vim.keymap.set("n", "P", api.node.navigate.parent, opts("Parent Directory"))
vim.keymap.set("n", "q", api.tree.close, opts("Close"))
vim.keymap.set("n", "r", api.fs.rename, opts("Rename"))
vim.keymap.set("n", "R", api.tree.reload, opts("Refresh"))
vim.keymap.set("n", "s", api.node.run.system, opts("Run System"))
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.tree.toggle_custom_filter, opts("Toggle Filter: Hidden"))
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", "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", "<2-LeftMouse>", api.node.open.edit, opts("Open"))
vim.keymap.set("n", "<2-RightMouse>", api.tree.change_root_to_node, opts("CD"))
-- END_DEFAULT_ON_ATTACH
end end
function M.setup(opts) function M.setup(opts)
M.keymaps = get_keymaps(opts.remove_keymaps) if type(opts.on_attach) ~= "function" then
M.on_attach = M.default_on_attach
else
M.on_attach = opts.on_attach
end
end end
return M return M

View File

@@ -1,44 +1,94 @@
local utils = require "nvim-tree.utils" local utils = require("nvim-tree.utils")
local notify = require "nvim-tree.notify" local notify = require("nvim-tree.notify")
local M = {} local M = {}
-- silently move, please add to help nvim-tree-legacy-opts
local function refactored(opts) local function refactored(opts)
-- mapping actions
if opts.view and opts.view.mappings and opts.view.mappings.list then
for _, m in pairs(opts.view.mappings.list) do
if m.action == "toggle_ignored" then
m.action = "toggle_git_ignored"
end
end
end
-- 2022/06/20 -- 2022/06/20
utils.move_missing_val(opts, "update_focused_file", "update_cwd", opts, "update_focused_file", "update_root") utils.move_missing_val(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") utils.move_missing_val(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) utils.move_missing_val(opts, "", "open_on_tab", opts, "tab.sync", "open", false)
utils.move_missing_val(opts, "", "open_on_tab", opts, "tab.sync", "close") utils.move_missing_val(opts, "", "open_on_tab", opts, "tab.sync", "close", true)
utils.move_missing_val(opts, "", "ignore_buf_on_tab_change", opts, "tab.sync", "ignore") utils.move_missing_val(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") utils.move_missing_val(opts, "renderer", "root_folder_modifier", opts, "renderer", "root_folder_label", true)
-- 2023/01/01
utils.move_missing_val(opts, "update_focused_file", "debounce_delay", opts, "view", "debounce_delay", true)
-- 2023/01/08
utils.move_missing_val(opts, "trash", "require_confirm", opts, "ui.confirm", "trash", true)
-- 2023/01/15
if type(opts.view) == "table" and opts.view.adaptive_size ~= nil then
if opts.view.adaptive_size and type(opts.view.width) ~= "table" then
local width = opts.view.width
opts.view.width = {
min = width,
}
end
opts.view.adaptive_size = nil
end
-- 2023/07/15
utils.move_missing_val(opts, "", "sort_by", opts, "sort", "sorter", true)
-- 2023/07/16
utils.move_missing_val(opts, "git", "ignore", opts, "filters", "git_ignored", true)
-- 2023/08/26
utils.move_missing_val(opts, "renderer.icons", "webdev_colors", opts, "renderer.icons.web_devicons.file", "color", true)
-- 2023/10/08
if type(opts.renderer) == "table" and type(opts.renderer.highlight_diagnostics) == "boolean" then
opts.renderer.highlight_diagnostics = opts.renderer.highlight_diagnostics and "name" or "none"
end
-- 2023/10/21
if type(opts.renderer) == "table" and type(opts.renderer.highlight_git) == "boolean" then
opts.renderer.highlight_git = opts.renderer.highlight_git and "name" or "none"
end
-- 2024/02/15
if type(opts.update_focused_file) == "table" then
if type(opts.update_focused_file.update_root) ~= "table" then
opts.update_focused_file.update_root = { enable = opts.update_focused_file.update_root }
end
end
utils.move_missing_val(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
local function deprecated(opts)
if type(opts.view) == "table" and opts.view.hide_root_folder then
notify.info("view.hide_root_folder is deprecated, please set renderer.root_folder_label = false")
end
end end
local function removed(opts) local function removed(opts)
if opts.auto_close then if opts.auto_close then
notify.warn "auto close feature has been removed, see note in the README (tips & reminder section)" notify.warn("auto close feature has been removed: https://github.com/nvim-tree/nvim-tree.lua/wiki/Auto-Close")
opts.auto_close = nil opts.auto_close = nil
end end
if opts.focus_empty_on_setup then if opts.focus_empty_on_setup then
notify.warn "focus_empty_on_setup has been removed and will be replaced by a new startup configuration. Please remove this option. See https://bit.ly/3yJch2T" notify.warn("focus_empty_on_setup has been removed: https://github.com/nvim-tree/nvim-tree.lua/wiki/Open-At-Startup")
opts.focus_empty_on_setup = nil opts.focus_empty_on_setup = nil
end end
if opts.create_in_closed_folder then if opts.create_in_closed_folder then
notify.warn "create_in_closed_folder has been removed and is now the default behaviour. You may use api.fs.create to add a file under your desired node." notify.warn(
"create_in_closed_folder has been removed and is now the default behaviour. You may use api.fs.create to add a file under your desired node.")
end end
opts.create_in_closed_folder = nil opts.create_in_closed_folder = nil
end end
@@ -47,6 +97,9 @@ function M.migrate_legacy_options(opts)
-- silently move -- silently move
refactored(opts) refactored(opts)
-- warn
deprecated(opts)
-- warn and delete -- warn and delete
removed(opts) removed(opts)
end end

View File

@@ -1,98 +1,16 @@
local renderer = require "nvim-tree.renderer" 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 notify = require("nvim-tree.notify")
local utils = require "nvim-tree.utils"
---@class LibOpenOpts
---@field path string|nil path
---@field current_window boolean|nil default false
---@field winid number|nil
local M = { local M = {
target_winid = nil, target_winid = nil,
} }
function M.get_node_at_cursor()
if not core.get_explorer() then
return
end
local winnr = view.get_winnr()
if not winnr then
return
end
local cursor = vim.api.nvim_win_get_cursor(view.get_winnr())
local line = cursor[1]
if view.is_help_ui() then
local help_lines = require("nvim-tree.renderer.help").compute_lines()
local help_text = utils.get_nodes_by_line(help_lines, 1)[line]
return { name = help_text }
end
if line == 1 and view.is_root_folder_visible(core.get_cwd()) then
return { name = ".." }
end
return utils.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line())[line]
end
---Create a sanitized partial copy of a node, populating children recursively.
---@param node table
---@return table|nil cloned node
local function clone_node(node)
if not node then
node = core.get_explorer()
if not node then
return nil
end
end
local n = {
absolute_path = node.absolute_path,
executable = node.executable,
extension = node.extension,
git_status = node.git_status,
has_children = node.has_children,
hidden = node.hidden,
link_to = node.link_to,
name = node.name,
open = node.open,
type = node.type,
}
if type(node.nodes) == "table" then
n.nodes = {}
for _, child in ipairs(node.nodes) do
table.insert(n.nodes, clone_node(child))
end
end
return n
end
---Api.tree.get_nodes
function M.get_nodes()
return clone_node(core.get_explorer())
end
-- If node is grouped, return the last node in the group. Otherwise, return the given node.
function M.get_last_group_node(node)
local next = node
while next.group_next do
next = next.group_next
end
return next
end
function M.expand_or_collapse(node)
node.open = not node.open
if node.has_children then
node.has_children = false
end
if #node.nodes == 0 then
core.get_explorer():expand(node)
end
renderer.draw()
end
function M.set_target_win() function M.set_target_win()
local id = vim.api.nvim_get_current_win() local id = vim.api.nvim_get_current_win()
local tree_id = view.get_winnr() local tree_id = view.get_winnr()
@@ -104,6 +22,7 @@ function M.set_target_win()
M.target_winid = id M.target_winid = id
end end
---@param cwd string
local function handle_buf_cwd(cwd) local function handle_buf_cwd(cwd)
if M.respect_buf_cwd and cwd ~= core.get_cwd() then if M.respect_buf_cwd and cwd ~= core.get_cwd() then
require("nvim-tree.actions.root.change-dir").fn(cwd) require("nvim-tree.actions.root.change-dir").fn(cwd)
@@ -114,14 +33,25 @@ local function open_view_and_draw()
local cwd = vim.fn.getcwd() local cwd = vim.fn.getcwd()
view.open() view.open()
handle_buf_cwd(cwd) handle_buf_cwd(cwd)
renderer.draw()
local explorer = core.get_explorer()
if explorer then
explorer.renderer:draw()
end
end end
local function should_hijack_current_buf() local function should_hijack_current_buf()
local bufnr = vim.api.nvim_get_current_buf() local bufnr = vim.api.nvim_get_current_buf()
local bufname = vim.api.nvim_buf_get_name(bufnr) local bufname = vim.api.nvim_buf_get_name(bufnr)
local bufmodified = vim.api.nvim_buf_get_option(bufnr, "modified")
local ft = vim.api.nvim_buf_get_option(bufnr, "ft") local bufmodified, ft
if vim.fn.has("nvim-0.10") == 1 then
bufmodified = vim.api.nvim_get_option_value("modified", { buf = bufnr })
ft = vim.api.nvim_get_option_value("ft", { buf = bufnr })
else
bufmodified = vim.api.nvim_buf_get_option(bufnr, "modified") ---@diagnostic disable-line: deprecated
ft = vim.api.nvim_buf_get_option(bufnr, "ft") ---@diagnostic disable-line: deprecated
end
local should_hijack_unnamed = M.hijack_unnamed_buffer_when_opening and bufname == "" and not bufmodified and ft == "" local should_hijack_unnamed = M.hijack_unnamed_buffer_when_opening and bufname == "" and not bufmodified and ft == ""
local should_hijack_dir = bufname ~= "" and vim.fn.isdirectory(bufname) == 1 and M.hijack_directories.enable local should_hijack_dir = bufname ~= "" and vim.fn.isdirectory(bufname) == 1 and M.hijack_directories.enable
@@ -129,7 +59,13 @@ local function should_hijack_current_buf()
return should_hijack_dir or should_hijack_unnamed return should_hijack_dir or should_hijack_unnamed
end end
function M.prompt(prompt_input, prompt_select, items_short, items_long, callback) ---@param prompt_input string
---@param prompt_select string
---@param items_short string[]
---@param items_long string[]
---@param kind string|nil
---@param callback fun(item_short: string|nil)
function M.prompt(prompt_input, prompt_select, items_short, items_long, kind, callback)
local function format_item(short) local function format_item(short)
for i, s in ipairs(items_short) do for i, s in ipairs(items_short) do
if short == s then if short == s then
@@ -140,49 +76,67 @@ function M.prompt(prompt_input, prompt_select, items_short, items_long, callback
end end
if M.select_prompts then if M.select_prompts then
vim.ui.select(items_short, { prompt = prompt_select, format_item = format_item }, function(item_short) vim.ui.select(items_short, { prompt = prompt_select, kind = kind, format_item = format_item }, function(item_short)
callback(item_short) callback(item_short)
end) end)
else else
vim.ui.input({ prompt = prompt_input }, function(item_short) vim.ui.input({ prompt = prompt_input, default = items_short[1] or "" }, function(item_short)
callback(item_short) if item_short then
callback(string.lower(item_short and item_short:sub(1, 1)) or nil)
end
end) end)
end end
end end
function M.open(cwd) ---Open the tree, initialising as needed. Maybe hijack the current buffer.
---@param opts LibOpenOpts|nil
function M.open(opts)
opts = opts or {}
M.set_target_win() M.set_target_win()
if not core.get_explorer() or cwd then if not core.get_explorer() or opts.path then
core.init(cwd or vim.loop.cwd()) if opts.path then
core.init(opts.path)
else
local cwd, err = vim.loop.cwd()
if not cwd then
notify.error(string.format("current working directory unavailable: %s", err))
return
end
core.init(cwd)
end
end end
local explorer = core.get_explorer()
if should_hijack_current_buf() then if should_hijack_current_buf() then
view.close_this_tab_only() view.close_this_tab_only()
view.open_in_current_win() view.open_in_win()
renderer.draw() if explorer then
explorer.renderer:draw()
end
elseif opts.winid then
view.open_in_win({ hijack_current_buf = false, resize = false, winid = opts.winid })
if explorer then
explorer.renderer:draw()
end
elseif opts.current_window then
view.open_in_win({ hijack_current_buf = false, resize = false })
if explorer then
explorer.renderer:draw()
end
else else
open_view_and_draw() open_view_and_draw()
end end
view.restore_tab_state() view.restore_tab_state()
end end
-- @deprecated: use nvim-tree.actions.tree-modifiers.collapse-all.fn
M.collapse_all = require("nvim-tree.actions.tree-modifiers.collapse-all").fn
-- @deprecated: use nvim-tree.actions.root.dir-up.fn
M.dir_up = require("nvim-tree.actions.root.dir-up").fn
-- @deprecated: use nvim-tree.actions.root.change-dir.fn
M.change_dir = require("nvim-tree.actions.root.change-dir").fn
-- @deprecated: use nvim-tree.actions.reloaders.reloaders.reload_explorer
M.refresh_tree = require("nvim-tree.actions.reloaders.reloaders").reload_explorer
-- @deprecated: use nvim-tree.actions.reloaders.reloaders.reload_git
M.reload_git = require("nvim-tree.actions.reloaders.reloaders").reload_git
-- @deprecated: use nvim-tree.actions.finders.find-file.fn
M.set_index_and_redraw = require("nvim-tree.actions.finders.find-file").fn
function M.setup(opts) function M.setup(opts)
M.hijack_unnamed_buffer_when_opening = opts.hijack_unnamed_buffer_when_opening M.hijack_unnamed_buffer_when_opening = opts.hijack_unnamed_buffer_when_opening
M.hijack_directories = opts.hijack_directories M.hijack_directories = opts.hijack_directories
M.respect_buf_cwd = opts.respect_buf_cwd M.respect_buf_cwd = opts.respect_buf_cwd
M.select_prompts = opts.select_prompts M.select_prompts = opts.select_prompts
M.group_empty = opts.renderer.group_empty
end end
return M return M

View File

@@ -1,158 +0,0 @@
local view = require "nvim-tree.view"
local utils = require "nvim-tree.utils"
local Iterator = require "nvim-tree.iterators.node-iterator"
local M = {
filter = nil,
}
local function redraw()
require("nvim-tree.renderer").draw()
end
local function reset_filter(node_)
node_ = node_ or TreeExplorer
Iterator.builder(node_.nodes)
:hidden()
:applier(function(node)
node.hidden = false
end)
:iterate()
end
local overlay_bufnr = nil
local overlay_winnr = nil
local function remove_overlay()
if view.View.float.enable and view.View.float.quit_on_focus_loss then
-- return to normal nvim-tree float behaviour when filter window is closed
vim.api.nvim_create_autocmd("WinLeave", {
pattern = "NvimTree_*",
group = vim.api.nvim_create_augroup("NvimTree", { clear = false }),
callback = function()
if utils.is_nvim_tree_buf(0) then
view.close()
end
end,
})
end
vim.api.nvim_win_close(overlay_winnr, { force = true })
overlay_bufnr = nil
overlay_winnr = nil
if M.filter == "" then
M.clear_filter()
end
end
local function matches(node)
local path = node.absolute_path
local name = vim.fn.fnamemodify(path, ":t")
return vim.regex(M.filter):match_str(name) ~= nil
end
function M.apply_filter(node_)
if not M.filter or M.filter == "" then
reset_filter(node_)
return
end
-- TODO(kiyan): this iterator cannot yet be refactored with the Iterator module
-- since the node mapper is based on its children
local function iterate(node)
local filtered_nodes = 0
local nodes = node.group_next and { node.group_next } or node.nodes
if nodes then
for _, n in pairs(nodes) do
iterate(n)
if n.hidden then
filtered_nodes = filtered_nodes + 1
end
end
end
local has_nodes = nodes and (M.always_show_folders or #nodes > filtered_nodes)
local ok, is_match = pcall(matches, node)
node.hidden = not (has_nodes or (ok and is_match))
end
iterate(node_ or TreeExplorer)
end
local function record_char()
vim.schedule(function()
M.filter = vim.api.nvim_buf_get_lines(overlay_bufnr, 0, -1, false)[1]
M.apply_filter()
redraw()
end)
end
local function configure_buffer_overlay()
overlay_bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_attach(overlay_bufnr, true, {
on_lines = record_char,
})
vim.api.nvim_create_autocmd("InsertLeave", {
callback = remove_overlay,
once = true,
})
vim.api.nvim_buf_set_keymap(overlay_bufnr, "i", "<CR>", "<cmd>stopinsert<CR>", {})
end
local function create_overlay()
local min_width = 20
if view.View.float.enable then
-- don't close nvim-tree float when focus is changed to filter window
vim.api.nvim_clear_autocmds {
event = "WinLeave",
pattern = "NvimTree_*",
group = vim.api.nvim_create_augroup("NvimTree", { clear = false }),
}
min_width = min_width - 2
end
configure_buffer_overlay()
overlay_winnr = vim.api.nvim_open_win(overlay_bufnr, true, {
col = 1,
row = 0,
relative = "cursor",
width = math.max(min_width, vim.api.nvim_win_get_width(view.get_winnr()) - #M.prefix - 2),
height = 1,
border = "none",
style = "minimal",
})
vim.api.nvim_buf_set_option(overlay_bufnr, "modifiable", true)
vim.api.nvim_buf_set_lines(overlay_bufnr, 0, -1, false, { M.filter })
vim.cmd "startinsert"
vim.api.nvim_win_set_cursor(overlay_winnr, { 1, #M.filter + 1 })
end
function M.start_filtering()
M.filter = M.filter or ""
redraw()
local row = require("nvim-tree.core").get_nodes_starting_line() - 1
local col = #M.prefix > 0 and #M.prefix - 1 or 1
view.set_cursor { row, col }
-- needs scheduling to let the cursor move before initializing the window
vim.schedule(create_overlay)
end
function M.clear_filter()
M.filter = nil
reset_filter()
redraw()
end
function M.setup(opts)
M.prefix = opts.live_filter.prefix
M.always_show_folders = opts.live_filter.always_show_folders
end
return M

View File

@@ -1,60 +1,123 @@
local M = { ---@alias LogTypes "all" | "config" | "copy_paste" | "dev" | "diagnostics" | "git" | "profile" | "watcher"
config = nil,
path = nil, ---@type table<LogTypes, boolean>
} local types = {}
---@type string
local file_path
local M = {}
--- Write to log file --- Write to log file
--- @param typ string as per log.types config ---@param typ string as per log.types config
--- @param fmt string for string.format ---@param fmt string for string.format
--- @vararg any arguments for string.format ---@param ... any arguments for string.format
function M.raw(typ, fmt, ...) function M.raw(typ, fmt, ...)
if not M.path or not M.config.types[typ] and not M.config.types.all then if not M.enabled(typ) then
return return
end end
local line = string.format(fmt, ...) local line = string.format(fmt, ...)
local file = io.open(M.path, "a") local file = io.open(file_path, "a")
io.output(file) if file then
io.write(line) io.output(file)
io.close(file) io.write(line)
io.close(file)
end
end end
--- Write to log file via M.line --- Write to a new file
---@param typ LogTypes as per log.types config
---@param path string absolute path
---@param fmt string for string.format
---@param ... any arguments for string.format
function M.file(typ, path, fmt, ...)
if not M.enabled(typ) then
return
end
local line = string.format(fmt, ...)
local file = io.open(path, "w")
if file then
io.output(file)
io.write(line)
io.close(file)
end
end
---@class Profile
---@field start number nanos
---@field tag string
--- Write profile start to log file
--- START is prefixed --- START is prefixed
--- @return number nanos to pass to profile_end ---@param fmt string for string.format
---@param ... any arguments for string.format
---@return Profile to pass to profile_end
function M.profile_start(fmt, ...) function M.profile_start(fmt, ...)
if not M.path or not M.config.types.profile and not M.config.types.all then local profile = {}
return if M.enabled("profile") then
profile.start = vim.loop.hrtime()
profile.tag = string.format((fmt or "???"), ...)
M.line("profile", "START %s", profile.tag)
end end
M.line("profile", "START " .. (fmt or "???"), ...) return profile
return vim.loop.hrtime()
end end
--- Write to log file via M.line --- Write profile end to log file
--- END is prefixed and duration in seconds is suffixed --- END is prefixed and duration in seconds is suffixed
--- @param start number nanos returned from profile_start ---@param profile Profile returned from profile_start
function M.profile_end(start, fmt, ...) function M.profile_end(profile)
if not M.path or not M.config.types.profile and not M.config.types.all then if M.enabled("profile") and type(profile) == "table" then
return local millis = profile.start and math.modf((vim.loop.hrtime() - profile.start) / 1000000) or -1
M.line("profile", "END %s %dms", profile.tag or "", millis)
end end
local millis = start and math.modf((vim.loop.hrtime() - start) / 1000000) or -1
M.line("profile", "END " .. (fmt or "???") .. " " .. millis .. "ms", ...)
end end
-- Write to log file via M.raw --- Write to log file
-- time and typ are prefixed and a trailing newline is added --- time and typ are prefixed and a trailing newline is added
---@param typ LogTypes as per log.types config
---@param fmt string for string.format
---@param ... any arguments for string.format
function M.line(typ, fmt, ...) function M.line(typ, fmt, ...)
M.raw(typ, string.format("[%s] [%s] %s\n", os.date "%Y-%m-%d %H:%M:%S", typ, fmt), ...) if M.enabled(typ) then
M.raw(typ, string.format("[%s] [%s] %s\n", os.date("%Y-%m-%d %H:%M:%S"), typ, (fmt or "???")), ...)
end
end
local inspect_opts = {}
---@param opts table
function M.set_inspect_opts(opts)
inspect_opts = opts
end
--- Write to log file the inspection of a node
---@param typ LogTypes as per log.types config
---@param node Node node to be inspected
---@param fmt string for string.format
---@param ... any arguments for string.format
function M.node(typ, node, fmt, ...)
if M.enabled(typ) then
M.raw(typ, string.format("[%s] [%s] %s\n%s\n", os.date("%Y-%m-%d %H:%M:%S"), typ, (fmt or "???"), vim.inspect(node, inspect_opts)), ...)
end
end
--- Logging is enabled for typ or all
---@param typ LogTypes as per log.types config
---@return boolean
function M.enabled(typ)
return file_path ~= nil and (types[typ] or types.all)
end end
function M.setup(opts) function M.setup(opts)
M.config = opts.log if opts.log and opts.log.enable and opts.log.types then
if M.config and M.config.enable and M.config.types then types = opts.log.types
M.path = string.format("%s/nvim-tree.log", vim.fn.stdpath "cache", os.date "%H:%M:%S", vim.env.USER) file_path = string.format("%s/nvim-tree.log", vim.fn.stdpath("log"), os.date("%H:%M:%S"), vim.env.USER)
if M.config.truncate then if opts.log.truncate then
os.remove(M.path) os.remove(file_path)
end end
require("nvim-tree.notify").debug("nvim-tree.lua logging to " .. M.path) require("nvim-tree.notify").debug("nvim-tree.lua logging to " .. file_path)
end end
end end

View File

@@ -1,42 +0,0 @@
local Marks = require "nvim-tree.marks"
local Core = require "nvim-tree.core"
local utils = require "nvim-tree.utils"
local FsRename = require "nvim-tree.actions.fs.rename-file"
local notify = require "nvim-tree.notify"
local M = {}
function M.bulk_move()
if #Marks.get_marks() == 0 then
notify.warn "no bookmark to perform bulk move on, aborting."
return
end
vim.ui.input({ prompt = "Move to: ", default = Core.get_cwd(), completion = "dir" }, function(location)
utils.clear_prompt()
if not location or location == "" then
return
end
if vim.fn.filewritable(location) ~= 2 then
notify.warn(location .. " is not writable, cannot move.")
return
end
local marks = Marks.get_marks()
for _, node in pairs(marks) do
local head = vim.fn.fnamemodify(node.absolute_path, ":t")
local to = utils.path_join { location, head }
FsRename.rename(node, to)
end
if M.enable_reload then
require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
end
end)
end
function M.setup(opts)
M.enable_reload = not opts.filesystem_watchers.enable
end
return M

View File

@@ -1,81 +1,271 @@
local view = require "nvim-tree.view" local Iterator = require("nvim-tree.iterators.node-iterator")
local Iterator = require "nvim-tree.iterators.node-iterator" local core = require("nvim-tree.core")
local core = require "nvim-tree.core" local lib = require("nvim-tree.lib")
local notify = require("nvim-tree.notify")
local open_file = require("nvim-tree.actions.node.open-file")
local remove_file = require("nvim-tree.actions.fs.remove-file")
local rename_file = require("nvim-tree.actions.fs.rename-file")
local trash = require("nvim-tree.actions.fs.trash")
local utils = require("nvim-tree.utils")
local NvimTreeMarks = {} local Class = require("nvim-tree.classic")
local DirectoryNode = require("nvim-tree.node.directory")
local M = {} ---@class (exact) Marks: Class
---@field private explorer Explorer
---@field private marks table<string, Node> by absolute path
local Marks = Class:extend()
local function add_mark(node) ---@class Marks
NvimTreeMarks[node.absolute_path] = node ---@overload fun(args: MarksArgs): Marks
M.draw()
---@class (exact) MarksArgs
---@field explorer Explorer
---@protected
---@param args MarksArgs
function Marks:new(args)
self.explorer = args.explorer
self.marks = {}
end end
local function remove_mark(node) ---Clear all marks and reload if watchers disabled
NvimTreeMarks[node.absolute_path] = nil ---@private
M.draw() function Marks:clear_reload()
self:clear()
if not self.explorer.opts.filesystem_watchers.enable then
self.explorer:reload_explorer()
end
end end
function M.toggle_mark(node) ---Clear all marks and redraw
---@public
function Marks:clear()
self.marks = {}
self.explorer.renderer:draw()
end
---@public
---@param node Node
function Marks:toggle(node)
if node.absolute_path == nil then if node.absolute_path == nil then
return return
end end
if M.get_mark(node) then if self:get(node) then
remove_mark(node) self.marks[node.absolute_path] = nil
else else
add_mark(node) self.marks[node.absolute_path] = node
end end
self.explorer.renderer:draw()
end end
function M.clear_marks() ---Return node if marked
NvimTreeMarks = {} ---@public
M.draw() ---@param node Node
---@return Node|nil
function Marks:get(node)
return node and self.marks[node.absolute_path]
end end
function M.get_mark(node) ---List marked nodes
return NvimTreeMarks[node.absolute_path] ---@public
end ---@return Node[]
function Marks:list()
function M.get_marks()
local list = {} local list = {}
for _, node in pairs(NvimTreeMarks) do for _, node in pairs(self.marks) do
table.insert(list, node) table.insert(list, node)
end end
return list return list
end end
local GROUP = "NvimTreeMarkSigns" ---Delete marked; each removal will be optionally notified
local SIGN_NAME = "NvimTreeMark" ---@public
function Marks:bulk_delete()
function M.clear() if not next(self.marks) then
vim.fn.sign_unplace(GROUP) notify.warn("No bookmarks to delete.")
end
function M.draw()
if not view.is_visible() then
return return
end end
M.clear() local function execute()
for _, node in pairs(self.marks) do
remove_file.remove(node)
end
self:clear_reload()
end
local buf = view.get_bufnr() if self.explorer.opts.ui.confirm.remove then
local add = core.get_nodes_starting_line() - 1 local prompt_select = "Remove bookmarked ?"
Iterator.builder(core.get_explorer().nodes) local prompt_input = prompt_select .. " y/N: "
:recursor(function(node) lib.prompt(prompt_input, prompt_select, { "", "y" }, { "No", "Yes" }, "nvimtree_bulk_delete", function(item_short)
return node.open and node.nodes utils.clear_prompt()
if item_short == "y" then
execute()
end
end) end)
:applier(function(node, idx) else
if M.get_mark(node) then execute()
vim.fn.sign_place(0, GROUP, SIGN_NAME, buf, { lnum = idx + add, priority = 3 }) end
end
---Trash marked; each removal will be optionally notified
---@public
function Marks:bulk_trash()
if not next(self.marks) then
notify.warn("No bookmarks to trash.")
return
end
local function execute()
for _, node in pairs(self.marks) do
trash.remove(node)
end
self:clear_reload()
end
if self.explorer.opts.ui.confirm.trash then
local prompt_select = "Trash bookmarked ?"
local prompt_input = prompt_select .. " y/N: "
lib.prompt(prompt_input, prompt_select, { "", "y" }, { "No", "Yes" }, "nvimtree_bulk_trash", function(item_short)
utils.clear_prompt()
if item_short == "y" then
execute()
end
end)
else
execute()
end
end
---Move marked
---@public
function Marks:bulk_move()
if not next(self.marks) then
notify.warn("No bookmarks to move.")
return
end
local node_at_cursor = self.explorer:get_node_at_cursor()
local default_path = core.get_cwd()
if node_at_cursor and node_at_cursor:is(DirectoryNode) then
default_path = node_at_cursor.absolute_path
elseif node_at_cursor and node_at_cursor.parent then
default_path = node_at_cursor.parent.absolute_path
end
local input_opts = {
prompt = "Move to: ",
default = default_path,
completion = "dir",
}
vim.ui.input(input_opts, function(location)
utils.clear_prompt()
if not location or location == "" then
return
end
if vim.fn.filewritable(location) ~= 2 then
notify.warn(location .. " is not writable, cannot move.")
return
end
for _, node in pairs(self.marks) do
local head = vim.fn.fnamemodify(node.absolute_path, ":t")
local to = utils.path_join({ location, head })
rename_file.rename(node, to)
end
self:clear_reload()
end)
end
---Focus nearest marked node in direction.
---@private
---@param up boolean
function Marks:navigate(up)
local node = self.explorer:get_node_at_cursor()
if not node then
return
end
local first, prev, next, last = nil, nil, nil, nil
local found = false
Iterator.builder(self.explorer.nodes)
:recursor(function(n)
local dir = n:as(DirectoryNode)
return dir and dir.open and dir.nodes
end)
:applier(function(n)
if n.absolute_path == node.absolute_path then
found = true
return
end
if not self:get(n) then
return
end
last = n
first = first or n
if found and not next then
next = n
end
if not found then
prev = n
end end
end) end)
:iterate() :iterate()
if not found then
return
end
if up then
utils.focus_node_or_parent(prev or last)
else
utils.focus_node_or_parent(next or first)
end
end end
function M.setup(opts) ---@public
vim.fn.sign_define(SIGN_NAME, { text = opts.renderer.icons.glyphs.bookmark, texthl = "NvimTreeBookmark" }) function Marks:navigate_prev()
require("nvim-tree.marks.bulk-move").setup(opts) self:navigate(true)
end end
return M ---@public
function Marks:navigate_next()
self:navigate(false)
end
---Prompts for selection of a marked node, sorted by absolute paths.
---A folder will be focused, a file will be opened.
---@public
function Marks:navigate_select()
local list = vim.tbl_map(function(n)
return n.absolute_path
end, self:list())
table.sort(list)
vim.ui.select(list, {
prompt = "Select a file to open or a folder to focus",
}, function(choice)
if not choice or choice == "" then
return
end
local node = self.marks[choice]
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)
elseif node then
utils.focus_file(node.absolute_path)
end
end)
end
return Marks

View File

@@ -1,93 +0,0 @@
local Iterator = require "nvim-tree.iterators.node-iterator"
local core = require "nvim-tree.core"
local Marks = require "nvim-tree.marks"
local open_file = require "nvim-tree.actions.node.open-file"
local utils = require "nvim-tree.utils"
local lib = require "nvim-tree.lib"
local function get_nearest(node, where)
local first, prev, next, last = nil, nil, nil, nil
local found = false
Iterator.builder(core.get_explorer().nodes)
:recursor(function(n)
return n.open and n.nodes
end)
:applier(function(n)
if n.absolute_path == node.absolute_path then
found = true
return
end
if not Marks.get_mark(n) then
return
end
last = n
first = first or n
if found and not next then
next = n
end
if not found then
prev = n
end
end)
:iterate()
if not found then
return
end
if where == "next" then
return next or first
else
return prev or last
end
end
local function get(where, node)
if node then
return get_nearest(node, where)
end
end
local function open_or_focus(node)
if node and not node.nodes and not utils.get_win_buf_from_path(node.absolute_path) then
open_file.fn("edit", node.absolute_path)
elseif node then
utils.focus_file(node.absolute_path)
end
end
local function navigate_to(where)
return function()
local node = lib.get_node_at_cursor()
local next = get(where, node)
open_or_focus(next)
end
end
local M = {}
M.next = navigate_to "next"
M.prev = navigate_to "prev"
function M.select()
local list = vim.tbl_map(function(n)
return n.absolute_path
end, Marks.get_marks())
vim.ui.select(list, {
prompt = "Select a file to open or a folder to focus",
}, function(choice)
if not choice or choice == "" then
return
end
local node = Marks.get_mark { absolute_path = choice }
open_or_focus(node)
end)
end
return M

View File

@@ -0,0 +1,87 @@
local git_utils = require("nvim-tree.git.utils")
local utils = require("nvim-tree.utils")
local DirectoryNode = require("nvim-tree.node.directory")
local LinkNode = require("nvim-tree.node.link")
---@class (exact) DirectoryLinkNode: DirectoryNode, LinkNode
local DirectoryLinkNode = DirectoryNode:extend()
DirectoryLinkNode:implement(LinkNode)
---@class DirectoryLinkNode
---@overload fun(args: LinkNodeArgs): DirectoryLinkNode
---@protected
---@param args LinkNodeArgs
function DirectoryLinkNode:new(args)
LinkNode.new(self, args)
-- create DirectoryNode with watcher on link_to
local absolute_path = args.absolute_path
args.absolute_path = args.link_to
DirectoryLinkNode.super.new(self, args)
self.type = "link"
-- reset absolute path to the link itself
self.absolute_path = absolute_path
end
function DirectoryLinkNode:destroy()
DirectoryNode.destroy(self)
end
---Update the directory git_status of link target and the file status of the link itself
---@param parent_ignored boolean
---@param project GitProject?
function DirectoryLinkNode:update_git_status(parent_ignored, project)
self.git_status = git_utils.git_status_dir(parent_ignored, project, self.link_to, self.absolute_path)
end
---@return HighlightedString name
function DirectoryLinkNode:highlighted_icon()
if not self.explorer.opts.renderer.icons.show.folder then
return self:highlighted_icon_empty()
end
local str, hl
if self.open then
str = self.explorer.opts.renderer.icons.glyphs.folder.symlink_open
hl = "NvimTreeOpenedFolderIcon"
else
str = self.explorer.opts.renderer.icons.glyphs.folder.symlink
hl = "NvimTreeClosedFolderIcon"
end
return { str = str, hl = { hl } }
end
---Maybe override name with arrow
---@return HighlightedString name
function DirectoryLinkNode:highlighted_name()
local name = DirectoryNode.highlighted_name(self)
if self.explorer.opts.renderer.symlink_destination then
local link_to = utils.path_relative(self.link_to, self.explorer.absolute_path)
name.str = string.format("%s%s%s", name.str, self.explorer.opts.renderer.icons.symlink_arrow, link_to)
name.hl = { "NvimTreeSymlinkFolderName" }
end
return name
end
---Create a sanitized partial copy of a node, populating children recursively.
---@param api_nodes table<number, nvim_tree.api.Node>? optional map of uids to api node to populate
---@return nvim_tree.api.DirectoryLinkNode cloned
function DirectoryLinkNode:clone(api_nodes)
local clone = DirectoryNode.clone(self, api_nodes) --[[@as nvim_tree.api.DirectoryLinkNode]]
clone.link_to = self.link_to
clone.fs_stat_target = self.fs_stat_target
return clone
end
return DirectoryLinkNode

View File

@@ -0,0 +1,293 @@
local git_utils = require("nvim-tree.git.utils")
local icons = require("nvim-tree.renderer.components.devicons")
local notify = require("nvim-tree.notify")
local Node = require("nvim-tree.node")
---@class (exact) DirectoryNode: Node
---@field has_children boolean
---@field group_next DirectoryNode? -- If node is grouped, this points to the next child dir/link node
---@field nodes Node[]
---@field open boolean
---@field hidden_stats table? -- Each field of this table is a key for source and value for count
---@field private watcher Watcher?
local DirectoryNode = Node:extend()
---@class DirectoryNode
---@overload fun(args: NodeArgs): DirectoryNode
---@protected
---@param args NodeArgs
function DirectoryNode:new(args)
DirectoryNode.super.new(self, args)
local handle = vim.loop.fs_scandir(args.absolute_path)
local has_children = handle and vim.loop.fs_scandir_next(handle) ~= nil or false
self.type = "directory"
self.has_children = has_children
self.group_next = nil
self.nodes = {}
self.open = false
self.hidden_stats = nil
self.watcher = require("nvim-tree.explorer.watch").create_watcher(self)
end
function DirectoryNode:destroy()
if self.watcher then
self.watcher:destroy()
self.watcher = nil
end
if self.nodes then
for _, node in pairs(self.nodes) do
node:destroy()
end
end
Node.destroy(self)
end
---Update the git_status of the directory
---@param parent_ignored boolean
---@param project GitProject?
function DirectoryNode:update_git_status(parent_ignored, project)
self.git_status = git_utils.git_status_dir(parent_ignored, project, self.absolute_path, nil)
end
---@return GitXY[]?
function DirectoryNode:get_git_xy()
if not self.git_status or not self.explorer.opts.git.show_on_dirs then
return nil
end
local xys = {}
if not self:last_group_node().open or self.explorer.opts.git.show_on_open_dirs then
-- dir is closed or we should show on open_dirs
if self.git_status.file ~= nil then
table.insert(xys, self.git_status.file)
end
if self.git_status.dir ~= nil then
if self.git_status.dir.direct ~= nil then
for _, s in pairs(self.git_status.dir.direct) do
table.insert(xys, s)
end
end
if self.git_status.dir.indirect ~= nil then
for _, s in pairs(self.git_status.dir.indirect) do
table.insert(xys, s)
end
end
end
else
-- dir is open and we shouldn't show on open_dirs
if self.git_status.file ~= nil then
table.insert(xys, self.git_status.file)
end
if self.git_status.dir ~= nil and self.git_status.dir.direct ~= nil then
local deleted = {
[" D"] = true,
["D "] = true,
["RD"] = true,
["DD"] = true,
}
for _, s in pairs(self.git_status.dir.direct) do
if deleted[s] then
table.insert(xys, s)
end
end
end
end
if #xys == 0 then
return nil
else
return xys
end
end
-- If node is grouped, return the last node in the group. Otherwise, return the given node.
---@return DirectoryNode
function DirectoryNode:last_group_node()
return self.group_next and self.group_next:last_group_node() or self
end
---Return the one and only one child directory
---@return DirectoryNode?
function DirectoryNode:single_child_directory()
if #self.nodes == 1 then
return self.nodes[1]:as(DirectoryNode)
end
end
---@private
-- Toggle group empty folders
function DirectoryNode:toggle_group_folders()
local is_grouped = self.group_next ~= nil
if is_grouped then
self:ungroup_empty_folders()
else
self:group_empty_folders()
end
end
---Group empty folders
-- Recursively group nodes
---@private
---@return Node[]
function DirectoryNode:group_empty_folders()
local single_child = self:single_child_directory()
if self.explorer.opts.renderer.group_empty and self.parent and single_child then
self.group_next = single_child
local ns = single_child:group_empty_folders()
self.nodes = ns or {}
return ns
end
return self.nodes
end
---Ungroup empty folders
-- If a node is grouped, ungroup it: put node.group_next to the node.nodes and set node.group_next to nil
---@private
function DirectoryNode:ungroup_empty_folders()
if self.group_next then
self.group_next:ungroup_empty_folders()
self.nodes = { self.group_next }
self.group_next = nil
end
end
---@param toggle_group boolean?
function DirectoryNode:expand_or_collapse(toggle_group)
toggle_group = toggle_group or false
if self.has_children then
self.has_children = false
end
if #self.nodes == 0 then
self.explorer:expand(self)
end
local head_node = self:get_parent_of_group() or self
if toggle_group then
head_node:toggle_group_folders()
end
local open = self:last_group_node().open
local next_open
if toggle_group then
next_open = open
else
next_open = not open
end
local node = head_node
while node do
node.open = next_open
node = node.group_next
end
self.explorer.renderer:draw()
end
---@return HighlightedString icon
function DirectoryNode:highlighted_icon()
if not self.explorer.opts.renderer.icons.show.folder then
return self:highlighted_icon_empty()
end
local str, hl
-- devicon if enabled and available
if self.explorer.opts.renderer.icons.web_devicons.folder.enable then
str, hl = icons.get_icon(self.name)
if not self.explorer.opts.renderer.icons.web_devicons.folder.color then
hl = nil
end
end
-- default icon from opts
if not str then
if #self.nodes ~= 0 or self.has_children then
if self.open then
str = self.explorer.opts.renderer.icons.glyphs.folder.open
else
str = self.explorer.opts.renderer.icons.glyphs.folder.default
end
else
if self.open then
str = self.explorer.opts.renderer.icons.glyphs.folder.empty_open
else
str = self.explorer.opts.renderer.icons.glyphs.folder.empty
end
end
end
-- default hl
if not hl then
if self.open then
hl = "NvimTreeOpenedFolderIcon"
else
hl = "NvimTreeClosedFolderIcon"
end
end
return { str = str, hl = { hl } }
end
---@return HighlightedString icon
function DirectoryNode:highlighted_name()
local str, hl
local name = self.name
local next = self.group_next
while next do
name = string.format("%s/%s", name, next.name)
next = next.group_next
end
if self.group_next and type(self.explorer.opts.renderer.group_empty) == "function" then
local new_name = self.explorer.opts.renderer.group_empty(name)
if type(new_name) == "string" then
name = new_name
else
notify.warn(string.format("Invalid return type for field renderer.group_empty. Expected string, got %s", type(new_name)))
end
end
str = string.format("%s%s", name, self.explorer.opts.renderer.add_trailing and "/" or "")
hl = "NvimTreeFolderName"
if vim.tbl_contains(self.explorer.opts.renderer.special_files, self.absolute_path) or vim.tbl_contains(self.explorer.opts.renderer.special_files, self.name) then
hl = "NvimTreeSpecialFolderName"
elseif self.open then
hl = "NvimTreeOpenedFolderName"
elseif #self.nodes == 0 and not self.has_children then
hl = "NvimTreeEmptyFolderName"
end
return { str = str, hl = { hl } }
end
---Create a sanitized partial copy of a node, populating children recursively.
---@param api_nodes table<number, nvim_tree.api.Node>? optional map of uids to api node to populate
---@return nvim_tree.api.DirectoryNode cloned
function DirectoryNode:clone(api_nodes)
local clone = Node.clone(self, api_nodes) --[[@as nvim_tree.api.DirectoryNode]]
clone.has_children = self.has_children
clone.nodes = {}
clone.open = self.open
local clone_child
for _, child in ipairs(self.nodes) do
clone_child = child:clone(api_nodes)
clone_child.parent = clone
table.insert(clone.nodes, clone_child)
end
return clone
end
return DirectoryNode

View File

@@ -0,0 +1,48 @@
local DirectoryLinkNode = require("nvim-tree.node.directory-link")
local DirectoryNode = require("nvim-tree.node.directory")
local FileLinkNode = require("nvim-tree.node.file-link")
local FileNode = require("nvim-tree.node.file")
local Watcher = require("nvim-tree.watcher")
local M = {}
---Factory function to create the appropriate Node
---nil on invalid stat or invalid link target stat
---@param args NodeArgs
---@return Node?
function M.create(args)
if not args.fs_stat then
return nil
end
if args.fs_stat.type == "directory" then
-- directory must be readable and enumerable
if vim.loop.fs_access(args.absolute_path, "R") and Watcher.is_fs_event_capable(args.absolute_path) then
return DirectoryNode(args)
end
elseif args.fs_stat.type == "file" then
return FileNode(args)
elseif args.fs_stat.type == "link" then
-- link target path and stat must resolve
local link_to = vim.loop.fs_realpath(args.absolute_path)
local link_to_stat = link_to and vim.loop.fs_stat(link_to)
if not link_to or not link_to_stat then
return
end
---@cast args LinkNodeArgs
args.link_to = link_to
args.fs_stat_target = link_to_stat
-- choose directory or file
if link_to_stat.type == "directory" then
return DirectoryLinkNode(args)
else
return FileLinkNode(args)
end
end
return nil
end
return M

View File

@@ -0,0 +1,72 @@
local git_utils = require("nvim-tree.git.utils")
local utils = require("nvim-tree.utils")
local FileNode = require("nvim-tree.node.file")
local LinkNode = require("nvim-tree.node.link")
---@class (exact) FileLinkNode: FileNode, LinkNode
local FileLinkNode = FileNode:extend()
FileLinkNode:implement(LinkNode)
---@class FileLinkNode
---@overload fun(args: LinkNodeArgs): FileLinkNode
---@protected
---@param args LinkNodeArgs
function FileLinkNode:new(args)
LinkNode.new(self, args)
FileLinkNode.super.new(self, args)
self.type = "link"
end
function FileLinkNode:destroy()
FileNode.destroy(self)
end
---Update the git_status of the target otherwise the link itself
---@param parent_ignored boolean
---@param project GitProject?
function FileLinkNode:update_git_status(parent_ignored, project)
self.git_status = git_utils.git_status_file(parent_ignored, project, self.link_to, self.absolute_path)
end
---@return HighlightedString icon
function FileLinkNode:highlighted_icon()
if not self.explorer.opts.renderer.icons.show.file then
return self:highlighted_icon_empty()
end
local str, hl
-- default icon from opts
str = self.explorer.opts.renderer.icons.glyphs.symlink
hl = "NvimTreeSymlinkIcon"
return { str = str, hl = { hl } }
end
---@return HighlightedString name
function FileLinkNode:highlighted_name()
local str = self.name
if self.explorer.opts.renderer.symlink_destination then
local link_to = utils.path_relative(self.link_to, self.explorer.absolute_path)
str = string.format("%s%s%s", str, self.explorer.opts.renderer.icons.symlink_arrow, link_to)
end
return { str = str, hl = { "NvimTreeSymlink" } }
end
---Create a sanitized partial copy of a node
---@param api_nodes table<number, nvim_tree.api.Node>? optional map of uids to api node to populate
---@return nvim_tree.api.FileLinkNode cloned
function FileLinkNode:clone(api_nodes)
local clone = FileNode.clone(self, api_nodes) --[[@as nvim_tree.api.FileLinkNode]]
clone.link_to = self.link_to
clone.fs_stat_target = self.fs_stat_target
return clone
end
return FileLinkNode

107
lua/nvim-tree/node/file.lua Normal file
View File

@@ -0,0 +1,107 @@
local git_utils = require("nvim-tree.git.utils")
local icons = require("nvim-tree.renderer.components.devicons")
local utils = require("nvim-tree.utils")
local Node = require("nvim-tree.node")
local PICTURE_MAP = {
jpg = true,
jpeg = true,
png = true,
gif = true,
webp = true,
jxl = true,
}
---@class (exact) FileNode: Node
---@field extension string
local FileNode = Node:extend()
---@class FileNode
---@overload fun(args: NodeArgs): FileNode
---@protected
---@param args NodeArgs
function FileNode:new(args)
FileNode.super.new(self, args)
self.type = "file"
self.extension = string.match(args.name, ".?[^.]+%.(.*)") or ""
self.executable = utils.is_executable(args.absolute_path)
end
function FileNode:destroy()
Node.destroy(self)
end
---Update the GitStatus of the file
---@param parent_ignored boolean
---@param project GitProject?
function FileNode:update_git_status(parent_ignored, project)
self.git_status = git_utils.git_status_file(parent_ignored, project, self.absolute_path, nil)
end
---@return GitXY[]?
function FileNode:get_git_xy()
if not self.git_status then
return nil
end
return self.git_status.file and { self.git_status.file }
end
---@return HighlightedString icon
function FileNode:highlighted_icon()
if not self.explorer.opts.renderer.icons.show.file then
return self:highlighted_icon_empty()
end
local str, hl
-- devicon if enabled and available, fallback to default
if self.explorer.opts.renderer.icons.web_devicons.file.enable then
str, hl = icons.get_icon(self.name, nil, { default = true })
if not self.explorer.opts.renderer.icons.web_devicons.file.color then
hl = nil
end
end
-- default icon from opts
if not str then
str = self.explorer.opts.renderer.icons.glyphs.default
end
-- default hl
if not hl then
hl = "NvimTreeFileIcon"
end
return { str = str, hl = { hl } }
end
---@return HighlightedString name
function FileNode:highlighted_name()
local hl
if vim.tbl_contains(self.explorer.opts.renderer.special_files, self.absolute_path) or vim.tbl_contains(self.explorer.opts.renderer.special_files, self.name) then
hl = "NvimTreeSpecialFile"
elseif self.executable then
hl = "NvimTreeExecFile"
elseif PICTURE_MAP[self.extension] then
hl = "NvimTreeImageFile"
end
return { str = self.name, hl = { hl } }
end
---Create a sanitized partial copy of a node
---@param api_nodes table<number, nvim_tree.api.Node>? optional map of uids to api node to populate
---@return nvim_tree.api.FileNode cloned
function FileNode:clone(api_nodes)
local clone = Node.clone(self, api_nodes) --[[@as nvim_tree.api.FileNode]]
clone.extension = self.extension
return clone
end
return FileNode

147
lua/nvim-tree/node/init.lua Normal file
View File

@@ -0,0 +1,147 @@
local Class = require("nvim-tree.classic")
---Abstract Node class.
---@class (exact) Node: Class
---@field uid_node number vim.loop.hrtime() at construction time
---@field type "file" | "directory" | "link" uv.fs_stat.result.type
---@field explorer Explorer
---@field absolute_path string
---@field executable boolean
---@field fs_stat uv.fs_stat.result?
---@field git_status GitNodeStatus?
---@field hidden boolean
---@field name string
---@field parent DirectoryNode?
---@field diag_status DiagStatus?
---@field private is_dot boolean cached is_dotfile
local Node = Class:extend()
---@class (exact) NodeArgs
---@field explorer Explorer
---@field parent DirectoryNode?
---@field absolute_path string
---@field name string
---@field fs_stat uv.fs_stat.result?
---@protected
---@param args NodeArgs
function Node:new(args)
self.uid_node = vim.loop.hrtime()
self.explorer = args.explorer
self.absolute_path = args.absolute_path
self.executable = false
self.fs_stat = args.fs_stat
self.git_status = nil
self.hidden = false
self.name = args.name
self.parent = args.parent
self.diag_status = nil
self.is_dot = false
end
function Node:destroy()
end
---Update the git_status of the node
---Abstract
---@param parent_ignored boolean
---@param project GitProject?
function Node:update_git_status(parent_ignored, project)
self:nop(parent_ignored, project)
end
---Short-format statuses
---@return GitXY[]?
function Node:get_git_xy()
end
---@return boolean
function Node:is_git_ignored()
return self.git_status ~= nil and self.git_status.file == "!!"
end
---Node or one of its parents begins with a dot
---@return boolean
function Node:is_dotfile()
if
self.is_dot
or (self.name and (self.name:sub(1, 1) == "."))
or (self.parent and self.parent:is_dotfile())
then
self.is_dot = true
return true
end
return false
end
---Get the highest parent of grouped nodes, nil when not grouped
---@return DirectoryNode?
function Node:get_parent_of_group()
if not self.parent or not self.parent.group_next then
return nil
end
local node = self.parent
while node do
if node.parent and node.parent.group_next then
node = node.parent
else
return node
end
end
end
---Empty highlighted icon
---@protected
---@return HighlightedString icon
function Node:highlighted_icon_empty()
return { str = "", hl = {} }
end
---Highlighted icon for the node
---Empty for base Node
---@return HighlightedString icon
function Node:highlighted_icon()
return self:highlighted_icon_empty()
end
---Empty highlighted name
---@protected
---@return HighlightedString name
function Node:highlighted_name_empty()
return { str = "", hl = {} }
end
---Highlighted name for the node
---Empty for base Node
---@return HighlightedString name
function Node:highlighted_name()
return self:highlighted_name_empty()
end
---Create a sanitized partial copy of a node, populating children recursively.
---@param api_nodes table<number, nvim_tree.api.Node>? optional map of uids to api node to populate
---@return nvim_tree.api.Node cloned
function Node:clone(api_nodes)
---@type nvim_tree.api.Node
local clone = {
uid_node = self.uid_node,
type = self.type,
absolute_path = self.absolute_path,
executable = self.executable,
fs_stat = self.fs_stat,
git_status = self.git_status,
hidden = self.hidden,
name = self.name,
parent = nil,
diag_severity = self.diag_status and self.diag_status.value or nil,
}
if api_nodes then
api_nodes[self.uid_node] = clone
end
return clone
end
return Node

Some files were not shown because too many files have changed in this diff Show More