Compare commits

...

50 Commits

Author SHA1 Message Date
Alexander Courtis
996219335c refactor(#2826): default lifecycle log off 2025-06-17 17:07:03 +10:00
Alexander Courtis
b1a5cb55b0 Merge remote-tracking branch 'origin/master' into 2826-multi-instance-view-amc 2025-06-17 17:01:20 +10:00
Yavorski
d54a1875a9 fix: invalid window id for popup info window (#3147) 2025-06-17 16:59:28 +10:00
Alexander Courtis
1fe6ed858d refactor(#2826): add lib.target_win_id to tabline 2025-06-17 16:38:47 +10:00
Alexander Courtis
0d975b4cb4 refactor(#2826): add View.tab_line for debugging 2025-06-17 16:22:33 +10:00
Alexander Courtis
c7779d93a7 refactor(#2826): better enumerate_options function 2025-06-17 13:11:04 +10:00
Garry Filakhtov
aa087788d7 docs: fix renderer.icons.bookmarks_placement parameter, misspelling (#3150)
Fix minor documentation issues

Add a missing double quotes around the default value for
`nvim-tree.renderer.icons.bookmarks_placement` config value and fix
spelling of `bookmarked`.
2025-06-17 10:23:34 +10:00
Alexander Courtis
d24f959bf1 refactor(#2826): remove unnecessary view setup and members 2025-06-16 16:18:49 +10:00
Alexander Courtis
8b9c9c107f refactor(#2826): split global View and instance Window 2025-06-16 16:11:06 +10:00
Alexander Courtis
54439447f1 refactor(#2826): add lifecycle logging to all Explorer members 2025-06-16 13:55:35 +10:00
Alexander Courtis
fc81249d4f refactor(#2826): rename View to Window 2025-06-16 13:42:56 +10:00
Alexander Courtis
5377a3fd69 refactor(#2826): move all winhl to appearance constants 2025-06-16 13:00:33 +10:00
Alexander Courtis
76186fa64b Merge remote-tracking branch 'origin/master' into 2826-multi-instance-view-amc 2025-06-16 12:59:49 +10:00
phanium
d87b41ca53 fix: window picker ignore hidden window (#3145) 2025-06-15 15:04:47 +10:00
github-actions[bot]
6b5b366596 chore(master): release nvim-tree 1.13.0 (#3120)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-14 17:30:22 +10:00
Rami Elwan
ae595611fb feat(#3132): add api.node.expand and api.node.collapse (#3133)
* feat: allow passing node to collapse all

* refactor: use snake case

* feat: handle api legacy calls and update signature

* refactor: make sure open is a boolean

* doc: collapse_all

* Revert "doc: collapse_all"

This reverts commit d243da3e14.

* add api.node.collapse

* add api.node.expand

* add api.node.expand

---------

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

* fix(#3143): doc

---------

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

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

* refactoring: move `extmarks_length` to utils.lua

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

---------

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

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

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

* add folder_arrow_padding

* update help docs

* refactor: renderer.icons.padding

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

* refactor: renderer.icons.padding

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-05-05 11:51:29 +10:00
Alexander Courtis
e0759880db refactor(#2826): retain necessary view globals 2025-05-03 08:04:06 +10:00
Alexander Courtis
6218f9c2f6 Merge branch 'master' into 2826-multi-instance-view-amc 2025-05-03 07:22:27 +10:00
Alexander Courtis
582ae48c9e chore: fix incorrect @param (#3115) 2025-04-26 12:55:36 +10:00
Alexander Courtis
21d532fdce refactor(#2826): move View into Explorer package 2025-04-21 14:23:52 +10:00
Alexander Courtis
b5e7406df4 Merge branch 'master' into 2826-multi-instance-view-amc 2025-04-21 14:13:37 +10:00
Alexander Courtis
9594528a8a refactor(#2826): API uses Explorer's View 2025-04-21 14:12:12 +10:00
Alexander Courtis
3a82885cc3 refactor(#2826): move autocmds to Explorer 2025-04-21 12:17:21 +10:00
github-actions[bot]
be5b788f2d chore(master): release nvim-tree 1.12.0 (#3099)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-04-21 12:04:41 +10:00
Alexander Courtis
44cb3d2f0a refactor(#2826): View is an Explorer member 2025-04-21 12:02:19 +10:00
Alexander Courtis
0eb21f66f7 Merge remote-tracking branch 'origin/master' into 2826-multi-instance-view-amc 2025-04-21 09:25:27 +10:00
Alexander Courtis
64bb47f868 ci: simplify luarocks release tag pattern as it was not firing 2025-04-21 08:59:56 +10:00
Devansh Sharma
c24c0470d9 feat: add TreePreOpen event (#3105)
* feat: Add `TreePreOpen` and `TreePreClose` events

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

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

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-04-21 08:39:48 +10:00
Alexander Courtis
f309ca21e9 refactor(#2826): singleton View class 2025-04-20 14:47:43 +10:00
Alexander Courtis
f098195667 refactor(#2826): singleton View class, WIP 2025-04-20 14:28:44 +10:00
Alexander Courtis
0a04e435b1 refactor(#2826): singleton View class, WIP 2025-04-20 14:07:02 +10:00
Alexander Courtis
b95b873626 refactor(#2826): singleton View class, WIP 2025-04-20 13:57:12 +10:00
Alexander Courtis
9d3d0d220f refactor(#2826): singleton View class, WIP 2025-04-20 13:54:37 +10:00
Alexander Courtis
a3fe0c9007 refactor(#2826): singleton View class, WIP 2025-04-20 13:20:41 +10:00
Alexander Courtis
3c024975fd refactor(#2826): singleton View class, WIP 2025-04-20 12:48:22 +10:00
Alexander Courtis
fa64574f16 refactor(#2826): singleton View class, WIP 2025-04-20 12:45:22 +10:00
Devansh Sharma
3a63717d3d fix: reliably dispatch exactly one TreeOpen and TreeClose events (#3107)
* fix: correctly handle `TreeOpen` and `TreeClose` event dispatch

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

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

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

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

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

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

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

* chore: use luals for style check

* chore: use luals for style check

* Revert "chore: use luals for style check"

This reverts commit e5fde80fab.

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-01 11:07:35 +11:00
50 changed files with 1276 additions and 782 deletions

View File

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

View File

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

View File

@@ -1,9 +1,11 @@
name: Luarocks Release name: Luarocks Release
on: on:
push: push:
tags: tags:
- 'v[0-9]+.[0-9]+.[0-9]+' - v*
workflow_dispatch: workflow_dispatch:
jobs: jobs:
luarocks-upload: luarocks-upload:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,42 @@
# Changelog # Changelog
## [1.13.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.12.0...nvim-tree-v1.13.0) (2025-06-14)
### Features
* **#3113:** add renderer.icons.folder_arrow_padding ([#3114](https://github.com/nvim-tree/nvim-tree.lua/issues/3114)) ([ea5097a](https://github.com/nvim-tree/nvim-tree.lua/commit/ea5097a1e2702b4827cb7380e7fa0bd6da87699c))
* **#3132:** add api.node.expand and api.node.collapse ([#3133](https://github.com/nvim-tree/nvim-tree.lua/issues/3133)) ([ae59561](https://github.com/nvim-tree/nvim-tree.lua/commit/ae595611fb2225f2041996c042aa4e4b8663b41e))
### Bug Fixes
* "Invalid buffer id" on closing nvim-tree window ([#3129](https://github.com/nvim-tree/nvim-tree.lua/issues/3129)) ([25d16aa](https://github.com/nvim-tree/nvim-tree.lua/commit/25d16aab7d29ca940a9feb92e6bb734697417009))
* **#2746:** background and right aligned icons in floating windows ([#3128](https://github.com/nvim-tree/nvim-tree.lua/issues/3128)) ([cbc3165](https://github.com/nvim-tree/nvim-tree.lua/commit/cbc3165e08893bb499da035c6f6f9d1512b57664))
* **#3117:** allow changing filename's casing ([bd54d1d](https://github.com/nvim-tree/nvim-tree.lua/commit/bd54d1d33c20d8630703b9842480291588dbad07))
* **#3117:** windows: change file/dir case ([#3135](https://github.com/nvim-tree/nvim-tree.lua/issues/3135)) ([bd54d1d](https://github.com/nvim-tree/nvim-tree.lua/commit/bd54d1d33c20d8630703b9842480291588dbad07))
* **#3122:** remove redundant vim.validate ([#3123](https://github.com/nvim-tree/nvim-tree.lua/issues/3123)) ([e7d1b7d](https://github.com/nvim-tree/nvim-tree.lua/commit/e7d1b7dadc62fe2eccc17d814354b0a5688621ce))
* **#3124:** fix icon padding for "right_align" placements, notably for dotfiles ([#3125](https://github.com/nvim-tree/nvim-tree.lua/issues/3125)) ([e4cd856](https://github.com/nvim-tree/nvim-tree.lua/commit/e4cd856ebf4fec51db10c69d63e43224b701cbce))
* **#3124:** prevent empty icons_right_align response from breaking padding ([e4cd856](https://github.com/nvim-tree/nvim-tree.lua/commit/e4cd856ebf4fec51db10c69d63e43224b701cbce))
* **#3134:** setting one glyph to "" no longer disables others ([#3136](https://github.com/nvim-tree/nvim-tree.lua/issues/3136)) ([ebcaccd](https://github.com/nvim-tree/nvim-tree.lua/commit/ebcaccda1c575fa19a8087445276e6671e2b9b37))
* **#3143:** actions.open_file.window_picker.exclude applies when not using window picker ([#3144](https://github.com/nvim-tree/nvim-tree.lua/issues/3144)) ([05d8172](https://github.com/nvim-tree/nvim-tree.lua/commit/05d8172ebf9cdb2d140cf25b75625374fbc3df7f))
* fixes [#3134](https://github.com/nvim-tree/nvim-tree.lua/issues/3134) ([ebcaccd](https://github.com/nvim-tree/nvim-tree.lua/commit/ebcaccda1c575fa19a8087445276e6671e2b9b37))
* invalid buffer issue ([25d16aa](https://github.com/nvim-tree/nvim-tree.lua/commit/25d16aab7d29ca940a9feb92e6bb734697417009))
## [1.12.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.11.0...nvim-tree-v1.12.0) (2025-04-20)
### Features
* add TreePreOpen event ([#3105](https://github.com/nvim-tree/nvim-tree.lua/issues/3105)) ([c24c047](https://github.com/nvim-tree/nvim-tree.lua/commit/c24c0470d9de277fbebecd718f33561ed7c90298))
### Bug Fixes
* **#3101:** when renderer.highlight_opened_files = "none" do not reload on BufUnload and BufReadPost ([#3102](https://github.com/nvim-tree/nvim-tree.lua/issues/3102)) ([5bea2b3](https://github.com/nvim-tree/nvim-tree.lua/commit/5bea2b37523a31288e0fcab42f3be5c1bd4516bb))
* explicitly set `border` to `"none"` in full name float ([#3094](https://github.com/nvim-tree/nvim-tree.lua/issues/3094)) ([c3c1935](https://github.com/nvim-tree/nvim-tree.lua/commit/c3c193594213c5e2f89ec5d7729cad805f76b256))
* reliably dispatch exactly one TreeOpen and TreeClose events ([#3107](https://github.com/nvim-tree/nvim-tree.lua/issues/3107)) ([3a63717](https://github.com/nvim-tree/nvim-tree.lua/commit/3a63717d3d332d8f39aaf65be7a0e4c2265af021))
## [1.11.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.10.0...nvim-tree-v1.11.0) (2025-02-22) ## [1.11.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.10.0...nvim-tree-v1.11.0) (2025-02-22)

View File

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

View File

@@ -17,7 +17,7 @@ luacheck:
# --diagnosis-as-error does not function for workspace, hence we post-process the output # --diagnosis-as-error does not function for workspace, hence we post-process the output
style-check: style-check:
CodeFormat check --config .editorconfig --diagnosis-as-error --workspace lua @scripts/luals-check.sh codestyle-check
style-doc: style-doc:
scripts/doc-comments.sh scripts/doc-comments.sh

View File

@@ -206,7 +206,7 @@ Show the mappings: `g?`
`S` Search |nvim-tree-api.tree.search_node()| `S` Search |nvim-tree-api.tree.search_node()|
`u` Rename: Full Path |nvim-tree-api.fs.rename_full()| `u` Rename: Full Path |nvim-tree-api.fs.rename_full()|
`U` Toggle Filter: Hidden |nvim-tree-api.tree.toggle_custom_filter()| `U` Toggle Filter: Hidden |nvim-tree-api.tree.toggle_custom_filter()|
`W` Collapse |nvim-tree-api.tree.collapse_all()| `W` Collapse All |nvim-tree-api.tree.collapse_all()|
`x` Cut |nvim-tree-api.fs.cut()| `x` Cut |nvim-tree-api.fs.cut()|
`y` Copy Name |nvim-tree-api.fs.copy.filename()| `y` Copy Name |nvim-tree-api.fs.copy.filename()|
`Y` Copy Relative Path |nvim-tree-api.fs.copy.relative_path()| `Y` Copy Relative Path |nvim-tree-api.fs.copy.relative_path()|
@@ -341,7 +341,7 @@ See |nvim-tree-highlight| for details.
See |nvim-tree-api.tree.collapse_all()| See |nvim-tree-api.tree.collapse_all()|
Calls: `api.tree.collapse_all(false)` Calls: `api.tree.collapse_all({ keep_buffers = false })`
*:NvimTreeCollapseKeepBuffers* *:NvimTreeCollapseKeepBuffers*
@@ -350,7 +350,7 @@ See |nvim-tree-highlight| for details.
See |nvim-tree-api.tree.collapse_all()| See |nvim-tree-api.tree.collapse_all()|
Calls: `api.tree.collapse_all(true)` Calls: `api.tree.collapse_all({ keep_buffers = true })`
*:NvimTreeHiTest* *:NvimTreeHiTest*
@@ -462,7 +462,10 @@ Following is the default configuration. See |nvim-tree-opts| for details. >lua
hidden_placement = "after", hidden_placement = "after",
diagnostics_placement = "signcolumn", diagnostics_placement = "signcolumn",
bookmarks_placement = "signcolumn", bookmarks_placement = "signcolumn",
padding = " ", padding = {
icon = " ",
folder_arrow = " ",
},
symlink_arrow = " ➛ ", symlink_arrow = " ➛ ",
show = { show = {
file = true, file = true,
@@ -646,6 +649,7 @@ Following is the default configuration. See |nvim-tree-opts| for details. >lua
dev = false, dev = false,
diagnostics = false, diagnostics = false,
git = false, git = false,
lifecycle = false,
profile = false, profile = false,
watcher = false, watcher = false,
}, },
@@ -1063,12 +1067,16 @@ Configuration options for icons.
*nvim-tree.renderer.icons.bookmarks_placement* *nvim-tree.renderer.icons.bookmarks_placement*
Bookmark icon placement. Bookmark icon placement.
Type: `string`, Default: `signcolumn` Type: `string`, Default: `"signcolumn"`
*nvim-tree.renderer.icons.padding* *nvim-tree.renderer.icons.padding.icon*
Inserted between icon and filename. Inserted between icon and filename.
Type: `string`, Default: `" "` Type: `string`, Default: `" "`
*nvim-tree.renderer.icons.padding.folder_arrow*
Inserted between folder arrow icon and file/folder icon.
Type: `string`, Default: `" "`
*nvim-tree.renderer.icons.symlink_arrow* *nvim-tree.renderer.icons.symlink_arrow*
Used as a separator between symlinks' source and target. Used as a separator between symlinks' source and target.
Type: `string`, Default: `" ➛ "` Type: `string`, Default: `" ➛ "`
@@ -1373,7 +1381,7 @@ delete/wipe. A reload or filesystem event will result in an update.
Type: `boolean`, Default: `false` Type: `boolean`, Default: `false`
*nvim-tree.filters.no_bookmark* *nvim-tree.filters.no_bookmark*
Do not show files that are not bookarked. Do not show files that are not bookmarked.
Toggle via |nvim-tree-api.tree.toggle_no_bookmark_filter()|, default `M` Toggle via |nvim-tree-api.tree.toggle_no_bookmark_filter()|, default `M`
Enabling this is not useful as there is no means yet to persist bookmarks. Enabling this is not useful as there is no means yet to persist bookmarks.
Type: `boolean`, Default: `false` Type: `boolean`, Default: `false`
@@ -1463,7 +1471,8 @@ vim |current-directory| behaviour.
Type: `boolean`, Default: `false` Type: `boolean`, Default: `false`
*nvim-tree.actions.expand_all* *nvim-tree.actions.expand_all*
Configuration for expand_all behaviour. Configuration for |nvim-tree-api.tree.expand_all()| and
|nvim-tree-api.node.expand()|
*nvim-tree.actions.expand_all.max_folder_discovery* *nvim-tree.actions.expand_all.max_folder_discovery*
Limit the number of folders being explored when expanding every folders. Limit the number of folders being explored when expanding every folders.
@@ -1516,7 +1525,8 @@ Configuration options for opening a file from nvim-tree.
*nvim-tree.actions.open_file.window_picker.enable* *nvim-tree.actions.open_file.window_picker.enable*
Enable the feature. If the feature is not enabled, files will open in Enable the feature. If the feature is not enabled, files will open in
window from which you last opened the tree. window from which you last opened the tree, obeying
|nvim-tree.actions.open_file.window_picker.exclude|
Type: `boolean`, Default: `true` Type: `boolean`, Default: `true`
*nvim-tree.actions.open_file.window_picker.picker* *nvim-tree.actions.open_file.window_picker.picker*
@@ -1535,9 +1545,10 @@ Configuration options for opening a file from nvim-tree.
Type: `string`, Default: `"ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"` Type: `string`, Default: `"ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"`
*nvim-tree.actions.open_file.window_picker.exclude* *nvim-tree.actions.open_file.window_picker.exclude*
Table of buffer option names mapped to a list of option values that Table of buffer option names mapped to a list of option values.
indicates to the picker that the buffer's window should not be Windows containing matching buffers will not be:
selectable. - available when using a window picker
- selected when not using a window picker
Type: `table`, Default: >lua Type: `table`, Default: >lua
{ {
filetype = { filetype = {
@@ -1822,10 +1833,13 @@ tree.find_file({opts}) *nvim-tree-api.tree.find_file()*
tree.search_node() *nvim-tree-api.tree.search_node()* tree.search_node() *nvim-tree-api.tree.search_node()*
Open the search dialogue as per the search_node action. Open the search dialogue as per the search_node action.
tree.collapse_all({keep_buffers}) *nvim-tree-api.tree.collapse_all()* tree.collapse_all({opts}) *nvim-tree-api.tree.collapse_all()*
Collapse the tree. Collapse the tree.
Parameters: ~ Parameters: ~
• {opts} (table) optional parameters
Options: ~
• {keep_buffers} (boolean) do not collapse nodes with open buffers. • {keep_buffers} (boolean) do not collapse nodes with open buffers.
tree.expand_all({node}) *nvim-tree-api.tree.expand_all()* tree.expand_all({node}) *nvim-tree-api.tree.expand_all()*
@@ -2266,6 +2280,23 @@ node.buffer.wipe({node}, {opts}) *nvim-tree-api.node.buffer.wipe()*
Options: ~ Options: ~
• {force} (boolean) wipe even if buffer is modified, default false • {force} (boolean) wipe even if buffer is modified, default false
node.expand({node}) *nvim-tree-api.node.expand()*
Recursively expand all nodes under a directory or a file's parent
directory.
Parameters: ~
• {node} (Node|nil) file or folder
node.collapse({node}, {opts}) *nvim-tree-api.node.collapse()*
Collapse the tree under a directory or a file's parent directory.
Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {keep_buffers} (boolean) do not collapse nodes with open buffers.
============================================================================== ==============================================================================
6.4 API GIT *nvim-tree-api.git* 6.4 API GIT *nvim-tree-api.git*
@@ -2520,7 +2551,7 @@ You are encouraged to copy these to your own |nvim-tree.on_attach| function. >lu
vim.keymap.set("n", "S", api.tree.search_node, opts("Search")) vim.keymap.set("n", "S", api.tree.search_node, opts("Search"))
vim.keymap.set("n", "u", api.fs.rename_full, opts("Rename: Full Path")) vim.keymap.set("n", "u", api.fs.rename_full, opts("Rename: Full Path"))
vim.keymap.set("n", "U", api.tree.toggle_custom_filter, opts("Toggle Filter: Hidden")) vim.keymap.set("n", "U", api.tree.toggle_custom_filter, opts("Toggle Filter: Hidden"))
vim.keymap.set("n", "W", api.tree.collapse_all, opts("Collapse")) vim.keymap.set("n", "W", api.tree.collapse_all, opts("Collapse All"))
vim.keymap.set("n", "x", api.fs.cut, opts("Cut")) vim.keymap.set("n", "x", api.fs.cut, opts("Cut"))
vim.keymap.set("n", "y", api.fs.copy.filename, opts("Copy Name")) vim.keymap.set("n", "y", api.fs.copy.filename, opts("Copy Name"))
vim.keymap.set("n", "Y", api.fs.copy.relative_path, opts("Copy Relative Path")) vim.keymap.set("n", "Y", api.fs.copy.relative_path, opts("Copy Relative Path"))
@@ -2769,13 +2800,21 @@ e.g. handler for node renamed: >lua
|nvim_tree_events_kind| |nvim_tree_events_kind|
- Event.Ready - Event.Ready
When NvimTree has been initialized When NvimTree has been initialized.
• Note: Handler takes no parameter.
- Event.TreePreOpen
Invoked before the window and buffer for NvimTree are created
or opened. Before |Event.TreeOpen| event.
• Note: Handler takes no parameter. • Note: Handler takes no parameter.
- Event.TreeOpen - Event.TreeOpen
Invoked after the NvimTree is opened.
• Note: Handler takes no parameter. • Note: Handler takes no parameter.
- Event.TreeClose - Event.TreeClose
Invoked after the NvimTree is closed, but before the window is
closed. Dispatched on |WinClosed| event for NvimTree window.
• Note: Handler takes no parameter. • Note: Handler takes no parameter.
- Event.Resize - When NvimTree is resized. - Event.Resize - When NvimTree is resized.
@@ -3019,6 +3058,7 @@ needed.
`sort_by` |nvim-tree.sort.sorter| `sort_by` |nvim-tree.sort.sorter|
`git.ignore` |nvim-tree.filters.git_ignored| `git.ignore` |nvim-tree.filters.git_ignored|
`renderer.icons.webdev_colors` |nvim-tree.renderer.icons.web_devicons.file.color| `renderer.icons.webdev_colors` |nvim-tree.renderer.icons.web_devicons.file.color|
`renderer.icons.padding` |nvim-tree.renderer.icons.padding.icon|
============================================================================== ==============================================================================
14.2 LEGACY: HIGHLIGHT *nvim-tree-legacy-highlight* 14.2 LEGACY: HIGHLIGHT *nvim-tree-legacy-highlight*
@@ -3175,7 +3215,8 @@ highlight group is not, hard linking as follows: >
|nvim-tree.renderer.icons.glyphs.symlink| |nvim-tree.renderer.icons.glyphs.symlink|
|nvim-tree.renderer.icons.hidden_placement| |nvim-tree.renderer.icons.hidden_placement|
|nvim-tree.renderer.icons.modified_placement| |nvim-tree.renderer.icons.modified_placement|
|nvim-tree.renderer.icons.padding| |nvim-tree.renderer.icons.padding.folder_arrow|
|nvim-tree.renderer.icons.padding.icon|
|nvim-tree.renderer.icons.show| |nvim-tree.renderer.icons.show|
|nvim-tree.renderer.icons.show.bookmarks| |nvim-tree.renderer.icons.show.bookmarks|
|nvim-tree.renderer.icons.show.diagnostics| |nvim-tree.renderer.icons.show.diagnostics|
@@ -3282,6 +3323,8 @@ highlight group is not, hard linking as follows: >
|nvim-tree-api.marks.toggle()| |nvim-tree-api.marks.toggle()|
|nvim-tree-api.node.buffer.delete()| |nvim-tree-api.node.buffer.delete()|
|nvim-tree-api.node.buffer.wipe()| |nvim-tree-api.node.buffer.wipe()|
|nvim-tree-api.node.collapse()|
|nvim-tree-api.node.expand()|
|nvim-tree-api.node.navigate.diagnostics.next()| |nvim-tree-api.node.navigate.diagnostics.next()|
|nvim-tree-api.node.navigate.diagnostics.next_recursive()| |nvim-tree-api.node.navigate.diagnostics.next_recursive()|
|nvim-tree-api.node.navigate.diagnostics.prev()| |nvim-tree-api.node.navigate.diagnostics.prev()|

View File

@@ -89,10 +89,10 @@ function M.tab_enter()
return return
end end
end end
view.open({ focus_tree = false })
local explorer = core.get_explorer() local explorer = core.get_explorer()
if explorer then if explorer then
explorer.window:open({ focus_tree = false })
explorer.renderer:draw() explorer.renderer:draw()
end end
end end
@@ -150,21 +150,6 @@ local function setup_autocommands(opts)
vim.api.nvim_create_autocmd(name, vim.tbl_extend("force", default_opts, custom_opts)) vim.api.nvim_create_autocmd(name, vim.tbl_extend("force", default_opts, custom_opts))
end end
-- prevent new opened file from opening in the same window as nvim-tree
create_nvim_tree_autocmd("BufWipeout", {
pattern = "NvimTree_*",
callback = function()
if not utils.is_nvim_tree_buf(0) then
return
end
if opts.actions.open_file.eject then
view._prevent_buffer_override()
else
view.abandon_current_window()
end
end,
})
if opts.tab.sync.open then if opts.tab.sync.open then
create_nvim_tree_autocmd("TabEnter", { callback = vim.schedule_wrap(M.tab_enter) }) create_nvim_tree_autocmd("TabEnter", { callback = vim.schedule_wrap(M.tab_enter) })
end end
@@ -226,16 +211,19 @@ local function setup_autocommands(opts)
}) })
end end
if opts.view.float.enable and opts.view.float.quit_on_focus_loss then -- Handles event dispatch when tree is closed by `:q`
create_nvim_tree_autocmd("WinLeave", { create_nvim_tree_autocmd("WinClosed", {
pattern = "NvimTree_*", pattern = "*",
callback = function() ---@param ev vim.api.keyset.create_autocmd.callback_args
if utils.is_nvim_tree_buf(0) then callback = function(ev)
view.close() if not vim.api.nvim_buf_is_valid(ev.buf) then
end return
end, end
}) if vim.api.nvim_get_option_value("filetype", { buf = ev.buf }) == "NvimTree" then
end require("nvim-tree.events")._dispatch_on_tree_close()
end
end,
})
end end
local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
@@ -323,7 +311,10 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
hidden_placement = "after", hidden_placement = "after",
diagnostics_placement = "signcolumn", diagnostics_placement = "signcolumn",
bookmarks_placement = "signcolumn", bookmarks_placement = "signcolumn",
padding = " ", padding = {
icon = " ",
folder_arrow = " ",
},
symlink_arrow = "", symlink_arrow = "",
show = { show = {
file = true, file = true,
@@ -507,6 +498,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
dev = false, dev = false,
diagnostics = false, diagnostics = false,
git = false, git = false,
lifecycle = false,
profile = false, profile = false,
watcher = false, watcher = false,
}, },
@@ -675,9 +667,11 @@ local function localise_default_opts()
end end
function M.purge_all_state() function M.purge_all_state()
view.close_all_tabs()
view.abandon_all_windows()
local explorer = core.get_explorer() local explorer = core.get_explorer()
if explorer then
explorer.window:close_all_tabs()
end
view.abandon_all_windows()
if explorer then if explorer then
require("nvim-tree.git").purge_state() require("nvim-tree.git").purge_state()
explorer:destroy() explorer:destroy()
@@ -731,7 +725,6 @@ function M.setup(conf)
require("nvim-tree.explorer.watch").setup(opts) require("nvim-tree.explorer.watch").setup(opts)
require("nvim-tree.git").setup(opts) require("nvim-tree.git").setup(opts)
require("nvim-tree.git.utils").setup(opts) require("nvim-tree.git.utils").setup(opts)
require("nvim-tree.view").setup(opts)
require("nvim-tree.lib").setup(opts) require("nvim-tree.lib").setup(opts)
require("nvim-tree.renderer.components").setup(opts) require("nvim-tree.renderer.components").setup(opts)
require("nvim-tree.buffers").setup(opts) require("nvim-tree.buffers").setup(opts)

View File

@@ -31,6 +31,8 @@ local Clipboard = Class:extend()
---@protected ---@protected
---@param args ClipboardArgs ---@param args ClipboardArgs
function Clipboard:new(args) function Clipboard:new(args)
args.explorer:log_new("Clipboard")
self.explorer = args.explorer self.explorer = args.explorer
self.data = { self.data = {
@@ -42,6 +44,10 @@ function Clipboard:new(args)
self.reg = self.explorer.opts.actions.use_system_clipboard and "+" or "1" self.reg = self.explorer.opts.actions.use_system_clipboard and "+" or "1"
end end
function Clipboard:destroy()
self.explorer:log_destroy("Clipboard")
end
---@param source string ---@param source string
---@param destination string ---@param destination string
---@return boolean ---@return boolean

View File

@@ -1,7 +1,6 @@
local core = require("nvim-tree.core") local core = require("nvim-tree.core")
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local events = require("nvim-tree.events") local events = require("nvim-tree.events")
local view = require("nvim-tree.view")
local lib = require("nvim-tree.lib") local lib = require("nvim-tree.lib")
local notify = require("nvim-tree.notify") local notify = require("nvim-tree.notify")
@@ -14,10 +13,12 @@ local M = {
---@param windows integer[] ---@param windows integer[]
local function close_windows(windows) local function close_windows(windows)
local explorer = core.get_explorer()
-- Prevent from closing when the win count equals 1 or 2, -- Prevent from closing when the win count equals 1 or 2,
-- where the win to remove could be the last opened. -- where the win to remove could be the last opened.
-- For details see #2503. -- For details see #2503.
if view.View.float.enable and #vim.api.nvim_list_wins() < 3 then if explorer and explorer.window.float.enable and #vim.api.nvim_list_wins() < 3 then
return return
end end
@@ -30,16 +31,17 @@ end
---@param absolute_path string ---@param absolute_path string
local function clear_buffer(absolute_path) local function clear_buffer(absolute_path)
local explorer = core.get_explorer()
local bufs = vim.fn.getbufinfo({ bufloaded = 1, buflisted = 1 }) local bufs = vim.fn.getbufinfo({ bufloaded = 1, buflisted = 1 })
for _, buf in pairs(bufs) do for _, buf in pairs(bufs) do
if buf.name == absolute_path then if buf.name == absolute_path then
local tree_winnr = vim.api.nvim_get_current_win() local tree_winnr = vim.api.nvim_get_current_win()
if buf.hidden == 0 and (#bufs > 1 or view.View.float.enable) then if buf.hidden == 0 and (#bufs > 1 or explorer and explorer.window.float.enable) then
vim.api.nvim_set_current_win(buf.windows[1]) vim.api.nvim_set_current_win(buf.windows[1])
vim.cmd(":bn") vim.cmd(":bn")
end end
vim.api.nvim_buf_delete(buf.bufnr, { force = true }) vim.api.nvim_buf_delete(buf.bufnr, { force = true })
if not view.View.float.quit_on_focus_loss then if explorer and not explorer.window.float.quit_on_focus_loss then
vim.api.nvim_set_current_win(tree_winnr) vim.api.nvim_set_current_win(tree_winnr)
end end
if M.config.actions.remove_file.close_window then if M.config.actions.remove_file.close_window then

View File

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

View File

@@ -2,6 +2,7 @@
local lib = require("nvim-tree.lib") local lib = require("nvim-tree.lib")
local notify = require("nvim-tree.notify") local notify = require("nvim-tree.notify")
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local core = require("nvim-tree.core")
local view = require("nvim-tree.view") local view = require("nvim-tree.view")
local M = {} local M = {}
@@ -39,21 +40,10 @@ local function usable_win_ids()
end end
local win_config = vim.api.nvim_win_get_config(id) local win_config = vim.api.nvim_win_get_config(id)
return id ~= tree_winid and win_config.focusable and not win_config.external or false return id ~= tree_winid and win_config.focusable and not win_config.hide and not win_config.external or false
end, win_ids) end, win_ids)
end end
---Find the first window in the tab that is not NvimTree.
---@return integer -1 if none available
local function first_win_id()
local selectable = usable_win_ids()
if #selectable > 0 then
return selectable[1]
else
return -1
end
end
---Get user to pick a window in the tab that is not NvimTree. ---Get user to pick a window in the tab that is not NvimTree.
---@return integer|nil -- If a valid window was picked, return its id. If an ---@return integer|nil -- If a valid window was picked, return its id. If an
--- invalid window was picked / user canceled, return nil. If there are --- invalid window was picked / user canceled, return nil. If there are
@@ -198,7 +188,10 @@ end
local function open_file_in_tab(filename) local function open_file_in_tab(filename)
if M.quit_on_open then if M.quit_on_open then
view.close() local explorer = core.get_explorer()
if explorer then
explorer.window:close()
end
end end
if M.relative_path then if M.relative_path then
filename = utils.path_relative(filename, vim.fn.getcwd()) filename = utils.path_relative(filename, vim.fn.getcwd())
@@ -208,7 +201,10 @@ end
local function drop(filename) local function drop(filename)
if M.quit_on_open then if M.quit_on_open then
view.close() local explorer = core.get_explorer()
if explorer then
explorer.window:close()
end
end end
if M.relative_path then if M.relative_path then
filename = utils.path_relative(filename, vim.fn.getcwd()) filename = utils.path_relative(filename, vim.fn.getcwd())
@@ -218,7 +214,10 @@ end
local function tab_drop(filename) local function tab_drop(filename)
if M.quit_on_open then if M.quit_on_open then
view.close() local explorer = core.get_explorer()
if explorer then
explorer.window:close()
end
end end
if M.relative_path then if M.relative_path then
filename = utils.path_relative(filename, vim.fn.getcwd()) filename = utils.path_relative(filename, vim.fn.getcwd())
@@ -239,16 +238,24 @@ local function on_preview(buf_loaded)
once = true, once = true,
}) })
end end
view.focus() local explorer = core.get_explorer()
if explorer then
explorer.window:focus()
end
end end
local function get_target_winid(mode) local function get_target_winid(mode)
local target_winid local target_winid
if not M.window_picker.enable or string.find(mode, "no_picker") then if not M.window_picker.enable or string.find(mode, "no_picker") then
target_winid = lib.target_winid target_winid = lib.target_winid
-- first available window local usable_wins = usable_win_ids()
if not vim.tbl_contains(vim.api.nvim_tabpage_list_wins(0), target_winid) then -- first available usable window
target_winid = first_win_id() if not vim.tbl_contains(usable_wins, target_winid) then
if #usable_wins > 0 then
target_winid = usable_wins[1]
else
target_winid = -1
end
end end
else else
-- pick a window -- pick a window
@@ -279,6 +286,8 @@ local function set_current_win_no_autocmd(winid, autocmd)
end end
local function open_in_new_window(filename, mode) local function open_in_new_window(filename, mode)
local explorer = core.get_explorer()
if type(mode) ~= "string" then if type(mode) ~= "string" then
mode = "" mode = ""
end end
@@ -301,7 +310,11 @@ local function open_in_new_window(filename, mode)
end, vim.api.nvim_list_wins()) 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 create_new_window = #win_ids == 1 -- This implies that the nvim-tree window is the only one
local new_window_side = (view.View.side == "right") and "aboveleft" or "belowright"
local new_window_side = "belowright"
if explorer and (explorer.window.side == "right") then
new_window_side = "aboveleft"
end
-- Target is invalid: create new window -- Target is invalid: create new window
if not vim.tbl_contains(win_ids, target_winid) then if not vim.tbl_contains(win_ids, target_winid) then
@@ -333,7 +346,7 @@ local function open_in_new_window(filename, mode)
end end
end end
if (mode == "preview" or mode == "preview_no_picker") and view.View.float.enable then if (mode == "preview" or mode == "preview_no_picker") and explorer and explorer.window.float.enable then
-- ignore "WinLeave" autocmd on preview -- ignore "WinLeave" autocmd on preview
-- because the registered "WinLeave" -- because the registered "WinLeave"
-- will kill the floating window immediately -- will kill the floating window immediately
@@ -384,6 +397,8 @@ end
---@param filename string ---@param filename string
---@return nil ---@return nil
function M.fn(mode, filename) function M.fn(mode, filename)
local explorer = core.get_explorer()
if type(mode) ~= "string" then if type(mode) ~= "string" then
mode = "" mode = ""
end end
@@ -418,16 +433,16 @@ function M.fn(mode, filename)
vim.bo.bufhidden = "" vim.bo.bufhidden = ""
end end
if M.resize_window then if M.resize_window and explorer then
view.resize() explorer.window:resize()
end end
if mode == "preview" or mode == "preview_no_picker" then if mode == "preview" or mode == "preview_no_picker" then
return on_preview(buf_loaded) return on_preview(buf_loaded)
end end
if M.quit_on_open then if M.quit_on_open and explorer then
view.close() explorer.window:close()
end end
end end

View File

@@ -41,11 +41,12 @@ function M.fn(opts)
return return
end end
if view.is_visible() then local explorer = core.get_explorer()
if explorer and view.is_visible() then
-- focus -- focus
if opts.focus then if opts.focus then
lib.set_target_win() lib.set_target_win()
view.focus() explorer.window:focus()
end end
elseif opts.open then elseif opts.open then
-- open -- open

View File

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

View File

@@ -2,6 +2,7 @@ local core = require("nvim-tree.core")
local Iterator = require("nvim-tree.iterators.node-iterator") local Iterator = require("nvim-tree.iterators.node-iterator")
local notify = require("nvim-tree.notify") local notify = require("nvim-tree.notify")
local FileNode = require("nvim-tree.node.file")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
local M = {} local M = {}
@@ -70,23 +71,38 @@ local function gen_iterator()
end end
end end
---Expand the directory node or the root ---@param node Node?
---@param node Node local function expand_node(node)
function M.fn(node) if not node then
local explorer = core.get_explorer()
local parent = node:as(DirectoryNode) or explorer
if not parent then
return return
end end
if gen_iterator()(parent) then if gen_iterator()(node) then
notify.warn("expansion iteration was halted after " .. M.MAX_FOLDER_DISCOVERY .. " discovered folders") notify.warn("expansion iteration was halted after " .. M.MAX_FOLDER_DISCOVERY .. " discovered folders")
end end
local explorer = core.get_explorer()
if explorer then if explorer then
explorer.renderer:draw() explorer.renderer:draw()
end end
end end
---Expand the directory node or the root
---@param node Node
function M.all(node)
expand_node(node and node:as(DirectoryNode) or core.get_explorer())
end
---Expand the directory node or parent node
---@param node Node
function M.node(node)
if not node then
return
end
expand_node(node:is(FileNode) and node.parent or node:as(DirectoryNode))
end
function M.setup(opts) function M.setup(opts)
M.MAX_FOLDER_DISCOVERY = opts.actions.expand_all.max_folder_discovery M.MAX_FOLDER_DISCOVERY = opts.actions.expand_all.max_folder_discovery
M.EXCLUDE = to_lookup_table(opts.actions.expand_all.exclude) M.EXCLUDE = to_lookup_table(opts.actions.expand_all.exclude)

View File

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

View File

@@ -1,3 +1,4 @@
local core = require("nvim-tree.core")
local lib = require("nvim-tree.lib") local lib = require("nvim-tree.lib")
local view = require("nvim-tree.view") local view = require("nvim-tree.view")
local finders_find_file = require("nvim-tree.actions.finders.find-file") local finders_find_file = require("nvim-tree.actions.finders.find-file")
@@ -23,10 +24,12 @@ function M.fn(opts)
opts.path = nil opts.path = nil
end end
if view.is_visible() then local explorer = core.get_explorer()
if explorer and view.is_visible() then
-- focus -- focus
lib.set_target_win() lib.set_target_win()
view.focus() explorer.window:focus()
else else
-- open -- open
lib.open({ lib.open({

View File

@@ -1,14 +1,19 @@
local view = require("nvim-tree.view") local core = require("nvim-tree.core")
local M = {} local M = {}
---Resize the tree, persisting the new size. ---Resize the tree, persisting the new size.
---@param opts ApiTreeResizeOpts|nil ---@param opts ApiTreeResizeOpts|nil
function M.fn(opts) function M.fn(opts)
local explorer = core.get_explorer()
if not explorer then
return
end
if opts == nil then if opts == nil then
-- reset to config values -- reset to config values
view.configure_width() explorer.window:configure_width()
view.resize() explorer.window:resize()
return return
end end
@@ -16,19 +21,19 @@ function M.fn(opts)
local width_cfg = options.width local width_cfg = options.width
if width_cfg ~= nil then if width_cfg ~= nil then
view.configure_width(width_cfg) explorer.window:configure_width(width_cfg)
view.resize() explorer.window:resize()
return return
end end
if not view.is_width_determined() then if not explorer.window:is_width_determined() then
-- {absolute} and {relative} do nothing when {width} is a function. -- {absolute} and {relative} do nothing when {width} is a function.
return return
end end
local absolute = options.absolute local absolute = options.absolute
if type(absolute) == "number" then if type(absolute) == "number" then
view.resize(absolute) explorer.window:resize(absolute)
return return
end end
@@ -39,7 +44,7 @@ function M.fn(opts)
relative_size = "+" .. relative_size relative_size = "+" .. relative_size
end end
view.resize(relative_size) explorer.window:resize(relative_size)
return return
end end
end end

View File

@@ -1,3 +1,4 @@
local core = require("nvim-tree.core")
local lib = require("nvim-tree.lib") local lib = require("nvim-tree.lib")
local view = require("nvim-tree.view") local view = require("nvim-tree.view")
local finders_find_file = require("nvim-tree.actions.finders.find-file") local finders_find_file = require("nvim-tree.actions.finders.find-file")
@@ -10,6 +11,8 @@ local M = {}
---@param cwd boolean|nil legacy -> opts.path ---@param cwd boolean|nil legacy -> opts.path
---@param bang boolean|nil legacy -> opts.update_root ---@param bang boolean|nil legacy -> opts.update_root
function M.fn(opts, no_focus, cwd, bang) function M.fn(opts, no_focus, cwd, bang)
local explorer = core.get_explorer()
-- legacy arguments -- legacy arguments
if type(opts) == "boolean" then if type(opts) == "boolean" then
opts = { opts = {
@@ -40,9 +43,9 @@ function M.fn(opts, no_focus, cwd, bang)
opts.path = nil opts.path = nil
end end
if view.is_visible() then if explorer and view.is_visible() then
-- close -- close
view.close() explorer.window:close()
else else
-- open -- open
lib.open({ lib.open({

View File

@@ -141,9 +141,9 @@ Api.tree.focus = Api.tree.open
---@field focus boolean|nil default true ---@field focus boolean|nil default true
Api.tree.toggle = wrap(actions.tree.toggle.fn) Api.tree.toggle = wrap(actions.tree.toggle.fn)
Api.tree.close = wrap(view.close) Api.tree.close = wrap_explorer_member("window", "close")
Api.tree.close_in_this_tab = wrap(view.close_this_tab_only) Api.tree.close_in_this_tab = wrap_explorer_member("window", "close_this_tab_only")
Api.tree.close_in_all_tabs = wrap(view.close_all_tabs) Api.tree.close_in_all_tabs = wrap_explorer_member("window", "close_all_tabs")
Api.tree.reload = wrap_explorer("reload_explorer") Api.tree.reload = wrap_explorer("reload_explorer")
---@class ApiTreeResizeOpts ---@class ApiTreeResizeOpts
@@ -182,8 +182,12 @@ Api.tree.get_nodes = wrap_explorer("get_nodes")
Api.tree.find_file = wrap(actions.tree.find_file.fn) Api.tree.find_file = wrap(actions.tree.find_file.fn)
Api.tree.search_node = wrap(actions.finders.search_node.fn) Api.tree.search_node = wrap(actions.finders.search_node.fn)
Api.tree.collapse_all = wrap(actions.tree.modifiers.collapse_all.fn)
Api.tree.expand_all = wrap_node(actions.tree.modifiers.expand_all.fn) ---@class ApiCollapseOpts
---@field keep_buffers boolean|nil default false
Api.tree.collapse_all = wrap(actions.tree.modifiers.collapse.all)
Api.tree.expand_all = wrap_node(actions.tree.modifiers.expand.all)
Api.tree.toggle_enable_filters = wrap_explorer_member("filters", "toggle") Api.tree.toggle_enable_filters = wrap_explorer_member("filters", "toggle")
Api.tree.toggle_gitignore_filter = wrap_explorer_member_args("filters", "toggle", "git_ignored") Api.tree.toggle_gitignore_filter = wrap_explorer_member_args("filters", "toggle", "git_ignored")
Api.tree.toggle_git_clean_filter = wrap_explorer_member_args("filters", "toggle", "git_clean") Api.tree.toggle_git_clean_filter = wrap_explorer_member_args("filters", "toggle", "git_clean")
@@ -235,13 +239,17 @@ local function edit(mode, node, edit_opts)
local path = file_link and file_link.link_to or node.absolute_path local path = file_link and file_link.link_to or node.absolute_path
local cur_tabpage = vim.api.nvim_get_current_tabpage() local cur_tabpage = vim.api.nvim_get_current_tabpage()
local explorer = core.get_explorer()
actions.node.open_file.fn(mode, path) actions.node.open_file.fn(mode, path)
edit_opts = edit_opts or {} edit_opts = edit_opts or {}
local mode_unsupported_quit_on_open = mode == "drop" or mode == "tab_drop" or mode == "edit_in_place" local mode_unsupported_quit_on_open = mode == "drop" or mode == "tab_drop" or mode == "edit_in_place"
if not mode_unsupported_quit_on_open and edit_opts.quit_on_open then if not mode_unsupported_quit_on_open and edit_opts.quit_on_open then
view.close(cur_tabpage) if explorer then
explorer.window:close(cur_tabpage)
end
end end
local mode_unsupported_focus = mode == "drop" or mode == "tab_drop" or mode == "edit_in_place" local mode_unsupported_focus = mode == "drop" or mode == "tab_drop" or mode == "edit_in_place"
@@ -251,7 +259,9 @@ local function edit(mode, node, edit_opts)
if mode == "tabnew" then if mode == "tabnew" then
vim.cmd(":tabprev") vim.cmd(":tabprev")
end end
view.focus() if explorer then
explorer.window:focus()
end
end end
end end
@@ -312,6 +322,9 @@ Api.node.navigate.diagnostics.prev_recursive = wrap_node(actions.moves.item.fn({
Api.node.navigate.opened.next = wrap_node(actions.moves.item.fn({ where = "next", what = "opened" })) Api.node.navigate.opened.next = wrap_node(actions.moves.item.fn({ where = "next", what = "opened" }))
Api.node.navigate.opened.prev = wrap_node(actions.moves.item.fn({ where = "prev", what = "opened" })) Api.node.navigate.opened.prev = wrap_node(actions.moves.item.fn({ where = "prev", what = "opened" }))
Api.node.expand = wrap_node(actions.tree.modifiers.expand.node)
Api.node.collapse = wrap_node(actions.tree.modifiers.collapse.node)
---@class ApiNodeDeleteWipeBufferOpts ---@class ApiNodeDeleteWipeBufferOpts
---@field force boolean|nil default false ---@field force boolean|nil default false

View File

@@ -134,6 +134,29 @@ M.HIGHLIGHT_GROUPS = {
{ group = "NvimTreeDiagnosticHintFolderHL", link = "NvimTreeDiagnosticHintFileHL" }, { group = "NvimTreeDiagnosticHintFolderHL", link = "NvimTreeDiagnosticHintFileHL" },
} }
-- winhighlight for most cases
M.WIN_HL = table.concat({
"EndOfBuffer:NvimTreeEndOfBuffer",
"CursorLine:NvimTreeCursorLine",
"CursorLineNr:NvimTreeCursorLineNr",
"LineNr:NvimTreeLineNr",
"WinSeparator:NvimTreeWinSeparator",
"StatusLine:NvimTreeStatusLine",
"StatusLineNC:NvimTreeStatuslineNC",
"SignColumn:NvimTreeSignColumn",
"Normal:NvimTreeNormal",
"NormalNC:NvimTreeNormalNC",
"NormalFloat:NvimTreeNormalFloat",
"FloatBorder:NvimTreeNormalFloatBorder",
}, ",")
-- winhighlight for help
M.WIN_HL_HELP = table.concat({
"NormalFloat:NvimTreeNormalFloat",
"WinSeparator:NvimTreeWinSeparator",
"CursorLine:NvimTreeCursorLine",
}, ",")
-- nvim-tree highlight groups to legacy -- nvim-tree highlight groups to legacy
M.LEGACY_LINKS = { M.LEGACY_LINKS = {
NvimTreeModifiedIcon = "NvimTreeModifiedFile", NvimTreeModifiedIcon = "NvimTreeModifiedFile",

View File

@@ -1,5 +1,5 @@
local api = require("nvim-tree.api") local api = require("nvim-tree.api")
local view = require("nvim-tree.view") local core = require("nvim-tree.core")
local M = {} local M = {}
@@ -111,7 +111,10 @@ local CMDS = {
bar = true, bar = true,
}, },
command = function(c) command = function(c)
view.resize(c.args) local explorer = core.get_explorer()
if explorer then
explorer.window:resize(c.args)
end
end, end,
}, },
{ {

View File

@@ -1,6 +1,5 @@
local events = require("nvim-tree.events") local events = require("nvim-tree.events")
local notify = require("nvim-tree.notify") local notify = require("nvim-tree.notify")
local view = require("nvim-tree.view")
local log = require("nvim-tree.log") local log = require("nvim-tree.log")
local M = {} local M = {}
@@ -55,7 +54,7 @@ end
---@return integer ---@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.window:is_root_folder_visible(M.get_cwd()) then
offset = offset + 1 offset = offset + 1
end end
if TreeExplorer and TreeExplorer.live_filter.filter then if TreeExplorer and TreeExplorer.live_filter.filter then

View File

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

View File

@@ -23,6 +23,8 @@ local Filters = Class:extend()
---@protected ---@protected
---@param args FiltersArgs ---@param args FiltersArgs
function Filters:new(args) function Filters:new(args)
args.explorer:log_new("Filters")
self.explorer = args.explorer self.explorer = args.explorer
self.ignore_list = {} self.ignore_list = {}
self.exclude_list = self.explorer.opts.filters.exclude self.exclude_list = self.explorer.opts.filters.exclude
@@ -50,6 +52,10 @@ function Filters:new(args)
end end
end end
function Filters:destroy()
self.explorer:log_destroy("Filters")
end
---@private ---@private
---@param path string ---@param path string
---@return boolean ---@return boolean

View File

@@ -20,6 +20,7 @@ local LiveFilter = require("nvim-tree.explorer.live-filter")
local Sorter = require("nvim-tree.explorer.sorter") local Sorter = require("nvim-tree.explorer.sorter")
local Clipboard = require("nvim-tree.actions.fs.clipboard") local Clipboard = require("nvim-tree.actions.fs.clipboard")
local Renderer = require("nvim-tree.renderer") local Renderer = require("nvim-tree.renderer")
local Window = require("nvim-tree.explorer.window")
local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON
@@ -35,6 +36,7 @@ local config
---@field sorters Sorter ---@field sorters Sorter
---@field marks Marks ---@field marks Marks
---@field clipboard Clipboard ---@field clipboard Clipboard
---@field window Window
local Explorer = RootNode:extend() local Explorer = RootNode:extend()
---@class Explorer ---@class Explorer
@@ -55,15 +57,18 @@ function Explorer:new(args)
self.uid_explorer = vim.loop.hrtime() self.uid_explorer = vim.loop.hrtime()
self.augroup_id = vim.api.nvim_create_augroup("NvimTree_Explorer_" .. self.uid_explorer, {}) self.augroup_id = vim.api.nvim_create_augroup("NvimTree_Explorer_" .. self.uid_explorer, {})
self.open = true self:log_new("Explorer")
self.opts = config
self.sorters = Sorter({ explorer = self }) self.open = true
self.renderer = Renderer({ explorer = self }) self.opts = config
self.filters = Filters({ explorer = self })
self.live_filter = LiveFilter({ explorer = self }) self.clipboard = Clipboard({ explorer = self })
self.marks = Marks({ explorer = self }) self.filters = Filters({ explorer = self })
self.clipboard = Clipboard({ explorer = self }) self.live_filter = LiveFilter({ explorer = self })
self.marks = Marks({ explorer = self })
self.renderer = Renderer({ explorer = self })
self.sorters = Sorter({ explorer = self })
self.window = Window({ explorer = self })
self:create_autocmds() self:create_autocmds()
@@ -71,7 +76,15 @@ function Explorer:new(args)
end end
function Explorer:destroy() function Explorer:destroy()
log.line("dev", "Explorer:destroy") self.explorer:log_destroy("Explorer")
self.clipboard:destroy()
self.filters:destroy()
self.live_filter:destroy()
self.marks:destroy()
self.renderer:destroy()
self.sorters:destroy()
self.window:destroy()
vim.api.nvim_del_augroup_by_id(self.augroup_id) vim.api.nvim_del_augroup_by_id(self.augroup_id)
@@ -84,11 +97,23 @@ function Explorer:create_autocmds()
group = self.augroup_id, group = self.augroup_id,
callback = function() callback = function()
appearance.setup() appearance.setup()
view.reset_winhl() self.window:reset_winhl()
self.renderer:draw() self.renderer:draw()
end, end,
}) })
if self.opts.view.float.enable and self.opts.view.float.quit_on_focus_loss then
vim.api.nvim_create_autocmd("WinLeave", {
group = self.augroup_id,
pattern = "NvimTree_*",
callback = function()
if utils.is_nvim_tree_buf(0) then
self.window:close()
end
end,
})
end
vim.api.nvim_create_autocmd("BufWritePost", { vim.api.nvim_create_autocmd("BufWritePost", {
group = self.augroup_id, group = self.augroup_id,
callback = function() callback = function()
@@ -101,10 +126,19 @@ function Explorer:create_autocmds()
vim.api.nvim_create_autocmd("BufReadPost", { vim.api.nvim_create_autocmd("BufReadPost", {
group = self.augroup_id, group = self.augroup_id,
callback = function(data) callback = function(data)
if (self.filters.state.no_buffer or self.opts.highlight_opened_files ~= "none") and vim.bo[data.buf].buftype == "" then -- only handle normal files
if vim.bo[data.buf].buftype ~= "" then
return
end
if self.filters.state.no_buffer then
-- full reload is required to update the filter state
utils.debounce("Buf:filter_buffer_" .. self.uid_explorer, self.opts.view.debounce_delay, function() utils.debounce("Buf:filter_buffer_" .. self.uid_explorer, self.opts.view.debounce_delay, function()
self:reload_explorer() self:reload_explorer()
end) end)
elseif self.opts.renderer.highlight_opened_files ~= "none" then
-- draw to update opened highlight
self.renderer:draw()
end end
end, end,
}) })
@@ -113,10 +147,37 @@ function Explorer:create_autocmds()
vim.api.nvim_create_autocmd("BufUnload", { vim.api.nvim_create_autocmd("BufUnload", {
group = self.augroup_id, group = self.augroup_id,
callback = function(data) callback = function(data)
if (self.filters.state.no_buffer or self.opts.highlight_opened_files ~= "none") and vim.bo[data.buf].buftype == "" then -- only handle normal files
if vim.bo[data.buf].buftype ~= "" then
return
end
if self.filters.state.no_buffer then
-- full reload is required to update the filter state
utils.debounce("Buf:filter_buffer_" .. self.uid_explorer, self.opts.view.debounce_delay, function() utils.debounce("Buf:filter_buffer_" .. self.uid_explorer, self.opts.view.debounce_delay, function()
self:reload_explorer() self:reload_explorer()
end) end)
elseif self.opts.renderer.highlight_opened_files ~= "none" then
-- draw to update opened highlight; must be delayed as the buffer is still loaded during BufUnload
vim.schedule(function()
self.renderer:draw()
end)
end
end,
})
-- prevent new opened file from opening in the same window as nvim-tree
vim.api.nvim_create_autocmd("BufWipeout", {
group = self.augroup_id,
pattern = "NvimTree_*",
callback = function()
if not utils.is_nvim_tree_buf(0) then
return
end
if self.opts.actions.open_file.eject then
self.window:prevent_buffer_override()
else
view.abandon_current_window()
end end
end, end,
}) })
@@ -214,8 +275,9 @@ function Explorer:reload(node, project)
end end
local abs = utils.path_join({ cwd, name }) local abs = utils.path_join({ cwd, name })
---@type uv.fs_stat.result|nil
local stat = vim.loop.fs_lstat(abs) -- path incorrectly specified as an integer
local stat = vim.loop.fs_lstat(abs) ---@diagnostic disable-line param-type-mismatch
local filter_reason = self.filters:should_filter_as_reason(abs, stat, filter_status) local filter_reason = self.filters:should_filter_as_reason(abs, stat, filter_status)
if filter_reason == FILTER_REASON.none then if filter_reason == FILTER_REASON.none then
@@ -373,8 +435,9 @@ function Explorer:populate_children(handle, cwd, node, project, parent)
if Watcher.is_fs_event_capable(abs) then if Watcher.is_fs_event_capable(abs) then
local profile = log.profile_start("populate_children %s", abs) local profile = log.profile_start("populate_children %s", abs)
---@type uv.fs_stat.result|nil -- path incorrectly specified as an integer
local stat = vim.loop.fs_lstat(abs) local stat = vim.loop.fs_lstat(abs) ---@diagnostic disable-line param-type-mismatch
local filter_reason = parent.filters:should_filter_as_reason(abs, stat, filter_status) local filter_reason = parent.filters:should_filter_as_reason(abs, stat, filter_status)
if filter_reason == FILTER_REASON.none and not nodes_by_path[abs] then if filter_reason == FILTER_REASON.none and not nodes_by_path[abs] then
local child = node_factory.create({ local child = node_factory.create({
@@ -501,7 +564,7 @@ function Explorer:get_node_at_cursor()
return return
end end
if cursor[1] == 1 and view.is_root_folder_visible(core.get_cwd()) then if cursor[1] == 1 and self.window:is_root_folder_visible(core.get_cwd()) then
return self return self
end end
@@ -535,6 +598,18 @@ function Explorer:get_nodes()
return self:clone() return self:clone()
end end
---Log a lifecycle message with uid_explorer and absolute_path
---@param msg string?
function Explorer:log_new(msg)
log.line("lifecycle", "+ %-15s %d %s", msg, self.uid_explorer, self.absolute_path)
end
---Log a lifecycle message with uid_explorer and absolute_path
---@param msg string?
function Explorer:log_destroy(msg)
log.line("lifecycle", "- %-15s %d %s", msg, self.uid_explorer, self.absolute_path)
end
function Explorer:setup(opts) function Explorer:setup(opts)
config = opts config = opts
end end

View File

@@ -21,12 +21,18 @@ local LiveFilter = Class:extend()
---@protected ---@protected
---@param args LiveFilterArgs ---@param args LiveFilterArgs
function LiveFilter:new(args) function LiveFilter:new(args)
args.explorer:log_new("LiveFilter")
self.explorer = args.explorer self.explorer = args.explorer
self.prefix = self.explorer.opts.live_filter.prefix self.prefix = self.explorer.opts.live_filter.prefix
self.always_show_folders = self.explorer.opts.live_filter.always_show_folders self.always_show_folders = self.explorer.opts.live_filter.always_show_folders
self.filter = nil self.filter = nil
end end
function LiveFilter:destroy()
self.explorer:log_destroy("LiveFilter")
end
---@param node_ Node? ---@param node_ Node?
local function reset_filter(self, node_) local function reset_filter(self, node_)
node_ = node_ or self.explorer node_ = node_ or self.explorer
@@ -56,14 +62,14 @@ local overlay_bufnr = 0
local overlay_winnr = 0 local overlay_winnr = 0
local function remove_overlay(self) local function remove_overlay(self)
if view.View.float.enable and view.View.float.quit_on_focus_loss then if self.explorer.view.float.enable and self.explorer.view.float.quit_on_focus_loss then
-- return to normal nvim-tree float behaviour when filter window is closed -- return to normal nvim-tree float behaviour when filter window is closed
vim.api.nvim_create_autocmd("WinLeave", { vim.api.nvim_create_autocmd("WinLeave", {
pattern = "NvimTree_*", pattern = "NvimTree_*",
group = vim.api.nvim_create_augroup("NvimTree", { clear = false }), group = vim.api.nvim_create_augroup("NvimTree", { clear = false }),
callback = function() callback = function()
if utils.is_nvim_tree_buf(0) then if utils.is_nvim_tree_buf(0) then
view.close() self.explorer.view:close()
end end
end, end,
}) })
@@ -156,7 +162,7 @@ end
---@return integer ---@return integer
local function calculate_overlay_win_width(self) local function calculate_overlay_win_width(self)
local wininfo = vim.fn.getwininfo(view.get_winnr())[1] local wininfo = vim.fn.getwininfo(self.explorer.view:get_winnr())[1]
if wininfo then if wininfo then
return wininfo.width - wininfo.textoff - #self.prefix return wininfo.width - wininfo.textoff - #self.prefix
@@ -166,7 +172,7 @@ local function calculate_overlay_win_width(self)
end end
local function create_overlay(self) local function create_overlay(self)
if view.View.float.enable then if self.explorer.view.float.enable then
-- don't close nvim-tree float when focus is changed to filter window -- don't close nvim-tree float when focus is changed to filter window
vim.api.nvim_clear_autocmds({ vim.api.nvim_clear_autocmds({
event = "WinLeave", event = "WinLeave",
@@ -198,7 +204,7 @@ local function create_overlay(self)
end end
function LiveFilter:start_filtering() function LiveFilter:start_filtering()
view.View.live_filter.prev_focused_node = self.explorer:get_node_at_cursor() self.explorer.window.live_filter.prev_focused_node = self.explorer:get_node_at_cursor()
self.filter = self.filter or "" self.filter = self.filter or ""
self.explorer.renderer:draw() self.explorer.renderer:draw()
@@ -213,7 +219,7 @@ end
function LiveFilter:clear_filter() function LiveFilter:clear_filter()
local node = self.explorer:get_node_at_cursor() local node = self.explorer:get_node_at_cursor()
local last_node = view.View.live_filter.prev_focused_node local last_node = self.explorer.window.live_filter.prev_focused_node
self.filter = nil self.filter = nil
reset_filter(self) reset_filter(self)

View File

@@ -19,9 +19,15 @@ local Sorter = Class:extend()
---@protected ---@protected
---@param args SorterArgs ---@param args SorterArgs
function Sorter:new(args) function Sorter:new(args)
args.explorer:log_new("Sorter")
self.explorer = args.explorer self.explorer = args.explorer
end end
function Sorter:destroy()
self.explorer:log_destroy("Sorter")
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
---@param first integer First index, inclusive ---@param first integer First index, inclusive

View File

@@ -0,0 +1,504 @@
local appearance = require("nvim-tree.appearance")
local events = require("nvim-tree.events")
local utils = require("nvim-tree.utils")
local log = require("nvim-tree.log")
local notify = require("nvim-tree.notify")
local view = require("nvim-tree.view")
local Class = require("nvim-tree.classic")
---@class OpenInWinOpts
---@field hijack_current_buf boolean|nil default true
---@field resize boolean|nil default true
---@field winid number|nil 0 or nil for current
local DEFAULT_MIN_WIDTH = 30
local DEFAULT_MAX_WIDTH = -1
local DEFAULT_PADDING = 1
---@class (exact) Window: Class
---@field live_filter table
---@field side string
---@field float table
---@field private explorer Explorer
---@field private adaptive_size boolean
---@field private centralize_selection boolean
---@field private hide_root_folder boolean
---@field private winopts table
---@field private height integer
---@field private preserve_window_proportions boolean
---@field private initial_width integer
---@field private width (fun():integer)|integer|string
---@field private max_width integer
---@field private padding integer
local Window = Class:extend()
---@class Window
---@overload fun(args: WindowArgs): Window
---@class (exact) WindowArgs
---@field explorer Explorer
---@protected
---@param args WindowArgs
function Window:new(args)
args.explorer:log_new("Window")
self.explorer = args.explorer
self.adaptive_size = false
self.centralize_selection = self.explorer.opts.view.centralize_selection
self.float = self.explorer.opts.view.float
self.height = self.explorer.opts.view.height
self.hide_root_folder = self.explorer.opts.renderer.root_folder_label == false
self.preserve_window_proportions = self.explorer.opts.view.preserve_window_proportions
self.side = (self.explorer.opts.view.side == "right") and "right" or "left"
self.live_filter = { prev_focused_node = nil, }
self.winopts = {
relativenumber = self.explorer.opts.view.relativenumber,
number = self.explorer.opts.view.number,
list = false,
foldenable = false,
winfixwidth = true,
winfixheight = true,
spell = false,
signcolumn = self.explorer.opts.view.signcolumn,
foldmethod = "manual",
foldcolumn = "0",
cursorcolumn = false,
cursorline = self.explorer.opts.view.cursorline,
cursorlineopt = "both",
colorcolumn = "0",
wrap = false,
winhl = appearance.WIN_HL,
}
self:configure_width(self.explorer.opts.view.width)
self.initial_width = self:get_width()
end
function Window:destroy()
self.explorer:log_destroy("Window")
end
---@type { name: string, value: any }[]
local BUFFER_OPTIONS = {
{ name = "bufhidden", value = "wipe" },
{ name = "buflisted", value = false },
{ name = "buftype", value = "nofile" },
{ name = "filetype", value = "NvimTree" },
{ name = "modifiable", value = false },
{ name = "swapfile", value = false },
}
---@private
---@param bufnr integer|false|nil
function Window:create_buffer(bufnr)
view.wipe_rogue_buffer()
local tab = view.create_buffer(bufnr)
vim.api.nvim_buf_set_name(view.get_bufnr(), "NvimTree_" .. tab)
bufnr = view.get_bufnr()
for _, option in ipairs(BUFFER_OPTIONS) do
vim.api.nvim_set_option_value(option.name, option.value, { buf = bufnr })
end
require("nvim-tree.keymap").on_attach(view.get_bufnr())
events._dispatch_tree_attached_post(view.get_bufnr())
end
---@private
---@param size (fun():integer)|integer|string
---@return integer
function Window:get_size(size)
if type(size) == "number" then
return size
elseif type(size) == "function" then
return self:get_size(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
---@private
---@param size (fun():integer)|integer|nil
---@return integer
function Window:get_width(size)
if size then
return self:get_size(size)
else
return self:get_size(self.width)
end
end
---@private
function Window:set_window_options_and_buffer()
pcall(vim.api.nvim_command, "buffer " .. view.get_bufnr())
if vim.fn.has("nvim-0.10") == 1 then
local eventignore = vim.api.nvim_get_option_value("eventignore", {})
vim.api.nvim_set_option_value("eventignore", "all", {})
for k, v in pairs(self.winopts) do
vim.api.nvim_set_option_value(k, v, { scope = "local" })
end
vim.api.nvim_set_option_value("eventignore", eventignore, {})
else
local eventignore = vim.api.nvim_get_option("eventignore") ---@diagnostic disable-line: deprecated
vim.api.nvim_set_option("eventignore", "all") ---@diagnostic disable-line: deprecated
-- #3009 vim.api.nvim_win_set_option does not set local scope without explicit winid.
-- Revert to opt_local instead of propagating it through for just the 0.10 path.
for k, v in pairs(self.winopts) do
vim.opt_local[k] = v
end
vim.api.nvim_set_option("eventignore", eventignore) ---@diagnostic disable-line: deprecated
end
end
---@private
---@return table
function Window:open_win_config()
if type(self.float.open_win_config) == "function" then
return self.float.open_win_config()
else
return self.float.open_win_config
end
end
---@private
function Window:open_window()
if self.float.enable then
vim.api.nvim_open_win(0, true, self:open_win_config())
else
vim.api.nvim_command("vsp")
self:reposition_window()
end
view.setup_tabpage(vim.api.nvim_get_current_tabpage())
self:set_window_options_and_buffer()
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
---@param tabpage integer
local function close(tabpage)
if not view.is_visible({ tabpage = tabpage }) then
return
end
view.save_tab_state(tabpage)
switch_buf_if_last_buf()
local tree_win = view.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
local success, error = pcall(vim.api.nvim_win_close, tree_win or 0, true)
if not success then
notify.debug("Failed to close window: " .. error)
return
end
end
return
end
end
end
function Window:close_this_tab_only()
close(vim.api.nvim_get_current_tabpage())
end
function Window:close_all_tabs()
view.all_tabs_callback(function(t)
close(t)
end)
end
---@param tabpage integer|nil
function Window:close(tabpage)
if self.explorer.opts.tab.sync.close then
self:close_all_tabs()
elseif tabpage then
close(tabpage)
else
self:close_this_tab_only()
end
end
---@param options table|nil
function Window:open(options)
if view.is_visible() then
return
end
local profile = log.profile_start("view open")
events._dispatch_on_tree_pre_open()
self:create_buffer()
self:open_window()
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
---@private
function Window:grow()
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(view.get_bufnr(), starts_at, -1, false)
-- number of columns of right-padding to indicate end of path
local padding = self:get_size(self.padding)
-- account for sign/number columns etc.
local wininfo = vim.fn.getwininfo(view.get_winnr())
if type(wininfo) == "table" and type(wininfo[1]) == "table" then
padding = padding + wininfo[1].textoff
end
local resizing_width = self.initial_width - padding
local max_width
-- maybe bound max
if self.max_width == -1 then
max_width = -1
else
max_width = self:get_width(self.max_width) - padding
end
local ns_id = vim.api.nvim_get_namespaces()["NvimTreeExtmarks"]
for line_nr, l in pairs(lines) do
local count = vim.fn.strchars(l)
-- also add space for right-aligned icons
local extmarks = vim.api.nvim_buf_get_extmarks(view.get_bufnr(), ns_id, { line_nr, 0 }, { line_nr, -1 }, { details = true })
count = count + utils.extmarks_length(extmarks)
if resizing_width < count then
resizing_width = count
end
if self.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 Window:grow_from_content()
if self.adaptive_size then
self:grow()
end
end
---@param size string|number|nil
function Window:resize(size)
if self.float.enable and not self.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.width + size
end
end
if type(size) == "number" and size <= 0 then
return
end
if size then
self.width = size
self.height = size
end
if not view.is_visible() then
return
end
local winnr = view.get_winnr() or 0
local new_size = self:get_width()
if new_size ~= vim.api.nvim_win_get_width(winnr) then
vim.api.nvim_win_set_width(winnr, new_size)
if not self.preserve_window_proportions then
vim.cmd(":wincmd =")
end
end
events._dispatch_on_tree_resize(new_size)
end
---@private
function Window:reposition_window()
vim.api.nvim_command("wincmd " .. (self.side == "left" and "H" or "L"))
self:resize()
end
---Open the tree in the a window
---@param opts OpenInWinOpts|nil
function Window:open_in_win(opts)
opts = opts or { hijack_current_buf = true, resize = true }
events._dispatch_on_tree_pre_open()
if opts.winid and vim.api.nvim_win_is_valid(opts.winid) then
vim.api.nvim_set_current_win(opts.winid)
end
self:create_buffer(opts.hijack_current_buf and vim.api.nvim_get_current_buf())
view.setup_tabpage(vim.api.nvim_get_current_tabpage())
view.set_current_win()
self:set_window_options_and_buffer()
if opts.resize then
self:reposition_window()
self:resize()
end
events._dispatch_on_tree_open()
end
---@param winnr number|nil
---@param open_if_closed boolean|nil
function Window:focus(winnr, open_if_closed)
local wnr = winnr or view.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 = view.get_winnr()
elseif open_if_closed and not view.is_visible() then
self:open()
end
if wnr then
vim.api.nvim_set_current_win(wnr)
end
end
function Window:prevent_buffer_override()
local view_winnr = view.get_winnr()
local view_bufnr = view.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
view.clear_tabpage(view_winnr)
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")
self:open({ focus_tree = false })
self.explorer.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 Window:is_root_folder_visible(cwd)
return cwd ~= "/" and not self.hide_root_folder
end
-- used on ColorScheme event
function Window:reset_winhl()
local winnr = view.get_winnr()
if winnr and vim.api.nvim_win_is_valid(winnr) then
vim.wo[view.get_winnr()].winhl = appearance.WIN_HL
end
end
---Check if width determined or calculated on-fly
---@return boolean
function Window:is_width_determined()
return type(self.width) ~= "function"
end
---Configure width-related config
---@param width string|function|number|table|nil
function Window:configure_width(width)
if type(width) == "table" then
self.adaptive_size = true
self.width = width.min or DEFAULT_MIN_WIDTH
self.max_width = width.max or DEFAULT_MAX_WIDTH
self.padding = width.padding or DEFAULT_PADDING
elseif width == nil then
if self.explorer.opts.view.width ~= nil then
-- if we had input config - fallback to it
self:configure_width(self.explorer.opts.view.width)
else
-- otherwise - restore initial width
self.width = self.initial_width
end
else
self.adaptive_size = false
self.width = width
end
end
return Window

View File

@@ -1,3 +1,4 @@
local appearance = require("nvim-tree.appearance")
local keymap = require("nvim-tree.keymap") local keymap = require("nvim-tree.keymap")
local api = {} -- circular dependency local api = {} -- circular dependency
@@ -5,12 +6,6 @@ local PAT_MOUSE = "^<.*Mouse"
local PAT_CTRL = "^<C%-" local PAT_CTRL = "^<C%-"
local PAT_SPECIAL = "^<.+" local PAT_SPECIAL = "^<.+"
local WIN_HL = table.concat({
"NormalFloat:NvimTreeNormalFloat",
"WinSeparator:NvimTreeWinSeparator",
"CursorLine:NvimTreeCursorLine",
}, ",")
local namespace_help_id = vim.api.nvim_create_namespace("NvimTreeHelp") local namespace_help_id = vim.api.nvim_create_namespace("NvimTreeHelp")
local M = { local M = {
@@ -210,7 +205,7 @@ local function open()
}) })
-- style it a bit like the tree -- style it a bit like the tree
vim.wo[M.winnr].winhl = WIN_HL vim.wo[M.winnr].winhl = appearance.WIN_HL_HELP
vim.wo[M.winnr].cursorline = M.config.cursorline vim.wo[M.winnr].cursorline = M.config.cursorline
local function toggle_sort() local function toggle_sort()

View File

@@ -48,7 +48,7 @@ function NodeIterator:recursor(f)
end end
---@return Node|nil ---@return Node|nil
---@return number|nil ---@return number
function NodeIterator:iterate() function NodeIterator:iterate()
local iteration_count = 0 local iteration_count = 0
local function iter(nodes) local function iter(nodes)

View File

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

View File

@@ -60,6 +60,13 @@ local function refactored(opts)
end end
end end
utils.move_missing_val(opts, "update_focused_file", "ignore_list", opts, "update_focused_file.update_root", "ignore_list", true) utils.move_missing_val(opts, "update_focused_file", "ignore_list", opts, "update_focused_file.update_root", "ignore_list", true)
-- 2025/04/30
if opts.renderer and opts.renderer.icons and type(opts.renderer.icons.padding) == "string" then
local icons_padding = opts.renderer.icons.padding
opts.renderer.icons.padding = {}
opts.renderer.icons.padding.icon = icons_padding
end
end end
local function deprecated(opts) local function deprecated(opts)

View File

@@ -1,6 +1,5 @@
local view = require("nvim-tree.view") local view = require("nvim-tree.view")
local core = require("nvim-tree.core") local core = require("nvim-tree.core")
local events = require("nvim-tree.events")
local notify = require("nvim-tree.notify") local notify = require("nvim-tree.notify")
---@class LibOpenOpts ---@class LibOpenOpts
@@ -31,11 +30,16 @@ local function handle_buf_cwd(cwd)
end end
local function open_view_and_draw() local function open_view_and_draw()
local explorer = core.get_explorer()
local cwd = vim.fn.getcwd() local cwd = vim.fn.getcwd()
view.open()
if explorer then
explorer.window:open()
end
handle_buf_cwd(cwd) handle_buf_cwd(cwd)
local explorer = core.get_explorer()
if explorer then if explorer then
explorer.renderer:draw() explorer.renderer:draw()
end end
@@ -111,26 +115,25 @@ function M.open(opts)
local explorer = core.get_explorer() local explorer = core.get_explorer()
if should_hijack_current_buf() then if should_hijack_current_buf() then
view.close_this_tab_only()
view.open_in_win()
if explorer then if explorer then
explorer.window:close_this_tab_only()
explorer.window:open_in_win()
explorer.renderer:draw() explorer.renderer:draw()
end end
elseif opts.winid then elseif opts.winid then
view.open_in_win({ hijack_current_buf = false, resize = false, winid = opts.winid })
if explorer then if explorer then
explorer.window:open_in_win({ hijack_current_buf = false, resize = false, winid = opts.winid })
explorer.renderer:draw() explorer.renderer:draw()
end end
elseif opts.current_window then elseif opts.current_window then
view.open_in_win({ hijack_current_buf = false, resize = false })
if explorer then if explorer then
explorer.window:open_in_win({ hijack_current_buf = false, resize = false })
explorer.renderer:draw() explorer.renderer:draw()
end end
else else
open_view_and_draw() open_view_and_draw()
end end
view.restore_tab_state() view.restore_tab_state()
events._dispatch_on_tree_open()
end end
function M.setup(opts) function M.setup(opts)

View File

@@ -1,4 +1,4 @@
---@alias LogTypes "all" | "config" | "copy_paste" | "dev" | "diagnostics" | "git" | "profile" | "watcher" ---@alias LogTypes "all" | "config" | "copy_paste" | "dev" | "diagnostics" | "git" | "lifecycle" | "profile" | "watcher"
---@type table<LogTypes, boolean> ---@type table<LogTypes, boolean>
local types = {} local types = {}

View File

@@ -25,11 +25,17 @@ local Marks = Class:extend()
---@protected ---@protected
---@param args MarksArgs ---@param args MarksArgs
function Marks:new(args) function Marks:new(args)
args.explorer:log_new("Marks")
self.explorer = args.explorer self.explorer = args.explorer
self.marks = {} self.marks = {}
end end
function Marks:destroy()
self.explorer:log_destroy("Marks")
end
---Clear all marks and reload if watchers disabled ---Clear all marks and reload if watchers disabled
---@private ---@private
function Marks:clear_reload() function Marks:clear_reload()

View File

@@ -1,6 +1,5 @@
local notify = require("nvim-tree.notify") local notify = require("nvim-tree.notify")
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local view = require("nvim-tree.view")
local Class = require("nvim-tree.classic") local Class = require("nvim-tree.classic")
@@ -135,12 +134,12 @@ end
function Builder:format_line(indent_markers, arrows, icon, name, node) function Builder:format_line(indent_markers, arrows, icon, name, node)
local added_len = 0 local added_len = 0
local function add_to_end(t1, t2) local function add_to_end(t1, t2)
if not t2 then if not t2 or vim.tbl_isempty(t2) then
return return
end end
for _, v in ipairs(t2) do for _, v in ipairs(t2) do
if added_len > 0 then if added_len > 0 then
table.insert(t1, { str = self.explorer.opts.renderer.icons.padding }) table.insert(t1, { str = self.explorer.opts.renderer.icons.padding.icon })
end end
table.insert(t1, v) table.insert(t1, v)
end end
@@ -379,7 +378,7 @@ end
---@private ---@private
function Builder:build_header() function Builder:build_header()
if view.is_root_folder_visible(self.explorer.absolute_path) then if self.explorer.window:is_root_folder_visible(self.explorer.absolute_path) then
local root_name = self:format_root_name(self.explorer.opts.renderer.root_folder_label) local root_name = self:format_root_name(self.explorer.opts.renderer.root_folder_label)
table.insert(self.lines, root_name) table.insert(self.lines, root_name)
self:insert_highlight({ "NvimTreeRootFolder" }, 0, string.len(root_name)) self:insert_highlight({ "NvimTreeRootFolder" }, 0, string.len(root_name))

View File

@@ -1,7 +1,8 @@
local M = {} local appearance = require("nvim-tree.appearance")
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local M = {}
local function hide(win) local function hide(win)
if win then if win then
if vim.api.nvim_win_is_valid(win) then if vim.api.nvim_win_is_valid(win) then
@@ -32,7 +33,7 @@ local function effective_win_width()
return win_width - win_info[1].textoff return win_width - win_info[1].textoff
end end
local function show() local function show(opts)
local line_nr = vim.api.nvim_win_get_cursor(0)[1] local line_nr = vim.api.nvim_win_get_cursor(0)[1]
if vim.wo.wrap then if vim.wo.wrap then
return return
@@ -52,6 +53,11 @@ local function show()
local text_width = vim.fn.strdisplaywidth(vim.fn.substitute(line, "[^[:print:]]*$", "", "g")) local text_width = vim.fn.strdisplaywidth(vim.fn.substitute(line, "[^[:print:]]*$", "", "g"))
local win_width = effective_win_width() local win_width = effective_win_width()
-- windows width reduced by right aligned icons
local icon_ns_id = vim.api.nvim_get_namespaces()["NvimTreeExtmarks"]
local icon_extmarks = vim.api.nvim_buf_get_extmarks(0, icon_ns_id, { line_nr - 1, 0 }, { line_nr - 1, -1 }, { details = true })
win_width = win_width - utils.extmarks_length(icon_extmarks)
if text_width < win_width then if text_width < win_width then
return return
end end
@@ -64,7 +70,9 @@ local function show()
height = 1, height = 1,
noautocmd = true, noautocmd = true,
style = "minimal", style = "minimal",
border = "none"
}) })
vim.wo[M.popup_win].winhl = appearance.WIN_HL
local ns_id = vim.api.nvim_get_namespaces()["NvimTreeHighlights"] local ns_id = vim.api.nvim_get_namespaces()["NvimTreeHighlights"]
local extmarks = vim.api.nvim_buf_get_extmarks(0, ns_id, { line_nr - 1, 0 }, { line_nr - 1, -1 }, { details = true }) local extmarks = vim.api.nvim_buf_get_extmarks(0, ns_id, { line_nr - 1, 0 }, { line_nr - 1, -1 }, { details = true })
@@ -87,7 +95,10 @@ local function show()
end end
end end
end end
vim.cmd([[ setlocal nowrap cursorline noswapfile nobuflisted buftype=nofile bufhidden=wipe ]]) vim.cmd([[ setlocal nowrap noswapfile nobuflisted buftype=nofile bufhidden=wipe ]])
if opts.view.cursorline then
vim.cmd([[ setlocal cursorline cursorlineopt=both ]])
end
end) end)
end end
@@ -113,7 +124,7 @@ M.setup = function(opts)
pattern = { "NvimTree_*" }, pattern = { "NvimTree_*" },
callback = function() callback = function()
if utils.is_nvim_tree_buf(0) then if utils.is_nvim_tree_buf(0) then
show() show(opts)
end end
end, end,
}) })

View File

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

View File

@@ -62,7 +62,7 @@ function GitDecorator:build_icons_by_status(glyphs)
self.icons_by_status.ignored = { str = glyphs.ignored, hl = { "NvimTreeGitIgnoredIcon" }, ord = 7 } self.icons_by_status.ignored = { str = glyphs.ignored, hl = { "NvimTreeGitIgnoredIcon" }, ord = 7 }
end end
---@param icons GitIconsByXY ---@param icons GitIconsByStatus
function GitDecorator:build_icons_by_xy(icons) function GitDecorator:build_icons_by_xy(icons)
self.icons_by_xy = { self.icons_by_xy = {
["M "] = { icons.staged }, ["M "] = { icons.staged },

View File

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

View File

@@ -26,9 +26,15 @@ local Renderer = Class:extend()
---@protected ---@protected
---@param args RendererArgs ---@param args RendererArgs
function Renderer:new(args) function Renderer:new(args)
args.explorer:log_new("Renderer")
self.explorer = args.explorer self.explorer = args.explorer
end end
function Renderer:destroy()
self.explorer:log_destroy("Renderer")
end
---@private ---@private
---@param bufnr number ---@param bufnr number
---@param lines string[] ---@param lines string[]
@@ -113,7 +119,7 @@ function Renderer:draw()
vim.api.nvim_win_set_cursor(view.get_winnr() or 0, cursor) vim.api.nvim_win_set_cursor(view.get_winnr() or 0, cursor)
end end
view.grow_from_content() self.explorer.window:grow_from_content()
log.profile_end(profile) log.profile_end(profile)

View File

@@ -1,5 +1,4 @@
local Iterator = require("nvim-tree.iterators.node-iterator") local Iterator = require("nvim-tree.iterators.node-iterator")
local notify = require("nvim-tree.notify")
local M = { local M = {
debouncers = {}, debouncers = {},
@@ -144,10 +143,16 @@ function M.find_node(nodes, fn)
return node.group_next and { node.group_next } or (node.open and #node.nodes > 0 and node.nodes) return node.group_next and { node.group_next } or (node.open and #node.nodes > 0 and node.nodes)
end) end)
:iterate() :iterate()
i = require("nvim-tree.view").is_root_folder_visible() and i or i - 1
if node and node.explorer.live_filter.filter then if node then
i = i + 1 if not node.explorer.window:is_root_folder_visible() then
i = i - 1
end
if node.explorer.live_filter.filter then
i = i + 1
end
end end
return node, i return node, i
end end
@@ -173,6 +178,21 @@ function M.find_node_line(node)
return -1 return -1
end end
---@param extmarks vim.api.keyset.get_extmark_item[] as per vim.api.nvim_buf_get_extmarks
---@return number
function M.extmarks_length(extmarks)
local length = 0
for _, extmark in ipairs(extmarks) do
local details = extmark[4]
if details and details.virt_text then
for _, text in ipairs(details.virt_text) do
length = length + vim.fn.strchars(text[1])
end
end
end
return length
end
-- get the node in the tree state depending on the absolute path of the node -- get the node in the tree state depending on the absolute path of the node
-- (grouped or hidden too) -- (grouped or hidden too)
---@param path string ---@param path string
@@ -289,11 +309,51 @@ function M.rename_loaded_buffers(old_path, new_path)
end end
end end
local is_windows_drive = function(path)
return (M.is_windows) and (path:match("^%a:\\$") ~= nil)
end
---@param path string path to file or directory ---@param path string path to file or directory
---@return boolean ---@return boolean
function M.file_exists(path) function M.file_exists(path)
local _, error = vim.loop.fs_stat(path) if not (M.is_windows or M.is_wsl) then
return error == nil local _, error = vim.loop.fs_stat(path)
return error == nil
end
-- Windows is case-insensetive, but case-preserving
-- If a file's name is being changed into itself
-- with different casing, windows will falsely
-- report that file is already existing, so a hand-rolled
-- implementation of checking for existance is needed.
-- Same holds for WSL, since it can sometimes
-- access Windows files directly.
-- For more details see (#3117).
if is_windows_drive(path) then
return vim.fn.isdirectory(path) == 1
end
local parent = vim.fn.fnamemodify(path, ":h")
local filename = vim.fn.fnamemodify(path, ":t")
local handle = vim.loop.fs_scandir(parent)
if not handle then
-- File can not exist if its parent directory does not exist
return false
end
while true do
local name, _ = vim.loop.fs_scandir_next(handle)
if not name then
break
end
if name == filename then
return true
end
end
return false
end end
---@param path string ---@param path string
@@ -349,20 +409,6 @@ end
---@param dst_pos string value pos ---@param dst_pos string value pos
---@param remove boolean ---@param remove boolean
function M.move_missing_val(src, src_path, src_pos, dst, dst_path, dst_pos, remove) function M.move_missing_val(src, src_path, src_pos, dst, dst_path, dst_pos, remove)
local ok, err = pcall(vim.validate, {
src = { src, "table" },
src_path = { src_path, "string" },
src_pos = { src_pos, "string" },
dst = { dst, "table" },
dst_path = { dst_path, "string" },
dst_pos = { dst_pos, "string" },
remove = { remove, "boolean" },
})
if not ok then
notify.warn("move_missing_val: " .. (err or "invalid arguments"))
return
end
for pos in string.gmatch(src_path, "([^%.]+)%.*") do for pos in string.gmatch(src_path, "([^%.]+)%.*") do
if src[pos] and type(src[pos]) == "table" then if src[pos] and type(src[pos]) == "table" then
src = src[pos] src = src[pos]
@@ -612,32 +658,29 @@ function M.is_executable(absolute_path)
end end
end end
---List of all option info/values ---@class UtilEnumerateOptionsOpts
---@param opts vim.api.keyset.option passed directly to vim.api.nvim_get_option_info2 and vim.api.nvim_get_option_value ---@field keyset_opts vim.api.keyset.option
---@param was_set boolean filter was_set ---@field was_set boolean? as per vim.api.keyset.get_option_info
---@return { info: vim.api.keyset.get_option_info, val: any }[]
function M.enumerate_options(opts, was_set)
local res = {}
local infos = vim.tbl_filter(function(info) ---Option name/values
if opts.buf and info.scope ~= "buf" then ---@param opts UtilEnumerateOptionsOpts
return false ---@return table<string, any>
elseif opts.win and info.scope ~= "win" then function M.enumerate_options(opts)
return false -- enumerate all options, limiting buf and win scopes
return vim.tbl_map(function(info)
if opts.keyset_opts.buf and info.scope ~= "buf" then
return nil
elseif opts.keyset_opts.win and info.scope ~= "win" then
return nil
else else
return true -- optional, lazy was_set check
if not opts.was_set or vim.api.nvim_get_option_info2(info.name, opts.keyset_opts).was_set then
return vim.api.nvim_get_option_value(info.name, opts.keyset_opts)
else
return nil
end
end end
end, vim.api.nvim_get_all_options_info()) end, vim.api.nvim_get_all_options_info())
for _, info in vim.spairs(infos) do
local _, info2 = pcall(vim.api.nvim_get_option_info2, info.name, opts)
if not was_set or info2.was_set then
local val = pcall(vim.api.nvim_get_option_value, info.name, opts)
table.insert(res, { info = info2, val = val })
end
end
return res
end end
return M return M

View File

@@ -1,60 +1,107 @@
local events = require("nvim-tree.events")
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local log = require("nvim-tree.log")
local notify = require("nvim-tree.notify")
---@class OpenInWinOpts
---@field hijack_current_buf boolean|nil default true
---@field resize boolean|nil default true
---@field winid number|nil 0 or nil for current
local M = {} local M = {}
local DEFAULT_MIN_WIDTH = 30 local BUFNR_PER_TAB = {}
local DEFAULT_MAX_WIDTH = -1 local CURSORS = {}
local DEFAULT_PADDING = 1 local TABPAGES = {}
M.View = { --- Debugging only.
adaptive_size = false, --- Tabs show TABPAGES winnr and BUFNR_PER_TAB bufnr for the tab.
centralize_selection = false, --- Orphans for inexistent tab_ids are shown at the right.
tabpages = {}, --- lib.target_winid is always shown at the right next to a close button.
cursors = {}, --- Enable with:
hide_root_folder = false, --- vim.opt.tabline = "%!v:lua.require('nvim-tree.view').tab_line()"
live_filter = { --- vim.opt.showtabline = 2
prev_focused_node = nil, function M.tab_line()
}, local tab_ids = vim.api.nvim_list_tabpages()
winopts = { local cur_tab_id = vim.api.nvim_get_current_tabpage()
relativenumber = false,
number = false, local bufnr_per_tab = vim.deepcopy(BUFNR_PER_TAB)
list = false, local tabpages = vim.deepcopy(TABPAGES)
foldenable = false,
winfixwidth = true, local tl = "%#TabLine#"
winfixheight = true,
spell = false, for i, tab_id in ipairs(tab_ids) do
signcolumn = "yes", -- click to select
foldmethod = "manual", tl = tl .. "%" .. i .. "T"
foldcolumn = "0",
cursorcolumn = false, -- style
cursorline = true, if tab_id == cur_tab_id then
cursorlineopt = "both", tl = tl .. "%#StatusLine#|"
colorcolumn = "0", else
wrap = false, tl = tl .. "|%#TabLine#"
winhl = table.concat({ end
"EndOfBuffer:NvimTreeEndOfBuffer",
"CursorLine:NvimTreeCursorLine", -- tab_id itself
"CursorLineNr:NvimTreeCursorLineNr", tl = tl .. " t" .. tab_id
"LineNr:NvimTreeLineNr",
"WinSeparator:NvimTreeWinSeparator", -- winnr, if present
"StatusLine:NvimTreeStatusLine", local tp = TABPAGES[tab_id]
"StatusLineNC:NvimTreeStatuslineNC", if tp then
"SignColumn:NvimTreeSignColumn", tl = tl .. " w" .. tp.winnr
"Normal:NvimTreeNormal", else
"NormalNC:NvimTreeNormalNC", tl = tl .. " "
"NormalFloat:NvimTreeNormalFloat", end
"FloatBorder:NvimTreeNormalFloatBorder",
}, ","), -- bufnr, if present
}, local bpt = BUFNR_PER_TAB[tab_id]
} if bpt then
tl = tl .. " b" .. bpt
else
tl = tl .. " "
end
tl = tl .. " "
-- remove actively mapped
bufnr_per_tab[tab_id] = nil
tabpages[tab_id] = nil
end
-- close last and reset
tl = tl .. "|%#CursorLine#%T"
-- collect orphans
local orphans = {}
for tab_id, bufnr in pairs(bufnr_per_tab) do
orphans[tab_id] = orphans[tab_id] or {}
orphans[tab_id].bufnr = bufnr
end
for tab_id, tp in pairs(tabpages) do
orphans[tab_id] = orphans[tab_id] or {}
orphans[tab_id].winnr = tp.winnr
end
-- right-align
tl = tl .. "%=%#TabLine#"
-- print orphans
for tab_id, orphan in pairs(orphans) do
-- inexistent tab
tl = tl .. "%#error#| t" .. tab_id
-- maybe winnr
if orphan.winnr then
tl = tl .. " w" .. orphan.winnr
else
tl = tl .. " "
end
-- maybe bufnr
if orphan.bufnr then
tl = tl .. " b" .. orphan.bufnr
else
tl = tl .. " "
end
tl = tl .. " "
end
-- target win id and close button
tl = tl .. "|%#TabLine# twi" .. (require("nvim-tree.lib").target_winid or "?") .. " %999X| X |"
return tl
end
-- The initial state of a tab -- The initial state of a tab
local tabinitial = { local tabinitial = {
@@ -64,18 +111,6 @@ local tabinitial = {
winnr = nil, winnr = nil,
} }
local BUFNR_PER_TAB = {}
---@type { name: string, value: any }[]
local BUFFER_OPTIONS = {
{ name = "bufhidden", value = "wipe" },
{ name = "buflisted", value = false },
{ name = "buftype", value = "nofile" },
{ name = "filetype", value = "NvimTree" },
{ name = "modifiable", value = false },
{ name = "swapfile", value = false },
}
---@param bufnr integer ---@param bufnr integer
---@return boolean ---@return boolean
local function matches_bufnr(bufnr) local function matches_bufnr(bufnr)
@@ -87,7 +122,7 @@ local function matches_bufnr(bufnr)
return false return false
end end
local function wipe_rogue_buffer() function M.wipe_rogue_buffer()
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
if not matches_bufnr(bufnr) and utils.is_nvim_tree_buf(bufnr) then if not matches_bufnr(bufnr) and utils.is_nvim_tree_buf(bufnr) then
pcall(vim.api.nvim_buf_delete, bufnr, { force = true }) pcall(vim.api.nvim_buf_delete, bufnr, { force = true })
@@ -96,350 +131,52 @@ local function wipe_rogue_buffer()
end end
---@param bufnr integer|boolean|nil ---@param bufnr integer|boolean|nil
local function create_buffer(bufnr) ---@return integer tab
wipe_rogue_buffer() function M.create_buffer(bufnr)
local tab = vim.api.nvim_get_current_tabpage() local tab = vim.api.nvim_get_current_tabpage()
BUFNR_PER_TAB[tab] = bufnr or vim.api.nvim_create_buf(false, false) BUFNR_PER_TAB[tab] = bufnr or vim.api.nvim_create_buf(false, false)
vim.api.nvim_buf_set_name(M.get_bufnr(), "NvimTree_" .. tab) return tab
bufnr = M.get_bufnr()
for _, option in ipairs(BUFFER_OPTIONS) do
vim.api.nvim_set_option_value(option.name, option.value, { buf = bufnr })
end
require("nvim-tree.keymap").on_attach(M.get_bufnr())
events._dispatch_tree_attached_post(M.get_bufnr())
end 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 get_size(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
---@return integer
local function get_width(size)
if size then
return get_size(size)
else
return get_size(M.View.width)
end
end
local move_tbl = {
left = "H",
right = "L",
}
-- setup_tabpage sets up the initial state of a tab -- setup_tabpage sets up the initial state of a tab
---@param tabpage integer ---@param tabpage integer
local function setup_tabpage(tabpage) function M.setup_tabpage(tabpage)
local winnr = vim.api.nvim_get_current_win() local winnr = vim.api.nvim_get_current_win()
M.View.tabpages[tabpage] = vim.tbl_extend("force", M.View.tabpages[tabpage] or tabinitial, { winnr = winnr }) TABPAGES[tabpage] = vim.tbl_extend("force", TABPAGES[tabpage] or tabinitial, { winnr = winnr })
end
local function set_window_options_and_buffer()
pcall(vim.api.nvim_command, "buffer " .. M.get_bufnr())
if vim.fn.has("nvim-0.10") == 1 then
local eventignore = vim.api.nvim_get_option_value("eventignore", {})
vim.api.nvim_set_option_value("eventignore", "all", {})
for k, v in pairs(M.View.winopts) do
vim.api.nvim_set_option_value(k, v, { scope = "local" })
end
vim.api.nvim_set_option_value("eventignore", eventignore, {})
else
local eventignore = vim.api.nvim_get_option("eventignore") ---@diagnostic disable-line: deprecated
vim.api.nvim_set_option("eventignore", "all") ---@diagnostic disable-line: deprecated
-- #3009 vim.api.nvim_win_set_option does not set local scope without explicit winid.
-- Revert to opt_local instead of propagating it through for just the 0.10 path.
for k, v in pairs(M.View.winopts) do
vim.opt_local[k] = v
end
vim.api.nvim_set_option("eventignore", eventignore) ---@diagnostic disable-line: deprecated
end
end
---@return table
local function open_win_config()
if type(M.View.float.open_win_config) == "function" then
return M.View.float.open_win_config()
else
return M.View.float.open_win_config
end
end
local function open_window()
if M.View.float.enable then
vim.api.nvim_open_win(0, true, open_win_config())
else
vim.api.nvim_command("vsp")
M.reposition_window()
end
setup_tabpage(vim.api.nvim_get_current_tabpage())
set_window_options_and_buffer()
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 end
-- save_tab_state saves any state that should be preserved across redraws. -- save_tab_state saves any state that should be preserved across redraws.
---@param tabnr integer ---@param tabnr integer
local function save_tab_state(tabnr) function M.save_tab_state(tabnr)
local tabpage = tabnr or vim.api.nvim_get_current_tabpage() local tabpage = tabnr or vim.api.nvim_get_current_tabpage()
M.View.cursors[tabpage] = vim.api.nvim_win_get_cursor(M.get_winnr(tabpage) or 0) CURSORS[tabpage] = vim.api.nvim_win_get_cursor(M.get_winnr(tabpage) or 0)
end end
---@param tabpage integer ---@param fn fun(tabpage: integer)
local function close(tabpage) function M.all_tabs_callback(fn)
if not M.is_visible({ tabpage = tabpage }) then for tabpage, _ in pairs(TABPAGES) do
return fn(tabpage)
end
save_tab_state(tabpage)
switch_buf_if_last_buf()
local tree_win = M.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
local success, error = pcall(vim.api.nvim_win_close, tree_win or 0, true)
if not success then
notify.debug("Failed to close window: " .. error)
return
end
end
events._dispatch_on_tree_close()
return
end
end end
end end
function M.close_this_tab_only() function M.set_current_win()
close(vim.api.nvim_get_current_tabpage())
end
function M.close_all_tabs()
for tabpage, _ in pairs(M.View.tabpages) do
close(tabpage)
end
end
---@param tabpage integer|nil
function M.close(tabpage)
if M.View.tab.sync.close then
M.close_all_tabs()
elseif tabpage then
close(tabpage)
else
M.close_this_tab_only()
end
end
---@param options table|nil
function M.open(options)
if M.is_visible() then
return
end
local profile = log.profile_start("view open")
create_buffer()
open_window()
M.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()
local starts_at = M.is_root_folder_visible(require("nvim-tree.core").get_cwd()) and 1 or 0
local lines = vim.api.nvim_buf_get_lines(M.get_bufnr(), starts_at, -1, false)
-- number of columns of right-padding to indicate end of path
local padding = get_size(M.View.padding)
-- account for sign/number columns etc.
local wininfo = vim.fn.getwininfo(M.get_winnr())
if type(wininfo) == "table" and type(wininfo[1]) == "table" then
padding = padding + wininfo[1].textoff
end
local resizing_width = M.View.initial_width - padding
local max_width
-- maybe bound max
if M.View.max_width == -1 then
max_width = -1
else
max_width = get_width(M.View.max_width) - padding
end
local ns_id = vim.api.nvim_get_namespaces()["NvimTreeExtmarks"]
for line_nr, l in pairs(lines) do
local count = vim.fn.strchars(l)
-- also add space for right-aligned icons
local extmarks = vim.api.nvim_buf_get_extmarks(M.get_bufnr(), ns_id, { line_nr, 0 }, { line_nr, -1 }, { details = true })
for _, extmark in ipairs(extmarks) do
local virt_texts = extmark[4].virt_text
if virt_texts then
for _, virt_text in ipairs(virt_texts) do
count = count + vim.fn.strchars(virt_text[1])
end
end
end
if resizing_width < count then
resizing_width = count
end
if M.View.adaptive_size and max_width >= 0 and resizing_width >= max_width then
resizing_width = max_width
break
end
end
M.resize(resizing_width + padding)
end
function M.grow_from_content()
if M.View.adaptive_size then
grow()
end
end
---@param size string|number|nil
function M.resize(size)
if M.View.float.enable and not M.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 = M.View.width + size
end
end
if type(size) == "number" and size <= 0 then
return
end
if size then
M.View.width = size
M.View.height = size
end
if not M.is_visible() then
return
end
local winnr = M.get_winnr() or 0
local new_size = get_width()
if new_size ~= vim.api.nvim_win_get_width(winnr) then
vim.api.nvim_win_set_width(winnr, new_size)
if not M.View.preserve_window_proportions then
vim.cmd(":wincmd =")
end
end
events._dispatch_on_tree_resize(new_size)
end
function M.reposition_window()
local move_to = move_tbl[M.View.side]
vim.api.nvim_command("wincmd " .. move_to)
M.resize()
end
local function set_current_win()
local current_tab = vim.api.nvim_get_current_tabpage() local current_tab = vim.api.nvim_get_current_tabpage()
M.View.tabpages[current_tab].winnr = vim.api.nvim_get_current_win() TABPAGES[current_tab].winnr = vim.api.nvim_get_current_win()
end
---Open the tree in the a window
---@param opts OpenInWinOpts|nil
function M.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(opts.hijack_current_buf and vim.api.nvim_get_current_buf())
setup_tabpage(vim.api.nvim_get_current_tabpage())
set_current_win()
set_window_options_and_buffer()
if opts.resize then
M.reposition_window()
M.resize()
end
end end
function M.abandon_current_window() function M.abandon_current_window()
local tab = vim.api.nvim_get_current_tabpage() local tab = vim.api.nvim_get_current_tabpage()
BUFNR_PER_TAB[tab] = nil BUFNR_PER_TAB[tab] = nil
if M.View.tabpages[tab] then if TABPAGES[tab] then
M.View.tabpages[tab].winnr = nil TABPAGES[tab].winnr = nil
end end
end end
function M.abandon_all_windows() function M.abandon_all_windows()
for tab, _ in pairs(vim.api.nvim_list_tabpages()) do for tab, _ in pairs(vim.api.nvim_list_tabpages()) do
BUFNR_PER_TAB[tab] = nil BUFNR_PER_TAB[tab] = nil
if M.View.tabpages[tab] then if TABPAGES[tab] then
M.View.tabpages[tab].winnr = nil TABPAGES[tab].winnr = nil
end end
end end
end end
@@ -448,15 +185,15 @@ end
---@return boolean ---@return boolean
function M.is_visible(opts) function M.is_visible(opts)
if opts and opts.tabpage then if opts and opts.tabpage then
if M.View.tabpages[opts.tabpage] == nil then if TABPAGES[opts.tabpage] == nil then
return false return false
end end
local winnr = M.View.tabpages[opts.tabpage].winnr local winnr = TABPAGES[opts.tabpage].winnr
return winnr and vim.api.nvim_win_is_valid(winnr) return winnr and vim.api.nvim_win_is_valid(winnr)
end end
if opts and opts.any_tabpage then if opts and opts.any_tabpage then
for _, v in pairs(M.View.tabpages) do for _, v in pairs(TABPAGES) do
if v.winnr and vim.api.nvim_win_is_valid(v.winnr) then if v.winnr and vim.api.nvim_win_is_valid(v.winnr) then
return true return true
end end
@@ -474,24 +211,6 @@ function M.set_cursor(opts)
end end
end end
---@param winnr number|nil
---@param open_if_closed boolean|nil
function M.focus(winnr, open_if_closed)
local wnr = winnr or M.get_winnr()
if vim.api.nvim_win_get_tabpage(wnr or 0) ~= vim.api.nvim_win_get_tabpage(0) then
M.close()
M.open()
wnr = M.get_winnr()
elseif open_if_closed and not M.is_visible() then
M.open()
end
if wnr then
vim.api.nvim_set_current_win(wnr)
end
end
--- Retrieve the winid of the open tree. --- Retrieve the winid of the open tree.
---@param opts ApiTreeWinIdOpts|nil ---@param opts ApiTreeWinIdOpts|nil
---@return number|nil winid unlike get_winnr(), this returns nil if the nvim-tree window is not visible ---@return number|nil winid unlike get_winnr(), this returns nil if the nvim-tree window is not visible
@@ -510,7 +229,7 @@ end
--- Restores the state of a NvimTree window if it was initialized before. --- Restores the state of a NvimTree window if it was initialized before.
function M.restore_tab_state() function M.restore_tab_state()
local tabpage = vim.api.nvim_get_current_tabpage() local tabpage = vim.api.nvim_get_current_tabpage()
M.set_cursor(M.View.cursors[tabpage]) M.set_cursor(CURSORS[tabpage])
end end
--- Returns the window number for nvim-tree within the tabpage specified --- Returns the window number for nvim-tree within the tabpage specified
@@ -518,7 +237,7 @@ end
---@return number|nil ---@return number|nil
function M.get_winnr(tabpage) function M.get_winnr(tabpage)
tabpage = tabpage or vim.api.nvim_get_current_tabpage() tabpage = tabpage or vim.api.nvim_get_current_tabpage()
local tabinfo = M.View.tabpages[tabpage] local tabinfo = TABPAGES[tabpage]
if tabinfo and tabinfo.winnr and vim.api.nvim_win_is_valid(tabinfo.winnr) then if tabinfo and tabinfo.winnr and vim.api.nvim_win_is_valid(tabinfo.winnr) then
return tabinfo.winnr return tabinfo.winnr
end end
@@ -530,115 +249,14 @@ function M.get_bufnr()
return BUFNR_PER_TAB[vim.api.nvim_get_current_tabpage()] return BUFNR_PER_TAB[vim.api.nvim_get_current_tabpage()]
end end
function M._prevent_buffer_override() ---@param winnr number|nil
local view_winnr = M.get_winnr() function M.clear_tabpage(winnr)
local view_bufnr = M.get_bufnr() for i, tabpage in ipairs(TABPAGES) do
if tabpage.winnr == winnr then
-- need to schedule to let the new buffer populate the window TABPAGES[i] = nil
-- because this event needs to be run on bufWipeout. break
-- 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(M.View.tabpages) do
if tabpage.winnr == view_winnr then
M.View.tabpages[i] = nil
break
end
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 })
local explorer = require("nvim-tree.core").get_explorer()
if explorer then
explorer.renderer:draw()
end
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 M.is_root_folder_visible(cwd)
return cwd ~= "/" and not M.View.hide_root_folder
end
-- used on ColorScheme event
function M.reset_winhl()
local winnr = M.get_winnr()
if winnr and vim.api.nvim_win_is_valid(winnr) then
vim.wo[M.get_winnr()].winhl = M.View.winopts.winhl
end end
end end
---Check if width determined or calculated on-fly
---@return boolean
function M.is_width_determined()
return type(M.View.width) ~= "function"
end
---Configure width-related config
---@param width string|function|number|table|nil
function M.configure_width(width)
if type(width) == "table" then
M.View.adaptive_size = true
M.View.width = width.min or DEFAULT_MIN_WIDTH
M.View.max_width = width.max or DEFAULT_MAX_WIDTH
M.View.padding = width.padding or DEFAULT_PADDING
elseif width == nil then
if M.config.width ~= nil then
-- if we had input config - fallback to it
M.configure_width(M.config.width)
else
-- otherwise - restore initial width
M.View.width = M.View.initial_width
end
else
M.View.adaptive_size = false
M.View.width = width
end
end
function M.setup(opts)
local options = opts.view or {}
M.View.centralize_selection = options.centralize_selection
M.View.side = (options.side == "right") and "right" or "left"
M.View.height = options.height
M.View.hide_root_folder = opts.renderer.root_folder_label == false
M.View.tab = opts.tab
M.View.preserve_window_proportions = options.preserve_window_proportions
M.View.winopts.cursorline = options.cursorline
M.View.winopts.number = options.number
M.View.winopts.relativenumber = options.relativenumber
M.View.winopts.signcolumn = options.signcolumn
M.View.float = options.float
M.on_attach = opts.on_attach
M.config = options
M.configure_width(options.width)
M.View.initial_width = get_width()
end
return M return M

View File

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

View File

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

View File

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