Compare commits

..

58 Commits

Author SHA1 Message Date
Alexander Courtis
ec5d41f182 Merge branch 'master' into 2826-remove-view-globals 2025-07-28 11:30:20 +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
Alexander Courtis
5737649ca9 Merge branch 'master' into 2826-remove-view-globals 2025-07-28 08:26:33 +10:00
Tomasz N
543ed3cac2 fix(picker): exclude full_name window id from the choice (#3165)
Problem: `full_name` window from is considered as usable by picker
Solution: exclude its ID (also true for nil values)

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-07-21 13:22:56 +10:00
Alexander Courtis
2a386fe567 ci: luals_version 3.13.9 -> 3.15.0 (#3167) 2025-07-21 13:22:07 +10:00
Alexander Courtis
b0b49552c9 docs: polish example decorator (#3160) 2025-06-21 10:57:48 +10:00
Alexander Courtis
fee19f491d Merge branch 'master' into 2826-remove-view-globals 2025-06-20 13:48:22 +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
0830436cca refactor(#2826): consistency check returns new 2025-06-20 13:11:21 +10:00
Alexander Courtis
51b269dc71 refactor(#2826): move global CURSORS to view member 2025-06-20 12:52:58 +10:00
Alexander Courtis
82cc80ffa4 Revert "refactor(#2826): move global CURSORS to view member"
This reverts commit d84dfad1c3.
2025-06-20 12:47:04 +10:00
Alexander Courtis
d84dfad1c3 refactor(#2826): move global CURSORS to view member 2025-06-20 12:46:30 +10:00
Alexander Courtis
4d6c42356a refactor(#2826): more consistency checking 2025-06-20 12:36:09 +10:00
Alexander Courtis
d72f85f524 refactor(#2826): more consistency checking 2025-06-20 12:33:16 +10:00
Alexander Courtis
e875f15b32 refactor(#2826): consistent naming of tabid 2025-06-20 12:01:24 +10:00
Alexander Courtis
83fdff7c4a refactor(#2826): globals.TABPAGES -> WINID_PER_TAB 2025-06-20 11:18:38 +10:00
Alexander Courtis
09ec00c085 refactor(#2826): get_winid returns new after consistency check 2025-06-20 10:01:12 +10:00
Alexander Courtis
3615c7dffe refactor(#2826): temporarily reuse BUFNR_PER_TAB in view constructor 2025-06-20 09:33:07 +10:00
Alexander Courtis
5fbd6745eb refactor(#2826): remove unused view member height 2025-06-20 09:19:33 +10:00
Alexander Courtis
f1e9d5165c refactor(#2826): remove unused view members centralize_selection and preserve_window_proportions 2025-06-20 09:12:29 +10:00
Alexander Courtis
414e576bc2 refactor(#2826): remove unnecessary view members float, hide_root_folder; use explorer opts 2025-06-20 09:03:19 +10:00
Alexander Courtis
de2ae0b06f refactor(#2826): consistent use of buffer registry, tidy, add todos 2025-06-20 08:32:40 +10:00
Alexander Courtis
d6cd465462 refactor(#2826): winnr->winid consistently 2025-06-19 16:46:27 +10:00
Alexander Courtis
b7e9789850 refactor(#2826): winnr->winid in view/globals, remove redundant get_winid and get_bufnr calls 2025-06-19 16:44:19 +10:00
Alexander Courtis
6e7ce8771b refactor(#2826): fuller error messages 2025-06-19 16:25:32 +10:00
Alexander Courtis
0a06f65bf0 refactor(#2826): move view to instanced window class (#3153)
* refactor(#2826): singleton View class, WIP

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

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

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

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

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

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

* refactor(#2826): singleton View class

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

* refactor(#2826): move autocmds to Explorer

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

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

* refactor(#2826): retain necessary view globals

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

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

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

* refactor(#2826): better enumerate_options function

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

* refactor(#2826): default lifecycle log off

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

* refactor(#2826): instrument View:get_winnr

* refactor(#2826): instrument View:setup_tabpage

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

* refactor(#2826): instrument View:get_bufnr

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

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

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

* vim: nvim-tree: more logging

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

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

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

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

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

* refactor: use snake case

* feat: handle api legacy calls and update signature

* refactor: make sure open is a boolean

* doc: collapse_all

* Revert "doc: collapse_all"

This reverts commit d243da3e14.

* add api.node.collapse

* add api.node.expand

* add api.node.expand

---------

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

* fix(#3143): doc

---------

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

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

* refactoring: move `extmarks_length` to utils.lua

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

---------

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

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

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

* add folder_arrow_padding

* update help docs

* refactor: renderer.icons.padding

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

* refactor: renderer.icons.padding

---------

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

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

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

---------

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

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

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

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

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

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

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

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

* chore: use luals for style check

* chore: use luals for style check

* Revert "chore: use luals for style check"

This reverts commit e5fde80fab.

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

* chore: use luals for style check

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-01 11:07:35 +11:00
github-actions[bot]
6709463b2d chore(master): release nvim-tree 1.11.0 (#3051)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-02-22 12:31:19 +11:00
Šimon Mandlík
b69914325a fix: window picker: hide fillchars: stl and stlnc (#3066)
fix: stl and stlnc fillchars are hidden in window picker

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-02-22 11:32:52 +11:00
Gabriel Crispino
3281f331f7 feat(#1984): add quit_on_open and focus opts to various api.node.open functions (#3054)
* feat: add quit_on_open opt to api.node.open.edit

* fix: fix missing @param annotation

* feat: add focus opt to api.node.open.edit

* fix: fix focus == false behaviour on api.node.open.tab command

* fix: add optional tabpage integer parameter to view.close

if tabpage is not nil, then the function closes the tabpage in this
specific tabpage

* fix: fix quit_on_open == true behaviour on api.node.open.tab command

* fix: add check to not use new opts for certain edit modes

* fix: add docs for new opts

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-02-22 11:09:49 +11:00
Hendrik Ziegler
80523101f0 fix: arithmetic on nil value error on first git project open (#3064)
* fixed error message when opening new git repo

* defensive nil + type check

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-02-10 08:06:02 +11:00
Alexander Courtis
70825f23db fix(#3059): test for presence of new 0.11 API vim.hl.range (#3060) 2025-02-03 15:42:22 +11:00
Alexander Courtis
d05881f65f docs: tidy readme contributing 2025-01-27 11:33:37 +11:00
Gabriel Crispino
fee1da8897 feat(#3037): add API node.buffer.delete, node.buffer.wipe (#3040)
* feat(mappings): add key map to close file buffer

* feat: implement Api.node.buffer.delete

* feat: implement Api.node.buffer.wipe

* refactor: add util fn for common delete ops on bufs

* fix: minor fixes

* refactor: fix lint issues

* fix: undo unintended ApiTreeToggleOpts change

* fix: change error message level to info

* fix: remove unused opts

* refactor: merge delete-buffer and wipe-buffer into single buffer file

* refactor: make wipe and delete fns take a node instead of a file path

* docs: update help with new API commands

* remove refactored utils.lua

* remove unused static setup

* tweak doc

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2025-01-25 12:47:34 +11:00
Alexander Courtis
db7403243d chore: resolve deprecated in 0.11 (#3053)
* chore: resolve deprecated in 0.11

* chore: resolve deprecated in 0.11

* chore: resolve deprecated in 0.11

* chore: resolve deprecated in 0.11

* chore: resolve deprecated in 0.11

* chore: resolve deprecated in 0.11
2025-01-24 11:57:18 +11:00
𝐍𝐆𝐏𝐎𝐍𝐆
fca0b67c0b fix(#3045): wipe scratch buffers for full name and show info popups (#3050) 2025-01-18 10:28:06 +11:00
61 changed files with 2016 additions and 1053 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.10.0" ".": "1.13.0"
} }

View File

@@ -1,5 +1,59 @@
# 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)
### Features
* **#1984:** add quit_on_open and focus opts to various api.node.open functions ([#3054](https://github.com/nvim-tree/nvim-tree.lua/issues/3054)) ([3281f33](https://github.com/nvim-tree/nvim-tree.lua/commit/3281f331f7f0bef13eb00fb2d5a9d28b2f6155a2))
* **#3037:** add API node.buffer.delete, node.buffer.wipe ([#3040](https://github.com/nvim-tree/nvim-tree.lua/issues/3040)) ([fee1da8](https://github.com/nvim-tree/nvim-tree.lua/commit/fee1da88972f5972a8296813f6c00d7598325ebd))
### Bug Fixes
* **#3045:** wipe scratch buffers for full name and show info popups ([#3050](https://github.com/nvim-tree/nvim-tree.lua/issues/3050)) ([fca0b67](https://github.com/nvim-tree/nvim-tree.lua/commit/fca0b67c0b5a31727fb33addc4d9c100736a2894))
* **#3059:** test for presence of new 0.11 API vim.hl.range ([#3060](https://github.com/nvim-tree/nvim-tree.lua/issues/3060)) ([70825f2](https://github.com/nvim-tree/nvim-tree.lua/commit/70825f23db61ecd900c4cfea169bffe931926a9d))
* arithmetic on nil value error on first git project open ([#3064](https://github.com/nvim-tree/nvim-tree.lua/issues/3064)) ([8052310](https://github.com/nvim-tree/nvim-tree.lua/commit/80523101f0ae48b7f1990e907b685a3d79776c01))
* stl and stlnc fillchars are hidden in window picker ([b699143](https://github.com/nvim-tree/nvim-tree.lua/commit/b69914325a945ee5157f0d21047210b42af5776e))
* window picker: hide fillchars: stl and stlnc ([#3066](https://github.com/nvim-tree/nvim-tree.lua/issues/3066)) ([b699143](https://github.com/nvim-tree/nvim-tree.lua/commit/b69914325a945ee5157f0d21047210b42af5776e))
## [1.10.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.9.0...nvim-tree-v1.10.0) (2025-01-13) ## [1.10.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.9.0...nvim-tree-v1.10.0) (2025-01-13)

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

@@ -162,13 +162,13 @@ nvim-tree exposes a public API. This is non breaking, with additions made as nec
See wiki [Recipes](https://github.com/nvim-tree/nvim-tree.lua/wiki/Recipes) and [Tips](https://github.com/nvim-tree/nvim-tree.lua/wiki/Tips) for ideas and inspiration. See wiki [Recipes](https://github.com/nvim-tree/nvim-tree.lua/wiki/Recipes) and [Tips](https://github.com/nvim-tree/nvim-tree.lua/wiki/Tips) for ideas and inspiration.
Please raise a [feature request](https://github.com/nvim-tree/nvim-tree.lua/issues/new?assignees=&labels=feature+request&template=feature_request.md&title=) if the API is insufficient for your needs. [Contributions](#Contributing) are always welcome. Please raise a [feature request](https://github.com/nvim-tree/nvim-tree.lua/issues/new?assignees=&labels=feature+request&template=feature_request.md&title=) if the API is insufficient for your needs. Contributions are always welcome, see below.
You may also subscribe to events that nvim-tree will dispatch in a variety of situations, see [:help nvim-tree-events](doc/nvim-tree-lua.txt) You may also subscribe to events that nvim-tree will dispatch in a variety of situations, see [:help nvim-tree-events](doc/nvim-tree-lua.txt)
## Contributing ## Contributing
PRs are always welcome. See [wiki](https://github.com/nvim-tree/nvim-tree.lua/wiki/Development) to get started. PRs are always welcome. See [CONTRIBUTING](CONTRIBUTING.md) and [wiki: Development](https://github.com/nvim-tree/nvim-tree.lua/wiki/Development) to get started.
See [bug](https://github.com/nvim-tree/nvim-tree.lua/issues?q=is%3Aissue+is%3Aopen+label%3Abug) and [PR Please](https://github.com/nvim-tree/nvim-tree.lua/issues?q=is%3Aopen+is%3Aissue+label%3A%22PR+please%22) issues if you are looking for some work to get you started. See [bug](https://github.com/nvim-tree/nvim-tree.lua/issues?q=is%3Aissue+is%3Aopen+label%3Abug) and [PR Please](https://github.com/nvim-tree/nvim-tree.lua/issues?q=is%3Aopen+is%3Aissue+label%3A%22PR+please%22) issues if you are looking for some work to get you started.

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()*
@@ -2007,43 +2021,99 @@ fs.print_clipboard() *nvim-tree-api.fs.print_clipboard()*
Parameters: ~ Parameters: ~
• {node} (Node|nil) file or folder • {node} (Node|nil) file or folder
node.open.edit({node}) *nvim-tree-api.node.open.edit()* node.open.edit({node}, {opts}) *nvim-tree-api.node.open.edit()*
File: open as per |nvim-tree.actions.open_file| File: open as per |nvim-tree.actions.open_file|
Folder: expand or collapse Folder: expand or collapse
Root: change directory up Root: change directory up
Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {quit_on_open} (boolean) quits the tree when opening the file
• {focus} (boolean) keep focus in the tree when opening the file
*nvim-tree-api.node.open.replace_tree_buffer()* *nvim-tree-api.node.open.replace_tree_buffer()*
node.open.replace_tree_buffer({node}) node.open.replace_tree_buffer({node})
|nvim-tree-api.node.edit()|, file will be opened in place: in the |nvim-tree-api.node.edit()|, file will be opened in place: in the
nvim-tree window. nvim-tree window.
*nvim-tree-api.node.open.no_window_picker()* *nvim-tree-api.node.open.no_window_picker()*
node.open.no_window_picker({node}) node.open.no_window_picker({node}, {opts})
|nvim-tree-api.node.edit()|, window picker will never be used as per |nvim-tree-api.node.edit()|, window picker will never be used as per
|nvim-tree.actions.open_file.window_picker.enable| `false` |nvim-tree.actions.open_file.window_picker.enable| `false`
node.open.vertical({node}) *nvim-tree-api.node.open.vertical()* Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {quit_on_open} (boolean) quits the tree when opening the file
• {focus} (boolean) keep focus in the tree when opening the file
node.open.vertical({node}, {opts}) *nvim-tree-api.node.open.vertical()*
|nvim-tree-api.node.edit()|, file will be opened in a new vertical split. |nvim-tree-api.node.edit()|, file will be opened in a new vertical split.
Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {quit_on_open} (boolean) quits the tree when opening the file
• {focus} (boolean) keep focus in the tree when opening the file
*nvim-tree-api.node.open.vertical_no_picker()* *nvim-tree-api.node.open.vertical_no_picker()*
node.open.vertical_no_picker({node}) node.open.vertical_no_picker({node}, {opts})
|nvim-tree-api.node.vertical()|, window picker will never be used as per |nvim-tree-api.node.vertical()|, window picker will never be used as per
|nvim-tree.actions.open_file.window_picker.enable| `false` |nvim-tree.actions.open_file.window_picker.enable| `false`
node.open.horizontal({node}) *nvim-tree-api.node.open.horizontal()* Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {quit_on_open} (boolean) quits the tree when opening the file
• {focus} (boolean) keep focus in the tree when opening the file
node.open.horizontal({node}, {opts}) *nvim-tree-api.node.open.horizontal()*
|nvim-tree-api.node.edit()|, file will be opened in a new horizontal split. |nvim-tree-api.node.edit()|, file will be opened in a new horizontal split.
Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {quit_on_open} (boolean) quits the tree when opening the file
• {focus} (boolean) keep focus in the tree when opening the file
*nvim-tree-api.node.open.horizontal_no_picker()* *nvim-tree-api.node.open.horizontal_no_picker()*
node.open.horizontal_no_picker({node}) node.open.horizontal_no_picker({node}, {opts})
|nvim-tree-api.node.horizontal()|, window picker will never be used as per |nvim-tree-api.node.horizontal()|, window picker will never be used as per
|nvim-tree.actions.open_file.window_picker.enable| `false` |nvim-tree.actions.open_file.window_picker.enable| `false`
Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {quit_on_open} (boolean) quits the tree when opening the file
• {focus} (boolean) keep focus in the tree when opening the file
*nvim-tree-api.node.open.toggle_group_empty()* *nvim-tree-api.node.open.toggle_group_empty()*
node.open.toggle_group_empty({node}) node.open.toggle_group_empty({node}, {opts})
Toggle |nvim-tree.renderer.group_empty| for a specific folder. Toggle |nvim-tree.renderer.group_empty| for a specific folder.
Does nothing on files. Does nothing on files.
Needs |nvim-tree.renderer.group_empty| set. Needs |nvim-tree.renderer.group_empty| set.
Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {quit_on_open} (boolean) quits the tree when opening the file
• {focus} (boolean) keep focus in the tree when opening the file
node.open.drop({node}) *nvim-tree-api.node.open.drop()* node.open.drop({node}) *nvim-tree-api.node.open.drop()*
Switch to window with selected file if it exists. Switch to window with selected file if it exists.
Open file otherwise. Open file otherwise.
@@ -2053,9 +2123,17 @@ node.open.drop({node}) *nvim-tree-api.node.open.drop()*
Folder: expand or collapse Folder: expand or collapse
Root: change directory up Root: change directory up
node.open.tab({node}) *nvim-tree-api.node.open.tab()* node.open.tab({node}, {opts}) *nvim-tree-api.node.open.tab()*
|nvim-tree-api.node.edit()|, file will be opened in a new tab. |nvim-tree-api.node.edit()|, file will be opened in a new tab.
Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {quit_on_open} (boolean) quits the tree when opening the file
• {focus} (boolean) keep focus in the tree when opening the file
*nvim-tree-api.node.open.tab_drop()* *nvim-tree-api.node.open.tab_drop()*
node.open.tab_drop({node}) node.open.tab_drop({node})
Switch to tab containing window with selected file if it exists. Switch to tab containing window with selected file if it exists.
@@ -2065,15 +2143,31 @@ node.open.tab_drop({node})
Folder: expand or collapse Folder: expand or collapse
Root: change directory up Root: change directory up
node.open.preview({node}) *nvim-tree-api.node.open.preview()* node.open.preview({node}, {opts}) *nvim-tree-api.node.open.preview()*
|nvim-tree-api.node.edit()|, file buffer will have |bufhidden| set to `delete`. |nvim-tree-api.node.edit()|, file buffer will have |bufhidden| set to `delete`.
Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {quit_on_open} (boolean) quits the tree when opening the file
• {focus} (boolean) keep focus in the tree when opening the file
*nvim-tree-api.node.open.preview_no_picker()* *nvim-tree-api.node.open.preview_no_picker()*
node.open.preview_no_picker({node}) node.open.preview_no_picker({node}, {opts})
|nvim-tree-api.node.edit()|, file buffer will have |bufhidden| set to `delete`. |nvim-tree-api.node.edit()|, file buffer will have |bufhidden| set to `delete`.
window picker will never be used as per window picker will never be used as per
|nvim-tree.actions.open_file.window_picker.enable| `false` |nvim-tree.actions.open_file.window_picker.enable| `false`
Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {quit_on_open} (boolean) quits the tree when opening the file
• {focus} (boolean) keep focus in the tree when opening the file
node.navigate.git.next({node}) *nvim-tree-api.node.navigate.git.next()* node.navigate.git.next({node}) *nvim-tree-api.node.navigate.git.next()*
Navigate to the next item showing git status. Navigate to the next item showing git status.
@@ -2164,6 +2258,45 @@ node.run.cmd({node}) *nvim-tree-api.node.run.cmd()*
node.run.system({node}) *nvim-tree-api.node.run.system()* node.run.system({node}) *nvim-tree-api.node.run.system()*
Execute |nvim-tree.system_open| Execute |nvim-tree.system_open|
node.buffer.delete({node}, {opts}) *nvim-tree-api.node.buffer.delete()*
Deletes node's related buffer, if one exists.
Executes |:bdelete| or |:bdelete|!
Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {force} (boolean) delete even if buffer is modified, default false
node.buffer.wipe({node}, {opts}) *nvim-tree-api.node.buffer.wipe()*
Wipes node's related buffer, if one exists.
Executes |:bwipe| or |:bwipe|!
Parameters: ~
• {node} (Node|nil) file or folder
• {opts} (table) optional parameters
Options: ~
• {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*
@@ -2418,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"))
@@ -2667,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.
@@ -2788,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",
@@ -2800,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
@@ -2832,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*
@@ -2868,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*
@@ -2917,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*
@@ -3004,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|
@@ -3073,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|
@@ -3124,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|
@@ -3178,6 +3348,10 @@ highlight group is not, hard linking as follows: >
|nvim-tree-api.marks.navigate.prev()| |nvim-tree-api.marks.navigate.prev()|
|nvim-tree-api.marks.navigate.select()| |nvim-tree-api.marks.navigate.select()|
|nvim-tree-api.marks.toggle()| |nvim-tree-api.marks.toggle()|
|nvim-tree-api.node.buffer.delete()|
|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 }, "nvim-tree.tab_enter") 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(nil, "nvim-tree.open_on_directory")
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,17 +209,20 @@ 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
on_attach = "default", on_attach = "default",
@@ -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()
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(nil, "finders/find-file.fn1") 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(nil, "finders/find-file.fn2") 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.opts.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.opts.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.opts.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

@@ -0,0 +1,58 @@
-- Copyright 2019 Yazdani Kiyan under MIT License
local notify = require("nvim-tree.notify")
local M = {}
---@param node Node
---@param opts ApiNodeDeleteWipeBufferOpts|nil
---@return nil
function M.delete(node, opts)
M.delete_buffer("delete", node.absolute_path, opts)
end
---@param node Node
---@param opts ApiNodeDeleteWipeBufferOpts|nil
---@return nil
function M.wipe(node, opts)
M.delete_buffer("wipe", node.absolute_path, opts)
end
---@alias ApiNodeDeleteWipeBufferMode '"delete"'|'"wipe"'
---@param mode ApiNodeDeleteWipeBufferMode
---@param filename string
---@param opts ApiNodeDeleteWipeBufferOpts|nil
---@return nil
function M.delete_buffer(mode, filename, opts)
if type(mode) ~= "string" then
mode = "delete"
end
local buf_fn = vim.cmd.bdelete
if mode == "wipe" then
buf_fn = vim.cmd.bwipe
end
opts = opts or { force = false }
local notify_node = notify.render_path(filename)
-- check if buffer for file at cursor exists and if it is loaded
local bufnr_at_filename = vim.fn.bufnr(filename)
if bufnr_at_filename == -1 or vim.fn.getbufinfo(bufnr_at_filename)[1].loaded == 0 then
notify.info("No loaded buffer coincides with " .. notify_node)
return
end
local force = opts.force
-- check if buffer is modified
local buf_modified = vim.fn.getbufinfo(bufnr_at_filename)[1].changed
if not force and buf_modified == 1 then
notify.error("Buffer for file " .. notify_node .. " is modified")
return
end
buf_fn({ filename, bang = force })
end
return M

View File

@@ -50,13 +50,16 @@ local function setup_window(node)
file_path = node.absolute_path, file_path = node.absolute_path,
} }
local bufnr = vim.api.nvim_create_buf(false, true) local bufnr = vim.api.nvim_create_buf(false, true)
vim.bo[bufnr].bufhidden = "wipe"
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
vim.api.nvim_win_set_buf(winnr, bufnr) vim.api.nvim_win_set_buf(winnr, bufnr)
end end
function M.close_popup() function M.close_popup()
if current_popup ~= nil then if current_popup ~= nil then
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

@@ -4,6 +4,7 @@ M.file_popup = require("nvim-tree.actions.node.file-popup")
M.open_file = require("nvim-tree.actions.node.open-file") M.open_file = require("nvim-tree.actions.node.open-file")
M.run_command = require("nvim-tree.actions.node.run-command") M.run_command = require("nvim-tree.actions.node.run-command")
M.system_open = require("nvim-tree.actions.node.system-open") M.system_open = require("nvim-tree.actions.node.system-open")
M.buffer = require("nvim-tree.actions.node.buffer")
function M.setup(opts) function M.setup(opts)
require("nvim-tree.actions.node.system-open").setup(opts) require("nvim-tree.actions.node.system-open").setup(opts)

View File

@@ -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_winid(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
@@ -80,6 +76,14 @@ local function pick_win_id()
local win_map = {} local win_map = {}
local laststatus = vim.o.laststatus local laststatus = vim.o.laststatus
vim.o.laststatus = 2 vim.o.laststatus = 2
local fillchars = vim.opt.fillchars:get()
local stl = fillchars.stl
local stlnc = fillchars.stlnc
fillchars.stl = nil
fillchars.stlnc = nil
vim.opt.fillchars = fillchars
fillchars.stl = stl
fillchars.stlnc = stlnc
local tabpage = vim.api.nvim_get_current_tabpage() local tabpage = vim.api.nvim_get_current_tabpage()
local win_ids = vim.api.nvim_tabpage_list_wins(tabpage) local win_ids = vim.api.nvim_tabpage_list_wins(tabpage)
@@ -179,6 +183,7 @@ local function pick_win_id()
end end
vim.o.laststatus = laststatus vim.o.laststatus = laststatus
vim.opt.fillchars = fillchars
if not vim.tbl_contains(vim.split(M.window_picker.chars, ""), resp) then if not vim.tbl_contains(vim.split(M.window_picker.chars, ""), resp) then
return return
@@ -189,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())
@@ -199,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())
@@ -209,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())
@@ -230,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
@@ -270,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
@@ -292,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
@@ -324,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.opts.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
@@ -364,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
@@ -375,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
@@ -409,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(nil, "tree/find-file.fn") 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 = {}
@@ -70,23 +71,38 @@ local function gen_iterator()
end end
end end
---Expand the directory node or the root ---@param node Node?
---@param node Node local function expand_node(node)
function M.fn(node) if not node then
local explorer = core.get_explorer()
local parent = node:as(DirectoryNode) or explorer
if not parent then
return return
end end
if gen_iterator()(parent) then if gen_iterator()(node) then
notify.warn("expansion iteration was halted after " .. M.MAX_FOLDER_DISCOVERY .. " discovered folders") notify.warn("expansion iteration was halted after " .. M.MAX_FOLDER_DISCOVERY .. " discovered folders")
end end
local explorer = core.get_explorer()
if explorer then if explorer then
explorer.renderer:draw() explorer.renderer:draw()
end end
end end
---Expand the directory node or the root
---@param node Node
function M.all(node)
expand_node(node and node:as(DirectoryNode) or core.get_explorer())
end
---Expand the directory node or parent node
---@param node Node
function M.node(node)
if not node then
return
end
expand_node(node:is(FileNode) and node.parent or node:as(DirectoryNode))
end
function M.setup(opts) function M.setup(opts)
M.MAX_FOLDER_DISCOVERY = opts.actions.expand_all.max_folder_discovery M.MAX_FOLDER_DISCOVERY = opts.actions.expand_all.max_folder_discovery
M.EXCLUDE = to_lookup_table(opts.actions.expand_all.exclude) M.EXCLUDE = to_lookup_table(opts.actions.expand_all.exclude)

View File

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

View File

@@ -1,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(nil, "open.fn") 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(nil, "toggle.fn") 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")
@@ -24,6 +23,7 @@ local Api = {
}, },
run = {}, run = {},
open = {}, open = {},
buffer = {},
}, },
events = {}, events = {},
marks = { marks = {
@@ -140,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
@@ -181,8 +181,12 @@ Api.tree.get_nodes = wrap_explorer("get_nodes")
Api.tree.find_file = wrap(actions.tree.find_file.fn) Api.tree.find_file = wrap(actions.tree.find_file.fn)
Api.tree.search_node = wrap(actions.finders.search_node.fn) Api.tree.search_node = wrap(actions.finders.search_node.fn)
Api.tree.collapse_all = wrap(actions.tree.modifiers.collapse_all.fn)
Api.tree.expand_all = wrap_node(actions.tree.modifiers.expand_all.fn) ---@class ApiCollapseOpts
---@field keep_buffers boolean|nil default false
Api.tree.collapse_all = wrap(actions.tree.modifiers.collapse.all)
Api.tree.expand_all = wrap_node(actions.tree.modifiers.expand.all)
Api.tree.toggle_enable_filters = wrap_explorer_member("filters", "toggle") Api.tree.toggle_enable_filters = wrap_explorer_member("filters", "toggle")
Api.tree.toggle_gitignore_filter = wrap_explorer_member_args("filters", "toggle", "git_ignored") Api.tree.toggle_gitignore_filter = wrap_explorer_member_args("filters", "toggle", "git_ignored")
Api.tree.toggle_git_clean_filter = wrap_explorer_member_args("filters", "toggle", "git_clean") Api.tree.toggle_git_clean_filter = wrap_explorer_member_args("filters", "toggle", "git_clean")
@@ -197,12 +201,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)
@@ -221,21 +225,52 @@ Api.fs.copy.absolute_path = wrap_node(wrap_explorer_member("clipboard", "copy_ab
Api.fs.copy.filename = wrap_node(wrap_explorer_member("clipboard", "copy_filename")) Api.fs.copy.filename = wrap_node(wrap_explorer_member("clipboard", "copy_filename"))
Api.fs.copy.basename = wrap_node(wrap_explorer_member("clipboard", "copy_basename")) Api.fs.copy.basename = wrap_node(wrap_explorer_member("clipboard", "copy_basename"))
Api.fs.copy.relative_path = wrap_node(wrap_explorer_member("clipboard", "copy_path")) Api.fs.copy.relative_path = wrap_node(wrap_explorer_member("clipboard", "copy_path"))
---
---@class NodeEditOpts
---@field quit_on_open boolean|nil default false
---@field focus boolean|nil default true
---@param mode string ---@param mode string
---@param node Node ---@param node Node
local function edit(mode, node) ---@param edit_opts NodeEditOpts?
local function edit(mode, node, edit_opts)
local file_link = node:as(FileLinkNode) local file_link = node:as(FileLinkNode)
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 explorer = core.get_explorer()
actions.node.open_file.fn(mode, path) actions.node.open_file.fn(mode, path)
edit_opts = edit_opts or {}
local mode_unsupported_quit_on_open = mode == "drop" or mode == "tab_drop" or mode == "edit_in_place"
if not mode_unsupported_quit_on_open and edit_opts.quit_on_open then
if explorer then
explorer.view:close(cur_tabpage, "api.edit " .. mode)
end
end
local mode_unsupported_focus = mode == "drop" or mode == "tab_drop" or mode == "edit_in_place"
local focus = edit_opts.focus == nil or edit_opts.focus == true
if not mode_unsupported_focus and not focus then
-- if mode == "tabnew" a new tab will be opened and we need to focus back to the previous tab
if mode == "tabnew" then
vim.cmd(":tabprev")
end
if explorer then
explorer.view:focus()
end
end
end end
---@param mode string ---@param mode string
---@param toggle_group boolean? ---@param toggle_group boolean?
---@return fun(node: Node) ---@return fun(node: Node, edit_opts: NodeEditOpts?)
local function open_or_expand_or_dir_up(mode, toggle_group) local function open_or_expand_or_dir_up(mode, toggle_group)
---@param node Node ---@param node Node
return function(node) ---@param edit_opts NodeEditOpts?
return function(node, edit_opts)
local root = node:as(RootNode) local root = node:as(RootNode)
local dir = node:as(DirectoryNode) local dir = node:as(DirectoryNode)
@@ -244,7 +279,7 @@ local function open_or_expand_or_dir_up(mode, toggle_group)
elseif dir then elseif dir then
dir:expand_or_collapse(toggle_group) dir:expand_or_collapse(toggle_group)
elseif not toggle_group then elseif not toggle_group then
edit(mode, node) edit(mode, node, edit_opts)
end end
end end
end end
@@ -286,6 +321,19 @@ 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
---@field force boolean|nil default false
Api.node.buffer.delete = wrap_node(function(node, opts)
actions.node.buffer.delete(node, opts)
end)
Api.node.buffer.wipe = wrap_node(function(node, opts)
actions.node.buffer.wipe(node, opts)
end)
Api.git.reload = wrap_explorer("reload_git") Api.git.reload = wrap_explorer("reload_git")
Api.events.subscribe = events.subscribe Api.events.subscribe = events.subscribe

View File

@@ -5,6 +5,8 @@ local Class = require("nvim-tree.classic")
-- others with name and links less than this arbitrary value are short -- others with name and links less than this arbitrary value are short
local SHORT_LEN = 50 local SHORT_LEN = 50
local namespace_hi_test_id = vim.api.nvim_create_namespace("NvimTreeHiTest")
---@class (exact) HighlightDisplay: Class for :NvimTreeHiTest ---@class (exact) HighlightDisplay: Class for :NvimTreeHiTest
---@field group string nvim-tree highlight group name ---@field group string nvim-tree highlight group name
---@field links string link chain to a concretely defined group ---@field links string link chain to a concretely defined group
@@ -52,7 +54,12 @@ function HighlightDisplay:render(bufnr, fmt, l)
local text = string.format(fmt, self.group, self.links, self.def) local text = string.format(fmt, self.group, self.links, self.def)
vim.api.nvim_buf_set_lines(bufnr, l, -1, true, { text }) vim.api.nvim_buf_set_lines(bufnr, l, -1, true, { text })
vim.api.nvim_buf_add_highlight(bufnr, -1, self.group, l, 0, #self.group)
if vim.fn.has("nvim-0.11") == 1 and vim.hl and vim.hl.range then
vim.hl.range(bufnr, namespace_hi_test_id, self.group, { l, 0 }, { l, #self.group, }, {})
else
vim.api.nvim_buf_add_highlight(bufnr, -1, self.group, l, 0, #self.group) ---@diagnostic disable-line: deprecated
end
return l + 1 return l + 1
end end

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({
@@ -389,9 +457,9 @@ function Explorer:populate_children(handle, cwd, node, project, parent)
nodes_by_path[child.absolute_path] = true nodes_by_path[child.absolute_path] = true
child:update_git_status(node_ignored, project) child:update_git_status(node_ignored, project)
end end
else elseif node.hidden_stats then
for reason, value in pairs(FILTER_REASON) do for reason, value in pairs(FILTER_REASON) do
if filter_reason == value then if filter_reason == value and type(node.hidden_stats[reason]) == "number" then
node.hidden_stats[reason] = node.hidden_stats[reason] + 1 node.hidden_stats[reason] = node.hidden_stats[reason] + 1
end end
end end
@@ -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(nil, "Explorer:reload_explorer") 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_winid(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.opts.view.float.enable and self.explorer.opts.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.opts.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,811 @@
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")
---Window and buffer related settings and operations
---@class (exact) View: Class
---@field live_filter table
---@field side string
---@field private explorer Explorer
---@field private adaptive_size boolean
---@field private winopts table
---@field private initial_width integer
---@field private width (fun():integer)|integer|string
---@field private max_width integer
---@field private padding integer
-- TODO multi-instance remove or replace with single member
---@field private bufnr_by_tabid table<integer, integer>
-- TODO multi-instance change to single member
---@field private cursors_by_tabid table<integer, integer[]> as per vim.api.nvim_win_get_cursor
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.side = (self.explorer.opts.view.side == "right") and "right" or "left"
self.live_filter = { prev_focused_node = nil, }
self.bufnr_by_tabid = {}
self.cursors_by_tabid = {}
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()
-- TODO multi-instance remove this; delete buffers rather than retaining them
local tabid = vim.api.nvim_get_current_tabpage()
self.bufnr_by_tabid[tabid] = globals.BUFNR_BY_TABID[tabid]
end
function View:destroy()
self.explorer:log_destroy("View")
end
---@type { name: string, value: any }[]
local BUFFER_OPTIONS = {
{ name = "bufhidden", value = "wipe" },
{ name = "buflisted", value = false },
{ name = "buftype", value = "nofile" },
{ name = "filetype", value = "NvimTree" },
{ name = "modifiable", value = false },
{ name = "swapfile", value = false },
}
-- TODO multi-instance remove this; delete buffers rather than retaining them
---@private
---@param bufnr integer
---@return boolean
function View:matches_bufnr(bufnr)
for _, b in pairs(globals.BUFNR_BY_TABID) do
if b == bufnr then
return true
end
end
return false
end
-- TODO multi-instance remove this; delete buffers rather than retaining them
---@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 tabid = vim.api.nvim_get_current_tabpage()
bufnr = bufnr or vim.api.nvim_create_buf(false, false)
-- set both bufnr registries
globals.BUFNR_BY_TABID[tabid] = bufnr
self.bufnr_by_tabid[tabid] = bufnr
vim.api.nvim_buf_set_name(bufnr, "NvimTree_" .. tabid)
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(bufnr)
events._dispatch_tree_attached_post(bufnr)
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",
}
---@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.explorer.opts.view.float.open_win_config) == "function" then
return self.explorer.opts.view.float.open_win_config()
else
return self.explorer.opts.view.float.open_win_config
end
end
---@private
function View:open_window()
if self.explorer.opts.view.float.enable then
vim.api.nvim_open_win(0, true, self:open_win_config())
else
vim.api.nvim_command("vsp")
self:reposition_window()
end
globals.WINID_BY_TABID[vim.api.nvim_get_current_tabpage()] = vim.api.nvim_get_current_win()
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 any state that should be preserved on reopening
---@private
---@param tabid integer
function View:save_state(tabid)
tabid = tabid or vim.api.nvim_get_current_tabpage()
self.cursors_by_tabid[tabid] = vim.api.nvim_win_get_cursor(self:get_winid(tabid, "View:save_tab_state") or 0)
end
---@private
---@param tabid integer
function View:close_internal(tabid)
--- BEGIN multi-instance FF
if self.explorer.opts.experimental.multi_instance then
log.line("dev", "View:close_internal(t%s)", tabid)
end
--- END multi-instance FF
if not self:is_visible({ tabpage = tabid }, "View:close_internal") then
return
end
self:save_state(tabid)
switch_buf_if_last_buf()
local tree_win = self:get_winid(tabid, "View:close_internal")
local current_win = vim.api.nvim_get_current_win()
for _, win in pairs(vim.api.nvim_tabpage_list_wins(tabid)) 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
--- BEGIN multi-instance FF
if self.explorer.opts.experimental.multi_instance then
log.line("dev", "View:close_internal(t%s) w%s", tabid, tree_win)
end
--- END multi-instance FF
---
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()
--- BEGIN multi-instance FF
if self.explorer.opts.experimental.multi_instance then
log.line("dev", "View:close_this_tab_only()")
end
--- END multi-instance FF
self:close_internal(vim.api.nvim_get_current_tabpage())
end
-- TODO this is broken at 1.13.0 - current tab does not close when tab.sync.close is set
function View:close_all_tabs()
log.line("dev", "View:close_all_tabs() globals.WINID_BY_TABID=%s", vim.inspect(globals.WINID_BY_TABID))
for tabid, _ in pairs(globals.WINID_BY_TABID) do
--- BEGIN multi-instance FF
if self.explorer.opts.experimental.multi_instance then
log.line("dev", "View:close_all_tabs()")
end
--- END multi-instance FF
self:close_internal(tabid)
end
end
---@param tabid integer|nil
---@param callsite string
function View:close(tabid, callsite)
--- BEGIN multi-instance FF
if self.explorer.opts.experimental.multi_instance then
log.line("dev", "View:close(t%s, %s)", tabid, callsite)
end
--- END multi-instance FF
if self.explorer.opts.tab.sync.close then
self:close_all_tabs()
elseif tabid then
self:close_internal(tabid)
else
self:close_this_tab_only()
end
end
---@param options table|nil
function View:open(options)
if self:is_visible(nil, "View:open") 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_winid(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.explorer.opts.view.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 self.explorer.opts.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
end
if not self:is_visible(nil, "View:resize") then
return
end
local winid = self:get_winid(nil, "View:resize") or 0
local new_size = self:get_width()
if new_size ~= vim.api.nvim_win_get_width(winid) then
vim.api.nvim_win_set_width(winid, new_size)
if not self.explorer.opts.view.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
function View:set_current_win()
local current_tab = vim.api.nvim_get_current_tabpage()
globals.WINID_BY_TABID[current_tab] = vim.api.nvim_get_current_win()
end
---@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
---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())
globals.WINID_BY_TABID[vim.api.nvim_get_current_tabpage()] = vim.api.nvim_get_current_win()
self:set_current_win()
self:set_window_options_and_buffer()
if opts.resize then
self:reposition_window()
self:resize()
end
events._dispatch_on_tree_open()
end
function View:abandon_current_window()
local tab = vim.api.nvim_get_current_tabpage()
--- BEGIN multi-instance FF
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.WINID_BY_TABID[tab],
globals.BUFNR_BY_TABID[tab],
self.bufnr_by_tabid[tab],
(globals.BUFNR_BY_TABID[tab] == self.bufnr_by_tabid[tab]) and "" or "MISMATCH")
end
--- END multi-instance FF
-- reset both bufnr registries
globals.BUFNR_BY_TABID[tab] = nil
self.bufnr_by_tabid[tab] = nil
globals.WINID_BY_TABID[tab] = nil
end
function View:abandon_all_windows()
for tab, _ in pairs(vim.api.nvim_list_tabpages()) do
globals.BUFNR_BY_TABID[tab] = nil
globals.WINID_BY_TABID[tab] = nil
end
end
---@param opts table|nil
---@param callsite string
---@return boolean
function View:is_visible(opts, callsite)
local msg
--- BEGIN multi-instance FF
if self.explorer.opts.experimental.multi_instance then
msg = string.format("View:is_visible(%s, %-20.20s)", vim.inspect(opts, { newline = "" }), callsite)
end
--- END multi-instance FF
if opts and opts.tabpage then
--- BEGIN multi-instance FF
if self.explorer.opts.experimental.multi_instance then
local winid = self:winid(opts.tabpage)
local winid_by_tabid = opts.tabpage and globals.WINID_BY_TABID[opts.tabpage] or nil
msg = string.format("%s globals.WINID_BY_TABID[%s]=w%s view.winid(%s)=w%s",
msg,
opts.tabpage, winid_by_tabid,
opts.tabpage, winid
)
if winid ~= winid_by_tabid then
msg = string.format("%s MISMATCH", msg)
notify.error(msg)
end
log.line("dev", "%s", msg)
return winid and vim.api.nvim_win_is_valid(winid) or false
--- END multi-instance FF
else
local winid = globals.WINID_BY_TABID[opts.tabpage]
return winid and vim.api.nvim_win_is_valid(winid)
end
end
if opts and opts.any_tabpage then
for tabid, winid_by_tabid in pairs(globals.WINID_BY_TABID) do
--- BEGIN multi-instance FF
if self.explorer.opts.experimental.multi_instance then
local winid = self:winid(tabid)
msg = string.format("%s globals.WINID_BY_TABID[%s]=w%s view.winid(%s)=w%s",
msg,
tabid, winid_by_tabid,
tabid, winid
)
if winid ~= winid_by_tabid then
msg = string.format("%s MISMATCH", msg)
notify.error(msg)
end
log.line("dev", "%s", msg)
if winid and vim.api.nvim_win_is_valid(winid) then
return true
end
--- END multi-instance FF
else
if winid_by_tabid and vim.api.nvim_win_is_valid(winid_by_tabid) then
return true
end
end
end
return false
end
local winid = self:get_winid(nil, "View:is_visible")
return winid ~= nil and vim.api.nvim_win_is_valid(winid or 0)
end
---@param opts table|nil
function View:set_cursor(opts)
if self:is_visible(nil, "View:set_cursor") then
pcall(vim.api.nvim_win_set_cursor, self:get_winid(nil, "View:set_cursor"), opts)
end
end
---@param winid number|nil
---@param open_if_closed boolean|nil
function View:focus(winid, open_if_closed)
local wid = winid or self:get_winid(nil, "View:focus1")
if vim.api.nvim_win_get_tabpage(wid or 0) ~= vim.api.nvim_win_get_tabpage(0) then
self:close(nil, "View:focus")
self:open()
wid = self:get_winid(nil, "View:focus2")
elseif open_if_closed and not self:is_visible(nil, "View:focus") then
self:open()
end
if wid then
vim.api.nvim_set_current_win(wid)
end
end
--- Retrieve the winid of the open tree.
---@param opts ApiTreeWinIdOpts|nil
---@return number|nil winid unlike get_winid(), 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 }, "View:api_winid") then
return self:get_winid(tabpage, "View:winid")
else
return nil
end
end
---restore any state from last close
function View:restore_state()
self:set_cursor(self.cursors_by_tabid[vim.api.nvim_get_current_tabpage()])
end
--- winid containing the buffer
---@param tabid number|nil (optional) the number of the chosen tabpage. Defaults to current tabpage.
---@return integer? winid
function View:winid(tabid)
local bufnr = self.bufnr_by_tabid[tabid]
if bufnr then
for _, winid in pairs(vim.api.nvim_tabpage_list_wins(tabid or 0)) do
if vim.api.nvim_win_get_buf(winid) == bufnr then
return winid
end
end
end
end
--- Returns the window number for nvim-tree within the tabpage specified
---@param tabid number|nil (optional) the number of the chosen tabpage. Defaults to current tabpage.
---@param callsite string
---@return number|nil
function View:get_winid(tabid, callsite)
local tabid_param = tabid
tabid = tabid or vim.api.nvim_get_current_tabpage()
local global_winid = nil
--- BEGIN multi-instance FF
if self.explorer.opts.experimental.multi_instance then
local msg_fault = ""
if not globals.WINID_BY_TABID[tabid] then
msg_fault = "no WINID_BY_TABID"
elseif not vim.api.nvim_win_is_valid(globals.WINID_BY_TABID[tabid]) then
msg_fault = string.format("invalid globals.WINID_BY_TABID[tabid] %d", globals.WINID_BY_TABID[tabid])
else
global_winid = globals.WINID_BY_TABID[tabid]
end
local winid = self:winid(tabid)
if winid ~= global_winid then
msg_fault = "MISMATCH"
end
local msg = string.format("View:get_winid(%3s, %-20.20s) globals.WINID_BY_TABID[%s]=w%s view.winid(%s)=w%s %s",
tabid_param,
callsite,
tabid, global_winid,
tabid, winid,
msg_fault
)
log.line("dev", "%s", msg)
if winid ~= global_winid then
notify.error(msg)
end
return winid
end
--- END multi-instance FF
-- legacy codepath
if global_winid and vim.api.nvim_win_is_valid(global_winid) then
return global_winid
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()
--- BEGIN multi-instance FF
if self.explorer.opts.experimental.multi_instance then
local msg = string.format("View:get_bufnr(%-20.20s) globals.BUFNR_BY_TABID[%s]=b%s view.bufnr_by_tab[%s]=b%s %s",
callsite,
tab, globals.BUFNR_BY_TABID[tab],
tab, self.bufnr_by_tabid[tab],
(globals.BUFNR_BY_TABID[tab] == self.bufnr_by_tabid[tab]) and "" or "MISMATCH"
)
if globals.BUFNR_BY_TABID[tab] ~= self.bufnr_by_tabid[tab] then
notify.error(msg)
end
log.line("dev", msg)
return self.bufnr_by_tabid[tab]
end
--- END multi-instance FF
return globals.BUFNR_BY_TABID[tab]
end
function View:prevent_buffer_override()
local view_winid = self:get_winid(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, winid in ipairs(globals.WINID_BY_TABID) do
if winid == view_winid then
globals.WINID_BY_TABID[i] = nil
break
end
end
end
if curwin ~= view_winid 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 self.explorer.opts.renderer.root_folder_label ~= false
end
-- used on ColorScheme event
function View:reset_winhl()
local winid = self:get_winid(nil, "View:reset_winhl")
if winid and vim.api.nvim_win_is_valid(winid) then
vim.wo[winid].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
-- These are needed as they are populated only by the user, not configuration
local DEFAULT_MIN_WIDTH = 30
local DEFAULT_MAX_WIDTH = -1
local DEFAULT_PADDING = 1
---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
WINID_BY_TABID = {},
BUFNR_BY_TABID = {},
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,11 +6,7 @@ local PAT_MOUSE = "^<.*Mouse"
local PAT_CTRL = "^<C%-" local PAT_CTRL = "^<C%-"
local PAT_SPECIAL = "^<.+" local PAT_SPECIAL = "^<.+"
local WIN_HL = table.concat({ local namespace_help_id = vim.api.nvim_create_namespace("NvimTreeHelp")
"NormalFloat:NvimTreeNormalFloat",
"WinSeparator:NvimTreeWinSeparator",
"CursorLine:NvimTreeCursorLine",
}, ",")
local M = { local M = {
config = {}, config = {},
@@ -82,8 +79,8 @@ end
--- Compute all lines for the buffer --- Compute all lines for the buffer
---@param map table keymap.get_keymap ---@param map table keymap.get_keymap
---@return table strings of text ---@return string[] lines of text
---@return table arrays of arguments 3-6 for nvim_buf_add_highlight() ---@return HighlightRangeArgs[] hl_range_args for lines
---@return number maximum length of text ---@return number maximum length of text
local function compute(map) local function compute(map)
local head_lhs = "nvim-tree mappings" local head_lhs = "nvim-tree mappings"
@@ -130,10 +127,10 @@ local function compute(map)
local width = #lines[1] local width = #lines[1]
-- header highlight, assume one character keys -- header highlight, assume one character keys
local hl = { local hl_range_args = {
{ "NvimTreeFolderName", 0, 0, #head_lhs }, { higroup = "NvimTreeFolderName", start = { 0, 0, }, finish = { 0, #head_lhs, }, },
{ "NvimTreeFolderName", 0, width - 1, width }, { higroup = "NvimTreeFolderName", start = { 0, width - 1, }, finish = { 0, width, }, },
{ "NvimTreeFolderName", 1, width - 1, width }, { higroup = "NvimTreeFolderName", start = { 1, width - 1, }, finish = { 1, width, }, },
} }
-- mappings, left padded 1 -- mappings, left padded 1
@@ -145,10 +142,10 @@ local function compute(map)
width = math.max(#line, width) width = math.max(#line, width)
-- highlight lhs -- highlight lhs
table.insert(hl, { "NvimTreeFolderName", i + 1, 1, #l.lhs + 1 }) table.insert(hl_range_args, { higroup = "NvimTreeFolderName", start = { i + 1, 1, }, finish = { i + 1, #l.lhs + 1, }, })
end end
return lines, hl, width return lines, hl_range_args, width
end end
--- close the window and delete the buffer, if they exist --- close the window and delete the buffer, if they exist
@@ -172,7 +169,7 @@ local function open()
local map = keymap.get_keymap() local map = keymap.get_keymap()
-- text and highlight -- text and highlight
local lines, hl, width = compute(map) local lines, hl_range_args, width = compute(map)
-- create the buffer -- create the buffer
M.bufnr = vim.api.nvim_create_buf(false, true) M.bufnr = vim.api.nvim_create_buf(false, true)
@@ -187,8 +184,12 @@ local function open()
end end
-- highlight it -- highlight it
for _, h in ipairs(hl) do for _, args in ipairs(hl_range_args) do
vim.api.nvim_buf_add_highlight(M.bufnr, -1, h[1], h[2], h[3], h[4]) if vim.fn.has("nvim-0.11") == 1 and vim.hl and vim.hl.range then
vim.hl.range(M.bufnr, namespace_help_id, args.higroup, args.start, args.finish, {})
else
vim.api.nvim_buf_add_highlight(M.bufnr, -1, args.higroup, args.start[1], args.start[2], args.finish[2]) ---@diagnostic disable-line: deprecated
end
end end
-- open a very restricted window -- open a very restricted window
@@ -204,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_winid(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_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 WINID_BY_TABID winid and BUFNR_BY_TABID 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 tabids = vim.api.nvim_list_tabpages()
local tabid_cur = vim.api.nvim_get_current_tabpage()
local bufnr_by_tabid = vim.deepcopy(globals.BUFNR_BY_TABID)
local winid_by_tabid = vim.deepcopy(globals.WINID_BY_TABID)
local tl = "%#TabLine#"
for i, tabid in ipairs(tabids) do
-- click to select
tl = tl .. "%" .. i .. "T"
-- style
if tabid == tabid_cur then
tl = tl .. "%#StatusLine#|"
else
tl = tl .. "|%#TabLine#"
end
-- tab_id itself
tl = tl .. " t" .. tabid
-- winid, if present
local tp = globals.WINID_BY_TABID[tabid]
if tp then
tl = tl .. " w" .. (tp or "nil")
else
tl = tl .. " "
end
-- bufnr, if present
local bpt = globals.BUFNR_BY_TABID[tabid]
if bpt then
tl = tl .. " b" .. bpt
else
tl = tl .. " "
end
tl = tl .. " "
-- remove actively mapped
bufnr_by_tabid[tabid] = nil
winid_by_tabid[tabid] = nil
end
-- close last and reset
tl = tl .. "|%#CursorLine#%T"
-- collect orphans
local orphans = {}
for tab_id, bufnr in pairs(bufnr_by_tabid) do
orphans[tab_id] = orphans[tab_id] or {}
orphans[tab_id].bufnr = bufnr
end
for tab_id, tp in pairs(winid_by_tabid) do
orphans[tab_id] = orphans[tab_id] or {}
orphans[tab_id].winid = tp
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 winid
if orphan.winid then
tl = tl .. " w" .. (orphan.winid 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")
@@ -33,15 +32,9 @@ local BUILTIN_DECORATORS = {
Cut = CutDecorator, Cut = CutDecorator,
} }
---@class (exact) AddHighlightArgs
---@field group string[]
---@field line number
---@field col_start number
---@field col_end number
---@class (exact) Builder ---@class (exact) Builder
---@field lines string[] includes icons etc. ---@field lines string[] includes icons etc.
---@field hl_args AddHighlightArgs[] line highlights ---@field hl_range_args HighlightRangeArgs[] highlights for lines
---@field signs string[] line signs ---@field signs string[] line signs
---@field extmarks table[] extra marks for right icon placement ---@field extmarks table[] extra marks for right icon placement
---@field virtual_lines table[] virtual lines for hidden count display ---@field virtual_lines table[] virtual lines for hidden count display
@@ -67,7 +60,7 @@ function Builder:new(args)
self.explorer = args.explorer self.explorer = args.explorer
self.index = 0 self.index = 0
self.depth = 0 self.depth = 0
self.hl_args = {} self.hl_range_args = {}
self.combined_groups = {} self.combined_groups = {}
self.lines = {} self.lines = {}
self.markers = {} self.markers = {}
@@ -106,7 +99,9 @@ end
---@param start number ---@param start number
---@param end_ number|nil ---@param end_ number|nil
function Builder:insert_highlight(groups, start, end_) function Builder:insert_highlight(groups, start, end_)
table.insert(self.hl_args, { groups, self.index, start, end_ or -1 }) for _, higroup in ipairs(groups) do
table.insert(self.hl_range_args, { higroup = higroup, start = { self.index, start, }, finish = { self.index, end_ or -1, } })
end
end end
---@private ---@private
@@ -139,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
@@ -383,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 })
@@ -79,9 +87,18 @@ local function show()
---@type vim.api.keyset.extmark_details ---@type vim.api.keyset.extmark_details
local details = extmark[4] local details = extmark[4]
vim.api.nvim_buf_add_highlight(0, ns_id, details.hl_group, 0, col, details.end_col) if type(details) == "table" then
if vim.fn.has("nvim-0.11") == 1 and vim.hl and vim.hl.range then
vim.hl.range(0, ns_id, details.hl_group, { 0, col }, { 0, details.end_col, }, {})
else
vim.api.nvim_buf_add_highlight(0, ns_id, details.hl_group, 0, col, details.end_col) ---@diagnostic disable-line: deprecated
end
end
end
vim.cmd([[ setlocal nowrap noswapfile nobuflisted buftype=nofile bufhidden=wipe ]])
if opts.view.cursorline then
vim.cmd([[ setlocal cursorline cursorlineopt=both ]])
end end
vim.cmd([[ setlocal nowrap cursorline noswapfile nobuflisted buftype=nofile bufhidden=hide ]])
end) end)
end end
@@ -107,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")
@@ -11,6 +10,8 @@ local namespace_highlights_id = vim.api.nvim_create_namespace("NvimTreeHighlight
local namespace_extmarks_id = vim.api.nvim_create_namespace("NvimTreeExtmarks") local namespace_extmarks_id = vim.api.nvim_create_namespace("NvimTreeExtmarks")
local namespace_virtual_lines_id = vim.api.nvim_create_namespace("NvimTreeVirtualLines") local namespace_virtual_lines_id = vim.api.nvim_create_namespace("NvimTreeVirtualLines")
---@alias HighlightRangeArgs { higroup:string, start:integer[], finish:integer[] } named arguments for vim.hl.range
---@class (exact) Renderer: Class ---@class (exact) Renderer: Class
---@field explorer Explorer ---@field explorer Explorer
local Renderer = Class:extend() local Renderer = Class:extend()
@@ -24,17 +25,23 @@ 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[]
---@param hl_args AddHighlightArgs[] ---@param hl_range_args HighlightRangeArgs[]
---@param signs string[] ---@param signs string[]
---@param extmarks table[] extra marks for right icon placement ---@param extmarks table[] extra marks for right icon placement
---@param virtual_lines table[] virtual lines for hidden count display ---@param virtual_lines table[] virtual lines for hidden count display
function Renderer:_draw(bufnr, lines, hl_args, signs, extmarks, virtual_lines) function Renderer:_draw(bufnr, lines, hl_range_args, signs, extmarks, virtual_lines)
if vim.fn.has("nvim-0.10") == 1 then if vim.fn.has("nvim-0.10") == 1 then
vim.api.nvim_set_option_value("modifiable", true, { buf = bufnr }) vim.api.nvim_set_option_value("modifiable", true, { buf = bufnr })
else else
@@ -42,7 +49,7 @@ function Renderer:_draw(bufnr, lines, hl_args, signs, extmarks, virtual_lines)
end end
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
self:render_hl(bufnr, hl_args) self:render_hl(bufnr, hl_range_args)
if vim.fn.has("nvim-0.10") == 1 then if vim.fn.has("nvim-0.10") == 1 then
vim.api.nvim_set_option_value("modifiable", false, { buf = bufnr }) vim.api.nvim_set_option_value("modifiable", false, { buf = bufnr })
@@ -77,43 +84,46 @@ function Renderer:_draw(bufnr, lines, hl_args, signs, extmarks, virtual_lines)
end end
---@private ---@private
function Renderer:render_hl(bufnr, hl) ---@param bufnr integer
---@param hl_range_args HighlightRangeArgs[]
function Renderer:render_hl(bufnr, hl_range_args)
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
vim.api.nvim_buf_clear_namespace(bufnr, namespace_highlights_id, 0, -1) vim.api.nvim_buf_clear_namespace(bufnr, namespace_highlights_id, 0, -1)
for _, data in ipairs(hl) do for _, args in ipairs(hl_range_args) do
if type(data[1]) == "table" then if vim.fn.has("nvim-0.11") == 1 and vim.hl and vim.hl.range then
for _, group in ipairs(data[1]) do vim.hl.range(bufnr, namespace_highlights_id, args.higroup, args.start, args.finish, {})
vim.api.nvim_buf_add_highlight(bufnr, namespace_highlights_id, group, data[2], data[3], data[4]) else
end vim.api.nvim_buf_add_highlight(bufnr, namespace_highlights_id, args.higroup, args.start[1], args.start[2], args.finish[2]) ---@diagnostic disable-line: deprecated
end end
end end
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 winid = self.explorer.view:get_winid(nil, "Renderer:draw")
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(winid or 0)
local builder = Builder(self.explorer):build() local builder = Builder(self.explorer):build()
self:_draw(bufnr, builder.lines, builder.hl_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(winid 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, winid)
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,13 +309,53 @@ 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 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
---@param path string ---@param path string
---@return string ---@return string
function M.canonical_path(path) function M.canonical_path(path)
@@ -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,641 +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
function M.close()
if M.View.tab.sync.close then
M.close_all_tabs()
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=$?