Compare commits

...

116 Commits

Author SHA1 Message Date
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
109 changed files with 5884 additions and 2995 deletions

View File

@@ -4,9 +4,17 @@ root = true
insert_final_newline = true insert_final_newline = true
end_of_line = lf end_of_line = lf
[*.lua]
indent_style = space
indent_size = 2
[nvim-tree-lua.txt] [nvim-tree-lua.txt]
max_line_length = 78 max_line_length = 78
[*.lua]
indent_style = space
max_line_length = 140
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

View File

@@ -59,9 +59,9 @@ body:
If not provided it is very unlikely that the nvim-tree team will be able to address your issue. If not provided it is very unlikely that the nvim-tree team will be able to address your issue.
See [wiki: Clean Room Replication](https://github.com/nvim-tree/nvim-tree.lua/wiki/Troubleshooting#clean-room-replication) for instructions. 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.
Please 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

@@ -2,11 +2,9 @@ name: CI
on: on:
pull_request: pull_request:
branches:
- '*'
push: push:
branches: branches: [master]
- master workflow_dispatch:
permissions: permissions:
contents: read contents: read
@@ -14,31 +12,83 @@ permissions:
jobs: jobs:
lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ matrix.lua_version }}-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true
strategy:
matrix:
lua_version: [ 5.1 ]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: leafo/gh-actions-lua@v10 - uses: leafo/gh-actions-lua@v10
with: with:
luaVersion: "5.1" luaVersion: ${{ matrix.lua_version }}
- uses: leafo/gh-actions-luarocks@v4 - uses: leafo/gh-actions-luarocks@v4
- name: luacheck - run: luarocks install luacheck 1.1.1
run: |
luarocks install luacheck 1.1.1 - run: make lint
luacheck lua
style: style:
runs-on: ubuntu-latest runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ matrix.emmy_lua_code_style_version }}-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true
strategy:
matrix:
emmy_lua_code_style_version: [ 1.5.6 ]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: stylua - name: install emmy_lua_code_style
uses: JohnnyMorganz/stylua-action@v3 run: |
with: mkdir -p CodeFormat
token: ${{ secrets.GITHUB_TOKEN }} curl -L "https://github.com/CppCXY/EmmyLuaCodeStyle/releases/download/${{ matrix.emmy_lua_code_style_version }}/linux-x64.tar.gz" | tar zx --directory CodeFormat
version: "0.19"
args: --check lua
- name: doc-comments - run: echo "CodeFormat/linux-x64/bin" >> "$GITHUB_PATH"
run: ./scripts/doc-comments.sh
- run: make style
- run: make style-doc
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.10.5 ]
steps:
- uses: actions/checkout@v4
- uses: rhysd/action-setup-vim@v1
with:
neovim: true
version: ${{ matrix.nvim_version }}
- name: install luals
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
- run: echo "luals/bin" >> "$GITHUB_PATH"
- name: make check
env:
VIMRUNTIME: /home/runner/nvim-${{ matrix.nvim_version }}/share/nvim/runtime
run: make check
- run: make help-check

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

@@ -0,0 +1,37 @@
name: Luarocks Release
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
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

View File

@@ -2,6 +2,10 @@ on:
push: push:
branches: branches:
- master - master
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true
name: release-please name: release-please
permissions: permissions:
contents: write contents: write
@@ -19,11 +23,15 @@ jobs:
git config user.name github-actions[bot] git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com 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 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 }} || 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 }} || true
git push origin :v${{ steps.release.outputs.major }} || 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 }}.${{ steps.release.outputs.minor }} || true git push origin :v${{ steps.release.outputs.major }} || true
git tag -a v${{ steps.release.outputs.major }} -m "Release v${{ steps.release.outputs.major }}" git push origin :v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} || true
git tag -a v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} -m "Release 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 }} || true
git push origin v${{ steps.release.outputs.major }} git tag -a v${{ steps.release.outputs.major }} -m "Release v${{ steps.release.outputs.major }}"
git push origin v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} 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

@@ -6,10 +6,14 @@ on:
- reopened - reopened
- edited - edited
- synchronize - synchronize
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref }}
cancel-in-progress: true
jobs: jobs:
semantic-pr-subject: semantic-pr-subject:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: amannn/action-semantic-pull-request@v5.4.0 - uses: amannn/action-semantic-pull-request@v5.5.3
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 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 #!/bin/sh
stylua . --check || exit 1 make
luacheck . || exit 1

View File

@@ -1,10 +1,15 @@
{ {
"$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", "runtime.version": "Lua 5.1",
"workspace": {
"library": [
"$VIMRUNTIME/lua/vim",
"${3rd}/luv/library"
]
},
"diagnostics": { "diagnostics": {
"globals": [ "libraryFiles": "Disable",
"vim" "globals": [],
],
"neededFileStatus": { "neededFileStatus": {
"ambiguity-1": "Any", "ambiguity-1": "Any",
"assign-type-mismatch": "Any", "assign-type-mismatch": "Any",
@@ -26,11 +31,19 @@
"duplicate-index": "Any", "duplicate-index": "Any",
"duplicate-set-field": "Any", "duplicate-set-field": "Any",
"empty-block": "Any", "empty-block": "Any",
"global-element": "Any",
"global-in-nil-env": "Any", "global-in-nil-env": "Any",
"incomplete-signature-doc": "None",
"inject-field": "Any",
"invisible": "Any",
"lowercase-global": "Any", "lowercase-global": "Any",
"missing-fields": "Any",
"missing-global-doc": "Any",
"missing-local-export-doc": "None",
"missing-parameter": "Any", "missing-parameter": "Any",
"missing-return": "Any", "missing-return": "Any",
"missing-return-value": "Any", "missing-return-value": "Any",
"name-style-check": "None",
"need-check-nil": "Any", "need-check-nil": "Any",
"newfield-call": "Any", "newfield-call": "Any",
"newline-call": "Any", "newline-call": "Any",
@@ -63,4 +76,3 @@
} }
} }
} }

View File

@@ -1,3 +1,3 @@
{ {
".": "0.99.0" ".": "1.7.1"
} }

View File

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

View File

@@ -1,5 +1,198 @@
# Changelog # Changelog
## [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) ## 0.99.0 (2024-01-01)

View File

