Compare commits

..

33 Commits

Author SHA1 Message Date
Alexander Courtis
06079a591d refactor api.lua for cleaner git history 2025-07-28 10:03:32 +10:00
Alexander Courtis
114fde0e07 Merge branch 'master' into expand-until-2 2025-07-28 09:47:03 +10:00
alexfinger21
10db6943cb fix(#3077): deleting a directory containing symlinked directory will delete the contents of the linked directory (#3168)
* fix(#3077) deleting a directory containing symlink file will delete all content inside the symlink

* fix(#3077): add diagnostic override TODO

---------

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

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-07-21 13:22:56 +10:00
Alexander Courtis
2a386fe567 ci: luals_version 3.13.9 -> 3.15.0 (#3167) 2025-07-21 13:22:07 +10:00
ghostbuster91
641f125f6b Fix warnings 2025-07-20 21:24:29 +02:00
ghostbuster91
c1948cbce0 feat: Allow to expand nodes until certain condition is met 2025-07-20 21:07:49 +02:00
Alexander Courtis
b0b49552c9 docs: polish example decorator (#3160) 2025-06-21 10:57:48 +10:00
Alexander Courtis
8eb5e0bfd1 feat(#3157): add view.cursorlineopt (#3158)
fix(#3157): add view.cursorlineopt
2025-06-20 13:46:38 +10:00
Alexander Courtis
0a06f65bf0 refactor(#2826): move view to instanced window class (#3153)
* refactor(#2826): singleton View class, WIP

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

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

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

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

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

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

* refactor(#2826): singleton View class

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

* refactor(#2826): move autocmds to Explorer

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

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

* refactor(#2826): retain necessary view globals

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

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

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

* refactor(#2826): better enumerate_options function

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

* refactor(#2826): default lifecycle log off

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

* refactor(#2826): instrument View:get_winnr

* refactor(#2826): instrument View:setup_tabpage

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

* refactor(#2826): instrument View:get_bufnr

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

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

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

* vim: nvim-tree: more logging

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

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

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

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

Add a missing double quotes around the default value for
`nvim-tree.renderer.icons.bookmarks_placement` config value and fix
spelling of `bookmarked`.
2025-06-17 10:23:34 +10:00
phanium
d87b41ca53 fix: window picker ignore hidden window (#3145) 2025-06-15 15:04:47 +10:00
github-actions[bot]
6b5b366596 chore(master): release nvim-tree 1.13.0 (#3120)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-14 17:30:22 +10:00
Rami Elwan
ae595611fb feat(#3132): add api.node.expand and api.node.collapse (#3133)
* feat: allow passing node to collapse all

* refactor: use snake case

* feat: handle api legacy calls and update signature

* refactor: make sure open is a boolean

* doc: collapse_all

* Revert "doc: collapse_all"

This reverts commit d243da3e14.

* add api.node.collapse

* add api.node.expand

* add api.node.expand

---------

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

* fix(#3143): doc

---------

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

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

* refactoring: move `extmarks_length` to utils.lua

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

---------

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

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

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

* add folder_arrow_padding

* update help docs

* refactor: renderer.icons.padding

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

* refactor: renderer.icons.padding

---------

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

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

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

---------

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

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

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

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

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

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

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

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

* chore: use luals for style check

* chore: use luals for style check

* Revert "chore: use luals for style check"

This reverts commit e5fde80fab.

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-01 11:07:35 +11:00
57 changed files with 1766 additions and 1014 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.15.0 ]
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*
@@ -398,6 +398,7 @@ Following is the default configuration. See |nvim-tree-opts| for details. >lua
view = { view = {
centralize_selection = false, centralize_selection = false,
cursorline = true, cursorline = true,
cursorlineopt = "both",
debounce_delay = 15, debounce_delay = 15,
side = "left", side = "left",
preserve_window_proportions = false, preserve_window_proportions = false,
@@ -462,7 +463,10 @@ Following is the default configuration. See |nvim-tree-opts| for details. >lua
hidden_placement = "after", hidden_placement = "after",
diagnostics_placement = "signcolumn", diagnostics_placement = "signcolumn",
bookmarks_placement = "signcolumn", bookmarks_placement = "signcolumn",
padding = " ", padding = {
icon = " ",
folder_arrow = " ",
},
symlink_arrow = " ➛ ", symlink_arrow = " ➛ ",
show = { show = {
file = true, file = true,
@@ -635,6 +639,7 @@ Following is the default configuration. See |nvim-tree-opts| for details. >lua
}, },
}, },
experimental = { experimental = {
multi_instance = false,
}, },
log = { log = {
enable = false, enable = false,
@@ -767,6 +772,10 @@ initially centralized, see |zz|.
Enable |cursorline| in the tree window. Enable |cursorline| in the tree window.
Type: `boolean`, Default: `true` Type: `boolean`, Default: `true`
*nvim-tree.view.cursorlineopt*
Set |cursorlineopt| in the tree window.
Type: `string`, Default: `"both"`
*nvim-tree.view.debounce_delay* *nvim-tree.view.debounce_delay*
Idle milliseconds before some reload / refresh operations. Idle milliseconds before some reload / refresh operations.
Increase if you experience performance issues around screen refresh. Increase if you experience performance issues around screen refresh.
@@ -1063,12 +1072,16 @@ Configuration options for icons.
*nvim-tree.renderer.icons.bookmarks_placement* *nvim-tree.renderer.icons.bookmarks_placement*
Bookmark icon placement. Bookmark icon placement.
Type: `string`, Default: `signcolumn` Type: `string`, Default: `"signcolumn"`
*nvim-tree.renderer.icons.padding* *nvim-tree.renderer.icons.padding.icon*
Inserted between icon and filename. Inserted between icon and filename.
Type: `string`, Default: `" "` Type: `string`, Default: `" "`
*nvim-tree.renderer.icons.padding.folder_arrow*
Inserted between folder arrow icon and file/folder icon.
Type: `string`, Default: `" "`
*nvim-tree.renderer.icons.symlink_arrow* *nvim-tree.renderer.icons.symlink_arrow*
Used as a separator between symlinks' source and target. Used as a separator between symlinks' source and target.
Type: `string`, Default: `" ➛ "` Type: `string`, Default: `" ➛ "`
@@ -1373,7 +1386,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 +1476,8 @@ vim |current-directory| behaviour.
Type: `boolean`, Default: `false` Type: `boolean`, Default: `false`
*nvim-tree.actions.expand_all* *nvim-tree.actions.expand_all*
Configuration for expand_all behaviour. Configuration for |nvim-tree-api.tree.expand_all()| and
|nvim-tree-api.node.expand()|
*nvim-tree.actions.expand_all.max_folder_discovery* *nvim-tree.actions.expand_all.max_folder_discovery*
Limit the number of folders being explored when expanding every folders. Limit the number of folders being explored when expanding every folders.
@@ -1506,17 +1520,13 @@ Configuration options for opening a file from nvim-tree.
Resizes the tree when opening a file. Resizes the tree when opening a file.
Type: `boolean`, Default: `true` Type: `boolean`, Default: `true`
*nvim-tree.experimental.actions.open_file.relative_path*
Buffers opened by nvim-tree will use with relative paths instead of
absolute.
Type: `boolean`, Default: `true`
*nvim-tree.actions.open_file.window_picker* *nvim-tree.actions.open_file.window_picker*
Window picker configuration. Window picker configuration.
*nvim-tree.actions.open_file.window_picker.enable* *nvim-tree.actions.open_file.window_picker.enable*
Enable the feature. If the feature is not enabled, files will open in Enable the feature. If the feature is not enabled, files will open in
window from which you last opened the tree. window from which you last opened the tree, obeying
|nvim-tree.actions.open_file.window_picker.exclude|
Type: `boolean`, Default: `true` Type: `boolean`, Default: `true`
*nvim-tree.actions.open_file.window_picker.picker* *nvim-tree.actions.open_file.window_picker.picker*
@@ -1535,9 +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.
@@ -2890,9 +2929,33 @@ Decorators may:
- Set highlight group for the name or icons - Set highlight group for the name or icons
- Override node icon - Override node icon
Specify decorators and their precedence via |nvim-tree.renderer.decorators| Create a `nvim_tree.api.decorator.UserDecorator` class and register it with
e.g. defaults with a user decorator class being overridden only by Cut: >lua precedence via |nvim-tree.renderer.decorators|
{
See |nvim-tree-decorator-example|
See `nvim-tree/_meta/api_decorator.lua` for full class documentation.
==============================================================================
11.1. DECORATOR EXAMPLE *nvim-tree-decorator-example*
A decorator class for nodes named "example", overridind all builtin decorators
except for Cut.
- Highlights node name with `IncSearch`
- Creates two icons `"1"` and `"2"` placed after the node name, highlighted with
`DiffAdd` and `DiffText`
- Replaces the node icon with `"N"`, highlighted with `Error `
Create a class file `~/.config/nvim/lua/my-decorator.lua`
Require and register it during |nvim-tree-setup|:
>lua
local MyDecorator = require("my-decorator")
require("nvim-tree").setup({
renderer = {
decorators = {
"Git", "Git",
"Open", "Open",
"Hidden", "Hidden",
@@ -2902,31 +2965,35 @@ e.g. defaults with a user decorator class being overridden only by Cut: >lua
"Copied", "Copied",
MyDecorator, MyDecorator,
"Cut", "Cut",
} },
},
See `nvim-tree/_meta/api_decorator.lua` for full })
`nvim_tree.api.decorator.UserDecorator` class documentation.
< <
============================================================================== Contents of `my-decorator.lua`:
11.1. DECORATOR EXAMPLE *nvim-tree-decorator-example*
>lua >lua
---Create your decorator class
---@class (exact) MyDecorator: nvim_tree.api.decorator.UserDecorator ---@class (exact) MyDecorator: nvim_tree.api.decorator.UserDecorator
---@field private my_icon nvim_tree.api.HighlightedString ---@field private my_icon1 nvim_tree.api.HighlightedString
---@field private my_icon2 nvim_tree.api.HighlightedString
---@field private my_icon_node nvim_tree.api.HighlightedString
---@field private my_highlight_group string
local MyDecorator = require("nvim-tree.api").decorator.UserDecorator:extend() local MyDecorator = require("nvim-tree.api").decorator.UserDecorator:extend()
---Mandatory constructor :new() will be called once per tree render, with no arguments. ---Mandatory constructor :new() will be called once per tree render, with no arguments.
function MyDecorator:new() function MyDecorator:new()
self.enabled = true self.enabled = true
self.highlight_range = "all" self.highlight_range = "name"
self.icon_placement = "signcolumn" self.icon_placement = "after"
-- create your icon once, for convenience -- create your icons and highlights once, applied to every node
self.my_icon = { str = "I", hl = { "MyIcon" } } self.my_icon1 = { str = "1", hl = { "DiffAdd" } }
self.my_icon2 = { str = "2", hl = { "DiffText" } }
self.my_icon_node = { str = "N", hl = { "Error" } }
self.my_highlight_group = "IncSearch"
-- Define the icon sign only once -- Define the icon signs only once
-- Only needed if you are using icon_placement = "signcolumn" -- Only needed if you are using icon_placement = "signcolumn"
self:define_sign(self.my_icon) -- self:define_sign(self.my_icon1)
-- self:define_sign(self.my_icon2)
end end
---Override node icon ---Override node icon
@@ -2934,33 +3001,35 @@ See `nvim-tree/_meta/api_decorator.lua` for full
---@return nvim_tree.api.HighlightedString? icon_node ---@return nvim_tree.api.HighlightedString? icon_node
function MyDecorator:icon_node(node) function MyDecorator:icon_node(node)
if node.name == "example" then if node.name == "example" then
return self.my_icon return self.my_icon_node
else else
return nil return nil
end end
end end
---Return one icon for DecoratorIconPlacement ---Return two icons for DecoratorIconPlacement "after"
---@param node nvim_tree.api.Node ---@param node nvim_tree.api.Node
---@return nvim_tree.api.HighlightedString[]? icons ---@return nvim_tree.api.HighlightedString[]? icons
function MyDecorator:icons(node) function MyDecorator:icons(node)
if node.name == "example" then if node.name == "example" then
return { self.my_icon } return { self.my_icon1, self.my_icon2, }
else else
return nil return nil
end end
end end
---Exactly one highlight group for DecoratorHighlightRange ---Exactly one highlight group for DecoratorHighlightRange "name"
---@param node nvim_tree.api.Node ---@param node nvim_tree.api.Node
---@return string? highlight_group ---@return string? highlight_group
function MyDecorator:highlight_group(node) function MyDecorator:highlight_group(node)
if node.name == "example" then if node.name == "example" then
return "MyHighlight" return self.my_highlight_group
else else
return nil return nil
end end
end end
return MyDecorator
< <
============================================================================== ==============================================================================
12. OS SPECIFIC RESTRICTIONS *nvim-tree-os-specific* 12. OS SPECIFIC RESTRICTIONS *nvim-tree-os-specific*
@@ -2970,9 +3039,6 @@ Windows WSL and PowerShell
- Executable file detection is disabled as this is non-performant and can - Executable file detection is disabled as this is non-performant and can
freeze nvim freeze nvim
- Some filesystem watcher error related to permissions will not be reported - Some filesystem watcher error related to permissions will not be reported
- Some users have reported unspecified issues with
|nvim-tree.experimental.actions.open_file.relative_path|. Please report any
issues or disable this feature.
============================================================================== ==============================================================================
13. NETRW *nvim-tree-netrw* 13. NETRW *nvim-tree-netrw*
@@ -3019,6 +3085,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*
@@ -3106,7 +3173,6 @@ highlight group is not, hard linking as follows: >
|nvim-tree.diagnostics.show_on_open_dirs| |nvim-tree.diagnostics.show_on_open_dirs|
|nvim-tree.disable_netrw| |nvim-tree.disable_netrw|
|nvim-tree.experimental| |nvim-tree.experimental|
|nvim-tree.experimental.actions.open_file.relative_path|
|nvim-tree.filesystem_watchers.debounce_delay| |nvim-tree.filesystem_watchers.debounce_delay|
|nvim-tree.filesystem_watchers.enable| |nvim-tree.filesystem_watchers.enable|
|nvim-tree.filesystem_watchers.ignore_dirs| |nvim-tree.filesystem_watchers.ignore_dirs|
@@ -3175,7 +3241,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|
@@ -3226,6 +3293,7 @@ highlight group is not, hard linking as follows: >
|nvim-tree.update_focused_file.update_root.ignore_list| |nvim-tree.update_focused_file.update_root.ignore_list|
|nvim-tree.view.centralize_selection| |nvim-tree.view.centralize_selection|
|nvim-tree.view.cursorline| |nvim-tree.view.cursorline|
|nvim-tree.view.cursorlineopt|
|nvim-tree.view.debounce_delay| |nvim-tree.view.debounce_delay|
|nvim-tree.view.float| |nvim-tree.view.float|
|nvim-tree.view.float.enable| |nvim-tree.view.float.enable|
@@ -3282,6 +3350,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

@@ -1,5 +1,4 @@
local log = require("nvim-tree.log") local log = require("nvim-tree.log")
local view = require("nvim-tree.view")
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local actions = require("nvim-tree.actions") local actions = require("nvim-tree.actions")
local core = require("nvim-tree.core") local core = require("nvim-tree.core")
@@ -74,7 +73,8 @@ function M.change_root(path, bufnr)
end end
function M.tab_enter() function M.tab_enter()
if view.is_visible({ any_tabpage = true }) then local explorer = core.get_explorer()
if explorer and explorer.view:is_visible({ any_tabpage = true }) then
local bufname = vim.api.nvim_buf_get_name(0) local bufname = vim.api.nvim_buf_get_name(0)
local ft local ft
@@ -89,17 +89,15 @@ function M.tab_enter()
return return
end end
end end
view.open({ focus_tree = false }) explorer.view:open({ focus_tree = false })
local explorer = core.get_explorer()
if explorer then
explorer.renderer:draw() explorer.renderer:draw()
end end
end
end end
function M.open_on_directory() function M.open_on_directory()
local should_proceed = _config.hijack_directories.auto_open or view.is_visible() local explorer = core.get_explorer()
local should_proceed = _config.hijack_directories.auto_open or explorer and explorer.view:is_visible()
if not should_proceed then if not should_proceed then
return return
end end
@@ -150,21 +148,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 +209,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
return
end
if vim.api.nvim_get_option_value("filetype", { buf = ev.buf }) == "NvimTree" then
require("nvim-tree.events")._dispatch_on_tree_close()
end end
end, end,
}) })
end
end end
local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
@@ -259,6 +245,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
view = { view = {
centralize_selection = false, centralize_selection = false,
cursorline = true, cursorline = true,
cursorlineopt = "both",
debounce_delay = 15, debounce_delay = 15,
side = "left", side = "left",
preserve_window_proportions = false, preserve_window_proportions = false,
@@ -323,7 +310,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,
@@ -496,6 +486,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
}, },
}, },
experimental = { experimental = {
multi_instance = false,
}, },
log = { log = {
enable = false, enable = false,
@@ -675,10 +666,10 @@ 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 if explorer then
explorer.view:close_all_tabs()
explorer.view:abandon_all_windows("purge_all_state")
require("nvim-tree.git").purge_state() require("nvim-tree.git").purge_state()
explorer:destroy() explorer:destroy()
core.reset_explorer() core.reset_explorer()
@@ -731,12 +722,12 @@ 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)
require("nvim-tree.help").setup(opts) require("nvim-tree.help").setup(opts)
require("nvim-tree.watcher").setup(opts) require("nvim-tree.watcher").setup(opts)
require("nvim-tree.multi-instance-debug").setup(opts)
setup_autocommands(opts) setup_autocommands(opts)

View File

@@ -1,5 +1,4 @@
local log = require("nvim-tree.log") local log = require("nvim-tree.log")
local view = require("nvim-tree.view")
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local core = require("nvim-tree.core") local core = require("nvim-tree.core")
@@ -14,7 +13,7 @@ local running = {}
---@param path string relative or absolute ---@param path string relative or absolute
function M.fn(path) function M.fn(path)
local explorer = core.get_explorer() local explorer = core.get_explorer()
if not explorer or not view.is_visible() then if not explorer or not explorer.view:is_visible() then
return return
end end
@@ -84,9 +83,9 @@ function M.fn(path)
end) end)
:iterate() :iterate()
if found and view.is_visible() then if found and explorer.view:is_visible() then
explorer.renderer:draw() explorer.renderer:draw()
view.set_cursor({ line, 0 }) explorer.view:set_cursor({ line, 0 })
end end
running[path_real] = false running[path_real] = false

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.view.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.view.float.enable) then
vim.api.nvim_set_current_win(buf.windows[1]) vim.api.nvim_set_current_win(buf.windows[1])
vim.cmd(":bn") vim.cmd(":bn")
end end
vim.api.nvim_buf_delete(buf.bufnr, { force = true }) vim.api.nvim_buf_delete(buf.bufnr, { force = true })
if not view.View.float.quit_on_focus_loss then if explorer and not explorer.view.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
@@ -69,9 +71,16 @@ local function remove_dir(cwd)
-- Type must come from fs_stat and not fs_scandir_next to maintain sshfs compatibility -- Type must come from fs_stat and not fs_scandir_next to maintain sshfs compatibility
local stat = vim.loop.fs_stat(new_cwd) local stat = vim.loop.fs_stat(new_cwd)
local type = stat and stat.type or nil -- TODO remove once 0.12 is the minimum neovim version
-- path incorrectly specified as an integer, fixed upstream for neovim 0.12 https://github.com/neovim/neovim/pull/33872
---@diagnostic disable-next-line: param-type-mismatch
local lstat = vim.loop.fs_lstat(new_cwd)
if type == "directory" then local type = stat and stat.type or nil
-- Checks if file is a link file to ensure deletion of the symlink instead of the file it points to
local ltype = lstat and lstat.type or nil
if type == "directory" and ltype ~= "link" then
local success = remove_dir(new_cwd) local success = remove_dir(new_cwd)
if not success then if not success then
return false return false

View File

@@ -1,5 +1,4 @@
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local view = require("nvim-tree.view")
local core = require("nvim-tree.core") local core = require("nvim-tree.core")
local diagnostics = require("nvim-tree.diagnostics") local diagnostics = require("nvim-tree.diagnostics")
@@ -67,9 +66,9 @@ local function move(explorer, where, what, skip_gitignored)
end end
if nex then if nex then
view.set_cursor({ nex, 0 }) explorer.view:set_cursor({ nex, 0 })
elseif vim.o.wrapscan and first then elseif vim.o.wrapscan and first then
view.set_cursor({ first, 0 }) explorer.view:set_cursor({ first, 0 })
end end
end end
@@ -189,13 +188,13 @@ local function move_prev_recursive(explorer, what, skip_gitignored)
-- 4.3) -- 4.3)
if node_init.name == ".." then -- root node if node_init.name == ".." then -- root node
view.set_cursor({ 1, 0 }) -- move to root node (position 1) explorer.view:set_cursor({ 1, 0 }) -- move to root node (position 1)
else else
local node_init_line = utils.find_node_line(node_init) local node_init_line = utils.find_node_line(node_init)
if node_init_line < 0 then if node_init_line < 0 then
return return
end end
view.set_cursor({ node_init_line, 0 }) explorer.view:set_cursor({ node_init_line, 0 })
end end
-- 4.4) -- 4.4)

View File

@@ -1,4 +1,3 @@
local view = require("nvim-tree.view")
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
@@ -25,7 +24,7 @@ function M.fn(should_close)
local parent = (node:get_parent_of_group() or node).parent local parent = (node:get_parent_of_group() or node).parent
if not parent or not parent.parent then if not parent or not parent.parent then
view.set_cursor({ 1, 0 }) node.explorer.view:set_cursor({ 1, 0 })
return return
end end
@@ -33,7 +32,7 @@ function M.fn(should_close)
return n.absolute_path == parent.absolute_path return n.absolute_path == parent.absolute_path
end) end)
view.set_cursor({ line + 1, 0 }) node.explorer.view:set_cursor({ line + 1, 0 })
if should_close then if should_close then
parent.open = false parent.open = false
parent.explorer.renderer:draw() parent.explorer.renderer:draw()

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
if vim.api.nvim_win_is_valid(current_popup.winnr) then
vim.api.nvim_win_close(current_popup.winnr, true) 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,7 +2,8 @@
local lib = require("nvim-tree.lib") local lib = require("nvim-tree.lib")
local notify = require("nvim-tree.notify") local notify = require("nvim-tree.notify")
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local view = require("nvim-tree.view") local core = require("nvim-tree.core")
local full_name = require("nvim-tree.renderer.components.full-name")
local M = {} local M = {}
@@ -19,9 +20,10 @@ end
---Get all windows in the current tabpage that aren't NvimTree. ---Get all windows in the current tabpage that aren't NvimTree.
---@return table with valid win_ids ---@return table with valid win_ids
local function usable_win_ids() local function usable_win_ids()
local explorer = core.get_explorer()
local tabpage = vim.api.nvim_get_current_tabpage() local tabpage = vim.api.nvim_get_current_tabpage()
local win_ids = vim.api.nvim_tabpage_list_wins(tabpage) local win_ids = vim.api.nvim_tabpage_list_wins(tabpage)
local tree_winid = view.get_winnr(tabpage) local tree_winid = explorer and explorer.view:get_winnr(tabpage, "open-file.usable_win_ids")
return vim.tbl_filter(function(id) return vim.tbl_filter(function(id)
local bufid = vim.api.nvim_win_get_buf(id) local bufid = vim.api.nvim_win_get_buf(id)
@@ -39,21 +41,15 @@ local function usable_win_ids()
end end
local win_config = vim.api.nvim_win_get_config(id) local win_config = vim.api.nvim_win_get_config(id)
return id ~= tree_winid and win_config.focusable and not win_config.external or false return id ~= tree_winid
and id ~= full_name.popup_win
and win_config.focusable
and not win_config.hide
and not win_config.external
or false
end, win_ids) end, win_ids)
end end
---Find the first window in the tab that is not NvimTree.
---@return integer -1 if none available
local function first_win_id()
local selectable = usable_win_ids()
if #selectable > 0 then
return selectable[1]
else
return -1
end
end
---Get user to pick a window in the tab that is not NvimTree. ---Get user to pick a window in the tab that is not NvimTree.
---@return integer|nil -- If a valid window was picked, return its id. If an ---@return integer|nil -- If a valid window was picked, return its id. If an
--- invalid window was picked / user canceled, return nil. If there are --- invalid window was picked / user canceled, return nil. If there are
@@ -198,7 +194,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.view:close(nil, "open-file.open_file_in_tab")
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 +207,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.view:close(nil, "open-file.drop")
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 +220,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.view:close(nil, "open-file.tab_drop")
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 +244,24 @@ local function on_preview(buf_loaded)
once = true, once = true,
}) })
end end
view.focus() local explorer = core.get_explorer()
if explorer then
explorer.view: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 +292,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 +316,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.view.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 +352,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.view.float.enable then
-- ignore "WinLeave" autocmd on preview -- ignore "WinLeave" autocmd on preview
-- because the registered "WinLeave" -- because the registered "WinLeave"
-- will kill the floating window immediately -- will kill the floating window immediately
@@ -373,7 +392,12 @@ local function is_already_loaded(filename)
end end
local function edit_in_current_buf(filename) local function edit_in_current_buf(filename)
require("nvim-tree.view").abandon_current_window() local explorer = core.get_explorer()
if explorer then
explorer.view:abandon_current_window()
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())
end end
@@ -384,6 +408,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 +444,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.view: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.view:close(nil, "open-file.fn")
end end
end end

View File

@@ -85,7 +85,7 @@ M.force_dirchange = add_profiling_to(function(foldername, should_open_view)
if should_change_dir() then if should_change_dir() then
cd(M.options.global, foldername) cd(M.options.global, foldername)
end end
core.init(foldername) core.init(foldername, "change-dir")
end end
if should_open_view then if should_open_view then

View File

@@ -1,6 +1,5 @@
local core = require("nvim-tree.core") local core = require("nvim-tree.core")
local lib = require("nvim-tree.lib") local lib = require("nvim-tree.lib")
local view = require("nvim-tree.view")
local finders_find_file = require("nvim-tree.actions.finders.find-file") local finders_find_file = require("nvim-tree.actions.finders.find-file")
local M = {} local M = {}
@@ -41,11 +40,12 @@ function M.fn(opts)
return return
end end
if view.is_visible() then local explorer = core.get_explorer()
if explorer and explorer.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.view: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 = {}
@@ -29,17 +30,35 @@ end
---@param expansion_count integer ---@param expansion_count integer
---@param node Node ---@param node Node
---@return boolean ---@return boolean
local function should_expand(expansion_count, node) local function descend_until_max_or_empty(expansion_count, node)
local dir = node:as(DirectoryNode) local dir = node:as(DirectoryNode)
if not dir then if not dir then
return false return false
end end
local should_halt = expansion_count >= M.MAX_FOLDER_DISCOVERY local should_halt = expansion_count >= M.MAX_FOLDER_DISCOVERY
local should_exclude = M.EXCLUDE[dir.name] local should_exclude = M.EXCLUDE[dir.name]
return not should_halt and not dir.open and not should_exclude
return not should_halt and not should_exclude
end end
local function gen_iterator() ---@param expansion_count integer
---@param node Node
---@param should_descend fun(expansion_count: integer, node: Node): boolean
---@return boolean
local function should_expand(expansion_count, node, should_descend)
local dir = node:as(DirectoryNode)
if not dir then
return false
end
return not dir.open and should_descend(expansion_count, node)
end
---@param should_descend fun(expansion_count: integer, node: Node): boolean
---@return fun(node): any
local function gen_iterator(should_descend)
local expansion_count = 0 local expansion_count = 0
return function(parent) return function(parent)
@@ -51,7 +70,7 @@ local function gen_iterator()
Iterator.builder(parent.nodes) Iterator.builder(parent.nodes)
:hidden() :hidden()
:applier(function(node) :applier(function(node)
if should_expand(expansion_count, node) then if should_expand(expansion_count, node, should_descend) then
expansion_count = expansion_count + 1 expansion_count = expansion_count + 1
node = node:as(DirectoryNode) node = node:as(DirectoryNode)
if node then if node then
@@ -60,7 +79,19 @@ local function gen_iterator()
end end
end) end)
:recursor(function(node) :recursor(function(node)
return expansion_count < M.MAX_FOLDER_DISCOVERY and (node.group_next and { node.group_next } or (node.open and node.nodes)) if not should_descend(expansion_count, node) then
return nil
end
if node.group_next then
return { node.group_next }
end
if node.open and node.nodes then
return node.nodes
end
return nil
end) end)
:iterate() :iterate()
@@ -70,23 +101,41 @@ local function gen_iterator()
end end
end end
---Expand the directory node or the root ---@param node Node?
---@param node Node ---@param expand_opts ApiTreeExpandAllOpts?
function M.fn(node) local function expand_node(node, expand_opts)
local explorer = core.get_explorer() if not node then
local parent = node:as(DirectoryNode) or explorer
if not parent then
return return
end end
local descend_until = (expand_opts and expand_opts.descend_until) or descend_until_max_or_empty
if gen_iterator()(parent) then if gen_iterator(descend_until)(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
---@param expand_opts ApiTreeExpandAllOpts?
function M.all(node, expand_opts)
expand_node(node and node:as(DirectoryNode) or core.get_explorer(), expand_opts)
end
---Expand the directory node or parent node
---@param node Node
---@param expand_opts ApiTreeExpandAllOpts?
function M.node(node, expand_opts)
if not node then
return
end
expand_node(node:is(FileNode) and node.parent or node:as(DirectoryNode), expand_opts)
end
function M.setup(opts) 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,5 +1,5 @@
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 finders_find_file = require("nvim-tree.actions.finders.find-file") local finders_find_file = require("nvim-tree.actions.finders.find-file")
local M = {} local M = {}
@@ -23,10 +23,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 explorer.view:is_visible() then
-- focus -- focus
lib.set_target_win() lib.set_target_win()
view.focus() explorer.view: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.view:configure_width()
view.resize() explorer.view: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.view:configure_width(width_cfg)
view.resize() explorer.view:resize()
return return
end end
if not view.is_width_determined() then if not explorer.view: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.view: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.view:resize(relative_size)
return return
end end
end end

View File

@@ -1,5 +1,5 @@
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 finders_find_file = require("nvim-tree.actions.finders.find-file") local finders_find_file = require("nvim-tree.actions.finders.find-file")
local M = {} local M = {}
@@ -10,6 +10,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 +42,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 explorer.view:is_visible() then
-- close -- close
view.close() explorer.view:close(nil, "toggle.fn")
else else
-- open -- open
lib.open({ lib.open({

View File

@@ -1,5 +1,4 @@
local core = require("nvim-tree.core") local core = require("nvim-tree.core")
local view = require("nvim-tree.view")
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local actions = require("nvim-tree.actions") local actions = require("nvim-tree.actions")
local appearance_hi_test = require("nvim-tree.appearance.hi-test") local appearance_hi_test = require("nvim-tree.appearance.hi-test")
@@ -141,9 +140,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("view", "close")
Api.tree.close_in_this_tab = wrap(view.close_this_tab_only) Api.tree.close_in_this_tab = wrap_explorer_member("view", "close_this_tab_only")
Api.tree.close_in_all_tabs = wrap(view.close_all_tabs) Api.tree.close_in_all_tabs = wrap_explorer_member("view", "close_all_tabs")
Api.tree.reload = wrap_explorer("reload_explorer") Api.tree.reload = wrap_explorer("reload_explorer")
---@class ApiTreeResizeOpts ---@class ApiTreeResizeOpts
@@ -182,8 +181,16 @@ Api.tree.get_nodes = wrap_explorer("get_nodes")
Api.tree.find_file = wrap(actions.tree.find_file.fn) Api.tree.find_file = wrap(actions.tree.find_file.fn)
Api.tree.search_node = wrap(actions.finders.search_node.fn) Api.tree.search_node = wrap(actions.finders.search_node.fn)
Api.tree.collapse_all = wrap(actions.tree.modifiers.collapse_all.fn)
Api.tree.expand_all = wrap_node(actions.tree.modifiers.expand_all.fn) ---@class ApiCollapseOpts
---@field keep_buffers boolean|nil default false
Api.tree.collapse_all = wrap(actions.tree.modifiers.collapse.all)
---@class ApiTreeExpandAllOpts
---@field descend_until (fun(expansion_count: integer, node: Node): boolean)|nil
Api.tree.expand_all = wrap_node(actions.tree.modifiers.expand.all)
Api.tree.toggle_enable_filters = wrap_explorer_member("filters", "toggle") Api.tree.toggle_enable_filters = wrap_explorer_member("filters", "toggle")
Api.tree.toggle_gitignore_filter = wrap_explorer_member_args("filters", "toggle", "git_ignored") Api.tree.toggle_gitignore_filter = wrap_explorer_member_args("filters", "toggle", "git_ignored")
Api.tree.toggle_git_clean_filter = wrap_explorer_member_args("filters", "toggle", "git_clean") Api.tree.toggle_git_clean_filter = wrap_explorer_member_args("filters", "toggle", "git_clean")
@@ -198,12 +205,12 @@ Api.tree.is_tree_buf = wrap(utils.is_nvim_tree_buf)
---@field tabpage number|nil ---@field tabpage number|nil
---@field any_tabpage boolean|nil default false ---@field any_tabpage boolean|nil default false
Api.tree.is_visible = wrap(view.is_visible) Api.tree.is_visible = wrap_explorer_member("view", "is_visible")
---@class ApiTreeWinIdOpts ---@class ApiTreeWinIdOpts
---@field tabpage number|nil default nil ---@field tabpage number|nil default nil
Api.tree.winid = wrap(view.winid) Api.tree.winid = wrap_explorer_member("view", "api_winid")
Api.fs.create = wrap_node_or_nil(actions.fs.create_file.fn) Api.fs.create = wrap_node_or_nil(actions.fs.create_file.fn)
Api.fs.remove = wrap_node(actions.fs.remove_file.fn) Api.fs.remove = wrap_node(actions.fs.remove_file.fn)
@@ -235,13 +242,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.view:close(cur_tabpage, "api.edit " .. mode)
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,10 +262,25 @@ 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.view:focus()
end
end end
end end
Api.tree.toggle_descend_until = wrap_node(function(node, descend_until)
local dir = node:as(DirectoryNode)
if dir then
if node.open then
dir:expand_or_collapse(nil)
else
actions.tree.modifiers.expand.all(node, { descend_until = descend_until })
end
else
edit("edit", node)
end
end)
---@param mode string ---@param mode string
---@param toggle_group boolean? ---@param toggle_group boolean?
---@return fun(node: Node, edit_opts: NodeEditOpts?) ---@return fun(node: Node, edit_opts: NodeEditOpts?)
@@ -312,6 +338,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.view: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 = {}
@@ -10,9 +9,12 @@ local TreeExplorer = nil
local first_init_done = false local first_init_done = false
---@param foldername string ---@param foldername string
function M.init(foldername) ---@param callsite string
function M.init(foldername, callsite)
local profile = log.profile_start("core init %s", foldername) local profile = log.profile_start("core init %s", foldername)
log.line("dev", "core.init(%s, %s)", foldername, callsite)
if TreeExplorer then if TreeExplorer then
TreeExplorer:destroy() TreeExplorer:destroy()
end end
@@ -55,7 +57,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.view:is_root_folder_visible(M.get_cwd()) then
offset = offset + 1 offset = offset + 1
end end
if TreeExplorer and TreeExplorer.live_filter.filter then if TreeExplorer and TreeExplorer.live_filter.filter then

View File

@@ -1,6 +1,5 @@
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 view = require("nvim-tree.view")
local log = require("nvim-tree.log") local log = require("nvim-tree.log")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
@@ -182,10 +181,15 @@ function M.update_coc()
end end
log.profile_end(profile) log.profile_end(profile)
local bufnr = view.get_bufnr() local explorer = core.get_explorer()
local bufnr
if explorer then
bufnr = explorer.view:get_bufnr("diagnostics.update_coc")
end
local should_draw = bufnr and vim.api.nvim_buf_is_valid(bufnr) and vim.api.nvim_buf_is_loaded(bufnr) local should_draw = bufnr and vim.api.nvim_buf_is_valid(bufnr) and vim.api.nvim_buf_is_loaded(bufnr)
if should_draw then if should_draw then
local explorer = core.get_explorer()
if explorer then if explorer then
explorer.renderer:draw() explorer.renderer:draw()
end end

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

@@ -4,7 +4,6 @@ local core = require("nvim-tree.core")
local git = require("nvim-tree.git") local git = require("nvim-tree.git")
local log = require("nvim-tree.log") local log = require("nvim-tree.log")
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local view = require("nvim-tree.view")
local node_factory = require("nvim-tree.node.factory") local node_factory = require("nvim-tree.node.factory")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
@@ -20,6 +19,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 View = require("nvim-tree.explorer.view")
local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON
@@ -35,6 +35,7 @@ local config
---@field sorters Sorter ---@field sorters Sorter
---@field marks Marks ---@field marks Marks
---@field clipboard Clipboard ---@field clipboard Clipboard
---@field view View
local Explorer = RootNode:extend() local Explorer = RootNode:extend()
---@class Explorer ---@class Explorer
@@ -55,6 +56,8 @@ 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:log_new("Explorer")
self.open = true self.open = true
self.opts = config self.opts = config
@@ -64,6 +67,7 @@ function Explorer:new(args)
self.live_filter = LiveFilter({ explorer = self }) self.live_filter = LiveFilter({ explorer = self })
self.marks = Marks({ explorer = self }) self.marks = Marks({ explorer = self })
self.clipboard = Clipboard({ explorer = self }) self.clipboard = Clipboard({ explorer = self })
self.view = View({ explorer = self })
self:create_autocmds() self:create_autocmds()
@@ -71,7 +75,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.view:destroy()
vim.api.nvim_del_augroup_by_id(self.augroup_id) vim.api.nvim_del_augroup_by_id(self.augroup_id)
@@ -84,11 +96,26 @@ function Explorer:create_autocmds()
group = self.augroup_id, group = self.augroup_id,
callback = function() callback = function()
appearance.setup() appearance.setup()
view.reset_winhl() self.view: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(data)
if self.opts.experimental.multi_instance then
log.line("dev", "WinLeave %s", vim.inspect(data, { newline = "" }))
end
if utils.is_nvim_tree_buf(0) then
self.view:close(nil, "WinLeave")
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 +128,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 +149,40 @@ 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(data)
if self.opts.experimental.multi_instance then
log.line("dev", "BufWipeout %s", vim.inspect(data, { newline = "" }))
end
if not utils.is_nvim_tree_buf(0) then
return
end
if self.opts.actions.open_file.eject then
self.view:prevent_buffer_override()
else
self.view:abandon_current_window()
end end
end, end,
}) })
@@ -214,8 +280,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 +440,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({
@@ -464,7 +532,7 @@ function Explorer:reload_explorer()
local projects = git.reload_all_projects() local projects = git.reload_all_projects()
self:refresh_nodes(projects) self:refresh_nodes(projects)
if view.is_visible() then if self.view:is_visible() then
self.renderer:draw() self.renderer:draw()
end end
event_running = false event_running = false
@@ -486,7 +554,7 @@ end
---nil on no explorer or invalid view win ---nil on no explorer or invalid view win
---@return integer[]|nil ---@return integer[]|nil
function Explorer:get_cursor_position() function Explorer:get_cursor_position()
local winnr = view.get_winnr() local winnr = self.view:get_winnr(nil, "Explorer:get_cursor_position")
if not winnr or not vim.api.nvim_win_is_valid(winnr) then if not winnr or not vim.api.nvim_win_is_valid(winnr) then
return return
end end
@@ -501,7 +569,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.view:is_root_folder_visible(core.get_cwd()) then
return self return self
end end
@@ -535,6 +603,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("dev", "+ %-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("dev", "- %-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

@@ -1,4 +1,3 @@
local view = require("nvim-tree.view")
local utils = require("nvim-tree.utils") local utils = require("nvim-tree.utils")
local Class = require("nvim-tree.classic") local Class = require("nvim-tree.classic")
@@ -21,12 +20,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 +61,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 +161,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 +171,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,13 +203,13 @@ 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.view.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()
local row = require("nvim-tree.core").get_nodes_starting_line() - 1 local row = require("nvim-tree.core").get_nodes_starting_line() - 1
local col = #self.prefix > 0 and #self.prefix - 1 or 1 local col = #self.prefix > 0 and #self.prefix - 1 or 1
view.set_cursor({ row, col }) self.explorer.view:set_cursor({ row, col })
-- needs scheduling to let the cursor move before initializing the window -- needs scheduling to let the cursor move before initializing the window
vim.schedule(function() vim.schedule(function()
return create_overlay(self) return create_overlay(self)
@@ -213,7 +218,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.view.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,799 @@
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 globals = require("nvim-tree.globals")
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) View: 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
---@field private bufnr_by_tab table<integer, integer> stored per tab until multi-instance is complete
local View = Class:extend()
---@class View
---@overload fun(args: ViewArgs): View
---@class (exact) ViewArgs
---@field explorer Explorer
---@protected
---@param args ViewArgs
function View:new(args)
args.explorer:log_new("View")
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.bufnr_by_tab = {}
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 = self.explorer.opts.view.cursorlineopt,
colorcolumn = "0",
wrap = false,
winhl = appearance.WIN_HL,
}
self:configure_width(self.explorer.opts.view.width)
self.initial_width = self:get_width()
end
function View:destroy()
self.explorer:log_destroy("View")
end
-- The initial state of a tab
local tabinitial = {
-- The position of the cursor { line, column }
cursor = { 0, 0 },
-- The NvimTree window number
winnr = nil,
}
---@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
---@return boolean
function View:matches_bufnr(bufnr)
for _, b in pairs(globals.BUFNR_PER_TAB) do
if b == bufnr then
return true
end
end
return false
end
---@private
function View:wipe_rogue_buffer()
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
if not self:matches_bufnr(bufnr) and utils.is_nvim_tree_buf(bufnr) then
pcall(vim.api.nvim_buf_delete, bufnr, { force = true })
end
end
end
---@private
---@param bufnr integer|false|nil
function View:create_buffer(bufnr)
self:wipe_rogue_buffer()
local tab = vim.api.nvim_get_current_tabpage()
globals.BUFNR_PER_TAB[tab] = bufnr or vim.api.nvim_create_buf(false, false)
if self.explorer.opts.experimental.multi_instance then
self.bufnr_by_tab[tab] = globals.BUFNR_PER_TAB[tab]
end
vim.api.nvim_buf_set_name(self:get_bufnr("View:create_buffer1"), "NvimTree_" .. tab)
bufnr = self:get_bufnr("View:create_buffer2")
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(self:get_bufnr("View:create_buffer3"))
events._dispatch_tree_attached_post(self:get_bufnr("View:create_buffer4"))
end
---@private
---@param size (fun():integer)|integer|string
---@return integer
function View: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
---@param size (fun():integer)|integer|nil
---@return integer
function View:get_width(size)
if size then
return self:get_size(size)
else
return self:get_size(self.width)
end
end
local move_tbl = {
left = "H",
right = "L",
}
-- setup_tabpage sets up the initial state of a tab
---@private
---@param tabpage integer
---@param callsite string
function View:setup_tabpage(tabpage, callsite)
local winnr = vim.api.nvim_get_current_win()
if self.explorer.opts.experimental.multi_instance then
log.line("dev", "View:setup_tabpage(%3s, %-20.20s) w%d %s",
tabpage,
callsite,
winnr,
globals.TABPAGES[tabpage] and vim.inspect(globals.TABPAGES[tabpage], { newline = "" }) or "tabinitial")
end
globals.TABPAGES[tabpage] = vim.tbl_extend("force", globals.TABPAGES[tabpage] or tabinitial, { winnr = winnr })
end
---@private
function View:set_window_options_and_buffer()
pcall(vim.api.nvim_command, "buffer " .. self:get_bufnr("View:set_window_options_and_buffer"))
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 View: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 View: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
self:setup_tabpage(vim.api.nvim_get_current_tabpage(), "View:open_window")
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
---save_tab_state saves any state that should be preserved across redraws.
---@private
---@param tabnr integer
function View:save_tab_state(tabnr)
local tabpage = tabnr or vim.api.nvim_get_current_tabpage()
globals.CURSORS[tabpage] = vim.api.nvim_win_get_cursor(self:get_winnr(tabpage, "View:save_tab_state") or 0)
end
---@private
---@param tabpage integer
function View:close_internal(tabpage)
if self.explorer.opts.experimental.multi_instance then
log.line("dev", "View:close_internal(t%s)", tabpage)
end
if not self:is_visible({ tabpage = tabpage }) then
return
end
self:save_tab_state(tabpage)
switch_buf_if_last_buf()
local tree_win = self:get_winnr(tabpage, "View:close_internal")
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
if self.explorer.opts.experimental.multi_instance then
log.line("dev", "View:close_internal(t%s) w%s", tabpage, tree_win)
end
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 View:close_this_tab_only()
self:close_internal(vim.api.nvim_get_current_tabpage())
end
function View:close_all_tabs()
for tabpage, _ in pairs(globals.TABPAGES) do
self:close_internal(tabpage)
end
end
---@param tabpage integer|nil
---@param callsite string
function View:close(tabpage, callsite)
if self.explorer.opts.experimental.multi_instance then
log.line("dev", "View:close(t%s, %s)", tabpage, callsite)
end
if self.explorer.opts.tab.sync.close then
self:close_all_tabs()
elseif tabpage then
self:close_internal(tabpage)
else
self:close_this_tab_only()
end
end
---@param options table|nil
function View:open(options)
if self: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 View: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(self:get_bufnr("View:grow1"), 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(self:get_winnr(nil, "View:grow"))
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(self:get_bufnr("View:grow2"), 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 View:grow_from_content()
if self.adaptive_size then
self:grow()
end
end
---@param size string|number|nil
function View: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 self:is_visible() then
return
end
local winnr = self:get_winnr(nil, "View:resize") 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 View:reposition_window()
local move_to = move_tbl[self.side]
vim.api.nvim_command("wincmd " .. move_to)
self:resize()
end
---@private
---@param callsite string
function View:set_current_win(callsite)
local current_tab = vim.api.nvim_get_current_tabpage()
local current_win = vim.api.nvim_get_current_win()
if self.explorer.opts.experimental.multi_instance then
log.line("dev", "View:set_current_win(%-20.20s) t%d w%3s->w%3s %s",
callsite,
current_tab,
globals.TABPAGES[current_tab].winnr,
current_win,
(globals.TABPAGES[current_tab].winnr == current_win) and "" or "MISMATCH"
)
end
globals.TABPAGES[current_tab].winnr = current_win
end
---Open the tree in the a window
---@param opts OpenInWinOpts|nil
function View: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())
self:setup_tabpage(vim.api.nvim_get_current_tabpage(), "View:open_in_win")
self:set_current_win("View:open_in_win")
self:set_window_options_and_buffer()
if opts.resize then
self:reposition_window()
self:resize()
end
events._dispatch_on_tree_open()
end
function View:abandon_current_window()
local tab = vim.api.nvim_get_current_tabpage()
if self.explorer.opts.experimental.multi_instance then
log.line("dev", "View:abandon_current_window() t%d w%s b%s member b%s %s",
tab,
globals.TABPAGES[tab] and globals.TABPAGES[tab].winnr or nil,
globals.BUFNR_PER_TAB[tab],
self.bufnr_by_tab[tab],
(globals.BUFNR_PER_TAB[tab] == self.bufnr_by_tab[tab]) and "" or "MISMATCH")
self.bufnr_by_tab[tab] = nil
end
-- TODO multi-instance kill the buffer instead of retaining
globals.BUFNR_PER_TAB[tab] = nil
if globals.TABPAGES[tab] then
globals.TABPAGES[tab].winnr = nil
end
end
---@param callsite string
function View:abandon_all_windows(callsite)
for tab, _ in pairs(vim.api.nvim_list_tabpages()) do
if self.explorer.opts.experimental.multi_instance then
log.line("dev", "View:abandon_all_windows(%-20.20s) t%d w%s b%s member b%s %s",
callsite,
tab,
globals.TABPAGES and globals.TABPAGES.winnr or nil,
globals.BUFNR_PER_TAB[tab],
self.bufnr_by_tab[tab],
(globals.BUFNR_PER_TAB[tab] == self.bufnr_by_tab[tab]) and "" or "MISMATCH")
end
-- TODO multi-instance kill the buffer instead of retaining
globals.BUFNR_PER_TAB[tab] = nil
if globals.TABPAGES[tab] then
globals.TABPAGES[tab].winnr = nil
end
end
end
---@param opts table|nil
---@return boolean
function View:is_visible(opts)
if opts and opts.tabpage then
if globals.TABPAGES[opts.tabpage] == nil then
return false
end
local winnr = globals.TABPAGES[opts.tabpage].winnr
return winnr and vim.api.nvim_win_is_valid(winnr)
end
if opts and opts.any_tabpage then
for _, v in pairs(globals.TABPAGES) do
if v.winnr and vim.api.nvim_win_is_valid(v.winnr) then
return true
end
end
return false
end
return self:get_winnr(nil, "View:is_visible1") ~= nil and vim.api.nvim_win_is_valid(self:get_winnr(nil, "View:is_visible2") or 0)
end
---@param opts table|nil
function View:set_cursor(opts)
if self:is_visible() then
pcall(vim.api.nvim_win_set_cursor, self:get_winnr(nil, "View:set_cursor"), opts)
end
end
---@param winnr number|nil
---@param open_if_closed boolean|nil
function View:focus(winnr, open_if_closed)
local wnr = winnr or self:get_winnr(nil, "View:focus1")
if vim.api.nvim_win_get_tabpage(wnr or 0) ~= vim.api.nvim_win_get_tabpage(0) then
self:close(nil, "View:focus")
self:open()
wnr = self:get_winnr(nil, "View:focus2")
elseif open_if_closed and not self:is_visible() then
self:open()
end
if wnr then
vim.api.nvim_set_current_win(wnr)
end
end
--- Retrieve the winid of the open tree.
---@param opts ApiTreeWinIdOpts|nil
---@return number|nil winid unlike get_winnr(), this returns nil if the nvim-tree window is not visible
function View:api_winid(opts)
local tabpage = opts and opts.tabpage
if tabpage == 0 then
tabpage = vim.api.nvim_get_current_tabpage()
end
if self:is_visible({ tabpage = tabpage }) then
return self:get_winnr(tabpage, "View:winid")
else
return nil
end
end
--- Restores the state of a NvimTree window if it was initialized before.
function View:restore_tab_state()
local tabpage = vim.api.nvim_get_current_tabpage()
self:set_cursor(globals.CURSORS[tabpage])
end
--- winid containing the buffer
---@param tabpage number|nil (optional) the number of the chosen tabpage. Defaults to current tabpage.
---@param callsite string
---@return integer? winid
function View:winid(tabpage, callsite)
local bufnr = self.bufnr_by_tab[tabpage]
local msg = string.format("View:winid(%3s, %-20.20s)", tabpage, callsite)
if bufnr then
for _, w in pairs(vim.api.nvim_tabpage_list_wins(tabpage or 0)) do
if vim.api.nvim_win_get_buf(w) == bufnr then
log.line("dev", "%s b%d : w%s", msg, bufnr, w)
return w
end
end
else
log.line("dev", "%s no bufnr", msg)
end
end
--- Returns the window number for nvim-tree within the tabpage specified
---@param tabpage number|nil (optional) the number of the chosen tabpage. Defaults to current tabpage.
---@param callsite string
---@return number|nil
function View:get_winnr(tabpage, callsite)
if self.explorer.opts.experimental.multi_instance then
local msg = string.format("View:get_winnr(%3s, %-20.20s)", tabpage, callsite)
tabpage = tabpage or vim.api.nvim_get_current_tabpage()
local tabinfo = globals.TABPAGES[tabpage]
local ret = nil
if not tabinfo then
msg = string.format("%s t%d no tabinfo", msg, tabpage)
elseif not tabinfo.winnr then
msg = string.format("%s t%d no tabinfo.winnr", msg, tabpage)
elseif not vim.api.nvim_win_is_valid(tabinfo.winnr) then
msg = string.format("%s t%d invalid tabinfo.winnr %d", msg, tabpage, tabinfo.winnr)
else
msg = string.format("%s t%d w%d", msg, tabpage, tabinfo.winnr)
ret = tabinfo.winnr
end
local winid = self:winid(tabpage, "View:get_winnr")
if ret ~= winid then
if ret then
msg = string.format("%s winid_from_bufnr w%s MISMATCH", msg, winid)
else
msg = string.format("%s winid_from_bufnr w%s STALE", msg, winid)
end
notify.error(string.format("View:get_winnr w%s View:winnr w%s MISMATCH", ret, winid))
end
log.line("dev", "%s", msg)
return ret
else
tabpage = tabpage or vim.api.nvim_get_current_tabpage()
local tabinfo = globals.TABPAGES[tabpage]
if tabinfo and tabinfo.winnr and vim.api.nvim_win_is_valid(tabinfo.winnr) then
return tabinfo.winnr
end
end
end
--- Returns the current nvim tree bufnr
---@param callsite string
---@return number
function View:get_bufnr(callsite)
local tab = vim.api.nvim_get_current_tabpage()
if self.explorer.opts.experimental.multi_instance then
log.line("dev", "View:get_bufnr(%-20.20s) t%d global b%s member b%s %s",
callsite,
tab,
globals.BUFNR_PER_TAB[tab],
self.bufnr_by_tab[tab],
(globals.BUFNR_PER_TAB[tab] == self.bufnr_by_tab[tab]) and "" or "MISMATCH")
if globals.BUFNR_PER_TAB[tab] ~= self.bufnr_by_tab[tab] then
notify.error(string.format("View:get_bufnr globals.BUFNR_PER_TAB[%s] b%s view.bufnr_by_tab[%s] b%s MISMATCH",
tab, globals.BUFNR_PER_TAB[tab],
tab, self.bufnr_by_tab[tab]
))
end
end
return globals.BUFNR_PER_TAB[tab]
end
function View:prevent_buffer_override()
local view_winnr = self:get_winnr(nil, "View:prevent_buffer_override")
local view_bufnr = self:get_bufnr("View:prevent_buffer_override")
-- need to schedule to let the new buffer populate the window
-- because this event needs to be run on bufWipeout.
-- Otherwise the curwin/curbuf would match the view buffer and the view window.
vim.schedule(function()
local curwin = vim.api.nvim_get_current_win()
local curwinconfig = vim.api.nvim_win_get_config(curwin)
local curbuf = vim.api.nvim_win_get_buf(curwin)
local bufname = vim.api.nvim_buf_get_name(curbuf)
if not bufname:match("NvimTree") then
for i, tabpage in ipairs(globals.TABPAGES) do
if tabpage.winnr == view_winnr then
if self.explorer.opts.experimental.multi_instance then
log.line("dev", "View:prevent_buffer_override() t%d w%d clearing", i, view_winnr)
end
globals.TABPAGES[i] = nil
break
end
end
end
if curwin ~= view_winnr or bufname == "" or curbuf == view_bufnr then
return
end
-- patch to avoid the overriding window to be fixed in size
-- might need a better patch
vim.cmd("setlocal nowinfixwidth")
vim.cmd("setlocal nowinfixheight")
self: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 View:is_root_folder_visible(cwd)
return cwd ~= "/" and not self.hide_root_folder
end
-- used on ColorScheme event
function View:reset_winhl()
local winnr = self:get_winnr(nil, "View:reset_winhl1")
if winnr and vim.api.nvim_win_is_valid(winnr) then
vim.wo[self:get_winnr(nil, "View:reset_winhl2")].winhl = appearance.WIN_HL
end
end
---Check if width determined or calculated on-fly
---@return boolean
function View:is_width_determined()
return type(self.width) ~= "function"
end
---Configure width-related config
---@param width string|function|number|table|nil
function View: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 View

10
lua/nvim-tree/globals.lua Normal file
View File

@@ -0,0 +1,10 @@
-- global state, to be refactored away during multi-instance
local M = {
-- from View
TABPAGES = {},
BUFNR_PER_TAB = {},
CURSORS = {},
}
return M

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,4 @@
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
@@ -13,9 +11,11 @@ local M = {
} }
function M.set_target_win() function M.set_target_win()
local explorer = core.get_explorer()
local id = vim.api.nvim_get_current_win() local id = vim.api.nvim_get_current_win()
local tree_id = view.get_winnr()
if tree_id and id == tree_id then if explorer and id == explorer.view:get_winnr(nil, "lib.set_target_win") then
M.target_winid = 0 M.target_winid = 0
return return
end end
@@ -31,11 +31,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.view: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
@@ -97,40 +102,42 @@ function M.open(opts)
M.set_target_win() M.set_target_win()
if not core.get_explorer() or opts.path then if not core.get_explorer() or opts.path then
if opts.path then if opts.path then
core.init(opts.path) core.init(opts.path, "lib.open - opts.path")
else else
local cwd, err = vim.loop.cwd() local cwd, err = vim.loop.cwd()
if not cwd then if not cwd then
notify.error(string.format("current working directory unavailable: %s", err)) notify.error(string.format("current working directory unavailable: %s", err))
return return
end end
core.init(cwd) core.init(cwd, "lib.open - cwd")
end end
end end
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.view:close_this_tab_only()
explorer.view: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.view: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.view: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()
events._dispatch_on_tree_open() if explorer then
explorer.view:restore_tab_state()
end
end end
function M.setup(opts) function M.setup(opts)

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

@@ -0,0 +1,112 @@
local globals = require("nvim-tree.globals")
local M = {}
--- Debugging only.
--- Tabs show TABPAGES winnr and BUFNR_PER_TAB bufnr for the tab.
--- Orphans for inexistent tab_ids are shown at the right.
--- lib.target_winid is always shown at the right next to a close button.
--- Enable with:
--- vim.opt.tabline = "%!v:lua.require('nvim-tree.explorer.view').tab_line()"
--- vim.opt.showtabline = 2
---@return string
function M.tab_line()
local tab_ids = vim.api.nvim_list_tabpages()
local cur_tab_id = vim.api.nvim_get_current_tabpage()
local bufnr_per_tab = vim.deepcopy(globals.BUFNR_PER_TAB)
local tabpages = vim.deepcopy(globals.TABPAGES)
local tl = "%#TabLine#"
for i, tab_id in ipairs(tab_ids) do
-- click to select
tl = tl .. "%" .. i .. "T"
-- style
if tab_id == cur_tab_id then
tl = tl .. "%#StatusLine#|"
else
tl = tl .. "|%#TabLine#"
end
-- tab_id itself
tl = tl .. " t" .. tab_id
-- winnr, if present
local tp = globals.TABPAGES[tab_id]
if tp then
tl = tl .. " w" .. (tp.winnr or "nil")
else
tl = tl .. " "
end
-- bufnr, if present
local bpt = globals.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 or "nil")
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
function M.setup(opts)
if not opts.experimental.multi_instance then
return
end
vim.opt.tabline = "%!v:lua.require('nvim-tree.multi-instance-debug').tab_line()"
vim.opt.showtabline = 2
end
return M

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.view: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

@@ -1,5 +1,4 @@
local log = require("nvim-tree.log") local log = require("nvim-tree.log")
local view = require("nvim-tree.view")
local events = require("nvim-tree.events") local events = require("nvim-tree.events")
local Class = require("nvim-tree.classic") local Class = require("nvim-tree.classic")
@@ -26,9 +25,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[]
@@ -96,28 +101,28 @@ function Renderer:render_hl(bufnr, hl_range_args)
end end
function Renderer:draw() function Renderer:draw()
local bufnr = view.get_bufnr() local bufnr = self.explorer.view:get_bufnr("Renderer:draw")
if not bufnr or not vim.api.nvim_buf_is_loaded(bufnr) then if not bufnr or not vim.api.nvim_buf_is_loaded(bufnr) then
return return
end end
local profile = log.profile_start("draw") local profile = log.profile_start("draw")
local cursor = vim.api.nvim_win_get_cursor(view.get_winnr() or 0) local cursor = vim.api.nvim_win_get_cursor(self.explorer.view:get_winnr(nil, "Renderer:draw1") or 0)
local builder = Builder(self.explorer):build() local builder = Builder(self.explorer):build()
self:_draw(bufnr, builder.lines, builder.hl_range_args, builder.signs, builder.extmarks, builder.virtual_lines) self:_draw(bufnr, builder.lines, builder.hl_range_args, builder.signs, builder.extmarks, builder.virtual_lines)
if cursor and #builder.lines >= cursor[1] then if cursor and #builder.lines >= cursor[1] then
vim.api.nvim_win_set_cursor(view.get_winnr() or 0, cursor) vim.api.nvim_win_set_cursor(self.explorer.view:get_winnr(nil, "Renderer:draw2") or 0, cursor)
end end
view.grow_from_content() self.explorer.view:grow_from_content()
log.profile_end(profile) log.profile_end(profile)
events._dispatch_on_tree_rendered(bufnr, view.get_winnr()) events._dispatch_on_tree_rendered(bufnr, self.explorer.view:get_winnr(nil, "Renderer:draw3"))
end end
return Renderer return Renderer

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
if not node.explorer.view:is_root_folder_visible() then
i = i - 1
end
if node.explorer.live_filter.filter then
i = i + 1 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)
if not (M.is_windows or M.is_wsl) then
local _, error = vim.loop.fs_stat(path) local _, error = vim.loop.fs_stat(path)
return error == nil 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]
@@ -497,7 +543,10 @@ function M.focus_file(path)
local _, i = M.find_node(require("nvim-tree.core").get_explorer().nodes, function(node) local _, i = M.find_node(require("nvim-tree.core").get_explorer().nodes, function(node)
return node.absolute_path == path return node.absolute_path == path
end) end)
require("nvim-tree.view").set_cursor({ i + 1, 1 }) local explorer = require("nvim-tree.core").get_explorer()
if explorer then
explorer.view:set_cursor({ i + 1, 1 })
end
end end
---Focus node passed as parameter if visible, otherwise focus first visible parent. ---Focus node passed as parameter if visible, otherwise focus first visible parent.
@@ -517,7 +566,7 @@ function M.focus_node_or_parent(node)
end) end)
if found_node or node.parent == nil then if found_node or node.parent == nil then
require("nvim-tree.view").set_cursor({ i + 1, 1 }) explorer.view:set_cursor({ i + 1, 1 })
break break
end end
@@ -612,32 +661,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,644 +0,0 @@
local events = require("nvim-tree.events")
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 DEFAULT_MIN_WIDTH = 30
local DEFAULT_MAX_WIDTH = -1
local DEFAULT_PADDING = 1
M.View = {
adaptive_size = false,
centralize_selection = false,
tabpages = {},
cursors = {},
hide_root_folder = false,
live_filter = {
prev_focused_node = nil,
},
winopts = {
relativenumber = false,
number = false,
list = false,
foldenable = false,
winfixwidth = true,
winfixheight = true,
spell = false,
signcolumn = "yes",
foldmethod = "manual",
foldcolumn = "0",
cursorcolumn = false,
cursorline = true,
cursorlineopt = "both",
colorcolumn = "0",
wrap = false,
winhl = table.concat({
"EndOfBuffer:NvimTreeEndOfBuffer",
"CursorLine:NvimTreeCursorLine",
"CursorLineNr:NvimTreeCursorLineNr",
"LineNr:NvimTreeLineNr",
"WinSeparator:NvimTreeWinSeparator",
"StatusLine:NvimTreeStatusLine",
"StatusLineNC:NvimTreeStatuslineNC",
"SignColumn:NvimTreeSignColumn",
"Normal:NvimTreeNormal",
"NormalNC:NvimTreeNormalNC",
"NormalFloat:NvimTreeNormalFloat",
"FloatBorder:NvimTreeNormalFloatBorder",
}, ","),
},
}
-- The initial state of a tab
local tabinitial = {
-- The position of the cursor { line, column }
cursor = { 0, 0 },
-- The NvimTree window number
winnr = nil,
}
local BUFNR_PER_TAB = {}
---@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
---@return boolean
local function matches_bufnr(bufnr)
for _, b in pairs(BUFNR_PER_TAB) do
if b == bufnr then
return true
end
end
return false
end
local function wipe_rogue_buffer()
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
if not matches_bufnr(bufnr) and utils.is_nvim_tree_buf(bufnr) then
pcall(vim.api.nvim_buf_delete, bufnr, { force = true })
end
end
end
---@param bufnr integer|boolean|nil
local function create_buffer(bufnr)
wipe_rogue_buffer()
local tab = vim.api.nvim_get_current_tabpage()
BUFNR_PER_TAB[tab] = bufnr or vim.api.nvim_create_buf(false, false)
vim.api.nvim_buf_set_name(M.get_bufnr(), "NvimTree_" .. 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
---@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
---@param tabpage integer
local function setup_tabpage(tabpage)
local winnr = vim.api.nvim_get_current_win()
M.View.tabpages[tabpage] = vim.tbl_extend("force", M.View.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
-- save_tab_state saves any state that should be preserved across redraws.
---@param tabnr integer
local function save_tab_state(tabnr)
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)
end
---@param tabpage integer
local function close(tabpage)
if not M.is_visible({ tabpage = tabpage }) then
return
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
function M.close_this_tab_only()
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()
M.View.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
function M.abandon_current_window()
local tab = vim.api.nvim_get_current_tabpage()
BUFNR_PER_TAB[tab] = nil
if M.View.tabpages[tab] then
M.View.tabpages[tab].winnr = nil
end
end
function M.abandon_all_windows()
for tab, _ in pairs(vim.api.nvim_list_tabpages()) do
BUFNR_PER_TAB[tab] = nil
if M.View.tabpages[tab] then
M.View.tabpages[tab].winnr = nil
end
end
end
---@param opts table|nil
---@return boolean
function M.is_visible(opts)
if opts and opts.tabpage then
if M.View.tabpages[opts.tabpage] == nil then
return false
end
local winnr = M.View.tabpages[opts.tabpage].winnr
return winnr and vim.api.nvim_win_is_valid(winnr)
end
if opts and opts.any_tabpage then
for _, v in pairs(M.View.tabpages) do
if v.winnr and vim.api.nvim_win_is_valid(v.winnr) then
return true
end
end
return false
end
return M.get_winnr() ~= nil and vim.api.nvim_win_is_valid(M.get_winnr() or 0)
end
---@param opts table|nil
function M.set_cursor(opts)
if M.is_visible() then
pcall(vim.api.nvim_win_set_cursor, M.get_winnr(), opts)
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.
---@param opts ApiTreeWinIdOpts|nil
---@return number|nil winid unlike get_winnr(), this returns nil if the nvim-tree window is not visible
function M.winid(opts)
local tabpage = opts and opts.tabpage
if tabpage == 0 then
tabpage = vim.api.nvim_get_current_tabpage()
end
if M.is_visible({ tabpage = tabpage }) then
return M.get_winnr(tabpage)
else
return nil
end
end
--- Restores the state of a NvimTree window if it was initialized before.
function M.restore_tab_state()
local tabpage = vim.api.nvim_get_current_tabpage()
M.set_cursor(M.View.cursors[tabpage])
end
--- Returns the window number for nvim-tree within the tabpage specified
---@param tabpage number|nil (optional) the number of the chosen tabpage. Defaults to current tabpage.
---@return number|nil
function M.get_winnr(tabpage)
tabpage = tabpage or vim.api.nvim_get_current_tabpage()
local tabinfo = M.View.tabpages[tabpage]
if tabinfo and tabinfo.winnr and vim.api.nvim_win_is_valid(tabinfo.winnr) then
return tabinfo.winnr
end
end
--- Returns the current nvim tree bufnr
---@return number
function M.get_bufnr()
return BUFNR_PER_TAB[vim.api.nvim_get_current_tabpage()]
end
function M._prevent_buffer_override()
local view_winnr = M.get_winnr()
local view_bufnr = M.get_bufnr()
-- need to schedule to let the new buffer populate the window
-- because this event needs to be run on bufWipeout.
-- Otherwise the curwin/curbuf would match the view buffer and the view window.
vim.schedule(function()
local curwin = vim.api.nvim_get_current_win()
local curwinconfig = vim.api.nvim_win_get_config(curwin)
local curbuf = vim.api.nvim_win_get_buf(curwin)
local bufname = vim.api.nvim_buf_get_name(curbuf)
if not bufname:match("NvimTree") then
for i, tabpage in ipairs(M.View.tabpages) do
if tabpage.winnr == view_winnr then
M.View.tabpages[i] = nil
break
end
end
end
if curwin ~= view_winnr or bufname == "" or curbuf == view_bufnr then
return
end
-- patch to avoid the overriding window to be fixed in size
-- might need a better patch
vim.cmd("setlocal nowinfixwidth")
vim.cmd("setlocal nowinfixheight")
M.open({ focus_tree = false })
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
---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

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=$?