Compare commits

...

651 Commits

Author SHA1 Message Date
Mateusz Russak
fa051cf990 refactor(#2826): multi instance nvim-tree.view 2024-08-04 13:31:11 +02:00
Mateusz Russak
0f2cda6ce0 docs: add missing live filter luadocs 2024-08-04 11:26:14 +02:00
Mateusz Russak
e6374abc7d Merge branch 'live-filter-multiinstace' of github.com:nvim-tree/nvim-tree.lua into live-filter-multiinstace 2024-08-04 11:20:04 +02:00
Mateusz Russak
32314fd3ee Update lua/nvim-tree/api.lua
Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-08-04 11:19:31 +02:00
Mateusz Russak
e2853ee4fb Merge branch 'master' into live-filter-multiinstace 2024-08-04 11:19:06 +02:00
Mateusz Russak
85fba095cd Merge branch 'master' into live-filter-multiinstace 2024-07-28 11:28:26 +02:00
Mateusz Russak
d7504b3963 fix: style 2024-07-28 11:26:59 +02:00
Mateusz Russak
a634a1bb4d fix: api and filtration 2024-07-28 11:19:37 +02:00
Mateusz Russak
9deac32a40 refactor: all usages going through the explorer 2024-07-28 10:16:02 +02: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
c6ae2431bc Merge branch 'master' into live-filter-multiinstace 2024-07-27 13:26:26 +02: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
Mateusz Russak
cc2d8c7475 feat(#2827): Multi Instance: Refactor: nvim-tree.live-filter 2024-07-23 18:53:16 +02:00
Alexander Courtis
4e396b2624 refactor(#2830): multi instance nvim-tree.marks (#2838)
refactor(#2380): multi instance nvim-tree.marks
2024-07-21 16:12:42 +10:00
Everton Jr.
48a9290757 feat: add renderer.highlight_hidden, renderer.icons.show.hidden and renderer.icons.hidden_placement for dotfile icons/highlights (#2840)
* feat(hidden_decorator): Allow hidden (dotfiles) to be highlighted, both icon and name (this not related to git highlights).

Better defaults

squashed

docs(hidden)

docs(hidden)

docs(hidden)

* fix(typo): small typo on hl groups

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

* docs: update docs on hidden highlight

---------

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

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

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

* feat(icon_placement): consolidate doc

---------

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

* fix(#2813): check if is macos

---------

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

* rely on  when resize

* Fix docs

---------

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

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

---
updated-dependencies:
- dependency-name: nvim-neorocks/luarocks-tag-release
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2024-07-07 12:55:47 +10:00
Epheien
869c064721 feat(#2127): add experimental.actions.open_file.relative_path to open files with a relative path rather than absolute (#2805)
* temp workaround for issue #2803

* fix #2127 and #2803

* chore(#2127): read the configuration correctly

* feat(#2127): add help

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

* feat(#2127): update help

---------

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

Bumps [amannn/action-semantic-pull-request](https://github.com/amannn/action-semantic-pull-request) from 5.5.2 to 5.5.3.
- [Release notes](https://github.com/amannn/action-semantic-pull-request/releases)
- [Changelog](https://github.com/amannn/action-semantic-pull-request/blob/main/CHANGELOG.md)
- [Commits](https://github.com/amannn/action-semantic-pull-request/compare/v5.5.2...v5.5.3)

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

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

* add neovim minimum version 0.9 notice

---------

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

* refactor(#2787): enable deprecated checks

* refactor(#2787): replace deprecated

* refactor(#2787): replace deprecated

* refactor(#2787): replace deprecated

* refactor(#2787): replace deprecated

* refactor(#2787): use inline deprecation disabling

* refactor(#2787): replace deprecated

* refactor(#2787): replace deprecated

* refactor(#2787): replace deprecated

* refactor(#2787): replace deprecated

* refactor(#2787): replace deprecated

* refactor(#2787): replace deprecated

* refactor(#2787): replace deprecated

* refactor(#2787): replace deprecated

* refactor(#2787): replace deprecated

* refactor(#2787): deprecated are now warnings

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

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

This reverts commit b6b4c32fcb.

* refactor(#2787): suppress deprecated until 0.11

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

* refactor(#2787): reset globals

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

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

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

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

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

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

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

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

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

* refactor(#2731): style

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

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

* refactor(#2731): resolve warnings

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

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

This reverts commit 9c0526b7b0.

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

This reverts commit f534fbc606.

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

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

* refactor(#2731): resolve warnings

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

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

* refactor(#2731): resolve warnings

* refactor(#2731): remove vim global

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

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

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

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

* Deprecation fix

* remove unnecessary assignment

---------

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

* refactor

* restrict to full semver versions

* tweak luarocks descriptions

* remove test release following successful run

---------

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

* fix(#2733): escape trash path

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

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

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

* fix format string

* style

---------

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

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

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

* fix(perf): add path_type to filter functions

* fix: replace undefined type

* fix: correct Node.fs_stat type

* fix: file info popup check fs_stat not nil

* refactor: add stat to should_filter, Node constructor

* perf: early return if bookmark is empty

---------

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

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

* Pass ci checks

* Add config migration for update_root and ignore_list

* Missed one mention of update root in find_file.lua

* Update migration code

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

* make docs consistent

* match on filename instead of entire path

* exclude as a function

* fix docs

* default exclude value

---------

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

* naming refactoring

* change name to enable

* fix default opt

* fix api name

* update doc

* remove default keybinding, toggle live filter

* add API doc

---------

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

* doc: remove duplicate bug fix entry

---------

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

* fix: dont match mark's siblings

* fix: match mark from the start

---------

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

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

---------

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

* fix: change keymap for copy basename

* fix: use double quotes

* fix: add missing help

---------

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

* fix: searchcount in pcall

---------

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

* Style check error? fixed

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

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

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

* docs: add nvim-tree-index-opts

* docs: add nvim-tree-index-api

* docs: sort indices

* docs: less verbose

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

* docs: add highlight examples to quickstart

* docs: document pre-overhaul SpellCap groups

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

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

* ci: fix: stylua check

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

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

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

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

* fix: update doc for custom filter function signature

* fix: add custom filter to `ACCEPTED_TYPES`

* fix: accept single function for custom filter

* fix: change custom filter on `ACCEPTED_TYPES`

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

* fix: use `function` type for custom filter

* fix: type for custom filter in help

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

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

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

* doc nit

---------

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

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

* add a new api to toggle empty folders

* solve comments

* solve comments

* update help

---------

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

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

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

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

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

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

---------

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

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

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

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

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

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

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

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

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

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

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

* docs: update CONTRIBUTING.md (#2485)

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

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

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

* feat(#2415): create DecoratorDiagnostics

* feat(#2415): create DecoratorGit

* feat(#2415): create DecoratorGit

* add DecoratorCopied DecoratorCut

* add DecoratorOpened

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

* Add `renderer.highlight_git` to accepted strings

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

* simplify builder signs

* decorators take care of themselves and are priority ordered

* simplify builder hl groups

* refactor builder for icon arrays

* builder use decorators generically

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

* fix(#2415): harden unicode signs

* Decorator tidy

* normalise git sign creation and tidy

* tidy builder

* NvimTreeBookmarkIcon

* tidy HL doc

* tidy HL doc

* tidy HL doc

* tidy builder doc

* standardise on '---@param'

* DiagnosticWarning -> DiagnosticWarn

* annotate decorators

* limit to two highlight groups for line rendering

* style

* apply #2519

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

* feat(#2415): create combined highlight groups

* feat(#2415): create combined highlight groups

* feat(#2415): create combined highlight groups

* ci: allow workflow_dispatch (#2620)

* one and only one hl namespace, required winhl removal

* small tidies

* colors.lua -> appearance.lua

* full-name uses one and only namespace

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

* gut builder (#2622)

collapse Builder

* fix group_empty function check

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

---------

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

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

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

* refactor: status logic in single function

Rename get_status to status_is_valid.

Use status_is_valid function in multiple place to avoid duplicating
code.

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

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

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

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

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

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

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

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

* feat(#1389): move find_node_line to utils

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

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

---------

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

* ci: add lls-check to ci.yml

* ci: download lua-language-server binary

* ci: download lua-language-server binary

* ci: dummy failure to test

* Revert "ci: dummy failure to test"

This reverts commit 2bc43bad430209e32a5049a16c56710c4f6e2f7b.

* ci: ignore lls-out

* ci: better name

* ci: shellcheck nits

* ci: add luals libs and tidy

* ci: tidy

* ci: add neovim 0.9.4

* ci: add ci neovim 0.9.4 to lib path

* ci: dummy failure to test

* Revert "ci: dummy failure to test"

This reverts commit 45987335d81ec65fecc6636b339671a9a9fcdd97.

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

This reverts commit 4f397d6ea8bbdf6e808f9dc9db5ecbae291d8cd4.

* Revert "ci: add neovim 0.9.4"

This reverts commit 46fd1b368d27a1892b55381691723db3b30a7527.

* ci: action downloads and installs luals

* ci: remove workspaces from luals

* ci: consistent script naming

* ci: add quality to contributing

* ci: consistent script naming

* ci: add lsp to diagnostics

* ci: temporary find to enumerate home

* ci: add VIMRUNTIME for lls

* ci: temporary find to enumerate home

* ci: temporary find to enumerate home

* ci: remove temporary find to enumerate home

* ci: correct VIMRUNTIME

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

* ci: note VIMRUNTIME override

* ci: add Makefile

* ci: add Makefile

* ci: add Makefile

* ci: add Makefile

* ci: document checks and fixes

* ci: add help check

* ci: add help check

* ci: dummy help failure

* Revert "ci: dummy help failure"

This reverts commit c50cceaa4a.

* ci: document checks and fixes

* ci: document checks and fixes

* ci: matrix nvim version

* ci: matrix nvim version

* Revert "ci: matrix nvim version"

This reverts commit fcef6a11e9.

* Revert "ci: matrix nvim version"

This reverts commit a8cb50d39d.

* ci: matrix nvim version from env

* ci: matrix nvim version from env

* ci: matrix nvim version from env

* ci: matrix nvim version

* ci: matrix nvim version

* ci: matrix per job

* ci: matrix per job

* ci: many lua versions

* ci: move doc to style

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

* doc: tidy changelog

---------

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

* Apply requested changes

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

---------

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

* feat: add kind param to prompts for bookmark actions

* docs: add section for prompts

* docs: add section for prompts

---------

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

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

* fix: Properly filter diagnostics from coc

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

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

* Assign diagnostic version per node to reduce overhead

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

* Require renderer once

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

* Revert "Require renderer once"

Causes circular requires after the previous commit.

This reverts commit 7413041630.

* Rename `buffer_severity_dict` to `BUFFER_SEVERITY`

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

* Log diagnostics update properly

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

* Implement error handling for coc.nvim

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

* CI style fixes

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

* Capture `Keyboard interrupt` when handling coc exceptions

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

* add more doc

---------

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

* Add API bindings

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

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

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

* stylua

* Add classes for symlink nodes

* Replace deprecated `@vararg`

* Move node classes to `node` module

* Fix `Symlink*` classes

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

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

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

* additional nil checks for nvim-tree.lua

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

* vim.tbl_filter predicate returns booleans

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

This reverts commit c70229cad9.

* Add `MinimalNode` class in `marks` module

* Fix various LSP warnings

* stylua

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

* Add missing annotations to `live-filter`

* Add temporary aliases for `uv.*` types

* Resolve remaining LSP warnings

* Revert changes not related to internal types

* Minor adjustments

* Update doc comments style

* Minor adjustments (pt. 2)

---------

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

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

---
updated-dependencies:
- dependency-name: google-github-actions/release-please-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-05 10:39:40 +11:00
Alexander Courtis
7e5c673180 docs(#285): clarify api.fs.create directory creation (#2572)
docs(#285): clarify api.fs.create directory creation
2023-12-04 11:32:03 +11:00
Azad
05f55c1fd6 chore: remove TreeExplorer global variable (#2561) 2023-11-28 10:39:52 +01:00
Alexander Courtis
d5cc938ab0 refactor: api and command focus call tree.open(), soft deprecate tree.focus() 2023-11-27 14:24:55 +02:00
kezhenxu94
5e4475d8bf fix: harden tree root cwd fetch (#2557)
Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-11-26 09:39:19 +11:00
Azad
5231562caf feat(log): add node inspection function (#2541)
* feat: add `api.tree.inspect_node_under_cursor`

* Add documentation

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

This reverts commit 784ee91cc6.

* Revert "Add documentation"

This reverts commit 6dc396d0a5.

* feat(log): add node inspection function

---------

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

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

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

* Document winid() opts

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

* Fix winid() docs

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

* Handle case where tabpage = 0

---------

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

* simplify logic

---------

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

* refactor: simplify calculate_width return if not wininfo

---------

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

* Fix docs

---------

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

* luacheck

* Remove useless nil check

* Cycle only if `wrapscan` is enabled

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-11-07 00:39:08 +01:00
dependabot[bot]
c763861afb chore(deps): bump amannn/action-semantic-pull-request
Bumps [amannn/action-semantic-pull-request](https://github.com/amannn/action-semantic-pull-request) from 5.3.0 to 5.4.0.
- [Release notes](https://github.com/amannn/action-semantic-pull-request/releases)
- [Changelog](https://github.com/amannn/action-semantic-pull-request/blob/main/CHANGELOG.md)
- [Commits](https://github.com/amannn/action-semantic-pull-request/compare/v5.3.0...v5.4.0)

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

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

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

---------

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

* fix range

---------

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

* simplify

---------

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

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

This reverts commit aa0b9aa9b8.

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

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

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

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

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

* stylua

* Bug fix

---------

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

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

* feat: add option to sort entries in help window

* stylua

* Add keymap to toggle sorting methods

* Bug fix

---------

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

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

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

* stylua nit

---------

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

* stylua

* Add keymap to toggle sorting methods

* Bug fix

---------

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

* stylua

* Bug fix

---------

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

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

* feat: preview_no_window_picker -> preview_no_picker

---------

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

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

---------

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

* Forgot a comma

---------

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

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

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

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

This reverts commit 8a0524d6bd.

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

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

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

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

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

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

* Fix boolean expressions

* Replace `pcall` with `package.loaded`

* Detect title support before sending notification

* Prevent `title_support` from being nil after evaluation

* temporary stylua suppression

---------

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

* fix docs for view.width.padding

* fix docs for view.width.padding

---------

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

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

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

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

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

* feat(#1079): add renderer.highlight_clipboard

* feat(#1079): add renderer.highlight_clipboard

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

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

* style

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

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

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

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

* Bug fix

* Fix check for default values

* Reduce error notifications verbosity

* Address issues introduced previously

* stylua

---------

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

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

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

* feat(#1079): add renderer.highlight_clipboard

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

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

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

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

* Update docs

* Follow documentation syntax

* Remove unnecessary refresh

* doc spacing

---------

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

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

* run stylua

---------

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

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

---------

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

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

* doc: reorder default options, add sections

* doc: collapse opts spacing and tidy

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

* feat(#1917): add renderer.highlight_diagnostics

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

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

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

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

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

* fix(#2382): consistent naming of toplevel

* fix(#2382): more doc and safety

* fix(#2382): consistent naming of toplevel

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

* refactor: rename some usages of `next`

---------

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

* Check if `M.devicons` exists

* Refactor `get_folder_icon`

* Add configuration options for both files and folders

* web_devicons.*.enabled -> enable

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

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

---------

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

* Refactor `folders_or_files_first` function

* Improve readability

* Remove `fallback` from `folders_or_files_first` function

---------

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

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

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

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

fixes #2362

* use utils for windows check

* Add function to escape special chars on windows

* Change escape string function to use and/or

* Add nil check in escape special chars function

---------

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

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

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

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

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

* fix: windows default trash command doc

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

* Moved option check inside the callback

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

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

* doc: remove 'Only available for UNIX'

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

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

* doc(trash): remove dot

* fix(trash): check for unix and windows

* fix(trash): comment

---------

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

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

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

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

* pass bufnr and winnr to TreeRendered event

---------

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

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

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

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

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

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

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

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

* chore(syntax): using string colon methods

* fix(regex): use alphanumeric match for extensions

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

---------

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

* fix(nil): files with no extension

* fix(nil): files with no extension

---------

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

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

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

---------

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

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

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

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

* docs: add annotations to `utils.focus_node_or_parent`

---------

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

* feat(#1837): add git.disable_for_dirs

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

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

* fix(#1785): apply requested changes

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

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

* refactor: store last focused node in view module

---------

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

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

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

* Remove defaults and update doc #2198

---------

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

* fix: follow default floatwin bg color

---------

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

* doc: human readable mappings help

* doc: human readable mappings help

* doc: more on_attach examples

* doc: enhance quick start

* doc: enhance quick start

* doc: enhance quick start

* doc: enhance quick start

* doc: enhance quick start

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

* feat: Focus file after pasting

---------

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

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

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

Resolves #1976

* Code review

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

* add comments for utils.norm_path

* get_git_directory use norm_path from utils

* watcher improvements

---------

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

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

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

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

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

* changed highlight names and links, changed folder build function

* remove NvimTreeSymlinkFolderName

---------

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

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

---------

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

* fix(#2109): floating help window

* fix(#2109): floating help window

* fix(#2109): floating help window

* fix(#2109): floating help window

* fix(#2109): floating help window

* fix(#2109): floating help window

* fix(#2109): floating help window

* help float no border

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

* Update lua/nvim-tree/help.lua

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

* Update lua/nvim-tree/help.lua

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

* Update lua/nvim-tree/help.lua

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

* Update lua/nvim-tree/help.lua

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

* fix(#2109): floating help window

* fix(#2109): floating help window

---------

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

* doc: add Event.WillRenameNode

* doc: api git, events, live_filter, marks

* doc: api fs

* doc: api tidy

* doc: api index

* doc: api tidy

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

* async git watcher reload; revert unnecessary extractions

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

* async git watcher reload

* async git watcher reload

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

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

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

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

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

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

* Update lua/nvim-tree.lua

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

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

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

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

---------

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

* Update change-dir.lua

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

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

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

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

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

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

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

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

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

Potential fix for #2043.

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

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

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

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

* chore(mappings): add desc to all mappings

* chore(mappings): add desc to all mappings

* chore(mappings): escape help keys

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

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

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

This reverts commit f6f439ba59.

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

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

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

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

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

* chore(mappings): merge cleanup

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

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

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

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

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

* feat: Add highlight group for opened folder

closes #1674

* docs: Add NvimTreeOpenedFolderIcon default

* feat: Add NvimTreeClosedFolderIcon highlight group

Defaults to NvimTreeFolderIcon

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

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

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

* docs: Update feature_request.md (#1788)

* Update feature_request.md

Closes #1654

* Update feature_request.md

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

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

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

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

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

* 1786 semantic nit

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

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

* fixed git rename not showing up for the renamed file

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

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

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

* Using -z and removed unnecessary logic

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

* Update view.lua

* set cursorline to true

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

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

* feat(view): add filters.git_clean

* feat(view): add filters.git_clean

* feat(view): add filters.no_buffer

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

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

* feat(view): filters.no_buffer clarify targets

* feat: add placeholder filters.diagnostics_ok, refactor filters

* feat(view): remove placeholder filters.diagnostics_ok

* doc: consolidate and clarify :help examples

* doc: format help

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

* Fix default for file creation in closed directories

* Make paste in closed directories consistent with create

* doc: clarify create_in_closed_folder

* Remove create_in_closed_folder option

* doc: clarify create_in_closed_folder removal message (whoops)

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

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

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

* on_attach_default hardcoded

* format default_on_attach

* source default on_attach directly

* remove human mappings help

* simplified on_attach generation

* simplified on_attach generation

* generate default on_attach

* generate default on_attach

* split out keymap_legacy

* add recently introduced mappings

* legacy api.config.mappings.active and default

* legacy api.config.mappings.active and default

* on_attach help and readme

* legacy generate handles action = ""

* legacy generate handles action =

* legacy generate gives defaults when no user mappings

* legacy generate handles action = ""

* legacy generate api handles overrides

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

* add wiki link to generated on_attach

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

---------

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

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

* style nit

---------

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

* #1961 add cycle detection to refresh_nodes_for_path

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

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

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

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

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

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

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

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

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

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

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

* Add dynamic sizing padding options

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

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

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

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

* allow padding function, update docs, rollback readme

* typo in example setup

* help formatting

---------

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

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

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

* doc: api.tree.*

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

* view grow calculates size correctly based on sign column visibility

* limit width to a minimum of 20

* adaptive_size -> min/max table

* harden view size calculations against bad user input

* style

* add back an extra column of padding to adaptive resizing

* back out: limit width to a minimum of 20

* revert unnecessary change

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

* remove adaptive_size from help

* backout unnecessary change M.View.config

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

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

* feat(git): support `DA` state

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

* Refactor different prompt configs to single ui table

* Remove unused fields

* add ui.confirm doc, format/tidy

* trash.require_confirm -> ui.confirm.trash

* Fix nil value trash field

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

* feat(event): add TreeAttachPost

* feat(event): add TreeAttachPost

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

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

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

* fixed BufReadPost & BufUnload nil pointer

* update_focused_file.debouce_delay to view.debounce_delay

* changed docs to be more accurate

* added debounce on modified update

* Using same event for filter buffer

* removed unused View.debounce_delay

* changed docs to be more accurate

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

* highlight_modified is highlight_opened_files

* prototype with autocmd

* moved modified into glyphs

* show_on_dirs and show_on_open_dirs

* icon placement before & after

* _get_filename_offset

* fixed :wq doesn't update modified indicator

* highlight_modified, signcolumn modified_placement

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

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

* fixed git signcolumn doesn't show

* fixed highlight_modified gets replaced by highlight_opened_files

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

* updated doc to reflect empty icon not breaking rendering

* removed debounce_delay to implement in a later PR

* doc nit: order placement

* change modified dirs default to be consistent with git

* illegal git & modified placement changed to default

* don't assume icon exist

* nit remove comment

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

* Don't sign_define if placement isn't signcolumn

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

Extends #1814

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

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

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

* doc: add cursorline

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

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

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

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

* outlined git.show_on_open_dirs behavior

* show some icon on opendir even if show_on_open_dir=false

and show all children's status on parent

* fixed renamed icon not showing

* sorted icons

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

* fixed update_git_status in reloaders not tested

* fixed Api.git.reload()

Tested update_git_status in reloaders.lua

* sort icon only if not git signcolumn

* fixed crashing when root dir isn't git dir

* made git.show_on_dirs doc more concise

* git_statuses -> git_status for consistency

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

* fixed #1784 conflict

* don't order icons

* Revert "don't order icons"

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

This reverts commit a8d26bb088.

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

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

WIP

* fix: move logic expression to if statement

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

* feat(open): add window_picker.picker

* feat(open): add window_picker.picker

* style nit

* feat(open): add window_picker.picker

* docs: add window_picker.picker documentation

* docs: add window_picker.picker documentation

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-12-16 15:14:33 +11:00
Richard Li
18272f8df3 fix(view): refresh opened files highlight on buffer read, unload (#1827) 2022-12-16 14:36:00 +11:00
Anton
e8ea62c198 fix(#1824): Don't modify jumplist when edit_in_place. (#1825) 2022-12-16 14:08:27 +11:00
Alexander Courtis
cdb40dc42e neovim requirement 0.7.0 -> 0.8.0, remove WinSeparator/VertSplit compatibility shims 2022-12-16 13:45:16 +11:00
Ian Homer
949913f186 feat(api): rename_basename API and action (#1791)
* relative rename action

* 🔥 remove debug print statement

* 🐛 better handling of dot files

Also pickout extension in filename with more one dot

* 🔧 keymap e for relative-rename action

* 📝 update help with relative-rename mapping

*  add API for rename_relative

* 🚨 correct lint warnings

* rename_relative -> rename_root

* stylua

* ♻️ use fnamemodify instead of custom logic

* 💥 refactor renaming api using vim filename modifiers

Rename API now supports filename modifiers as arguments, although
only with limited support of options. The function signature however
will allow improvements going forward. The API signature is backward
compatible, although the behviour has changed as per the next comment.

This change changes the default behaviour of the renames, rename_full is
what rename was, rename now just renames the tail (i.e. the filename)

* 🐛 make api rename, without args, functional

*  allow modifier argument to be used in API call

* 📝 update documentation with new command name

* rename-file.fn takes only a modifier as argument

* add Api.fs.rename_basename, specify modifiers for rename, rename_sub

* add Api.fs.rename_node

* rename-file tidy allowed modifiers

* 🐛 fix bugs after last refactoring

rename ":t" and ":t:r" was moving file to root of project and not
maintaining sub-directory

* 🐛 correct absolute rename

which was loosing sub-directory on rename

* 🔥 remove debug print statements

* stylua

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-12-16 13:32:48 +11:00
Alexander Courtis
a8d26bb088 Revert "fix(#1815): don't schedule find_file calls, debounce update_focused_file with 15ms default (#1820)"
This reverts commit 623cecb809.
2022-12-16 13:15:32 +11:00
Alexander Courtis
623cecb809 fix(#1815): don't schedule find_file calls, debounce update_focused_file with 15ms default (#1820)
* fix(#1815): don't schedule find file calls

* fix(#1815): debounce BufEnter find_file

* fix(#1815): deprecate nvim-tree.find_file

* fix(#1815): debounce BufEnter find_file

* fix(#1815): debounce BufEnter find_file
2022-12-16 13:01:37 +11:00
Alexander Courtis
95ed588211 fix(#549): add more profiling ~tree init 2022-12-16 12:12:06 +11:00
Alexander Courtis
899ed45602 fix(watcher): only purge on subsequent setup calls, add git_purge log 2022-12-16 11:47:50 +11:00
David
0cd8ac4751 fix: Implicit current buf on centralize selection (#1792) 2022-12-12 13:14:41 +01:00
Anton
a2c75567ad feat(event): add WillRenameNode (#1821) 2022-12-12 11:56:50 +11:00
Alexander Courtis
8b4aaff783 remove legacy g: option migration 2022-12-12 09:50:52 +11:00
Eric Haynes
7177d95ac0 feat: paste and create always target closed folder, remove create_in_closed_folder (#1802)
* Fix default for file creation in closed directories

* Make paste in closed directories consistent with create

* doc: clarify create_in_closed_folder

* Remove create_in_closed_folder option

* doc: clarify create_in_closed_folder removal message (whoops)

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-12-11 14:40:38 +11:00
Alexander Courtis
b9aaf805a1 doc: format help 2022-12-11 12:12:19 +11:00
Alexander Courtis
3c5d9dd31f doc: consolidate and clarify :help examples 2022-12-10 16:25:02 +11:00
Alexander Courtis
c5dc80c36b feat(view): add filters.git_clean, filters.no_buffer (#1784)
* feat(view): add filters.git_clean

* feat(view): add filters.git_clean

* feat(view): add filters.no_buffer

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

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

* feat(view): filters.no_buffer clarify targets

* feat: add placeholder filters.diagnostics_ok, refactor filters

* feat(view): remove placeholder filters.diagnostics_ok
2022-12-10 15:55:33 +11:00
Anton
e49fa4e529 feat(event): dispatch Event.NodeRenamed on cut-paste (#1817) 2022-12-10 11:29:05 +11:00
rishabhjain9191
69a07d169a feat(view): always enable cursorline, users may change this behaviour via Event.TreeOpen (#1814)
* Update view.lua

* set cursorline to true
2022-12-10 11:20:40 +11:00
Richard Li
f8489c9929 fix(git): git rename not showing up for the renamed file (#1783)
* fixed git rename not showing up for the renamed file

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

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

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

* Using -z and removed unnecessary logic
2022-12-03 14:56:38 +11:00
Richard Li
9d9c5711dc 1786 git next prev land on dirs (#1787)
* Filtered dir with git status that are open when show_on_open_dir is false

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

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

* 1786 semantic nit

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-12-03 14:39:00 +11:00
gegoune
07149daa0c docs: Update feature_request.md (#1788)
* Update feature_request.md

Closes #1654

* Update feature_request.md

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-12-03 13:01:30 +11:00
Richard Li
829e9f68e1 feat: add diagnostics.show_on_open_dirs git.show_on_open_dirs (#1778)
* feat(diagnostics): only show diagnostic on closed folder

* feat(git): only show git icon on closed folder
2022-11-29 11:12:34 +11:00
baahrens
0b319a1b28 feat(renderer): add NvimTreeOpenedFolderIcon NvimTreeClosedFolderIcon (#1768)
* feat: Add highlight group for opened folder

closes #1674

* docs: Add NvimTreeOpenedFolderIcon default

* feat: Add NvimTreeClosedFolderIcon highlight group

Defaults to NvimTreeFolderIcon
2022-11-28 11:17:09 +11:00
Alexander Courtis
9f7bed5536 doc: specify that the terminal emulator must be configured to use the patched font 2022-11-28 10:26:37 +11:00
Alexander Courtis
b17358ff4d fix(#1731 #1723 #1716): handle all external file system changes (#1757)
* fix(#1731): watcher refreshes node rather than the first node matching absolute path, profile refresh

* fix(#1731): reload explorer reloads closed folders

* fix(#1731): do not fire folder created event on file create

* fix(#1731): reload profile absolute path, not link to

* fix(#1731): find-file locks/profiles on real path, reloads when watchers disabled

* Revert "fix(#1731): reload explorer reloads closed folders"

This reverts commit 5dfd8bd2fa.

* fix(#1731): tidy watch reload

* fix(#1731): move refresh_node from watch to reload

* fix(#1731): find-file reloads all nodes for the containing directory

* fix(#1731): create-file refreshes synchronously

* fix(#1731): remove unused watch node

* fix(#1731): find-file refreshes root

* fix(#1716): create-file invokes find-file

* fix(#1731): refresh path walks down the tree to the targedt
2022-11-26 14:19:09 +11:00
David Aguilera
99d713644d feat(renderer): add renderer.root_folder_label (#1746)
* Add new renderer setting `add_root_updir` to fix #1743.

* Fix default value in docs.

* Remove proposed “add_root_updir” and rename “root_folder_modifier” to “root_folder_label”. Also, “root_folder_label” can be also a function now.

* chore: warn users about breaking change

* fix(#1743): use silent migration of root_folder_modifier

* fix(#1743): add example, document previous renderer.root_folder_modifier

* Add check to validate return type of “root_folder_label” is string.

* Change “root_folder_label” default value to “:~:s?$?/..?”.

* Add missing keyword “local” to local variable “label”.

Co-authored-by: David Aguilera <david.aguilera@neliosoftware.com>
Co-authored-by: gegoune <dev@clog.rocks>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-11-26 14:02:05 +11:00
Ibrahim Abdelkareem
68a2a0971e feat(diagnostics): add diagnostics.severity (#1755)
* feat: Support diagnostics severity

* fix: Revert Hunk

* feat: Supports min/max severity

* feat: Supports min/max severity: tidy doc

* feat: Supports min/max severity: tidy doc

* feat: Supports min/max severity: tidy doc

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-11-21 09:09:09 +11:00
Alexander Courtis
cc18122be1 fix(notify): log file notice info->debug 2022-11-20 12:26:59 +11:00
Alexander Courtis
e38e061710 feat(api): add api.tree.get_nodes 2022-11-19 15:54:16 +11:00
Alexander Courtis
a65063cb0a fix(notify): remove unused varargs, log file notice debug->info 2022-11-19 14:34:39 +11:00
Wessel Blokzijl
c49499413a feat(tabs): add tab.sync options (#1698)
* Sync closing of nvim-tree across tabs

* chore: remove vim.* "requires"

* Sync closing of nvim-tree across tabs

* Fix api.close calls

* Fix issue from merge

* Implement changes

* Finish todos and add close_all_tabs

* silently refactor options, add doc

* fix vinegar example

* Refactor close to work with tabid

* Close nvim tree if last buffer

* close and abandon all tabs on subsequent setup calls

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-11-19 13:57:45 +11:00
yazdani kiyan
1837751efb fix(paste): allow pasting into empty root directory
assign main explorer when node name is `..` to allow pasting into the
root directory when its empty.
Fixes #1736
2022-11-16 13:41:35 +01:00
yazdani kiyan
059e4cadd6 fix(paste): paste into empty root directory 2022-11-16 13:40:48 +01:00
Alexander Courtis
9d241e5f58 fix(system-open): use notify for system-open failures and tidy messages 2022-11-15 13:01:15 +11:00
Maxim Sokolov
cf908370fb fix(#1740): Error while running :NvimTreeCollapseKeepBuffers (#1741) 2022-11-13 15:52:44 +11:00
Alexander Courtis
f1f89f2062 contributions reference wiki 2022-11-13 15:09:20 +11:00
Alexander Courtis
bed442fd93 move troubleshooting from readme to wiki 2022-11-13 14:45:13 +11:00
Alexander Courtis
77dd666e36 doc: move tips to wiki 2022-11-13 14:33:31 +11:00
Alexander Courtis
e0e23f2d62 doc: add labels hint to contributing 2022-11-13 10:37:21 +11:00
Alexander Courtis
e204a7d819 fix(#1728): escape cwd changes to prevent environment variable expansion (#1729) 2022-11-12 14:50:14 +11:00
Alexander Courtis
bcb2a5a80d fix(#1720): .git watch only FETCH_HEAD, HEAD, HEAD.lock, config, index (#1732)
* fix(#1720): .git watch only HEAD, config and index

* fix(#1720): .git watch only FETCH_HEAD, HEAD, HEAD.lock, config, index
2022-11-12 14:38:33 +11:00
Alexander Courtis
7e892767bd Revert "fix(#1716): focus file/directory when created in a sub-directory, don't dispatch FolderCreated on file creation (#1722)"
This reverts commit bdc4ec6abd.
2022-11-06 13:32:24 +11:00
Alexander Courtis
d91f885819 Revert "fix(#1723): find_file for externally created new file results in folder unable to be opened"
This reverts commit be2ccd4b1a.
2022-11-06 13:21:24 +11:00
Alexander Courtis
8cc369695b fix: replace vim.* "requires" with explicit calls to vim functions (#1701) 2022-11-06 10:37:33 +11:00
Tomohiro Endo
6d6a44626d fix(watcher): failure on watcher teardown message (#1726) 2022-11-06 10:30:12 +11:00
Alexander Courtis
bdc4ec6abd fix(#1716): focus file/directory when created in a sub-directory, don't dispatch FolderCreated on file creation (#1722)
* fix(#1716): focus file/directory when created in a sub-directory, don't dispatch FolderCreated on file creation

* fix(#1716): focus file/directory when created in a sub-directory

* fix(#1716): focus file/directory when created in a sub-directory
2022-11-06 10:08:32 +11:00
Alexander Courtis
be2ccd4b1a fix(#1723): find_file for externally created new file results in folder unable to be opened
* fix(#1723): find_file for externally created new file results in folder unable to be opened

* fix(#1723): find_file for externally created new file results in folder unable to be opened
2022-11-05 16:34:41 +11:00
Alexander Courtis
a0f3e99b2d feat(watcher): tear down watcher on failue, warning the user (#1707) 2022-11-05 10:25:14 +11:00
Alexander Courtis
ed9db632a8 feat(watcher): add filesystem_watchers.ignore_dirs (#1705) 2022-11-05 10:24:25 +11:00
Alexander Courtis
33ce8e3c73 fix(#1711): open in a new window when no window picker and no available window (#1715) 2022-11-05 10:23:03 +11:00
kylo252
6ca6f99e76 feat(notify): add notify.threshold (#1693)
* feat: configurable notification level

add `notify.threshold` to setup opts

* feat: configurable notification level: add threshold example doc

* feat: configurable notification level: log always comes last

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-11-01 10:24:40 +11:00
David Brouwer
ada2c6441d fix(#1712): invalid window ID on colorscheme (#1714) 2022-11-01 10:04:47 +11:00
wyrid
cd2f7569db feat(api): add api.marks.clear (#1708) 2022-11-01 08:46:56 +11:00
wyrid
cbb5313f90 feat(api): add api.fs.clear_clipboard (#1706)
* feat: command to clear the clipboard

* feat: command to clear the clipboard: stylua

* feat: command to clear the clipboard: add to :help

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-10-31 15:48:05 +11:00
Sabu Siyad
fba97517bb fix(#1679): renderer.full_name correctly shows for one character outside (#1688)
ref: `:h getwininfo()`

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

Signed-off-by: Sabu Siyad <hello@ssiyad.com>
2022-10-31 15:38:09 +11:00
Alexander Courtis
1044eba9e7 doc: update event subscription example 2022-10-31 13:59:03 +11:00
wyrid
3845039c1a fix: use pcall to prevent live-filter regex errors (#1689)
* fix: use pcall to prevent live-filter regex errors

Wrap live filter's match function in pcall to prevent errors caused by
invalid regex while typing.

* nit: use ok for pcall rc

* nit: stylua fix

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-10-29 14:22:14 +11:00
Alexander Courtis
dd90bfa155 fix(#1671): split with no window picker will always find an available window (#1677) 2022-10-29 13:42:56 +11:00
Alexander Courtis
cb98892dea doc: add hitest notes and add termguicolors to example setups 2022-10-29 12:45:24 +11:00
kiyan
65c2ba8952 fix(colorscheme): update winhl on colorscheme change
fixes #1696
2022-10-28 14:19:01 +02:00
Alexander Courtis
1be1e17be5 doc(#1676): clarify view.mapping.list contents including case of key 2022-10-25 09:37:02 +11:00
Alexander Courtis
49c32c0dda Revert "fix(#1676) case insensitive mapping key remove and override (#1682)"
This reverts commit 5a798b3be0.
2022-10-25 08:00:17 +11:00
Ofir Gal
23c0fe9a0a Added NvimTreeLineNr higlight (#1684) 2022-10-24 19:53:13 +00:00
Alexander Courtis
5a798b3be0 fix(#1676) case insensitive mapping key remove and override (#1682)
* fix(#1676): remove_keymaps matches case insensitively

* fix(#1676): mappings.list.n.key matches case insensitively for overrides
2022-10-24 14:51:07 +11:00
Alexander Courtis
58055a0397 doc(#1672): clarify mappings example and doc 2022-10-24 11:45:33 +11:00
Alexander Courtis
0122a71fce doc(#1655): add roadmap and expand on API/events/actions (#1666)
* doc(#1655): add roadmap and expand on API/events/actions

* doc(#1655): add roadmap and expand on API/events/actions
2022-10-23 15:01:58 +11:00
Alexander Courtis
3170e33462 doc(#1606): specify watcher and git when profiling 2022-10-23 10:47:47 +11:00
Alexander Courtis
ea09ab497e doc(#731): add single mouse mapping notes 2022-10-23 10:25:18 +11:00
Alexander Courtis
e94f517798 fix(#1675): open-file sanity check mode 2022-10-22 14:40:20 +11:00
Alexander Courtis
2b970635d1 fix(#1668): update issue link 2022-10-18 11:34:26 +11:00
Alexander Courtis
3a2f68b9d5 fix(#1668): revert all startup behaviour changes back to 540055b 2022-10-18 11:14:35 +11:00
Alexander Courtis
4e24505e2b fix(#1664): respect hijack_directories.enable on startup when not open_on_setup (#1665) 2022-10-17 14:49:57 +11:00
Alexander Courtis
48992fd3e8 fix(#1639): ensure tree autocommands match filetype as well as name (#1640)
* fix(#1629): nvim start with file named *NvimTree* opens tree instead of buffer

* Revert "fix(#1629): nvim start with file named *NvimTree* opens tree instead of buffer"

This reverts commit e7136078f7.

* fix(#1629): nvim start with file named *NvimTree* treats file as tree

* fix(#1629): nvim start with file named *NvimTree* treats file as tree

* fix(#1639): ensure tree autocommands match filetype as well as name

* fix(#1639): fix bad merge

* fix(#1639): ensure tree autocommands match filetype as well as name
2022-10-17 12:31:41 +11:00
Ali Almohaya
c995ce0878 fix(#1643): preview on floating window (#1648)
* fix: preview on floating window

* chore: redrawing the tree after setting current win

* chore: ignore winleave autocmd on preview action

* fix: typo in comment

* chore: call correct window id

* chore: revert changes in focus method

* chore: check if float window is enabled before ignoring WinLeave
2022-10-16 12:17:17 +11:00
Alexander Courtis
c446527056 chore(#1649): remove workaround for https://github.com/neovim/neovim/issues/17395 which was fixed in nvim 0.7.0 (#1650) 2022-10-15 13:49:13 +11:00
Alexander Courtis
55aa0062b9 fix(#1270): ensure explorer exists at startup before propagating FS changes 2022-10-15 12:44:59 +11:00
emmanueltouzery
187388b7f5 fix(#1632): autocenter: avoid use of feedkeys (#1632) 2022-10-15 11:10:42 +11:00
Alexander Courtis
6ff828b25b doc: vim.g.loaded -> vim.g.loaded_netrw 2022-10-15 10:50:07 +11:00
Alexander Courtis
c4ac723a83 fix(#1626): obey splitright/below when splitting existing windows (#1641) 2022-10-15 10:22:03 +11:00
Alexander Courtis
23a564f1cd doc: link screenshots to showcases page 2022-10-15 10:19:14 +11:00
Ali Almohaya
b07701f9da chore: remove pngs from .github folder and use direct links for imgs in README.md (#1642) 2022-10-13 16:55:12 +02:00
Ali Almohaya
b01e7beaa6 fix(#1628): quit_on_open e: do not open in the tree's window (#1637)
* fix: prevent quit_on_open from opening in same window

* chore: remove condition for quit_on_open

* fix: prevent opening file in a new window on floting nvim-tree

* stylua

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-10-11 10:28:30 +11:00
Alexander Courtis
c66cbdfc25 fix(#1629): nvim start with file named *NvimTree* opens tree instead of buffer (#1634)
* fix(#1629): nvim start with file named *NvimTree* opens tree instead of buffer

* Revert "fix(#1629): nvim start with file named *NvimTree* opens tree instead of buffer"

This reverts commit e7136078f7.

* fix(#1629): nvim start with file named *NvimTree* treats file as tree

* fix(#1629): nvim start with file named *NvimTree* treats file as tree
2022-10-11 10:00:03 +11:00
Alexander Courtis
875d38e52c doc: add alex-courtis screenshot with source 2022-10-09 14:54:55 +11:00
Alexander Courtis
0db4977303 doc: update hero screenshots with default options 2022-10-09 14:18:00 +11:00
Alexander Courtis
54afa503a9 chore: replace urls from kyazdani42 -> nvim-tree 2022-10-09 12:52:54 +11:00
kiyan
3d58a9b2cf fix(fs): create file failure when reloading watch path for node
because node can be a file or a symlink.
fixes #1633
2022-10-08 16:11:34 +02:00
kiyan
b4d704e88d chore: replace urls from kyazdani42 -> nvim-tree 2022-10-08 11:31:57 +02:00
Alexander Courtis
4a01f90d11 feat(view): float.quit_on_focus_loss documentation clarification 2022-10-08 14:39:23 +11:00
emmanueltouzery
79f631bc1d feat(view): add float.quit_on_focus_loss, float respects actions.open_file.quit_on_open (#1621) 2022-10-08 14:35:20 +11:00
Alexander Courtis
be2b4f58e6 fix(#1615): focus created file when command line prompt requires confirmation (#1622)
* fix(#1615): focus created file when command line prompt requires confirmation

* fix(#1615): focus created file when command line prompt requires confirmation
2022-10-08 14:26:31 +11:00
Alexander Courtis
c5536db0b7 fix(#1270): open_on_setup_file does not override open_on_setup, hijack_directories does not override startup behaviour (#1618) 2022-10-08 14:25:38 +11:00
longguzzz
7282f7de8a feat: add NvimTreeCursorLineNr (#1616) 2022-10-01 12:51:22 +02:00
rapan931
45d386a359 fix: remove unnecessary conditions (#1614) 2022-09-30 13:37:31 +02:00
kiyan
43fd138544 fix(ci): stylua action version number 2022-09-29 14:04:30 +02:00
kiyan
4aef454cd2 fix(styling): empty line 2022-09-29 13:59:05 +02:00
rapan931
11b524899f fix: restore eventignore (#1612) 2022-09-29 13:55:24 +02:00
Xyhlon
9914780cba fix(#1547): pass explicit system arguments to for git toplevel and untracked actions
* the nice fix

* fix(#1547): pass git toplevel cwd unescaped, pass git untracked arguments as per toplevel

Co-authored-by: Maximilian Philipp <philipp@student.tugraz.at>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-09-25 12:58:05 +10:00
Alexander Courtis
52b0c32152 chore: add help screenshot, update example 2022-09-23 11:16:21 +10:00
Alexander Courtis
0417d9148b feat: focus_empty_on_setup 2022-09-22 15:15:03 +10:00
Alexander Courtis
540055be5f chore: document :NvimTreeFindFile! and add bang :NvimTreeFindFileToggle! 2022-09-22 14:18:49 +10:00
Alexander Courtis
fbd421da71 chore: remove non-functional sides bottom/top 2022-09-22 13:53:09 +10:00
Alexander Courtis
ac8d259bad fix(prompt): add select_prompts to suit UI decorator plugins such as dressing and telescope 2022-09-22 13:23:49 +10:00
Alexander Courtis
5cb87c037d fix(#1553): set correct side on vim open directory (#1594) 2022-09-22 09:06:23 +10:00
KuuWang
3676e0b124 feat(sorters): allow user sort_by
* feat: Mixin Sorter (#1565) Self Solved

adding `mixin` sort options for `rust` like package systems

```

package.rs
package/
  __inside__

lib.rs
lib/
  _inside_

a.rs
b.rs
module.rs

```

* feat: sort_by, after_sort options for more convinient using

```
*nvim-tree.sort_by*
Changes how files within the same directory are sorted.
Can be one of 'name', 'case_sensitive', 'modification_time' or 'extension',
'function'.
>
  sort_by = function(a, b)
    if not (a and b) then
      return true
    end
    if a.nodes and not b.nodes then
      return true
    elseif not a.nodes and b.nodes then
      return false
    end

    return a.name:lower() <= b.name:lower()
  end

  end
  Type: `string | function(a, b)`, Default: `"name"`

*nvim-tree.after_sort*
Related to nvim-tree.sort_by, this function runs without mergesort.
Can be defined by your own after-sort works.
  Type: `function(table)`, Default: `disable`

>
  after_sort = function(t)
    local i = 1

    while i <= #t do
      if t[i] and t[i].nodes then
        local j = i + 1
        while j <= #t do
          if t[j] and not t[j].nodes and t[i].name:lower() == t[j].name:lower():match "(.+)%..+$" then
            local change_target = t[j]
            table.remove(t, j)
            table.insert(t, i, change_target)
            break
          end
          j = j + 1
        end
      end
      i = i + 1
    end
  end

```

* remove: after_sort ( misunderstood feature )

sort_by parameter can be function.

``` lua
  sort_by = function(t)
    local sorters = require "nvim-tree.explorer.sorters"
    local comparator = sorters.retrieve_comparator("name")
    sorters.split_merge(t, 1, #t, comparator) -- run default merge_sort
    local i = 1

    while i <= #t do
      if t[i] and t[i].nodes then
        local j = i + 1
        while j <= #t do
          if t[j] and not t[j].nodes and t[i].name:lower() == t[j].name:lower():match "(.+)%..+$" then
            local change_target = t[j]
            table.remove(t, j)
            table.insert(t, i, change_target)
            break
          end
          j = j + 1
        end
      end
      i = i + 1
    end
  end,

```

* try-fix: change existing merge_sort function, call user's sort_by

hope.. like it...?

* doc: explain function parameter and return, add more complex example

* fix: reorder with user-comparator exceed memory limit

apply merge_sort
check nil & type for senitize

* fix: user_index based sorting ( create index )

for performance, create index once,
using index to re-ordering

* fix: fence problems

* doc & fix: merge_sort problem fix & nil sorting

add complex example

* fix: sort_by detect and use string and nil

* doc: revert sort_by to simple

* fix: sort_by does not return anything

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-09-18 16:00:49 +10:00
Alexander Courtis
fb8735e96c doc: eager netrw disabling 2022-09-11 11:41:12 +10:00
Alexander Courtis
3e49d9b748 doc: direct users to ask questions instead of raising issues 2022-09-05 14:22:38 +10:00
Alexander Courtis
9ba5366115 doc: direct users to ask questions instead of raising issues 2022-09-05 14:21:03 +10:00
Alexander Courtis
b83e06f7fe Revert "doc: direct users to ask questions instead of raising issues"
This reverts commit 904110f8a9.
2022-09-05 14:15:54 +10:00
Alexander Courtis
904110f8a9 doc: direct users to ask questions instead of raising issues 2022-09-05 14:15:03 +10:00
Alexander Courtis
e282420111 fix(#1564): reset &bufhidden when opening an existing (maybe previewed) buffer (#1573) 2022-09-05 10:10:58 +10:00
ADoyle
951e10a64e fix(#1568): show relative path of symlink destination (#1569) 2022-09-04 12:23:56 +10:00
Krzysztof Cieśla
d753a1da9a fix(view): file filter and info popup above floating view
Co-authored-by: Krzysztof Cieśla <krzysztof.marcin.ciesla@cern.ch>
2022-09-03 14:29:18 +10:00
Alexander Courtis
011a7816b8 doc: add PR tips to contributing 2022-08-30 16:42:55 +10:00
Mivort
e8bf3d778a feat(renderer): add renderer.indent_width (#1505)
* feat: add config option for a tree indent width

add 'indent_width' option to configure visible indent for tree nesting
levels (default is 2).

* add 'bottom' char for a corner extension

* apply stylua formatting

* provide value constraints in documentation

* limit minimal indent width

* make marker symbols have one utf8 char width

* match stylua formatting

* add the commentary regarding utf-8 first symbol match
2022-08-30 09:44:30 +10:00
Bryan Baron
757951ba6b feat(view): floating window's optional adaptive size specification (#1559) 2022-08-30 08:50:18 +10:00
Krzysztof Cieśla
07f59e7450 fix(#1539): Fix closing nvim-tree float when file is removed (#1546)
* Fix closing nvim-tree float when file is removed

* Revert changes for non-float

Co-authored-by: Krzysztof Cieśla <krzysztof.marcin.ciesla@cern.ch>
2022-08-30 08:47:13 +10:00
Piotr Doan
4a725c0ca5 fix(#1555): incorrect exe highlight in Windows filesystem from WSL (#1557) 2022-08-29 10:53:23 +10:00
Krzysztof Cieśla
ce5d0a6b7d fix(#1543): Do not resize nvim-tree window if float is enabled (#1556)
Co-authored-by: Krzysztof Cieśla <krzysztof.marcin.ciesla@cern.ch>
2022-08-27 13:36:26 +10:00
Alexander Courtis
c272c88daf fix(#1551): handle git status TT as staged 2022-08-27 11:58:16 +10:00
Alexander Courtis
c3ea264947 feat(view): allow function for view.float.open_win_config (#1538) 2022-08-23 17:14:23 +10:00
Alexander Courtis
259efeee62 fix(#1540): watcher ignore directories with name exactly '.git' 2022-08-23 10:29:45 +10:00
Alexander Courtis
e3353c4cb4 fix(#1529): ensure tree window exists before closing (#1537) 2022-08-22 16:58:41 +10:00
Alexander Courtis
2bb15fd98f doc: add PR tips to contributing 2022-08-22 16:22:32 +10:00
Sebastian Volland
90dcf42bba fix(#1533): make toggle_mark ignore non-togglable nodes. (#1534) 2022-08-22 14:24:25 +10:00
Sebastian Volland
049cdd3073 fix(#1518): sort_by=modification_time not reordering on refresh. (#1519) 2022-08-22 14:19:06 +10:00
Sebastian Volland
c5fba1ec18 fix(#1520): file type changes are not detected. (#1521) 2022-08-22 11:41:11 +10:00
Alexander Courtis
81eb718394 fix: inverted diagnostic navigation keymaps 2022-08-20 14:40:09 +10:00
Jonathan Gollnick
9fd7b7ae29 fix(#1514): inverted git navigation keymaps (#1515) 2022-08-20 14:37:51 +10:00
Alexander Courtis
d9edddb849 fix(#1503): focus last win before close (#1509) 2022-08-20 14:32:28 +10:00
axlauri
09a51266bc fix(#1494): git showUntracked value and log (#1504)
* should_show_untracked correctly evaluates status.showUntrackedFiles

* git.Runner:_run_git_job removes nils before logging args
2022-08-15 14:30:22 +10:00
Carlos Castillo
b314b3a699 fix(#1500): focusing directories with a trailing slash in their path doesn't work (#1501) 2022-08-14 15:00:04 +10:00
Alexander Courtis
261a5c380c fix(#1480): break symlink cycle on find-file, search-node (#1482)
* fix(#1480): break symlink cycle on find-file

* fix(#1480): break symlink cycle on search-node

* fix(#1480): break symlink cycle on search-node

* fix(#1480): break symlink cycle on find-file
2022-08-08 12:46:09 +10:00
Hoang Nguyen
a73d0d4800 feat(file-popup): add actions.file_popup.open_win_config
* file-popup: add nvim_open_win configuration

* docs: update file-popup configuration
2022-08-08 10:52:14 +10:00
Alexander Courtis
ff6e7966f3 fix(#1484): better error handling in git utils get_toplevel 2022-08-07 12:16:03 +10:00
Krzysztof Cieśla
7323c81bd6 feat(view): Floating nvim tree window #1377 (#1462)
* Simple mock-up of floating nvim-tree window

* Passing whole table to nvim_open_win()

* Run update-help.sh

* Use vim.api alias

* Add comment to float options

* Added `anchor` to float options

* Enabling float window enforces `actions.open_file.quit_on_open`

* Added documentation

* add view.float.open_win_config, skipping validation

* Made nvim-tree window closes when float is enabled

* Close nvim-tree window when out of focus

* Update help

Co-authored-by: Krzysztof Cieśla <krzysztof.marcin.ciesla@cern.ch>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-08-06 15:40:07 +10:00
Alexander Courtis
1685484738 doc: reinstate mapping doc, mark on_attach as experimental (#1481) 2022-08-02 13:59:51 +02:00
Alexander Courtis
cfc4692a3f fix(#1479): apply remove_keymaps to default mappings 2022-08-02 09:29:57 +10:00
kiyan
451901ca9c chore(keymap): extract filtering into function 2022-07-31 12:32:55 +02:00
kiyan
9bbf95e616 fix(keymaps): get_keymaps takes remove_keymaps as parameter 2022-07-30 10:50:10 +02:00
kiyan
665813b9e6 fix(perf): explorer was creating new table for each new entry
augment performance on large folder by a factor of 10.
my /nix/store explorer goes from ~12sec to ~1.5sec.
2022-07-29 09:35:15 +02:00
Austin Harris
7fcb48c852 feat: add option for folder arrows to be inline with indent markers (#1468) 2022-07-28 11:49:23 +02:00
kiyan
ac90664001 fix(watchers): disable watchers on kernel filesystems
fixes #1465
2022-07-28 11:45:47 +02:00
Kian-Meng Ang
2928f8fe31 fix(docs): typos (#1470) 2022-07-27 13:48:14 +02:00
kiyan
e632ac7c81 fix(create-file): when node is nil, create the file at root
fixes #1467
2022-07-27 09:33:01 +02:00
Kiyan
64cc3c17e1 feat(mapping): deprecate user mappings and add on_attach (#1424) 2022-07-26 11:09:39 +02:00
kiyan
5f30a7bee4 chore(config): enable filesystem watchers by default 2022-07-26 10:46:59 +02:00
Alexander Courtis
eff1db341c chore(watchers): Watcher shares single fs_event from Event, node watchers use unique path prefixed debounce context (#1453) 2022-07-26 10:43:58 +02:00
kiyan
e5222970d9 chore(api): add missing functions 2022-07-25 15:02:55 +02:00
kiyan
e95bfbfc2d fix(api): use copy.node instead of copy.name
ref #1461
2022-07-25 14:35:01 +02:00
kiyan
a0448184af fix(api): copy is a table, copy -> copy.name
fixes #1461
2022-07-25 13:38:28 +02:00
Alexander Courtis
86b9da5ca5 chore(git): get_project_root cache cwd_to_project_root after checking existence (#1457) 2022-07-25 11:27:12 +02:00
Kiyan
e7832785d2 feat(api): add public API module (#1432) 2022-07-25 11:11:48 +02:00
John Fred Fadrigalan
d927e89aa9 refactor(actions): remove linefeed on info messages. (#1450) 2022-07-22 10:10:58 +02:00
kiyan
08ab346f03 fix(scripts): default mappings should put a comma at the end of each line 2022-07-21 14:38:29 +02:00
Gutyina Gergő
522bde4ea5 fix(docs) Add commas in example config for docs (#1447) 2022-07-21 14:31:18 +02:00
kiyan
79434c2b3c feat(tab_change): introduce new option to filter buffer by bufname or ft
Also fixes changing tab by deferring the call on tab enter.
New option `ignore_buf_on_tab_change` to avoid opening for some tabs.
Some example could be neogit, vim fugitive, man pages ...
2022-07-21 11:14:40 +02:00
FotiadisM
1e3c578eeb fix: count unicode codepoints instead of bytes (#1445) 2022-07-20 23:05:44 +02:00
kiyan
630305c233 fix(executable): prevent nil extensions in executable check
fix on windows
fixes #1444
2022-07-19 14:11:53 +02:00
yehy4
c964fa24d0 fix(git): fix inverted condition logic introduced in #1433 (#1443) 2022-07-19 11:48:32 +02:00
kiyan
8dc2144e87 refactor: use vim.ui.input for y/n selections
also add clear_prompt again.
fixes #1441
2022-07-19 11:31:09 +02:00
Klesh Wong
b754eb8359 fix(explorer): reload executable stat (#1427) 2022-07-19 11:25:41 +02:00
Alexander Courtis
f85b4d9952 chore(git): profile git toplevel and untracked calls (#1435) 2022-07-19 17:44:24 +10:00
yehy4
203bb7e176 fix(git): prevent execution of git commands when git integration is disabled (#1433) 2022-07-19 17:44:05 +10:00
Alexander Courtis
ecca8118f8 doc: remove fs_poll interval and update doc 2022-07-19 09:39:03 +10:00
kiyan
2edbe759cd fix(open-file): vim.op -> vim.opt 2022-07-18 15:44:52 +02:00
kiyan
ba5c18dc2b feat: add confirmation kind to select y/n 2022-07-18 14:38:32 +02:00
kiyan
1018a83e10 fix(create-file): pass file in parameter 2022-07-18 14:34:10 +02:00
kiyan
1ee6a3ea65 feat(create-file): use vim.ui.select for confirmation
fixes #1434
fixes #1294
2022-07-18 14:32:19 +02:00
kiyan
2d629cab78 feat(remove-file): use vim.ui.select 2022-07-18 14:28:08 +02:00
kiyan
7cffe14743 feat(trash): use vim.ui.select for confirmation 2022-07-18 14:25:52 +02:00
kiyan
1b667bc99e feat(search-node): use vim.ui.input 2022-07-18 14:20:55 +02:00
kiyan
69aec67edb feat(copy-paste): use vim.ui from prompting 2022-07-18 14:17:25 +02:00
kiyan
18447132fc feat(notify): switch all print/nvim_*write statements to utils.notify 2022-07-18 14:04:48 +02:00
kiyan
21fadc1f38 chore: move nvim-tree.utils.warn -> notify.warn
add notify.error and notify.info
2022-07-18 13:46:11 +02:00
kiyan
ec530e73be fix(wipe): wipe all nvim-tree rogue buffers
also prevent find_file if bufnr is not valid
fixes #1438
2022-07-18 13:29:56 +02:00
Alexander Courtis
16753d5e25 doc: add help hint to invalid config warning 2022-07-18 10:13:16 +10:00
Alexander Courtis
06e48c29c4 chore(watchers): refactor events and make debouncer safe
- fs poll -> fs events
- make debouncer safe and fix diagnostics events
2022-07-17 08:50:24 +02:00
Kiyan
26512c369f feat(marks): add bulk move action (#1419) 2022-07-17 08:25:11 +02:00
kiyan
208ce0b153 doc(contrib): add notice for git hook setup 2022-07-16 15:44:05 +02:00
kiyan
4900d66370 fix(open-file): focus file if already opened 2022-07-16 15:38:50 +02:00
Kiyan
89becc7604 feat(marks): add navigation (next, previous, select) (#1415) 2022-07-16 10:40:47 +02:00
Kiyan
b32c88333f feat(movement): allow circular movement for sibling next and prev (#1416) 2022-07-16 10:39:24 +02:00
kiyan
449b5bd0cb fix(renderer): padding indent with folders only 2022-07-15 18:09:28 +02:00
kiyan
9a02dedd92 fix(renderer): indent markers with arrows
breaking: glyphs for indent markers should only be one block large
2022-07-15 09:33:40 +02:00
kiyan
19425c5896 refactor(renderer): extract bools into variables 2022-07-14 19:07:12 +02:00
kiyan
8632ac2739 fix(renderer): indent markers + folder arrows should offset
fixes #1421
2022-07-14 19:04:01 +02:00
kiyan
80dc86e874 refactor(actions): use vim.keymap.set/del 2022-07-14 09:57:37 +02:00
kiyan
7087af83f3 fix(keypress): use <cmd> instead of : to avoid triggering CmdLineEnter
fixes #1417
2022-07-14 09:33:19 +02:00
ii14
c231933fcd feat: add -bar option to command definitions (#1422)
Co-authored-by: ii14 <ii14@users.noreply.github.com>
2022-07-14 09:17:30 +02:00
kiyan
0f96e32326 fix(actions): dispatching filter should not match for "live" keyword
fixes #1420
2022-07-12 10:16:01 +02:00
kiyan
6a49a0301f refactor(marks): fix offset line and move into init.lua
also set node in marks record instead of true
2022-07-12 09:34:26 +02:00
kiyan
078a9e5bf9 chore: move focus_file to utils 2022-07-11 16:55:33 +02:00
Kiyan
df92f1527f feat(bookmarks): add bookmark feature (#1412) 2022-07-11 10:00:12 +02:00
kiyan
0fa2ec1950 fix(actions): create file should defer more to focus 2022-07-11 09:49:10 +02:00
Alexander Courtis
26d0757bd9 doc: fix bad link in readme 2022-07-11 09:44:43 +10:00
kiyan
ad1f3ef3bc feat(renderer): show symlink folder destination
fixes https://github.com/kyazdani42/nvim-tree.lua/issues/980
2022-07-10 10:47:51 +02:00
kiyan
8d0c93db4a refactor(collapse-all): extract buffer matching logic 2022-07-10 10:03:48 +02:00
kiyan
2d2cbe63f4 refactor(actions): split movements into multiple modules 2022-07-10 09:53:58 +02:00
Kiyan
831f1158c3 refactor(actions): move actions into semantic modules (#1410) 2022-07-10 09:47:52 +02:00
kiyan
90bf14014e fix(file rename): edit buffer when renaming to reset filetype
fixes https://github.com/kyazdani42/nvim-tree.lua/issues/1404
2022-07-10 09:39:11 +02:00
Alexander Courtis
fd562ede63 fix(#1406): allow nvim-tree.renderer.icons.show.folder_arrow
* fix(#1406): allow nvim-tree.renderer.icons.show.folder_arrow when not folder

* fix(#1406): allow nvim-tree.renderer.icons.show.folder_arrow when indent markers enabled

* fix(builder): highlight first iteration for arrow column

* fix stylua

Co-authored-by: kiyan <yazdani.kiyan@protonmail.com>
2022-07-10 12:14:18 +10:00
kiyan
95c57e034a fix(dispatch): dispatch help toggle when its not shown
fixes #1411
2022-07-09 14:42:14 +02:00
kiyan
c037c7ae84 refactor(change-dir): add profile from higher order function 2022-07-09 12:14:41 +02:00
Arthur
fdcdb0ddf3 :help nvim-tree.setup -> :help nvim-tree-setup (#1409) 2022-07-09 12:10:59 +02:00
kiyan
1e7019f91e refactor(dispatch): cleanup dispatch logic 2022-07-09 12:09:51 +02:00
kiyan
63831d5179 refactor(actions): move on_keypress to dispatch module 2022-07-09 11:43:58 +02:00
Alexander Courtis
b81ab199a5 fix(help): clear git signs before draw 2022-07-09 11:44:39 +10:00
Alexander Courtis
d0ca2dab00 stylua 0.14.0 2022-07-09 11:26:55 +10:00
Alexander Courtis
08db5a576d doc: clarify need for patched font for nvim-web-devicons 2022-07-09 11:14:15 +10:00
Alexander Courtis
22044589fe doc: add tip to hide .git folder 2022-07-09 10:37:18 +10:00
kiyan
4bd919a75f fix(get-node-from-path): group dirs should be returned before nodes 2022-07-06 14:00:43 +02:00
kiyan
9d3602e8ea fix(find-file): do not recurse on closed nodes 2022-07-06 13:52:36 +02:00
kiyan
c84735483f feat: add on_tree_resize event 2022-07-06 13:35:29 +02:00
kiyan
eb6dde4733 fix(change-dir): cd command concatenation 2022-07-06 09:06:29 +02:00
Edwar Martinez Vale
418fc971fc fix(iterators): the index does not increase (#1399) 2022-07-06 01:07:36 +02:00
Rammiah
269820e800 fix(actions): change_dir to wrong directory (#1398) 2022-07-05 19:29:15 +02:00
Toby O'Sullivan
38fabe86cb Optionally suppress the symlink destination (#1396) 2022-07-05 10:05:21 +02:00
Kiyan
f43b8af8f4 chore(iterators): create Iterator module and migrate iterators to use it (#1392) 2022-07-04 14:13:14 +02:00
kiyan
70bdf496ea chore: remove quit_on_open from view and use abandon current window 2022-07-04 14:12:56 +02:00
Grzegorz Rozdzialik
40e515df87 fix(view): do not close window when NvimTree buffer is replaced (#1391) 2022-07-04 14:11:42 +02:00
kiyan
28c4bb01f6 fix: close view before hijacking current window 2022-07-04 10:17:06 +02:00
kiyan
19dcacf06e chore: cleanup change dir module 2022-07-03 12:41:13 +02:00
Alexander Courtis
736cc843e1 feat(#1389): add git.show_on_dirs (#1390) 2022-07-03 11:57:12 +02:00
Alexander Courtis
80d4f28383 feat(#1245): add next_diag_item and prev_diag_item actions 2022-07-03 16:04:49 +10:00
Krasimir Zahariev
21516f447b feat(actions): expand_all 'exclude' option (#1388) 2022-07-02 18:17:39 +02:00
kiyan
cbbc799e6c fix(trash): do not run trash command when trash is not executable 2022-07-02 12:08:46 +02:00
Rammiah
ec09b80c7b fix(actions): close_node doesn't close for grouped node (#1385) 2022-07-01 15:49:31 +02:00
Alexander Courtis
72858986f9 fix(#1366): warn when trash cmd missing (#1378) 2022-06-28 18:29:42 +02:00
Alexander Courtis
c18aa389a3 doc: setup may be invoked multiple times 2022-06-28 11:27:14 +10:00
Alexander Courtis
e401a4c957 feat(watcher): debounce FS watchers 2022-06-28 11:18:22 +10:00
Alexander Courtis
7a795d78fa feat(watcher): partial git refresh (#1373) 2022-06-27 11:12:28 +10:00
Alexander Courtis
247f80b633 doc: clarify quit-on-last-window-hack support status 2022-06-27 09:34:24 +10:00
Kiyan
e6c1b4cd5b chore(setup): make setup idempotent (#1340)
Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-06-26 12:14:03 +02:00
Alexander Courtis
0c13bd76a8 chore: update_root, sync_root_with_cwd, refactor with move_missing_val (#1359)
* chore: opts.update_focused_file.update_cwd -> update_root

* chore: opts.update_cwd -> sync_root_with_cwd

* chore: refactor options with utils move_missing_val

* chore: refactor options with utils move_missing_val

* chore: refactor options with utils move_missing_val

* chore: refactor options with utils move_missing_val

* chore: refactor options with utils move_missing_val

* chore: refactor options with utils move_missing_val
2022-06-26 12:18:14 +10:00
lixvbnet
b299a877ad fix (#1363): use change_dir.fn instead of lib.open in M.change_root 2022-06-25 14:14:08 +10:00
Alexander Courtis
65beb55ac7 doc(#1368): more quit-on-last-window warnings 2022-06-25 12:43:14 +10:00
Alexander Courtis
0db63a350a doc: tidy help 2022-06-25 11:39:24 +10:00
Alexander Courtis
7160e68d5a doc: tidy help 2022-06-25 11:23:19 +10:00
litao91
79258f1d67 fix: window picker can't be correctly rendered when cmdheight = 0 (#1349)
Co-authored-by: litao <litao912002@hotmail.com>
2022-06-20 08:29:47 +02:00
Alexander Courtis
104292c8f9 chore: rename and simplify help update script 2022-06-20 11:54:14 +10:00
Alexander Courtis
6548287e8b feat: add cwd arg to open_replacing_current_buffer: retain existing valid buffer check 2022-06-20 09:52:56 +10:00
Kiyan
f262236107 chore: add matrix link to readme 2022-06-19 10:49:53 +02:00
kiyan
3bc2207f4a chore: simplify get_alt_or_next_buf 2022-06-19 09:51:40 +02:00
Alexander Courtis
d9aaa2f985 fix(#1356): view.close switch_buf_if_last_buf prefers alt buf (#1357) 2022-06-19 09:51:01 +02:00
javiertury
6b7b1b34fa feat: add cwd arg to open_replacing_current_buffer (#1348) 2022-06-19 13:59:35 +10:00
Alexander Courtis
1fc0eee946 fix(#1354): add missing :hi NvimTreeFileIgnored (#1358) 2022-06-18 10:42:12 +02:00
btstream
e82a921baa fix(view): prevent buffer override when actions.open_file.quit_on_open
* fix(view): prevent open buffer on NvimTree window when actions.open_file.quit_on_open is true

* fix(view): add view.quit_on_open function to prevent open on NvimTree window

Co-authored-by: btstream <btstream@gmail.com>
2022-06-18 17:07:24 +10:00
lixvbnet
b08003f546 feat: add NvimTreeFindFile!, root_dirs and prefer_startup_root 2022-06-18 15:32:56 +10:00
Alexander Courtis
84c2bd77ff docs: remove duplicate help tag 2022-06-18 14:18:37 +10:00
gegoune
aba394896b docs: lighten up readme and rework docs (#1327)
* docs: lighten up readme and rework docs

* docs: clean up mappings from readme + some other small changes

* docs: move sections around

* ci: remove readme parts of docs' autogen

* docs: discuss nvim-web-devicons and provide an example

* docs: add an example setup

* docs: fix opts scraping; try and make more macos compatible

* docs: add *nvim-tree* tag at start of help

* docs: add an example setup

* docs: update quick start to match readme

* docs: add basic commands

* docs: add g? hint

* docs: add :help links to readme

* docs: restore help wanted

* docs: add example screenshot

* docs: add features

* docs: add example screenshot

* docs: add features to help

* docs: clarify option functions

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-06-18 14:14:42 +10:00
kiyan
bdb6d4a254 fix(actions): reloading copy-paste logic inverted
fixes #1331
2022-06-11 11:22:45 +02:00
Alexander Courtis
9219831aa2 fix: .luarc.json globals/disables 2022-06-11 13:41:33 +10:00
kylo252
1caca62854 feat(view): ability to centralize view on entering (#1310) 2022-06-07 10:09:58 +10:00
lu5je0
821f050fda feat: full-name of node in floating window (#1305) 2022-06-06 11:15:03 +02:00
Raafat Turki
3c936c7cb6 feat(trash): default command 'trash' -> 'gio trash' (#1323) 2022-06-06 11:13:37 +02:00
Alexander Courtis
f6eef4a1f0 feat(explorer): add filesystem watchers: reinstate git output logging 2022-06-06 11:19:55 +10:00
Rammiah
6f6eab14dc feat(renderer): indent_markers add a item icon (#1321) 2022-06-05 14:19:19 +02:00
Kiyan
b0d27c09b6 feat(explorer): add filesystem watchers (#1304)
* feat(explorer): add experimental watchers

This commit introduces watchers to update the tree.
This behavior is introduced behind an "filesystem_watchers" option
which should prevent instabilities.
It will become the default at some point.

Co-authored-by: Alexander Courtis <alex@courtis.org>
2022-06-05 12:39:39 +02:00
Alexander Courtis
a5793f1edb doc: add help wanted and contributing (#1319) 2022-06-05 11:52:08 +02:00
kiyan
25921aa87a fix(open-file): do not open file when target winid is nil 2022-06-04 14:06:35 +02:00
kiyan
92ed3c487a fix(view): call get size after setting the height
fixes bottom and top placement for the tree
fixes #1311
2022-06-04 11:56:03 +02:00
Alexander Courtis
3aeb59b075 doc: setup call is only allowed once 2022-06-04 12:37:02 +10:00
kiyan
a0f705995a fix(view): grow condition for left or right was always true for right 2022-05-31 20:25:13 +02:00
Kiyan
2002b21be7 feat(resize): add ability to grow and shrink the tree when drawing (#1293)
Will adapt the view size based on the longest line.
fixes #865
2022-05-31 09:05:00 +02:00
kiyan
6b26628acf fix(preview): open file in preview should delete buffer when hidden
fixes #1307
2022-05-30 18:59:43 +02:00
Alexander Courtis
b1ecb75a6c feat: guard against multiple setup calls (#1308) 2022-05-30 12:46:56 +02:00
Alexander Courtis
8198fa01fc doc: default mappings 2022-05-30 12:53:28 +10:00
kiyan
5e900c2f29 refacto: tree explorer root should be absolute_path not cwd 2022-05-29 11:40:06 +02:00
emmanueltouzery
3806653d75 new option: close windows displaying removed files (#1300) 2022-05-29 11:23:01 +02:00
Kiyan
3a95c5a9cf feat(actions): expand all under folder (#1292) 2022-05-29 11:15:32 +02:00
Alexander Courtis
0373680819 #1301 nvim uses LuaJIT 2.1 -> lua 5.1 2022-05-29 12:29:40 +10:00
Alexander Courtis
c3b7be8d19 add .luarc.json for lua-language-server and fix a couple of nits (#1296) 2022-05-28 11:16:54 +02:00
Alexander Courtis
e482bad61c doc: tidy spacing and consistency 2022-05-28 15:45:41 +10:00
Alexander Courtis
84cb79e760 update auto-close answer 2022-05-28 12:13:50 +10:00
Alexander Courtis
3ba383d591 chore/remove globals (#1279)
* remove renderer globals: nvim_tree_add_trailing nvim_tree_highlight_opened_files nvim_tree_root_folder_modifier nvim_tree_special_files

* remove renderer globals: nvim_tree_icon_padding

* remove renderer globals: nvim_tree_symlink_arrow

* remove renderer globals: nvim_tree_show_icons, nvim_tree_show_icons

* remove renderer globals: nvim_tree_git_hl

* remove renderer globals: nvim_tree_group_empty

* remove renderer globals: respect_buf_cwd

* remove renderer globals: nvim_tree_create_in_closed_folder

* remove globals: consistency in legacy checks

* remove renderer globals: nvim_tree_special_files

* renderer.icons.symbols -> glyphs
2022-05-28 11:08:40 +10:00
bstaint
6abc87b1d9 feat: msys2 git support (#1295) 2022-05-26 15:22:42 +02:00
kiyan
540c811cb2 fix(open file): do not trigger buf enter event when setting target win
Fixes #1288
Also starts a refactoring of the open-file code, to make it easier to
debug and improve.
2022-05-26 13:37:50 +02:00
Taxo Rubio
b2ba6dea71 feat: optional path argument for NvimTreeToggle and NvimTreeFindFileToggle (#1276) 2022-05-21 13:31:14 +02:00
kiyan
73ab312820 refactor: simplify opening file in new tab
fixes #1271. Also fixes opening a file in new tab when close_on_open was
true.

This introduces breaking change since we don't do any extra behavior
and let the buffer be opened by the tree.
The previous behavior was a bit old and i believe this should've been
fixed by now.
Reference this commit if unexpected behavior appears while opening files
in new tabs from nvim-tree.
2022-05-21 12:09:09 +02:00
kiyan
9d6f4c184b chore: remove custom set local implementation
Seems vim.opt_local has been fixed.
see https://github.com/neovim/neovim/issues/14670
2022-05-21 11:36:07 +02:00
kiyan
17d5bd64e8 chore(config): auto resize the tree by default when opening a file.
config.open_file.auto_resize is now true by default.
Breaking change for default configurations.
See https://github.com/kyazdani42/nvim-tree.lua/issues/1275#issuecomment-1133515999
2022-05-21 11:27:49 +02:00
Michael
9563a11ce0 feat: reload explorer on buf enter (#1265) 2022-05-17 10:04:08 +02:00
Kiyan
6343813a35 feat(live-filter): add ability to live filter out nodes in the tree (#1056) 2022-05-17 10:03:49 +02:00
Alexander Courtis
99e32fea14 add fish performance tip to README.md 2022-05-17 13:03:59 +10:00
kiyan
9d26594b6c fix(renderer): empty space at end of line
fixes #1253
2022-05-15 10:28:17 +02:00
kiyan
7293f8dc70 fix(renderer): padding when git icons are after the name
fixes #1253
2022-05-14 13:41:58 +02:00
kiyan
d88d12f5bc Revert "#1253 only pad git icons when they are present (#1259)"
This reverts commit 90d7b8edb1.
fixes #1267
2022-05-14 13:31:37 +02:00
muro3r
aefa66c04d feat: extension sorter (#1181) (#1264) 2022-05-14 10:54:01 +02:00
Kiyan
f8312cd06f feat(renderer): add ability to set git icons in signcolumn (#1242) 2022-05-14 09:54:27 +02:00
Michael
46014449b6 refactor: use lua api for user commands and autocommands (#1206)
BREAKING: plugin now requires nvim-0.7
2022-05-14 09:49:45 +02:00
133 changed files with 13889 additions and 5032 deletions

View File

@@ -7,3 +7,6 @@ end_of_line = lf
[*.lua] [*.lua]
indent_style = space indent_style = space
indent_size = 2 indent_size = 2
[nvim-tree-lua.txt]
max_line_length = 78

View File

@@ -5,7 +5,16 @@ body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
Before reporting: search [existing issues](https://github.com/kyazdani42/nvim-tree.lua/issues) and make sure that nvim-tree is updated to the latest version. If you are experiencing performance issues, please [enable profiling](https://github.com/kyazdani42/nvim-tree.lua#performance-issues) and attach the logs. Is this a question?
* Please start a new [Q&A discussion](https://github.com/nvim-tree/nvim-tree.lua/discussions/new) instead of raising a bug.
Before reporting:
* search [existing issues](https://github.com/nvim-tree/nvim-tree.lua/issues)
* ensure that nvim-tree is updated to the latest version
If you are experiencing performance issues, please [enable profiling](https://github.com/nvim-tree/nvim-tree.lua#performance-issues) and attach the logs.
Please note that nvim-tree team members do not have access to nor expertise with Windows. You will need to be an active participant during resolution.
- type: textarea - type: textarea
attributes: attributes:
label: "Description" label: "Description"
@@ -15,7 +24,7 @@ body:
- type: textarea - type: textarea
attributes: attributes:
label: "Neovim version" label: "Neovim version"
description: "Output of `nvim --version`. Please see nvim-tree.lua [minimum required version](https://github.com/kyazdani42/nvim-tree.lua#notice)." description: "Output of `nvim --version`. Please see nvim-tree.lua [minimum required version](https://github.com/nvim-tree/nvim-tree.lua#notice)."
placeholder: | placeholder: |
NVIM v0.6.1 NVIM v0.6.1
Build type&#58 Release Build type&#58 Release
@@ -29,6 +38,12 @@ body:
placeholder: "Linux 5.16.11-arch1-1, macOS 11.5, Windows 10" placeholder: "Linux 5.16.11-arch1-1, macOS 11.5, Windows 10"
validations: validations:
required: true required: true
- type: input
attributes:
label: "Windows variant"
placeholder: "WSL, PowerShell, cygwin, msys"
validations:
required: false
- type: input - type: input
attributes: attributes:
label: "nvim-tree version" label: "nvim-tree version"
@@ -39,14 +54,14 @@ body:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: "Minimal config" label: "Clean room replication"
description: "Minimal(!) configuration necessary to reproduce the issue. description: "Minimal(!) configuration necessary to reproduce the issue.
(Right click) save [nvt-min.lua](https://raw.githubusercontent.com/kyazdani42/nvim-tree.lua/master/.github/ISSUE_TEMPLATE/nvt-min.lua) to `/tmp` and run using `nvim -nu /tmp/nvt-min.lua` If not provided it is very unlikely that the nvim-tree team will be able to address your issue.
If _absolutely_ necessary, add plugins and modify the nvim-tree setup at the indicated lines. See [wiki: Clean Room Replication](https://github.com/nvim-tree/nvim-tree.lua/wiki/Troubleshooting#clean-room-replication) for instructions and paste the contents of your `/tmp/nvt-min.lua` here.
Paste the contents of your `/tmp/nvt-min.lua` here." Please do NOT post a configuration that uses other plugin managers such as lazy, see [wiki: Lazy Loading](https://github.com/nvim-tree/nvim-tree.lua/wiki/Installation#lazy-loading)"
render: lua render: lua
validations: validations:
required: true required: true

View File

@@ -6,6 +6,12 @@ labels: feature request
assignees: '' assignees: ''
--- ---
**Is this a question?**
Please start a new [Q&A discussion](https://github.com/nvim-tree/nvim-tree.lua/discussions/new) instead of raising a feature request.
**Can this functionality be implemented utilising API?**
nvim-tree exposes extensive API (see `:h nvim-tree-api`). Can it be used to achieve your goal? Is there a missing API that would make it possible?
Given stable status of nvim-tree it's preferred to add new API than new functionality.
**Is your feature request related to a problem? Please describe.** **Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

View File

@@ -1,3 +1,6 @@
vim.g.loaded_netrw = 1
vim.g.loaded_netrwPlugin = 1
vim.cmd [[set runtimepath=$VIMRUNTIME]] vim.cmd [[set runtimepath=$VIMRUNTIME]]
vim.cmd [[set packpath=/tmp/nvt-min/site]] vim.cmd [[set packpath=/tmp/nvt-min/site]]
local package_root = "/tmp/nvt-min/site/pack" local package_root = "/tmp/nvt-min/site/pack"
@@ -6,8 +9,8 @@ local function load_plugins()
require("packer").startup { require("packer").startup {
{ {
"wbthomason/packer.nvim", "wbthomason/packer.nvim",
"kyazdani42/nvim-tree.lua", "nvim-tree/nvim-tree.lua",
"kyazdani42/nvim-web-devicons", "nvim-tree/nvim-web-devicons",
-- ADD PLUGINS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE -- ADD PLUGINS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE
}, },
config = { config = {
@@ -32,3 +35,13 @@ _G.setup = function()
require("nvim-tree").setup {} require("nvim-tree").setup {}
end end
-- UNCOMMENT this block for diagnostics issues, substituting pattern and cmd as appropriate.
-- Requires diagnostics.enable = true in setup.
--[[
vim.api.nvim_create_autocmd("FileType", {
pattern = "lua",
callback = function()
vim.lsp.start { cmd = { "lua-language-server" } }
end,
})
]]

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

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

BIN
.github/screenshot.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 615 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 565 KiB

View File

@@ -2,33 +2,91 @@ name: CI
on: on:
pull_request: pull_request:
branches:
- '*'
push: push:
branches: branches: [master]
- master workflow_dispatch:
permissions:
contents: read
jobs: jobs:
luacheck: lint:
name: luacheck
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Prepare concurrency:
run: | group: ${{ github.workflow }}-${{ matrix.lua_version }}-${{ github.head_ref || github.ref_name }}
sudo apt-get update cancel-in-progress: true
sudo add-apt-repository universe
sudo apt install luarocks -y strategy:
sudo luarocks install luacheck matrix:
- name: Run luacheck lua_version: [ 5.1 ]
run: luacheck .
stylua:
name: stylua
runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- uses: JohnnyMorganz/stylua-action@1.0.0
- uses: leafo/gh-actions-lua@v10
with:
luaVersion: ${{ matrix.lua_version }}
- uses: leafo/gh-actions-luarocks@v4
- run: luarocks install luacheck 1.1.1
- run: make lint
style:
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ matrix.stylua_version }}-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true
strategy:
matrix:
stylua_version: [ 0.19.1 ]
steps:
- uses: actions/checkout@v4
- name: stylua
uses: JohnnyMorganz/stylua-action@v4
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
args: --color always --check lua/ version: ${{ matrix.stylua_version }}
args: --check lua
- 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.9.1 ]
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

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

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

View File

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

4
.gitignore vendored Normal file
View File

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

View File

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

78
.luarc.json Normal file
View File

@@ -0,0 +1,78 @@
{
"$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json",
"runtime.version": "Lua 5.1",
"workspace": {
"library": [
"$VIMRUNTIME/lua/vim",
"${3rd}/luv/library"
]
},
"diagnostics": {
"libraryFiles": "Disable",
"globals": [],
"neededFileStatus": {
"ambiguity-1": "Any",
"assign-type-mismatch": "Any",
"await-in-sync": "Any",
"cast-local-type": "Any",
"cast-type-mismatch": "Any",
"circle-doc-class": "Any",
"close-non-object": "Any",
"code-after-break": "Any",
"codestyle-check": "None",
"count-down-loop": "Any",
"deprecated": "Any",
"different-requires": "Any",
"discard-returns": "Any",
"doc-field-no-class": "Any",
"duplicate-doc-alias": "Any",
"duplicate-doc-field": "Any",
"duplicate-doc-param": "Any",
"duplicate-index": "Any",
"duplicate-set-field": "Any",
"empty-block": "Any",
"global-element": "Any",
"global-in-nil-env": "Any",
"incomplete-signature-doc": "None",
"inject-field": "Any",
"invisible": "Any",
"lowercase-global": "Any",
"missing-fields": "Any",
"missing-global-doc": "Any",
"missing-local-export-doc": "None",
"missing-parameter": "Any",
"missing-return": "Any",
"missing-return-value": "Any",
"name-style-check": "None",
"need-check-nil": "Any",
"newfield-call": "Any",
"newline-call": "Any",
"no-unknown": "None",
"not-yieldable": "Any",
"param-type-mismatch": "Any",
"redefined-local": "Any",
"redundant-parameter": "Any",
"redundant-return": "Any",
"redundant-return-value": "Any",
"redundant-value": "Any",
"return-type-mismatch": "Any",
"spell-check": "None",
"trailing-space": "Any",
"unbalanced-assignments": "Any",
"undefined-doc-class": "Any",
"undefined-doc-name": "Any",
"undefined-doc-param": "Any",
"undefined-env-child": "Any",
"undefined-field": "None",
"undefined-global": "Any",
"unknown-cast-variable": "Any",
"unknown-diag-code": "Any",
"unknown-operator": "Any",
"unreachable-code": "Any",
"unused-function": "Any",
"unused-label": "Any",
"unused-local": "Any",
"unused-vararg": "Any"
}
}
}

View File

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

View File

@@ -1,4 +1,4 @@
column_width = 120 column_width = 140
line_endings = "Unix" line_endings = "Unix"
indent_type = "Spaces" indent_type = "Spaces"
indent_width = 2 indent_width = 2

153
CHANGELOG.md Normal file
View File

@@ -0,0 +1,153 @@
# Changelog
## [1.5.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.4.0...nvim-tree-v1.5.0) (2024-07-11)
### Features
* **#2127:** add experimental.actions.open_file.relative_path to open files with a relative path rather than absolute ([#2805](https://github.com/nvim-tree/nvim-tree.lua/issues/2805)) ([869c064](https://github.com/nvim-tree/nvim-tree.lua/commit/869c064721a6c2091f22c3541e8f0ff958361771))
* **#2598:** add api.tree.resize ([#2811](https://github.com/nvim-tree/nvim-tree.lua/issues/2811)) ([2ede0de](https://github.com/nvim-tree/nvim-tree.lua/commit/2ede0de67b47e89e2b4cb488ea3f58b8f5a8c90a))
* **#2799:** `filesystem_watchers.ignore_dirs` and `git.disable_for_dirs` may be functions ([#2800](https://github.com/nvim-tree/nvim-tree.lua/issues/2800)) ([8b2c5c6](https://github.com/nvim-tree/nvim-tree.lua/commit/8b2c5c678be4b49dff6a2df794877000113fd77b))
* **#2799:** filesystem_watchers.ignore_dirs and git.disable_for_dirs may be functions ([8b2c5c6](https://github.com/nvim-tree/nvim-tree.lua/commit/8b2c5c678be4b49dff6a2df794877000113fd77b))
### Bug Fixes
* **#2813:** macos: enable file renaming with changed capitalization ([#2814](https://github.com/nvim-tree/nvim-tree.lua/issues/2814)) ([abfd1d1](https://github.com/nvim-tree/nvim-tree.lua/commit/abfd1d1b6772540364743531cc0331e08a0027a9))
* **#2819:** experimental.actions.open_file.relative_path issue following change directory ([#2820](https://github.com/nvim-tree/nvim-tree.lua/issues/2820)) ([12a9a99](https://github.com/nvim-tree/nvim-tree.lua/commit/12a9a995a455d2c2466e47140663275365a5d2fc))
## [1.4.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.3.3...nvim-tree-v1.4.0) (2024-06-09)
### Notice
* Neovim 0.9 is now the minimum supported version; please upgrade to neovim release version 0.9 or 0.10.
### Reverts
* **#2781:** "refactor: replace deprecated use of vim.diagnostic.is_disabled()" ([#2784](https://github.com/nvim-tree/nvim-tree.lua/issues/2784)) ([517e4fb](https://github.com/nvim-tree/nvim-tree.lua/commit/517e4fbb9ef3c0986da7047f44b4b91a2400f93c))
### Miscellaneous Chores
* release 1.4.0 ([1cac800](https://github.com/nvim-tree/nvim-tree.lua/commit/1cac8005df6da484c97499247754afa59fef92db))
## [1.3.3](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.3.2...nvim-tree-v1.3.3) (2024-05-14)
### Bug Fixes
* nil access exception with git integration when changing branches ([#2774](https://github.com/nvim-tree/nvim-tree.lua/issues/2774)) ([340d3a9](https://github.com/nvim-tree/nvim-tree.lua/commit/340d3a9795e06bdd1814228de398cd510f9bfbb0))
## [1.3.2](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.3.1...nvim-tree-v1.3.2) (2024-05-12)
### Bug Fixes
* **#2758:** use nvim-webdevicons default file icon, not renderer.icons.glyphs.default, as per :help ([#2759](https://github.com/nvim-tree/nvim-tree.lua/issues/2759)) ([347e1eb](https://github.com/nvim-tree/nvim-tree.lua/commit/347e1eb35264677f66a79466bb5e3d111968e12c))
* **#2758:** use nvim-webdevicons default for default files ([347e1eb](https://github.com/nvim-tree/nvim-tree.lua/commit/347e1eb35264677f66a79466bb5e3d111968e12c))
* **#925:** handle newlines in file names ([#2754](https://github.com/nvim-tree/nvim-tree.lua/issues/2754)) ([64f61e4](https://github.com/nvim-tree/nvim-tree.lua/commit/64f61e4c913047a045ff90bd188dd3b54ee443cf))
## [1.3.1](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.3.0...nvim-tree-v1.3.1) (2024-04-25)
### Bug Fixes
* **#2535:** TextYankPost event sends vim.v.event ([#2734](https://github.com/nvim-tree/nvim-tree.lua/issues/2734)) ([d8d3a15](https://github.com/nvim-tree/nvim-tree.lua/commit/d8d3a1590a05b2d8b5eb26e2ed1c6052b1b47a77))
* **#2733:** escape trash path ([#2735](https://github.com/nvim-tree/nvim-tree.lua/issues/2735)) ([81eb8d5](https://github.com/nvim-tree/nvim-tree.lua/commit/81eb8d519233c105f30dc0a278607e62b20502fd))
## [1.3.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.2.0...nvim-tree-v1.3.0) (2024-03-30)
### Features
* add update_focused_file.exclude ([#2673](https://github.com/nvim-tree/nvim-tree.lua/issues/2673)) ([e20966a](https://github.com/nvim-tree/nvim-tree.lua/commit/e20966ae558524f8d6f93dc37f5d2a8605f893e2))
### Bug Fixes
* **#2658:** change SpellCap groups to reduce confusion: ExecFile-&gt;Question, ImageFile->Question, SpecialFile->Title, Symlink->Underlined; add all other highlight groups to :NvimTreeHiTest ([#2732](https://github.com/nvim-tree/nvim-tree.lua/issues/2732)) ([0aca092](https://github.com/nvim-tree/nvim-tree.lua/commit/0aca0920f44b12a8383134bcb52da9faec123608))
* bookmark filter shows marked directory children ([#2719](https://github.com/nvim-tree/nvim-tree.lua/issues/2719)) ([2d97059](https://github.com/nvim-tree/nvim-tree.lua/commit/2d97059661c83787372c8c003e743c984ba3ac50))
## [1.2.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.1.1...nvim-tree-v1.2.0) (2024-03-24)
### Features
* add api.tree.toggle_enable_filters ([#2706](https://github.com/nvim-tree/nvim-tree.lua/issues/2706)) ([f7c09bd](https://github.com/nvim-tree/nvim-tree.lua/commit/f7c09bd72e50e1795bd3afb9e2a2b157b4bfb3c3))
## [1.1.1](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.1.0...nvim-tree-v1.1.1) (2024-03-15)
### Bug Fixes
* **#2395:** marks.bulk.move defaults to directory at cursor ([#2688](https://github.com/nvim-tree/nvim-tree.lua/issues/2688)) ([cfea5bd](https://github.com/nvim-tree/nvim-tree.lua/commit/cfea5bd0806aab41bef6014c6cf5a510910ddbdb))
* **#2705:** change NvimTreeWindowPicker cterm background from Cyan to more visible DarkBlue ([#2708](https://github.com/nvim-tree/nvim-tree.lua/issues/2708)) ([1fd9c98](https://github.com/nvim-tree/nvim-tree.lua/commit/1fd9c98960463d2d5d400916c0633b2df016941d))
* bookmark filter should include parent directory ([#2704](https://github.com/nvim-tree/nvim-tree.lua/issues/2704)) ([76b9810](https://github.com/nvim-tree/nvim-tree.lua/commit/76b98109f62caa12b2f1dff472060b2233ea2e90))
## [1.1.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.0.0...nvim-tree-v1.1.0) (2024-03-14)
### Features
* **#2630:** file renames can now create directories ([#2657](https://github.com/nvim-tree/nvim-tree.lua/issues/2657)) ([efafd73](https://github.com/nvim-tree/nvim-tree.lua/commit/efafd73efa9bc8c26282aed563ba0f01c7465b06))
* add api.fs.copy.basename, default mapping ge ([#2698](https://github.com/nvim-tree/nvim-tree.lua/issues/2698)) ([8f2a50f](https://github.com/nvim-tree/nvim-tree.lua/commit/8f2a50f1cd0c64003042364cf317c8788eaa6c8c))
### Bug Fixes
* **#2695:** git toplevel guard against missing paths ([#2696](https://github.com/nvim-tree/nvim-tree.lua/issues/2696)) ([3c4267e](https://github.com/nvim-tree/nvim-tree.lua/commit/3c4267eb5045fa86b67fe40c0c63d31efc801e77))
* searchcount exception on invalid search regex ([#2693](https://github.com/nvim-tree/nvim-tree.lua/issues/2693)) ([041dbd1](https://github.com/nvim-tree/nvim-tree.lua/commit/041dbd18f440207ad161503a384e7c82d575db66))
## [1.0.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v0.100.0...nvim-tree-v1.0.0) (2024-02-18)
### Features
* **#2654:** filters.custom may be a function ([#2655](https://github.com/nvim-tree/nvim-tree.lua/issues/2655)) ([4a87b8b](https://github.com/nvim-tree/nvim-tree.lua/commit/4a87b8b46b4a30107971871df3cb7f4c30fdd5d0))
### Miscellaneous Chores
* release 1.0.0 ([#2678](https://github.com/nvim-tree/nvim-tree.lua/issues/2678)) ([d16246a](https://github.com/nvim-tree/nvim-tree.lua/commit/d16246a7575538f77e9246520449b99333c469f7))
## [0.100.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v0.99.0...nvim-tree-v0.100.0) (2024-02-11)
### Features
* **#1389:** api: recursive node navigation for git and diagnostics ([#2525](https://github.com/nvim-tree/nvim-tree.lua/issues/2525)) ([5d13cc8](https://github.com/nvim-tree/nvim-tree.lua/commit/5d13cc8205bce4963866f73c50f6fdc18a515ffe))
* **#2415:** add :NvimTreeHiTest ([#2664](https://github.com/nvim-tree/nvim-tree.lua/issues/2664)) ([b278fc2](https://github.com/nvim-tree/nvim-tree.lua/commit/b278fc25ae0fc95e4808eb5618f07fc2522fd2b3))
* **#2415:** colour and highlight overhaul, see :help nvim-tree-highlight-overhaul ([#2455](https://github.com/nvim-tree/nvim-tree.lua/issues/2455)) ([e9c5abe](https://github.com/nvim-tree/nvim-tree.lua/commit/e9c5abe073a973f54d3ca10bfe30f253569f4405))
* add node.open.toggle_group_empty, default mapping L ([#2647](https://github.com/nvim-tree/nvim-tree.lua/issues/2647)) ([8cbb1db](https://github.com/nvim-tree/nvim-tree.lua/commit/8cbb1db8e90b62fc56f379992e622e9f919792ce))
### Bug Fixes
* **#2415:** disambiguate highlight groups, see :help nvim-tree-highlight-overhaul ([#2639](https://github.com/nvim-tree/nvim-tree.lua/issues/2639)) ([d9cb432](https://github.com/nvim-tree/nvim-tree.lua/commit/d9cb432d2c8d8fa9267ddbd7535d76fe4df89360))
* **#2415:** fix NvimTreeIndentMarker highlight group: FileIcon-&gt;FolderIcon ([e9ac136](https://github.com/nvim-tree/nvim-tree.lua/commit/e9ac136a3ab996aa8e4253253521dcf2cb66b81b))
* **#2415:** highlight help header and mappings ([#2669](https://github.com/nvim-tree/nvim-tree.lua/issues/2669)) ([39e6fef](https://github.com/nvim-tree/nvim-tree.lua/commit/39e6fef85ac3bb29532b877aa7c9c34911c661af))
* **#2415:** nvim 0.8 highlight overhaul support, limited to only show highest highlight precedence ([#2642](https://github.com/nvim-tree/nvim-tree.lua/issues/2642)) ([f39f7b6](https://github.com/nvim-tree/nvim-tree.lua/commit/f39f7b6fcd3865ac2146de4cb4045286308f2935))
* **#2415:** NvimTreeIndentMarker highlight group: FileIcon-&gt;FolderIcon ([#2656](https://github.com/nvim-tree/nvim-tree.lua/issues/2656)) ([e9ac136](https://github.com/nvim-tree/nvim-tree.lua/commit/e9ac136a3ab996aa8e4253253521dcf2cb66b81b))
* **#2624:** open file from docked floating window ([#2627](https://github.com/nvim-tree/nvim-tree.lua/issues/2627)) ([f24afa2](https://github.com/nvim-tree/nvim-tree.lua/commit/f24afa2cef551122b8bd53bb2e4a7df42343ce2e))
* **#2632:** occasional error stack when locating nvim-tree window ([#2633](https://github.com/nvim-tree/nvim-tree.lua/issues/2633)) ([48b1d86](https://github.com/nvim-tree/nvim-tree.lua/commit/48b1d8638fa3726236ae22e0e48a74ac8ea6592a))
* **#2637:** show buffer modified icons and highlights ([#2638](https://github.com/nvim-tree/nvim-tree.lua/issues/2638)) ([7bdb220](https://github.com/nvim-tree/nvim-tree.lua/commit/7bdb220d0fe604a77361e92cdbc7af1b8a412126))
* **#2643:** correctly apply linked highlight groups in tree window ([#2653](https://github.com/nvim-tree/nvim-tree.lua/issues/2653)) ([fbee8a6](https://github.com/nvim-tree/nvim-tree.lua/commit/fbee8a69a46f558d29ab84e96301425b0501c668))
* allow highlight overrides for DEFAULT_DEFS: NvimTreeFolderIcon, NvimTreeWindowPicker ([#2636](https://github.com/nvim-tree/nvim-tree.lua/issues/2636)) ([74525ac](https://github.com/nvim-tree/nvim-tree.lua/commit/74525ac04760bf0d9fec2bf51474d2b05f36048e))
* bad column offset when using full_name ([#2629](https://github.com/nvim-tree/nvim-tree.lua/issues/2629)) ([75ff64e](https://github.com/nvim-tree/nvim-tree.lua/commit/75ff64e6663fc3b23c72dca32b2f838acefe7c8a))
* passing nil as window handle in view.get_winnr ([48b1d86](https://github.com/nvim-tree/nvim-tree.lua/commit/48b1d8638fa3726236ae22e0e48a74ac8ea6592a))
## 0.99.0 (2024-01-01)
### Features
* **#1850:** add "no bookmark" filter ([#2571](https://github.com/nvim-tree/nvim-tree.lua/issues/2571)) ([8f92e1e](https://github.com/nvim-tree/nvim-tree.lua/commit/8f92e1edd399f839a23776dcc6eee4ba18030370))
* add kind param to vim.ui.select function calls ([#2602](https://github.com/nvim-tree/nvim-tree.lua/issues/2602)) ([dc839a7](https://github.com/nvim-tree/nvim-tree.lua/commit/dc839a72a6496ce22ebd3dd959115cf97c1b20a0))
* add option to skip gitignored files on git navigation ([#2583](https://github.com/nvim-tree/nvim-tree.lua/issues/2583)) ([50f30bc](https://github.com/nvim-tree/nvim-tree.lua/commit/50f30bcd8c62ac4a83d133d738f268279f2c2ce2))
### Bug Fixes
* **#2519:** Diagnostics Not Updated When Tree Not Visible ([#2597](https://github.com/nvim-tree/nvim-tree.lua/issues/2597)) ([96a783f](https://github.com/nvim-tree/nvim-tree.lua/commit/96a783fbd606a458bcce2ef8041240a8b94510ce))
* **#2609:** help toggle ([#2611](https://github.com/nvim-tree/nvim-tree.lua/issues/2611)) ([fac4900](https://github.com/nvim-tree/nvim-tree.lua/commit/fac4900bd18a9fa15be3d104645d9bdef7b3dcec))
* hijack_cursor on update focused file and vim search ([#2600](https://github.com/nvim-tree/nvim-tree.lua/issues/2600)) ([02ae523](https://github.com/nvim-tree/nvim-tree.lua/commit/02ae52357ba4da77a4c120390791584a81d15340))

View File

@@ -2,23 +2,119 @@
Thank you for contributing. Thank you for contributing.
## Styling and formatting See [Development](https://github.com/nvim-tree/nvim-tree.lua/wiki/Development) for environment setup, tips and tools.
Code is formatted using luacheck, and linted using stylua. # Tools
You can install these with:
```bash Following are used during CI and strongly recommended during local development.
luarocks install luacheck
cargo install stylua Lint: [luacheck](https://github.com/lunarmodules/luacheck/)
Style: [StyLua](https://github.com/JohnnyMorganz/StyLua)
Language server: [luals](https://luals.github.io)
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
``` ```
## Adding new actions ## style
1. Runs stylua using `.stylua.toml` settings
1. Runs `scripts/doc-comments.sh` to validate annotated documentation
```sh
make style
```
You can automatically fix stylua 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.
## 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 `update-default-opts.sh` script which will update the default documentation in the README and the help file.
Documentation for options should also be added, see how this is done after `nvim-tree.disable_netrw` in the `nvim-tree-lua.txt` file. Documentation for options should also be added to `nvim-tree-opts` in `doc/nvim-tree-lua.txt`
## API
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.
## 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)

47
Makefile Normal file
View File

@@ -0,0 +1,47 @@
all: lint style check
#
# mandatory checks
#
lint: luacheck
style: stylua style-doc
check: luals
#
# subtasks
#
luacheck:
luacheck -q lua
stylua:
stylua lua --check
style-doc:
scripts/doc-comments.sh
luals:
@scripts/luals-check.sh
#
# fixes
#
style-fix:
stylua 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 stylua style-doc luals style-fix help-update help-check

484
README.md
View File

@@ -1,392 +1,186 @@
# A File Explorer For Neovim Written In Lua # A File Explorer For Neovim Written In Lua
[![CI](https://github.com/kyazdani42/nvim-tree.lua/actions/workflows/ci.yml/badge.svg)](https://github.com/kyazdani42/nvim-tree.lua/actions/workflows/ci.yml) [![CI](https://github.com/nvim-tree/nvim-tree.lua/actions/workflows/ci.yml/badge.svg)](https://github.com/nvim-tree/nvim-tree.lua/actions/workflows/ci.yml)
## Notice <img align="left" width="199" height="598" src="https://user-images.githubusercontent.com/1505378/232662694-8dc494e0-24da-497a-8541-29344293378c.png">
<img align="left" width="199" height="598" src="https://user-images.githubusercontent.com/1505378/232662698-2f321315-c67a-486b-85d8-8c391de52392.png">
This plugin requires [neovim >=0.6.0](https://github.com/neovim/neovim/wiki/Installing-Neovim). Automatic updates
If you have issues since the recent setup migration, check out [this guide](https://github.com/kyazdani42/nvim-tree.lua/issues/674) File type icons
Git integration
Diagnostics integration: LSP and COC
(Live) filtering
Cut, copy, paste, rename, delete, create
Highly customisable
<br clear="left"/>
<br />
Take a look at the [wiki](https://github.com/nvim-tree/nvim-tree.lua/wiki) for Showcases, Tips, Recipes and more.
Questions and general support: [Discussions](https://github.com/nvim-tree/nvim-tree.lua/discussions)
## Requirements
[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"
## Install ## Install
Install with [vim-plug](https://github.com/junegunn/vim-plug): Please install via your preferred package manager. See [Installation](https://github.com/nvim-tree/nvim-tree.lua/wiki/Installation) for specific package manager instructions.
```vim `nvim-tree/nvim-tree.lua`
" requires
Plug 'kyazdani42/nvim-web-devicons' " for file icons
Plug 'kyazdani42/nvim-tree.lua'
```
Install with [packer](https://github.com/wbthomason/packer.nvim): Major or minor versions may be specified via tags: `v<MAJOR>` e.g. `v1` or `v<MAJOR>.<MINOR>` e.g. `v1.23`
`nvim-tree/nvim-web-devicons` optional, for file icons
Disabling [netrw](https://neovim.io/doc/user/pi_netrw.html) is strongly advised, see [:help nvim-tree-netrw](doc/nvim-tree-lua.txt)
## Quick Start
### Setup
Setup the plugin in your `init.lua`
```lua ```lua
use { -- disable netrw at the very start of your init.lua
'kyazdani42/nvim-tree.lua', vim.g.loaded_netrw = 1
requires = { vim.g.loaded_netrwPlugin = 1
'kyazdani42/nvim-web-devicons', -- optional, for file icon
-- optionally enable 24-bit colour
vim.opt.termguicolors = true
-- empty setup using defaults
require("nvim-tree").setup()
-- OR setup with some options
require("nvim-tree").setup({
sort = {
sorter = "case_sensitive",
}, },
tag = 'nightly' -- optional, updated every week. (see issue #1193)
}
```
## Setup
Options are currently being migrated into the setup function, you need to run `require'nvim-tree'.setup()` in your personal configurations.
Setup should be run in a lua file or in a lua heredoc (`:help lua-heredoc`) if using in a vim file.
Note that options under the `g:` command should be set **BEFORE** running the setup function.
These are being migrated to the setup function incrementally, check [this issue](https://github.com/kyazdani42/nvim-tree.lua/issues/674) if you encounter any problems related to configs not working after update.
```vim
" vimrc
let g:nvim_tree_git_hl = 1 "0 by default, will enable file highlight for git attributes (can be used without the icons).
let g:nvim_tree_highlight_opened_files = 1 "0 by default, will enable folder and file icon highlight for opened files/directories.
let g:nvim_tree_root_folder_modifier = ':~' "This is the default. See :help filename-modifiers for more options
let g:nvim_tree_add_trailing = 1 "0 by default, append a trailing slash to folder names
let g:nvim_tree_group_empty = 1 " 0 by default, compact folders that only contain a single folder into one node in the file tree
let g:nvim_tree_icon_padding = ' ' "one space by default, used for rendering the space between the icon and the filename. Use with caution, it could break rendering if you set an empty string depending on your font.
let g:nvim_tree_symlink_arrow = ' >> ' " defaults to ' ➛ '. used as a separator between symlinks' source and target.
let g:nvim_tree_respect_buf_cwd = 1 "0 by default, will change cwd of nvim-tree to that of new buffer's when opening nvim-tree.
let g:nvim_tree_create_in_closed_folder = 1 "0 by default, When creating files, sets the path of a file when cursor is on a closed folder to the parent folder when 0, and inside the folder when 1.
let g:nvim_tree_special_files = { 'README.md': 1, 'Makefile': 1, 'MAKEFILE': 1 } " List of filenames that gets highlighted with NvimTreeSpecialFile
let g:nvim_tree_show_icons = {
\ 'git': 1,
\ 'folders': 0,
\ 'files': 0,
\ 'folder_arrows': 0,
\ }
"If 0, do not show the icons for one of 'git' 'folder' and 'files'
"1 by default, notice that if 'files' is 1, it will only display
"if nvim-web-devicons is installed and on your runtimepath.
"if folder is 1, you can also tell folder_arrows 1 to show small arrows next to the folder icons.
"but this will not work when you set renderer.indent_markers.enable (because of UI conflict)
" default will show icon by default if no icon is provided
" default shows no icon by default
let g:nvim_tree_icons = {
\ 'default': "",
\ 'symlink': "",
\ 'git': {
\ 'unstaged': "✗",
\ 'staged': "✓",
\ 'unmerged': "",
\ 'renamed': "➜",
\ 'untracked': "★",
\ 'deleted': "",
\ 'ignored': "◌"
\ },
\ 'folder': {
\ 'arrow_open': "",
\ 'arrow_closed': "",
\ 'default': "",
\ 'open': "",
\ 'empty': "",
\ 'empty_open': "",
\ 'symlink': "",
\ 'symlink_open': "",
\ }
\ }
nnoremap <C-n> :NvimTreeToggle<CR>
nnoremap <leader>r :NvimTreeRefresh<CR>
nnoremap <leader>n :NvimTreeFindFile<CR>
" More available functions:
" NvimTreeOpen
" NvimTreeClose
" NvimTreeFocus
" NvimTreeFindFileToggle
" NvimTreeResize
" NvimTreeCollapse
" NvimTreeCollapseKeepBuffers
set termguicolors " this variable must be enabled for colors to be applied properly
" a list of groups can be found at `:help nvim_tree_highlight`
highlight NvimTreeFolderIcon guibg=blue
```
```lua
-- init.lua
-- empty setup using defaults: add your own options
require'nvim-tree'.setup {
}
-- OR
-- setup with all defaults
-- each of these are documented in `:help nvim-tree.OPTION_NAME`
-- nested options are documented by accessing them with `.` (eg: `:help nvim-tree.view.mappings.list`).
require'nvim-tree'.setup { -- BEGIN_DEFAULT_OPTS
auto_reload_on_write = true,
disable_netrw = false,
hijack_cursor = false,
hijack_netrw = true,
hijack_unnamed_buffer_when_opening = false,
ignore_buffer_on_setup = false,
open_on_setup = false,
open_on_setup_file = false,
open_on_tab = false,
sort_by = "name",
update_cwd = false,
view = { view = {
width = 30, width = 30,
height = 30,
hide_root_folder = false,
side = "left",
preserve_window_proportions = false,
number = false,
relativenumber = false,
signcolumn = "yes",
mappings = {
custom_only = false,
list = {
-- user mappings go here
},
},
}, },
renderer = { renderer = {
indent_markers = { group_empty = true,
enable = false,
icons = {
corner = "",
edge = "",
none = " ",
},
},
icons = {
webdev_colors = true,
git_placement = "before",
}
},
hijack_directories = {
enable = true,
auto_open = true,
},
update_focused_file = {
enable = false,
update_cwd = false,
ignore_list = {},
},
ignore_ft_on_setup = {},
system_open = {
cmd = "",
args = {},
},
diagnostics = {
enable = false,
show_on_dirs = false,
icons = {
hint = "",
info = "",
warning = "",
error = "",
},
}, },
filters = { filters = {
dotfiles = false, dotfiles = true,
custom = {},
exclude = {},
}, },
git = { })
enable = true,
ignore = true,
timeout = 400,
},
actions = {
use_system_clipboard = true,
change_dir = {
enable = true,
global = false,
restrict_above_cwd = false,
},
open_file = {
quit_on_open = false,
resize_window = false,
window_picker = {
enable = true,
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
exclude = {
filetype = { "notify", "packer", "qf", "diff", "fugitive", "fugitiveblame" },
buftype = { "nofile", "terminal", "help" },
},
},
},
},
trash = {
cmd = "trash",
require_confirm = true,
},
log = {
enable = false,
truncate = false,
types = {
all = false,
config = false,
copy_paste = false,
diagnostics = false,
git = false,
profile = false,
},
},
} -- END_DEFAULT_OPTS
``` ```
## Key Bindings ### Help
### Default actions Open the tree: `:NvimTreeOpen`
- `<CR>` or `o` on the root folder will cd in the above directory Show the mappings: `g?`
- `<C-]>` will cd in the directory under the cursor
- `<BS>` will close current opened directory or parent
- type `a` to add a file. Adding a directory requires leaving a leading `/` at the end of the path.
> you can add multiple directories by doing foo/bar/baz/f and it will add foo bar and baz directories and f as a file
- type `r` to rename a file
- type `<C-r>` to rename a file and omit the filename on input
- type `x` to add/remove file/directory to cut clipboard
- type `c` to add/remove file/directory to copy clipboard
- type `y` will copy name to system clipboard
- type `Y` will copy relative path to system clipboard
- type `gy` will copy absolute path to system clipboard
- type `p` to paste from clipboard. Cut clipboard has precedence over copy (will prompt for confirmation)
- type `d` to delete a file (will prompt for confirmation)
- type `D` to trash a file (configured in setup())
- type `]c` to go to next git item
- type `[c` to go to prev git item
- type `-` to navigate up to the parent directory of the current file/directory
- type `s` to open a file with default system application or a folder with default file manager (if you want to change the command used to do it see `:h nvim-tree.setup` under `system_open`)
- if the file is a directory, `<CR>` will open the directory otherwise it will open the file in the buffer near the tree
- if the file is a symlink, `<CR>` will follow the symlink (if the target is a file)
- `<C-v>` will open the file in a vertical split
- `<C-x>` will open the file in a horizontal split
- `<C-t>` will open the file in a new tab
- `<Tab>` will open the file as a preview (keeps the cursor in the tree)
- `I` will toggle visibility of hidden folders / files
- `H` will toggle visibility of dotfiles (files/folders starting with a `.`)
- `R` will refresh the tree
- Double left click acts like `<CR>`
- Double right click acts like `<C-]>`
- `W` will collapse the whole tree
- `S` will prompt the user to enter a path and then expands the tree to match the path
- `.` will enter vim command mode with the file the cursor is on
- `C-k` will toggle a popup with file infos about the file under the cursor
### Settings ### Custom Mappings
[:help nvim-tree-mappings-default](doc/nvim-tree-lua.txt) are applied by default however you may customise via |nvim-tree.on_attach| e.g.
The `list` option in `view.mappings.list` is a table of
```lua ```lua
-- key can be either a string or a table of string (lhs) local function my_on_attach(bufnr)
-- action is the name of the action, set to `""` to remove default action local api = require "nvim-tree.api"
-- action_cb is the function that will be called, it receives the node as a parameter. Optional for default actions
-- mode is normal by default
local tree_cb = require'nvim-tree.config'.nvim_tree_callback local function opts(desc)
return { desc = "nvim-tree: " .. desc, buffer = bufnr, noremap = true, silent = true, nowait = true }
local function print_node_path(node) { end
print(node.absolute_path)
}
local list = {
{ key = {"<CR>", "o" }, action = "edit", mode = "n"},
{ key = "p", action = "print_path", action_cb = print_node_path },
{ key = "s", cb = tree_cb("vsplit") }, --tree_cb and the cb property are deprecated
{ key = "<2-RightMouse>", action = "" }, -- will remove default cd action
}
```
These are the default bindings:
```lua
-- default mappings -- default mappings
local list = { api.config.mappings.default_on_attach(bufnr)
{ key = {"<CR>", "o", "<2-LeftMouse>"}, action = "edit" },
{ key = "<C-e>", action = "edit_in_place" }, -- custom mappings
{ key = {"O"}, action = "edit_no_picker" }, vim.keymap.set('n', '<C-t>', api.tree.change_root_to_parent, opts('Up'))
{ key = {"<2-RightMouse>", "<C-]>"}, action = "cd" }, vim.keymap.set('n', '?', api.tree.toggle_help, opts('Help'))
{ key = "<C-v>", action = "vsplit" }, end
{ key = "<C-x>", action = "split" },
{ key = "<C-t>", action = "tabnew" }, -- pass to setup along with your other options
{ key = "<", action = "prev_sibling" }, require("nvim-tree").setup {
{ key = ">", action = "next_sibling" }, ---
{ key = "P", action = "parent_node" }, on_attach = my_on_attach,
{ key = "<BS>", action = "close_node" }, ---
{ key = "<Tab>", action = "preview" },
{ key = "K", action = "first_sibling" },
{ key = "J", action = "last_sibling" },
{ key = "I", action = "toggle_git_ignored" },
{ key = "H", action = "toggle_dotfiles" },
{ key = "R", action = "refresh" },
{ key = "a", action = "create" },
{ key = "d", action = "remove" },
{ key = "D", action = "trash" },
{ key = "r", action = "rename" },
{ key = "<C-r>", action = "full_rename" },
{ key = "x", action = "cut" },
{ key = "c", action = "copy" },
{ key = "p", action = "paste" },
{ key = "y", action = "copy_name" },
{ key = "Y", action = "copy_path" },
{ key = "gy", action = "copy_absolute_path" },
{ key = "[c", action = "prev_git_item" },
{ key = "]c", action = "next_git_item" },
{ key = "-", action = "dir_up" },
{ key = "s", action = "system_open" },
{ key = "q", action = "close" },
{ key = "g?", action = "toggle_help" },
{ key = "W", action = "collapse_all" },
{ key = "S", action = "search_node" },
{ key = "<C-k>", action = "toggle_file_info" },
{ key = ".", action = "run_file_command" }
} }
``` ```
You can toggle the help UI by pressing `g?`. ### Highlight
## Tips & reminders Run `:NvimTreeHiTest` to show all the highlights that nvim-tree uses.
1. You can add a directory by adding a `/` at the end of the paths, entering multiple directories `BASE/foo/bar/baz` will add directory foo, then bar and add a file baz to it. They can be customised before or after setup is called and will be immediately
2. You can update window options for the tree by setting `require"nvim-tree.view".View.winopts.MY_OPTION = MY_OPTION_VALUE` applied at runtime. e.g.
3. `toggle` has a second parameter which allows to toggle without focusing the explorer (`require"nvim-tree".toggle(false, true)`).
4. You can allow nvim-tree to behave like vinegar (see `:help nvim-tree-vinegar`).
5. If you `:set nosplitright`, the files will open on the left side of the tree, placing the tree window in the right side of the file you opened.
6. You can automatically close the tab/vim when nvim-tree is the last window in the tab. WARNING: other plugins or automation may interfere with this:
```vim
autocmd BufEnter * ++nested if winnr('$') == 1 && bufname() == 'NvimTree_' . tabpagenr() | quit | endif
```
## Diagnostic Logging
You may enable diagnostic logging to `$XDG_CACHE_HOME/nvim/nvim-tree.log`. See `:help nvim-tree.log`.
## Performance Issues
If you are experiencing performance issues with nvim-tree.lua, you can enable profiling in the logs. It is advisable to enable git logging at the same time, as that can be a source of performance problems.
```lua ```lua
log = { vim.cmd([[
enable = true, :hi NvimTreeExecFile guifg=#ffa0a0
truncate = true, :hi NvimTreeSpecialFile guifg=#ff80ff gui=underline
types = { :hi NvimTreeSymlink guifg=Yellow gui=italic
git = true, :hi link NvimTreeImageFile Title
profile = true, ]])
},
},
``` ```
See [:help nvim-tree-highlight](doc/nvim-tree-lua.txt) for details.
Please attach `$XDG_CACHE_HOME/nvim/nvim-tree.log` if you raise an issue. ## Commands
*Performance Tips:* See [:help nvim-tree-commands](doc/nvim-tree-lua.txt)
* If you are using fish as an editor shell (which might be fixed in the future), try set `shell=/bin/bash` in your vim config. Basic commands:
* Try manually running the git command (see the logs) in your shell e.g. `git --no-optional-locks status --porcelain=v1 --ignored=matching -u`. `:NvimTreeToggle` Open or close the tree. Takes an optional path argument.
* Huge git repositories may timeout after the default `git.timeout` of 400ms. Try increasing that in your setup if you see `[git] job timed out` in the logs. `:NvimTreeFocus` Open the tree if it is closed, and then focus on the tree.
* Try temporarily disabling git integration by setting `git.enable = false` in your setup. `:NvimTreeFindFile` Move the cursor in the tree for the current buffer, opening folders if needed.
`:NvimTreeCollapse` Collapses the nvim-tree recursively.
## Roadmap
nvim-tree is stable and new major features will not be added. The focus is on existing user experience.
Users are encouraged to add their own custom features via the public [API](#api).
Development is focused on:
* Bug fixes
* Performance
* Quality of Life improvements
* API / Events
* Enhancements to existing features
## API
nvim-tree exposes a public API. This is non breaking, with additions made as necessary. See [:help nvim-tree-api](doc/nvim-tree-lua.txt)
See wiki [Recipes](https://github.com/nvim-tree/nvim-tree.lua/wiki/Recipes) and [Tips](https://github.com/nvim-tree/nvim-tree.lua/wiki/Tips) for ideas and inspiration.
Please raise a [feature request](https://github.com/nvim-tree/nvim-tree.lua/issues/new?assignees=&labels=feature+request&template=feature_request.md&title=) if the API is insufficient for your needs. [Contributions](#Contributing) are always welcome.
You may also subscribe to events that nvim-tree will dispatch in a variety of situations, see [:help nvim-tree-events](doc/nvim-tree-lua.txt)
## Contributing
PRs are always welcome. See [wiki](https://github.com/nvim-tree/nvim-tree.lua/wiki/Development) to get started.
See [bug](https://github.com/nvim-tree/nvim-tree.lua/issues?q=is%3Aissue+is%3Aopen+label%3Abug) and [PR Please](https://github.com/nvim-tree/nvim-tree.lua/issues?q=is%3Aopen+is%3Aissue+label%3A%22PR+please%22) issues if you are looking for some work to get you started.
## Screenshots ## Screenshots
![alt text](.github/screenshot.png?raw=true "kyazdani42 tree") See [Showcases](https://github.com/nvim-tree/nvim-tree.lua/wiki/Showcases) wiki page for examples of user's configurations with sources.
![alt text](.github/screenshot2.png?raw=true "akin909 tree")
![alt text](.github/screenshot3.png?raw=true "stsewd tree") Please add your own!
![alt text](.github/screenshot4.png?raw=true "reyhankaplan tree")
## Team
* [@alex-courtis](https://github.com/alex-courtis) Arch Linux
* [@gegoune](https://github.com/gegoune) macOS
* [@Akmadan23](https://github.com/Akmadan23) Linux
* [@dependabot[bot]](https://github.com/apps/dependabot) Ubuntu Linux

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,52 +0,0 @@
local a = vim.api
local log = require "nvim-tree.log"
local utils = require "nvim-tree.utils"
local core = require "nvim-tree.core"
local M = {
current_tab = a.nvim_get_current_tabpage(),
}
function M.fn(name, with_open)
if not core.get_explorer() then
return
end
local foldername = name == ".." and vim.fn.fnamemodify(utils.path_remove_trailing(core.get_cwd()), ":h") or name
local no_cwd_change = vim.fn.expand(foldername) == core.get_cwd()
or M.options.restrict_above_cwd and foldername < vim.fn.getcwd(-1, -1)
local new_tab = a.nvim_get_current_tabpage()
local is_window = (vim.v.event.scope == "window" or vim.v.event.changed_window) and new_tab == M.current_tab
if no_cwd_change or is_window then
return
end
M.current_tab = new_tab
M.force_dirchange(foldername, with_open)
end
function M.force_dirchange(foldername, with_open)
local ps = log.profile_start("change dir %s", foldername)
if M.options.enable and vim.tbl_isempty(vim.v.event) then
if M.options.global then
vim.cmd("cd " .. vim.fn.fnameescape(foldername))
else
vim.cmd("lcd " .. vim.fn.fnameescape(foldername))
end
end
core.init(foldername)
if with_open then
require("nvim-tree.lib").open()
else
require("nvim-tree.renderer").draw()
end
log.profile_end(ps, "change dir %s", foldername)
end
function M.setup(options)
M.options = options.actions.change_dir
end
return M

View File

@@ -1,43 +0,0 @@
local renderer = require "nvim-tree.renderer"
local utils = require "nvim-tree.utils"
local core = require "nvim-tree.core"
local M = {}
function M.fn(keep_buffers)
if not core.get_explorer() then
return
end
local buffer_paths = {}
for _, buffer in ipairs(vim.api.nvim_list_bufs()) do
table.insert(buffer_paths, vim.api.nvim_buf_get_name(buffer))
end
local function iter(nodes)
for _, node in pairs(nodes) do
if node.open then
local new_open = false
if keep_buffers == true then
for _, buffer_path in ipairs(buffer_paths) do
local matches = utils.str_find(buffer_path, node.absolute_path)
if matches then
new_open = true
end
end
end
node.open = new_open
end
if node.nodes then
iter(node.nodes)
end
end
end
iter(core.get_explorer().nodes)
renderer.draw()
end
return M

View File

@@ -1,247 +0,0 @@
local a = vim.api
local uv = vim.loop
local lib = require "nvim-tree.lib"
local log = require "nvim-tree.log"
local utils = require "nvim-tree.utils"
local core = require "nvim-tree.core"
local M = {}
local clipboard = {
move = {},
copy = {},
}
local function do_copy(source, destination)
local source_stats, handle
local success, errmsg
source_stats, errmsg = uv.fs_stat(source)
if not source_stats then
log.line("copy_paste", "do_copy fs_stat '%s' failed '%s'", source, errmsg)
return false, errmsg
end
log.line("copy_paste", "do_copy %s '%s' -> '%s'", source_stats.type, source, destination)
if source == destination then
log.line("copy_paste", "do_copy source and destination are the same, exiting early")
return true
end
if source_stats.type == "file" then
success, errmsg = uv.fs_copyfile(source, destination)
if not success then
log.line("copy_paste", "do_copy fs_copyfile failed '%s'", errmsg)
return false, errmsg
end
return true
elseif source_stats.type == "directory" then
handle, errmsg = uv.fs_scandir(source)
if type(handle) == "string" then
return false, handle
elseif not handle then
log.line("copy_paste", "do_copy fs_scandir '%s' failed '%s'", source, errmsg)
return false, errmsg
end
success, errmsg = uv.fs_mkdir(destination, source_stats.mode)
if not success then
log.line("copy_paste", "do_copy fs_mkdir '%s' failed '%s'", destination, errmsg)
return false, errmsg
end
while true do
local name, _ = uv.fs_scandir_next(handle)
if not name then
break
end
local new_name = utils.path_join { source, name }
local new_destination = utils.path_join { destination, name }
success, errmsg = do_copy(new_name, new_destination)
if not success then
return false, errmsg
end
end
else
errmsg = string.format("'%s' illegal file type '%s'", source, source_stats.type)
log.line("copy_paste", "do_copy %s", errmsg)
return false, errmsg
end
return true
end
local function do_single_paste(source, dest, action_type, action_fn)
local dest_stats
local success, errmsg, errcode
log.line("copy_paste", "do_single_paste '%s' -> '%s'", source, dest)
dest_stats, errmsg, errcode = uv.fs_stat(dest)
if not dest_stats and errcode ~= "ENOENT" then
a.nvim_err_writeln("Could not " .. action_type .. " " .. source .. " - " .. (errmsg or "???"))
return false, errmsg
end
local should_process = true
local should_rename = false
if dest_stats then
print(dest .. " already exists. Overwrite? y/n/r(ename)")
local ans = utils.get_user_input_char()
utils.clear_prompt()
should_process = ans:match "^y"
should_rename = ans:match "^r"
end
if should_rename then
local new_dest = vim.fn.input("New name: ", dest)
return do_single_paste(source, new_dest, action_type, action_fn)
end
if should_process then
success, errmsg = action_fn(source, dest)
if not success then
a.nvim_err_writeln("Could not " .. action_type .. " " .. source .. " - " .. (errmsg or "???"))
return false, errmsg
end
end
end
local function add_to_clipboard(node, clip)
if node.name == ".." then
return
end
for idx, _node in ipairs(clip) do
if _node.absolute_path == node.absolute_path then
table.remove(clip, idx)
return a.nvim_out_write(node.absolute_path .. " removed to clipboard.\n")
end
end
table.insert(clip, node)
a.nvim_out_write(node.absolute_path .. " added to clipboard.\n")
end
function M.copy(node)
add_to_clipboard(node, clipboard.copy)
end
function M.cut(node)
add_to_clipboard(node, clipboard.move)
end
local function do_paste(node, action_type, action_fn)
node = lib.get_last_group_node(node)
if node.name == ".." then
return
end
local clip = clipboard[action_type]
if #clip == 0 then
return
end
local destination = node.absolute_path
local stats, errmsg, errcode = uv.fs_stat(destination)
if not stats and errcode ~= "ENOENT" then
log.line("copy_paste", "do_paste fs_stat '%s' failed '%s'", destination, errmsg)
a.nvim_err_writeln("Could not " .. action_type .. " " .. destination .. " - " .. (errmsg or "???"))
return
end
local is_dir = stats and stats.type == "directory"
if not is_dir then
destination = vim.fn.fnamemodify(destination, ":p:h")
elseif not node.open then
destination = vim.fn.fnamemodify(destination, ":p:h:h")
end
for _, _node in ipairs(clip) do
local dest = utils.path_join { destination, _node.name }
do_single_paste(_node.absolute_path, dest, action_type, action_fn)
end
clipboard[action_type] = {}
return require("nvim-tree.actions.reloaders").reload_explorer()
end
local function do_cut(source, destination)
log.line("copy_paste", "do_cut '%s' -> '%s'", source, destination)
if source == destination then
log.line("copy_paste", "do_cut source and destination are the same, exiting early")
return true
end
local success, errmsg = uv.fs_rename(source, destination)
if not success then
log.line("copy_paste", "do_cut fs_rename failed '%s'", errmsg)
return false, errmsg
end
utils.rename_loaded_buffers(source, destination)
return true
end
function M.paste(node)
if clipboard.move[1] ~= nil then
return do_paste(node, "move", do_cut)
end
return do_paste(node, "copy", do_copy)
end
function M.print_clipboard()
local content = {}
if #clipboard.move > 0 then
table.insert(content, "Cut")
for _, item in pairs(clipboard.move) do
table.insert(content, " * " .. item.absolute_path)
end
end
if #clipboard.copy > 0 then
table.insert(content, "Copy")
for _, item in pairs(clipboard.copy) do
table.insert(content, " * " .. item.absolute_path)
end
end
return a.nvim_out_write(table.concat(content, "\n") .. "\n")
end
local function copy_to_clipboard(content)
if M.use_system_clipboard == true then
vim.fn.setreg("+", content)
vim.fn.setreg('"', content)
return a.nvim_out_write(string.format("Copied %s to system clipboard! \n", content))
else
vim.fn.setreg('"', content)
vim.fn.setreg("1", content)
return a.nvim_out_write(string.format("Copied %s to neovim clipboard \n", content))
end
end
function M.copy_filename(node)
return copy_to_clipboard(node.name)
end
function M.copy_path(node)
local absolute_path = node.absolute_path
local relative_path = utils.path_relative(absolute_path, core.get_cwd())
local content = node.nodes ~= nil and utils.path_add_trailing(relative_path) or relative_path
return copy_to_clipboard(content)
end
function M.copy_absolute_path(node)
local absolute_path = node.absolute_path
local content = node.nodes ~= nil and utils.path_add_trailing(absolute_path) or absolute_path
return copy_to_clipboard(content)
end
function M.setup(opts)
M.use_system_clipboard = opts.actions.use_system_clipboard
end
return M

View File

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

View File

@@ -1,77 +0,0 @@
local log = require "nvim-tree.log"
local uv = vim.loop
local view = require "nvim-tree.view"
local utils = require "nvim-tree.utils"
local renderer = require "nvim-tree.renderer"
local core = require "nvim-tree.core"
local M = {}
local running = {}
---Find a path in the tree, expand it and focus it
---@param fname string full path
function M.fn(fname)
if running[fname] or not core.get_explorer() then
return
end
running[fname] = true
local ps = log.profile_start("find file %s", fname)
-- always match against the real path
local fname_real = uv.fs_realpath(fname)
if not fname_real then
return
end
local i = core.get_nodes_starting_line() - 1
local tree_altered = false
local function iterate_nodes(nodes)
for _, node in ipairs(nodes) do
i = i + 1
if not node.absolute_path or not uv.fs_stat(node.absolute_path) then
break
end
-- match against node absolute and link, as symlinks themselves will differ
if node.absolute_path == fname_real or node.link_to == fname_real then
return i
end
local abs_match = vim.startswith(fname_real, node.absolute_path .. utils.path_separator)
local link_match = node.link_to and vim.startswith(fname_real, node.link_to .. utils.path_separator)
local path_matches = node.nodes and (abs_match or link_match)
if path_matches then
if not node.open then
node.open = true
tree_altered = true
end
if #node.nodes == 0 then
core.get_explorer():expand(node)
end
if iterate_nodes(node.nodes) ~= nil then
return i
end
-- mandatory to iterate i
elseif node.open then
iterate_nodes(node.nodes)
end
end
end
local index = iterate_nodes(core.get_explorer().nodes)
if tree_altered then
renderer.draw()
end
if index and view.is_visible() then
view.set_cursor { index, 0 }
end
running[fname] = false
log.profile_end(ps, "find file %s", fname)
end
return M

View File

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

View File

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

View File

@@ -0,0 +1,116 @@
local core = require "nvim-tree.core"
local find_file = require("nvim-tree.actions.finders.find-file").fn
local M = {}
---@param search_dir string|nil
---@param input_path string
---@return string|nil
local function search(search_dir, input_path)
local realpaths_searched = {}
local explorer = core.get_explorer()
if not explorer then
return
end
if not search_dir then
return
end
---@param dir string
---@return string|nil
local function iter(dir)
local realpath, path, name, stat, handle, _
local filter_status = explorer.filters:prepare()
handle, _ = vim.loop.fs_scandir(dir)
if not handle then
return
end
realpath, _ = vim.loop.fs_realpath(dir)
if not realpath or vim.tbl_contains(realpaths_searched, realpath) then
return
end
table.insert(realpaths_searched, realpath)
name, _ = vim.loop.fs_scandir_next(handle)
while name do
path = dir .. "/" .. name
---@type uv.fs_stat.result|nil
stat, _ = vim.loop.fs_stat(path)
if not stat then
break
end
if not explorer.filters:should_filter(path, stat, filter_status) then
if string.find(path, "/" .. input_path .. "$") then
return path
end
if stat.type == "directory" then
path = iter(path)
if path then
return path
end
end
end
name, _ = vim.loop.fs_scandir_next(handle)
end
end
return iter(search_dir)
end
function M.fn()
if not core.get_explorer() then
return
end
-- temporarily set &path
local bufnr = vim.api.nvim_get_current_buf()
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)
if not input_path or input_path == "" then
return
end
-- reset &path
if path_existed then
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
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
-- strip trailing slash
input_path = string.gsub(input_path, "/$", "")
-- search under cwd
local found = search(core.get_cwd(), input_path)
if found then
find_file(found)
end
end)
end
return M

View File

@@ -0,0 +1,350 @@
local lib = require "nvim-tree.lib"
local log = require "nvim-tree.log"
local utils = require "nvim-tree.utils"
local core = require "nvim-tree.core"
local events = require "nvim-tree.events"
local notify = require "nvim-tree.notify"
local renderer = require "nvim-tree.renderer"
local reloaders = require "nvim-tree.actions.reloaders"
local find_file = require("nvim-tree.actions.finders.find-file").fn
local M = {
config = {},
}
local clipboard = {
cut = {},
copy = {},
}
---@param source string
---@param destination string
---@return boolean
---@return string|nil
local function do_copy(source, destination)
local source_stats, handle
local success, errmsg
source_stats, errmsg = vim.loop.fs_stat(source)
if not source_stats then
log.line("copy_paste", "do_copy fs_stat '%s' failed '%s'", source, errmsg)
return false, errmsg
end
log.line("copy_paste", "do_copy %s '%s' -> '%s'", source_stats.type, source, destination)
if source == destination then
log.line("copy_paste", "do_copy source and destination are the same, exiting early")
return true
end
if source_stats.type == "file" then
success, errmsg = vim.loop.fs_copyfile(source, destination)
if not success then
log.line("copy_paste", "do_copy fs_copyfile failed '%s'", errmsg)
return false, errmsg
end
return true
elseif source_stats.type == "directory" then
handle, errmsg = vim.loop.fs_scandir(source)
if type(handle) == "string" then
return false, handle
elseif not handle then
log.line("copy_paste", "do_copy fs_scandir '%s' failed '%s'", source, errmsg)
return false, errmsg
end
success, errmsg = vim.loop.fs_mkdir(destination, source_stats.mode)
if not success then
log.line("copy_paste", "do_copy fs_mkdir '%s' failed '%s'", destination, errmsg)
return false, errmsg
end
while true do
local name, _ = vim.loop.fs_scandir_next(handle)
if not name then
break
end
local new_name = utils.path_join { source, name }
local new_destination = utils.path_join { destination, name }
success, errmsg = do_copy(new_name, new_destination)
if not success then
return false, errmsg
end
end
else
errmsg = string.format("'%s' illegal file type '%s'", source, source_stats.type)
log.line("copy_paste", "do_copy %s", errmsg)
return false, errmsg
end
return true
end
---@param source string
---@param dest string
---@param action_type string
---@param action_fn fun(source: string, dest: string)
---@return boolean|nil -- success
---@return string|nil -- error message
local function do_single_paste(source, dest, action_type, action_fn)
local dest_stats
local success, errmsg, errcode
local notify_source = notify.render_path(source)
log.line("copy_paste", "do_single_paste '%s' -> '%s'", source, dest)
dest_stats, errmsg, errcode = vim.loop.fs_stat(dest)
if not dest_stats and errcode ~= "ENOENT" then
notify.error("Could not " .. action_type .. " " .. notify_source .. " - " .. (errmsg or "???"))
return false, errmsg
end
local function on_process()
success, errmsg = action_fn(source, dest)
if not success then
notify.error("Could not " .. action_type .. " " .. notify_source .. " - " .. (errmsg or "???"))
return false, errmsg
end
find_file(utils.path_remove_trailing(dest))
end
if dest_stats then
local input_opts = {
prompt = "Rename to ",
default = dest,
completion = "dir",
}
if source == dest then
vim.ui.input(input_opts, function(new_dest)
utils.clear_prompt()
if new_dest then
do_single_paste(source, new_dest, action_type, action_fn)
end
end)
else
local prompt_select = "Overwrite " .. dest .. " ?"
local prompt_input = prompt_select .. " R(ename)/y/n: "
lib.prompt(prompt_input, prompt_select, { "", "y", "n" }, { "Rename", "Yes", "No" }, "nvimtree_overwrite_rename", function(item_short)
utils.clear_prompt()
if item_short == "y" then
on_process()
elseif item_short == "" or item_short == "r" then
vim.ui.input(input_opts, function(new_dest)
utils.clear_prompt()
if new_dest then
do_single_paste(source, new_dest, action_type, action_fn)
end
end)
end
end)
end
else
on_process()
end
end
---@param node Node
---@param clip table
local function toggle(node, clip)
if node.name == ".." then
return
end
local notify_node = notify.render_path(node.absolute_path)
if utils.array_remove(clip, node) then
notify.info(notify_node .. " removed from clipboard.")
return
end
table.insert(clip, node)
notify.info(notify_node .. " added to clipboard.")
end
function M.clear_clipboard()
clipboard.cut = {}
clipboard.copy = {}
notify.info "Clipboard has been emptied."
renderer.draw()
end
---@param node Node
function M.copy(node)
utils.array_remove(clipboard.cut, node)
toggle(node, clipboard.copy)
renderer.draw()
end
---@param node Node
function M.cut(node)
utils.array_remove(clipboard.copy, node)
toggle(node, clipboard.cut)
renderer.draw()
end
---@param node Node
---@param action_type string
---@param action_fn fun(source: string, dest: string)
local function do_paste(node, action_type, action_fn)
node = lib.get_last_group_node(node)
local explorer = core.get_explorer()
if node.name == ".." and explorer then
node = explorer
end
local clip = clipboard[action_type]
if #clip == 0 then
return
end
local destination = node.absolute_path
local stats, errmsg, errcode = vim.loop.fs_stat(destination)
if not stats and errcode ~= "ENOENT" then
log.line("copy_paste", "do_paste fs_stat '%s' failed '%s'", destination, errmsg)
notify.error("Could not " .. action_type .. " " .. notify.render_path(destination) .. " - " .. (errmsg or "???"))
return
end
local is_dir = stats and stats.type == "directory"
if not is_dir then
destination = vim.fn.fnamemodify(destination, ":p:h")
end
for _, _node in ipairs(clip) do
local dest = utils.path_join { destination, _node.name }
do_single_paste(_node.absolute_path, dest, action_type, action_fn)
end
clipboard[action_type] = {}
if not M.config.filesystem_watchers.enable then
reloaders.reload_explorer()
end
end
---@param source string
---@param destination string
---@return boolean
---@return string?
local function do_cut(source, destination)
log.line("copy_paste", "do_cut '%s' -> '%s'", source, destination)
if source == destination then
log.line("copy_paste", "do_cut source and destination are the same, exiting early")
return true
end
events._dispatch_will_rename_node(source, destination)
local success, errmsg = vim.loop.fs_rename(source, destination)
if not success then
log.line("copy_paste", "do_cut fs_rename failed '%s'", errmsg)
return false, errmsg
end
utils.rename_loaded_buffers(source, destination)
events._dispatch_node_renamed(source, destination)
return true
end
---@param node Node
function M.paste(node)
if clipboard.cut[1] ~= nil then
do_paste(node, "cut", do_cut)
else
do_paste(node, "copy", do_copy)
end
end
function M.print_clipboard()
local content = {}
if #clipboard.cut > 0 then
table.insert(content, "Cut")
for _, node in pairs(clipboard.cut) do
table.insert(content, " * " .. (notify.render_path(node.absolute_path)))
end
end
if #clipboard.copy > 0 then
table.insert(content, "Copy")
for _, node in pairs(clipboard.copy) do
table.insert(content, " * " .. (notify.render_path(node.absolute_path)))
end
end
notify.info(table.concat(content, "\n") .. "\n")
end
---@param content string
local function copy_to_clipboard(content)
local clipboard_name
local reg
if M.config.actions.use_system_clipboard == true then
clipboard_name = "system"
reg = "+"
else
clipboard_name = "neovim"
reg = "1"
end
-- 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))
end
---@param node Node
function M.copy_filename(node)
copy_to_clipboard(node.name)
end
---@param node Node
function M.copy_basename(node)
local basename = vim.fn.fnamemodify(node.name, ":r")
copy_to_clipboard(basename)
end
---@param node Node
function M.copy_path(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)
local content = node.nodes ~= nil and utils.path_add_trailing(relative_path) or relative_path
copy_to_clipboard(content)
end
---@param node Node
function M.copy_absolute_path(node)
local absolute_path = node.absolute_path
local content = node.nodes ~= nil and utils.path_add_trailing(absolute_path) or absolute_path
copy_to_clipboard(content)
end
---Node is cut. Will not be copied.
---@param node Node
---@return boolean
function M.is_cut(node)
return vim.tbl_contains(clipboard.cut, node)
end
---Node is copied. Will not be cut.
---@param node Node
---@return boolean
function M.is_copied(node)
return vim.tbl_contains(clipboard.copy, node)
end
function M.setup(opts)
M.config.filesystem_watchers = opts.filesystem_watchers
M.config.actions = opts.actions
end
return M

View File

@@ -1,38 +1,27 @@
local a = vim.api
local uv = vim.loop
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 find_file = require("nvim-tree.actions.finders.find-file").fn
local M = {} local M = {}
local function focus_file(file) ---@param file string
local _, i = utils.find_node(core.get_explorer().nodes, function(node) local function create_and_notify(file)
return node.absolute_path == file events._dispatch_will_create_file(file)
end) local ok, fd = pcall(vim.loop.fs_open, file, "w", 420)
require("nvim-tree.view").set_cursor { i + 1, 1 } if not ok or type(fd) ~= "number" then
end notify.error("Couldn't create file " .. notify.render_path(file))
local function create_file(file)
if utils.file_exists(file) then
print(file .. " already exists. Overwrite? y/n")
local ans = utils.get_user_input_char()
utils.clear_prompt()
if ans ~= "y" then
return return
end end
end vim.loop.fs_close(fd)
local ok, fd = pcall(uv.fs_open, file, "w", 420)
if not ok then
a.nvim_err_writeln("Couldn't create file " .. file)
return
end
uv.fs_close(fd)
events._dispatch_file_created(file) events._dispatch_file_created(file)
end end
---@param iter function iterable
---@return integer
local function get_num_nodes(iter) local function get_num_nodes(iter)
local i = 0 local i = 0
for _ in iter do for _ in iter do
@@ -41,20 +30,28 @@ local function get_num_nodes(iter)
return i return i
end end
---@param node Node
---@return string
local function get_containing_folder(node) local function get_containing_folder(node)
local is_open = vim.g.nvim_tree_create_in_closed_folder == 1 or node.open if node.nodes ~= nil then
if node.nodes ~= nil and is_open then
return utils.path_add_trailing(node.absolute_path) return utils.path_add_trailing(node.absolute_path)
end end
local node_name_size = #(node.name or "") local node_name_size = #(node.name or "")
return node.absolute_path:sub(0, -node_name_size - 1) return node.absolute_path:sub(0, -node_name_size - 1)
end end
---@param node Node|nil
function M.fn(node) function M.fn(node)
node = lib.get_last_group_node(node) local cwd = core.get_cwd()
if node.name == ".." then if cwd == nil then
return
end
node = node and lib.get_last_group_node(node)
if not node or node.name == ".." then
node = { node = {
absolute_path = core.get_cwd(), absolute_path = cwd,
name = "",
nodes = core.get_explorer().nodes, nodes = core.get_explorer().nodes,
open = true, open = true,
} }
@@ -62,17 +59,20 @@ function M.fn(node)
local containing_folder = get_containing_folder(node) local containing_folder = get_containing_folder(node)
local input_opts = { prompt = "Create file ", default = containing_folder, completion = "file" } local input_opts = {
prompt = "Create file ",
default = containing_folder,
completion = "file",
}
vim.ui.input(input_opts, function(new_file_path) vim.ui.input(input_opts, function(new_file_path)
utils.clear_prompt()
if not new_file_path or new_file_path == containing_folder then if not new_file_path or new_file_path == containing_folder then
return return
end end
utils.clear_prompt()
if utils.file_exists(new_file_path) then if utils.file_exists(new_file_path) then
utils.warn "Cannot create: file already exists" notify.warn "Cannot create: file already exists"
return return
end end
@@ -93,22 +93,23 @@ function M.fn(node)
path_to_create = utils.path_join { path_to_create, p } path_to_create = utils.path_join { path_to_create, p }
end end
if is_last_path_file and idx == num_nodes then if is_last_path_file and idx == num_nodes then
create_file(path_to_create) create_and_notify(path_to_create)
elseif not utils.file_exists(path_to_create) then elseif not utils.file_exists(path_to_create) then
local success = uv.fs_mkdir(path_to_create, 493) local success = vim.loop.fs_mkdir(path_to_create, 493)
if not success then if not success then
a.nvim_err_writeln("Could not create folder " .. path_to_create) notify.error("Could not create folder " .. notify.render_path(path_to_create))
is_error = true is_error = true
break break
end end
events._dispatch_folder_created(new_file_path)
end end
end end
if not is_error then if not is_error then
a.nvim_out_write(new_file_path .. " was properly created\n") notify.info(notify.render_path(new_file_path) .. " was properly created")
end end
events._dispatch_folder_created(new_file_path)
require("nvim-tree.actions.reloaders").reload_explorer() -- synchronously refreshes as we can't wait for the watchers
focus_file(new_file_path) find_file(utils.path_remove_trailing(new_file_path))
end) end)
end end

View File

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

View File

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

View File

@@ -0,0 +1,170 @@
local lib = require "nvim-tree.lib"
local utils = require "nvim-tree.utils"
local events = require "nvim-tree.events"
local notify = require "nvim-tree.notify"
local find_file = require("nvim-tree.actions.finders.find-file").fn
local M = {
config = {},
}
---@param iter function iterable
---@return integer
local function get_num_nodes(iter)
local i = 0
for _ in iter do
i = i + 1
end
return i
end
local ALLOWED_MODIFIERS = {
[":p"] = true,
[":p:h"] = true,
[":t"] = true,
[":t:r"] = true,
}
local function err_fmt(from, to, reason)
return string.format("Cannot rename %s -> %s: %s", from, to, reason)
end
local function rename_file_exists(node, to)
if not utils.is_macos then
return utils.file_exists(to)
end
if string.lower(node) == string.lower(to) then
return false
end
return utils.file_exists(to)
end
---@param node Node
---@param to string
function M.rename(node, to)
local notify_from = notify.render_path(node.absolute_path)
local notify_to = notify.render_path(to)
if rename_file_exists(notify_from, notify_to) then
notify.warn(err_fmt(notify_from, notify_to, "file already exists"))
return
end
-- create a folder for each path element if the folder does not exist
local idx = 0
local path_to_create = ""
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
---@param default_modifier string|nil
---@return fun(node: Node, modifier: string)
function M.fn(default_modifier)
default_modifier = default_modifier or ":t"
return function(node, modifier)
if type(node) ~= "table" then
node = lib.get_node_at_cursor()
end
if node == nil then
return
end
if type(modifier) ~= "string" then
modifier = default_modifier
end
-- support for only specific modifiers have been implemented
if not ALLOWED_MODIFIERS[modifier] then
notify.warn("Modifier " .. vim.inspect(modifier) .. " is not in allowed list : " .. table.concat(ALLOWED_MODIFIERS, ","))
return
end
node = lib.get_last_group_node(node)
if node.name == ".." then
return
end
local namelen = node.name:len()
local directory = node.absolute_path:sub(0, namelen * -1 - 1)
local default_path
local prepend = ""
local append = ""
default_path = vim.fn.fnamemodify(node.absolute_path, modifier)
if modifier:sub(0, 2) == ":t" then
prepend = directory
end
if modifier == ":t:r" then
local extension = vim.fn.fnamemodify(node.name, ":e")
append = extension:len() == 0 and "" or "." .. extension
end
if modifier == ":p:h" then
default_path = default_path .. "/"
end
local input_opts = {
prompt = "Rename to ",
default = default_path,
completion = "file",
}
vim.ui.input(input_opts, function(new_file_path)
utils.clear_prompt()
if not new_file_path then
return
end
M.rename(node, prepend .. new_file_path .. append)
if not M.config.filesystem_watchers.enable then
require("nvim-tree.actions.reloaders").reload_explorer()
end
find_file(utils.path_remove_trailing(new_file_path))
end)
end
end
function M.setup(opts)
M.config.filesystem_watchers = opts.filesystem_watchers
end
return M

View File

@@ -0,0 +1,123 @@
local lib = require "nvim-tree.lib"
local notify = require "nvim-tree.notify"
local reloaders = require "nvim-tree.actions.reloaders"
local M = {
config = {},
}
local utils = require "nvim-tree.utils"
local events = require "nvim-tree.events"
---@param absolute_path string
local function clear_buffer(absolute_path)
local bufs = vim.fn.getbufinfo { bufloaded = 1, buflisted = 1 }
for _, buf in pairs(bufs) do
if buf.name == absolute_path then
if buf.hidden == 0 and #bufs > 1 then
local winnr = vim.api.nvim_get_current_win()
vim.api.nvim_set_current_win(buf.windows[1])
vim.cmd ":bn"
vim.api.nvim_set_current_win(winnr)
end
vim.api.nvim_buf_delete(buf.bufnr, {})
return
end
end
end
---@param node Node
function M.remove(node)
local binary = M.config.trash.cmd:gsub(" .*$", "")
if vim.fn.executable(binary) == 0 then
notify.warn(string.format("trash.cmd '%s' is not an executable.", M.config.trash.cmd))
return
end
local err_msg = ""
local function on_stderr(_, data)
err_msg = err_msg .. (data and table.concat(data, " "))
end
-- trashes a path (file or folder)
local function trash_path(on_exit)
local need_sync_wait = utils.is_windows
local job = vim.fn.jobstart(M.config.trash.cmd .. " " .. vim.fn.shellescape(node.absolute_path), {
detach = not need_sync_wait,
on_exit = on_exit,
on_stderr = on_stderr,
})
if need_sync_wait then
vim.fn.jobwait { job }
end
end
if node.nodes ~= nil and not node.link_to then
trash_path(function(_, rc)
if rc ~= 0 then
notify.warn("trash failed: " .. err_msg .. "; please see :help nvim-tree.trash")
return
end
events._dispatch_folder_removed(node.absolute_path)
if not M.config.filesystem_watchers.enable then
reloaders.reload_explorer()
end
end)
else
events._dispatch_will_remove_file(node.absolute_path)
trash_path(function(_, rc)
if rc ~= 0 then
notify.warn("trash failed: " .. err_msg .. "; please see :help nvim-tree.trash")
return
end
events._dispatch_file_removed(node.absolute_path)
clear_buffer(node.absolute_path)
if not M.config.filesystem_watchers.enable then
reloaders.reload_explorer()
end
end)
end
end
---@param node Node
function M.fn(node)
if node.name == ".." then
return
end
local function do_trash()
M.remove(node)
end
if M.config.ui.confirm.trash then
local prompt_select = "Trash " .. node.name .. "?"
local prompt_input, items_short, items_long
if M.config.ui.confirm.default_yes then
prompt_input = prompt_select .. " Y/n: "
items_short = { "", "n" }
items_long = { "Yes", "No" }
else
prompt_input = prompt_select .. " y/N: "
items_short = { "", "y" }
items_long = { "No", "Yes" }
end
lib.prompt(prompt_input, prompt_select, items_short, items_long, "nvimtree_trash", function(item_short)
utils.clear_prompt()
if item_short == "y" or item_short == (M.config.ui.confirm.default_yes and "") then
do_trash()
end
end)
else
do_trash()
end
end
function M.setup(opts)
M.config.ui = opts.ui
M.config.trash = opts.trash
M.config.filesystem_watchers = opts.filesystem_watchers
end
return M

View File

@@ -1,243 +1,18 @@
local a = vim.api local M = {}
local lib = require "nvim-tree.lib" M.finders = require "nvim-tree.actions.finders"
local log = require "nvim-tree.log" M.fs = require "nvim-tree.actions.fs"
local view = require "nvim-tree.view" M.moves = require "nvim-tree.actions.moves"
local util = require "nvim-tree.utils" M.node = require "nvim-tree.actions.node"
local nvim_tree_callback = require("nvim-tree.config").nvim_tree_callback M.reloaders = require "nvim-tree.actions.reloaders"
M.root = require "nvim-tree.actions.root"
local M = { M.tree = require "nvim-tree.actions.tree"
mappings = {
{ key = { "<CR>", "o", "<2-LeftMouse>" }, action = "edit" },
{ key = "<C-e>", action = "edit_in_place" },
{ key = "O", action = "edit_no_picker" },
{ key = { "<2-RightMouse>", "<C-]>" }, action = "cd" },
{ key = "<C-v>", action = "vsplit" },
{ key = "<C-x>", action = "split" },
{ key = "<C-t>", action = "tabnew" },
{ key = "<", action = "prev_sibling" },
{ key = ">", action = "next_sibling" },
{ key = "P", action = "parent_node" },
{ key = "<BS>", action = "close_node" },
{ key = "<Tab>", action = "preview" },
{ key = "K", action = "first_sibling" },
{ key = "J", action = "last_sibling" },
{ key = "I", action = "toggle_git_ignored" },
{ key = "H", action = "toggle_dotfiles" },
{ key = "R", action = "refresh" },
{ key = "a", action = "create" },
{ key = "d", action = "remove" },
{ key = "D", action = "trash" },
{ key = "r", action = "rename" },
{ key = "<C-r>", action = "full_rename" },
{ key = "x", action = "cut" },
{ key = "c", action = "copy" },
{ key = "p", action = "paste" },
{ key = "y", action = "copy_name" },
{ key = "Y", action = "copy_path" },
{ key = "gy", action = "copy_absolute_path" },
{ key = "[c", action = "prev_git_item" },
{ key = "]c", action = "next_git_item" },
{ key = "-", action = "dir_up" },
{ key = "s", action = "system_open" },
{ key = "q", action = "close" },
{ key = "g?", action = "toggle_help" },
{ key = "W", action = "collapse_all" },
{ key = "S", action = "search_node" },
{ key = ".", action = "run_file_command" },
{ key = "<C-k>", action = "toggle_file_info" },
{ key = "U", action = "toggle_custom" },
},
custom_keypress_funcs = {},
}
local keypress_funcs = {
close = view.close,
close_node = require("nvim-tree.actions.movements").parent_node(true),
collapse_all = require("nvim-tree.actions.collapse-all").fn,
copy_absolute_path = require("nvim-tree.actions.copy-paste").copy_absolute_path,
copy_name = require("nvim-tree.actions.copy-paste").copy_filename,
copy_path = require("nvim-tree.actions.copy-paste").copy_path,
copy = require("nvim-tree.actions.copy-paste").copy,
create = require("nvim-tree.actions.create-file").fn,
cut = require("nvim-tree.actions.copy-paste").cut,
dir_up = require("nvim-tree.actions.dir-up").fn,
first_sibling = require("nvim-tree.actions.movements").sibling(-math.huge),
full_rename = require("nvim-tree.actions.rename-file").fn(true),
last_sibling = require("nvim-tree.actions.movements").sibling(math.huge),
next_git_item = require("nvim-tree.actions.movements").find_git_item "next",
next_sibling = require("nvim-tree.actions.movements").sibling(1),
parent_node = require("nvim-tree.actions.movements").parent_node(false),
paste = require("nvim-tree.actions.copy-paste").paste,
prev_git_item = require("nvim-tree.actions.movements").find_git_item "prev",
prev_sibling = require("nvim-tree.actions.movements").sibling(-1),
refresh = require("nvim-tree.actions.reloaders").reload_explorer,
remove = require("nvim-tree.actions.remove-file").fn,
rename = require("nvim-tree.actions.rename-file").fn(false),
run_file_command = require("nvim-tree.actions.run-command").run_file_command,
search_node = require("nvim-tree.actions.search-node").fn,
toggle_file_info = require("nvim-tree.actions.file-popup").toggle_file_info,
system_open = require("nvim-tree.actions.system-open").fn,
toggle_dotfiles = require("nvim-tree.actions.toggles").dotfiles,
toggle_help = require("nvim-tree.actions.toggles").help,
toggle_custom = require("nvim-tree.actions.toggles").custom,
toggle_git_ignored = require("nvim-tree.actions.toggles").git_ignored,
trash = require("nvim-tree.actions.trash").fn,
}
function M.on_keypress(action)
if view.is_help_ui() and action == "close" then
action = "toggle_help"
end
if view.is_help_ui() and action ~= "toggle_help" then
return
end
local node = lib.get_node_at_cursor()
if not node then
return
end
local custom_function = M.custom_keypress_funcs[action]
local default_function = keypress_funcs[action]
if type(custom_function) == "function" then
return custom_function(node)
elseif default_function then
return default_function(node)
end
if action == "preview" then
if node.name == ".." then
return
end
if not node.nodes then
return require("nvim-tree.actions.open-file").fn("preview", node.absolute_path)
end
elseif node.name == ".." then
return require("nvim-tree.actions.change-dir").fn ".."
elseif action == "cd" then
if node.nodes ~= nil then
require("nvim-tree.actions.change-dir").fn(lib.get_last_group_node(node).absolute_path)
end
return
end
if node.link_to and not node.nodes then
require("nvim-tree.actions.open-file").fn(action, node.link_to)
elseif node.nodes ~= nil then
lib.expand_or_collapse(node)
else
require("nvim-tree.actions.open-file").fn(action, node.absolute_path)
end
end
function M.apply_mappings(bufnr)
for _, b in pairs(M.mappings) do
local mapping_rhs = b.cb or nvim_tree_callback(b.action)
if type(b.key) == "table" then
for _, key in pairs(b.key) do
a.nvim_buf_set_keymap(bufnr, b.mode or "n", key, mapping_rhs, { noremap = true, silent = true, nowait = true })
end
elseif mapping_rhs then
a.nvim_buf_set_keymap(bufnr, b.mode or "n", b.key, mapping_rhs, { noremap = true, silent = true, nowait = true })
end
end
end
local function merge_mappings(user_mappings)
if #user_mappings == 0 then
return M.mappings
end
local function is_empty(s)
return s == ""
end
local user_keys = {}
local removed_keys = {}
-- remove default mappings if action is a empty string
for _, map in pairs(user_mappings) do
if type(map.key) == "table" then
for _, key in pairs(map.key) do
table.insert(user_keys, key)
if is_empty(map.action) then
table.insert(removed_keys, key)
end
end
else
table.insert(user_keys, map.key)
if is_empty(map.action) then
table.insert(removed_keys, map.key)
end
end
if map.action and type(map.action_cb) == "function" then
if not is_empty(map.action) then
M.custom_keypress_funcs[map.action] = map.action_cb
else
util.warn "action can't be empty if action_cb provided"
end
end
end
local default_map = vim.tbl_filter(function(map)
if type(map.key) == "table" then
local filtered_keys = {}
for _, key in pairs(map.key) do
if not vim.tbl_contains(user_keys, key) and not vim.tbl_contains(removed_keys, key) then
table.insert(filtered_keys, key)
end
end
map.key = filtered_keys
return not vim.tbl_isempty(map.key)
else
return not vim.tbl_contains(user_keys, map.key) and not vim.tbl_contains(removed_keys, map.key)
end
end, M.mappings)
local user_map = vim.tbl_filter(function(map)
return not is_empty(map.action)
end, user_mappings)
return vim.fn.extend(default_map, user_map)
end
local function copy_mappings(user_mappings)
if #user_mappings == 0 then
return M.mappings
end
for _, map in pairs(user_mappings) do
if map.action and type(map.action_cb) == "function" then
M.custom_keypress_funcs[map.action] = map.action_cb
end
end
return user_mappings
end
local DEFAULT_MAPPING_CONFIG = {
custom_only = false,
list = {},
}
function M.setup(opts) function M.setup(opts)
require("nvim-tree.actions.system-open").setup(opts.system_open) M.fs.setup(opts)
require("nvim-tree.actions.trash").setup(opts.trash) M.node.setup(opts)
require("nvim-tree.actions.open-file").setup(opts) M.root.setup(opts)
require("nvim-tree.actions.change-dir").setup(opts) M.tree.setup(opts)
require("nvim-tree.actions.copy-paste").setup(opts)
local user_map_config = (opts.view or {}).mappings or {}
local options = vim.tbl_deep_extend("force", DEFAULT_MAPPING_CONFIG, user_map_config)
if options.custom_only then
M.mappings = copy_mappings(options.list)
else
M.mappings = merge_mappings(options.list)
end
log.line("config", "active mappings")
log.raw("config", "%s\n", vim.inspect(M.mappings))
end end
return M return M

View File

@@ -1,145 +0,0 @@
local utils = require "nvim-tree.utils"
local view = require "nvim-tree.view"
local renderer = require "nvim-tree.renderer"
local core = require "nvim-tree.core"
local lib = require "nvim-tree.lib"
local M = {}
local function get_line_from_node(node, find_parent)
local node_path = node.absolute_path
if find_parent then
node_path = node.absolute_path:match("(.*)" .. utils.path_separator)
end
local line = core.get_nodes_starting_line()
local function iter(nodes, recursive)
for _, _node in ipairs(nodes) do
local n = lib.get_last_group_node(_node)
if node_path == n.absolute_path then
return line, _node
end
line = line + 1
if _node.open == true and recursive then
local _, child = iter(_node.nodes, recursive)
if child ~= nil then
return line, child
end
end
end
end
return iter
end
function M.parent_node(should_close)
return function(node)
if should_close and node.open then
node.open = false
return renderer.draw()
end
local parent = node.parent
if not parent or parent.cwd then
return view.set_cursor { 1, 0 }
end
local _, line = utils.find_node(core.get_explorer().nodes, function(n)
return n.absolute_path == parent.absolute_path
end)
view.set_cursor { line + 1, 0 }
if should_close then
parent.open = false
renderer.draw()
end
end
end
function M.sibling(direction)
return function(node)
if node.name == ".." or not direction then
return
end
local iter = get_line_from_node(node, true)
local node_path = node.absolute_path
local line = 0
local parent, _
-- Check if current node is already at root nodes
for index, _node in ipairs(core.get_explorer().nodes) do
if node_path == _node.absolute_path then
line = index
end
end
if line > 0 then
parent = core.get_explorer()
else
_, parent = iter(core.get_explorer().nodes, true)
if parent ~= nil and #parent.nodes > 1 then
line, _ = get_line_from_node(node)(parent.nodes)
end
-- Ignore parent line count
line = line - 1
end
local index = line + direction
if index < 1 then
index = 1
elseif index > #parent.nodes then
index = #parent.nodes
end
local target_node = parent.nodes[index]
line, _ = get_line_from_node(target_node)(core.get_explorer().nodes, true)
view.set_cursor { line, 0 }
end
end
function M.find_git_item(where)
return function()
local node_cur = lib.get_node_at_cursor()
local nodes_by_line = utils.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line())
local cur, first, prev, nex = nil, nil, nil, nil
for line, node in pairs(nodes_by_line) do
if not first and node.git_status then
first = line
end
if node == node_cur then
cur = line
elseif node.git_status then
if not cur then
prev = line
end
if cur and not nex then
nex = line
break
end
end
end
if where == "prev" then
if prev then
view.set_cursor { prev, 0 }
end
else
if cur then
if nex then
view.set_cursor { nex, 0 }
end
elseif first then
view.set_cursor { first, 0 }
end
end
end
end
return M

View File

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

View File

@@ -0,0 +1,247 @@
local utils = require "nvim-tree.utils"
local core = require "nvim-tree.core"
local lib = require "nvim-tree.lib"
local explorer_node = require "nvim-tree.explorer.node"
local diagnostics = require "nvim-tree.diagnostics"
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 node_cur = lib.get_node_at_cursor()
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
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 node == node_cur then
cur = line
elseif valid and cur then
nex = line
break
end
end
local explorer = core.get_explorer()
if not explorer then
return
end
if nex then
explorer.view:set_cursor { nex, 0 }
elseif vim.o.wrapscan and first then
explorer.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
local explorer = core.get_explorer()
if explorer then
explorer.view:set_cursor { 1, 0 } -- move to root node (position 1)
end
else
local node_init_line = utils.find_node_line(node_init)
if node_init_line < 0 then
return
end
local explorer = core.get_explorer()
if explorer then
explorer.view:set_cursor { node_init_line, 0 } -- move to root node (position 1)
end
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
---@field where string
---@field what string
---@param opts NavigationItemOpts
---@return fun()
function M.fn(opts)
return function()
local recurse = false
local skip_gitignored = false
-- recurse only valid for git and diag moves.
if (opts.what == "git" or opts.what == "diag") and opts.recurse ~= nil then
recurse = opts.recurse
end
if 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
move_next_recursive(opts.what, skip_gitignored)
elseif opts.where == "prev" then
move_prev_recursive(opts.what, skip_gitignored)
end
end
end
return M

View File

@@ -0,0 +1,44 @@
local renderer = require "nvim-tree.renderer"
local utils = require "nvim-tree.utils"
local core = require "nvim-tree.core"
local lib = require "nvim-tree.lib"
local M = {}
---@param should_close boolean|nil
---@return fun(node: Node): boolean|nil
function M.fn(should_close)
should_close = should_close or false
return function(node)
node = lib.get_last_group_node(node)
if should_close and node.open then
node.open = false
return renderer.draw()
end
local parent = utils.get_parent_of_group(node).parent
if not parent or not parent.parent then
local explorer = core.get_explorer()
if explorer then
return explorer.view:set_cursor { 1, 0 }
end
end
local _, line = utils.find_node(core.get_explorer().nodes, function(n)
return n.absolute_path == parent.absolute_path
end)
local explorer = core.get_explorer()
if explorer then
explorer.view:set_cursor { line + 1, 0 }
end
if should_close then
parent.open = false
renderer.draw()
end
end
end
return M

View File

@@ -0,0 +1,53 @@
local utils = require "nvim-tree.utils"
local core = require "nvim-tree.core"
local Iterator = require "nvim-tree.iterators.node-iterator"
local M = {}
---@param direction string
---@return fun(node: Node): nil
function M.fn(direction)
return function(node)
if node.name == ".." or not direction then
return
end
local first, last, next, prev = nil, nil, nil, nil
local found = false
local parent = node.parent or core.get_explorer()
Iterator.builder(parent.nodes)
:recursor(function()
return nil
end)
:applier(function(n)
first = first or n
last = n
if n.absolute_path == node.absolute_path then
found = true
return
end
prev = not found and n or prev
if found and not next then
next = n
end
end)
:iterate()
local target_node
if direction == "first" then
target_node = first
elseif direction == "last" then
target_node = last
elseif direction == "next" then
target_node = next or first
else
target_node = prev or last
end
if target_node then
utils.focus_file(target_node.absolute_path)
end
end
end
return M

View File

@@ -1,10 +1,19 @@
local utils = require "nvim-tree.utils" local utils = require "nvim-tree.utils"
local a = vim.api
local M = {} local M = {}
---@param node Node
---@return table
local function get_formatted_lines(node) local function get_formatted_lines(node)
local stats = node.fs_stat local stats = node.fs_stat
if stats == nil then
return {
"",
" Can't retrieve file information",
"",
}
end
local fpath = " fullpath: " .. node.absolute_path local fpath = " fullpath: " .. node.absolute_path
local created_at = " created: " .. os.date("%x %X", stats.birthtime.sec) local created_at = " created: " .. os.date("%x %X", stats.birthtime.sec)
local modified_at = " modified: " .. os.date("%x %X", stats.mtime.sec) local modified_at = " modified: " .. os.date("%x %X", stats.mtime.sec)
@@ -22,40 +31,39 @@ end
local current_popup = nil local current_popup = nil
---@param node Node
local function setup_window(node) local function setup_window(node)
local lines = get_formatted_lines(node) local lines = get_formatted_lines(node)
local max_width = vim.fn.max(vim.tbl_map(function(n) local max_width = vim.fn.max(vim.tbl_map(function(n)
return #n return #n
end, lines)) end, lines))
local winnr = a.nvim_open_win(0, false, { local open_win_config = vim.tbl_extend("force", M.open_win_config, {
col = 1,
row = 1,
relative = "cursor",
width = max_width + 1, width = max_width + 1,
height = #lines, height = #lines,
border = "shadow",
noautocmd = true, noautocmd = true,
style = "minimal", zindex = 60,
}) })
local winnr = vim.api.nvim_open_win(0, false, open_win_config)
current_popup = { current_popup = {
winnr = winnr, winnr = winnr,
file_path = node.absolute_path, file_path = node.absolute_path,
} }
local bufnr = a.nvim_create_buf(false, true) local bufnr = vim.api.nvim_create_buf(false, true)
a.nvim_buf_set_lines(bufnr, 0, -1, false, lines) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
a.nvim_win_set_buf(winnr, bufnr) vim.api.nvim_win_set_buf(winnr, bufnr)
end end
function M.close_popup() function M.close_popup()
if current_popup ~= nil then if current_popup ~= nil then
a.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
end end
---@param node Node
function M.toggle_file_info(node) function M.toggle_file_info(node)
if node.name == ".." then if node.name == ".." then
return return
@@ -72,11 +80,14 @@ function M.toggle_file_info(node)
setup_window(node) setup_window(node)
vim.cmd [[ vim.api.nvim_create_autocmd("CursorMoved", {
augroup NvimTreeRemoveFilePopup group = vim.api.nvim_create_augroup("NvimTreeRemoveFilePopup", {}),
au CursorMoved * lua require'nvim-tree.actions.file-popup'.close_popup() callback = M.close_popup,
augroup END })
]] end
function M.setup(opts)
M.open_win_config = opts.actions.file_popup.open_win_config
end end
return M return M

View File

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

View File

@@ -0,0 +1,452 @@
-- Copyright 2019 Yazdani Kiyan under MIT License
local lib = require "nvim-tree.lib"
local notify = require "nvim-tree.notify"
local utils = require "nvim-tree.utils"
local M = {}
---Get single char from user input
---@return string
local function get_user_input_char()
local c = vim.fn.getchar()
while type(c) ~= "number" do
c = vim.fn.getchar()
end
return vim.fn.nr2char(c)
end
---Get all windows in the current tabpage that aren't NvimTree.
---@return table with valid win_ids
local function usable_win_ids()
local explorer = require "nvim-tree.core".get_explorer()
local tabpage = vim.api.nvim_get_current_tabpage()
local win_ids = vim.api.nvim_tabpage_list_wins(tabpage)
local tree_winid = explorer and explorer.view:get_winnr(tabpage)
return vim.tbl_filter(function(id)
local bufid = vim.api.nvim_win_get_buf(id)
for option, v in pairs(M.window_picker.exclude) do
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
return false
end
end
local win_config = vim.api.nvim_win_get_config(id)
return id ~= tree_winid and win_config.focusable and not win_config.external or false
end, win_ids)
end
---Find the first window in the tab that is not NvimTree.
---@return integer -1 if none available
local function first_win_id()
local selectable = usable_win_ids()
if #selectable > 0 then
return selectable[1]
else
return -1
end
end
---Get user to pick a window in the tab that is not NvimTree.
---@return integer|nil -- If a valid window was picked, return its id. If an
--- invalid window was picked / user canceled, return nil. If there are
--- no selectable windows, return -1.
local function pick_win_id()
local selectable = usable_win_ids()
-- If there are no selectable windows: return. If there's only 1, return it without picking.
if #selectable == 0 then
return -1
end
if #selectable == 1 then
return selectable[1]
end
if #M.window_picker.chars < #selectable then
notify.error(string.format("More windows (%d) than actions.open_file.window_picker.chars (%d).", #selectable, #M.window_picker.chars))
return nil
end
local i = 1
local win_opts = {}
local win_map = {}
local laststatus = vim.o.laststatus
vim.o.laststatus = 2
local tabpage = vim.api.nvim_get_current_tabpage()
local win_ids = vim.api.nvim_tabpage_list_wins(tabpage)
local not_selectable = vim.tbl_filter(function(id)
return not vim.tbl_contains(selectable, id)
end, win_ids)
if laststatus == 3 then
for _, win_id in ipairs(not_selectable) do
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 = 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] = {
statusline = ok_status and statusline or "",
winhl = ok_hl and winhl or "",
}
-- Clear statusline for windows not selectable
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
-- Setup UI
for _, id in ipairs(selectable) do
local char = M.window_picker.chars:sub(i, i)
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] = {
statusline = ok_status and statusline or "",
winhl = ok_hl and winhl or "",
}
win_map[char] = id
if vim.fn.has "nvim-0.10" == 1 then
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
if i > #M.window_picker.chars then
break
end
end
vim.cmd "redraw"
if vim.opt.cmdheight._value ~= 0 then
print "Pick window: "
end
local _, resp = pcall(get_user_input_char)
resp = (resp or ""):upper()
utils.clear_prompt()
-- Restore window options
for _, id in ipairs(selectable) do
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
if laststatus == 3 then
for _, id in ipairs(not_selectable) do
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
vim.o.laststatus = laststatus
if not vim.tbl_contains(vim.split(M.window_picker.chars, ""), resp) then
return
end
return win_map[resp]
end
local function open_file_in_tab(filename)
if M.quit_on_open then
local explorer = require "nvim-tree.core".get_explorer()
if explorer then
explorer.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))
end
local function drop(filename)
if M.quit_on_open then
local explorer = require"nvim-tree.core".get_explorer()
if explorer then
explorer.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))
end
local function tab_drop(filename)
if M.quit_on_open then
local explorer = require"nvim-tree.core".get_explorer()
if explorer then
explorer.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))
end
local function on_preview(buf_loaded)
if not buf_loaded then
vim.bo.bufhidden = "delete"
vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, {
group = vim.api.nvim_create_augroup("RemoveBufHidden", {}),
buffer = vim.api.nvim_get_current_buf(),
callback = function()
vim.bo.bufhidden = ""
end,
once = true,
})
end
local explorer = require"nvim-tree.core".get_explorer()
if explorer then
explorer.view:focus()
end
end
local function get_target_winid(mode)
local target_winid
if not M.window_picker.enable or mode == "edit_no_picker" or mode == "preview_no_picker" then
target_winid = lib.target_winid
-- first available window
if not vim.tbl_contains(vim.api.nvim_tabpage_list_wins(0), target_winid) then
target_winid = first_win_id()
end
else
-- pick a window
if type(M.window_picker.picker) == "function" then
target_winid = M.window_picker.picker()
else
target_winid = pick_win_id()
end
if target_winid == nil then
-- pick failed/cancelled
return
end
end
if target_winid == -1 then
target_winid = lib.target_winid
end
return target_winid
end
-- This is only to avoid the BufEnter for nvim-tree to trigger
-- which would cause find-file to run on an invalid file.
local function set_current_win_no_autocmd(winid, autocmd)
local eventignore = vim.opt.eventignore:get()
vim.opt.eventignore:append(autocmd)
vim.api.nvim_set_current_win(winid)
vim.opt.eventignore = eventignore
end
local function open_in_new_window(filename, mode)
if type(mode) ~= "string" then
mode = ""
end
local target_winid = get_target_winid(mode)
if not target_winid then
return
end
-- non-floating, non-nvim-tree windows
local win_ids = vim.tbl_filter(function(id)
local config = vim.api.nvim_win_get_config(id)
local bufnr = vim.api.nvim_win_get_buf(id)
return config and config.relative == "" or utils.is_nvim_tree_buf(bufnr)
end, vim.api.nvim_list_wins())
local create_new_window = #win_ids == 1 -- This implies that the nvim-tree window is the only one
local explorer = require"nvim-tree.core".get_explorer()
local new_window_side = (explorer and view.View.side == "right") and "aboveleft" or "belowright"
-- Target is invalid: create new window
if not vim.tbl_contains(win_ids, target_winid) then
vim.cmd(new_window_side .. " vsplit")
target_winid = vim.api.nvim_get_current_win()
lib.target_winid = target_winid
-- No need to split, as we created a new window.
create_new_window = false
if mode:match "split$" then
mode = "edit"
end
elseif not vim.o.hidden then
-- If `hidden` is not enabled, check if buffer in target window is
-- modified, and create new split if it is.
local target_bufid = vim.api.nvim_win_get_buf(target_winid)
local modified
if vim.fn.has "nvim-0.10" == 1 then
modified = vim.api.nvim_get_option_value("modified", { buf = target_bufid })
else
modified = vim.api.nvim_buf_get_option(target_bufid, "modified") ---@diagnostic disable-line: deprecated
end
if modified then
if not mode:match "split$" then
mode = "vsplit"
end
end
end
if (mode == "preview" or mode == "preview_no_picker") and explorer and explorer.view.View.float.enable then
-- ignore "WinLeave" autocmd on preview
-- because the registered "WinLeave"
-- will kill the floating window immediately
set_current_win_no_autocmd(target_winid, { "WinLeave", "BufEnter" })
else
set_current_win_no_autocmd(target_winid, { "BufEnter" })
end
local fname
if M.relative_path then
fname = utils.escape_special_chars(vim.fn.fnameescape(utils.path_relative(filename, vim.fn.getcwd())))
else
fname = utils.escape_special_chars(vim.fn.fnameescape(filename))
end
local command
if create_new_window then
-- generated from vim.api.nvim_parse_cmd("belowright vsplit foo", {})
command = { cmd = "vsplit", mods = { split = new_window_side }, args = { fname } }
elseif mode:match "split$" then
command = { cmd = mode, args = { fname } }
else
command = { cmd = "edit", args = { fname } }
end
pcall(vim.api.nvim_cmd, command, { output = false })
lib.set_target_win()
end
local function is_already_loaded(filename)
for _, buf_id in ipairs(vim.api.nvim_list_bufs()) do
if vim.api.nvim_buf_is_loaded(buf_id) and filename == vim.api.nvim_buf_get_name(buf_id) then
return true
end
end
return false
end
local function edit_in_current_buf(filename)
local explorer = require"nvim-tree.core".get_explorer()
if explorer then
explorer.view:abandon_current_window()
end
if M.relative_path then
filename = utils.path_relative(filename, vim.fn.getcwd())
end
vim.cmd("keepalt keepjumps edit " .. vim.fn.fnameescape(filename))
end
---@param mode string
---@param filename string
function M.fn(mode, filename)
if type(mode) ~= "string" then
mode = ""
end
if mode == "tabnew" then
return open_file_in_tab(filename)
end
if mode == "drop" then
return drop(filename)
end
if mode == "tab_drop" then
return tab_drop(filename)
end
if mode == "edit_in_place" then
return edit_in_current_buf(filename)
end
local buf_loaded = is_already_loaded(filename)
local found_win = utils.get_win_buf_from_path(filename)
if found_win and (mode == "preview" or mode == "preview_no_picker") then
return
end
if not found_win then
open_in_new_window(filename, mode)
else
vim.api.nvim_set_current_win(found_win)
vim.bo.bufhidden = ""
end
if M.resize_window then
local explorer = require"nvim-tree.core".get_explorer()
if explorer then
explorer.view:resize()
end
end
if mode == "preview" or mode == "preview_no_picker" then
return on_preview(buf_loaded)
end
if M.quit_on_open then
local explorer = require"nvim-tree.core".get_explorer()
if explorer then
explorer.view:close()
end
end
end
function M.setup(opts)
M.quit_on_open = opts.actions.open_file.quit_on_open
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
opts.actions.open_file.window_picker.chars = tostring(opts.actions.open_file.window_picker.chars):upper()
end
M.window_picker = opts.actions.open_file.window_picker
end
return M

View File

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

View File

@@ -0,0 +1,70 @@
local notify = require "nvim-tree.notify"
local utils = require "nvim-tree.utils"
local M = {}
---@param node Node
function M.fn(node)
if #M.config.system_open.cmd == 0 then
require("nvim-tree.utils").notify.warn "Cannot open file with system application. Unrecognized platform."
return
end
local process = {
cmd = M.config.system_open.cmd,
args = M.config.system_open.args,
errors = "\n",
stderr = vim.loop.new_pipe(false),
}
table.insert(process.args, node.link_to or node.absolute_path)
local opts = {
args = process.args,
stdio = { nil, nil, process.stderr },
detached = true,
}
process.handle, process.pid = vim.loop.spawn(process.cmd, opts, function(code)
process.stderr:read_stop()
process.stderr:close()
process.handle:close()
if code ~= 0 then
notify.warn(string.format("system_open failed with return code %d: %s", code, process.errors))
end
end)
table.remove(process.args)
if not process.handle then
notify.warn(string.format("system_open failed to spawn command '%s': %s", process.cmd, process.pid))
return
end
vim.loop.read_start(process.stderr, function(err, data)
if err then
return
end
if data then
process.errors = process.errors .. data
end
end)
vim.loop.unref(process.handle)
end
function M.setup(opts)
M.config = {}
M.config.system_open = opts.system_open or {}
if #M.config.system_open.cmd == 0 then
if utils.is_windows then
M.config.system_open = {
cmd = "cmd",
args = { "/c", "start", '""' },
}
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
return M

View File

@@ -1,286 +0,0 @@
-- Copyright 2019 Yazdani Kiyan under MIT License
local api = vim.api
local lib = require "nvim-tree.lib"
local utils = require "nvim-tree.utils"
local view = require "nvim-tree.view"
local M = {}
local function get_split_cmd()
local side = view.View.side
if side == "right" then
return "aboveleft"
end
if side == "left" then
return "belowright"
end
if side == "top" then
return "bot"
end
return "top"
end
---Get user to pick a window. Selectable windows are all windows in the current
---tabpage that aren't NvimTree.
---@return integer|nil -- If a valid window was picked, return its id. If an
--- invalid window was picked / user canceled, return nil. If there are
--- no selectable windows, return -1.
local function pick_window()
local tabpage = api.nvim_get_current_tabpage()
local win_ids = api.nvim_tabpage_list_wins(tabpage)
local tree_winid = view.get_winnr(tabpage)
local selectable = vim.tbl_filter(function(id)
local bufid = api.nvim_win_get_buf(id)
for option, v in pairs(M.window_picker.exclude) do
local ok, option_value = pcall(api.nvim_buf_get_option, bufid, option)
if ok and vim.tbl_contains(v, option_value) then
return false
end
end
local win_config = api.nvim_win_get_config(id)
return id ~= tree_winid and win_config.focusable and not win_config.external
end, win_ids)
-- If there are no selectable windows: return. If there's only 1, return it without picking.
if #selectable == 0 then
return -1
end
if #selectable == 1 then
return selectable[1]
end
local i = 1
local win_opts = {}
local win_map = {}
local laststatus = vim.o.laststatus
vim.o.laststatus = 2
local not_selectable = vim.tbl_filter(function(id)
return not vim.tbl_contains(selectable, id)
end, win_ids)
if laststatus == 3 then
for _, win_id in ipairs(not_selectable) do
local ok_status, statusline = pcall(api.nvim_win_get_option, win_id, "statusline")
local ok_hl, winhl = pcall(api.nvim_win_get_option, win_id, "winhl")
win_opts[win_id] = {
statusline = ok_status and statusline or "",
winhl = ok_hl and winhl or "",
}
-- Clear statusline for windows not selectable
api.nvim_win_set_option(win_id, "statusline", " ")
end
end
-- Setup UI
for _, id in ipairs(selectable) do
local char = M.window_picker.chars:sub(i, i)
local ok_status, statusline = pcall(api.nvim_win_get_option, id, "statusline")
local ok_hl, winhl = pcall(api.nvim_win_get_option, id, "winhl")
win_opts[id] = {
statusline = ok_status and statusline or "",
winhl = ok_hl and winhl or "",
}
win_map[char] = id
api.nvim_win_set_option(id, "statusline", "%=" .. char .. "%=")
api.nvim_win_set_option(id, "winhl", "StatusLine:NvimTreeWindowPicker,StatusLineNC:NvimTreeWindowPicker")
i = i + 1
if i > #M.window_picker.chars then
break
end
end
vim.cmd "redraw"
print "Pick window: "
local _, resp = pcall(utils.get_user_input_char)
resp = (resp or ""):upper()
utils.clear_prompt()
-- Restore window options
for _, id in ipairs(selectable) do
for opt, value in pairs(win_opts[id]) do
api.nvim_win_set_option(id, opt, value)
end
end
if laststatus == 3 then
for _, id in ipairs(not_selectable) do
for opt, value in pairs(win_opts[id]) do
api.nvim_win_set_option(id, opt, value)
end
end
end
vim.o.laststatus = laststatus
if not vim.tbl_contains(vim.split(M.window_picker.chars, ""), resp) then
return
end
return win_map[resp]
end
local function open_file_in_tab(filename)
if M.quit_on_open then
view.close()
else
-- Switch window first to ensure new window doesn't inherit settings from
-- NvimTree
if lib.target_winid > 0 and api.nvim_win_is_valid(lib.target_winid) then
api.nvim_set_current_win(lib.target_winid)
else
vim.cmd "wincmd p"
end
end
-- This sequence of commands are here to ensure a number of things: the new
-- buffer must be opened in the current tabpage first so that focus can be
-- brought back to the tree if it wasn't quit_on_open. It also ensures that
-- when we open the new tabpage with the file, its window doesn't inherit
-- settings from NvimTree, as it was already loaded.
vim.cmd("edit " .. vim.fn.fnameescape(filename))
local alt_bufid = vim.fn.bufnr "#"
if alt_bufid ~= -1 then
api.nvim_set_current_buf(alt_bufid)
end
if not M.quit_on_open then
vim.cmd "wincmd p"
end
vim.cmd("tabe " .. vim.fn.fnameescape(filename))
end
function M.fn(mode, filename)
if mode == "tabnew" then
open_file_in_tab(filename)
return
end
if mode == "edit_in_place" then
require("nvim-tree.view").abandon_current_window()
vim.cmd("edit " .. vim.fn.fnameescape(filename))
return
end
local tabpage = api.nvim_get_current_tabpage()
local win_ids = api.nvim_tabpage_list_wins(tabpage)
local target_winid
if not M.window_picker.enable or mode == "edit_no_picker" then
target_winid = lib.target_winid
else
local pick_window_id = pick_window()
if pick_window_id == nil then
return
end
target_winid = pick_window_id
end
if target_winid == -1 then
target_winid = lib.target_winid
end
local do_split = mode == "split" or mode == "vsplit"
local vertical = mode ~= "split"
-- Check if file is already loaded in a buffer
local buf_loaded = false
for _, buf_id in ipairs(api.nvim_list_bufs()) do
if api.nvim_buf_is_loaded(buf_id) and filename == api.nvim_buf_get_name(buf_id) then
buf_loaded = true
break
end
end
-- Check if filename is already open in a window
local found = false
for _, id in ipairs(win_ids) do
if filename == api.nvim_buf_get_name(api.nvim_win_get_buf(id)) then
if mode == "preview" then
return
end
found = true
api.nvim_set_current_win(id)
break
end
end
if not found then
if not target_winid or not vim.tbl_contains(win_ids, target_winid) then
-- Target is invalid, or window does not exist in current tabpage: create
-- new window
local split_cmd = get_split_cmd()
local splitside = view.is_vertical() and "vsp" or "sp"
vim.cmd(split_cmd .. " " .. splitside)
target_winid = api.nvim_get_current_win()
lib.target_winid = target_winid
-- No need to split, as we created a new window.
do_split = false
elseif not vim.o.hidden then
-- If `hidden` is not enabled, check if buffer in target window is
-- modified, and create new split if it is.
local target_bufid = api.nvim_win_get_buf(target_winid)
if api.nvim_buf_get_option(target_bufid, "modified") then
do_split = true
end
end
local cmd
if do_split or #api.nvim_list_wins() == 1 then
cmd = string.format("%ssplit ", vertical and "vertical " or "")
else
cmd = "edit "
end
cmd = cmd .. vim.fn.fnameescape(filename)
api.nvim_set_current_win(target_winid)
pcall(vim.cmd, cmd)
lib.set_target_win()
end
if M.resize_window then
view.resize()
end
if mode == "preview" then
if not buf_loaded then
vim.bo.bufhidden = "delete"
vim.cmd [[
augroup RemoveBufHidden
autocmd!
autocmd TextChanged <buffer> setlocal bufhidden= | autocmd! RemoveBufHidden
autocmd TextChangedI <buffer> setlocal bufhidden= | autocmd! RemoveBufHidden
augroup end
]]
end
view.focus()
return
end
if M.quit_on_open then
view.close()
end
end
function M.setup(opts)
M.quit_on_open = opts.actions.open_file.quit_on_open
M.resize_window = opts.actions.open_file.resize_window
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()
end
M.window_picker = opts.actions.open_file.window_picker
end
return M

View File

@@ -1,31 +1,39 @@
local git = require "nvim-tree.git" local git = require "nvim-tree.git"
local view = require "nvim-tree.view"
local renderer = require "nvim-tree.renderer" local renderer = require "nvim-tree.renderer"
local explorer_module = require "nvim-tree.explorer" local explorer_module = require "nvim-tree.explorer"
local core = require "nvim-tree.core" local core = require "nvim-tree.core"
local explorer_node = require "nvim-tree.explorer.node"
local Iterator = require "nvim-tree.iterators.node-iterator"
local M = {} local M = {}
---@param node Explorer|nil
---@param projects table
local function refresh_nodes(node, projects) local function refresh_nodes(node, projects)
local cwd = node.cwd or node.link_to or node.absolute_path Iterator.builder({ node })
local project_root = git.get_project_root(cwd) :applier(function(n)
explorer_module.reload(node, projects[project_root] or {}) if n.nodes then
for _, _node in ipairs(node.nodes) do local toplevel = git.get_toplevel(n.cwd or n.link_to or n.absolute_path)
if _node.nodes and _node.open then explorer_module.reload(n, projects[toplevel] or {})
refresh_nodes(_node, projects)
end
end end
end)
:recursor(function(n)
return n.group_next and { n.group_next } or (n.open and n.nodes)
end)
:iterate()
end end
---@param parent_node Node|nil
---@param projects table
function M.reload_node_status(parent_node, projects) function M.reload_node_status(parent_node, projects)
local project_root = git.get_project_root(parent_node.absolute_path or parent_node.cwd) if parent_node == nil then
local status = projects[project_root] or {} return
for _, node in ipairs(parent_node.nodes) do
if node.nodes then
node.git_status = status.dirs and status.dirs[node.absolute_path]
else
node.git_status = status.files and status.files[node.absolute_path]
end 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 if node.nodes and #node.nodes > 0 then
M.reload_node_status(node, projects) M.reload_node_status(node, projects)
end end
@@ -34,21 +42,22 @@ end
local event_running = false local event_running = false
function M.reload_explorer() function M.reload_explorer()
if event_running or not core.get_explorer() or vim.v.exiting ~= vim.NIL then local explorer = core.get_explorer()
if event_running or not explorer or vim.v.exiting ~= vim.NIL then
return return
end end
event_running = true event_running = true
local projects = git.reload() local projects = git.reload()
refresh_nodes(core.get_explorer(), projects) refresh_nodes(core.get_explorer(), projects)
if view.is_visible() then if explorer.view:is_visible() then
renderer.draw() renderer.draw()
end end
event_running = false event_running = false
end end
function M.reload_git() function M.reload_git()
if not core.get_explorer() or not git.config.enable or event_running then if not core.get_explorer() or not git.config.git.enable or event_running then
return return
end end
event_running = true event_running = true

View File

@@ -1,91 +0,0 @@
local a = vim.api
local luv = vim.loop
local utils = require "nvim-tree.utils"
local events = require "nvim-tree.events"
local M = {}
local function close_windows(windows)
for _, window in ipairs(windows) do
if a.nvim_win_is_valid(window) then
a.nvim_win_close(window, true)
end
end
end
local function clear_buffer(absolute_path)
local bufs = vim.fn.getbufinfo { bufloaded = 1, buflisted = 1 }
for _, buf in pairs(bufs) do
if buf.name == absolute_path then
if buf.hidden == 0 and #bufs > 1 then
local winnr = a.nvim_get_current_win()
a.nvim_set_current_win(buf.windows[1])
vim.cmd ":bn"
a.nvim_set_current_win(winnr)
end
a.nvim_buf_delete(buf.bufnr, { force = true })
close_windows(buf.windows)
return
end
end
end
local function remove_dir(cwd)
local handle = luv.fs_scandir(cwd)
if type(handle) == "string" then
return a.nvim_err_writeln(handle)
end
while true do
local name, t = luv.fs_scandir_next(handle)
if not name then
break
end
local new_cwd = utils.path_join { cwd, name }
if t == "directory" then
local success = remove_dir(new_cwd)
if not success then
return false
end
else
local success = luv.fs_unlink(new_cwd)
if not success then
return false
end
clear_buffer(new_cwd)
end
end
return luv.fs_rmdir(cwd)
end
function M.fn(node)
if node.name == ".." then
return
end
print("Remove " .. node.name .. " ? y/n")
local ans = utils.get_user_input_char()
utils.clear_prompt()
if ans:match "^y" then
if node.nodes ~= nil and not node.link_to then
local success = remove_dir(node.absolute_path)
if not success then
return a.nvim_err_writeln("Could not remove " .. node.name)
end
events._dispatch_folder_removed(node.absolute_path)
else
local success = luv.fs_unlink(node.absolute_path)
if not success then
return a.nvim_err_writeln("Could not remove " .. node.name)
end
events._dispatch_file_removed(node.absolute_path)
clear_buffer(node.absolute_path)
end
require("nvim-tree.actions.reloaders").reload_explorer()
end
end
return M

View File

@@ -1,45 +0,0 @@
local a = vim.api
local uv = vim.loop
local lib = require "nvim-tree.lib"
local utils = require "nvim-tree.utils"
local events = require "nvim-tree.events"
local M = {}
function M.fn(with_sub)
return function(node)
node = lib.get_last_group_node(node)
if node.name == ".." then
return
end
local namelen = node.name:len()
local abs_path = with_sub and node.absolute_path:sub(0, namelen * -1 - 1) or node.absolute_path
local input_opts = { prompt = "Rename to ", default = abs_path, completion = "file" }
vim.ui.input(input_opts, function(new_file_path)
if not new_file_path then
return
end
if utils.file_exists(new_file_path) then
utils.warn "Cannot rename: file already exists"
return
end
local success = uv.fs_rename(node.absolute_path, new_file_path)
if not success then
return a.nvim_err_writeln("Could not rename " .. node.absolute_path .. " to " .. new_file_path)
end
utils.clear_prompt()
a.nvim_out_write(node.absolute_path .. "" .. new_file_path .. "\n")
utils.rename_loaded_buffers(node.absolute_path, new_file_path)
events._dispatch_node_renamed(abs_path, new_file_path)
require("nvim-tree.actions.reloaders").reload_explorer()
end)
end
end
return M

View File

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

View File

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

View File

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

View File

@@ -1,79 +0,0 @@
local api = vim.api
local uv = vim.loop
local utils = require "nvim-tree.utils"
local core = require "nvim-tree.core"
local filters = require "nvim-tree.explorer.filters"
local find_file = require("nvim-tree.actions.find-file").fn
local M = {}
local function search(dir, input_path)
local path, name, stat, handle, _
if not dir then
return
end
handle, _ = uv.fs_scandir(dir)
if not handle then
return
end
name, _ = uv.fs_scandir_next(handle)
while name do
path = dir .. "/" .. name
stat, _ = uv.fs_stat(path)
if not stat then
break
end
if not filters.should_ignore(path) then
if string.find(path, "/" .. input_path .. "$") then
return path
end
if stat.type == "directory" then
path = search(path, input_path)
if path then
return path
end
end
end
name, _ = uv.fs_scandir_next(handle)
end
end
function M.fn()
if not core.get_explorer() then
return
end
-- temporarily set &path
local bufnr = api.nvim_get_current_buf()
local path_existed, path_opt = pcall(api.nvim_buf_get_option, bufnr, "path")
api.nvim_buf_set_option(bufnr, "path", core.get_cwd() .. "/**")
-- completes files/dirs under cwd
local input_path = vim.fn.input("Search: ", "", "file_in_path")
utils.clear_prompt()
-- reset &path
if path_existed then
api.nvim_buf_set_option(bufnr, "path", path_opt)
else
api.nvim_buf_set_option(bufnr, "path", nil)
end
-- strip trailing slash
input_path = string.gsub(input_path, "/$", "")
-- search under cwd
local found = search(core.get_cwd(), input_path)
if found then
find_file(found)
end
end
return M

View File

@@ -1,70 +0,0 @@
local uv = vim.loop
local M = {
config = {
is_windows = vim.fn.has "win32" == 1 or vim.fn.has "win32unix" == 1,
is_macos = vim.fn.has "mac" == 1 or vim.fn.has "macunix" == 1,
is_unix = vim.fn.has "unix" == 1,
},
}
function M.fn(node)
if #M.config.system_open.cmd == 0 then
require("nvim-tree.utils").warn "Cannot open file with system application. Unrecognized platform."
return
end
local process = {
cmd = M.config.system_open.cmd,
args = M.config.system_open.args,
errors = "\n",
stderr = uv.new_pipe(false),
}
table.insert(process.args, node.link_to or node.absolute_path)
process.handle, process.pid = uv.spawn(
process.cmd,
{ args = process.args, stdio = { nil, nil, process.stderr }, detached = true },
function(code)
process.stderr:read_stop()
process.stderr:close()
process.handle:close()
if code ~= 0 then
process.errors = process.errors .. string.format("NvimTree system_open: return code %d.", code)
error(process.errors)
end
end
)
table.remove(process.args)
if not process.handle then
error("\n" .. process.pid .. "\nNvimTree system_open: failed to spawn process using '" .. process.cmd .. "'.")
return
end
uv.read_start(process.stderr, function(err, data)
if err then
return
end
if data then
process.errors = process.errors .. data
end
end)
uv.unref(process.handle)
end
function M.setup(opts)
M.config.system_open = opts or {}
if #M.config.system_open.cmd == 0 then
if M.config.is_windows then
M.config.system_open = {
cmd = "cmd",
args = { "/c", "start", '""' },
}
elseif M.config.is_macos then
M.config.system_open.cmd = "open"
elseif M.config.is_unix then
M.config.system_open.cmd = "xdg-open"
end
end
end
return M

View File

@@ -1,28 +0,0 @@
local view = require "nvim-tree.view"
local filters = require "nvim-tree.explorer.filters"
local renderer = require "nvim-tree.renderer"
local reloaders = require "nvim-tree.actions.reloaders"
local M = {}
function M.custom()
filters.config.filter_custom = not filters.config.filter_custom
return reloaders.reload_explorer()
end
function M.git_ignored()
filters.config.filter_git_ignored = not filters.config.filter_git_ignored
return reloaders.reload_explorer()
end
function M.dotfiles()
filters.config.filter_dotfiles = not filters.config.filter_dotfiles
return reloaders.reload_explorer()
end
function M.help()
view.toggle_help()
renderer.draw()
end
return M

View File

@@ -1,90 +0,0 @@
local a = vim.api
local M = {
config = {
is_windows = vim.fn.has "win32" == 1 or vim.fn.has "win32unix" == 1,
is_macos = vim.fn.has "mac" == 1 or vim.fn.has "macunix" == 1,
is_unix = vim.fn.has "unix" == 1,
},
}
local utils = require "nvim-tree.utils"
local events = require "nvim-tree.events"
local function clear_buffer(absolute_path)
local bufs = vim.fn.getbufinfo { bufloaded = 1, buflisted = 1 }
for _, buf in pairs(bufs) do
if buf.name == absolute_path then
if buf.hidden == 0 and #bufs > 1 then
local winnr = a.nvim_get_current_win()
a.nvim_set_current_win(buf.windows[1])
vim.cmd ":bn"
a.nvim_set_current_win(winnr)
end
vim.api.nvim_buf_delete(buf.bufnr, {})
return
end
end
end
function M.fn(node)
if node.name == ".." then
return
end
-- configs
if M.config.is_unix then
if M.config.trash.cmd == nil then
M.config.trash.cmd = "trash"
end
if M.config.trash.require_confirm == nil then
M.config.trash.require_confirm = true
end
else
utils.warn "Trash is currently a UNIX only feature!"
return
end
-- trashes a path (file or folder)
local function trash_path(on_exit)
vim.fn.jobstart(M.config.trash.cmd .. ' "' .. node.absolute_path .. '"', {
detach = true,
on_exit = on_exit,
})
end
local is_confirmed = true
-- confirmation prompt
if M.config.trash.require_confirm then
is_confirmed = false
print("Trash " .. node.name .. " ? y/n")
local ans = utils.get_user_input_char()
if ans:match "^y" then
is_confirmed = true
end
utils.clear_prompt()
end
-- trashing
if is_confirmed then
if node.nodes ~= nil and not node.link_to then
trash_path(function()
events._dispatch_folder_removed(node.absolute_path)
require("nvim-tree.actions.reloaders").reload_explorer()
end)
else
trash_path(function()
events._dispatch_file_removed(node.absolute_path)
clear_buffer(node.absolute_path)
require("nvim-tree.actions.reloaders").reload_explorer()
end)
end
end
end
function M.setup(opts)
M.config.trash = opts or {}
end
return M

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,65 @@
local lib = require "nvim-tree.lib"
local utils = require "nvim-tree.utils"
local reloaders = require "nvim-tree.actions.reloaders"
local core = require "nvim-tree.core"
local M = {}
local function reload()
local node = lib.get_node_at_cursor()
reloaders.reload_explorer()
utils.focus_node_or_parent(node)
end
local function wrap_explorer(fn)
return function(...)
local explorer = core.get_explorer()
if explorer then
return fn(explorer, ...)
end
end
end
local function custom(explorer)
explorer.filters.config.filter_custom = not explorer.filters.config.filter_custom
reload()
end
local function git_ignored(explorer)
explorer.filters.config.filter_git_ignored = not explorer.filters.config.filter_git_ignored
reload()
end
local function git_clean(explorer)
explorer.filters.config.filter_git_clean = not explorer.filters.config.filter_git_clean
reload()
end
local function no_buffer(explorer)
explorer.filters.config.filter_no_buffer = not explorer.filters.config.filter_no_buffer
reload()
end
local function no_bookmark(explorer)
explorer.filters.config.filter_no_bookmark = not explorer.filters.config.filter_no_bookmark
reload()
end
local function dotfiles(explorer)
explorer.filters.config.filter_dotfiles = not explorer.filters.config.filter_dotfiles
reload()
end
local function enable(explorer)
explorer.filters.config.enable = not explorer.filters.config.enable
reload()
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

View File

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

View File

@@ -0,0 +1,55 @@
local M = {}
---Resize the tree, persisting the new size.
---@param opts ApiTreeResizeOpts|nil
function M.fn(opts)
local explorer = require"nvim-tree.core".get_explorer()
if not explorer then
return
end
if opts == nil then
-- reset to config values
explorer.view:configure_width()
explorer.view:resize()
return
end
local options = opts or {}
local width_cfg = options.width
if width_cfg ~= nil then
explorer.view:configure_width(width_cfg)
explorer.view:resize()
return
end
if not explorer.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
explorer.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
explorer.view:resize(relative_size)
return
end
end
function M.setup(opts)
M.config = opts or {}
end
return M

View File

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

290
lua/nvim-tree/api.lua Normal file
View File

@@ -0,0 +1,290 @@
local lib = require "nvim-tree.lib"
local core = require "nvim-tree.core"
local utils = require "nvim-tree.utils"
local actions = require "nvim-tree.actions"
local appearance_diagnostics = require "nvim-tree.appearance.diagnostics"
local events = require "nvim-tree.events"
local help = require "nvim-tree.help"
local marks_navigation = require "nvim-tree.marks.navigation"
local marks_bulk_delete = require "nvim-tree.marks.bulk-delete"
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 = {
tree = {},
node = {
navigate = {
sibling = {},
git = {},
diagnostics = {},
opened = {},
},
run = {},
open = {},
},
events = {},
marks = {
bulk = {},
navigate = {},
},
fs = {
copy = {},
},
git = {},
live_filter = {},
config = {
mappings = {},
},
commands = {},
diagnostics = {},
}
--- Print error when setup not called.
--- f function to invoke
---@param f function
---@return fun(...) : any
local function wrap(f)
return function(...)
if vim.g.NvimTreeSetup == 1 then
return f(...)
else
notify.error "nvim-tree setup not called"
end
end
end
---Inject the node as the first argument if present otherwise do nothing.
---@param fn function function to invoke
local function wrap_node(fn)
return function(node, ...)
node = node or lib.get_node_at_cursor()
if node then
return fn(node, ...)
end
end
end
---Inject the node or nil as the first argument if absent.
---@param fn function function to invoke
local function wrap_node_or_nil(fn)
return function(node, ...)
node = node or lib.get_node_at_cursor()
return fn(node, ...)
end
end
---Inject the explorer as the first argument if present otherwise do nothing.
---@param fn function function to invoke
---@return fun(...) : any
local function wrap_explorer(fn)
return function(...)
local explorer = core.get_explorer()
if explorer then
return fn(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
---@field path string|nil path
---@field current_window boolean|nil default false
---@field winid number|nil
---@field find_file boolean|nil default false
---@field update_root boolean|nil default false
Api.tree.open = wrap(actions.tree.open.fn)
Api.tree.focus = Api.tree.open
---@class ApiTreeToggleOpts
---@field path string|nil
---@field current_window boolean|nil default false
---@field winid number|nil
---@field find_file boolean|nil default false
---@field update_root boolean|nil default false
---@field focus boolean|nil default true
Api.tree.toggle = wrap(actions.tree.toggle.fn)
Api.tree.close = wrap_explorer_member("view", "close")
Api.tree.close_in_this_tab = wrap_explorer_member("view", "close_this_tab_only")
Api.tree.close_in_all_tabs = wrap_explorer_member("view", "close_all_tabs")
Api.tree.reload = wrap(actions.reloaders.reload_explorer)
---@class ApiTreeResizeOpts
---@field width string|function|number|table|nil
---@field absolute number|nil
---@field relative number|nil
Api.tree.resize = wrap(actions.tree.resize.fn)
Api.tree.change_root = wrap(function(...)
require("nvim-tree").change_dir(...)
end)
Api.tree.change_root_to_node = wrap_node(function(node)
if node.name == ".." then
actions.root.change_dir.fn ".."
elseif node.nodes ~= nil then
actions.root.change_dir.fn(lib.get_last_group_node(node).absolute_path)
end
end)
Api.tree.change_root_to_parent = wrap_node(actions.root.dir_up.fn)
Api.tree.get_node_under_cursor = wrap(lib.get_node_at_cursor)
Api.tree.get_nodes = wrap(lib.get_nodes)
---@class ApiTreeFindFileOpts
---@field buf string|number|nil
---@field open boolean|nil default false
---@field current_window boolean|nil default false
---@field winid number|nil
---@field update_root boolean|nil default false
---@field focus boolean|nil default false
Api.tree.find_file = wrap(actions.tree.find_file.fn)
Api.tree.search_node = wrap(actions.finders.search_node.fn)
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.toggle_enable_filters = wrap(actions.tree.modifiers.toggles.enable)
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_no_buffer_filter = wrap(actions.tree.modifiers.toggles.no_buffer)
Api.tree.toggle_custom_filter = wrap(actions.tree.modifiers.toggles.custom)
Api.tree.toggle_hidden_filter = wrap(actions.tree.modifiers.toggles.dotfiles)
Api.tree.toggle_no_bookmark_filter = wrap(actions.tree.modifiers.toggles.no_bookmark)
Api.tree.toggle_help = wrap(help.toggle)
Api.tree.is_tree_buf = wrap(utils.is_nvim_tree_buf)
---@class ApiTreeIsVisibleOpts
---@field tabpage number|nil
---@field any_tabpage boolean|nil default false
Api.tree.is_visible = wrap_explorer_member("view", "is_visible")
---@class ApiTreeWinIdOpts
---@field tabpage number|nil default nil
Api.tree.winid = wrap_explorer_member("view", "winid")
Api.fs.create = wrap_node_or_nil(actions.fs.create_file.fn)
Api.fs.remove = wrap_node(actions.fs.remove_file.fn)
Api.fs.trash = wrap_node(actions.fs.trash.fn)
Api.fs.rename_node = wrap_node(actions.fs.rename_file.fn ":t")
Api.fs.rename = wrap_node(actions.fs.rename_file.fn ":t")
Api.fs.rename_sub = wrap_node(actions.fs.rename_file.fn ":p:h")
Api.fs.rename_basename = wrap_node(actions.fs.rename_file.fn ":t:r")
Api.fs.rename_full = wrap_node(actions.fs.rename_file.fn ":p")
Api.fs.cut = wrap_node(actions.fs.copy_paste.cut)
Api.fs.paste = wrap_node(actions.fs.copy_paste.paste)
Api.fs.clear_clipboard = wrap(actions.fs.copy_paste.clear_clipboard)
Api.fs.print_clipboard = wrap(actions.fs.copy_paste.print_clipboard)
Api.fs.copy.node = wrap_node(actions.fs.copy_paste.copy)
Api.fs.copy.absolute_path = wrap_node(actions.fs.copy_paste.copy_absolute_path)
Api.fs.copy.filename = wrap_node(actions.fs.copy_paste.copy_filename)
Api.fs.copy.basename = wrap_node(actions.fs.copy_paste.copy_basename)
Api.fs.copy.relative_path = wrap_node(actions.fs.copy_paste.copy_path)
---@param mode string
---@param node table
local function edit(mode, node)
local path = node.absolute_path
if node.link_to and not node.nodes then
path = node.link_to
end
actions.node.open_file.fn(mode, path)
end
---@param mode string
---@return fun(node: table)
local function open_or_expand_or_dir_up(mode, toggle_group)
return function(node)
if node.name == ".." then
actions.root.change_dir.fn ".."
elseif node.nodes then
lib.expand_or_collapse(node, toggle_group)
elseif not toggle_group then
edit(mode, node)
end
end
end
Api.node.open.edit = wrap_node(open_or_expand_or_dir_up "edit")
Api.node.open.drop = wrap_node(open_or_expand_or_dir_up "drop")
Api.node.open.tab_drop = wrap_node(open_or_expand_or_dir_up "tab_drop")
Api.node.open.replace_tree_buffer = wrap_node(open_or_expand_or_dir_up "edit_in_place")
Api.node.open.no_window_picker = wrap_node(open_or_expand_or_dir_up "edit_no_picker")
Api.node.open.vertical = wrap_node(open_or_expand_or_dir_up "vsplit")
Api.node.open.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.toggle_group_empty = wrap_node(open_or_expand_or_dir_up("toggle_group_empty", true))
Api.node.open.preview = wrap_node(open_or_expand_or_dir_up "preview")
Api.node.open.preview_no_picker = wrap_node(open_or_expand_or_dir_up "preview_no_picker")
Api.node.show_info_popup = wrap_node(actions.node.file_popup.toggle_file_info)
Api.node.run.cmd = wrap_node(actions.node.run_command.run_file_command)
Api.node.run.system = wrap_node(actions.node.system_open.fn)
Api.node.navigate.sibling.next = wrap_node(actions.moves.sibling.fn "next")
Api.node.navigate.sibling.prev = wrap_node(actions.moves.sibling.fn "prev")
Api.node.navigate.sibling.first = wrap_node(actions.moves.sibling.fn "first")
Api.node.navigate.sibling.last = wrap_node(actions.moves.sibling.fn "last")
Api.node.navigate.parent = wrap_node(actions.moves.parent.fn(false))
Api.node.navigate.parent_close = wrap_node(actions.moves.parent.fn(true))
Api.node.navigate.git.next = wrap_node(actions.moves.item.fn { where = "next", what = "git" })
Api.node.navigate.git.next_skip_gitignored = wrap_node(actions.moves.item.fn { where = "next", what = "git", skip_gitignored = true })
Api.node.navigate.git.next_recursive = wrap_node(actions.moves.item.fn { where = "next", what = "git", recurse = true })
Api.node.navigate.git.prev = wrap_node(actions.moves.item.fn { where = "prev", what = "git" })
Api.node.navigate.git.prev_skip_gitignored = wrap_node(actions.moves.item.fn { where = "prev", what = "git", skip_gitignored = true })
Api.node.navigate.git.prev_recursive = wrap_node(actions.moves.item.fn { where = "prev", what = "git", recurse = true })
Api.node.navigate.diagnostics.next = wrap_node(actions.moves.item.fn { where = "next", what = "diag" })
Api.node.navigate.diagnostics.next_recursive = wrap_node(actions.moves.item.fn { where = "next", what = "diag", recurse = true })
Api.node.navigate.diagnostics.prev = wrap_node(actions.moves.item.fn { where = "prev", what = "diag" })
Api.node.navigate.diagnostics.prev_recursive = wrap_node(actions.moves.item.fn { where = "prev", what = "diag", recurse = true })
Api.node.navigate.opened.next = wrap_node(actions.moves.item.fn { where = "next", what = "opened" })
Api.node.navigate.opened.prev = wrap_node(actions.moves.item.fn { where = "prev", what = "opened" })
Api.git.reload = wrap(actions.reloaders.reload_git)
Api.events.subscribe = events.subscribe
Api.events.Event = events.Event
Api.live_filter.start = wrap_explorer_member("live_filter", "start_filtering")
Api.live_filter.clear = wrap_explorer_member("live_filter", "clear_filter")
Api.marks.get = wrap_node(wrap_explorer_member("marks", "get_mark"))
Api.marks.list = wrap_explorer_member("marks", "get_marks")
Api.marks.toggle = wrap_node(wrap_explorer_member("marks", "toggle_mark"))
Api.marks.clear = wrap_explorer_member("marks", "clear_marks")
Api.marks.bulk.delete = wrap_explorer(marks_bulk_delete.bulk_delete)
Api.marks.bulk.trash = wrap_explorer(marks_bulk_trash.bulk_trash)
Api.marks.bulk.move = wrap_explorer(marks_bulk_move.bulk_move)
Api.marks.navigate.next = wrap(marks_navigation.next)
Api.marks.navigate.prev = wrap(marks_navigation.prev)
Api.marks.navigate.select = wrap(marks_navigation.select)
Api.config.mappings.get_keymap = wrap(keymap.get_keymap)
Api.config.mappings.get_keymap_default = wrap(keymap.get_keymap_default)
Api.config.mappings.default_on_attach = keymap.default_on_attach
Api.diagnostics.hi_test = wrap(appearance_diagnostics.hi_test)
Api.commands.get = wrap(function()
return require("nvim-tree.commands").get()
end)
return Api

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,206 @@
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 = "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" },
-- 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

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

@@ -0,0 +1,50 @@
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
-- no need to keep going if already recorded
-- This also prevents an infinite loop
do
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,102 +0,0 @@
local api = vim.api
local icons = require "nvim-tree.renderer.icon-config"
local M = {}
local function get_color_from_hl(hl_name, fallback)
local id = vim.api.nvim_get_hl_id_by_name(hl_name)
if not id then
return fallback
end
local foreground = vim.fn.synIDattr(vim.fn.synIDtrans(id), "fg")
if not foreground or foreground == "" then
return fallback
end
return foreground
end
local function get_colors()
return {
red = vim.g.terminal_color_1 or get_color_from_hl("Keyword", "Red"),
green = vim.g.terminal_color_2 or get_color_from_hl("Character", "Green"),
yellow = vim.g.terminal_color_3 or get_color_from_hl("PreProc", "Yellow"),
blue = vim.g.terminal_color_4 or get_color_from_hl("Include", "Blue"),
purple = vim.g.terminal_color_5 or get_color_from_hl("Define", "Purple"),
cyan = vim.g.terminal_color_6 or get_color_from_hl("Conditional", "Cyan"),
dark_red = vim.g.terminal_color_9 or get_color_from_hl("Keyword", "DarkRed"),
orange = vim.g.terminal_color_11 or get_color_from_hl("Number", "Orange"),
}
end
local function get_hl_groups()
local colors = get_colors()
return {
IndentMarker = { fg = "#8094b4" },
Symlink = { gui = "bold", fg = colors.cyan },
FolderIcon = { fg = "#8094b4" },
RootFolder = { fg = colors.purple },
ExecFile = { gui = "bold", fg = colors.green },
SpecialFile = { gui = "bold,underline", fg = colors.yellow },
ImageFile = { gui = "bold", fg = colors.purple },
OpenedFile = { gui = "bold", fg = colors.green },
GitDirty = { fg = colors.dark_red },
GitDeleted = { fg = colors.dark_red },
GitStaged = { fg = colors.green },
GitMerge = { fg = colors.orange },
GitRenamed = { fg = colors.purple },
GitNew = { fg = colors.yellow },
WindowPicker = { gui = "bold", fg = "#ededed", bg = "#4493c8" },
}
end
local function get_links()
return {
FolderName = "Directory",
EmptyFolderName = "Directory",
OpenedFolderName = "Directory",
Normal = "Normal",
NormalNC = "NvimTreeNormal",
EndOfBuffer = "EndOfBuffer",
CursorLine = "CursorLine",
VertSplit = "VertSplit",
WinSeparator = "NvimTreeVertSplit",
CursorColumn = "CursorColumn",
FileDirty = "NvimTreeGitDirty",
FileNew = "NvimTreeGitNew",
FileRenamed = "NvimTreeGitRenamed",
FileMerge = "NvimTreeGitMerge",
FileStaged = "NvimTreeGitStaged",
FileDeleted = "NvimTreeGitDeleted",
Popup = "Normal",
GitIgnored = "Comment",
StatusLine = "StatusLine",
StatusLineNC = "StatusLineNC",
SignColumn = "NvimTreeNormal",
}
end
function M.setup()
if icons.get_config().show_file_icon and icons.get_config().has_devicons then
require("nvim-web-devicons").setup()
end
local higlight_groups = get_hl_groups()
for k, d in pairs(higlight_groups) do
local gui = d.gui and " gui=" .. d.gui or ""
local fg = d.fg and " guifg=" .. d.fg or ""
local bg = d.bg and " guibg=" .. d.bg or ""
api.nvim_command("hi def NvimTree" .. k .. gui .. fg .. bg)
end
local links = get_links()
for k, d in pairs(links) do
api.nvim_command("hi def link NvimTree" .. k .. " " .. d)
end
end
return M

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

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

View File

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

View File

@@ -1,33 +1,51 @@
local events = require "nvim-tree.events" local events = require "nvim-tree.events"
local explorer = require "nvim-tree.explorer" local explorer = require "nvim-tree.explorer"
local view = require "nvim-tree.view" local log = require "nvim-tree.log"
local M = {} local M = {}
TreeExplorer = nil ---@type Explorer|nil
local TreeExplorer = nil
local first_init_done = false local first_init_done = false
---@param foldername string
function M.init(foldername) function M.init(foldername)
local profile = log.profile_start("core init %s", foldername)
if TreeExplorer then
TreeExplorer:destroy()
end
TreeExplorer = explorer.Explorer.new(foldername) TreeExplorer = explorer.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
end end
log.profile_end(profile)
end end
---@return Explorer|nil
function M.get_explorer() function M.get_explorer()
return TreeExplorer return TreeExplorer
end end
function M.get_cwd() function M.reset_explorer()
return TreeExplorer.cwd TreeExplorer = nil
end end
---@return string|nil
function M.get_cwd()
return TreeExplorer and TreeExplorer.absolute_path
end
---@return integer
function M.get_nodes_starting_line() function M.get_nodes_starting_line()
local offset = 1 local offset = 1
if view.is_root_folder_visible(M.get_cwd()) then if TreeExplorer and TreeExplorer.view:is_root_folder_visible(M.get_cwd()) then
offset = offset + 1 offset = offset + 1
end end
if TreeExplorer and TreeExplorer.live_filter.filter then
return offset + 1
end
return offset return offset
end end

View File

@@ -1,177 +1,220 @@
local a = vim.api
local utils = require "nvim-tree.utils" local utils = require "nvim-tree.utils"
local view = require "nvim-tree.view"
local core = require "nvim-tree.core"
local log = require "nvim-tree.log" local log = require "nvim-tree.log"
local M = {} local M = {}
local GROUP = "NvimTreeDiagnosticSigns" ---COC severity level strings to LSP severity levels
---@enum COC_SEVERITY_LEVELS
local function get_lowest_severity(diagnostics) local COC_SEVERITY_LEVELS = {
local severity = math.huge Error = 1,
for _, v in ipairs(diagnostics) do Warning = 2,
if v.severity < severity then Information = 3,
severity = v.severity Hint = 4,
end
end
return severity
end
local severity_levels = { Error = 1, Warning = 2, Information = 3, Hint = 4 }
local sign_names = {
{ "NvimTreeSignError", "NvimTreeLspDiagnosticsError" },
{ "NvimTreeSignWarning", "NvimTreeLspDiagnosticsWarning" },
{ "NvimTreeSignInformation", "NvimTreeLspDiagnosticsInformation" },
{ "NvimTreeSignHint", "NvimTreeLspDiagnosticsHint" },
} }
local function add_sign(linenr, severity) ---Absolute Node path to LSP severity level
local buf = view.get_bufnr() ---@alias NodeSeverities table<string, lsp.DiagnosticSeverity>
if not a.nvim_buf_is_valid(buf) or not a.nvim_buf_is_loaded(buf) then
return ---@class DiagStatus
end ---@field value lsp.DiagnosticSeverity|nil
local sign_name = sign_names[severity][1] ---@field cache_version integer
vim.fn.sign_place(1, GROUP, sign_name, buf, { lnum = linenr })
--- The buffer-severity mappings derived during the last diagnostic list update.
---@type NodeSeverities
local NODE_SEVERITIES = {}
---The cache version number of the buffer-severity mappings.
---@type integer
local NODE_SEVERITIES_VERSION = 0
---@param path string
---@return string
local function uniformize_path(path)
return utils.canonical_path(path:gsub("\\", "/"))
end end
---Marshal severities from LSP. Does nothing when LSP disabled.
---@return NodeSeverities
local function from_nvim_lsp() local function from_nvim_lsp()
local buffer_severity = {} local buffer_severity = {}
-- vim.lsp.diagnostic.get_all was deprecated in nvim 0.7 and replaced with vim.diagnostic.get -- is_enabled is not present in all 0.10 builds/releases, see #2781
-- This conditional can be removed when the minimum required version of nvim is changed to 0.7. local is_enabled = false
if vim.diagnostic then if vim.fn.has "nvim-0.10" == 1 and type(vim.diagnostic.is_enabled) == "function" then
-- nvim version >= 0.7 is_enabled = vim.diagnostic.is_enabled()
for _, diagnostic in ipairs(vim.diagnostic.get()) do elseif type(vim.diagnostic.is_disabled) == "function" then ---@diagnostic disable-line: deprecated
local buf = diagnostic.bufnr is_enabled = not vim.diagnostic.is_disabled() ---@diagnostic disable-line: deprecated
if a.nvim_buf_is_valid(buf) then end
local bufname = a.nvim_buf_get_name(buf)
local lowest_severity = buffer_severity[bufname] if is_enabled then
if not lowest_severity or diagnostic.severity < lowest_severity then for _, diagnostic in ipairs(vim.diagnostic.get(nil, { severity = M.severity })) do
if diagnostic.severity and diagnostic.bufnr and vim.api.nvim_buf_is_valid(diagnostic.bufnr) then
local bufname = uniformize_path(vim.api.nvim_buf_get_name(diagnostic.bufnr))
if not buffer_severity[bufname] or diagnostic.severity < buffer_severity[bufname] then
buffer_severity[bufname] = diagnostic.severity buffer_severity[bufname] = diagnostic.severity
end end
end end
end end
else
-- nvim version < 0.7
for buf, diagnostics in pairs(vim.lsp.diagnostic.get_all()) do
if a.nvim_buf_is_valid(buf) then
local bufname = a.nvim_buf_get_name(buf)
if not buffer_severity[bufname] then
local severity = get_lowest_severity(diagnostics)
buffer_severity[bufname] = severity
end
end
end
end end
return buffer_severity return buffer_severity
end end
local function from_coc() ---Severity is within diagnostics.severity.min, diagnostics.severity.max
if vim.g.coc_service_initialized ~= 1 then ---@param severity lsp.DiagnosticSeverity
return {} ---@param config table
---@return boolean
local function is_severity_in_range(severity, config)
return config.max <= severity and severity <= config.min
end end
local diagnostic_list = vim.fn.CocAction "diagnosticList" ---Handle any COC exceptions, preventing any propagation
if type(diagnostic_list) ~= "table" or vim.tbl_isempty(diagnostic_list) then ---@param err string
return {} local function handle_coc_exception(err)
log.line("diagnostics", "handle_coc_exception: %s", vim.inspect(err))
local notify = true
-- avoid distractions on interrupts (CTRL-C)
if err:find "Vim:Interrupt" or err:find "Keyboard interrupt" then
notify = false
end end
local buffer_severity = {} if notify then
local diagnostics = {} require("nvim-tree.notify").error("Diagnostics update from coc.nvim failed. " .. vim.inspect(err))
for _, diagnostic in ipairs(diagnostic_list) do
local bufname = diagnostic.file
local severity = severity_levels[diagnostic.severity]
local severity_list = diagnostics[bufname] or {}
table.insert(severity_list, severity)
diagnostics[bufname] = severity_list
end
for bufname, severity_list in pairs(diagnostics) do
if not buffer_severity[bufname] then
local severity = math.min(unpack(severity_list))
buffer_severity[bufname] = severity
end end
end end
return buffer_severity ---COC service initialized
end ---@return boolean
local function is_using_coc() local function is_using_coc()
return vim.g.coc_service_initialized == 1 return vim.g.coc_service_initialized == 1
end end
function M.clear() ---Marshal severities from COC. Does nothing when COC service not started.
if not M.enable or not view.is_buf_valid(view.get_bufnr()) then ---@return NodeSeverities
return local function from_coc()
if not is_using_coc() then
return {}
end end
vim.fn.sign_unplace(GROUP) local ok, diagnostic_list = xpcall(function()
return vim.fn.CocAction "diagnosticList"
end, handle_coc_exception)
if not ok or type(diagnostic_list) ~= "table" or vim.tbl_isempty(diagnostic_list) then
return {}
end end
function M.update() local buffer_severity = {}
if not M.enable or not core.get_explorer() or not view.is_buf_valid(view.get_bufnr()) then for _, diagnostic in ipairs(diagnostic_list) do
return local bufname = uniformize_path(diagnostic.file)
local coc_severity = COC_SEVERITY_LEVELS[diagnostic.severity]
local highest_severity = buffer_severity[bufname] or coc_severity
if is_severity_in_range(highest_severity, M.severity) then
buffer_severity[bufname] = math.min(highest_severity, coc_severity)
end
end end
local ps = log.profile_start "diagnostics update"
log.line("diagnostics", "update")
local buffer_severity return buffer_severity
if is_using_coc() then end
buffer_severity = from_coc()
---Maybe retrieve severity level from the cache
---@param node Node
---@return DiagStatus
local function from_cache(node)
local nodepath = uniformize_path(node.absolute_path)
local max_severity = nil
if not node.nodes then
-- direct cache hit for files
max_severity = NODE_SEVERITIES[nodepath]
else else
buffer_severity = from_nvim_lsp() -- dirs should be searched in the list of cached buffer names by prefix
for bufname, severity in pairs(NODE_SEVERITIES) do
local node_contains_buf = vim.startswith(bufname, nodepath .. "/")
if node_contains_buf then
if severity == M.severity.max then
max_severity = severity
break
else
max_severity = math.min(max_severity or severity, severity)
end
end
end
end
return { value = max_severity, cache_version = NODE_SEVERITIES_VERSION }
end end
M.clear() ---Fired on DiagnosticChanged and CocDiagnosticChanged events:
for bufname, severity in pairs(buffer_severity) do ---debounced retrieval, cache update, version increment and draw
local bufpath = utils.canonical_path(bufname) function M.update()
log.line("diagnostics", " bufpath '%s' severity %d", bufpath, severity) if not M.enable then
if 0 < severity and severity < 5 then return
local nodes_by_line = utils.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line()) end
for line, node in pairs(nodes_by_line) do utils.debounce("diagnostics", M.debounce_delay, function()
local nodepath = utils.canonical_path(node.absolute_path) local profile = log.profile_start "diagnostics update"
log.line("diagnostics", " %d checking nodepath '%s'", line, nodepath) if is_using_coc() then
if M.show_on_dirs and vim.startswith(bufpath, nodepath) then NODE_SEVERITIES = from_coc()
log.line("diagnostics", " matched fold node '%s'", node.absolute_path) else
add_sign(line, severity) NODE_SEVERITIES = from_nvim_lsp()
elseif nodepath == bufpath then end
log.line("diagnostics", " matched file node '%s'", node.absolute_path) NODE_SEVERITIES_VERSION = NODE_SEVERITIES_VERSION + 1
add_sign(line, severity) if log.enabled "diagnostics" then
for bufname, severity in pairs(NODE_SEVERITIES) do
log.line("diagnostics", "Indexing bufname '%s' with severity %d", bufname, severity)
end end
end end
log.profile_end(profile)
local explorer = require "nvim-tree.core".get_explorer()
if explorer and explorer.view:is_buf_valid(explorer.view:get_bufnr()) then
require("nvim-tree.renderer").draw()
end end
end end)
log.profile_end(ps, "diagnostics update")
end end
local links = { ---Maybe retrieve diagnostic status for a node.
NvimTreeLspDiagnosticsError = "DiagnosticError", ---Returns cached value when node's version matches.
NvimTreeLspDiagnosticsWarning = "DiagnosticWarn", ---@param node Node
NvimTreeLspDiagnosticsInformation = "DiagnosticInfo", ---@return DiagStatus|nil
NvimTreeLspDiagnosticsHint = "DiagnosticHint", function M.get_diag_status(node)
} if not M.enable then
return nil
end
-- dir but we shouldn't show on dirs at all
if node.nodes ~= nil and not M.show_on_dirs then
return nil
end
-- here, we do a lazy update of the diagnostic status carried by the node.
-- This is by design, as diagnostics and nodes live in completely separate
-- worlds, and this module is the link between the two
if not node.diag_status or node.diag_status.cache_version < NODE_SEVERITIES_VERSION then
node.diag_status = from_cache(node)
end
-- file
if not node.nodes then
return node.diag_status
end
-- dir is closed or we should show on open_dirs
if not node.open or M.show_on_open_dirs then
return node.diag_status
end
return nil
end
function M.setup(opts) function M.setup(opts)
M.enable = opts.diagnostics.enable M.enable = opts.diagnostics.enable
M.show_on_dirs = opts.diagnostics.show_on_dirs M.debounce_delay = opts.diagnostics.debounce_delay
vim.fn.sign_define(sign_names[1][1], { text = opts.diagnostics.icons.error, texthl = sign_names[1][2] }) M.severity = opts.diagnostics.severity
vim.fn.sign_define(sign_names[2][1], { text = opts.diagnostics.icons.warning, texthl = sign_names[2][2] })
vim.fn.sign_define(sign_names[3][1], { text = opts.diagnostics.icons.info, texthl = sign_names[3][2] })
vim.fn.sign_define(sign_names[4][1], { text = opts.diagnostics.icons.hint, texthl = sign_names[4][2] })
for lhs, rhs in pairs(links) do
vim.cmd("hi def link " .. lhs .. " " .. rhs)
end
if M.enable then if M.enable then
log.line("diagnostics", "setup") log.line("diagnostics", "setup")
vim.cmd "au DiagnosticChanged * lua require'nvim-tree.diagnostics'.update()"
vim.cmd "au User CocDiagnosticChange lua require'nvim-tree.diagnostics'.update()"
end end
M.show_on_dirs = opts.diagnostics.show_on_dirs
M.show_on_open_dirs = opts.diagnostics.show_on_open_dirs
end end
return M return M

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

@@ -0,0 +1,22 @@
local M = {}
---Setup options for "highlight_*"
---@enum HL_POSITION
M.HL_POSITION = {
none = 0,
icon = 1,
name = 2,
all = 4,
}
---Setup options for "*_placement"
---@enum ICON_PLACEMENT
M.ICON_PLACEMENT = {
none = 0,
signcolumn = 1,
before = 2,
after = 3,
right_align = 4,
}
return M

View File

@@ -1,129 +1,119 @@
local notify = require "nvim-tree.notify"
local M = {} local M = {}
local global_handlers = {} local global_handlers = {}
local Event = { M.Event = {
Ready = "Ready", Ready = "Ready",
WillRenameNode = "WillRenameNode",
NodeRenamed = "NodeRenamed", NodeRenamed = "NodeRenamed",
TreeOpen = "TreeOpen", TreeOpen = "TreeOpen",
TreeClose = "TreeClose", TreeClose = "TreeClose",
WillCreateFile = "WillCreateFile",
FileCreated = "FileCreated", FileCreated = "FileCreated",
WillRemoveFile = "WillRemoveFile",
FileRemoved = "FileRemoved", FileRemoved = "FileRemoved",
FolderCreated = "FolderCreated", FolderCreated = "FolderCreated",
FolderRemoved = "FolderRemoved", FolderRemoved = "FolderRemoved",
Resize = "Resize",
TreeAttachedPost = "TreeAttachedPost",
TreeRendered = "TreeRendered",
} }
---@param event_name string
---@return table
local function get_handlers(event_name) local function get_handlers(event_name)
return global_handlers[event_name] or {} return global_handlers[event_name] or {}
end end
local function register_handler(event_name, handler) ---@param event_name string
---@param handler function
function M.subscribe(event_name, handler)
local handlers = get_handlers(event_name) local handlers = get_handlers(event_name)
table.insert(handlers, handler) table.insert(handlers, handler)
global_handlers[event_name] = handlers global_handlers[event_name] = handlers
end end
---@param event_name string
---@param payload table|nil
local function dispatch(event_name, payload) local function dispatch(event_name, payload)
for _, handler in pairs(get_handlers(event_name)) do for _, handler in pairs(get_handlers(event_name)) do
local success, error = pcall(handler, payload) local success, error = pcall(handler, payload)
if not success then if not success then
vim.api.nvim_err_writeln("Handler for event " .. event_name .. " errored. " .. vim.inspect(error)) notify.error("Handler for event " .. event_name .. " errored. " .. vim.inspect(error))
end end
end end
end end
--@private --@private
function M._dispatch_ready() function M._dispatch_ready()
dispatch(Event.Ready) dispatch(M.Event.Ready)
end
--@private
function M._dispatch_will_rename_node(old_name, new_name)
dispatch(M.Event.WillRenameNode, { old_name = old_name, new_name = new_name })
end end
--@private --@private
function M._dispatch_node_renamed(old_name, new_name) function M._dispatch_node_renamed(old_name, new_name)
dispatch(Event.NodeRenamed, { old_name = old_name, new_name = new_name }) dispatch(M.Event.NodeRenamed, { old_name = old_name, new_name = new_name })
end
--@private
function M._dispatch_will_remove_file(fname)
dispatch(M.Event.WillRemoveFile, { fname = fname })
end end
--@private --@private
function M._dispatch_file_removed(fname) function M._dispatch_file_removed(fname)
dispatch(Event.FileRemoved, { fname = fname }) dispatch(M.Event.FileRemoved, { fname = fname })
end
--@private
function M._dispatch_will_create_file(fname)
dispatch(M.Event.WillCreateFile, { fname = fname })
end end
--@private --@private
function M._dispatch_file_created(fname) function M._dispatch_file_created(fname)
dispatch(Event.FileCreated, { fname = fname }) dispatch(M.Event.FileCreated, { fname = fname })
end end
--@private --@private
function M._dispatch_folder_created(folder_name) function M._dispatch_folder_created(folder_name)
dispatch(Event.FolderCreated, { folder_name = folder_name }) dispatch(M.Event.FolderCreated, { folder_name = folder_name })
end end
--@private --@private
function M._dispatch_folder_removed(folder_name) function M._dispatch_folder_removed(folder_name)
dispatch(Event.FolderRemoved, { folder_name = folder_name }) dispatch(M.Event.FolderRemoved, { folder_name = folder_name })
end end
--@private --@private
function M._dispatch_on_tree_open() function M._dispatch_on_tree_open()
dispatch(Event.TreeOpen, nil) dispatch(M.Event.TreeOpen, nil)
end end
--@private --@private
function M._dispatch_on_tree_close() function M._dispatch_on_tree_close()
dispatch(Event.TreeClose, nil) dispatch(M.Event.TreeClose, nil)
end end
--Registers a handler for the Ready event. --@private
--@param handler (function) Handler with the signature `function()` function M._dispatch_on_tree_resize(size)
function M.on_nvim_tree_ready(handler) dispatch(M.Event.Resize, size)
register_handler(Event.Ready, handler)
end end
--Registers a handler for the NodeRenamed event. --@private
--@param handler (function) Handler with the signature function(payload), where payload is a table containing: function M._dispatch_tree_attached_post(buf)
-- - old_name (string) Absolute path to the old node location. dispatch(M.Event.TreeAttachedPost, buf)
-- - new_name (string) Absolute path to the new node location.
function M.on_node_renamed(handler)
register_handler(Event.NodeRenamed, handler)
end end
--Registers a handler for the FileCreated event. --@private
--@param handler (function) Handler with the signature function(payload), where payload is a table containing: function M._dispatch_on_tree_rendered(bufnr, winnr)
-- - fname (string) Absolute path to the created file. dispatch(M.Event.TreeRendered, { bufnr = bufnr, winnr = winnr })
function M.on_file_created(handler)
register_handler(Event.FileCreated, handler)
end
--Registers a handler for the FileRemoved event.
--@param handler (function) Handler with the signature function(payload), where payload is a table containing:
-- - fname (string) Absolute path to the removed file.
function M.on_file_removed(handler)
register_handler(Event.FileRemoved, handler)
end
--Registers a handler for the FolderCreated event.
--@param handler (function) Handler with the signature function(payload), where payload is a table containing:
-- - folder_name (string) Absolute path to the created folder.
function M.on_folder_created(handler)
register_handler(Event.FolderCreated, handler)
end
--Registers a handler for the FolderRemoved event.
--@param handler (function) Handler with the signature function(payload), where payload is a table containing:
-- - folder_name (string) Absolute path to the removed folder.
function M.on_folder_removed(handler)
register_handler(Event.FolderRemoved, handler)
end
--Registers a handler for the TreeOpen event.
--@param handler (function) Handler with the signature function(payload)
function M.on_tree_open(handler)
register_handler(Event.TreeOpen, handler)
end
--Registers a handler for the TreeClose event.
--@param handler (function) Handler with the signature function(payload)
function M.on_tree_close(handler)
register_handler(Event.TreeClose, handler)
end end
return M return M

View File

@@ -1,40 +0,0 @@
local uv = vim.loop
local M = {}
local function get_dir_git_status(parent_ignored, status, absolute_path)
if parent_ignored then
return "!!"
end
local dir_status = status.dirs and status.dirs[absolute_path]
local file_status = status.files and status.files[absolute_path]
return dir_status or file_status
end
local function get_git_status(parent_ignored, status, absolute_path)
return parent_ignored and "!!" or status.files and status.files[absolute_path]
end
function M.has_one_child_folder(node)
return #node.nodes == 1 and node.nodes[1].nodes and uv.fs_access(node.nodes[1].absolute_path, "R")
end
function M.update_git_status(node, parent_ignored, status)
-- status of the node's absolute path
if node.nodes then
node.git_status = get_dir_git_status(parent_ignored, status, node.absolute_path)
else
node.git_status = get_git_status(parent_ignored, status, node.absolute_path)
end
-- status of the link target, if the link itself is not dirty
if node.link_to and not node.git_status then
if node.nodes then
node.git_status = get_dir_git_status(parent_ignored, status, node.link_to)
else
node.git_status = get_git_status(parent_ignored, status, node.link_to)
end
end
end
return M

View File

@@ -1,82 +1,94 @@
local api = vim.api
local uv = vim.loop
local utils = require "nvim-tree.utils" local utils = require "nvim-tree.utils"
local builders = require "nvim-tree.explorer.node-builders" local builders = require "nvim-tree.explorer.node-builders"
local common = require "nvim-tree.explorer.common" local explorer_node = require "nvim-tree.explorer.node"
local sorters = require "nvim-tree.explorer.sorters" local git = require "nvim-tree.git"
local filters = require "nvim-tree.explorer.filters" local log = require "nvim-tree.log"
local Watcher = require "nvim-tree.watcher"
local M = {} local M = {}
local function get_type_from(type_, cwd) ---@param handle uv.uv_fs_t
return type_ or (uv.fs_stat(cwd) or {}).type ---@param cwd string
end ---@param node Node
---@param git_status table
local function populate_children(handle, cwd, node, status) ---@param parent Explorer
local node_ignored = node.git_status == "!!" local function 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)
while true do while true do
local name, t = uv.fs_scandir_next(handle) local name, t = vim.loop.fs_scandir_next(handle)
if not name then if not name then
break break
end end
local nodes_by_path = utils.key_by(node.nodes, "absolute_path")
local abs = utils.path_join { cwd, name } local abs = utils.path_join { cwd, name }
t = get_type_from(t, abs) local profile = log.profile_start("explore populate_children %s", abs)
if
not filters.should_ignore(abs) ---@type uv.fs_stat.result|nil
and not filters.should_ignore_git(abs, status.files) local stat = vim.loop.fs_stat(abs)
and not nodes_by_path[abs]
then if not parent.filters:should_filter(abs, stat, filter_status) and not nodes_by_path[abs] and Watcher.is_fs_event_capable(abs) then
local child = nil local child = nil
if t == "directory" and uv.fs_access(abs, "R") then if t == "directory" and vim.loop.fs_access(abs, "R") then
child = builders.folder(node, abs, name, status, node_ignored) child = builders.folder(node, abs, name, stat)
elseif t == "file" then elseif t == "file" then
child = builders.file(node, abs, name, status, node_ignored) child = builders.file(node, abs, name, stat)
elseif t == "link" then elseif t == "link" then
local link = builders.link(node, abs, name, status, node_ignored) local link = builders.link(node, abs, name, stat)
if link.link_to ~= nil then if link.link_to ~= nil then
child = link child = link
end end
end end
if child then if child then
table.insert(node.nodes, child) table.insert(node.nodes, child)
common.update_git_status(child, node_ignored, status) nodes_by_path[child.absolute_path] = true
end explorer_node.update_git_status(child, node_ignored, git_status)
end
end end
end end
local function get_dir_handle(cwd) log.profile_end(profile)
local handle = uv.fs_scandir(cwd)
if type(handle) == "string" then
api.nvim_err_writeln(handle)
return
end end
return handle
end end
function M.explore(node, status) ---@param node Node
local cwd = node.cwd or node.link_to or node.absolute_path ---@param status table
local handle = get_dir_handle(cwd) ---@param parent Explorer
---@return Node[]|nil
function M.explore(node, status, parent)
local cwd = node.link_to or node.absolute_path
local handle = vim.loop.fs_scandir(cwd)
if not handle then if not handle then
return return
end end
populate_children(handle, cwd, node, status) local profile = log.profile_start("explore init %s", node.absolute_path)
local is_root = node.cwd ~= nil populate_children(handle, cwd, node, status, parent)
local child_folder_only = common.has_one_child_folder(node) and node.nodes[1]
if vim.g.nvim_tree_group_empty == 1 and not is_root and child_folder_only then 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 node.group_next = child_folder_only
local ns = M.explore(child_folder_only, status) local ns = M.explore(child_folder_only, child_status, parent)
node.nodes = ns or {} node.nodes = ns or {}
log.profile_end(profile)
return ns return ns
end end
sorters.merge_sort(node.nodes, sorters.node_comparator) parent.sorters:sort(node.nodes)
parent.live_filter:apply_filter(node)
log.profile_end(profile)
return node.nodes return node.nodes
end end
function M.setup(opts)
M.config = opts.renderer
end
return M return M

View File

@@ -1,12 +1,52 @@
local utils = require "nvim-tree.utils" local utils = require "nvim-tree.utils"
local M = { ---@class Filters to handle all opts.filters and related API
---@field config table hydrated user opts.filters
---@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 = {}, ignore_list = {},
exclude_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 function is_excluded(path) local custom_filter = opts.filters.custom
for _, node in ipairs(M.exclude_list) do 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
---@return boolean
local function is_excluded(self, path)
for _, node in ipairs(self.exclude_list) do
if path:match(node) then if path:match(node) then
return true return true
end end
@@ -14,28 +54,111 @@ local function is_excluded(path)
return false return false
end end
---Check if the given path should be ignored. ---Check if the given path is git clean/ignored
---@param path string Absolute path ---@param path string Absolute path
---@param git_status table from prepare
---@return boolean ---@return boolean
function M.should_ignore(path) local function git(self, path, git_status)
local basename = utils.path_basename(path) if type(git_status) ~= "table" or type(git_status.files) ~= "table" or type(git_status.dirs) ~= "table" then
if is_excluded(path) then
return false return false
end end
if M.config.filter_dotfiles then -- default status to clean
if basename:sub(1, 1) == "." then local status = git_status.files[path]
status = status or git_status.dirs.direct[path] and git_status.dirs.direct[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
if self.config.filter_git_ignored and status == "!!" then
return true return true
end end
-- filter clean
if self.config.filter_git_clean and not status then
return true
end end
if not M.config.filter_custom then
return false return false
end end
---Check if the given path has no listed buffer
---@param path string Absolute path
---@param bufinfo table vim.fn.getbufinfo { buflisted = 1 }
---@return boolean
local function buf(self, path, bufinfo)
if not self.config.filter_no_buffer or type(bufinfo) ~= "table" then
return false
end
-- filter files with no open buffer and directories containing no open buffers
for _, b in ipairs(bufinfo) do
if b.name == path or b.name:find(path .. "/", 1, true) then
return false
end
end
return true
end
---@param path string
---@return boolean
local function dotfile(self, path)
return self.config.filter_dotfiles and utils.path_basename(path):sub(1, 1) == "."
end
---@param path string
---@param path_type string|nil filetype of path
---@param bookmarks table<string, string|nil> path, filetype table of bookmarked files
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
---@param path string
---@return boolean
local function custom(self, path)
if not self.config.filter_custom then
return false
end
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
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
@@ -43,7 +166,7 @@ function M.should_ignore(path)
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
@@ -51,27 +174,53 @@ function M.should_ignore(path)
return false return false
end end
function M.should_ignore_git(path, status) ---Prepare arguments for should_filter. This is done prior to should_filter for efficiency reasons.
return M.config.filter_git_ignored ---@param git_status table|nil optional results of git.load_project_status(...)
and (M.config.filter_git_ignored and status and status[path] == "!!") ---@return table
and not is_excluded(path) --- git_status: reference
end --- bufinfo: empty unless no_buffer set: vim.fn.getbufinfo { buflisted = 1 }
--- bookmarks: absolute paths to boolean
function M.setup(opts) function Filters:prepare(git_status)
M.config = { local status = {
filter_custom = true, git_status = git_status or {},
filter_dotfiles = opts.filters.dotfiles, bufinfo = {},
filter_git_ignored = opts.git.ignore, bookmarks = {},
} }
M.exclude_list = opts.filters.exclude if self.config.filter_no_buffer then
status.bufinfo = vim.fn.getbufinfo { buflisted = 1 }
local custom_filter = opts.filters.custom
if custom_filter and #custom_filter > 0 then
for _, filter_name in pairs(custom_filter) do
M.ignore_list[filter_name] = true
end end
local explorer = require("nvim-tree.core").get_explorer()
if explorer then
for _, node in pairs(explorer.marks:get_marks()) do
status.bookmarks[node.absolute_path] = node.type
end end
end end
return M return status
end
---Check if the given path should be filtered.
---@param path string Absolute path
---@param fs_stat uv.fs_stat.result|nil fs_stat of file
---@param status table from prepare
---@return boolean
function Filters:should_filter(path, fs_stat, status)
if not self.config.enable then
return false
end
-- exclusions override all filters
if is_excluded(self, path) then
return false
end
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
return Filters

View File

@@ -1,38 +1,92 @@
local uv = vim.loop
local git = require "nvim-tree.git" local git = require "nvim-tree.git"
local notify = require "nvim-tree.notify"
local watch = require "nvim-tree.explorer.watch"
local explorer_node = require "nvim-tree.explorer.node"
local Filters = require "nvim-tree.explorer.filters"
local Marks = require "nvim-tree.marks"
local LiveFilter = require "nvim-tree.explorer.live-filter"
local Sorters = require "nvim-tree.explorer.sorters"
local View = require "nvim-tree.explorer.view"
local M = {} local M = {}
M.explore = require("nvim-tree.explorer.explore").explore M.explore = require("nvim-tree.explorer.explore").explore
M.reload = require("nvim-tree.explorer.reload").reload M.reload = require("nvim-tree.explorer.reload").reload
---@class Explorer
---@field absolute_path string
---@field nodes Node[]
---@field open boolean
---@field filters Filters
---@field live_filter LiveFilter
---@field sorters Sorter
---@field marks Marks
local Explorer = {} local Explorer = {}
Explorer.__index = Explorer Explorer.__index = Explorer
function Explorer.new(cwd) ---@param path string|nil
cwd = uv.fs_realpath(cwd or uv.cwd()) ---@return Explorer|nil
function Explorer.new(path)
local err
if path then
path, err = vim.loop.fs_realpath(path)
else
path, err = vim.loop.cwd()
end
if not path then
notify.error(err)
return
end
---@class Explorer
local explorer = setmetatable({ local explorer = setmetatable({
cwd = cwd, absolute_path = path,
nodes = {}, nodes = {},
open = true,
marks = Marks:new(),
sorters = Sorters:new(M.config),
view = View:new(M.config),
}, Explorer) }, Explorer)
explorer.watcher = watch.create_watcher(explorer)
explorer.filters = Filters:new(M.config, explorer)
explorer.live_filter = LiveFilter:new(M.config, explorer)
explorer:_load(explorer) explorer:_load(explorer)
return explorer return explorer
end end
---@private
---@param node Node
function Explorer:_load(node) function Explorer:_load(node)
local cwd = node.cwd or node.link_to or node.absolute_path local cwd = node.link_to or node.absolute_path
local git_statuses = git.load_project_status(cwd) local git_status = git.load_project_status(cwd)
M.explore(node, git_statuses) M.explore(node, git_status, self)
end end
---@param node Node
function Explorer:expand(node) function Explorer:expand(node)
self:_load(node) self:_load(node)
end end
function Explorer:destroy()
local function iterate(node)
explorer_node.node_destroy(node)
if node.nodes then
for _, child in pairs(node.nodes) do
iterate(child)
end
end
end
iterate(self)
end
function M.setup(opts) function M.setup(opts)
require("nvim-tree.explorer.filters").setup(opts) M.config = opts
require("nvim-tree.explorer.sorters").setup(opts) require("nvim-tree.explorer.node").setup(opts)
require("nvim-tree.explorer.explore").setup(opts)
require("nvim-tree.explorer.reload").setup(opts)
require("nvim-tree.explorer.watch").setup(opts)
end end
M.Explorer = Explorer M.Explorer = Explorer

View File

@@ -0,0 +1,213 @@
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
local function redraw()
require("nvim-tree.renderer").draw()
end
---@param node_ Node|nil
local function reset_filter(self, node_)
node_ = node_ or self.explorer
if node_ == nil then
return
end
Iterator.builder(node_.nodes)
:hidden()
:applier(function(node)
node.hidden = false
end)
:iterate()
end
local overlay_bufnr = 0
local overlay_winnr = 0
local function remove_overlay(self)
if self.explorer.view.View.float.enable and self.explorer.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
self.explorer.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
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 (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()
redraw()
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(self.explorer.view:get_winnr())[1]
if wininfo then
return wininfo.width - wininfo.textoff - #self.prefix
end
return 20
end
local function create_overlay(self)
if self.explorer.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()
self.explorer.view.View.live_filter.prev_focused_node = require("nvim-tree.lib").get_node_at_cursor()
self.filter = self.filter or ""
redraw()
local row = require("nvim-tree.core").get_nodes_starting_line() - 1
local col = #self.prefix > 0 and #self.prefix - 1 or 1
self.explorer.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 = self.explorer.view.View.live_filter.prev_focused_node
self.filter = nil
reset_filter(self)
redraw()
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,17 +1,21 @@
local uv = vim.loop
local utils = require "nvim-tree.utils" local utils = require "nvim-tree.utils"
local watch = require "nvim-tree.explorer.watch"
local M = { local M = {}
is_windows = vim.fn.has "win32" == 1,
}
function M.folder(parent, absolute_path, name) ---@param parent Node
local handle = uv.fs_scandir(absolute_path) ---@param absolute_path string
local has_children = handle and uv.fs_scandir_next(handle) ~= nil ---@param name string
---@param fs_stat uv.fs_stat.result|nil
---@return Node
function M.folder(parent, absolute_path, name, fs_stat)
local handle = vim.loop.fs_scandir(absolute_path)
local has_children = handle and vim.loop.fs_scandir_next(handle) ~= nil
return { local node = {
type = "directory",
absolute_path = absolute_path, absolute_path = absolute_path,
fs_stat = uv.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,
@@ -19,23 +23,38 @@ function M.folder(parent, absolute_path, name)
open = false, open = false,
parent = parent, parent = parent,
} }
node.watcher = watch.create_watcher(node)
return node
end end
local function is_executable(absolute_path, ext) --- path is an executable file or directory
if M.is_windows then ---@param absolute_path string
return utils.is_windows_exe(ext) ---@return boolean|nil
function M.is_executable(absolute_path)
if utils.is_windows or utils.is_wsl then
--- executable detection on windows is buggy and not performant hence it is disabled
return false
else
return vim.loop.fs_access(absolute_path, "X")
end end
return uv.fs_access(absolute_path, "X")
end end
function M.file(parent, absolute_path, name) ---@param parent Node
---@param absolute_path string
---@param name string
---@param fs_stat uv.fs_stat.result|nil
---@return Node
function M.file(parent, absolute_path, name, fs_stat)
local ext = string.match(name, ".?[^.]+%.(.*)") or "" local ext = string.match(name, ".?[^.]+%.(.*)") or ""
return { return {
type = "file",
absolute_path = absolute_path, absolute_path = absolute_path,
executable = is_executable(absolute_path, ext), executable = M.is_executable(absolute_path),
extension = ext, extension = ext,
fs_stat = uv.fs_stat(absolute_path), fs_stat = fs_stat,
name = name, name = name,
parent = parent, parent = parent,
} }
@@ -46,20 +65,29 @@ end
-- links (for instance libr2.so in /usr/lib) and thus even with a C program realpath fails -- links (for instance libr2.so in /usr/lib) and thus even with a C program realpath fails
-- when it has no real reason to. Maybe there is a reason, but errno is definitely wrong. -- when it has no real reason to. Maybe there is a reason, but errno is definitely wrong.
-- So we need to check for link_to ~= nil when adding new links to the main tree -- So we need to check for link_to ~= nil when adding new links to the main tree
function M.link(parent, absolute_path, name) ---@param parent Node
--- I dont know if this is needed, because in my understanding, there isnt hard links in windows, but just to be sure i changed it. ---@param absolute_path string
local link_to = uv.fs_realpath(absolute_path) ---@param name string
---@param fs_stat uv.fs_stat.result|nil
---@return Node
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.
local link_to = vim.loop.fs_realpath(absolute_path)
local open, nodes, has_children local open, nodes, has_children
if (link_to ~= nil) and uv.fs_stat(link_to).type == "directory" then
local handle = uv.fs_scandir(link_to) local is_dir_link = (link_to ~= nil) and vim.loop.fs_stat(link_to).type == "directory"
has_children = handle and uv.fs_scandir_next(handle) ~= nil
if is_dir_link and link_to then
local handle = vim.loop.fs_scandir(link_to)
has_children = handle and vim.loop.fs_scandir_next(handle) ~= nil
open = false open = false
nodes = {} nodes = {}
end end
return { local node = {
type = "link",
absolute_path = absolute_path, absolute_path = absolute_path,
fs_stat = uv.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,
@@ -68,6 +96,12 @@ function M.link(parent, absolute_path, name)
open = open, open = open,
parent = parent, parent = parent,
} }
if is_dir_link then
node.watcher = watch.create_watcher(node)
end
return node
end end
return M return M

View File

@@ -0,0 +1,162 @@
local M = {}
---@class GitStatus
---@field file string|nil
---@field dir table|nil
---@param parent_ignored boolean
---@param status table|nil
---@param absolute_path string
---@return GitStatus|nil
local function get_dir_git_status(parent_ignored, status, absolute_path)
if parent_ignored then
return { file = "!!" }
end
if status then
return {
file = status.files and status.files[absolute_path],
dir = status.dirs and {
direct = status.dirs.direct[absolute_path],
indirect = status.dirs.indirect[absolute_path],
},
}
end
end
---@param parent_ignored boolean
---@param status table
---@param absolute_path string
---@return GitStatus
local function get_git_status(parent_ignored, status, absolute_path)
local file_status = parent_ignored and "!!" or (status and status.files and status.files[absolute_path])
return { file = file_status }
end
---@param node Node
---@return boolean
function M.has_one_child_folder(node)
return #node.nodes == 1 and node.nodes[1].nodes and vim.loop.fs_access(node.nodes[1].absolute_path, "R") or false
end
---@param node Node
---@param parent_ignored boolean
---@param status table|nil
function M.update_git_status(node, parent_ignored, status)
local get_status
if node.nodes then
get_status = get_dir_git_status
else
get_status = get_git_status
end
-- status of the node's absolute path
node.git_status = get_status(parent_ignored, status, node.absolute_path)
-- status of the link target, if the link itself is not dirty
if node.link_to and not node.git_status then
node.git_status = get_status(parent_ignored, status, node.link_to)
end
end
---@param node Node
---@return GitStatus|nil
function M.get_git_status(node)
local git_status = node and node.git_status
if not git_status then
-- status doesn't exist
return nil
end
if not node.nodes then
-- file
return git_status.file and { git_status.file }
end
-- dir
if not M.config.git.show_on_dirs then
return nil
end
local status = {}
if not require("nvim-tree.lib").get_last_group_node(node).open or M.config.git.show_on_open_dirs then
-- dir is closed or we should show on open_dirs
if git_status.file ~= nil then
table.insert(status, git_status.file)
end
if git_status.dir ~= nil then
if git_status.dir.direct ~= nil then
for _, s in pairs(node.git_status.dir.direct) do
table.insert(status, s)
end
end
if git_status.dir.indirect ~= nil then
for _, s in pairs(node.git_status.dir.indirect) do
table.insert(status, s)
end
end
end
else
-- dir is open and we shouldn't show on open_dirs
if git_status.file ~= nil then
table.insert(status, git_status.file)
end
if git_status.dir ~= nil and git_status.dir.direct ~= nil then
local deleted = {
[" D"] = true,
["D "] = true,
["RD"] = true,
["DD"] = true,
}
for _, s in pairs(node.git_status.dir.direct) do
if deleted[s] then
table.insert(status, s)
end
end
end
end
if #status == 0 then
return nil
else
return status
end
end
---@param node Node
---@return boolean
function M.is_git_ignored(node)
return node and node.git_status ~= nil and node.git_status.file == "!!"
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
function M.node_destroy(node)
if not node then
return
end
if node.watcher then
node.watcher:destroy()
node.watcher = nil
end
end
function M.setup(opts)
M.config = {
git = opts.git,
}
end
return M

View File

@@ -1,83 +1,233 @@
local api = vim.api
local uv = vim.loop
local utils = require "nvim-tree.utils" local utils = require "nvim-tree.utils"
local builders = require "nvim-tree.explorer.node-builders" local builders = require "nvim-tree.explorer.node-builders"
local common = require "nvim-tree.explorer.common" local explorer_node = require "nvim-tree.explorer.node"
local filters = require "nvim-tree.explorer.filters" local git = require "nvim-tree.git"
local sorters = require "nvim-tree.explorer.sorters" local log = require "nvim-tree.log"
local NodeIterator = require "nvim-tree.iterators.node-iterator"
local Watcher = require "nvim-tree.watcher"
local M = {} 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) local function update_status(nodes_by_path, node_ignored, status)
return function(node) return function(node)
if nodes_by_path[node.absolute_path] then if nodes_by_path[node.absolute_path] then
common.update_git_status(node, node_ignored, status) explorer_node.update_git_status(node, node_ignored, status)
end end
return node return node
end end
end end
function M.reload(node, status) ---@param path string
local cwd = node.cwd or node.link_to or node.absolute_path ---@param callback fun(toplevel: string|nil, project: table|nil)
local handle = uv.fs_scandir(cwd) local function reload_and_get_git_project(path, callback)
if type(handle) == "string" then local toplevel = git.get_toplevel(path)
api.nvim_err_writeln(handle)
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
function M.reload(node, git_status)
local explorer = require("nvim-tree.core").get_explorer()
if not explorer then
return return
end end
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 = explorer.filters:prepare(git_status)
if node.group_next then if node.group_next then
node.nodes = { node.group_next } node.nodes = { node.group_next }
node.group_next = nil node.group_next = nil
end end
local child_names = {} local remain_childs = {}
local node_ignored = node.git_status == "!!" local node_ignored = explorer_node.is_git_ignored(node)
---@type table<string, Node>
local nodes_by_path = utils.key_by(node.nodes, "absolute_path") local nodes_by_path = utils.key_by(node.nodes, "absolute_path")
while true do while true do
local name, t = uv.fs_scandir_next(handle) local name, t = vim.loop.fs_scandir_next(handle)
if not name then if not name then
break break
end end
local abs = utils.path_join { cwd, name } local abs = utils.path_join { cwd, name }
t = t or (uv.fs_stat(abs) or {}).type ---@type uv.fs_stat.result|nil
if not filters.should_ignore(abs) and not filters.should_ignore_git(abs, status.files) then local stat = vim.loop.fs_stat(abs)
child_names[abs] = true
if not nodes_by_path[abs] then if not explorer.filters:should_filter(abs, stat, filter_status) then
if t == "directory" and uv.fs_access(abs, "R") then remain_childs[abs] = true
table.insert(node.nodes, builders.folder(node, abs, name, status, node_ignored))
elseif t == "file" then -- Recreate node if type changes.
table.insert(node.nodes, builders.file(node, abs, name, status, node_ignored)) if nodes_by_path[abs] then
elseif t == "link" then local n = nodes_by_path[abs]
local link = builders.link(node, abs, name, status, node_ignored)
if link.link_to ~= nil then if n.type ~= t then
table.insert(node.nodes, link) utils.array_remove(node.nodes, n)
explorer_node.node_destroy(n)
nodes_by_path[abs] = nil
end end
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 end
end end
end end
node.nodes = vim.tbl_map( node.nodes = vim.tbl_map(
update_status(nodes_by_path, node_ignored, status), update_status(nodes_by_path, node_ignored, git_status),
vim.tbl_filter(function(n) vim.tbl_filter(function(n)
return child_names[n.absolute_path] 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) end, node.nodes)
) )
local is_root = node.cwd ~= nil local is_root = not node.parent
local child_folder_only = common.has_one_child_folder(node) and node.nodes[1] local child_folder_only = explorer_node.has_one_child_folder(node) and node.nodes[1]
if vim.g.nvim_tree_group_empty == 1 and not is_root and child_folder_only then if M.config.group_empty and not is_root and child_folder_only then
node.group_next = child_folder_only node.group_next = child_folder_only
local ns = M.reload(child_folder_only, status) local ns = M.reload(child_folder_only, git_status)
node.nodes = ns or {} node.nodes = ns or {}
log.profile_end(profile)
return ns return ns
end end
sorters.merge_sort(node.nodes, sorters.node_comparator) explorer.sorters:sort(node.nodes)
explorer.live_filter:apply_filter(node)
log.profile_end(profile)
return node.nodes return node.nodes
end 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 return M

View File

@@ -1,7 +1,28 @@
local M = { local C = {}
sort_by = nil,
node_comparator = nil, ---@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
---@param sorter string as per options
---@return function
function Sorter:get_comparator(sorter)
return function(a, b)
return (C[sorter] or C.name)(a, b, self.config)
end
end
---Create a shallow copy of a portion of a list. ---Create a shallow copy of a portion of a list.
---@param t table ---@param t table
@@ -17,6 +38,29 @@ local function tbl_slice(t, first, last)
return slice return slice
end end
---Evaluate `sort.folders_first` and `sort.files_first`
---@param a Node
---@param b Node
---@return boolean|nil
local function folders_or_files_first(a, b, cfg)
if not (cfg.folders_first or cfg.files_first) then
return
end
if not a.nodes and b.nodes then
-- file <> folder
return cfg.files_first
elseif a.nodes and not b.nodes then
-- folder <> file
return not cfg.files_first
end
end
---@param t table
---@param first number
---@param mid number
---@param last number
---@param comparator fun(a: Node, b: Node): boolean
local function merge(t, first, mid, last, comparator) local function merge(t, first, mid, last, comparator)
local n1 = mid - first + 1 local n1 = mid - first + 1
local n2 = last - mid local n2 = last - mid
@@ -50,6 +94,10 @@ local function merge(t, first, mid, last, comparator)
end end
end end
---@param t table
---@param first number
---@param last number
---@param comparator fun(a: Node, b: Node): boolean
local function split_merge(t, first, last, comparator) local function split_merge(t, first, last, comparator)
if (last - first) < 1 then if (last - first) < 1 then
return return
@@ -62,27 +110,69 @@ local function split_merge(t, first, last, comparator)
merge(t, first, mid, last, comparator) merge(t, first, mid, last, comparator)
end end
---Perform a merge sort on a given list. ---Perform a merge sort using sorter option.
---@param t any[] ---@param t table nodes
---@param comparator function|nil function Sorter:sort(t)
function M.merge_sort(t, comparator) if self.user then
if not comparator then local t_user = {}
comparator = function(left, right) local origin_index = {}
return left < right
for _, n in ipairs(t) do
table.insert(t_user, {
absolute_path = n.absolute_path,
executable = n.executable,
extension = n.extension,
filetype = vim.filetype.match { filename = n.name },
link_to = n.link_to,
name = n.name,
type = n.type,
})
table.insert(origin_index, n)
end
local predefined = self.user(t_user)
if predefined then
split_merge(t, 1, #t, self:get_comparator(predefined))
return
end
-- do merge sort for prevent memory exceed
local user_index = {}
for i, v in ipairs(t_user) do
if type(v.absolute_path) == "string" and user_index[v.absolute_path] == nil then
user_index[v.absolute_path] = i
end end
end end
split_merge(t, 1, #t, comparator) -- if missing value found, then using origin_index
local mini_comparator = function(a, b)
local a_index = user_index[a.absolute_path] or origin_index[a.absolute_path]
local b_index = user_index[b.absolute_path] or origin_index[b.absolute_path]
if type(a_index) == "number" and type(b_index) == "number" then
return a_index <= b_index
end
return (a_index or 0) <= (b_index or 0)
end end
local function node_comparator_name_ignorecase_or_not(a, b, ignorecase) split_merge(t, 1, #t, mini_comparator) -- sort by user order
else
split_merge(t, 1, #t, self:get_comparator(self.config.sorter))
end
end
---@param a Node
---@param b Node
---@param ignorecase boolean|nil
---@return boolean
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
if a.nodes and not b.nodes then
return true local early_return = folders_or_files_first(a, b, cfg)
elseif not a.nodes and b.nodes then if early_return ~= nil then
return false return early_return
end end
if ignorecase then if ignorecase then
@@ -92,22 +182,22 @@ local function node_comparator_name_ignorecase_or_not(a, b, ignorecase)
end end
end end
function M.node_comparator_name_case_sensisive(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 M.node_comparator_name_ignorecase(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 M.node_comparator_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
if a.nodes and not b.nodes then
return true local early_return = folders_or_files_first(a, b, cfg)
elseif not a.nodes and b.nodes then if early_return ~= nil then
return false return early_return
end end
local last_modified_a = 0 local last_modified_a = 0
@@ -124,15 +214,103 @@ function M.node_comparator_modification_time(a, b)
return last_modified_b <= last_modified_a return last_modified_b <= last_modified_a
end end
function M.setup(opts) function C.suffix(a, b, cfg)
M.sort_by = opts.sort_by if not (a and b) then
if M.sort_by == "modification_time" then return true
M.node_comparator = M.node_comparator_modification_time
elseif M.sort_by == "case_sensitive" then
M.node_comparator = M.node_comparator_name_case_sensisive
else
M.node_comparator = M.node_comparator_name_ignorecase
end
end end
return M -- directories go first
local early_return = folders_or_files_first(a, b, cfg)
if early_return ~= nil then
return early_return
elseif a.nodes and b.nodes then
return C.name(a, b, cfg)
end
-- dotfiles go second
if a.name:sub(1, 1) == "." and b.name:sub(1, 1) ~= "." then
return true
elseif a.name:sub(1, 1) ~= "." and b.name:sub(1, 1) == "." then
return false
elseif a.name:sub(1, 1) == "." and b.name:sub(1, 1) == "." then
return C.name(a, b, cfg)
end
-- unsuffixed go third
local a_suffix_ndx = a.name:find "%.%w+$"
local b_suffix_ndx = b.name:find "%.%w+$"
if not a_suffix_ndx and b_suffix_ndx then
return true
elseif a_suffix_ndx and not b_suffix_ndx then
return false
elseif not (a_suffix_ndx and b_suffix_ndx) then
return C.name(a, b, cfg)
end
-- finally, compare by suffixes
local a_suffix = a.name:sub(a_suffix_ndx)
local b_suffix = b.name:sub(b_suffix_ndx)
if a_suffix and not b_suffix then
return true
elseif not a_suffix and b_suffix then
return false
elseif a_suffix:lower() == b_suffix:lower() then
return C.name(a, b, cfg)
end
return a_suffix:lower() < b_suffix:lower()
end
function C.extension(a, b, cfg)
if not (a and b) then
return true
end
local early_return = folders_or_files_first(a, b, cfg)
if early_return ~= nil then
return early_return
end
if a.extension and not b.extension then
return true
elseif not a.extension and b.extension then
return false
end
local a_ext = (a.extension or ""):lower()
local b_ext = (b.extension or ""):lower()
if a_ext == b_ext then
return C.name(a, b, cfg)
end
return a_ext < b_ext
end
function C.filetype(a, b, cfg)
local a_ft = vim.filetype.match { filename = a.name }
local b_ft = vim.filetype.match { filename = b.name }
-- directories first
local early_return = folders_or_files_first(a, b, cfg)
if early_return ~= nil then
return early_return
end
-- one is nil, the other wins
if a_ft and not b_ft then
return true
elseif not a_ft and b_ft then
return false
end
-- same filetype or both nil, sort by name
if a_ft == b_ft then
return C.name(a, b, cfg)
end
return a_ft < b_ft
end
return Sorter

View File

@@ -0,0 +1,599 @@
local events = require "nvim-tree.events"
local utils = require "nvim-tree.utils"
local log = require "nvim-tree.log"
local ExplorerView = {}
local DEFAULT_MIN_WIDTH = 30
local DEFAULT_MAX_WIDTH = -1
local DEFAULT_PADDING = 1
function ExplorerView:new(opts)
local o = {
View = {
adaptive_size = false,
centralize_selection = false,
tabpages = {},
cursors = {},
hide_root_folder = false,
live_filter = {
prev_focused_node = nil,
},
winopts = {
relativenumber = false,
number = false,
list = false,
foldenable = false,
winfixwidth = true,
winfixheight = true,
spell = false,
signcolumn = "yes",
foldmethod = "manual",
foldcolumn = "0",
cursorcolumn = false,
cursorline = true,
cursorlineopt = "both",
colorcolumn = "0",
wrap = false,
winhl = table.concat({
"EndOfBuffer:NvimTreeEndOfBuffer",
"CursorLine:NvimTreeCursorLine",
"CursorLineNr:NvimTreeCursorLineNr",
"LineNr:NvimTreeLineNr",
"WinSeparator:NvimTreeWinSeparator",
"StatusLine:NvimTreeStatusLine",
"StatusLineNC:NvimTreeStatuslineNC",
"SignColumn:NvimTreeSignColumn",
"Normal:NvimTreeNormal",
"NormalNC:NvimTreeNormalNC",
"NormalFloat:NvimTreeNormalFloat",
}, ","),
},
}
}
local options = opts.view or {}
o.View.centralize_selection = options.centralize_selection
o.View.side = (options.side == "right") and "right" or "left"
o.View.height = options.height
o.View.hide_root_folder = opts.renderer.root_folder_label == false
o.View.tab = opts.tab
o.View.preserve_window_proportions = options.preserve_window_proportions
o.View.winopts.cursorline = options.cursorline
o.View.winopts.number = options.number
o.View.winopts.relativenumber = options.relativenumber
o.View.winopts.signcolumn = options.signcolumn
o.View.float = options.float
o.on_attach = opts.on_attach
o.config = vim.deepcopy(options)
setmetatable(o, self)
self.__index = self
o:configure_width(options.width)
o.View.initial_width = o:get_width()
end
-- The initial state of a tab
local tabinitial = {
-- The position of the cursor { line, column }
cursor = { 0, 0 },
-- The NvimTree window number
winnr = nil,
}
local BUFNR_PER_TAB = {}
local BUFFER_OPTIONS = {
swapfile = false,
buftype = "nofile",
modifiable = false,
filetype = "NvimTree",
bufhidden = "wipe",
buflisted = false,
}
---@param bufnr integer
---@return boolean
local function matches_bufnr(bufnr)
for _, b in pairs(BUFNR_PER_TAB) do
if b == bufnr then
return true
end
end
return false
end
local function wipe_rogue_buffer()
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
if not matches_bufnr(bufnr) and utils.is_nvim_tree_buf(bufnr) then
pcall(vim.api.nvim_buf_delete, bufnr, { force = true })
end
end
end
---@param bufnr integer|boolean|nil
local function create_buffer(self, bufnr)
wipe_rogue_buffer()
local tab = vim.api.nvim_get_current_tabpage()
BUFNR_PER_TAB[tab] = bufnr or vim.api.nvim_create_buf(false, false)
vim.api.nvim_buf_set_name(self:get_bufnr(), "NvimTree_" .. tab)
for option, value in pairs(BUFFER_OPTIONS) do
vim.bo[self:get_bufnr()][option] = value
end
require("nvim-tree.keymap").on_attach(self:get_bufnr())
events._dispatch_tree_attached_post(self:get_bufnr())
end
---@param size (fun():integer)|integer|string
---@return integer
local function get_size(size)
if type(size) == "number" then
return size
elseif type(size) == "function" then
return size()
end
local size_as_number = tonumber(size:sub(0, -2))
local percent_as_decimal = size_as_number / 100
return math.floor(vim.o.columns * percent_as_decimal)
end
---@param size (fun():integer)|integer|nil
function ExplorerView:get_width(size)
if size then
return get_size(size)
else
return get_size(self.View.width)
end
end
local move_tbl = {
left = "H",
right = "L",
}
-- setup_tabpage sets up the initial state of a tab
---@param tabpage integer
local function setup_tabpage(self, tabpage)
local winnr = vim.api.nvim_get_current_win()
self.View.tabpages[tabpage] = vim.tbl_extend("force", self.View.tabpages[tabpage] or tabinitial, { winnr = winnr })
end
local function set_window_options_and_buffer(self)
pcall(vim.api.nvim_command, "buffer " .. self:get_bufnr())
local eventignore = vim.opt.eventignore:get()
vim.opt.eventignore = "all"
for k, v in pairs(self.View.winopts) do
vim.opt_local[k] = v
end
vim.opt.eventignore = eventignore
end
---@return table
local function open_win_config(self)
if type(self.View.float.open_win_config) == "function" then
return self.View.float.open_win_config(self)
else
return self.View.float.open_win_config
end
end
local function open_window(self)
if self.View.float.enable then
vim.api.nvim_open_win(0, true, open_win_config(self))
else
vim.api.nvim_command "vsp"
self:reposition_window()
end
setup_tabpage(self, vim.api.nvim_get_current_tabpage())
set_window_options_and_buffer(self)
end
---@param buf integer
---@return boolean
local function is_buf_displayed(buf)
return vim.api.nvim_buf_is_valid(buf) and vim.fn.buflisted(buf) == 1
end
---@return number|nil
local function get_alt_or_next_buf()
local alt_buf = vim.fn.bufnr "#"
if is_buf_displayed(alt_buf) then
return alt_buf
end
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
if is_buf_displayed(buf) then
return buf
end
end
end
local function switch_buf_if_last_buf()
if #vim.api.nvim_list_wins() == 1 then
local buf = get_alt_or_next_buf()
if buf then
vim.cmd("sb" .. buf)
else
vim.cmd "new"
end
end
end
-- save_tab_state saves any state that should be preserved across redraws.
---@param tabnr integer
local function save_tab_state(self, tabnr)
local tabpage = tabnr or vim.api.nvim_get_current_tabpage()
self.View.cursors[tabpage] = vim.api.nvim_win_get_cursor(self:get_winnr(tabpage) or 0)
end
---@param tabpage integer
local function close(self, tabpage)
if not self:is_visible { tabpage = tabpage } then
return
end
save_tab_state(self, tabpage)
switch_buf_if_last_buf()
local tree_win = self:get_winnr(tabpage)
local current_win = vim.api.nvim_get_current_win()
for _, win in pairs(vim.api.nvim_tabpage_list_wins(tabpage)) do
if vim.api.nvim_win_get_config(win).relative == "" then
local prev_win = vim.fn.winnr "#" -- this tab only
if tree_win == current_win and prev_win > 0 then
vim.api.nvim_set_current_win(vim.fn.win_getid(prev_win))
end
if vim.api.nvim_win_is_valid(tree_win or 0) then
vim.api.nvim_win_close(tree_win or 0, true)
end
events._dispatch_on_tree_close()
return
end
end
end
function ExplorerView:close_this_tab_only()
close(self, vim.api.nvim_get_current_tabpage())
end
function ExplorerView:close_all_tabs()
for tabpage, _ in pairs(self.View.tabpages) do
close(self, tabpage)
end
end
function ExplorerView:close()
if self.View.tab.sync.close then
self:close_all_tabs()
else
self:close_this_tab_only()
end
end
---@param options table|nil
function ExplorerView:open(options)
if self:is_visible() then
return
end
local profile = log.profile_start "view open"
create_buffer(self)
open_window(self)
self:resize()
local opts = options or { focus_tree = true }
if not opts.focus_tree then
vim.cmd "wincmd p"
end
events._dispatch_on_tree_open()
log.profile_end(profile)
end
local function grow(self)
local starts_at = self:is_root_folder_visible(require("nvim-tree.core").get_cwd()) and 1 or 0
local lines = vim.api.nvim_buf_get_lines(self:get_bufnr(), starts_at, -1, false)
-- number of columns of right-padding to indicate end of path
local padding = get_size(self.View.padding)
-- account for sign/number columns etc.
local wininfo = vim.fn.getwininfo(self:get_winnr())
if type(wininfo) == "table" and type(wininfo[1]) == "table" then
padding = padding + wininfo[1].textoff
end
local resizing_width = self.View.initial_width - padding
local max_width
-- maybe bound max
if self.View.max_width == -1 then
max_width = -1
else
max_width = self:get_width(self.View.max_width) - padding
end
for _, l in pairs(lines) do
local count = vim.fn.strchars(l)
if resizing_width < count then
resizing_width = count
end
if self.View.adaptive_size and max_width >= 0 and resizing_width >= max_width then
resizing_width = max_width
break
end
end
self:resize(resizing_width + padding)
end
function ExplorerView:grow_from_content()
if self.View.adaptive_size then
grow(self)
end
end
---@param size string|number|nil
function ExplorerView:resize(size)
if self.View.float.enable and not self.View.adaptive_size then
-- if the floating windows's adaptive size is not desired, then the
-- float size should be defined in view.float.open_win_config
return
end
if type(size) == "string" then
size = vim.trim(size)
local first_char = size:sub(1, 1)
size = tonumber(size)
if first_char == "+" or first_char == "-" then
size = self.View.width + size
end
end
if type(size) == "number" and size <= 0 then
return
end
if size then
self.View.width = size
self.View.height = size
end
if not self:is_visible() then
return
end
local new_size = self:get_width()
vim.api.nvim_win_set_width(self:get_winnr() or 0, new_size)
events._dispatch_on_tree_resize(new_size)
if not self.View.preserve_window_proportions then
vim.cmd ":wincmd ="
end
end
function ExplorerView:reposition_window()
local move_to = move_tbl[self.View.side]
vim.api.nvim_command("wincmd " .. move_to)
self:resize()
end
local function set_current_win(self)
local current_tab = vim.api.nvim_get_current_tabpage()
self.View.tabpages[current_tab].winnr = vim.api.nvim_get_current_win()
end
---Open the tree in the a window
---@param opts OpenInWinOpts|nil
function ExplorerView:open_in_win(opts)
opts = opts or { hijack_current_buf = true, resize = true }
if opts.winid and vim.api.nvim_win_is_valid(opts.winid) then
vim.api.nvim_set_current_win(opts.winid)
end
create_buffer(self, opts.hijack_current_buf and vim.api.nvim_get_current_buf())
setup_tabpage(self, vim.api.nvim_get_current_tabpage())
set_current_win(self)
set_window_options_and_buffer(self)
if opts.resize then
self:reposition_window()
self:resize()
end
end
function ExplorerView:abandon_current_window()
local tab = vim.api.nvim_get_current_tabpage()
BUFNR_PER_TAB[tab] = nil
if self.View.tabpages[tab] then
self.View.tabpages[tab].winnr = nil
end
end
function ExplorerView:abandon_all_windows()
for tab, _ in pairs(vim.api.nvim_list_tabpages()) do
BUFNR_PER_TAB[tab] = nil
if self.View.tabpages[tab] then
self.View.tabpages[tab].winnr = nil
end
end
end
---@param opts table|nil
function ExplorerView:is_visible(opts)
if opts and opts.tabpage then
if self.View.tabpages[opts.tabpage] == nil then
return false
end
local winnr = self.View.tabpages[opts.tabpage].winnr
return winnr and vim.api.nvim_win_is_valid(winnr)
end
if opts and opts.any_tabpage then
for _, v in pairs(self.View.tabpages) do
if v.winnr and vim.api.nvim_win_is_valid(v.winnr) then
return true
end
end
return false
end
return self:get_winnr() ~= nil and vim.api.nvim_win_is_valid(self:get_winnr() or 0)
end
---@param opts table|nil
function ExplorerView:set_cursor(opts)
if self:is_visible() then
pcall(vim.api.nvim_win_set_cursor, self:get_winnr(), opts)
end
end
---@param winnr number|nil
---@param open_if_closed boolean|nil
function ExplorerView:focus(winnr, open_if_closed)
local wnr = winnr or self:get_winnr()
if vim.api.nvim_win_get_tabpage(wnr or 0) ~= vim.api.nvim_win_get_tabpage(0) then
self:close()
self:open()
wnr = self:get_winnr()
elseif open_if_closed and not self:is_visible() then
self:open()
end
if wnr then
vim.api.nvim_set_current_win(wnr)
end
end
--- Retrieve the winid of the open tree.
---@param opts ApiTreeWinIdOpts|nil
---@return number|nil winid unlike get_winnr(), this returns nil if the nvim-tree window is not visible
function ExplorerView:winid(opts)
local tabpage = opts and opts.tabpage
if tabpage == 0 then
tabpage = vim.api.nvim_get_current_tabpage()
end
if self:is_visible { tabpage = tabpage } then
return self:get_winnr(tabpage)
else
return nil
end
end
--- Restores the state of a NvimTree window if it was initialized before.
function ExplorerView:restore_tab_state()
local tabpage = vim.api.nvim_get_current_tabpage()
self:set_cursor(self.View.cursors[tabpage])
end
--- Returns the window number for nvim-tree within the tabpage specified
---@param tabpage number|nil (optional) the number of the chosen tabpage. Defaults to current tabpage.
---@return number|nil
function ExplorerView:get_winnr(tabpage)
tabpage = tabpage or vim.api.nvim_get_current_tabpage()
local tabinfo = self.View.tabpages[tabpage]
if tabinfo and tabinfo.winnr and vim.api.nvim_win_is_valid(tabinfo.winnr) then
return tabinfo.winnr
end
end
--- Returns the current nvim tree bufnr
---@return number
function ExplorerView:get_bufnr()
return BUFNR_PER_TAB[vim.api.nvim_get_current_tabpage()]
end
---@param bufnr number
---@return boolean
function ExplorerView:is_buf_valid(bufnr)
return bufnr and vim.api.nvim_buf_is_valid(bufnr) and vim.api.nvim_buf_is_loaded(bufnr)
end
function ExplorerView:_prevent_buffer_override()
local view_winnr = self:get_winnr()
local view_bufnr = self:get_bufnr()
-- need to schedule to let the new buffer populate the window
-- because this event needs to be run on bufWipeout.
-- Otherwise the curwin/curbuf would match the view buffer and the view window.
vim.schedule(function()
local curwin = vim.api.nvim_get_current_win()
local curwinconfig = vim.api.nvim_win_get_config(curwin)
local curbuf = vim.api.nvim_win_get_buf(curwin)
local bufname = vim.api.nvim_buf_get_name(curbuf)
if not bufname:match "NvimTree" then
for i, tabpage in ipairs(self.View.tabpages) do
if tabpage.winnr == view_winnr then
M.View.tabpages[i] = nil
break
end
end
end
if curwin ~= view_winnr or bufname == "" or curbuf == view_bufnr then
return
end
-- patch to avoid the overriding window to be fixed in size
-- might need a better patch
vim.cmd "setlocal nowinfixwidth"
vim.cmd "setlocal nowinfixheight"
M.open { focus_tree = false }
require("nvim-tree.renderer").draw()
pcall(vim.api.nvim_win_close, curwin, { force = true })
-- to handle opening a file using :e when nvim-tree is on floating mode
-- falling back to the current window instead of creating a new one
if curwinconfig.relative ~= "" then
require("nvim-tree.actions.node.open-file").fn("edit_in_place", bufname)
else
require("nvim-tree.actions.node.open-file").fn("edit", bufname)
end
end)
end
---@param cwd string|nil
---@return boolean
function ExplorerView:is_root_folder_visible(cwd)
return cwd ~= "/" and not self.View.hide_root_folder
end
-- used on ColorScheme event
function ExplorerView:reset_winhl()
local winnr = self:get_winnr()
if winnr and vim.api.nvim_win_is_valid(winnr) then
vim.wo[self:get_winnr()].winhl = self.View.winopts.winhl
end
end
---Check if width determined or calculated on-fly
---@return boolean
function ExplorerView:is_width_determined()
return type(self.View.width) ~= "function"
end
---Configure width-related config
---@param width string|function|number|table|nil
function ExplorerView:configure_width(width)
if type(width) == "table" then
self.View.adaptive_size = true
self.View.width = width.min or DEFAULT_MIN_WIDTH
self.View.max_width = width.max or DEFAULT_MAX_WIDTH
self.View.padding = width.padding or DEFAULT_PADDING
elseif width == nil then
if self.config.width ~= nil then
-- if we had input config - fallback to it
self.configure_width(self.config.width)
else
-- otherwise - restore initial width
self.View.width = self.View.initial_width
end
else
self.View.adaptive_size = false
self.View.width = width
end
end
return ExplorerView

View File

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

View File

@@ -1,78 +1,309 @@
local log = require "nvim-tree.log"
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 Iterator = require "nvim-tree.iterators.node-iterator"
local explorer_node = require "nvim-tree.explorer.node"
local M = { local M = {
config = nil, config = {},
projects = {},
cwd_to_project_root = {}, -- all projects keyed by toplevel
_projects_by_toplevel = {},
-- index of paths inside toplevels, false when not inside a project
_toplevels_by_path = {},
-- git dirs by toplevel
_git_dirs_by_toplevel = {},
} }
-- Files under .git that should result in a reload when changed.
-- Utilities (like watchman) can also write to this directory (often) and aren't useful for us.
local WATCHED_FILES = {
"FETCH_HEAD", -- remote ref
"HEAD", -- local ref
"HEAD.lock", -- HEAD will not always be updated e.g. revert
"config", -- user config
"index", -- staging area
}
---@param toplevel string|nil
---@param path string|nil
---@param project table
---@param git_status table|nil
local function reload_git_status(toplevel, path, project, git_status)
if path then
for p in pairs(project.files) do
if p:find(path, 1, true) == 1 then
project.files[p] = nil
end
end
project.files = vim.tbl_deep_extend("force", project.files, git_status)
else
project.files = git_status
end
project.dirs = git_utils.file_status_to_dir_status(project.files, toplevel)
end
--- Is this path in a known ignored directory?
---@param path string
---@param project table git status
---@return boolean
local function path_ignored_in_project(path, project)
if not path or not project then
return false
end
if project and project.files then
for file, status in pairs(project.files) do
if status == "!!" and vim.startswith(path, file) then
return true
end
end
end
return false
end
--- Reload all projects
---@return table projects maybe empty
function M.reload() function M.reload()
if not M.config.enable then if not M.config.git.enable then
return {} return {}
end end
for project_root in pairs(M.projects) do for toplevel in pairs(M._projects_by_toplevel) do
M.projects[project_root] = {} M.reload_project(toplevel)
local git_status = Runner.run { end
project_root = project_root,
list_untracked = git_utils.should_show_untracked(project_root), return M._projects_by_toplevel
end
--- Reload one project. Does nothing when no project or path is ignored
---@param toplevel string|nil
---@param path string|nil optional path to update only
---@param callback function|nil
function M.reload_project(toplevel, path, callback)
local project = M._projects_by_toplevel[toplevel]
if not toplevel or not project or not M.config.git.enable then
if callback then
callback()
end
return
end
if path and (path:find(toplevel, 1, true) ~= 1 or path_ignored_in_project(path, project)) then
if callback then
callback()
end
return
end
local opts = {
toplevel = toplevel,
path = path,
list_untracked = git_utils.should_show_untracked(toplevel),
list_ignored = true, list_ignored = true,
timeout = M.config.timeout, timeout = M.config.git.timeout,
}
M.projects[project_root] = {
files = git_status,
dirs = git_utils.file_status_to_dir_status(git_status, project_root),
} }
if callback then
Runner.run(opts, function(git_status)
reload_git_status(toplevel, path, project, git_status)
callback()
end)
else
-- TODO use callback once async/await is available
local git_status = Runner.run(opts)
reload_git_status(toplevel, path, project, git_status)
end
end end
return M.projects --- Retrieve a known project
---@param toplevel string|nil
---@return table|nil project
function M.get_project(toplevel)
return M._projects_by_toplevel[toplevel]
end end
function M.get_project_root(cwd) --- Retrieve the toplevel for a path. nil on:
if M.cwd_to_project_root[cwd] then --- git disabled
return M.cwd_to_project_root[cwd] --- not part of a project
end --- not a directory
--- path in git.disable_for_dirs
if M.cwd_to_project_root[cwd] == false then ---@param path string absolute
---@return string|nil
function M.get_toplevel(path)
if not path then
return nil return nil
end end
local project_root = git_utils.get_toplevel(cwd) if not M.config.git.enable then
return project_root return nil
end end
function M.load_project_status(cwd) if M._toplevels_by_path[path] then
if not M.config.enable then return M._toplevels_by_path[path]
end
if M._toplevels_by_path[path] == false then
return nil
end
local stat, _ = vim.loop.fs_stat(path)
if not stat or stat.type ~= "directory" then
return nil
end
-- short-circuit any known ignored paths
for root, project in pairs(M._projects_by_toplevel) do
if project and path_ignored_in_project(path, project) then
M._toplevels_by_path[path] = root
return root
end
end
-- attempt to fetch toplevel
local toplevel, git_dir = git_utils.get_toplevel(path)
if not toplevel or not git_dir then
return nil
end
local toplevel_norm = vim.fn.fnamemodify(toplevel, ":p")
-- ignore disabled paths
if type(M.config.git.disable_for_dirs) == "table" then
for _, disabled_for_dir in ipairs(M.config.git.disable_for_dirs) do
local disabled_norm = vim.fn.fnamemodify(disabled_for_dir, ":p")
if toplevel_norm == disabled_norm then
return nil
end
end
elseif type(M.config.git.disable_for_dirs) == "function" then
if M.config.git.disable_for_dirs(toplevel_norm) then
return nil
end
end
M._toplevels_by_path[path] = toplevel
M._git_dirs_by_toplevel[toplevel] = git_dir
return M._toplevels_by_path[path]
end
local function reload_tree_at(toplevel)
if not M.config.git.enable or not toplevel then
return nil
end
log.line("watcher", "git event executing '%s'", toplevel)
local root_node = utils.get_node_from_path(toplevel)
if not root_node then
return
end
M.reload_project(toplevel, nil, function()
local git_status = M.get_project(toplevel)
Iterator.builder(root_node.nodes)
:hidden()
:applier(function(node)
local parent_ignored = explorer_node.is_git_ignored(node.parent)
explorer_node.update_git_status(node, parent_ignored, git_status)
end)
:recursor(function(node)
return node.nodes and #node.nodes > 0 and node.nodes
end)
:iterate()
require("nvim-tree.renderer").draw()
end)
end
--- Load the project status for a path. Does nothing when no toplevel for path.
--- Only fetches project status when unknown, otherwise returns existing.
---@param path string absolute
---@return table project maybe empty
function M.load_project_status(path)
if not M.config.git.enable then
return {} return {}
end end
local project_root = M.get_project_root(cwd) local toplevel = M.get_toplevel(path)
if not project_root then if not toplevel then
M.cwd_to_project_root[cwd] = false M._toplevels_by_path[path] = false
return {} return {}
end end
local status = M.projects[project_root] local status = M._projects_by_toplevel[toplevel]
if status then if status then
return status return status
end end
local git_status = Runner.run { local git_status = Runner.run {
project_root = project_root, toplevel = toplevel,
list_untracked = git_utils.should_show_untracked(project_root), list_untracked = git_utils.should_show_untracked(toplevel),
list_ignored = true, list_ignored = true,
timeout = M.config.timeout, timeout = M.config.git.timeout,
} }
M.projects[project_root] = {
local watcher = nil
if M.config.filesystem_watchers.enable then
log.line("watcher", "git start")
local callback = function(w)
log.line("watcher", "git event scheduled '%s'", w.toplevel)
utils.debounce("git:watcher:" .. w.toplevel, M.config.filesystem_watchers.debounce_delay, function()
if w.destroyed then
return
end
reload_tree_at(w.toplevel)
end)
end
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, {
toplevel = toplevel,
})
end
if git_status then
M._projects_by_toplevel[toplevel] = {
files = git_status, files = git_status,
dirs = git_utils.file_status_to_dir_status(git_status, project_root), dirs = git_utils.file_status_to_dir_status(git_status, toplevel),
watcher = watcher,
} }
return M.projects[project_root] return M._projects_by_toplevel[toplevel]
else
M._toplevels_by_path[path] = false
return {}
end
end
function M.purge_state()
log.line("git", "purge_state")
for _, project in pairs(M._projects_by_toplevel) do
if project.watcher then
project.watcher:destroy()
end
end
M._projects_by_toplevel = {}
M._toplevels_by_path = {}
M._git_dirs_by_toplevel = {}
end
--- Disable git integration permanently
function M.disable_git_integration()
log.line("git", "disabling git integration")
M.purge_state()
M.config.git.enable = false
end end
function M.setup(opts) function M.setup(opts)
M.config = opts.git M.config.git = opts.git
M.config.filesystem_watchers = opts.filesystem_watchers
end end
return M return M

View File

@@ -1,30 +1,53 @@
local uv = vim.loop
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"
---@class Runner
local Runner = {} local Runner = {}
Runner.__index = Runner Runner.__index = Runner
function Runner:_parse_status_output(line) local timeouts = 0
local status = line:sub(1, 2) local MAX_TIMEOUTS = 5
-- removing `"` when git is returning special file status containing spaces
local path = line:sub(4, -2):gsub('^"', ""):gsub('"$', "") ---@private
---@param status string
---@param path string|nil
function Runner:_parse_status_output(status, path)
if not path then
return
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.project_root, path })] = status self.output[utils.path_remove_trailing(utils.path_join { self.toplevel, path })] = status
end end
return #line
end end
---@private
---@param prev_output string
---@param incoming string
---@return string
function Runner:_handle_incoming_data(prev_output, incoming) function Runner:_handle_incoming_data(prev_output, incoming)
if incoming and utils.str_find(incoming, "\n") then if incoming and utils.str_find(incoming, "\n") then
local prev = prev_output .. incoming local prev = prev_output .. incoming
local i = 1 local i = 1
local skip_next_line = false
for line in prev:gmatch "[^\n]*\n" do for line in prev:gmatch "[^\n]*\n" do
i = i + self:_parse_status_output(line) if skip_next_line then
skip_next_line = false
else
local status = line:sub(1, 2)
local path = line:sub(4, -2)
if utils.str_find(status, "R") then
-- skip next line if it is a rename entry
skip_next_line = true
end
self:_parse_status_output(status, path)
end
i = i + #line
end end
return prev:sub(i, -1) return prev:sub(i, -1)
@@ -35,37 +58,50 @@ function Runner:_handle_incoming_data(prev_output, incoming)
end end
for line in prev_output:gmatch "[^\n]*\n" do for line in prev_output:gmatch "[^\n]*\n" do
self._parse_status_output(line) self:_parse_status_output(line)
end end
return nil return ""
end end
---@param stdout_handle uv.uv_pipe_t
---@param stderr_handle uv.uv_pipe_t
---@return table
function Runner:_getopts(stdout_handle, stderr_handle) function Runner:_getopts(stdout_handle, stderr_handle)
local untracked = self.list_untracked and "-u" or nil local untracked = self.list_untracked and "-u" or nil
local ignored = (self.list_untracked and self.list_ignored) and "--ignored=matching" or "--ignored=no" local ignored = (self.list_untracked and self.list_ignored) and "--ignored=matching" or "--ignored=no"
return { return {
args = { "--no-optional-locks", "status", "--porcelain=v1", ignored, untracked }, args = { "--no-optional-locks", "status", "--porcelain=v1", "-z", ignored, untracked, self.path },
cwd = self.project_root, cwd = self.toplevel,
stdio = { nil, stdout_handle, stderr_handle }, stdio = { nil, stdout_handle, stderr_handle },
} }
end end
---@param output string
function Runner:_log_raw_output(output) function Runner:_log_raw_output(output)
if 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")
end end
end end
function Runner:_run_git_job() ---@param callback function|nil
function Runner:_run_git_job(callback)
local handle, pid local handle, pid
local stdout = uv.new_pipe(false) local stdout = vim.loop.new_pipe(false)
local stderr = uv.new_pipe(false) local stderr = vim.loop.new_pipe(false)
local timer = uv.new_timer() local timer = vim.loop.new_timer()
if stdout == nil or stderr == nil or timer == nil then
return
end
local function on_finish(rc) local function on_finish(rc)
self.rc = rc or 0 self.rc = rc or 0
if timer:is_closing() or stdout:is_closing() or stderr:is_closing() or (handle and handle:is_closing()) then if timer:is_closing() or stdout:is_closing() or stderr:is_closing() or (handle and handle:is_closing()) then
if callback then
callback()
end
return return
end end
timer:stop() timer:stop()
@@ -74,18 +110,24 @@ function Runner:_run_git_job()
stderr:read_stop() stderr:read_stop()
stdout:close() stdout:close()
stderr:close() stderr:close()
if handle then
-- don't close the handle when killing as it will leave a zombie
if rc == -1 then
pcall(vim.loop.kill, pid, "sigkill")
elseif handle then
handle:close() handle:close()
end end
pcall(uv.kill, pid) if callback then
callback()
end
end end
local opts = self:_getopts(stdout, stderr) local opts = self:_getopts(stdout, stderr)
log.line("git", "running job with timeout %dms", self.timeout) log.line("git", "running job with timeout %dms", self.timeout)
log.line("git", "git %s", table.concat(opts.args, " ")) log.line("git", "git %s", table.concat(utils.array_remove_nils(opts.args), " "))
handle, pid = uv.spawn( handle, pid = vim.loop.spawn(
"git", "git",
opts, opts,
vim.schedule_wrap(function(rc) vim.schedule_wrap(function(rc)
@@ -106,6 +148,9 @@ function Runner:_run_git_job()
if err then if err then
return return
end end
if data then
data = data:gsub("%z", "\n")
end
self:_log_raw_output(data) self:_log_raw_output(data)
output_leftover = self:_handle_incoming_data(output_leftover, data) output_leftover = self:_handle_incoming_data(output_leftover, data)
end end
@@ -114,24 +159,43 @@ function Runner:_run_git_job()
self:_log_raw_output(data) self:_log_raw_output(data)
end end
uv.read_start(stdout, vim.schedule_wrap(manage_stdout)) vim.loop.read_start(stdout, vim.schedule_wrap(manage_stdout))
uv.read_start(stderr, vim.schedule_wrap(manage_stderr)) vim.loop.read_start(stderr, vim.schedule_wrap(manage_stderr))
end end
function Runner:_wait() function Runner:_wait()
local function is_done() local function is_done()
return self.rc ~= nil return self.rc ~= nil
end end
while not vim.wait(30, is_done) do while not vim.wait(30, is_done) do
end end
end end
-- This module runs a git process, which will be killed if it takes more than timeout which defaults to 400ms ---@param opts table
function Runner.run(opts) function Runner:_finalise(opts)
local ps = log.profile_start("git job %s", opts.project_root) if self.rc == -1 then
log.line("git", "job timed out %s %s", opts.toplevel, opts.path)
timeouts = timeouts + 1
if timeouts == MAX_TIMEOUTS then
notify.warn(string.format("%d git jobs have timed out after git.timeout %dms, disabling git integration.", timeouts, opts.timeout))
require("nvim-tree.git").disable_git_integration()
end
elseif self.rc ~= 0 then
log.line("git", "job fail rc %d %s %s", self.rc, opts.toplevel, opts.path)
else
log.line("git", "job success %s %s", opts.toplevel, opts.path)
end
end
--- Runs a git process, which will be killed if it takes more than timeout which defaults to 400ms
---@param opts table
---@param callback function|nil executed passing return when complete
---@return table|nil status by absolute path, nil if callback present
function Runner.run(opts, callback)
local self = setmetatable({ local self = setmetatable({
project_root = opts.project_root, toplevel = opts.toplevel,
path = opts.path,
list_untracked = opts.list_untracked, list_untracked = opts.list_untracked,
list_ignored = opts.list_ignored, list_ignored = opts.list_ignored,
timeout = opts.timeout or 400, timeout = opts.timeout or 400,
@@ -139,20 +203,33 @@ function Runner.run(opts)
rc = nil, -- -1 indicates timeout rc = nil, -- -1 indicates timeout
}, Runner) }, Runner)
local async = callback ~= nil
local profile = log.profile_start("git %s job %s %s", async and "async" or "sync", opts.toplevel, opts.path)
if async and callback then
-- async, always call back
self:_run_git_job(function()
log.profile_end(profile)
self:_finalise(opts)
callback(self.output)
end)
else
-- sync, maybe call back
self:_run_git_job() self:_run_git_job()
self:_wait() self:_wait()
log.profile_end(ps, "git job %s", opts.project_root) log.profile_end(profile)
if self.rc == -1 then self:_finalise(opts)
log.line("git", "job timed out")
elseif self.rc ~= 0 then if callback then
log.line("git", "job failed with return code %d", self.rc) callback(self.output)
else else
log.line("git", "job success")
end
return self.output return self.output
end end
end
end
return Runner return Runner

View File

@@ -1,53 +1,135 @@
local M = {} local log = require "nvim-tree.log"
local utils = require "nvim-tree.utils"
local M = {
use_cygpath = false,
}
--- Retrieve the git toplevel directory
---@param cwd string path
---@return string|nil toplevel absolute path
---@return string|nil git_dir absolute path
function M.get_toplevel(cwd) function M.get_toplevel(cwd)
local cmd = "git -C " .. vim.fn.shellescape(cwd) .. " rev-parse --show-toplevel" local profile = log.profile_start("git toplevel git_dir %s", cwd)
local toplevel = vim.fn.system(cmd)
if not toplevel or #toplevel == 0 or toplevel:match "fatal" then -- both paths are absolute
return nil local cmd = { "git", "-C", cwd, "rev-parse", "--show-toplevel", "--absolute-git-dir" }
log.line("git", "%s", table.concat(cmd, " "))
local out = vim.fn.system(cmd)
log.raw("git", out)
log.profile_end(profile)
if vim.v.shell_error ~= 0 or not out or #out == 0 or out:match "fatal" then
return nil, nil
end
local toplevel, git_dir = out:match "([^\n]+)\n+([^\n]+)"
if not toplevel then
return nil, nil
end
if not git_dir then
git_dir = utils.path_join { toplevel, ".git" }
end end
-- git always returns path with forward slashes -- git always returns path with forward slashes
if vim.fn.has "win32" == 1 then if vim.fn.has "win32" == 1 then
-- msys2 git support
-- cygpath calls must in array format to avoid shell compatibility issues
if M.use_cygpath then
toplevel = vim.fn.system { "cygpath", "-w", toplevel }
if vim.v.shell_error ~= 0 then
return nil, nil
end
-- remove trailing newline(\n) character added by vim.fn.system
toplevel = toplevel:gsub("\n", "")
git_dir = vim.fn.system { "cygpath", "-w", git_dir }
if vim.v.shell_error ~= 0 then
return nil, nil
end
-- remove trailing newline(\n) character added by vim.fn.system
git_dir = git_dir:gsub("\n", "")
end
toplevel = toplevel:gsub("/", "\\") toplevel = toplevel:gsub("/", "\\")
git_dir = git_dir:gsub("/", "\\")
end end
-- remove newline return toplevel, git_dir
return toplevel:sub(0, -2)
end end
local untracked = {} local untracked = {}
---@param cwd string
---@return string|nil
function M.should_show_untracked(cwd) function M.should_show_untracked(cwd)
if untracked[cwd] ~= nil then if untracked[cwd] ~= nil then
return untracked[cwd] return untracked[cwd]
end end
local cmd = "git -C " .. cwd .. " config --type=bool status.showUntrackedFiles" local profile = log.profile_start("git untracked %s", cwd)
local cmd = { "git", "-C", cwd, "config", "status.showUntrackedFiles" }
log.line("git", table.concat(cmd, " "))
local has_untracked = vim.fn.system(cmd) local has_untracked = vim.fn.system(cmd)
untracked[cwd] = vim.trim(has_untracked) ~= "false"
log.raw("git", has_untracked)
log.profile_end(profile)
untracked[cwd] = vim.trim(has_untracked) ~= "no"
return untracked[cwd] return untracked[cwd]
end end
---@param t table|nil
---@param k string
---@return table
local function nil_insert(t, k)
t = t or {}
t[k] = true
return t
end
---@param status table
---@param cwd string|nil
---@return table
function M.file_status_to_dir_status(status, cwd) function M.file_status_to_dir_status(status, cwd)
local dirs = {} local direct = {}
for p, s in pairs(status) do for p, s in pairs(status) do
if s ~= "!!" then if s ~= "!!" then
local modified = vim.fn.fnamemodify(p, ":h") local modified = vim.fn.fnamemodify(p, ":h")
dirs[modified] = s direct[modified] = nil_insert(direct[modified], s)
end end
end end
for dirname, s in pairs(dirs) do local indirect = {}
for dirname, statuses in pairs(direct) do
for s, _ in pairs(statuses) do
local modified = dirname local modified = dirname
while modified ~= cwd and modified ~= "/" do while modified ~= cwd and modified ~= "/" do
modified = vim.fn.fnamemodify(modified, ":h") modified = vim.fn.fnamemodify(modified, ":h")
dirs[modified] = s indirect[modified] = nil_insert(indirect[modified], s)
end
end end
end end
return dirs local r = { indirect = indirect, direct = direct }
for _, d in pairs(r) do
for dirname, statuses in pairs(d) do
local new_statuses = {}
for s, _ in pairs(statuses) do
table.insert(new_statuses, s)
end
d[dirname] = new_statuses
end
end
return r
end
function M.setup(opts)
if opts.git.cygwin_support then
M.use_cygpath = vim.fn.executable "cygpath" == 1
end
end end
return M return M

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

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

View File

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

118
lua/nvim-tree/keymap.lua Normal file
View File

@@ -0,0 +1,118 @@
local M = {}
--- Apply mappings to a scratch buffer and return buffer local mappings
---@param fn function(bufnr) on_attach or default_on_attach
---@return table as per vim.api.nvim_buf_get_keymap
local function generate_keymap(fn)
-- create an unlisted scratch buffer
local scratch_bufnr = vim.api.nvim_create_buf(false, true)
-- apply mappings
fn(scratch_bufnr)
-- retrieve all
local keymap = vim.api.nvim_buf_get_keymap(scratch_bufnr, "")
-- delete the scratch buffer
vim.api.nvim_buf_delete(scratch_bufnr, { force = true })
return keymap
end
-- stylua: ignore start
---@param bufnr integer
function M.default_on_attach(bufnr)
local api = require('nvim-tree.api')
local function opts(desc)
return {
desc = 'nvim-tree: ' .. desc,
buffer = bufnr,
noremap = true,
silent = true,
nowait = true,
}
end
-- BEGIN_DEFAULT_ON_ATTACH
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-k>', api.node.show_info_popup, opts('Info'))
vim.keymap.set('n', '<C-r>', api.fs.rename_sub, opts('Rename: Omit Filename'))
vim.keymap.set('n', '<C-t>', api.node.open.tab, opts('Open: New Tab'))
vim.keymap.set('n', '<C-v>', api.node.open.vertical, opts('Open: Vertical Split'))
vim.keymap.set('n', '<C-x>', api.node.open.horizontal, opts('Open: Horizontal Split'))
vim.keymap.set('n', '<BS>', api.node.navigate.parent_close, opts('Close Directory'))
vim.keymap.set('n', '<CR>', api.node.open.edit, opts('Open'))
vim.keymap.set('n', '<Tab>', api.node.open.preview, opts('Open Preview'))
vim.keymap.set('n', '>', api.node.navigate.sibling.next, opts('Next Sibling'))
vim.keymap.set('n', '<', api.node.navigate.sibling.prev, opts('Previous Sibling'))
vim.keymap.set('n', '.', api.node.run.cmd, opts('Run Command'))
vim.keymap.set('n', '-', api.tree.change_root_to_parent, opts('Up'))
vim.keymap.set('n', 'a', api.fs.create, opts('Create File Or Directory'))
vim.keymap.set('n', 'bd', api.marks.bulk.delete, opts('Delete Bookmarked'))
vim.keymap.set('n', 'bt', api.marks.bulk.trash, opts('Trash Bookmarked'))
vim.keymap.set('n', 'bmv', api.marks.bulk.move, opts('Move Bookmarked'))
vim.keymap.set('n', 'B', api.tree.toggle_no_buffer_filter, opts('Toggle Filter: No Buffer'))
vim.keymap.set('n', 'c', api.fs.copy.node, opts('Copy'))
vim.keymap.set('n', 'C', api.tree.toggle_git_clean_filter, opts('Toggle Filter: Git Clean'))
vim.keymap.set('n', '[c', api.node.navigate.git.prev, opts('Prev Git'))
vim.keymap.set('n', ']c', api.node.navigate.git.next, opts('Next Git'))
vim.keymap.set('n', 'd', api.fs.remove, opts('Delete'))
vim.keymap.set('n', 'D', api.fs.trash, opts('Trash'))
vim.keymap.set('n', 'E', api.tree.expand_all, opts('Expand All'))
vim.keymap.set('n', 'e', api.fs.rename_basename, opts('Rename: Basename'))
vim.keymap.set('n', ']e', api.node.navigate.diagnostics.next, opts('Next Diagnostic'))
vim.keymap.set('n', '[e', api.node.navigate.diagnostics.prev, opts('Prev Diagnostic'))
vim.keymap.set('n', 'F', api.live_filter.clear, opts('Live Filter: Clear'))
vim.keymap.set('n', 'f', api.live_filter.start, opts('Live Filter: Start'))
vim.keymap.set('n', 'g?', api.tree.toggle_help, opts('Help'))
vim.keymap.set('n', 'gy', api.fs.copy.absolute_path, opts('Copy Absolute Path'))
vim.keymap.set('n', 'ge', api.fs.copy.basename, opts('Copy Basename'))
vim.keymap.set('n', 'H', api.tree.toggle_hidden_filter, opts('Toggle Filter: Dotfiles'))
vim.keymap.set('n', 'I', api.tree.toggle_gitignore_filter, opts('Toggle Filter: Git Ignore'))
vim.keymap.set('n', 'J', api.node.navigate.sibling.last, opts('Last Sibling'))
vim.keymap.set('n', 'K', api.node.navigate.sibling.first, opts('First Sibling'))
vim.keymap.set('n', 'L', api.node.open.toggle_group_empty, opts('Toggle Group Empty'))
vim.keymap.set('n', 'M', api.tree.toggle_no_bookmark_filter, opts('Toggle Filter: No Bookmark'))
vim.keymap.set('n', 'm', api.marks.toggle, opts('Toggle Bookmark'))
vim.keymap.set('n', 'o', api.node.open.edit, opts('Open'))
vim.keymap.set('n', 'O', api.node.open.no_window_picker, opts('Open: No Window Picker'))
vim.keymap.set('n', 'p', api.fs.paste, opts('Paste'))
vim.keymap.set('n', 'P', api.node.navigate.parent, opts('Parent Directory'))
vim.keymap.set('n', 'q', api.tree.close, opts('Close'))
vim.keymap.set('n', 'r', api.fs.rename, opts('Rename'))
vim.keymap.set('n', 'R', api.tree.reload, opts('Refresh'))
vim.keymap.set('n', 's', api.node.run.system, opts('Run System'))
vim.keymap.set('n', 'S', api.tree.search_node, opts('Search'))
vim.keymap.set('n', 'u', api.fs.rename_full, opts('Rename: Full Path'))
vim.keymap.set('n', 'U', api.tree.toggle_custom_filter, opts('Toggle Filter: Hidden'))
vim.keymap.set('n', 'W', api.tree.collapse_all, opts('Collapse'))
vim.keymap.set('n', 'x', api.fs.cut, opts('Cut'))
vim.keymap.set('n', 'y', api.fs.copy.filename, opts('Copy Name'))
vim.keymap.set('n', 'Y', api.fs.copy.relative_path, opts('Copy Relative Path'))
vim.keymap.set('n', '<2-LeftMouse>', api.node.open.edit, opts('Open'))
vim.keymap.set('n', '<2-RightMouse>', api.tree.change_root_to_node, opts('CD'))
-- END_DEFAULT_ON_ATTACH
end
-- 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

View File

@@ -1,238 +1,97 @@
local utils = require "nvim-tree.utils" local utils = require "nvim-tree.utils"
local notify = require "nvim-tree.notify"
local M = {} local M = {}
-- TODO update bit.ly/3vIpEOJ when adding a migration -- silently move, please add to help nvim-tree-legacy-opts
-- migrate the g: to o if the user has not specified that when calling setup
local g_migrations = {
nvim_tree_disable_netrw = function(o)
if o.disable_netrw == nil then
o.disable_netrw = vim.g.nvim_tree_disable_netrw ~= 0
end
end,
nvim_tree_hijack_netrw = function(o)
if o.hijack_netrw == nil then
o.hijack_netrw = vim.g.nvim_tree_hijack_netrw ~= 0
end
end,
nvim_tree_auto_open = function(o)
if o.open_on_setup == nil then
o.open_on_setup = vim.g.nvim_tree_auto_open ~= 0
end
end,
nvim_tree_tab_open = function(o)
if o.open_on_tab == nil then
o.open_on_tab = vim.g.nvim_tree_tab_open ~= 0
end
end,
nvim_tree_update_cwd = function(o)
if o.update_cwd == nil then
o.update_cwd = vim.g.nvim_tree_update_cwd ~= 0
end
end,
nvim_tree_hijack_cursor = function(o)
if o.hijack_cursor == nil then
o.hijack_cursor = vim.g.nvim_tree_hijack_cursor ~= 0
end
end,
nvim_tree_system_open_command = function(o)
utils.table_create_missing(o, "system_open")
if o.system_open.cmd == nil then
o.system_open.cmd = vim.g.nvim_tree_system_open_command
end
end,
nvim_tree_system_open_command_args = function(o)
utils.table_create_missing(o, "system_open")
if o.system_open.args == nil then
o.system_open.args = vim.g.nvim_tree_system_open_command_args
end
end,
nvim_tree_follow = function(o)
utils.table_create_missing(o, "update_focused_file")
if o.update_focused_file.enable == nil then
o.update_focused_file.enable = vim.g.nvim_tree_follow ~= 0
end
end,
nvim_tree_follow_update_path = function(o)
utils.table_create_missing(o, "update_focused_file")
if o.update_focused_file.update_cwd == nil then
o.update_focused_file.update_cwd = vim.g.nvim_tree_follow_update_path ~= 0
end
end,
nvim_tree_lsp_diagnostics = function(o)
utils.table_create_missing(o, "diagnostics")
if o.diagnostics.enable == nil then
o.diagnostics.enable = vim.g.nvim_tree_lsp_diagnostics ~= 0
if o.diagnostics.show_on_dirs == nil then
o.diagnostics.show_on_dirs = vim.g.nvim_tree_lsp_diagnostics ~= 0
end
end
end,
nvim_tree_auto_resize = function(o)
utils.table_create_missing(o, "actions.open_file")
if o.actions.open_file.resize_window == nil then
o.actions.open_file.resize_window = vim.g.nvim_tree_auto_resize ~= 0
end
end,
nvim_tree_bindings = function(o)
utils.table_create_missing(o, "view.mappings")
if o.view.mappings.list == nil then
o.view.mappings.list = vim.g.nvim_tree_bindings
end
end,
nvim_tree_disable_keybindings = function(o)
utils.table_create_missing(o, "view.mappings")
if o.view.mappings.custom_only == nil then
if vim.g.nvim_tree_disable_keybindings ~= 0 then
o.view.mappings.custom_only = true
-- specify one mapping so that defaults do not apply
o.view.mappings.list = {
{ key = "g?", action = "" },
}
end
end
end,
nvim_tree_disable_default_keybindings = function(o)
utils.table_create_missing(o, "view.mappings")
if o.view.mappings.custom_only == nil then
o.view.mappings.custom_only = vim.g.nvim_tree_disable_default_keybindings ~= 0
end
end,
nvim_tree_hide_dotfiles = function(o)
utils.table_create_missing(o, "filters")
if o.filters.dotfiles == nil then
o.filters.dotfiles = vim.g.nvim_tree_hide_dotfiles ~= 0
end
end,
nvim_tree_ignore = function(o)
utils.table_create_missing(o, "filters")
if o.filters.custom == nil then
o.filters.custom = vim.g.nvim_tree_ignore
end
end,
nvim_tree_gitignore = function(o)
utils.table_create_missing(o, "git")
if o.git.ignore == nil then
o.git.ignore = vim.g.nvim_tree_gitignore ~= 0
end
end,
nvim_tree_disable_window_picker = function(o)
utils.table_create_missing(o, "actions.open_file.window_picker")
if o.actions.open_file.window_picker.enable == nil then
o.actions.open_file.window_picker.enable = vim.g.nvim_tree_disable_window_picker == 0
end
end,
nvim_tree_window_picker_chars = function(o)
utils.table_create_missing(o, "actions.open_file.window_picker")
if o.actions.open_file.window_picker.chars == nil then
o.actions.open_file.window_picker.chars = vim.g.nvim_tree_window_picker_chars
end
end,
nvim_tree_window_picker_exclude = function(o)
utils.table_create_missing(o, "actions.open_file.window_picker")
if o.actions.open_file.window_picker.exclude == nil then
o.actions.open_file.window_picker.exclude = vim.g.nvim_tree_window_picker_exclude
end
end,
nvim_tree_quit_on_open = function(o)
utils.table_create_missing(o, "actions.open_file")
if o.actions.open_file.quit_on_open == nil then
o.actions.open_file.quit_on_open = vim.g.nvim_tree_quit_on_open == 1
end
end,
nvim_tree_change_dir_global = function(o)
utils.table_create_missing(o, "actions.change_dir")
if o.actions.change_dir.global == nil then
o.actions.change_dir.global = vim.g.nvim_tree_change_dir_global == 1
end
end,
nvim_tree_indent_markers = function(o)
utils.table_create_missing(o, "renderer.indent_markers")
if o.renderer.indent_markers.enable == nil then
o.renderer.indent_markers.enable = vim.g.nvim_tree_indent_markers == 1
end
end,
}
local function refactored(opts) local function refactored(opts)
-- mapping actions -- 2022/06/20
if opts.view and opts.view.mappings and opts.view.mappings.list then utils.move_missing_val(opts, "update_focused_file", "update_cwd", opts, "update_focused_file", "update_root", true)
for _, m in pairs(opts.view.mappings.list) do utils.move_missing_val(opts, "", "update_cwd", opts, "", "sync_root_with_cwd", true)
if m.action == "toggle_ignored" then
m.action = "toggle_git_ignored" -- 2022/11/07
end utils.move_missing_val(opts, "", "open_on_tab", opts, "tab.sync", "open", false)
utils.move_missing_val(opts, "", "open_on_tab", opts, "tab.sync", "close", true)
utils.move_missing_val(opts, "", "ignore_buf_on_tab_change", opts, "tab.sync", "ignore", true)
-- 2022/11/22
utils.move_missing_val(opts, "renderer", "root_folder_modifier", opts, "renderer", "root_folder_label", true)
-- 2023/01/01
utils.move_missing_val(opts, "update_focused_file", "debounce_delay", opts, "view", "debounce_delay", true)
-- 2023/01/08
utils.move_missing_val(opts, "trash", "require_confirm", opts, "ui.confirm", "trash", true)
-- 2023/01/15
if type(opts.view) == "table" and opts.view.adaptive_size ~= nil then
if opts.view.adaptive_size and type(opts.view.width) ~= "table" then
local width = opts.view.width
opts.view.width = {
min = width,
}
end end
opts.view.adaptive_size = nil
end end
-- update_to_buf_dir -> hijack_directories -- 2023/07/15
if opts.update_to_buf_dir ~= nil then utils.move_missing_val(opts, "", "sort_by", opts, "sort", "sorter", true)
utils.table_create_missing(opts, "hijack_directories")
if opts.hijack_directories.enable == nil then -- 2023/07/16
opts.hijack_directories.enable = opts.update_to_buf_dir.enable utils.move_missing_val(opts, "git", "ignore", opts, "filters", "git_ignored", true)
end
if opts.hijack_directories.auto_open == nil then -- 2023/08/26
opts.hijack_directories.auto_open = opts.update_to_buf_dir.auto_open utils.move_missing_val(opts, "renderer.icons", "webdev_colors", opts, "renderer.icons.web_devicons.file", "color", true)
end
opts.update_to_buf_dir = nil -- 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 end
-- view.auto_resize -> actions.open_file.resize_window -- 2023/10/21
if opts.view and opts.view.auto_resize ~= nil then if type(opts.renderer) == "table" and type(opts.renderer.highlight_git) == "boolean" then
utils.table_create_missing(opts, "actions.open_file") opts.renderer.highlight_git = opts.renderer.highlight_git and "name" or "none"
if opts.actions.open_file.resize_window == nil then
opts.actions.open_file.resize_window = opts.view.auto_resize
end end
opts.view.auto_resize = nil
-- 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
local function deprecated(opts)
if type(opts.view) == "table" and opts.view.hide_root_folder then
notify.info "view.hide_root_folder is deprecated, please set renderer.root_folder_label = false"
end end
end end
local function removed(opts) local function removed(opts)
if opts.auto_close then if opts.auto_close then
utils.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
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
end
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."
end
opts.create_in_closed_folder = nil
end end
function M.migrate_legacy_options(opts) function M.migrate_legacy_options(opts)
-- g: options
local msg
for g, m in pairs(g_migrations) do
if vim.fn.exists("g:" .. g) ~= 0 then
m(opts)
msg = (msg and msg .. ", " or "Following options were moved to setup, see bit.ly/3vIpEOJ: ") .. g
end
end
if msg then
utils.warn(msg)
end
-- silently move -- silently move
refactored(opts) refactored(opts)
-- warn
deprecated(opts)
-- warn and delete -- warn and delete
removed(opts) removed(opts)
end end

View File

@@ -1,48 +1,148 @@
local api = vim.api
local renderer = require "nvim-tree.renderer" local renderer = require "nvim-tree.renderer"
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 notify = require "nvim-tree.notify"
local explorer_node = require "nvim-tree.explorer.node"
---@class LibOpenOpts
---@field path string|nil path
---@field current_window boolean|nil default false
---@field winid number|nil
local M = { local M = {
target_winid = nil, target_winid = nil,
} }
---@return Node|nil
function M.get_node_at_cursor() function M.get_node_at_cursor()
if not core.get_explorer() then local explorer = core.get_explorer()
if not explorer then
return return
end end
local winnr = view.get_winnr()
local winnr = explorer.view:get_winnr()
if not winnr then if not winnr then
return return
end end
local cursor = api.nvim_win_get_cursor(view.get_winnr())
local cursor = vim.api.nvim_win_get_cursor(winnr)
local line = cursor[1] local line = cursor[1]
if view.is_help_ui() then
local help_lines = require("nvim-tree.renderer.help").compute_lines() if line == 1 and explorer.view:is_root_folder_visible(core.get_cwd()) then
local help_text = utils.get_nodes_by_line(help_lines, 1)[line]
return { name = help_text }
else
if line == 1 and core.get_explorer().cwd ~= "/" and view.is_root_folder_visible() 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())[line]
end end
---Create a sanitized partial copy of a node, populating children recursively.
---@param node Node|nil
---@return Node|nil cloned node
local function clone_node(node)
if not node then
node = core.get_explorer()
if not node then
return nil
end
end
local n = {
absolute_path = node.absolute_path,
executable = node.executable,
extension = node.extension,
git_status = node.git_status,
has_children = node.has_children,
hidden = node.hidden,
link_to = node.link_to,
name = node.name,
open = node.open,
type = node.type,
fs_stat = node.fs_stat,
}
if type(node.nodes) == "table" then
n.nodes = {}
for _, child in ipairs(node.nodes) do
table.insert(n.nodes, clone_node(child))
end
end
return n
end
---Api.tree.get_nodes
---@return Node[]|nil
function M.get_nodes()
return clone_node(core.get_explorer())
end end
-- If node is grouped, return the last node in the group. Otherwise, return the given node. -- If node is grouped, return the last node in the group. Otherwise, return the given node.
---@param node Node
---@return Node
function M.get_last_group_node(node) function M.get_last_group_node(node)
local next = node while node and node.group_next do
while next.group_next do node = node.group_next
next = next.group_next
end
return next
end end
function M.expand_or_collapse(node) return node ---@diagnostic disable-line: return-type-mismatch -- it can't be nil
node.open = not node.open 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
---@param node Node
---@return Node[]
function M.get_all_nodes_in_group(node)
local next_node = utils.get_parent_of_group(node)
local nodes = {}
while next_node do
table.insert(nodes, next_node)
next_node = next_node.group_next
end
return nodes
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
function M.expand_or_collapse(node, toggle_group)
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
@@ -51,11 +151,27 @@ function M.expand_or_collapse(node)
core.get_explorer():expand(node) core.get_explorer():expand(node)
end end
local head_node = utils.get_parent_of_group(node)
if toggle_group then
toggle_group_folders(head_node)
end
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
renderer.draw() renderer.draw()
end end
function M.set_target_win() function M.set_target_win()
local id = api.nvim_get_current_win() local id = vim.api.nvim_get_current_win()
local tree_id = view.get_winnr() local tree_id = view.get_winnr()
if tree_id and id == tree_id then if tree_id and id == tree_id then
M.target_winid = 0 M.target_winid = 0
@@ -65,25 +181,36 @@ function M.set_target_win()
M.target_winid = id M.target_winid = id
end end
---@param cwd string
local function handle_buf_cwd(cwd) local function handle_buf_cwd(cwd)
local respect_buf_cwd = vim.g.nvim_tree_respect_buf_cwd or 0 if M.respect_buf_cwd and cwd ~= core.get_cwd() then
if respect_buf_cwd == 1 and cwd ~= core.get_explorer().cwd then require("nvim-tree.actions.root.change-dir").fn(cwd)
require("nvim-tree.actions.change-dir").fn(cwd)
end end
end end
local function open_view_and_draw() local function open_view_and_draw()
local explorer = core.get_explorer()
if not explorer then
return
end
local cwd = vim.fn.getcwd() local cwd = vim.fn.getcwd()
view.open() explorer.view:open()
handle_buf_cwd(cwd) handle_buf_cwd(cwd)
renderer.draw() renderer.draw()
end end
local function should_hijack_current_buf() local function should_hijack_current_buf()
local bufnr = api.nvim_get_current_buf() local bufnr = vim.api.nvim_get_current_buf()
local bufname = api.nvim_buf_get_name(bufnr) local bufname = vim.api.nvim_buf_get_name(bufnr)
local bufmodified = api.nvim_buf_get_option(bufnr, "modified")
local ft = 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
@@ -91,36 +218,80 @@ local function should_hijack_current_buf()
return should_hijack_dir or should_hijack_unnamed return should_hijack_dir or should_hijack_unnamed
end end
function M.open(cwd) ---@param prompt_input string
---@param prompt_select string
---@param items_short string[]
---@param items_long string[]
---@param kind string|nil
---@param callback fun(item_short: string)
function M.prompt(prompt_input, prompt_select, items_short, items_long, kind, callback)
local function format_item(short)
for i, s in ipairs(items_short) do
if short == s then
return items_long[i]
end
end
return ""
end
if M.select_prompts then
vim.ui.select(items_short, { prompt = prompt_select, kind = kind, format_item = format_item }, function(item_short)
callback(item_short)
end)
else
vim.ui.input({ prompt = prompt_input, default = items_short[1] or "" }, function(item_short)
if item_short then
callback(string.lower(item_short and item_short:sub(1, 1)) or nil)
end
end)
end
end
---Open the tree, initialising as needed. Maybe hijack the current buffer.
---@param opts LibOpenOpts|nil
function M.open(opts)
opts = opts or {}
M.set_target_win() M.set_target_win()
if not core.get_explorer() or cwd then if not core.get_explorer() or opts.path then
core.init(cwd or vim.loop.cwd()) if opts.path then
core.init(opts.path)
else
local cwd, err = vim.loop.cwd()
if not cwd then
notify.error(string.format("current working directory unavailable: %s", err))
return
end
core.init(cwd)
end
end
local explorer = core.get_explorer()
if not explorer then
return
end end
if should_hijack_current_buf() then if should_hijack_current_buf() then
view.open_in_current_win() explorer.view:close_this_tab_only()
explorer.view:open_in_win()
renderer.draw()
elseif opts.winid then
explorer.view:open_in_win { hijack_current_buf = false, resize = false, winid = opts.winid }
renderer.draw()
elseif opts.current_window then
explorer.view:open_in_win { hijack_current_buf = false, resize = false }
renderer.draw() renderer.draw()
else else
open_view_and_draw() open_view_and_draw()
end end
view.restore_tab_state() explorer.view:restore_tab_state()
events._dispatch_on_tree_open()
end end
-- @deprecated: use nvim-tree.actions.collapse-all.fn
M.collapse_all = require("nvim-tree.actions.collapse-all").fn
-- @deprecated: use nvim-tree.actions.dir-up.fn
M.dir_up = require("nvim-tree.actions.dir-up").fn
-- @deprecated: use nvim-tree.actions.change-dir.fn
M.change_dir = require("nvim-tree.actions.change-dir").fn
-- @deprecated: use nvim-tree.actions.reloaders.reload_explorer
M.refresh_tree = require("nvim-tree.actions.reloaders").reload_explorer
-- @deprecated: use nvim-tree.actions.reloaders.reload_git
M.reload_git = require("nvim-tree.actions.reloaders").reload_git
-- @deprecated: use nvim-tree.actions.find-file.fn
M.set_index_and_redraw = require("nvim-tree.actions.find-file").fn
function M.setup(opts) function M.setup(opts)
M.hijack_unnamed_buffer_when_opening = opts.hijack_unnamed_buffer_when_opening M.hijack_unnamed_buffer_when_opening = opts.hijack_unnamed_buffer_when_opening
M.hijack_directories = opts.hijack_directories M.hijack_directories = opts.hijack_directories
M.respect_buf_cwd = opts.respect_buf_cwd
M.select_prompts = opts.select_prompts
M.group_empty = opts.renderer.group_empty
end end
return M return M

View File

@@ -1,5 +1,3 @@
local uv = vim.loop
local M = { local M = {
config = nil, config = nil,
path = nil, path = nil,
@@ -8,55 +6,96 @@ local M = {
--- Write to log file --- Write to log file
---@param typ string as per log.types config ---@param typ string as per log.types config
---@param fmt string for string.format ---@param fmt string for string.format
--- @vararg any arguments for string.format ---@param ... any arguments for string.format
function M.raw(typ, fmt, ...) function M.raw(typ, fmt, ...)
if not M.path or not M.config.types[typ] and not M.config.types.all then if not M.enabled(typ) then
return return
end end
local line = string.format(fmt, ...) local line = string.format(fmt, ...)
local file = io.open(M.path, "a") local file = io.open(M.path, "a")
if file then
io.output(file) io.output(file)
io.write(line) io.write(line)
io.close(file) io.close(file)
end end
end
--- Write to log file via M.line ---@class Profile
---@field start number nanos
---@field tag string
--- Write profile start to log file
--- START is prefixed --- START is prefixed
--- @return number nanos to pass to profile_end ---@param fmt string for string.format
---@param ... any arguments for string.format
---@return Profile to pass to profile_end
function M.profile_start(fmt, ...) function M.profile_start(fmt, ...)
if not M.path or not M.config.types.profile and not M.config.types.all then local profile = {}
return if M.enabled "profile" then
profile.start = vim.loop.hrtime()
profile.tag = string.format((fmt or "???"), ...)
M.line("profile", "START %s", profile.tag)
end end
M.line("profile", "START " .. (fmt or "???"), ...) return profile
return uv.hrtime()
end end
--- Write to log file via M.line --- Write profile end to log file
--- END is prefixed and duration in seconds is suffixed --- END is prefixed and duration in seconds is suffixed
--- @param start number nanos returned from profile_start ---@param profile Profile returned from profile_start
function M.profile_end(start, fmt, ...) function M.profile_end(profile)
if not M.path or not M.config.types.profile and not M.config.types.all then if M.enabled "profile" and type(profile) == "table" then
return local millis = profile.start and math.modf((vim.loop.hrtime() - profile.start) / 1000000) or -1
M.line("profile", "END %s %dms", profile.tag or "", millis)
end end
local millis = start and math.modf((uv.hrtime() - start) / 1000000) or -1
M.line("profile", "END " .. (fmt or "???") .. " " .. millis .. "ms", ...)
end end
-- Write to log file via M.raw --- Write to log file
-- time and typ are prefixed and a trailing newline is added --- time and typ are prefixed and a trailing newline is added
---@param typ string as per log.types config
---@param fmt string for string.format
---@param ... any arguments for string.format
function M.line(typ, fmt, ...) function M.line(typ, fmt, ...)
M.raw(typ, string.format("[%s] [%s] %s\n", os.date "%Y-%m-%d %H:%M:%S", typ, fmt), ...) if M.enabled(typ) then
M.raw(typ, string.format("[%s] [%s] %s\n", os.date "%Y-%m-%d %H:%M:%S", typ, (fmt or "???")), ...)
end
end
local inspect_opts = {}
---@param opts table
function M.set_inspect_opts(opts)
inspect_opts = opts
end
--- Write to log file the inspection of a node
--- defaults to the node under cursor if none is provided
---@param typ string as per log.types config
---@param node table|nil node to be inspected
---@param fmt string for string.format
---@vararg any arguments for string.format
function M.node(typ, node, fmt, ...)
if M.enabled(typ) then
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)), ...)
end
end
--- Logging is enabled for typ or all
---@param typ string as per log.types config
---@return boolean
function M.enabled(typ)
return M.path ~= nil and (M.config.types[typ] or M.config.types.all)
end 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 "cache", 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
print("nvim-tree.lua logging to " .. M.path) require("nvim-tree.notify").debug("nvim-tree.lua logging to " .. M.path)
end end
end end

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