@@ -4,35 +4,119 @@ Thank you for contributing.
See [Development](https://github.com/nvim-tree/nvim-tree.lua/wiki/Development) for environment setup, tips and tools. See [Development](https://github.com/nvim-tree/nvim-tree.lua/wiki/Development) for environment setup, tips and tools.
## Styling and formatting # Tools
Code is formatted using luacheck, and linted using stylua. Following are used during CI and strongly recommended during local development.
You can install these with:
```bash Language server: [luals](https://luals.github.io)
luarocks install luacheck
cargo install stylua Lint: [luacheck](https://github.com/lunarmodules/luacheck/)
Style: [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
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 CodeCheck using `.editorconfig` settings
1. Runs `scripts/doc-comments.sh` to validate annotated documentation
```sh
make style
```
You can automatically fix `CodeCheck` issues via:
```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
```
# 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 to `nvim-tree-opts` in `doc/nvim-tree-lua.txt` 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
# 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.
A test case to reproduce the issue is required. A ["Clean Room" Replication](https://github.com/nvim-tree/nvim-tree.lua/wiki/Troubleshooting#clean-room-replication) is preferred. ## 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 -q lua
# --diagnosis-as-error does not function for workspace, hence we post-process the output
style-check:
CodeFormat check --config .editorconfig --diagnosis-as-error --workspace lua
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

View File

@@ -24,11 +24,11 @@
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.
Community support: [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.8.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"
@@ -38,6 +38,8 @@ Please install via your preferred package manager. See [Installation](https://gi
`nvim-tree/nvim-tree.lua` `nvim-tree/nvim-tree.lua`
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 `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) Disabling [netrw](https://neovim.io/doc/user/pi_netrw.html) is strongly advised, see [:help nvim-tree-netrw](doc/nvim-tree-lua.txt)
@@ -53,7 +55,7 @@ Setup the plugin in your `init.lua`
vim.g.loaded_netrw = 1 vim.g.loaded_netrw = 1
vim.g.loaded_netrwPlugin = 1 vim.g.loaded_netrwPlugin = 1
-- set termguicolors to enable highlight groups -- optionally enable 24-bit colour
vim.opt.termguicolors = true vim.opt.termguicolors = true
-- empty setup using defaults -- empty setup using defaults
@@ -110,6 +112,23 @@ require("nvim-tree").setup {
} }
``` ```
### 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
See [:help nvim-tree-commands](doc/nvim-tree-lua.txt) See [:help nvim-tree-commands](doc/nvim-tree-lua.txt)

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,11 @@
local lib = require "nvim-tree.lib" local lib = require("nvim-tree.lib")
local log = require "nvim-tree.log" local log = require("nvim-tree.log")
local colors = require "nvim-tree.colors" local appearance = require("nvim-tree.appearance")
local renderer = require "nvim-tree.renderer" local view = require("nvim-tree.view")
local view = require "nvim-tree.view" local utils = require("nvim-tree.utils")
local commands = require "nvim-tree.commands" local actions = require("nvim-tree.actions")
local utils = require "nvim-tree.utils" local core = require("nvim-tree.core")
local actions = require "nvim-tree.actions" local notify = require("nvim-tree.notify")
local legacy = require "nvim-tree.legacy"
local core = require "nvim-tree.core"
local git = require "nvim-tree.git"
local filters = require "nvim-tree.explorer.filters"
local modified = require "nvim-tree.modified"
local events = require "nvim-tree.events"
local notify = require "nvim-tree.notify"
local _config = {} local _config = {}
@@ -26,8 +19,15 @@ local M = {
function M.change_root(path, bufnr) function M.change_root(path, bufnr)
-- skip if current file is in ignore_list -- skip if current file is in ignore_list
if type(bufnr) == "number" then if type(bufnr) == "number" then
local ft = vim.api.nvim_buf_get_option(bufnr, "filetype") or "" local ft
for _, value in pairs(_config.update_focused_file.ignore_list) do
if vim.fn.has("nvim-0.10") == 1 then
ft = vim.api.nvim_get_option_value("filetype", { buf = bufnr }) or ""
else
ft = vim.api.nvim_buf_get_option(bufnr, "filetype") or "" ---@diagnostic disable-line: deprecated
end
for _, value in pairs(_config.update_focused_file.update_root.ignore_list) do
if utils.str_find(path, value) or utils.str_find(ft, value) then if utils.str_find(path, value) or utils.str_find(ft, value) then
return return
end end
@@ -76,16 +76,27 @@ function M.change_root(path, bufnr)
end end
function M.tab_enter() function M.tab_enter()
if view.is_visible { any_tabpage = true } then if view.is_visible({ any_tabpage = true }) then
local bufname = vim.api.nvim_buf_get_name(0) local bufname = vim.api.nvim_buf_get_name(0)
local ft = vim.api.nvim_buf_get_option(0, "ft")
local ft
if vim.fn.has("nvim-0.10") == 1 then
ft = vim.api.nvim_get_option_value("filetype", { buf = 0 }) or ""
else
ft = vim.api.nvim_buf_get_option(0, "ft") ---@diagnostic disable-line: deprecated
end
for _, filter in ipairs(M.config.tab.sync.ignore) do for _, filter in ipairs(M.config.tab.sync.ignore) do
if bufname:match(filter) ~= nil or ft:match(filter) ~= nil then if bufname:match(filter) ~= nil or ft:match(filter) ~= nil then
return return
end end
end end
view.open { focus_tree = false } view.open({ focus_tree = false })
renderer.draw()
local explorer = core.get_explorer()
if explorer then
explorer.renderer:draw()
end
end end
end end
@@ -104,15 +115,9 @@ function M.open_on_directory()
actions.root.change_dir.force_dirchange(bufname, true) actions.root.change_dir.force_dirchange(bufname, true)
end end
function M.reset_highlight()
colors.setup()
view.reset_winhl()
renderer.render_hl(view.get_bufnr())
end
function M.place_cursor_on_node() function M.place_cursor_on_node()
local search = vim.fn.searchcount() local ok, search = pcall(vim.fn.searchcount)
if search and search.exact_match == 1 then if ok and search and search.exact_match == 1 then
return return
end end
@@ -140,8 +145,8 @@ end
---@param hijack_netrw boolean ---@param hijack_netrw boolean
local function manage_netrw(disable_netrw, hijack_netrw) local function manage_netrw(disable_netrw, hijack_netrw)
if hijack_netrw then if hijack_netrw then
vim.cmd "silent! autocmd! FileExplorer *" vim.cmd("silent! autocmd! FileExplorer *")
vim.cmd "autocmd VimEnter * ++once silent! autocmd! FileExplorer *" vim.cmd("autocmd VimEnter * ++once silent! autocmd! FileExplorer *")
end end
if disable_netrw then if disable_netrw then
vim.g.loaded_netrw = 1 vim.g.loaded_netrw = 1
@@ -155,7 +160,7 @@ function M.change_dir(name)
actions.root.change_dir.fn(name) actions.root.change_dir.fn(name)
end end
if _config.update_focused_file.enable then if _config.update_focused_file.update_root.enable then
actions.tree.find_file.fn() actions.tree.find_file.fn()
end end
end end
@@ -168,8 +173,18 @@ local function setup_autocommands(opts)
vim.api.nvim_create_autocmd(name, vim.tbl_extend("force", default_opts, custom_opts)) vim.api.nvim_create_autocmd(name, vim.tbl_extend("force", default_opts, custom_opts))
end end
-- reset highlights when colorscheme is changed -- reset and draw (highlights) when colorscheme is changed
create_nvim_tree_autocmd("ColorScheme", { callback = M.reset_highlight }) create_nvim_tree_autocmd("ColorScheme", {
callback = function()
appearance.setup()
view.reset_winhl()
local explorer = core.get_explorer()
if explorer then
explorer.renderer:draw()
end
end,
})
-- prevent new opened file from opening in the same window as nvim-tree -- prevent new opened file from opening in the same window as nvim-tree
create_nvim_tree_autocmd("BufWipeout", { create_nvim_tree_autocmd("BufWipeout", {
@@ -189,7 +204,10 @@ local function setup_autocommands(opts)
create_nvim_tree_autocmd("BufWritePost", { create_nvim_tree_autocmd("BufWritePost", {
callback = function() callback = function()
if opts.auto_reload_on_write and not opts.filesystem_watchers.enable then if opts.auto_reload_on_write and not opts.filesystem_watchers.enable then
actions.reloaders.reload_explorer() local explorer = core.get_explorer()
if explorer then
explorer:reload_explorer()
end
end end
end, end,
}) })
@@ -197,9 +215,15 @@ local function setup_autocommands(opts)
create_nvim_tree_autocmd("BufReadPost", { create_nvim_tree_autocmd("BufReadPost", {
callback = function(data) callback = function(data)
-- update opened file buffers -- update opened file buffers
if (filters.config.filter_no_buffer or renderer.config.highlight_opened_files ~= "none") and vim.bo[data.buf].buftype == "" then local explorer = core.get_explorer()
if not explorer then
return
end
if
(explorer.filters.config.filter_no_buffer or explorer.opts.highlight_opened_files ~= "none") and vim.bo[data.buf].buftype == ""
then
utils.debounce("Buf:filter_buffer", opts.view.debounce_delay, function() utils.debounce("Buf:filter_buffer", opts.view.debounce_delay, function()
actions.reloaders.reload_explorer() explorer:reload_explorer()
end) end)
end end
end, end,
@@ -208,9 +232,15 @@ local function setup_autocommands(opts)
create_nvim_tree_autocmd("BufUnload", { create_nvim_tree_autocmd("BufUnload", {
callback = function(data) callback = function(data)
-- update opened file buffers -- update opened file buffers
if (filters.config.filter_no_buffer or renderer.config.highlight_opened_files ~= "none") and vim.bo[data.buf].buftype == "" then local explorer = core.get_explorer()
if not explorer then
return
end
if
(explorer.filters.config.filter_no_buffer or explorer.opts.highlight_opened_files ~= "none") and vim.bo[data.buf].buftype == ""
then
utils.debounce("Buf:filter_buffer", opts.view.debounce_delay, function() utils.debounce("Buf:filter_buffer", opts.view.debounce_delay, function()
actions.reloaders.reload_explorer(nil, data.buf) explorer:reload_explorer()
end) end)
end end
end, end,
@@ -220,7 +250,10 @@ local function setup_autocommands(opts)
pattern = { "FugitiveChanged", "NeogitStatusRefreshed" }, pattern = { "FugitiveChanged", "NeogitStatusRefreshed" },
callback = function() callback = function()
if not opts.filesystem_watchers.enable and opts.git.enable then if not opts.filesystem_watchers.enable and opts.git.enable then
actions.reloaders.reload_git() local explorer = core.get_explorer()
if explorer then
explorer:reload_git()
end
end end
end, end,
}) })
@@ -247,7 +280,11 @@ local function setup_autocommands(opts)
end end
if opts.update_focused_file.enable then if opts.update_focused_file.enable then
create_nvim_tree_autocmd("BufEnter", { create_nvim_tree_autocmd("BufEnter", {
callback = function() callback = function(event)
local exclude = opts.update_focused_file.exclude
if type(exclude) == "function" and exclude(event) then
return
end
utils.debounce("BufEnter:find_file", opts.view.debounce_delay, function() utils.debounce("BufEnter:find_file", opts.view.debounce_delay, function()
actions.tree.find_file.fn() actions.tree.find_file.fn()
end) end)
@@ -264,7 +301,10 @@ local function setup_autocommands(opts)
callback = function() callback = function()
if utils.is_nvim_tree_buf(0) then if utils.is_nvim_tree_buf(0) then
if vim.fn.getcwd() ~= core.get_cwd() or (opts.reload_on_bufenter and not opts.filesystem_watchers.enable) then if vim.fn.getcwd() ~= core.get_cwd() or (opts.reload_on_bufenter and not opts.filesystem_watchers.enable) then
actions.reloaders.reload_explorer() local explorer = core.get_explorer()
if explorer then
explorer:reload_explorer()
end
end end
end end
end, end,
@@ -276,7 +316,7 @@ local function setup_autocommands(opts)
callback = function() callback = function()
vim.schedule(function() vim.schedule(function()
vim.api.nvim_buf_call(0, function() vim.api.nvim_buf_call(0, function()
vim.cmd [[norm! zz]] vim.cmd([[norm! zz]])
end) end)
end) end)
end, end,
@@ -314,24 +354,12 @@ local function setup_autocommands(opts)
create_nvim_tree_autocmd({ "BufModifiedSet", "BufWritePost" }, { create_nvim_tree_autocmd({ "BufModifiedSet", "BufWritePost" }, {
callback = function() callback = function()
utils.debounce("Buf:modified", opts.view.debounce_delay, function() utils.debounce("Buf:modified", opts.view.debounce_delay, function()
modified.reload() require("nvim-tree.buffers").reload_modified()
actions.reloaders.reload_explorer() local explorer = core.get_explorer()
end) if explorer then
end, explorer:reload_explorer()
})
end
-- TODO #1545 remove similar check from view.resize
if vim.fn.has "nvim-0.9" == 1 then
create_nvim_tree_autocmd("WinResized", {
callback = function()
if vim.v.event and vim.v.event.windows then
for _, winid in ipairs(vim.v.event.windows) do
if vim.api.nvim_win_is_valid(winid) and utils.is_nvim_tree_buf(vim.api.nvim_win_get_buf(winid)) then
events._dispatch_on_tree_resize(vim.api.nvim_win_get_width(winid))
end
end end
end end)
end, end,
}) })
end end
@@ -385,11 +413,13 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
root_folder_label = ":~:s?$?/..?", root_folder_label = ":~:s?$?/..?",
indent_width = 2, indent_width = 2,
special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" }, special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" },
hidden_display = "none",
symlink_destination = true, symlink_destination = true,
highlight_git = false, highlight_git = "none",
highlight_diagnostics = false, highlight_diagnostics = "none",
highlight_opened_files = "none", highlight_opened_files = "none",
highlight_modified = "none", highlight_modified = "none",
highlight_hidden = "none",
highlight_bookmarks = "none", highlight_bookmarks = "none",
highlight_clipboard = "name", highlight_clipboard = "name",
indent_markers = { indent_markers = {
@@ -416,6 +446,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
}, },
git_placement = "before", git_placement = "before",
modified_placement = "after", modified_placement = "after",
hidden_placement = "after",
diagnostics_placement = "signcolumn", diagnostics_placement = "signcolumn",
bookmarks_placement = "signcolumn", bookmarks_placement = "signcolumn",
padding = " ", padding = " ",
@@ -426,6 +457,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
folder_arrow = true, folder_arrow = true,
git = true, git = true,
modified = true, modified = true,
hidden = false,
diagnostics = true, diagnostics = true,
bookmarks = true, bookmarks = true,
}, },
@@ -434,6 +466,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
symlink = "", symlink = "",
bookmark = "󰆤", bookmark = "󰆤",
modified = "", modified = "",
hidden = "󰜌",
folder = { folder = {
arrow_closed = "", arrow_closed = "",
arrow_open = "", arrow_open = "",
@@ -462,8 +495,11 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
}, },
update_focused_file = { update_focused_file = {
enable = false, enable = false,
update_root = false, update_root = {
ignore_list = {}, enable = false,
ignore_list = {},
},
exclude = false,
}, },
system_open = { system_open = {
cmd = "", cmd = "",
@@ -499,6 +535,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
show_on_open_dirs = true, show_on_open_dirs = true,
}, },
filters = { filters = {
enable = true,
git_ignored = true, git_ignored = true,
dotfiles = false, dotfiles = false,
git_clean = false, git_clean = false,
@@ -578,7 +615,13 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
default_yes = false, default_yes = false,
}, },
}, },
experimental = {}, experimental = {
actions = {
open_file = {
relative_path = false,
},
},
},
log = { log = {
enable = false, enable = false,
truncate = false, truncate = false,
@@ -620,9 +663,22 @@ local ACCEPTED_TYPES = {
}, },
}, },
renderer = { renderer = {
hidden_display = { "function", "string" },
group_empty = { "boolean", "function" }, group_empty = { "boolean", "function" },
root_folder_label = { "function", "string", "boolean" }, root_folder_label = { "function", "string", "boolean" },
}, },
update_focused_file = {
exclude = { "function" },
},
git = {
disable_for_dirs = { "function" },
},
filters = {
custom = { "function" },
},
filesystem_watchers = {
ignore_dirs = { "function" },
},
actions = { actions = {
open_file = { open_file = {
window_picker = { window_picker = {
@@ -641,15 +697,20 @@ local ACCEPTED_STRINGS = {
signcolumn = { "yes", "no", "auto" }, signcolumn = { "yes", "no", "auto" },
}, },
renderer = { renderer = {
hidden_display = { "none", "simple", "all" },
highlight_git = { "none", "icon", "name", "all" },
highlight_opened_files = { "none", "icon", "name", "all" }, highlight_opened_files = { "none", "icon", "name", "all" },
highlight_modified = { "none", "icon", "name", "all" }, highlight_modified = { "none", "icon", "name", "all" },
highlight_hidden = { "none", "icon", "name", "all" },
highlight_bookmarks = { "none", "icon", "name", "all" }, highlight_bookmarks = { "none", "icon", "name", "all" },
highlight_diagnostics = { "none", "icon", "name", "all" },
highlight_clipboard = { "none", "icon", "name", "all" }, highlight_clipboard = { "none", "icon", "name", "all" },
icons = { icons = {
git_placement = { "before", "after", "signcolumn" }, git_placement = { "before", "after", "signcolumn", "right_align" },
modified_placement = { "before", "after", "signcolumn" }, modified_placement = { "before", "after", "signcolumn", "right_align" },
diagnostics_placement = { "before", "after", "signcolumn" }, hidden_placement = { "before", "after", "signcolumn", "right_align" },
bookmarks_placement = { "before", "after", "signcolumn" }, diagnostics_placement = { "before", "after", "signcolumn", "right_align" },
bookmarks_placement = { "before", "after", "signcolumn", "right_align" },
}, },
}, },
help = { help = {
@@ -743,15 +804,15 @@ function M.purge_all_state()
view.close_all_tabs() view.close_all_tabs()
view.abandon_all_windows() view.abandon_all_windows()
if core.get_explorer() ~= nil then if core.get_explorer() ~= nil then
git.purge_state() require("nvim-tree.git").purge_state()
core.reset_explorer() core.reset_explorer()
end end
end end
---@param conf table|nil ---@param conf table|nil
function M.setup(conf) function M.setup(conf)
if vim.fn.has "nvim-0.8" == 0 then if vim.fn.has("nvim-0.9") == 0 then
notify.warn "nvim-tree.lua requires Neovim 0.8 or higher" notify.warn("nvim-tree.lua requires Neovim 0.9 or higher")
return return
end end
@@ -759,7 +820,7 @@ function M.setup(conf)
localise_default_opts() localise_default_opts()
legacy.migrate_legacy_options(conf or {}) require("nvim-tree.legacy").migrate_legacy_options(conf or {})
validate_options(conf) validate_options(conf)
@@ -779,24 +840,22 @@ function M.setup(conf)
require("nvim-tree.notify").setup(opts) require("nvim-tree.notify").setup(opts)
require("nvim-tree.log").setup(opts) require("nvim-tree.log").setup(opts)
if log.enabled "config" then if log.enabled("config") then
log.line("config", "default config + user") log.line("config", "default config + user")
log.raw("config", "%s\n", vim.inspect(opts)) log.raw("config", "%s\n", vim.inspect(opts))
end end
require("nvim-tree.actions").setup(opts) require("nvim-tree.actions").setup(opts)
require("nvim-tree.keymap").setup(opts) require("nvim-tree.keymap").setup(opts)
require("nvim-tree.colors").setup() require("nvim-tree.appearance").setup()
require("nvim-tree.diagnostics").setup(opts) require("nvim-tree.diagnostics").setup(opts)
require("nvim-tree.explorer").setup(opts) require("nvim-tree.explorer").setup(opts)
require("nvim-tree.git").setup(opts) require("nvim-tree.git").setup(opts)
require("nvim-tree.git.utils").setup(opts) require("nvim-tree.git.utils").setup(opts)
require("nvim-tree.view").setup(opts) require("nvim-tree.view").setup(opts)
require("nvim-tree.lib").setup(opts) require("nvim-tree.lib").setup(opts)
require("nvim-tree.renderer").setup(opts) require("nvim-tree.renderer.components").setup(opts)
require("nvim-tree.live-filter").setup(opts) require("nvim-tree.buffers").setup(opts)
require("nvim-tree.marks").setup(opts)
require("nvim-tree.modified").setup(opts)
require("nvim-tree.help").setup(opts) require("nvim-tree.help").setup(opts)
require("nvim-tree.watcher").setup(opts) require("nvim-tree.watcher").setup(opts)
if M.config.renderer.icons.show.file and pcall(require, "nvim-web-devicons") then if M.config.renderer.icons.show.file and pcall(require, "nvim-web-devicons") then
@@ -807,7 +866,7 @@ function M.setup(conf)
if vim.g.NvimTreeSetup ~= 1 then if vim.g.NvimTreeSetup ~= 1 then
-- first call to setup -- first call to setup
commands.setup() require("nvim-tree.commands").setup()
else else
-- subsequent calls to setup -- subsequent calls to setup
M.purge_all_state() M.purge_all_state()

View File

@@ -1,10 +1,8 @@
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 reload = require "nvim-tree.explorer.reload" local Iterator = require("nvim-tree.iterators.node-iterator")
local core = require "nvim-tree.core"
local Iterator = require "nvim-tree.iterators.node-iterator"
local M = {} local M = {}
@@ -13,7 +11,8 @@ 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 path string relative or absolute ---@param path string relative or absolute
function M.fn(path) function M.fn(path)
if not core.get_explorer() or not view.is_visible() then local explorer = core.get_explorer()
if not explorer or not view.is_visible() then
return return
end end
@@ -32,7 +31,7 @@ function M.fn(path)
-- refresh the contents of all parents, expanding groups as needed -- refresh the contents of all parents, expanding groups as needed
if utils.get_node_from_path(path_real) == nil then if utils.get_node_from_path(path_real) == nil then
reload.refresh_parent_nodes_for_path(vim.fn.fnamemodify(path_real, ":h")) explorer:refresh_parent_nodes_for_path(vim.fn.fnamemodify(path_real, ":h"))
end end
local line = core.get_nodes_starting_line() local line = core.get_nodes_starting_line()
@@ -76,8 +75,8 @@ function M.fn(path)
: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[path_real] = false running[path_real] = false

View File

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

View File

@@ -1,5 +1,4 @@
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 = {}
@@ -9,6 +8,11 @@ local M = {}
---@return string|nil ---@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
@@ -19,7 +23,7 @@ local function search(search_dir, input_path)
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
@@ -36,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
@@ -68,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
@@ -77,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

@@ -1,24 +1,44 @@
local lib = require "nvim-tree.lib" local lib = require("nvim-tree.lib")
local log = require "nvim-tree.log" local log = require("nvim-tree.log")
local utils = require "nvim-tree.utils" local utils = require("nvim-tree.utils")
local core = require "nvim-tree.core" local core = require("nvim-tree.core")
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 renderer = require "nvim-tree.renderer"
local reloaders = require "nvim-tree.actions.reloaders"
local HL_POSITION = require("nvim-tree.enum").HL_POSITION
local find_file = require("nvim-tree.actions.finders.find-file").fn local find_file = require("nvim-tree.actions.finders.find-file").fn
local M = { ---@enum ACTION
config = {}, local ACTION = {
copy = "copy",
cut = "cut",
} }
local clipboard = { ---@class Clipboard to handle all actions.fs clipboard API
cut = {}, ---@field config table hydrated user opts.filters
copy = {}, ---@field private explorer Explorer
} ---@field private data table<ACTION, Node[]>
local Clipboard = {}
---@param opts table user options
---@param explorer Explorer
---@return Clipboard
function Clipboard:new(opts, explorer)
local o = {
explorer = explorer,
data = {
[ACTION.copy] = {},
[ACTION.cut] = {},
},
config = {
filesystem_watchers = opts.filesystem_watchers,
actions = opts.actions,
},
}
setmetatable(o, self)
self.__index = self
return o
end
---@param source string ---@param source string
---@param destination string ---@param destination string
@@ -69,8 +89,8 @@ local function do_copy(source, destination)
break break
end end
local new_name = utils.path_join { source, name } local new_name = utils.path_join({ source, name })
local new_destination = utils.path_join { destination, name } local new_destination = utils.path_join({ destination, name })
success, errmsg = do_copy(new_name, new_destination) success, errmsg = do_copy(new_name, new_destination)
if not success then if not success then
return false, errmsg return false, errmsg
@@ -87,11 +107,11 @@ end
---@param source string ---@param source string
---@param dest string ---@param dest string
---@param action_type string ---@param action ACTION
---@param action_fn fun(source: string, dest: string) ---@param action_fn fun(source: string, dest: string)
---@return boolean|nil -- success ---@return boolean|nil -- success
---@return string|nil -- error message ---@return string|nil -- error message
local function do_single_paste(source, dest, action_type, action_fn) local function do_single_paste(source, dest, action, action_fn)
local dest_stats local dest_stats
local success, errmsg, errcode local success, errmsg, errcode
local notify_source = notify.render_path(source) local notify_source = notify.render_path(source)
@@ -100,14 +120,14 @@ local function do_single_paste(source, dest, action_type, action_fn)
dest_stats, errmsg, errcode = vim.loop.fs_stat(dest) dest_stats, errmsg, errcode = vim.loop.fs_stat(dest)
if not dest_stats and errcode ~= "ENOENT" then if not dest_stats and errcode ~= "ENOENT" then
notify.error("Could not " .. action_type .. " " .. notify_source .. " - " .. (errmsg or "???")) notify.error("Could not " .. action .. " " .. notify_source .. " - " .. (errmsg or "???"))
return false, errmsg return false, errmsg
end end
local function on_process() local function on_process()
success, errmsg = action_fn(source, dest) success, errmsg = action_fn(source, dest)
if not success then if not success then
notify.error("Could not " .. action_type .. " " .. notify_source .. " - " .. (errmsg or "???")) notify.error("Could not " .. action .. " " .. notify_source .. " - " .. (errmsg or "???"))
return false, errmsg return false, errmsg
end end
@@ -125,7 +145,7 @@ local function do_single_paste(source, dest, action_type, action_fn)
vim.ui.input(input_opts, function(new_dest) vim.ui.input(input_opts, function(new_dest)
utils.clear_prompt() utils.clear_prompt()
if new_dest then if new_dest then
do_single_paste(source, new_dest, action_type, action_fn) do_single_paste(source, new_dest, action, action_fn)
end end
end) end)
else else
@@ -139,7 +159,7 @@ local function do_single_paste(source, dest, action_type, action_fn)
vim.ui.input(input_opts, function(new_dest) vim.ui.input(input_opts, function(new_dest)
utils.clear_prompt() utils.clear_prompt()
if new_dest then if new_dest then
do_single_paste(source, new_dest, action_type, action_fn) do_single_paste(source, new_dest, action, action_fn)
end end
end) end)
end end
@@ -167,37 +187,42 @@ local function toggle(node, clip)
notify.info(notify_node .. " added to clipboard.") notify.info(notify_node .. " added to clipboard.")
end end
function M.clear_clipboard() ---Clear copied and cut
clipboard.cut = {} function Clipboard:clear_clipboard()
clipboard.copy = {} self.data[ACTION.copy] = {}
notify.info "Clipboard has been emptied." self.data[ACTION.cut] = {}
renderer.draw() notify.info("Clipboard has been emptied.")
self.explorer.renderer:draw()
end end
---Copy one node
---@param node Node ---@param node Node
function M.copy(node) function Clipboard:copy(node)
utils.array_remove(clipboard.cut, node) utils.array_remove(self.data[ACTION.cut], node)
toggle(node, clipboard.copy) toggle(node, self.data[ACTION.copy])
renderer.draw() self.explorer.renderer:draw()
end end
---Cut one node
---@param node Node ---@param node Node
function M.cut(node) function Clipboard:cut(node)
utils.array_remove(clipboard.copy, node) utils.array_remove(self.data[ACTION.copy], node)
toggle(node, clipboard.cut) toggle(node, self.data[ACTION.cut])
renderer.draw() self.explorer.renderer:draw()
end end
---Paste cut or cop
---@private
---@param node Node ---@param node Node
---@param action_type string ---@param action ACTION
---@param action_fn fun(source: string, dest: string) ---@param action_fn fun(source: string, dest: string)
local function do_paste(node, action_type, action_fn) function Clipboard:do_paste(node, action, action_fn)
node = lib.get_last_group_node(node) node = lib.get_last_group_node(node)
local explorer = core.get_explorer() local explorer = core.get_explorer()
if node.name == ".." and explorer then if node.name == ".." and explorer then
node = explorer node = explorer
end end
local clip = clipboard[action_type] local clip = self.data[action]
if #clip == 0 then if #clip == 0 then
return return
end end
@@ -206,7 +231,7 @@ local function do_paste(node, action_type, action_fn)
local stats, errmsg, errcode = vim.loop.fs_stat(destination) local stats, errmsg, errcode = vim.loop.fs_stat(destination)
if not stats and errcode ~= "ENOENT" then if not stats and errcode ~= "ENOENT" then
log.line("copy_paste", "do_paste fs_stat '%s' failed '%s'", destination, errmsg) log.line("copy_paste", "do_paste fs_stat '%s' failed '%s'", destination, errmsg)
notify.error("Could not " .. action_type .. " " .. notify.render_path(destination) .. " - " .. (errmsg or "???")) notify.error("Could not " .. action .. " " .. notify.render_path(destination) .. " - " .. (errmsg or "???"))
return return
end end
local is_dir = stats and stats.type == "directory" local is_dir = stats and stats.type == "directory"
@@ -215,13 +240,13 @@ local function do_paste(node, action_type, action_fn)
end end
for _, _node in ipairs(clip) do for _, _node in ipairs(clip) do
local dest = utils.path_join { destination, _node.name } local dest = utils.path_join({ destination, _node.name })
do_single_paste(_node.absolute_path, dest, action_type, action_fn) do_single_paste(_node.absolute_path, dest, action, action_fn)
end end
clipboard[action_type] = {} self.data[action] = {}
if not M.config.filesystem_watchers.enable then if not self.config.filesystem_watchers.enable then
reloaders.reload_explorer() self.explorer:reload_explorer()
end end
end end
@@ -248,26 +273,27 @@ local function do_cut(source, destination)
return true return true
end end
---Paste cut (if present) or copy (if present)
---@param node Node ---@param node Node
function M.paste(node) function Clipboard:paste(node)
if clipboard.cut[1] ~= nil then if self.data[ACTION.cut][1] ~= nil then
do_paste(node, "cut", do_cut) self:do_paste(node, ACTION.cut, do_cut)
else elseif self.data[ACTION.copy][1] ~= nil then
do_paste(node, "copy", do_copy) self:do_paste(node, ACTION.copy, do_copy)
end end
end end
function M.print_clipboard() function Clipboard:print_clipboard()
local content = {} local content = {}
if #clipboard.cut > 0 then if #self.data[ACTION.cut] > 0 then
table.insert(content, "Cut") table.insert(content, "Cut")
for _, node in pairs(clipboard.cut) do for _, node in pairs(self.data[ACTION.cut]) do
table.insert(content, " * " .. (notify.render_path(node.absolute_path))) table.insert(content, " * " .. (notify.render_path(node.absolute_path)))
end end
end end
if #clipboard.copy > 0 then if #self.data[ACTION.copy] > 0 then
table.insert(content, "Copy") table.insert(content, "Copy")
for _, node in pairs(clipboard.copy) do for _, node in pairs(self.data[ACTION.copy]) do
table.insert(content, " * " .. (notify.render_path(node.absolute_path))) table.insert(content, " * " .. (notify.render_path(node.absolute_path)))
end end
end end
@@ -276,75 +302,104 @@ function M.print_clipboard()
end end
---@param content string ---@param content string
local function copy_to_clipboard(content) function Clipboard:copy_to_reg(content)
local clipboard_name local clipboard_name
if M.config.actions.use_system_clipboard == true then local reg
vim.fn.setreg("+", content) if self.config.actions.use_system_clipboard == true then
vim.fn.setreg('"', content)
clipboard_name = "system" clipboard_name = "system"
reg = "+"
else else
vim.fn.setreg('"', content)
vim.fn.setreg("1", content)
clipboard_name = "neovim" clipboard_name = "neovim"
reg = "1"
end end
vim.api.nvim_exec_autocmds("TextYankPost", {}) -- 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$', reg))
end)
vim.api.nvim_buf_delete(temp_buf, {})
notify.info(string.format("Copied %s to %s clipboard!", content, clipboard_name)) notify.info(string.format("Copied %s to %s clipboard!", content, clipboard_name))
end end
---@param node Node ---@param node Node
function M.copy_filename(node) function Clipboard:copy_filename(node)
copy_to_clipboard(node.name) local content
end
---@param node Node if node.name == ".." then
function M.copy_path(node) -- root
local absolute_path = node.absolute_path content = vim.fn.fnamemodify(self.explorer.absolute_path, ":t")
local cwd = core.get_cwd() else
if cwd == nil then -- node
return content = node.name
end end
local relative_path = utils.path_relative(absolute_path, cwd) self:copy_to_reg(content)
local content = node.nodes ~= nil and utils.path_add_trailing(relative_path) or relative_path
copy_to_clipboard(content)
end end
---@param node Node ---@param node Node
function M.copy_absolute_path(node) function Clipboard:copy_basename(node)
local content
if node.name == ".." then
-- root
content = vim.fn.fnamemodify(self.explorer.absolute_path, ":t:r")
else
-- node
content = vim.fn.fnamemodify(node.name, ":r")
end
self:copy_to_reg(content)
end
---@param node Node
function Clipboard:copy_path(node)
local content
if node.name == ".." then
-- root
content = 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)
content = node.nodes ~= nil and utils.path_add_trailing(relative_path) or relative_path
end
self:copy_to_reg(content)
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 absolute_path = node.absolute_path
local content = node.nodes ~= nil and utils.path_add_trailing(absolute_path) or absolute_path local content = node.nodes ~= nil and utils.path_add_trailing(absolute_path) or absolute_path
copy_to_clipboard(content) self:copy_to_reg(content)
end end
--- Clipboard text highlight group and position when highlight_clipboard. ---Node is cut. Will not be copied.
---@param node Node ---@param node Node
---@return HL_POSITION position none when clipboard empty ---@return boolean
---@return string|nil group only when node present in clipboard function Clipboard:is_cut(node)
function M.get_highlight(node) return vim.tbl_contains(self.data[ACTION.cut], node)
if M.hl_pos == HL_POSITION.none then
return HL_POSITION.none, nil
end
for _, n in ipairs(clipboard.cut) do
if node == n then
return M.hl_pos, "NvimTreeCutHL"
end
end
for _, n in ipairs(clipboard.copy) do
if node == n then
return M.hl_pos, "NvimTreeCopiedHL"
end
end
return HL_POSITION.none, nil
end end
function M.setup(opts) ---Node is copied. Will not be cut.
M.config.filesystem_watchers = opts.filesystem_watchers ---@param node Node
M.config.actions = opts.actions ---@return boolean
M.hl_pos = HL_POSITION[opts.renderer.highlight_clipboard] or HL_POSITION.none function Clipboard:is_copied(node)
return vim.tbl_contains(self.data[ACTION.copy], node)
end end
return M return Clipboard

View File

@@ -1,8 +1,8 @@
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 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
@@ -12,7 +12,7 @@ local M = {}
local function create_and_notify(file) local function create_and_notify(file)
events._dispatch_will_create_file(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 " .. notify.render_path(file)) notify.error("Couldn't create file " .. notify.render_path(file))
return return
end end
@@ -72,7 +72,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
@@ -87,10 +87,10 @@ 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_and_notify(path_to_create) create_and_notify(path_to_create)

View File

@@ -1,13 +1,11 @@
local M = {} local M = {}
M.copy_paste = require "nvim-tree.actions.fs.copy-paste" M.create_file = require("nvim-tree.actions.fs.create-file")
M.create_file = require "nvim-tree.actions.fs.create-file" M.remove_file = require("nvim-tree.actions.fs.remove-file")
M.remove_file = require "nvim-tree.actions.fs.remove-file" M.rename_file = require("nvim-tree.actions.fs.rename-file")
M.rename_file = require "nvim-tree.actions.fs.rename-file" M.trash = require("nvim-tree.actions.fs.trash")
M.trash = require "nvim-tree.actions.fs.trash"
function M.setup(opts) function M.setup(opts)
M.copy_paste.setup(opts)
M.remove_file.setup(opts) M.remove_file.setup(opts)
M.rename_file.setup(opts) M.rename_file.setup(opts)
M.trash.setup(opts) M.trash.setup(opts)

View File

@@ -1,8 +1,9 @@
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 M = {
config = {}, config = {},
@@ -26,13 +27,13 @@ end
---@param absolute_path string ---@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() 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
vim.api.nvim_set_current_win(buf.windows[1]) vim.api.nvim_set_current_win(buf.windows[1])
vim.cmd ":bn" vim.cmd(":bn")
end end
vim.api.nvim_buf_delete(buf.bufnr, { force = true }) vim.api.nvim_buf_delete(buf.bufnr, { force = true })
if not view.View.float.quit_on_focus_loss then if not view.View.float.quit_on_focus_loss then
@@ -49,20 +50,25 @@ end
---@param cwd string ---@param cwd string
---@return boolean|nil ---@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
notify.error(handle) notify.error(err)
return 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)
local type = stat and stat.type or nil
if type == "directory" 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
@@ -111,8 +117,9 @@ function M.fn(node)
local function do_remove() local function do_remove()
M.remove(node) M.remove(node)
if not M.config.filesystem_watchers.enable then local explorer = core.get_explorer()
require("nvim-tree.actions.reloaders").reload_explorer() if not M.config.filesystem_watchers.enable and explorer then
explorer:reload_explorer()
end end
end end

View File

@@ -1,7 +1,8 @@
local lib = require "nvim-tree.lib" local core = require("nvim-tree.core")
local utils = require "nvim-tree.utils" local lib = require("nvim-tree.lib")
local events = require "nvim-tree.events" local utils = require("nvim-tree.utils")
local notify = require "nvim-tree.notify" local events = require("nvim-tree.events")
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
@@ -9,6 +10,16 @@ local M = {
config = {}, 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, [":p:h"] = true,
@@ -20,26 +31,69 @@ 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 node Node
---@param to string ---@param to string
function M.rename(node, to) function M.rename(node, to)
local notify_from = notify.render_path(node.absolute_path) local notify_from = notify.render_path(node.absolute_path)
local notify_to = notify.render_path(to) local notify_to = notify.render_path(to)
if utils.file_exists(to) then if rename_file_exists(notify_from, notify_to) then
notify.warn(err_fmt(notify_from, notify_to, "file already exists")) 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 = ""
notify.warn(err_fmt(notify_from, notify_to, err))
return 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(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
---@param default_modifier string|nil ---@param default_modifier string|nil
@@ -102,7 +156,10 @@ function M.fn(default_modifier)
M.rename(node, prepend .. new_file_path .. append) M.rename(node, prepend .. new_file_path .. append)
if not M.config.filesystem_watchers.enable then if not M.config.filesystem_watchers.enable then
require("nvim-tree.actions.reloaders").reload_explorer() local explorer = core.get_explorer()
if explorer then
explorer:reload_explorer()
end
end end
find_file(utils.path_remove_trailing(new_file_path)) find_file(utils.path_remove_trailing(new_file_path))

View File

@@ -1,23 +1,23 @@
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 reloaders = require "nvim-tree.actions.reloaders" local notify = require("nvim-tree.notify")
local M = { local M = {
config = {}, config = {},
} }
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 ---@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, {})
@@ -42,16 +42,18 @@ function M.remove(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)
local need_sync_wait = utils.is_windows local need_sync_wait = utils.is_windows
local job = vim.fn.jobstart(M.config.trash.cmd .. ' "' .. node.absolute_path .. '"', { local job = vim.fn.jobstart(M.config.trash.cmd .. " " .. vim.fn.shellescape(node.absolute_path), {
detach = not need_sync_wait, detach = not need_sync_wait,
on_exit = on_exit, on_exit = on_exit,
on_stderr = on_stderr, on_stderr = on_stderr,
}) })
if need_sync_wait then if need_sync_wait then
vim.fn.jobwait { job } vim.fn.jobwait({ job })
end end
end end
local explorer = core.get_explorer()
if node.nodes ~= nil and not node.link_to then if node.nodes ~= nil and not node.link_to then
trash_path(function(_, rc) trash_path(function(_, rc)
if rc ~= 0 then if rc ~= 0 then
@@ -59,8 +61,8 @@ function M.remove(node)
return return
end end
events._dispatch_folder_removed(node.absolute_path) events._dispatch_folder_removed(node.absolute_path)
if not M.config.filesystem_watchers.enable then if not M.config.filesystem_watchers.enable and explorer then
reloaders.reload_explorer() explorer:reload_explorer()
end end
end) end)
else else
@@ -72,8 +74,8 @@ function M.remove(node)
end end
events._dispatch_file_removed(node.absolute_path) events._dispatch_file_removed(node.absolute_path)
clear_buffer(node.absolute_path) clear_buffer(node.absolute_path)
if not M.config.filesystem_watchers.enable then if not M.config.filesystem_watchers.enable and explorer then
reloaders.reload_explorer() explorer:reload_explorer()
end end
end) end)
end end

View File

@@ -1,12 +1,11 @@
local M = {} local M = {}
M.finders = require "nvim-tree.actions.finders" M.finders = require("nvim-tree.actions.finders")
M.fs = require "nvim-tree.actions.fs" M.fs = require("nvim-tree.actions.fs")
M.moves = require "nvim-tree.actions.moves" M.moves = require("nvim-tree.actions.moves")
M.node = require "nvim-tree.actions.node" M.node = require("nvim-tree.actions.node")
M.reloaders = require "nvim-tree.actions.reloaders" M.root = require("nvim-tree.actions.root")
M.root = require "nvim-tree.actions.root" M.tree = require("nvim-tree.actions.tree")
M.tree = require "nvim-tree.actions.tree"
function M.setup(opts) function M.setup(opts)
M.fs.setup(opts) M.fs.setup(opts)

View File

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

View File

@@ -1,11 +1,210 @@
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 lib = require("nvim-tree.lib")
local explorer_node = require "nvim-tree.explorer.node" local explorer_node = require("nvim-tree.explorer.node")
local diagnostics = require "nvim-tree.diagnostics" local diagnostics = require("nvim-tree.diagnostics")
local M = {} local M = {}
local MAX_DEPTH = 100
---Return the status of the node or nil if no status, depending on the type of
---status.
---@param node table node to inspect
---@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_status = explorer_node.get_git_status(node)
return git_status ~= nil and (not skip_gitignored or git_status[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
return false
end
---Move to the next node that has a valid status. If none found, don't move.
---@param where string where to move (forwards or backwards)
---@param what string type of status
---@param skip_gitignored boolean default false
local function move(where, what, skip_gitignored)
local first_node_line = core.get_nodes_starting_line()
local nodes_by_line = utils.get_nodes_by_line(core.get_explorer().nodes, first_node_line)
local iter_start, iter_end, iter_step, cur, first, nex
local cursor = lib.get_cursor_position()
if cursor and cursor[1] < first_node_line then
cur = cursor[1]
end
if where == "next" then
iter_start, iter_end, iter_step = first_node_line, #nodes_by_line, 1
elseif where == "prev" then
iter_start, iter_end, iter_step = #nodes_by_line, first_node_line, -1
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
if cursor and line == cursor[1] then
cur = line
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
local function expand_node(node)
if not node.open then
-- Expand the node.
-- Should never collapse since we checked open.
lib.expand_or_collapse(node)
end
end
--- Move to the next node recursively.
---@param what string type of status
---@param skip_gitignored boolean default false
local function move_next_recursive(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 = lib.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
if node_init.nodes ~= nil and valid and not node_init.open then
lib.expand_or_collapse(node_init)
end
move("next", what, skip_gitignored)
local node_cur = lib.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 is_dir = node_cur.nodes ~= nil
while is_dir and i < MAX_DEPTH do
expand_node(node_cur)
move("next", what, skip_gitignored)
-- Save current node.
node_cur = lib.get_node_at_cursor()
-- Update is_dir.
if node_cur then
is_dir = node_cur.nodes ~= nil
else
is_dir = false
end
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 what string type of status
---@param skip_gitignored boolean default false
local function move_prev_recursive(what, skip_gitignored)
local node_init, node_cur
-- 1)
node_init = lib.get_node_at_cursor()
if node_init == nil then
return
end
-- 2)
move("prev", what, skip_gitignored)
node_cur = lib.get_node_at_cursor()
if node_cur == node_init.parent then
-- 3)
move_prev_recursive(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 not node_cur.nodes -- node is a file
then
return
end
-- 4.2)
local node_dir = node_cur
expand_node(node_dir)
-- 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
view.set_cursor({ node_init_line, 0 })
end
-- 4.4)
move("prev", what, skip_gitignored)
-- 4.5)
node_cur = lib.get_node_at_cursor()
i = i + 1
end
end
end
---@class NavigationItemOpts ---@class NavigationItemOpts
---@field where string ---@field where string
@@ -15,47 +214,27 @@ local M = {}
---@return fun() ---@return fun()
function M.fn(opts) function M.fn(opts)
return function() return function()
local node_cur = lib.get_node_at_cursor() local recurse = false
local first_node_line = core.get_nodes_starting_line() local skip_gitignored = false
local nodes_by_line = utils.get_nodes_by_line(core.get_explorer().nodes, first_node_line)
local iter_start, iter_end, iter_step, cur, first, nex -- 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 opts.skip_gitignored ~= nil then
skip_gitignored = opts.skip_gitignored
end
if not recurse then
move(opts.where, opts.what, skip_gitignored)
return
end
if opts.where == "next" then if opts.where == "next" then
iter_start, iter_end, iter_step = first_node_line, #nodes_by_line, 1 move_next_recursive(opts.what, skip_gitignored)
elseif opts.where == "prev" then elseif opts.where == "prev" then
iter_start, iter_end, iter_step = #nodes_by_line, first_node_line, -1 move_prev_recursive(opts.what, skip_gitignored)
end
for line = iter_start, iter_end, iter_step do
local node = nodes_by_line[line]
local valid = false
if opts.what == "git" then
local git_status = explorer_node.get_git_status(node)
valid = git_status ~= nil and (not opts.skip_gitignored or git_status[1] ~= "!!")
elseif opts.what == "diag" then
local diag_status = diagnostics.get_diag_status(node)
valid = diag_status ~= nil and diag_status.value ~= nil
elseif opts.what == "opened" then
valid = vim.fn.bufloaded(node.absolute_path) ~= 0
end
if not first and valid then
first = line
end
if node == node_cur then
cur = line
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
end end
end end

View File

@@ -1,8 +1,7 @@
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 core = require "nvim-tree.core" local lib = require("nvim-tree.lib")
local lib = require "nvim-tree.lib"
local M = {} local M = {}
@@ -12,26 +11,32 @@ function M.fn(should_close)
should_close = should_close or false should_close = should_close or false
return function(node) return function(node)
local explorer = core.get_explorer()
node = lib.get_last_group_node(node) node = lib.get_last_group_node(node)
if should_close and node.open then if should_close and node.open then
node.open = false node.open = false
return renderer.draw() if explorer then
explorer.renderer:draw()
end
return
end end
local parent = utils.get_parent_of_group(node).parent local parent = utils.get_parent_of_group(node).parent
if not parent or not parent.parent then if not parent or not parent.parent then
return view.set_cursor { 1, 0 } return view.set_cursor({ 1, 0 })
end end
local _, line = utils.find_node(core.get_explorer().nodes, function(n) local _, line = utils.find_node(core.get_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() if explorer then
explorer.renderer:draw()
end
end end
end end
end end

View File

@@ -1,6 +1,6 @@
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 = {}

View File

@@ -1,4 +1,4 @@
local utils = require "nvim-tree.utils" local utils = require("nvim-tree.utils")
local M = {} local M = {}
@@ -6,6 +6,14 @@ local M = {}
---@return table ---@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)
@@ -48,8 +56,8 @@ 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 }) vim.api.nvim_win_close(current_popup.winnr, true)
vim.cmd "augroup NvimTreeRemoveFilePopup | au! CursorMoved | augroup END" vim.cmd("augroup NvimTreeRemoveFilePopup | au! CursorMoved | augroup END")
current_popup = nil current_popup = nil
end end

View File

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

View File

@@ -1,8 +1,8 @@
-- 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 notify = require "nvim-tree.notify" local notify = require("nvim-tree.notify")
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 M = {} local M = {}
@@ -26,14 +26,20 @@ 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 win_config.focusable and not win_config.external or false
end, win_ids) end, win_ids)
end end
@@ -83,8 +89,15 @@ 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, ok_hl, winhl
local ok_hl, winhl = pcall(vim.api.nvim_win_get_option, win_id, "winhl")
if vim.fn.has("nvim-0.10") == 1 then
ok_status, statusline = pcall(vim.api.nvim_get_option_value, "statusline", { win = win_id })
ok_hl, winhl = pcall(vim.api.nvim_get_option_value, "winhl", { win = win_id })
else
ok_status, statusline = pcall(vim.api.nvim_win_get_option, win_id, "statusline") ---@diagnostic disable-line: deprecated
ok_hl, winhl = pcall(vim.api.nvim_win_get_option, win_id, "winhl") ---@diagnostic disable-line: deprecated
end
win_opts[win_id] = { win_opts[win_id] = {
statusline = ok_status and statusline or "", statusline = ok_status and statusline or "",
@@ -92,15 +105,26 @@ local function pick_win_id()
} }
-- 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") 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[id] = { win_opts[id] = {
statusline = ok_status and statusline or "", statusline = ok_status and statusline or "",
@@ -108,8 +132,13 @@ local function pick_win_id()
} }
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
@@ -117,9 +146,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()
@@ -128,14 +157,25 @@ 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[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[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
@@ -153,6 +193,9 @@ 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
@@ -160,6 +203,9 @@ local function drop(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("drop " .. vim.fn.fnameescape(filename)) vim.cmd("drop " .. vim.fn.fnameescape(filename))
end end
@@ -167,6 +213,9 @@ local function tab_drop(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("tab :drop " .. vim.fn.fnameescape(filename)) vim.cmd("tab :drop " .. vim.fn.fnameescape(filename))
end end
@@ -251,32 +300,28 @@ local function open_in_new_window(filename, mode)
-- 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
if not mode:match "split$" then 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" mode = "vsplit"
end end
end end
end end
local fname = vim.fn.fnameescape(filename)
fname = utils.escape_special_chars(fname)
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" or mode == "preview_no_picker") and view.View.float.enable then if (mode == "preview" or mode == "preview_no_picker") and view.View.float.enable then
-- ignore "WinLeave" autocmd on preview -- ignore "WinLeave" autocmd on preview
-- because the registered "WinLeave" -- because the registered "WinLeave"
@@ -286,7 +331,24 @@ local function open_in_new_window(filename, mode)
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 = vim.fn.fnameescape(utils.path_relative(filename, vim.fn.getcwd()))
else
fname = 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
@@ -301,33 +363,37 @@ 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()
if M.relative_path then
filename = utils.path_relative(filename, vim.fn.getcwd())
end
vim.cmd("keepalt keepjumps edit " .. vim.fn.fnameescape(filename)) vim.cmd("keepalt keepjumps edit " .. vim.fn.fnameescape(filename))
end end
---@param mode string ---@param mode string
---@param filename string ---@param filename string
function M.fn(mode, filename) function M.fn(mode, filename)
local fname = utils.escape_special_chars(filename)
if type(mode) ~= "string" then if type(mode) ~= "string" then
mode = "" mode = ""
end end
if mode == "tabnew" then if mode == "tabnew" then
return open_file_in_tab(filename) return open_file_in_tab(fname)
end end
if mode == "drop" then if mode == "drop" then
return drop(filename) return drop(fname)
end end
if mode == "tab_drop" then if mode == "tab_drop" then
return tab_drop(filename) return tab_drop(fname)
end end
if mode == "edit_in_place" then if mode == "edit_in_place" then
return edit_in_current_buf(filename) return edit_in_current_buf(fname)
end end
local buf_loaded = is_already_loaded(filename) local buf_loaded = is_already_loaded(fname)
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" or mode == "preview_no_picker") then if found_win and (mode == "preview" or mode == "preview_no_picker") then
@@ -335,7 +401,7 @@ function M.fn(mode, filename)
end end
if not found_win then if not found_win then
open_in_new_window(filename, mode) open_in_new_window(fname, 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 = ""
@@ -357,6 +423,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.experimental.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,5 +1,5 @@
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 = {}

View File

@@ -1,12 +1,12 @@
local notify = require "nvim-tree.notify" local notify = require("nvim-tree.notify")
local utils = require "nvim-tree.utils" local utils = require("nvim-tree.utils")
local M = {} local M = {}
---@param node Node ---@param node Node
function M.fn(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
@@ -49,20 +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 always use native once 0.10 is the minimum neovim version
function M.setup(opts) function M.setup(opts)
M.config = {} 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 utils.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 utils.is_macos then M.config.system_open = {
M.config.system_open.cmd = "open" cmd = "cmd",
elseif utils.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,74 +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 explorer_node = require "nvim-tree.explorer.node"
local Iterator = require "nvim-tree.iterators.node-iterator"
local M = {}
---@param node Explorer|nil
---@param projects table
---@param unloaded_bufnr number|nil
local function refresh_nodes(node, projects, unloaded_bufnr)
Iterator.builder({ node })
:applier(function(n)
if n.nodes then
local toplevel = git.get_toplevel(n.cwd or n.link_to or n.absolute_path)
explorer_module.reload(n, projects[toplevel] or {}, unloaded_bufnr)
end
end)
:recursor(function(n)
return n.group_next and { n.group_next } or (n.open and n.nodes)
end)
:iterate()
end
---@param parent_node Node|nil
---@param projects table
function M.reload_node_status(parent_node, projects)
if parent_node == nil then
return
end
local toplevel = git.get_toplevel(parent_node.absolute_path)
local status = projects[toplevel] or {}
for _, node in ipairs(parent_node.nodes) do
explorer_node.update_git_status(node, explorer_node.is_git_ignored(parent_node), status)
if node.nodes and #node.nodes > 0 then
M.reload_node_status(node, projects)
end
end
end
local event_running = false
---@param _ table|nil unused node passed by action
---@param unloaded_bufnr number|nil optional bufnr recently unloaded via BufUnload event
function M.reload_explorer(_, unloaded_bufnr)
if event_running or not core.get_explorer() or vim.v.exiting ~= vim.NIL then
return
end
event_running = true
local projects = git.reload()
refresh_nodes(core.get_explorer(), projects, unloaded_bufnr)
if view.is_visible() then
renderer.draw(unloaded_bufnr)
end
event_running = false
end
function M.reload_git()
if not core.get_explorer() or not git.config.git.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,6 +1,6 @@
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(),
@@ -91,7 +91,10 @@ M.force_dirchange = add_profiling_to(function(foldername, should_open_view)
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,12 +1,12 @@
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 ---@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
require("nvim-tree.actions.root.change-dir").fn ".." require("nvim-tree.actions.root.change-dir").fn("..")
else else
local cwd = core.get_cwd() local cwd = core.get_cwd()
if cwd == nil then if cwd == nil then

View File

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

View File

@@ -1,7 +1,7 @@
local core = require "nvim-tree.core" local core = require("nvim-tree.core")
local lib = require "nvim-tree.lib" local lib = require("nvim-tree.lib")
local view = require "nvim-tree.view" local view = require("nvim-tree.view")
local finders_find_file = require "nvim-tree.actions.finders.find-file" local finders_find_file = require("nvim-tree.actions.finders.find-file")
local M = {} local M = {}
@@ -24,18 +24,19 @@ function M.fn(opts)
local bufnr, path local bufnr, path
-- (optional) buffer number and path -- (optional) buffer number and path
if type(opts.buf) == "nil" then local opts_buf = opts.buf
if type(opts_buf) == "nil" then
bufnr = vim.api.nvim_get_current_buf() bufnr = vim.api.nvim_get_current_buf()
path = vim.api.nvim_buf_get_name(bufnr) path = vim.api.nvim_buf_get_name(bufnr)
elseif type(opts.buf) == "number" then elseif type(opts_buf) == "number" then
if not vim.api.nvim_buf_is_valid(opts.buf) then if not vim.api.nvim_buf_is_valid(opts_buf) then
return return
end end
bufnr = tonumber(opts.buf) bufnr = opts_buf
path = vim.api.nvim_buf_get_name(bufnr) path = vim.api.nvim_buf_get_name(bufnr)
elseif type(opts.buf) == "string" then elseif type(opts_buf) == "string" then
bufnr = nil bufnr = nil
path = tostring(opts.buf) path = tostring(opts_buf)
else else
return return
end end
@@ -48,14 +49,14 @@ function M.fn(opts)
end end
elseif opts.open then elseif opts.open then
-- open -- open
lib.open { current_window = opts.current_window, winid = opts.winid } lib.open({ current_window = opts.current_window, winid = opts.winid })
if not opts.focus then if not opts.focus then
vim.cmd "noautocmd wincmd p" vim.cmd("noautocmd wincmd p")
end end
end end
-- update root -- update root
if opts.update_root or M.config.update_focused_file.update_root then if opts.update_root or M.config.update_focused_file.update_root.enable then
require("nvim-tree").change_root(path, bufnr) require("nvim-tree").change_root(path, bufnr)
end end

View File

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

View File

@@ -1,8 +1,7 @@
local renderer = require "nvim-tree.renderer" 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 lib = require("nvim-tree.lib")
local lib = require "nvim-tree.lib" local Iterator = require("nvim-tree.iterators.node-iterator")
local Iterator = require "nvim-tree.iterators.node-iterator"
local M = {} local M = {}
@@ -46,7 +45,7 @@ function M.fn(keep_buffers)
end) end)
:iterate() :iterate()
renderer.draw() explorer.renderer:draw()
utils.focus_node_or_parent(node) utils.focus_node_or_parent(node)
end end

View File

@@ -1,8 +1,7 @@
local core = require "nvim-tree.core" local core = require("nvim-tree.core")
local renderer = require "nvim-tree.renderer" local Iterator = require("nvim-tree.iterators.node-iterator")
local Iterator = require "nvim-tree.iterators.node-iterator" local notify = require("nvim-tree.notify")
local notify = require "nvim-tree.notify" local lib = require("nvim-tree.lib")
local lib = require "nvim-tree.lib"
local M = {} local M = {}
@@ -65,11 +64,14 @@ end
---@param base_node table ---@param base_node table
function M.fn(base_node) function M.fn(base_node)
local node = base_node.nodes and base_node or core.get_explorer() local explorer = core.get_explorer()
local node = base_node.nodes and base_node or explorer
if gen_iterator()(node) then if gen_iterator()(node) then
notify.warn("expansion iteration was halted after " .. M.MAX_FOLDER_DISCOVERY .. " discovered folders") notify.warn("expansion iteration was halted after " .. M.MAX_FOLDER_DISCOVERY .. " discovered folders")
end end
renderer.draw() if explorer then
explorer.renderer:draw()
end
end end
function M.setup(opts) function M.setup(opts)

View File

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

View File

@@ -1,44 +1,72 @@
local lib = require "nvim-tree.lib" local lib = require("nvim-tree.lib")
local utils = require "nvim-tree.utils" local utils = require("nvim-tree.utils")
local filters = require "nvim-tree.explorer.filters" local core = require("nvim-tree.core")
local reloaders = require "nvim-tree.actions.reloaders"
local M = {} local M = {}
local function reload() ---@param explorer Explorer
local function reload(explorer)
local node = lib.get_node_at_cursor() local node = lib.get_node_at_cursor()
reloaders.reload_explorer() explorer:reload_explorer()
utils.focus_node_or_parent(node) utils.focus_node_or_parent(node)
end end
function M.custom() local function wrap_explorer(fn)
filters.config.filter_custom = not filters.config.filter_custom return function(...)
reload() local explorer = core.get_explorer()
if explorer then
return fn(explorer, ...)
end
end
end end
function M.git_ignored() ---@param explorer Explorer
filters.config.filter_git_ignored = not filters.config.filter_git_ignored local function custom(explorer)
reload() explorer.filters.config.filter_custom = not explorer.filters.config.filter_custom
reload(explorer)
end end
function M.git_clean() ---@param explorer Explorer
filters.config.filter_git_clean = not filters.config.filter_git_clean local function git_ignored(explorer)
reload() explorer.filters.config.filter_git_ignored = not explorer.filters.config.filter_git_ignored
reload(explorer)
end end
function M.no_buffer() ---@param explorer Explorer
filters.config.filter_no_buffer = not filters.config.filter_no_buffer local function git_clean(explorer)
reload() explorer.filters.config.filter_git_clean = not explorer.filters.config.filter_git_clean
reload(explorer)
end end
function M.no_bookmark() ---@param explorer Explorer
filters.config.filter_no_bookmark = not filters.config.filter_no_bookmark local function no_buffer(explorer)
reload() explorer.filters.config.filter_no_buffer = not explorer.filters.config.filter_no_buffer
reload(explorer)
end end
function M.dotfiles() ---@param explorer Explorer
filters.config.filter_dotfiles = not filters.config.filter_dotfiles local function no_bookmark(explorer)
reload() explorer.filters.config.filter_no_bookmark = not explorer.filters.config.filter_no_bookmark
reload(explorer)
end end
---@param explorer Explorer
local function dotfiles(explorer)
explorer.filters.config.filter_dotfiles = not explorer.filters.config.filter_dotfiles
reload(explorer)
end
---@param explorer Explorer
local function enable(explorer)
explorer.filters.config.enable = not explorer.filters.config.enable
reload(explorer)
end
M.custom = wrap_explorer(custom)
M.git_ignored = wrap_explorer(git_ignored)
M.git_clean = wrap_explorer(git_clean)
M.no_buffer = wrap_explorer(no_buffer)
M.no_bookmark = wrap_explorer(no_bookmark)
M.dotfiles = wrap_explorer(dotfiles)
M.enable = wrap_explorer(enable)
return M return M

View File

@@ -1,6 +1,6 @@
local lib = require "nvim-tree.lib" local lib = require("nvim-tree.lib")
local view = require "nvim-tree.view" local view = require("nvim-tree.view")
local finders_find_file = require "nvim-tree.actions.finders.find-file" local finders_find_file = require("nvim-tree.actions.finders.find-file")
local M = {} local M = {}
@@ -29,11 +29,11 @@ function M.fn(opts)
view.focus() view.focus()
else else
-- open -- open
lib.open { lib.open({
path = opts.path, path = opts.path,
current_window = opts.current_window, current_window = opts.current_window,
winid = opts.winid, winid = opts.winid,
} })
end end
-- find file -- find file

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

@@ -1,6 +1,6 @@
local lib = require "nvim-tree.lib" local lib = require("nvim-tree.lib")
local view = require "nvim-tree.view" local view = require("nvim-tree.view")
local finders_find_file = require "nvim-tree.actions.finders.find-file" local finders_find_file = require("nvim-tree.actions.finders.find-file")
local M = {} local M = {}
@@ -45,11 +45,11 @@ function M.fn(opts, no_focus, cwd, bang)
view.close() view.close()
else else
-- open -- open
lib.open { lib.open({
path = opts.path, path = opts.path,
current_window = opts.current_window, current_window = opts.current_window,
winid = opts.winid, winid = opts.winid,
} })
-- find file -- find file
if M.config.update_focused_file.enable or opts.find_file then if M.config.update_focused_file.enable or opts.find_file then
@@ -64,7 +64,7 @@ function M.fn(opts, no_focus, cwd, bang)
-- restore focus -- restore focus
if not opts.focus then if not opts.focus then
vim.cmd "noautocmd wincmd p" vim.cmd("noautocmd wincmd p")
end end
end end
end end

View File

@@ -1,17 +1,13 @@
local lib = require "nvim-tree.lib" local lib = require("nvim-tree.lib")
local view = require "nvim-tree.view" local core = require("nvim-tree.core")
local utils = require "nvim-tree.utils" local view = require("nvim-tree.view")
local actions = require "nvim-tree.actions" local utils = require("nvim-tree.utils")
local events = require "nvim-tree.events" local actions = require("nvim-tree.actions")
local help = require "nvim-tree.help" local appearance_diagnostics = require("nvim-tree.appearance.diagnostics")
local live_filter = require "nvim-tree.live-filter" local events = require("nvim-tree.events")
local marks = require "nvim-tree.marks" local help = require("nvim-tree.help")
local marks_navigation = require "nvim-tree.marks.navigation" local keymap = require("nvim-tree.keymap")
local marks_bulk_delete = require "nvim-tree.marks.bulk-delete" local notify = require("nvim-tree.notify")
local marks_bulk_trash = require "nvim-tree.marks.bulk-trash"
local marks_bulk_move = require "nvim-tree.marks.bulk-move"
local keymap = require "nvim-tree.keymap"
local notify = require "nvim-tree.notify"
local Api = { local Api = {
tree = {}, tree = {},
@@ -39,28 +35,30 @@ local Api = {
mappings = {}, mappings = {},
}, },
commands = {}, commands = {},
diagnostics = {},
} }
--- Do nothing when setup not called. --- Print error when setup not called.
--- f function to invoke --- f function to invoke
---@param f function ---@param f function
---@return fun(...) : any
local function wrap(f) local function wrap(f)
return function(...) return function(...)
if vim.g.NvimTreeSetup == 1 then if vim.g.NvimTreeSetup == 1 then
return f(...) return f(...)
else else
notify.error "nvim-tree setup not called" notify.error("nvim-tree setup not called")
end end
end end
end end
---Inject the node as the first argument if absent. ---Inject the node as the first argument if present otherwise do nothing.
---@param fn function function to invoke ---@param fn function function to invoke
local function wrap_node(fn) local function wrap_node(fn)
return function(node, ...) return function(node, ...)
node = node or lib.get_node_at_cursor() node = node or lib.get_node_at_cursor()
if node then if node then
fn(node, ...) return fn(node, ...)
end end
end end
end end
@@ -70,10 +68,37 @@ end
local function wrap_node_or_nil(fn) local function wrap_node_or_nil(fn)
return function(node, ...) return function(node, ...)
node = node or lib.get_node_at_cursor() node = node or lib.get_node_at_cursor()
fn(node, ...) return fn(node, ...)
end end
end end
---Invoke a method on the singleton explorer.
---Print error when setup not called.
---@param explorer_method string explorer method name
---@return fun(...) : any
local function wrap_explorer(explorer_method)
return wrap(function(...)
local explorer = core.get_explorer()
if explorer then
return explorer[explorer_method](explorer, ...)
end
end)
end
---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
---@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
---@class ApiTreeOpenOpts ---@class ApiTreeOpenOpts
---@field path string|nil path ---@field path string|nil path
---@field current_window boolean|nil default false ---@field current_window boolean|nil default false
@@ -96,7 +121,14 @@ Api.tree.toggle = wrap(actions.tree.toggle.fn)
Api.tree.close = wrap(view.close) Api.tree.close = wrap(view.close)
Api.tree.close_in_this_tab = wrap(view.close_this_tab_only) 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.close_in_all_tabs = wrap(view.close_all_tabs)
Api.tree.reload = wrap(actions.reloaders.reload_explorer) 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(...) Api.tree.change_root = wrap(function(...)
require("nvim-tree").change_dir(...) require("nvim-tree").change_dir(...)
@@ -104,7 +136,7 @@ end)
Api.tree.change_root_to_node = wrap_node(function(node) Api.tree.change_root_to_node = wrap_node(function(node)
if node.name == ".." then if node.name == ".." then
actions.root.change_dir.fn ".." actions.root.change_dir.fn("..")
elseif node.nodes ~= nil then elseif node.nodes ~= nil then
actions.root.change_dir.fn(lib.get_last_group_node(node).absolute_path) actions.root.change_dir.fn(lib.get_last_group_node(node).absolute_path)
end end
@@ -126,6 +158,7 @@ Api.tree.find_file = wrap(actions.tree.find_file.fn)
Api.tree.search_node = wrap(actions.finders.search_node.fn) Api.tree.search_node = wrap(actions.finders.search_node.fn)
Api.tree.collapse_all = wrap(actions.tree.modifiers.collapse_all.fn) Api.tree.collapse_all = wrap(actions.tree.modifiers.collapse_all.fn)
Api.tree.expand_all = wrap_node(actions.tree.modifiers.expand_all.fn) Api.tree.expand_all = wrap_node(actions.tree.modifiers.expand_all.fn)
Api.tree.toggle_enable_filters = wrap(actions.tree.modifiers.toggles.enable)
Api.tree.toggle_gitignore_filter = wrap(actions.tree.modifiers.toggles.git_ignored) Api.tree.toggle_gitignore_filter = wrap(actions.tree.modifiers.toggles.git_ignored)
Api.tree.toggle_git_clean_filter = wrap(actions.tree.modifiers.toggles.git_clean) Api.tree.toggle_git_clean_filter = wrap(actions.tree.modifiers.toggles.git_clean)
Api.tree.toggle_no_buffer_filter = wrap(actions.tree.modifiers.toggles.no_buffer) Api.tree.toggle_no_buffer_filter = wrap(actions.tree.modifiers.toggles.no_buffer)
@@ -149,19 +182,20 @@ Api.tree.winid = wrap(view.winid)
Api.fs.create = wrap_node_or_nil(actions.fs.create_file.fn) Api.fs.create = wrap_node_or_nil(actions.fs.create_file.fn)
Api.fs.remove = wrap_node(actions.fs.remove_file.fn) Api.fs.remove = wrap_node(actions.fs.remove_file.fn)
Api.fs.trash = wrap_node(actions.fs.trash.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_node = wrap_node(actions.fs.rename_file.fn(":t"))
Api.fs.rename = 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_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_basename = wrap_node(actions.fs.rename_file.fn(":t:r"))
Api.fs.rename_full = wrap_node(actions.fs.rename_file.fn ":p") Api.fs.rename_full = wrap_node(actions.fs.rename_file.fn(":p"))
Api.fs.cut = wrap_node(actions.fs.copy_paste.cut) Api.fs.cut = wrap_node(wrap_explorer_member("clipboard", "cut"))
Api.fs.paste = wrap_node(actions.fs.copy_paste.paste) Api.fs.paste = wrap_node(wrap_explorer_member("clipboard", "paste"))
Api.fs.clear_clipboard = wrap(actions.fs.copy_paste.clear_clipboard) Api.fs.clear_clipboard = wrap_explorer_member("clipboard", "clear_clipboard")
Api.fs.print_clipboard = wrap(actions.fs.copy_paste.print_clipboard) Api.fs.print_clipboard = wrap_explorer_member("clipboard", "print_clipboard")
Api.fs.copy.node = wrap_node(actions.fs.copy_paste.copy) Api.fs.copy.node = wrap_node(wrap_explorer_member("clipboard", "copy"))
Api.fs.copy.absolute_path = wrap_node(actions.fs.copy_paste.copy_absolute_path) Api.fs.copy.absolute_path = wrap_node(wrap_explorer_member("clipboard", "copy_absolute_path"))
Api.fs.copy.filename = wrap_node(actions.fs.copy_paste.copy_filename) Api.fs.copy.filename = wrap_node(wrap_explorer_member("clipboard", "copy_filename"))
Api.fs.copy.relative_path = wrap_node(actions.fs.copy_paste.copy_path) 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"))
---@param mode string ---@param mode string
---@param node table ---@param node table
@@ -175,71 +209,78 @@ end
---@param mode string ---@param mode string
---@return fun(node: table) ---@return fun(node: table)
local function open_or_expand_or_dir_up(mode) local function open_or_expand_or_dir_up(mode, toggle_group)
return function(node) return function(node)
if node.name == ".." then if node.name == ".." then
actions.root.change_dir.fn ".." actions.root.change_dir.fn("..")
elseif node.nodes then elseif node.nodes then
lib.expand_or_collapse(node) lib.expand_or_collapse(node, toggle_group)
else elseif not toggle_group then
edit(mode, node) edit(mode, node)
end end
end end
end end
Api.node.open.edit = wrap_node(open_or_expand_or_dir_up "edit") 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.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.tab_drop = wrap_node(open_or_expand_or_dir_up("tab_drop"))
Api.node.open.replace_tree_buffer = wrap_node(open_or_expand_or_dir_up "edit_in_place") Api.node.open.replace_tree_buffer = wrap_node(open_or_expand_or_dir_up("edit_in_place"))
Api.node.open.no_window_picker = wrap_node(open_or_expand_or_dir_up "edit_no_picker") Api.node.open.no_window_picker = wrap_node(open_or_expand_or_dir_up("edit_no_picker"))
Api.node.open.vertical = wrap_node(open_or_expand_or_dir_up "vsplit") Api.node.open.vertical = wrap_node(open_or_expand_or_dir_up("vsplit"))
Api.node.open.horizontal = wrap_node(open_or_expand_or_dir_up "split") Api.node.open.horizontal = wrap_node(open_or_expand_or_dir_up("split"))
Api.node.open.tab = wrap_node(open_or_expand_or_dir_up "tabnew") Api.node.open.tab = wrap_node(open_or_expand_or_dir_up("tabnew"))
Api.node.open.preview = wrap_node(open_or_expand_or_dir_up "preview") Api.node.open.toggle_group_empty = wrap_node(open_or_expand_or_dir_up("toggle_group_empty", true))
Api.node.open.preview_no_picker = wrap_node(open_or_expand_or_dir_up "preview_no_picker") 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.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.cmd = wrap_node(actions.node.run_command.run_file_command)
Api.node.run.system = wrap_node(actions.node.system_open.fn) 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.next = wrap_node(actions.moves.sibling.fn("next"))
Api.node.navigate.sibling.prev = wrap_node(actions.moves.sibling.fn "prev") 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.first = wrap_node(actions.moves.sibling.fn("first"))
Api.node.navigate.sibling.last = wrap_node(actions.moves.sibling.fn "last") 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 = wrap_node(actions.moves.parent.fn(false))
Api.node.navigate.parent_close = wrap_node(actions.moves.parent.fn(true)) 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 = 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_skip_gitignored = wrap_node(actions.moves.item.fn({ where = "next", what = "git", skip_gitignored = true }))
Api.node.navigate.git.prev = wrap_node(actions.moves.item.fn { where = "prev", what = "git" }) Api.node.navigate.git.next_recursive = wrap_node(actions.moves.item.fn({ where = "next", what = "git", recurse = true }))
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 = wrap_node(actions.moves.item.fn({ where = "prev", what = "git" }))
Api.node.navigate.diagnostics.next = wrap_node(actions.moves.item.fn { where = "next", what = "diag" }) Api.node.navigate.git.prev_skip_gitignored = wrap_node(actions.moves.item.fn({ where = "prev", what = "git", skip_gitignored = true }))
Api.node.navigate.diagnostics.prev = wrap_node(actions.moves.item.fn { where = "prev", what = "diag" }) Api.node.navigate.git.prev_recursive = wrap_node(actions.moves.item.fn({ where = "prev", what = "git", recurse = true }))
Api.node.navigate.opened.next = wrap_node(actions.moves.item.fn { where = "next", what = "opened" }) Api.node.navigate.diagnostics.next = wrap_node(actions.moves.item.fn({ where = "next", what = "diag" }))
Api.node.navigate.opened.prev = wrap_node(actions.moves.item.fn { where = "prev", what = "opened" }) 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.git.reload = wrap(actions.reloaders.reload_git) Api.git.reload = wrap_explorer("reload_git")
Api.events.subscribe = events.subscribe Api.events.subscribe = events.subscribe
Api.events.Event = events.Event Api.events.Event = events.Event
Api.live_filter.start = wrap(live_filter.start_filtering) Api.live_filter.start = wrap_explorer_member("live_filter", "start_filtering")
Api.live_filter.clear = wrap(live_filter.clear_filter) Api.live_filter.clear = wrap_explorer_member("live_filter", "clear_filter")
Api.marks.get = wrap_node(marks.get_mark) Api.marks.get = wrap_node(wrap_explorer_member("marks", "get"))
Api.marks.list = wrap(marks.get_marks) Api.marks.list = wrap_explorer_member("marks", "list")
Api.marks.toggle = wrap_node(marks.toggle_mark) Api.marks.toggle = wrap_node(wrap_explorer_member("marks", "toggle"))
Api.marks.clear = wrap(marks.clear_marks) Api.marks.clear = wrap_explorer_member("marks", "clear")
Api.marks.bulk.delete = wrap(marks_bulk_delete.bulk_delete) Api.marks.bulk.delete = wrap_explorer_member("marks", "bulk_delete")
Api.marks.bulk.trash = wrap(marks_bulk_trash.bulk_trash) Api.marks.bulk.trash = wrap_explorer_member("marks", "bulk_trash")
Api.marks.bulk.move = wrap(marks_bulk_move.bulk_move) Api.marks.bulk.move = wrap_explorer_member("marks", "bulk_move")
Api.marks.navigate.next = wrap(marks_navigation.next) Api.marks.navigate.next = wrap_explorer_member("marks", "navigate_next")
Api.marks.navigate.prev = wrap(marks_navigation.prev) Api.marks.navigate.prev = wrap_explorer_member("marks", "navigate_prev")
Api.marks.navigate.select = wrap(marks_navigation.select) Api.marks.navigate.select = wrap_explorer_member("marks", "navigate_select")
Api.config.mappings.get_keymap = wrap(keymap.get_keymap) Api.config.mappings.get_keymap = wrap(keymap.get_keymap)
Api.config.mappings.get_keymap_default = wrap(keymap.get_keymap_default) Api.config.mappings.get_keymap_default = wrap(keymap.get_keymap_default)
Api.config.mappings.default_on_attach = keymap.default_on_attach Api.config.mappings.default_on_attach = keymap.default_on_attach
Api.diagnostics.hi_test = wrap(appearance_diagnostics.hi_test)
Api.commands.get = wrap(function() Api.commands.get = wrap(function()
return require("nvim-tree.commands").get() return require("nvim-tree.commands").get()
end) end)

View File

@@ -0,0 +1,141 @@
local appearance = require("nvim-tree.appearance")
-- others with name and links less than this arbitrary value are short
local SHORT_LEN = 50
local M = {}
---@class HighlightDisplay 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 = {}
---@param group string nvim-tree highlight group name
---@return HighlightDisplay
function HighlightDisplay:new(group)
local o = {}
setmetatable(o, self)
self.__index = self
o.group = group
local concrete = o.group
-- maybe follow links
local links = {}
local link = vim.api.nvim_get_hl(0, { name = o.group }).link
while link do
table.insert(links, link)
concrete = link
link = vim.api.nvim_get_hl(0, { name = link }).link
end
o.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
o.def = res:gsub(".*xxx *", "")
else
o.def = ""
end
return o
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 })
vim.api.nvim_buf_add_highlight(bufnr, -1, self.group, l, 0, #self.group)
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
function M.hi_test()
-- 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:new(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:new(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
return M

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

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

@@ -0,0 +1,48 @@
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 table
---@return boolean
function M.is_modified(node)
return node
and M.config.modified.enable
and M._modified[node.absolute_path]
and (not node.nodes or M.config.modified.show_on_dirs)
and (not node.open or M.config.modified.show_on_open_dirs)
end
---A buffer exists for the node's absolute path
---@param node table
---@return boolean
function M.is_opened(node)
return node and vim.fn.bufloaded(node.absolute_path) > 0
end
---@param opts table
function M.setup(opts)
M.config = {
modified = opts.modified,
}
end
return M

View File

@@ -1,130 +0,0 @@
local M = {}
local function get_color_from_hl(hl_name, fallback)
local id = vim.api.nvim_get_hl_id_by_name(hl_name)
if not id then
return fallback
end
local foreground = vim.fn.synIDattr(vim.fn.synIDtrans(id), "fg")
if not foreground or foreground == "" then
return fallback
end
return foreground
end
local function get_colors()
return {
red = vim.g.terminal_color_1 or get_color_from_hl("Keyword", "Red"),
green = vim.g.terminal_color_2 or get_color_from_hl("Character", "Green"),
yellow = vim.g.terminal_color_3 or get_color_from_hl("PreProc", "Yellow"),
blue = vim.g.terminal_color_4 or get_color_from_hl("Include", "Blue"),
purple = vim.g.terminal_color_5 or get_color_from_hl("Define", "Purple"),
cyan = vim.g.terminal_color_6 or get_color_from_hl("Conditional", "Cyan"),
dark_red = vim.g.terminal_color_9 or get_color_from_hl("Keyword", "DarkRed"),
orange = vim.g.terminal_color_11 or get_color_from_hl("Number", "Orange"),
}
end
local function get_hl_groups()
local colors = get_colors()
return {
IndentMarker = { fg = "#8094b4" },
Symlink = { gui = "bold", fg = colors.cyan },
FolderIcon = { fg = "#8094b4" },
RootFolder = { fg = colors.purple },
ExecFile = { gui = "bold", fg = colors.green },
SpecialFile = { gui = "bold,underline", fg = colors.yellow },
ImageFile = { gui = "bold", fg = colors.purple },
OpenedFile = { gui = "bold", fg = colors.green },
ModifiedFile = { fg = colors.green },
GitDirty = { fg = colors.dark_red },
GitDeleted = { fg = colors.dark_red },
GitStaged = { fg = colors.green },
GitMerge = { fg = colors.orange },
GitRenamed = { fg = colors.purple },
GitNew = { fg = colors.yellow },
WindowPicker = { gui = "bold", fg = "#ededed", bg = "#4493c8" },
LiveFilterPrefix = { gui = "bold", fg = colors.purple },
LiveFilterValue = { gui = "bold", fg = "#fff" },
Bookmark = { fg = colors.green },
}
end
local function get_links()
return {
FolderName = "Directory",
EmptyFolderName = "Directory",
OpenedFolderName = "Directory",
SymlinkFolderName = "Directory",
OpenedFolderIcon = "NvimTreeFolderIcon",
ClosedFolderIcon = "NvimTreeFolderIcon",
OpenedFileIcon = "NvimTreeOpenedFile",
Normal = "Normal",
NormalFloat = "NormalFloat",
NormalNC = "NvimTreeNormal",
EndOfBuffer = "EndOfBuffer",
CursorLineNr = "CursorLineNr",
LineNr = "LineNr",
CursorLine = "CursorLine",
WinSeparator = "WinSeparator",
CursorColumn = "CursorColumn",
FileDirty = "NvimTreeGitDirty",
FileNew = "NvimTreeGitNew",
FileRenamed = "NvimTreeGitRenamed",
FileMerge = "NvimTreeGitMerge",
FileStaged = "NvimTreeGitStaged",
FileDeleted = "NvimTreeGitDeleted",
FileIgnored = "NvimTreeGitIgnored",
FolderDirty = "NvimTreeFileDirty",
FolderNew = "NvimTreeFileNew",
FolderRenamed = "NvimTreeFileRenamed",
FolderMerge = "NvimTreeFileMerge",
FolderStaged = "NvimTreeFileStaged",
FolderDeleted = "NvimTreeFileDeleted",
FolderIgnored = "NvimTreeFileIgnored",
LspDiagnosticsError = "DiagnosticError",
LspDiagnosticsWarning = "DiagnosticWarn",
LspDiagnosticsInformation = "DiagnosticInfo",
LspDiagnosticsHint = "DiagnosticHint",
LspDiagnosticsErrorText = "NvimTreeLspDiagnosticsError",
LspDiagnosticsWarningText = "NvimTreeLspDiagnosticsWarning",
LspDiagnosticsInformationText = "NvimTreeLspDiagnosticsInformation",
LspDiagnosticsHintText = "NvimTreeLspDiagnosticsHintFile",
LspDiagnosticsErrorFolderText = "NvimTreeLspDiagnosticsErrorText",
LspDiagnosticsWarningFolderText = "NvimTreeLspDiagnosticsWarningText",
LspDiagnosticsInformationFolderText = "NvimTreeLspDiagnosticsInformationText",
LspDiagnosticsHintFolderText = "NvimTreeLspDiagnosticsHintFileText",
Popup = "Normal",
GitIgnored = "Comment",
StatusLine = "StatusLine",
StatusLineNC = "StatusLineNC",
SignColumn = "NvimTreeNormal",
CutHL = "SpellBad",
CopiedHL = "SpellRare",
BookmarkHL = "SpellLocal",
}
end
function M.setup()
local highlight_groups = get_hl_groups()
for k, d in pairs(highlight_groups) do
local gui = d.gui and " gui=" .. d.gui or ""
local fg = d.fg and " guifg=" .. d.fg or ""
local bg = d.bg and " guibg=" .. d.bg or ""
vim.api.nvim_command("hi def NvimTree" .. k .. gui .. fg .. bg)
end
local links = get_links()
for k, d in pairs(links) do
vim.api.nvim_command("hi def link NvimTree" .. k .. " " .. d)
end
end
return M

View File

@@ -1,5 +1,5 @@
local api = require "nvim-tree.api" local api = require("nvim-tree.api")
local view = require "nvim-tree.view" local view = require("nvim-tree.view")
local M = {} local M = {}
@@ -12,7 +12,7 @@ local CMDS = {
complete = "dir", complete = "dir",
}, },
command = function(c) command = function(c)
api.tree.open { path = c.args } api.tree.open({ path = c.args })
end, end,
}, },
{ {
@@ -33,12 +33,12 @@ local CMDS = {
complete = "dir", complete = "dir",
}, },
command = function(c) command = function(c)
api.tree.toggle { api.tree.toggle({
find_file = false, find_file = false,
focus = true, focus = true,
path = c.args, path = c.args,
update_root = false, update_root = false,
} })
end, end,
}, },
{ {
@@ -79,11 +79,11 @@ local CMDS = {
bar = true, bar = true,
}, },
command = function(c) command = function(c)
api.tree.find_file { api.tree.find_file({
open = true, open = true,
focus = true, focus = true,
update_root = c.bang, update_root = c.bang,
} })
end, end,
}, },
{ {
@@ -95,12 +95,12 @@ local CMDS = {
complete = "dir", complete = "dir",
}, },
command = function(c) command = function(c)
api.tree.toggle { api.tree.toggle({
find_file = true, find_file = true,
focus = true, focus = true,
path = c.args, path = c.args,
update_root = c.bang, update_root = c.bang,
} })
end, end,
}, },
{ {
@@ -134,6 +134,13 @@ local CMDS = {
api.tree.collapse_all(true) api.tree.collapse_all(true)
end, end,
}, },
{
name = "NvimTreeHiTest",
opts = {
desc = "nvim-tree: highlight test",
},
command = api.diagnostics.hi_test,
},
} }
function M.get() function M.get()

View File

@@ -1,8 +1,6 @@
local events = require "nvim-tree.events" local events = require("nvim-tree.events")
local explorer = require "nvim-tree.explorer" local view = require("nvim-tree.view")
local live_filter = require "nvim-tree.live-filter" local log = require("nvim-tree.log")
local view = require "nvim-tree.view"
local log = require "nvim-tree.log"
local M = {} local M = {}
@@ -17,7 +15,7 @@ function M.init(foldername)
if TreeExplorer then if TreeExplorer then
TreeExplorer:destroy() TreeExplorer:destroy()
end end
TreeExplorer = explorer.Explorer.new(foldername) TreeExplorer = require("nvim-tree.explorer"):new(foldername)
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
@@ -45,7 +43,7 @@ function M.get_nodes_starting_line()
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,12 +1,10 @@
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 log = require "nvim-tree.log" local view = require("nvim-tree.view")
local log = require("nvim-tree.log")
local M = {} local M = {}
---TODO add "$VIMRUNTIME" to "workspace.library" and use the @enum instead of this integer
---@alias lsp.DiagnosticSeverity integer
---COC severity level strings to LSP severity levels ---COC severity level strings to LSP severity levels
---@enum COC_SEVERITY_LEVELS ---@enum COC_SEVERITY_LEVELS
local COC_SEVERITY_LEVELS = { local COC_SEVERITY_LEVELS = {
@@ -42,19 +40,21 @@ end
local function from_nvim_lsp() local function from_nvim_lsp()
local buffer_severity = {} local buffer_severity = {}
local is_disabled = false -- is_enabled is not present in all 0.10 builds/releases, see #2781
if vim.fn.has "nvim-0.9" == 1 then local is_enabled = false
is_disabled = vim.diagnostic.is_disabled() if vim.fn.has("nvim-0.10") == 1 and type(vim.diagnostic.is_enabled) == "function" then
is_enabled = vim.diagnostic.is_enabled()
elseif type(vim.diagnostic.is_disabled) == "function" then ---@diagnostic disable-line: deprecated
is_enabled = not vim.diagnostic.is_disabled() ---@diagnostic disable-line: deprecated
end end
if not is_disabled then if is_enabled then
for _, diagnostic in ipairs(vim.diagnostic.get(nil, { severity = M.severity })) do for _, diagnostic in ipairs(vim.diagnostic.get(nil, { severity = M.severity })) do
local buf = diagnostic.bufnr if diagnostic.severity and diagnostic.bufnr and vim.api.nvim_buf_is_valid(diagnostic.bufnr) then
if vim.api.nvim_buf_is_valid(buf) then local bufname = uniformize_path(vim.api.nvim_buf_get_name(diagnostic.bufnr))
local bufname = uniformize_path(vim.api.nvim_buf_get_name(buf)) if not buffer_severity[bufname] or diagnostic.severity < buffer_severity[bufname] then
local severity = diagnostic.severity buffer_severity[bufname] = diagnostic.severity
local highest_severity = buffer_severity[bufname] or severity end
buffer_severity[bufname] = math.min(highest_severity, severity)
end end
end end
end end
@@ -77,7 +77,7 @@ local function handle_coc_exception(err)
local notify = true local notify = true
-- avoid distractions on interrupts (CTRL-C) -- avoid distractions on interrupts (CTRL-C)
if err:find "Vim:Interrupt" or err:find "Keyboard interrupt" then if err:find("Vim:Interrupt") or err:find("Keyboard interrupt") then
notify = false notify = false
end end
@@ -100,7 +100,7 @@ local function from_coc()
end end
local ok, diagnostic_list = xpcall(function() local ok, diagnostic_list = xpcall(function()
return vim.fn.CocAction "diagnosticList" return vim.fn.CocAction("diagnosticList")
end, handle_coc_exception) end, handle_coc_exception)
if not ok or type(diagnostic_list) ~= "table" or vim.tbl_isempty(diagnostic_list) then if not ok or type(diagnostic_list) ~= "table" or vim.tbl_isempty(diagnostic_list) then
return {} return {}
@@ -152,21 +152,24 @@ function M.update()
return return
end end
utils.debounce("diagnostics", M.debounce_delay, function() utils.debounce("diagnostics", M.debounce_delay, function()
local profile = log.profile_start "diagnostics update" local profile = log.profile_start("diagnostics update")
if is_using_coc() then if is_using_coc() then
NODE_SEVERITIES = from_coc() NODE_SEVERITIES = from_coc()
else else
NODE_SEVERITIES = from_nvim_lsp() NODE_SEVERITIES = from_nvim_lsp()
end end
NODE_SEVERITIES_VERSION = NODE_SEVERITIES_VERSION + 1 NODE_SEVERITIES_VERSION = NODE_SEVERITIES_VERSION + 1
if log.enabled "diagnostics" then if log.enabled("diagnostics") then
for bufname, severity in pairs(NODE_SEVERITIES) do for bufname, severity in pairs(NODE_SEVERITIES) do
log.line("diagnostics", "Indexing bufname '%s' with severity %d", bufname, severity) log.line("diagnostics", "Indexing bufname '%s' with severity %d", bufname, severity)
end end
end end
log.profile_end(profile) log.profile_end(profile)
if view.is_buf_valid(view.get_bufnr()) then if view.is_buf_valid(view.get_bufnr()) then
require("nvim-tree.renderer").draw() local explorer = core.get_explorer()
if explorer then
explorer.renderer:draw()
end
end end
end) end)
end end

View File

@@ -12,9 +12,22 @@ M.HL_POSITION = {
---Setup options for "*_placement" ---Setup options for "*_placement"
---@enum ICON_PLACEMENT ---@enum ICON_PLACEMENT
M.ICON_PLACEMENT = { M.ICON_PLACEMENT = {
signcolumn = 0, none = 0,
before = 1, signcolumn = 1,
after = 2, before = 2,
after = 3,
right_align = 4,
}
---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 return M

View File

@@ -1,4 +1,4 @@
local notify = require "nvim-tree.notify" local notify = require("nvim-tree.notify")
local M = {} local M = {}

View File

@@ -1,101 +0,0 @@
local utils = require "nvim-tree.utils"
local builders = require "nvim-tree.explorer.node-builders"
local explorer_node = require "nvim-tree.explorer.node"
local git = require "nvim-tree.git"
local sorters = require "nvim-tree.explorer.sorters"
local filters = require "nvim-tree.explorer.filters"
local live_filter = require "nvim-tree.live-filter"
local log = require "nvim-tree.log"
local Watcher = require "nvim-tree.watcher"
local M = {}
---@param type_ string|nil
---@param cwd string
---@return any
local function get_type_from(type_, cwd)
return type_ or (vim.loop.fs_stat(cwd) or {}).type
end
---@param handle uv.uv_fs_t
---@param cwd string
---@param node Node
---@param git_status table
local function populate_children(handle, cwd, node, git_status)
local node_ignored = explorer_node.is_git_ignored(node)
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 }
local profile = log.profile_start("explore populate_children %s", abs)
t = get_type_from(t, abs)
if not filters.should_filter(abs, filter_status) and not nodes_by_path[abs] and Watcher.is_fs_event_capable(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
explorer_node.update_git_status(child, node_ignored, git_status)
end
end
log.profile_end(profile)
end
end
---@param node Node
---@param status table
---@return Node[]|nil
function M.explore(node, status)
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 init %s", node.absolute_path)
populate_children(handle, cwd, node, status)
local is_root = not node.parent
local child_folder_only = explorer_node.has_one_child_folder(node) and node.nodes[1]
if M.config.group_empty and not is_root and child_folder_only then
local child_cwd = child_folder_only.link_to or child_folder_only.absolute_path
local child_status = git.load_project_status(child_cwd)
node.group_next = child_folder_only
local ns = M.explore(child_folder_only, child_status)
node.nodes = ns or {}
log.profile_end(profile)
return ns
end
sorters.sort(node.nodes)
live_filter.apply_filter(node)
log.profile_end(profile)
return node.nodes
end
function M.setup(opts)
M.config = opts.renderer
end
return M

View File

@@ -1,15 +1,53 @@
local utils = require "nvim-tree.utils" local utils = require("nvim-tree.utils")
local marks = require "nvim-tree.marks" local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON
local M = { ---@class Filters to handle all opts.filters and related API
ignore_list = {}, ---@field config table hydrated user opts.filters
exclude_list = {}, ---@field private explorer Explorer
} ---@field private exclude_list string[] filters.exclude
---@field private ignore_list string[] filters.custom string table
---@field private custom_function (fun(absolute_path: string): boolean)|nil filters.custom function
local Filters = {}
---@param opts table user options
---@param explorer Explorer
---@return Filters
function Filters:new(opts, explorer)
local o = {
explorer = explorer,
ignore_list = {},
exclude_list = opts.filters.exclude,
custom_function = nil,
config = {
enable = opts.filters.enable,
filter_custom = true,
filter_dotfiles = opts.filters.dotfiles,
filter_git_ignored = opts.filters.git_ignored,
filter_git_clean = opts.filters.git_clean,
filter_no_buffer = opts.filters.no_buffer,
filter_no_bookmark = opts.filters.no_bookmark,
},
}
local custom_filter = opts.filters.custom
if type(custom_filter) == "function" then
o.custom_function = custom_filter
else
if custom_filter and #custom_filter > 0 then
for _, filter_name in pairs(custom_filter) do
o.ignore_list[filter_name] = true
end
end
end
setmetatable(o, self)
self.__index = self
return o
end
---@param path string ---@param path string
---@return boolean ---@return boolean
local function is_excluded(path) local function is_excluded(self, path)
for _, node in ipairs(M.exclude_list) do for _, node in ipairs(self.exclude_list) do
if path:match(node) then if path:match(node) then
return true return true
end end
@@ -21,7 +59,7 @@ end
---@param path string Absolute path ---@param path string Absolute path
---@param git_status table from prepare ---@param git_status table from prepare
---@return boolean ---@return boolean
local function git(path, git_status) local function git(self, path, git_status)
if type(git_status) ~= "table" or type(git_status.files) ~= "table" or type(git_status.dirs) ~= "table" then if type(git_status) ~= "table" or type(git_status.files) ~= "table" or type(git_status.dirs) ~= "table" then
return false return false
end end
@@ -32,12 +70,12 @@ local function git(path, git_status)
status = status or git_status.dirs.indirect[path] and git_status.dirs.indirect[path][1] status = status or git_status.dirs.indirect[path] and git_status.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.config.filter_git_ignored and status == "!!" then
return true return true
end end
-- filter clean -- filter clean
if M.config.filter_git_clean and not status then if self.config.filter_git_clean and not status then
return true return true
end end
@@ -47,16 +85,15 @@ end
---Check if the given path has no listed buffer ---Check if the given path has no listed buffer
---@param path string Absolute path ---@param path string Absolute path
---@param bufinfo table vim.fn.getbufinfo { buflisted = 1 } ---@param bufinfo table vim.fn.getbufinfo { buflisted = 1 }
---@param unloaded_bufnr number optional bufnr recently unloaded via BufUnload event
---@return boolean ---@return boolean
local function buf(path, bufinfo, unloaded_bufnr) local function buf(self, path, bufinfo)
if not M.config.filter_no_buffer or type(bufinfo) ~= "table" then if not self.config.filter_no_buffer or type(bufinfo) ~= "table" then
return false return false
end end
-- filter files with no open buffer and directories containing no open buffers -- filter files with no open buffer and directories containing no open buffers
for _, b in ipairs(bufinfo) do for _, b in ipairs(bufinfo) do
if b.name == path or b.name:find(path .. "/", 1, true) and b.bufnr ~= unloaded_bufnr then if b.name == path or b.name:find(path .. "/", 1, true) then
return false return false
end end
end end
@@ -66,36 +103,71 @@ end
---@param path string ---@param path string
---@return boolean ---@return boolean
local function dotfile(path) local function dotfile(self, path)
return M.config.filter_dotfiles and utils.path_basename(path):sub(1, 1) == "." return self.config.filter_dotfiles and utils.path_basename(path):sub(1, 1) == "."
end end
---@param path string ---@param path string
---@param bookmarks table<string, boolean> absolute paths bookmarked ---@param path_type string|nil filetype of path
local function bookmark(path, bookmarks) ---@param bookmarks table<string, string|nil> path, filetype table of bookmarked files
return M.config.filter_no_bookmark and not bookmarks[path] local function bookmark(self, path, path_type, bookmarks)
if not self.config.filter_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 end
---@param path string ---@param path string
---@return boolean ---@return boolean
local function custom(path) local function custom(self, path)
if not M.config.filter_custom then if not self.config.filter_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
@@ -105,26 +177,26 @@ end
---Prepare arguments for should_filter. This is done prior to should_filter for efficiency reasons. ---Prepare arguments for should_filter. This is done prior to should_filter for efficiency reasons.
---@param git_status table|nil optional results of git.load_project_status(...) ---@param git_status table|nil optional results of git.load_project_status(...)
---@param unloaded_bufnr number|nil optional bufnr recently unloaded via BufUnload event
---@return table ---@return table
--- git_status: reference --- git_status: reference
--- unloaded_bufnr: copy
--- bufinfo: empty unless no_buffer set: vim.fn.getbufinfo { buflisted = 1 } --- bufinfo: empty unless no_buffer set: vim.fn.getbufinfo { buflisted = 1 }
--- bookmarks: absolute paths to boolean --- bookmarks: absolute paths to boolean
function M.prepare(git_status, unloaded_bufnr) function Filters:prepare(git_status)
local status = { local status = {
git_status = git_status or {}, git_status = git_status or {},
unloaded_bufnr = unloaded_bufnr,
bufinfo = {}, bufinfo = {},
bookmarks = {}, bookmarks = {},
} }
if M.config.filter_no_buffer then if self.config.filter_no_buffer then
status.bufinfo = vim.fn.getbufinfo { buflisted = 1 } status.bufinfo = vim.fn.getbufinfo({ buflisted = 1 })
end end
for _, node in pairs(marks.get_marks()) do local explorer = require("nvim-tree.core").get_explorer()
status.bookmarks[node.absolute_path] = true 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
@@ -132,40 +204,53 @@ 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.config.enable 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 is_excluded(self, path) then
or dotfile(path) return false
or custom(path) end
or bookmark(path, status.bookmarks)
return git(self, path, status.git_status)
or buf(self, path, status.bufinfo)
or dotfile(self, path)
or custom(self, path)
or bookmark(self, 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.filters.git_ignored, ---@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.config.enable then
filter_no_bookmark = opts.filters.no_bookmark, return FILTER_REASON.none
} end
M.ignore_list = {} if is_excluded(self, path) then
M.exclude_list = opts.filters.exclude return FILTER_REASON.none
end
local custom_filter = opts.filters.custom if git(self, path, status.git_status) then
if custom_filter and #custom_filter > 0 then return FILTER_REASON.git
for _, filter_name in pairs(custom_filter) do elseif buf(self, path, status.bufinfo) then
M.ignore_list[filter_name] = true return FILTER_REASON.buf
end elseif dotfile(self, path) then
return FILTER_REASON.dotfile
elseif custom(self, path) then
return FILTER_REASON.custom
elseif bookmark(self, 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 return Filters

View File

@@ -1,42 +1,77 @@
local git = require "nvim-tree.git" local builders = require("nvim-tree.explorer.node-builders")
local watch = require "nvim-tree.explorer.watch" local git = require("nvim-tree.git")
local explorer_node = require "nvim-tree.explorer.node" local log = require("nvim-tree.log")
local notify = require("nvim-tree.notify")
local utils = require("nvim-tree.utils")
local view = require("nvim-tree.view")
local watch = require("nvim-tree.explorer.watch")
local explorer_node = require("nvim-tree.explorer.node")
local M = {} local Iterator = require("nvim-tree.iterators.node-iterator")
local NodeIterator = require("nvim-tree.iterators.node-iterator")
local Watcher = require("nvim-tree.watcher")
M.explore = require("nvim-tree.explorer.explore").explore local Filters = require("nvim-tree.explorer.filters")
M.reload = require("nvim-tree.explorer.reload").reload local Marks = require("nvim-tree.marks")
local LiveFilter = require("nvim-tree.explorer.live-filter")
local Sorters = require("nvim-tree.explorer.sorters")
local Clipboard = require("nvim-tree.actions.fs.clipboard")
local Renderer = require("nvim-tree.renderer")
local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON
local config
---@class Explorer ---@class Explorer
---@field opts table user options
---@field absolute_path string ---@field absolute_path string
---@field nodes Node[] ---@field nodes Node[]
---@field open boolean ---@field open boolean
---@field watcher Watcher|nil
---@field renderer Renderer
---@field filters Filters
---@field live_filter LiveFilter
---@field sorters Sorter
---@field marks Marks
---@field clipboard Clipboard
local Explorer = {} local Explorer = {}
Explorer.__index = Explorer
---@param cwd string|nil ---@param path string|nil
---@return Explorer ---@return Explorer|nil
function Explorer.new(cwd) function Explorer:new(path)
cwd = vim.loop.fs_realpath(cwd or vim.loop.cwd()) local err
---@class Explorer if path then
local explorer = setmetatable({ path, err = vim.loop.fs_realpath(path)
absolute_path = cwd, else
path, err = vim.loop.cwd()
end
if not path then
notify.error(err)
return
end
local o = {
opts = config,
absolute_path = path,
nodes = {}, nodes = {},
open = true, open = true,
}, Explorer) sorters = Sorters:new(config),
explorer.watcher = watch.create_watcher(explorer) }
explorer:_load(explorer)
return explorer
end
---@private setmetatable(o, self)
---@param node Node self.__index = self
function Explorer:_load(node)
local cwd = node.link_to or node.absolute_path o.watcher = watch.create_watcher(o)
local git_status = git.load_project_status(cwd) o.renderer = Renderer:new(config, o)
M.explore(node, git_status) o.filters = Filters:new(config, o)
o.live_filter = LiveFilter:new(config, o)
o.marks = Marks:new(config, o)
o.clipboard = Clipboard:new(config, o)
o:_load(o)
return o
end end
---@param node Node ---@param node Node
@@ -56,15 +91,397 @@ function Explorer:destroy()
iterate(self) iterate(self)
end end
function M.setup(opts) ---@param node Node
---@param git_status table|nil
function Explorer:reload(node, git_status)
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("reload %s", node.absolute_path)
local filter_status = self.filters:prepare(git_status)
if node.group_next then
node.nodes = { node.group_next }
node.group_next = nil
end
local remain_childs = {}
local node_ignored = explorer_node.is_git_ignored(node)
---@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 })
---@type uv.fs_stat.result|nil
local stat = vim.loop.fs_lstat(abs)
local filter_reason = self.filters:should_filter_as_reason(abs, stat, filter_status)
if filter_reason == FILTER_REASON.none then
remain_childs[abs] = true
-- Type must come from fs_stat and not fs_scandir_next to maintain sshfs compatibility
local t = stat and stat.type or nil
-- 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)
explorer_node.node_destroy(n)
nodes_by_path[abs] = nil
end
end
if not nodes_by_path[abs] then
local new_child = nil
if t == "directory" and vim.loop.fs_access(abs, "R") and Watcher.is_fs_event_capable(abs) then
new_child = builders.folder(node, abs, name, stat)
elseif t == "file" then
new_child = builders.file(node, abs, name, stat)
elseif t == "link" then
local link = builders.link(node, abs, name, stat)
if link.link_to ~= nil then
new_child = link
end
end
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 = builders.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
node.nodes = vim.tbl_map(
self:update_status(nodes_by_path, node_ignored, git_status),
vim.tbl_filter(function(n)
if remain_childs[n.absolute_path] then
return remain_childs[n.absolute_path]
else
explorer_node.node_destroy(n)
return false
end
end, node.nodes)
)
local is_root = not node.parent
local child_folder_only = explorer_node.has_one_child_folder(node) and node.nodes[1]
if config.renderer.group_empty and not is_root and child_folder_only then
node.group_next = child_folder_only
local ns = self:reload(child_folder_only, git_status)
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
---TODO #2837 #2871 move this and similar to node
---Refresh contents and git status for a single node
---@param node Node
---@param callback function
function Explorer:refresh_node(node, callback)
if type(node) ~= "table" then
callback()
end
local parent_node = utils.get_parent_of_group(node)
self:reload_and_get_git_project(node.absolute_path, function(toplevel, project)
self:reload(parent_node, project)
self:update_parent_statuses(parent_node, project, toplevel)
callback()
end)
end
---Refresh contents of all nodes to a path: actual directory and links.
---Groups will be expanded if needed.
---@param path string absolute path
function Explorer:refresh_parent_nodes_for_path(path)
local profile = log.profile_start("refresh_parent_nodes_for_path %s", path)
-- 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)
self:update_parent_statuses(node, project, toplevel)
end
log.profile_end(profile)
end
---@private
---@param node Node
function Explorer:_load(node)
local cwd = node.link_to or node.absolute_path
local git_status = git.load_project_status(cwd)
self:explore(node, git_status, self)
end
---@private
---@param nodes_by_path table
---@param node_ignored boolean
---@param status table|nil
---@return fun(node: Node): table
function Explorer:update_status(nodes_by_path, node_ignored, status)
return function(node)
if nodes_by_path[node.absolute_path] then
explorer_node.update_git_status(node, node_ignored, status)
end
return node
end
end
---TODO #2837 #2871 move this and similar to node
---@private
---@param path string
---@param callback fun(toplevel: string|nil, project: table|nil)
function Explorer:reload_and_get_git_project(path, callback)
local toplevel = git.get_toplevel(path)
git.reload_project(toplevel, path, function()
callback(toplevel, git.get_project(toplevel) or {})
end)
end
---TODO #2837 #2871 move this and similar to node
---@private
---@param node Node
---@param project table|nil
---@param root string|nil
function Explorer:update_parent_statuses(node, project, root)
while project and node do
-- step up to the containing project
if node.absolute_path == root then
-- stop at the top of the tree
if not node.parent then
break
end
root = git.get_toplevel(node.parent.absolute_path)
-- stop when no more projects
if not root then
break
end
-- update the containing project
project = git.get_project(root)
git.reload_project(root, node.absolute_path, nil)
end
-- update status
explorer_node.update_git_status(node, explorer_node.is_git_ignored(node.parent), project)
-- maybe parent
node = node.parent
end
end
---@private
---@param handle uv.uv_fs_t
---@param cwd string
---@param node Node
---@param git_status table
---@param parent Explorer
function Explorer:populate_children(handle, cwd, node, git_status, parent)
local node_ignored = explorer_node.is_git_ignored(node)
local nodes_by_path = utils.bool_record(node.nodes, "absolute_path")
local filter_status = parent.filters:prepare(git_status)
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)
---@type uv.fs_stat.result|nil
local stat = vim.loop.fs_lstat(abs)
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
-- Type must come from fs_stat and not fs_scandir_next to maintain sshfs compatibility
local t = stat and stat.type or nil
local child = nil
if t == "directory" and vim.loop.fs_access(abs, "R") then
child = builders.folder(node, abs, name, stat)
elseif t == "file" then
child = builders.file(node, abs, name, stat)
elseif t == "link" then
local link = builders.link(node, abs, name, stat)
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
explorer_node.update_git_status(child, node_ignored, git_status)
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
log.profile_end(profile)
end
end
end
---@private
---@param node Node
---@param status table
---@param parent Explorer
---@return Node[]|nil
function Explorer:explore(node, status, 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, status, parent)
local is_root = not node.parent
local child_folder_only = explorer_node.has_one_child_folder(node) and node.nodes[1]
if config.renderer.group_empty and not is_root and child_folder_only then
local child_cwd = child_folder_only.link_to or child_folder_only.absolute_path
local child_status = git.load_project_status(child_cwd)
node.group_next = child_folder_only
local ns = self:explore(child_folder_only, child_status, 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 table
function Explorer:refresh_nodes(projects)
Iterator.builder({ self })
:applier(function(n)
if n.nodes then
local toplevel = git.get_toplevel(n.cwd or n.link_to or n.absolute_path)
self:reload(n, 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()
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()
explorer_node.reload_node_status(self, projects)
self.renderer:draw()
event_running = false
end
function Explorer.setup(opts)
config = opts
require("nvim-tree.explorer.node").setup(opts) require("nvim-tree.explorer.node").setup(opts)
require("nvim-tree.explorer.explore").setup(opts)
require("nvim-tree.explorer.filters").setup(opts)
require("nvim-tree.explorer.sorters").setup(opts)
require("nvim-tree.explorer.reload").setup(opts)
require("nvim-tree.explorer.watch").setup(opts) require("nvim-tree.explorer.watch").setup(opts)
end end
M.Explorer = Explorer return Explorer
return M

View File

@@ -0,0 +1,223 @@
local view = require("nvim-tree.view")
local utils = require("nvim-tree.utils")
local Iterator = require("nvim-tree.iterators.node-iterator")
---@class LiveFilter
---@field explorer Explorer
---@field prefix string
---@field always_show_folders boolean
---@field filter string
local LiveFilter = {}
---@param opts table
---@param explorer Explorer
function LiveFilter:new(opts, explorer)
local o = {
explorer = explorer,
prefix = opts.live_filter.prefix,
always_show_folders = opts.live_filter.always_show_folders,
filter = nil,
}
setmetatable(o, self)
self.__index = self
return o
end
---@param node_ Node|nil
local function reset_filter(self, node_)
node_ = node_ or self.explorer
if node_ == nil then
return
end
node_.hidden_stats = vim.tbl_deep_extend("force", node_.hidden_stats or {}, {
live_filter = 0,
})
Iterator.builder(node_.nodes)
:hidden()
:applier(function(node)
node.hidden = false
node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, {
live_filter = 0,
})
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.config.enable 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_ Node|nil
function LiveFilter:apply_filter(node_)
if not self.filter or self.filter == "" then
reset_filter(self, 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
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 = require("nvim-tree.lib").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 = require("nvim-tree.lib").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,20 +1,21 @@
local utils = require "nvim-tree.utils" local utils = require("nvim-tree.utils")
local watch = require "nvim-tree.explorer.watch" local watch = require("nvim-tree.explorer.watch")
local M = {} local M = {}
---@param parent Node ---@param parent Node
---@param absolute_path string ---@param absolute_path string
---@param name string ---@param name string
---@param fs_stat uv.fs_stat.result|nil
---@return Node ---@return Node
function M.folder(parent, absolute_path, name) function M.folder(parent, absolute_path, name, fs_stat)
local handle = vim.loop.fs_scandir(absolute_path) local handle = vim.loop.fs_scandir(absolute_path)
local has_children = handle and vim.loop.fs_scandir_next(handle) ~= nil local has_children = handle and vim.loop.fs_scandir_next(handle) ~= nil
local node = { local node = {
type = "directory", type = "directory",
absolute_path = absolute_path, absolute_path = absolute_path,
fs_stat = vim.loop.fs_stat(absolute_path), fs_stat = fs_stat,
group_next = nil, -- If node is grouped, this points to the next child dir/link node group_next = nil, -- If node is grouped, this points to the next child dir/link node
has_children = has_children, has_children = has_children,
name = name, name = name,
@@ -43,8 +44,9 @@ end
---@param parent Node ---@param parent Node
---@param absolute_path string ---@param absolute_path string
---@param name string ---@param name string
---@param fs_stat uv.fs_stat.result|nil
---@return Node ---@return Node
function M.file(parent, absolute_path, name) function M.file(parent, absolute_path, name, fs_stat)
local ext = string.match(name, ".?[^.]+%.(.*)") or "" local ext = string.match(name, ".?[^.]+%.(.*)") or ""
return { return {
@@ -52,7 +54,7 @@ function M.file(parent, absolute_path, name)
absolute_path = absolute_path, absolute_path = absolute_path,
executable = M.is_executable(absolute_path), executable = M.is_executable(absolute_path),
extension = ext, extension = ext,
fs_stat = vim.loop.fs_stat(absolute_path), fs_stat = fs_stat,
name = name, name = name,
parent = parent, parent = parent,
} }
@@ -66,15 +68,16 @@ end
---@param parent Node ---@param parent Node
---@param absolute_path string ---@param absolute_path string
---@param name string ---@param name string
---@param fs_stat uv.fs_stat.result|nil
---@return Node ---@return Node
function M.link(parent, absolute_path, name) function M.link(parent, absolute_path, name, fs_stat)
--- 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. --- 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 link_to = vim.loop.fs_realpath(absolute_path)
local open, nodes, has_children local open, nodes, has_children
local is_dir_link = (link_to ~= nil) and vim.loop.fs_stat(link_to).type == "directory" local is_dir_link = (link_to ~= nil) and vim.loop.fs_stat(link_to).type == "directory"
if is_dir_link then if is_dir_link and link_to then
local handle = vim.loop.fs_scandir(link_to) local handle = vim.loop.fs_scandir(link_to)
has_children = handle and vim.loop.fs_scandir_next(handle) ~= nil has_children = handle and vim.loop.fs_scandir_next(handle) ~= nil
open = false open = false
@@ -84,7 +87,7 @@ function M.link(parent, absolute_path, name)
local node = { local node = {
type = "link", type = "link",
absolute_path = absolute_path, absolute_path = absolute_path,
fs_stat = vim.loop.fs_stat(absolute_path), fs_stat = fs_stat,
group_next = nil, -- If node is grouped, this points to the next child dir/link node group_next = nil, -- If node is grouped, this points to the next child dir/link node
has_children = has_children, has_children = has_children,
link_to = link_to, link_to = link_to,

View File

@@ -1,3 +1,5 @@
local git = {} -- circular dependencies
local M = {} local M = {}
---@class GitStatus ---@class GitStatus
@@ -29,7 +31,7 @@ end
---@param absolute_path string ---@param absolute_path string
---@return GitStatus ---@return GitStatus
local function get_git_status(parent_ignored, status, absolute_path) local function get_git_status(parent_ignored, status, absolute_path)
local file_status = parent_ignored and "!!" or status.files and status.files[absolute_path] local file_status = parent_ignored and "!!" or (status and status.files and status.files[absolute_path])
return { file = file_status } return { file = file_status }
end end
@@ -122,12 +124,42 @@ function M.get_git_status(node)
end end
end end
---@param parent_node Node|nil
---@param projects table
function M.reload_node_status(parent_node, projects)
if parent_node == nil then
return
end
local toplevel = git.get_toplevel(parent_node.absolute_path)
local status = projects[toplevel] or {}
for _, node in ipairs(parent_node.nodes) do
M.update_git_status(node, M.is_git_ignored(parent_node), status)
if node.nodes and #node.nodes > 0 then
M.reload_node_status(node, projects)
end
end
end
---@param node Node ---@param node Node
---@return boolean ---@return boolean
function M.is_git_ignored(node) function M.is_git_ignored(node)
return node and node.git_status ~= nil and node.git_status.file == "!!" return node and node.git_status ~= nil and node.git_status.file == "!!"
end end
---@param node Node
---@return boolean
function M.is_dotfile(node)
if node == nil then
return false
end
if node.is_dot or (node.name and (node.name:sub(1, 1) == ".")) or M.is_dotfile(node.parent) then
node.is_dot = true
return true
end
return false
end
---@param node Node ---@param node Node
function M.node_destroy(node) function M.node_destroy(node)
if not node then if not node then
@@ -144,6 +176,8 @@ function M.setup(opts)
M.config = { M.config = {
git = opts.git, git = opts.git,
} }
git = require("nvim-tree.git")
end end
return M return M

View File

@@ -1,240 +0,0 @@
local utils = require "nvim-tree.utils"
local builders = require "nvim-tree.explorer.node-builders"
local explorer_node = require "nvim-tree.explorer.node"
local filters = require "nvim-tree.explorer.filters"
local sorters = require "nvim-tree.explorer.sorters"
local live_filter = require "nvim-tree.live-filter"
local git = require "nvim-tree.git"
local log = require "nvim-tree.log"
local NodeIterator = require "nvim-tree.iterators.node-iterator"
local Watcher = require "nvim-tree.watcher"
local M = {}
---@param nodes_by_path table
---@param node_ignored boolean
---@param status table
---@return fun(node: Node): table
local function update_status(nodes_by_path, node_ignored, status)
return function(node)
if nodes_by_path[node.absolute_path] then
explorer_node.update_git_status(node, node_ignored, status)
end
return node
end
end
---@param path string
---@param callback fun(toplevel: string|nil, project: table|nil)
local function reload_and_get_git_project(path, callback)
local toplevel = git.get_toplevel(path)
git.reload_project(toplevel, path, function()
callback(toplevel, git.get_project(toplevel) or {})
end)
end
---@param node Node
---@param project table|nil
---@param root string|nil
local function update_parent_statuses(node, project, root)
while project and node do
-- step up to the containing project
if node.absolute_path == root then
-- stop at the top of the tree
if not node.parent then
break
end
root = git.get_toplevel(node.parent.absolute_path)
-- stop when no more projects
if not root then
break
end
-- update the containing project
project = git.get_project(root)
git.reload_project(root, node.absolute_path, nil)
end
-- update status
explorer_node.update_git_status(node, explorer_node.is_git_ignored(node.parent), project)
-- maybe parent
node = node.parent
end
end
---@param node Node
---@param git_status table
---@param unloaded_bufnr number|nil
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 not handle then
return
end
local profile = 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 = explorer_node.is_git_ignored(node)
local nodes_by_path = utils.key_by(node.nodes, "absolute_path")
while true do
local name, t = vim.loop.fs_scandir_next(handle, cwd)
if 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)
explorer_node.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") and Watcher.is_fs_event_capable(abs) 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(abs)
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
explorer_node.node_destroy(n)
return false
end
end, node.nodes)
)
local is_root = not node.parent
local child_folder_only = explorer_node.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(profile)
return ns
end
sorters.sort(node.nodes)
live_filter.apply_filter(node)
log.profile_end(profile)
return node.nodes
end
---Refresh contents and git status for a single node
---@param node Node
---@param callback function
function M.refresh_node(node, callback)
if type(node) ~= "table" then
callback()
end
local parent_node = utils.get_parent_of_group(node)
reload_and_get_git_project(node.absolute_path, function(toplevel, project)
require("nvim-tree.explorer.reload").reload(parent_node, project)
update_parent_statuses(parent_node, project, toplevel)
callback()
end)
end
---Refresh contents of all nodes to a path: actual directory and links.
---Groups will be expanded if needed.
---@param path string absolute path
function M.refresh_parent_nodes_for_path(path)
local explorer = require("nvim-tree.core").get_explorer()
if not explorer then
return
end
local profile = log.profile_start("refresh_parent_nodes_for_path %s", path)
-- collect parent nodes from the top down
local parent_nodes = {}
NodeIterator.builder({ explorer })
: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 {}
M.reload(node, project)
update_parent_statuses(node, project, toplevel)
end
log.profile_end(profile)
end
function M.setup(opts)
M.config = opts.renderer
end
return M

View File

@@ -1,12 +1,27 @@
local M = {}
local C = {} local C = {}
---@class Sorter
local Sorter = {}
function Sorter:new(opts)
local o = {}
setmetatable(o, self)
self.__index = self
o.config = vim.deepcopy(opts.sort)
if type(o.config.sorter) == "function" then
o.user = o.config.sorter
end
return o
end
--- Predefined comparator, defaulting to name --- Predefined comparator, defaulting to name
---@param sorter string as per options ---@param sorter string as per options
---@return function ---@return function
local function get_comparator(sorter) function Sorter:get_comparator(sorter)
return C[sorter] or C.name return function(a, b)
return (C[sorter] or C.name)(a, b, self.config)
end
end end
---Create a shallow copy of a portion of a list. ---Create a shallow copy of a portion of a list.
@@ -27,17 +42,17 @@ end
---@param a Node ---@param a Node
---@param b Node ---@param b Node
---@return boolean|nil ---@return boolean|nil
local function folders_or_files_first(a, b) local function folders_or_files_first(a, b, cfg)
if not (M.config.sort.folders_first or M.config.sort.files_first) then if not (cfg.folders_first or cfg.files_first) then
return return
end end
if not a.nodes and b.nodes then if not a.nodes and b.nodes then
-- file <> folder -- file <> folder
return M.config.sort.files_first return cfg.files_first
elseif a.nodes and not b.nodes then elseif a.nodes and not b.nodes then
-- folder <> file -- folder <> file
return not M.config.sort.files_first return not cfg.files_first
end end
end end
@@ -97,8 +112,8 @@ end
---Perform a merge sort using sorter option. ---Perform a merge sort using sorter option.
---@param t table nodes ---@param t table nodes
function M.sort(t) function Sorter:sort(t)
if C.user then if self.user then
local t_user = {} local t_user = {}
local origin_index = {} local origin_index = {}
@@ -107,7 +122,7 @@ function M.sort(t)
absolute_path = n.absolute_path, absolute_path = n.absolute_path,
executable = n.executable, executable = n.executable,
extension = n.extension, extension = n.extension,
filetype = vim.filetype.match { filename = n.name }, filetype = vim.filetype.match({ filename = n.name }),
link_to = n.link_to, link_to = n.link_to,
name = n.name, name = n.name,
type = n.type, type = n.type,
@@ -115,9 +130,9 @@ function M.sort(t)
table.insert(origin_index, n) table.insert(origin_index, n)
end end
local predefined = C.user(t_user) local predefined = self.user(t_user)
if predefined then if predefined then
split_merge(t, 1, #t, get_comparator(predefined)) split_merge(t, 1, #t, self:get_comparator(predefined))
return return
end end
@@ -142,7 +157,7 @@ function M.sort(t)
split_merge(t, 1, #t, mini_comparator) -- sort by user order split_merge(t, 1, #t, mini_comparator) -- sort by user order
else else
split_merge(t, 1, #t, get_comparator(M.config.sort.sorter)) split_merge(t, 1, #t, self:get_comparator(self.config.sorter))
end end
end end
@@ -150,12 +165,12 @@ end
---@param b Node ---@param b Node
---@param ignorecase boolean|nil ---@param ignorecase boolean|nil
---@return boolean ---@return boolean
local function node_comparator_name_ignorecase_or_not(a, b, ignorecase) local function node_comparator_name_ignorecase_or_not(a, b, ignorecase, cfg)
if not (a and b) then if not (a and b) then
return true return true
end end
local early_return = folders_or_files_first(a, b) local early_return = folders_or_files_first(a, b, cfg)
if early_return ~= nil then if early_return ~= nil then
return early_return return early_return
end end
@@ -167,20 +182,20 @@ local function node_comparator_name_ignorecase_or_not(a, b, ignorecase)
end end
end end
function C.case_sensitive(a, b) function C.case_sensitive(a, b, cfg)
return node_comparator_name_ignorecase_or_not(a, b, false) return node_comparator_name_ignorecase_or_not(a, b, false, cfg)
end end
function C.name(a, b) function C.name(a, b, cfg)
return node_comparator_name_ignorecase_or_not(a, b, true) return node_comparator_name_ignorecase_or_not(a, b, true, cfg)
end end
function C.modification_time(a, b) function C.modification_time(a, b, cfg)
if not (a and b) then if not (a and b) then
return true return true
end end
local early_return = folders_or_files_first(a, b) local early_return = folders_or_files_first(a, b, cfg)
if early_return ~= nil then if early_return ~= nil then
return early_return return early_return
end end
@@ -199,17 +214,17 @@ function C.modification_time(a, b)
return last_modified_b <= last_modified_a return last_modified_b <= last_modified_a
end end
function C.suffix(a, b) function C.suffix(a, b, cfg)
if not (a and b) then if not (a and b) then
return true return true
end end
-- directories go first -- directories go first
local early_return = folders_or_files_first(a, b) local early_return = folders_or_files_first(a, b, cfg)
if early_return ~= nil then if early_return ~= nil then
return early_return return early_return
elseif a.nodes and b.nodes then elseif a.nodes and b.nodes then
return C.name(a, b) return C.name(a, b, cfg)
end end
-- dotfiles go second -- dotfiles go second
@@ -218,19 +233,19 @@ function C.suffix(a, b)
elseif a.name:sub(1, 1) ~= "." and b.name:sub(1, 1) == "." then elseif a.name:sub(1, 1) ~= "." and b.name:sub(1, 1) == "." then
return false return false
elseif a.name:sub(1, 1) == "." and b.name:sub(1, 1) == "." then elseif a.name:sub(1, 1) == "." and b.name:sub(1, 1) == "." then
return C.name(a, b) return C.name(a, b, cfg)
end end
-- unsuffixed go third -- unsuffixed go third
local a_suffix_ndx = a.name:find "%.%w+$" local a_suffix_ndx = a.name:find("%.%w+$")
local b_suffix_ndx = b.name:find "%.%w+$" local b_suffix_ndx = b.name:find("%.%w+$")
if not a_suffix_ndx and b_suffix_ndx then if not a_suffix_ndx and b_suffix_ndx then
return true return true
elseif a_suffix_ndx and not b_suffix_ndx then elseif a_suffix_ndx and not b_suffix_ndx then
return false return false
elseif not (a_suffix_ndx and b_suffix_ndx) then elseif not (a_suffix_ndx and b_suffix_ndx) then
return C.name(a, b) return C.name(a, b, cfg)
end end
-- finally, compare by suffixes -- finally, compare by suffixes
@@ -242,18 +257,18 @@ function C.suffix(a, b)
elseif not a_suffix and b_suffix then elseif not a_suffix and b_suffix then
return false return false
elseif a_suffix:lower() == b_suffix:lower() then elseif a_suffix:lower() == b_suffix:lower() then
return C.name(a, b) return C.name(a, b, cfg)
end end
return a_suffix:lower() < b_suffix:lower() return a_suffix:lower() < b_suffix:lower()
end end
function C.extension(a, b) function C.extension(a, b, cfg)
if not (a and b) then if not (a and b) then
return true return true
end end
local early_return = folders_or_files_first(a, b) local early_return = folders_or_files_first(a, b, cfg)
if early_return ~= nil then if early_return ~= nil then
return early_return return early_return
end end
@@ -267,18 +282,18 @@ function C.extension(a, b)
local a_ext = (a.extension or ""):lower() local a_ext = (a.extension or ""):lower()
local b_ext = (b.extension or ""):lower() local b_ext = (b.extension or ""):lower()
if a_ext == b_ext then if a_ext == b_ext then
return C.name(a, b) return C.name(a, b, cfg)
end end
return a_ext < b_ext return a_ext < b_ext
end end
function C.filetype(a, b) function C.filetype(a, b, cfg)
local a_ft = vim.filetype.match { filename = a.name } local a_ft = vim.filetype.match({ filename = a.name })
local b_ft = vim.filetype.match { filename = b.name } local b_ft = vim.filetype.match({ filename = b.name })
-- directories first -- directories first
local early_return = folders_or_files_first(a, b) local early_return = folders_or_files_first(a, b, cfg)
if early_return ~= nil then if early_return ~= nil then
return early_return return early_return
end end
@@ -292,19 +307,10 @@ function C.filetype(a, b)
-- same filetype or both nil, sort by name -- same filetype or both nil, sort by name
if a_ft == b_ft then if a_ft == b_ft then
return C.name(a, b) return C.name(a, b, cfg)
end end
return a_ft < b_ft return a_ft < b_ft
end end
function M.setup(opts) return Sorter
M.config = {}
M.config.sort = opts.sort
if type(M.config.sort.sorter) == "function" then
C.user = M.config.sort.sorter
end
end
return M

View File

@@ -1,5 +1,5 @@
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 Watcher = require("nvim-tree.watcher").Watcher local Watcher = require("nvim-tree.watcher").Watcher
local M = { local M = {
@@ -40,10 +40,14 @@ local function is_folder_ignored(path)
end end
end end
for _, ignore_dir in ipairs(M.config.filesystem_watchers.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
@@ -72,9 +76,12 @@ function M.create_watcher(node)
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, function() local explorer = require("nvim-tree.core").get_explorer()
require("nvim-tree.renderer").draw() if explorer then
end) explorer:refresh_node(node, function()
explorer.renderer:draw()
end)
end
end) end)
end end

View File

@@ -1,10 +1,10 @@
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 Runner = 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 explorer_node = require "nvim-tree.explorer.node" local explorer_node = require("nvim-tree.explorer.node")
local M = { local M = {
config = {}, config = {},
@@ -23,10 +23,10 @@ local M = {
-- 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
} }
---@param toplevel string|nil ---@param toplevel string|nil
@@ -136,6 +136,10 @@ end
---@param path string absolute ---@param path string absolute
---@return string|nil ---@return string|nil
function M.get_toplevel(path) 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
@@ -166,12 +170,18 @@ function M.get_toplevel(path)
if not toplevel or not git_dir then if not toplevel or not git_dir then
return nil return nil
end end
local toplevel_norm = vim.fn.fnamemodify(toplevel, ":p")
-- ignore disabled paths -- ignore disabled paths
for _, disabled_for_dir in ipairs(M.config.git.disable_for_dirs) do if type(M.config.git.disable_for_dirs) == "table" then
local toplevel_norm = vim.fn.fnamemodify(toplevel, ":p") for _, disabled_for_dir in ipairs(M.config.git.disable_for_dirs) do
local disabled_norm = vim.fn.fnamemodify(disabled_for_dir, ":p") local disabled_norm = vim.fn.fnamemodify(disabled_for_dir, ":p")
if toplevel_norm == disabled_norm then 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 return nil
end end
end end
@@ -206,7 +216,10 @@ local function reload_tree_at(toplevel)
end) end)
:iterate() :iterate()
require("nvim-tree.renderer").draw() local explorer = require("nvim-tree.core").get_explorer()
if explorer then
explorer.renderer:draw()
end
end) end)
end end
@@ -230,12 +243,12 @@ function M.load_project_status(path)
return status return status
end end
local git_status = Runner.run { local git_status = Runner.run({
toplevel = toplevel, toplevel = toplevel,
list_untracked = git_utils.should_show_untracked(toplevel), 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
@@ -251,7 +264,7 @@ function M.load_project_status(path)
end) end)
end end
local git_dir = vim.env.GIT_DIR or M._git_dirs_by_toplevel[toplevel] or utils.path_join { toplevel, ".git" } local git_dir = vim.env.GIT_DIR or M._git_dirs_by_toplevel[toplevel] or utils.path_join({ toplevel, ".git" })
watcher = Watcher:new(git_dir, WATCHED_FILES, callback, { watcher = Watcher:new(git_dir, WATCHED_FILES, callback, {
toplevel = toplevel, toplevel = toplevel,
}) })

View File

@@ -1,11 +1,6 @@
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 notify = require("nvim-tree.notify")
-- TODO add "${3rd}/luv/library" to "workspace.library"
---@class uv.uv_handle_t: table
---@class uv.uv_stream_t: uv.uv_handle_t
---@class uv.uv_pipe_t: uv.uv_stream_t
---@class Runner ---@class Runner
local Runner = {} local Runner = {}
@@ -23,11 +18,11 @@ function Runner:_parse_status_output(status, path)
end end
-- 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.toplevel, path })] = status self.output[utils.path_remove_trailing(utils.path_join({ self.toplevel, path }))] = status
end end
end end
@@ -40,7 +35,7 @@ function Runner:_handle_incoming_data(prev_output, incoming)
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
@@ -62,7 +57,7 @@ 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
@@ -84,7 +79,7 @@ end
---@param output string ---@param output string
function Runner:_log_raw_output(output) function Runner:_log_raw_output(output)
if log.enabled "git" and output and type(output) == "string" then 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

View File

@@ -1,5 +1,5 @@
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 M = { local M = {
use_cygpath = false, use_cygpath = false,
@@ -21,30 +21,30 @@ function M.get_toplevel(cwd)
log.raw("git", out) log.raw("git", out)
log.profile_end(profile) log.profile_end(profile)
if vim.v.shell_error ~= 0 or not out or #out == 0 or out:match "fatal" then if vim.v.shell_error ~= 0 or not out or #out == 0 or out:match("fatal") then
return nil, nil return nil, nil
end end
local toplevel, git_dir = out:match "([^\n]+)\n+([^\n]+)" local toplevel, git_dir = out:match("([^\n]+)\n+([^\n]+)")
if not toplevel then if not toplevel then
return nil, nil return nil, nil
end end
if not git_dir then if not git_dir then
git_dir = utils.path_join { toplevel, ".git" } 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
-- cygpath calls must in array format to avoid shell compatibility issues -- cygpath calls must in array format to avoid shell compatibility issues
if M.use_cygpath then if M.use_cygpath then
toplevel = vim.fn.system { "cygpath", "-w", toplevel } toplevel = vim.fn.system({ "cygpath", "-w", toplevel })
if vim.v.shell_error ~= 0 then if vim.v.shell_error ~= 0 then
return nil, nil return nil, nil
end end
-- remove trailing newline(\n) character added by vim.fn.system -- remove trailing newline(\n) character added by vim.fn.system
toplevel = toplevel:gsub("\n", "") toplevel = toplevel:gsub("\n", "")
git_dir = vim.fn.system { "cygpath", "-w", git_dir } git_dir = vim.fn.system({ "cygpath", "-w", git_dir })
if vim.v.shell_error ~= 0 then if vim.v.shell_error ~= 0 then
return nil, nil return nil, nil
end end
@@ -128,7 +128,7 @@ end
function M.setup(opts) function M.setup(opts)
if opts.git.cygwin_support then if opts.git.cygwin_support then
M.use_cygpath = vim.fn.executable "cygpath" == 1 M.use_cygpath = vim.fn.executable("cygpath") == 1
end end
end end

View File

@@ -1,4 +1,5 @@
local keymap = require "nvim-tree.keymap" local keymap = require("nvim-tree.keymap")
local api = {} -- circular dependency
local PAT_MOUSE = "^<.*Mouse" local PAT_MOUSE = "^<.*Mouse"
local PAT_CTRL = "^<C%-" local PAT_CTRL = "^<C%-"
@@ -26,12 +27,12 @@ local function tidy_lhs(lhs)
lhs = lhs:gsub("^<lt>", "<") lhs = lhs:gsub("^<lt>", "<")
-- shorten ctrls -- shorten ctrls
if lhs:lower():match "^<ctrl%-" then if lhs:lower():match("^<ctrl%-") then
lhs = lhs:lower():gsub("^<ctrl%-", "<C%-") lhs = lhs:lower():gsub("^<ctrl%-", "<C%-")
end end
-- uppercase ctrls -- uppercase ctrls
if lhs:lower():match "^<c%-" then if lhs:lower():match("^<c%-") then
lhs = lhs:upper() lhs = lhs:upper()
end end
@@ -79,18 +80,19 @@ local function sort_lhs(a, b)
end end
--- Compute all lines for the buffer --- Compute all lines for the buffer
---@param map table keymap.get_keymap
---@return table strings of text ---@return table strings of text
---@return table arrays of arguments 3-6 for nvim_buf_add_highlight() ---@return table arrays of arguments 3-6 for nvim_buf_add_highlight()
---@return number maximum length of text ---@return number maximum length of text
local function compute() local function compute(map)
local head_lhs = "nvim-tree mappings" local head_lhs = "nvim-tree mappings"
local head_rhs1 = "exit: q" local head_rhs1 = "exit: q"
local head_rhs2 = string.format("sort by %s: s", M.config.sort_by == "key" and "description" or "keymap") 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 -- formatted lhs and desc from active keymap
local mappings = vim.tbl_map(function(map) local mappings = vim.tbl_map(function(m)
return { lhs = tidy_lhs(map.lhs), desc = tidy_desc(map.desc) } return { lhs = tidy_lhs(m.lhs), desc = tidy_desc(m.desc) }
end, keymap.get_keymap()) end, map)
-- sorter function for mappings -- sorter function for mappings
local sort_fn local sort_fn
@@ -119,14 +121,20 @@ local function compute()
-- increase desc if lines are shorter than the header -- increase desc if lines are shorter than the header
max_desc = math.max(max_desc, #head_lhs + #head_rhs1 - max_lhs) max_desc = math.max(max_desc, #head_lhs + #head_rhs1 - max_lhs)
-- header, not padded -- header text, not padded
local hl = { { "NvimTreeRootFolder", 0, 0, #head_lhs } }
local lines = { local lines = {
head_lhs .. string.rep(" ", max_desc + max_lhs - #head_lhs - #head_rhs1 + 2) .. head_rhs1, 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, string.rep(" ", max_desc + max_lhs - #head_rhs2 + 2) .. head_rhs2,
} }
local width = #lines[1] local width = #lines[1]
-- header highlight, assume one character keys
local hl = {
{ "NvimTreeFolderName", 0, 0, #head_lhs },
{ "NvimTreeFolderName", 0, width - 1, width },
{ "NvimTreeFolderName", 1, width - 1, width },
}
-- mappings, left padded 1 -- mappings, left padded 1
local fmt = string.format(" %%-%ds %%-%ds", max_lhs, max_desc) local fmt = string.format(" %%-%ds %%-%ds", max_lhs, max_desc)
for i, l in ipairs(mappings) do for i, l in ipairs(mappings) do
@@ -159,15 +167,23 @@ local function open()
-- close existing, shouldn't be necessary -- close existing, shouldn't be necessary
close() close()
-- fetch all mappings
local map = keymap.get_keymap()
-- text and highlight -- text and highlight
local lines, hl, width = compute() local lines, hl, width = compute(map)
-- create the buffer -- create the buffer
M.bufnr = vim.api.nvim_create_buf(false, true) M.bufnr = vim.api.nvim_create_buf(false, true)
-- populate it -- populate it
vim.api.nvim_buf_set_lines(M.bufnr, 0, -1, false, lines) vim.api.nvim_buf_set_lines(M.bufnr, 0, -1, false, lines)
vim.api.nvim_buf_set_option(M.bufnr, "modifiable", false)
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 -- highlight it
for _, h in ipairs(hl) do for _, h in ipairs(hl) do
@@ -195,12 +211,21 @@ local function open()
open() open()
end end
local keymaps = { -- hardcoded
local help_keymaps = {
q = { fn = close, desc = "nvim-tree: exit help" }, 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" }, s = { fn = toggle_sort, desc = "nvim-tree: toggle sorting method" },
} }
for k, v in pairs(keymaps) do -- 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, { vim.keymap.set("n", k, v.fn, {
desc = v.desc, desc = v.desc,
buffer = M.bufnr, buffer = M.bufnr,
@@ -229,6 +254,8 @@ end
function M.setup(opts) function M.setup(opts)
M.config.cursorline = opts.view.cursorline M.config.cursorline = opts.view.cursorline
M.config.sort_by = opts.help.sort_by M.config.sort_by = opts.help.sort_by
api = require("nvim-tree.api")
end end
return M return M

View File

@@ -19,21 +19,40 @@ local function generate_keymap(fn)
return keymap return keymap
end end
-- stylua: ignore start ---@return table
function M.get_keymap()
return generate_keymap(M.on_attach)
end
---@return table
function M.get_keymap_default()
return generate_keymap(M.default_on_attach)
end
function M.setup(opts)
if type(opts.on_attach) ~= "function" then
M.on_attach = M.default_on_attach
else
M.on_attach = opts.on_attach
end
end
---@param bufnr integer ---@param bufnr integer
function M.default_on_attach(bufnr) function M.default_on_attach(bufnr)
local api = require('nvim-tree.api') local api = require("nvim-tree.api")
local function opts(desc) local function opts(desc)
return { return {
desc = 'nvim-tree: ' .. desc, desc = "nvim-tree: " .. desc,
buffer = bufnr, buffer = bufnr,
noremap = true, noremap = true,
silent = true, silent = true,
nowait = true, nowait = true,
} }
end end
-- formatting cannot be re-enabled, hence this is at the end
---@format disable
-- BEGIN_DEFAULT_ON_ATTACH -- BEGIN_DEFAULT_ON_ATTACH
vim.keymap.set('n', '<C-]>', api.tree.change_root_to_node, opts('CD')) vim.keymap.set('n', '<C-]>', api.tree.change_root_to_node, opts('CD'))
vim.keymap.set('n', '<C-e>', api.node.open.replace_tree_buffer, opts('Open: In Place')) vim.keymap.set('n', '<C-e>', api.node.open.replace_tree_buffer, opts('Open: In Place'))
@@ -68,10 +87,12 @@ function M.default_on_attach(bufnr)
vim.keymap.set('n', 'f', api.live_filter.start, opts('Live Filter: Start')) 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', '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', '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', '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', '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', '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', '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.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', '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.edit, opts('Open'))
@@ -93,24 +114,5 @@ function M.default_on_attach(bufnr)
vim.keymap.set('n', '<2-RightMouse>', api.tree.change_root_to_node, opts('CD')) vim.keymap.set('n', '<2-RightMouse>', api.tree.change_root_to_node, opts('CD'))
-- END_DEFAULT_ON_ATTACH -- END_DEFAULT_ON_ATTACH
end end
-- stylua: ignore end
---@return table
function M.get_keymap()
return generate_keymap(M.on_attach)
end
---@return table
function M.get_keymap_default()
return generate_keymap(M.default_on_attach)
end
function M.setup(opts)
if type(opts.on_attach) ~= "function" then
M.on_attach = M.default_on_attach
else
M.on_attach = opts.on_attach
end
end
return M return M

View File

@@ -1,8 +1,9 @@
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)
-- 2022/06/20 -- 2022/06/20
utils.move_missing_val(opts, "update_focused_file", "update_cwd", opts, "update_focused_file", "update_root", true) utils.move_missing_val(opts, "update_focused_file", "update_cwd", opts, "update_focused_file", "update_root", true)
@@ -41,27 +42,46 @@ local function refactored(opts)
-- 2023/08/26 -- 2023/08/26
utils.move_missing_val(opts, "renderer.icons", "webdev_colors", opts, "renderer.icons.web_devicons.file", "color", true) utils.move_missing_val(opts, "renderer.icons", "webdev_colors", opts, "renderer.icons.web_devicons.file", "color", true)
-- 2023/10/08
if type(opts.renderer) == "table" and type(opts.renderer.highlight_diagnostics) == "boolean" then
opts.renderer.highlight_diagnostics = opts.renderer.highlight_diagnostics and "name" or "none"
end
-- 2023/10/21
if type(opts.renderer) == "table" and type(opts.renderer.highlight_git) == "boolean" then
opts.renderer.highlight_git = opts.renderer.highlight_git and "name" or "none"
end
-- 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)
end end
local function deprecated(opts) local function deprecated(opts)
if type(opts.view) == "table" and opts.view.hide_root_folder then 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" notify.info("view.hide_root_folder is deprecated, please set renderer.root_folder_label = false")
end 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

View File

@@ -1,8 +1,9 @@
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 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 explorer_node = require("nvim-tree.explorer.node")
---@class LibOpenOpts ---@class LibOpenOpts
---@field path string|nil path ---@field path string|nil path
@@ -13,25 +14,33 @@ local M = {
target_winid = nil, target_winid = nil,
} }
---@return Node|nil ---Cursor position as per vim.api.nvim_win_get_cursor
function M.get_node_at_cursor() ---@return integer[]|nil
function M.get_cursor_position()
if not core.get_explorer() then if not core.get_explorer() then
return return
end end
local winnr = view.get_winnr() local winnr = view.get_winnr()
if not winnr then if not winnr or not vim.api.nvim_win_is_valid(winnr) then
return return
end end
local cursor = vim.api.nvim_win_get_cursor(view.get_winnr()) return vim.api.nvim_win_get_cursor(winnr)
local line = cursor[1] end
if line == 1 and view.is_root_folder_visible(core.get_cwd()) then ---@return Node|nil
function M.get_node_at_cursor()
local cursor = M.get_cursor_position()
if not cursor then
return
end
if cursor[1] == 1 and view.is_root_folder_visible(core.get_cwd()) then
return { name = ".." } return { name = ".." }
end end
return utils.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line())[line] return utils.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line())[cursor[1]]
end end
---Create a sanitized partial copy of a node, populating children recursively. ---Create a sanitized partial copy of a node, populating children recursively.
@@ -56,6 +65,7 @@ local function clone_node(node)
name = node.name, name = node.name,
open = node.open, open = node.open,
type = node.type, type = node.type,
fs_stat = node.fs_stat,
} }
if type(node.nodes) == "table" then if type(node.nodes) == "table" then
@@ -82,8 +92,35 @@ function M.get_last_group_node(node)
node = node.group_next node = node.group_next
end end
---@diagnostic disable-next-line: return-type-mismatch -- it can't be nil return node ---@diagnostic disable-line: return-type-mismatch -- it can't be nil
return node end
---Group empty folders
-- Recursively group nodes
---@param node Node
---@return Node[]
function M.group_empty_folders(node)
local is_root = not node.parent
local child_folder_only = explorer_node.has_one_child_folder(node) and node.nodes[1]
if M.group_empty and not is_root and child_folder_only then
node.group_next = child_folder_only
local ns = M.group_empty_folders(child_folder_only)
node.nodes = ns or {}
return ns
end
return node.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
---@param node Node
function M.ungroup_empty_folders(node)
local cur = node
while cur and cur.group_next do
cur.nodes = { cur.group_next }
cur.group_next = nil
cur = cur.nodes[1]
end
end end
---@param node Node ---@param node Node
@@ -98,22 +135,50 @@ function M.get_all_nodes_in_group(node)
return nodes return nodes
end end
-- Toggle group empty folders
---@param head_node Node
local function toggle_group_folders(head_node)
local is_grouped = head_node.group_next ~= nil
if is_grouped then
M.ungroup_empty_folders(head_node)
else
M.group_empty_folders(head_node)
end
end
---@param node Node ---@param node Node
function M.expand_or_collapse(node) function M.expand_or_collapse(node, toggle_group)
local explorer = core.get_explorer()
toggle_group = toggle_group or false
if node.has_children then if node.has_children then
node.has_children = false node.has_children = false
end end
if #node.nodes == 0 then if #node.nodes == 0 and explorer then
core.get_explorer():expand(node) explorer:expand(node)
end end
local open = not M.get_last_group_node(node).open local head_node = utils.get_parent_of_group(node)
for _, n in ipairs(M.get_all_nodes_in_group(node)) do if toggle_group then
n.open = open toggle_group_folders(head_node)
end end
renderer.draw() local open = M.get_last_group_node(node).open
local next_open
if toggle_group then
next_open = open
else
next_open = not open
end
for _, n in ipairs(M.get_all_nodes_in_group(head_node)) do
n.open = next_open
end
if explorer then
explorer.renderer:draw()
end
end end
function M.set_target_win() function M.set_target_win()
@@ -138,14 +203,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
@@ -189,18 +265,36 @@ function M.open(opts)
M.set_target_win() M.set_target_win()
if not core.get_explorer() or opts.path then if not core.get_explorer() or opts.path then
core.init(opts.path 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_win() view.open_in_win()
renderer.draw() if explorer then
explorer.renderer:draw()
end
elseif opts.winid then elseif opts.winid then
view.open_in_win { hijack_current_buf = false, resize = false, winid = opts.winid } view.open_in_win({ hijack_current_buf = false, resize = false, winid = opts.winid })
renderer.draw() if explorer then
explorer.renderer:draw()
end
elseif opts.current_window then elseif opts.current_window then
view.open_in_win { hijack_current_buf = false, resize = false } view.open_in_win({ hijack_current_buf = false, resize = false })
renderer.draw() if explorer then
explorer.renderer:draw()
end
else else
open_view_and_draw() open_view_and_draw()
end end
@@ -213,6 +307,7 @@ function M.setup(opts)
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,186 +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
---@param node_ Node|nil
local function reset_filter(node_)
node_ = node_ or require("nvim-tree.core").get_explorer()
if node_ == nil then
return
end
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, true)
vim.api.nvim_buf_delete(overlay_bufnr, { force = true })
overlay_bufnr = nil
overlay_winnr = nil
if M.filter == "" then
M.clear_filter()
end
end
---@param node Node
---@return boolean
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
---@param node_ Node|nil
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 require("nvim-tree.core").get_explorer())
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
---@return integer
local function calculate_overlay_win_width()
local wininfo = vim.fn.getwininfo(view.get_winnr())[1]
if wininfo then
return wininfo.width - wininfo.textoff - #M.prefix
end
return 20
end
local function create_overlay()
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()
overlay_winnr = vim.api.nvim_open_win(overlay_bufnr, true, {
col = 1,
row = 0,
relative = "cursor",
width = calculate_overlay_win_width(),
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()
view.View.live_filter.prev_focused_node = require("nvim-tree.lib").get_node_at_cursor()
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()
local node = require("nvim-tree.lib").get_node_at_cursor()
local last_node = view.View.live_filter.prev_focused_node
M.filter = nil
reset_filter()
redraw()
if node then
utils.focus_file(node.absolute_path)
elseif last_node then
utils.focus_file(last_node.absolute_path)
end
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

@@ -32,7 +32,7 @@ end
---@return Profile to pass to profile_end ---@return Profile to pass to profile_end
function M.profile_start(fmt, ...) function M.profile_start(fmt, ...)
local profile = {} local profile = {}
if M.enabled "profile" then if M.enabled("profile") then
profile.start = vim.loop.hrtime() profile.start = vim.loop.hrtime()
profile.tag = string.format((fmt or "???"), ...) profile.tag = string.format((fmt or "???"), ...)
M.line("profile", "START %s", profile.tag) M.line("profile", "START %s", profile.tag)
@@ -44,7 +44,7 @@ end
--- END is prefixed and duration in seconds is suffixed --- END is prefixed and duration in seconds is suffixed
---@param profile Profile returned from profile_start ---@param profile Profile returned from profile_start
function M.profile_end(profile) function M.profile_end(profile)
if M.enabled "profile" and type(profile) == "table" then if M.enabled("profile") and type(profile) == "table" then
local millis = profile.start and math.modf((vim.loop.hrtime() - profile.start) / 1000000) or -1 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) M.line("profile", "END %s %dms", profile.tag or "", millis)
end end
@@ -57,7 +57,7 @@ end
---@param ... any arguments for string.format ---@param ... any arguments for string.format
function M.line(typ, fmt, ...) function M.line(typ, fmt, ...)
if M.enabled(typ) then 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 "???")), ...) M.raw(typ, string.format("[%s] [%s] %s\n", os.date("%Y-%m-%d %H:%M:%S"), typ, (fmt or "???")), ...)
end end
end end
@@ -77,7 +77,7 @@ end
function M.node(typ, node, fmt, ...) function M.node(typ, node, fmt, ...)
if M.enabled(typ) then if M.enabled(typ) then
node = node or require("nvim-tree.lib").get_node_at_cursor() node = node or require("nvim-tree.lib").get_node_at_cursor()
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)), ...) 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
end end
@@ -91,7 +91,7 @@ end
function M.setup(opts) function M.setup(opts)
M.config = opts.log M.config = opts.log
if M.config and M.config.enable and M.config.types then if M.config and M.config.enable and M.config.types then
M.path = string.format("%s/nvim-tree.log", vim.fn.stdpath "log", os.date "%H:%M:%S", vim.env.USER) M.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 M.config.truncate then
os.remove(M.path) os.remove(M.path)
end end

View File

@@ -1,52 +0,0 @@
local marks = require "nvim-tree.marks"
local utils = require "nvim-tree.utils"
local remove_file = require "nvim-tree.actions.fs.remove-file"
local notify = require "nvim-tree.notify"
local lib = require "nvim-tree.lib"
local M = {
config = {},
}
--- Delete nodes; each removal will be optionally notified
---@param nodes Node[]
local function do_delete(nodes)
for _, node in pairs(nodes) do
remove_file.remove(node)
end
marks.clear_marks()
if not M.config.filesystem_watchers.enable then
require("nvim-tree.actions.reloaders").reload_explorer()
end
end
--- Delete marked nodes, optionally prompting
function M.bulk_delete()
local nodes = marks.get_marks()
if not nodes or #nodes == 0 then
notify.warn "No bookmarksed to delete."
return
end
if M.config.ui.confirm.remove then
local prompt_select = "Remove bookmarked ?"
local prompt_input = prompt_select .. " y/N: "
lib.prompt(prompt_input, prompt_select, { "", "y" }, { "No", "Yes" }, "nvimtree_bulk_delete", function(item_short)
utils.clear_prompt()
if item_short == "y" then
do_delete(nodes)
end
end)
else
do_delete(nodes)
end
end
function M.setup(opts)
M.config.ui = opts.ui
M.config.filesystem_watchers = opts.filesystem_watchers
end
return M

View File

@@ -1,52 +0,0 @@
local marks = require "nvim-tree.marks"
local core = require "nvim-tree.core"
local utils = require "nvim-tree.utils"
local rename_file = require "nvim-tree.actions.fs.rename-file"
local notify = require "nvim-tree.notify"
local M = {
config = {},
}
function M.bulk_move()
if #marks.get_marks() == 0 then
notify.warn "No bookmarks to move."
return
end
local input_opts = {
prompt = "Move to: ",
default = core.get_cwd(),
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
local nodes = marks.get_marks()
for _, node in pairs(nodes) do
local head = vim.fn.fnamemodify(node.absolute_path, ":t")
local to = utils.path_join { location, head }
rename_file.rename(node, to)
end
marks.clear_marks()
if not M.config.filesystem_watchers.enable then
require("nvim-tree.actions.reloaders").reload_explorer()
end
end)
end
function M.setup(opts)
M.config.filesystem_watchers = opts.filesystem_watchers
end
return M

View File

@@ -1,47 +0,0 @@
local marks = require "nvim-tree.marks"
local utils = require "nvim-tree.utils"
local remove_file = require "nvim-tree.actions.fs.trash"
local notify = require "nvim-tree.notify"
local lib = require "nvim-tree.lib"
local M = {
config = {},
}
--- Delete nodes; each removal will be optionally notified
---@param nodes Node[]
local function do_trash(nodes)
for _, node in pairs(nodes) do
remove_file.remove(node)
end
marks.clear_marks()
end
function M.bulk_trash()
local nodes = marks.get_marks()
if not nodes or #nodes == 0 then
notify.warn "No bookmarks to trash."
return
end
if M.config.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
do_trash(nodes)
end
end)
else
do_trash(nodes)
end
end
function M.setup(opts)
M.config.ui = opts.ui
M.config.filesystem_watchers = opts.filesystem_watchers
end
return M

View File

@@ -1,68 +1,272 @@
local renderer = {} -- circular dependency local Iterator = require("nvim-tree.iterators.node-iterator")
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 = {} ---@class Marks
---@field config table hydrated user opts.filters
---@field private explorer Explorer
---@field private marks table<string, Node> by absolute path
local Marks = {}
local M = {} ---@return Marks
---@param opts table user options
---@param explorer Explorer
function Marks:new(opts, explorer)
local o = {
explorer = explorer,
config = {
ui = opts.ui,
filesystem_watchers = opts.filesystem_watchers,
},
marks = {},
}
---@class MinimalNode setmetatable(o, self)
---@field absolute_path string self.__index = self
return o
---@param node Node|MinimalNode
local function add_mark(node)
NvimTreeMarks[node.absolute_path] = node
renderer.draw()
end end
---@param node Node|MinimalNode ---Clear all marks and reload if watchers disabled
local function remove_mark(node) ---@private
NvimTreeMarks[node.absolute_path] = nil function Marks:clear_reload()
self:clear()
renderer.draw() if not self.config.filesystem_watchers.enable then
self.explorer:reload_explorer()
end
end end
---@param node Node|MinimalNode ---Clear all marks and redraw
function M.toggle_mark(node) ---@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
renderer.draw() self.explorer.renderer:draw()
end end
function M.clear_marks() ---Return node if marked
NvimTreeMarks = {} ---@public
---@param node Node
renderer.draw() ---@return Node|nil
function Marks:get(node)
return node and self.marks[node.absolute_path]
end end
---@param node Node|MinimalNode ---List marked nodes
---@return table|nil ---@public
function M.get_mark(node) ---@return Node[]
return NvimTreeMarks[node.absolute_path] function Marks:list()
end
---@return table
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
function M.setup(opts) ---Delete marked; each removal will be optionally notified
renderer = require "nvim-tree.renderer" ---@public
function Marks:bulk_delete()
if not next(self.marks) then
notify.warn("No bookmarks to delete.")
return
end
require("nvim-tree.marks.bulk-delete").setup(opts) local function execute()
require("nvim-tree.marks.bulk-trash").setup(opts) for _, node in pairs(self.marks) do
require("nvim-tree.marks.bulk-move").setup(opts) remove_file.remove(node)
end
self:clear_reload()
end
if self.config.ui.confirm.remove then
local prompt_select = "Remove bookmarked ?"
local prompt_input = prompt_select .. " y/N: "
lib.prompt(prompt_input, prompt_select, { "", "y" }, { "No", "Yes" }, "nvimtree_bulk_delete", function(item_short)
utils.clear_prompt()
if item_short == "y" then
execute()
end
end)
else
execute()
end
end end
return M ---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.config.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 = lib.get_node_at_cursor()
local default_path = core.get_cwd()
if node_at_cursor and node_at_cursor.type == "directory" 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 = lib.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)
return n.open and n.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)
: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
---@public
function Marks:navigate_prev()
self:navigate(true)
end
---@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.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)
end
return Marks

View File

@@ -1,102 +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"
---@param node table
---@param where string
---@return Node|nil
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
---@param where string
---@param node table|nil
---@return Node|nil
local function get(where, node)
if node then
return get_nearest(node, where)
end
end
---@param node table|nil
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
---@param where string
---@return function
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

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

View File

@@ -1,18 +1,15 @@
---@meta ---@meta
-- TODO add "${3rd}/luv/library" to "workspace.library"
---@class uv.uv_req_t: table
---@class uv.uv_fs_t: uv.uv_req_t
---@class ParentNode ---@class ParentNode
---@field name string ---@field name string
---@class BaseNode ---@class BaseNode
---@field absolute_path string ---@field absolute_path string
---@field executable boolean ---@field executable boolean
---@field fs_stat uv.uv_fs_t ---@field fs_stat uv.fs_stat.result|nil
---@field git_status GitStatus|nil ---@field git_status GitStatus|nil
---@field hidden boolean ---@field hidden boolean
---@field is_dot boolean
---@field name string ---@field name string
---@field parent DirNode ---@field parent DirNode
---@field type string ---@field type string
@@ -24,6 +21,7 @@
---@field group_next Node|nil ---@field group_next Node|nil
---@field nodes Node[] ---@field nodes Node[]
---@field open boolean ---@field open boolean
---@field hidden_stats table -- Each field of this table is a key for source and value for count
---@class FileNode: BaseNode ---@class FileNode: BaseNode
---@field extension string ---@field extension string

View File

@@ -9,9 +9,9 @@ local title_support
---@return boolean ---@return boolean
function M.supports_title() function M.supports_title()
if title_support == nil then if title_support == nil then
title_support = (package.loaded.notify and (vim.notify == require "notify" or vim.notify == require("notify").notify)) title_support = (package.loaded.notify and (vim.notify == require("notify") or vim.notify == require("notify").notify))
or (package.loaded.noice and (vim.notify == require("noice").notify or vim.notify == require("noice.source.notify").notify)) or (package.loaded.noice and (vim.notify == require("noice").notify or vim.notify == require("noice.source.notify").notify))
or (package.loaded.notifier and require("notifier.config").has_component "nvim") or (package.loaded.notifier and require("notifier.config").has_component("nvim"))
or false or false
end end
@@ -21,8 +21,8 @@ end
local modes = { local modes = {
{ name = "trace", level = vim.log.levels.TRACE }, { name = "trace", level = vim.log.levels.TRACE },
{ name = "debug", level = vim.log.levels.DEBUG }, { name = "debug", level = vim.log.levels.DEBUG },
{ name = "info", level = vim.log.levels.INFO }, { name = "info", level = vim.log.levels.INFO },
{ name = "warn", level = vim.log.levels.WARN }, { name = "warn", level = vim.log.levels.WARN },
{ name = "error", level = vim.log.levels.ERROR }, { name = "error", level = vim.log.levels.ERROR },
} }
@@ -35,7 +35,7 @@ do
vim.schedule(function() vim.schedule(function()
if not M.supports_title() then if not M.supports_title() then
-- add title to the message, with a newline if the message is multiline -- add title to the message, with a newline if the message is multiline
msg = string.format("[NvimTree]%s%s", (msg:match "\n" and "\n" or " "), msg) msg = string.format("[NvimTree]%s%s", (msg:match("\n") and "\n" or " "), msg)
end end
vim.notify(msg, level, { title = "NvimTree" }) vim.notify(msg, level, { title = "NvimTree" })

View File

@@ -1,136 +1,111 @@
local utils = require "nvim-tree.utils" local notify = require("nvim-tree.notify")
local core = require "nvim-tree.core" local utils = require("nvim-tree.utils")
local notify = require "nvim-tree.notify" local view = require("nvim-tree.view")
local git = require "nvim-tree.renderer.components.git" local DecoratorBookmarks = require("nvim-tree.renderer.decorator.bookmarks")
local pad = require "nvim-tree.renderer.components.padding" local DecoratorCopied = require("nvim-tree.renderer.decorator.copied")
local icons = require "nvim-tree.renderer.components.icons" local DecoratorCut = require("nvim-tree.renderer.decorator.cut")
local modified = require "nvim-tree.renderer.components.modified" local DecoratorDiagnostics = require("nvim-tree.renderer.decorator.diagnostics")
local diagnostics = require "nvim-tree.renderer.components.diagnostics" local DecoratorGit = require("nvim-tree.renderer.decorator.git")
local bookmarks = require "nvim-tree.renderer.components.bookmarks" local DecoratorModified = require("nvim-tree.renderer.decorator.modified")
local DecoratorHidden = require("nvim-tree.renderer.decorator.hidden")
local DecoratorOpened = require("nvim-tree.renderer.decorator.opened")
local HL_POSITION = require("nvim-tree.enum").HL_POSITION local pad = require("nvim-tree.renderer.components.padding")
local icons = require("nvim-tree.renderer.components.icons")
local PICTURE_MAP = {
jpg = true,
jpeg = true,
png = true,
gif = true,
webp = true,
jxl = true,
}
---@class (exact) HighlightedString
---@field str string
---@field hl string[]
---@class (exact) AddHighlightArgs
---@field group string[]
---@field line number
---@field col_start number
---@field col_end number
---@class (exact) Builder
---@field private __index? table
---@field lines string[] includes icons etc.
---@field hl_args AddHighlightArgs[] line highlights
---@field signs string[] line signs
---@field extmarks table[] extra marks for right icon placement
---@field virtual_lines table[] virtual lines for hidden count display
---@field private explorer Explorer
---@field private index number
---@field private depth number
---@field private combined_groups table<string, boolean> combined group names
---@field private markers boolean[] indent markers
---@field private decorators Decorator[]
---@field private hidden_display fun(node: Node): string|nil
local Builder = {} local Builder = {}
Builder.__index = Builder
local DEFAULT_ROOT_FOLDER_LABEL = ":~:s?$?/..?" ---@param opts table user options
---@param explorer Explorer
function Builder.new(root_cwd) ---@return Builder
return setmetatable({ function Builder:new(opts, explorer)
---@type Builder
local o = {
opts = opts,
explorer = explorer,
index = 0, index = 0,
depth = 0, depth = 0,
highlights = {}, hl_args = {},
combined_groups = {},
lines = {}, lines = {},
markers = {}, markers = {},
signs = {}, signs = {},
root_cwd = root_cwd, extmarks = {},
}, Builder) virtual_lines = {},
decorators = {
-- priority order
DecoratorCut:new(opts, explorer),
DecoratorCopied:new(opts, explorer),
DecoratorDiagnostics:new(opts, explorer),
DecoratorBookmarks:new(opts, explorer),
DecoratorModified:new(opts, explorer),
DecoratorHidden:new(opts, explorer),
DecoratorOpened:new(opts, explorer),
DecoratorGit:new(opts, explorer),
},
hidden_display = Builder:setup_hidden_display_function(opts),
}
setmetatable(o, self)
self.__index = self
return o
end end
function Builder:configure_root_label(root_folder_label) ---Insert ranged highlight groups into self.highlights
self.root_folder_label = root_folder_label or DEFAULT_ROOT_FOLDER_LABEL ---@private
return self ---@param groups string[]
---@param start number
---@param end_ number|nil
function Builder:insert_highlight(groups, start, end_)
table.insert(self.hl_args, { groups, self.index, start, end_ or -1 })
end end
function Builder:configure_trailing_slash(with_trailing) ---@private
self.trailing_slash = with_trailing and "/" or "" function Builder:get_folder_name(node)
return self
end
function Builder:configure_special_files(special_files)
self.special_files = special_files
return self
end
function Builder:configure_picture_map(picture_map)
self.picture_map = picture_map
return self
end
function Builder:configure_filter(filter, prefix)
self.filter_prefix = prefix
self.filter = filter
return self
end
function Builder:configure_opened_file_highlighting(highlight_opened_files)
self.highlight_opened_files = highlight_opened_files
return self
end
function Builder:configure_modified_highlighting(highlight_modified)
self.highlight_modified = highlight_modified
return self
end
function Builder:configure_icon_padding(padding)
self.icon_padding = padding or " "
return self
end
function Builder:configure_git_icons_placement(where)
if where ~= "after" and where ~= "before" and where ~= "signcolumn" then
where = "before" -- default before
end
self.git_placement = where
return self
end
function Builder:configure_diagnostics_icon_placement(where)
if where ~= "after" and where ~= "before" and where ~= "signcolumn" then
where = "before" -- default before
end
self.diagnostics_placement = where
return self
end
function Builder:configure_bookmark_icon_placement(where)
if where ~= "after" and where ~= "before" and where ~= "signcolumn" then
where = "before" -- default before
end
self.bookmarks_placement = where
return self
end
function Builder:configure_modified_placement(where)
if where ~= "after" and where ~= "before" and where ~= "signcolumn" then
where = "after" -- default after
end
self.modified_placement = where
return self
end
function Builder:configure_symlink_destination(show)
self.symlink_destination = show
return self
end
function Builder:configure_group_name_modifier(group_name_modifier)
if type(group_name_modifier) == "function" then
self.group_name_modifier = group_name_modifier
end
return self
end
function Builder:_insert_highlight(group, start, end_)
table.insert(self.highlights, { group, self.index, start, end_ or -1 })
end
function Builder:_insert_line(line)
table.insert(self.lines, line)
end
function Builder:_get_folder_name(node)
local name = node.name local name = node.name
local next = node.group_next local next = node.group_next
while next do while next do
name = name .. "/" .. next.name name = string.format("%s/%s", name, next.name)
next = next.group_next next = next.group_next
end end
if node.group_next and self.group_name_modifier then if node.group_next and type(self.opts.renderer.group_empty) == "function" then
local new_name = self.group_name_modifier(name) local new_name = self.opts.renderer.group_empty(name)
if type(new_name) == "string" then if type(new_name) == "string" then
name = new_name name = new_name
else else
@@ -138,16 +113,13 @@ function Builder:_get_folder_name(node)
end end
end end
return name .. self.trailing_slash return string.format("%s%s", name, self.opts.renderer.add_trailing and "/" or "")
end end
---@class HighlightedString ---@private
---@field str string
---@field hl string[]
---@param highlighted_strings HighlightedString[] ---@param highlighted_strings HighlightedString[]
---@return string ---@return string
function Builder:_unwrap_highlighted_strings(highlighted_strings) function Builder:unwrap_highlighted_strings(highlighted_strings)
if not highlighted_strings then if not highlighted_strings then
return "" return ""
end end
@@ -156,21 +128,22 @@ function Builder:_unwrap_highlighted_strings(highlighted_strings)
for _, v in ipairs(highlighted_strings) do for _, v in ipairs(highlighted_strings) do
if #v.str > 0 then if #v.str > 0 then
if v.hl and type(v.hl) == "table" then if v.hl and type(v.hl) == "table" then
self:_insert_highlight(v.hl, #string, #string + #v.str) self:insert_highlight(v.hl, #string, #string + #v.str)
end end
string = string .. v.str string = string.format("%s%s", string, v.str)
end end
end end
return string return string
end end
---@private
---@param node table ---@param node table
---@return HighlightedString icon ---@return HighlightedString icon
---@return HighlightedString name ---@return HighlightedString name
function Builder:_build_folder(node) function Builder:build_folder(node)
local has_children = #node.nodes ~= 0 or node.has_children local has_children = #node.nodes ~= 0 or node.has_children
local icon, icon_hl = icons.get_folder_icon(node, has_children) local icon, icon_hl = icons.get_folder_icon(node, has_children)
local foldername = self:_get_folder_name(node) local foldername = self:get_folder_name(node)
if #icon > 0 and icon_hl == nil then if #icon > 0 and icon_hl == nil then
if node.open then if node.open then
@@ -181,12 +154,14 @@ function Builder:_build_folder(node)
end end
local foldername_hl = "NvimTreeFolderName" local foldername_hl = "NvimTreeFolderName"
if node.link_to and self.symlink_destination then if node.link_to and self.opts.renderer.symlink_destination then
local arrow = icons.i.symlink_arrow local arrow = icons.i.symlink_arrow
local link_to = utils.path_relative(node.link_to, core.get_cwd()) local link_to = utils.path_relative(node.link_to, self.explorer.absolute_path)
foldername = foldername .. arrow .. link_to foldername = string.format("%s%s%s", foldername, arrow, link_to)
foldername_hl = "NvimTreeSymlinkFolderName" foldername_hl = "NvimTreeSymlinkFolderName"
elseif vim.tbl_contains(self.special_files, node.absolute_path) or vim.tbl_contains(self.special_files, node.name) then elseif
vim.tbl_contains(self.opts.renderer.special_files, node.absolute_path) or vim.tbl_contains(self.opts.renderer.special_files, node.name)
then
foldername_hl = "NvimTreeSpecialFolderName" foldername_hl = "NvimTreeSpecialFolderName"
elseif node.open then elseif node.open then
foldername_hl = "NvimTreeOpenedFolderName" foldername_hl = "NvimTreeOpenedFolderName"
@@ -197,174 +172,58 @@ function Builder:_build_folder(node)
return { str = icon, hl = { icon_hl } }, { str = foldername, hl = { foldername_hl } } return { str = icon, hl = { icon_hl } }, { str = foldername, hl = { foldername_hl } }
end end
---@private
---@param node table ---@param node table
---@return HighlightedString icon ---@return HighlightedString icon
---@return HighlightedString name ---@return HighlightedString name
function Builder:_build_symlink(node) function Builder:build_symlink(node)
local icon = icons.i.symlink local icon = icons.i.symlink
local arrow = icons.i.symlink_arrow local arrow = icons.i.symlink_arrow
local symlink_formatted = node.name local symlink_formatted = node.name
if self.symlink_destination then if self.opts.renderer.symlink_destination then
local link_to = utils.path_relative(node.link_to, core.get_cwd()) local link_to = utils.path_relative(node.link_to, self.explorer.absolute_path)
symlink_formatted = symlink_formatted .. arrow .. link_to symlink_formatted = string.format("%s%s%s", symlink_formatted, arrow, link_to)
end end
return { str = icon, hl = { "NvimTreeSymlinkIcon" } }, { str = symlink_formatted, hl = { "NvimTreeSymlink" } } return { str = icon, hl = { "NvimTreeSymlinkIcon" } }, { str = symlink_formatted, hl = { "NvimTreeSymlink" } }
end end
---@param node table ---@private
---@return HighlightedString icon
function Builder:_build_file_icon(node)
local icon, hl_group = icons.get_file_icon(node.name, node.extension)
return { str = icon, hl = { hl_group } }
end
---@param node table ---@param node table
---@return HighlightedString icon ---@return HighlightedString icon
---@return HighlightedString name ---@return HighlightedString name
function Builder:_build_file(node) function Builder:build_file(node)
local icon = self:_build_file_icon(node)
local hl local hl
if vim.tbl_contains(self.special_files, node.absolute_path) or vim.tbl_contains(self.special_files, node.name) then if
vim.tbl_contains(self.opts.renderer.special_files, node.absolute_path) or vim.tbl_contains(self.opts.renderer.special_files, node.name)
then
hl = "NvimTreeSpecialFile" hl = "NvimTreeSpecialFile"
elseif node.executable then elseif node.executable then
hl = "NvimTreeExecFile" hl = "NvimTreeExecFile"
elseif self.picture_map[node.extension] then elseif PICTURE_MAP[node.extension] then
hl = "NvimTreeImageFile" hl = "NvimTreeImageFile"
end end
return icon, { str = node.name, hl = { hl } } local icon, hl_group = icons.get_file_icon(node.name, node.extension)
end return { str = icon, hl = { hl_group } }, { str = node.name, hl = { hl } }
---@param node table
---@return HighlightedString[]|nil icon
function Builder:_get_git_icons(node)
local git_icons = git.get_icons(node)
if git_icons and #git_icons > 0 and self.git_placement == "signcolumn" then
table.insert(self.signs, {
sign = git_icons[1].hl[1],
lnum = self.index + 1,
priority = 1,
})
git_icons = nil
end
return git_icons
end
---@param node table
---@return HighlightedString[]|nil icon
function Builder:_get_diagnostics_icon(node)
local diagnostics_icon = diagnostics.get_icon(node)
if diagnostics_icon and self.diagnostics_placement == "signcolumn" then
table.insert(self.signs, {
sign = diagnostics_icon.hl[1],
lnum = self.index + 1,
priority = 2,
})
diagnostics_icon = nil
end
return diagnostics_icon
end
---@param node table
---@return HighlightedString|nil icon
function Builder:_get_modified_icon(node)
local modified_icon = modified.get_icon(node)
if modified_icon and self.modified_placement == "signcolumn" then
table.insert(self.signs, {
sign = modified_icon.hl[1],
lnum = self.index + 1,
priority = 3,
})
modified_icon = nil
end
return modified_icon
end
---@param node table
---@return HighlightedString[]|nil icon
function Builder:_get_bookmark_icon(node)
local bookmark_icon = bookmarks.get_icon(node)
if bookmark_icon and self.bookmarks_placement == "signcolumn" then
table.insert(self.signs, {
sign = bookmark_icon.hl[1],
lnum = self.index + 1,
priority = 4,
})
bookmark_icon = nil
end
return bookmark_icon
end
---@param node table
---@return string|nil icon_hl
---@return string|nil name_hl
function Builder:_get_highlight_override(node, unloaded_bufnr)
local name_hl, icon_hl
-- git
local git_highlight = git.get_highlight(node)
if git_highlight then
name_hl = git_highlight
end
-- opened file
if self.highlight_opened_files and vim.fn.bufloaded(node.absolute_path) > 0 and vim.fn.bufnr(node.absolute_path) ~= unloaded_bufnr then
if self.highlight_opened_files == "all" or self.highlight_opened_files == "name" then
name_hl = "NvimTreeOpenedFile"
end
if self.highlight_opened_files == "all" or self.highlight_opened_files == "icon" then
icon_hl = "NvimTreeOpenedFileIcon"
end
end
-- modified file
local modified_highlight = modified.get_highlight(node)
if modified_highlight then
if self.highlight_modified == "all" or self.highlight_modified == "name" then
name_hl = modified_highlight
end
if self.highlight_modified == "all" or self.highlight_modified == "icon" then
icon_hl = modified_highlight
end
end
return icon_hl, name_hl
end
---Append optional highlighting to icon or name.
---@param node table
---@param get_hl fun(node: table): HL_POSITION, string
---@param icon_hl string[] icons to append to
---@param name_hl string[] names to append to
function Builder:_append_highlight(node, get_hl, icon_hl, name_hl)
local pos, hl = get_hl(node)
if pos ~= HL_POSITION.none and hl then
if pos == HL_POSITION.all or pos == HL_POSITION.icon then
table.insert(icon_hl, hl)
end
if pos == HL_POSITION.all or pos == HL_POSITION.name then
table.insert(name_hl, hl)
end
end
end end
---@private
---@param indent_markers HighlightedString[] ---@param indent_markers HighlightedString[]
---@param arrows HighlightedString[]|nil ---@param arrows HighlightedString[]|nil
---@param icon HighlightedString ---@param icon HighlightedString
---@param name HighlightedString ---@param name HighlightedString
---@param git_icons HighlightedString[]|nil ---@param node table
---@param diagnostics_icon HighlightedString|nil
---@param modified_icon HighlightedString|nil
---@param bookmark_icon HighlightedString|nil
---@return HighlightedString[] ---@return HighlightedString[]
function Builder:_format_line(indent_markers, arrows, icon, name, git_icons, diagnostics_icon, modified_icon, bookmark_icon) function Builder:format_line(indent_markers, arrows, icon, name, node)
local added_len = 0 local added_len = 0
local function add_to_end(t1, t2) local function add_to_end(t1, t2)
if not t2 then
return
end
for _, v in ipairs(t2) do for _, v in ipairs(t2) do
if added_len > 0 then if added_len > 0 then
table.insert(t1, { str = self.icon_padding }) table.insert(t1, { str = self.opts.renderer.icons.padding })
end end
table.insert(t1, v) table.insert(t1, v)
end end
@@ -379,92 +238,174 @@ function Builder:_format_line(indent_markers, arrows, icon, name, git_icons, dia
local line = { indent_markers, arrows } local line = { indent_markers, arrows }
add_to_end(line, { icon }) add_to_end(line, { icon })
if git_icons and self.git_placement == "before" then
add_to_end(line, git_icons) for i = #self.decorators, 1, -1 do
end add_to_end(line, self.decorators[i]:icons_before(node))
if modified_icon and self.modified_placement == "before" then
add_to_end(line, { modified_icon })
end
if diagnostics_icon and self.diagnostics_placement == "before" then
add_to_end(line, { diagnostics_icon })
end
if bookmark_icon and self.bookmarks_placement == "before" then
add_to_end(line, { bookmark_icon })
end end
add_to_end(line, { name }) add_to_end(line, { name })
if git_icons and self.git_placement == "after" then for i = #self.decorators, 1, -1 do
add_to_end(line, git_icons) add_to_end(line, self.decorators[i]:icons_after(node))
end end
if modified_icon and self.modified_placement == "after" then
add_to_end(line, { modified_icon }) local rights = {}
for i = #self.decorators, 1, -1 do
add_to_end(rights, self.decorators[i]:icons_right_align(node))
end end
if diagnostics_icon and self.diagnostics_placement == "after" then if #rights > 0 then
add_to_end(line, { diagnostics_icon }) self.extmarks[self.index] = rights
end
if bookmark_icon and self.bookmarks_placement == "after" then
add_to_end(line, { bookmark_icon })
end end
return line return line
end end
function Builder:_build_line(node, idx, num_children, unloaded_bufnr) ---@private
local copy_paste = require "nvim-tree.actions.fs.copy-paste" ---@param node Node
function Builder:build_signs(node)
-- first in priority order
local sign_name
for _, d in ipairs(self.decorators) do
sign_name = d:sign_name(node)
if sign_name then
self.signs[self.index] = sign_name
break
end
end
end
---Create a highlight group for groups with later groups overriding previous.
---Combined group name is less than the 200 byte limit of highlight group names
---@private
---@param groups string[] highlight group names
---@return string group_name "NvimTreeCombinedHL" .. sha256
function Builder:create_combined_group(groups)
local combined_name = string.format("NvimTreeCombinedHL%s", vim.fn.sha256(table.concat(groups)))
-- only create if necessary
if not self.combined_groups[combined_name] then
self.combined_groups[combined_name] = true
local combined_hl = {}
-- build the highlight, overriding values
for _, group in ipairs(groups) do
local hl = vim.api.nvim_get_hl(0, { name = group, link = false })
combined_hl = vim.tbl_extend("force", combined_hl, hl)
end
-- add highlights to the global namespace
vim.api.nvim_set_hl(0, combined_name, combined_hl)
table.insert(self.combined_groups, combined_name)
end
return combined_name
end
---Calculate highlight group for icon and name. A combined highlight group will be created
---when there is more than one highlight.
---A highlight group is always calculated and upserted for the case of highlights changing.
---@private
---@param node Node
---@return string|nil icon_hl_group
---@return string|nil name_hl_group
function Builder:add_highlights(node)
-- result
local icon_hl_group, name_hl_group
-- calculate all groups
local icon_groups = {}
local name_groups = {}
local d, icon, name
for i = #self.decorators, 1, -1 do
d = self.decorators[i]
icon, name = d:groups_icon_name(node)
table.insert(icon_groups, icon)
table.insert(name_groups, name)
end
-- one or many icon groups
if #icon_groups > 1 then
icon_hl_group = self:create_combined_group(icon_groups)
else
icon_hl_group = icon_groups[1]
end
-- one or many name groups
if #name_groups > 1 then
name_hl_group = self:create_combined_group(name_groups)
else
name_hl_group = name_groups[1]
end
return icon_hl_group, name_hl_group
end
---@private
function Builder:build_line(node, idx, num_children)
-- various components -- various components
local indent_markers = pad.get_indent_markers(self.depth, idx, num_children, node, self.markers) local indent_markers = pad.get_indent_markers(self.depth, idx, num_children, node, self.markers)
local arrows = pad.get_arrows(node) local arrows = pad.get_arrows(node)
-- adds icons to signcolumn
local bookmark_icon = self:_get_bookmark_icon(node)
local git_icons = self:_get_git_icons(node)
local modified_icon = self:_get_modified_icon(node)
local diagnostics_icon = self:_get_diagnostics_icon(node)
-- main components -- main components
local is_folder = node.nodes ~= nil local is_folder = node.nodes ~= nil
local is_symlink = node.link_to ~= nil local is_symlink = node.link_to ~= nil
local icon, name local icon, name
if is_folder then if is_folder then
icon, name = self:_build_folder(node) icon, name = self:build_folder(node)
elseif is_symlink then elseif is_symlink then
icon, name = self:_build_symlink(node) icon, name = self:build_symlink(node)
else else
icon, name = self:_build_file(node) icon, name = self:build_file(node)
end end
-- highlight override -- highighting
local icon_hl_override, name_hl_override = self:_get_highlight_override(node, unloaded_bufnr) local icon_hl_group, name_hl_group = self:add_highlights(node)
if icon_hl_override then table.insert(icon.hl, icon_hl_group)
icon.hl = { icon_hl_override } table.insert(name.hl, name_hl_group)
end
if name_hl_override then
name.hl = { name_hl_override }
end
-- extra highighting local line = self:format_line(indent_markers, arrows, icon, name, node)
self:_append_highlight(node, bookmarks.get_highlight, icon.hl, name.hl) table.insert(self.lines, self:unwrap_highlighted_strings(line))
self:_append_highlight(node, diagnostics.get_highlight, icon.hl, name.hl)
self:_append_highlight(node, copy_paste.get_highlight, icon.hl, name.hl)
local line = self:_format_line(indent_markers, arrows, icon, name, git_icons, diagnostics_icon, modified_icon, bookmark_icon)
self:_insert_line(self:_unwrap_highlighted_strings(line))
self.index = self.index + 1 self.index = self.index + 1
node = require("nvim-tree.lib").get_last_group_node(node) node = require("nvim-tree.lib").get_last_group_node(node)
if node.open then if node.open then
self.depth = self.depth + 1 self.depth = self.depth + 1
self:build(node, unloaded_bufnr) self:build_lines(node)
self.depth = self.depth - 1 self.depth = self.depth - 1
end end
end end
function Builder:_get_nodes_number(nodes) ---Add virtual lines for rendering hidden count information per node
if not self.filter then ---@private
function Builder:add_hidden_count_string(node, idx, num_children)
if not node.open then
return
end
local hidden_count_string = self.hidden_display(node.hidden_stats)
if hidden_count_string and hidden_count_string ~= "" then
local indent_markers = pad.get_indent_markers(self.depth, idx or 0, num_children or 0, node, self.markers, 1)
local indent_width = self.opts.renderer.indent_width
local indent_padding = string.rep(" ", indent_width)
local indent_string = indent_padding .. indent_markers.str
local line_nr = #self.lines - 1
self.virtual_lines[line_nr] = self.virtual_lines[line_nr] or {}
-- NOTE: We are inserting in depth order because of current traversal
-- if we change the traversal, we might need to sort by depth before rendering `self.virtual_lines`
-- to maintain proper ordering of parent and child folder hidden count info.
table.insert(self.virtual_lines[line_nr], {
{ indent_string, indent_markers.hl },
{ string.rep(indent_padding, (node.parent == nil and 0 or 1)) .. hidden_count_string, "NvimTreeHiddenDisplay" },
})
end
end
---@private
function Builder:get_nodes_number(nodes)
if not self.explorer.live_filter.filter then
return #nodes return #nodes
end end
@@ -477,53 +418,116 @@ function Builder:_get_nodes_number(nodes)
return i return i
end end
function Builder:build(tree, unloaded_bufnr) ---@private
local num_children = self:_get_nodes_number(tree.nodes) function Builder:build_lines(node)
if not node then
node = self.explorer
end
local num_children = self:get_nodes_number(node.nodes)
local idx = 1 local idx = 1
for _, node in ipairs(tree.nodes) do for _, n in ipairs(node.nodes) do
if not node.hidden then if not n.hidden then
self:_build_line(node, idx, num_children, unloaded_bufnr) self:build_signs(n)
self:build_line(n, idx, num_children)
idx = idx + 1 idx = idx + 1
end end
end end
self:add_hidden_count_string(node)
return self
end end
local function format_root_name(root_cwd, root_label) ---@private
---@param root_label function|string
---@return string
function Builder:format_root_name(root_label)
if type(root_label) == "function" then if type(root_label) == "function" then
local label = root_label(root_cwd) local label = root_label(self.explorer.absolute_path)
if type(label) == "string" then if type(label) == "string" then
return label return label
else
root_label = DEFAULT_ROOT_FOLDER_LABEL
end end
elseif type(root_label) == "string" then
return utils.path_remove_trailing(vim.fn.fnamemodify(self.explorer.absolute_path, root_label))
end end
return utils.path_remove_trailing(vim.fn.fnamemodify(root_cwd, root_label)) return "???"
end end
function Builder:build_header(show_header) ---@private
if show_header then function Builder:build_header()
local root_name = format_root_name(self.root_cwd, self.root_folder_label) if view.is_root_folder_visible(self.explorer.absolute_path) then
self:_insert_line(root_name) local root_name = self:format_root_name(self.opts.renderer.root_folder_label)
self:_insert_highlight({ "NvimTreeRootFolder" }, 0, string.len(root_name)) table.insert(self.lines, root_name)
self:insert_highlight({ "NvimTreeRootFolder" }, 0, string.len(root_name))
self.index = 1 self.index = 1
end end
if self.filter then if self.explorer.live_filter.filter then
local filter_line = self.filter_prefix .. "/" .. self.filter .. "/" local filter_line = string.format("%s/%s/", self.opts.live_filter.prefix, self.explorer.live_filter.filter)
self:_insert_line(filter_line) table.insert(self.lines, filter_line)
local prefix_length = string.len(self.filter_prefix) local prefix_length = string.len(self.opts.live_filter.prefix)
self:_insert_highlight({ "NvimTreeLiveFilterPrefix" }, 0, prefix_length) self:insert_highlight({ "NvimTreeLiveFilterPrefix" }, 0, prefix_length)
self:_insert_highlight({ "NvimTreeLiveFilterValue" }, prefix_length, string.len(filter_line)) self:insert_highlight({ "NvimTreeLiveFilterValue" }, prefix_length, string.len(filter_line))
self.index = self.index + 1 self.index = self.index + 1
end end
end
---Sanitize lines for rendering.
---Replace newlines with literal \n
---@private
function Builder:sanitize_lines()
self.lines = vim.tbl_map(function(line)
return line and line:gsub("\n", "\\n") or ""
end, self.lines)
end
---Build all lines with highlights and signs
---@return Builder
function Builder:build()
self:build_header()
self:build_lines()
self:sanitize_lines()
return self return self
end end
function Builder:unwrap() ---TODO refactor back to function; this was left here to reduce PR noise
return self.lines, self.highlights, self.signs ---@param opts table
---@return fun(node: Node): string|nil
function Builder:setup_hidden_display_function(opts)
local hidden_display = opts.renderer.hidden_display
-- options are already validated, so ´hidden_display´ can ONLY be `string` or `function` if type(hidden_display) == "string" then
if type(hidden_display) == "string" then
if hidden_display == "none" then
return function()
return nil
end
elseif hidden_display == "simple" then
return function(hidden_stats)
return utils.default_format_hidden_count(hidden_stats, true)
end
else -- "all"
return function(hidden_stats)
return utils.default_format_hidden_count(hidden_stats, false)
end
end
else -- "function
return function(hidden_stats)
-- In case of missing field such as live_filter we zero it, otherwise keep field as is
hidden_stats = vim.tbl_deep_extend("force", {
live_filter = 0,
git = 0,
buf = 0,
dotfile = 0,
custom = 0,
bookmark = 0,
}, hidden_stats or {})
local ok, result = pcall(hidden_display, hidden_stats)
if not ok then
notify.warn(
"Problem occurred in the function ``opts.renderer.hidden_display`` see nvim-tree.renderer.hidden_display on :h nvim-tree")
return nil
end
return result
end
end
end end
return Builder return Builder

View File

@@ -1,51 +0,0 @@
local marks = require "nvim-tree.marks"
local HL_POSITION = require("nvim-tree.enum").HL_POSITION
local M = {
ICON = {},
hl_pos = HL_POSITION.none,
}
---Bookmark highlight group and position when highlight_bookmark.
---@param node table
---@return HL_POSITION position none when clipboard empty
---@return string|nil group only when node present in clipboard
function M.get_highlight(node)
if M.hl_pos == HL_POSITION.none then
return HL_POSITION.none, nil
end
local mark = marks.get_mark(node)
if mark then
return M.hl_pos, "NvimTreeBookmarkHL"
else
return HL_POSITION.none, nil
end
end
---bookmark icon if marked
---@param node table
---@return HighlightedString|nil bookmark icon
function M.get_icon(node)
if M.config.renderer.icons.show.bookmarks and marks.get_mark(node) then
return M.ICON
end
end
function M.setup(opts)
M.config = {
renderer = opts.renderer,
}
M.hl_pos = HL_POSITION[opts.renderer.highlight_bookmarks] or HL_POSITION.none
M.ICON = {
str = opts.renderer.icons.glyphs.bookmark,
hl = { "NvimTreeBookmark" },
}
vim.fn.sign_define(M.ICON.hl[1], { text = M.ICON.str, texthl = M.ICON.hl[1] })
end
return M

View File

@@ -1,19 +1,24 @@
local HL_POSITION = require("nvim-tree.enum").HL_POSITION local HL_POSITION = require("nvim-tree.enum").HL_POSITION
local diagnostics = require "nvim-tree.diagnostics" local diagnostics = require("nvim-tree.diagnostics")
local M = { local M = {
HS_FILE = {}, -- highlight strings for the icons
HS_FOLDER = {}, HS_ICON = {},
ICON = {},
hl_pos = HL_POSITION.none, -- highlight groups for HL
HG_FILE = {},
HG_FOLDER = {},
-- position for HL
HL_POS = HL_POSITION.none,
} }
---Diagnostics text highlight group when highlight_diagnostics. ---Diagnostics highlight group and position when highlight_diagnostics.
---@param node table ---@param node table
---@return HL_POSITION position none when no status ---@return HL_POSITION position none when no status
---@return string|nil group only when status ---@return string|nil group only when status
function M.get_highlight(node) function M.get_highlight(node)
if not node or M.hl_pos == HL_POSITION.none then if not node or M.HL_POS == HL_POSITION.none then
return HL_POSITION.none, nil return HL_POSITION.none, nil
end end
@@ -26,7 +31,7 @@ function M.get_highlight(node)
end end
if group then if group then
return M.hl_pos, group return M.HL_POS, group
else else
return HL_POSITION.none, nil return HL_POSITION.none, nil
end end
@@ -49,40 +54,38 @@ function M.setup(opts)
} }
if opts.diagnostics.enable and opts.renderer.highlight_diagnostics then if opts.diagnostics.enable and opts.renderer.highlight_diagnostics then
-- TODO add a HL_POSITION M.HL_POS = HL_POSITION[opts.renderer.highlight_diagnostics]
-- M.hl_pos = HL_POSITION[opts.renderer.highlight_diagnostics]
M.hl_pos = HL_POSITION.name
end end
M.HS_FILE[vim.diagnostic.severity.ERROR] = "NvimTreeLspDiagnosticsErrorText" M.HG_FILE[vim.diagnostic.severity.ERROR] = "NvimTreeDiagnosticErrorFileHL"
M.HS_FILE[vim.diagnostic.severity.WARN] = "NvimTreeLspDiagnosticsWarningText" M.HG_FILE[vim.diagnostic.severity.WARN] = "NvimTreeDiagnosticWarningFileHL"
M.HS_FILE[vim.diagnostic.severity.INFO] = "NvimTreeLspDiagnosticsInfoText" M.HG_FILE[vim.diagnostic.severity.INFO] = "NvimTreeDiagnosticInfoFileHL"
M.HS_FILE[vim.diagnostic.severity.HINT] = "NvimTreeLspDiagnosticsHintText" M.HG_FILE[vim.diagnostic.severity.HINT] = "NvimTreeDiagnosticHintFileHL"
M.HS_FOLDER[vim.diagnostic.severity.ERROR] = "NvimTreeLspDiagnosticsErrorFolderText" M.HG_FOLDER[vim.diagnostic.severity.ERROR] = "NvimTreeDiagnosticErrorFolderHL"
M.HS_FOLDER[vim.diagnostic.severity.WARN] = "NvimTreeLspDiagnosticsWarningFolderText" M.HG_FOLDER[vim.diagnostic.severity.WARN] = "NvimTreeDiagnosticWarningFolderHL"
M.HS_FOLDER[vim.diagnostic.severity.INFO] = "NvimTreeLspDiagnosticsInfoFolderText" M.HG_FOLDER[vim.diagnostic.severity.INFO] = "NvimTreeDiagnosticInfoFolderHL"
M.HS_FOLDER[vim.diagnostic.severity.HINT] = "NvimTreeLspDiagnosticsHintFolderText" M.HG_FOLDER[vim.diagnostic.severity.HINT] = "NvimTreeDiagnosticHintFolderHL"
M.ICON[vim.diagnostic.severity.ERROR] = { M.HS_ICON[vim.diagnostic.severity.ERROR] = {
str = M.config.diagnostics.icons.error, str = M.config.diagnostics.icons.error,
hl = { "NvimTreeLspDiagnosticsError" }, hl = { "NvimTreeDiagnosticErrorIcon" },
} }
M.ICON[vim.diagnostic.severity.WARN] = { M.HS_ICON[vim.diagnostic.severity.WARN] = {
str = M.config.diagnostics.icons.warning, str = M.config.diagnostics.icons.warning,
hl = { "NvimTreeLspDiagnosticsWarning" }, hl = { "NvimTreeDiagnosticWarningIcon" },
} }
M.ICON[vim.diagnostic.severity.INFO] = { M.HS_ICON[vim.diagnostic.severity.INFO] = {
str = M.config.diagnostics.icons.info, str = M.config.diagnostics.icons.info,
hl = { "NvimTreeLspDiagnosticsInformation" }, hl = { "NvimTreeDiagnosticInfoIcon" },
} }
M.ICON[vim.diagnostic.severity.HINT] = { M.HS_ICON[vim.diagnostic.severity.HINT] = {
str = M.config.diagnostics.icons.hint, str = M.config.diagnostics.icons.hint,
hl = { "NvimTreeLspDiagnosticsHint" }, hl = { "NvimTreeDiagnosticHintIcon" },
} }
for _, i in ipairs(M.ICON) do for _, i in ipairs(M.HS_ICON) do
vim.fn.sign_define(i.hl[1], { text = i.str, texthl = i.hl[1] }) vim.fn.sign_define(i.hl[1], { text = i.str, texthl = i.hl[1] })
end end
end end

View File

@@ -1,6 +1,6 @@
local M = {} local M = {}
local utils = require "nvim-tree.utils" local utils = require("nvim-tree.utils")
local function hide(win) local function hide(win)
if win then if win then
@@ -42,7 +42,7 @@ local function show()
return return
end end
local line = vim.fn.getline "." local line = vim.fn.getline(".")
local leftcol = vim.fn.winsaveview().leftcol local leftcol = vim.fn.winsaveview().leftcol
-- hide full name if left column of node in nvim-tree win is not zero -- hide full name if left column of node in nvim-tree win is not zero
if leftcol ~= 0 then if leftcol ~= 0 then
@@ -57,9 +57,9 @@ local function show()
end end
M.popup_win = vim.api.nvim_open_win(vim.api.nvim_create_buf(false, false), false, { M.popup_win = vim.api.nvim_open_win(vim.api.nvim_create_buf(false, false), false, {
relative = "cursor", relative = "win",
row = 0, row = 0,
col = 1 - vim.fn.getcursorcharpos()[3], bufpos = { vim.api.nvim_win_get_cursor(0)[1] - 1, 0 },
width = math.min(text_width, vim.o.columns - 2), width = math.min(text_width, vim.o.columns - 2),
height = 1, height = 1,
noautocmd = true, noautocmd = true,
@@ -67,14 +67,21 @@ local function show()
}) })
local ns_id = vim.api.nvim_get_namespaces()["NvimTreeHighlights"] local ns_id = vim.api.nvim_get_namespaces()["NvimTreeHighlights"]
local extmarks = vim.api.nvim_buf_get_extmarks(0, ns_id, { line_nr - 1, 0 }, { line_nr - 1, -1 }, { details = 1 }) local extmarks = vim.api.nvim_buf_get_extmarks(0, ns_id, { line_nr - 1, 0 }, { line_nr - 1, -1 }, { details = true })
vim.api.nvim_win_call(M.popup_win, function() vim.api.nvim_win_call(M.popup_win, function()
vim.api.nvim_buf_set_lines(0, 0, -1, true, { line }) vim.api.nvim_buf_set_lines(0, 0, -1, true, { line })
for _, extmark in ipairs(extmarks) do for _, extmark in ipairs(extmarks) do
local hl = extmark[4] -- nvim 0.10 luadoc is incorrect: vim.api.keyset.get_extmark_item is missing the extmark_id at the start
vim.api.nvim_buf_add_highlight(0, ns_id, hl.hl_group, 0, extmark[3], hl.end_col)
---@cast extmark table
---@type integer
local col = extmark[3]
---@type vim.api.keyset.extmark_details
local details = extmark[4]
vim.api.nvim_buf_add_highlight(0, ns_id, details.hl_group, 0, col, details.end_col)
end end
vim.cmd [[ setlocal nowrap cursorline noswapfile nobuflisted buftype=nofile bufhidden=hide ]] vim.cmd([[ setlocal nowrap cursorline noswapfile nobuflisted buftype=nofile bufhidden=hide ]])
end) end)
end end

View File

@@ -1,191 +0,0 @@
local notify = require "nvim-tree.notify"
local explorer_node = require "nvim-tree.explorer.node"
local M = {}
local function build_icons_table(i)
local icons = {
staged = { str = i.staged, hl = { "NvimTreeGitStaged" }, ord = 1 },
unstaged = { str = i.unstaged, hl = { "NvimTreeGitDirty" }, ord = 2 },
renamed = { str = i.renamed, hl = { "NvimTreeGitRenamed" }, ord = 3 },
deleted = { str = i.deleted, hl = { "NvimTreeGitDeleted" }, ord = 4 },
unmerged = { str = i.unmerged, hl = { "NvimTreeGitMerge" }, ord = 5 },
untracked = { str = i.untracked, hl = { "NvimTreeGitNew" }, ord = 6 },
ignored = { str = i.ignored, hl = { "NvimTreeGitIgnored" }, ord = 7 },
}
return {
["M "] = { icons.staged },
[" M"] = { icons.unstaged },
["C "] = { icons.staged },
[" C"] = { icons.unstaged },
["CM"] = { icons.unstaged },
[" T"] = { icons.unstaged },
["T "] = { icons.staged },
["TM"] = { icons.staged, icons.unstaged },
["MM"] = { icons.staged, icons.unstaged },
["MD"] = { icons.staged },
["A "] = { icons.staged },
["AD"] = { icons.staged },
[" A"] = { icons.untracked },
-- not sure about this one
["AA"] = { icons.unmerged, icons.untracked },
["AU"] = { icons.unmerged, icons.untracked },
["AM"] = { icons.staged, icons.unstaged },
["??"] = { icons.untracked },
["R "] = { icons.renamed },
[" R"] = { icons.renamed },
["RM"] = { icons.unstaged, icons.renamed },
["UU"] = { icons.unmerged },
["UD"] = { icons.unmerged },
["UA"] = { icons.unmerged },
[" D"] = { icons.deleted },
["D "] = { icons.deleted },
["DA"] = { icons.unstaged },
["RD"] = { icons.deleted },
["DD"] = { icons.deleted },
["DU"] = { icons.deleted, icons.unmerged },
["!!"] = { icons.ignored },
dirty = { icons.unstaged },
}
end
local function build_hl_table()
local file = {
["M "] = "NvimTreeFileStaged",
["C "] = "NvimTreeFileStaged",
["AA"] = "NvimTreeFileStaged",
["AD"] = "NvimTreeFileStaged",
["MD"] = "NvimTreeFileStaged",
["T "] = "NvimTreeFileStaged",
["TT"] = "NvimTreeFileStaged",
[" M"] = "NvimTreeFileDirty",
["CM"] = "NvimTreeFileDirty",
[" C"] = "NvimTreeFileDirty",
[" T"] = "NvimTreeFileDirty",
["MM"] = "NvimTreeFileDirty",
["AM"] = "NvimTreeFileDirty",
dirty = "NvimTreeFileDirty",
["A "] = "NvimTreeFileStaged",
["??"] = "NvimTreeFileNew",
["AU"] = "NvimTreeFileMerge",
["UU"] = "NvimTreeFileMerge",
["UD"] = "NvimTreeFileMerge",
["DU"] = "NvimTreeFileMerge",
["UA"] = "NvimTreeFileMerge",
[" D"] = "NvimTreeFileDeleted",
["DD"] = "NvimTreeFileDeleted",
["RD"] = "NvimTreeFileDeleted",
["D "] = "NvimTreeFileDeleted",
["R "] = "NvimTreeFileRenamed",
["RM"] = "NvimTreeFileRenamed",
[" R"] = "NvimTreeFileRenamed",
["!!"] = "NvimTreeFileIgnored",
[" A"] = "none",
}
local folder = {}
for k, v in pairs(file) do
folder[k] = v:gsub("File", "Folder")
end
return file, folder
end
local function nil_() end
local function warn_status(git_status)
notify.warn(string.format("Unrecognized git state '%s'", git_status))
end
---@param node table
---@return HighlightedString[]|nil
local function get_icons_(node)
local git_status = explorer_node.get_git_status(node)
if git_status == nil then
return nil
end
local inserted = {}
local iconss = {}
for _, s in pairs(git_status) do
local icons = M.git_icons[s]
if not icons then
if not M.config.highlight_git then
warn_status(s)
end
return nil
end
for _, icon in pairs(icons) do
if #icon.str > 0 then
if not inserted[icon] then
table.insert(iconss, icon)
inserted[icon] = true
end
end
end
end
if #iconss == 0 then
return nil
end
-- sort icons so it looks slightly better
table.sort(iconss, function(a, b)
return a.ord < b.ord
end)
return iconss
end
function M.setup_signs(i)
vim.fn.sign_define("NvimTreeGitDirty", { text = i.unstaged, texthl = "NvimTreeGitDirty" })
vim.fn.sign_define("NvimTreeGitStaged", { text = i.staged, texthl = "NvimTreeGitStaged" })
vim.fn.sign_define("NvimTreeGitMerge", { text = i.unmerged, texthl = "NvimTreeGitMerge" })
vim.fn.sign_define("NvimTreeGitRenamed", { text = i.renamed, texthl = "NvimTreeGitRenamed" })
vim.fn.sign_define("NvimTreeGitNew", { text = i.untracked, texthl = "NvimTreeGitNew" })
vim.fn.sign_define("NvimTreeGitDeleted", { text = i.deleted, texthl = "NvimTreeGitDeleted" })
vim.fn.sign_define("NvimTreeGitIgnored", { text = i.ignored, texthl = "NvimTreeGitIgnored" })
end
local function get_highlight_(node)
local git_status = explorer_node.get_git_status(node)
if git_status == nil then
return
end
if node.nodes then
return M.folder_hl[git_status[1]]
else
return M.file_hl[git_status[1]]
end
end
function M.setup(opts)
M.config = opts.renderer
M.git_icons = build_icons_table(opts.renderer.icons.glyphs.git)
M.file_hl, M.folder_hl = build_hl_table()
if opts.renderer.icons.git_placement == "signcolumn" then
M.setup_signs(opts.renderer.icons.glyphs.git)
end
if opts.renderer.icons.show.git then
M.get_icons = get_icons_
else
M.get_icons = nil_
end
if opts.renderer.highlight_git then
M.get_highlight = get_highlight_
else
M.get_highlight = nil_
end
M.git_show_on_open_dirs = opts.git.show_on_open_dirs
end
return M

View File

@@ -65,7 +65,12 @@ local function get_file_icon_webdev(fname, extension)
-- If there are more extensions to the file, try to grab the icon for them recursively -- If there are more extensions to the file, try to grab the icon for them recursively
return get_file_icon_webdev(fname, string.match(extension, "%.(.*)")) return get_file_icon_webdev(fname, string.match(extension, "%.(.*)"))
else else
return get_file_icon_default() local devicons_default = M.devicons.get_default_icon()
if devicons_default and type(devicons_default.icon) == "string" and type(devicons_default.name) == "string" then
return devicons_default.icon, "DevIcon" .. devicons_default.name
else
return get_file_icon_default()
end
end end
end end
@@ -102,7 +107,7 @@ end
function M.setup(opts) function M.setup(opts)
M.config = opts.renderer.icons M.config = opts.renderer.icons
M.devicons = pcall(require, "nvim-web-devicons") and require "nvim-web-devicons" or nil M.devicons = pcall(require, "nvim-web-devicons") and require("nvim-web-devicons") or nil
end end
return M return M

View File

@@ -0,0 +1,15 @@
local M = {}
M.diagnostics = require("nvim-tree.renderer.components.diagnostics")
M.full_name = require("nvim-tree.renderer.components.full-name")
M.icons = require("nvim-tree.renderer.components.icons")
M.padding = require("nvim-tree.renderer.components.padding")
function M.setup(opts)
M.diagnostics.setup(opts)
M.full_name.setup(opts)
M.icons.setup(opts)
M.padding.setup(opts)
end
return M

View File

@@ -1,41 +0,0 @@
local modified = require "nvim-tree.modified"
local M = {}
local HIGHLIGHT = "NvimTreeModifiedFile"
---return modified icon if node is modified, otherwise return empty string
---@param node table
---@return HighlightedString|nil modified icon
function M.get_icon(node)
if not modified.is_modified(node) or not M.show_icon then
return nil
end
return { str = M.icon, hl = { HIGHLIGHT } }
end
function M.setup_signs()
vim.fn.sign_define(HIGHLIGHT, { text = M.icon, texthl = HIGHLIGHT })
end
---@param node table
---@return string|nil
function M.get_highlight(node)
if not modified.is_modified(node) then
return nil
end
return HIGHLIGHT
end
function M.setup(opts)
M.icon = opts.renderer.icons.glyphs.modified
M.show_icon = opts.renderer.icons.show.modified
if opts.renderer.icons.modified_placement == "signcolumn" then
M.setup_signs()
end
end
return M

View File

@@ -19,7 +19,7 @@ local function check_siblings_for_folder(node, with_arrows)
return false return false
end end
local function get_padding_indent_markers(depth, idx, nodes_number, markers, with_arrows, inline_arrows, node) local function get_padding_indent_markers(depth, idx, nodes_number, markers, with_arrows, inline_arrows, node, early_stop)
local base_padding = with_arrows and (not node.nodes or depth > 0) and " " or "" local base_padding = with_arrows and (not node.nodes or depth > 0) and " " or ""
local padding = (inline_arrows or depth == 0) and base_padding or "" local padding = (inline_arrows or depth == 0) and base_padding or ""
@@ -27,7 +27,7 @@ local function get_padding_indent_markers(depth, idx, nodes_number, markers, wit
local has_folder_sibling = check_siblings_for_folder(node, with_arrows) local has_folder_sibling = check_siblings_for_folder(node, with_arrows)
local indent = string.rep(" ", M.config.indent_width - 1) local indent = string.rep(" ", M.config.indent_width - 1)
markers[depth] = idx ~= nodes_number markers[depth] = idx ~= nodes_number
for i = 1, depth do for i = 1, depth - early_stop do
local glyph local glyph
if idx == nodes_number and i == depth then if idx == nodes_number and i == depth then
local bottom_width = M.config.indent_width - 2 + (with_arrows and not inline_arrows and has_folder_sibling and 2 or 0) local bottom_width = M.config.indent_width - 2 + (with_arrows and not inline_arrows and has_folder_sibling and 2 or 0)
@@ -62,7 +62,7 @@ end
---@param node table ---@param node table
---@param markers table ---@param markers table
---@return HighlightedString[] ---@return HighlightedString[]
function M.get_indent_markers(depth, idx, nodes_number, node, markers) function M.get_indent_markers(depth, idx, nodes_number, node, markers, early_stop)
local str = "" local str = ""
local show_arrows = M.config.icons.show.folder_arrow local show_arrows = M.config.icons.show.folder_arrow
@@ -71,7 +71,7 @@ function M.get_indent_markers(depth, idx, nodes_number, node, markers)
local indent_width = M.config.indent_width local indent_width = M.config.indent_width
if show_markers then if show_markers then
str = str .. get_padding_indent_markers(depth, idx, nodes_number, markers, show_arrows, inline_arrows, node) str = str .. get_padding_indent_markers(depth, idx, nodes_number, markers, show_arrows, inline_arrows, node, early_stop or 0)
else else
str = str .. string.rep(" ", depth * indent_width) str = str .. string.rep(" ", depth * indent_width)
end end
@@ -117,7 +117,7 @@ function M.setup(opts)
return " " return " "
end end
-- return the first character from the UTF-8 encoded string; we may use utf8.codes from Lua 5.3 when available -- return the first character from the UTF-8 encoded string; we may use utf8.codes from Lua 5.3 when available
return symbol:match "[%z\1-\127\194-\244][\128-\191]*" return symbol:match("[%z\1-\127\194-\244][\128-\191]*")
end end
for k, v in pairs(M.config.indent_markers.icons) do for k, v in pairs(M.config.indent_markers.icons) do

View File

@@ -0,0 +1,51 @@
local HL_POSITION = require("nvim-tree.enum").HL_POSITION
local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT
local Decorator = require("nvim-tree.renderer.decorator")
---@class (exact) DecoratorBookmarks: Decorator
---@field icon HighlightedString
local DecoratorBookmarks = Decorator:new()
---@param opts table
---@param explorer Explorer
---@return DecoratorBookmarks
function DecoratorBookmarks:new(opts, explorer)
local o = Decorator.new(self, {
explorer = explorer,
enabled = true,
hl_pos = HL_POSITION[opts.renderer.highlight_bookmarks] or HL_POSITION.none,
icon_placement = ICON_PLACEMENT[opts.renderer.icons.bookmarks_placement] or ICON_PLACEMENT.none,
})
---@cast o DecoratorBookmarks
if opts.renderer.icons.show.bookmarks then
o.icon = {
str = opts.renderer.icons.glyphs.bookmark,
hl = { "NvimTreeBookmarkIcon" },
}
o:define_sign(o.icon)
end
return o
end
---Bookmark icon: renderer.icons.show.bookmarks and node is marked
---@param node Node
---@return HighlightedString[]|nil icons
function DecoratorBookmarks:calculate_icons(node)
if self.explorer.marks:get(node) then
return { self.icon }
end
end
---Bookmark highlight: renderer.highlight_bookmarks and node is marked
---@param node Node
---@return string|nil group
function DecoratorBookmarks:calculate_highlight(node)
if self.hl_pos ~= HL_POSITION.none and self.explorer.marks:get(node) then
return "NvimTreeBookmarkHL"
end
end
return DecoratorBookmarks

View File

@@ -0,0 +1,35 @@
local HL_POSITION = require("nvim-tree.enum").HL_POSITION
local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT
local Decorator = require("nvim-tree.renderer.decorator")
---@class (exact) DecoratorCopied: Decorator
---@field enabled boolean
---@field icon HighlightedString|nil
local DecoratorCopied = Decorator:new()
---@param opts table
---@param explorer Explorer
---@return DecoratorCopied
function DecoratorCopied:new(opts, explorer)
local o = Decorator.new(self, {
explorer = explorer,
enabled = true,
hl_pos = HL_POSITION[opts.renderer.highlight_clipboard] or HL_POSITION.none,
icon_placement = ICON_PLACEMENT.none,
})
---@cast o DecoratorCopied
return o
end
---Copied highlight: renderer.highlight_clipboard and node is copied
---@param node Node
---@return string|nil group
function DecoratorCopied:calculate_highlight(node)
if self.hl_pos ~= HL_POSITION.none and self.explorer.clipboard:is_copied(node) then
return "NvimTreeCopiedHL"
end
end
return DecoratorCopied

View File

@@ -0,0 +1,35 @@
local HL_POSITION = require("nvim-tree.enum").HL_POSITION
local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT
local Decorator = require("nvim-tree.renderer.decorator")
---@class (exact) DecoratorCut: Decorator
---@field enabled boolean
---@field icon HighlightedString|nil
local DecoratorCut = Decorator:new()
---@param opts table
---@param explorer Explorer
---@return DecoratorCut
function DecoratorCut:new(opts, explorer)
local o = Decorator.new(self, {
explorer = explorer,
enabled = true,
hl_pos = HL_POSITION[opts.renderer.highlight_clipboard] or HL_POSITION.none,
icon_placement = ICON_PLACEMENT.none,
})
---@cast o DecoratorCut
return o
end
---Cut highlight: renderer.highlight_clipboard and node is cut
---@param node Node
---@return string|nil group
function DecoratorCut:calculate_highlight(node)
if self.hl_pos ~= HL_POSITION.none and self.explorer.clipboard:is_cut(node) then
return "NvimTreeCutHL"
end
end
return DecoratorCut

View File

@@ -0,0 +1,112 @@
local diagnostics = require("nvim-tree.diagnostics")
local HL_POSITION = require("nvim-tree.enum").HL_POSITION
local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT
local Decorator = require("nvim-tree.renderer.decorator")
-- highlight groups by severity
local HG_ICON = {
[vim.diagnostic.severity.ERROR] = "NvimTreeDiagnosticErrorIcon",
[vim.diagnostic.severity.WARN] = "NvimTreeDiagnosticWarnIcon",
[vim.diagnostic.severity.INFO] = "NvimTreeDiagnosticInfoIcon",
[vim.diagnostic.severity.HINT] = "NvimTreeDiagnosticHintIcon",
}
local HG_FILE = {
[vim.diagnostic.severity.ERROR] = "NvimTreeDiagnosticErrorFileHL",
[vim.diagnostic.severity.WARN] = "NvimTreeDiagnosticWarnFileHL",
[vim.diagnostic.severity.INFO] = "NvimTreeDiagnosticInfoFileHL",
[vim.diagnostic.severity.HINT] = "NvimTreeDiagnosticHintFileHL",
}
local HG_FOLDER = {
[vim.diagnostic.severity.ERROR] = "NvimTreeDiagnosticErrorFolderHL",
[vim.diagnostic.severity.WARN] = "NvimTreeDiagnosticWarnFolderHL",
[vim.diagnostic.severity.INFO] = "NvimTreeDiagnosticInfoFolderHL",
[vim.diagnostic.severity.HINT] = "NvimTreeDiagnosticHintFolderHL",
}
-- opts.diagnostics.icons.
local ICON_KEYS = {
["error"] = vim.diagnostic.severity.ERROR,
["warning"] = vim.diagnostic.severity.WARN,
["info"] = vim.diagnostic.severity.INFO,
["hint"] = vim.diagnostic.severity.HINT,
}
---@class (exact) DecoratorDiagnostics: Decorator
---@field icons HighlightedString[]
local DecoratorDiagnostics = Decorator:new()
---@param opts table
---@param explorer Explorer
---@return DecoratorDiagnostics
function DecoratorDiagnostics:new(opts, explorer)
local o = Decorator.new(self, {
explorer = explorer,
enabled = opts.diagnostics.enable,
hl_pos = HL_POSITION[opts.renderer.highlight_diagnostics] or HL_POSITION.none,
icon_placement = ICON_PLACEMENT[opts.renderer.icons.diagnostics_placement] or ICON_PLACEMENT.none,
})
---@cast o DecoratorDiagnostics
if not o.enabled then
return o
end
if opts.renderer.icons.show.diagnostics then
o.icons = {}
for name, sev in pairs(ICON_KEYS) do
o.icons[sev] = {
str = opts.diagnostics.icons[name],
hl = { HG_ICON[sev] },
}
o:define_sign(o.icons[sev])
end
end
return o
end
---Diagnostic icon: diagnostics.enable, renderer.icons.show.diagnostics and node has status
---@param node Node
---@return HighlightedString[]|nil icons
function DecoratorDiagnostics:calculate_icons(node)
if node and self.enabled and self.icons then
local diag_status = diagnostics.get_diag_status(node)
local diag_value = diag_status and diag_status.value
if diag_value then
return { self.icons[diag_value] }
end
end
end
---Diagnostic highlight: diagnostics.enable, renderer.highlight_diagnostics and node has status
---@param node Node
---@return string|nil group
function DecoratorDiagnostics:calculate_highlight(node)
if not node or not self.enabled or self.hl_pos == HL_POSITION.none then
return nil
end
local diag_status = diagnostics.get_diag_status(node)
local diag_value = diag_status and diag_status.value
if not diag_value then
return nil
end
local group
if node.nodes then
group = HG_FOLDER[diag_value]
else
group = HG_FILE[diag_value]
end
if group then
return group
else
return nil
end
end
return DecoratorDiagnostics

View File

@@ -0,0 +1,223 @@
local notify = require("nvim-tree.notify")
local explorer_node = require("nvim-tree.explorer.node")
local HL_POSITION = require("nvim-tree.enum").HL_POSITION
local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT
local Decorator = require("nvim-tree.renderer.decorator")
---@class HighlightedStringGit: HighlightedString
---@field ord number decreasing priority
---@class (exact) DecoratorGit: Decorator
---@field file_hl table<string, string> by porcelain status e.g. "AM"
---@field folder_hl table<string, string> by porcelain status
---@field icons_by_status HighlightedStringGit[] by human status
---@field icons_by_xy table<string, HighlightedStringGit[]> by porcelain status
local DecoratorGit = Decorator:new()
---@param opts table
---@param explorer Explorer
---@return DecoratorGit
function DecoratorGit:new(opts, explorer)
local o = Decorator.new(self, {
explorer = explorer,
enabled = opts.git.enable,
hl_pos = HL_POSITION[opts.renderer.highlight_git] or HL_POSITION.none,
icon_placement = ICON_PLACEMENT[opts.renderer.icons.git_placement] or ICON_PLACEMENT.none,
})
---@cast o DecoratorGit
if not o.enabled then
return o
end
if o.hl_pos ~= HL_POSITION.none then
o:build_hl_table()
end
if opts.renderer.icons.show.git then
o:build_icons_by_status(opts.renderer.icons.glyphs.git)
o:build_icons_by_xy(o.icons_by_status)
for _, icon in pairs(o.icons_by_status) do
self:define_sign(icon)
end
end
return o
end
---@param glyphs table<string, string> user glyps
function DecoratorGit:build_icons_by_status(glyphs)
self.icons_by_status = {
staged = { str = glyphs.staged, hl = { "NvimTreeGitStagedIcon" }, ord = 1 },
unstaged = { str = glyphs.unstaged, hl = { "NvimTreeGitDirtyIcon" }, ord = 2 },
renamed = { str = glyphs.renamed, hl = { "NvimTreeGitRenamedIcon" }, ord = 3 },
deleted = { str = glyphs.deleted, hl = { "NvimTreeGitDeletedIcon" }, ord = 4 },
unmerged = { str = glyphs.unmerged, hl = { "NvimTreeGitMergeIcon" }, ord = 5 },
untracked = { str = glyphs.untracked, hl = { "NvimTreeGitNewIcon" }, ord = 6 },
ignored = { str = glyphs.ignored, hl = { "NvimTreeGitIgnoredIcon" }, ord = 7 },
}
end
---@param icons HighlightedStringGit[]
function DecoratorGit:build_icons_by_xy(icons)
self.icons_by_xy = {
["M "] = { icons.staged },
[" M"] = { icons.unstaged },
["C "] = { icons.staged },
[" C"] = { icons.unstaged },
["CM"] = { icons.unstaged },
[" T"] = { icons.unstaged },
["T "] = { icons.staged },
["TM"] = { icons.staged, icons.unstaged },
["MM"] = { icons.staged, icons.unstaged },
["MD"] = { icons.staged },
["A "] = { icons.staged },
["AD"] = { icons.staged },
[" A"] = { icons.untracked },
-- not sure about this one
["AA"] = { icons.unmerged, icons.untracked },
["AU"] = { icons.unmerged, icons.untracked },
["AM"] = { icons.staged, icons.unstaged },
["??"] = { icons.untracked },
["R "] = { icons.renamed },
[" R"] = { icons.renamed },
["RM"] = { icons.unstaged, icons.renamed },
["UU"] = { icons.unmerged },
["UD"] = { icons.unmerged },
["UA"] = { icons.unmerged },
[" D"] = { icons.deleted },
["D "] = { icons.deleted },
["DA"] = { icons.unstaged },
["RD"] = { icons.deleted },
["DD"] = { icons.deleted },
["DU"] = { icons.deleted, icons.unmerged },
["!!"] = { icons.ignored },
dirty = { icons.unstaged },
}
end
function DecoratorGit:build_hl_table()
self.file_hl = {
["M "] = "NvimTreeGitFileStagedHL",
["C "] = "NvimTreeGitFileStagedHL",
["AA"] = "NvimTreeGitFileStagedHL",
["AD"] = "NvimTreeGitFileStagedHL",
["MD"] = "NvimTreeGitFileStagedHL",
["T "] = "NvimTreeGitFileStagedHL",
["TT"] = "NvimTreeGitFileStagedHL",
[" M"] = "NvimTreeGitFileDirtyHL",
["CM"] = "NvimTreeGitFileDirtyHL",
[" C"] = "NvimTreeGitFileDirtyHL",
[" T"] = "NvimTreeGitFileDirtyHL",
["MM"] = "NvimTreeGitFileDirtyHL",
["AM"] = "NvimTreeGitFileDirtyHL",
dirty = "NvimTreeGitFileDirtyHL",
["A "] = "NvimTreeGitFileStagedHL",
["??"] = "NvimTreeGitFileNewHL",
["AU"] = "NvimTreeGitFileMergeHL",
["UU"] = "NvimTreeGitFileMergeHL",
["UD"] = "NvimTreeGitFileMergeHL",
["DU"] = "NvimTreeGitFileMergeHL",
["UA"] = "NvimTreeGitFileMergeHL",
[" D"] = "NvimTreeGitFileDeletedHL",
["DD"] = "NvimTreeGitFileDeletedHL",
["RD"] = "NvimTreeGitFileDeletedHL",
["D "] = "NvimTreeGitFileDeletedHL",
["R "] = "NvimTreeGitFileRenamedHL",
["RM"] = "NvimTreeGitFileRenamedHL",
[" R"] = "NvimTreeGitFileRenamedHL",
["!!"] = "NvimTreeGitFileIgnoredHL",
[" A"] = "none",
}
self.folder_hl = {}
for k, v in pairs(self.file_hl) do
self.folder_hl[k] = v:gsub("File", "Folder")
end
end
---Git icons: git.enable, renderer.icons.show.git and node has status
---@param node Node
---@return HighlightedString[]|nil modified icon
function DecoratorGit:calculate_icons(node)
if not node or not self.enabled or not self.icons_by_xy then
return nil
end
local git_status = explorer_node.get_git_status(node)
if git_status == nil then
return nil
end
local inserted = {}
local iconss = {}
for _, s in pairs(git_status) do
local icons = self.icons_by_xy[s]
if not icons then
if self.hl_pos == HL_POSITION.none then
notify.warn(string.format("Unrecognized git state '%s'", git_status))
end
return nil
end
for _, icon in pairs(icons) do
if #icon.str > 0 then
if not inserted[icon] then
table.insert(iconss, icon)
inserted[icon] = true
end
end
end
end
if #iconss == 0 then
return nil
end
-- sort icons so it looks slightly better
table.sort(iconss, function(a, b)
return a.ord < b.ord
end)
return iconss
end
---Get the first icon as the sign if appropriate
---@param node Node
---@return string|nil name
function DecoratorGit:sign_name(node)
if self.icon_placement ~= ICON_PLACEMENT.signcolumn then
return
end
local icons = self:calculate_icons(node)
if icons and #icons > 0 then
return icons[1].hl[1]
end
end
---Git highlight: git.enable, renderer.highlight_git and node has status
---@param node Node
---@return string|nil group
function DecoratorGit:calculate_highlight(node)
if not node or not self.enabled or self.hl_pos == HL_POSITION.none then
return nil
end
local git_status = explorer_node.get_git_status(node)
if not git_status then
return nil
end
if node.nodes then
return self.folder_hl[git_status[1]]
else
return self.file_hl[git_status[1]]
end
end
return DecoratorGit

View File

@@ -0,0 +1,57 @@
local HL_POSITION = require("nvim-tree.enum").HL_POSITION
local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT
local explorer_node = require("nvim-tree.explorer.node")
local Decorator = require("nvim-tree.renderer.decorator")
---@class (exact) DecoratorHidden: Decorator
---@field icon HighlightedString|nil
local DecoratorHidden = Decorator:new()
---@param opts table
---@param explorer Explorer
---@return DecoratorHidden
function DecoratorHidden:new(opts, explorer)
local o = Decorator.new(self, {
explorer = explorer,
enabled = true,
hl_pos = HL_POSITION[opts.renderer.highlight_hidden] or HL_POSITION.none,
icon_placement = ICON_PLACEMENT[opts.renderer.icons.hidden_placement] or ICON_PLACEMENT.none,
})
---@cast o DecoratorHidden
if opts.renderer.icons.show.hidden then
o.icon = {
str = opts.renderer.icons.glyphs.hidden,
hl = { "NvimTreeHiddenIcon" },
}
o:define_sign(o.icon)
end
return o
end
---Hidden icon: renderer.icons.show.hidden and node starts with `.` (dotfile).
---@param node Node
---@return HighlightedString[]|nil icons
function DecoratorHidden:calculate_icons(node)
if self.enabled and explorer_node.is_dotfile(node) then
return { self.icon }
end
end
---Hidden highlight: renderer.highlight_hidden and node starts with `.` (dotfile).
---@param node Node
---@return string|nil group
function DecoratorHidden:calculate_highlight(node)
if not self.enabled or self.hl_pos == HL_POSITION.none or (not explorer_node.is_dotfile(node)) then
return nil
end
if node.nodes then
return "NvimTreeHiddenFolderHL"
else
return "NvimTreeHiddenFileHL"
end
end
return DecoratorHidden

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