Repository: nvim-tree/nvim-tree.lua Branch: master Commit: dfeeb12aaae3 Files: 151 Total size: 681.9 KB Directory structure: gitextract_y381tt17/ ├── .editorconfig ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ ├── feature_request.md │ │ └── nvt-min.lua │ ├── dependabot.yml │ └── workflows/ │ ├── ci.yml │ ├── luarocks-release.yml │ ├── release-please.yml │ └── semantic-pr-subject.yml ├── .gitignore ├── .hooks/ │ └── pre-commit.sh ├── .luacheckrc ├── .luarc.json ├── .release-please-manifest.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── doc/ │ ├── .gitignore │ └── nvim-tree-lua.txt ├── lua/ │ ├── nvim-tree/ │ │ ├── _meta/ │ │ │ ├── api/ │ │ │ │ ├── appearance.lua │ │ │ │ ├── commands.lua │ │ │ │ ├── config.lua │ │ │ │ ├── decorator.lua │ │ │ │ ├── decorator_example.lua │ │ │ │ ├── deprecated.lua │ │ │ │ ├── events.lua │ │ │ │ ├── filter.lua │ │ │ │ ├── fs.lua │ │ │ │ ├── git.lua │ │ │ │ ├── map.lua │ │ │ │ ├── marks.lua │ │ │ │ ├── node.lua │ │ │ │ └── tree.lua │ │ │ ├── classes.lua │ │ │ ├── config/ │ │ │ │ ├── actions.lua │ │ │ │ ├── bookmarks.lua │ │ │ │ ├── default.lua │ │ │ │ ├── diagnostics.lua │ │ │ │ ├── experimental.lua │ │ │ │ ├── filesystem_watchers.lua │ │ │ │ ├── filters.lua │ │ │ │ ├── git.lua │ │ │ │ ├── help.lua │ │ │ │ ├── hijack_directories.lua │ │ │ │ ├── live_filter.lua │ │ │ │ ├── log.lua │ │ │ │ ├── modified.lua │ │ │ │ ├── notify.lua │ │ │ │ ├── renderer.lua │ │ │ │ ├── sort.lua │ │ │ │ ├── system_open.lua │ │ │ │ ├── tab.lua │ │ │ │ ├── trash.lua │ │ │ │ ├── ui.lua │ │ │ │ ├── update_focused_file.lua │ │ │ │ └── view.lua │ │ │ └── config.lua │ │ ├── actions/ │ │ │ ├── finders/ │ │ │ │ ├── find-file.lua │ │ │ │ ├── init.lua │ │ │ │ └── search-node.lua │ │ │ ├── fs/ │ │ │ │ ├── clipboard.lua │ │ │ │ ├── create-file.lua │ │ │ │ ├── init.lua │ │ │ │ ├── remove-file.lua │ │ │ │ ├── rename-file.lua │ │ │ │ └── trash.lua │ │ │ ├── init.lua │ │ │ ├── moves/ │ │ │ │ ├── init.lua │ │ │ │ ├── item.lua │ │ │ │ ├── parent.lua │ │ │ │ └── sibling.lua │ │ │ ├── node/ │ │ │ │ ├── buffer.lua │ │ │ │ ├── file-popup.lua │ │ │ │ ├── init.lua │ │ │ │ ├── open-file.lua │ │ │ │ ├── run-command.lua │ │ │ │ └── system-open.lua │ │ │ └── tree/ │ │ │ ├── change-dir.lua │ │ │ ├── collapse.lua │ │ │ ├── find-file.lua │ │ │ ├── init.lua │ │ │ ├── open.lua │ │ │ ├── resize.lua │ │ │ └── toggle.lua │ │ ├── api/ │ │ │ └── impl.lua │ │ ├── api.lua │ │ ├── appearance/ │ │ │ ├── hi-test.lua │ │ │ └── init.lua │ │ ├── buffers.lua │ │ ├── classic.lua │ │ ├── commands.lua │ │ ├── config.lua │ │ ├── core.lua │ │ ├── diagnostics.lua │ │ ├── enum.lua │ │ ├── events.lua │ │ ├── explorer/ │ │ │ ├── filters.lua │ │ │ ├── init.lua │ │ │ ├── live-filter.lua │ │ │ ├── sorter.lua │ │ │ └── watch.lua │ │ ├── git/ │ │ │ ├── init.lua │ │ │ ├── runner.lua │ │ │ └── utils.lua │ │ ├── help.lua │ │ ├── iterators/ │ │ │ └── node-iterator.lua │ │ ├── keymap.lua │ │ ├── legacy.lua │ │ ├── lib.lua │ │ ├── log.lua │ │ ├── marks/ │ │ │ └── init.lua │ │ ├── node/ │ │ │ ├── directory-link.lua │ │ │ ├── directory.lua │ │ │ ├── factory.lua │ │ │ ├── file-link.lua │ │ │ ├── file.lua │ │ │ ├── init.lua │ │ │ ├── link.lua │ │ │ └── root.lua │ │ ├── notify.lua │ │ ├── renderer/ │ │ │ ├── builder.lua │ │ │ ├── components/ │ │ │ │ ├── devicons.lua │ │ │ │ ├── full-name.lua │ │ │ │ ├── init.lua │ │ │ │ └── padding.lua │ │ │ ├── decorator/ │ │ │ │ ├── bookmarks.lua │ │ │ │ ├── builtin.lua │ │ │ │ ├── copied.lua │ │ │ │ ├── cut.lua │ │ │ │ ├── diagnostics.lua │ │ │ │ ├── git.lua │ │ │ │ ├── hidden.lua │ │ │ │ ├── init.lua │ │ │ │ ├── modified.lua │ │ │ │ └── opened.lua │ │ │ └── init.lua │ │ ├── utils.lua │ │ ├── view.lua │ │ └── watcher.lua │ └── nvim-tree.lua ├── plugin/ │ └── nvim-tree.lua ├── release-please-config.json └── scripts/ ├── doc-comments.sh ├── help-defaults.sh ├── luals-check.sh ├── setup-hooks.sh ├── vimdoc.sh └── vimdoc_config.lua ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] insert_final_newline = true end_of_line = lf [nvim-tree-lua.txt] 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] indent_style = space max_line_length = 140 indent_size = 2 # EmmyLuaCodeStyle specific, see # https://github.com/CppCXY/EmmyLuaCodeStyle/blob/master/lua.template.editorconfig continuation_indent = 2 quote_style = double call_arg_parentheses = always space_before_closure_open_parenthesis = false align_continuous_similar_call_args = true ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: kyazdani42 ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: Bug report description: Report a problem with nvim-tree labels: [bug] body: - type: markdown attributes: value: | Is this a question? * Please start a new [Q&A discussion](https://github.com/nvim-tree/nvim-tree.lua/discussions/new) instead of raising a bug. Before reporting: * search [existing issues](https://github.com/nvim-tree/nvim-tree.lua/issues) * ensure that nvim-tree is updated to the latest version If you are experiencing performance issues, please [enable profiling](https://github.com/nvim-tree/nvim-tree.lua#performance-issues) and attach the logs. Please note that nvim-tree team members do not have access to nor expertise with Windows. You will need to be an active participant during resolution. - type: textarea attributes: label: "Description" description: "A short description of the problem you are reporting." validations: required: true - type: textarea attributes: label: "Neovim version" description: "Output of `nvim --version`. Please see nvim-tree.lua [minimum required version](https://github.com/nvim-tree/nvim-tree.lua#notice)." placeholder: | NVIM v0.6.1 Build type: Release LuaJIT 2.1.0-beta3 render: text validations: required: true - type: input attributes: label: "Operating system and version" placeholder: "Linux 5.16.11-arch1-1, macOS 11.5, Windows 10" validations: required: true - type: input attributes: label: "Windows variant" placeholder: "WSL, PowerShell, cygwin, msys" validations: required: false - type: input attributes: label: "nvim-tree version" description: "`cd /nvim-tree.lua ; git log --format='%h' -n 1`" placeholder: | nvim-tree branch, commit or tag number validations: required: true - type: textarea attributes: label: "Clean room replication" description: "Minimal(!) configuration necessary to reproduce the issue. If not provided it is very unlikely that the nvim-tree team will be able to address your issue. See [wiki: Clean Room Replication](https://github.com/nvim-tree/nvim-tree.lua/wiki/Troubleshooting#clean-room-replication) for instructions and paste the contents of your `/tmp/nvt-min.lua` here. Please do NOT post a configuration that uses other plugin managers such as lazy, see [wiki: Lazy Loading](https://github.com/nvim-tree/nvim-tree.lua/wiki/Installation#lazy-loading)" render: lua validations: required: true - type: textarea attributes: label: "Steps to reproduce" description: "Steps to reproduce using the minimal config provided below." placeholder: | 1. nvim -nu /tmp/nvt-min.lua 2. :NvimTreeOpen 3. ... validations: required: true - type: textarea attributes: label: "Expected behavior" description: "A description of the behavior you expected:" - type: textarea attributes: label: "Actual behavior" description: "Observed behavior (may optionally include images, videos or a screencast)." ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: feature request assignees: '' --- **Is this a question?** Please start a new [Q&A discussion](https://github.com/nvim-tree/nvim-tree.lua/discussions/new) instead of raising a feature request. **Can this functionality be implemented utilising API?** nvim-tree exposes extensive API (see `:h nvim-tree-api`). Can it be used to achieve your goal? Is there a missing API that would make it possible? Given stable status of nvim-tree it's preferred to add new API than new functionality. **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/ISSUE_TEMPLATE/nvt-min.lua ================================================ vim.g.loaded_netrw = 1 vim.g.loaded_netrwPlugin = 1 vim.cmd([[set runtimepath=$VIMRUNTIME]]) vim.cmd([[set packpath=/tmp/nvt-min/site]]) local package_root = "/tmp/nvt-min/site/pack" local install_path = package_root .. "/packer/start/packer.nvim" local function load_plugins() require("packer").startup({ { "wbthomason/packer.nvim", "nvim-tree/nvim-tree.lua", "nvim-tree/nvim-web-devicons", -- ADD PLUGINS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE }, config = { package_root = package_root, compile_path = install_path .. "/plugin/packer_compiled.lua", display = { non_interactive = true }, }, }) end if vim.fn.isdirectory(install_path) == 0 then print("Installing nvim-tree and dependencies.") vim.fn.system({ "git", "clone", "--depth=1", "https://github.com/wbthomason/packer.nvim", install_path }) end load_plugins() require("packer").sync() vim.cmd([[autocmd User PackerComplete ++once echo "Ready!" | lua setup()]]) vim.opt.termguicolors = true vim.opt.cursorline = true -- MODIFY NVIM-TREE SETTINGS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE _G.setup = function() require("nvim-tree").setup({}) end -- UNCOMMENT this block for diagnostics issues, substituting pattern and cmd as appropriate. -- Requires diagnostics.enable = true in setup. --[[ vim.api.nvim_create_autocmd("FileType", { pattern = "lua", callback = function() vim.lsp.start { name = "my-luals", cmd = { "lua-language-server" }, root_dir = vim.loop.cwd(), } end, }) ]] ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" reviewers: - "gegoune" ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: pull_request: push: branches: [master] workflow_dispatch: permissions: contents: read jobs: lint: runs-on: ubuntu-latest concurrency: group: ${{ github.workflow }}-${{ matrix.lua_version }}-${{ github.head_ref || github.ref_name }} cancel-in-progress: true strategy: matrix: lua_version: [ 5.1 ] luacheck_version: [ 1.2.0 ] steps: - name: checkout uses: actions/checkout@v6 - name: install lua ${{ matrix.lua_version }} uses: leafo/gh-actions-lua@v12 with: luaVersion: ${{ matrix.lua_version }} - name: install luarocks uses: leafo/gh-actions-luarocks@v6 - name: install luacheck ${{ matrix.luacheck_version }} run: luarocks install luacheck ${{ matrix.luacheck_version }} - run: make lint check: runs-on: ubuntu-latest concurrency: group: ${{ github.workflow }}-${{ matrix.nvim_version }}-${{ matrix.luals_version }}-${{ matrix.emmyluacodestyle_version }}-${{ github.head_ref || github.ref_name }} cancel-in-progress: true strategy: matrix: nvim_version: [ stable, nightly ] luals_version: [ 3.15.0 ] emmyluacodestyle_version: [ 1.6.0 ] env: VIMRUNTIME: /home/runner/nvim-${{ matrix.nvim_version }}/share/nvim/runtime DIR_NVIM_SRC: /home/runner/src/neovim-${{ matrix.nvim_version }} steps: - name: checkout uses: actions/checkout@v6 - name: install nvim ${{ matrix.nvim_version }} uses: rhysd/action-setup-vim@v1 with: neovim: true version: ${{ matrix.nvim_version }} - name: install lua-language-server ${{ matrix.luals_version }} run: | mkdir -p luals curl -L "https://github.com/LuaLS/lua-language-server/releases/download/${{ matrix.luals_version }}/lua-language-server-${{ matrix.luals_version }}-linux-x64.tar.gz" | tar zx --directory luals echo "luals/bin" >> "$GITHUB_PATH" - name: install EmmyLuaCodeStyle ${{ matrix.emmyluacodestyle_version }} run: | mkdir -p EmmyLuaCodeStyle curl -L "https://github.com/CppCXY/EmmyLuaCodeStyle/releases/download/${{ matrix.emmyluacodestyle_version }}/linux-x64.tar.gz" | tar zx --directory EmmyLuaCodeStyle echo "EmmyLuaCodeStyle/linux-x64/bin" >> "$GITHUB_PATH" - run: make check - run: make style - run: make style-doc - run: make format-check - name: build Nvim from source run: | mkdir -p "${DIR_NVIM_SRC}" curl -L "https://github.com/neovim/neovim/archive/refs/tags/${{ matrix.nvim_version }}.tar.gz" | tar zx --directory "${DIR_NVIM_SRC}/.." cd "${DIR_NVIM_SRC}" make doc make lintdoc - run: make help-check ================================================ FILE: .github/workflows/luarocks-release.yml ================================================ name: Luarocks Release on: push: tags: - v* workflow_dispatch: jobs: luarocks-upload: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: LuaRocks Upload uses: nvim-neorocks/luarocks-tag-release@v7 env: LUAROCKS_API_KEY: ${{ secrets.LUAROCKS_API_KEY }} with: summary: A File Explorer For Neovim detailed_description: | Automatic updates File type icons Git integration Diagnostics integration - LSP and COC (Live) filtering Cut, copy, paste, rename, delete, create etc. Highly customisable Rich API license: "GPL-3.0" labels: neovim dependencies: | nvim-web-devicons ================================================ FILE: .github/workflows/release-please.yml ================================================ on: push: branches: - master workflow_dispatch: concurrency: group: ${{ github.workflow }} cancel-in-progress: true name: release-please permissions: contents: write pull-requests: write jobs: release-please: runs-on: ubuntu-latest steps: - uses: googleapis/release-please-action@v4 id: release - uses: actions/checkout@v6 - name: tag major and minor versions if: ${{ steps.release.outputs.release_created }} run: | git config user.name github-actions[bot] git config user.email 41898282+github-actions[bot]@users.noreply.github.com git remote add gh-token "https://${{ secrets.GITHUB_TOKEN }}@github.com/googleapis/release-please-action.git" git tag -d v${{ steps.release.outputs.major }} || true git tag -d v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} || true git tag -d v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}.${{ steps.release.outputs.patch }} || true git push origin :v${{ steps.release.outputs.major }} || true git push origin :v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} || true git push origin :v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}.${{ steps.release.outputs.patch }} || true git tag -a v${{ steps.release.outputs.major }} -m "Release v${{ steps.release.outputs.major }}" git tag -a v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} -m "Release v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}" git tag -a v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}.${{ steps.release.outputs.patch }} -m "Release v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}.${{ steps.release.outputs.patch }}" git push origin v${{ steps.release.outputs.major }} git push origin v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} git push origin v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}.${{ steps.release.outputs.patch }} ================================================ FILE: .github/workflows/semantic-pr-subject.yml ================================================ name: Semantic Pull Request Subject on: pull_request: types: - opened - reopened - edited - synchronize workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.head_ref }} cancel-in-progress: true jobs: semantic-pr-subject: runs-on: ubuntu-latest steps: - uses: amannn/action-semantic-pull-request@v6.1.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .gitignore ================================================ /luals-out/ /luals/ # backup vim files *~ ================================================ FILE: .hooks/pre-commit.sh ================================================ #!/usr/bin/env sh make ================================================ FILE: .luacheckrc ================================================ local M = {} -- Don't report unused self arguments of methods. M.self = false M.ignore = { "631", -- max_line_length } -- Global objects defined by the C code M.globals = { "vim", } return M ================================================ FILE: .luarc.json ================================================ { "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", "runtime.version": "Lua 5.1", "workspace": { "library": [ "$VIMRUNTIME/lua/vim", "${3rd}/luv/library" ] }, "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": { "libraryFiles": "Disable", "globals": [], "neededFileStatus": { "ambiguity-1": "Any", "assign-type-mismatch": "Any", "await-in-sync": "Any", "cast-local-type": "Any", "cast-type-mismatch": "Any", "circle-doc-class": "Any", "close-non-object": "Any", "code-after-break": "Any", "codestyle-check": "None", "count-down-loop": "Any", "deprecated": "Any", "different-requires": "Any", "discard-returns": "Any", "doc-field-no-class": "Any", "duplicate-doc-alias": "Any", "duplicate-doc-field": "Any", "duplicate-doc-param": "Any", "duplicate-index": "Any", "duplicate-set-field": "Any", "empty-block": "Any", "global-element": "Any", "global-in-nil-env": "Any", "incomplete-signature-doc": "Any", "inject-field": "Any", "invisible": "Any", "lowercase-global": "Any", "missing-fields": "Any", "missing-global-doc": "Any", "missing-local-export-doc": "Any", "missing-parameter": "Any", "missing-return": "Any", "missing-return-value": "Any", "name-style-check": "None", "need-check-nil": "Any", "newfield-call": "Any", "newline-call": "Any", "no-unknown": "None", "not-yieldable": "Any", "param-type-mismatch": "Any", "redefined-local": "Any", "redundant-parameter": "Any", "redundant-return": "Any", "redundant-return-value": "Any", "redundant-value": "Any", "return-type-mismatch": "Any", "spell-check": "None", "trailing-space": "Any", "unbalanced-assignments": "Any", "undefined-doc-class": "Any", "undefined-doc-name": "Any", "undefined-doc-param": "Any", "undefined-env-child": "Any", "undefined-field": "None", "undefined-global": "Any", "unknown-cast-variable": "Any", "unknown-diag-code": "Any", "unknown-operator": "Any", "unreachable-code": "Any", "unused-function": "Any", "unused-label": "Any", "unused-local": "Any", "unused-vararg": "Any" } } } ================================================ FILE: .release-please-manifest.json ================================================ { ".": "1.16.0" } ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## [1.16.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.15.0...nvim-tree-v1.16.0) (2026-03-17) ### Features * **#2994:** add visual mode operations: copy, cut, delete, trash, toggle bookmark ([#3268](https://github.com/nvim-tree/nvim-tree.lua/issues/3268)) ([9197f3e](https://github.com/nvim-tree/nvim-tree.lua/commit/9197f3ee3f0c9a754aab5b16500db6d7da5f68fd)) * add default <Del> mapping for api.fs.remove ([#3238](https://github.com/nvim-tree/nvim-tree.lua/issues/3238)) ([ca8d82f](https://github.com/nvim-tree/nvim-tree.lua/commit/ca8d82fff26cb12ced239713e3222f4a9dcd0da0)) ### Bug Fixes * **##3288:** restore single-item paste conflict prompts ([#3289](https://github.com/nvim-tree/nvim-tree.lua/issues/3289)) ([b3772ad](https://github.com/nvim-tree/nvim-tree.lua/commit/b3772adec8db61ba9098c5624a0823a77be3a23d)) * **#3178:** handle Windows paths in ignore_dirs and add .zig-cache to defaults ([#3261](https://github.com/nvim-tree/nvim-tree.lua/issues/3261)) ([5499299](https://github.com/nvim-tree/nvim-tree.lua/commit/5499299746b95c49ef4fc89b30c29a79b577881f)) * **#3187:** prevent closing the last non-floating window when deleting files ([#3282](https://github.com/nvim-tree/nvim-tree.lua/issues/3282)) ([018a078](https://github.com/nvim-tree/nvim-tree.lua/commit/018a078c1e149bcd0c4e65c92aabb8e12501d769)) * **#3198:** add filesystem_watchers.max_events to handle runaway filesystem events on PowerShell ([#3232](https://github.com/nvim-tree/nvim-tree.lua/issues/3232)) ([c07ce43](https://github.com/nvim-tree/nvim-tree.lua/commit/c07ce43527e5f0242121f4eb1feb7ac0ecea8275)) * **#3248:** bookmark filter shows contents of marked directories ([#3249](https://github.com/nvim-tree/nvim-tree.lua/issues/3249)) ([5757bcf](https://github.com/nvim-tree/nvim-tree.lua/commit/5757bcf0447d22d8f78826bc5c59b28da2824c3b)) * **#3251:** pass git.timeout to all vim.system git calls ([#3277](https://github.com/nvim-tree/nvim-tree.lua/issues/3277)) ([e49b0d9](https://github.com/nvim-tree/nvim-tree.lua/commit/e49b0d9bfa70989cf8b5abf5557f51e6e57f68d6)) * **#3265:** rename module api.health to api.appearance, to avoid :checkhealth detection ([#3266](https://github.com/nvim-tree/nvim-tree.lua/issues/3266)) ([1df1960](https://github.com/nvim-tree/nvim-tree.lua/commit/1df1960d0e3a26643a4100f64fa03b991b9f4b85)) * **#3267:** renderer.icons.*_placement 'right_align' at the right hand edge, not the right of the name ([#3270](https://github.com/nvim-tree/nvim-tree.lua/issues/3270)) ([fa3c458](https://github.com/nvim-tree/nvim-tree.lua/commit/fa3c45875f9b1f56ace57711c6f2ac22484ed956)) * **#3281:** fix a bug when a view width of -1 is returned from a function ([#3283](https://github.com/nvim-tree/nvim-tree.lua/issues/3283)) ([c988e28](https://github.com/nvim-tree/nvim-tree.lua/commit/c988e289428d9202b28ba27479647033c7dd2956)) * allow 0 (unlimited) filesystem_watchers.max_events which is the new default, except for windows at 1000 ([#3279](https://github.com/nvim-tree/nvim-tree.lua/issues/3279)) ([87594aa](https://github.com/nvim-tree/nvim-tree.lua/commit/87594aa7c8808701b418b285e9adf690acead201)) * increase filesystem_watchers.max_events from 100 to 1000 ([#3263](https://github.com/nvim-tree/nvim-tree.lua/issues/3263)) ([0f4d2d6](https://github.com/nvim-tree/nvim-tree.lua/commit/0f4d2d6998dc324d54d6adce94186bee43725afb)) * restore bookmark filter for marked directories ([5757bcf](https://github.com/nvim-tree/nvim-tree.lua/commit/5757bcf0447d22d8f78826bc5c59b28da2824c3b)) ### Performance Improvements * **#3284:** re-draw instead of refreshing the tree when a buffer is modified ([#3285](https://github.com/nvim-tree/nvim-tree.lua/issues/3285)) ([c8d8d51](https://github.com/nvim-tree/nvim-tree.lua/commit/c8d8d515c29f0f0b1a352e0d75616f74f42fc03b)) ## [1.15.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.14.0...nvim-tree-v1.15.0) (2026-01-11) ### Features * **#1826:** add diagnostics.diagnostic_opts: vim.diagnostic.Opts will override diagnostics.severity and diagnostics.icons ([#3190](https://github.com/nvim-tree/nvim-tree.lua/issues/3190)) ([fefa335](https://github.com/nvim-tree/nvim-tree.lua/commit/fefa335f1c8f690eb668a1efd18ee4fc6d64cd3e)) * **#1851:** add bookmarks.persist, default path: vim.fn.stdpath("data") .. "/nvim-tree-bookmarks.json", disabled by default ([#3033](https://github.com/nvim-tree/nvim-tree.lua/issues/3033)) ([c89215d](https://github.com/nvim-tree/nvim-tree.lua/commit/c89215d6a1107a3c0c134750be48657ba8e6a9aa)) * **#3213:** add `view.width.lines_excluded` option ([776a5cd](https://github.com/nvim-tree/nvim-tree.lua/commit/776a5cdfac948b490e06f1d1d22c4cb986e40699)) * **#3213:** add view.width.lines_excluded option ([#3214](https://github.com/nvim-tree/nvim-tree.lua/issues/3214)) ([776a5cd](https://github.com/nvim-tree/nvim-tree.lua/commit/776a5cdfac948b490e06f1d1d22c4cb986e40699)) * add NvimTreeFilter filetype ([64e2192](https://github.com/nvim-tree/nvim-tree.lua/commit/64e2192f5250796aa4a7f33c6ad888515af50640)) * load command definitions at nvim startup ([#3211](https://github.com/nvim-tree/nvim-tree.lua/issues/3211)) ([1eda256](https://github.com/nvim-tree/nvim-tree.lua/commit/1eda2569394f866360e61f590f1796877388cb8a)) * load command definitions in `plugin` directory ([1eda256](https://github.com/nvim-tree/nvim-tree.lua/commit/1eda2569394f866360e61f590f1796877388cb8a)) * set filter input filetype to NvimTreeFilter ([#3207](https://github.com/nvim-tree/nvim-tree.lua/issues/3207)) ([64e2192](https://github.com/nvim-tree/nvim-tree.lua/commit/64e2192f5250796aa4a7f33c6ad888515af50640)) * use `add_trailing` also for symlink destination ([81ede55](https://github.com/nvim-tree/nvim-tree.lua/commit/81ede55c47528ff7c81b2a498fbee61b298c4e2f)) ### Bug Fixes * **#3226:** set &swapfile=false before setting tree buffer name, avoiding any potential collisions with a swapfile ([#3227](https://github.com/nvim-tree/nvim-tree.lua/issues/3227)) ([8298117](https://github.com/nvim-tree/nvim-tree.lua/commit/8298117311a1f23f039c278e4e4977ab80a15e33)) * api.tree.change_root_to_node on a file now changes directory to parent as per documentation ([#3228](https://github.com/nvim-tree/nvim-tree.lua/issues/3228)) ([b8b44b6](https://github.com/nvim-tree/nvim-tree.lua/commit/b8b44b6a2494d086a9177251a119f9daec6cace8)) * correctly assign extmarks to lines when computing tree window width in `grow` when `nvim-tree.view.width.lines_excluded` contains "root" ([e66994d](https://github.com/nvim-tree/nvim-tree.lua/commit/e66994d40db2d57c91bf9aeaee8bf7ab8b1131f6)) * incorrect window width when right_align icons present ([#3239](https://github.com/nvim-tree/nvim-tree.lua/issues/3239)) ([e66994d](https://github.com/nvim-tree/nvim-tree.lua/commit/e66994d40db2d57c91bf9aeaee8bf7ab8b1131f6)) * prevent NvimTree to be alternate buffer when tab open ([#3205](https://github.com/nvim-tree/nvim-tree.lua/issues/3205)) ([e397756](https://github.com/nvim-tree/nvim-tree.lua/commit/e397756d2a79d74314ea4cd3efc41300e91c0ff0)) * renderer.add_trailing applies to symlink destination ([#3217](https://github.com/nvim-tree/nvim-tree.lua/issues/3217)) ([81ede55](https://github.com/nvim-tree/nvim-tree.lua/commit/81ede55c47528ff7c81b2a498fbee61b298c4e2f)) ### Performance Improvements * **commands:** defer module loading ([#3210](https://github.com/nvim-tree/nvim-tree.lua/issues/3210)) ([68c67ad](https://github.com/nvim-tree/nvim-tree.lua/commit/68c67adfabfd1ce923839570507ef2e81ab8a408)) ## [1.14.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.13.0...nvim-tree-v1.14.0) (2025-08-12) ### Features * **#2685:** highlight git new tracked with NvimTreeGitFileNewHL ([#3176](https://github.com/nvim-tree/nvim-tree.lua/issues/3176)) ([0a52012](https://github.com/nvim-tree/nvim-tree.lua/commit/0a52012d611f3c1492b8d2aba363fabf734de91d)) * **#2789:** add optional function expand_until to api.tree.expand_all and api.node.expand ([#3166](https://github.com/nvim-tree/nvim-tree.lua/issues/3166)) ([1b876db](https://github.com/nvim-tree/nvim-tree.lua/commit/1b876db04903b93c78c97fd3f3dd85d59eeef5ff)) * **#2826:** allow only one window with nvim-tree buffer per tab ([#3174](https://github.com/nvim-tree/nvim-tree.lua/issues/3174)) ([dd2364d](https://github.com/nvim-tree/nvim-tree.lua/commit/dd2364d6802f7f57a98acb8b545ed484c6697626)) * **#3157:** add view.cursorlineopt ([#3158](https://github.com/nvim-tree/nvim-tree.lua/issues/3158)) ([8eb5e0b](https://github.com/nvim-tree/nvim-tree.lua/commit/8eb5e0bfd1c4da6efc03ab0c1ccf463dbaae831e)) ### Bug Fixes * **#3077:** deleting a directory containing symlinked directory will delete the contents of the linked directory ([#3168](https://github.com/nvim-tree/nvim-tree.lua/issues/3168)) ([10db694](https://github.com/nvim-tree/nvim-tree.lua/commit/10db6943cb40625941a35235eeb385ffdfbf827a)) * **#3157:** add view.cursorlineopt ([8eb5e0b](https://github.com/nvim-tree/nvim-tree.lua/commit/8eb5e0bfd1c4da6efc03ab0c1ccf463dbaae831e)) * **#3172:** live filter exception ([#3173](https://github.com/nvim-tree/nvim-tree.lua/issues/3173)) ([0a7fcdf](https://github.com/nvim-tree/nvim-tree.lua/commit/0a7fcdf3f8ba208f4260988a198c77ec11748339)) * invalid window id for popup info window ([#3147](https://github.com/nvim-tree/nvim-tree.lua/issues/3147)) ([d54a187](https://github.com/nvim-tree/nvim-tree.lua/commit/d54a1875a91e1a705795ea26074795210b92ce7f)) * **picker:** exclude full_name window id from the choice ([#3165](https://github.com/nvim-tree/nvim-tree.lua/issues/3165)) ([543ed3c](https://github.com/nvim-tree/nvim-tree.lua/commit/543ed3cac212dc3993ef9f042f6c0812e34ddd43)) * window picker ignore hidden window ([#3145](https://github.com/nvim-tree/nvim-tree.lua/issues/3145)) ([d87b41c](https://github.com/nvim-tree/nvim-tree.lua/commit/d87b41ca537e2131622d48a6c25ccf2fbe0e5d62)) ### Performance Improvements * **#3171:** cache toplevel for untracked ([#3185](https://github.com/nvim-tree/nvim-tree.lua/issues/3185)) ([4425136](https://github.com/nvim-tree/nvim-tree.lua/commit/442513648c6936e754c3308a1c58591a399493e5)) * **#3171:** use vim.system() instead of vim.fn.system() to execute git toplevel ([#3175](https://github.com/nvim-tree/nvim-tree.lua/issues/3175)) ([9a05b9e](https://github.com/nvim-tree/nvim-tree.lua/commit/9a05b9e9f928856ca23dbf876fab372003180c3f)) ### Reverts * **#3180, #3177:** invalid group or tabpage ([#3181](https://github.com/nvim-tree/nvim-tree.lua/issues/3181)) ([9b289ab](https://github.com/nvim-tree/nvim-tree.lua/commit/9b289abd6998e30fd24cbc9919e0b0cbed6364ce)) * **#3180, #3177:** resolve live filter failures ([#3183](https://github.com/nvim-tree/nvim-tree.lua/issues/3183)) ([a4699c0](https://github.com/nvim-tree/nvim-tree.lua/commit/a4699c0904103e7767334f6da05f5c2ea5514845)) ## [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) ### Features * **api:** add node.open.vertical_no_picker, node.open.horizontal_no_picker ([#3031](https://github.com/nvim-tree/nvim-tree.lua/issues/3031)) ([68fc4c2](https://github.com/nvim-tree/nvim-tree.lua/commit/68fc4c20f5803444277022c681785c5edd11916d)) ### Bug Fixes * **#3015:** dynamic width no longer truncates on right_align icons ([#3022](https://github.com/nvim-tree/nvim-tree.lua/issues/3022)) ([f7b76cd](https://github.com/nvim-tree/nvim-tree.lua/commit/f7b76cd1a75615c8d6254fc58bedd2a7304eb7d8)) * **#3018:** error when focusing nvim-tree when in terminal mode ([#3019](https://github.com/nvim-tree/nvim-tree.lua/issues/3019)) ([db8d7ac](https://github.com/nvim-tree/nvim-tree.lua/commit/db8d7ac1f524fc6f808764b29fa695c51e014aa6)) * **#3041:** use vim.diagnostic.get for updating diagnostics ([#3042](https://github.com/nvim-tree/nvim-tree.lua/issues/3042)) ([aae0185](https://github.com/nvim-tree/nvim-tree.lua/commit/aae01853ddbd790d1efd6ff04ff96cf38c02c95f)) * Can't re-enter normal mode from terminal mode ([db8d7ac](https://github.com/nvim-tree/nvim-tree.lua/commit/db8d7ac1f524fc6f808764b29fa695c51e014aa6)) * hijack directory "BufEnter", "BufNewFile" events are nested ([#3044](https://github.com/nvim-tree/nvim-tree.lua/issues/3044)) ([39bc630](https://github.com/nvim-tree/nvim-tree.lua/commit/39bc63081605c1d4b974131ebecaea11e8a8595f)) * view.width functions may return strings ([#3020](https://github.com/nvim-tree/nvim-tree.lua/issues/3020)) ([6b4be1d](https://github.com/nvim-tree/nvim-tree.lua/commit/6b4be1dc0cd4d5d5b8e8b56b510a75016e99746f)) ## [1.9.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.8.0...nvim-tree-v1.9.0) (2024-12-07) ### Features * **#2948:** add custom decorators, :help nvim-tree-decorators ([#2996](https://github.com/nvim-tree/nvim-tree.lua/issues/2996)) ([7a4ff1a](https://github.com/nvim-tree/nvim-tree.lua/commit/7a4ff1a516fe92a5ed6b79d7ce31ea4d8f341a72)) ### Bug Fixes * **#2954:** more efficient LSP updates, increase diagnostics.debounce_delay from 50ms to 500ms ([#3007](https://github.com/nvim-tree/nvim-tree.lua/issues/3007)) ([1f3ffd6](https://github.com/nvim-tree/nvim-tree.lua/commit/1f3ffd6af145af2a4930a61c50f763264922c3fe)) * **#2990:** Do not check if buffer is buflisted in diagnostics.update() ([#2998](https://github.com/nvim-tree/nvim-tree.lua/issues/2998)) ([28eac28](https://github.com/nvim-tree/nvim-tree.lua/commit/28eac2801b201f301449e976d7a9e8cfde053ba3)) * **#3009:** nvim < 0.10 apply view options locally ([#3010](https://github.com/nvim-tree/nvim-tree.lua/issues/3010)) ([ca7c4c3](https://github.com/nvim-tree/nvim-tree.lua/commit/ca7c4c33cac2ad66ec69d45e465379716ef0cc97)) * **api:** correct argument types in `wrap_node` and `wrap_node_or_nil` ([#3006](https://github.com/nvim-tree/nvim-tree.lua/issues/3006)) ([f7c65e1](https://github.com/nvim-tree/nvim-tree.lua/commit/f7c65e11d695a084ca10b93df659bb7e68b71f9f)) ## [1.8.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.7.1...nvim-tree-v1.8.0) (2024-11-09) ### Features * **#2819:** add actions.open_file.relative_path, default enabled, following successful experiment ([#2995](https://github.com/nvim-tree/nvim-tree.lua/issues/2995)) ([2ee1c5e](https://github.com/nvim-tree/nvim-tree.lua/commit/2ee1c5e17fdfbf5013af31b1410e4a5f28f4cadd)) * **#2938:** add default filesystem_watchers.ignore_dirs = { "/.ccls-cache", "/build", "/node_modules", "/target", } ([#2940](https://github.com/nvim-tree/nvim-tree.lua/issues/2940)) ([010ae03](https://github.com/nvim-tree/nvim-tree.lua/commit/010ae0365aafd6275c478d932515d2e8e897b7bb)) ### Bug Fixes * **#2945:** stack overflow on api.git.reload or fugitive event with watchers disabled ([#2949](https://github.com/nvim-tree/nvim-tree.lua/issues/2949)) ([5ad8762](https://github.com/nvim-tree/nvim-tree.lua/commit/5ad87620ec9d1190d15c88171a3f0122bc16b0fe)) * **#2947:** root is never a dotfile, so that it doesn't propagate to children ([#2958](https://github.com/nvim-tree/nvim-tree.lua/issues/2958)) ([f5f6789](https://github.com/nvim-tree/nvim-tree.lua/commit/f5f67892996b280ae78b1b0a2d07c4fa29ae0905)) * **#2951:** highlights incorrect following cancelled pick ([#2952](https://github.com/nvim-tree/nvim-tree.lua/issues/2952)) ([1c9553a](https://github.com/nvim-tree/nvim-tree.lua/commit/1c9553a19f70df3dcb171546a3d5e034531ef093)) * **#2954:** resolve occasional tree flashing on diagnostics, set tree buffer options in deterministic order ([#2980](https://github.com/nvim-tree/nvim-tree.lua/issues/2980)) ([82ab19e](https://github.com/nvim-tree/nvim-tree.lua/commit/82ab19ebf79c1839d7351f2fed213d1af13a598e)) * **#2961:** windows: escape brackets and parentheses when opening file ([#2962](https://github.com/nvim-tree/nvim-tree.lua/issues/2962)) ([63c7ad9](https://github.com/nvim-tree/nvim-tree.lua/commit/63c7ad9037fb7334682dd0b3a177cee25c5c8a0f)) * **#2969:** After a rename, the node loses selection ([#2974](https://github.com/nvim-tree/nvim-tree.lua/issues/2974)) ([1403933](https://github.com/nvim-tree/nvim-tree.lua/commit/14039337a563f4efd72831888f332a15585f0ea1)) * **#2972:** error on :colorscheme ([#2973](https://github.com/nvim-tree/nvim-tree.lua/issues/2973)) ([6e5a204](https://github.com/nvim-tree/nvim-tree.lua/commit/6e5a204ca659bb8f2a564df75df2739edec03cb0)) * **#2976:** use vim.loop to preserve neovim 0.9 compatibility ([#2977](https://github.com/nvim-tree/nvim-tree.lua/issues/2977)) ([00dff48](https://github.com/nvim-tree/nvim-tree.lua/commit/00dff482f9a8fb806a54fd980359adc6cd45d435)) * **#2978:** grouped folder not showing closed icon ([#2979](https://github.com/nvim-tree/nvim-tree.lua/issues/2979)) ([120ba58](https://github.com/nvim-tree/nvim-tree.lua/commit/120ba58254835d412bbc91cffe847e9be835fadd)) * **#2981:** windows: root changed when navigating with LSP ([#2982](https://github.com/nvim-tree/nvim-tree.lua/issues/2982)) ([c22124b](https://github.com/nvim-tree/nvim-tree.lua/commit/c22124b37409bee6d1a0da77f4f3a1526f7a204d)) * symlink file icons rendered when renderer.icons.show.file = false, folder.symlink* was incorrectly rendered as folder.default|open ([#2983](https://github.com/nvim-tree/nvim-tree.lua/issues/2983)) ([2156bc0](https://github.com/nvim-tree/nvim-tree.lua/commit/2156bc08c982d3c4b4cfc2b8fd7faeff58a88e10)) ## [1.7.1](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.7.0...nvim-tree-v1.7.1) (2024-09-30) ### Bug Fixes * **#2794:** sshfs compatibility ([#2922](https://github.com/nvim-tree/nvim-tree.lua/issues/2922)) ([9650e73](https://github.com/nvim-tree/nvim-tree.lua/commit/9650e735baad0d39505f4cb4867a60f02858536a)) * **#2928:** nil explorer in parent move action ([#2929](https://github.com/nvim-tree/nvim-tree.lua/issues/2929)) ([0429f28](https://github.com/nvim-tree/nvim-tree.lua/commit/0429f286b350c65118d66b646775bf187936fa47)) * **#2930:** empty groups expanded on reload ([#2935](https://github.com/nvim-tree/nvim-tree.lua/issues/2935)) ([4520c03](https://github.com/nvim-tree/nvim-tree.lua/commit/4520c0355cc561830ee2cf90dc37a2a75abf7995)) * invalid explorer on open ([#2927](https://github.com/nvim-tree/nvim-tree.lua/issues/2927)) ([59a8a6a](https://github.com/nvim-tree/nvim-tree.lua/commit/59a8a6ae5e9d3eae99d08ab655d12fd51d5d17f3)) ### Reverts * **#2794:** sshfs compatibility ([#2920](https://github.com/nvim-tree/nvim-tree.lua/issues/2920)) ([8405ecf](https://github.com/nvim-tree/nvim-tree.lua/commit/8405ecfbd6bb08a94ffc9c68fef211eea56e8a3b)) ## [1.7.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.6.1...nvim-tree-v1.7.0) (2024-09-21) ### Features * **#2430:** use vim.ui.open as default system_open, for neovim 0.10+ ([#2912](https://github.com/nvim-tree/nvim-tree.lua/issues/2912)) ([03f737e](https://github.com/nvim-tree/nvim-tree.lua/commit/03f737e5744a2b3ebb4b086f7636a3399224ec0c)) * help closes on <Esc> and api.tree.toggle_help mappings ([#2909](https://github.com/nvim-tree/nvim-tree.lua/issues/2909)) ([b652dbd](https://github.com/nvim-tree/nvim-tree.lua/commit/b652dbd0e0489c5fbb81fbededf0d99029cd2f38)) ### Bug Fixes * **#2862:** windows path replaces backslashes with forward slashes ([#2903](https://github.com/nvim-tree/nvim-tree.lua/issues/2903)) ([45a93d9](https://github.com/nvim-tree/nvim-tree.lua/commit/45a93d99794fff3064141d5b3a50db98ce352697)) * **#2906:** resource leak on populate children ([#2907](https://github.com/nvim-tree/nvim-tree.lua/issues/2907)) ([a4dd5ad](https://github.com/nvim-tree/nvim-tree.lua/commit/a4dd5ad5c8f9349142291d24e0e6466995594b9a)) * **#2917:** fix root copy paths: Y, ge, gy, y ([#2918](https://github.com/nvim-tree/nvim-tree.lua/issues/2918)) ([b18ce8b](https://github.com/nvim-tree/nvim-tree.lua/commit/b18ce8be8f162eee0bc37addcfe17d7d019fcec7)) * safely close last tree window ([#2913](https://github.com/nvim-tree/nvim-tree.lua/issues/2913)) ([bd48816](https://github.com/nvim-tree/nvim-tree.lua/commit/bd4881660bf0ddfa6acb21259f856ba3dcb26a93)) * safely close tree window with pcall and debug logging ([bd48816](https://github.com/nvim-tree/nvim-tree.lua/commit/bd4881660bf0ddfa6acb21259f856ba3dcb26a93)) ## [1.6.1](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.6.0...nvim-tree-v1.6.1) (2024-09-09) ### Bug Fixes * **#2794:** sshfs compatibility ([#2893](https://github.com/nvim-tree/nvim-tree.lua/issues/2893)) ([2d6e64d](https://github.com/nvim-tree/nvim-tree.lua/commit/2d6e64dd8c45a86f312552b7a47eef2c8623a25c)) * **#2868:** windows: do not visit unenumerable directories such as Application Data ([#2874](https://github.com/nvim-tree/nvim-tree.lua/issues/2874)) ([2104786](https://github.com/nvim-tree/nvim-tree.lua/commit/210478677cb9d672c4265deb0e9b59d58b675bd4)) * **#2878:** nowrapscan prevents move from root ([#2880](https://github.com/nvim-tree/nvim-tree.lua/issues/2880)) ([4234095](https://github.com/nvim-tree/nvim-tree.lua/commit/42340952af598a08ab80579d067b6da72a9e6d29)) * **#2879:** remove unnecessary tree window width setting to prevent unnecessary :wincmd = ([#2881](https://github.com/nvim-tree/nvim-tree.lua/issues/2881)) ([d43ab67](https://github.com/nvim-tree/nvim-tree.lua/commit/d43ab67d0eb4317961c5e9d15fffe908519debe0)) ## [1.6.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.5.0...nvim-tree-v1.6.0) (2024-08-10) ### Features * **#2225:** add renderer.hidden_display to show a summary of hidden files below the tree ([#2856](https://github.com/nvim-tree/nvim-tree.lua/issues/2856)) ([e25eb7f](https://github.com/nvim-tree/nvim-tree.lua/commit/e25eb7fa83f7614bb23d762e91d2de44fcd7103b)) * **#2349:** add "right_align" option for renderer.icons.*_placement ([#2839](https://github.com/nvim-tree/nvim-tree.lua/issues/2839)) ([1d629a5](https://github.com/nvim-tree/nvim-tree.lua/commit/1d629a5d3f7d83d516494c221a2cfc079f43bc47)) * **#2349:** add "right_align" option for renderer.icons.*_placement ([#2846](https://github.com/nvim-tree/nvim-tree.lua/issues/2846)) ([48d0e82](https://github.com/nvim-tree/nvim-tree.lua/commit/48d0e82f9434691cc50d970898142a8c084a49d6)) * add renderer.highlight_hidden, renderer.icons.show.hidden and renderer.icons.hidden_placement for dotfile icons/highlights ([#2840](https://github.com/nvim-tree/nvim-tree.lua/issues/2840)) ([48a9290](https://github.com/nvim-tree/nvim-tree.lua/commit/48a92907575df1dbd7242975a04e98169cb3a115)) ### Bug Fixes * **#2859:** make sure window still exists when restoring options ([#2863](https://github.com/nvim-tree/nvim-tree.lua/issues/2863)) ([466fbed](https://github.com/nvim-tree/nvim-tree.lua/commit/466fbed3e4b61fcc23a48fe99de7bfa264a9fee8)) ## [1.5.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.4.0...nvim-tree-v1.5.0) (2024-07-11) ### Features * **#2127:** add experimental.actions.open_file.relative_path to open files with a relative path rather than absolute ([#2805](https://github.com/nvim-tree/nvim-tree.lua/issues/2805)) ([869c064](https://github.com/nvim-tree/nvim-tree.lua/commit/869c064721a6c2091f22c3541e8f0ff958361771)) * **#2598:** add api.tree.resize ([#2811](https://github.com/nvim-tree/nvim-tree.lua/issues/2811)) ([2ede0de](https://github.com/nvim-tree/nvim-tree.lua/commit/2ede0de67b47e89e2b4cb488ea3f58b8f5a8c90a)) * **#2799:** `filesystem_watchers.ignore_dirs` and `git.disable_for_dirs` may be functions ([#2800](https://github.com/nvim-tree/nvim-tree.lua/issues/2800)) ([8b2c5c6](https://github.com/nvim-tree/nvim-tree.lua/commit/8b2c5c678be4b49dff6a2df794877000113fd77b)) * **#2799:** filesystem_watchers.ignore_dirs and git.disable_for_dirs may be functions ([8b2c5c6](https://github.com/nvim-tree/nvim-tree.lua/commit/8b2c5c678be4b49dff6a2df794877000113fd77b)) ### Bug Fixes * **#2813:** macos: enable file renaming with changed capitalization ([#2814](https://github.com/nvim-tree/nvim-tree.lua/issues/2814)) ([abfd1d1](https://github.com/nvim-tree/nvim-tree.lua/commit/abfd1d1b6772540364743531cc0331e08a0027a9)) * **#2819:** experimental.actions.open_file.relative_path issue following change directory ([#2820](https://github.com/nvim-tree/nvim-tree.lua/issues/2820)) ([12a9a99](https://github.com/nvim-tree/nvim-tree.lua/commit/12a9a995a455d2c2466e47140663275365a5d2fc)) ## [1.4.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.3.3...nvim-tree-v1.4.0) (2024-06-09) ### Notice * Neovim 0.9 is now the minimum supported version; please upgrade to neovim release version 0.9 or 0.10. ### Reverts * **#2781:** "refactor: replace deprecated use of vim.diagnostic.is_disabled()" ([#2784](https://github.com/nvim-tree/nvim-tree.lua/issues/2784)) ([517e4fb](https://github.com/nvim-tree/nvim-tree.lua/commit/517e4fbb9ef3c0986da7047f44b4b91a2400f93c)) ### Miscellaneous Chores * release 1.4.0 ([1cac800](https://github.com/nvim-tree/nvim-tree.lua/commit/1cac8005df6da484c97499247754afa59fef92db)) ## [1.3.3](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.3.2...nvim-tree-v1.3.3) (2024-05-14) ### Bug Fixes * nil access exception with git integration when changing branches ([#2774](https://github.com/nvim-tree/nvim-tree.lua/issues/2774)) ([340d3a9](https://github.com/nvim-tree/nvim-tree.lua/commit/340d3a9795e06bdd1814228de398cd510f9bfbb0)) ## [1.3.2](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.3.1...nvim-tree-v1.3.2) (2024-05-12) ### Bug Fixes * **#2758:** use nvim-webdevicons default file icon, not renderer.icons.glyphs.default, as per :help ([#2759](https://github.com/nvim-tree/nvim-tree.lua/issues/2759)) ([347e1eb](https://github.com/nvim-tree/nvim-tree.lua/commit/347e1eb35264677f66a79466bb5e3d111968e12c)) * **#2758:** use nvim-webdevicons default for default files ([347e1eb](https://github.com/nvim-tree/nvim-tree.lua/commit/347e1eb35264677f66a79466bb5e3d111968e12c)) * **#925:** handle newlines in file names ([#2754](https://github.com/nvim-tree/nvim-tree.lua/issues/2754)) ([64f61e4](https://github.com/nvim-tree/nvim-tree.lua/commit/64f61e4c913047a045ff90bd188dd3b54ee443cf)) ## [1.3.1](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.3.0...nvim-tree-v1.3.1) (2024-04-25) ### Bug Fixes * **#2535:** TextYankPost event sends vim.v.event ([#2734](https://github.com/nvim-tree/nvim-tree.lua/issues/2734)) ([d8d3a15](https://github.com/nvim-tree/nvim-tree.lua/commit/d8d3a1590a05b2d8b5eb26e2ed1c6052b1b47a77)) * **#2733:** escape trash path ([#2735](https://github.com/nvim-tree/nvim-tree.lua/issues/2735)) ([81eb8d5](https://github.com/nvim-tree/nvim-tree.lua/commit/81eb8d519233c105f30dc0a278607e62b20502fd)) ## [1.3.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.2.0...nvim-tree-v1.3.0) (2024-03-30) ### Features * add update_focused_file.exclude ([#2673](https://github.com/nvim-tree/nvim-tree.lua/issues/2673)) ([e20966a](https://github.com/nvim-tree/nvim-tree.lua/commit/e20966ae558524f8d6f93dc37f5d2a8605f893e2)) ### Bug Fixes * **#2658:** change SpellCap groups to reduce confusion: ExecFile->Question, ImageFile->Question, SpecialFile->Title, Symlink->Underlined; add all other highlight groups to :NvimTreeHiTest ([#2732](https://github.com/nvim-tree/nvim-tree.lua/issues/2732)) ([0aca092](https://github.com/nvim-tree/nvim-tree.lua/commit/0aca0920f44b12a8383134bcb52da9faec123608)) * bookmark filter shows marked directory children ([#2719](https://github.com/nvim-tree/nvim-tree.lua/issues/2719)) ([2d97059](https://github.com/nvim-tree/nvim-tree.lua/commit/2d97059661c83787372c8c003e743c984ba3ac50)) ## [1.2.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.1.1...nvim-tree-v1.2.0) (2024-03-24) ### Features * add api.tree.toggle_enable_filters ([#2706](https://github.com/nvim-tree/nvim-tree.lua/issues/2706)) ([f7c09bd](https://github.com/nvim-tree/nvim-tree.lua/commit/f7c09bd72e50e1795bd3afb9e2a2b157b4bfb3c3)) ## [1.1.1](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.1.0...nvim-tree-v1.1.1) (2024-03-15) ### Bug Fixes * **#2395:** marks.bulk.move defaults to directory at cursor ([#2688](https://github.com/nvim-tree/nvim-tree.lua/issues/2688)) ([cfea5bd](https://github.com/nvim-tree/nvim-tree.lua/commit/cfea5bd0806aab41bef6014c6cf5a510910ddbdb)) * **#2705:** change NvimTreeWindowPicker cterm background from Cyan to more visible DarkBlue ([#2708](https://github.com/nvim-tree/nvim-tree.lua/issues/2708)) ([1fd9c98](https://github.com/nvim-tree/nvim-tree.lua/commit/1fd9c98960463d2d5d400916c0633b2df016941d)) * bookmark filter should include parent directory ([#2704](https://github.com/nvim-tree/nvim-tree.lua/issues/2704)) ([76b9810](https://github.com/nvim-tree/nvim-tree.lua/commit/76b98109f62caa12b2f1dff472060b2233ea2e90)) ## [1.1.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.0.0...nvim-tree-v1.1.0) (2024-03-14) ### Features * **#2630:** file renames can now create directories ([#2657](https://github.com/nvim-tree/nvim-tree.lua/issues/2657)) ([efafd73](https://github.com/nvim-tree/nvim-tree.lua/commit/efafd73efa9bc8c26282aed563ba0f01c7465b06)) * add api.fs.copy.basename, default mapping ge ([#2698](https://github.com/nvim-tree/nvim-tree.lua/issues/2698)) ([8f2a50f](https://github.com/nvim-tree/nvim-tree.lua/commit/8f2a50f1cd0c64003042364cf317c8788eaa6c8c)) ### Bug Fixes * **#2695:** git toplevel guard against missing paths ([#2696](https://github.com/nvim-tree/nvim-tree.lua/issues/2696)) ([3c4267e](https://github.com/nvim-tree/nvim-tree.lua/commit/3c4267eb5045fa86b67fe40c0c63d31efc801e77)) * searchcount exception on invalid search regex ([#2693](https://github.com/nvim-tree/nvim-tree.lua/issues/2693)) ([041dbd1](https://github.com/nvim-tree/nvim-tree.lua/commit/041dbd18f440207ad161503a384e7c82d575db66)) ## [1.0.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v0.100.0...nvim-tree-v1.0.0) (2024-02-18) ### Features * **#2654:** filters.custom may be a function ([#2655](https://github.com/nvim-tree/nvim-tree.lua/issues/2655)) ([4a87b8b](https://github.com/nvim-tree/nvim-tree.lua/commit/4a87b8b46b4a30107971871df3cb7f4c30fdd5d0)) ### Miscellaneous Chores * release 1.0.0 ([#2678](https://github.com/nvim-tree/nvim-tree.lua/issues/2678)) ([d16246a](https://github.com/nvim-tree/nvim-tree.lua/commit/d16246a7575538f77e9246520449b99333c469f7)) ## [0.100.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v0.99.0...nvim-tree-v0.100.0) (2024-02-11) ### Features * **#1389:** api: recursive node navigation for git and diagnostics ([#2525](https://github.com/nvim-tree/nvim-tree.lua/issues/2525)) ([5d13cc8](https://github.com/nvim-tree/nvim-tree.lua/commit/5d13cc8205bce4963866f73c50f6fdc18a515ffe)) * **#2415:** add :NvimTreeHiTest ([#2664](https://github.com/nvim-tree/nvim-tree.lua/issues/2664)) ([b278fc2](https://github.com/nvim-tree/nvim-tree.lua/commit/b278fc25ae0fc95e4808eb5618f07fc2522fd2b3)) * **#2415:** colour and highlight overhaul, see :help nvim-tree-highlight-overhaul ([#2455](https://github.com/nvim-tree/nvim-tree.lua/issues/2455)) ([e9c5abe](https://github.com/nvim-tree/nvim-tree.lua/commit/e9c5abe073a973f54d3ca10bfe30f253569f4405)) * add node.open.toggle_group_empty, default mapping L ([#2647](https://github.com/nvim-tree/nvim-tree.lua/issues/2647)) ([8cbb1db](https://github.com/nvim-tree/nvim-tree.lua/commit/8cbb1db8e90b62fc56f379992e622e9f919792ce)) ### Bug Fixes * **#2415:** disambiguate highlight groups, see :help nvim-tree-highlight-overhaul ([#2639](https://github.com/nvim-tree/nvim-tree.lua/issues/2639)) ([d9cb432](https://github.com/nvim-tree/nvim-tree.lua/commit/d9cb432d2c8d8fa9267ddbd7535d76fe4df89360)) * **#2415:** fix NvimTreeIndentMarker highlight group: FileIcon->FolderIcon ([e9ac136](https://github.com/nvim-tree/nvim-tree.lua/commit/e9ac136a3ab996aa8e4253253521dcf2cb66b81b)) * **#2415:** highlight help header and mappings ([#2669](https://github.com/nvim-tree/nvim-tree.lua/issues/2669)) ([39e6fef](https://github.com/nvim-tree/nvim-tree.lua/commit/39e6fef85ac3bb29532b877aa7c9c34911c661af)) * **#2415:** nvim 0.8 highlight overhaul support, limited to only show highest highlight precedence ([#2642](https://github.com/nvim-tree/nvim-tree.lua/issues/2642)) ([f39f7b6](https://github.com/nvim-tree/nvim-tree.lua/commit/f39f7b6fcd3865ac2146de4cb4045286308f2935)) * **#2415:** NvimTreeIndentMarker highlight group: FileIcon->FolderIcon ([#2656](https://github.com/nvim-tree/nvim-tree.lua/issues/2656)) ([e9ac136](https://github.com/nvim-tree/nvim-tree.lua/commit/e9ac136a3ab996aa8e4253253521dcf2cb66b81b)) * **#2624:** open file from docked floating window ([#2627](https://github.com/nvim-tree/nvim-tree.lua/issues/2627)) ([f24afa2](https://github.com/nvim-tree/nvim-tree.lua/commit/f24afa2cef551122b8bd53bb2e4a7df42343ce2e)) * **#2632:** occasional error stack when locating nvim-tree window ([#2633](https://github.com/nvim-tree/nvim-tree.lua/issues/2633)) ([48b1d86](https://github.com/nvim-tree/nvim-tree.lua/commit/48b1d8638fa3726236ae22e0e48a74ac8ea6592a)) * **#2637:** show buffer modified icons and highlights ([#2638](https://github.com/nvim-tree/nvim-tree.lua/issues/2638)) ([7bdb220](https://github.com/nvim-tree/nvim-tree.lua/commit/7bdb220d0fe604a77361e92cdbc7af1b8a412126)) * **#2643:** correctly apply linked highlight groups in tree window ([#2653](https://github.com/nvim-tree/nvim-tree.lua/issues/2653)) ([fbee8a6](https://github.com/nvim-tree/nvim-tree.lua/commit/fbee8a69a46f558d29ab84e96301425b0501c668)) * allow highlight overrides for DEFAULT_DEFS: NvimTreeFolderIcon, NvimTreeWindowPicker ([#2636](https://github.com/nvim-tree/nvim-tree.lua/issues/2636)) ([74525ac](https://github.com/nvim-tree/nvim-tree.lua/commit/74525ac04760bf0d9fec2bf51474d2b05f36048e)) * bad column offset when using full_name ([#2629](https://github.com/nvim-tree/nvim-tree.lua/issues/2629)) ([75ff64e](https://github.com/nvim-tree/nvim-tree.lua/commit/75ff64e6663fc3b23c72dca32b2f838acefe7c8a)) * passing nil as window handle in view.get_winnr ([48b1d86](https://github.com/nvim-tree/nvim-tree.lua/commit/48b1d8638fa3726236ae22e0e48a74ac8ea6592a)) ## 0.99.0 (2024-01-01) ### Features * **#1850:** add "no bookmark" filter ([#2571](https://github.com/nvim-tree/nvim-tree.lua/issues/2571)) ([8f92e1e](https://github.com/nvim-tree/nvim-tree.lua/commit/8f92e1edd399f839a23776dcc6eee4ba18030370)) * add kind param to vim.ui.select function calls ([#2602](https://github.com/nvim-tree/nvim-tree.lua/issues/2602)) ([dc839a7](https://github.com/nvim-tree/nvim-tree.lua/commit/dc839a72a6496ce22ebd3dd959115cf97c1b20a0)) * add option to skip gitignored files on git navigation ([#2583](https://github.com/nvim-tree/nvim-tree.lua/issues/2583)) ([50f30bc](https://github.com/nvim-tree/nvim-tree.lua/commit/50f30bcd8c62ac4a83d133d738f268279f2c2ce2)) ### Bug Fixes * **#2519:** Diagnostics Not Updated When Tree Not Visible ([#2597](https://github.com/nvim-tree/nvim-tree.lua/issues/2597)) ([96a783f](https://github.com/nvim-tree/nvim-tree.lua/commit/96a783fbd606a458bcce2ef8041240a8b94510ce)) * **#2609:** help toggle ([#2611](https://github.com/nvim-tree/nvim-tree.lua/issues/2611)) ([fac4900](https://github.com/nvim-tree/nvim-tree.lua/commit/fac4900bd18a9fa15be3d104645d9bdef7b3dcec)) * hijack_cursor on update focused file and vim search ([#2600](https://github.com/nvim-tree/nvim-tree.lua/issues/2600)) ([02ae523](https://github.com/nvim-tree/nvim-tree.lua/commit/02ae52357ba4da77a4c120390791584a81d15340)) ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to `nvim-tree.lua` Thank you for contributing. See [wiki: Development](https://github.com/nvim-tree/nvim-tree.lua/wiki/Development) for environment setup, tips and tools. - [Tools](#tools) - [Quality](#quality) * [lint](#lint) * [style](#style) * [format-fix](#format-fix) * [check](#check) * [format-check](#format-check) - [Diagnostics](#diagnostics) - [Backwards Compatibility](#backwards-compatibility) - [:help Documentation](#help-documentation) * [Generated Content](#generated-content) * [Updating And Generating](#updating-and-generating) * [Checking And Linting](#checking-and-linting) - [Windows](#windows) - [Pull Request](#pull-request) * [Subject](#subject) - [AI Usage Policy: Highly Discouraged](#ai-usage-policy-highly-discouraged) * [nvim-tree Is A Community Project](#nvim-tree-is-a-community-project) * [The Burden Of Review Must Not Increase](#the-burden-of-review-must-not-increase) * [AI Generated PR Rules](#ai-generated-pr-rules) # Tools Following are used during CI and strongly recommended during local development. Language server: [luals](https://luals.github.io) Lint: [luacheck](https://github.com/lunarmodules/luacheck/) nvim-tree 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) Formatting: [EmmyLuaCodeStyle](https://github.com/CppCXY/EmmyLuaCodeStyle): `CodeFormat` executable You can install them via you OS package manager e.g. `pacman`, `brew` or other via other package managers such as `cargo` or `luarocks` # Quality The following quality checks are mandatory and are performed during CI. They run on the entire `lua` directory and return 1 on any failure. You can run them all via `make` or `make all` You can setup git hooks to run all checks by running `scripts/setup-hooks.sh` ## lint 1. Runs luacheck quietly using `.luacheck` settings ```sh make lint ``` ## style 1. Runs lua language server `codestyle-check` only, using `.luarc.json` settings 1. Runs `scripts/doc-comments.sh` to normalise annotated documentation ```sh make style ``` ## format-fix You can automatically fix most style issues using `CodeFormat`: ```sh make format-fix ``` ## check 1. Runs the checks that the LSP lua language server runs inside nvim using `.luarc.json` via `scripts/luals-check.sh` ```sh make check ``` Assumes `$VIMRUNTIME` is `/usr/share/nvim/runtime`. Adjust as necessary e.g. ```sh VIMRUNTIME="/my/path/to/runtime" make check ``` If `lua-language-server` is not available or `--check` doesn't function (e.g. Arch Linux 3.9.1-1) you can manually install it as per `ci.yml` using its current `luals_version` e.g. ```sh mkdir luals curl -L "https://github.com/LuaLS/lua-language-server/releases/download/3.15.0/lua-language-server-3.15.0-linux-x64.tar.gz" | tar zx --directory luals PATH="luals/bin:${PATH}" make check ``` ## format-check This is run in CI. Commit or stage your changes and run: ```sh make format-check ``` - Re-runs `make format-fix` - Checks that `git diff` is empty, to ensure that all content has been generated. This is why a stage or commit is necessary. # Diagnostics Diagnostics issues may not be suppressed. See [luals](https://luals.github.io) documentation for details on how to structure the code and comments. Suppressions are permitted only in the following cases: - Backwards compatibility shims - neovim API metadata incorrect, awaiting upstream fix - classic class framework # Backwards Compatibility Whenever new neovim API is introduced, please ensure that it is available in older versions. See `:help deprecated.txt` and `$VIMRUNTIME/lua/vim/_meta/api.lua` See `nvim-tree.setup` for the oldest supported version of neovim. If the API is not availble in that version, a backwards compatibility shim must be used e.g. ```lua if vim.fn.has("nvim-0.10") == 1 then modified = vim.api.nvim_get_option_value("modified", { buf = target_bufid }) else modified = vim.api.nvim_buf_get_option(target_bufid, "modified") ---@diagnostic disable-line: deprecated end ``` # :help Documentation Please update or add to `doc/nvim-tree-lua.txt` as needed. ## Generated Content `doc/nvim-tree-lua.txt` content starting at `*nvim-tree-config*` will be replaced with generated content. Do not manually edit that content. ### API and Config Help is generated for: - `nvim_tree.config` classes from `lua/nvim-tree/_meta/config/` - `nvim_tree.api` functions from `lua/nvim-tree/_meta/api/` Please add or update documentation when you make changes, see `:help dev-lua-doc` for docstring format. `scripts/vimdoc_config.lua` contains the manifest of help sources. ### Config And Mappings Help is updated for: - Default keymap at `keymap.on_attach_default` - Default config at `--- config-default-start` ## Updating And Generating Nvim sources are required. You will be prompted with instructions on fetching and using the sources. See comments at the start of each script for complete details. ```sh make help-update ``` - `scripts/help-defaults.sh` - Update config defaults `*nvim-tree-config-default*` - Update default mappings: - `*nvim-tree-mappings-default*` - `*nvim-tree-quickstart-help*` - `scripts/vimdoc.sh doc` - Remove content starting at `*nvim-tree-config*` - Generate config classes `*nvim-tree-config*` - Generate API `*nvim-tree-api*` ## Checking And Linting This is run in CI. Commit or stage your changes and run: ```sh make help-check ``` - Re-runs `make help-update` - Checks that `git diff` is empty, to ensure that all content has been generated. This is why a stage or commit is necessary. - Lints `doc/nvim-tree-lua.txt` using `scripts/vimdoc.sh lintdoc` to check for no broken links etc. # Windows Please note that nvim-tree team members do not have access to nor expertise with Windows. You will need to be an active participant during development and raise a PR to resolve any issues that may arise. Please ensure that windows specific features and fixes are behind the appropriate feature flag, see [wiki: OS Feature Flags](https://github.com/nvim-tree/nvim-tree.lua/wiki/Development#os-feature-flags) # Pull Request Please reference any issues in the description e.g. "resolves #1234", which will be closed upon merge. Please check "allow edits by maintainers" to allow nvim-tree developers to make small changes such as documentation tweaks. Do not enable or use any AI review tools (e.g. Copilot) on the Pull Request. ## Subject The merge commit message will be the subject of the PR. A [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0) subject will be validated by the Semantic Pull Request Subject CI job. Reference the issue to be used in the release notes e.g. `fix(#2395): marks.bulk.move defaults to directory at cursor` Available types: * feat: A new feature * fix: A bug fix * docs: Documentation only changes * style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) * refactor: A code change that neither fixes a bug nor adds a feature * perf: A code change that improves performance * test: Adding missing tests or correcting existing tests * build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm) * ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs) * chore: Other changes that don't modify src or test files * revert: Reverts a previous commit If in doubt, look at previous commits. See also [The Conventional Commits ultimate cheatsheet](https://gist.github.com/gabrielecanepa/fa6cca1a8ae96f77896fe70ddee65527) # AI Usage Policy: Highly Discouraged ## nvim-tree Is A Community Project nvim-tree is a work of passion, building a community of free thinking individuals who contribute highly polished, elegant and maintainable code. Community members range from novices to professionals and all are welcome. Teaching, encouraging and celebrating the growth of less experienced developers is of great importance. AI generated code is discouraged as this doesn't match these nvim-tree values. Human PR reviews will always be prioritised over AI generated PRs. ## The Burden Of Review Must Not Increase There must be a human in the loop at all times. The contributor is the author of and is fully accountable for AI generated contributions. AI generated PRs have low, or even non-existent entry level. Human generated PRs require you to be motivated, do research, familiarise yourself with code, make effort to write the code and test it. Low effort or unqualified code increases the burden of review and testing on maintainers, who are limited in time. Contributors must: - Read and review all generated code and documentation before raising a PR - Fully understand all code and documentation - Be able to answer any questions during review ## AI Generated PR Rules The PR description and comments must be written by the contributor, with no AI assistance beyond grammar or English translations. The description must: - Describe the solution design, justifying all decisions - Clearly state which AI was used - List which code and documentation was written by a human and which was written by AI - Detail all testing performed There must be far greater than usual number of detailed, explicit comments: - File level: overview of the changes made - Line level: 1-2 comments per function/method Do not enable or use any AI review tools (e.g. Copilot) on the Pull Request. ================================================ FILE: LICENSE ================================================ nvim-tree.lua is a file explorer / filesystem tree view plugin for neovim Copyright © 2019 Yazdani Kiyan This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ================================================ FILE: Makefile ================================================ all: lint style check # # mandatory checks # lint: luacheck style: style-check style-doc check: luals # # subtasks # luacheck: luacheck --codes --quiet lua --exclude-files "**/_meta/**" style-check: scripts/luals-check.sh codestyle-check style-doc: scripts/doc-comments.sh luals: scripts/luals-check.sh # # format # format-fix: CodeFormat format --config .editorconfig --workspace lua format-check: CodeFormat format --config .editorconfig --workspace lua git diff --exit-code lua # # utility # help-update: scripts/vimdoc.sh doc scripts/help-defaults.sh # # CI # --ignore-blank-lines is used as nightly has removed unnecessary blank lines that stable (0.11.5) currently inserts # help-check: help-update scripts/vimdoc.sh lintdoc git diff --ignore-blank-lines --exit-code doc/nvim-tree-lua.txt .PHONY: all lint style check luacheck style-check style-doc luals format-fix format-check help-update help-check ================================================ FILE: README.md ================================================ # A File Explorer For Neovim Written In Lua [![CI](https://github.com/nvim-tree/nvim-tree.lua/actions/workflows/ci.yml/badge.svg)](https://github.com/nvim-tree/nvim-tree.lua/actions/workflows/ci.yml) Automatic updates File type icons Git integration Diagnostics integration: LSP and COC (Live) filtering Cut, copy, paste, rename, delete, create Highly customisable

Take a look at the [wiki](https://github.com/nvim-tree/nvim-tree.lua/wiki) for Showcases, Tips, Recipes and more. Questions and general support: [Discussions](https://github.com/nvim-tree/nvim-tree.lua/discussions) - [Requirements](#requirements) - [Installing](#installing) - [Quick Start](#quick-start) * [Setup](#setup) * [Help](#help) * [Custom Mappings](#custom-mappings) * [Highlight Groups](#highlight-groups) - [Commands](#commands) - [Roadmap](#roadmap) - [API](#api) - [Contributing](#contributing) - [Screenshots](#screenshots) - [Team](#team) # Requirements [neovim >=0.9.0](https://github.com/neovim/neovim/wiki/Installing-Neovim) [nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons) is optional and used to display file icons. It requires a [patched font](https://www.nerdfonts.com/). Your terminal emulator must be configured to use that font, usually "Hack Nerd Font" # Installing Please install via your preferred package manager. See [Installation](https://github.com/nvim-tree/nvim-tree.lua/wiki/Installation) for some specific package manager instructions. Major or minor versions may be specified via tags: `v` e.g. `v1` or `v.` e.g. `v1.23` # Quick Start Install the plugins via your package manager: `"nvim-tree/nvim-tree.lua"` `"nvim-tree/nvim-web-devicons"` Disabling [netrw](https://neovim.io/doc/user/pi_netrw.html) is strongly advised, see [:help nvim-tree-netrw](doc/nvim-tree-lua.txt) ## Setup Setup the plugin in your `init.lua`. See [:help nvim-tree-setup](doc/nvim-tree-lua.txt) and [:help nvim-tree-config-default](doc/nvim-tree-lua.txt) ```lua -- disable netrw at the very start of your init.lua vim.g.loaded_netrw = 1 vim.g.loaded_netrwPlugin = 1 -- optionally enable 24-bit colour vim.opt.termguicolors = true -- empty setup using defaults require("nvim-tree").setup() -- OR setup with a config ---@type nvim_tree.config local config = { sort = { sorter = "case_sensitive", }, view = { width = 30, }, renderer = { group_empty = true, }, filters = { dotfiles = true, }, } require("nvim-tree").setup(config) ``` ## Help Open the tree: `:NvimTreeOpen` Show the mappings: `g?` ## Custom Mappings [:help nvim-tree-mappings-default](doc/nvim-tree-lua.txt) are applied by default however you may customise via [:help nvim_tree.config](doc/nvim-tree-lua.txt) `{on_attach}` e.g. ```lua local function my_on_attach(bufnr) local api = require "nvim-tree.api" local function opts(desc) return { desc = "nvim-tree: " .. desc, buffer = bufnr, noremap = true, silent = true, nowait = true } end -- default mappings api.map.on_attach.default(bufnr) -- custom mappings vim.keymap.set("n", "", api.tree.change_root_to_parent, opts("Up")) vim.keymap.set("n", "?", api.tree.toggle_help, opts("Help")) end -- pass to setup along with your other config require("nvim-tree").setup({ --- on_attach = my_on_attach, --- }) ``` ## Highlight Groups See [:help nvim-tree-highlight-groups](doc/nvim-tree-lua.txt) Run `:NvimTreeHiTest` to show all the highlights that nvim-tree uses. They can be customised before or after setup is called and will be immediately applied at runtime. e.g. ```lua vim.cmd([[ :hi NvimTreeExecFile guifg=#ffa0a0 :hi NvimTreeSpecialFile guifg=#ff80ff gui=underline :hi NvimTreeSymlink guifg=Yellow gui=italic :hi link NvimTreeImageFile Title ]]) ``` # Commands See [:help nvim-tree-commands](doc/nvim-tree-lua.txt) Some commands may be executed with a bang `!` or take a `path` string argument. All commands execute public API. Some basic commands: `:NvimTreeFocus` [:help nvim_tree.api.tree.open()](doc/nvim-tree-lua.txt) ```lua require("nvim-tree.api").tree.open() ``` `:NvimTreeToggle` [:help nvim_tree.api.tree.toggle()](doc/nvim-tree-lua.txt) ```lua require("nvim-tree.api").tree.toggle({ path = "", find_file = false, update_root = false, focus = true, }) ``` `:NvimTreeFindFile` [:help nvim_tree.api.tree.find_file()](doc/nvim-tree-lua.txt) ```lua require("nvim-tree.api").tree.find_file({ open = true, update_root = "", focus = true, }) ``` `:NvimTreeCollapse` [:help nvim_tree.api.tree.collapse_all()](doc/nvim-tree-lua.txt) ```lua require("nvim-tree.api").tree.collapse_all({ keep_buffers = false }) ``` # Roadmap nvim-tree is stable and new major features will not be added. The focus is on existing user experience. Users are encouraged to add their own custom features via the public [API](#api). Development is focused on: - Bug fixes - Performance - Quality of Life improvements - API / Events - Enhancements to existing features - Multi-instance capabilities # API nvim-tree exposes a public API. This is non breaking, with additions made as necessary. See [:help nvim-tree-api](doc/nvim-tree-lua.txt) See wiki [Recipes](https://github.com/nvim-tree/nvim-tree.lua/wiki/Recipes) and [Tips](https://github.com/nvim-tree/nvim-tree.lua/wiki/Tips) for ideas and inspiration. Please raise a [feature request](https://github.com/nvim-tree/nvim-tree.lua/issues/new?assignees=&labels=feature+request&template=feature_request.md&title=) if the API is insufficient for your needs. Contributions 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) # Contributing 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. # Screenshots See [Showcases](https://github.com/nvim-tree/nvim-tree.lua/wiki/Showcases) wiki page for examples of user's configurations with sources. Please add your own! # Team * [@alex-courtis](https://github.com/alex-courtis) Arch Linux * [@gegoune](https://github.com/gegoune) macOS * [@Akmadan23](https://github.com/Akmadan23) Linux * [@dependabot[bot]](https://github.com/apps/dependabot) Ubuntu Linux ================================================ FILE: doc/.gitignore ================================================ tags ================================================ FILE: doc/nvim-tree-lua.txt ================================================ *nvim-tree-lua.txt* A File Explorer For Nvim *nvim_tree* *nvim-tree* Author: Yazdani Kiyan Type |gO| to see the table of contents. Help tag prefixes: • `nvim-tree-` Help Sections e.g. • |nvim-tree-mappings| • |nvim-tree-config| • `nvim_tree.` API and classes e.g. • |nvim_tree.api.node.navigate.parent()| • |nvim_tree.config.filesystem_watchers| ============================================================================== Introduction *nvim-tree-introduction* Features - Automatic updates - File type icons - Git integration - Diagnostics integration: LSP and COC - (Live) filtering - Cut, copy, paste, rename, delete, create - Highly customisable File Icons https://github.com/nvim-tree/nvim-web-devicons is optional and used to display file icons. It requires a patched font: https://www.nerdfonts.com Your terminal emulator must be configured to use that font, usually "Hack Nerd Font"  should look like an open folder. Disable the display of icons with |nvim_tree.config.renderer.icons.show| Colours Syntax highlighting uses g:terminal_color_ from colorschemes, falls back to ugly colors otherwise. Git Integration One or two icons for git status. When two are shown, the left is staged. ✗ unstaged ✓ staged  unmerged ➜ renamed ★ untracked  deleted ◌ ignored Requirements Nvim >= 0.9 ============================================================================== Quickstart *nvim-tree-quickstart* Install the plugins via your package manager: `"nvim-tree/nvim-tree.lua"` `"nvim-tree/nvim-web-devicons"` Disabling |netrw| is strongly advised, see |nvim-tree-netrw| ============================================================================== Quickstart: Setup *nvim-tree-quickstart-setup* Setup the plugin in your `init.lua`. See |nvim-tree-setup| and |nvim-tree-config-default| >lua -- disable netrw at the very start of your init.lua vim.g.loaded_netrw = 1 vim.g.loaded_netrwPlugin = 1 -- optionally enable 24-bit colour vim.opt.termguicolors = true -- empty setup using defaults require("nvim-tree").setup() -- OR setup with a config ---@type nvim_tree.config local config = { sort = { sorter = "case_sensitive", }, view = { width = 30, }, renderer = { group_empty = true, }, filters = { dotfiles = true, }, } require("nvim-tree").setup(config) < ============================================================================== Quickstart: Help *nvim-tree-quickstart-help* Open the tree: `:NvimTreeOpen` Show the mappings: `g?` `` n CD |nvim_tree.api.tree.change_root_to_node()| `` n Open: In Place |nvim_tree.api.node.open.replace_tree_buffer()| `` n Info |nvim_tree.api.node.show_info_popup()| `` n Rename: Omit Filename |nvim_tree.api.fs.rename_sub()| `` n Open: New Tab |nvim_tree.api.node.open.tab()| `` n Open: Vertical Split |nvim_tree.api.node.open.vertical()| `` n Open: Horizontal Split |nvim_tree.api.node.open.horizontal()| `` n Close Directory |nvim_tree.api.node.navigate.parent_close()| `` n Open |nvim_tree.api.node.open.edit()| `` nx Delete |nvim_tree.api.fs.remove()| `` n Open Preview |nvim_tree.api.node.open.preview()| `>` n Next Sibling |nvim_tree.api.node.navigate.sibling.next()| `<` n Previous Sibling |nvim_tree.api.node.navigate.sibling.prev()| `.` n Run Command |nvim_tree.api.node.run.cmd()| `-` n Up |nvim_tree.api.tree.change_root_to_parent()| `a` n Create File Or Directory |nvim_tree.api.fs.create()| `bd` n Delete Bookmarked |nvim_tree.api.marks.bulk.delete()| `bt` n Trash Bookmarked |nvim_tree.api.marks.bulk.trash()| `bmv` n Move Bookmarked |nvim_tree.api.marks.bulk.move()| `B` n Toggle Filter: No Buffer |nvim_tree.api.filter.no_buffer.toggle()| `c` nx Copy |nvim_tree.api.fs.copy.node()| `C` n Toggle Filter: Git Clean |nvim_tree.api.filter.git.clean.toggle()| `[c` n Prev Git |nvim_tree.api.node.navigate.git.prev()| `]c` n Next Git |nvim_tree.api.node.navigate.git.next()| `d` nx Delete |nvim_tree.api.fs.remove()| `D` nx Trash |nvim_tree.api.fs.trash()| `E` n Expand All |nvim_tree.api.tree.expand_all()| `e` n Rename: Basename |nvim_tree.api.fs.rename_basename()| `]e` n Next Diagnostic |nvim_tree.api.node.navigate.diagnostics.next()| `[e` n Prev Diagnostic |nvim_tree.api.node.navigate.diagnostics.prev()| `F` n Live Filter: Clear |nvim_tree.api.filter.live.clear()| `f` n Live Filter: Start |nvim_tree.api.filter.live.start()| `g?` n Help |nvim_tree.api.tree.toggle_help()| `gy` n Copy Absolute Path |nvim_tree.api.fs.copy.absolute_path()| `ge` n Copy Basename |nvim_tree.api.fs.copy.basename()| `H` n Toggle Filter: Dotfiles |nvim_tree.api.filter.dotfiles.toggle()| `I` n Toggle Filter: Git Ignored |nvim_tree.api.filter.git.ignored.toggle()| `J` n Last Sibling |nvim_tree.api.node.navigate.sibling.last()| `K` n First Sibling |nvim_tree.api.node.navigate.sibling.first()| `L` n Toggle Group Empty |nvim_tree.api.node.open.toggle_group_empty()| `M` n Toggle Filter: No Bookmark |nvim_tree.api.filter.no_bookmark.toggle()| `m` nx Toggle Bookmark |nvim_tree.api.marks.toggle()| `o` n Open |nvim_tree.api.node.open.edit()| `O` n Open: No Window Picker |nvim_tree.api.node.open.no_window_picker()| `p` n Paste |nvim_tree.api.fs.paste()| `P` n Parent Directory |nvim_tree.api.node.navigate.parent()| `q` n Close |nvim_tree.api.tree.close()| `r` n Rename |nvim_tree.api.fs.rename()| `R` n Refresh |nvim_tree.api.tree.reload()| `s` n Run System |nvim_tree.api.node.run.system()| `S` n Search |nvim_tree.api.tree.search_node()| `u` n Rename: Full Path |nvim_tree.api.fs.rename_full()| `U` n Toggle Filter: Custom |nvim_tree.api.filter.custom.toggle()| `W` n Collapse All |nvim_tree.api.tree.collapse_all()| `x` nx Cut |nvim_tree.api.fs.cut()| `y` n Copy Name |nvim_tree.api.fs.copy.filename()| `Y` n Copy Relative Path |nvim_tree.api.fs.copy.relative_path()| `<2-LeftMouse>` n Open |nvim_tree.api.node.open.edit()| `<2-RightMouse>` n CD |nvim_tree.api.tree.change_root_to_node()| ============================================================================== Quickstart: Custom Mappings *nvim-tree-quickstart-custom-mappings* |nvim-tree-mappings-default| are applied by default however you may customise via |nvim_tree.config| {on_attach} e.g. >lua local function my_on_attach(bufnr) local api = require "nvim-tree.api" local function opts(desc) return { desc = "nvim-tree: " .. desc, buffer = bufnr, noremap = true, silent = true, nowait = true } end -- default mappings api.map.on_attach.default(bufnr) -- custom mappings vim.keymap.set("n", "", api.tree.change_root_to_parent, opts("Up")) vim.keymap.set("n", "?", api.tree.toggle_help, opts("Help")) end -- pass to setup along with your other config require("nvim-tree").setup({ --- on_attach = my_on_attach, --- }) < ============================================================================== Quickstart: Highlight Groups *nvim-tree-quickstart-highlight* Run |:NvimTreeHiTest| to show all the highlights that nvim-tree uses. They can be customised before or after setup is called and will be immediately applied at runtime. e.g. >lua vim.cmd([[ :hi NvimTreeExecFile guifg=#ffa0a0 :hi NvimTreeSpecialFile guifg=#ff80ff gui=underline :hi NvimTreeSymlink guifg=Yellow gui=italic :hi link NvimTreeImageFile Title ]]) < See |nvim-tree-highlight-groups| for details. ============================================================================== Commands *nvim-tree-commands* Some commands may be executed with a || `!` or take a `path` string argument. All commands execute public API. *:NvimTreeOpen* |nvim_tree.api.tree.open()| >lua require("nvim-tree.api").tree.open({ path = "" }) < *:NvimTreeClose* |nvim_tree.api.tree.close()| >lua require("nvim-tree.api").tree.close() < *:NvimTreeToggle* |nvim_tree.api.tree.toggle()| >lua require("nvim-tree.api").tree.toggle({ path = "", find_file = false, update_root = false, focus = true, }) < *:NvimTreeFocus* |nvim_tree.api.tree.open()| >lua require("nvim-tree.api").tree.open() < *:NvimTreeRefresh* |nvim_tree.api.tree.reload()| >lua require("nvim-tree.api").tree.reload() < *:NvimTreeFindFile* |nvim_tree.api.tree.find_file()| >lua require("nvim-tree.api").tree.find_file({ open = true, update_root = "", focus = true, }) < *:NvimTreeFindFileToggle* |nvim_tree.api.tree.toggle()| >lua require("nvim-tree.api").tree.toggle({ path = "", find_file = true, update_root = "", focus = true, }) < *:NvimTreeClipboard* |nvim_tree.api.fs.print_clipboard()| >lua require("nvim-tree.api").fs.print_clipboard() < *:NvimTreeCollapse* |nvim_tree.api.tree.collapse_all()| >lua require("nvim-tree.api").tree.collapse_all({ keep_buffers = false }) < *:NvimTreeCollapseKeepBuffers* |nvim_tree.api.tree.collapse_all()| >lua require("nvim-tree.api").tree.collapse_all({ keep_buffers = true }) < *:NvimTreeHiTest* |nvim_tree.api.appearance.hi_test()| >lua require("nvim-tree.api").appearance.hi_test() < *:NvimTreeResize* |nvim_tree.api.tree.resize()| >lua local sign = c.args:sub(1, 1) if sign == "+" or sign == "-" then require("nvim-tree.api").tree.resize({ relative = tonumber(c.args) }) else require("nvim-tree.api").tree.resize({ absolute = tonumber(c.args) }) end < ============================================================================== Setup *nvim-tree-setup* You must run the `setup()` function once to initialise nvim-tree. It may be called again to apply a change in configuration without restarting Nvim. The `setup()` function takes one optional argument: |nvim_tree.config|. If omitted nvim-tree will be initialised with default configuration: |nvim-tree-config-default|. Config can be validated with |lsp| when passed directly e.g. >lua require("nvim-tree").setup({ hijack_cursor = true, }) < or as a typed variable e.g. >lua ---@type nvim_tree.config local config = { hijack_cursor = true, } require("nvim-tree").setup(config) < The first `setup()` call is cheap: it does nothing more than validate / apply the configuration. Nothing happens until the tree is first opened. Subsequent `setup()` calls are expensive as they tear down the world before applying configuration. ============================================================================== Mappings *nvim-tree-mappings* Mappings are set via the |nvim_tree.config| {on_attach} function, which is run upon creating the nvim-tree buffer. Mappings are usually |nvim-tree-api| functions however may be your own. When {on_attach} is not a function, |nvim-tree-mappings-default| will be used. Active mappings may be viewed via HELP, default `g?`. The mapping's description is used when displaying HELP. The `on_attach` function is passed the `bufnr` of nvim-tree. Use |vim.keymap.set()| or |nvim_set_keymap()| to define mappings as usual. e.g. >lua local function my_on_attach(bufnr) local api = require("nvim-tree.api") local function opts(desc) return { desc = "nvim-tree: " .. desc, buffer = bufnr, noremap = true, silent = true, nowait = true } end -- copy default mappings here from defaults in next section vim.keymap.set("n", "", api.tree.change_root_to_node, opts("CD")) vim.keymap.set("n", "", api.node.open.replace_tree_buffer, opts("Open: In Place")) --- -- OR use all default mappings api.map.on_attach.default(bufnr) -- remove a default vim.keymap.del("n", "", { buffer = bufnr }) -- override a default vim.keymap.set("n", "", api.tree.reload, opts("Refresh")) -- add your mappings vim.keymap.set("n", "?", api.tree.toggle_help, opts("Help")) --- end require("nvim-tree").setup({ --- on_attach = my_on_attach, --- }) < Single left mouse mappings can be achieved via ``. Single right / middle mouse mappings will require changes to |'mousemodel'| or |'mouse'|. |vim.keymap.set()| {rhs} is a `(function|string)` thus it may be necessary to define your own function to map complex functionality e.g. >lua local function print_node_path() local api = require("nvim-tree.api") local node = api.tree.get_node_under_cursor() print(node.absolute_path) end -- on_attach vim.keymap.set("n", "", print_node_path, opts("Print Path")) < ============================================================================== Mappings: Default *nvim-tree-mappings-default* In the absence of an |nvim_tree.config| {on_attach} function, the following defaults will be applied. You are encouraged to copy these to your {on_attach} function. >lua local api = require("nvim-tree.api") local function opts(desc) return { desc = "nvim-tree: " .. desc, buffer = bufnr, noremap = true, silent = true, nowait = true } end -- BEGIN_ON_ATTACH_DEFAULT vim.keymap.set("n", "", api.tree.change_root_to_node, opts("CD")) vim.keymap.set("n", "", api.node.open.replace_tree_buffer, opts("Open: In Place")) vim.keymap.set("n", "", api.node.show_info_popup, opts("Info")) vim.keymap.set("n", "", api.fs.rename_sub, opts("Rename: Omit Filename")) vim.keymap.set("n", "", api.node.open.tab, opts("Open: New Tab")) vim.keymap.set("n", "", api.node.open.vertical, opts("Open: Vertical Split")) vim.keymap.set("n", "", api.node.open.horizontal, opts("Open: Horizontal Split")) vim.keymap.set("n", "", api.node.navigate.parent_close, opts("Close Directory")) vim.keymap.set("n", "", api.node.open.edit, opts("Open")) vim.keymap.set({ "n", "x" }, "", api.fs.remove, opts("Delete")) vim.keymap.set("n", "", api.node.open.preview, opts("Open Preview")) vim.keymap.set("n", ">", api.node.navigate.sibling.next, opts("Next Sibling")) vim.keymap.set("n", "<", api.node.navigate.sibling.prev, opts("Previous Sibling")) vim.keymap.set("n", ".", api.node.run.cmd, opts("Run Command")) vim.keymap.set("n", "-", api.tree.change_root_to_parent, opts("Up")) vim.keymap.set("n", "a", api.fs.create, opts("Create File Or Directory")) vim.keymap.set("n", "bd", api.marks.bulk.delete, opts("Delete Bookmarked")) vim.keymap.set("n", "bt", api.marks.bulk.trash, opts("Trash Bookmarked")) vim.keymap.set("n", "bmv", api.marks.bulk.move, opts("Move Bookmarked")) vim.keymap.set("n", "B", api.filter.no_buffer.toggle, opts("Toggle Filter: No Buffer")) vim.keymap.set({ "n", "x" }, "c", api.fs.copy.node, opts("Copy")) vim.keymap.set("n", "C", api.filter.git.clean.toggle, opts("Toggle Filter: Git Clean")) vim.keymap.set("n", "[c", api.node.navigate.git.prev, opts("Prev Git")) vim.keymap.set("n", "]c", api.node.navigate.git.next, opts("Next Git")) vim.keymap.set({ "n", "x" }, "d", api.fs.remove, opts("Delete")) vim.keymap.set({ "n", "x" }, "D", api.fs.trash, opts("Trash")) vim.keymap.set("n", "E", api.tree.expand_all, opts("Expand All")) vim.keymap.set("n", "e", api.fs.rename_basename, opts("Rename: Basename")) vim.keymap.set("n", "]e", api.node.navigate.diagnostics.next, opts("Next Diagnostic")) vim.keymap.set("n", "[e", api.node.navigate.diagnostics.prev, opts("Prev Diagnostic")) vim.keymap.set("n", "F", api.filter.live.clear, opts("Live Filter: Clear")) vim.keymap.set("n", "f", api.filter.live.start, opts("Live Filter: Start")) vim.keymap.set("n", "g?", api.tree.toggle_help, opts("Help")) vim.keymap.set("n", "gy", api.fs.copy.absolute_path, opts("Copy Absolute Path")) vim.keymap.set("n", "ge", api.fs.copy.basename, opts("Copy Basename")) vim.keymap.set("n", "H", api.filter.dotfiles.toggle, opts("Toggle Filter: Dotfiles")) vim.keymap.set("n", "I", api.filter.git.ignored.toggle, opts("Toggle Filter: Git Ignored")) vim.keymap.set("n", "J", api.node.navigate.sibling.last, opts("Last Sibling")) vim.keymap.set("n", "K", api.node.navigate.sibling.first, opts("First Sibling")) vim.keymap.set("n", "L", api.node.open.toggle_group_empty, opts("Toggle Group Empty")) vim.keymap.set("n", "M", api.filter.no_bookmark.toggle, opts("Toggle Filter: No Bookmark")) vim.keymap.set({ "n", "x" }, "m", api.marks.toggle, opts("Toggle Bookmark")) vim.keymap.set("n", "o", api.node.open.edit, opts("Open")) vim.keymap.set("n", "O", api.node.open.no_window_picker, opts("Open: No Window Picker")) vim.keymap.set("n", "p", api.fs.paste, opts("Paste")) vim.keymap.set("n", "P", api.node.navigate.parent, opts("Parent Directory")) vim.keymap.set("n", "q", api.tree.close, opts("Close")) vim.keymap.set("n", "r", api.fs.rename, opts("Rename")) vim.keymap.set("n", "R", api.tree.reload, opts("Refresh")) vim.keymap.set("n", "s", api.node.run.system, opts("Run System")) vim.keymap.set("n", "S", api.tree.search_node, opts("Search")) vim.keymap.set("n", "u", api.fs.rename_full, opts("Rename: Full Path")) vim.keymap.set("n", "U", api.filter.custom.toggle, opts("Toggle Filter: Custom")) vim.keymap.set("n", "W", api.tree.collapse_all, opts("Collapse All")) vim.keymap.set({ "n", "x" }, "x", api.fs.cut, opts("Cut")) vim.keymap.set("n", "y", api.fs.copy.filename, opts("Copy Name")) vim.keymap.set("n", "Y", api.fs.copy.relative_path, opts("Copy Relative Path")) vim.keymap.set("n", "<2-LeftMouse>", api.node.open.edit, opts("Open")) vim.keymap.set("n", "<2-RightMouse>", api.tree.change_root_to_node, opts("CD")) -- END_ON_ATTACH_DEFAULT < Alternatively, you may apply these default mappings from your |nvim_tree.config| {on_attach} via |nvim_tree.api.map.on_attach.default()| e.g. >lua local function my_on_attach(bufnr) local api = require("nvim-tree.api") local function opts(desc) return { desc = "nvim-tree: " .. desc, buffer = bufnr, noremap = true, silent = true, nowait = true } end api.map.on_attach.default(bufnr) -- your removals and mappings go here end < ============================================================================== Icons And Highlighting *nvim-tree-icons-highlighting* Icons may be displayed before files and directories. Additional icons and highlighting may be displayed to indicate various states for files and and directories. Highlighting is additive. Decorators are responsible for providing the icons and highlighting. They apply additively in order of precedence. `DECORATOR` See |nvim_tree.config.renderer.decorator| for available decorators and their default precedence. `ICON?` Enable via |nvim_tree.config.renderer.icons.show `REQUIRES` Features that must be enabled to show icons and highlighting. `PLACEMENT` Where to place icons: |nvim_tree.config.renderer.icons.placement| `HIGHLIGHT` What should be highlighted: |nvim_tree.config.renderer.highlight| `DEVICONS?` Glyphs and their colors will be overridden by optional plugin: `nvim-tree/nvim-web-devicons` See |nvim_tree.config.renderer.icons.web_devicons| `GLYPHS` Icon glyphs definitions. `GROUPS` Applicable highlight groups: |nvim-tree-highlight-groups| Some defaults noted. In ascending order of default decorator additive precedence: `WHAT DECORATOR ICON? REQUIRES PLACEMENT HIGHLIGHT GLYPHS DEVICONS? GROUPS` File Icon - {file} Y - - - |nvim_tree.config.renderer.icons.glyphs| {default} Y `NvimTreeNormal`, `NvimTreeFileIcon` Folder Icon - {folder} Y - - - |nvim_tree.config.renderer.icons.glyphs.folder| Y `NvimTree*FolderName`, `NvimTree*FolderIcon` Git Status `"Git"` {git} Y |nvim_tree.config.git| {git_placement} `"before"` {highlight_git} `"none"` |nvim_tree.config.renderer.icons.glyphs.git| N `NvimTreeGit*` |bufloaded()| `"Open"` - - - {highlight_opened_files}`"none"` - N ` NvimTreeOpened*` Dotfiles `"Hidden"` {hidden} N - {hidden_placement} `"after"` {highlight_hidden} `"none"` |nvim_tree.config.renderer.icons.glyphs| {hidden} N `NvimTreeHidden*` |'modified'| `"Modified"` {modified} Y |nvim_tree.config.modified| {modified_placement} `"after"` {highlight_modified} `"none"` |nvim_tree.config.renderer.icons.glyphs| {modified} N `NvimTreeModified*` Bookmarked `"Bookmark"` {bookmarks} Y - {bookmarks_placement} `"signcolumn"` {highlight_bookmarks} `"none"` |nvim_tree.config.renderer.icons.glyphs| {bookmark} N `NvimTreeBookmark*` Diag Status ` "Diagnostics"` {diagnostics}Y |nvim_tree.config.diagnostics| {diagnostics_placement}`"signcolumn"` {highlight_diagnostics} `"none" ` |nvim_tree.config.diagnostics.icons| N `NvimTreeDiagnostic*` Copied `"Copied"` - - - {highlight_clipboard} `"name"` - N `NvimTreeCopiedHL` Cut `"Cut"` - - - {highlight_clipboard} `"name"` - N `NvimTreeCutHL` ============================================================================== Highlight Groups *nvim-tree-highlight-groups* All the following highlight groups can be configured by hand. Aside from `NvimTreeWindowPicker`, it is not advised to colorize the background of these groups. Example |:highlight| >vim :hi NvimTreeSymlink guifg=blue gui=bold,underline < It is recommended to enable |'termguicolors'| for the more pleasant 24-bit colours. To view the nvim-tree highlight groups run |:NvimTreeHiTest| To view all active highlight groups run `:so $VIMRUNTIME/syntax/hitest.vim` as per |:highlight| The `*HL` groups are additive, following |nvim_tree.config.renderer.decorator| precedence. Only present attributes will clobber each other. In this example a modified, opened file will have magenta text, with cyan undercurl: >vim :hi NvimTreeOpenedHL guifg=magenta guisp=red gui=underline :hi NvimTreeModifiedFileHL guisp=cyan gui=undercurl < To prevent usage of a highlight: - Before setup: link the group to `Normal` e.g. >vim :hi NvimTreeExecFile Normal < - After setup: link it to `NONE`, to override the default link e.g. >lua :hi! link NvimTreeExecFile NONE < ============================================================================== Highlight Groups: Default *nvim-tree-highlight-groups-default* |:highlight-link| `default` or |:highlight-default| define the groups on setup: Standard: > NvimTreeNormal Normal NvimTreeNormalFloat NormalFloat NvimTreeNormalNC NormalFloat NvimTreeLineNr LineNr NvimTreeWinSeparator WinSeparator NvimTreeEndOfBuffer EndOfBuffer NvimTreePopup Normal NvimTreeSignColumn NvimTreeNormal NvimTreeCursorColumn CursorColumn NvimTreeCursorLine CursorLine NvimTreeCursorLineNr CursorLineNr NvimTreeStatusLine StatusLine NvimTreeStatusLineNC StatusLineNC < File Text: > NvimTreeExecFile SpellCap NvimTreeImageFile SpellCap NvimTreeSpecialFile SpellCap NvimTreeSymlink SpellCap < Folder Text: > NvimTreeRootFolder Title NvimTreeFolderName Directory NvimTreeEmptyFolderName Directory NvimTreeOpenedFolderName Directory NvimTreeSymlinkFolderName Directory < File Icons: > NvimTreeFileIcon NvimTreeNormal NvimTreeSymlinkIcon NvimTreeNormal < Folder Icons: > NvimTreeFolderIcon guifg=#8094b4 ctermfg=Blue NvimTreeOpenedFolderIcon NvimTreeFolderIcon NvimTreeClosedFolderIcon NvimTreeFolderIcon NvimTreeFolderArrowClosed NvimTreeIndentMarker NvimTreeFolderArrowOpen NvimTreeIndentMarker < Indent: > NvimTreeIndentMarker NvimTreeFileIcon < Picker: > NvimTreeWindowPicker guifg=#ededed guibg=#4493c8 gui=bold ctermfg=White ctermbg=Cyan < Live Filter: > NvimTreeLiveFilterPrefix PreProc NvimTreeLiveFilterValue ModeMsg < Clipboard: > NvimTreeCopiedHL SpellRare NvimTreeCutHL SpellBad < Bookmarks: > NvimTreeBookmarkIcon NvimTreeFolderIcon NvimTreeBookmarkHL SpellLocal < Modified: > NvimTreeModifiedIcon Type NvimTreeModifiedFileHL NvimTreeModifiedIcon NvimTreeModifiedFolderHL NvimTreeModifiedIcon Hidden: > NvimTreeModifiedIcon Conceal NvimTreeModifiedFileHL NvimTreeHiddenIcon NvimTreeModifiedFolderHL NvimTreeHiddenFileHL < Hidden Display: > NvimTreeHiddenDisplay Conceal < Opened: > NvimTreeOpenedHL Special < Git Icon: > NvimTreeGitDeletedIcon Statement NvimTreeGitDirtyIcon Statement NvimTreeGitIgnoredIcon Comment NvimTreeGitMergeIcon Constant NvimTreeGitNewIcon PreProc NvimTreeGitRenamedIcon PreProc NvimTreeGitStagedIcon Constant < Git File File Highlight: > NvimTreeGitFileDeletedHL NvimTreeGitDeletedIcon NvimTreeGitFileDirtyHL NvimTreeGitDirtyIcon NvimTreeGitFileIgnoredHL NvimTreeGitIgnoredIcon NvimTreeGitFileMergeHL NvimTreeGitMergeIcon NvimTreeGitFileNewHL NvimTreeGitNewIcon NvimTreeGitFileRenamedHL NvimTreeGitRenamedIcon NvimTreeGitFileStagedHL NvimTreeGitStagedIcon < Git Folder Folder Highlight: > NvimTreeGitFolderDeletedHL NvimTreeGitFileDeletedHL NvimTreeGitFolderDirtyHL NvimTreeGitFileDirtyHL NvimTreeGitFolderIgnoredHL NvimTreeGitFileIgnoredHL NvimTreeGitFolderMergeHL NvimTreeGitFileMergeHL NvimTreeGitFolderNewHL NvimTreeGitFileNewHL NvimTreeGitFolderRenamedHL NvimTreeGitFileRenamedHL NvimTreeGitFolderStagedHL NvimTreeGitFileStagedHL < Diagnostics Icon: > NvimTreeDiagnosticErrorIcon DiagnosticError NvimTreeDiagnosticWarnIcon DiagnosticWarn NvimTreeDiagnosticInfoIcon DiagnosticInfo NvimTreeDiagnosticHintIcon DiagnosticHint < Diagnostics File Highlight: > NvimTreeDiagnosticErrorFileHL DiagnosticUnderlineError NvimTreeDiagnosticWarnFileHL DiagnosticUnderlineWarn NvimTreeDiagnosticInfoFileHL DiagnosticUnderlineInfo NvimTreeDiagnosticHintFileHL DiagnosticUnderlineHint < Diagnostics Folder Highlight: > NvimTreeDiagnosticErrorFolderHL NvimTreeDiagnosticErrorFileHL NvimTreeDiagnosticWarnFolderHL NvimTreeDiagnosticWarnFileHL NvimTreeDiagnosticInfoFolderHL NvimTreeDiagnosticInfoFileHL NvimTreeDiagnosticHintFolderHL NvimTreeDiagnosticHintFileHL < ============================================================================== Events *nvim-tree-events* nvim-tree will dispatch events whenever an action is made. These events can be subscribed to through handler functions. This allows for even further customization of nvim-tree. A handler for an event is just a function which receives one argument, the payload of the event. The payload is different for each event type. Refer to |nvim_tree_registering_handlers| for more information. *nvim_tree_registering_handlers* Handlers are registered by calling |nvim_tree.api.events.subscribe()| function with an |nvim_tree_events_kind|. e.g. handler for node renamed: >lua local api = require("nvim-tree.api") local Event = api.events.Event api.events.subscribe(Event.NodeRenamed, function(data) print("Node renamed from " .. data.old_name .. " to " .. data.new_name) end) < *nvim_tree_events_kind* - Event.Ready 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` • Note: Handler takes no parameter. - Event.TreeOpen Invoked after the NvimTree is opened. • Note: Handler takes no parameter. - 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. - Event.Resize - When NvimTree is resized. handler parameters: ~ size: `number` size of the view in columns. - Event.WillRenameNode • Note: A node can either be a file or a directory. handler parameters: ~ {old_name} `{string}` Absolute path to the old node location. {new_name} `{string}` Absolute path to the new node location. - Event.NodeRenamed • Note: A node can either be a file or a directory. handler parameters: ~ {old_name} `{string}` Absolute path to the old node location. {new_name} `{string}` Absolute path to the new node location. - Event.FileCreated handler parameters: ~ {fname} `{string}` Absolute path to the created file - Event.WillCreateFile handler parameters: ~ {fname} `{string}` Absolute path to the file to be created - Event.FileRemoved handler parameters: ~ {fname} `{string}` Absolute path to the removed file. - Event.WillRemoveFile handler parameters: ~ {fname} `{string}` Absolute path to the file to be removed - Event.FolderCreated handler parameters: ~ {folder_name} `{string}` Absolute path to the created folder. - Event.FolderRemoved handler parameters: ~ {folder_name} `{string}` Absolute path to the removed folder. - Event.TreeAttachedPost Invoked after the tree's buffer has been created and mappings have been applied: |nvim-tree-mappings| or |nvim_tree.config| {on_attach} handler parameters: ~ {buf} `{number} `API buffer handle (buffer number) - Event.TreeRendered Invoked every time the tree is redrawn. Normally this event happens after `Event.TreeOpen` except that handlers of this one will have access to the tree buffer populated with the final content. handler parameters: ~ {bufnr} `{number} `API buffer handle (buffer number) {winnr} `{number} `API window handle (window number) *nvim_tree_events_startup* There are two special startup events in the form of User autocommands: `NvimTreeRequired` first `require("nvim-tree")` `NvimTreeSetup` `setup({})` completed Immediately before firing: a global variable of the same name will be set to a value of 1. Example subscription: >lua vim.api.nvim_create_autocmd("User", { pattern = "NvimTreeRequired", callback = function(data) --- end, }) < ============================================================================== Prompts *nvim-tree-prompts* Some NvimTree actions use the builtin |vim.ui.select()| prompt API for confirmations when the |nvim_tree.config| {select_prompts} option is set. The API accepts the optional `kind` key as part of the {opts} parameter, which can can be used to identify the type of prompt, to allow user side configurations for different types of prompts. - `nvimtree_overwrite_rename` overwrite or rename during |nvim_tree.api.fs.paste()| - `nvimtree_remove` delete during |nvim_tree.api.fs.remove()| - `nvimtree_trash` send to trash during |nvim_tree.api.fs.trash()| - `nvimtree_bulk_delete` delete all bookmarked during |nvim_tree.api.marks.bulk.delete()| - `nvimtree_bulk_trash` send all bookmarked to trash during |nvim_tree.api.marks.bulk.trash()| ============================================================================== OS Specific Restrictions *nvim-tree-os-specific* Windows WSL and PowerShell - Trash is synchronized - Executable file detection is disabled as this is non-performant and can freeze Nvim - Some filesystem watcher error related to permissions will not be reported Powershell - Observed Nvim hanging after a runaway (infinite) number of events on a single directory. See |nvim_tree.config.filesystem_watchers| {max_events} ============================================================================== netrw *nvim-tree-netrw* |netrw| is a standard Nvim plugin that is enabled by default. It provides, amongst other functionality, a file/directory browser. It interferes with nvim-tree and the intended user experience is nvim-tree replacing the |netrw| browser. It is strongly recommended to disable |netrw|. As it is a bundled plugin it must be disabled manually at the start of your `init.lua` as per |netrw-noload|: >lua vim.g.loaded_netrw = 1 vim.g.loaded_netrwPlugin = 1 < There are many |netrw| features beyond the file browser. If you want to keep using |netrw| without its browser features please ensure: |nvim_tree.config| {disable_netrw}` = false` |nvim_tree.config| {hijack_netrw}` = true` ============================================================================== Legacy *nvim-tree-legacy* Backwards compatible, silent refactors have been done. ============================================================================== Legacy: Config *nvim-tree-legacy-config* Legacy config is translated to the current, making type and value changes as needed. `update_cwd` |nvim_tree.config| {sync_root_with_cwd} `update_focused_file.update_cwd` |nvim_tree.config.update_focused_file| {update_root} `open_on_tab` |nvim_tree.config.tab.sync| {open} `ignore_buf_on_tab_change` |nvim_tree.config.tab.sync| {ignore} `renderer.root_folder_modifier` |nvim_tree.config.renderer| {root_folder_label} `update_focused_file.debounce_delay` |nvim_tree.config.view| {debounce_delay} `trash.require_confirm` |nvim_tree.config.ui.confirm| {trash} `view.adaptive_size` |nvim_tree.config.view| {width} `sort_by` |nvim_tree.config.sort| {sorter} `git.ignore` |nvim_tree.config.filters| {git_ignored} `renderer.icons.webdev_colors` |nvim_tree.config.renderer.icons.web_devicons.file| {color} `renderer.icons.padding` |nvim_tree.config.renderer.icons.padding| {icon} ============================================================================== Legacy: API *nvim-tree-legacy-api* Deprecated API with unchanged function signature: `api.config.mappings.get_keymap` |nvim_tree.api.map.keymap.current()| `api.config.mappings.get_keymap_default` |nvim_tree.api.map.keymap.default()| `api.config.mappings.default_on_attach` |nvim_tree.api.map.on_attach.default()| `api.diagnostics.hi_test` |nvim_tree.api.appearance.hi_test()| `api.live_filter.start` |nvim_tree.api.filter.live.start()| `api.live_filter.clear` |nvim_tree.api.filter.live.clear()| `api.tree.toggle_enable_filters` |nvim_tree.api.filter.toggle()| `api.tree.toggle_gitignore_filter` |nvim_tree.api.filter.git.ignored.toggle()| `api.tree.toggle_git_clean_filter` |nvim_tree.api.filter.git.clean.toggle()| `api.tree.toggle_no_buffer_filter` |nvim_tree.api.filter.no_buffer.toggle()| `api.tree.toggle_custom_filter` |nvim_tree.api.filter.custom.toggle()| `api.tree.toggle_hidden_filter` |nvim_tree.api.filter.dotfiles.toggle()| `api.tree.toggle_no_bookmark_filter` |nvim_tree.api.filter.no_bookmark.toggle()| ============================================================================== Legacy: Highlight *nvim-tree-legacy-highlight* Legacy highlight group are still obeyed when they are defined and the current highlight group is not, hard linking as follows: > NvimTreeModifiedIcon NvimTreeModifiedFile NvimTreeOpenedHL NvimTreeOpenedFile NvimTreeBookmarkIcon NvimTreeBookmark NvimTreeGitDeletedIcon NvimTreeGitDeleted NvimTreeGitDirtyIcon NvimTreeGitDirty NvimTreeGitIgnoredIcon NvimTreeGitIgnored NvimTreeGitMergeIcon NvimTreeGitMerge NvimTreeGitNewIcon NvimTreeGitNew NvimTreeGitRenamedIcon NvimTreeGitRenamed NvimTreeGitStagedIcon NvimTreeGitStaged NvimTreeGitFileDeletedHL NvimTreeFileDeleted NvimTreeGitFileDirtyHL NvimTreeFileDirty NvimTreeGitFileIgnoredHL NvimTreeFileIgnored NvimTreeGitFileMergeHL NvimTreeFileMerge NvimTreeGitFileNewHL NvimTreeFileNew NvimTreeGitFileRenamedHL NvimTreeFileRenamed NvimTreeGitFileStagedHL NvimTreeFileStaged NvimTreeGitFolderDeletedHL NvimTreeFolderDeleted NvimTreeGitFolderDirtyHL NvimTreeFolderDirty NvimTreeGitFolderIgnoredHL NvimTreeFolderIgnored NvimTreeGitFolderMergeHL NvimTreeFolderMerge NvimTreeGitFolderNewHL NvimTreeFolderNew NvimTreeGitFolderRenamedHL NvimTreeFolderRenamed NvimTreeGitFolderStagedHL NvimTreeFolderStaged NvimTreeLspDiagnosticsError NvimTreeDiagnosticErrorIcon NvimTreeLspDiagnosticsWarning NvimTreeDiagnosticWarnIcon NvimTreeLspDiagnosticsInformation NvimTreeDiagnosticInfoIcon NvimTreeLspDiagnosticsHint NvimTreeDiagnosticHintIcon NvimTreeLspDiagnosticsErrorText NvimTreeDiagnosticErrorFileHL NvimTreeLspDiagnosticsWarningText NvimTreeDiagnosticWarnFileHL NvimTreeLspDiagnosticsInformationText NvimTreeDiagnosticInfoFileHL NvimTreeLspDiagnosticsHintText NvimTreeDiagnosticHintFileHL NvimTreeLspDiagnosticsErrorFolderText NvimTreeDiagnosticErrorFolderHL NvimTreeLspDiagnosticsWarningFolderText NvimTreeDiagnosticWarnFolderHL NvimTreeLspDiagnosticsInformationFolderText NvimTreeDiagnosticInfoFolderHL NvimTreeLspDiagnosticsHintFolderText NvimTreeDiagnosticHintFolderHL < ============================================================================== Config *nvim-tree-config* *nvim_tree.config* Arguments to pass to |nvim-tree-setup|. When a value is not present/nil, the default will be used. {on_attach} Runs when creating the nvim-tree buffer. Use this to set your |nvim-tree-mappings|. When not a function, |nvim-tree-mappings-default| will be used. {hijack_cursor} keep the cursor on the first letter of the filename when moving in the tree. {auto_reload_on_write} reload the explorer every time a buffer is written to. {disable_netrw} completely disables |netrw|, see |nvim-tree-netrw| for details. It is strongly advised to eagerly disable netrw, due to race conditions at vim startup. {hijack_netrw} hijacks netrw windows, ignored when {disable_netrw}. {hijack_unnamed_buffer_when_opening} opens in place of the unnamed buffer if it's empty. {root_dirs} preferred root directories, requires |nvim_tree.config.update_focused_file.update_root|. {prefer_startup_root} prefer startup root directory when updating root directory of the tree. Requires |nvim_tree.config.update_focused_file.update_root|. {sync_root_with_cwd} changes the tree root directory on |DirChanged| and refreshes the tree. {reload_on_bufenter} automatically reloads the tree on |BufEnter| nvim-tree. {respect_buf_cwd} changes the |current-directory| of nvim-tree to that of new buffer's when opening nvim-tree. {select_prompts} uses |vim.ui.select()| style prompts. Necessary when using a UI prompt decorator such as dressing.nvim or telescope-ui-select.nvim Fields: ~ • {on_attach}? (`"default"|(fun(bufnr: integer))`) (default: `default`) • {hijack_cursor}? (`boolean`) (default: `false`) • {auto_reload_on_write}? (`boolean`) (default: `true`) • {disable_netrw}? (`boolean`) (default: `false`) • {hijack_netrw}? (`boolean`) (default: `true`) • {hijack_unnamed_buffer_when_opening}? (`boolean`) (default: `false`) • {root_dirs}? (`string[]`) (default: `{}`) • {prefer_startup_root}? (`boolean`) (default: `false`) • {sync_root_with_cwd}? (`boolean`) (default: `false`) • {reload_on_bufenter}? (`boolean`) (default: `false`) • {respect_buf_cwd}? (`boolean`) (default: `false`) • {select_prompts}? (`boolean`) (default: `false`) • {sort}? (`nvim_tree.config.sort`) |nvim_tree.config.sort| • {view}? (`nvim_tree.config.view`) |nvim_tree.config.view| • {renderer}? (`nvim_tree.config.renderer`) |nvim_tree.config.renderer| • {hijack_directories}? (`nvim_tree.config.hijack_directories`) |nvim_tree.config.hijack_directories| • {update_focused_file}? (`nvim_tree.config.update_focused_file`) |nvim_tree.config.update_focused_file| • {system_open}? (`nvim_tree.config.system_open`) |nvim_tree.config.system_open| • {git}? (`nvim_tree.config.git`) |nvim_tree.config.git| • {diagnostics}? (`nvim_tree.config.diagnostics`) |nvim_tree.config.diagnostics| • {modified}? (`nvim_tree.config.modified`) |nvim_tree.config.modified| • {filters}? (`nvim_tree.config.filters`) |nvim_tree.config.filters| • {live_filter}? (`nvim_tree.config.live_filter`) |nvim_tree.config.live_filter| • {filesystem_watchers}? (`nvim_tree.config.filesystem_watchers`) |nvim_tree.config.filesystem_watchers| • {actions}? (`nvim_tree.config.actions`) |nvim_tree.config.actions| • {trash}? (`nvim_tree.config.trash`) |nvim_tree.config.trash| • {tab}? (`nvim_tree.config.tab`) |nvim_tree.config.tab| • {bookmarks}? (`nvim_tree.config.bookmarks`) |nvim_tree.config.bookmarks| • {notify}? (`nvim_tree.config.notify`) |nvim_tree.config.notify| • {help}? (`nvim_tree.config.help`) |nvim_tree.config.help| • {ui}? (`nvim_tree.config.ui`) |nvim_tree.config.ui| • {experimental}? (`nvim_tree.config.experimental`) |nvim_tree.config.experimental| • {log}? (`nvim_tree.config.log`) |nvim_tree.config.log| ============================================================================== Config: sort *nvim-tree-config-sort* *nvim_tree.config.sort* Sort files within a directory. {sorter} presets *nvim_tree.config.sort.Sorter* • `"name"` • `"case_sensitive"` name • `"modification_time"` • `"extension"` uses all suffixes e.g. `foo.tar.gz` -> `.tar.gz` • `"suffix"` uses the last e.g. `foo.tar.gz` -> `.gz` • `"filetype"` |filetype| {sorter} may be a function that is passed a list of `nvim_tree.api.Node` to be sorted in place e.g. >lua ---Sort by name length ---@param nodes nvim_tree.api.Node[] ---@return nvim_tree.config.sort.Sorter? local sorter = function(nodes) table.sort(nodes, function(a, b) return #a.name < #b.name end) end < {sorter} may be a function that returns a |nvim_tree.config.sort.Sorter| Fields: ~ • {sorter}? (`nvim_tree.config.sort.Sorter|(fun(nodes: nvim_tree.api.Node[]): nvim_tree.config.sort.Sorter?)`) (default: `"name"`) • {folders_first}? (`boolean`, default: `true`) Sort folders before files. Has no effect when {sorter} is a function. • {files_first}? (`boolean`, default: `false`) Sort files before folders. Has no effect when {sorter} is a function. Overrides {folders_first}. ============================================================================== Config: view *nvim-tree-config-view* *nvim_tree.config.view* Configures the dimensions and appearance of the nvim-tree window. The window is "docked" at the left by default, however may be configured to float: |nvim_tree.config.view.float| {width} can be a |nvim_tree.config.view.width.spec| for static control or a |nvim_tree.config.view.width| for fully dynamic control based on longest line. *nvim_tree.config.view.width.spec* • `string`: `x%` string e.g. `30%` • `integer`: number of columns • `function`: returns one of the above Fields: ~ • {centralize_selection}? (`boolean`, default: `false`) When entering nvim-tree, reposition the view so that the current node is initially centralized, see |zz|. • {cursorline}? (`boolean`, default: `true`) |'cursorline'| • {cursorlineopt}? (`string`, default: `both`) |'cursorlineopt'| • {debounce_delay}? (`integer`, default: `15`) Idle milliseconds before some reload / refresh operations. Increase if you experience performance issues around screen refresh. • {side}? (`"left"|"right"`) (default: `"left"`) • {preserve_window_proportions}? (`boolean`, default: `false`) Preserves window proportions when opening a file. If `false`, the height and width of windows other than nvim-tree will be equalized. • {number}? (`boolean`, default: `false`) |'number'| • {relativenumber}? (`boolean`, default: `false`) |'relativenumber'| • {signcolumn}? (`"yes"|"auto"|"no"`, default: `"yes"`) |'signcolumn'| • {width}? (`nvim_tree.config.view.width.spec|nvim_tree.config.view.width`) (default: `30`) • {float}? (`nvim_tree.config.view.float`) |nvim_tree.config.view.float| *nvim_tree.config.view.float* Configure floating window behaviour {open_win_config} is passed to |nvim_open_win()|, default: >lua { relative = "editor", border = "rounded", width = 30, height = 30, row = 1, col = 1, } < Fields: ~ • {enable}? (`boolean`) (default: `false`) • {quit_on_focus_loss}? (`boolean`, default: `true`) Close the floating window when it loses focus. • {open_win_config}? (`vim.api.keyset.win_config|(fun(): vim.api.keyset.win_config)`) (default: `{ relative = "editor", border = "rounded", width = 30, height = 30, row = 1, col = 1, }`) *nvim_tree.config.view.width* Configure dynamic width based on longest line. Fields: ~ • {min}? (`nvim_tree.config.view.width.spec`) (default: `30`) • {max}? (`nvim_tree.config.view.width.spec`, default: `-1`) -1 for unbounded. • {lines_excluded}? (`("root")[]`, default: `{ "root" }`) Exclude these lines when computing width. • {padding}? (`nvim_tree.config.view.width.spec`, default: `1`) Extra padding to the right. ============================================================================== Config: renderer *nvim-tree-config-renderer* *nvim_tree.config.renderer* Controls the appearance of the tree. {highlight_} *nvim_tree.config.renderer.highlight* See |nvim-tree-icons-highlighting| • `"none"`: no highlighting • `"icon"`: icon only • `"name"`: name only • `"all"`: icon and name {decorators} *nvim_tree.config.renderer.decorator* See |nvim-tree-icons-highlighting| A builtin decorator name `string` or |nvim_tree.api.Decorator| class. Builtin decorators in their default order: • `"Git"` • `"Open"` • `"Hidden"` • `"Modified"` • `"Bookmark"` • `"Diagnostics"` • `"Copied"` • `"Cut"` Specify {decorators} is a list e.g. `{ "Git", MyDecorator, "Cut" }` {root_folder_label} *nvim_tree.config.renderer.root_folder_label* Controls the root folder name and visibility: • `string`: |filename-modifiers| format string, default `":~:s?$?/..?"` • `false`: to disable • `fun(root_cwd: string): string`: return a literal string from root's absolute path e.g. >lua my_root_folder_label = function(path) return ".../" .. vim.fn.fnamemodify(path, ":t") end < {hidden_display} *nvim_tree.config.renderer.hidden_display* Summary of hidden nodes, below the last node in the directory, highlighted with `NvimTreeHiddenDisplay`. • `"none"`: disabled, default • `"simple"`: total number of hidden files e.g. • (3 hidden) • `"all"`: total and by reason: the filter that hid the node e.g. • (14 total git: 5, dotfile: 9) • `(fun(hidden_stats: nvim_tree.config.renderer.hidden_stats): string)` See |nvim_tree.config.renderer.hidden_stats| for details and example. Fields: ~ • {add_trailing}? (`boolean`, default: `false`) Appends a trailing slash to folder and symlink folder target names. • {group_empty}? (`boolean|(fun(relative_path: string): string)`, default: `false`) Compact folders that only contain a single folder into one node. Function variant takes the relative path of grouped folders and returns a string to be displayed. • {full_name}? (`boolean`, default: `false`) Display nodes whose name length is wider than the width of nvim-tree window in floating window. • {root_folder_label}? (`nvim_tree.config.renderer.root_folder_label`, default: `":~:s?$?/..?"`) |nvim_tree.config.renderer.root_folder_label| • {indent_width}? (`integer`, default: `2`) Number of spaces for each tree nesting level. Minimum 1. • {hidden_display}? (`nvim_tree.config.renderer.hidden_display`, default: `none`) |nvim_tree.config.renderer.hidden_display| • {symlink_destination}? (`boolean`, default: `true`) Appends an arrow followed by the target of the symlink. • {decorators}? (`nvim_tree.config.renderer.decorator[]`, default: `{ "Git", "Open", "Hidden", "Modified", "Bookmark", "Diagnostics", "Copied", "Cut", }`) List in order of additive precedence. • {highlight_git}? (`nvim_tree.config.renderer.highlight`) (default: `"none"`) • {highlight_opened_files}? (`nvim_tree.config.renderer.highlight`) (default: `"none"`) • {highlight_hidden}? (`nvim_tree.config.renderer.highlight`) (default: `"none"`) • {highlight_modified}? (`nvim_tree.config.renderer.highlight`) (default: `"none"`) • {highlight_bookmarks}? (`nvim_tree.config.renderer.highlight`) (default: `"none"`) • {highlight_diagnostics}? (`nvim_tree.config.renderer.highlight`) (default: `"none"`) • {highlight_clipboard}? (`nvim_tree.config.renderer.highlight`) (default: `"name"`) • {special_files}? (`string[]`, default: `{ "Cargo.toml", "Makefile", "README.md", "readme.md", }`) Highlight special files and directories with `NvimTreeSpecial*`. • {indent_markers}? (`nvim_tree.config.renderer.indent_markers`) |nvim_tree.config.renderer.indent_markers| • {icons}? (`nvim_tree.config.renderer.icons`) |nvim_tree.config.renderer.icons| *nvim_tree.config.renderer.hidden_stats* Number of hidden nodes in a directory by reason: the filter that hid the node. Passed to your |nvim_tree.config.renderer.hidden_display| function e.g. >lua ---@param hidden_stats nvim_tree.config.renderer.hidden_stats ---@return string? summary local my_hidden_display = function(hidden_stats) local total_count = 0 for reason, count in pairs(hidden_stats) do total_count = total_count + count end if total_count > 0 then return "(" .. tostring(total_count) .. " hidden)" end return nil end < Fields: ~ • {bookmark} (`integer`) • {buf} (`integer`) • {custom} (`integer`) • {dotfile} (`integer`) • {git} (`integer`) • {live_filter} (`integer`) *nvim_tree.config.renderer.icons* Icons and separators {_placement} *nvim_tree.config.renderer.icons.placement* • `"before"`: before file/folder, after the file/folders icons • `"after"`: after file/folder • `"signcolumn"`: far left, requires |nvim_tree.config.view| {signcolumn}. • `"right_align"`: far right Fields: ~ • {git_placement}? (`nvim_tree.config.renderer.icons.placement`) (default: `before`) • {hidden_placement}? (`nvim_tree.config.renderer.icons.placement`) (default: `after`) • {modified_placement}? (`nvim_tree.config.renderer.icons.placement`) (default: `after`) • {bookmarks_placement}? (`nvim_tree.config.renderer.icons.placement`) (default: `signcolumn`) • {diagnostics_placement}? (`nvim_tree.config.renderer.icons.placement`) (default: `signcolumn`) • {padding}? (`table`) *nvim_tree.config.renderer.icons.padding* • {icon}? (`string`, default: `" "`) Between icon and filename. • {folder_arrow}? (`string`, default: `" "`) Between folder arrow icon and file/folder icon. • {symlink_arrow}? (`string`, default: `" ➛ "`) Separator between symlink source and target. • {show}? (`nvim_tree.config.renderer.icons.show`) |nvim_tree.config.renderer.icons.show| • {glyphs}? (`nvim_tree.config.renderer.icons.glyphs`) |nvim_tree.config.renderer.icons.glyphs| • {web_devicons}? (`nvim_tree.config.renderer.icons.web_devicons`) |nvim_tree.config.renderer.icons.web_devicons| *nvim_tree.config.renderer.icons.glyphs* See |nvim-tree-icons-highlighting|. Glyphs that appear in the sign column must have length <= 2 Fields: ~ • {default}? (`string`, default: `""`) Files • {symlink}? (`string`) (default: `""`) • {bookmark}? (`string`) (default: `"󰆤"`) • {modified}? (`string`) (default: `"●"`) • {hidden}? (`string`) (default: `"󰜌"`) • {folder}? (`table`) *nvim_tree.config.renderer.icons.glyphs.folder* • {arrow_closed}? (`string`) (default: left arrow) • {arrow_open}? (`string`) (default: down arrow) • {default}? (`string`) (default: `""`) • {open}? (`string`) (default: `""`) • {empty}? (`string`) (default: `""`) • {empty_open}? (`string`) (default: `""`) • {symlink}? (`string`) (default: `""`) • {symlink_open}? (`string`) (default: `""`) • {git}? (`table`) *nvim_tree.config.renderer.icons.glyphs.git* • {unstaged}? (`string`) (default: `"✗"`) • {staged}? (`string`) (default: `"✓"`) • {unmerged}? (`string`) (default: `""`) • {renamed}? (`string`) (default: `"➜"`) • {untracked}? (`string`) (default: `"★"`) • {deleted}? (`string`) (default: `""`) • {ignored}? (`string`) (default: `"◌"`) *nvim_tree.config.renderer.icons.show* See |nvim-tree-icons-highlighting|. Fields: ~ • {file}? (`boolean`) (default: `true`) • {folder}? (`boolean`) (default: `true`) • {git}? (`boolean`) (default: `true`) • {modified}? (`boolean`) (default: `true`) • {hidden}? (`boolean`) (default: `false`) • {diagnostics}? (`boolean`) (default: `true`) • {bookmarks}? (`boolean`) (default: `true`) • {folder_arrow}? (`boolean`, default: `true`) Show a small arrow before the folder node. Arrow will be a part of the node when using |nvim_tree.config.renderer.indent_markers|. *nvim_tree.config.renderer.icons.web_devicons* Configure optional plugin `nvim-tree/nvim-web-devicons`, see |nvim-tree-icons-highlighting|. Fields: ~ • {file}? (`table`) *nvim_tree.config.renderer.icons.web_devicons.file* • {enable}? (`boolean`) (default: `true`) • {color}? (`boolean`) (default: `true`) • {folder}? (`table`) *nvim_tree.config.renderer.icons.web_devicons.folder* • {enable}? (`boolean`) (default: `false`) • {color}? (`boolean`) (default: `true`) *nvim_tree.config.renderer.indent_markers* Fields: ~ • {enable}? (`boolean`, default: `false`) Display indent markers when folders are open. • {inline_arrows}? (`boolean`, default: `true`) Display folder arrows in the same column as indent marker when using |nvim_tree.config.renderer.icons.padding| {folder_arrow} • {icons}? (`table`) *nvim_tree.config.renderer.indent_markers.icons* Before the file/directory, length 1. • {corner}? (`string`) (default: `"└"`) • {edge}? (`string`) (default: `"│"`) • {item}? (`string`) (default: `"│"`) • {bottom}? (`string`) (default: `"─"`) • {none}? (`string`) (default: `" "`) ============================================================================== Config: hijack_directories *nvim-tree-config-hijack-directories* *nvim_tree.config.hijack_directories* Hijack directory buffers by replacing the directory buffer with the tree. Disable this option if you use vim-dirvish or dirbuf.nvim. If |nvim_tree.config| {hijack_netrw} and {disable_netrw} are `false` this feature will be disabled. Fields: ~ • {enable}? (`boolean`) (default: `true`) • {auto_open}? (`boolean`, default: `true`) Open if the tree was previously closed. ============================================================================== Config: update_focused_file *nvim-tree-config-update-focused-file* *nvim_tree.config.update_focused_file* Update the focused file on |BufEnter|, uncollapsing folders recursively. Fields: ~ • {enable}? (`boolean`) (default: `false`) • {update_root}? (`nvim_tree.config.update_focused_file.update_root`) |nvim_tree.config.update_focused_file.update_root| • {exclude}? (`boolean|(fun(args: vim.api.keyset.create_autocmd.callback_args): boolean)`, default: `false`) A function called on |BufEnter| that returns true if the file should not be focused when opening. *nvim_tree.config.update_focused_file.update_root* Update the root directory of the tree if the file is not under the current root directory. Prefers vim's cwd and |nvim_tree.config| {root_dirs}, falling back to the directory containing the file. Requires |nvim_tree.config.update_focused_file| Fields: ~ • {enable}? (`boolean`) (default: `false`) • {ignore_list}? (`string[]`, default: `{}`) List of buffer names and filetypes that will not update the root dir of the tree if the file isn't found under the current root directory. ============================================================================== Config: system_open *nvim-tree-config-system-open* *nvim_tree.config.system_open* Open files or directories via the OS. Nvim: • `>=` 0.10 uses |vim.ui.open()| unless {cmd} is specified • `<` 0.10 calls external {cmd}: • UNIX: `xdg-open` • macOS: `open` • Windows: `cmd` Once nvim-tree minimum Nvim version is updated to 0.10, this configuration will no longer be necessary and will be removed. Fields: ~ • {cmd}? (`string`) The open command itself • {args}? (`string[]`, default: `{}` or `{ "/c", "start", '""' }` on windows) Optional argument list. Leave empty for OS specific default. ============================================================================== Config: git *nvim-tree-config-git* *nvim_tree.config.git* Git operations are run in the background thus status may not immediately appear. Processes will be killed if they exceed {timeout} ms. Git integration will be disabled following 5 timeouts and you will be notified. Git integration may be disabled for git top-level directories via {disable_for_dirs}: • A list of relative paths evaluated with |fnamemodify()| `:p` OR • A function that is passed an absolute path and returns `true` to disable See |nvim-tree-icons-highlighting|. Fields: ~ • {enable}? (`boolean`) (default: `true`) • {show_on_dirs}? (`boolean`, default: `true`) Show status icons of children when directory itself has no status icon • {show_on_open_dirs}? (`boolean`, default: `true`) Show status icons of children on directories that are open. Requires {show_on_dirs}. • {disable_for_dirs}? (`string[]|(fun(path: string): boolean)`, default: `{}`) Disable for top level paths. • {timeout}? (`integer`, default: `400`) `git` processes timeout milliseconds. • {cygwin_support}? (`boolean`, default: `false`) Use `cygpath` if available to resolve paths for git. ============================================================================== Config: diagnostics *nvim-tree-config-diagnostics* *nvim_tree.config.diagnostics* Integrate with |lsp| or COC diagnostics. See |nvim-tree-icons-highlighting|. Fields: ~ • {enable}? (`boolean`) (default: `false`) • {debounce_delay}? (`integer`, default: `500`) Idle milliseconds between diagnostic event and tree update. • {show_on_dirs}? (`boolean`, default: `false`) Show diagnostic icons on parent directories. • {show_on_open_dirs}? (`boolean`, default: `true`) Show diagnostics icons on directories that are open. Requires {show_on_dirs}. • {diagnostic_opts}? (`boolean`, default: `false`) Global |vim.diagnostic.Opts| overrides {severity} and {icons} • {severity}? (`table`) *nvim_tree.config.diagnostics.severity* • {min}? (`vim.diagnostic.Severity`, default: HINT) |vim.diagnostic.severity| • {max}? (`vim.diagnostic.Severity`, default: ERROR) |vim.diagnostic.severity| • {icons}? (`table`) *nvim_tree.config.diagnostics.icons* • {hint}? (`string`) (default: `""` ) • {info}? (`string`) (default: `""` ) • {warning}? (`string`) (default: `""` ) • {error}? (`string`) (default: `""` ) ============================================================================== Config: modified *nvim-tree-config-modified* *nvim_tree.config.modified* Indicate which files have unsaved modification. To see modified status in the tree you will need: • |nvim_tree.config.renderer.icons.show| {modified} OR • |nvim_tree.config.renderer| {highlight_modified} See |nvim-tree-icons-highlighting|. Fields: ~ • {enable}? (`boolean`) (default: `false`) • {show_on_dirs}? (`boolean`, default: `true`) Show modified indication on directory whose children are modified. • {show_on_open_dirs}? (`boolean`, default: `false`) Show modified indication on open directories. Requires {show_on_dirs}. ============================================================================== Config: filters *nvim-tree-config-filters* *nvim_tree.config.filters* Filters may be applied to the tree to exlude the display of file/directories. Multiple filters may be applied at once. Filters can be set at startup or toggled live via API with default mappings. `I `{git_ignored}` `|nvim_tree.api.filter.git.ignored.toggle()| Ignore files based on `.gitignore`. Requires |nvim_tree.config.git| `H `{dotfiles}` `|nvim_tree.api.filter.dotfiles.toggle()| Filter dotfiles: files/directories starting with a `.` `C `{git_clean}` `|nvim_tree.api.filter.git.clean.toggle()| Filter files with no git status. `.gitignore` files will not be filtered when {git_ignored}, as they are effectively dirty. `B `{no_buffer}` `|nvim_tree.api.filter.no_buffer.toggle()| Filter files that have no |buflisted()| buffer. For performance reasons buffer delete/wipe may not be immediately shown. A reload or filesystem event will always result in an update. `M `{no_bookmark}` `|nvim_tree.api.filter.no_bookmark.toggle()| Filter files that are not bookmarked. Enabling this is not useful as there is no means yet to persist bookmarks. `U `{custom}` `|nvim_tree.api.filter.custom.toggle()| Disable specific file/directory names via: • a list of backslash escaped |regular-expression| e.g. `"^\\.git""` • a function passed the absolute path of the directory. All filters including live filter may be disabled via {enable} and toggled with |nvim_tree.api.filter.toggle()| Files/directories may be {exclude}d from filtering: they will always be shown, overriding {git_ignored}, {dotfiles} and {custom}. Fields: ~ • {enable}? (`boolean`, default: `true`) Enable all filters. • {git_ignored}? (`boolean`) (default: `true`) • {dotfiles}? (`boolean`) (default: `false`) • {git_clean}? (`boolean`) (default: `false`) • {no_buffer}? (`boolean`) (default: `false`) • {no_bookmark}? (`boolean`) (default: `false`) • {custom}? (`string[]|(fun(absolute_path: string): boolean)`) (default: `{}`) • {exclude}? (`string[]`) (default: `{}`) ============================================================================== Config: live_filter *nvim-tree-config-live-filter* *nvim_tree.config.live_filter* Live filter allows you to filter the tree nodes dynamically using |regular-expression| matching. This feature is bound to the `f` key by default. The filter can be cleared with the `F` key by default. Fields: ~ • {prefix}? (`string`, default: `"[FILTER]: "`) Prefix of the filter displayed in the buffer. • {always_show_folders}? (`boolean`, default: `true`) Whether to filter folders or not. ============================================================================== Config: filesystem_watchers *nvim-tree-config-filesystem-watchers* *nvim_tree.config.filesystem_watchers* Use file system watchers (libuv `uv_fs_event_t`) to monitor the filesystem for changes and update the tree. With this feature, the tree will be partially updated on specific directory changes, resulting in better performance. Watchers may be disabled for absolute directory paths via {ignore_dirs}. • A list of |regular-expression| to match a path, backslash escaped e.g. `"my-proj/\\.build$"` OR • A function that is passed an absolute path and returns `true` to disable This may be useful when a path is not in `.gitignore` or git integration is disabled. After {max_events} consecutive filesystem events on a single directory with an interval < {debounce_delay}: • The filesystem watcher will be disabled for that directory. • A warning notification will be shown. • Consider adding this directory to {ignore_dirs} Fields: ~ • {enable}? (`boolean`) (default: `true`) • {debounce_delay}? (`integer`, default: `50`) Idle milliseconds between filesystem change and tree update. • {ignore_dirs}? (`string[]|(fun(path: string): boolean)`, default: `{ "/.ccls-cache", "/build", "/node_modules", "/target", "/.zig-cache"}`) Disable for specific directories. • {max_events}? (`integer`, default: `0` or `1000` on windows) Disable for a single directory after {max_events} consecutive events with an interval < {debounce_delay}. Set to 0 to allow unlimited consecutive events. ============================================================================== Config: actions *nvim-tree-config-actions* *nvim_tree.config.actions* Fields: ~ • {use_system_clipboard}? (`boolean`, default: `true`) Use the system clipboard for copy/paste. Copied text will be stored in registers `+` (system), otherwise, it will be stored in `1` and `"` • {change_dir}? (`nvim_tree.config.actions.change_dir`) |nvim_tree.config.actions.change_dir| • {expand_all}? (`nvim_tree.config.actions.expand_all`) |nvim_tree.config.actions.expand_all| • {file_popup}? (`nvim_tree.config.actions.file_popup`) |nvim_tree.config.actions.file_popup| • {open_file}? (`nvim_tree.config.actions.open_file`) |nvim_tree.config.actions.open_file| • {remove_file}? (`nvim_tree.config.actions.remove_file`) |nvim_tree.config.actions.remove_file| *nvim_tree.config.actions.change_dir* vim |current-directory| behaviour Fields: ~ • {enable}? (`boolean`, default: `true`) Change the working directory when changing directories in the tree • {global}? (`boolean`, default: `false`) Use `:cd` instead of `:lcd` when changing directories. • {restrict_above_cwd}? (`boolean`, default: `false`) Restrict changing to a directory above the global cwd. *nvim_tree.config.actions.expand_all* Configure |nvim_tree.api.tree.expand_all()| and |nvim_tree.api.node.expand()| Fields: ~ • {max_folder_discovery}? (`integer`, default: `300`) Limit the number of folders being explored when expanding every folder. Avoids hanging Nvim when running this action on very large folders. • {exclude}? (`string[]`, default: `{}`) A list of directories that should not be expanded automatically e.g `{ ".git", "target", "build" }` *nvim_tree.config.actions.file_popup* {file_popup} floating window. {open_win_config} is passed to |nvim_open_win()|, default: >lua { col = 1, row = 1, relative = "cursor", border = "shadow", style = "minimal", } < You shouldn't define {width} and {height} values here. They will be overridden to fit the file_popup content. Fields: ~ • {open_win_config}? (`vim.api.keyset.win_config`) (default: `{ col = 1, row = 1, relative = "cursor", border = "shadow", style = "minimal", }`) *nvim_tree.config.actions.open_file* Opening files. Fields: ~ • {quit_on_open}? (`boolean`, default: `false`) Closes the explorer when opening a file • {eject}? (`boolean`, default: `true`) Prevent new opened file from opening in the same window as the tree. • {resize_window}? (`boolean`, default: `true`) Resizes the tree when opening a file • {window_picker}? (`nvim_tree.config.actions.open_file.window_picker`) |nvim_tree.config.actions.open_file.window_picker| *nvim_tree.config.actions.open_file.window_picker* A window picker will be shown when there are multiple windows available to open a file. It will show a single character identifier in each window's status line. When it is not enabled the file will open in the window from which you last opened the tree, obeying {exclude} You may define a {picker} function that should return the window id that will open the node, or `nil` if an invalid window is picked or user cancelled the action. The picker may create a new window. Fields: ~ • {enable}? (`boolean`) (default: `true`) • {picker}? (`"default"|(fun(): integer)`, default: `"default"`) Change the default window picker or define your own. • {chars}? (`string`, default: `"ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"`) Identifier characters to use. • {exclude}? (`nvim_tree.config.actions.open_file.window_picker.exclude`) |nvim_tree.config.actions.open_file.window_picker.exclude| *nvim_tree.config.actions.open_file.window_picker.exclude* Tables of buffer option names mapped to a list of option values. Windows containing matching buffers will not be: • available when using a window picker • selected when not using a window picker Fields: ~ • {filetype}? (`string[]`) (default: `{ "notify", "lazy", "qf", "diff", "fugitive", "fugitiveblame", }`) • {buftype}? (`string[]`) (default: `{ "nofile", "terminal", "help", }`) *nvim_tree.config.actions.remove_file* Removing files. Fields: ~ • {close_window}? (`boolean`, default: `true`) Close any window that displays a file when removing that file from the tree. ============================================================================== Config: trash *nvim-tree-config-trash* *nvim_tree.config.trash* Files may be trashed via an external command that must be installed on your system. • linux: `gio trash`, from linux package `glib2` • macOS: `trash`, from homebrew package `trash` • windows: `trash`, requires `trash-cli` or similar Fields: ~ • {cmd}? (`string`) (default: `"gio trash"` or `"trash"`) ============================================================================== Config: tab *nvim-tree-config-tab* *nvim_tree.config.tab* Fields: ~ • {sync}? (`nvim_tree.config.tab.sync`) |nvim_tree.config.tab.sync| *nvim_tree.config.tab.sync* Fields: ~ • {open}? (`boolean`, default: `false`) Opens the tree automatically when switching tabpage or opening a new tabpage if the tree was previously open. • {close}? (`boolean`, default: `false`) Closes the tree across all tabpages when the tree is closed. • {ignore}? (`string[]`, default: `{}`) List of filetypes or buffer names on new tab that will prevent `open` and `close` ============================================================================== Config: notify *nvim-tree-config-notify* *nvim_tree.config.notify* nvim-tree |vim.log.levels| • `ERROR`: hard errors e.g. failure to read from the file system. • `WARN`: non-fatal errors e.g. unable to system open a file. • `INFO`: information only e.g. file copy path confirmation. • `DEBUG`: information for troubleshooting, e.g. failures in some window closing operations. Fields: ~ • {threshold}? (`vim.log.levels`, default: `vim.log.levels.INFO`) Specify minimum notification |vim.log.levels| • {absolute_path}? (`boolean`, default: `true`) Use absolute paths in FS action notifications, otherwise item names. ============================================================================== Config: bookmarks *nvim-tree-config-bookmarks* *nvim_tree.config.bookmarks* Optionally {persist} bookmarks to a json file: • `true` use default: `stdpath("data") .. "/nvim-tree-bookmarks.json"` • `false` do not persist • `string` absolute path of your choice Fields: ~ • {persist}? (`boolean|string`) (default: `false`) ============================================================================== Config: help *nvim-tree-config-help* *nvim_tree.config.help* Fields: ~ • {sort_by}? (`"key"|"desc"`, default: `"key"`) Alphabetically. ============================================================================== Config: ui *nvim-tree-config-ui* *nvim_tree.config.ui* Fields: ~ • {confirm}? (`nvim_tree.config.ui.confirm`) |nvim_tree.config.ui.confirm| *nvim_tree.config.ui.confirm* Confirmation prompts. Fields: ~ • {remove}? (`boolean`, default: `true`) Prompt before removing. • {trash}? (`boolean`, default: `true`) Prompt before trashing. • {default_yes}? (`boolean`, default: `false`) If `true` the prompt will be `Y/n`, otherwise `y/N` ============================================================================== Config: experimental *nvim-tree-config-experimental* *nvim_tree.config.experimental* Experimental features that may become default or optional functionality. In the event of a problem please disable the experiment and raise an issue. ============================================================================== Config: log *nvim-tree-config-log* *nvim_tree.config.log* Log to a file `nvim-tree.log` in |stdpath()| `log`, usually `${XDG_STATE_HOME}/nvim` Fields: ~ • {enable}? (`boolean`) (default: `false`) • {truncate}? (`boolean`, default: `false`) Remove existing log file at startup. • {types}? (`nvim_tree.config.log.types`) |nvim_tree.config.log.types| *nvim_tree.config.log.types* Specify which information to log. Fields: ~ • {all}? (`boolean`, default: `false`) Everything. • {profile}? (`boolean`, default: `false`) Timing of some operations. • {config}? (`boolean`, default: `false`) Config and mappings, at startup. • {copy_paste}? (`boolean`, default: `false`) File copy and paste actions. • {dev}? (`boolean`, default: `false`) Used for local development only. Not useful for users. • {diagnostics}? (`boolean`, default: `false`) LSP and COC processing, verbose. • {git}? (`boolean`, default: `false`) Git processing, verbose. • {watcher}? (`boolean`, default: `false`) |nvim_tree.config.filesystem_watchers| processing, verbose. ============================================================================== Config: Default *nvim-tree-config-default* Following is the default configuration, see |nvim_tree.config| for details. >lua ---@type nvim_tree.config local config = { on_attach = "default", hijack_cursor = false, auto_reload_on_write = true, disable_netrw = false, hijack_netrw = true, hijack_unnamed_buffer_when_opening = false, root_dirs = {}, prefer_startup_root = false, sync_root_with_cwd = false, reload_on_bufenter = false, respect_buf_cwd = false, select_prompts = false, sort = { sorter = "name", folders_first = true, files_first = false, }, view = { centralize_selection = false, cursorline = true, cursorlineopt = "both", debounce_delay = 15, side = "left", preserve_window_proportions = false, number = false, relativenumber = false, signcolumn = "yes", width = 30, float = { enable = false, quit_on_focus_loss = true, open_win_config = { relative = "editor", border = "rounded", width = 30, height = 30, row = 1, col = 1, }, }, }, renderer = { add_trailing = false, group_empty = false, full_name = false, root_folder_label = ":~:s?$?/..?", indent_width = 2, special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" }, hidden_display = "none", symlink_destination = true, decorators = { "Git", "Open", "Hidden", "Modified", "Bookmark", "Diagnostics", "Copied", "Cut", }, highlight_git = "none", highlight_diagnostics = "none", highlight_opened_files = "none", highlight_modified = "none", highlight_hidden = "none", highlight_bookmarks = "none", highlight_clipboard = "name", indent_markers = { enable = false, inline_arrows = true, icons = { corner = "└", edge = "│", item = "│", bottom = "─", none = " ", }, }, icons = { web_devicons = { file = { enable = true, color = true, }, folder = { enable = false, color = true, }, }, git_placement = "before", modified_placement = "after", hidden_placement = "after", diagnostics_placement = "signcolumn", bookmarks_placement = "signcolumn", padding = { icon = " ", folder_arrow = " ", }, symlink_arrow = " ➛ ", show = { file = true, folder = true, folder_arrow = true, git = true, modified = true, hidden = false, diagnostics = true, bookmarks = true, }, glyphs = { default = "", symlink = "", bookmark = "󰆤", modified = "●", hidden = "󰜌", folder = { arrow_closed = "", arrow_open = "", default = "", open = "", empty = "", empty_open = "", symlink = "", symlink_open = "", }, git = { unstaged = "✗", staged = "✓", unmerged = "", renamed = "➜", untracked = "★", deleted = "", ignored = "◌", }, }, }, }, hijack_directories = { enable = true, auto_open = true, }, update_focused_file = { enable = false, update_root = { enable = false, ignore_list = {}, }, exclude = false, }, system_open = { cmd = "", args = {}, }, git = { enable = true, show_on_dirs = true, show_on_open_dirs = true, disable_for_dirs = {}, timeout = 400, cygwin_support = false, }, diagnostics = { enable = false, show_on_dirs = false, show_on_open_dirs = true, debounce_delay = 500, severity = { min = vim.diagnostic.severity.HINT, max = vim.diagnostic.severity.ERROR, }, icons = { hint = "", info = "", warning = "", error = "", }, diagnostic_opts = false, }, modified = { enable = false, show_on_dirs = true, show_on_open_dirs = true, }, filters = { enable = true, git_ignored = true, dotfiles = false, git_clean = false, no_buffer = false, no_bookmark = false, custom = {}, exclude = {}, }, live_filter = { prefix = "[FILTER]: ", always_show_folders = true, }, filesystem_watchers = { enable = true, debounce_delay = 50, max_events = 0, ignore_dirs = { "/.ccls-cache", "/build", "/node_modules", "/target", "/.zig-cache", }, }, actions = { use_system_clipboard = true, change_dir = { enable = true, global = false, restrict_above_cwd = false, }, expand_all = { max_folder_discovery = 300, exclude = {}, }, file_popup = { open_win_config = { col = 1, row = 1, relative = "cursor", border = "shadow", style = "minimal", }, }, open_file = { quit_on_open = false, eject = true, resize_window = true, relative_path = true, window_picker = { enable = true, picker = "default", chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", exclude = { filetype = { "notify", "packer", "qf", "diff", "fugitive", "fugitiveblame" }, buftype = { "nofile", "terminal", "help" }, }, }, }, remove_file = { close_window = true, }, }, trash = { cmd = "gio trash", }, tab = { sync = { open = false, close = false, ignore = {}, }, }, notify = { threshold = vim.log.levels.INFO, absolute_path = true, }, help = { sort_by = "key", }, ui = { confirm = { remove = true, trash = true, default_yes = false, }, }, bookmarks = { persist = false, }, experimental = { }, log = { enable = false, truncate = false, types = { all = false, config = false, copy_paste = false, dev = false, diagnostics = false, git = false, profile = false, watcher = false, }, }, } < ============================================================================== API *nvim-tree-api* nvim-tree exposes a public API. This is non breaking, with additions made as necessary. Please do not require or use modules other than `nvim-tree.api`, as internal modules will change without notice. The API is separated into multiple modules: • |nvim-tree-api-appearance| • |nvim-tree-api-commands| • |nvim-tree-api-events| • |nvim-tree-api-filter| • |nvim-tree-api-fs| • |nvim-tree-api-git| • |nvim-tree-api-map| • |nvim-tree-api-marks| • |nvim-tree-api-node| • |nvim-tree-api-tree| Modules are accessed via `api..` Example invocation of the `reload` function in the `tree` module: >lua local api = require("nvim-tree.api") api.tree.reload() < Generally, functions accepting a |nvim_tree.api.Node| as their first argument will use the node under the cursor when that argument is not present or nil. Some functions are mode-dependent: when invoked in visual mode they will operate on all nodes in the visual selection instead of a single node. See |nvim-tree-mappings-default| for which mappings support visual mode. e.g. the following are functionally identical: >lua api.node.open.edit(nil, { focus = true }) api.node.open.edit(api.tree.get_node_under_cursor(), { focus = true }) < *nvim_tree.api.Node* The Node class is a data class. Instances may be provided by API functions for use as a: • handle to pass back to API functions e.g. |nvim_tree.api.node.run.cmd()| • reference in callbacks e.g. |nvim_tree.config.sort.Sorter| {sorter} Please do not mutate the contents of any Node object. Fields: ~ • {absolute_path} (`string`) of the file or directory • {name} (`string`) file or directory name • {parent}? (`nvim_tree.api.DirectoryNode`) parent directory, nil for root • {type} (`"file"|"directory"|"link"`) |uv.fs_stat()| {type} • {executable} (`boolean`) file is executable • {fs_stat}? (`uv.fs_stat.result`) at time of last tree display, see |uv.fs_stat()| • {git_status} (`nvim_tree.git.Status?`) for files and directories • {diag_severity}? (`lsp.DiagnosticSeverity`) diagnostic status • {hidden} (`boolean`) node is not visible in the tree *nvim_tree.git.Status* Git statuses for a single node. `nvim_tree.git.XY`: 2 character string, see `man 1 git-status` "Short Format" {dir} status is derived from its contents: • `direct`: inherited from child files • `indirect`: inherited from child directories Fields: ~ • {file}? (`nvim_tree.git.XY`) status of a file node • {dir}? (`table<"direct"|"indirect", nvim_tree.git.XY[]>`) direct inclusive-or indirect status ============================================================================== API: appearance *nvim-tree-api-appearance* hi_test() *nvim_tree.api.appearance.hi_test()* Open a new buffer displaying all nvim-tree highlight groups, their link chain and concrete definition. Similar to `:so $VIMRUNTIME/syntax/hitest.vim` as per |:highlight| ============================================================================== API: commands *nvim-tree-api-commands* get() *nvim_tree.api.commands.get()* Retrieve all |nvim-tree-commands| They have been created via |nvim_create_user_command()|, see also |lua-guide-commands-create| Return: ~ (`table[]`) • {name} (`string`) name of the `:NvimTree*` command • {command} (`fun(args: vim.api.keyset.create_user_command.command_args)`) function that the command will execute • {opts} (`vim.api.keyset.user_command`) |command-attributes| ============================================================================== API: config *nvim-tree-api-config* default() *nvim_tree.api.config.default()* Default nvim-tree config. Return: ~ (`nvim_tree.config`) immutable deep clone global() *nvim_tree.api.config.global()* Global current nvim-tree config. Return: ~ (`nvim_tree.config`) immutable deep clone user() *nvim_tree.api.config.user()* Reference to config passed to |nvim-tree-setup| Return: ~ (`nvim_tree.config?`) nil when no config passed to setup ============================================================================== API: events *nvim-tree-api-events* subscribe({event_type}, {callback}) *nvim_tree.api.events.subscribe()* Register a handler for an event, see |nvim-tree-events|. Parameters: ~ • {event_type} (`nvim_tree.api.events.Event`) |nvim_tree_events_kind| • {callback} (`fun(payload: table?)`) ============================================================================== API: filter *nvim-tree-api-filter* custom.toggle() *nvim_tree.api.filter.custom.toggle()* Toggle |nvim_tree.config.filters| {custom} filter. dotfiles.toggle() *nvim_tree.api.filter.dotfiles.toggle()* Toggle |nvim_tree.config.filters| {dotfiles} filter. git.clean.toggle() *nvim_tree.api.filter.git.clean.toggle()* Toggle |nvim_tree.config.filters| {git_clean} filter. git.ignored.toggle() *nvim_tree.api.filter.git.ignored.toggle()* Toggle |nvim_tree.config.filters| {git_ignored} filter. live.clear() *nvim_tree.api.filter.live.clear()* Exit live filter mode. live.start() *nvim_tree.api.filter.live.start()* Enter live filter mode. Opens an input window with |filetype| `NvimTreeFilter` no_bookmark.toggle() *nvim_tree.api.filter.no_bookmark.toggle()* Toggle |nvim_tree.config.filters| {no_bookmark} filter. no_buffer.toggle() *nvim_tree.api.filter.no_buffer.toggle()* Toggle |nvim_tree.config.filters| {no_buffer} filter. toggle() *nvim_tree.api.filter.toggle()* Toggle |nvim_tree.config.filters| {enable} which applies to ALL filters. ============================================================================== API: fs *nvim-tree-api-fs* clear_clipboard() *nvim_tree.api.fs.clear_clipboard()* Clear the nvim-tree clipboard. copy.absolute_path({node}) *nvim_tree.api.fs.copy.absolute_path()* Copy the absolute path to the system clipboard. Parameters: ~ • {node} (`nvim_tree.api.Node?`) copy.basename({node}) *nvim_tree.api.fs.copy.basename()* Copy the name with extension omitted to the system clipboard. Parameters: ~ • {node} (`nvim_tree.api.Node?`) copy.filename({node}) *nvim_tree.api.fs.copy.filename()* Copy the name to the system clipboard. Parameters: ~ • {node} (`nvim_tree.api.Node?`) copy.node({node}) *nvim_tree.api.fs.copy.node()* Copy to the nvim-tree clipboard. In visual mode, copies all nodes in the visual selection. Parameters: ~ • {node} (`nvim_tree.api.Node?`) copy.relative_path({node}) *nvim_tree.api.fs.copy.relative_path()* Copy the path relative to the tree root to the system clipboard. Parameters: ~ • {node} (`nvim_tree.api.Node?`) create({node}) *nvim_tree.api.fs.create()* Prompt to create a file or directory. When {node} is a file it will be created in the parent directory. Use a trailing `"/"` to create a directory e.g. `"foo/"` Multiple directories/files may be created e.g. `"foo/bar/baz"` Parameters: ~ • {node} (`nvim_tree.api.Node?`) cut({node}) *nvim_tree.api.fs.cut()* Cut to the nvim-tree clipboard. In visual mode, cuts all nodes in the visual selection. Parameters: ~ • {node} (`nvim_tree.api.Node?`) paste({node}) *nvim_tree.api.fs.paste()* Paste from the nvim-tree clipboard. If {node} is a file it will pasted in the parent directory. Parameters: ~ • {node} (`nvim_tree.api.Node?`) print_clipboard() *nvim_tree.api.fs.print_clipboard()* Print the contents of the nvim-tree clipboard. remove({node}) *nvim_tree.api.fs.remove()* Delete from the file system. In visual mode, deletes all nodes in the visual selection with a single prompt. Parameters: ~ • {node} (`nvim_tree.api.Node?`) rename({node}) *nvim_tree.api.fs.rename()* Prompt to rename by name. Parameters: ~ • {node} (`nvim_tree.api.Node?`) rename_basename({node}) *nvim_tree.api.fs.rename_basename()* Prompt to rename by name with extension omitted. Parameters: ~ • {node} (`nvim_tree.api.Node?`) rename_full({node}) *nvim_tree.api.fs.rename_full()* Prompt to rename by absolute path. Parameters: ~ • {node} (`nvim_tree.api.Node?`) rename_node({node}) *nvim_tree.api.fs.rename_node()* Prompt to rename. Parameters: ~ • {node} (`nvim_tree.api.Node?`) rename_sub({node}) *nvim_tree.api.fs.rename_sub()* Prompt to rename by absolute path with name omitted. Parameters: ~ • {node} (`nvim_tree.api.Node?`) trash({node}) *nvim_tree.api.fs.trash()* Trash as per |nvim_tree.config.trash|. In visual mode, trashes all nodes in the visual selection with a single prompt. Parameters: ~ • {node} (`nvim_tree.api.Node?`) ============================================================================== API: git *nvim-tree-api-git* reload() *nvim_tree.api.git.reload()* Update the git status of the entire tree. ============================================================================== API: map *nvim-tree-api-map* keymap.current() *nvim_tree.api.map.keymap.current()* Retrieve all buffer local mappings for nvim-tree. These are the mappings that are applied by |nvim_tree.config| {on_attach}, which may include default mappings. Return: ~ (`vim.api.keyset.get_keymap[]`) keymap.default() *nvim_tree.api.map.keymap.default()* Retrieves the buffer local mappings for nvim-tree that are applied by |nvim_tree.api.map.on_attach.default()| Return: ~ (`vim.api.keyset.get_keymap[]`) on_attach.default({bufnr}) *nvim_tree.api.map.on_attach.default()* Apply all |nvim-tree-mappings-default|. Call from your |nvim_tree.config| {on_attach}. Parameters: ~ • {bufnr} (`integer`) use the `bufnr` passed to {on_attach} ============================================================================== API: marks *nvim-tree-api-marks* bulk.delete() *nvim_tree.api.marks.bulk.delete()* Delete all marked, prompting if |nvim_tree.config.ui.confirm| {remove} bulk.move() *nvim_tree.api.marks.bulk.move()* Prompts for a directory to move all marked nodes into. bulk.trash() *nvim_tree.api.marks.bulk.trash()* Delete all marked, prompting if |nvim_tree.config.ui.confirm| {trash} clear() *nvim_tree.api.marks.clear()* Clear all marks. get({node}) *nvim_tree.api.marks.get()* Return the node if it is marked. Parameters: ~ • {node} (`nvim_tree.api.Node?`) file or directory Return: ~ (`nvim_tree.api.Node?`) list() *nvim_tree.api.marks.list()* Retrieve all marked nodes. Return: ~ (`nvim_tree.api.Node[]`) navigate.next() *nvim_tree.api.marks.navigate.next()* Navigate to the next marked node, wraps. navigate.prev() *nvim_tree.api.marks.navigate.prev()* Navigate to the previous marked node, wraps. navigate.select() *nvim_tree.api.marks.navigate.select()* Prompts for selection of a marked node, sorted by absolute paths. A folder will be focused, a file will be opened. toggle({node}) *nvim_tree.api.marks.toggle()* Toggle mark. In visual mode, toggles all nodes in the visual selection. Parameters: ~ • {node} (`nvim_tree.api.Node?`) file or directory ============================================================================== API: node *nvim-tree-api-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} (`nvim_tree.api.Node?`) file • {opts} (`table?`) • {force}? (`boolean`, default: false) Proceed even if the buffer is modified. buffer.wipe({node}, {opts}) *nvim_tree.api.node.buffer.wipe()* Wipes node's related buffer, if one exists. Executes |:bwipe| or |:bwipe|! Parameters: ~ • {node} (`nvim_tree.api.Node?`) file • {opts} (`table?`) optional • {force}? (`boolean`, default: false) Proceed even if the buffer is modified. collapse({node}, {opts}) *nvim_tree.api.node.collapse()* Collapse the tree under a directory or a file's parent directory. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file • {opts} (`table?`) optional • {keep_buffers}? (`boolean`, default: false) Do not collapse nodes with open buffers. expand({node}, {opts}) *nvim_tree.api.node.expand()* Recursively expand all nodes under a directory or a file's parent directory. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file • {opts} (`table?`) optional • {expand_until}? (`fun(expansion_count: integer, node: Node): boolean`) Return `true` if `node` should be expanded. `expansion_count` is the total number of folders expanded. *nvim_tree.api.node.navigate.diagnostics.next()* navigate.diagnostics.next({node}) Navigate to the next item showing diagnostic status. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file *nvim_tree.api.node.navigate.diagnostics.next_recursive()* navigate.diagnostics.next_recursive({node}) Navigate to the next item showing diagnostic status, recursively. Needs |nvim_tree.config.diagnostics| {show_on_dirs} Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file *nvim_tree.api.node.navigate.diagnostics.prev()* navigate.diagnostics.prev({node}) Navigate to the previous item showing diagnostic status. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file *nvim_tree.api.node.navigate.diagnostics.prev_recursive()* navigate.diagnostics.prev_recursive({node}) Navigate to the previous item showing diagnostic status, recursively. Needs |nvim_tree.config.diagnostics| {show_on_dirs} Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file navigate.git.next({node}) *nvim_tree.api.node.navigate.git.next()* Navigate to the next item showing git status. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file *nvim_tree.api.node.navigate.git.next_recursive()* navigate.git.next_recursive({node}) Navigate to the next item showing git status, recursively. Needs |nvim_tree.config.git| {show_on_dirs} Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file *nvim_tree.api.node.navigate.git.next_skip_gitignored()* navigate.git.next_skip_gitignored({node}) Navigate to the next item showing git status, skipping `.gitignore` Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file navigate.git.prev({node}) *nvim_tree.api.node.navigate.git.prev()* Navigate to the previous item showing git status. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file *nvim_tree.api.node.navigate.git.prev_recursive()* navigate.git.prev_recursive({node}) Navigate to the previous item showing git status, recursively. Needs |nvim_tree.config.git| {show_on_dirs} Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file *nvim_tree.api.node.navigate.git.prev_skip_gitignored()* navigate.git.prev_skip_gitignored({node}) Navigate to the previous item showing git status, skipping `.gitignore` Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file *nvim_tree.api.node.navigate.opened.next()* navigate.opened.next({node}) Navigate to the next |bufloaded()| file. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file *nvim_tree.api.node.navigate.opened.prev()* navigate.opened.prev({node}) Navigate to the previous |bufloaded()| file. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file navigate.parent({node}) *nvim_tree.api.node.navigate.parent()* Navigate to the parent directory of the node. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file *nvim_tree.api.node.navigate.parent_close()* navigate.parent_close({node}) Navigate to the parent directory of the node, closing it. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file *nvim_tree.api.node.navigate.sibling.first()* navigate.sibling.first({node}) Navigate to the first node in the current node's folder. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file *nvim_tree.api.node.navigate.sibling.last()* navigate.sibling.last({node}) Navigate to the last node in the current node's folder. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file *nvim_tree.api.node.navigate.sibling.next()* navigate.sibling.next({node}) Navigate to the next node in the current node's folder, wraps. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file *nvim_tree.api.node.navigate.sibling.prev()* navigate.sibling.prev({node}) Navigate to the previous node in the current node's folder, wraps. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file open.edit({node}, {opts}) *nvim_tree.api.node.open.edit()* • file: open as per |nvim_tree.config.actions.open_file| • directory: expand or collapse • root: change directory up Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file • {opts} (`table?`) optional • {quit_on_open}? (`boolean`, default: false) Quits the tree when opening the file. • {focus}? (`boolean`, default: false) Keep focus in the tree when opening the file. open.horizontal({node}) *nvim_tree.api.node.open.horizontal()* Open file in a new horizontal split. Parameters: ~ • {node} (`nvim_tree.api.Node?`) file *nvim_tree.api.node.open.horizontal_no_picker()* open.horizontal_no_picker({node}) Open file in a new horizontal split without using the window picker. Parameters: ~ • {node} (`nvim_tree.api.Node?`) file *nvim_tree.api.node.open.no_window_picker()* open.no_window_picker({node}) Open file without using the window picker. Parameters: ~ • {node} (`nvim_tree.api.Node?`) file open.preview({node}) *nvim_tree.api.node.open.preview()* Open file with |'bufhidden'| set to `delete`. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file *nvim_tree.api.node.open.preview_no_picker()* open.preview_no_picker({node}) Open file with |'bufhidden'| set to `delete` without using the window picker. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file *nvim_tree.api.node.open.replace_tree_buffer()* open.replace_tree_buffer({node}) Open file in place: in the nvim-tree window. Parameters: ~ • {node} (`nvim_tree.api.Node?`) file open.tab({node}) *nvim_tree.api.node.open.tab()* Open file in a new tab. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file open.tab_drop({node}) *nvim_tree.api.node.open.tab_drop()* Switch to tab containing window with selected file if it exists. Open file in new tab otherwise. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file *nvim_tree.api.node.open.toggle_group_empty()* open.toggle_group_empty({node}) Toggle |nvim_tree.config.renderer| {group_empty} for a directory. Needs {group_empty} set. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory open.vertical({node}) *nvim_tree.api.node.open.vertical()* Open file in a new vertical split. Parameters: ~ • {node} (`nvim_tree.api.Node?`) file *nvim_tree.api.node.open.vertical_no_picker()* open.vertical_no_picker({node}) Open file in a new vertical split without using the window picker. Parameters: ~ • {node} (`nvim_tree.api.Node?`) file run.cmd({node}) *nvim_tree.api.node.run.cmd()* Enter |cmdline| with the full path of the node and the cursor at the start of the line. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file run.system({node}) *nvim_tree.api.node.run.system()* Execute |nvim_tree.config.system_open|. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file show_info_popup({node}) *nvim_tree.api.node.show_info_popup()* Open a popup window showing: fullpath, size, accessed, modified, created. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file ============================================================================== API: tree *nvim-tree-api-tree* change_root({path}) *nvim_tree.api.tree.change_root()* Change the tree's root to a path. Parameters: ~ • {path} (`string?`) absolute or relative path. change_root_to_node({node}) *nvim_tree.api.tree.change_root_to_node()* Change the tree's root to a folder node or the parent of a file node. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file *nvim_tree.api.tree.change_root_to_parent()* change_root_to_parent({node}) Change the tree's root to the parent of a node. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory or file close() *nvim_tree.api.tree.close()* Close the tree, affecting all tabs as per |nvim_tree.config.tab.sync| {close} close_in_all_tabs() *nvim_tree.api.tree.close_in_all_tabs()* Close the tree in all tabs. close_in_this_tab() *nvim_tree.api.tree.close_in_this_tab()* Close the tree in this tab only. collapse_all({opts}) *nvim_tree.api.tree.collapse_all()* Collapse the tree. Parameters: ~ • {opts} (`table?`) optional • {keep_buffers}? (`boolean`, default: false) Do not collapse nodes with open buffers. expand_all({node}, {opts}) *nvim_tree.api.tree.expand_all()* Recursively expand all nodes under the tree root or specified folder. Parameters: ~ • {node} (`nvim_tree.api.Node?`) directory • {opts} (`table?`) optional • {expand_until}? (`fun(expansion_count: integer, node: Node): boolean`) Return `true` if `node` should be expanded. `expansion_count` is the total number of folders expanded. find_file({opts}) *nvim_tree.api.tree.find_file()* Find and focus a file or folder in the tree. Finds current buffer unless otherwise specified. Parameters: ~ • {opts} (`table?`) optional • {buf}? (`string|integer`) Absolute/relative path OR `bufnr` to find. • {open}? (`boolean`, default: false) Open the tree if necessary. • {current_window}? (`boolean`, default: false) Requires {open}: open in the current window. • {winid}? (`integer`) Open the tree in the specified |window-ID|, overrides {current_window} • {update_root}? (`boolean`, default: false) Update root after find, see |nvim_tree.config.update_focused_file| {update_root} • {focus}? (`boolean`, default: false) Focus the tree window. focus() *nvim_tree.api.tree.focus()* Focus the tree, opening it if necessary. Retained for compatibility, use |nvim_tree.api.tree.open()| with no arguments instead. get_node_under_cursor() *nvim_tree.api.tree.get_node_under_cursor()* Retrieve the currently focused node. Return: ~ (`nvim_tree.api.Node?`) nil if tree is not visible. get_nodes() *nvim_tree.api.tree.get_nodes()* Retrieve a hierarchical list of all the nodes. Return: ~ (`nvim_tree.api.Node[]`) is_tree_buf({bufnr}) *nvim_tree.api.tree.is_tree_buf()* Checks if a buffer is an nvim-tree. Parameters: ~ • {bufnr} (`integer?`) 0 or nil for current buffer. Return: ~ (`boolean`) is_visible({opts}) *nvim_tree.api.tree.is_visible()* Checks if nvim-tree is visible on the current, specified or any tab. Parameters: ~ • {opts} (`table?`) optional • {tabpage}? (`integer`) |tab-ID| 0 or nil for current. • {any_tabpage}? (`boolean`, default: false) Visible on any tab. Return: ~ (`boolean`) open({opts}) *nvim_tree.api.tree.open()* Open the tree, focusing it if already open. Parameters: ~ • {opts} (`table?`) optional • {path}? (`string`) Root directory for the tree • {current_window}? (`boolean`, default: false) Open the tree in the current window • {winid}? (`integer`) Open the tree in the specified |window-ID|, overrides {current_window} • {find_file}? (`boolean`, default: false) Find the current buffer. • {update_root}? (`boolean`, default: false) Update root following {find_file}, see |nvim_tree.config.update_focused_file| {update_root} reload() *nvim_tree.api.tree.reload()* Refresh the tree. Does nothing if closed. resize({opts}) *nvim_tree.api.tree.resize()* Resize the tree, persisting the new size. Resets to |nvim_tree.config.view| {width} when no {opts} provided. Only one option is supported, priority order: {width}, {absolute}, {relative}. {absolute} and {relative} do nothing when |nvim_tree.config.view| {width} is a function. Parameters: ~ • {opts} (`table?`) optional • {width}? (`nvim_tree.config.view.width.spec|nvim_tree.config.view.width`) New |nvim_tree.config.view| {width} value. • {absolute}? (`integer`) Set the width. • {relative}? (`integer`) Increase or decrease the width. search_node() *nvim_tree.api.tree.search_node()* Open the search dialogue. toggle({opts}) *nvim_tree.api.tree.toggle()* Open or close the tree. Parameters: ~ • {opts} (`table?`) optional • {path}? (`string`) Root directory for the tree • {current_window}? (`boolean`, default: false) Open the tree in the current window • {winid}? (`integer`) Open the tree in the specified |window-ID|, overrides {current_window} • {find_file}? (`boolean`, default: false) Find the current buffer. • {update_root}? (`boolean`, default: false) Update root following {find_file}, see |nvim_tree.config.update_focused_file| {update_root} • {focus}? (`boolean`, default: true) Focus the tree when opening. toggle_help() *nvim_tree.api.tree.toggle_help()* Toggle help view. winid({opts}) *nvim_tree.api.tree.winid()* Retrieve the window of the open tree. Parameters: ~ • {opts} (`table?`) optional • {tabpage}? (`integer`) |tab-ID| 0 or nil for current. Return: ~ (`integer?`) |window-ID|, nil if tree is not visible. ============================================================================== Class: Class *nvim-tree-class* nvim-tree uses the https://github.com/rxi/classic class framework adding safe casts, instanceof mixin and conventional destructors. The key differences between classic and ordinary Lua classes: • The constructor |nvim_tree.Class:new()| is not responsible for allocation: `self` is available when the constructor is called. • Instances are constructed via the `__call` meta method: `SomeClass(args)` Classes are conventionally named using camel case e.g. `MyClass` Classes are created by extending another class: >lua local Class = require("nvim-tree.classic") ---@class (exact) Fruit: nvim_tree.Class ---@field ... local Fruit = Class:extend() ---@class (exact) Apple: Fruit ---@field ... local Apple = Fruit:extend() < Implementing a constructor |nvim_tree.Class:new()| is optional, however it must call the `super` constructor: >lua ---@protected ---@param args AppleArgs function Apple:new(args) ---@type FruitArgs local fruit_args = ... Apple.super.new(self, fruit_args) --- < Create an instance of a class using the `__call` meta method that will invoke the constructor: >lua ---@type AppleArgs local args = ... local an_apple = Apple(args) -- above will call `Apple:new(args)` < In order to strongly type instantiation, the following pattern is used to type the meta method `__call` with arguments and return: >lua ---@class (exact) Fruit: nvim_tree.Class ---@field ... local Fruit = Class:extend() ---@class (exact) FruitArgs ---@field ... ---@class Fruit ---@overload fun(args: FruitArgs): Fruit ---@protected ---@param args FruitArgs function Fruit:new(args) < *nvim_tree.Class* Fields: ~ • {super} (`nvim_tree.Class`) Parent class, `Class` for base classes. • {new} (`fun(self: nvim_tree.Class, ...: any)`) See |nvim_tree.Class:new()|. • {destroy} (`fun(self: nvim_tree.Class)`) See |nvim_tree.Class:destroy()|. • {extend} (`fun(self: nvim_tree.Class): [nvim_tree.Class]`) See |nvim_tree.Class:extend()|. • {implement} (`fun(self: nvim_tree.Class, mixin: nvim_tree.Class)`) See |nvim_tree.Class:implement()|. • {is} (`fun(self: nvim_tree.Class, class: T): boolean`) See |nvim_tree.Class:is()|. • {as} (`fun(self: nvim_tree.Class, class: T): T?`) See |nvim_tree.Class:as()|. • {nop} (`fun(self: nvim_tree.Class, ...: any)`) See |nvim_tree.Class:nop()|. Class:as({class}) *nvim_tree.Class:as()* Type safe cast. If instance |nvim_tree.Class:is()|, cast to {class} and return it, otherwise nil. Parameters: ~ • {class} (`any`) `` Return: ~ (`any?`) `` Class:destroy() *nvim_tree.Class:destroy()* Conventional destructor, optional, must be called by the owner. Parent destructor must be invoked using the form `Parent.destroy(self)` Class:extend() *nvim_tree.Class:extend()* Create a new class by extending another class. Base classes extend `Class` Return: ~ (`[nvim_tree.Class]`) child class Class:implement({mixin}) *nvim_tree.Class:implement()* Add the methods and fields of a mixin using the form `SomeClass:implement(MixinClass)` If the mixin has fields, it must implement a constructor. Parameters: ~ • {mixin} (`nvim_tree.Class`) Class:is({class}) *nvim_tree.Class:is()* Instance of. Test whether an object is {class}, inherits {class} or implements mixin {class}. Parameters: ~ • {class} (`any`) `` Return: ~ (`boolean`) Class:new({...}) *nvim_tree.Class:new()* Constructor: `self` has been allocated and is available. Super constructor must be called using the form `Child.super.new(self, parent_args)` Parameters: ~ • {...} (`any`) constructor arguments Class:nop({...}) *nvim_tree.Class:nop()* Utility method to bypass unused param warnings in abstract methods. Parameters: ~ • {...} (`any`) ============================================================================== Class: Decorator *nvim-tree-class-decorator* Highlighting and icons for nodes are provided by Decorators, see |nvim-tree-icons-highlighting| for an overview. Decorators are rendered in |nvim_tree.config.renderer| {decorators} order of additive precedence, with later decorators applying additively over earlier. You may provide your own in addition to the builtin decorators, see |nvim-tree-class-decorator-example|. Decorators may: • Add icons • Set a highlight group name for the name or icons • Override node icon To register your decorator: • Create a class that extends |nvim_tree.api.Decorator| • Register it by adding the class to |nvim_tree.config.renderer| {decorators} Your decorator will be constructed and executed each time the tree is rendered. Your class must: • |nvim_tree.Class:extend()| the interface |nvim_tree.api.Decorator| • Provide a no-arguments constructor |nvim_tree.Class:new()| that sets the mandatory fields: • {enabled} • {highlight_range} • {icon_placement} • Call |nvim_tree.api.Decorator:define_sign()| in your constructor when {icon_placement} is `"signcolumn"` Your class may: • Implement methods to provide decorations: • |nvim_tree.api.Decorator:highlight_group()| • |nvim_tree.api.Decorator:icon_node()| • |nvim_tree.api.Decorator:icons()| *nvim_tree.api.Decorator* Extends: |nvim_tree.Class| Decorator interface Fields: ~ • {enabled} (`boolean`) Enable this decorator. • {highlight_range} (`nvim_tree.config.renderer.highlight`) What to highlight: |nvim_tree.config.renderer.highlight| • {icon_placement} (`"none"|nvim_tree.config.renderer.icons.placement`) Where to place the icons: |nvim_tree.config.renderer.icons.placement| • {icon_node} (`fun(self: nvim_tree.api.Decorator, node: nvim_tree.api.Node): nvim_tree.api.highlighted_string?`) See |nvim_tree.api.Decorator:icon_node()|. • {icons} (`fun(self: nvim_tree.api.Decorator, node: nvim_tree.api.Node): nvim_tree.api.highlighted_string[]?`) See |nvim_tree.api.Decorator:icons()|. • {highlight_group} (`fun(self: nvim_tree.api.Decorator, node: nvim_tree.api.Node): string?`) See |nvim_tree.api.Decorator:highlight_group()|. • {define_sign} (`fun(self: nvim_tree.api.Decorator, icon: nvim_tree.api.highlighted_string?)`) See |nvim_tree.api.Decorator:define_sign()|. *nvim_tree.api.highlighted_string* Text or glyphs with optional highlight group names to apply to it. Fields: ~ • {str} (`string`) One or many glyphs/characters. • {hl} (`string[]`) Highlight group names to apply in order. Empty table for no highlighting. Decorator:define_sign({icon}) *nvim_tree.api.Decorator:define_sign()* Defines a sign for an icon. This is mandatory and necessary only when {icon_placement} is `"signcolumn"` This must be called during your constructor for all icons that you will return from |nvim_tree.api.Decorator:icons()| Parameters: ~ • {icon} (`nvim_tree.api.highlighted_string?`) does nothing if nil *nvim_tree.api.Decorator:highlight_group()* Decorator:highlight_group({node}) One highlight group that applies additively to the {node} name for {highlight_range}. Abstract, optional to implement. Parameters: ~ • {node} (`nvim_tree.api.Node`) Return: ~ (`string?`) highlight group name `nil` when no highlighting to apply to the node Decorator:icon_node({node}) *nvim_tree.api.Decorator:icon_node()* Icon to override for the node. Abstract, optional to implement. Parameters: ~ • {node} (`nvim_tree.api.Node`) Return: ~ (`nvim_tree.api.highlighted_string?`) icon `nil` for no override Decorator:icons({node}) *nvim_tree.api.Decorator:icons()* Icons to add to the node as per {icon_placement} Abstract, optional to implement. Parameters: ~ • {node} (`nvim_tree.api.Node`) Return: ~ (`nvim_tree.api.highlighted_string[]?`) icons `nil` or empty table for no icons. Only the first glyph of {str} is used when {icon_placement} is `"signcolumn"` ============================================================================== Class: Decorator example *nvim-tree-class-decorator-example* A decorator class for nodes named "example", overriding 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", "Open", "Hidden", "Modified", "Bookmark", "Diagnostics", "Copied", MyDecorator, "Cut", }, }, }) < Contents of `my-decorator.lua`: >lua ---@class (exact) MyDecorator: nvim_tree.api.Decorator ---@field private my_icon1 nvim_tree.api.highlighted_string ---@field private my_icon2 nvim_tree.api.highlighted_string ---@field private my_icon_node nvim_tree.api.highlighted_string ---@field private my_highlight_group string local MyDecorator = require("nvim-tree.api").Decorator:extend() ---Mandatory constructor :new() will be called once per tree render, with no arguments. function MyDecorator:new() self.enabled = true self.highlight_range = "name" self.icon_placement = "after" -- create your icons and highlights once, applied to every node 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 signs only once -- Only needed if you are using icon_placement = "signcolumn" -- self:define_sign(self.my_icon1) -- self:define_sign(self.my_icon2) end ---Override node icon ---@param node nvim_tree.api.Node ---@return nvim_tree.api.highlighted_string? icon_node function MyDecorator:icon_node(node) if node.name == "example" then return self.my_icon_node else return nil end end ---Return two icons for DecoratorIconPlacement "after" ---@param node nvim_tree.api.Node ---@return nvim_tree.api.highlighted_string[]? icons function MyDecorator:icons(node) if node.name == "example" then return { self.my_icon1, self.my_icon2, } else return nil end end ---Exactly one highlight group for DecoratorHighlightRange "name" ---@param node nvim_tree.api.Node ---@return string? highlight_group function MyDecorator:highlight_group(node) if node.name == "example" then return self.my_highlight_group else return nil end end return MyDecorator < vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl: ================================================ FILE: lua/nvim-tree/_meta/api/appearance.lua ================================================ ---@meta local nvim_tree = { api = { appearance = {} } } --- ---Open a new buffer displaying all nvim-tree highlight groups, their link chain and concrete definition. --- ---Similar to `:so $VIMRUNTIME/syntax/hitest.vim` as per |:highlight| --- function nvim_tree.api.appearance.hi_test() end return nvim_tree.api.appearance ================================================ FILE: lua/nvim-tree/_meta/api/commands.lua ================================================ ---@meta local nvim_tree = { api = { commands = {} } } --- ---@class nvim_tree.api.commands.Command ---@inlinedoc --- ---@field name string name of the `:NvimTree*` command ---@field command fun(args: vim.api.keyset.create_user_command.command_args) function that the command will execute ---@field opts vim.api.keyset.user_command [command-attributes] --- ---Retrieve all [nvim-tree-commands] --- ---They have been created via [nvim_create_user_command()], see also [lua-guide-commands-create] --- ---@return nvim_tree.api.commands.Command[] function nvim_tree.api.commands.get() end return nvim_tree.api.commands ================================================ FILE: lua/nvim-tree/_meta/api/config.lua ================================================ ---@meta local nvim_tree = { api = { config = {} } } --- ---Default nvim-tree config. --- ---@return nvim_tree.config immutable deep clone function nvim_tree.api.config.default() end --- ---Global current nvim-tree config. --- ---@return nvim_tree.config immutable deep clone function nvim_tree.api.config.global() end --- ---Reference to config passed to [nvim-tree-setup] --- ---@return nvim_tree.config? nil when no config passed to setup function nvim_tree.api.config.user() end nvim_tree.api.config.mappings = {} ---@deprecated use `nvim_tree.api.map.keymap.current()` function nvim_tree.api.config.mappings.get_keymap() end ---@deprecated use `nvim_tree.api.map.keymap.default()` function nvim_tree.api.config.mappings.get_keymap_default() end ---@deprecated use `nvim_tree.api.map.on_attach.default()` function nvim_tree.api.config.mappings.default_on_attach(bufnr) end return nvim_tree.api.config ================================================ FILE: lua/nvim-tree/_meta/api/decorator.lua ================================================ ---@meta ---@brief ---Highlighting and icons for nodes are provided by Decorators, see [nvim-tree-icons-highlighting] for an overview. --- ---Decorators are rendered in [nvim_tree.config.renderer] {decorators} order of additive precedence, with later decorators applying additively over earlier. --- ---You may provide your own in addition to the builtin decorators, see |nvim-tree-class-decorator-example|. --- ---Decorators may: ---- Add icons ---- Set a highlight group name for the name or icons ---- Override node icon --- ---To register your decorator: ---- Create a class that extends [nvim_tree.api.Decorator] ---- Register it by adding the class to [nvim_tree.config.renderer] {decorators} --- ---Your decorator will be constructed and executed each time the tree is rendered. --- ---Your class must: ---- [nvim_tree.Class:extend()] the interface [nvim_tree.api.Decorator] ---- Provide a no-arguments constructor [nvim_tree.Class:new()] that sets the mandatory fields: --- - {enabled} --- - {highlight_range} --- - {icon_placement} ---- Call [nvim_tree.api.Decorator:define_sign()] in your constructor when {icon_placement} is `"signcolumn"` --- ---Your class may: ---- Implement methods to provide decorations: --- - [nvim_tree.api.Decorator:highlight_group()] --- - [nvim_tree.api.Decorator:icon_node()] --- - [nvim_tree.api.Decorator:icons()] local nvim_tree = { api = {} } local Class = require("nvim-tree.classic") --- ---Text or glyphs with optional highlight group names to apply to it. --- ---@class nvim_tree.api.highlighted_string --- ---One or many glyphs/characters. ---@field str string --- ---Highlight group names to apply in order. Empty table for no highlighting. ---@field hl string[] --- ---Decorator interface --- ---@class nvim_tree.api.Decorator: nvim_tree.Class --- ---Enable this decorator. ---@field enabled boolean --- ---What to highlight: [nvim_tree.config.renderer.highlight] ---@field highlight_range nvim_tree.config.renderer.highlight --- ---Where to place the icons: [nvim_tree.config.renderer.icons.placement] ---@field icon_placement "none"|nvim_tree.config.renderer.icons.placement --- local Decorator = Class:extend() nvim_tree.api.Decorator = Decorator --- ---Icon to override for the node. --- ---Abstract, optional to implement. --- ---@param node nvim_tree.api.Node ---@return nvim_tree.api.highlighted_string? icon `nil` for no override function Decorator:icon_node(node) end --- ---Icons to add to the node as per {icon_placement} --- ---Abstract, optional to implement. --- ---@param node nvim_tree.api.Node ---@return nvim_tree.api.highlighted_string[]? icons `nil` or empty table for no icons. Only the first glyph of {str} is used when {icon_placement} is `"signcolumn"` function Decorator:icons(node) end --- ---One highlight group that applies additively to the {node} name for {highlight_range}. --- ---Abstract, optional to implement. --- ---@param node nvim_tree.api.Node ---@return string? highlight group name `nil` when no highlighting to apply to the node function Decorator:highlight_group(node) end --- ---Defines a sign for an icon. This is mandatory and necessary only when {icon_placement} is `"signcolumn"` --- ---This must be called during your constructor for all icons that you will return from [nvim_tree.api.Decorator:icons()] --- ---@param icon nvim_tree.api.highlighted_string? does nothing if nil function Decorator:define_sign(icon) end return nvim_tree.api.Decorator ================================================ FILE: lua/nvim-tree/_meta/api/decorator_example.lua ================================================ ---@meta error("Cannot require a meta file") ---@brief --- ---A decorator class for nodes named "example", overriding 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", --- "Open", --- "Hidden", --- "Modified", --- "Bookmark", --- "Diagnostics", --- "Copied", --- MyDecorator, --- "Cut", --- }, --- }, --- }) ---``` ---Contents of `my-decorator.lua`: ---```lua --- --- ---@class (exact) MyDecorator: nvim_tree.api.Decorator --- ---@field private my_icon1 nvim_tree.api.highlighted_string --- ---@field private my_icon2 nvim_tree.api.highlighted_string --- ---@field private my_icon_node nvim_tree.api.highlighted_string --- ---@field private my_highlight_group string --- local MyDecorator = require("nvim-tree.api").Decorator:extend() --- --- ---Mandatory constructor :new() will be called once per tree render, with no arguments. --- function MyDecorator:new() --- self.enabled = true --- self.highlight_range = "name" --- self.icon_placement = "after" --- --- -- create your icons and highlights once, applied to every node --- 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 signs only once --- -- Only needed if you are using icon_placement = "signcolumn" --- -- self:define_sign(self.my_icon1) --- -- self:define_sign(self.my_icon2) --- end --- --- ---Override node icon --- ---@param node nvim_tree.api.Node --- ---@return nvim_tree.api.highlighted_string? icon_node --- function MyDecorator:icon_node(node) --- if node.name == "example" then --- return self.my_icon_node --- else --- return nil --- end --- end --- --- ---Return two icons for DecoratorIconPlacement "after" --- ---@param node nvim_tree.api.Node --- ---@return nvim_tree.api.highlighted_string[]? icons --- function MyDecorator:icons(node) --- if node.name == "example" then --- return { self.my_icon1, self.my_icon2, } --- else --- return nil --- end --- end --- --- ---Exactly one highlight group for DecoratorHighlightRange "name" --- ---@param node nvim_tree.api.Node --- ---@return string? highlight_group --- function MyDecorator:highlight_group(node) --- if node.name == "example" then --- return self.my_highlight_group --- else --- return nil --- end --- end --- --- return MyDecorator ---``` ================================================ FILE: lua/nvim-tree/_meta/api/deprecated.lua ================================================ ---@meta -- Deprecated top level API modules. -- Remember to add mappings in legacy.lua `api_map` local nvim_tree = { api = {} } nvim_tree.api.live_filter = {} ---@deprecated use `nvim_tree.api.filter.live.start()` function nvim_tree.api.live_filter.start() end ---@deprecated use `nvim_tree.api.filter.live.clear()` function nvim_tree.api.live_filter.clear() end nvim_tree.api.diagnostics = {} ---@deprecated use `nvim_tree.api.appearance.hi_test()` function nvim_tree.api.diagnostics.hi_test() end nvim_tree.api.decorator = {} ---@class nvim_tree.api.decorator.UserDecorator: nvim_tree.api.Decorator ---@deprecated use `nvim_tree.api.Decorator` nvim_tree.api.decorator.UserDecorator = nvim_tree.api.Decorator return nvim_tree.api ================================================ FILE: lua/nvim-tree/_meta/api/events.lua ================================================ ---@meta local nvim_tree = { api = { events = {} } } ---@enum nvim_tree.api.events.Event nvim_tree.api.events.Event = { Ready = "Ready", WillRenameNode = "WillRenameNode", NodeRenamed = "NodeRenamed", TreePreOpen = "TreePreOpen", TreeOpen = "TreeOpen", TreeClose = "TreeClose", WillCreateFile = "WillCreateFile", FileCreated = "FileCreated", WillRemoveFile = "WillRemoveFile", FileRemoved = "FileRemoved", FolderCreated = "FolderCreated", FolderRemoved = "FolderRemoved", Resize = "Resize", TreeAttachedPost = "TreeAttachedPost", TreeRendered = "TreeRendered", } --- ---Register a handler for an event, see [nvim-tree-events]. --- ---@param event_type nvim_tree.api.events.Event [nvim_tree_events_kind] ---@param callback fun(payload: table?) function nvim_tree.api.events.subscribe(event_type, callback) end return nvim_tree.api.events ================================================ FILE: lua/nvim-tree/_meta/api/filter.lua ================================================ ---@meta local nvim_tree = { api = { filter = {} } } --- ---Toggle [nvim_tree.config.filters] {enable} which applies to ALL filters. --- function nvim_tree.api.filter.toggle() end nvim_tree.api.filter.live = {} --- ---Enter live filter mode. Opens an input window with [filetype] `NvimTreeFilter` --- function nvim_tree.api.filter.live.start() end --- ---Exit live filter mode. --- function nvim_tree.api.filter.live.clear() end nvim_tree.api.filter.git = {} nvim_tree.api.filter.git.clean = {} --- ---Toggle [nvim_tree.config.filters] {git_clean} filter. --- function nvim_tree.api.filter.git.clean.toggle() end nvim_tree.api.filter.git.ignored = {} --- ---Toggle [nvim_tree.config.filters] {git_ignored} filter. --- function nvim_tree.api.filter.git.ignored.toggle() end nvim_tree.api.filter.dotfiles = {} --- ---Toggle [nvim_tree.config.filters] {dotfiles} filter. --- function nvim_tree.api.filter.dotfiles.toggle() end nvim_tree.api.filter.no_buffer = {} --- ---Toggle [nvim_tree.config.filters] {no_buffer} filter. --- function nvim_tree.api.filter.no_buffer.toggle() end nvim_tree.api.filter.no_bookmark = {} --- ---Toggle [nvim_tree.config.filters] {no_bookmark} filter. --- function nvim_tree.api.filter.no_bookmark.toggle() end nvim_tree.api.filter.custom = {} --- ---Toggle [nvim_tree.config.filters] {custom} filter. --- function nvim_tree.api.filter.custom.toggle() end return nvim_tree.api.filter ================================================ FILE: lua/nvim-tree/_meta/api/fs.lua ================================================ ---@meta local nvim_tree = { api = { fs = {} } } --- ---Clear the nvim-tree clipboard. --- function nvim_tree.api.fs.clear_clipboard() end nvim_tree.api.fs.copy = {} --- ---Copy the absolute path to the system clipboard. --- ---@param node? nvim_tree.api.Node function nvim_tree.api.fs.copy.absolute_path(node) end --- ---Copy the name with extension omitted to the system clipboard. --- ---@param node? nvim_tree.api.Node function nvim_tree.api.fs.copy.basename(node) end --- ---Copy the name to the system clipboard. --- ---@param node? nvim_tree.api.Node function nvim_tree.api.fs.copy.filename(node) end --- ---Copy to the nvim-tree clipboard. ---In visual mode, copies all nodes in the visual selection. --- ---@param node? nvim_tree.api.Node function nvim_tree.api.fs.copy.node(node) end --- ---Copy the path relative to the tree root to the system clipboard. --- ---@param node? nvim_tree.api.Node function nvim_tree.api.fs.copy.relative_path(node) end --- ---Prompt to create a file or directory. --- ---When {node} is a file it will be created in the parent directory. --- ---Use a trailing `"/"` to create a directory e.g. `"foo/"` --- ---Multiple directories/files may be created e.g. `"foo/bar/baz"` --- ---@param node? nvim_tree.api.Node function nvim_tree.api.fs.create(node) end --- ---Cut to the nvim-tree clipboard. ---In visual mode, cuts all nodes in the visual selection. --- ---@param node? nvim_tree.api.Node function nvim_tree.api.fs.cut(node) end --- ---Paste from the nvim-tree clipboard. --- ---If {node} is a file it will pasted in the parent directory. --- ---@param node? nvim_tree.api.Node function nvim_tree.api.fs.paste(node) end --- ---Print the contents of the nvim-tree clipboard. --- function nvim_tree.api.fs.print_clipboard() end --- ---Delete from the file system. ---In visual mode, deletes all nodes in the visual selection with a single prompt. --- ---@param node? nvim_tree.api.Node function nvim_tree.api.fs.remove(node) end --- ---Prompt to rename by name. --- ---@param node? nvim_tree.api.Node function nvim_tree.api.fs.rename(node) end --- ---Prompt to rename by name with extension omitted. --- ---@param node? nvim_tree.api.Node function nvim_tree.api.fs.rename_basename(node) end --- ---Prompt to rename by absolute path. --- ---@param node? nvim_tree.api.Node function nvim_tree.api.fs.rename_full(node) end --- ---Prompt to rename. --- ---@param node? nvim_tree.api.Node function nvim_tree.api.fs.rename_node(node) end --- ---Prompt to rename by absolute path with name omitted. --- ---@param node? nvim_tree.api.Node function nvim_tree.api.fs.rename_sub(node) end --- ---Trash as per |nvim_tree.config.trash|. ---In visual mode, trashes all nodes in the visual selection with a single prompt. --- ---@param node? nvim_tree.api.Node function nvim_tree.api.fs.trash(node) end return nvim_tree.api.fs ================================================ FILE: lua/nvim-tree/_meta/api/git.lua ================================================ ---@meta local nvim_tree = { api = { git = {} } } --- ---Update the git status of the entire tree. --- function nvim_tree.api.git.reload() end return nvim_tree.api.git ================================================ FILE: lua/nvim-tree/_meta/api/map.lua ================================================ ---@meta local nvim_tree = { api = { map = {} } } nvim_tree.api.map.keymap = {} --- ---Retrieve all buffer local mappings for nvim-tree. These are the mappings that are applied by [nvim_tree.config] {on_attach}, which may include default mappings. --- ---@return vim.api.keyset.get_keymap[] function nvim_tree.api.map.keymap.current() end --- --- Retrieves the buffer local mappings for nvim-tree that are applied by [nvim_tree.api.map.on_attach.default()] --- ---@return vim.api.keyset.get_keymap[] function nvim_tree.api.map.keymap.default() end nvim_tree.api.map.on_attach = {} --- ---Apply all [nvim-tree-mappings-default]. Call from your [nvim_tree.config] {on_attach}. --- ---@param bufnr integer use the `bufnr` passed to {on_attach} function nvim_tree.api.map.on_attach.default(bufnr) end return nvim_tree.api.map ================================================ FILE: lua/nvim-tree/_meta/api/marks.lua ================================================ ---@meta local nvim_tree = { api = { marks = {} } } --- ---Return the node if it is marked. --- ---@param node? nvim_tree.api.Node file or directory ---@return nvim_tree.api.Node? function nvim_tree.api.marks.get(node) end --- ---Retrieve all marked nodes. --- ---@return nvim_tree.api.Node[] function nvim_tree.api.marks.list() end --- ---Toggle mark. In visual mode, toggles all nodes in the visual selection. --- ---@param node? nvim_tree.api.Node file or directory function nvim_tree.api.marks.toggle(node) end --- ---Clear all marks. --- function nvim_tree.api.marks.clear() end nvim_tree.api.marks.bulk = {} --- ---Delete all marked, prompting if [nvim_tree.config.ui.confirm] {remove} --- function nvim_tree.api.marks.bulk.delete() end --- ---Delete all marked, prompting if [nvim_tree.config.ui.confirm] {trash} --- function nvim_tree.api.marks.bulk.trash() end --- ---Prompts for a directory to move all marked nodes into. --- function nvim_tree.api.marks.bulk.move() end nvim_tree.api.marks.navigate = {} --- ---Navigate to the next marked node, wraps. --- function nvim_tree.api.marks.navigate.next() end --- ---Navigate to the previous marked node, wraps. --- function nvim_tree.api.marks.navigate.prev() end --- ---Prompts for selection of a marked node, sorted by absolute paths. ---A folder will be focused, a file will be opened. --- function nvim_tree.api.marks.navigate.select() end return nvim_tree.api.marks ================================================ FILE: lua/nvim-tree/_meta/api/node.lua ================================================ ---@meta local nvim_tree = { api = { node = {} } } nvim_tree.api.node.open = {} --- ---@class nvim_tree.api.node.open.Opts ---@inlinedoc --- ---Quits the tree when opening the file. ---(default: false) ---@field quit_on_open? boolean --- ---Keep focus in the tree when opening the file. ---(default: false) ---@field focus? boolean --- ---- file: open as per [nvim_tree.config.actions.open_file] ---- directory: expand or collapse ---- root: change directory up --- ---@param node? nvim_tree.api.Node directory or file ---@param opts? nvim_tree.api.node.open.Opts optional function nvim_tree.api.node.open.edit(node, opts) end --- ---Open file in a new horizontal split. --- ---@param node? nvim_tree.api.Node file function nvim_tree.api.node.open.horizontal(node) end --- ---Open file in a new horizontal split without using the window picker. --- ---@param node? nvim_tree.api.Node file function nvim_tree.api.node.open.horizontal_no_picker(node) end --- ---Open file without using the window picker. --- ---@param node? nvim_tree.api.Node file function nvim_tree.api.node.open.no_window_picker(node) end --- ---Open file with ['bufhidden'] set to `delete`. --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.open.preview(node) end --- ---Open file with ['bufhidden'] set to `delete` without using the window picker. --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.open.preview_no_picker(node) end --- ---Open file in place: in the nvim-tree window. --- ---@param node? nvim_tree.api.Node file function nvim_tree.api.node.open.replace_tree_buffer(node) end --- ---Open file in a new tab. --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.open.tab(node) end --- ---Switch to tab containing window with selected file if it exists. Open file in new tab otherwise. --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.open.tab_drop(node) end --- ---Toggle [nvim_tree.config.renderer] {group_empty} for a directory. Needs {group_empty} set. --- ---@param node? nvim_tree.api.Node directory function nvim_tree.api.node.open.toggle_group_empty(node) end --- ---Open file in a new vertical split. --- ---@param node? nvim_tree.api.Node file function nvim_tree.api.node.open.vertical(node) end --- ---Open file in a new vertical split without using the window picker. --- ---@param node? nvim_tree.api.Node file function nvim_tree.api.node.open.vertical_no_picker(node) end --- ---@class nvim_tree.api.node.buffer.RemoveOpts ---@inlinedoc --- ---Proceed even if the buffer is modified. ---(default: false) ---@field force? boolean nvim_tree.api.node.buffer = {} --- ---Deletes node's related buffer, if one exists. Executes [:bdelete] or [:bdelete]! --- ---@param node? nvim_tree.api.Node file ---@param opts? nvim_tree.api.node.buffer.RemoveOpts function nvim_tree.api.node.buffer.delete(node, opts) end --- ---Wipes node's related buffer, if one exists. Executes [:bwipe] or [:bwipe]! --- ---@param node? nvim_tree.api.Node file ---@param opts? nvim_tree.api.node.buffer.RemoveOpts optional function nvim_tree.api.node.buffer.wipe(node, opts) end --- ---Collapse the tree under a directory or a file's parent directory. --- ---@param node? nvim_tree.api.Node directory or file ---@param opts? nvim_tree.api.node.collapse.Opts optional function nvim_tree.api.node.collapse(node, opts) end ---@class nvim_tree.api.node.collapse.Opts ---@inlinedoc --- ---Do not collapse nodes with open buffers. ---(default: false) ---@field keep_buffers? boolean --- ---Recursively expand all nodes under a directory or a file's parent directory. --- ---@param node? nvim_tree.api.Node directory or file ---@param opts? nvim_tree.api.node.expand.Opts optional function nvim_tree.api.node.expand(node, opts) end ---@class nvim_tree.api.node.expand.Opts ---@inlinedoc --- ---Return `true` if `node` should be expanded. `expansion_count` is the total number of folders expanded. ---@field expand_until? fun(expansion_count: integer, node: Node): boolean nvim_tree.api.node.navigate = {} --- ---Navigate to the parent directory of the node. --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.navigate.parent(node) end --- ---Navigate to the parent directory of the node, closing it. --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.navigate.parent_close(node) end nvim_tree.api.node.navigate.diagnostics = {} --- ---Navigate to the next item showing diagnostic status. --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.navigate.diagnostics.next(node) end --- ---Navigate to the next item showing diagnostic status, recursively. Needs [nvim_tree.config.diagnostics] {show_on_dirs} --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.navigate.diagnostics.next_recursive(node) end --- ---Navigate to the previous item showing diagnostic status. --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.navigate.diagnostics.prev(node) end --- ---Navigate to the previous item showing diagnostic status, recursively. Needs [nvim_tree.config.diagnostics] {show_on_dirs} --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.navigate.diagnostics.prev_recursive(node) end nvim_tree.api.node.navigate.git = {} --- ---Navigate to the next item showing git status. --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.navigate.git.next(node) end --- ---Navigate to the next item showing git status, recursively. Needs [nvim_tree.config.git] {show_on_dirs} --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.navigate.git.next_recursive(node) end --- ---Navigate to the next item showing git status, skipping `.gitignore` --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.navigate.git.next_skip_gitignored(node) end --- ---Navigate to the previous item showing git status. --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.navigate.git.prev(node) end --- ---Navigate to the previous item showing git status, recursively. Needs [nvim_tree.config.git] {show_on_dirs} --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.navigate.git.prev_recursive(node) end --- ---Navigate to the previous item showing git status, skipping `.gitignore` --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.navigate.git.prev_skip_gitignored(node) end nvim_tree.api.node.navigate.opened = {} --- ---Navigate to the next [bufloaded()] file. --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.navigate.opened.next(node) end --- ---Navigate to the previous [bufloaded()] file. --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.navigate.opened.prev(node) end nvim_tree.api.node.navigate.sibling = {} --- ---Navigate to the first node in the current node's folder. --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.navigate.sibling.first(node) end --- ---Navigate to the last node in the current node's folder. --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.navigate.sibling.last(node) end --- ---Navigate to the next node in the current node's folder, wraps. --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.navigate.sibling.next(node) end --- ---Navigate to the previous node in the current node's folder, wraps. --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.navigate.sibling.prev(node) end nvim_tree.api.node.run = {} --- ---Enter [cmdline] with the full path of the node and the cursor at the start of the line. --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.run.cmd(node) end --- ---Execute [nvim_tree.config.system_open]. --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.run.system(node) end --- ---Open a popup window showing: fullpath, size, accessed, modified, created. --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.node.show_info_popup(node) end return nvim_tree.api.node ================================================ FILE: lua/nvim-tree/_meta/api/tree.lua ================================================ ---@meta local nvim_tree = { api = { tree = {} } } --- ---Open the tree, focusing it if already open. --- ---@param opts? nvim_tree.api.tree.open.Opts optional function nvim_tree.api.tree.open(opts) end ---@class nvim_tree.api.tree.open.Opts ---@inlinedoc --- ---Root directory for the tree ---@field path? string --- ---Open the tree in the current window ---(default: false) ---@field current_window? boolean --- ---Open the tree in the specified [window-ID], overrides {current_window} ---@field winid? integer --- ---Find the current buffer. ---(default: false) ---@field find_file? boolean --- ---Update root following {find_file}, see [nvim_tree.config.update_focused_file] {update_root} ---(default: false) ---@field update_root? boolean --- ---Open or close the tree. --- ---@param opts? nvim_tree.api.tree.toggle.Opts optional function nvim_tree.api.tree.toggle(opts) end ---@class nvim_tree.api.tree.toggle.Opts ---@inlinedoc --- ---Root directory for the tree ---@field path? string --- ---Open the tree in the current window ---(default: false) ---@field current_window? boolean --- ---Open the tree in the specified [window-ID], overrides {current_window} ---@field winid? integer --- ---Find the current buffer. ---(default: false) ---@field find_file? boolean --- ---Update root following {find_file}, see [nvim_tree.config.update_focused_file] {update_root} ---(default: false) ---@field update_root? boolean --- ---Focus the tree when opening. ---(default: true) ---@field focus? boolean --- ---Close the tree, affecting all tabs as per [nvim_tree.config.tab.sync] {close} --- function nvim_tree.api.tree.close() end --- ---Close the tree in this tab only. --- function nvim_tree.api.tree.close_in_this_tab() end --- ---Close the tree in all tabs. --- function nvim_tree.api.tree.close_in_all_tabs() end --- ---Focus the tree, opening it if necessary. Retained for compatibility, use [nvim_tree.api.tree.open()] with no arguments instead. --- function nvim_tree.api.tree.focus() end --- ---Refresh the tree. Does nothing if closed. --- function nvim_tree.api.tree.reload() end --- ---Resize the tree, persisting the new size. Resets to [nvim_tree.config.view] {width} when no {opts} provided. --- ---Only one option is supported, priority order: {width}, {absolute}, {relative}. --- ---{absolute} and {relative} do nothing when [nvim_tree.config.view] {width} is a function. --- ---@param opts? nvim_tree.api.tree.resize.Opts optional function nvim_tree.api.tree.resize(opts) end ---@class nvim_tree.api.tree.resize.Opts ---@inlinedoc --- ---New [nvim_tree.config.view] {width} value. ---@field width? nvim_tree.config.view.width.spec|nvim_tree.config.view.width --- ---Set the width. ---@field absolute? integer --- ---Increase or decrease the width. ---@field relative? integer --- ---Change the tree's root to a path. --- ---@param path? string absolute or relative path. function nvim_tree.api.tree.change_root(path) end --- ---Change the tree's root to a folder node or the parent of a file node. --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.tree.change_root_to_node(node) end --- ---Change the tree's root to the parent of a node. --- ---@param node? nvim_tree.api.Node directory or file function nvim_tree.api.tree.change_root_to_parent(node) end --- ---Retrieve the currently focused node. --- ---@return nvim_tree.api.Node? nil if tree is not visible. function nvim_tree.api.tree.get_node_under_cursor() end --- ---Retrieve a hierarchical list of all the nodes. --- ---@return nvim_tree.api.Node[] function nvim_tree.api.tree.get_nodes() end --- ---Find and focus a file or folder in the tree. Finds current buffer unless otherwise specified. --- ---@param opts? nvim_tree.api.tree.find_file.Opts optional function nvim_tree.api.tree.find_file(opts) end ---@class nvim_tree.api.tree.find_file.Opts ---@inlinedoc --- ---Absolute/relative path OR `bufnr` to find. ---@field buf? string|integer --- ---Open the tree if necessary. ---(default: false) ---@field open? boolean --- ---Requires {open}: open in the current window. ---(default: false) ---@field current_window? boolean --- ---Open the tree in the specified [window-ID], overrides {current_window} ---@field winid? integer --- ---Update root after find, see [nvim_tree.config.update_focused_file] {update_root} ---(default: false) ---@field update_root? boolean --- ---Focus the tree window. ---(default: false) ---@field focus? boolean --- ---Open the search dialogue. --- function nvim_tree.api.tree.search_node() end --- ---Collapse the tree. --- ---@param opts? nvim_tree.api.node.collapse.Opts optional function nvim_tree.api.tree.collapse_all(opts) end --- ---Recursively expand all nodes under the tree root or specified folder. --- ---@param node? nvim_tree.api.Node directory ---@param opts? nvim_tree.api.node.expand.Opts optional function nvim_tree.api.tree.expand_all(node, opts) end --- ---Toggle help view. --- function nvim_tree.api.tree.toggle_help() end --- ---Checks if a buffer is an nvim-tree. --- ---@param bufnr? integer 0 or nil for current buffer. --- ---@return boolean function nvim_tree.api.tree.is_tree_buf(bufnr) end --- ---Checks if nvim-tree is visible on the current, specified or any tab. --- ---@param opts? nvim_tree.api.tree.is_visible.Opts optional ---@return boolean function nvim_tree.api.tree.is_visible(opts) end ---@class nvim_tree.api.tree.is_visible.Opts ---@inlinedoc --- --- [tab-ID] 0 or nil for current. ---@field tabpage? integer --- ---Visible on any tab. ---(default: false) ---@field any_tabpage? boolean --- ---Retrieve the window of the open tree. --- ---@param opts? nvim_tree.api.tree.winid.Opts optional ---@return integer? [window-ID], nil if tree is not visible. function nvim_tree.api.tree.winid(opts) end ---@class nvim_tree.api.tree.winid.Opts ---@inlinedoc --- ---[tab-ID] 0 or nil for current. ---@field tabpage? integer ---@deprecated use `nvim_tree.api.filter.toggle()` function nvim_tree.api.tree.toggle_enable_filters() end ---@deprecated use `nvim_tree.api.filter.git.ignored.toggle()` function nvim_tree.api.tree.toggle_gitignore_filter() end ---@deprecated use `nvim_tree.api.filter.git.clean.toggle()` function nvim_tree.api.tree.toggle_git_clean_filter() end ---@deprecated use `nvim_tree.api.filter.no_buffer.toggle()` function nvim_tree.api.tree.toggle_no_buffer_filter() end ---@deprecated use `nvim_tree.api.filter.custom.toggle()` function nvim_tree.api.tree.toggle_custom_filter() end ---@deprecated use `nvim_tree.api.filter.dotfiles.toggle()` function nvim_tree.api.tree.toggle_hidden_filter() end ---@deprecated use `nvim_tree.api.filter.no_bookmark.toggle()` function nvim_tree.api.tree.toggle_no_bookmark_filter() end return nvim_tree.api.tree ================================================ FILE: lua/nvim-tree/_meta/classes.lua ================================================ ---@meta error("Cannot require a meta file") --- ---File --- ---@class (exact) nvim_tree.api.FileNode: nvim_tree.api.Node ---@field extension string --- ---Directory --- ---@class (exact) nvim_tree.api.DirectoryNode: nvim_tree.api.Node ---@field has_children boolean ---@field nodes nvim_tree.api.Node[] ---@field open boolean --- ---Root Directory --- ---@class (exact) nvim_tree.api.RootNode: nvim_tree.api.DirectoryNode --- ---Link mixin --- ---@class (exact) nvim_tree.api.LinkNode ---@field link_to string ---@field fs_stat_target uv.fs_stat.result --- ---File Link --- ---@class (exact) nvim_tree.api.FileLinkNode: nvim_tree.api.FileNode, nvim_tree.api.LinkNode --- ---DirectoryLink --- ---@class (exact) nvim_tree.api.DirectoryLinkNode: nvim_tree.api.DirectoryNode, nvim_tree.api.LinkNode ================================================ FILE: lua/nvim-tree/_meta/config/actions.lua ================================================ ---@meta error("Cannot require a meta file") ---@class nvim_tree.config.actions --- ---Use the system clipboard for copy/paste. Copied text will be stored in registers `+` (system), otherwise, it will be stored in `1` and `"` ---(default: `true`) ---@field use_system_clipboard? boolean --- ---[nvim_tree.config.actions.change_dir] ---@field change_dir? nvim_tree.config.actions.change_dir --- ---[nvim_tree.config.actions.expand_all] ---@field expand_all? nvim_tree.config.actions.expand_all --- ---[nvim_tree.config.actions.file_popup] ---@field file_popup? nvim_tree.config.actions.file_popup --- ---[nvim_tree.config.actions.open_file] ---@field open_file? nvim_tree.config.actions.open_file --- ---[nvim_tree.config.actions.remove_file] ---@field remove_file? nvim_tree.config.actions.remove_file --- vim [current-directory] behaviour ---@class nvim_tree.config.actions.change_dir --- ---Change the working directory when changing directories in the tree ---(default: `true`) ---@field enable? boolean --- ---Use `:cd` instead of `:lcd` when changing directories. ---(default: `false`) ---@field global? boolean --- --- Restrict changing to a directory above the global cwd. ---(default: `false`) ---@field restrict_above_cwd? boolean ---Configure [nvim_tree.api.tree.expand_all()] and [nvim_tree.api.node.expand()] ---@class nvim_tree.config.actions.expand_all --- ---Limit the number of folders being explored when expanding every folder. Avoids hanging Nvim when running this action on very large folders. ---(default: `300`) ---@field max_folder_discovery? integer --- ---A list of directories that should not be expanded automatically e.g `{ ".git", "target", "build" }` ---(default: `{}`) ---@field exclude? string[] ---{file_popup} floating window. --- ---{open_win_config} is passed to [nvim_open_win()], default: ---```lua --- { --- col = 1, --- row = 1, --- relative = "cursor", --- border = "shadow", --- style = "minimal", --- } ---``` ---You shouldn't define {width} and {height} values here. They will be overridden to fit the file_popup content. ---@class nvim_tree.config.actions.file_popup --- ---(default: `{ col = 1, row = 1, relative = "cursor", border = "shadow", style = "minimal", }`) ---@field open_win_config? vim.api.keyset.win_config ---Opening files. ---@class nvim_tree.config.actions.open_file --- ---Closes the explorer when opening a file ---(default: `false`) ---@field quit_on_open? boolean --- ---Prevent new opened file from opening in the same window as the tree. ---(default: `true`) ---@field eject? boolean --- ---Resizes the tree when opening a file ---(default: `true`) ---@field resize_window? boolean --- ---[nvim_tree.config.actions.open_file.window_picker] ---@field window_picker? nvim_tree.config.actions.open_file.window_picker ---A window picker will be shown when there are multiple windows available to open a file. It will show a single character identifier in each window's status line. --- ---When it is not enabled the file will open in the window from which you last opened the tree, obeying {exclude} --- ---You may define a {picker} function that should return the window id that will open the node, or `nil` if an invalid window is picked or user cancelled the action. The picker may create a new window. --- ---@class nvim_tree.config.actions.open_file.window_picker --- ---(default: `true`) ---@field enable? boolean --- ---Change the default window picker or define your own. ---(default: `"default"`) ---@field picker? "default"|(fun(): integer) --- ---Identifier characters to use. ---(default: `"ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"`) ---@field chars? string --- ---[nvim_tree.config.actions.open_file.window_picker.exclude] ---@field exclude? nvim_tree.config.actions.open_file.window_picker.exclude ---Tables of buffer option names mapped to a list of option values. Windows containing matching buffers will not be: --- - available when using a window picker --- - selected when not using a window picker ---@class nvim_tree.config.actions.open_file.window_picker.exclude --- ---(default: `{ "notify", "lazy", "qf", "diff", "fugitive", "fugitiveblame", }`) ---@field filetype? string[] --- ---(default: `{ "nofile", "terminal", "help", }`) ---@field buftype? string[] ---Removing files. ---@class nvim_tree.config.actions.remove_file --- ---Close any window that displays a file when removing that file from the tree. ---(default: `true`) ---@field close_window? boolean ================================================ FILE: lua/nvim-tree/_meta/config/bookmarks.lua ================================================ ---@meta error("Cannot require a meta file") ---Optionally {persist} bookmarks to a json file: ---- `true` use default: `stdpath("data") .. "/nvim-tree-bookmarks.json"` ---- `false` do not persist ---- `string` absolute path of your choice --- ---@class nvim_tree.config.bookmarks --- ---(default: `false`) ---@field persist? boolean|string ================================================ FILE: lua/nvim-tree/_meta/config/default.lua ================================================ ---@brief ---Following is the default configuration, see |nvim_tree.config| for details. --- ---```lua --- --- ---@type nvim_tree.config --- local config = { --- config-default-injection-placeholder --- } ---``` ================================================ FILE: lua/nvim-tree/_meta/config/diagnostics.lua ================================================ ---@meta error("Cannot require a meta file") ---Integrate with [lsp] or COC diagnostics. --- ---See [nvim-tree-icons-highlighting]. --- ---@class nvim_tree.config.diagnostics --- ---(default: `false`) ---@field enable? boolean --- ---Idle milliseconds between diagnostic event and tree update. ---(default: `500`) ---@field debounce_delay? integer --- ---Show diagnostic icons on parent directories. ---(default: `false`) ---@field show_on_dirs? boolean --- ---Show diagnostics icons on directories that are open. Requires {show_on_dirs}. ---(default: `true`) ---@field show_on_open_dirs? boolean --- ---Global [vim.diagnostic.Opts] overrides {severity} and {icons} ---(default: `false`) ---@field diagnostic_opts? boolean --- ---@field severity? nvim_tree.config.diagnostics.severity --- ---@field icons? nvim_tree.config.diagnostics.icons ---[nvim_tree.config.diagnostics.severity]() ---@class nvim_tree.config.diagnostics.severity ---@inlinedoc --- ---[vim.diagnostic.severity] ---(default: HINT) ---@field min? vim.diagnostic.Severity --- ---[vim.diagnostic.severity] ---(default: ERROR) ---@field max? vim.diagnostic.Severity ---[nvim_tree.config.diagnostics.icons]() ---@class nvim_tree.config.diagnostics.icons ---@inlinedoc --- ---(default: `""` ) ---@field hint? string --- ---(default: `""` ) ---@field info? string --- ---(default: `""` ) ---@field warning? string --- ---(default: `""` ) ---@field error? string ================================================ FILE: lua/nvim-tree/_meta/config/experimental.lua ================================================ ---@meta error("Cannot require a meta file") ---Experimental features that may become default or optional functionality. --- ---In the event of a problem please disable the experiment and raise an issue. --- ---@class nvim_tree.config.experimental --- --Example below for future reference: -- --Buffers opened by nvim-tree will use with relative paths instead of absolute. --(default: false) --@field relative_path? boolean ================================================ FILE: lua/nvim-tree/_meta/config/filesystem_watchers.lua ================================================ ---@meta error("Cannot require a meta file") ---Use file system watchers (libuv `uv_fs_event_t`) to monitor the filesystem for changes and update the tree. --- ---With this feature, the tree will be partially updated on specific directory changes, resulting in better performance. --- ---Watchers may be disabled for absolute directory paths via {ignore_dirs}. --- - A list of [regular-expression] to match a path, backslash escaped e.g. `"my-proj/\\.build$"` OR --- - A function that is passed an absolute path and returns `true` to disable ---This may be useful when a path is not in `.gitignore` or git integration is disabled. --- ---After {max_events} consecutive filesystem events on a single directory with an interval < {debounce_delay}: ---- The filesystem watcher will be disabled for that directory. ---- A warning notification will be shown. ---- Consider adding this directory to {ignore_dirs} --- ---@class nvim_tree.config.filesystem_watchers --- ---(default: `true`) ---@field enable? boolean --- ---Idle milliseconds between filesystem change and tree update. ---(default: `50`) ---@field debounce_delay? integer --- ---Disable for specific directories. ---(default: `{ "/.ccls-cache", "/build", "/node_modules", "/target", "/.zig-cache"}`) ---@field ignore_dirs? string[]|(fun(path: string): boolean) --- ---Disable for a single directory after {max_events} consecutive events with an interval < {debounce_delay}. ---Set to 0 to allow unlimited consecutive events. ---(default: `0` or `1000` on windows) ---@field max_events? integer ================================================ FILE: lua/nvim-tree/_meta/config/filters.lua ================================================ ---@meta error("Cannot require a meta file") ---Filters may be applied to the tree to exlude the display of file/directories. --- ---Multiple filters may be applied at once. --- ---Filters can be set at startup or toggled live via API with default mappings. --- ---`I `{git_ignored}` `|nvim_tree.api.filter.git.ignored.toggle()| ---Ignore files based on `.gitignore`. Requires |nvim_tree.config.git| --- ---`H `{dotfiles}` `|nvim_tree.api.filter.dotfiles.toggle()| ---Filter dotfiles: files/directories starting with a `.` --- ---`C `{git_clean}` `|nvim_tree.api.filter.git.clean.toggle()| ---Filter files with no git status. `.gitignore` files will not be filtered when {git_ignored}, as they are effectively dirty. --- ---`B `{no_buffer}` `|nvim_tree.api.filter.no_buffer.toggle()| ---Filter files that have no |buflisted()| buffer. For performance reasons buffer delete/wipe may not be immediately shown. A reload or filesystem event will always result in an update. --- ---`M `{no_bookmark}` `|nvim_tree.api.filter.no_bookmark.toggle()| ---Filter files that are not bookmarked. Enabling this is not useful as there is no means yet to persist bookmarks. --- ---`U `{custom}` `|nvim_tree.api.filter.custom.toggle()| ---Disable specific file/directory names via: ---- a list of backslash escaped |regular-expression| e.g. `"^\\.git""` ---- a function passed the absolute path of the directory. --- ---All filters including live filter may be disabled via {enable} and toggled with |nvim_tree.api.filter.toggle()| --- ---Files/directories may be {exclude}d from filtering: they will always be shown, overriding {git_ignored}, {dotfiles} and {custom}. ---@class nvim_tree.config.filters --- ---Enable all filters. ---(default: `true`) ---@field enable? boolean --- ---(default: `true`) ---@field git_ignored? boolean --- ---(default: `false`) ---@field dotfiles? boolean --- ---(default: `false`) ---@field git_clean? boolean --- ---(default: `false`) ---@field no_buffer? boolean --- ---(default: `false`) ---@field no_bookmark? boolean --- ---(default: `{}`) ---@field custom? string[]|(fun(absolute_path: string): boolean) --- ---(default: `{}`) ---@field exclude? string[] ================================================ FILE: lua/nvim-tree/_meta/config/git.lua ================================================ ---@meta error("Cannot require a meta file") ---Git operations are run in the background thus status may not immediately appear. --- ---Processes will be killed if they exceed {timeout} ms. Git integration will be disabled following 5 timeouts and you will be notified. --- ---Git integration may be disabled for git top-level directories via {disable_for_dirs}: --- - A list of relative paths evaluated with [fnamemodify()] `:p` OR --- - A function that is passed an absolute path and returns `true` to disable --- ---See [nvim-tree-icons-highlighting]. --- ---@class nvim_tree.config.git --- ---(default: `true`) ---@field enable? boolean --- ---Show status icons of children when directory itself has no status icon ---(default: `true`) ---@field show_on_dirs? boolean --- ---Show status icons of children on directories that are open. Requires {show_on_dirs}. ---(default: `true`) ---@field show_on_open_dirs? boolean --- ---Disable for top level paths. ---(default: `{}`) ---@field disable_for_dirs? string[]|(fun(path: string): boolean) --- ---`git` processes timeout milliseconds. ---(default: `400`) ---@field timeout? integer --- ---Use `cygpath` if available to resolve paths for git. ---(Default: `false`) ---@field cygwin_support? boolean ================================================ FILE: lua/nvim-tree/_meta/config/help.lua ================================================ ---@meta error("Cannot require a meta file") --- ---@class nvim_tree.config.help --- ---Alphabetically. ---(default: `"key"`) ---@field sort_by? "key"|"desc" ================================================ FILE: lua/nvim-tree/_meta/config/hijack_directories.lua ================================================ ---@meta error("Cannot require a meta file") ---Hijack directory buffers by replacing the directory buffer with the tree. --- ---Disable this option if you use vim-dirvish or dirbuf.nvim. --- ---If [nvim_tree.config] {hijack_netrw} and {disable_netrw} are `false` this feature will be disabled. --- ---@class nvim_tree.config.hijack_directories --- ---(default: `true`) ---@field enable? boolean --- ---Open if the tree was previously closed. ---(default: `true`) ---@field auto_open? boolean ================================================ FILE: lua/nvim-tree/_meta/config/live_filter.lua ================================================ ---@meta error("Cannot require a meta file") --- Live filter allows you to filter the tree nodes dynamically using [regular-expression] matching. --- --- This feature is bound to the `f` key by default. The filter can be cleared with the `F` key by default. --- ---@class nvim_tree.config.live_filter --- ---Prefix of the filter displayed in the buffer. ---(default: `"[FILTER]: "`) ---@field prefix? string --- ---Whether to filter folders or not. ---(default: `true`) ---@field always_show_folders? boolean ================================================ FILE: lua/nvim-tree/_meta/config/log.lua ================================================ ---@meta error("Cannot require a meta file") ---Log to a file `nvim-tree.log` in [stdpath()] `log`, usually `${XDG_STATE_HOME}/nvim` --- ---@class nvim_tree.config.log --- ---(default: `false`) ---@field enable? boolean --- ---Remove existing log file at startup. ---(default: `false`) ---@field truncate? boolean --- ---[nvim_tree.config.log.types] ---@field types? nvim_tree.config.log.types ---Specify which information to log. ---@class nvim_tree.config.log.types --- ---Everything. ---(default: `false`) ---@field all? boolean --- --- Timing of some operations. ---(default: `false`) ---@field profile? boolean --- ---Config and mappings, at startup. ---(default: `false`) ---@field config? boolean --- ---File copy and paste actions. ---(default: `false`) ---@field copy_paste? boolean --- ---Used for local development only. Not useful for users. ---(default: `false`) ---@field dev? boolean --- ---LSP and COC processing, verbose. ---(default: `false`) ---@field diagnostics? boolean --- ---Git processing, verbose. ---(default: `false`) ---@field git? boolean --- ---[nvim_tree.config.filesystem_watchers] processing, verbose. ---(default: `false`) ---@field watcher? boolean ================================================ FILE: lua/nvim-tree/_meta/config/modified.lua ================================================ ---@meta error("Cannot require a meta file") ---Indicate which files have unsaved modification. ---To see modified status in the tree you will need: --- - [nvim_tree.config.renderer.icons.show] {modified} OR --- - [nvim_tree.config.renderer] {highlight_modified} --- ---See [nvim-tree-icons-highlighting]. --- ---@class nvim_tree.config.modified --- ---(default: `false`) ---@field enable? boolean --- ---Show modified indication on directory whose children are modified. ---(default: `true`) ---@field show_on_dirs? boolean --- ---Show modified indication on open directories. Requires {show_on_dirs}. ---(default: `false`) ---@field show_on_open_dirs? boolean ================================================ FILE: lua/nvim-tree/_meta/config/notify.lua ================================================ ---@meta error("Cannot require a meta file") ---nvim-tree |vim.log.levels| ---- `ERROR`: hard errors e.g. failure to read from the file system. ---- `WARN`: non-fatal errors e.g. unable to system open a file. ---- `INFO`: information only e.g. file copy path confirmation. ---- `DEBUG`: information for troubleshooting, e.g. failures in some window closing operations. --- ---@class nvim_tree.config.notify --- ---Specify minimum notification |vim.log.levels| ---(Default: `vim.log.levels.INFO`) ---@field threshold? vim.log.levels --- ---Use absolute paths in FS action notifications, otherwise item names. ---(default: `true`) ---@field absolute_path? boolean ================================================ FILE: lua/nvim-tree/_meta/config/renderer.lua ================================================ ---@meta error("Cannot require a meta file") ---Controls the appearance of the tree. --- ---{highlight_} [nvim_tree.config.renderer.highlight]() --- ---See [nvim-tree-icons-highlighting] --- ---- `"none"`: no highlighting ---- `"icon"`: icon only ---- `"name"`: name only ---- `"all"`: icon and name ---@alias nvim_tree.config.renderer.highlight "none"|"icon"|"name"|"all" --- --- --- ---{decorators} [nvim_tree.config.renderer.decorator]() --- ---See [nvim-tree-icons-highlighting] --- ---A builtin decorator name `string` or |nvim_tree.api.Decorator| class. --- ---Builtin decorators in their default order: ---- `"Git"` ---- `"Open"` ---- `"Hidden"` ---- `"Modified"` ---- `"Bookmark"` ---- `"Diagnostics"` ---- `"Copied"` ---- `"Cut"` --- ---Specify {decorators} is a list e.g. `{ "Git", MyDecorator, "Cut" }` --- ---@alias nvim_tree.config.renderer.decorator nvim_tree.api.Decorator|"Git"|"Open"|"Hidden"|"Modified"|"Bookmark"|"Diagnostics"|"Copied"|"Cut" --- --- --- ---{root_folder_label} [nvim_tree.config.renderer.root_folder_label]() --- ---Controls the root folder name and visibility: ---- `string`: [filename-modifiers] format string, default `":~:s?$?/..?"` ---- `false`: to disable ---- `fun(root_cwd: string): string`: return a literal string from root's absolute path e.g. ---```lua --- my_root_folder_label = function(path) --- return ".../" .. vim.fn.fnamemodify(path, ":t") --- end ---``` ---@alias nvim_tree.config.renderer.root_folder_label string|false|(fun(root_cwd: string): string) --- --- --- ---{hidden_display} [nvim_tree.config.renderer.hidden_display]() --- ---Summary of hidden nodes, below the last node in the directory, highlighted with `NvimTreeHiddenDisplay`. ---- `"none"`: disabled, default ---- `"simple"`: total number of hidden files e.g. --- - (3 hidden) ---- `"all"`: total and by reason: the filter that hid the node e.g. --- - (14 total git: 5, dotfile: 9) ---- `(fun(hidden_stats: nvim_tree.config.renderer.hidden_stats): string)` --- ---See [nvim_tree.config.renderer.hidden_stats] for details and example. ---@alias nvim_tree.config.renderer.hidden_display "none"|"simple"|"all"|(fun(hidden_stats: nvim_tree.config.renderer.hidden_stats): string?) --- --- --- ---@class nvim_tree.config.renderer --- ---Appends a trailing slash to folder and symlink folder target names. ---(default: `false`) ---@field add_trailing? boolean --- ---Compact folders that only contain a single folder into one node. Function variant takes the relative path of grouped folders and returns a string to be displayed. ---(default: `false`) ---@field group_empty? boolean|(fun(relative_path: string): string) --- ---Display nodes whose name length is wider than the width of nvim-tree window in floating window. ---(default: `false`) ---@field full_name? boolean --- ---[nvim_tree.config.renderer.root_folder_label] ---(default: `":~:s?$?/..?"`) ---@field root_folder_label? nvim_tree.config.renderer.root_folder_label --- ---Number of spaces for each tree nesting level. Minimum 1. ---(default: `2`) ---@field indent_width? integer --- ---[nvim_tree.config.renderer.hidden_display] ---(default: `none`) ---@field hidden_display? nvim_tree.config.renderer.hidden_display --- ---Appends an arrow followed by the target of the symlink. ---(default: `true`) ---@field symlink_destination? boolean --- ---List in order of additive precedence. ---(default: `{ "Git", "Open", "Hidden", "Modified", "Bookmark", "Diagnostics", "Copied", "Cut", }`) ---@field decorators? nvim_tree.config.renderer.decorator[] --- ---(default: `"none"`) ---@field highlight_git? nvim_tree.config.renderer.highlight --- ---(default: `"none"`) ---@field highlight_opened_files? nvim_tree.config.renderer.highlight --- ---(default: `"none"`) ---@field highlight_hidden? nvim_tree.config.renderer.highlight --- ---(default: `"none"`) ---@field highlight_modified? nvim_tree.config.renderer.highlight --- ---(default: `"none"`) ---@field highlight_bookmarks? nvim_tree.config.renderer.highlight --- ---(default: `"none"`) ---@field highlight_diagnostics? nvim_tree.config.renderer.highlight --- ---(default: `"name"`) ---@field highlight_clipboard? nvim_tree.config.renderer.highlight --- ---Highlight special files and directories with `NvimTreeSpecial*`. ---(default: `{ "Cargo.toml", "Makefile", "README.md", "readme.md", }`) ---@field special_files? string[] --- ---[nvim_tree.config.renderer.indent_markers] ---@field indent_markers? nvim_tree.config.renderer.indent_markers --- ---[nvim_tree.config.renderer.icons] ---@field icons? nvim_tree.config.renderer.icons ---@class nvim_tree.config.renderer.indent_markers --- ---Display indent markers when folders are open. ---(default: `false`) ---@field enable? boolean --- ---Display folder arrows in the same column as indent marker when using [nvim_tree.config.renderer.icons.padding] {folder_arrow} ---(default: `true`) ---@field inline_arrows? boolean --- ---@field icons? nvim_tree.config.renderer.indent_markers.icons ---[nvim_tree.config.renderer.indent_markers.icons]() ---Before the file/directory, length 1. ---@class nvim_tree.config.renderer.indent_markers.icons ---@inlinedoc --- ---(default: `"└"`) ---@field corner? string ---(default: `"│"`) ---@field edge? string ---(default: `"│"`) ---@field item? string ---(default: `"─"`) ---@field bottom? string ---(default: `" "`) ---@field none? string ---Icons and separators --- ---{_placement} [nvim_tree.config.renderer.icons.placement]() ---- `"before"`: before file/folder, after the file/folders icons ---- `"after"`: after file/folder ---- `"signcolumn"`: far left, requires |nvim_tree.config.view| {signcolumn}. ---- `"right_align"`: far right ---@alias nvim_tree.config.renderer.icons.placement "before"|"after"|"signcolumn"|"right_align" --- --- ---@class nvim_tree.config.renderer.icons --- ---(default: `before`) ---@field git_placement? nvim_tree.config.renderer.icons.placement --- ---(default: `after`) ---@field hidden_placement? nvim_tree.config.renderer.icons.placement --- ---(default: `after`) ---@field modified_placement? nvim_tree.config.renderer.icons.placement --- ---(default: `signcolumn`) ---@field bookmarks_placement? nvim_tree.config.renderer.icons.placement --- ---(default: `signcolumn`) ---@field diagnostics_placement? nvim_tree.config.renderer.icons.placement --- ---@field padding? nvim_tree.config.renderer.icons.padding --- ---Separator between symlink source and target. ---(default: `" ➛ "`) ---@field symlink_arrow? string --- ---[nvim_tree.config.renderer.icons.show] ---@field show? nvim_tree.config.renderer.icons.show --- ---[nvim_tree.config.renderer.icons.glyphs] ---@field glyphs? nvim_tree.config.renderer.icons.glyphs --- ---[nvim_tree.config.renderer.icons.web_devicons] ---@field web_devicons? nvim_tree.config.renderer.icons.web_devicons ---Configure optional plugin `nvim-tree/nvim-web-devicons`, see [nvim-tree-icons-highlighting]. --- ---@class nvim_tree.config.renderer.icons.web_devicons --- ---@field file? nvim_tree.config.renderer.icons.web_devicons.file --- ---@field folder? nvim_tree.config.renderer.icons.web_devicons.folder ---[nvim_tree.config.renderer.icons.web_devicons.file]() ---@class nvim_tree.config.renderer.icons.web_devicons.file ---@inlinedoc --- ---(default: `true`) ---@field enable? boolean --- ---(default: `true`) ---@field color? boolean ---[nvim_tree.config.renderer.icons.web_devicons.folder]() ---@class nvim_tree.config.renderer.icons.web_devicons.folder ---@inlinedoc --- ---(default: `false`) ---@field enable? boolean --- ---(default: `true`) ---@field color? boolean ---[nvim_tree.config.renderer.icons.padding]() ---@class nvim_tree.config.renderer.icons.padding ---@inlinedoc --- ---Between icon and filename. ---(default: `" "`) ---@field icon? string --- ---Between folder arrow icon and file/folder icon. ---(default: `" "`) ---@field folder_arrow? string ---See [nvim-tree-icons-highlighting]. ---@class nvim_tree.config.renderer.icons.show --- ---(default: `true`) ---@field file? boolean --- ---(default: `true`) ---@field folder? boolean --- ---(default: `true`) ---@field git? boolean --- ---(default: `true`) ---@field modified? boolean --- ---(default: `false`) ---@field hidden? boolean --- ---(default: `true`) ---@field diagnostics? boolean --- ---(default: `true`) ---@field bookmarks? boolean --- ---Show a small arrow before the folder node. Arrow will be a part of the node when using [nvim_tree.config.renderer.indent_markers]. ---(default: `true`) ---@field folder_arrow? boolean ---See [nvim-tree-icons-highlighting]. --- ---Glyphs that appear in the sign column must have length <= 2 ---@class nvim_tree.config.renderer.icons.glyphs --- ---Files ---(default: `""`) ---@field default? string --- ---(default: `""`) ---@field symlink? string --- ---(default: `"󰆤"`) ---@field bookmark? string --- ---(default: `"●"`) ---@field modified? string --- ---(default: `"󰜌"`) ---@field hidden? string --- ---@field folder? nvim_tree.config.renderer.icons.glyphs.folder --- ---@field git? nvim_tree.config.renderer.icons.glyphs.git ---[nvim_tree.config.renderer.icons.glyphs.folder]() ---@class nvim_tree.config.renderer.icons.glyphs.folder ---@inlinedoc ---(default: left arrow) ---@field arrow_closed? string ---(default: down arrow) ---@field arrow_open? string ---(default: `""`) ---@field default? string ---(default: `""`) ---@field open? string ---(default: `""`) ---@field empty? string ---(default: `""`) ---@field empty_open? string ---(default: `""`) ---@field symlink? string ---(default: `""`) ---@field symlink_open? string ---[nvim_tree.config.renderer.icons.glyphs.git]() ---@class nvim_tree.config.renderer.icons.glyphs.git ---@inlinedoc ---(default: `"✗"`) ---@field unstaged? string ---(default: `"✓"`) ---@field staged? string ---(default: `""`) ---@field unmerged? string ---(default: `"➜"`) ---@field renamed? string ---(default: `"★"`) ---@field untracked? string ---(default: `""`) ---@field deleted? string ---(default: `"◌"`) ---@field ignored? string ---Number of hidden nodes in a directory by reason: the filter that hid the node. --- ---Passed to your [nvim_tree.config.renderer.hidden_display] function e.g. ---```lua --- --- ---@param hidden_stats nvim_tree.config.renderer.hidden_stats --- ---@return string? summary --- local my_hidden_display = function(hidden_stats) --- local total_count = 0 --- for reason, count in pairs(hidden_stats) do --- total_count = total_count + count --- end --- --- if total_count > 0 then --- return "(" .. tostring(total_count) .. " hidden)" --- end --- return nil --- end ---``` --- ---@class nvim_tree.config.renderer.hidden_stats ---@field bookmark integer ---@field buf integer ---@field custom integer ---@field dotfile integer ---@field git integer ---@field live_filter integer ================================================ FILE: lua/nvim-tree/_meta/config/sort.lua ================================================ ---@meta error("Cannot require a meta file") ---Sort files within a directory. --- ---{sorter} presets [nvim_tree.config.sort.Sorter]() ---- `"name"` ---- `"case_sensitive"` name ---- `"modification_time"` ---- `"extension"` uses all suffixes e.g. `foo.tar.gz` -> `.tar.gz` ---- `"suffix"` uses the last e.g. `foo.tar.gz` -> `.gz` ---- `"filetype"` [filetype] ---@alias nvim_tree.config.sort.Sorter "name"|"case_sensitive"|"modification_time"|"extension"|"suffix"|"filetype" --- ---{sorter} may be a function that is passed a list of `nvim_tree.api.Node` to be sorted in place e.g. ---```lua --- --- ---Sort by name length --- ---@param nodes nvim_tree.api.Node[] --- ---@return nvim_tree.config.sort.Sorter? --- local sorter = function(nodes) --- table.sort(nodes, function(a, b) --- return #a.name < #b.name --- end) --- end ---``` ---{sorter} may be a function that returns a [nvim_tree.config.sort.Sorter] --- ---@class nvim_tree.config.sort --- ---(default: `"name"`) ---@field sorter? nvim_tree.config.sort.Sorter|(fun(nodes: nvim_tree.api.Node[]): nvim_tree.config.sort.Sorter?) --- ---Sort folders before files. Has no effect when {sorter} is a function. ---(default: `true`) ---@field folders_first? boolean --- ---Sort files before folders. Has no effect when {sorter} is a function. Overrides {folders_first}. ---(default: `false`) ---@field files_first? boolean ================================================ FILE: lua/nvim-tree/_meta/config/system_open.lua ================================================ ---@meta error("Cannot require a meta file") ---Open files or directories via the OS. --- ---Nvim: ---- `>=` 0.10 uses [vim.ui.open()] unless {cmd} is specified ---- `<` 0.10 calls external {cmd}: --- - UNIX: `xdg-open` --- - macOS: `open` --- - Windows: `cmd` --- ---Once nvim-tree minimum Nvim version is updated to 0.10, this configuration will no longer be necessary and will be removed. --- ---@class nvim_tree.config.system_open --- ---The open command itself ---@field cmd? string --- ---Optional argument list. Leave empty for OS specific default. ---(default: `{}` or `{ "/c", "start", '""' }` on windows) ---@field args? string[] ================================================ FILE: lua/nvim-tree/_meta/config/tab.lua ================================================ ---@meta error("Cannot require a meta file") ---@class nvim_tree.config.tab --- ---[nvim_tree.config.tab.sync] ---@field sync? nvim_tree.config.tab.sync ---@class nvim_tree.config.tab.sync --- ---Opens the tree automatically when switching tabpage or opening a new tabpage if the tree was previously open. ---(default: `false`) ---@field open? boolean --- ---Closes the tree across all tabpages when the tree is closed. ---(default: `false`) ---@field close? boolean --- --- ---List of filetypes or buffer names on new tab that will prevent `open` and `close` ---(default: `{}`) ---@field ignore? string[] ================================================ FILE: lua/nvim-tree/_meta/config/trash.lua ================================================ ---@meta error("Cannot require a meta file") ---Files may be trashed via an external command that must be installed on your system. --- - linux: `gio trash`, from linux package `glib2` --- - macOS: `trash`, from homebrew package `trash` --- - windows: `trash`, requires `trash-cli` or similar --- ---@class nvim_tree.config.trash --- ---(default: `"gio trash"` or `"trash"`) ---@field cmd? string ================================================ FILE: lua/nvim-tree/_meta/config/ui.lua ================================================ ---@meta error("Cannot require a meta file") ---@class nvim_tree.config.ui --- ---[nvim_tree.config.ui.confirm] ---@field confirm? nvim_tree.config.ui.confirm ---Confirmation prompts. ---@class nvim_tree.config.ui.confirm --- ---Prompt before removing. ---(default: `true`) ---@field remove? boolean --- ---Prompt before trashing. ---(default: `true`) ---@field trash? boolean --- ---If `true` the prompt will be `Y/n`, otherwise `y/N` ---(default: `false`) ---@field default_yes? boolean ================================================ FILE: lua/nvim-tree/_meta/config/update_focused_file.lua ================================================ ---@meta error("Cannot require a meta file") ---Update the focused file on [BufEnter], uncollapsing folders recursively. --- ---@class nvim_tree.config.update_focused_file --- ---(default: `false`) ---@field enable? boolean --- ---[nvim_tree.config.update_focused_file.update_root] ---@field update_root? nvim_tree.config.update_focused_file.update_root --- ---A function called on [BufEnter] that returns true if the file should not be focused when opening. ---(default: `false`) ---@field exclude? boolean|(fun(args: vim.api.keyset.create_autocmd.callback_args): boolean) ---Update the root directory of the tree if the file is not under the current root directory. --- ---Prefers vim's cwd and [nvim_tree.config] {root_dirs}, falling back to the directory containing the file. --- ---Requires [nvim_tree.config.update_focused_file] --- ---@class nvim_tree.config.update_focused_file.update_root --- ---(default: `false`) ---@field enable? boolean --- ---List of buffer names and filetypes that will not update the root dir of the tree if the file isn't found under the current root directory. ---(default: `{}`) ---@field ignore_list? string[] ================================================ FILE: lua/nvim-tree/_meta/config/view.lua ================================================ ---@meta error("Cannot require a meta file") ---Configures the dimensions and appearance of the nvim-tree window. --- ---The window is "docked" at the left by default, however may be configured to float: [nvim_tree.config.view.float] --- ---{width} can be a [nvim_tree.config.view.width.spec] for static control or a [nvim_tree.config.view.width] for fully dynamic control based on longest line. --- ---[nvim_tree.config.view.width.spec]() ---- `string`: `x%` string e.g. `30%` ---- `integer`: number of columns ---- `function`: returns one of the above ---@alias nvim_tree.config.view.width.spec string|integer|(fun(): integer|string) --- ---@class nvim_tree.config.view --- ---When entering nvim-tree, reposition the view so that the current node is initially centralized, see [zz]. ---(default: `false`) ---@field centralize_selection? boolean --- ---['cursorline'] ---(default: `true`) ---@field cursorline? boolean --- ---['cursorlineopt'] ---(default: `both`) ---@field cursorlineopt? string --- ---Idle milliseconds before some reload / refresh operations. Increase if you experience performance issues around screen refresh. ---(default: `15`) ---@field debounce_delay? integer --- ---(default: `"left"`) ---@field side? "left"|"right" --- ---Preserves window proportions when opening a file. If `false`, the height and width of windows other than nvim-tree will be equalized. ---(default: `false`) ---@field preserve_window_proportions? boolean --- ---['number'] ---(default: `false`) ---@field number? boolean --- ---['relativenumber'] ---(default: `false`) ---@field relativenumber? boolean --- ---['signcolumn'] ---(default: `"yes"`) ---@field signcolumn? "yes"|"auto"|"no" --- ---(default: `30`) ---@field width? nvim_tree.config.view.width.spec|nvim_tree.config.view.width --- ---[nvim_tree.config.view.float] ---@field float? nvim_tree.config.view.float ---Configure dynamic width based on longest line. --- ---@class nvim_tree.config.view.width --- ---(default: `30`) ---@field min? nvim_tree.config.view.width.spec --- ----1 for unbounded. ---(default: `-1`) ---@field max? nvim_tree.config.view.width.spec --- ---Exclude these lines when computing width. ---(default: `{ "root" }`) ---@field lines_excluded? ("root")[] --- ---Extra padding to the right. ---(default: `1`) ---@field padding? nvim_tree.config.view.width.spec ---Configure floating window behaviour --- ---{open_win_config} is passed to [nvim_open_win()], default: ---```lua --- { --- relative = "editor", --- border = "rounded", --- width = 30, --- height = 30, --- row = 1, --- col = 1, --- } ---``` ---@class nvim_tree.config.view.float --- ---(default: `false`) ---@field enable? boolean --- ---Close the floating window when it loses focus. ---(default: `true`) ---@field quit_on_focus_loss? boolean --- ---(default: `{ relative = "editor", border = "rounded", width = 30, height = 30, row = 1, col = 1, }`) ---@field open_win_config? vim.api.keyset.win_config|(fun(): vim.api.keyset.win_config) ================================================ FILE: lua/nvim-tree/_meta/config.lua ================================================ ---@meta error("Cannot require a meta file") -- Root class {field}s are documented manually above "Fields:" as there is insufficent room for them in the column. ---Arguments to pass to [nvim-tree-setup]. --- ---When a value is not present/nil, the default will be used. --- ---{on_attach} Runs when creating the nvim-tree buffer. Use this to set your [nvim-tree-mappings]. When not a function, [nvim-tree-mappings-default] will be used. --- ---{hijack_cursor} keep the cursor on the first letter of the filename when moving in the tree. --- ---{auto_reload_on_write} reload the explorer every time a buffer is written to. --- ---{disable_netrw} completely disables [netrw], see [nvim-tree-netrw] for details. It is strongly advised to eagerly disable netrw, due to race conditions at vim startup. --- ---{hijack_netrw} hijacks netrw windows, ignored when {disable_netrw}. --- ---{hijack_unnamed_buffer_when_opening} opens in place of the unnamed buffer if it's empty. --- ---{root_dirs} preferred root directories, requires [nvim_tree.config.update_focused_file.update_root]. --- ---{prefer_startup_root} prefer startup root directory when updating root directory of the tree. Requires [nvim_tree.config.update_focused_file.update_root]. --- ---{sync_root_with_cwd} changes the tree root directory on [DirChanged] and refreshes the tree. --- ---{reload_on_bufenter} automatically reloads the tree on [BufEnter] nvim-tree. --- ---{respect_buf_cwd} changes the [current-directory] of nvim-tree to that of new buffer's when opening nvim-tree. --- ---{select_prompts} uses [vim.ui.select()] style prompts. Necessary when using a UI prompt decorator such as dressing.nvim or telescope-ui-select.nvim ---@class nvim_tree.config --- ---(default: `default`) ---@field on_attach? "default"|(fun(bufnr: integer)) --- ---(default: `false`) ---@field hijack_cursor? boolean --- ---(default: `true`) ---@field auto_reload_on_write? boolean --- ---(default: `false`) ---@field disable_netrw? boolean --- ---(default: `true`) ---@field hijack_netrw? boolean --- ---(default: `false`) ---@field hijack_unnamed_buffer_when_opening? boolean --- ---(default: `{}`) ---@field root_dirs? string[] --- ---(default: `false`) ---@field prefer_startup_root? boolean --- ---(default: `false`) ---@field sync_root_with_cwd? boolean --- ---(default: `false`) ---@field reload_on_bufenter? boolean --- ---(default: `false`) ---@field respect_buf_cwd? boolean --- ---(default: `false`) ---@field select_prompts? boolean --- ---[nvim_tree.config.sort] ---@field sort? nvim_tree.config.sort --- ---[nvim_tree.config.view] ---@field view? nvim_tree.config.view --- ---[nvim_tree.config.renderer] ---@field renderer? nvim_tree.config.renderer --- ---[nvim_tree.config.hijack_directories] ---@field hijack_directories? nvim_tree.config.hijack_directories --- ---[nvim_tree.config.update_focused_file] ---@field update_focused_file? nvim_tree.config.update_focused_file --- ---[nvim_tree.config.system_open] ---@field system_open? nvim_tree.config.system_open --- ---[nvim_tree.config.git] ---@field git? nvim_tree.config.git --- ---[nvim_tree.config.diagnostics] ---@field diagnostics? nvim_tree.config.diagnostics --- ---[nvim_tree.config.modified] ---@field modified? nvim_tree.config.modified --- ---[nvim_tree.config.filters] ---@field filters? nvim_tree.config.filters --- ---[nvim_tree.config.live_filter] ---@field live_filter? nvim_tree.config.live_filter --- ---[nvim_tree.config.filesystem_watchers] ---@field filesystem_watchers? nvim_tree.config.filesystem_watchers --- ---[nvim_tree.config.actions] ---@field actions? nvim_tree.config.actions --- ---[nvim_tree.config.trash] ---@field trash? nvim_tree.config.trash --- ---[nvim_tree.config.tab] ---@field tab? nvim_tree.config.tab --- ---[nvim_tree.config.bookmarks] ---@field bookmarks? nvim_tree.config.bookmarks --- ---[nvim_tree.config.notify] ---@field notify? nvim_tree.config.notify --- ---[nvim_tree.config.help] ---@field help? nvim_tree.config.help --- ---[nvim_tree.config.ui] ---@field ui? nvim_tree.config.ui --- ---[nvim_tree.config.experimental] ---@field experimental? nvim_tree.config.experimental --- ---[nvim_tree.config.log] ---@field log? nvim_tree.config.log ================================================ FILE: lua/nvim-tree/actions/finders/find-file.lua ================================================ local log = require("nvim-tree.log") local view = require("nvim-tree.view") local utils = require("nvim-tree.utils") local core = require("nvim-tree.core") local DirectoryNode = require("nvim-tree.node.directory") local Iterator = require("nvim-tree.iterators.node-iterator") local M = {} local running = {} ---Find a path in the tree, expand it and focus it ---@param path string relative or absolute function M.fn(path) local explorer = core.get_explorer() if not explorer or not view.is_visible() then return end -- always match against the real path local path_real = vim.loop.fs_realpath(path) if not path_real then return end if running[path_real] then return end running[path_real] = true local profile = log.profile_start("find file %s", path_real) -- refresh the contents of all parents, expanding groups as needed if explorer:get_node_from_path(path_real) == nil then explorer:refresh_parent_nodes_for_path(vim.fn.fnamemodify(path_real, ":h")) end local line = core.get_nodes_starting_line() local absolute_paths_searched = {} local found = Iterator.builder(core.get_explorer().nodes) :matcher(function(node) return node.absolute_path == path_real or node.link_to == path_real end) :applier(function(node) local incremented_line = false if not node.group_next then line = line + 1 incremented_line = true end if vim.tbl_contains(absolute_paths_searched, node.absolute_path) then return end table.insert(absolute_paths_searched, node.absolute_path) local abs_match = vim.startswith(path_real, node.absolute_path .. utils.path_separator) local link_match = node.link_to and vim.startswith(path_real, node.link_to .. utils.path_separator) if abs_match or link_match then local dir = node:as(DirectoryNode) if dir then if not dir.group_next then dir.open = true end if #dir.nodes == 0 then core.get_explorer():expand_dir_node(dir) if dir.group_next and incremented_line then line = line - 1 end end end end end) :recursor(function(node) node = node and node:as(DirectoryNode) if node then return node.group_next and { node.group_next } or (node.open and #node.nodes > 0 and node.nodes) else return nil end end) :iterate() if found and view.is_visible() then explorer.renderer:draw() view.set_cursor({ line, 0 }) end running[path_real] = false log.profile_end(profile) end return M ================================================ FILE: lua/nvim-tree/actions/finders/init.lua ================================================ local M = {} M.find_file = require("nvim-tree.actions.finders.find-file") M.search_node = require("nvim-tree.actions.finders.search-node") return M ================================================ FILE: lua/nvim-tree/actions/finders/search-node.lua ================================================ local core = require("nvim-tree.core") local find_file = require("nvim-tree.actions.finders.find-file").fn local M = {} ---@param search_dir string|nil ---@param input_path string ---@return string|nil local function search(search_dir, input_path) local realpaths_searched = {} local explorer = core.get_explorer() if not explorer then return end if not search_dir then return end ---@param dir string ---@return string|nil local function iter(dir) local realpath, path, name, stat, handle, _ local filter_status = explorer.filters:prepare() handle, _ = vim.loop.fs_scandir(dir) if not handle then return end realpath, _ = vim.loop.fs_realpath(dir) if not realpath or vim.tbl_contains(realpaths_searched, realpath) then return end table.insert(realpaths_searched, realpath) name, _ = vim.loop.fs_scandir_next(handle) while name do path = dir .. "/" .. name ---@type uv.fs_stat.result|nil stat, _ = vim.loop.fs_stat(path) if not stat then break end if not explorer.filters:should_filter(path, stat, filter_status) then if string.find(path, "/" .. input_path .. "$") then return path end if stat.type == "directory" then path = iter(path) if path then return path end end end name, _ = vim.loop.fs_scandir_next(handle) end end return iter(search_dir) end function M.fn() if not core.get_explorer() then return end -- temporarily set &path local bufnr = vim.api.nvim_get_current_buf() local path_existed, path_opt if vim.fn.has("nvim-0.10") == 1 then path_existed, path_opt = pcall(vim.api.nvim_get_option_value, "path", { buf = bufnr }) vim.api.nvim_set_option_value("path", core.get_cwd() .. "/**", { buf = bufnr }) else path_existed, path_opt = pcall(vim.api.nvim_buf_get_option, bufnr, "path") ---@diagnostic disable-line: deprecated vim.api.nvim_buf_set_option(bufnr, "path", core.get_cwd() .. "/**") ---@diagnostic disable-line: deprecated end vim.ui.input({ prompt = "Search: ", completion = "file_in_path" }, function(input_path) if not input_path or input_path == "" then return end -- reset &path if path_existed then if vim.fn.has("nvim-0.10") == 1 then vim.api.nvim_set_option_value("path", path_opt, { buf = bufnr }) else vim.api.nvim_buf_set_option(bufnr, "path", path_opt) ---@diagnostic disable-line: deprecated end else if vim.fn.has("nvim-0.10") == 1 then vim.api.nvim_set_option_value("path", nil, { buf = bufnr }) else vim.api.nvim_buf_set_option(bufnr, "path", nil) ---@diagnostic disable-line: deprecated end end -- strip trailing slash input_path = string.gsub(input_path, "/$", "") -- search under cwd local found = search(core.get_cwd(), input_path) if found then find_file(found) end end) end return M ================================================ FILE: lua/nvim-tree/actions/fs/clipboard.lua ================================================ local lib = require("nvim-tree.lib") local log = require("nvim-tree.log") local utils = require("nvim-tree.utils") local core = require("nvim-tree.core") local events = require("nvim-tree.events") local notify = require("nvim-tree.notify") local find_file = require("nvim-tree.actions.finders.find-file").fn local Class = require("nvim-tree.classic") local DirectoryNode = require("nvim-tree.node.directory") local Node = require("nvim-tree.node") ---@alias ClipboardAction "copy" | "cut" ---@alias ClipboardData table ---@alias ClipboardActionFn fun(source: string, dest: string): boolean, string? ---@class (exact) Clipboard: nvim_tree.Class ---@field private explorer Explorer ---@field private data ClipboardData ---@field private clipboard_name string ---@field private reg string local Clipboard = Class:extend() ---@class Clipboard ---@overload fun(args: ClipboardArgs): Clipboard ---@class (exact) ClipboardArgs ---@field explorer Explorer ---@protected ---@param args ClipboardArgs function Clipboard:new(args) self.explorer = args.explorer self.data = { copy = {}, cut = {}, } self.clipboard_name = self.explorer.opts.actions.use_system_clipboard and "system" or "neovim" self.reg = self.explorer.opts.actions.use_system_clipboard and "+" or "1" end ---@param source string ---@param destination string ---@return boolean ---@return string|nil local function do_copy(source, destination) local source_stats, err = vim.loop.fs_stat(source) if not source_stats then log.line("copy_paste", "do_copy fs_stat '%s' failed '%s'", source, err) return false, err end log.line("copy_paste", "do_copy %s '%s' -> '%s'", source_stats.type, source, destination) if source == destination then log.line("copy_paste", "do_copy source and destination are the same, exiting early") return true end if source_stats.type == "file" then local success success, err = vim.loop.fs_copyfile(source, destination) if not success then log.line("copy_paste", "do_copy fs_copyfile failed '%s'", err) return false, err end return true elseif source_stats.type == "directory" then local handle handle, err = vim.loop.fs_scandir(source) if type(handle) == "string" then return false, handle elseif not handle then log.line("copy_paste", "do_copy fs_scandir '%s' failed '%s'", source, err) return false, err end local success success, err = vim.loop.fs_mkdir(destination, source_stats.mode) if not success then log.line("copy_paste", "do_copy fs_mkdir '%s' failed '%s'", destination, err) return false, err end while true do local name, _ = vim.loop.fs_scandir_next(handle) if not name then break end local new_name = utils.path_join({ source, name }) local new_destination = utils.path_join({ destination, name }) success, err = do_copy(new_name, new_destination) if not success then return false, err end end else err = string.format("'%s' illegal file type '%s'", source, source_stats.type) log.line("copy_paste", "do_copy %s", err) return false, err end return true end ---Paste a single item with no conflict handling. ---@param source string ---@param dest string ---@param action ClipboardAction ---@param action_fn ClipboardActionFn local function do_paste_one(source, dest, action, action_fn) log.line("copy_paste", "do_paste_one '%s' -> '%s'", source, dest) local success, err = action_fn(source, dest) if not success then notify.error("Could not " .. action .. " " .. notify.render_path(source) .. " - " .. (err or "???")) end find_file(utils.path_remove_trailing(dest)) end ---@param node Node ---@param clip ClipboardData local function toggle(node, clip) if node.name == ".." then return end local notify_node = notify.render_path(node.absolute_path) if utils.array_remove(clip, node) then notify.info(notify_node .. " removed from clipboard.") return end table.insert(clip, node) notify.info(notify_node .. " added to clipboard.") end ---Clear copied and cut function Clipboard:clear_clipboard() self.data.copy = {} self.data.cut = {} notify.info("Clipboard has been emptied.") self.explorer.renderer:draw() end ---Bulk add/remove nodes to/from a clipboard list. ---@private ---@param nodes Node[] filtered nodes to operate on ---@param from Node[] list to remove from (the opposite clipboard) ---@param to Node[] list to add to ---@param verb string notification verb ("added to" or "cut to") function Clipboard:bulk_clipboard(nodes, from, to, verb) local added = 0 local removed = 0 for _, node in ipairs(nodes) do if node.name ~= ".." then utils.array_remove(from, node) if utils.array_remove(to, node) then removed = removed + 1 else table.insert(to, node) added = added + 1 end end end if added > 0 then notify.info(string.format("%d nodes %s clipboard.", added, verb)) elseif removed > 0 then notify.info(string.format("%d nodes removed from clipboard.", removed)) end self.explorer.renderer:draw() end ---Copy one or more nodes ---@param node_or_nodes Node|Node[] function Clipboard:copy(node_or_nodes) if type(node_or_nodes) == "table" and node_or_nodes.is and node_or_nodes:is(Node) then utils.array_remove(self.data.cut, node_or_nodes) toggle(node_or_nodes, self.data.copy) self.explorer.renderer:draw() else self:bulk_clipboard(utils.filter_descendant_nodes(node_or_nodes), self.data.cut, self.data.copy, "added to") end end ---Cut one or more nodes ---@param node_or_nodes Node|Node[] function Clipboard:cut(node_or_nodes) if type(node_or_nodes) == "table" and node_or_nodes.is and node_or_nodes:is(Node) then utils.array_remove(self.data.copy, node_or_nodes) toggle(node_or_nodes, self.data.cut) self.explorer.renderer:draw() else self:bulk_clipboard(utils.filter_descendant_nodes(node_or_nodes), self.data.copy, self.data.cut, "cut to") end end ---Clear clipboard for action and reload to reflect filesystem changes from paste. ---@private ---@param action ClipboardAction function Clipboard:finish_paste(action) self.data[action] = {} self.explorer:reload_explorer() end ---Resolve conflicting paste items. ---Single conflict: per-file prompt with full-path rename (pre-visual-mode behavior). ---Multiple conflicts: batch prompt with suffix rename. ---@private ---@param conflict {node: Node, dest: string}[] ---@param destination string ---@param action ClipboardAction ---@param action_fn ClipboardActionFn function Clipboard:resolve_conflicts(conflict, destination, action, action_fn) if #conflict == 1 then local source = conflict[1].node.absolute_path local dest = conflict[1].dest local function rename_prompt(default_dest) vim.ui.input({ prompt = "Rename to ", default = default_dest, completion = "dir" }, function(new_dest) utils.clear_prompt() if not new_dest or new_dest == "" then self:finish_paste(action) return end if vim.loop.fs_stat(new_dest) then self:resolve_conflicts({ { node = conflict[1].node, dest = new_dest } }, destination, action, action_fn) else do_paste_one(source, new_dest, action, action_fn) self:finish_paste(action) end end) end if source == dest then rename_prompt(dest) else local prompt_select = "Overwrite " .. dest .. " ?" lib.prompt(prompt_select .. " R(ename)/y/n: ", prompt_select, { "", "y", "n" }, { "Rename", "Yes", "No" }, "nvimtree_overwrite_rename", function(item_short) utils.clear_prompt() if item_short == "y" then do_paste_one(source, dest, action, action_fn) self:finish_paste(action) elseif item_short == "" or item_short == "r" then rename_prompt(dest) else self:finish_paste(action) end end) end return end local prompt_select = #conflict .. " file(s) already exist" local prompt_input = prompt_select .. ". R(ename suffix)/y/n: " lib.prompt(prompt_input, prompt_select, { "", "y", "n" }, { "Rename (suffix)", "Overwrite all", "Skip all" }, "nvimtree_paste_conflict", function(item_short) utils.clear_prompt() if item_short == "y" then for _, item in ipairs(conflict) do do_paste_one(item.node.absolute_path, item.dest, action, action_fn) end self:finish_paste(action) elseif item_short == "" or item_short == "r" then vim.ui.input({ prompt = "Suffix: " }, function(suffix) utils.clear_prompt() if not suffix or suffix == "" then return end local still_conflict = {} for _, item in ipairs(conflict) do local basename = vim.fn.fnamemodify(item.node.name, ":r") local extension = vim.fn.fnamemodify(item.node.name, ":e") local new_name = extension ~= "" and (basename .. suffix .. "." .. extension) or (item.node.name .. suffix) local new_dest = utils.path_join({ destination, new_name }) local stats = vim.loop.fs_stat(new_dest) if stats then table.insert(still_conflict, { node = item.node, dest = new_dest }) else do_paste_one(item.node.absolute_path, new_dest, action, action_fn) end end if #still_conflict > 0 then self:resolve_conflicts(still_conflict, destination, action, action_fn) else self:finish_paste(action) end end) else self:finish_paste(action) end end) end ---Paste cut or copy with batch conflict resolution. ---@private ---@param node Node ---@param action ClipboardAction ---@param action_fn ClipboardActionFn function Clipboard:do_paste(node, action, action_fn) if node.name == ".." then node = self.explorer else local dir = node:as(DirectoryNode) if dir then node = dir:last_group_node() end end local clip = self.data[action] if #clip == 0 then return end local destination = node.absolute_path local stats, err, err_name = vim.loop.fs_stat(destination) if not stats and err_name ~= "ENOENT" then log.line("copy_paste", "do_paste fs_stat '%s' failed '%s'", destination, err) notify.error("Could not " .. action .. " " .. notify.render_path(destination) .. " - " .. (err or "???")) return end local is_dir = stats and stats.type == "directory" if not is_dir then destination = vim.fn.fnamemodify(destination, ":p:h") end -- Partition into conflict / no-conflict local no_conflict = {} local conflict = {} for _, _node in ipairs(clip) do local dest = utils.path_join({ destination, _node.name }) local dest_stats = vim.loop.fs_stat(dest) if dest_stats then table.insert(conflict, { node = _node, dest = dest }) else table.insert(no_conflict, { node = _node, dest = dest }) end end -- Paste non-conflicting items immediately for _, item in ipairs(no_conflict) do do_paste_one(item.node.absolute_path, item.dest, action, action_fn) end -- Resolve conflicts in batch if #conflict > 0 then self:resolve_conflicts(conflict, destination, action, action_fn) else self:finish_paste(action) end end ---@param source string ---@param destination string ---@return boolean ---@return string? local function do_cut(source, destination) log.line("copy_paste", "do_cut '%s' -> '%s'", source, destination) if source == destination then log.line("copy_paste", "do_cut source and destination are the same, exiting early") return true end events._dispatch_will_rename_node(source, destination) local success, errmsg = vim.loop.fs_rename(source, destination) if not success then log.line("copy_paste", "do_cut fs_rename failed '%s'", errmsg) return false, errmsg end utils.rename_loaded_buffers(source, destination) events._dispatch_node_renamed(source, destination) return true end ---Paste cut (if present) or copy (if present) ---@param node Node function Clipboard:paste(node) if self.data.cut[1] ~= nil then self:do_paste(node, "cut", do_cut) elseif self.data.copy[1] ~= nil then self:do_paste(node, "copy", do_copy) end end function Clipboard:print_clipboard() local content = {} if #self.data.cut > 0 then table.insert(content, "Cut") for _, node in pairs(self.data.cut) do table.insert(content, " * " .. (notify.render_path(node.absolute_path))) end end if #self.data.copy > 0 then table.insert(content, "Copy") for _, node in pairs(self.data.copy) do table.insert(content, " * " .. (notify.render_path(node.absolute_path))) end end notify.info(table.concat(content, "\n") .. "\n") end ---@param content string function Clipboard:copy_to_reg(content) -- manually firing TextYankPost does not set vim.v.event -- workaround: create a scratch buffer with the clipboard contents and send a yank command local temp_buf = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_text(temp_buf, 0, 0, 0, 0, { content }) vim.api.nvim_buf_call(temp_buf, function() vim.cmd(string.format('normal! "%sy$', self.reg)) end) vim.api.nvim_buf_delete(temp_buf, {}) notify.info(string.format("Copied %s to %s clipboard!", content, self.clipboard_name)) end ---@param node Node function Clipboard:copy_filename(node) if node.name == ".." then -- root self:copy_to_reg(vim.fn.fnamemodify(self.explorer.absolute_path, ":t")) else -- node self:copy_to_reg(node.name) end end ---@param node Node function Clipboard:copy_basename(node) if node.name == ".." then -- root self:copy_to_reg(vim.fn.fnamemodify(self.explorer.absolute_path, ":t:r")) else -- node self:copy_to_reg(vim.fn.fnamemodify(node.name, ":r")) end end ---@param node Node function Clipboard:copy_path(node) if node.name == ".." then -- root self:copy_to_reg(utils.path_add_trailing("")) else -- node local absolute_path = node.absolute_path local cwd = core.get_cwd() if cwd == nil then return end local relative_path = utils.path_relative(absolute_path, cwd) if node:is(DirectoryNode) then self:copy_to_reg(utils.path_add_trailing(relative_path)) else self:copy_to_reg(relative_path) end end end ---@param node Node function Clipboard:copy_absolute_path(node) if node.name == ".." then node = self.explorer end local absolute_path = node.absolute_path local content = node.nodes ~= nil and utils.path_add_trailing(absolute_path) or absolute_path self:copy_to_reg(content) end ---Node is cut. Will not be copied. ---@param node Node ---@return boolean function Clipboard:is_cut(node) return vim.tbl_contains(self.data.cut, node) end ---Node is copied. Will not be cut. ---@param node Node ---@return boolean function Clipboard:is_copied(node) return vim.tbl_contains(self.data.copy, node) end return Clipboard ================================================ FILE: lua/nvim-tree/actions/fs/create-file.lua ================================================ local utils = require("nvim-tree.utils") local events = require("nvim-tree.events") local core = require("nvim-tree.core") local notify = require("nvim-tree.notify") local find_file = require("nvim-tree.actions.finders.find-file").fn local FileNode = require("nvim-tree.node.file") local DirectoryNode = require("nvim-tree.node.directory") local M = {} ---@param file string local function create_and_notify(file) events._dispatch_will_create_file(file) local ok, fd = pcall(vim.loop.fs_open, file, "w", 420) if not ok or type(fd) ~= "number" then notify.error("Couldn't create file " .. notify.render_path(file)) return end vim.loop.fs_close(fd) events._dispatch_file_created(file) end ---@param iter function iterable ---@return integer local function get_num_nodes(iter) local i = 0 for _ in iter do i = i + 1 end return i end ---@param node Node? function M.fn(node) node = node or core.get_explorer() if not node then return end local dir = node:is(FileNode) and node.parent or node:as(DirectoryNode) if not dir then return end dir = dir:last_group_node() local containing_folder = utils.path_add_trailing(dir.absolute_path) local input_opts = { prompt = "Create file ", default = containing_folder, completion = "file", } vim.ui.input(input_opts, function(new_file_path) utils.clear_prompt() if not new_file_path or new_file_path == containing_folder then return end if utils.file_exists(new_file_path) then notify.warn("Cannot create: file already exists") return end -- create a folder for each path element if the folder does not exist -- if the answer ends with a /, create a file for the last path element local is_last_path_file = not new_file_path:match(utils.path_separator .. "$") local path_to_create = "" local idx = 0 local num_nodes = get_num_nodes(utils.path_split(utils.path_remove_trailing(new_file_path))) local is_error = false for path in utils.path_split(new_file_path) do idx = idx + 1 local p = utils.path_remove_trailing(path) if #path_to_create == 0 and vim.fn.has("win32") == 1 then path_to_create = utils.path_join({ p, path_to_create }) else path_to_create = utils.path_join({ path_to_create, p }) end if is_last_path_file and idx == num_nodes then create_and_notify(path_to_create) elseif not utils.file_exists(path_to_create) then local success = vim.loop.fs_mkdir(path_to_create, 493) if not success then notify.error("Could not create folder " .. notify.render_path(path_to_create)) is_error = true break end events._dispatch_folder_created(new_file_path) end end if not is_error then notify.info(notify.render_path(new_file_path) .. " was properly created") end -- synchronously refreshes as we can't wait for the watchers find_file(utils.path_remove_trailing(new_file_path)) end) end return M ================================================ FILE: lua/nvim-tree/actions/fs/init.lua ================================================ local M = {} M.create_file = require("nvim-tree.actions.fs.create-file") M.remove_file = require("nvim-tree.actions.fs.remove-file") M.rename_file = require("nvim-tree.actions.fs.rename-file") M.trash = require("nvim-tree.actions.fs.trash") function M.setup(opts) M.remove_file.setup(opts) M.rename_file.setup(opts) M.trash.setup(opts) end return M ================================================ FILE: lua/nvim-tree/actions/fs/remove-file.lua ================================================ local core = require("nvim-tree.core") local utils = require("nvim-tree.utils") local events = require("nvim-tree.events") local view = require("nvim-tree.view") local lib = require("nvim-tree.lib") local notify = require("nvim-tree.notify") local DirectoryLinkNode = require("nvim-tree.node.directory-link") local DirectoryNode = require("nvim-tree.node.directory") local Node = require("nvim-tree.node") local RootNode = require("nvim-tree.node.root") local M = { config = {}, } ---@param windows integer[] local function close_windows(windows) -- When floating, prevent closing the last non-floating window. -- For details see #2503, #3187. if view.View.float.enable then local non_float_count = 0 for _, win in ipairs(vim.api.nvim_list_wins()) do if vim.api.nvim_win_get_config(win).relative == "" then non_float_count = non_float_count + 1 end end if non_float_count <= 1 then return end end for _, window in ipairs(windows) do if vim.api.nvim_win_is_valid(window) then vim.api.nvim_win_close(window, true) end end end ---@param absolute_path string local function clear_buffer(absolute_path) local bufs = vim.fn.getbufinfo({ bufloaded = 1, buflisted = 1 }) for _, buf in pairs(bufs) do if buf.name == absolute_path then local tree_winnr = vim.api.nvim_get_current_win() if buf.hidden == 0 and (#bufs > 1 or view.View.float.enable) then vim.api.nvim_set_current_win(buf.windows[1]) vim.cmd(":bn") end vim.api.nvim_buf_delete(buf.bufnr, { force = true }) if not view.View.float.quit_on_focus_loss then vim.api.nvim_set_current_win(tree_winnr) end if M.config.actions.remove_file.close_window then close_windows(buf.windows) end return end end end ---@param cwd string ---@return boolean|nil local function remove_dir(cwd) local handle, err = vim.loop.fs_scandir(cwd) if not handle then notify.error(err) return end while true do local name, _ = vim.loop.fs_scandir_next(handle) if not name then break end local new_cwd = utils.path_join({ cwd, name }) -- Type must come from fs_stat and not fs_scandir_next to maintain sshfs compatibility local stat = vim.loop.fs_stat(new_cwd) -- 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) 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) if not success then return false end else local success = vim.loop.fs_unlink(new_cwd) if not success then return false end clear_buffer(new_cwd) end end return vim.loop.fs_rmdir(cwd) end --- Remove a node, notify errors, dispatch events ---@param node Node ---@return boolean success function M.remove(node) local notify_node = notify.render_path(node.absolute_path) if node:is(DirectoryNode) and not node:is(DirectoryLinkNode) then local success = remove_dir(node.absolute_path) if not success then notify.error("Could not remove " .. notify_node) return false end events._dispatch_folder_removed(node.absolute_path) else events._dispatch_will_remove_file(node.absolute_path) local success = vim.loop.fs_unlink(node.absolute_path) if not success then notify.error("Could not remove " .. notify_node) return false end events._dispatch_file_removed(node.absolute_path) clear_buffer(node.absolute_path) end return true end ---Remove a single node with confirmation. ---@param node Node local function remove_one(node) if node:is(RootNode) then return end local function do_remove() if M.remove(node) then notify.info(notify.render_path(node.absolute_path) .. " was properly removed.") end local explorer = core.get_explorer() if not M.config.filesystem_watchers.enable and explorer then explorer:reload_explorer() end end if M.config.ui.confirm.remove then local prompt_select = "Remove " .. node.name .. "?" local prompt_input, items_short, items_long = utils.confirm_prompt(prompt_select, M.config.ui.confirm.default_yes) lib.prompt(prompt_input, prompt_select, items_short, items_long, "nvimtree_remove", function(item_short) utils.clear_prompt() if item_short == "y" or item_short == (M.config.ui.confirm.default_yes and "") then do_remove() end end) else do_remove() end end ---Remove multiple nodes with a single confirmation prompt. ---@param nodes Node[] local function remove_many(nodes) if #nodes == 0 then return end nodes = utils.filter_descendant_nodes(nodes) local function execute() local removed = 0 for _, node in ipairs(nodes) do if not node:is(RootNode) and M.remove(node) then removed = removed + 1 end end if removed > 0 then notify.info(string.format("%d nodes properly removed.", removed)) end local explorer = core.get_explorer() if not M.config.filesystem_watchers.enable and explorer then explorer:reload_explorer() end end if M.config.ui.confirm.remove then local prompt_select = string.format("Remove %d selected?", #nodes) local prompt_input, items_short, items_long = utils.confirm_prompt(prompt_select, M.config.ui.confirm.default_yes) lib.prompt(prompt_input, prompt_select, items_short, items_long, "nvimtree_remove", function(item_short) utils.clear_prompt() if item_short == "y" or item_short == (M.config.ui.confirm.default_yes and "") then execute() end end) else execute() end end ---@param node_or_nodes Node|Node[] function M.fn(node_or_nodes) if type(node_or_nodes) == "table" and node_or_nodes.is and node_or_nodes:is(Node) then remove_one(node_or_nodes) else remove_many(node_or_nodes) end end function M.setup(opts) M.config.ui = opts.ui M.config.actions = opts.actions M.config.filesystem_watchers = opts.filesystem_watchers end return M ================================================ FILE: lua/nvim-tree/actions/fs/rename-file.lua ================================================ local core = require("nvim-tree.core") local utils = require("nvim-tree.utils") local events = require("nvim-tree.events") local notify = require("nvim-tree.notify") local find_file = require("nvim-tree.actions.finders.find-file").fn local DirectoryNode = require("nvim-tree.node.directory") local M = { config = {}, } ---@param iter function iterable ---@return integer local function get_num_nodes(iter) local i = 0 for _ in iter do i = i + 1 end return i end local ALLOWED_MODIFIERS = { [":p"] = true, [":p:h"] = true, [":t"] = true, [":t:r"] = true, } local function err_fmt(from, to, reason) return string.format("Cannot rename %s -> %s: %s", from, to, reason) end local function rename_file_exists(node, to) if not utils.is_macos then return utils.file_exists(to) end if string.lower(node) == string.lower(to) then return false end return utils.file_exists(to) end ---@param node Node ---@param to string function M.rename(node, to) local notify_from = notify.render_path(node.absolute_path) local notify_to = notify.render_path(to) if rename_file_exists(notify_from, notify_to) then notify.warn(err_fmt(notify_from, notify_to, "file already exists")) return end -- create a folder for each path element if the folder does not exist local idx = 0 local path_to_create = "" local num_nodes = get_num_nodes(utils.path_split(utils.path_remove_trailing(to))) local is_error = false for path in utils.path_split(to) do idx = idx + 1 local p = utils.path_remove_trailing(path) if #path_to_create == 0 and vim.fn.has("win32") == 1 then path_to_create = utils.path_join({ p, path_to_create }) else path_to_create = utils.path_join({ path_to_create, p }) end if idx == num_nodes then events._dispatch_will_rename_node(node.absolute_path, to) local success, err = vim.loop.fs_rename(node.absolute_path, to) if not success then notify.warn(err_fmt(notify_from, notify_to, err)) return end elseif not rename_file_exists(notify_from, path_to_create) then local success = vim.loop.fs_mkdir(path_to_create, 493) if not success then notify.error("Could not create folder " .. notify.render_path(path_to_create)) is_error = true break end is_error = false end end if not is_error then notify.info(string.format("%s -> %s", notify_from, notify_to)) utils.rename_loaded_buffers(node.absolute_path, to) events._dispatch_node_renamed(node.absolute_path, to) end end ---@param node Node ---@param modifier? string local function prompt_to_rename(node, modifier) if not modifier or type(modifier) ~= "string" then modifier = ":t" end local explorer = core.get_explorer() if not explorer then return end if type(node) ~= "table" then local node_at_cursor = explorer:get_node_at_cursor() if not node_at_cursor then return end node = node_at_cursor end -- support for only specific modifiers have been implemented if not ALLOWED_MODIFIERS[modifier] then notify.warn("Modifier " .. vim.inspect(modifier) .. " is not in allowed list : " .. table.concat(ALLOWED_MODIFIERS, ",")) return end local dir = node:as(DirectoryNode) if dir then node = dir:last_group_node() end if node.name == ".." then return end local namelen = node.name:len() local directory = node.absolute_path:sub(0, namelen * -1 - 1) local default_path local prepend = "" local append = "" default_path = vim.fn.fnamemodify(node.absolute_path, modifier) if modifier:sub(0, 2) == ":t" then prepend = directory end if modifier == ":t:r" then local extension = vim.fn.fnamemodify(node.name, ":e") append = extension:len() == 0 and "" or "." .. extension end if modifier == ":p:h" then default_path = default_path .. "/" end local input_opts = { prompt = "Rename to ", default = default_path, completion = "file", } vim.ui.input(input_opts, function(new_file_path) utils.clear_prompt() if not new_file_path then return end local full_new_path = prepend .. new_file_path .. append M.rename(node, full_new_path) if not M.config.filesystem_watchers.enable then explorer:reload_explorer() end find_file(utils.path_remove_trailing(full_new_path)) end) end ---@param node Node function M.rename_node(node) prompt_to_rename(node, ":t") end ---@param node Node function M.rename_sub(node) prompt_to_rename(node, ":p:h") end ---@param node Node function M.rename_basename(node) prompt_to_rename(node, ":t:r") end ---@param node Node function M.rename_full(node) prompt_to_rename(node, ":p") end function M.setup(opts) M.config.filesystem_watchers = opts.filesystem_watchers end return M ================================================ FILE: lua/nvim-tree/actions/fs/trash.lua ================================================ local core = require("nvim-tree.core") local lib = require("nvim-tree.lib") local notify = require("nvim-tree.notify") local utils = require("nvim-tree.utils") local events = require("nvim-tree.events") local DirectoryLinkNode = require("nvim-tree.node.directory-link") local DirectoryNode = require("nvim-tree.node.directory") local Node = require("nvim-tree.node") local RootNode = require("nvim-tree.node.root") local M = { config = {}, } ---@param absolute_path string local function clear_buffer(absolute_path) local bufs = vim.fn.getbufinfo({ bufloaded = 1, buflisted = 1 }) for _, buf in pairs(bufs) do if buf.name == absolute_path then if buf.hidden == 0 and #bufs > 1 then local winnr = vim.api.nvim_get_current_win() vim.api.nvim_set_current_win(buf.windows[1]) vim.cmd(":bn") vim.api.nvim_set_current_win(winnr) end vim.api.nvim_buf_delete(buf.bufnr, {}) return end end end ---@param node Node function M.remove(node) local binary = M.config.trash.cmd:gsub(" .*$", "") if vim.fn.executable(binary) == 0 then notify.warn(string.format("trash.cmd '%s' is not an executable.", M.config.trash.cmd)) return end local err_msg = "" local function on_stderr(_, data) err_msg = err_msg .. (data and table.concat(data, " ")) end -- trashes a path (file or folder) local function trash_path(on_exit) local need_sync_wait = utils.is_windows local job = vim.fn.jobstart(M.config.trash.cmd .. " " .. vim.fn.shellescape(node.absolute_path), { detach = not need_sync_wait, on_exit = on_exit, on_stderr = on_stderr, }) if need_sync_wait then vim.fn.jobwait({ job }) end end local explorer = core.get_explorer() if node:is(DirectoryNode) and not node:is(DirectoryLinkNode) then trash_path(function(_, rc) if rc ~= 0 then notify.warn("trash failed: " .. err_msg .. "; please see :help nvim-tree.trash") return end events._dispatch_folder_removed(node.absolute_path) if not M.config.filesystem_watchers.enable and explorer then explorer:reload_explorer() end end) else events._dispatch_will_remove_file(node.absolute_path) trash_path(function(_, rc) if rc ~= 0 then notify.warn("trash failed: " .. err_msg .. "; please see :help nvim-tree.trash") return end events._dispatch_file_removed(node.absolute_path) clear_buffer(node.absolute_path) if not M.config.filesystem_watchers.enable and explorer then explorer:reload_explorer() end end) end end ---Trash a single node with confirmation. ---@param node Node local function trash_one(node) if node:is(RootNode) then return end local function do_trash() M.remove(node) end if M.config.ui.confirm.trash then local prompt_select = "Trash " .. node.name .. "?" local prompt_input, items_short, items_long = utils.confirm_prompt(prompt_select, M.config.ui.confirm.default_yes) lib.prompt(prompt_input, prompt_select, items_short, items_long, "nvimtree_trash", function(item_short) utils.clear_prompt() if item_short == "y" or item_short == (M.config.ui.confirm.default_yes and "") then do_trash() end end) else do_trash() end end ---Trash multiple nodes with a single confirmation prompt. ---@param nodes Node[] local function trash_many(nodes) if #nodes == 0 then return end nodes = utils.filter_descendant_nodes(nodes) local function execute() for _, node in ipairs(nodes) do if not node:is(RootNode) then M.remove(node) end end end if M.config.ui.confirm.trash then local prompt_select = string.format("Trash %d selected?", #nodes) local prompt_input, items_short, items_long = utils.confirm_prompt(prompt_select, M.config.ui.confirm.default_yes) lib.prompt(prompt_input, prompt_select, items_short, items_long, "nvimtree_trash", function(item_short) utils.clear_prompt() if item_short == "y" or item_short == (M.config.ui.confirm.default_yes and "") then execute() end end) else execute() end end ---@param node_or_nodes Node|Node[] function M.fn(node_or_nodes) if type(node_or_nodes) == "table" and node_or_nodes.is and node_or_nodes:is(Node) then trash_one(node_or_nodes) else trash_many(node_or_nodes) end end function M.setup(opts) M.config.ui = opts.ui M.config.trash = opts.trash M.config.filesystem_watchers = opts.filesystem_watchers end return M ================================================ FILE: lua/nvim-tree/actions/init.lua ================================================ local M = {} M.finders = require("nvim-tree.actions.finders") M.fs = require("nvim-tree.actions.fs") M.moves = require("nvim-tree.actions.moves") M.node = require("nvim-tree.actions.node") M.tree = require("nvim-tree.actions.tree") function M.setup(opts) M.fs.setup(opts) M.node.setup(opts) M.tree.setup(opts) end return M ================================================ FILE: lua/nvim-tree/actions/moves/init.lua ================================================ local M = {} M.item = require("nvim-tree.actions.moves.item") M.parent = require("nvim-tree.actions.moves.parent") M.sibling = require("nvim-tree.actions.moves.sibling") return M ================================================ FILE: lua/nvim-tree/actions/moves/item.lua ================================================ local view = require("nvim-tree.view") local core = require("nvim-tree.core") local diagnostics = require("nvim-tree.diagnostics") local FileNode = require("nvim-tree.node.file") local DirectoryNode = require("nvim-tree.node.directory") local M = {} local MAX_DEPTH = 100 ---Return the status of the node or nil if no status, depending on the type of ---status. ---@param node Node to inspect ---@param what string? type of status ---@param skip_gitignored boolean? default false ---@return boolean local function status_is_valid(node, what, skip_gitignored) if what == "git" then local git_xy = node:get_git_xy() return git_xy ~= nil and (not skip_gitignored or git_xy[1] ~= "!!") elseif what == "diag" then local diag_status = diagnostics.get_diag_status(node) return diag_status ~= nil and diag_status.value ~= nil elseif what == "opened" then return vim.fn.bufloaded(node.absolute_path) ~= 0 end return false end ---Move to the next node that has a valid status. If none found, don't move. ---@param explorer Explorer ---@param where string? where to move (forwards or backwards) ---@param what string? type of status ---@param skip_gitignored boolean? default false local function move(explorer, where, what, skip_gitignored) local first_node_line = core.get_nodes_starting_line() local nodes_by_line = explorer:get_nodes_by_line(first_node_line) local iter_start, iter_end, iter_step, cur, first, nex local cursor = explorer:get_cursor_position() if cursor and cursor[1] < first_node_line then cur = cursor[1] end if where == "next" then iter_start, iter_end, iter_step = first_node_line, #nodes_by_line, 1 elseif where == "prev" then iter_start, iter_end, iter_step = #nodes_by_line, first_node_line, -1 end for line = iter_start, iter_end, iter_step do local node = nodes_by_line[line] local valid = status_is_valid(node, what, skip_gitignored) if not first and valid then first = line end if cursor and line == cursor[1] then cur = line elseif valid and cur then nex = line break end end if nex then view.set_cursor({ nex, 0 }) elseif vim.o.wrapscan and first then view.set_cursor({ first, 0 }) end end ---@param node DirectoryNode local function expand_node(node) if not node.open then -- Expand the node. -- Should never collapse since we checked open. node:expand_or_collapse(false) end end --- Move to the next node recursively. ---@param explorer Explorer ---@param what string? type of status ---@param skip_gitignored? boolean default false local function move_next_recursive(explorer, what, skip_gitignored) -- If the current node: -- * is a directory -- * and is not the root node -- * and has a git/diag status -- * and is not opened -- expand it. local node_init = explorer:get_node_at_cursor() if not node_init then return end local valid = false if node_init.name ~= ".." then -- root node cannot have a status valid = status_is_valid(node_init, what, skip_gitignored) end local node_dir = node_init:as(DirectoryNode) if node_dir and valid and not node_dir.open then node_dir:expand_or_collapse(false) end move(explorer, "next", what, skip_gitignored) local node_cur = explorer:get_node_at_cursor() if not node_cur then return end -- If we haven't moved at all at this point, return. if node_init == node_cur then return end -- i is used to limit iterations. local i = 0 local dir_cur = node_cur:as(DirectoryNode) while dir_cur and i < MAX_DEPTH do expand_node(dir_cur) move(explorer, "next", what, skip_gitignored) -- Save current node. node_cur = explorer:get_node_at_cursor() dir_cur = node_cur and node_cur:as(DirectoryNode) i = i + 1 end end --- Move to the previous node recursively. --- --- move_prev_recursive: --- --- 1) Save current as node_init. -- 2) Call a non-recursive prev. --- 3) If current node is node_init's parent, call move_prev_recursive. --- 4) Else: --- 4.1) If current node is nil, is node_init (we didn't move), or is a file, return. --- 4.2) The current file is a directory, expand it. --- 4.3) Find node_init in current window, and move to it (if not found, return). --- If node_init is the root node (name = ".."), directly move to position 1. --- 4.4) Call a non-recursive prev. --- 4.5) Save the current node and start back from 4.1. --- ---@param explorer Explorer ---@param what string? type of status ---@param skip_gitignored boolean? default false local function move_prev_recursive(explorer, what, skip_gitignored) local node_init, node_cur -- 1) node_init = explorer:get_node_at_cursor() if node_init == nil then return end -- 2) move(explorer, "prev", what, skip_gitignored) node_cur = explorer:get_node_at_cursor() if node_cur == node_init.parent then -- 3) move_prev_recursive(explorer, what, skip_gitignored) else -- i is used to limit iterations. local i = 0 while i < MAX_DEPTH do -- 4.1) if node_cur == nil or node_cur == node_init -- we didn't move or node_cur:is(FileNode) -- node is a file then return end -- 4.2) local node_dir = node_cur:as(DirectoryNode) if node_dir then expand_node(node_dir) end -- 4.3) if node_init.name == ".." then -- root node view.set_cursor({ 1, 0 }) -- move to root node (position 1) else local node_init_line = explorer:find_node_line(node_init) if node_init_line < 0 then return end view.set_cursor({ node_init_line, 0 }) end -- 4.4) move(explorer, "prev", what, skip_gitignored) -- 4.5) node_cur = explorer:get_node_at_cursor() i = i + 1 end end end ---@class NavigationItemOpts ---@field where string? ---@field what string? ---@field skip_gitignored boolean? ---@field recurse boolean? ---@param opts NavigationItemOpts local function item(opts) local explorer = core.get_explorer() if not explorer then return end local recurse = false -- recurse only valid for git and diag moves. if (opts.what == "git" or opts.what == "diag") and opts.recurse ~= nil then recurse = opts.recurse end if not recurse then move(explorer, opts.where, opts.what, opts.skip_gitignored) return end if opts.where == "next" then move_next_recursive(explorer, opts.what, opts.skip_gitignored) elseif opts.where == "prev" then move_prev_recursive(explorer, opts.what, opts.skip_gitignored) end end function M.git_next() item({ where = "next", what = "git" }) end function M.git_next_skip_gitignored() item({ where = "next", what = "git", skip_gitignored = true }) end function M.git_next_recursive() item({ where = "next", what = "git", recurse = true }) end function M.git_prev() item({ where = "prev", what = "git" }) end function M.git_prev_skip_gitignored() item({ where = "prev", what = "git", skip_gitignored = true }) end function M.git_prev_recursive() item({ where = "prev", what = "git", recurse = true }) end function M.diagnostics_next() item({ where = "next", what = "diag" }) end function M.diagnostics_next_recursive() item({ where = "next", what = "diag", recurse = true }) end function M.diagnostics_prev() item({ where = "prev", what = "diag" }) end function M.diagnostics_prev_recursive() item({ where = "prev", what = "diag", recurse = true }) end function M.opened_next() item({ where = "next", what = "opened" }) end function M.opened_prev() item({ where = "prev", what = "opened" }) end return M ================================================ FILE: lua/nvim-tree/actions/moves/parent.lua ================================================ local view = require("nvim-tree.view") local DirectoryNode = require("nvim-tree.node.directory") local M = {} ---@param node Node ---@param should_close boolean local function move(node, should_close) local dir = node:as(DirectoryNode) if dir then dir = dir:last_group_node() if should_close and dir.open then dir.open = false dir.explorer.renderer:draw() return end end local parent = (node:get_parent_of_group() or node).parent if not parent or not parent.parent then view.set_cursor({ 1, 0 }) return end local _, line = parent.explorer:find_node(function(n) return n.absolute_path == parent.absolute_path end) view.set_cursor({ line + 1, 0 }) if should_close then parent.open = false parent.explorer.renderer:draw() end end ---@param node Node function M.move(node) move(node, false) end ---@param node Node function M.move_close(node) move(node, true) end return M ================================================ FILE: lua/nvim-tree/actions/moves/sibling.lua ================================================ local core = require("nvim-tree.core") local Iterator = require("nvim-tree.iterators.node-iterator") local M = {} ---@param node Node ---@param direction "next"|"prev"|"first"|"last" local function move(node, direction) if node.name == ".." or not direction then return end local explorer = core.get_explorer() if not explorer then return end local first, last, next, prev = nil, nil, nil, nil local found = false local parent = node.parent or explorer Iterator.builder(parent and parent.nodes or {}) :recursor(function() return nil end) :applier(function(n) first = first or n last = n if n.absolute_path == node.absolute_path then found = true return end prev = not found and n or prev if found and not next then next = n end end) :iterate() local target_node if direction == "first" then target_node = first elseif direction == "last" then target_node = last elseif direction == "next" then target_node = next or first else target_node = prev or last end if target_node then explorer:focus_node_or_parent(target_node) end end ---@param node Node function M.next(node) move(node, "next") end ---@param node Node function M.prev(node) move(node, "prev") end ---@param node Node function M.first(node) move(node, "first") end ---@param node Node function M.last(node) move(node, "last") end return M ================================================ FILE: lua/nvim-tree/actions/node/buffer.lua ================================================ -- Copyright 2019 Yazdani Kiyan under MIT License local notify = require("nvim-tree.notify") local M = {} ---@param node Node ---@param opts? nvim_tree.api.node.buffer.RemoveOpts function M.delete(node, opts) M.delete_buffer("delete", node.absolute_path, opts) end ---@param node Node ---@param opts? nvim_tree.api.node.buffer.RemoveOpts 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? nvim_tree.api.node.buffer.RemoveOpts 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 ================================================ FILE: lua/nvim-tree/actions/node/file-popup.lua ================================================ local utils = require("nvim-tree.utils") local M = {} ---@param node Node ---@return table local function get_formatted_lines(node) local stats = node.fs_stat if stats == nil then return { "", " Can't retrieve file information", "", } end local fpath = " fullpath: " .. node.absolute_path local created_at = " created: " .. os.date("%x %X", stats.birthtime.sec) local modified_at = " modified: " .. os.date("%x %X", stats.mtime.sec) local accessed_at = " accessed: " .. os.date("%x %X", stats.atime.sec) local size = " size: " .. utils.format_bytes(stats.size) return { fpath, size, accessed_at, modified_at, created_at, } end local current_popup = nil ---@param node Node local function setup_window(node) local lines = get_formatted_lines(node) local max_width = vim.fn.max(vim.tbl_map(function(n) return #n end, lines)) local open_win_config = vim.tbl_extend("force", M.open_win_config, { width = max_width + 1, height = #lines, noautocmd = true, zindex = 60, }) local winnr = vim.api.nvim_open_win(0, false, open_win_config) current_popup = { winnr = winnr, file_path = node.absolute_path, } 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_win_set_buf(winnr, bufnr) end function M.close_popup() 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) end vim.cmd("augroup NvimTreeRemoveFilePopup | au! CursorMoved | augroup END") current_popup = nil end end ---@param node Node function M.toggle_file_info(node) if node.name == ".." then return end if current_popup ~= nil then local is_same_node = current_popup.file_path == node.absolute_path M.close_popup() if is_same_node then return end end setup_window(node) vim.api.nvim_create_autocmd("CursorMoved", { group = vim.api.nvim_create_augroup("NvimTreeRemoveFilePopup", {}), callback = M.close_popup, }) end function M.setup(opts) M.open_win_config = opts.actions.file_popup.open_win_config end return M ================================================ FILE: lua/nvim-tree/actions/node/init.lua ================================================ local M = {} M.file_popup = require("nvim-tree.actions.node.file-popup") M.open_file = require("nvim-tree.actions.node.open-file") M.run_command = require("nvim-tree.actions.node.run-command") M.system_open = require("nvim-tree.actions.node.system-open") M.buffer = require("nvim-tree.actions.node.buffer") function M.setup(opts) require("nvim-tree.actions.node.system-open").setup(opts) require("nvim-tree.actions.node.file-popup").setup(opts) require("nvim-tree.actions.node.open-file").setup(opts) end return M ================================================ FILE: lua/nvim-tree/actions/node/open-file.lua ================================================ -- Copyright 2019 Yazdani Kiyan under MIT License local lib = require("nvim-tree.lib") local notify = require("nvim-tree.notify") local utils = require("nvim-tree.utils") local full_name = require("nvim-tree.renderer.components.full-name") local view = require("nvim-tree.view") local DirectoryNode = require("nvim-tree.node.directory") local FileLinkNode = require("nvim-tree.node.file-link") local RootNode = require("nvim-tree.node.root") ---@class NodeEditOpts ---@field quit_on_open boolean|nil default false ---@field focus boolean|nil default true ---@alias NodeOpenFileMode ""|"change_dir"|"drop"|"edit"|"edit_in_place"|"edit_no_picker"|"preview"|"preview_no_picker"|"split"|"split_no_picker"|"tab_drop"|"tabnew"|"toggle_group_empty"|"vsplit"|"vsplit_no_picker" local M = {} ---Get single char from user input ---@return string local function get_user_input_char() local c = vim.fn.getchar() while type(c) ~= "number" do c = vim.fn.getchar() end return vim.fn.nr2char(c) end ---Get all windows in the current tabpage that aren't NvimTree. ---@return table with valid win_ids local function usable_win_ids() local tabpage = vim.api.nvim_get_current_tabpage() local win_ids = vim.api.nvim_tabpage_list_wins(tabpage) local tree_winid = view.get_winnr(tabpage) return vim.tbl_filter(function(id) local bufid = vim.api.nvim_win_get_buf(id) for option, v in pairs(M.window_picker.exclude) do local ok, option_value if vim.fn.has("nvim-0.10") == 1 then ok, option_value = pcall(vim.api.nvim_get_option_value, option, { buf = bufid }) else ok, option_value = pcall(vim.api.nvim_buf_get_option, bufid, option) ---@diagnostic disable-line: deprecated end if ok and vim.tbl_contains(v, option_value) then return false end end local win_config = vim.api.nvim_win_get_config(id) return id ~= tree_winid and 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 ---Get user to pick a window in the tab that is not NvimTree. ---@return integer|nil -- If a valid window was picked, return its id. If an --- invalid window was picked / user canceled, return nil. If there are --- no selectable windows, return -1. local function pick_win_id() local selectable = usable_win_ids() -- If there are no selectable windows: return. If there's only 1, return it without picking. if #selectable == 0 then return -1 end if #selectable == 1 then return selectable[1] end if #M.window_picker.chars < #selectable then notify.error(string.format("More windows (%d) than actions.open_file.window_picker.chars (%d).", #selectable, #M.window_picker.chars)) return nil end local i = 1 local win_opts_selectable = {} local win_opts_unselectable = {} local win_map = {} local laststatus = vim.o.laststatus 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 win_ids = vim.api.nvim_tabpage_list_wins(tabpage) local not_selectable = vim.tbl_filter(function(id) return not vim.tbl_contains(selectable, id) end, win_ids) if laststatus == 3 then for _, win_id in ipairs(not_selectable) do local ok_status, statusline if vim.fn.has("nvim-0.10") == 1 then ok_status, statusline = pcall(vim.api.nvim_get_option_value, "statusline", { win = win_id }) else ok_status, statusline = pcall(vim.api.nvim_win_get_option, win_id, "statusline") ---@diagnostic disable-line: deprecated end win_opts_unselectable[win_id] = { statusline = ok_status and statusline or "", } -- Clear statusline for windows not selectable if vim.fn.has("nvim-0.10") == 1 then vim.api.nvim_set_option_value("statusline", " ", { win = win_id }) else vim.api.nvim_win_set_option(win_id, "statusline", " ") ---@diagnostic disable-line: deprecated end end end -- Setup UI for _, id in ipairs(selectable) do local char = M.window_picker.chars:sub(i, i) local ok_status, statusline, ok_hl, winhl if vim.fn.has("nvim-0.10") == 1 then ok_status, statusline = pcall(vim.api.nvim_get_option_value, "statusline", { win = id }) ok_hl, winhl = pcall(vim.api.nvim_get_option_value, "winhl", { win = id }) else ok_status, statusline = pcall(vim.api.nvim_win_get_option, id, "statusline") ---@diagnostic disable-line: deprecated ok_hl, winhl = pcall(vim.api.nvim_win_get_option, id, "winhl") ---@diagnostic disable-line: deprecated end win_opts_selectable[id] = { statusline = ok_status and statusline or "", winhl = ok_hl and winhl or "", } win_map[char] = id if vim.fn.has("nvim-0.10") == 1 then vim.api.nvim_set_option_value("statusline", "%=" .. char .. "%=", { win = id }) vim.api.nvim_set_option_value("winhl", "StatusLine:NvimTreeWindowPicker,StatusLineNC:NvimTreeWindowPicker", { win = id }) else vim.api.nvim_win_set_option(id, "statusline", "%=" .. char .. "%=") ---@diagnostic disable-line: deprecated vim.api.nvim_win_set_option(id, "winhl", "StatusLine:NvimTreeWindowPicker,StatusLineNC:NvimTreeWindowPicker") ---@diagnostic disable-line: deprecated end i = i + 1 if i > #M.window_picker.chars then break end end vim.cmd("redraw") if vim.opt.cmdheight._value ~= 0 then print("Pick window: ") end local _, resp = pcall(get_user_input_char) resp = (resp or ""):upper() utils.clear_prompt() -- Restore window options for _, id in ipairs(selectable) do for opt, value in pairs(win_opts_selectable[id]) do if vim.fn.has("nvim-0.10") == 1 then vim.api.nvim_set_option_value(opt, value, { win = id }) else vim.api.nvim_win_set_option(id, opt, value) ---@diagnostic disable-line: deprecated end end end if laststatus == 3 then for _, id in ipairs(not_selectable) do -- Ensure window still exists at this point if vim.api.nvim_win_is_valid(id) then for opt, value in pairs(win_opts_unselectable[id]) do if vim.fn.has("nvim-0.10") == 1 then vim.api.nvim_set_option_value(opt, value, { win = id }) else vim.api.nvim_win_set_option(id, opt, value) ---@diagnostic disable-line: deprecated end end end end end vim.o.laststatus = laststatus vim.opt.fillchars = fillchars if not vim.tbl_contains(vim.split(M.window_picker.chars, ""), resp) then return end return win_map[resp] end local function open_file_in_tab(filename) if M.quit_on_open then view.close() end if M.relative_path then filename = utils.path_relative(filename, vim.fn.getcwd()) end vim.cmd.tabnew() vim.bo.bufhidden = "wipe" -- Following vim.fn.tabnew the # buffer may be set to the tree buffer. There is no way to clear the # buffer via vim.fn.setreg as it requires a valid buffer. Clear # by setting it to a new temporary scratch buffer. if utils.is_nvim_tree_buf(vim.fn.bufnr("#")) then local tmpbuf = vim.api.nvim_create_buf(false, true) vim.fn.setreg("#", tmpbuf) vim.api.nvim_buf_delete(tmpbuf, { force = true }) end vim.cmd.edit(vim.fn.fnameescape(filename)) end local function drop(filename) if M.quit_on_open then view.close() end if M.relative_path then filename = utils.path_relative(filename, vim.fn.getcwd()) end vim.cmd("drop " .. vim.fn.fnameescape(filename)) end local function tab_drop(filename) if M.quit_on_open then view.close() end if M.relative_path then filename = utils.path_relative(filename, vim.fn.getcwd()) end vim.cmd("tab :drop " .. vim.fn.fnameescape(filename)) end local function on_preview(buf_loaded) if not buf_loaded then vim.bo.bufhidden = "delete" vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, { group = vim.api.nvim_create_augroup("RemoveBufHidden", {}), buffer = vim.api.nvim_get_current_buf(), callback = function() vim.bo.bufhidden = "" end, once = true, }) end view.focus() end local function get_target_winid(mode) local target_winid if not M.window_picker.enable or string.find(mode, "no_picker") then target_winid = lib.target_winid local usable_wins = usable_win_ids() -- first available usable window 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 else -- pick a window if type(M.window_picker.picker) == "function" then target_winid = M.window_picker.picker() else target_winid = pick_win_id() end if target_winid == nil then -- pick failed/cancelled return end end if target_winid == -1 then target_winid = lib.target_winid end return target_winid end -- This is only to avoid the BufEnter for nvim-tree to trigger -- which would cause find-file to run on an invalid file. local function set_current_win_no_autocmd(winid, autocmd) local eventignore = vim.opt.eventignore:get() vim.opt.eventignore:append(autocmd) vim.api.nvim_set_current_win(winid) vim.opt.eventignore = eventignore end ---@param filename string ---@param mode NodeOpenFileMode local function open_in_new_window(filename, mode) if type(mode) ~= "string" then mode = "" end local target_winid = get_target_winid(mode) if not target_winid then return end local position = string.find(mode, "no_picker") if position then mode = string.sub(mode, 0, position - 2) end -- non-floating, non-nvim-tree windows local win_ids = vim.tbl_filter(function(id) local config = vim.api.nvim_win_get_config(id) local bufnr = vim.api.nvim_win_get_buf(id) return config and config.relative == "" or utils.is_nvim_tree_buf(bufnr) end, vim.api.nvim_list_wins()) local create_new_window = #win_ids == 1 -- This implies that the nvim-tree window is the only one local new_window_side = (view.View.side == "right") and "aboveleft" or "belowright" -- Target is invalid: create new window if not vim.tbl_contains(win_ids, target_winid) then vim.cmd(new_window_side .. " vsplit") target_winid = vim.api.nvim_get_current_win() lib.target_winid = target_winid -- No need to split, as we created a new window. create_new_window = false if mode:match("split$") then mode = "edit" end elseif not vim.o.hidden then -- If `hidden` is not enabled, check if buffer in target window is -- modified, and create new split if it is. local target_bufid = vim.api.nvim_win_get_buf(target_winid) local modified if vim.fn.has("nvim-0.10") == 1 then modified = vim.api.nvim_get_option_value("modified", { buf = target_bufid }) else modified = vim.api.nvim_buf_get_option(target_bufid, "modified") ---@diagnostic disable-line: deprecated end if modified then if not mode:match("split$") then mode = "vsplit" end end end if (mode == "preview" or mode == "preview_no_picker") and view.View.float.enable then -- ignore "WinLeave" autocmd on preview -- because the registered "WinLeave" -- will kill the floating window immediately set_current_win_no_autocmd(target_winid, { "WinLeave", "BufEnter" }) else set_current_win_no_autocmd(target_winid, { "BufEnter" }) end local fname if M.relative_path then fname = utils.escape_special_chars(vim.fn.fnameescape(utils.path_relative(filename, vim.fn.getcwd()))) else fname = utils.escape_special_chars(vim.fn.fnameescape(filename)) end local command if create_new_window then -- generated from vim.api.nvim_parse_cmd("belowright vsplit foo", {}) command = { cmd = "vsplit", mods = { split = new_window_side }, args = { fname } } elseif mode:match("split$") then command = { cmd = mode, args = { fname } } else command = { cmd = "edit", args = { fname } } end pcall(vim.api.nvim_cmd, command, { output = false }) lib.set_target_win() end local function is_already_loaded(filename) for _, buf_id in ipairs(vim.api.nvim_list_bufs()) do if vim.api.nvim_buf_is_loaded(buf_id) and filename == vim.api.nvim_buf_get_name(buf_id) then return true end end return false end local function edit_in_current_buf(filename) require("nvim-tree.view").abandon_current_window() if M.relative_path then filename = utils.path_relative(filename, vim.fn.getcwd()) end vim.cmd("keepalt keepjumps edit " .. vim.fn.fnameescape(filename)) end ---@param mode NodeOpenFileMode ---@param filename string ---@return nil function M.fn(mode, filename) if type(mode) ~= "string" then mode = "" end if mode == "tabnew" then return open_file_in_tab(filename) end if mode == "drop" then return drop(filename) end if mode == "tab_drop" then return tab_drop(filename) end if mode == "edit_in_place" then return edit_in_current_buf(filename) end local buf_loaded = is_already_loaded(filename) local found_win = utils.get_win_buf_from_path(filename) if found_win and (mode == "preview" or mode == "preview_no_picker") then return end if not found_win then open_in_new_window(filename, mode) else vim.api.nvim_set_current_win(found_win) vim.bo.bufhidden = "" end if M.resize_window then view.resize() end if mode == "preview" or mode == "preview_no_picker" then return on_preview(buf_loaded) end if M.quit_on_open then view.close() end end ---@param mode string ---@param node Node ---@param edit_opts NodeEditOpts? local function edit(mode, node, edit_opts) local file_link = node:as(FileLinkNode) local path = file_link and file_link.link_to or node.absolute_path local cur_tabpage = vim.api.nvim_get_current_tabpage() M.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 view.close(cur_tabpage) 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 view.focus() end end ---@param node Node ---@param mode NodeOpenFileMode ---@param toggle_group boolean? ---@param edit_opts NodeEditOpts? local function open_or_expand_or_dir_up(node, mode, toggle_group, edit_opts) local root = node:as(RootNode) local dir = node:as(DirectoryNode) if root or node.name == ".." then local explorer = require("nvim-tree.core").get_explorer() if explorer then explorer:change_dir("..") end elseif dir then dir:expand_or_collapse(toggle_group) elseif not toggle_group then edit(mode, node, edit_opts) end end ---@param node Node function M.toggle_group_empty(node) open_or_expand_or_dir_up(node, "toggle_group_empty", true) end ---@param node Node function M.preview(node) open_or_expand_or_dir_up(node, "preview") end ---@param node Node function M.preview_no_picker(node) open_or_expand_or_dir_up(node, "preview_no_picker") end ---@param node Node function M.edit(node) open_or_expand_or_dir_up(node, "edit") end ---@param node Node function M.drop(node) open_or_expand_or_dir_up(node, "drop") end ---@param node Node function M.tab_drop(node) open_or_expand_or_dir_up(node, "tab_drop") end ---@param node Node function M.replace_tree_buffer(node) open_or_expand_or_dir_up(node, "edit_in_place") end ---@param node Node function M.no_window_picker(node) open_or_expand_or_dir_up(node, "edit_no_picker") end ---@param node Node function M.vertical(node) open_or_expand_or_dir_up(node, "vsplit") end ---@param node Node function M.vertical_no_picker(node) open_or_expand_or_dir_up(node, "vsplit_no_picker") end ---@param node Node function M.horizontal(node) open_or_expand_or_dir_up(node, "split") end ---@param node Node function M.horizontal_no_picker(node) open_or_expand_or_dir_up(node, "split_no_picker") end ---@param node Node function M.tab(node) open_or_expand_or_dir_up(node, "tabnew") end function M.setup(opts) M.quit_on_open = opts.actions.open_file.quit_on_open M.resize_window = opts.actions.open_file.resize_window M.relative_path = opts.actions.open_file.relative_path if opts.actions.open_file.window_picker.chars then opts.actions.open_file.window_picker.chars = tostring(opts.actions.open_file.window_picker.chars):upper() end M.window_picker = opts.actions.open_file.window_picker end return M ================================================ FILE: lua/nvim-tree/actions/node/run-command.lua ================================================ local utils = require("nvim-tree.utils") local core = require("nvim-tree.core") local M = {} ---Retrieves the absolute path to the node. ---Safely handles the node representing the current directory ---(the topmost node in the nvim-tree window) ---@param node Node ---@return string local function get_node_path(node) local cwd = core.get_cwd() if node.name == ".." and cwd then return utils.path_remove_trailing(cwd) else return node.absolute_path end end ---@param node Node function M.run_file_command(node) local node_path = get_node_path(node) vim.api.nvim_input(": " .. node_path .. "") end return M ================================================ FILE: lua/nvim-tree/actions/node/system-open.lua ================================================ local notify = require("nvim-tree.notify") local utils = require("nvim-tree.utils") local M = {} ---@param node Node local function user(node) if #M.config.system_open.cmd == 0 then require("nvim-tree.utils").notify.warn("Cannot open file with system application. Unrecognized platform.") return end local process = { cmd = M.config.system_open.cmd, args = M.config.system_open.args, errors = "\n", stderr = vim.loop.new_pipe(false), } table.insert(process.args, node.link_to or node.absolute_path) local opts = { args = process.args, stdio = { nil, nil, process.stderr }, detached = true, } process.handle, process.pid = vim.loop.spawn(process.cmd, opts, function(code) process.stderr:read_stop() process.stderr:close() process.handle:close() if code ~= 0 then notify.warn(string.format("system_open failed with return code %d: %s", code, process.errors)) end end) table.remove(process.args) if not process.handle then notify.warn(string.format("system_open failed to spawn command '%s': %s", process.cmd, process.pid)) return end vim.loop.read_start(process.stderr, function(err, data) if err then return end if data then process.errors = process.errors .. data end end) vim.loop.unref(process.handle) end ---@param node Node local function native(node) local _, err = vim.ui.open(node.link_to or node.absolute_path) -- err only provided on opener executable not found hence logging path is not useful if err then notify.warn(err) end end ---@param node Node function M.fn(node) M.open(node) end -- TODO #2430 always use native once 0.10 is the minimum neovim version function M.setup(opts) M.config = {} M.config.system_open = opts.system_open or {} if vim.fn.has("nvim-0.10") == 1 and #M.config.system_open.cmd == 0 then M.open = native else M.open = user if #M.config.system_open.cmd == 0 then if utils.is_windows then M.config.system_open = { cmd = "cmd", args = { "/c", "start", '""' }, } elseif utils.is_macos then M.config.system_open.cmd = "open" elseif utils.is_unix then M.config.system_open.cmd = "xdg-open" end end end end return M ================================================ FILE: lua/nvim-tree/actions/tree/change-dir.lua ================================================ local core = require("nvim-tree.core") local find_file = require("nvim-tree.actions.tree.find-file") local M = {} ---@param name? string function M.fn(name) local explorer = core.get_explorer() if name and explorer then explorer:change_dir(name) end if M.config.update_focused_file.update_root.enable then find_file.fn() end end ---@param config nvim_tree.config function M.setup(config) M.config = config end return M ================================================ FILE: lua/nvim-tree/actions/tree/collapse.lua ================================================ local utils = require("nvim-tree.utils") local core = require("nvim-tree.core") local Iterator = require("nvim-tree.iterators.node-iterator") local FileNode = require("nvim-tree.node.file") local DirectoryNode = require("nvim-tree.node.directory") local M = {} ---@return fun(path: string): boolean local function buf_match() local buffer_paths = vim.tbl_map(function(buffer) return vim.api.nvim_buf_get_name(buffer) end, vim.api.nvim_list_bufs()) return function(path) for _, buffer_path in ipairs(buffer_paths) do local matches = utils.str_find(buffer_path, path) if matches then return true end end return false end end ---Collapse a node, root if nil ---@param node? Node ---@param opts nvim_tree.api.node.collapse.Opts local function collapse(node, opts) local explorer = core.get_explorer() if not explorer then return end node = node or explorer local node_at_cursor = explorer:get_node_at_cursor() if not node_at_cursor then return end local matches = buf_match() Iterator.builder({ node:is(FileNode) and node.parent or node:as(DirectoryNode) }) :hidden() :applier(function(n) local dir = n:as(DirectoryNode) if dir then dir.open = opts.keep_buffers == true and matches(dir.absolute_path) end end) :recursor(function(n) return n.group_next and { n.group_next } or n.nodes end) :iterate() explorer.renderer:draw() explorer:focus_node_or_parent(node_at_cursor) end ---@param opts? nvim_tree.api.node.collapse.Opts|boolean 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? nvim_tree.api.node.collapse.Opts function M.node(node, opts) collapse(node, opts or {}) end return M ================================================ FILE: lua/nvim-tree/actions/tree/find-file.lua ================================================ local core = require("nvim-tree.core") local lib = require("nvim-tree.lib") local view = require("nvim-tree.view") local finders_find_file = require("nvim-tree.actions.finders.find-file") local M = {} --- Find file or buffer ---@param opts? nvim_tree.api.tree.find_file.Opts|boolean legacy -> opts.buf function M.fn(opts) -- legacy arguments if type(opts) == "string" then opts = { buf = opts, } end opts = opts or {} -- do nothing if closed and open not requested if not opts.open and not core.get_explorer() then return end local bufnr, path -- (optional) buffer number and path local opts_buf = opts.buf if type(opts_buf) == "nil" then bufnr = vim.api.nvim_get_current_buf() path = vim.api.nvim_buf_get_name(bufnr) elseif type(opts_buf) == "number" then if not vim.api.nvim_buf_is_valid(opts_buf) then return end bufnr = opts_buf path = vim.api.nvim_buf_get_name(bufnr) elseif type(opts_buf) == "string" then bufnr = nil path = tostring(opts_buf) else return end if view.is_visible() then -- focus if opts.focus then lib.set_target_win() view.focus() end elseif opts.open then -- open lib.open({ current_window = opts.current_window, winid = opts.winid }) if not opts.focus then vim.cmd("noautocmd wincmd p") end end -- update root if opts.update_root or M.config.update_focused_file.update_root.enable then require("nvim-tree").change_root(path, bufnr) end -- find finders_find_file.fn(path) end function M.setup(opts) M.config = opts or {} end return M ================================================ FILE: lua/nvim-tree/actions/tree/init.lua ================================================ local M = {} M.change_dir = require("nvim-tree.actions.tree.change-dir") M.find_file = require("nvim-tree.actions.tree.find-file") M.collapse = require("nvim-tree.actions.tree.collapse") M.open = require("nvim-tree.actions.tree.open") M.toggle = require("nvim-tree.actions.tree.toggle") M.resize = require("nvim-tree.actions.tree.resize") function M.setup(opts) M.change_dir.setup(opts) M.find_file.setup(opts) M.open.setup(opts) M.toggle.setup(opts) M.resize.setup(opts) end return M ================================================ FILE: lua/nvim-tree/actions/tree/open.lua ================================================ local lib = require("nvim-tree.lib") local view = require("nvim-tree.view") local finders_find_file = require("nvim-tree.actions.finders.find-file") local M = {} ---Open the tree, focusing if already open. ---@param opts? nvim_tree.api.tree.open.Opts|string legacy -> opts.path function M.fn(opts) -- legacy arguments if type(opts) == "string" then opts = { path = opts, } end opts = opts or {} local previous_buf = vim.api.nvim_get_current_buf() local previous_path = vim.api.nvim_buf_get_name(previous_buf) -- sanitise path if type(opts.path) ~= "string" or vim.fn.isdirectory(opts.path) == 0 then opts.path = nil end if view.is_visible() then -- focus lib.set_target_win() view.focus() else -- open lib.open({ path = opts.path, current_window = opts.current_window, winid = opts.winid, }) end -- find file if M.config.update_focused_file.enable or opts.find_file then -- update root if opts.update_root then require("nvim-tree").change_root(previous_path, previous_buf) end -- find finders_find_file.fn(previous_path) end end function M.setup(opts) M.config = opts or {} end return M ================================================ FILE: lua/nvim-tree/actions/tree/resize.lua ================================================ local view = require("nvim-tree.view") local M = {} ---Resize the tree, persisting the new size. ---@param opts? nvim_tree.api.tree.resize.Opts function M.fn(opts) if opts == nil then -- reset to config values view.configure_width() view.resize() return end local options = opts or {} local width_cfg = options.width if width_cfg ~= nil then view.configure_width(width_cfg) view.resize() return end if not view.is_width_determined() then -- {absolute} and {relative} do nothing when {width} is a function. return end local absolute = options.absolute if type(absolute) == "number" then view.resize(absolute) return end local relative = options.relative if type(relative) == "number" then local relative_size = tostring(relative) if relative > 0 then relative_size = "+" .. relative_size end view.resize(relative_size) return end end function M.setup(opts) M.config = opts or {} end return M ================================================ FILE: lua/nvim-tree/actions/tree/toggle.lua ================================================ local lib = require("nvim-tree.lib") local view = require("nvim-tree.view") local finders_find_file = require("nvim-tree.actions.finders.find-file") local M = {} ---Toggle the tree. ---@param opts? nvim_tree.api.tree.toggle.Opts|boolean legacy -> opts.find_file ---@param no_focus? string legacy -> opts.focus ---@param cwd? boolean legacy -> opts.path ---@param bang? boolean legacy -> opts.update_root function M.fn(opts, no_focus, cwd, bang) -- legacy arguments if type(opts) == "boolean" then opts = { find_file = opts, } if type(cwd) == "string" then opts.path = cwd end if type(no_focus) == "boolean" then opts.focus = not no_focus end if type(bang) == "boolean" then opts.update_root = bang end end opts = opts or {} -- defaults if opts.focus == nil then opts.focus = true end local previous_buf = vim.api.nvim_get_current_buf() local previous_path = vim.api.nvim_buf_get_name(previous_buf) -- sanitise path if type(opts.path) ~= "string" or vim.fn.isdirectory(opts.path) == 0 then opts.path = nil end if view.is_visible() then -- close view.close() else -- open lib.open({ path = opts.path, current_window = opts.current_window, winid = opts.winid, }) -- find file if M.config.update_focused_file.enable or opts.find_file then -- update root if opts.update_root then require("nvim-tree").change_root(previous_path, previous_buf) end -- find finders_find_file.fn(previous_path) end -- restore focus if not opts.focus then vim.cmd("noautocmd wincmd p") end end end function M.setup(opts) M.config = opts or {} end return M ================================================ FILE: lua/nvim-tree/api/impl.lua ================================================ ---Hydrates API meta functions with their implementations. ---For startup performance reasons, all API implementation's requires must be ---done at call time, not when this module is required. local M = {} ---Walk the entire API, hydrating all functions with the error notification. ---Do not hydrate classes i.e. anything with a metatable. ---@param api table not properly typed to prevent LSP from referencing implementations local function hydrate_not_setup(api) for k, v in pairs(api) do if type(v) == "function" then api[k] = function() require("nvim-tree.notify").error("nvim-tree setup not called") end elseif type(v) == "table" and not getmetatable(v) then hydrate_not_setup(v) end end end ---Returns the node under the cursor. ---@return Node? local function node_at_cursor() local e = require("nvim-tree.core").get_explorer() return e and e:get_node_at_cursor() or nil end ---Returns the visually selected nodes, if we are in visual mode. ---@return Node[]? local function visual_nodes() local utils = require("nvim-tree.utils") return utils.is_visual_mode() and utils.get_visual_nodes() or nil end ---Injects: ---- n: n or node at cursor ---@param fn fun(n?: Node, ...): any ---@return fun(n?: Node, ...): any local function _n(fn) return function(n, ...) return fn(n or node_at_cursor(), ...) end end ---Injects: ---- n: visual nodes or n or node at cursor ---@param fn fun(n?: Node|Node[], ...): any ---@return fun(n?: Node|Node[], ...): any local function _v(fn) return function(n, ...) return fn(visual_nodes() or n or node_at_cursor(), ...) end end ---Injects: ---- e: Explorer instance ---Does nothing when no explorer instance. ---@param fn fun(e: Explorer, ...): any ---@return fun(e: Explorer, ...): any local function e_(fn) return function(...) local e = require("nvim-tree.core").get_explorer() if e then return fn(e, ...) end end end ---Injects: ---- e: Explorer instance ---- n: n or node at cursor ---Does nothing when no explorer instance. ---@param fn fun(e: Explorer, n?: Node, ...): any ---@return fun(e: Explorer, n?: Node, ...): any local function en(fn) return function(n, ...) local e = require("nvim-tree.core").get_explorer() if e then return fn(e, n or node_at_cursor(), ...) end end end ---Injects: ---- e: Explorer instance ---- n: visual nodes or n or node at cursor ---Does nothing when no explorer instance. ---@param fn fun(e: Explorer, n?: Node|Node[], ...): any ---@return fun(e: Explorer, n?: Node|Node[], ...): any local function ev(fn) return function(n, ...) local e = require("nvim-tree.core").get_explorer() if e then return fn(e, visual_nodes() or n or node_at_cursor(), ...) end end end ---NOP function wrapper, exists for formatting purposes only. ---@param fn fun(...): any ---@return fun(...): any local function __(fn) return function(...) return fn(...) end end ---Hydrates API meta functions pre-setup: --- Pre-setup functions will be hydrated with their implementation. --- Post-setup functions will notify error: "nvim-tree setup not called" --- All classes will be hydrated with their implementations. ---Called once when api is first required ---@param api table not properly typed to prevent LSP from referencing implementations function M.hydrate_pre_setup(api) hydrate_not_setup(api) api.appearance.hi_test = __(function() require("nvim-tree.appearance.hi-test")() end) api.commands.get = __(function() return require("nvim-tree.commands").get() end) api.config.default = __(function() return require("nvim-tree.config").d_clone() end) api.events.subscribe = __(function(event_name, handler) require("nvim-tree.events").subscribe(event_name, handler) end) api.map.keymap.default = __(function() return require("nvim-tree.keymap").get_keymap_default() end) api.map.on_attach.default = __(function(bufnr) require("nvim-tree.keymap").on_attach_default(bufnr) end) api.Decorator = require("nvim-tree.renderer.decorator") -- Map any legacy functions to implementations above or to meta require("nvim-tree.legacy").map_api(api) end ---Re-hydrates all API functions with implementations, replacing any "nvim-tree setup not called" error functions. ---Called explicitly after nvim-tree setup ---@param api table not properly typed to prevent LSP from referencing implementations function M.hydrate_post_setup(api) api.config.global = __(function() return require("nvim-tree.config").g_clone() end) api.config.user = __(function() return require("nvim-tree.config").u_clone() end) api.filter.custom.toggle = e_(function(e) e.filters:toggle("custom") end) api.filter.dotfiles.toggle = e_(function(e) e.filters:toggle("dotfiles") end) api.filter.git.clean.toggle = e_(function(e) e.filters:toggle("git_clean") end) api.filter.git.ignored.toggle = e_(function(e) e.filters:toggle("git_ignored") end) api.filter.live.clear = e_(function(e) e.live_filter:clear_filter() end) api.filter.live.start = e_(function(e) e.live_filter:start_filtering() end) api.filter.no_bookmark.toggle = e_(function(e) e.filters:toggle("no_bookmark") end) api.filter.no_buffer.toggle = e_(function(e) e.filters:toggle("no_buffer") end) api.filter.toggle = e_(function(e) e.filters:toggle() end) api.fs.clear_clipboard = e_(function(e) e.clipboard:clear_clipboard() end) api.fs.copy.absolute_path = en(function(e, n) e.clipboard:copy_absolute_path(n) end) api.fs.copy.basename = en(function(e, n) e.clipboard:copy_basename(n) end) api.fs.copy.filename = en(function(e, n) e.clipboard:copy_filename(n) end) api.fs.copy.node = ev(function(e, n) e.clipboard:copy(n) end) api.fs.copy.relative_path = en(function(e, n) e.clipboard:copy_path(n) end) api.fs.create = _n(function(n) require("nvim-tree.actions").fs.create_file.fn(n) end) api.fs.cut = ev(function(e, n) e.clipboard:cut(n) end) api.fs.paste = en(function(e, n) e.clipboard:paste(n) end) api.fs.print_clipboard = e_(function(e) e.clipboard:print_clipboard() end) api.fs.remove = _v(function(n) require("nvim-tree.actions").fs.remove_file.fn(n) end) api.fs.rename = _n(function(n) require("nvim-tree.actions").fs.rename_file.rename_node(n) end) api.fs.rename_basename = _n(function(n) require("nvim-tree.actions").fs.rename_file.rename_basename(n) end) api.fs.rename_full = _n(function(n) require("nvim-tree.actions").fs.rename_file.rename_full(n) end) api.fs.rename_node = _n(function(n) require("nvim-tree.actions").fs.rename_file.rename_node(n) end) api.fs.rename_sub = _n(function(n) require("nvim-tree.actions").fs.rename_file.rename_sub(n) end) api.fs.trash = _v(function(n) require("nvim-tree.actions").fs.trash.fn(n) end) api.git.reload = e_(function(e) e:reload_git() end) api.map.keymap.current = __(function() return require("nvim-tree.keymap").get_keymap() end) api.marks.bulk.delete = e_(function(e) e.marks:bulk_delete() end) api.marks.bulk.move = e_(function(e) e.marks:bulk_move() end) api.marks.bulk.trash = e_(function(e) e.marks:bulk_trash() end) api.marks.clear = e_(function(e) e.marks:clear() end) api.marks.get = en(function(e, n) return e.marks:get(n) end) api.marks.list = e_(function(e) return e.marks:list() end) api.marks.navigate.next = e_(function(e) e.marks:navigate_next() end) api.marks.navigate.prev = e_(function(e) e.marks:navigate_prev() end) api.marks.navigate.select = e_(function(e) e.marks:navigate_select() end) api.marks.toggle = ev(function(e, n) e.marks:toggle(n) end) api.node.buffer.delete = _n(function(n, opts) require("nvim-tree.actions").node.buffer.delete(n, opts) end) api.node.buffer.wipe = _n(function(n, opts) require("nvim-tree.actions").node.buffer.wipe(n, opts) end) api.node.collapse = _n(function(n, opts) require("nvim-tree.actions").tree.collapse.node(n, opts) end) api.node.expand = en(function(e, n, opts) e:expand_node(n, opts) end) api.node.navigate.diagnostics.next = __(function() require("nvim-tree.actions").moves.item.diagnostics_next() end) api.node.navigate.diagnostics.next_recursive = __(function() require("nvim-tree.actions").moves.item.diagnostics_next_recursive() end) api.node.navigate.diagnostics.prev = __(function() require("nvim-tree.actions").moves.item.diagnostics_prev() end) api.node.navigate.diagnostics.prev_recursive = __(function() require("nvim-tree.actions").moves.item.diagnostics_prev_recursive() end) api.node.navigate.git.next = __(function() require("nvim-tree.actions").moves.item.git_next() end) api.node.navigate.git.next_recursive = __(function() require("nvim-tree.actions").moves.item.git_next_recursive() end) api.node.navigate.git.next_skip_gitignored = __(function() require("nvim-tree.actions").moves.item.git_next_skip_gitignored() end) api.node.navigate.git.prev = __(function() require("nvim-tree.actions").moves.item.git_prev() end) api.node.navigate.git.prev_recursive = __(function() require("nvim-tree.actions").moves.item.git_prev_recursive() end) api.node.navigate.git.prev_skip_gitignored = __(function() require("nvim-tree.actions").moves.item.git_prev_skip_gitignored() end) api.node.navigate.opened.next = __(function() require("nvim-tree.actions").moves.item.opened_next() end) api.node.navigate.opened.prev = __(function() require("nvim-tree.actions").moves.item.opened_prev() end) api.node.navigate.parent = _n(function(n) require("nvim-tree.actions").moves.parent.move(n) end) api.node.navigate.parent_close = _n(function(n) require("nvim-tree.actions").moves.parent.move_close(n) end) api.node.navigate.sibling.first = _n(function(n) require("nvim-tree.actions").moves.sibling.first(n) end) api.node.navigate.sibling.last = _n(function(n) require("nvim-tree.actions").moves.sibling.last(n) end) api.node.navigate.sibling.next = _n(function(n) require("nvim-tree.actions").moves.sibling.next(n) end) api.node.navigate.sibling.prev = _n(function(n) require("nvim-tree.actions").moves.sibling.prev(n) end) api.node.open.drop = _n(function(n) require("nvim-tree.actions").node.open_file.drop(n) end) api.node.open.edit = _n(function(n) require("nvim-tree.actions").node.open_file.edit(n) end) api.node.open.horizontal = _n(function(n) require("nvim-tree.actions").node.open_file.horizontal(n) end) api.node.open.horizontal_no_picker = _n(function(n) require("nvim-tree.actions").node.open_file.horizontal_no_picker(n) end) api.node.open.no_window_picker = _n(function(n) require("nvim-tree.actions").node.open_file.no_window_picker(n) end) api.node.open.preview = _n(function(n) require("nvim-tree.actions").node.open_file.preview(n) end) api.node.open.preview_no_picker = _n(function(n) require("nvim-tree.actions").node.open_file.preview_no_picker(n) end) api.node.open.replace_tree_buffer = _n(function(n) require("nvim-tree.actions").node.open_file.replace_tree_buffer(n) end) api.node.open.tab = _n(function(n) require("nvim-tree.actions").node.open_file.tab(n) end) api.node.open.tab_drop = _n(function(n) require("nvim-tree.actions").node.open_file.tab_drop(n) end) api.node.open.toggle_group_empty = _n(function(n) require("nvim-tree.actions").node.open_file.toggle_group_empty(n) end) api.node.open.vertical = _n(function(n) require("nvim-tree.actions").node.open_file.vertical(n) end) api.node.open.vertical_no_picker = _n(function(n) require("nvim-tree.actions").node.open_file.vertical_no_picker(n) end) api.node.run.cmd = _n(function(n) require("nvim-tree.actions").node.run_command.run_file_command(n) end) api.node.run.system = _n(function(n) require("nvim-tree.actions").node.system_open.fn(n) end) api.node.show_info_popup = _n(function(n) require("nvim-tree.actions").node.file_popup.toggle_file_info(n) end) api.tree.change_root = __(function(path) require("nvim-tree.actions").tree.change_dir.fn(path) end) api.tree.change_root_to_node = en(function(e, n) e:change_dir_to_node(n) end) api.tree.change_root_to_parent = en(function(e, n) e:dir_up(n) end) api.tree.close = __(function() require("nvim-tree.view").close() end) api.tree.close_in_all_tabs = __(function() require("nvim-tree.view").close_all_tabs() end) api.tree.close_in_this_tab = __(function() require("nvim-tree.view").close_this_tab_only() end) api.tree.collapse_all = __(function(opts) require("nvim-tree.actions").tree.collapse.all(opts) end) api.tree.expand_all = en(function(e, n, opts) e:expand_all(n, opts) end) api.tree.find_file = __(function(opts) require("nvim-tree.actions").tree.find_file.fn(opts) end) api.tree.focus = __(function(opts) require("nvim-tree.actions").tree.open.fn(opts) end) api.tree.get_node_under_cursor = en(function(e) return e:get_node_at_cursor() end) api.tree.get_nodes = en(function(e) return e:get_nodes() end) api.tree.is_tree_buf = __(function(bufnr) return require("nvim-tree.utils").is_nvim_tree_buf(bufnr) end) api.tree.is_visible = __(function(opts) return require("nvim-tree.view").is_visible(opts) end) api.tree.open = __(function(opts) require("nvim-tree.actions").tree.open.fn(opts) end) api.tree.reload = e_(function(e) e:reload_explorer() end) api.tree.reload_git = e_(function(e) e:reload_git() end) api.tree.resize = __(function(opts) require("nvim-tree.actions").tree.resize.fn(opts) end) api.tree.search_node = __(function() require("nvim-tree.actions").finders.search_node.fn() end) api.tree.toggle = __(function(opts) require("nvim-tree.actions").tree.toggle.fn(opts) end) api.tree.toggle_help = __(function() require("nvim-tree.help").toggle() end) api.tree.winid = __(function(opts) return require("nvim-tree.view").winid(opts) end) -- Map all legacy functions to implementations require("nvim-tree.legacy").map_api(api) end return M ================================================ FILE: lua/nvim-tree/api.lua ================================================ ---@brief ---nvim-tree exposes a public API. This is non breaking, with additions made as necessary. --- ---Please do not require or use modules other than `nvim-tree.api`, as internal modules will change without notice. --- ---The API is separated into multiple modules: --- ---- [nvim-tree-api-appearance] ---- [nvim-tree-api-commands] ---- [nvim-tree-api-events] ---- [nvim-tree-api-filter] ---- [nvim-tree-api-fs] ---- [nvim-tree-api-git] ---- [nvim-tree-api-map] ---- [nvim-tree-api-marks] ---- [nvim-tree-api-node] ---- [nvim-tree-api-tree] --- ---Modules are accessed via `api..` --- ---Example invocation of the `reload` function in the `tree` module: ---```lua --- ---local api = require("nvim-tree.api") ---api.tree.reload() ---``` ---Generally, functions accepting a [nvim_tree.api.Node] as their first argument will use the node under the cursor when that argument is not present or nil. Some functions are mode-dependent: when invoked in visual mode they will operate on all nodes in the visual selection instead of a single node. See |nvim-tree-mappings-default| for which mappings support visual mode. --- ---e.g. the following are functionally identical: ---```lua --- ---api.node.open.edit(nil, { focus = true }) --- ---api.node.open.edit(api.tree.get_node_under_cursor(), { focus = true }) ---``` ---The Node class is a data class. Instances may be provided by API functions for use as a: ---- handle to pass back to API functions e.g. [nvim_tree.api.node.run.cmd()] ---- reference in callbacks e.g. [nvim_tree.config.sort.Sorter] {sorter} --- ---Please do not mutate the contents of any Node object. --- ---@class nvim_tree.api.Node ---@field absolute_path string of the file or directory ---@field name string file or directory name ---@field parent? nvim_tree.api.DirectoryNode parent directory, nil for root ---@field type "file" | "directory" | "link" [uv.fs_stat()] {type} ---@field executable boolean file is executable ---@field fs_stat? uv.fs_stat.result at time of last tree display, see [uv.fs_stat()] ---@field git_status nvim_tree.git.Status? for files and directories ---@field diag_severity? lsp.DiagnosticSeverity diagnostic status ---@field hidden boolean node is not visible in the tree --- ---Git statuses for a single node. --- ---`nvim_tree.git.XY`: 2 character string, see `man 1 git-status` "Short Format" ---@alias nvim_tree.git.XY string --- ---{dir} status is derived from its contents: ---- `direct`: inherited from child files ---- `indirect`: inherited from child directories --- ---@class nvim_tree.git.Status ---@field file? nvim_tree.git.XY status of a file node ---@field dir? table<"direct" | "indirect", nvim_tree.git.XY[]> direct inclusive-or indirect status --- ---nvim-tree Public API --- ---@class nvim_tree.api ---@nodoc local api = { appearance = require("nvim-tree._meta.api.appearance"), commands = require("nvim-tree._meta.api.commands"), config = require("nvim-tree._meta.api.config"), events = require("nvim-tree._meta.api.events"), filter = require("nvim-tree._meta.api.filter"), fs = require("nvim-tree._meta.api.fs"), git = require("nvim-tree._meta.api.git"), map = require("nvim-tree._meta.api.map"), marks = require("nvim-tree._meta.api.marks"), node = require("nvim-tree._meta.api.node"), tree = require("nvim-tree._meta.api.tree"), Decorator = require("nvim-tree._meta.api.decorator"), decorator = require("nvim-tree._meta.api.deprecated").decorator, ---@deprecated diagnostics = require("nvim-tree._meta.api.deprecated").diagnostics, ---@deprecated live_filter = require("nvim-tree._meta.api.deprecated").live_filter, ---@deprecated } require("nvim-tree.api.impl").hydrate_pre_setup(api) return api ================================================ FILE: lua/nvim-tree/appearance/hi-test.lua ================================================ local appearance = require("nvim-tree.appearance") local Class = require("nvim-tree.classic") -- others with name and links less than this arbitrary value are short local SHORT_LEN = 50 local namespace_hi_test_id = vim.api.nvim_create_namespace("NvimTreeHiTest") ---For :NvimTreeHiTest ---@class (exact) HighlightDisplay: nvim_tree.Class ---@field group string nvim-tree highlight group name ---@field links string link chain to a concretely defined group ---@field def string :hi concrete definition after following any links local HighlightDisplay = Class:extend() ---@class HighlightDisplay ---@overload fun(args: HighlightDisplayArgs): HighlightDisplay ---@class (exact) HighlightDisplayArgs ---@field group string nvim-tree highlight group name ---@protected ---@param args HighlightDisplayArgs function HighlightDisplay:new(args) self.group = args.group local concrete = self.group -- maybe follow links local links = {} local link = vim.api.nvim_get_hl(0, { name = self.group }).link while link do table.insert(links, link) concrete = link link = vim.api.nvim_get_hl(0, { name = link }).link end self.links = table.concat(links, " ") -- concrete definition local ok, res = pcall(vim.api.nvim_cmd, { cmd = "highlight", args = { concrete } }, { output = true }) if ok and type(res) == "string" then self.def = res:gsub(".*xxx *", "") else self.def = "" end end ---Render one group. ---@param bufnr number to render in ---@param fmt string format string for group, links, def ---@param l number line number to render at ---@return number l next line number function HighlightDisplay:render(bufnr, fmt, l) local text = string.format(fmt, self.group, self.links, self.def) vim.api.nvim_buf_set_lines(bufnr, l, -1, true, { text }) 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 end ---Render many groups. ---@param header string before with underline line ---@param displays HighlightDisplay[] highlight group ---@param bufnr number to render in ---@param l number line number to start at ---@return number l next line number local function render_displays(header, displays, bufnr, l) local max_group_len = 0 local max_links_len = 0 -- build all highlight groups, using name only for _, display in ipairs(displays) do max_group_len = math.max(max_group_len, #display.group) max_links_len = math.max(max_links_len, #display.links) end -- header vim.api.nvim_buf_set_lines(bufnr, l, -1, true, { header, (header:gsub(".", "-")) }) l = l + 2 -- render and highlight local fmt = string.format("%%-%d.%ds %%-%d.%ds %%s", max_group_len, max_group_len, max_links_len, max_links_len) for _, display in ipairs(displays) do l = display:render(bufnr, fmt, l) end return l end ---Run a test similar to :so $VIMRUNTIME/syntax/hitest.vim ---Display all nvim-tree and neovim highlight groups, their link chain and actual definition return function() -- create a buffer local bufnr = vim.api.nvim_create_buf(false, true) local l = 0 -- nvim-tree groups, ordered local displays = {} for _, highlight_group in ipairs(appearance.HIGHLIGHT_GROUPS) do local display = HighlightDisplay({ group = highlight_group.group }) table.insert(displays, display) end l = render_displays("nvim-tree", displays, bufnr, l) vim.api.nvim_buf_set_lines(bufnr, l, -1, true, { "" }) l = l + 1 -- built in groups, ordered opaquely by nvim local displays_short, displays_long = {}, {} local ok, out = pcall(vim.api.nvim_cmd, { cmd = "highlight" }, { output = true }) if ok then for group in string.gmatch(out, "(%w*)%s+xxx") do if group:find("NvimTree", 1, true) ~= 1 then local display = HighlightDisplay({ group = group }) if #display.group + #display.links > SHORT_LEN then table.insert(displays_long, display) else table.insert(displays_short, display) end end end end -- short ones first l = render_displays("other, short", displays_short, bufnr, l) vim.api.nvim_buf_set_lines(bufnr, l, -1, true, { "" }) l = l + 1 -- long render_displays("other, long", displays_long, bufnr, l) -- finalise and focus the buffer if vim.fn.has("nvim-0.10") == 1 then vim.api.nvim_set_option_value("modifiable", false, { buf = bufnr }) else vim.api.nvim_buf_set_option(bufnr, "modifiable", false) ---@diagnostic disable-line: deprecated end vim.cmd.buffer(bufnr) end ================================================ FILE: lua/nvim-tree/appearance/init.lua ================================================ local M = {} ---@class HighlightGroup ---@field group string ---@field link string|nil ---@field def string|nil ---@type HighlightGroup[] -- All highlight groups: linked or directly defined. -- Please add new groups to help and preserve order. -- Please avoid directly defined groups to preserve accessibility for TUI. M.HIGHLIGHT_GROUPS = { -- Standard { group = "NvimTreeNormal", link = "Normal" }, { group = "NvimTreeNormalFloat", link = "NormalFloat" }, { group = "NvimTreeNormalFloatBorder", link = "FloatBorder" }, { group = "NvimTreeNormalNC", link = "NvimTreeNormal" }, { group = "NvimTreeLineNr", link = "LineNr" }, { group = "NvimTreeWinSeparator", link = "WinSeparator" }, { group = "NvimTreeEndOfBuffer", link = "EndOfBuffer" }, { group = "NvimTreePopup", link = "Normal" }, { group = "NvimTreeSignColumn", link = "NvimTreeNormal" }, { group = "NvimTreeCursorColumn", link = "CursorColumn" }, { group = "NvimTreeCursorLine", link = "CursorLine" }, { group = "NvimTreeCursorLineNr", link = "CursorLineNr" }, { group = "NvimTreeStatusLine", link = "StatusLine" }, { group = "NvimTreeStatusLineNC", link = "StatusLineNC" }, -- File Text { group = "NvimTreeExecFile", link = "Question" }, { group = "NvimTreeImageFile", link = "Question" }, { group = "NvimTreeSpecialFile", link = "Title" }, { group = "NvimTreeSymlink", link = "Underlined" }, -- Folder Text { group = "NvimTreeRootFolder", link = "Title" }, { group = "NvimTreeFolderName", link = "Directory" }, { group = "NvimTreeEmptyFolderName", link = "Directory" }, { group = "NvimTreeOpenedFolderName", link = "Directory" }, { group = "NvimTreeSymlinkFolderName", link = "Directory" }, -- File Icons { group = "NvimTreeFileIcon", link = "NvimTreeNormal" }, { group = "NvimTreeSymlinkIcon", link = "NvimTreeNormal" }, -- Folder Icons { group = "NvimTreeFolderIcon", def = "guifg=#8094b4 ctermfg=Blue" }, { group = "NvimTreeOpenedFolderIcon", link = "NvimTreeFolderIcon" }, { group = "NvimTreeClosedFolderIcon", link = "NvimTreeFolderIcon" }, { group = "NvimTreeFolderArrowClosed", link = "NvimTreeIndentMarker" }, { group = "NvimTreeFolderArrowOpen", link = "NvimTreeIndentMarker" }, -- Indent { group = "NvimTreeIndentMarker", link = "NvimTreeFolderIcon" }, -- Picker { group = "NvimTreeWindowPicker", def = "guifg=#ededed guibg=#4493c8 gui=bold ctermfg=White ctermbg=DarkBlue" }, -- LiveFilter { group = "NvimTreeLiveFilterPrefix", link = "PreProc" }, { group = "NvimTreeLiveFilterValue", link = "ModeMsg" }, -- Clipboard { group = "NvimTreeCutHL", link = "SpellBad" }, { group = "NvimTreeCopiedHL", link = "SpellRare" }, -- Bookmark { group = "NvimTreeBookmarkIcon", link = "NvimTreeFolderIcon" }, { group = "NvimTreeBookmarkHL", link = "SpellLocal" }, -- Modified { group = "NvimTreeModifiedIcon", link = "Type" }, { group = "NvimTreeModifiedFileHL", link = "NvimTreeModifiedIcon" }, { group = "NvimTreeModifiedFolderHL", link = "NvimTreeModifiedFileHL" }, -- Hidden { group = "NvimTreeHiddenIcon", link = "Conceal" }, { group = "NvimTreeHiddenFileHL", link = "NvimTreeHiddenIcon" }, { group = "NvimTreeHiddenFolderHL", link = "NvimTreeHiddenFileHL" }, -- Hidden Display { group = "NvimTreeHiddenDisplay", link = "Conceal" }, -- Opened { group = "NvimTreeOpenedHL", link = "Special" }, -- Git Icon { group = "NvimTreeGitDeletedIcon", link = "Statement" }, { group = "NvimTreeGitDirtyIcon", link = "Statement" }, { group = "NvimTreeGitIgnoredIcon", link = "Comment" }, { group = "NvimTreeGitMergeIcon", link = "Constant" }, { group = "NvimTreeGitNewIcon", link = "PreProc" }, { group = "NvimTreeGitRenamedIcon", link = "PreProc" }, { group = "NvimTreeGitStagedIcon", link = "Constant" }, -- Git File Highlight { group = "NvimTreeGitFileDeletedHL", link = "NvimTreeGitDeletedIcon" }, { group = "NvimTreeGitFileDirtyHL", link = "NvimTreeGitDirtyIcon" }, { group = "NvimTreeGitFileIgnoredHL", link = "NvimTreeGitIgnoredIcon" }, { group = "NvimTreeGitFileMergeHL", link = "NvimTreeGitMergeIcon" }, { group = "NvimTreeGitFileNewHL", link = "NvimTreeGitNewIcon" }, { group = "NvimTreeGitFileRenamedHL", link = "NvimTreeGitRenamedIcon" }, { group = "NvimTreeGitFileStagedHL", link = "NvimTreeGitStagedIcon" }, -- Git Folder Highlight { group = "NvimTreeGitFolderDeletedHL", link = "NvimTreeGitFileDeletedHL" }, { group = "NvimTreeGitFolderDirtyHL", link = "NvimTreeGitFileDirtyHL" }, { group = "NvimTreeGitFolderIgnoredHL", link = "NvimTreeGitFileIgnoredHL" }, { group = "NvimTreeGitFolderMergeHL", link = "NvimTreeGitFileMergeHL" }, { group = "NvimTreeGitFolderNewHL", link = "NvimTreeGitFileNewHL" }, { group = "NvimTreeGitFolderRenamedHL", link = "NvimTreeGitFileRenamedHL" }, { group = "NvimTreeGitFolderStagedHL", link = "NvimTreeGitFileStagedHL" }, -- Diagnostics Icon { group = "NvimTreeDiagnosticErrorIcon", link = "DiagnosticError" }, { group = "NvimTreeDiagnosticWarnIcon", link = "DiagnosticWarn" }, { group = "NvimTreeDiagnosticInfoIcon", link = "DiagnosticInfo" }, { group = "NvimTreeDiagnosticHintIcon", link = "DiagnosticHint" }, -- Diagnostics File Highlight { group = "NvimTreeDiagnosticErrorFileHL", link = "DiagnosticUnderlineError" }, { group = "NvimTreeDiagnosticWarnFileHL", link = "DiagnosticUnderlineWarn" }, { group = "NvimTreeDiagnosticInfoFileHL", link = "DiagnosticUnderlineInfo" }, { group = "NvimTreeDiagnosticHintFileHL", link = "DiagnosticUnderlineHint" }, -- Diagnostics Folder Highlight { group = "NvimTreeDiagnosticErrorFolderHL", link = "NvimTreeDiagnosticErrorFileHL" }, { group = "NvimTreeDiagnosticWarnFolderHL", link = "NvimTreeDiagnosticWarnFileHL" }, { group = "NvimTreeDiagnosticInfoFolderHL", link = "NvimTreeDiagnosticInfoFileHL" }, { group = "NvimTreeDiagnosticHintFolderHL", link = "NvimTreeDiagnosticHintFileHL" }, } -- nvim-tree highlight groups to legacy M.LEGACY_LINKS = { NvimTreeModifiedIcon = "NvimTreeModifiedFile", NvimTreeOpenedHL = "NvimTreeOpenedFile", NvimTreeBookmarkIcon = "NvimTreeBookmark", NvimTreeGitDeletedIcon = "NvimTreeGitDeleted", NvimTreeGitDirtyIcon = "NvimTreeGitDirty", NvimTreeGitIgnoredIcon = "NvimTreeGitIgnored", NvimTreeGitMergeIcon = "NvimTreeGitMerge", NvimTreeGitNewIcon = "NvimTreeGitNew", NvimTreeGitRenamedIcon = "NvimTreeGitRenamed", NvimTreeGitStagedIcon = "NvimTreeGitStaged", NvimTreeGitFileDeletedHL = "NvimTreeFileDeleted", NvimTreeGitFileDirtyHL = "NvimTreeFileDirty", NvimTreeGitFileIgnoredHL = "NvimTreeFileIgnored", NvimTreeGitFileMergeHL = "NvimTreeFileMerge", NvimTreeGitFileNewHL = "NvimTreeFileNew", NvimTreeGitFileRenamedHL = "NvimTreeFileRenamed", NvimTreeGitFileStagedHL = "NvimTreeFileStaged", NvimTreeGitFolderDeletedHL = "NvimTreeFolderDeleted", NvimTreeGitFolderDirtyHL = "NvimTreeFolderDirty", NvimTreeGitFolderIgnoredHL = "NvimTreeFolderIgnored", NvimTreeGitFolderMergeHL = "NvimTreeFolderMerge", NvimTreeGitFolderNewHL = "NvimTreeFolderNew", NvimTreeGitFolderRenamedHL = "NvimTreeFolderRenamed", NvimTreeGitFolderStagedHL = "NvimTreeFolderStaged", NvimTreeDiagnosticErrorIcon = "NvimTreeLspDiagnosticsError", NvimTreeDiagnosticWarnIcon = "NvimTreeLspDiagnosticsWarning", NvimTreeDiagnosticInfoIcon = "NvimTreeLspDiagnosticsInformation", NvimTreeDiagnosticHintIcon = "NvimTreeLspDiagnosticsHint", NvimTreeDiagnosticErrorFileHL = "NvimTreeLspDiagnosticsErrorText", NvimTreeDiagnosticWarnFileHL = "NvimTreeLspDiagnosticsWarningText", NvimTreeDiagnosticInfoFileHL = "NvimTreeLspDiagnosticsInformationText", NvimTreeDiagnosticHintFileHL = "NvimTreeLspDiagnosticsHintText", NvimTreeDiagnosticErrorFolderHL = "NvimTreeLspDiagnosticsErrorFolderText", NvimTreeDiagnosticWarnFolderHL = "NvimTreeLspDiagnosticsWarningFolderText", NvimTreeDiagnosticInfoFolderHL = "NvimTreeLspDiagnosticsInformationFolderText", NvimTreeDiagnosticHintFolderHL = "NvimTreeLspDiagnosticsHintFolderText", } function M.setup() -- non-linked for _, g in ipairs(M.HIGHLIGHT_GROUPS) do if g.def then vim.api.nvim_command("hi def " .. g.group .. " " .. g.def) end end -- hard link override when legacy only is present for from, to in pairs(M.LEGACY_LINKS) do local hl_from = vim.api.nvim_get_hl(0, { name = from }) local hl_to = vim.api.nvim_get_hl(0, { name = to }) if vim.tbl_isempty(hl_from) and not vim.tbl_isempty(hl_to) then vim.api.nvim_command("hi link " .. from .. " " .. to) end end -- default links for _, g in ipairs(M.HIGHLIGHT_GROUPS) do if g.link then vim.api.nvim_command("hi def link " .. g.group .. " " .. g.link) end end end return M ================================================ FILE: lua/nvim-tree/buffers.lua ================================================ local DirectoryNode = require("nvim-tree.node.directory") local M = {} ---@type table record of which file is modified M._modified = {} ---refresh M._modified function M.reload_modified() M._modified = {} local bufs = vim.fn.getbufinfo({ bufmodified = 1, buflisted = 1 }) for _, buf in pairs(bufs) do local path = buf.name if path ~= "" then -- not a [No Name] buffer -- mark all the parent as modified as well while M._modified[path] ~= true do -- no need to keep going if already recorded -- This also prevents an infinite loop M._modified[path] = true path = vim.fn.fnamemodify(path, ":h") end end end end ---@param node Node ---@return boolean function M.is_modified(node) if not M.config.modified.enable then return false end if not M._modified[node.absolute_path] then return false end local dir = node:as(DirectoryNode) if dir then if not M.config.modified.show_on_dirs then return false end if dir.open and not M.config.modified.show_on_open_dirs then return false end end return true end ---A buffer exists for the node's absolute path ---@param node Node ---@return boolean function M.is_opened(node) return node and vim.fn.bufloaded(node.absolute_path) > 0 end ---@param opts nvim_tree.config function M.setup(opts) M.config = { modified = opts.modified, } end return M ================================================ FILE: lua/nvim-tree/classic.lua ================================================ -- -- classic -- -- Copyright (c) 2014, rxi -- -- This module is free software; you can redistribute it and/or modify it under -- the terms of the MIT license. See LICENSE for details. -- -- https://github.com/rxi/classic -- ---@brief --- ---nvim-tree uses the https://github.com/rxi/classic class framework adding safe casts, instanceof mixin and conventional destructors. --- ---The key differences between classic and ordinary Lua classes: ---- The constructor [nvim_tree.Class:new()] is not responsible for allocation: `self` is available when the constructor is called. ---- Instances are constructed via the `__call` meta method: `SomeClass(args)` --- ---Classes are conventionally named using camel case e.g. `MyClass` --- ---Classes are created by extending another class: ---```lua --- --- local Class = require("nvim-tree.classic") --- --- ---@class (exact) Fruit: nvim_tree.Class --- ---@field ... --- local Fruit = Class:extend() --- --- ---@class (exact) Apple: Fruit --- ---@field ... --- local Apple = Fruit:extend() ---``` --- ---Implementing a constructor [nvim_tree.Class:new()] is optional, however it must call the `super` constructor: ---```lua --- --- ---@protected --- ---@param args AppleArgs --- function Apple:new(args) --- --- ---@type FruitArgs --- local fruit_args = ... --- --- Apple.super.new(self, fruit_args) --- --- ---``` --- ---Create an instance of a class using the `__call` meta method that will invoke the constructor: ---```lua --- --- ---@type AppleArgs --- local args = ... --- --- local an_apple = Apple(args) --- -- above will call `Apple:new(args)` ---``` --- ---In order to strongly type instantiation, the following pattern is used to type the meta method `__call` with arguments and return: ---```lua --- --- ---@class (exact) Fruit: nvim_tree.Class --- ---@field ... --- local Fruit = Class:extend() --- --- ---@class (exact) FruitArgs --- ---@field ... --- --- ---@class Fruit --- ---@overload fun(args: FruitArgs): Fruit --- --- ---@protected --- ---@param args FruitArgs --- function Fruit:new(args) ---``` --- ---@class nvim_tree.Class --- ---Parent class, `Class` for base classes. ---@field super nvim_tree.Class --- ---mixin classes that are implemented. ---@field private implements table local Class = {} Class.__index = Class ---@diagnostic disable-line: inject-field --- ---Constructor: `self` has been allocated and is available. --- ---Super constructor must be called using the form `Child.super.new(self, parent_args)` --- ---@param ... any constructor arguments function Class:new(...) --luacheck: ignore 212 end --- ---Conventional destructor, optional, must be called by the owner. --- ---Parent destructor must be invoked using the form `Parent.destroy(self)` --- function Class:destroy() end --- ---Create a new class by extending another class. --- ---Base classes extend `Class` --- ---@return [nvim_tree.Class] child class function Class:extend() local cls = {} for k, v in pairs(self) do if k:find("__") == 1 then cls[k] = v end end cls.__index = cls cls.super = self setmetatable(cls, self) return cls end --- ---Add the methods and fields of a mixin using the form `SomeClass:implement(MixinClass)` --- ---If the mixin has fields, it must implement a constructor. --- ---@param mixin nvim_tree.Class function Class:implement(mixin) if not rawget(self, "implements") then -- set on the class itself instead of parents rawset(self, "implements", {}) end self.implements[mixin] = true for k, v in pairs(mixin) do if self[k] == nil and type(v) == "function" then self[k] = v end end end --- ---Instance of. --- ---Test whether an object is {class}, inherits {class} or implements mixin {class}. --- ---@generic T ---@param class T `` ---@return boolean function Class:is(class) local mt = getmetatable(self) while mt do if mt == class then return true end if mt.implements and mt.implements[class] then return true end mt = getmetatable(mt) end return false end --- ---Type safe cast. --- ---If instance [nvim_tree.Class:is()], cast to {class} and return it, otherwise nil. --- ---@generic T ---@param class T `` ---@return T? `` function Class:as(class) return self:is(class) and self or nil end --- ---Constructs an instance: calls [nvim_tree.Class:new()] and returns the new instance. --- ---@param ... any constructor args ---@return nvim_tree.Class function Class:__call(...) local obj = setmetatable({}, self) obj:new(...) return obj end --- ---Utility method to bypass unused param warnings in abstract methods. --- ---@param ... any function Class:nop(...) --luacheck: ignore 212 end return Class ================================================ FILE: lua/nvim-tree/commands.lua ================================================ local M = {} ---@type nvim_tree.api.commands.Command[] local CMDS = { { name = "NvimTreeOpen", opts = { desc = "nvim-tree: open", nargs = "?", complete = "dir", }, command = function(c) require("nvim-tree.api").tree.open({ path = c.args }) end, }, { name = "NvimTreeClose", opts = { desc = "nvim-tree: close", bar = true, }, command = function() require("nvim-tree.api").tree.close() end, }, { name = "NvimTreeToggle", opts = { desc = "nvim-tree: toggle", nargs = "?", complete = "dir", }, command = function(c) require("nvim-tree.api").tree.toggle({ find_file = false, focus = true, path = c.args, update_root = false, }) end, }, { name = "NvimTreeFocus", opts = { desc = "nvim-tree: focus", bar = true, }, command = function() require("nvim-tree.api").tree.open() end, }, { name = "NvimTreeRefresh", opts = { desc = "nvim-tree: refresh", bar = true, }, command = function() require("nvim-tree.api").tree.reload() end, }, { name = "NvimTreeClipboard", opts = { desc = "nvim-tree: print clipboard", bar = true, }, command = function() require("nvim-tree.api").fs.print_clipboard() end, }, { name = "NvimTreeFindFile", opts = { desc = "nvim-tree: find file", bang = true, bar = true, }, command = function(c) require("nvim-tree.api").tree.find_file({ open = true, focus = true, update_root = c.bang, }) end, }, { name = "NvimTreeFindFileToggle", opts = { desc = "nvim-tree: find file, toggle", bang = true, nargs = "?", complete = "dir", }, command = function(c) require("nvim-tree.api").tree.toggle({ find_file = true, focus = true, path = c.args, update_root = c.bang, }) end, }, { name = "NvimTreeResize", opts = { desc = "nvim-tree: resize", nargs = 1, bar = true, }, command = function(c) local sign = c.args:sub(1, 1) if sign == "+" or sign == "-" then require("nvim-tree.api").tree.resize({ relative = tonumber(c.args) }) else require("nvim-tree.api").tree.resize({ absolute = tonumber(c.args) }) end end, }, { name = "NvimTreeCollapse", opts = { desc = "nvim-tree: collapse", bar = true, }, command = function() require("nvim-tree.api").tree.collapse_all({ keep_buffers = false }) end, }, { name = "NvimTreeCollapseKeepBuffers", opts = { desc = "nvim-tree: collapse, keep directories open", bar = true, }, command = function() require("nvim-tree.api").tree.collapse_all({ keep_buffers = true }) end, }, { name = "NvimTreeHiTest", opts = { desc = "nvim-tree: highlight test", }, command = function() require("nvim-tree.api").appearance.hi_test() end, }, } ---@return nvim_tree.api.commands.Command[] function M.get() return vim.deepcopy(CMDS) end function M.setup() for _, cmd in ipairs(CMDS) do local opts = vim.tbl_extend("force", cmd.opts, { force = true }) vim.api.nvim_create_user_command(cmd.name, cmd.command, opts) end end return M ================================================ FILE: lua/nvim-tree/config.lua ================================================ --This will be required after api, before setup. --This file should have minimal requires that are cheap and have no dependencies or are already required. local notify = require("nvim-tree.notify") local legacy = require("nvim-tree.legacy") local utils = require("nvim-tree.utils") -- short names like g are used rather than getters to keep code brief local M = { ---@type nvim_tree.config immutable default config d = {}, ---@type nvim_tree.config? global current config, nil until setup called g = nil, ---@type nvim_tree.config? raw user config, nil when no user config passed to setup u = nil, } M.d = { -- config-default-start on_attach = "default", hijack_cursor = false, auto_reload_on_write = true, disable_netrw = false, hijack_netrw = true, hijack_unnamed_buffer_when_opening = false, root_dirs = {}, prefer_startup_root = false, sync_root_with_cwd = false, reload_on_bufenter = false, respect_buf_cwd = false, select_prompts = false, sort = { sorter = "name", folders_first = true, files_first = false, }, view = { centralize_selection = false, cursorline = true, cursorlineopt = "both", debounce_delay = 15, side = "left", preserve_window_proportions = false, number = false, relativenumber = false, signcolumn = "yes", width = 30, float = { enable = false, quit_on_focus_loss = true, open_win_config = { relative = "editor", border = "rounded", width = 30, height = 30, row = 1, col = 1, }, }, }, renderer = { add_trailing = false, group_empty = false, full_name = false, root_folder_label = ":~:s?$?/..?", indent_width = 2, special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" }, hidden_display = "none", symlink_destination = true, decorators = { "Git", "Open", "Hidden", "Modified", "Bookmark", "Diagnostics", "Copied", "Cut", }, highlight_git = "none", highlight_diagnostics = "none", highlight_opened_files = "none", highlight_modified = "none", highlight_hidden = "none", highlight_bookmarks = "none", highlight_clipboard = "name", indent_markers = { enable = false, inline_arrows = true, icons = { corner = "└", edge = "│", item = "│", bottom = "─", none = " ", }, }, icons = { web_devicons = { file = { enable = true, color = true, }, folder = { enable = false, color = true, }, }, git_placement = "before", modified_placement = "after", hidden_placement = "after", diagnostics_placement = "signcolumn", bookmarks_placement = "signcolumn", padding = { icon = " ", folder_arrow = " ", }, symlink_arrow = " ➛ ", show = { file = true, folder = true, folder_arrow = true, git = true, modified = true, hidden = false, diagnostics = true, bookmarks = true, }, glyphs = { default = "", symlink = "", bookmark = "󰆤", modified = "●", hidden = "󰜌", folder = { arrow_closed = "", arrow_open = "", default = "", open = "", empty = "", empty_open = "", symlink = "", symlink_open = "", }, git = { unstaged = "✗", staged = "✓", unmerged = "", renamed = "➜", untracked = "★", deleted = "", ignored = "◌", }, }, }, }, hijack_directories = { enable = true, auto_open = true, }, update_focused_file = { enable = false, update_root = { enable = false, ignore_list = {}, }, exclude = false, }, system_open = { cmd = "", args = {}, }, git = { enable = true, show_on_dirs = true, show_on_open_dirs = true, disable_for_dirs = {}, timeout = 400, cygwin_support = false, }, diagnostics = { enable = false, show_on_dirs = false, show_on_open_dirs = true, debounce_delay = 500, severity = { min = vim.diagnostic.severity.HINT, max = vim.diagnostic.severity.ERROR, }, icons = { hint = "", info = "", warning = "", error = "", }, diagnostic_opts = false, }, modified = { enable = false, show_on_dirs = true, show_on_open_dirs = true, }, filters = { enable = true, git_ignored = true, dotfiles = false, git_clean = false, no_buffer = false, no_bookmark = false, custom = {}, exclude = {}, }, live_filter = { prefix = "[FILTER]: ", always_show_folders = true, }, filesystem_watchers = { enable = true, debounce_delay = 50, max_events = 0, ignore_dirs = { "/.ccls-cache", "/build", "/node_modules", "/target", "/.zig-cache", }, }, actions = { use_system_clipboard = true, change_dir = { enable = true, global = false, restrict_above_cwd = false, }, expand_all = { max_folder_discovery = 300, exclude = {}, }, file_popup = { open_win_config = { col = 1, row = 1, relative = "cursor", border = "shadow", style = "minimal", }, }, open_file = { quit_on_open = false, eject = true, resize_window = true, relative_path = true, window_picker = { enable = true, picker = "default", chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", exclude = { filetype = { "notify", "packer", "qf", "diff", "fugitive", "fugitiveblame" }, buftype = { "nofile", "terminal", "help" }, }, }, }, remove_file = { close_window = true, }, }, trash = { cmd = "gio trash", }, tab = { sync = { open = false, close = false, ignore = {}, }, }, notify = { threshold = vim.log.levels.INFO, absolute_path = true, }, help = { sort_by = "key", }, ui = { confirm = { remove = true, trash = true, default_yes = false, }, }, bookmarks = { persist = false, }, experimental = { }, log = { enable = false, truncate = false, types = { all = false, config = false, copy_paste = false, dev = false, diagnostics = false, git = false, profile = false, watcher = false, }, }, } -- config-default-end -- Immediately apply OS specific localisations to defaults if utils.is_macos or utils.is_windows then M.d.trash.cmd = "trash" end if utils.is_windows then M.d.filesystem_watchers.max_events = 1000 end local FIELD_SKIP_VALIDATE = { open_win_config = true, } local ACCEPTED_TYPES = { on_attach = { "function", "string" }, sort = { sorter = { "function", "string" }, }, view = { width = { "string", "function", "number", "table", min = { "string", "function", "number" }, max = { "string", "function", "number" }, lines_excluded = { "table" }, padding = { "function", "number" }, }, }, renderer = { hidden_display = { "function", "string" }, group_empty = { "boolean", "function" }, root_folder_label = { "function", "string", "boolean" }, }, update_focused_file = { exclude = { "function" }, }, git = { disable_for_dirs = { "function" }, }, filters = { custom = { "function" }, }, filesystem_watchers = { ignore_dirs = { "function" }, }, actions = { open_file = { window_picker = { picker = { "function", "string" }, }, }, }, bookmarks = { persist = { "boolean", "string" }, }, } local ACCEPTED_STRINGS = { sort = { sorter = { "name", "case_sensitive", "modification_time", "extension", "suffix", "filetype" }, }, view = { side = { "left", "right" }, signcolumn = { "yes", "no", "auto" }, }, renderer = { hidden_display = { "none", "simple", "all" }, highlight_git = { "none", "icon", "name", "all" }, highlight_opened_files = { "none", "icon", "name", "all" }, highlight_modified = { "none", "icon", "name", "all" }, highlight_hidden = { "none", "icon", "name", "all" }, highlight_bookmarks = { "none", "icon", "name", "all" }, highlight_diagnostics = { "none", "icon", "name", "all" }, highlight_clipboard = { "none", "icon", "name", "all" }, icons = { git_placement = { "before", "after", "signcolumn", "right_align" }, modified_placement = { "before", "after", "signcolumn", "right_align" }, hidden_placement = { "before", "after", "signcolumn", "right_align" }, diagnostics_placement = { "before", "after", "signcolumn", "right_align" }, bookmarks_placement = { "before", "after", "signcolumn", "right_align" }, }, }, help = { sort_by = { "key", "desc" }, }, } local ACCEPTED_ENUMS = { view = { width = { lines_excluded = { "root", }, }, }, } ---Validate types and values of the user supplied config. ---Warns and removes invalid in place. ---@param u nvim_tree.config local function validate_config(u) local msg ---@param user any ---@param def any ---@param strs table ---@param types table ---@param enums table ---@param prefix string local function validate(user, def, strs, types, enums, prefix) -- if user's option is not a table there is nothing to do if type(user) ~= "table" then return end -- we have hit a leaf enum to validate against - it's an integer indexed table local enum_value = type(enums) == "table" and next(enums) and type(next(enums)) == "number" -- only compare tables with contents that are not integer indexed nor enums if not enum_value and (type(def) ~= "table" or not next(def) or type(next(def)) == "number") then -- unless the field can be a table (and is not a table in default config) if vim.tbl_contains(types, "table") then -- use a dummy default to allow all checks def = {} else return end end for k, v in pairs(user) do if not FIELD_SKIP_VALIDATE[k] then local invalid if enum_value then if not vim.tbl_contains(enums, v) then invalid = string.format("Invalid value for field %s%s: Expected one of enum '%s', got '%s'", prefix, k, table.concat(enums, "'|'"), tostring(v)) end else if def[k] == nil and types[k] == nil then -- option does not exist invalid = string.format("Unknown option: %s%s", prefix, k) elseif type(v) ~= type(def[k]) then local expected if types[k] and #types[k] > 0 then if not vim.tbl_contains(types[k], type(v)) then expected = table.concat(types[k], "|") end else expected = type(def[k]) end if expected then -- option is of the wrong type invalid = string.format("Invalid option: %s%s. Expected %s, got %s", prefix, k, expected, type(v)) end elseif type(v) == "string" and strs[k] and not vim.tbl_contains(strs[k], v) then -- option has type `string` but value is not accepted invalid = string.format("Invalid value for field %s%s: '%s'", prefix, k, v) end end if invalid then if msg then msg = string.format("%s\n%s", msg, invalid) else msg = invalid end user[k] = nil elseif not enum_value then validate(v, def[k], strs[k] or {}, types[k] or {}, enums[k] or {}, prefix .. k .. ".") end end end end validate(u, M.d, ACCEPTED_STRINGS, ACCEPTED_TYPES, ACCEPTED_ENUMS, "") if msg then notify.warn(msg .. "\n\nsee :help nvim-tree-config for available configuration options") end end ---Validate user config and migrate legacy. ---Merge with M.d and persist as M.g ---When no user config M.g is set to M.d and M.u is set to nil ---@param u? nvim_tree.config user supplied subset of config function M.setup(u) if not u or type(u) ~= "table" then if u then notify.warn(string.format("invalid config type \"%s\" passed to setup, using defaults", type(u))) end M.g = vim.deepcopy(M.d) M.u = nil return end -- retain user for reference M.u = vim.deepcopy(u) legacy.migrate_config(u) validate_config(u) -- set global to the validated and populated user config M.g = vim.tbl_deep_extend("force", M.d, u) end ---Deep clone defaults ---@return nvim_tree.config function M.d_clone() return vim.deepcopy(M.d) end ---Deep clone user ---@return nvim_tree.config? nil when no config passed to setup function M.u_clone() return vim.deepcopy(M.u) end ---Deep clone global ---@return nvim_tree.config? nil when setup not called function M.g_clone() return vim.deepcopy(M.g) end return M ================================================ FILE: lua/nvim-tree/core.lua ================================================ local events = require("nvim-tree.events") local notify = require("nvim-tree.notify") local view = require("nvim-tree.view") local log = require("nvim-tree.log") local M = {} ---@type Explorer|nil local TreeExplorer = nil local first_init_done = false ---@param foldername string function M.init(foldername) local profile = log.profile_start("core init %s", foldername) if TreeExplorer then TreeExplorer:destroy() end local err, path if foldername then path, err = vim.loop.fs_realpath(foldername) else path, err = vim.loop.cwd() end if path then TreeExplorer = require("nvim-tree.explorer")({ path = path }) else notify.error(err) TreeExplorer = nil end if not first_init_done then events._dispatch_ready() first_init_done = true end log.profile_end(profile) end ---@return Explorer|nil function M.get_explorer() return TreeExplorer end function M.reset_explorer() TreeExplorer = nil end ---@return string|nil function M.get_cwd() return TreeExplorer and TreeExplorer.absolute_path end ---@return integer function M.get_nodes_starting_line() local offset = 1 if view.is_root_folder_visible(M.get_cwd()) then offset = offset + 1 end if TreeExplorer and TreeExplorer.live_filter.filter then return offset + 1 end return offset end return M ================================================ FILE: lua/nvim-tree/diagnostics.lua ================================================ local core = require("nvim-tree.core") local utils = require("nvim-tree.utils") local view = require("nvim-tree.view") local log = require("nvim-tree.log") local DirectoryNode = require("nvim-tree.node.directory") local M = {} ---COC severity level strings to LSP severity levels ---@enum COC_SEVERITY_LEVELS local COC_SEVERITY_LEVELS = { Error = 1, Warning = 2, Information = 3, Hint = 4, } ---Absolute Node path to LSP severity level ---@alias NodeSeverities table ---@class DiagStatus ---@field value lsp.DiagnosticSeverity|nil ---@field cache_version integer --- The buffer-severity mappings derived during the last diagnostic list update. ---@type NodeSeverities local NODE_SEVERITIES = {} ---The cache version number of the buffer-severity mappings. ---@type integer local NODE_SEVERITIES_VERSION = 0 ---@param path string ---@return string local function uniformize_path(path) return utils.canonical_path(path:gsub("\\", "/")) end ---Severity is within diagnostics.severity.min, diagnostics.severity.max ---@param severity lsp.DiagnosticSeverity ---@param config table ---@return boolean local function is_severity_in_range(severity, config) return config.max <= severity and severity <= config.min end ---Handle any COC exceptions, preventing any propagation ---@param err string local function handle_coc_exception(err) log.line("diagnostics", "handle_coc_exception: %s", vim.inspect(err)) local notify = true -- avoid distractions on interrupts (CTRL-C) if err:find("Vim:Interrupt") or err:find("Keyboard interrupt") then notify = false end if notify then require("nvim-tree.notify").error("Diagnostics update from coc.nvim failed. " .. vim.inspect(err)) end end ---COC service initialized ---@return boolean local function is_using_coc() return vim.g.coc_service_initialized == 1 end ---Marshal severities from COC. Does nothing when COC service not started. ---@return NodeSeverities local function from_coc() if not is_using_coc() then return {} end local ok, diagnostic_list = xpcall(function() return vim.fn.CocAction("diagnosticList") end, handle_coc_exception) if not ok or type(diagnostic_list) ~= "table" or vim.tbl_isempty(diagnostic_list) then return {} end local buffer_severity = {} for _, diagnostic in ipairs(diagnostic_list) do local bufname = uniformize_path(diagnostic.file) local coc_severity = COC_SEVERITY_LEVELS[diagnostic.severity] local highest_severity = buffer_severity[bufname] or coc_severity if is_severity_in_range(highest_severity, M.severity) then buffer_severity[bufname] = math.min(highest_severity, coc_severity) end end return buffer_severity end ---Maybe retrieve severity level from the cache ---@param node Node ---@return DiagStatus local function from_cache(node) local nodepath = uniformize_path(node.absolute_path) local max_severity = nil if not node:is(DirectoryNode) then -- direct cache hit for files max_severity = NODE_SEVERITIES[nodepath] else -- dirs should be searched in the list of cached buffer names by prefix for bufname, severity in pairs(NODE_SEVERITIES) do local node_contains_buf = vim.startswith(bufname, nodepath .. "/") if node_contains_buf then if not max_severity or severity < max_severity then max_severity = severity end end end end return { value = max_severity, cache_version = NODE_SEVERITIES_VERSION } end ---Fired on DiagnosticChanged for a single buffer. ---This will be called on set and reset of diagnostics. ---On disabling LSP, a reset event will be sent for all buffers. ---@param ev table standard event with data.diagnostics populated function M.update_lsp(ev) if not M.enable or not ev or not ev.data or not ev.data.diagnostics then return end local profile_event = log.profile_start("DiagnosticChanged event") local diagnostics = vim.diagnostic.get(ev.buf) -- use the buffer from the event, as ev.data.diagnostics will be empty on resolved diagnostics local bufname = uniformize_path(vim.api.nvim_buf_get_name(ev.buf)) ---@type vim.diagnostic.Severity? local new_severity = nil -- most severe (lowest) severity in user range for _, diagnostic in ipairs(diagnostics) do if diagnostic.severity >= M.severity.max and diagnostic.severity <= M.severity.min then if not new_severity or diagnostic.severity < new_severity then new_severity = diagnostic.severity end end end -- record delta and schedule a redraw if new_severity ~= NODE_SEVERITIES[bufname] then NODE_SEVERITIES[bufname] = new_severity NODE_SEVERITIES_VERSION = NODE_SEVERITIES_VERSION + 1 utils.debounce("DiagnosticChanged redraw", M.debounce_delay, function() local profile_redraw = log.profile_start("DiagnosticChanged redraw") local explorer = core.get_explorer() if explorer then explorer.renderer:draw() end log.profile_end(profile_redraw) end) end log.profile_end(profile_event) end ---Fired on CocDiagnosticChanged events: ---debounced retrieval, cache update, version increment and draw function M.update_coc() if not M.enable then return end utils.debounce("CocDiagnosticChanged update", M.debounce_delay, function() local profile = log.profile_start("CocDiagnosticChanged update") NODE_SEVERITIES = from_coc() NODE_SEVERITIES_VERSION = NODE_SEVERITIES_VERSION + 1 if log.enabled("diagnostics") then for bufname, severity in pairs(NODE_SEVERITIES) do log.line("diagnostics", "COC Indexing bufname '%s' with severity %d", bufname, severity) end end log.profile_end(profile) local bufnr = view.get_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 local explorer = core.get_explorer() if explorer then explorer.renderer:draw() end end end) end ---Maybe retrieve diagnostic status for a node. ---Returns cached value when node's version matches. ---@param node Node ---@return DiagStatus|nil function M.get_diag_status(node) if not M.enable then return nil end -- dir but we shouldn't show on dirs at all if node:is(DirectoryNode) and not M.show_on_dirs then return nil end -- here, we do a lazy update of the diagnostic status carried by the node. -- This is by design, as diagnostics and nodes live in completely separate -- worlds, and this module is the link between the two if not node.diag_status or node.diag_status.cache_version < NODE_SEVERITIES_VERSION then node.diag_status = from_cache(node) end local dir = node:as(DirectoryNode) -- file if not dir then return node.diag_status end -- dir is closed or we should show on open_dirs if not dir.open or M.show_on_open_dirs then return node.diag_status end return nil end function M.setup(opts) M.enable = opts.diagnostics.enable M.debounce_delay = opts.diagnostics.debounce_delay M.severity = opts.diagnostics.diagnostic_opts and { min = vim.diagnostic.severity.HINT, max = vim.diagnostic.severity.ERROR } or opts.diagnostics.severity if M.enable then log.line("diagnostics", "setup") end M.show_on_dirs = opts.diagnostics.show_on_dirs M.show_on_open_dirs = opts.diagnostics.show_on_open_dirs end return M ================================================ FILE: lua/nvim-tree/enum.lua ================================================ local M = {} ---Reason for filter in filter.lua ---@enum FILTER_REASON M.FILTER_REASON = { none = 0, -- It's not filtered git = 1, buf = 2, dotfile = 4, custom = 8, bookmark = 16, } return M ================================================ FILE: lua/nvim-tree/events.lua ================================================ local notify = require("nvim-tree.notify") local Event = require("nvim-tree._meta.api.events").Event local M = {} local global_handlers = {} ---@param event_name string ---@return table local function get_handlers(event_name) return global_handlers[event_name] or {} end ---@param event_name string ---@param handler function function M.subscribe(event_name, handler) local handlers = get_handlers(event_name) table.insert(handlers, handler) global_handlers[event_name] = handlers end ---@param event_name string ---@param payload table|nil local function dispatch(event_name, payload) for _, handler in pairs(get_handlers(event_name)) do local success, error = pcall(handler, payload) if not success then notify.error("Handler for event " .. event_name .. " errored. " .. vim.inspect(error)) end end end --@private function M._dispatch_ready() dispatch(Event.Ready) end --@private function M._dispatch_will_rename_node(old_name, new_name) dispatch(Event.WillRenameNode, { old_name = old_name, new_name = new_name }) end --@private function M._dispatch_node_renamed(old_name, new_name) dispatch(Event.NodeRenamed, { old_name = old_name, new_name = new_name }) end --@private function M._dispatch_will_remove_file(fname) dispatch(Event.WillRemoveFile, { fname = fname }) end --@private function M._dispatch_file_removed(fname) dispatch(Event.FileRemoved, { fname = fname }) end --@private function M._dispatch_will_create_file(fname) dispatch(Event.WillCreateFile, { fname = fname }) end --@private function M._dispatch_file_created(fname) dispatch(Event.FileCreated, { fname = fname }) end --@private function M._dispatch_folder_created(folder_name) dispatch(Event.FolderCreated, { folder_name = folder_name }) end --@private function M._dispatch_folder_removed(folder_name) dispatch(Event.FolderRemoved, { folder_name = folder_name }) end --@private function M._dispatch_on_tree_pre_open() dispatch(Event.TreePreOpen, nil) end --@private function M._dispatch_on_tree_open() dispatch(Event.TreeOpen, nil) end --@private function M._dispatch_on_tree_close() dispatch(Event.TreeClose, nil) end --@private function M._dispatch_on_tree_resize(size) dispatch(Event.Resize, size) end --@private function M._dispatch_tree_attached_post(buf) dispatch(Event.TreeAttachedPost, buf) end --@private function M._dispatch_on_tree_rendered(bufnr, winnr) dispatch(Event.TreeRendered, { bufnr = bufnr, winnr = winnr }) end return M ================================================ FILE: lua/nvim-tree/explorer/filters.lua ================================================ local utils = require("nvim-tree.utils") local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON local Class = require("nvim-tree.classic") ---@alias FilterType "custom" | "dotfiles" | "git_ignored" | "git_clean" | "no_buffer" | "no_bookmark" ---@class (exact) Filters: nvim_tree.Class ---@field enabled boolean ---@field state table ---@field private explorer Explorer ---@field private exclude_list string[] filters.exclude ---@field private ignore_list table filters.custom string table ---@field private custom_function (fun(absolute_path: string): boolean)|nil filters.custom function local Filters = Class:extend() ---@class Filters ---@overload fun(args: FiltersArgs): Filters ---@class (exact) FiltersArgs ---@field explorer Explorer ---@protected ---@param args FiltersArgs function Filters:new(args) self.explorer = args.explorer self.ignore_list = {} self.exclude_list = self.explorer.opts.filters.exclude self.custom_function = nil self.enabled = self.explorer.opts.filters.enable self.state = { custom = true, dotfiles = self.explorer.opts.filters.dotfiles, git_ignored = self.explorer.opts.filters.git_ignored, git_clean = self.explorer.opts.filters.git_clean, no_buffer = self.explorer.opts.filters.no_buffer, no_bookmark = self.explorer.opts.filters.no_bookmark, } local custom_filter = self.explorer.opts.filters.custom if type(custom_filter) == "function" then self.custom_function = custom_filter else if custom_filter and #custom_filter > 0 then for _, filter_name in pairs(custom_filter) do self.ignore_list[filter_name] = true end end end end ---@private ---@param path string ---@return boolean function Filters:is_excluded(path) for _, node in ipairs(self.exclude_list) do if path:match(node) then return true end end return false end ---Check if the given path is git clean/ignored ---@private ---@param path string Absolute path ---@param project nvim_tree.git.Project from prepare ---@return boolean function Filters:git(path, project) if type(project) ~= "table" or type(project.files) ~= "table" or type(project.dirs) ~= "table" then return false end -- default status to clean local xy = project.files[path] xy = xy or project.dirs.direct[path] and project.dirs.direct[path][1] xy = xy or project.dirs.indirect[path] and project.dirs.indirect[path][1] -- filter ignored; overrides clean as they are effectively dirty if self.state.git_ignored and xy == "!!" then return true end -- filter clean if self.state.git_clean and not xy then return true end return false end ---Check if the given path has no listed buffer ---@private ---@param path string Absolute path ---@param bufinfo table vim.fn.getbufinfo { buflisted = 1 } ---@return boolean function Filters:buf(path, bufinfo) if not self.state.no_buffer or type(bufinfo) ~= "table" then return false end -- filter files with no open buffer and directories containing no open buffers for _, b in ipairs(bufinfo) do if b.name == path or b.name:find(path .. "/", 1, true) then return false end end return true end ---@private ---@param path string ---@return boolean function Filters:dotfile(path) return self.state.dotfiles and utils.path_basename(path):sub(1, 1) == "." end ---Bookmark is present ---@private ---@param path string ---@param path_type string|nil filetype of path ---@param bookmarks table path to bookmarked Node ---@return boolean function Filters:bookmark(path, path_type, bookmarks) if not self.state.no_bookmark then return false end -- if bookmark is empty, we should see a empty filetree if next(bookmarks) == nil then return true end local DirectoryNode = require("nvim-tree.node.directory") local mark_parent = utils.path_add_trailing(path) for bookmark_path, bookmark_entry in pairs(bookmarks) do if path == bookmark_path then return false end if path_type == "directory" then -- check if path is mark's parent if vim.fn.stridx(bookmark_path, mark_parent) == 0 then return false end end ---@type DirectoryNode? local dir = bookmark_entry:as(DirectoryNode) if dir then -- check if mark is path's parent local path_parent = utils.path_add_trailing(bookmark_path) if vim.fn.stridx(path, path_parent) == 0 then return false end end end return true end ---@private ---@param path string ---@return boolean function Filters:custom(path) if not self.state.custom then return false end local basename = utils.path_basename(path) -- filter user's custom function if self.custom_function and self.custom_function(path) then return true end -- filter custom regexes local relpath = utils.path_relative(path, vim.loop.cwd()) for pat, _ in pairs(self.ignore_list) do if vim.fn.match(relpath, pat) ~= -1 or vim.fn.match(basename, pat) ~= -1 then return true end end local idx = path:match(".+()%.[^.]+$") if idx then if self.ignore_list["*" .. string.sub(path, idx)] == true then return true end end return false end ---Prepare arguments for should_filter. This is done prior to should_filter for efficiency reasons. ---@param project nvim_tree.git.Project? optional results of git.load_projects(...) ---@return table --- project: reference --- bufinfo: empty unless no_buffer set: vim.fn.getbufinfo { buflisted = 1 } --- bookmarks: absolute paths to boolean function Filters:prepare(project) local status = { project = project or {}, bufinfo = {}, bookmarks = {}, } if self.state.no_buffer then status.bufinfo = vim.fn.getbufinfo({ buflisted = 1 }) end local explorer = require("nvim-tree.core").get_explorer() if explorer then for _, node in ipairs(explorer.marks:list()) do status.bookmarks[node.absolute_path] = node end end return status end ---Check if the given path should be filtered. ---@param path string Absolute path ---@param fs_stat uv.fs_stat.result|nil fs_stat of file ---@param status table from prepare ---@return boolean function Filters:should_filter(path, fs_stat, status) if not self.enabled then return false end -- exclusions override all filters if self:is_excluded(path) then return false end return self:git(path, status.project) or self:buf(path, status.bufinfo) or self:dotfile(path) or self:custom(path) or self:bookmark(path, fs_stat and fs_stat.type, status.bookmarks) end --- Check if the given path should be filtered, and provide the reason why it was ---@param path string Absolute path ---@param fs_stat uv.fs_stat.result|nil fs_stat of file ---@param status table from prepare ---@return FILTER_REASON function Filters:should_filter_as_reason(path, fs_stat, status) if not self.enabled then return FILTER_REASON.none end if self:is_excluded(path) then return FILTER_REASON.none end if self:git(path, status.project) then return FILTER_REASON.git elseif self:buf(path, status.bufinfo) then return FILTER_REASON.buf elseif self:dotfile(path) then return FILTER_REASON.dotfile elseif self:custom(path) then return FILTER_REASON.custom elseif self:bookmark(path, fs_stat and fs_stat.type, status.bookmarks) then return FILTER_REASON.bookmark else return FILTER_REASON.none end end ---Toggle a type and refresh ---@param type FilterType? nil to disable all function Filters:toggle(type) if not type or self.state[type] == nil then self.enabled = not self.enabled else self.state[type] = not self.state[type] end local node = self.explorer:get_node_at_cursor() self.explorer:reload_explorer() if node then self.explorer:focus_node_or_parent(node) end end return Filters ================================================ FILE: lua/nvim-tree/explorer/init.lua ================================================ local appearance = require("nvim-tree.appearance") local buffers = require("nvim-tree.buffers") local core = require("nvim-tree.core") local git = require("nvim-tree.git") local log = require("nvim-tree.log") local utils = require("nvim-tree.utils") local view = require("nvim-tree.view") local node_factory = require("nvim-tree.node.factory") local DirectoryNode = require("nvim-tree.node.directory") local RootNode = require("nvim-tree.node.root") local Watcher = require("nvim-tree.watcher") local Iterator = require("nvim-tree.iterators.node-iterator") local NodeIterator = require("nvim-tree.iterators.node-iterator") local Filters = require("nvim-tree.explorer.filters") local Marks = require("nvim-tree.marks") local LiveFilter = require("nvim-tree.explorer.live-filter") local Sorter = require("nvim-tree.explorer.sorter") local Clipboard = require("nvim-tree.actions.fs.clipboard") local Renderer = require("nvim-tree.renderer") local FileNode = require("nvim-tree.node.file") local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON local find_file = require("nvim-tree.actions.finders.find-file") local config ---@class (exact) Explorer: RootNode ---@field uid_explorer number vim.loop.hrtime() at construction time ---@field opts table user options ---@field augroup_id integer ---@field current_tab integer ---@field renderer Renderer ---@field filters Filters ---@field live_filter LiveFilter ---@field sorters Sorter ---@field marks Marks ---@field clipboard Clipboard local Explorer = RootNode:extend() ---@class Explorer ---@overload fun(args: ExplorerArgs): Explorer ---@class (exact) ExplorerArgs ---@field path string ---@protected ---@param args ExplorerArgs function Explorer:new(args) Explorer.super.new(self, { explorer = self, absolute_path = args.path, name = "..", }) self.uid_explorer = vim.loop.hrtime() self.augroup_id = vim.api.nvim_create_augroup("NvimTree_Explorer_" .. self.uid_explorer, {}) self.open = true self.opts = config self.sorters = Sorter({ explorer = self }) self.renderer = Renderer({ explorer = self }) self.filters = Filters({ explorer = self }) self.live_filter = LiveFilter({ explorer = self }) self.marks = Marks({ explorer = self }) self.clipboard = Clipboard({ explorer = self }) self.current_tab = vim.api.nvim_get_current_tabpage() self:create_autocmds() self:_load(self) end function Explorer:destroy() log.line("dev", "Explorer:destroy") vim.api.nvim_del_augroup_by_id(self.augroup_id) RootNode.destroy(self) end function Explorer:create_autocmds() -- reset and draw (highlights) when colorscheme is changed vim.api.nvim_create_autocmd("ColorScheme", { group = self.augroup_id, callback = function() appearance.setup() view.reset_winhl() self.renderer:draw() end, }) vim.api.nvim_create_autocmd("BufWritePost", { group = self.augroup_id, callback = function() if self.opts.auto_reload_on_write and not self.opts.filesystem_watchers.enable then self:reload_explorer() end end, }) vim.api.nvim_create_autocmd("BufReadPost", { group = self.augroup_id, callback = function(data) -- 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() self:reload_explorer() end) elseif self.opts.renderer.highlight_opened_files ~= "none" then -- draw to update opened highlight self.renderer:draw() end end, }) -- update opened file buffers vim.api.nvim_create_autocmd("BufUnload", { group = self.augroup_id, callback = function(data) -- 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() self:reload_explorer() 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, }) vim.api.nvim_create_autocmd("BufEnter", { group = self.augroup_id, pattern = "NvimTree_*", callback = function() if utils.is_nvim_tree_buf(0) then if vim.fn.getcwd() ~= self.absolute_path or (self.opts.reload_on_bufenter and not self.opts.filesystem_watchers.enable) then self:reload_explorer() end end end, }) vim.api.nvim_create_autocmd("User", { group = self.augroup_id, pattern = { "FugitiveChanged", "NeogitStatusRefreshed" }, callback = function() if not self.opts.filesystem_watchers.enable and self.opts.git.enable then self:reload_git() end end, }) if self.opts.hijack_cursor then vim.api.nvim_create_autocmd("CursorMoved", { group = self.augroup_id, pattern = "NvimTree_*", callback = function() if utils.is_nvim_tree_buf(0) then self:place_cursor_on_node() end end, }) end if self.opts.modified.enable then vim.api.nvim_create_autocmd({ "BufModifiedSet", "BufWritePost" }, { group = self.augroup_id, callback = function() utils.debounce("Buf:modified_" .. self.uid_explorer, self.opts.view.debounce_delay, function() buffers.reload_modified() self.renderer:draw() end) end, }) end end ---@param node DirectoryNode function Explorer:expand_dir_node(node) self:_load(node) end ---@param node DirectoryNode ---@param project nvim_tree.git.Project? ---@return Node[]? function Explorer:reload(node, project) local cwd = node.link_to or node.absolute_path local handle = vim.loop.fs_scandir(cwd) if not handle then return end local profile = log.profile_start("reload %s", node.absolute_path) local filter_status = self.filters:prepare(project) if node.group_next then node.nodes = { node.group_next } node.group_next = nil end local remain_childs = {} local node_ignored = node:is_git_ignored() ---@type table local nodes_by_path = utils.key_by(node.nodes, "absolute_path") -- To reset we must 'zero' everything that we use node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, { git = 0, buf = 0, dotfile = 0, custom = 0, bookmark = 0, }) while true do local name, _ = vim.loop.fs_scandir_next(handle) if not name then break end local abs = utils.path_join({ cwd, name }) -- 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) if filter_reason == FILTER_REASON.none then remain_childs[abs] = true -- Recreate node if type changes. if nodes_by_path[abs] then local n = nodes_by_path[abs] if not stat or n.type ~= stat.type then utils.array_remove(node.nodes, n) n:destroy() nodes_by_path[abs] = nil end end if not nodes_by_path[abs] then local new_child = node_factory.create({ explorer = self, parent = node, absolute_path = abs, name = name, fs_stat = stat }) if new_child then table.insert(node.nodes, new_child) nodes_by_path[abs] = new_child end else local n = nodes_by_path[abs] if n then n.executable = utils.is_executable(abs) or false n.fs_stat = stat end end else for reason, value in pairs(FILTER_REASON) do if filter_reason == value then node.hidden_stats[reason] = node.hidden_stats[reason] + 1 end end end end node.nodes = vim.tbl_map( self:update_git_statuses(nodes_by_path, node_ignored, project), vim.tbl_filter(function(n) if remain_childs[n.absolute_path] then return remain_childs[n.absolute_path] else n:destroy() return false end end, node.nodes) ) local single_child = node:single_child_directory() if config.renderer.group_empty and node.parent and single_child then node.group_next = single_child local ns = self:reload(single_child, project) node.nodes = ns or {} log.profile_end(profile) return ns end self.sorters:sort(node.nodes) self.live_filter:apply_filter(node) log.profile_end(profile) return node.nodes end ---Refresh contents of all nodes to a path: actual directory and links. ---Groups will be expanded if needed. ---@param path string absolute path function Explorer:refresh_parent_nodes_for_path(path) local profile = log.profile_start("refresh_parent_nodes_for_path %s", path) -- collect parent nodes from the top down local parent_nodes = {} NodeIterator.builder({ self }) :recursor(function(node) return node.nodes end) :applier(function(node) local abs_contains = node.absolute_path and path:find(node.absolute_path, 1, true) == 1 local link_contains = node.link_to and path:find(node.link_to, 1, true) == 1 if abs_contains or link_contains then table.insert(parent_nodes, node) end end) :iterate() -- refresh in order; this will expand groups as needed for _, node in ipairs(parent_nodes) do local toplevel = git.get_toplevel(node.absolute_path) local project = git.get_project(toplevel) or {} self:reload(node, project) git.update_parent_projects(node, project, toplevel) end log.profile_end(profile) end ---@private ---@param node DirectoryNode function Explorer:_load(node) local cwd = node.link_to or node.absolute_path local project = git.load_project(cwd) self:explore(node, project, self) end ---@private ---@param nodes_by_path Node[] ---@param node_ignored boolean ---@param project nvim_tree.git.Project? ---@return fun(node: Node): Node function Explorer:update_git_statuses(nodes_by_path, node_ignored, project) return function(node) if nodes_by_path[node.absolute_path] then node:update_git_status(node_ignored, project) end return node end end ---@private ---@param handle uv.uv_fs_t ---@param cwd string ---@param node DirectoryNode ---@param project nvim_tree.git.Project ---@param parent Explorer function Explorer:populate_children(handle, cwd, node, project, parent) local node_ignored = node:is_git_ignored() local nodes_by_path = utils.bool_record(node.nodes, "absolute_path") local filter_status = parent.filters:prepare(project) node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, { git = 0, buf = 0, dotfile = 0, custom = 0, bookmark = 0, }) while true do local name, _ = vim.loop.fs_scandir_next(handle) if not name then break end local abs = utils.path_join({ cwd, name }) if Watcher.is_fs_event_capable(abs) then local profile = log.profile_start("populate_children %s", abs) -- path incorrectly specified as an integer 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) if filter_reason == FILTER_REASON.none and not nodes_by_path[abs] then local child = node_factory.create({ explorer = self, parent = node, absolute_path = abs, name = name, fs_stat = stat }) if child then table.insert(node.nodes, child) nodes_by_path[child.absolute_path] = true child:update_git_status(node_ignored, project) end elseif node.hidden_stats then for reason, value in pairs(FILTER_REASON) do if filter_reason == value and type(node.hidden_stats[reason]) == "number" then node.hidden_stats[reason] = node.hidden_stats[reason] + 1 end end end log.profile_end(profile) end end end ---@private ---@param node DirectoryNode ---@param project nvim_tree.git.Project ---@param parent Explorer ---@return Node[]|nil function Explorer:explore(node, project, parent) local cwd = node.link_to or node.absolute_path local handle = vim.loop.fs_scandir(cwd) if not handle then return end local profile = log.profile_start("explore %s", node.absolute_path) self:populate_children(handle, cwd, node, project, parent) local is_root = not node.parent local single_child = node:single_child_directory() if config.renderer.group_empty and not is_root and single_child then local child_cwd = single_child.link_to or single_child.absolute_path local child_project = git.load_project(child_cwd) node.group_next = single_child local ns = self:explore(single_child, child_project, parent) node.nodes = ns or {} log.profile_end(profile) return ns end parent.sorters:sort(node.nodes) parent.live_filter:apply_filter(node) log.profile_end(profile) return node.nodes end ---@private ---@param projects nvim_tree.git.Project[] function Explorer:refresh_nodes(projects) Iterator.builder({ self }) :applier(function(n) local dir = n:as(DirectoryNode) if dir then local toplevel = git.get_toplevel(dir.cwd or dir.link_to or dir.absolute_path) self:reload(dir, projects[toplevel] or {}) end end) :recursor(function(n) return n.group_next and { n.group_next } or (n.open and n.nodes) end) :iterate() end local event_running = false function Explorer:reload_explorer() if event_running or vim.v.exiting ~= vim.NIL then return end event_running = true local projects = git.reload_all_projects() self:refresh_nodes(projects) if view.is_visible() then self.renderer:draw() end event_running = false end function Explorer:reload_git() if not git.config.git.enable or event_running then return end event_running = true local projects = git.reload_all_projects() git.reload_node_status(self, projects) self.renderer:draw() event_running = false end ---Cursor position as per vim.api.nvim_win_get_cursor ---nil on no explorer or invalid view win ---@return integer[]|nil function Explorer:get_cursor_position() local winnr = view.get_winnr() if not winnr or not vim.api.nvim_win_is_valid(winnr) then return end return vim.api.nvim_win_get_cursor(winnr) end ---@return Node|nil function Explorer:get_node_at_cursor() local cursor = self:get_cursor_position() if not cursor then return end if cursor[1] == 1 and view.is_root_folder_visible(self.absolute_path) then return self end return self:get_nodes_by_line(core.get_nodes_starting_line())[cursor[1]] end function Explorer:place_cursor_on_node() local ok, search = pcall(vim.fn.searchcount) if ok and search and search.exact_match == 1 then return end local node = self:get_node_at_cursor() if not node or node.name == ".." then return end node = node:get_parent_of_group() or node local line = vim.api.nvim_get_current_line() local cursor = vim.api.nvim_win_get_cursor(0) local idx = vim.fn.stridx(line, node.name) if idx >= 0 then vim.api.nvim_win_set_cursor(0, { cursor[1], idx }) end end -- Find the line number of a node. ---@param node Node? ---@return integer -1 not found function Explorer:find_node_line(node) if not node then return -1 end local first_node_line = core.get_nodes_starting_line() local nodes_by_line = self:get_nodes_by_line(first_node_line) local iter_start, iter_end = first_node_line, #nodes_by_line for line = iter_start, iter_end, 1 do if nodes_by_line[line] == node then return line end end return -1 end -- get the node in the tree state depending on the absolute path of the node -- (grouped or hidden too) ---@param path string ---@return Node|nil ---@return number|nil function Explorer:get_node_from_path(path) if self.absolute_path == path then return self end return Iterator.builder(self.nodes) :hidden() :matcher(function(node) return node.absolute_path == path or node.link_to == path end) :recursor(function(node) if node.group_next then return { node.group_next } end if node.nodes then return node.nodes end end) :iterate() end ---Focus node passed as parameter if visible, otherwise focus first visible parent. ---If none of the parents is visible focus root. ---If node is nil do nothing. ---@param node Node? node to focus function Explorer:focus_node_or_parent(node) while node do local found_node, i = self:find_node(function(node_) return node_.absolute_path == node.absolute_path end) if found_node or node.parent == nil then view.set_cursor({ i + 1, 1 }) break end node = node.parent end end --- Get the node and index of the node from the tree that matches the predicate. --- The explored nodes are those displayed on the view. ---@param fn fun(node: Node): boolean ---@return table|nil ---@return number function Explorer:find_node(fn) local node, i = Iterator.builder(self.nodes) :matcher(fn) :recursor(function(node) return node.group_next and { node.group_next } or (node.open and #node.nodes > 0 and node.nodes) end) :iterate() i = view.is_root_folder_visible() and i or i - 1 if node and node.explorer.live_filter.filter then i = i + 1 end return node, i end ---Get all nodes in a line range (inclusive), for visual selection operations. ---@param start_line integer ---@param end_line integer ---@return Node[] function Explorer:get_nodes_in_range(start_line, end_line) local nodes_by_line = self:get_nodes_by_line(core.get_nodes_starting_line()) local nodes = {} for line = start_line, end_line do local node = nodes_by_line[line] if node and node.absolute_path then table.insert(nodes, node) end end return nodes end --- Return visible nodes indexed by line ---@param line_start number ---@return table function Explorer:get_nodes_by_line(line_start) local nodes_by_line = {} local line = line_start Iterator.builder(self.nodes) :applier(function(node) if node.group_next then return end nodes_by_line[line] = node line = line + 1 end) :recursor(function(node) return node.group_next and { node.group_next } or (node.open and #node.nodes > 0 and node.nodes) end) :iterate() return nodes_by_line end ---@param node Node function Explorer:dir_up(node) if not node or node.name == ".." then self:change_dir("..") else local cwd = self.absolute_path if cwd == nil then return end local newdir = vim.fn.fnamemodify(utils.path_remove_trailing(cwd), ":h") self:change_dir(newdir) find_file.fn(node.absolute_path) end end ---Api.tree.get_nodes ---@return nvim_tree.api.Node function Explorer:get_nodes() return self:clone() end ---Expand the directory node or the root ---@param node Node ---@param expand_opts? nvim_tree.api.node.expand.Opts function Explorer:expand_all(node, expand_opts) if node then node:expand(expand_opts) else self:expand(expand_opts) end end ---Expand the directory node or parent node ---@param node? Node ---@param expand_opts? nvim_tree.api.node.expand.Opts function Explorer:expand_node(node, expand_opts) if not node then return end node:expand(expand_opts) end ---@private ---@param new_tabpage integer ---@return boolean function Explorer:is_window_event(new_tabpage) local is_event_scope_window = vim.v.event.scope == "window" or vim.v.event.changed_window or false return is_event_scope_window and new_tabpage == self.current_tab end ---@private ---@param name string ---@return string|nil function Explorer:clean_input_cwd(name) name = vim.fn.fnameescape(name) local cwd = self.absolute_path if cwd == nil then return end local root_parent_cwd = vim.fn.fnamemodify(utils.path_remove_trailing(cwd), ":h") if name == ".." and root_parent_cwd then return vim.fn.expand(root_parent_cwd) else return vim.fn.expand(name) end end ---@private ---@param foldername string ---@return boolean function Explorer:prevent_cwd_change(foldername) local is_same_cwd = foldername == self.absolute_path local is_restricted_above = config.actions.change_dir.restrict_above_cwd and foldername < vim.fn.getcwd(-1, -1) return is_same_cwd or is_restricted_above end ---@private ---@return boolean function Explorer:should_change_dir() return config.actions.change_dir.enable and vim.tbl_isempty(vim.v.event) end ---@private ---@param global boolean ---@param path string function Explorer:cd(global, path) vim.cmd((global and "cd " or "lcd ") .. vim.fn.fnameescape(path)) end ---@private ---@param foldername string ---@param should_open_view boolean|nil ---@param should_init boolean|nil function Explorer:force_dirchange(foldername, should_open_view, should_init) local profile = log.profile_start("change dir %s", foldername) local valid_dir = vim.fn.isdirectory(foldername) == 1 -- prevent problems on non existing dirs if valid_dir then if self:should_change_dir() then self:cd(config.actions.change_dir.global, foldername) end if should_init ~= false then core.init(foldername) end end if should_open_view then require("nvim-tree.lib").open() else -- TODO #2255 -- The call to core.init destroyed this Explorer instance hence we need to fetch the new instance. -- Problem is described at https://github.com/nvim-tree/nvim-tree.lua/pull/3233#issuecomment-3704402527 local explorer = core.get_explorer() if explorer then explorer.renderer:draw() end end log.profile_end(profile) end ---@param input_cwd string ---@param with_open boolean|nil function Explorer:change_dir(input_cwd, with_open) local new_tabpage = vim.api.nvim_get_current_tabpage() if self:is_window_event(new_tabpage) then return end local foldername = self:clean_input_cwd(input_cwd) if foldername == nil or self:prevent_cwd_change(foldername) then return end self.current_tab = new_tabpage self:force_dirchange(foldername, with_open) end function Explorer:change_dir_to_node(node) if node.name == ".." or node:is(RootNode) then self:change_dir("..") elseif node:is(FileNode) and node.parent ~= nil then self:change_dir(node.parent:last_group_node().absolute_path) else node = node:as(DirectoryNode) if node then self:change_dir(node:last_group_node().absolute_path) end end end function Explorer:setup(opts) config = opts end return Explorer ================================================ FILE: lua/nvim-tree/explorer/live-filter.lua ================================================ local view = require("nvim-tree.view") local utils = require("nvim-tree.utils") local Class = require("nvim-tree.classic") local Iterator = require("nvim-tree.iterators.node-iterator") local DirectoryNode = require("nvim-tree.node.directory") ---@class (exact) LiveFilter: nvim_tree.Class ---@field explorer Explorer ---@field prefix string ---@field always_show_folders boolean ---@field filter string local LiveFilter = Class:extend() ---@class LiveFilter ---@overload fun(args: LiveFilterArgs): LiveFilter ---@class (exact) LiveFilterArgs ---@field explorer Explorer ---@protected ---@param args LiveFilterArgs function LiveFilter:new(args) self.explorer = args.explorer self.prefix = self.explorer.opts.live_filter.prefix self.always_show_folders = self.explorer.opts.live_filter.always_show_folders self.filter = nil end ---@param node_ Node? local function reset_filter(self, node_) node_ = node_ or self.explorer if node_ == nil then return end local dir_ = node_:as(DirectoryNode) if dir_ then dir_.hidden_stats = vim.tbl_deep_extend("force", dir_.hidden_stats or {}, { live_filter = 0, }) end Iterator.builder(node_.nodes) :hidden() :applier(function(node) node.hidden = false local dir = node:as(DirectoryNode) if dir then dir.hidden_stats = vim.tbl_deep_extend("force", dir.hidden_stats or {}, { live_filter = 0, }) end end) :iterate() end local overlay_bufnr = 0 local overlay_winnr = 0 local function remove_overlay(self) if view.View.float.enable and view.View.float.quit_on_focus_loss then -- return to normal nvim-tree float behaviour when filter window is closed vim.api.nvim_create_autocmd("WinLeave", { pattern = "NvimTree_*", group = vim.api.nvim_create_augroup("NvimTree", { clear = false }), callback = function() if utils.is_nvim_tree_buf(0) then view.close() end end, }) end vim.api.nvim_win_close(overlay_winnr, true) vim.api.nvim_buf_delete(overlay_bufnr, { force = true }) overlay_bufnr = 0 overlay_winnr = 0 if self.filter == "" then self:clear_filter() end end ---@param node Node ---@return boolean local function matches(self, node) if not self.explorer.filters.enabled then return true end local path = node.absolute_path local name = vim.fn.fnamemodify(path, ":t") return vim.regex(self.filter):match_str(name) ~= nil end ---@param node_ DirectoryNode? function LiveFilter:apply_filter(node_) if not self.filter or self.filter == "" then reset_filter(self, node_) return end -- this iterator cannot yet be refactored with the Iterator module -- since the node mapper is based on its children local function iterate(node) local filtered_nodes = 0 local nodes = node.group_next and { node.group_next } or node.nodes node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, { live_filter = 0, }) if nodes then for _, n in pairs(nodes) do iterate(n) if n.hidden then filtered_nodes = filtered_nodes + 1 end end end node.hidden_stats.live_filter = filtered_nodes local has_nodes = nodes and (self.always_show_folders or #nodes > filtered_nodes) local ok, is_match = pcall(matches, self, node) node.hidden = not (has_nodes or (ok and is_match)) end iterate(node_ or self.explorer) end local function record_char(self) vim.schedule(function() self.filter = vim.api.nvim_buf_get_lines(overlay_bufnr, 0, -1, false)[1] self:apply_filter() self.explorer.renderer:draw() end) end local function configure_buffer_overlay(self) overlay_bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_attach(overlay_bufnr, true, { on_lines = function() return record_char(self) end, }) vim.api.nvim_create_autocmd("InsertLeave", { callback = function() return remove_overlay(self) end, once = true, }) vim.api.nvim_buf_set_keymap(overlay_bufnr, "i", "", "stopinsert", {}) end ---@return integer local function calculate_overlay_win_width(self) local wininfo = vim.fn.getwininfo(view.get_winnr())[1] if wininfo then return wininfo.width - wininfo.textoff - #self.prefix end return 20 end local function create_overlay(self) if view.View.float.enable then -- don't close nvim-tree float when focus is changed to filter window vim.api.nvim_clear_autocmds({ event = "WinLeave", pattern = "NvimTree_*", group = vim.api.nvim_create_augroup("NvimTree", { clear = false }), }) end configure_buffer_overlay(self) overlay_winnr = vim.api.nvim_open_win(overlay_bufnr, true, { col = 1, row = 0, relative = "cursor", width = calculate_overlay_win_width(self), height = 1, border = "none", style = "minimal", }) if vim.fn.has("nvim-0.10") == 1 then vim.api.nvim_set_option_value("modifiable", true, { buf = overlay_bufnr }) vim.api.nvim_set_option_value("filetype", "NvimTreeFilter", { buf = overlay_bufnr }) else vim.api.nvim_buf_set_option(overlay_bufnr, "modifiable", true) ---@diagnostic disable-line: deprecated vim.api.nvim_buf_set_option(overlay_bufnr, "filetype", "NvimTreeFilter") ---@diagnostic disable-line: deprecated end vim.api.nvim_buf_set_lines(overlay_bufnr, 0, -1, false, { self.filter }) vim.cmd("startinsert") vim.api.nvim_win_set_cursor(overlay_winnr, { 1, #self.filter + 1 }) end function LiveFilter:start_filtering() view.View.live_filter.prev_focused_node = self.explorer:get_node_at_cursor() self.filter = self.filter or "" self.explorer.renderer:draw() local row = require("nvim-tree.core").get_nodes_starting_line() - 1 local col = #self.prefix > 0 and #self.prefix - 1 or 1 view.set_cursor({ row, col }) -- needs scheduling to let the cursor move before initializing the window vim.schedule(function() return create_overlay(self) end) end function LiveFilter:clear_filter() local node = self.explorer:get_node_at_cursor() local last_node = view.View.live_filter.prev_focused_node self.filter = nil reset_filter(self) self.explorer.renderer:draw() if node then self.explorer:focus_node_or_parent(node) elseif last_node then self.explorer:focus_node_or_parent(last_node) end end return LiveFilter ================================================ FILE: lua/nvim-tree/explorer/sorter.lua ================================================ local Class = require("nvim-tree.classic") local DirectoryNode = require("nvim-tree.node.directory") ---@alias SorterType "name" | "case_sensitive" | "modification_time" | "extension" | "suffix" | "filetype" ---@alias SorterComparator fun(self: Sorter, a: Node, b: Node): boolean? ---@alias SorterUser fun(nodes: Node[]): SorterType? ---@class (exact) Sorter: nvim_tree.Class ---@field private explorer Explorer local Sorter = Class:extend() ---@class Sorter ---@overload fun(args: SorterArgs): Sorter ---@class (exact) SorterArgs ---@field explorer Explorer ---@protected ---@param args SorterArgs function Sorter:new(args) self.explorer = args.explorer end ---Create a shallow copy of a portion of a list. ---@param t table ---@param first integer First index, inclusive ---@param last integer Last index, inclusive ---@return table local function tbl_slice(t, first, last) local slice = {} for i = first, last or #t, 1 do table.insert(slice, t[i]) end return slice end ---Evaluate folders_first and sort.files_first returning nil when no order is necessary ---@private ---@type SorterComparator function Sorter:folders_or_files_first(a, b) if not (self.explorer.opts.sort.folders_first or self.explorer.opts.sort.files_first) then return nil end if not a:is(DirectoryNode) and b:is(DirectoryNode) then -- file <> folder return self.explorer.opts.sort.files_first elseif a:is(DirectoryNode) and not b:is(DirectoryNode) then -- folder <> file return not self.explorer.opts.sort.files_first end return nil end ---@private ---@param t Node[] ---@param first number ---@param mid number ---@param last number ---@param comparator SorterComparator function Sorter:merge(t, first, mid, last, comparator) local n1 = mid - first + 1 local n2 = last - mid local ls = tbl_slice(t, first, mid) local rs = tbl_slice(t, mid + 1, last) local i = 1 local j = 1 local k = first while i <= n1 and j <= n2 do if comparator(self, ls[i], rs[j]) then t[k] = ls[i] i = i + 1 else t[k] = rs[j] j = j + 1 end k = k + 1 end while i <= n1 do t[k] = ls[i] i = i + 1 k = k + 1 end while j <= n2 do t[k] = rs[j] j = j + 1 k = k + 1 end end ---@private ---@param t Node[] ---@param first number ---@param last number ---@param comparator SorterComparator function Sorter:split_merge(t, first, last, comparator) if (last - first) < 1 then return end local mid = math.floor((first + last) / 2) self:split_merge(t, first, mid, comparator) self:split_merge(t, mid + 1, last, comparator) self:merge(t, first, mid, last, comparator) end ---Perform a merge sort using sorter option. ---@param t Node[] function Sorter:sort(t) if self[self.explorer.opts.sort.sorter] then self:split_merge(t, 1, #t, self[self.explorer.opts.sort.sorter]) elseif type(self.explorer.opts.sort.sorter) == "function" then local t_user = {} local origin_index = {} for _, n in ipairs(t) do table.insert(t_user, { absolute_path = n.absolute_path, executable = n.executable, extension = n.extension, filetype = vim.filetype.match({ filename = n.name }), link_to = n.link_to, name = n.name, type = n.type, }) table.insert(origin_index, n) end -- user may return a SorterType local ret = self.explorer.opts.sort.sorter(t_user) if self[ret] then self:split_merge(t, 1, #t, self[ret]) return end -- do merge sort for prevent memory exceed local user_index = {} for i, v in ipairs(t_user) do if type(v.absolute_path) == "string" and user_index[v.absolute_path] == nil then user_index[v.absolute_path] = i end end -- if missing value found, then using origin_index local mini_comparator = function(_, a, b) local a_index = user_index[a.absolute_path] or origin_index[a.absolute_path] local b_index = user_index[b.absolute_path] or origin_index[b.absolute_path] if type(a_index) == "number" and type(b_index) == "number" then return a_index <= b_index end return (a_index or 0) <= (b_index or 0) end self:split_merge(t, 1, #t, mini_comparator) -- sort by user order end end ---@private ---@param a Node ---@param b Node ---@param ignore_case boolean ---@return boolean function Sorter:name_case(a, b, ignore_case) if not (a and b) then return true end local early_return = self:folders_or_files_first(a, b) if early_return ~= nil then return early_return end if ignore_case then return a.name:lower() <= b.name:lower() else return a.name <= b.name end end ---@private ---@type SorterComparator function Sorter:case_sensitive(a, b) return self:name_case(a, b, false) end ---@private ---@type SorterComparator function Sorter:name(a, b) return self:name_case(a, b, true) end ---@private ---@type SorterComparator function Sorter:modification_time(a, b) if not (a and b) then return true end local early_return = self:folders_or_files_first(a, b) if early_return ~= nil then return early_return end local last_modified_a = 0 local last_modified_b = 0 if a.fs_stat ~= nil then last_modified_a = a.fs_stat.mtime.sec end if b.fs_stat ~= nil then last_modified_b = b.fs_stat.mtime.sec end return last_modified_b <= last_modified_a end ---@private ---@type SorterComparator function Sorter:suffix(a, b) if not (a and b) then return true end -- directories go first local early_return = self:folders_or_files_first(a, b) if early_return ~= nil then return early_return elseif a.nodes and b.nodes then return self:name(a, b) end -- dotfiles go second if a.name:sub(1, 1) == "." and b.name:sub(1, 1) ~= "." then return true elseif a.name:sub(1, 1) ~= "." and b.name:sub(1, 1) == "." then return false elseif a.name:sub(1, 1) == "." and b.name:sub(1, 1) == "." then return self:name(a, b) end -- unsuffixed go third local a_suffix_ndx = a.name:find("%.%w+$") local b_suffix_ndx = b.name:find("%.%w+$") if not a_suffix_ndx and b_suffix_ndx then return true elseif a_suffix_ndx and not b_suffix_ndx then return false elseif not (a_suffix_ndx and b_suffix_ndx) then return self:name(a, b) end -- finally, compare by suffixes local a_suffix = a.name:sub(a_suffix_ndx) local b_suffix = b.name:sub(b_suffix_ndx) if a_suffix and not b_suffix then return true elseif not a_suffix and b_suffix then return false elseif a_suffix:lower() == b_suffix:lower() then return self:name(a, b) end return a_suffix:lower() < b_suffix:lower() end ---@private ---@type SorterComparator function Sorter:extension(a, b) if not (a and b) then return true end local early_return = self:folders_or_files_first(a, b) if early_return ~= nil then return early_return end if a.extension and not b.extension then return true elseif not a.extension and b.extension then return false end local a_ext = (a.extension or ""):lower() local b_ext = (b.extension or ""):lower() if a_ext == b_ext then return self:name(a, b) end return a_ext < b_ext end ---@private ---@type SorterComparator function Sorter:filetype(a, b) local a_ft = vim.filetype.match({ filename = a.name }) local b_ft = vim.filetype.match({ filename = b.name }) -- directories first local early_return = self:folders_or_files_first(a, b) if early_return ~= nil then return early_return end -- one is nil, the other wins if a_ft and not b_ft then return true elseif not a_ft and b_ft then return false end -- same filetype or both nil, sort by name if a_ft == b_ft then return self:name(a, b) end return a_ft < b_ft end return Sorter ================================================ FILE: lua/nvim-tree/explorer/watch.lua ================================================ local log = require("nvim-tree.log") local git = require("nvim-tree.git") local utils = require("nvim-tree.utils") local notify = require("nvim-tree.notify") local Watcher = require("nvim-tree.watcher").Watcher local M = { config = {}, uid = 0, } ---@param path string ---@return boolean local function is_git(path) -- If $GIT_DIR is set, consider its value to be equivalent to '.git'. -- Expand $GIT_DIR (and `path`) to a full path (see :help filename-modifiers), since -- it's possible to set it to a relative path. We want to make our best -- effort to expand that to a valid absolute path. if vim.fn.fnamemodify(path, ":p") == vim.fn.fnamemodify(vim.env.GIT_DIR, ":p") then return true elseif vim.fn.fnamemodify(path, ":t") == ".git" then return true else return false end end local IGNORED_PATHS = { -- disable watchers on kernel filesystems -- which have a lot of unwanted events "/sys", "/proc", "/dev", } ---@param path string ---@return boolean local function is_folder_ignored(path) for _, folder in ipairs(IGNORED_PATHS) do if vim.startswith(path, folder) then return true end end if type(M.config.filesystem_watchers.ignore_dirs) == "table" then for _, ignore_dir in ipairs(M.config.filesystem_watchers.ignore_dirs) do if utils.is_windows then ignore_dir = ignore_dir:gsub("/", "\\\\") or ignore_dir end if vim.fn.match(path, ignore_dir) ~= -1 then return true end end elseif type(M.config.filesystem_watchers.ignore_dirs) == "function" then return M.config.filesystem_watchers.ignore_dirs(path) end return false end ---@param node DirectoryNode ---@return Watcher|nil function M.create_watcher(node) if not M.config.filesystem_watchers.enable or type(node) ~= "table" then return nil end local path = node.link_to or node.absolute_path if is_git(path) or is_folder_ignored(path) then return nil end ---@param watcher Watcher local function callback(watcher) log.line("watcher", "node event scheduled refresh %s", watcher.data.context) -- event is awaiting debouncing and handling watcher.data.outstanding_events = watcher.data.outstanding_events + 1 -- disable watcher when outstanding exceeds max if M.config.filesystem_watchers.max_events > 0 and watcher.data.outstanding_events > M.config.filesystem_watchers.max_events then notify.error(string.format( "Observed %d consecutive file system events with interval < %dms, exceeding filesystem_watchers.max_events=%s. Disabling watcher for directory '%s'. Consider adding this directory to filesystem_watchers.ignore_dirs", watcher.data.outstanding_events, M.config.filesystem_watchers.debounce_delay, M.config.filesystem_watchers.max_events, node.absolute_path )) node:destroy_watcher() end utils.debounce(watcher.data.context, M.config.filesystem_watchers.debounce_delay, function() if watcher.destroyed then return end -- event has been handled watcher.data.outstanding_events = 0 if node.link_to then log.line("watcher", "node event executing refresh '%s' -> '%s'", node.link_to, node.absolute_path) else log.line("watcher", "node event executing refresh '%s'", node.absolute_path) end git.refresh_dir(node) end) end M.uid = M.uid + 1 return Watcher:create({ path = path, callback = callback, data = { context = "explorer:watch:" .. path .. ":" .. M.uid, outstanding_events = 0, -- unprocessed events that have not been debounced } }) end function M.setup(opts) M.config.filesystem_watchers = opts.filesystem_watchers M.uid = 0 end return M ================================================ FILE: lua/nvim-tree/git/init.lua ================================================ local log = require("nvim-tree.log") local utils = require("nvim-tree.utils") local git_utils = require("nvim-tree.git.utils") local GitRunner = require("nvim-tree.git.runner") local Watcher = require("nvim-tree.watcher").Watcher local Iterator = require("nvim-tree.iterators.node-iterator") local DirectoryNode = require("nvim-tree.node.directory") -- Git short-format status ---@alias nvim_tree.git.PathXY table -- Git short-format statuses ---@alias nvim_tree.git.PathXYs table ---Git state for an entire repo ---@class (exact) nvim_tree.git.Project ---@field files nvim_tree.git.ProjectFiles? ---@field dirs nvim_tree.git.ProjectDirs? ---@field watcher Watcher? ---@alias nvim_tree.git.ProjectFiles nvim_tree.git.PathXY ---@alias nvim_tree.git.ProjectDirs table<"direct" | "indirect", nvim_tree.git.PathXYs> local M = { config = {}, ---all projects keyed by toplevel ---@type table _projects_by_toplevel = {}, ---index of paths inside toplevels, false when not inside a project ---@type table _toplevels_by_path = {}, -- git dirs by toplevel ---@type table _git_dirs_by_toplevel = {}, } -- Files under .git that should result in a reload when changed. -- Utilities (like watchman) can also write to this directory (often) and aren't useful for us. local WATCHED_FILES = { "FETCH_HEAD", -- remote ref "HEAD", -- local ref "HEAD.lock", -- HEAD will not always be updated e.g. revert "config", -- user config "index", -- staging area } ---@param toplevel string|nil ---@param path string|nil ---@param project nvim_tree.git.Project ---@param project_files nvim_tree.git.ProjectFiles? local function reload_git_project(toplevel, path, project, project_files) if path then for p in pairs(project.files) do if p:find(path, 1, true) == 1 then project.files[p] = nil end end project.files = vim.tbl_deep_extend("force", project.files, project_files) else project.files = project_files or {} end project.dirs = git_utils.project_files_to_project_dirs(project.files, toplevel) end --- Is this path in a known ignored directory? ---@param path string ---@param project nvim_tree.git.Project ---@return boolean local function path_ignored_in_project(path, project) if not path or not project then return false end if project.files then for p, xy in pairs(project.files) do if xy == "!!" and vim.startswith(path, p) then return true end end end return false end ---@return nvim_tree.git.Project[] maybe empty function M.reload_all_projects() if not M.config.git.enable then return {} end for toplevel in pairs(M._projects_by_toplevel) do M.reload_project(toplevel) end return M._projects_by_toplevel end --- Reload one project. Does nothing when no project or path is ignored ---@param toplevel string? ---@param path string? optional path to update only ---@param callback function? function M.reload_project(toplevel, path, callback) local project = M._projects_by_toplevel[toplevel] --[[@as nvim_tree.git.Project]] if not toplevel or not project or not M.config.git.enable then if callback then callback() end return end if path and (path:find(toplevel, 1, true) ~= 1 or path_ignored_in_project(path, project)) then if callback then callback() end return end ---@type GitRunnerArgs local args = { toplevel = toplevel, path = path, list_untracked = git_utils.should_show_untracked(toplevel), list_ignored = true, timeout = M.config.git.timeout, } if callback then ---@param path_xy nvim_tree.git.PathXY args.callback = function(path_xy) reload_git_project(toplevel, path, project, path_xy) callback() end GitRunner:run(args) else -- TODO #1974 use callback once async/await is available reload_git_project(toplevel, path, project, GitRunner:run(args)) end end --- Retrieve a known project ---@param toplevel string? ---@return nvim_tree.git.Project? project function M.get_project(toplevel) return M._projects_by_toplevel[toplevel] end --- Retrieve the toplevel for a path. nil on: --- git disabled --- not part of a project --- not a directory --- path in git.disable_for_dirs ---@param path string absolute ---@return string|nil function M.get_toplevel(path) if not path then return nil end if not M.config.git.enable then return nil end local tl = M._toplevels_by_path[path] if tl then return tl elseif tl == false then return nil end local stat, _ = vim.loop.fs_stat(path) if not stat or stat.type ~= "directory" then return nil end -- short-circuit any known ignored paths for root, project in pairs(M._projects_by_toplevel) do if project and path_ignored_in_project(path, project) then M._toplevels_by_path[path] = root return root end end -- attempt to fetch toplevel, cache if untracked local toplevel, git_dir = git_utils.get_toplevel(path) if not toplevel or not git_dir then M._toplevels_by_path[path] = false return nil end local toplevel_norm = vim.fn.fnamemodify(toplevel, ":p") -- ignore disabled paths if type(M.config.git.disable_for_dirs) == "table" then for _, disabled_for_dir in ipairs(M.config.git.disable_for_dirs) do local disabled_norm = vim.fn.fnamemodify(disabled_for_dir, ":p") if toplevel_norm == disabled_norm then return nil end end elseif type(M.config.git.disable_for_dirs) == "function" then if M.config.git.disable_for_dirs(toplevel_norm) then return nil end end M._toplevels_by_path[path] = toplevel M._git_dirs_by_toplevel[toplevel] = git_dir toplevel = M._toplevels_by_path[path] if toplevel == false then return nil else return toplevel end end local function reload_tree_at(toplevel) if not M.config.git.enable or not toplevel then return nil end log.line("watcher", "git event executing '%s'", toplevel) local explorer = require("nvim-tree.core").get_explorer() if not explorer then return nil end local root_node = explorer:get_node_from_path(toplevel) if not root_node then return end M.reload_project(toplevel, nil, function() local project = M.get_project(toplevel) Iterator.builder(root_node.nodes) :hidden() :applier(function(node) local parent_ignored = node.parent and node.parent:is_git_ignored() or false node:update_git_status(parent_ignored, project) end) :recursor(function(node) return node.nodes and #node.nodes > 0 and node.nodes end) :iterate() explorer.renderer:draw() end) end --- Load the project status for a path. Does nothing when no toplevel for path. --- Only fetches project status when unknown, otherwise returns existing. ---@param path string absolute ---@return nvim_tree.git.Project maybe empty function M.load_project(path) if not M.config.git.enable then return {} end local toplevel = M.get_toplevel(path) if not toplevel then M._toplevels_by_path[path] = false return {} end local project = M._projects_by_toplevel[toplevel] if project then return project end local path_xys = GitRunner:run({ toplevel = toplevel, list_untracked = git_utils.should_show_untracked(toplevel), list_ignored = true, timeout = M.config.git.timeout, }) local watcher = nil if M.config.filesystem_watchers.enable then log.line("watcher", "git start") ---@param w Watcher local callback = function(w) log.line("watcher", "git event scheduled '%s'", w.data.toplevel) utils.debounce("git:watcher:" .. w.data.toplevel, M.config.filesystem_watchers.debounce_delay, function() if w.destroyed then return end reload_tree_at(w.data.toplevel) end) end local git_dir = vim.env.GIT_DIR or M._git_dirs_by_toplevel[toplevel] or utils.path_join({ toplevel, ".git" }) watcher = Watcher:create({ path = git_dir, files = WATCHED_FILES, callback = callback, data = { toplevel = toplevel, } }) end if path_xys then M._projects_by_toplevel[toplevel] = { files = path_xys, dirs = git_utils.project_files_to_project_dirs(path_xys, toplevel), watcher = watcher, } return M._projects_by_toplevel[toplevel] else M._toplevels_by_path[path] = false return {} end end ---@param dir DirectoryNode ---@param project nvim_tree.git.Project? ---@param root string? function M.update_parent_projects(dir, project, root) while project and dir do -- step up to the containing project if dir.absolute_path == root then -- stop at the top of the tree if not dir.parent then break end root = M.get_toplevel(dir.parent.absolute_path) -- stop when no more projects if not root then break end -- update the containing project project = M.get_project(root) M.reload_project(root, dir.absolute_path, nil) end -- update status dir:update_git_status(dir.parent and dir.parent:is_git_ignored() or false, project) -- maybe parent dir = dir.parent end end ---Refresh contents and git status for a single directory ---@param dir DirectoryNode function M.refresh_dir(dir) local node = dir:get_parent_of_group() or dir local toplevel = M.get_toplevel(dir.absolute_path) M.reload_project(toplevel, dir.absolute_path, function() local project = M.get_project(toplevel) or {} dir.explorer:reload(node, project) M.update_parent_projects(dir, project, toplevel) dir.explorer.renderer:draw() end) end ---@param dir DirectoryNode? ---@param projects nvim_tree.git.Project[] function M.reload_node_status(dir, projects) dir = dir and dir:as(DirectoryNode) if not dir or #dir.nodes == 0 then return end local toplevel = M.get_toplevel(dir.absolute_path) local project = projects[toplevel] or {} for _, node in ipairs(dir.nodes) do node:update_git_status(dir:is_git_ignored(), project) M.reload_node_status(node:as(DirectoryNode), projects) end end function M.purge_state() log.line("git", "purge_state") for _, project in pairs(M._projects_by_toplevel) do if project.watcher then project.watcher:destroy() end end M._projects_by_toplevel = {} M._toplevels_by_path = {} M._git_dirs_by_toplevel = {} end --- Disable git integration permanently function M.disable_git_integration() log.line("git", "disabling git integration") M.purge_state() M.config.git.enable = false end function M.setup(opts) M.config.git = opts.git M.config.filesystem_watchers = opts.filesystem_watchers end return M ================================================ FILE: lua/nvim-tree/git/runner.lua ================================================ local log = require("nvim-tree.log") local utils = require("nvim-tree.utils") local notify = require("nvim-tree.notify") local Class = require("nvim-tree.classic") ---@class (exact) GitRunner: nvim_tree.Class ---@field private toplevel string absolute path ---@field private path string? absolute path ---@field private list_untracked boolean ---@field private list_ignored boolean ---@field private timeout integer ---@field private callback fun(path_xy: nvim_tree.git.PathXY)? ---@field private path_xy nvim_tree.git.PathXY ---@field private rc integer? -- -1 indicates timeout local GitRunner = Class:extend() ---@class GitRunner ---@overload fun(args: GitRunnerArgs): GitRunner ---@class (exact) GitRunnerArgs ---@field toplevel string absolute path ---@field path string? absolute path ---@field list_untracked boolean ---@field list_ignored boolean ---@field timeout integer ---@field callback fun(path_xy: nvim_tree.git.PathXY)? local timeouts = 0 local MAX_TIMEOUTS = 5 ---@protected ---@param args GitRunnerArgs function GitRunner:new(args) self.toplevel = args.toplevel self.path = args.path self.list_untracked = args.list_untracked self.list_ignored = args.list_ignored self.timeout = args.timeout self.callback = args.callback self.path_xy = {} self.rc = nil end ---@private ---@param status string ---@param path string|nil function GitRunner:parse_status_output(status, path) if not path then return end -- replacing slashes if on windows if vim.fn.has("win32") == 1 then path = path:gsub("/", "\\") end if #status > 0 and #path > 0 then self.path_xy[utils.path_remove_trailing(utils.path_join({ self.toplevel, path }))] = status end end ---@private ---@param prev_output string ---@param incoming string ---@return string function GitRunner:handle_incoming_data(prev_output, incoming) if incoming and utils.str_find(incoming, "\n") then local prev = prev_output .. incoming local i = 1 local skip_next_line = false for line in prev:gmatch("[^\n]*\n") do if skip_next_line then skip_next_line = false else local status = line:sub(1, 2) local path = line:sub(4, -2) if utils.str_find(status, "R") then -- skip next line if it is a rename entry skip_next_line = true end self:parse_status_output(status, path) end i = i + #line end return prev:sub(i, -1) end if incoming then return prev_output .. incoming end for line in prev_output:gmatch("[^\n]*\n") do self:parse_status_output(line) end return "" end ---@private ---@param stdout_handle uv.uv_pipe_t ---@param stderr_handle uv.uv_pipe_t ---@return uv.spawn.options function GitRunner:get_spawn_options(stdout_handle, stderr_handle) local untracked = self.list_untracked and "-u" or nil local ignored = (self.list_untracked and self.list_ignored) and "--ignored=matching" or "--ignored=no" return { args = { "--no-optional-locks", "status", "--porcelain=v1", "-z", ignored, untracked, self.path }, cwd = self.toplevel, stdio = { nil, stdout_handle, stderr_handle }, } end ---@private ---@param output string function GitRunner:log_raw_output(output) if log.enabled("git") and output and type(output) == "string" then log.raw("git", "%s", output) log.line("git", "done") end end ---@private ---@param callback function|nil function GitRunner:run_git_job(callback) local handle, pid local stdout = vim.loop.new_pipe(false) local stderr = vim.loop.new_pipe(false) local timer = vim.loop.new_timer() if stdout == nil or stderr == nil or timer == nil then return end local function on_finish(rc) self.rc = rc or 0 if timer:is_closing() or stdout:is_closing() or stderr:is_closing() or (handle and handle:is_closing()) then if callback then callback() end return end timer:stop() timer:close() stdout:read_stop() stderr:read_stop() stdout:close() stderr:close() -- don't close the handle when killing as it will leave a zombie if rc == -1 then pcall(vim.loop.kill, pid, "sigkill") elseif handle then handle:close() end if callback then callback() end end local spawn_options = self:get_spawn_options(stdout, stderr) log.line("git", "running job with timeout %dms", self.timeout) log.line("git", "git %s", table.concat(utils.array_remove_nils(spawn_options.args), " ")) handle, pid = vim.loop.spawn( "git", spawn_options, vim.schedule_wrap(function(rc) on_finish(rc) end) ) timer:start( self.timeout, 0, vim.schedule_wrap(function() on_finish(-1) end) ) local output_leftover = "" local function manage_stdout(err, data) if err then return end if data then data = data:gsub("%z", "\n") end self:log_raw_output(data) output_leftover = self:handle_incoming_data(output_leftover, data) end local function manage_stderr(_, data) self:log_raw_output(data) end vim.loop.read_start(stdout, vim.schedule_wrap(manage_stdout)) vim.loop.read_start(stderr, vim.schedule_wrap(manage_stderr)) end ---@private function GitRunner:wait() local function is_done() return self.rc ~= nil end while not vim.wait(30, is_done) do end end ---@private function GitRunner:finalise() if self.rc == -1 then log.line("git", "job timed out %s %s", self.toplevel, self.path) timeouts = timeouts + 1 if timeouts == MAX_TIMEOUTS then notify.warn(string.format("%d git jobs have timed out after git.timeout %dms, disabling git integration.", timeouts, self.timeout)) require("nvim-tree.git").disable_git_integration() end elseif self.rc ~= 0 then log.line("git", "job fail rc %d %s %s", self.rc, self.toplevel, self.path) else log.line("git", "job success %s %s", self.toplevel, self.path) end end ---Return nil when callback present ---@private ---@return nvim_tree.git.PathXY? function GitRunner:execute() local async = self.callback ~= nil local profile = log.profile_start("git %s job %s %s", async and "async" or "sync", self.toplevel, self.path) if async and self.callback then -- async, always call back self:run_git_job(function() log.profile_end(profile) self:finalise() self.callback(self.path_xy) end) else -- sync, maybe call back self:run_git_job() self:wait() log.profile_end(profile) self:finalise() if self.callback then self.callback(self.path_xy) else return self.path_xy end end end ---Static method to run a git process, which will be killed if it takes more than timeout ---Return nil when callback present ---@param args GitRunnerArgs ---@return nvim_tree.git.PathXY? function GitRunner:run(args) local runner = GitRunner(args) return runner:execute() end return GitRunner ================================================ FILE: lua/nvim-tree/git/utils.lua ================================================ local log = require("nvim-tree.log") local utils = require("nvim-tree.utils") local M = { use_cygpath = false, } --- Execute system command ---@param cmd string[] ---@return string stdout ---@return integer exit code local function system(cmd) if vim.fn.has("nvim-0.10") == 1 then local obj = vim.system(cmd):wait(M.opts.git.timeout) return obj.stdout or "", obj.code else return vim.fn.system(cmd), vim.v.shell_error end end --- Retrieve the git toplevel directory ---@param cwd string path ---@return string|nil toplevel absolute path ---@return string|nil git_dir absolute path function M.get_toplevel(cwd) local profile = log.profile_start("git toplevel git_dir %s", cwd) -- both paths are absolute local cmd = { "git", "-C", cwd, "rev-parse", "--show-toplevel", "--absolute-git-dir" } log.line("git", "%s", table.concat(cmd, " ")) local out, exitCode = system(cmd) log.raw("git", out) log.profile_end(profile) if exitCode ~= 0 or not out or #out == 0 or out:match("fatal") then return nil, nil end local toplevel, git_dir = out:match("([^\n]+)\n+([^\n]+)") if not toplevel then return nil, nil end if not git_dir then git_dir = utils.path_join({ toplevel, ".git" }) end -- git always returns path with forward slashes if vim.fn.has("win32") == 1 then -- msys2 git support -- cygpath calls must in array format to avoid shell compatibility issues if M.use_cygpath then toplevel = vim.fn.system({ "cygpath", "-w", toplevel }) if vim.v.shell_error ~= 0 then return nil, nil end -- remove trailing newline(\n) character added by vim.fn.system toplevel = toplevel:gsub("\n", "") git_dir = vim.fn.system({ "cygpath", "-w", git_dir }) if vim.v.shell_error ~= 0 then return nil, nil end -- remove trailing newline(\n) character added by vim.fn.system git_dir = git_dir:gsub("\n", "") end toplevel = toplevel:gsub("/", "\\") git_dir = git_dir:gsub("/", "\\") end return toplevel, git_dir end ---@type table local untracked = {} ---@param cwd string ---@return boolean function M.should_show_untracked(cwd) if untracked[cwd] ~= nil then return untracked[cwd] end local profile = log.profile_start("git untracked %s", cwd) local cmd = { "git", "-C", cwd, "config", "status.showUntrackedFiles" } log.line("git", table.concat(cmd, " ")) local has_untracked = system(cmd) log.raw("git", has_untracked) log.profile_end(profile) untracked[cwd] = vim.trim(has_untracked) ~= "no" return untracked[cwd] end ---@param t table? ---@param k string|integer ---@return table local function nil_insert(t, k) t = t or {} t[k] = true return t end ---@param project_files nvim_tree.git.ProjectFiles ---@param cwd string|nil ---@return nvim_tree.git.ProjectDirs function M.project_files_to_project_dirs(project_files, cwd) ---@type nvim_tree.git.ProjectDirs local project_dirs = {} project_dirs.direct = {} for p, s in pairs(project_files) do if s ~= "!!" then local modified = vim.fn.fnamemodify(p, ":h") project_dirs.direct[modified] = nil_insert(project_dirs.direct[modified], s) end end project_dirs.indirect = {} for dirname, statuses in pairs(project_dirs.direct) do for s, _ in pairs(statuses) do local modified = dirname while modified ~= cwd and modified ~= "/" do modified = vim.fn.fnamemodify(modified, ":h") project_dirs.indirect[modified] = nil_insert(project_dirs.indirect[modified], s) end end end for _, d in pairs(project_dirs) do for dirname, statuses in pairs(d) do local new_statuses = {} for s, _ in pairs(statuses) do table.insert(new_statuses, s) end d[dirname] = new_statuses end end return project_dirs end ---Git file status for an absolute path ---@param parent_ignored boolean ---@param project nvim_tree.git.Project? ---@param path string ---@param path_fallback string? alternative file path when no other file status ---@return nvim_tree.git.Status function M.git_status_file(parent_ignored, project, path, path_fallback) ---@type nvim_tree.git.Status local ns if parent_ignored then ns = { file = "!!" } elseif project and project.files then ns = { file = project.files[path] or project.files[path_fallback] } else ns = {} end return ns end ---Git file and directory status for an absolute path ---@param parent_ignored boolean ---@param project nvim_tree.git.Project? ---@param path string ---@param path_fallback string? alternative file path when no other file status ---@return nvim_tree.git.Status? function M.git_status_dir(parent_ignored, project, path, path_fallback) ---@type nvim_tree.git.Status? local ns if parent_ignored then ns = { file = "!!" } elseif project then ns = { file = project.files and (project.files[path] or project.files[path_fallback]), dir = project.dirs and { direct = project.dirs.direct and project.dirs.direct[path], indirect = project.dirs.indirect and project.dirs.indirect[path], }, } end return ns end function M.setup(opts) if opts.git.cygwin_support then M.use_cygpath = vim.fn.executable("cygpath") == 1 end M.opts = opts end return M ================================================ FILE: lua/nvim-tree/help.lua ================================================ local keymap = require("nvim-tree.keymap") local api = {} -- circular dependency local PAT_MOUSE = "^<.*Mouse" local PAT_CTRL = "^" e.g. "CTRL-v>" lhs = lhs:gsub("^", "<") -- shorten ctrls if lhs:lower():match("^") return lhs end --- Remove prefix 'nvim-tree: ' --- Hardcoded to keep on_attach_default simple ---@param desc string ---@return string local function tidy_desc(desc) return desc and desc:gsub("^nvim%-tree: ", "") or "" end --- sort vim command lhs roughly as per :help index ---@param a string ---@param b string ---@return boolean local function sort_lhs(a, b) -- mouse first if a:match(PAT_MOUSE) and not b:match(PAT_MOUSE) then return true elseif not a:match(PAT_MOUSE) and b:match(PAT_MOUSE) then return false end -- ctrl next if a:match(PAT_CTRL) and not b:match(PAT_CTRL) then return true elseif not a:match(PAT_CTRL) and b:match(PAT_CTRL) then return false end -- special next if a:match(PAT_SPECIAL) and not b:match(PAT_SPECIAL) then return true elseif not a:match(PAT_SPECIAL) and b:match(PAT_SPECIAL) then return false end -- remainder alpha return a:gsub("[^a-zA-Z]", "") < b:gsub("[^a-zA-Z]", "") end --- Compute all lines for the buffer ---@param map table keymap.get_keymap ---@return string[] lines of text ---@return HighlightRangeArgs[] hl_range_args for lines ---@return number maximum length of text local function compute(map) local head_lhs = "nvim-tree mappings" local head_rhs1 = "exit: q" local head_rhs2 = string.format("sort by %s: s", M.config.sort_by == "key" and "description" or "keymap") -- merge modes for duplicate lhs+desc entries e.g. "n" + "x" -> "nx" local merged = {} local mappings = {} for _, m in ipairs(map) do local lhs = tidy_lhs(m.lhs) local desc = tidy_desc(m.desc) local key = lhs .. "\0" .. desc if merged[key] then merged[key].mode = merged[key].mode .. m.mode else local entry = { lhs = lhs, desc = desc, mode = m.mode or "n" } merged[key] = entry table.insert(mappings, entry) end end -- sorter function for mappings local sort_fn if M.config.sort_by == "desc" then sort_fn = function(a, b) return a.desc:lower() < b.desc:lower() end else -- by default sort roughly by lhs sort_fn = function(a, b) return sort_lhs(a.lhs, b.lhs) end end table.sort(mappings, sort_fn) -- sort mode characters for deterministic display e.g. "nx" not "xn" for _, entry in ipairs(mappings) do local chars = {} for c in entry.mode:gmatch(".") do table.insert(chars, c) end table.sort(chars) entry.mode = table.concat(chars) end -- longest lhs, mode and description local max_lhs = 0 local max_mode = 0 local max_desc = 0 for _, l in ipairs(mappings) do max_lhs = math.max(#l.lhs, max_lhs) max_mode = math.max(#l.mode, max_mode) max_desc = math.max(#l.desc, max_desc) end -- increase desc if lines are shorter than the header max_desc = math.max(max_desc, #head_lhs + #head_rhs1 - max_lhs - max_mode) -- header text, not padded local lines = { head_lhs .. string.rep(" ", max_lhs + max_mode + max_desc - #head_lhs - #head_rhs1 + 3) .. head_rhs1, string.rep(" ", max_lhs + max_mode + max_desc - #head_rhs2 + 3) .. head_rhs2, } local width = #lines[1] -- header highlight, assume one character keys local hl_range_args = { { higroup = "NvimTreeFolderName", start = { 0, 0, }, finish = { 0, #head_lhs, }, }, { higroup = "NvimTreeFolderName", start = { 0, width - 1, }, finish = { 0, width, }, }, { higroup = "NvimTreeFolderName", start = { 1, width - 1, }, finish = { 1, width, }, }, } -- mappings, left padded 1 local fmt = string.format(" %%-%ds %%-%ds %%-%ds", max_lhs, max_mode, max_desc) for i, l in ipairs(mappings) do -- format in left aligned columns local line = string.format(fmt, l.lhs, l.mode, l.desc) table.insert(lines, line) width = math.max(#line, width) -- highlight lhs table.insert(hl_range_args, { higroup = "NvimTreeFolderName", start = { i + 1, 1, }, finish = { i + 1, #l.lhs + 1, }, }) end return lines, hl_range_args, width end --- close the window and delete the buffer, if they exist local function close() if M.winnr then vim.api.nvim_win_close(M.winnr, true) M.winnr = nil end if M.bufnr then vim.api.nvim_buf_delete(M.bufnr, { force = true }) M.bufnr = nil end end --- open a new window and buffer local function open() -- close existing, shouldn't be necessary close() -- fetch all mappings local map = keymap.get_keymap() -- text and highlight local lines, hl_range_args, width = compute(map) -- create the buffer M.bufnr = vim.api.nvim_create_buf(false, true) -- populate it vim.api.nvim_buf_set_lines(M.bufnr, 0, -1, false, lines) if vim.fn.has("nvim-0.10") == 1 then vim.api.nvim_set_option_value("modifiable", false, { buf = M.bufnr }) else vim.api.nvim_buf_set_option(M.bufnr, "modifiable", false) ---@diagnostic disable-line: deprecated end -- highlight it for _, args in ipairs(hl_range_args) do 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 -- open a very restricted window M.winnr = vim.api.nvim_open_win(M.bufnr, true, { relative = "editor", border = "single", width = width, height = #lines, row = 1, col = 0, style = "minimal", noautocmd = true, }) -- style it a bit like the tree vim.wo[M.winnr].winhl = WIN_HL vim.wo[M.winnr].cursorline = M.config.cursorline local function toggle_sort() M.config.sort_by = (M.config.sort_by == "desc") and "key" or "desc" open() end -- hardcoded local help_keymaps = { q = { fn = close, desc = "nvim-tree: exit help" }, [""] = { fn = close, desc = "nvim-tree: exit help" }, -- hidden s = { fn = toggle_sort, desc = "nvim-tree: toggle sorting method" }, } -- api help binding closes for _, m in ipairs(map) do if m.callback == api.tree.toggle_help then help_keymaps[m.lhs] = { fn = close, desc = "nvim-tree: exit help" } end end for k, v in pairs(help_keymaps) do vim.keymap.set("n", k, v.fn, { desc = v.desc, buffer = M.bufnr, noremap = true, silent = true, nowait = true, }) end -- close window and delete buffer on leave vim.api.nvim_create_autocmd({ "BufLeave", "WinLeave" }, { buffer = M.bufnr, once = true, callback = close, }) end function M.toggle() if M.winnr or M.bufnr then close() else open() end end function M.setup(opts) M.config.cursorline = opts.view.cursorline M.config.sort_by = opts.help.sort_by api = require("nvim-tree.api") end return M ================================================ FILE: lua/nvim-tree/iterators/node-iterator.lua ================================================ ---@class NodeIterator local NodeIterator = {} NodeIterator.__index = NodeIterator ---@param nodes Node[] ---@return NodeIterator function NodeIterator.builder(nodes) return setmetatable({ nodes = nodes, _filter_hidden = function(node) return not node.hidden end, _apply_fn_on_node = function(_) end, _match = function(_) end, _recurse_with = function(node) return node.nodes end, }, NodeIterator) end ---@return NodeIterator function NodeIterator:hidden() self._filter_hidden = function(_) return true end return self end ---@param f fun(node: Node): boolean ---@return NodeIterator function NodeIterator:matcher(f) self._match = f return self end ---@param f fun(node: Node, i: number) ---@return NodeIterator function NodeIterator:applier(f) self._apply_fn_on_node = f return self end ---@param f fun(node: Node): any ---@return NodeIterator function NodeIterator:recursor(f) self._recurse_with = f return self end ---@return Node|nil ---@return number|nil function NodeIterator:iterate() local iteration_count = 0 local function iter(nodes) for _, node in ipairs(nodes) do if self._filter_hidden(node) then if not node.group_next then iteration_count = iteration_count + 1 end if self._match(node) then return node, iteration_count end self._apply_fn_on_node(node, iteration_count) local children = self._recurse_with(node) if children then local n = iter(children) if n then return n, iteration_count end end end end return nil, 0 end return iter(self.nodes) end return NodeIterator ================================================ FILE: lua/nvim-tree/keymap.lua ================================================ local M = {} --- Apply mappings to a scratch buffer and return buffer local mappings ---@param fn fun(bufnr: integer) on_attach or on_attach_default ---@return table as per vim.api.nvim_buf_get_keymap local function generate_keymap(fn) -- create an unlisted scratch buffer local scratch_bufnr = vim.api.nvim_create_buf(false, true) -- apply mappings fn(scratch_bufnr) -- retrieve all local keymap = vim.api.nvim_buf_get_keymap(scratch_bufnr, "") -- delete the scratch buffer vim.api.nvim_buf_delete(scratch_bufnr, { force = true }) return keymap end ---@return table function M.get_keymap() return generate_keymap(M.on_attach) end ---@return table function M.get_keymap_default() return generate_keymap(M.on_attach_default) end ---@param bufnr integer function M.on_attach_default(bufnr) local api = require("nvim-tree.api") local function opts(desc) return { desc = "nvim-tree: " .. desc, buffer = bufnr, noremap = true, silent = true, nowait = true, } end -- BEGIN_ON_ATTACH_DEFAULT vim.keymap.set("n", "", api.tree.change_root_to_node, opts("CD")) vim.keymap.set("n", "", api.node.open.replace_tree_buffer, opts("Open: In Place")) vim.keymap.set("n", "", api.node.show_info_popup, opts("Info")) vim.keymap.set("n", "", api.fs.rename_sub, opts("Rename: Omit Filename")) vim.keymap.set("n", "", api.node.open.tab, opts("Open: New Tab")) vim.keymap.set("n", "", api.node.open.vertical, opts("Open: Vertical Split")) vim.keymap.set("n", "", api.node.open.horizontal, opts("Open: Horizontal Split")) vim.keymap.set("n", "", api.node.navigate.parent_close, opts("Close Directory")) vim.keymap.set("n", "", api.node.open.edit, opts("Open")) vim.keymap.set({ "n", "x" }, "", api.fs.remove, opts("Delete")) vim.keymap.set("n", "", api.node.open.preview, opts("Open Preview")) vim.keymap.set("n", ">", api.node.navigate.sibling.next, opts("Next Sibling")) vim.keymap.set("n", "<", api.node.navigate.sibling.prev, opts("Previous Sibling")) vim.keymap.set("n", ".", api.node.run.cmd, opts("Run Command")) vim.keymap.set("n", "-", api.tree.change_root_to_parent, opts("Up")) vim.keymap.set("n", "a", api.fs.create, opts("Create File Or Directory")) vim.keymap.set("n", "bd", api.marks.bulk.delete, opts("Delete Bookmarked")) vim.keymap.set("n", "bt", api.marks.bulk.trash, opts("Trash Bookmarked")) vim.keymap.set("n", "bmv", api.marks.bulk.move, opts("Move Bookmarked")) vim.keymap.set("n", "B", api.filter.no_buffer.toggle, opts("Toggle Filter: No Buffer")) vim.keymap.set({ "n", "x" }, "c", api.fs.copy.node, opts("Copy")) vim.keymap.set("n", "C", api.filter.git.clean.toggle, opts("Toggle Filter: Git Clean")) vim.keymap.set("n", "[c", api.node.navigate.git.prev, opts("Prev Git")) vim.keymap.set("n", "]c", api.node.navigate.git.next, opts("Next Git")) vim.keymap.set({ "n", "x" }, "d", api.fs.remove, opts("Delete")) vim.keymap.set({ "n", "x" }, "D", api.fs.trash, opts("Trash")) vim.keymap.set("n", "E", api.tree.expand_all, opts("Expand All")) vim.keymap.set("n", "e", api.fs.rename_basename, opts("Rename: Basename")) vim.keymap.set("n", "]e", api.node.navigate.diagnostics.next, opts("Next Diagnostic")) vim.keymap.set("n", "[e", api.node.navigate.diagnostics.prev, opts("Prev Diagnostic")) vim.keymap.set("n", "F", api.filter.live.clear, opts("Live Filter: Clear")) vim.keymap.set("n", "f", api.filter.live.start, opts("Live Filter: Start")) vim.keymap.set("n", "g?", api.tree.toggle_help, opts("Help")) vim.keymap.set("n", "gy", api.fs.copy.absolute_path, opts("Copy Absolute Path")) vim.keymap.set("n", "ge", api.fs.copy.basename, opts("Copy Basename")) vim.keymap.set("n", "H", api.filter.dotfiles.toggle, opts("Toggle Filter: Dotfiles")) vim.keymap.set("n", "I", api.filter.git.ignored.toggle, opts("Toggle Filter: Git Ignored")) vim.keymap.set("n", "J", api.node.navigate.sibling.last, opts("Last Sibling")) vim.keymap.set("n", "K", api.node.navigate.sibling.first, opts("First Sibling")) vim.keymap.set("n", "L", api.node.open.toggle_group_empty, opts("Toggle Group Empty")) vim.keymap.set("n", "M", api.filter.no_bookmark.toggle, opts("Toggle Filter: No Bookmark")) vim.keymap.set({ "n", "x" }, "m", api.marks.toggle, opts("Toggle Bookmark")) vim.keymap.set("n", "o", api.node.open.edit, opts("Open")) vim.keymap.set("n", "O", api.node.open.no_window_picker, opts("Open: No Window Picker")) vim.keymap.set("n", "p", api.fs.paste, opts("Paste")) vim.keymap.set("n", "P", api.node.navigate.parent, opts("Parent Directory")) vim.keymap.set("n", "q", api.tree.close, opts("Close")) vim.keymap.set("n", "r", api.fs.rename, opts("Rename")) vim.keymap.set("n", "R", api.tree.reload, opts("Refresh")) vim.keymap.set("n", "s", api.node.run.system, opts("Run System")) vim.keymap.set("n", "S", api.tree.search_node, opts("Search")) vim.keymap.set("n", "u", api.fs.rename_full, opts("Rename: Full Path")) vim.keymap.set("n", "U", api.filter.custom.toggle, opts("Toggle Filter: Custom")) vim.keymap.set("n", "W", api.tree.collapse_all, opts("Collapse All")) vim.keymap.set({ "n", "x" }, "x", api.fs.cut, opts("Cut")) vim.keymap.set("n", "y", api.fs.copy.filename, opts("Copy Name")) vim.keymap.set("n", "Y", api.fs.copy.relative_path, opts("Copy Relative Path")) vim.keymap.set("n", "<2-LeftMouse>", api.node.open.edit, opts("Open")) vim.keymap.set("n", "<2-RightMouse>", api.tree.change_root_to_node, opts("CD")) -- END_ON_ATTACH_DEFAULT end function M.setup(opts) if type(opts.on_attach) ~= "function" then M.on_attach = M.on_attach_default else M.on_attach = opts.on_attach end end return M ================================================ FILE: lua/nvim-tree/legacy.lua ================================================ local notify = require("nvim-tree.notify") local M = {} --- Create empty sub-tables if not present ---@param tbl table to create empty inside of ---@param path string dot separated string of sub-tables ---@return table deepest sub-table local function create(tbl, path) local t = tbl for s in string.gmatch(path, "([^%.]+)%.*") do if t[s] == nil then t[s] = {} end t = t[s] end return t end --- Move a value from src to dst if value is nil on dst. --- Remove value from src ---@param src table to copy from ---@param src_path string dot separated string of sub-tables ---@param src_pos string value pos ---@param dst table to copy to ---@param dst_path string dot separated string of sub-tables, created when missing ---@param dst_pos string value pos ---@param remove boolean local function move(src, src_path, src_pos, dst, dst_path, dst_pos, remove) for pos in string.gmatch(src_path, "([^%.]+)%.*") do if src[pos] and type(src[pos]) == "table" then src = src[pos] else return end end local src_val = src[src_pos] if src_val == nil then return end dst = create(dst, dst_path) if dst[dst_pos] == nil then dst[dst_pos] = src_val end if remove then src[src_pos] = nil end end -- silently move, please add to help nvim-tree-legacy-config ---@param u nvim_tree.config user supplied subset of config local function refactored_config(u) -- 2022/06/20 move(u, "update_focused_file", "update_cwd", u, "update_focused_file", "update_root", true) move(u, "", "update_cwd", u, "", "sync_root_with_cwd", true) -- 2022/11/07 move(u, "", "open_on_tab", u, "tab.sync", "open", false) move(u, "", "open_on_tab", u, "tab.sync", "close", true) move(u, "", "ignore_buf_on_tab_change", u, "tab.sync", "ignore", true) -- 2022/11/22 move(u, "renderer", "root_folder_modifier", u, "renderer", "root_folder_label", true) -- 2023/01/01 move(u, "update_focused_file", "debounce_delay", u, "view", "debounce_delay", true) -- 2023/01/08 move(u, "trash", "require_confirm", u, "ui.confirm", "trash", true) -- 2023/01/15 if type(u.view) == "table" and u.view.adaptive_size ~= nil then if u.view.adaptive_size and type(u.view.width) ~= "table" then local width = u.view.width --[[@as nvim_tree.config.view.width.spec]] u.view.width = { min = width, } end u.view["adaptive_size"] = nil end -- 2023/07/15 move(u, "", "sort_by", u, "sort", "sorter", true) -- 2023/07/16 move(u, "git", "ignore", u, "filters", "git_ignored", true) -- 2023/08/26 move(u, "renderer.icons", "webdev_colors", u, "renderer.icons.web_devicons.file", "color", true) -- 2023/10/08 if type(u.renderer) == "table" and type(u.renderer.highlight_diagnostics) == "boolean" then u.renderer.highlight_diagnostics = u.renderer.highlight_diagnostics and "name" or "none" end -- 2023/10/21 if type(u.renderer) == "table" and type(u.renderer.highlight_git) == "boolean" then u.renderer.highlight_git = u.renderer.highlight_git and "name" or "none" end -- 2024/02/15 if type(u.update_focused_file) == "table" then if type(u.update_focused_file.update_root) ~= "table" then u.update_focused_file.update_root = { enable = u.update_focused_file.update_root == true } end end move(u, "update_focused_file", "ignore_list", u, "update_focused_file.update_root", "ignore_list", true) -- 2025/04/30 if u.renderer and u.renderer.icons and type(u.renderer.icons.padding) == "string" then local icons_padding = u.renderer.icons.padding --[[@as string]] u.renderer.icons.padding = {} u.renderer.icons.padding.icon = icons_padding end end ---@param u nvim_tree.config user supplied subset of config local function deprecated_config(u) if type(u.view) == "table" and u.view.hide_root_folder then notify.info("view.hide_root_folder is deprecated, please set renderer.root_folder_label = false") end end ---@param u nvim_tree.config user supplied subset of config local function removed_config(u) if u.auto_close then notify.warn("auto close feature has been removed: https://github.com/nvim-tree/nvim-tree.lua/wiki/Auto-Close") u["auto_close"] = nil end if u.focus_empty_on_setup then notify.warn("focus_empty_on_setup has been removed: https://github.com/nvim-tree/nvim-tree.lua/wiki/Open-At-Startup") u["focus_empty_on_setup"] = nil end if u.create_in_closed_folder then notify.warn( "create_in_closed_folder has been removed and is now the default behaviour. You may use api.fs.create to add a file under your desired node.") end u["create_in_closed_folder"] = nil end ---Migrate legacy config in place. ---Refactored are silently migrated. Deprecated and removed result in a warning. ---@param u nvim_tree.config user supplied subset of config function M.migrate_config(u) -- silently move refactored_config(u) -- warn deprecated_config(u) -- warn and delete removed_config(u) end ---Silently create new api entries pointing legacy functions to current ---@param api table not properly typed to prevent LSP from referencing implementations function M.map_api(api) api.config = api.config or {} api.config.mappings = api.config.mappings or {} api.config.mappings.get_keymap = api.map.keymap.current api.config.mappings.get_keymap_default = api.map.keymap.default api.config.mappings.default_on_attach = api.map.on_attach.default api.live_filter = api.live_filter or {} api.live_filter.start = api.filter.live.start api.live_filter.clear = api.filter.live.clear api.tree = api.tree or {} api.tree.toggle_enable_filters = api.filter.toggle api.tree.toggle_gitignore_filter = api.filter.git.ignored.toggle api.tree.toggle_git_clean_filter = api.filter.git.clean.toggle api.tree.toggle_no_buffer_filter = api.filter.no_buffer.toggle api.tree.toggle_custom_filter = api.filter.custom.toggle api.tree.toggle_hidden_filter = api.filter.dotfiles.toggle api.tree.toggle_no_bookmark_filter = api.filter.no_bookmark.toggle api.diagnostics = api.diagnostics or {} api.diagnostics.hi_test = api.appearance.hi_test api.decorator.UserDecorator = api.Decorator end return M ================================================ FILE: lua/nvim-tree/lib.lua ================================================ local view = require("nvim-tree.view") local core = require("nvim-tree.core") local notify = require("nvim-tree.notify") ---@class LibOpenOpts ---@field path string|nil path ---@field current_window boolean|nil default false ---@field winid number|nil local M = { target_winid = nil, } function M.set_target_win() local id = vim.api.nvim_get_current_win() local tree_id = view.get_winnr() if tree_id and id == tree_id then M.target_winid = 0 return end M.target_winid = id end ---@param cwd string local function handle_buf_cwd(cwd) local explorer = core.get_explorer() if M.respect_buf_cwd and cwd ~= core.get_cwd() and explorer then explorer:change_dir(cwd) end end local function open_view_and_draw() local cwd = vim.fn.getcwd() view.open() handle_buf_cwd(cwd) local explorer = core.get_explorer() if explorer then explorer.renderer:draw() end end local function should_hijack_current_buf() local bufnr = vim.api.nvim_get_current_buf() local bufname = vim.api.nvim_buf_get_name(bufnr) local bufmodified, ft if vim.fn.has("nvim-0.10") == 1 then bufmodified = vim.api.nvim_get_option_value("modified", { buf = bufnr }) ft = vim.api.nvim_get_option_value("ft", { buf = bufnr }) else bufmodified = vim.api.nvim_buf_get_option(bufnr, "modified") ---@diagnostic disable-line: deprecated ft = vim.api.nvim_buf_get_option(bufnr, "ft") ---@diagnostic disable-line: deprecated end local should_hijack_unnamed = M.hijack_unnamed_buffer_when_opening and bufname == "" and not bufmodified and ft == "" local should_hijack_dir = bufname ~= "" and vim.fn.isdirectory(bufname) == 1 and M.hijack_directories.enable return should_hijack_dir or should_hijack_unnamed end ---@param prompt_input string ---@param prompt_select string ---@param items_short string[] ---@param items_long string[] ---@param kind string|nil ---@param callback fun(item_short: string|nil) function M.prompt(prompt_input, prompt_select, items_short, items_long, kind, callback) local function format_item(short) for i, s in ipairs(items_short) do if short == s then return items_long[i] end end return "" end if M.select_prompts then vim.ui.select(items_short, { prompt = prompt_select, kind = kind, format_item = format_item }, function(item_short) callback(item_short) end) else vim.ui.input({ prompt = prompt_input, default = items_short[1] or "" }, function(item_short) if item_short then callback(string.lower(item_short and item_short:sub(1, 1)) or nil) end end) end end ---Open the tree, initialising as needed. Maybe hijack the current buffer. ---@param opts LibOpenOpts|nil function M.open(opts) opts = opts or {} M.set_target_win() if not core.get_explorer() or opts.path then if opts.path then core.init(opts.path) else local cwd, err = vim.loop.cwd() if not cwd then notify.error(string.format("current working directory unavailable: %s", err)) return end core.init(cwd) end end local explorer = core.get_explorer() if should_hijack_current_buf() then view.close_this_tab_only() view.open_in_win() if explorer then explorer.renderer:draw() end elseif opts.winid then view.open_in_win({ hijack_current_buf = false, resize = false, winid = opts.winid }) if explorer then explorer.renderer:draw() end elseif opts.current_window then view.open_in_win({ hijack_current_buf = false, resize = false }) if explorer then explorer.renderer:draw() end else open_view_and_draw() end view.restore_tab_state() end function M.setup(opts) M.hijack_unnamed_buffer_when_opening = opts.hijack_unnamed_buffer_when_opening M.hijack_directories = opts.hijack_directories M.respect_buf_cwd = opts.respect_buf_cwd M.select_prompts = opts.select_prompts M.group_empty = opts.renderer.group_empty end return M ================================================ FILE: lua/nvim-tree/log.lua ================================================ ---@alias LogTypes "all" | "config" | "copy_paste" | "dev" | "diagnostics" | "git" | "profile" | "watcher" ---@type table local types = {} ---@type string local file_path local M = {} --- Write to log file ---@param typ string as per log.types config ---@param fmt string for string.format ---@param ... any arguments for string.format function M.raw(typ, fmt, ...) if not M.enabled(typ) then return end local line = string.format(fmt, ...) local file = io.open(file_path, "a") if file then io.output(file) io.write(line) io.close(file) end end --- Write to a new file ---@param typ LogTypes as per log.types config ---@param path string absolute path ---@param fmt string for string.format ---@param ... any arguments for string.format function M.file(typ, path, fmt, ...) if not M.enabled(typ) then return end local line = string.format(fmt, ...) local file = io.open(path, "w") if file then io.output(file) io.write(line) io.close(file) end end ---@class Profile ---@field start number nanos ---@field tag string --- Write profile start to log file --- START is prefixed ---@param fmt string for string.format ---@param ... any arguments for string.format ---@return Profile to pass to profile_end function M.profile_start(fmt, ...) local profile = {} if M.enabled("profile") then profile.start = vim.loop.hrtime() profile.tag = string.format((fmt or "???"), ...) M.line("profile", "START %s", profile.tag) end return profile end --- Write profile end to log file --- END is prefixed and duration in seconds is suffixed ---@param profile Profile returned from profile_start function M.profile_end(profile) if M.enabled("profile") and type(profile) == "table" then local millis = profile.start and math.modf((vim.loop.hrtime() - profile.start) / 1000000) or -1 M.line("profile", "END %s %dms", profile.tag or "", millis) end end --- Write to log file --- time and typ are prefixed and a trailing newline is added ---@param typ LogTypes as per log.types config ---@param fmt string for string.format ---@param ... any arguments for string.format function M.line(typ, fmt, ...) if M.enabled(typ) then M.raw(typ, string.format("[%s] [%s] %s\n", os.date("%Y-%m-%d %H:%M:%S"), typ, (fmt or "???")), ...) end end local inspect_opts = {} ---@param opts table function M.set_inspect_opts(opts) inspect_opts = opts end --- Write to log file the inspection of a node ---@param typ LogTypes as per log.types config ---@param node Node node to be inspected ---@param fmt string for string.format ---@param ... any arguments for string.format function M.node(typ, node, fmt, ...) if M.enabled(typ) then M.raw(typ, string.format("[%s] [%s] %s\n%s\n", os.date("%Y-%m-%d %H:%M:%S"), typ, (fmt or "???"), vim.inspect(node, inspect_opts)), ...) end end --- Logging is enabled for typ or all ---@param typ LogTypes as per log.types config ---@return boolean function M.enabled(typ) return file_path ~= nil and (types[typ] or types.all) end function M.setup(opts) if opts.log and opts.log.enable and opts.log.types then types = opts.log.types file_path = string.format("%s/nvim-tree.log", vim.fn.stdpath("log"), os.date("%H:%M:%S"), vim.env.USER) if opts.log.truncate then os.remove(file_path) end require("nvim-tree.notify").debug("nvim-tree.lua logging to " .. file_path) end end return M ================================================ FILE: lua/nvim-tree/marks/init.lua ================================================ local Iterator = require("nvim-tree.iterators.node-iterator") local core = require("nvim-tree.core") local lib = require("nvim-tree.lib") local notify = require("nvim-tree.notify") local open_file = require("nvim-tree.actions.node.open-file") local remove_file = require("nvim-tree.actions.fs.remove-file") local rename_file = require("nvim-tree.actions.fs.rename-file") local trash = require("nvim-tree.actions.fs.trash") local utils = require("nvim-tree.utils") local Class = require("nvim-tree.classic") local DirectoryNode = require("nvim-tree.node.directory") local Node = require("nvim-tree.node") local function get_save_path(opts) if type(opts.bookmarks.persist) == "string" then return opts.bookmarks.persist else return vim.fn.stdpath("data") .. "/nvim-tree-bookmarks.json" end end local function save_bookmarks(marks, opts) if not opts.bookmarks.persist then return end local storepath = get_save_path(opts) local file, errmsg = io.open(storepath, "w") if file then local data = {} for path, _ in pairs(marks) do table.insert(data, path) end file:write(vim.json.encode(data)) file:close() else notify.warn(string.format("Invalid bookmarks.persist, disabling persistence: %s", errmsg)) opts.bookmarks.persist = false end end local function load_bookmarks(opts) local storepath = get_save_path(opts) local file = io.open(storepath, "r") if file then local content = file:read("*all") file:close() if content and content ~= "" then local data = vim.json.decode(content) local marks = {} for _, path in ipairs(data) do -- Store as boolean initially; will be lazily resolved to node on first access marks[path] = true end return marks end end return {} end ---@class (exact) Marks: nvim_tree.Class ---@field private explorer Explorer ---@field private marks table by absolute path local Marks = Class:extend() ---@class Marks ---@overload fun(args: MarksArgs): Marks ---@class (exact) MarksArgs ---@field explorer Explorer ---@protected ---@param args MarksArgs function Marks:new(args) self.explorer = args.explorer self.marks = {} if self.explorer.opts.bookmarks.persist then local ok, loaded_marks = pcall(load_bookmarks, self.explorer.opts) if ok then self.marks = loaded_marks else notify.warn(string.format("Failed to load bookmarks: %s", loaded_marks)) end end end ---Clear all marks and reload if watchers disabled ---@private function Marks:clear_reload() self:clear() if not self.explorer.opts.filesystem_watchers.enable then self.explorer:reload_explorer() end end ---Clear all marks and redraw ---@public function Marks:clear() self.marks = {} self.explorer.renderer:draw() end ---@private ---@param node Node function Marks:toggle_one(node) if node.absolute_path == nil then return end if self:get(node) then self.marks[node.absolute_path] = nil else self.marks[node.absolute_path] = node end end ---@public ---@param node_or_nodes Node|Node[] function Marks:toggle(node_or_nodes) if type(node_or_nodes) == "table" and node_or_nodes.is and node_or_nodes:is(Node) then self:toggle_one(node_or_nodes) else for _, node in ipairs(node_or_nodes) do self:toggle_one(node) end end if self.explorer.opts.bookmarks.persist then local ok, err = pcall(save_bookmarks, self.marks, self.explorer.opts) if not ok then notify.warn(string.format("Failed to save bookmarks: %s", err)) end end self.explorer.renderer:draw() end ---Return node if marked ---@public ---@param node Node ---@return Node|nil function Marks:get(node) if not node or not node.absolute_path then return nil end local mark = self.marks[node.absolute_path] if mark == true then -- Lazy resolve: try to find node in explorer tree local resolved_node = self.explorer:get_node_from_path(node.absolute_path) if resolved_node then -- Cache the resolved node self.marks[node.absolute_path] = resolved_node return resolved_node end return nil end return mark end ---List marked nodes ---@public ---@return Node[] function Marks:list() local list = {} for path, mark in pairs(self.marks) do local node if mark == true then -- Lazy resolve: try to find node in explorer tree node = self.explorer:get_node_from_path(path) if node then -- Cache the resolved node for future access self.marks[path] = node end -- If node not found (file deleted/moved), skip it silently else -- Already a node object node = mark end if node then table.insert(list, node) end end return list end ---Delete marked; each removal will be optionally notified ---@public function Marks:bulk_delete() if not next(self.marks) then notify.warn("No bookmarks to delete.") return end local function execute() for _, node in ipairs(self:list()) do remove_file.remove(node) end self:clear_reload() end if self.explorer.opts.ui.confirm.remove then local prompt_select = "Remove bookmarked ?" local prompt_input = prompt_select .. " y/N: " lib.prompt(prompt_input, prompt_select, { "", "y" }, { "No", "Yes" }, "nvimtree_bulk_delete", function(item_short) utils.clear_prompt() if item_short == "y" then execute() end end) else execute() end end ---Trash marked; each removal will be optionally notified ---@public function Marks:bulk_trash() if not next(self.marks) then notify.warn("No bookmarks to trash.") return end local function execute() for _, node in ipairs(self:list()) do trash.remove(node) end self:clear_reload() end if self.explorer.opts.ui.confirm.trash then local prompt_select = "Trash bookmarked ?" local prompt_input = prompt_select .. " y/N: " lib.prompt(prompt_input, prompt_select, { "", "y" }, { "No", "Yes" }, "nvimtree_bulk_trash", function(item_short) utils.clear_prompt() if item_short == "y" then execute() end end) else execute() end end ---Move marked ---@public function Marks:bulk_move() if not next(self.marks) then notify.warn("No bookmarks to move.") return end local node_at_cursor = self.explorer:get_node_at_cursor() local default_path = core.get_cwd() if node_at_cursor and node_at_cursor:is(DirectoryNode) then default_path = node_at_cursor.absolute_path elseif node_at_cursor and node_at_cursor.parent then default_path = node_at_cursor.parent.absolute_path end local input_opts = { prompt = "Move to: ", default = default_path, completion = "dir", } vim.ui.input(input_opts, function(location) utils.clear_prompt() if not location or location == "" then return end if vim.fn.filewritable(location) ~= 2 then notify.warn(location .. " is not writable, cannot move.") return end for _, node in ipairs(self:list()) do local head = vim.fn.fnamemodify(node.absolute_path, ":t") local to = utils.path_join({ location, head }) rename_file.rename(node, to) end self:clear_reload() end) end ---Focus nearest marked node in direction. ---@private ---@param up boolean function Marks:navigate(up) local node = self.explorer:get_node_at_cursor() if not node then return end local first, prev, next, last = nil, nil, nil, nil local found = false Iterator.builder(self.explorer.nodes) :recursor(function(n) local dir = n:as(DirectoryNode) return dir and dir.open and dir.nodes end) :applier(function(n) if n.absolute_path == node.absolute_path then found = true return end if not self:get(n) then return end last = n first = first or n if found and not next then next = n end if not found then prev = n end end) :iterate() if not found then return end if up then self.explorer:focus_node_or_parent(prev or last) else self.explorer:focus_node_or_parent(next or first) end end ---@public function Marks:navigate_prev() self:navigate(true) end ---@public function Marks:navigate_next() self:navigate(false) end ---Prompts for selection of a marked node, sorted by absolute paths. ---A folder will be focused, a file will be opened. ---@public function Marks:navigate_select() local list = vim.tbl_map(function(n) return n.absolute_path end, self:list()) table.sort(list) vim.ui.select(list, { prompt = "Select a file to open or a folder to focus", }, function(choice) if not choice or choice == "" then return end local mark = self.marks[choice] local node if mark == true then -- Lazy resolve node = self.explorer:get_node_from_path(choice) if node then self.marks[choice] = node end else node = mark end if node and not node:is(DirectoryNode) and not utils.get_win_buf_from_path(node.absolute_path) then open_file.fn("edit", node.absolute_path) elseif node then self.explorer:focus_node_or_parent(node) end end) end return Marks ================================================ FILE: lua/nvim-tree/node/directory-link.lua ================================================ local git_utils = require("nvim-tree.git.utils") local utils = require("nvim-tree.utils") local DirectoryNode = require("nvim-tree.node.directory") local LinkNode = require("nvim-tree.node.link") ---@class (exact) DirectoryLinkNode: DirectoryNode, LinkNode local DirectoryLinkNode = DirectoryNode:extend() DirectoryLinkNode:implement(LinkNode) ---@class DirectoryLinkNode ---@overload fun(args: LinkNodeArgs): DirectoryLinkNode ---@protected ---@param args LinkNodeArgs function DirectoryLinkNode:new(args) LinkNode.new(self, args) -- create DirectoryNode with watcher on link_to local absolute_path = args.absolute_path args.absolute_path = args.link_to DirectoryLinkNode.super.new(self, args) self.type = "link" -- reset absolute path to the link itself self.absolute_path = absolute_path end function DirectoryLinkNode:destroy() DirectoryNode.destroy(self) end ---Update the directory git_status of link target and the file status of the link itself ---@param parent_ignored boolean ---@param project nvim_tree.git.Project? function DirectoryLinkNode:update_git_status(parent_ignored, project) self.git_status = git_utils.git_status_dir(parent_ignored, project, self.link_to, self.absolute_path) end ---@return nvim_tree.api.highlighted_string name function DirectoryLinkNode:highlighted_icon() if not self.explorer.opts.renderer.icons.show.folder then return self:highlighted_icon_empty() end local str, hl if self.open then str = self.explorer.opts.renderer.icons.glyphs.folder.symlink_open hl = "NvimTreeOpenedFolderIcon" else str = self.explorer.opts.renderer.icons.glyphs.folder.symlink hl = "NvimTreeClosedFolderIcon" end return { str = str, hl = { hl } } end ---Maybe override name with arrow ---@return nvim_tree.api.highlighted_string name function DirectoryLinkNode:highlighted_name() local name = DirectoryNode.highlighted_name(self) if self.explorer.opts.renderer.symlink_destination then local link_to = utils.path_relative(self.link_to, self.explorer.absolute_path) if self.explorer.opts.renderer.add_trailing then link_to = utils.path_add_trailing(link_to) end name.str = string.format("%s%s%s", name.str, self.explorer.opts.renderer.icons.symlink_arrow, link_to) name.hl = { "NvimTreeSymlinkFolderName" } end return name end ---Create a sanitized partial copy of a node, populating children recursively. ---@param api_nodes table? optional map of uids to api node to populate ---@return nvim_tree.api.DirectoryLinkNode cloned function DirectoryLinkNode:clone(api_nodes) local clone = DirectoryNode.clone(self, api_nodes) --[[@as nvim_tree.api.DirectoryLinkNode]] clone.link_to = self.link_to clone.fs_stat_target = self.fs_stat_target return clone end return DirectoryLinkNode ================================================ FILE: lua/nvim-tree/node/directory.lua ================================================ local git_utils = require("nvim-tree.git.utils") local icons = require("nvim-tree.renderer.components.devicons") local notify = require("nvim-tree.notify") local Iterator = require("nvim-tree.iterators.node-iterator") local Node = require("nvim-tree.node") ---@class (exact) DirectoryNode: Node ---@field has_children boolean ---@field group_next DirectoryNode? -- If node is grouped, this points to the next child dir/link node ---@field nodes Node[] ---@field open boolean ---@field hidden_stats table? -- Each field of this table is a key for source and value for count ---@field private watcher Watcher? local DirectoryNode = Node:extend() ---@class DirectoryNode ---@overload fun(args: NodeArgs): DirectoryNode ---@protected ---@param args NodeArgs function DirectoryNode:new(args) DirectoryNode.super.new(self, args) local handle = vim.loop.fs_scandir(args.absolute_path) local has_children = handle and vim.loop.fs_scandir_next(handle) ~= nil or false self.type = "directory" self.has_children = has_children self.group_next = nil self.nodes = {} self.open = false self.hidden_stats = nil self.watcher = require("nvim-tree.explorer.watch").create_watcher(self) end function DirectoryNode:destroy() self:destroy_watcher() if self.nodes then for _, node in pairs(self.nodes) do node:destroy() end end Node.destroy(self) end ---Halt and remove the watcher for this node function DirectoryNode:destroy_watcher() if self.watcher then self.watcher:destroy() self.watcher = nil end end ---Update the git_status of the directory ---@param parent_ignored boolean ---@param project nvim_tree.git.Project? function DirectoryNode:update_git_status(parent_ignored, project) self.git_status = git_utils.git_status_dir(parent_ignored, project, self.absolute_path, nil) end ---@return nvim_tree.git.XY[]? function DirectoryNode:get_git_xy() if not self.git_status or not self.explorer.opts.git.show_on_dirs then return nil end local xys = {} if not self:last_group_node().open or self.explorer.opts.git.show_on_open_dirs then -- dir is closed or we should show on open_dirs if self.git_status.file ~= nil then table.insert(xys, self.git_status.file) end if self.git_status.dir ~= nil then if self.git_status.dir.direct ~= nil then for _, s in pairs(self.git_status.dir.direct) do table.insert(xys, s) end end if self.git_status.dir.indirect ~= nil then for _, s in pairs(self.git_status.dir.indirect) do table.insert(xys, s) end end end else -- dir is open and we shouldn't show on open_dirs if self.git_status.file ~= nil then table.insert(xys, self.git_status.file) end if self.git_status.dir ~= nil and self.git_status.dir.direct ~= nil then local deleted = { [" D"] = true, ["D "] = true, ["RD"] = true, ["DD"] = true, } for _, s in pairs(self.git_status.dir.direct) do if deleted[s] then table.insert(xys, s) end end end end if #xys == 0 then return nil else return xys end end -- If node is grouped, return the last node in the group. Otherwise, return the given node. ---@return DirectoryNode function DirectoryNode:last_group_node() return self.group_next and self.group_next:last_group_node() or self end ---Return the one and only one child directory ---@return DirectoryNode? function DirectoryNode:single_child_directory() if #self.nodes == 1 then return self.nodes[1]:as(DirectoryNode) end end ---@private -- Toggle group empty folders function DirectoryNode:toggle_group_folders() local is_grouped = self.group_next ~= nil if is_grouped then self:ungroup_empty_folders() else self:group_empty_folders() end end ---Group empty folders -- Recursively group nodes ---@private ---@return Node[] function DirectoryNode:group_empty_folders() local single_child = self:single_child_directory() if self.explorer.opts.renderer.group_empty and self.parent and single_child then self.group_next = single_child local ns = single_child:group_empty_folders() self.nodes = ns or {} return ns end return self.nodes end ---Ungroup empty folders -- If a node is grouped, ungroup it: put node.group_next to the node.nodes and set node.group_next to nil ---@private function DirectoryNode:ungroup_empty_folders() if self.group_next then self.group_next:ungroup_empty_folders() self.nodes = { self.group_next } self.group_next = nil end end ---@param toggle_group boolean? function DirectoryNode:expand_or_collapse(toggle_group) toggle_group = toggle_group or false if self.has_children then self.has_children = false end if #self.nodes == 0 then self.explorer:expand_dir_node(self) end local head_node = self:get_parent_of_group() or self if toggle_group then head_node:toggle_group_folders() end local open = self:last_group_node().open local next_open if toggle_group then next_open = open else next_open = not open end local node = head_node while node do node.open = next_open node = node.group_next end self.explorer.renderer:draw() end ---@return nvim_tree.api.highlighted_string icon function DirectoryNode:highlighted_icon() if not self.explorer.opts.renderer.icons.show.folder then return self:highlighted_icon_empty() end local str, hl -- devicon if enabled and available if self.explorer.opts.renderer.icons.web_devicons.folder.enable then str, hl = icons.get_icon(self.name) if not self.explorer.opts.renderer.icons.web_devicons.folder.color then hl = nil end end -- default icon from opts if not str then if #self.nodes ~= 0 or self.has_children then if self.open then str = self.explorer.opts.renderer.icons.glyphs.folder.open else str = self.explorer.opts.renderer.icons.glyphs.folder.default end else if self.open then str = self.explorer.opts.renderer.icons.glyphs.folder.empty_open else str = self.explorer.opts.renderer.icons.glyphs.folder.empty end end end -- default hl if not hl then if self.open then hl = "NvimTreeOpenedFolderIcon" else hl = "NvimTreeClosedFolderIcon" end end return { str = str, hl = { hl } } end ---@return nvim_tree.api.highlighted_string icon function DirectoryNode:highlighted_name() local str, hl local name = self.name local next = self.group_next while next do name = string.format("%s/%s", name, next.name) next = next.group_next end if self.group_next and type(self.explorer.opts.renderer.group_empty) == "function" then local new_name = self.explorer.opts.renderer.group_empty(name) if type(new_name) == "string" then name = new_name else notify.warn(string.format("Invalid return type for field renderer.group_empty. Expected string, got %s", type(new_name))) end end str = string.format("%s%s", name, self.explorer.opts.renderer.add_trailing and "/" or "") hl = "NvimTreeFolderName" if vim.tbl_contains(self.explorer.opts.renderer.special_files, self.absolute_path) or vim.tbl_contains(self.explorer.opts.renderer.special_files, self.name) then hl = "NvimTreeSpecialFolderName" elseif self.open then hl = "NvimTreeOpenedFolderName" elseif #self.nodes == 0 and not self.has_children then hl = "NvimTreeEmptyFolderName" end return { str = str, hl = { hl } } end ---Create a sanitized partial copy of a node, populating children recursively. ---@param api_nodes table? optional map of uids to api node to populate ---@return nvim_tree.api.DirectoryNode cloned function DirectoryNode:clone(api_nodes) local clone = Node.clone(self, api_nodes) --[[@as nvim_tree.api.DirectoryNode]] clone.has_children = self.has_children clone.nodes = {} clone.open = self.open local clone_child for _, child in ipairs(self.nodes) do clone_child = child:clone(api_nodes) clone_child.parent = clone table.insert(clone.nodes, clone_child) end return clone end ---@private ---@param should_descend fun(expansion_count: integer, node: Node): boolean ---@return fun(expansion_count: integer, node: Node): boolean function DirectoryNode:limit_folder_discovery(should_descend) local MAX_FOLDER_DISCOVERY = self.explorer.opts.actions.expand_all.max_folder_discovery return function(expansion_count, node) local should_halt = expansion_count >= MAX_FOLDER_DISCOVERY if should_halt then notify.warn("expansion iteration was halted after " .. MAX_FOLDER_DISCOVERY .. " discovered folders") return false end return should_descend(expansion_count, node) end end ---@param expansion_count integer ---@param should_descend fun(expansion_count: integer, node: Node): boolean ---@return boolean function DirectoryNode:should_expand(expansion_count, should_descend) if not self.open and should_descend(expansion_count, self) then if #self.nodes == 0 then self.explorer:expand_dir_node(self) -- populate node.group_next end if self.group_next then local expand_next = self.group_next:should_expand(expansion_count, should_descend) if expand_next then self.open = true end return expand_next else return true end end return false end ---@param list string[] ---@return table local function to_lookup_table(list) local table = {} for _, element in ipairs(list) do table[element] = true end return table end ---@param _ integer ---@param node Node ---@return boolean local function descend_until_empty(_, node) local EXCLUDE = to_lookup_table(node.explorer.opts.actions.expand_all.exclude) local should_exclude = EXCLUDE[node.name] return not should_exclude end ---@param expand_opts? nvim_tree.api.node.expand.Opts function DirectoryNode:expand(expand_opts) local expansion_count = 0 local should_descend = self:limit_folder_discovery((expand_opts and expand_opts.expand_until) or descend_until_empty) if self.parent and self.nodes and not self.open then expansion_count = expansion_count + 1 self:expand_dir_node() end Iterator.builder(self.nodes) :hidden() :applier(function(node) if node:should_expand(expansion_count, should_descend) then expansion_count = expansion_count + 1 node:expand_dir_node() end end) :recursor(function(node) if not should_descend(expansion_count, node) then return nil end if node.group_next then return { node.group_next } end if node.open and node.nodes then return node.nodes end return nil end) :iterate() self.explorer.renderer:draw() end function DirectoryNode:expand_dir_node() local node = self:last_group_node() node.open = true if #node.nodes == 0 then self.explorer:expand_dir_node(node) end end return DirectoryNode ================================================ FILE: lua/nvim-tree/node/factory.lua ================================================ local DirectoryLinkNode = require("nvim-tree.node.directory-link") local DirectoryNode = require("nvim-tree.node.directory") local FileLinkNode = require("nvim-tree.node.file-link") local FileNode = require("nvim-tree.node.file") local Watcher = require("nvim-tree.watcher") local M = {} ---Factory function to create the appropriate Node ---nil on invalid stat or invalid link target stat ---@param args NodeArgs ---@return Node? function M.create(args) if not args.fs_stat then return nil end if args.fs_stat.type == "directory" then -- directory must be readable and enumerable if vim.loop.fs_access(args.absolute_path, "R") and Watcher.is_fs_event_capable(args.absolute_path) then return DirectoryNode(args) end elseif args.fs_stat.type == "file" then return FileNode(args) elseif args.fs_stat.type == "link" then -- link target path and stat must resolve local link_to = vim.loop.fs_realpath(args.absolute_path) local link_to_stat = link_to and vim.loop.fs_stat(link_to) if not link_to or not link_to_stat then return end ---@cast args LinkNodeArgs args.link_to = link_to args.fs_stat_target = link_to_stat -- choose directory or file if link_to_stat.type == "directory" then return DirectoryLinkNode(args) else return FileLinkNode(args) end end return nil end return M ================================================ FILE: lua/nvim-tree/node/file-link.lua ================================================ local git_utils = require("nvim-tree.git.utils") local utils = require("nvim-tree.utils") local FileNode = require("nvim-tree.node.file") local LinkNode = require("nvim-tree.node.link") ---@class (exact) FileLinkNode: FileNode, LinkNode local FileLinkNode = FileNode:extend() FileLinkNode:implement(LinkNode) ---@class FileLinkNode ---@overload fun(args: LinkNodeArgs): FileLinkNode ---@protected ---@param args LinkNodeArgs function FileLinkNode:new(args) LinkNode.new(self, args) FileLinkNode.super.new(self, args) self.type = "link" end function FileLinkNode:destroy() FileNode.destroy(self) end ---Update the git_status of the target otherwise the link itself ---@param parent_ignored boolean ---@param project nvim_tree.git.Project? function FileLinkNode:update_git_status(parent_ignored, project) self.git_status = git_utils.git_status_file(parent_ignored, project, self.link_to, self.absolute_path) end ---@return nvim_tree.api.highlighted_string icon function FileLinkNode:highlighted_icon() if not self.explorer.opts.renderer.icons.show.file then return self:highlighted_icon_empty() end local str, hl -- default icon from opts str = self.explorer.opts.renderer.icons.glyphs.symlink hl = "NvimTreeSymlinkIcon" return { str = str, hl = { hl } } end ---@return nvim_tree.api.highlighted_string name function FileLinkNode:highlighted_name() local str = self.name if self.explorer.opts.renderer.symlink_destination then local link_to = utils.path_relative(self.link_to, self.explorer.absolute_path) str = string.format("%s%s%s", str, self.explorer.opts.renderer.icons.symlink_arrow, link_to) end return { str = str, hl = { "NvimTreeSymlink" } } end ---Create a sanitized partial copy of a node ---@param api_nodes table? optional map of uids to api node to populate ---@return nvim_tree.api.FileLinkNode cloned function FileLinkNode:clone(api_nodes) local clone = FileNode.clone(self, api_nodes) --[[@as nvim_tree.api.FileLinkNode]] clone.link_to = self.link_to clone.fs_stat_target = self.fs_stat_target return clone end return FileLinkNode ================================================ FILE: lua/nvim-tree/node/file.lua ================================================ local git_utils = require("nvim-tree.git.utils") local icons = require("nvim-tree.renderer.components.devicons") local utils = require("nvim-tree.utils") local Node = require("nvim-tree.node") local PICTURE_MAP = { jpg = true, jpeg = true, png = true, gif = true, webp = true, jxl = true, } ---@class (exact) FileNode: Node ---@field extension string local FileNode = Node:extend() ---@class FileNode ---@overload fun(args: NodeArgs): FileNode ---@protected ---@param args NodeArgs function FileNode:new(args) FileNode.super.new(self, args) self.type = "file" self.extension = string.match(args.name, ".?[^.]+%.(.*)") or "" self.executable = utils.is_executable(args.absolute_path) end function FileNode:destroy() Node.destroy(self) end ---Update the GitStatus of the file ---@param parent_ignored boolean ---@param project nvim_tree.git.Project? function FileNode:update_git_status(parent_ignored, project) self.git_status = git_utils.git_status_file(parent_ignored, project, self.absolute_path, nil) end ---@return nvim_tree.git.XY[]? function FileNode:get_git_xy() if not self.git_status then return nil end return self.git_status.file and { self.git_status.file } end ---@return nvim_tree.api.highlighted_string icon function FileNode:highlighted_icon() if not self.explorer.opts.renderer.icons.show.file then return self:highlighted_icon_empty() end local str, hl -- devicon if enabled and available, fallback to default if self.explorer.opts.renderer.icons.web_devicons.file.enable then str, hl = icons.get_icon(self.name, nil, { default = true }) if not self.explorer.opts.renderer.icons.web_devicons.file.color then hl = nil end end -- default icon from opts if not str then str = self.explorer.opts.renderer.icons.glyphs.default end -- default hl if not hl then hl = "NvimTreeFileIcon" end return { str = str, hl = { hl } } end ---@return nvim_tree.api.highlighted_string name function FileNode:highlighted_name() local hl if vim.tbl_contains(self.explorer.opts.renderer.special_files, self.absolute_path) or vim.tbl_contains(self.explorer.opts.renderer.special_files, self.name) then hl = "NvimTreeSpecialFile" elseif self.executable then hl = "NvimTreeExecFile" elseif PICTURE_MAP[self.extension] then hl = "NvimTreeImageFile" end return { str = self.name, hl = { hl } } end ---Create a sanitized partial copy of a node ---@param api_nodes table? optional map of uids to api node to populate ---@return nvim_tree.api.FileNode cloned function FileNode:clone(api_nodes) local clone = Node.clone(self, api_nodes) --[[@as nvim_tree.api.FileNode]] clone.extension = self.extension return clone end return FileNode ================================================ FILE: lua/nvim-tree/node/init.lua ================================================ local Class = require("nvim-tree.classic") ---Abstract Node class. ---@class (exact) Node: nvim_tree.Class ---@field uid_node number vim.loop.hrtime() at construction time ---@field type "file" | "directory" | "link" uv.fs_stat.result.type ---@field explorer Explorer ---@field absolute_path string ---@field executable boolean ---@field fs_stat uv.fs_stat.result? ---@field git_status nvim_tree.git.Status? ---@field hidden boolean ---@field name string ---@field parent DirectoryNode? ---@field diag_status DiagStatus? ---@field private is_dot boolean cached is_dotfile local Node = Class:extend() ---@class (exact) NodeArgs ---@field explorer Explorer ---@field parent DirectoryNode? ---@field absolute_path string ---@field name string ---@field fs_stat uv.fs_stat.result? ---@protected ---@param args NodeArgs function Node:new(args) self.uid_node = vim.loop.hrtime() self.explorer = args.explorer self.absolute_path = args.absolute_path self.executable = false self.fs_stat = args.fs_stat self.git_status = nil self.hidden = false self.name = args.name self.parent = args.parent self.diag_status = nil self.is_dot = false end function Node:destroy() end ---Update the git_status of the node ---Abstract ---@param parent_ignored boolean ---@param project nvim_tree.git.Project? function Node:update_git_status(parent_ignored, project) self:nop(parent_ignored, project) end ---Short-format statuses ---@return nvim_tree.git.XY[]? function Node:get_git_xy() end ---@return boolean function Node:is_git_ignored() return self.git_status ~= nil and self.git_status.file == "!!" end ---Node or one of its parents begins with a dot ---@return boolean function Node:is_dotfile() if self.is_dot or (self.name and (self.name:sub(1, 1) == ".")) or (self.parent and self.parent:is_dotfile()) then self.is_dot = true return true end return false end ---Get the highest parent of grouped nodes, nil when not grouped ---@return DirectoryNode? function Node:get_parent_of_group() if not self.parent or not self.parent.group_next then return nil end local node = self.parent while node do if node.parent and node.parent.group_next then node = node.parent else return node end end end ---Empty highlighted icon ---@protected ---@return nvim_tree.api.highlighted_string icon function Node:highlighted_icon_empty() return { str = "", hl = {} } end ---Highlighted icon for the node ---Empty for base Node ---@return nvim_tree.api.highlighted_string icon function Node:highlighted_icon() return self:highlighted_icon_empty() end ---Empty highlighted name ---@protected ---@return nvim_tree.api.highlighted_string name function Node:highlighted_name_empty() return { str = "", hl = {} } end ---Highlighted name for the node ---Empty for base Node ---@return nvim_tree.api.highlighted_string name function Node:highlighted_name() return self:highlighted_name_empty() end ---Create a sanitized partial copy of a node, populating children recursively. ---@param api_nodes table? optional map of uids to api node to populate ---@return nvim_tree.api.Node cloned function Node:clone(api_nodes) ---@type nvim_tree.api.Node local clone = { uid_node = self.uid_node, type = self.type, absolute_path = self.absolute_path, executable = self.executable, fs_stat = self.fs_stat, git_status = self.git_status, hidden = self.hidden, name = self.name, parent = nil, diag_severity = self.diag_status and self.diag_status.value or nil, } if api_nodes then api_nodes[self.uid_node] = clone end return clone end ---@param expansion_count integer ---@param should_descend fun(expansion_count: integer, node: Node): boolean ---@return boolean function Node:should_expand(expansion_count, should_descend) self:nop(expansion_count, should_descend) return false end ---@param expand_opts? nvim_tree.api.node.expand.Opts function Node:expand(expand_opts) if self.parent then self.parent:expand(expand_opts) end end return Node ================================================ FILE: lua/nvim-tree/node/link.lua ================================================ local Class = require("nvim-tree.classic") ---@class (exact) LinkNode: nvim_tree.Class ---@field link_to string ---@field fs_stat_target uv.fs_stat.result local LinkNode = Class:extend() ---@class (exact) LinkNodeArgs: NodeArgs ---@field link_to string ---@field fs_stat_target uv.fs_stat.result ---@protected ---@param args LinkNodeArgs function LinkNode:new(args) self.link_to = args.link_to self.fs_stat_target = args.fs_stat_target end return LinkNode ================================================ FILE: lua/nvim-tree/node/root.lua ================================================ local DirectoryNode = require("nvim-tree.node.directory") ---@class (exact) RootNode: DirectoryNode local RootNode = DirectoryNode:extend() ---@class RootNode ---@overload fun(args: NodeArgs): RootNode ---@protected ---@param args NodeArgs function RootNode:new(args) RootNode.super.new(self, args) end ---Root is never a dotfile ---@return boolean function RootNode:is_dotfile() return false end function RootNode:destroy() DirectoryNode.destroy(self) end ---Create a sanitized partial copy of a node, populating children recursively. ---@param api_nodes table? optional map of uids to api node to populate ---@return nvim_tree.api.RootNode cloned function RootNode:clone(api_nodes) local clone = DirectoryNode.clone(self, api_nodes) --[[@as nvim_tree.api.RootNode]] return clone end return RootNode ================================================ FILE: lua/nvim-tree/notify.lua ================================================ local M = {} local config = { threshold = vim.log.levels.INFO, absolute_path = true, } local title_support ---@return boolean function M.supports_title() if title_support == nil then title_support = (package.loaded.notify and (vim.notify == require("notify") or vim.notify == require("notify").notify)) or (package.loaded.noice and (vim.notify == require("noice").notify or vim.notify == require("noice.source.notify").notify)) or (package.loaded.notifier and require("notifier.config").has_component("nvim")) or false end return title_support end local modes = { { name = "trace", level = vim.log.levels.TRACE }, { name = "debug", level = vim.log.levels.DEBUG }, { name = "info", level = vim.log.levels.INFO }, { name = "warn", level = vim.log.levels.WARN }, { name = "error", level = vim.log.levels.ERROR }, } do local dispatch = function(level, msg) if level < config.threshold or not msg then return end vim.schedule(function() if not M.supports_title() then -- add title to the message, with a newline if the message is multiline msg = string.format("[NvimTree]%s%s", (msg:match("\n") and "\n" or " "), msg) end vim.notify(msg, level, { title = "NvimTree" }) end) end for _, x in ipairs(modes) do M[x.name] = function(msg) return dispatch(x.level, msg) end end end ---@param path string ---@return string function M.render_path(path) if config.absolute_path then return path else return vim.fn.fnamemodify(path .. "/", ":h:t") end end function M.setup(opts) opts = opts or {} config.threshold = opts.notify.threshold or vim.log.levels.INFO config.absolute_path = opts.notify.absolute_path end return M ================================================ FILE: lua/nvim-tree/renderer/builder.lua ================================================ local notify = require("nvim-tree.notify") local utils = require("nvim-tree.utils") local view = require("nvim-tree.view") local Class = require("nvim-tree.classic") local DirectoryNode = require("nvim-tree.node.directory") local BookmarkDecorator = require("nvim-tree.renderer.decorator.bookmarks") local CopiedDecorator = require("nvim-tree.renderer.decorator.copied") local CutDecorator = require("nvim-tree.renderer.decorator.cut") local DiagnosticsDecorator = require("nvim-tree.renderer.decorator.diagnostics") local GitDecorator = require("nvim-tree.renderer.decorator.git") local HiddenDecorator = require("nvim-tree.renderer.decorator.hidden") local ModifiedDecorator = require("nvim-tree.renderer.decorator.modified") local OpenDecorator = require("nvim-tree.renderer.decorator.opened") local Decorator = require("nvim-tree.renderer.decorator") local BuiltinDecorator = require("nvim-tree.renderer.decorator.builtin") local pad = require("nvim-tree.renderer.components.padding") -- Builtin Decorators ---@type table local BUILTIN_DECORATORS = { Git = GitDecorator, Open = OpenDecorator, Hidden = HiddenDecorator, Modified = ModifiedDecorator, Bookmark = BookmarkDecorator, Diagnostics = DiagnosticsDecorator, Copied = CopiedDecorator, Cut = CutDecorator, } ---@class (exact) Builder ---@field lines string[] includes icons etc. ---@field hl_range_args HighlightRangeArgs[] highlights for lines ---@field signs string[] line signs ---@field extmarks table[] extra marks for right icon placement ---@field virtual_lines table[] virtual lines for hidden count display ---@field private explorer Explorer ---@field private index number ---@field private depth number ---@field private combined_groups table combined group names ---@field private markers boolean[] indent markers ---@field private decorators Decorator[] ---@field private hidden_display fun(node: Node): string|nil ---@field private api_nodes table? optional map of uids to api node for user decorators local Builder = Class:extend() ---@class Builder ---@overload fun(args: BuilderArgs): Builder ---@class (exact) BuilderArgs ---@field explorer Explorer ---@protected ---@param args BuilderArgs function Builder:new(args) self.explorer = args.explorer self.index = 0 self.depth = 0 self.hl_range_args = {} self.combined_groups = {} self.lines = {} self.markers = {} self.signs = {} self.extmarks = {} self.virtual_lines = {} self.decorators = {} self.hidden_display = Builder:setup_hidden_display_function(self.explorer.opts) -- instantiate all the builtin and user decorator instances local builtin, user for _, d in ipairs(self.explorer.opts.renderer.decorators) do ---@type BuiltinDecorator builtin = BUILTIN_DECORATORS[d] ---@type Decorator user = type(d) == "table" and type(d.as) == "function" and d:as(Decorator) if builtin then table.insert(self.decorators, builtin({ explorer = self.explorer })) elseif user then table.insert(self.decorators, user()) -- clone user nodes once if not self.api_nodes then self.api_nodes = {} self.explorer:clone(self.api_nodes) end end end end ---Insert ranged highlight groups into self.highlights ---@private ---@param groups string[] ---@param start number ---@param end_ number|nil function Builder:insert_highlight(groups, start, end_) 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 ---@private ---@param highlighted_strings nvim_tree.api.highlighted_string[] ---@return string function Builder:unwrap_highlighted_strings(highlighted_strings) if not highlighted_strings then return "" end local string = "" for _, v in ipairs(highlighted_strings) do if #v.str > 0 then if v.hl and type(v.hl) == "table" then self:insert_highlight(v.hl, #string, #string + #v.str) end string = string.format("%s%s", string, v.str) end end return string end ---@private ---@param indent_markers nvim_tree.api.highlighted_string[] ---@param arrows? nvim_tree.api.highlighted_string[] ---@param icon nvim_tree.api.highlighted_string ---@param name nvim_tree.api.highlighted_string ---@param node Node ---@return nvim_tree.api.highlighted_string[] function Builder:format_line(indent_markers, arrows, icon, name, node) local added_len = 0 local function add_to_end(t1, t2) if not t2 or vim.tbl_isempty(t2) then return end for _, v in ipairs(t2) do if added_len > 0 then table.insert(t1, { str = self.explorer.opts.renderer.icons.padding.icon }) end table.insert(t1, v) end -- first add_to_end don't need padding -- hence added_len is calculated at the end to be used next time added_len = 0 for _, v in ipairs(t2) do added_len = added_len + #v.str end end local api_node = self.api_nodes and self.api_nodes[node.uid_node] local b local line = { indent_markers, arrows } add_to_end(line, { icon }) for _, d in ipairs(self.decorators) do b = d:as(BuiltinDecorator) if b then add_to_end(line, b:icons_before(node)) elseif api_node then add_to_end(line, d:icons_before(api_node)) end end add_to_end(line, { name }) for _, d in ipairs(self.decorators) do b = d:as(BuiltinDecorator) if b then add_to_end(line, b:icons_after(node)) elseif api_node then add_to_end(line, d:icons_after(api_node)) end end local rights = {} for _, d in ipairs(self.decorators) do b = d:as(BuiltinDecorator) if b then add_to_end(rights, b:icons_right_align(node)) elseif api_node then add_to_end(rights, d:icons_right_align(api_node)) end end if #rights > 0 then self.extmarks[self.index] = rights end return line end ---@private ---@param node Node function Builder:build_signs(node) local api_node = self.api_nodes and self.api_nodes[node.uid_node] -- first in priority order local d, b, sign_name for i = #self.decorators, 1, -1 do d = self.decorators[i] b = d:as(BuiltinDecorator) if b then sign_name = b:sign_name(node) elseif api_node then sign_name = d:sign_name(api_node) end if sign_name then self.signs[self.index] = sign_name break end end end ---Create a highlight group for groups with later groups overriding previous. ---Combined group name is less than the 200 byte limit of highlight group names ---@private ---@param groups string[] highlight group names ---@return string group_name "NvimTreeCombinedHL" .. sha256 function Builder:create_combined_group(groups) local combined_name = string.format("NvimTreeCombinedHL%s", vim.fn.sha256(table.concat(groups))) -- only create if necessary if not self.combined_groups[combined_name] then self.combined_groups[combined_name] = true local combined_hl = {} -- build the highlight, overriding values for _, group in ipairs(groups) do local hl = vim.api.nvim_get_hl(0, { name = group, link = false }) combined_hl = vim.tbl_extend("force", combined_hl, hl) end -- add highlights to the global namespace vim.api.nvim_set_hl(0, combined_name, combined_hl) table.insert(self.combined_groups, combined_name) end return combined_name end ---Calculate decorated icon and name for a node. ---A combined highlight group will be created when there is more than one highlight. ---A highlight group is always calculated and upserted for the case of highlights changing. ---@private ---@param node Node ---@return nvim_tree.api.highlighted_string icon ---@return nvim_tree.api.highlighted_string name function Builder:icon_name_decorated(node) local api_node = self.api_nodes and self.api_nodes[node.uid_node] -- base case local icon = node:highlighted_icon() local name = node:highlighted_name() -- calculate node icon and all decorated highlight groups local icon_groups = {} local name_groups = {} local hl_icon, hl_name local b for _, d in ipairs(self.decorators) do -- maybe override icon b = d:as(BuiltinDecorator) if b then icon = b:icon_node(node) or icon hl_icon, hl_name = b:highlight_group_icon_name(node) elseif api_node then icon = d:icon_node(api_node) or icon hl_icon, hl_name = d:highlight_group_icon_name(api_node) end table.insert(icon_groups, hl_icon) table.insert(name_groups, hl_name) end -- add one or many icon groups if #icon_groups > 1 then table.insert(icon.hl, self:create_combined_group(icon_groups)) else table.insert(icon.hl, icon_groups[1]) end -- add one or many name groups if #name_groups > 1 then table.insert(name.hl, self:create_combined_group(name_groups)) else table.insert(name.hl, name_groups[1]) end return icon, name end ---Insert node line into self.lines, calling Builder:build_lines for each directory ---@private ---@param node Node ---@param idx integer line number starting at 1 ---@param num_children integer of node function Builder:build_line(node, idx, num_children) -- various components local indent_markers = pad.get_indent_markers(self.depth, idx, num_children, node, self.markers) local arrows = pad.get_arrows(node) -- decorated node icon and name local icon, name = self:icon_name_decorated(node) local line = self:format_line(indent_markers, arrows, icon, name, node) table.insert(self.lines, self:unwrap_highlighted_strings(line)) self.index = self.index + 1 local dir = node:as(DirectoryNode) if dir then dir = dir:last_group_node() if dir.open then self.depth = self.depth + 1 self:build_lines(dir) self.depth = self.depth - 1 end end end ---Add virtual lines for rendering hidden count information per node ---@private function Builder:add_hidden_count_string(node, idx, num_children) if not node.open then return end local hidden_count_string = self.hidden_display(node.hidden_stats) if hidden_count_string and hidden_count_string ~= "" then local indent_markers = pad.get_indent_markers(self.depth, idx or 0, num_children or 0, node, self.markers, 1) local indent_width = self.explorer.opts.renderer.indent_width local indent_padding = string.rep(" ", indent_width) local indent_string = indent_padding .. indent_markers.str local line_nr = #self.lines - 1 self.virtual_lines[line_nr] = self.virtual_lines[line_nr] or {} -- NOTE: We are inserting in depth order because of current traversal -- if we change the traversal, we might need to sort by depth before rendering `self.virtual_lines` -- to maintain proper ordering of parent and child folder hidden count info. table.insert(self.virtual_lines[line_nr], { { indent_string, indent_markers.hl }, { string.rep(indent_padding, (node.parent == nil and 0 or 1)) .. hidden_count_string, "NvimTreeHiddenDisplay" }, }) end end ---Number of visible nodes ---@private ---@param nodes Node[] ---@return integer function Builder:num_visible(nodes) if not self.explorer.live_filter.filter then return #nodes end local i = 0 for _, n in pairs(nodes) do if not n.hidden then i = i + 1 end end return i end ---@private function Builder:build_lines(node) if not node then node = self.explorer end local num_children = self:num_visible(node.nodes) local idx = 1 for _, n in ipairs(node.nodes) do if not n.hidden then self:build_signs(n) self:build_line(n, idx, num_children) idx = idx + 1 end end self:add_hidden_count_string(node) end ---@private ---@param root_label function|string ---@return string function Builder:format_root_name(root_label) if type(root_label) == "function" then local label = root_label(self.explorer.absolute_path) if type(label) == "string" then return label end elseif type(root_label) == "string" then return utils.path_remove_trailing(vim.fn.fnamemodify(self.explorer.absolute_path, root_label)) end return "???" end ---@private function Builder:build_header() if view.is_root_folder_visible(self.explorer.absolute_path) then local root_name = self:format_root_name(self.explorer.opts.renderer.root_folder_label) table.insert(self.lines, root_name) self:insert_highlight({ "NvimTreeRootFolder" }, 0, string.len(root_name)) self.index = 1 end if self.explorer.live_filter.filter then local filter_line = string.format("%s/%s/", self.explorer.opts.live_filter.prefix, self.explorer.live_filter.filter) table.insert(self.lines, filter_line) local prefix_length = string.len(self.explorer.opts.live_filter.prefix) self:insert_highlight({ "NvimTreeLiveFilterPrefix" }, 0, prefix_length) self:insert_highlight({ "NvimTreeLiveFilterValue" }, prefix_length, string.len(filter_line)) self.index = self.index + 1 end end ---Sanitize lines for rendering. ---Replace newlines with literal \n ---@private function Builder:sanitize_lines() self.lines = vim.tbl_map(function(line) return line and line:gsub("\n", "\\n") or "" end, self.lines) end ---Build all lines with highlights and signs ---@return Builder function Builder:build() self:build_header() self:build_lines() self:sanitize_lines() return self end ---@private ---@param opts table ---@return fun(node: Node): string|nil function Builder:setup_hidden_display_function(opts) local hidden_display = opts.renderer.hidden_display -- options are already validated, so ´hidden_display´ can ONLY be `string` or `function` if type(hidden_display) == "string" then if type(hidden_display) == "string" then if hidden_display == "none" then return function() return nil end elseif hidden_display == "simple" then return function(hidden_stats) return utils.default_format_hidden_count(hidden_stats, true) end else -- "all" return function(hidden_stats) return utils.default_format_hidden_count(hidden_stats, false) end end else -- "function return function(hidden_stats) -- In case of missing field such as live_filter we zero it, otherwise keep field as is hidden_stats = vim.tbl_deep_extend("force", { live_filter = 0, git = 0, buf = 0, dotfile = 0, custom = 0, bookmark = 0, }, hidden_stats or {}) local ok, result = pcall(hidden_display, hidden_stats) if not ok then notify.warn( "Problem occurred in the function ``opts.renderer.hidden_display`` see nvim-tree.renderer.hidden_display on :h nvim-tree") return nil end return result end end end return Builder ================================================ FILE: lua/nvim-tree/renderer/components/devicons.lua ================================================ ---@alias devicons_get_icon fun(name: string, ext: string?, opts: table?): string?, string? ---@alias devicons_setup fun(opts: table?) ---@class (strict) DevIcons? ---@field setup devicons_setup ---@field get_icon devicons_get_icon local devicons local M = {} ---Wrapper around nvim-web-devicons, nils if devicons not available ---@type devicons_get_icon function M.get_icon(name, ext, opts) if devicons then return devicons.get_icon(name, ext, opts) else return nil, nil end end ---Attempt to use nvim-web-devicons if present and enabled for file or folder ---@param opts table function M.setup(opts) if opts.renderer.icons.show.file or opts.renderer.icons.show.folder then local ok, di = pcall(require, "nvim-web-devicons") if ok then devicons = di --[[@as DevIcons]] -- does nothing if already called i.e. doesn't clobber previous user setup devicons.setup() end end end return M ================================================ FILE: lua/nvim-tree/renderer/components/full-name.lua ================================================ local M = {} local utils = require("nvim-tree.utils") local view = require("nvim-tree.view") local function hide(win) if win then if vim.api.nvim_win_is_valid(win) then vim.api.nvim_win_close(win, true) end end end -- reduce signcolumn/foldcolumn from window width local function effective_win_width() local win_width = vim.fn.winwidth(0) -- return zero if the window cannot be found local win_id = vim.fn.win_getid() if win_id == 0 then return win_width end -- if the window does not exist the result is an empty list local win_info = vim.fn.getwininfo(win_id) -- check if result table is empty if next(win_info) == nil then return win_width end return win_width - win_info[1].textoff end local function show(opts) local line_nr = vim.api.nvim_win_get_cursor(0)[1] if vim.wo.wrap then return end -- only work for left tree if vim.api.nvim_win_get_position(0)[2] ~= 0 then return end local line = vim.fn.getline(".") local leftcol = vim.fn.winsaveview().leftcol -- hide full name if left column of node in nvim-tree win is not zero if leftcol ~= 0 then return end local text_width = vim.fn.strdisplaywidth(vim.fn.substitute(line, "[^[:print:]]*$", "", "g")) 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 return end M.popup_win = vim.api.nvim_open_win(vim.api.nvim_create_buf(false, false), false, { relative = "win", row = 0, bufpos = { vim.api.nvim_win_get_cursor(0)[1] - 1, 0 }, width = math.min(text_width, vim.o.columns - 2), height = 1, noautocmd = true, style = "minimal", border = "none" }) vim.wo[M.popup_win].winhl = view.View.winopts.winhl 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 }) vim.api.nvim_win_call(M.popup_win, function() vim.api.nvim_buf_set_lines(0, 0, -1, true, { line }) for _, extmark in ipairs(extmarks) do -- nvim 0.10 luadoc is incorrect: vim.api.keyset.get_extmark_item is missing the extmark_id at the start ---@cast extmark table ---@type integer local col = extmark[3] ---@type vim.api.keyset.extmark_details local details = extmark[4] 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) end M.setup = function(opts) M.config = opts.renderer if not M.config.full_name then return end local group = vim.api.nvim_create_augroup("nvim_tree_floating_node", { clear = true }) vim.api.nvim_create_autocmd({ "BufLeave", "CursorMoved" }, { group = group, pattern = { "NvimTree_*" }, callback = function() if utils.is_nvim_tree_buf(0) then hide(M.popup_win) end end, }) vim.api.nvim_create_autocmd({ "CursorMoved" }, { group = group, pattern = { "NvimTree_*" }, callback = function() if utils.is_nvim_tree_buf(0) then show(opts) end end, }) end return M ================================================ FILE: lua/nvim-tree/renderer/components/init.lua ================================================ local M = {} M.full_name = require("nvim-tree.renderer.components.full-name") M.devicons = require("nvim-tree.renderer.components.devicons") M.padding = require("nvim-tree.renderer.components.padding") function M.setup(opts) M.full_name.setup(opts) M.devicons.setup(opts) M.padding.setup(opts) end return M ================================================ FILE: lua/nvim-tree/renderer/components/padding.lua ================================================ local DirectoryNode = require("nvim-tree.node.directory") local M = {} local function check_siblings_for_folder(node, with_arrows) if with_arrows then local has_files = false local has_folders = false for _, n in pairs(node.parent.nodes) do if n.nodes and node.absolute_path ~= n.absolute_path then has_folders = true end if not n.nodes then has_files = true end if has_files and has_folders then return true end end end return false end local function get_padding_indent_markers(depth, idx, nodes_number, markers, with_arrows, inline_arrows, node, early_stop) local base_padding = with_arrows and (not node.nodes or depth > 0) and " " or "" local padding = (inline_arrows or depth == 0) and base_padding or "" if depth > 0 then local has_folder_sibling = check_siblings_for_folder(node, with_arrows) local indent = string.rep(" ", M.config.indent_width - 1) markers[depth] = idx ~= nodes_number for i = 1, depth - early_stop do local glyph if idx == nodes_number and i == depth then local bottom_width = M.config.indent_width - 2 + (with_arrows and not inline_arrows and has_folder_sibling and 2 or 0) glyph = M.config.indent_markers.icons.corner .. string.rep(M.config.indent_markers.icons.bottom, bottom_width) .. (M.config.indent_width > 1 and " " or "") elseif markers[i] and i == depth then glyph = M.config.indent_markers.icons.item .. indent elseif markers[i] then glyph = M.config.indent_markers.icons.edge .. indent else glyph = M.config.indent_markers.icons.none .. indent end if not with_arrows or (inline_arrows and (depth ~= i or not node.nodes)) then padding = padding .. glyph elseif inline_arrows then padding = padding elseif idx ~= nodes_number and depth == i and not node.nodes and has_folder_sibling then padding = padding .. base_padding .. glyph .. base_padding else padding = padding .. base_padding .. glyph end end end return padding end ---@param depth integer ---@param idx integer ---@param nodes_number integer ---@param node Node ---@param markers table ---@param early_stop integer? ---@return nvim_tree.api.highlighted_string function M.get_indent_markers(depth, idx, nodes_number, node, markers, early_stop) local str = "" local show_arrows = M.config.icons.show.folder_arrow local show_markers = M.config.indent_markers.enable local inline_arrows = M.config.indent_markers.inline_arrows local indent_width = M.config.indent_width if show_markers then str = str .. get_padding_indent_markers(depth, idx, nodes_number, markers, show_arrows, inline_arrows, node, early_stop or 0) else str = str .. string.rep(" ", depth * indent_width) end return { str = str, hl = { "NvimTreeIndentMarker" } } end ---@param node Node ---@return nvim_tree.api.highlighted_string[]? function M.get_arrows(node) if not M.config.icons.show.folder_arrow then return end local str local hl = "NvimTreeFolderArrowClosed" local dir = node:as(DirectoryNode) if dir then if dir.open then str = M.config.icons.glyphs.folder["arrow_open"] .. M.config.icons.padding.folder_arrow hl = "NvimTreeFolderArrowOpen" else str = M.config.icons.glyphs.folder["arrow_closed"] .. M.config.icons.padding.folder_arrow end elseif M.config.indent_markers.enable then str = "" else str = " " .. string.rep(" ", #M.config.icons.padding.folder_arrow) end return { str = str, hl = { hl } } end function M.setup(opts) M.config = opts.renderer if M.config.indent_width < 1 then M.config.indent_width = 1 end local function check_marker(symbol) if #symbol == 0 then return " " end -- return the first character from the UTF-8 encoded string; we may use utf8.codes from Lua 5.3 when available return symbol:match("[%z\1-\127\194-\244][\128-\191]*") end for k, v in pairs(M.config.indent_markers.icons) do M.config.indent_markers.icons[k] = check_marker(v) end end return M ================================================ FILE: lua/nvim-tree/renderer/decorator/bookmarks.lua ================================================ local BuiltinDecorator = require("nvim-tree.renderer.decorator.builtin") ---@class (exact) BookmarkDecorator: BuiltinDecorator ---@field private icon nvim_tree.api.highlighted_string? local BookmarkDecorator = BuiltinDecorator:extend() ---@class BookmarkDecorator ---@overload fun(args: BuiltinDecoratorArgs): BookmarkDecorator ---@protected ---@param args BuiltinDecoratorArgs function BookmarkDecorator:new(args) BookmarkDecorator.super.new(self, args) self.enabled = true self.highlight_range = self.explorer.opts.renderer.highlight_bookmarks or "none" self.icon_placement = self.explorer.opts.renderer.icons.bookmarks_placement or "none" if self.explorer.opts.renderer.icons.show.bookmarks then self.icon = { str = self.explorer.opts.renderer.icons.glyphs.bookmark, hl = { "NvimTreeBookmarkIcon" }, } self:define_sign(self.icon) end end ---Bookmark icon: renderer.icons.show.bookmarks and node is marked ---@param node Node ---@return nvim_tree.api.highlighted_string[]? icons function BookmarkDecorator:icons(node) if self.explorer.marks:get(node) then return { self.icon } end end ---Bookmark highlight: renderer.highlight_bookmarks and node is marked ---@param node Node ---@return string? highlight_group function BookmarkDecorator:highlight_group(node) if self.highlight_range ~= "none" and self.explorer.marks:get(node) then return "NvimTreeBookmarkHL" end end return BookmarkDecorator ================================================ FILE: lua/nvim-tree/renderer/decorator/builtin.lua ================================================ local Decorator = require("nvim-tree.renderer.decorator") ---Abstract builtin decorator ---Overrides all methods to use a Node instead of nvim_tree.api.Node as we don't have generics. --- ---@class (exact) BuiltinDecorator: Decorator --- ---@field protected explorer Explorer --- ---@field icon_node fun(self: BuiltinDecorator, node: Node): nvim_tree.api.highlighted_string? ---@field icons fun(self: BuiltinDecorator, node: Node): nvim_tree.api.highlighted_string? ---@field highlight_group fun(self: BuiltinDecorator, node: Node): string? ---@field highlight_group_icon_name fun(self: BuiltinDecorator, node: Node): string?, string? ---@field sign_name fun(self: BuiltinDecorator, node: Node): string? ---@field icons_before fun(self: BuiltinDecorator, node: Node): nvim_tree.api.highlighted_string[]? ---@field icons_after fun(self: BuiltinDecorator, node: Node): nvim_tree.api.highlighted_string[]? ---@field icons_right_align fun(self: BuiltinDecorator, node: Node): nvim_tree.api.highlighted_string[]? local BuiltinDecorator = Decorator:extend() ---@class (exact) BuiltinDecoratorArgs ---@field explorer Explorer ---@protected ---@param args BuiltinDecoratorArgs function BuiltinDecorator:new(args) self.explorer = args.explorer end return BuiltinDecorator ================================================ FILE: lua/nvim-tree/renderer/decorator/copied.lua ================================================ local BuiltinDecorator = require("nvim-tree.renderer.decorator.builtin") ---@class (exact) CopiedDecorator: BuiltinDecorator local CopiedDecorator = BuiltinDecorator:extend() ---@class CopiedDecorator ---@overload fun(args: BuiltinDecoratorArgs): CopiedDecorator ---@protected ---@param args BuiltinDecoratorArgs function CopiedDecorator:new(args) CopiedDecorator.super.new(self, args) self.enabled = true self.highlight_range = self.explorer.opts.renderer.highlight_clipboard or "none" self.icon_placement = "none" end ---Copied highlight: renderer.highlight_clipboard and node is copied ---@param node Node ---@return string? highlight_group function CopiedDecorator:highlight_group(node) if self.highlight_range ~= "none" and self.explorer.clipboard:is_copied(node) then return "NvimTreeCopiedHL" end end return CopiedDecorator ================================================ FILE: lua/nvim-tree/renderer/decorator/cut.lua ================================================ local BuiltinDecorator = require("nvim-tree.renderer.decorator.builtin") ---@class (exact) CutDecorator: BuiltinDecorator local CutDecorator = BuiltinDecorator:extend() ---@class CutDecorator ---@overload fun(args: BuiltinDecoratorArgs): CutDecorator ---@protected ---@param args BuiltinDecoratorArgs function CutDecorator:new(args) CutDecorator.super.new(self, args) self.enabled = true self.highlight_range = self.explorer.opts.renderer.highlight_clipboard or "none" self.icon_placement = "none" end ---Cut highlight: renderer.highlight_clipboard and node is cut ---@param node Node ---@return string? highlight_group function CutDecorator:highlight_group(node) if self.highlight_range ~= "none" and self.explorer.clipboard:is_cut(node) then return "NvimTreeCutHL" end end return CutDecorator ================================================ FILE: lua/nvim-tree/renderer/decorator/diagnostics.lua ================================================ local diagnostics = require("nvim-tree.diagnostics") local BuiltinDecorator = require("nvim-tree.renderer.decorator.builtin") local DirectoryNode = require("nvim-tree.node.directory") -- highlight groups by severity local HG_ICON = { [vim.diagnostic.severity.ERROR] = "NvimTreeDiagnosticErrorIcon", [vim.diagnostic.severity.WARN] = "NvimTreeDiagnosticWarnIcon", [vim.diagnostic.severity.INFO] = "NvimTreeDiagnosticInfoIcon", [vim.diagnostic.severity.HINT] = "NvimTreeDiagnosticHintIcon", } local HG_FILE = { [vim.diagnostic.severity.ERROR] = "NvimTreeDiagnosticErrorFileHL", [vim.diagnostic.severity.WARN] = "NvimTreeDiagnosticWarnFileHL", [vim.diagnostic.severity.INFO] = "NvimTreeDiagnosticInfoFileHL", [vim.diagnostic.severity.HINT] = "NvimTreeDiagnosticHintFileHL", } local HG_FOLDER = { [vim.diagnostic.severity.ERROR] = "NvimTreeDiagnosticErrorFolderHL", [vim.diagnostic.severity.WARN] = "NvimTreeDiagnosticWarnFolderHL", [vim.diagnostic.severity.INFO] = "NvimTreeDiagnosticInfoFolderHL", [vim.diagnostic.severity.HINT] = "NvimTreeDiagnosticHintFolderHL", } -- opts.diagnostics.icons. local ICON_KEYS = { ["error"] = vim.diagnostic.severity.ERROR, ["warning"] = vim.diagnostic.severity.WARN, ["info"] = vim.diagnostic.severity.INFO, ["hint"] = vim.diagnostic.severity.HINT, } ---@class (exact) DiagnosticsDecorator: BuiltinDecorator ---@field private diag_icons nvim_tree.api.highlighted_string[]? local DiagnosticsDecorator = BuiltinDecorator:extend() ---@class DiagnosticsDecorator ---@overload fun(args: BuiltinDecoratorArgs): DiagnosticsDecorator ---@protected ---@param args BuiltinDecoratorArgs function DiagnosticsDecorator:new(args) DiagnosticsDecorator.super.new(self, args) self.enabled = true self.highlight_range = self.explorer.opts.renderer.highlight_diagnostics or "none" self.icon_placement = self.explorer.opts.renderer.icons.diagnostics_placement or "none" local vim_diagnostic_icons = {} if self.explorer.opts.diagnostics.diagnostic_opts then local vim_diagnostic_config = vim.diagnostic.config() or {} local signs = vim_diagnostic_config.signs or {} if type(signs) == "function" then signs = signs(0, 0) end vim_diagnostic_icons = (type(signs) == "table" and signs.text) or {} end if self.explorer.opts.renderer.icons.show.diagnostics then self.diag_icons = {} for name, sev in pairs(ICON_KEYS) do self.diag_icons[sev] = { str = vim_diagnostic_icons[sev] or self.explorer.opts.diagnostics.icons[name], hl = { HG_ICON[sev] }, } self:define_sign(self.diag_icons[sev]) end end end ---Diagnostic icon: diagnostics.enable, renderer.icons.show.diagnostics and node has status ---@param node Node ---@return nvim_tree.api.highlighted_string[]? icons function DiagnosticsDecorator:icons(node) if node and self.diag_icons then local diag_status = diagnostics.get_diag_status(node) local diag_value = diag_status and diag_status.value if diag_value then return { self.diag_icons[diag_value] } end end end ---Diagnostic highlight: diagnostics.enable, renderer.highlight_diagnostics and node has status ---@param node Node ---@return string? highlight_group function DiagnosticsDecorator:highlight_group(node) if self.highlight_range == "none" then return nil end local diag_status = diagnostics.get_diag_status(node) local diag_value = diag_status and diag_status.value if not diag_value then return nil end local group if node:is(DirectoryNode) then group = HG_FOLDER[diag_value] else group = HG_FILE[diag_value] end if group then return group else return nil end end return DiagnosticsDecorator ================================================ FILE: lua/nvim-tree/renderer/decorator/git.lua ================================================ local notify = require("nvim-tree.notify") local BuiltinDecorator = require("nvim-tree.renderer.decorator.builtin") local DirectoryNode = require("nvim-tree.node.directory") ---@class (exact) GitHighlightedString: nvim_tree.api.highlighted_string ---@field ord number decreasing priority ---@alias GitStatusStrings "deleted" | "ignored" | "renamed" | "staged" | "unmerged" | "unstaged" | "untracked" ---@alias GitIconsByStatus table human status ---@alias GitIconsByXY table porcelain status ---@alias GitGlyphsByStatus table from opts ---@class (exact) GitDecorator: BuiltinDecorator ---@field private file_hl_by_xy table? ---@field private folder_hl_by_xy table? ---@field private icons_by_status GitIconsByStatus? ---@field private icons_by_xy GitIconsByXY? local GitDecorator = BuiltinDecorator:extend() ---@class GitDecorator ---@overload fun(args: BuiltinDecoratorArgs): GitDecorator ---@protected ---@param args BuiltinDecoratorArgs function GitDecorator:new(args) GitDecorator.super.new(self, args) self.enabled = self.explorer.opts.git.enable self.highlight_range = self.explorer.opts.renderer.highlight_git or "none" self.icon_placement = self.explorer.opts.renderer.icons.git_placement or "none" if not self.enabled then return end if self.highlight_range ~= "none" then self:build_file_folder_hl_by_xy() end if self.explorer.opts.renderer.icons.show.git then self:build_icons_by_status(self.explorer.opts.renderer.icons.glyphs.git) self:build_icons_by_xy(self.icons_by_status) for _, icon in pairs(self.icons_by_status) do self:define_sign(icon) end end end ---@param glyphs GitGlyphsByStatus function GitDecorator:build_icons_by_status(glyphs) self.icons_by_status = {} self.icons_by_status.staged = { str = glyphs.staged, hl = { "NvimTreeGitStagedIcon" }, ord = 1 } self.icons_by_status.unstaged = { str = glyphs.unstaged, hl = { "NvimTreeGitDirtyIcon" }, ord = 2 } self.icons_by_status.renamed = { str = glyphs.renamed, hl = { "NvimTreeGitRenamedIcon" }, ord = 3 } self.icons_by_status.deleted = { str = glyphs.deleted, hl = { "NvimTreeGitDeletedIcon" }, ord = 4 } self.icons_by_status.unmerged = { str = glyphs.unmerged, hl = { "NvimTreeGitMergeIcon" }, ord = 5 } self.icons_by_status.untracked = { str = glyphs.untracked, hl = { "NvimTreeGitNewIcon" }, ord = 6 } self.icons_by_status.ignored = { str = glyphs.ignored, hl = { "NvimTreeGitIgnoredIcon" }, ord = 7 } end ---@param icons GitIconsByStatus function GitDecorator:build_icons_by_xy(icons) self.icons_by_xy = { ["M "] = { icons.staged }, [" M"] = { icons.unstaged }, ["C "] = { icons.staged }, [" C"] = { icons.unstaged }, ["CM"] = { icons.unstaged }, [" T"] = { icons.unstaged }, ["T "] = { icons.staged }, ["TM"] = { icons.staged, icons.unstaged }, ["MM"] = { icons.staged, icons.unstaged }, ["MD"] = { icons.staged }, ["A "] = { icons.staged }, ["AD"] = { icons.staged }, [" A"] = { icons.untracked }, -- not sure about this one ["AA"] = { icons.unmerged, icons.untracked }, ["AU"] = { icons.unmerged, icons.untracked }, ["AM"] = { icons.staged, icons.unstaged }, ["??"] = { icons.untracked }, ["R "] = { icons.renamed }, [" R"] = { icons.renamed }, ["RM"] = { icons.unstaged, icons.renamed }, ["UU"] = { icons.unmerged }, ["UD"] = { icons.unmerged }, ["UA"] = { icons.unmerged }, [" D"] = { icons.deleted }, ["D "] = { icons.deleted }, ["DA"] = { icons.unstaged }, ["RD"] = { icons.deleted }, ["DD"] = { icons.deleted }, ["DU"] = { icons.deleted, icons.unmerged }, ["!!"] = { icons.ignored }, dirty = { icons.unstaged }, } end function GitDecorator:build_file_folder_hl_by_xy() self.file_hl_by_xy = { ["M "] = "NvimTreeGitFileStagedHL", ["C "] = "NvimTreeGitFileStagedHL", ["AA"] = "NvimTreeGitFileStagedHL", ["AD"] = "NvimTreeGitFileStagedHL", ["MD"] = "NvimTreeGitFileStagedHL", ["T "] = "NvimTreeGitFileStagedHL", ["TT"] = "NvimTreeGitFileStagedHL", [" M"] = "NvimTreeGitFileDirtyHL", ["CM"] = "NvimTreeGitFileDirtyHL", [" C"] = "NvimTreeGitFileDirtyHL", [" T"] = "NvimTreeGitFileDirtyHL", ["MM"] = "NvimTreeGitFileDirtyHL", ["AM"] = "NvimTreeGitFileDirtyHL", dirty = "NvimTreeGitFileDirtyHL", ["A "] = "NvimTreeGitFileStagedHL", ["??"] = "NvimTreeGitFileNewHL", ["AU"] = "NvimTreeGitFileMergeHL", ["UU"] = "NvimTreeGitFileMergeHL", ["UD"] = "NvimTreeGitFileMergeHL", ["DU"] = "NvimTreeGitFileMergeHL", ["UA"] = "NvimTreeGitFileMergeHL", [" D"] = "NvimTreeGitFileDeletedHL", ["DD"] = "NvimTreeGitFileDeletedHL", ["RD"] = "NvimTreeGitFileDeletedHL", ["D "] = "NvimTreeGitFileDeletedHL", ["R "] = "NvimTreeGitFileRenamedHL", ["RM"] = "NvimTreeGitFileRenamedHL", [" R"] = "NvimTreeGitFileRenamedHL", ["!!"] = "NvimTreeGitFileIgnoredHL", [" A"] = "NvimTreeGitFileNewHL", } self.folder_hl_by_xy = {} for k, v in pairs(self.file_hl_by_xy) do self.folder_hl_by_xy[k] = v:gsub("File", "Folder") end end ---Git icons: git.enable, renderer.icons.show.git and node has status ---@param node Node ---@return nvim_tree.api.highlighted_string[]? icons function GitDecorator:icons(node) if not self.icons_by_xy then return nil end local git_xy = node:get_git_xy() if git_xy == nil then return nil end local inserted = {} local iconss = {} for _, s in pairs(git_xy) do local icons = self.icons_by_xy[s] if not icons then if self.highlight_range == "none" then notify.warn(string.format("Unrecognized git state '%s'", git_xy)) end return nil end for _, icon in pairs(icons) do if #icon.str > 0 then if not inserted[icon] then table.insert(iconss, icon) inserted[icon] = true end end end end if #iconss == 0 then return nil end -- sort icons so it looks slightly better table.sort(iconss, function(a, b) return a.ord < b.ord end) return iconss end ---Get the first icon as the sign if appropriate ---@param node Node ---@return string|nil name function GitDecorator:sign_name(node) if self.icon_placement ~= "signcolumn" then return end local icons = self:icons(node) if icons and #icons > 0 then return icons[1].hl[1] end end ---Git highlight: git.enable, renderer.highlight_git and node has status ---@param node Node ---@return string? highlight_group function GitDecorator:highlight_group(node) if self.highlight_range == "none" then return nil end local git_xy = node:get_git_xy() if not git_xy then return nil end if node:is(DirectoryNode) then return self.folder_hl_by_xy[git_xy[1]] else return self.file_hl_by_xy[git_xy[1]] end end return GitDecorator ================================================ FILE: lua/nvim-tree/renderer/decorator/hidden.lua ================================================ local BuiltinDecorator = require("nvim-tree.renderer.decorator.builtin") local DirectoryNode = require("nvim-tree.node.directory") ---@class (exact) HiddenDecorator: BuiltinDecorator ---@field private icon nvim_tree.api.highlighted_string? local HiddenDecorator = BuiltinDecorator:extend() ---@class HiddenDecorator ---@overload fun(args: BuiltinDecoratorArgs): HiddenDecorator ---@protected ---@param args BuiltinDecoratorArgs function HiddenDecorator:new(args) HiddenDecorator.super.new(self, args) self.enabled = true self.highlight_range = self.explorer.opts.renderer.highlight_hidden or "none" self.icon_placement = self.explorer.opts.renderer.icons.hidden_placement or "none" if self.explorer.opts.renderer.icons.show.hidden then self.icon = { str = self.explorer.opts.renderer.icons.glyphs.hidden, hl = { "NvimTreeHiddenIcon" }, } self:define_sign(self.icon) end end ---Hidden icon: renderer.icons.show.hidden and node starts with `.` (dotfile). ---@param node Node ---@return nvim_tree.api.highlighted_string[]? icons function HiddenDecorator:icons(node) if node:is_dotfile() then return { self.icon } end end ---Hidden highlight: renderer.highlight_hidden and node starts with `.` (dotfile). ---@param node Node ---@return string? highlight_group function HiddenDecorator:highlight_group(node) if self.highlight_range == "none" or not node:is_dotfile() then return nil end if node:is(DirectoryNode) then return "NvimTreeHiddenFolderHL" else return "NvimTreeHiddenFileHL" end end return HiddenDecorator ================================================ FILE: lua/nvim-tree/renderer/decorator/init.lua ================================================ local DecoratorInterface = require("nvim-tree._meta.api.decorator") --- ---Abstract decorator --- ---@class (exact) Decorator: nvim_tree.api.Decorator local Decorator = DecoratorInterface:extend() ---Maybe highlight groups for icon and name ---@param node nvim_tree.api.Node ---@return string? icon highlight group ---@return string? name highlight group function Decorator:highlight_group_icon_name(node) local icon_hl, name_hl if self.enabled and self.highlight_range ~= "none" then local hl = self:highlight_group(node) if self.highlight_range == "all" or self.highlight_range == "icon" then icon_hl = hl end if self.highlight_range == "all" or self.highlight_range == "name" then name_hl = hl end end return icon_hl, name_hl end ---Maybe icon sign ---@param node nvim_tree.api.Node ---@return string? name function Decorator:sign_name(node) if not self.enabled or self.icon_placement ~= "signcolumn" then return end local icons = self:icons(node) if icons and #icons > 0 then return icons[1].hl[1] end end ---Icons when "before" ---@param node nvim_tree.api.Node ---@return nvim_tree.api.highlighted_string[]? icons function Decorator:icons_before(node) if not self.enabled or self.icon_placement ~= "before" then return end return self:icons(node) end ---Icons when "after" ---@param node nvim_tree.api.Node ---@return nvim_tree.api.highlighted_string[]? icons function Decorator:icons_after(node) if not self.enabled or self.icon_placement ~= "after" then return end return self:icons(node) end ---Icons when "right_align" ---@param node nvim_tree.api.Node ---@return nvim_tree.api.highlighted_string[]? icons function Decorator:icons_right_align(node) if not self.enabled or self.icon_placement ~= "right_align" then return end return self:icons(node) end ---Define a sign ---@protected ---@param icon nvim_tree.api.highlighted_string? function Decorator:define_sign(icon) if icon and #icon.hl > 0 then local name = icon.hl[1] if not vim.tbl_isempty(vim.fn.sign_getdefined(name)) then vim.fn.sign_undefine(name) end -- don't render sign if empty if #icon.str < 1 then return end -- byte index of the next character, allowing for wide local bi = vim.fn.byteidx(icon.str, 1) -- first (wide) character, falls back to empty string local text = string.sub(icon.str, 1, bi) vim.fn.sign_define(name, { text = text, texthl = name, }) end end ---@type Decorator return Decorator ================================================ FILE: lua/nvim-tree/renderer/decorator/modified.lua ================================================ local buffers = require("nvim-tree.buffers") local BuiltinDecorator = require("nvim-tree.renderer.decorator.builtin") local DirectoryNode = require("nvim-tree.node.directory") ---@class (exact) ModifiedDecorator: BuiltinDecorator ---@field private icon nvim_tree.api.highlighted_string? local ModifiedDecorator = BuiltinDecorator:extend() ---@class ModifiedDecorator ---@overload fun(args: BuiltinDecoratorArgs): ModifiedDecorator ---@protected ---@param args BuiltinDecoratorArgs function ModifiedDecorator:new(args) ModifiedDecorator.super.new(self, args) self.enabled = true self.highlight_range = self.explorer.opts.renderer.highlight_modified or "none" self.icon_placement = self.explorer.opts.renderer.icons.modified_placement or "none" if self.explorer.opts.renderer.icons.show.modified then self.icon = { str = self.explorer.opts.renderer.icons.glyphs.modified, hl = { "NvimTreeModifiedIcon" }, } self:define_sign(self.icon) end end ---Modified icon: modified.enable, renderer.icons.show.modified and node is modified ---@param node Node ---@return nvim_tree.api.highlighted_string[]? icons function ModifiedDecorator:icons(node) if buffers.is_modified(node) then return { self.icon } end end ---Modified highlight: modified.enable, renderer.highlight_modified and node is modified ---@param node Node ---@return string? highlight_group function ModifiedDecorator:highlight_group(node) if self.highlight_range == "none" or not buffers.is_modified(node) then return nil end if node:is(DirectoryNode) then return "NvimTreeModifiedFolderHL" else return "NvimTreeModifiedFileHL" end end return ModifiedDecorator ================================================ FILE: lua/nvim-tree/renderer/decorator/opened.lua ================================================ local buffers = require("nvim-tree.buffers") local BuiltinDecorator = require("nvim-tree.renderer.decorator.builtin") ---@class (exact) OpenDecorator: BuiltinDecorator ---@field private icon? nvim_tree.api.highlighted_string local OpenDecorator = BuiltinDecorator:extend() ---@class OpenDecorator ---@overload fun(args: BuiltinDecoratorArgs): OpenDecorator ---@protected ---@param args BuiltinDecoratorArgs function OpenDecorator:new(args) OpenDecorator.super.new(self, args) self.enabled = true self.highlight_range = self.explorer.opts.renderer.highlight_opened_files or "none" self.icon_placement = "none" end ---Opened highlight: renderer.highlight_opened_files and node has an open buffer ---@param node Node ---@return string? highlight_group function OpenDecorator:highlight_group(node) if self.highlight_range ~= "none" and buffers.is_opened(node) then return "NvimTreeOpenedHL" end end return OpenDecorator ================================================ FILE: lua/nvim-tree/renderer/init.lua ================================================ local log = require("nvim-tree.log") local view = require("nvim-tree.view") local events = require("nvim-tree.events") local Class = require("nvim-tree.classic") local Builder = require("nvim-tree.renderer.builder") local SIGN_GROUP = "NvimTreeRendererSigns" local namespace_highlights_id = vim.api.nvim_create_namespace("NvimTreeHighlights") local namespace_extmarks_id = vim.api.nvim_create_namespace("NvimTreeExtmarks") 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: nvim_tree.Class ---@field explorer Explorer local Renderer = Class:extend() ---@class Renderer ---@overload fun(args: RendererArgs): Renderer ---@class (exact) RendererArgs ---@field explorer Explorer ---@protected ---@param args RendererArgs function Renderer:new(args) self.explorer = args.explorer end ---@private ---@param bufnr number ---@param lines string[] ---@param hl_range_args HighlightRangeArgs[] ---@param signs string[] ---@param extmarks table[] extra marks for right icon placement ---@param virtual_lines table[] virtual lines for hidden count display function Renderer:_draw(bufnr, lines, hl_range_args, signs, extmarks, virtual_lines) if vim.fn.has("nvim-0.10") == 1 then vim.api.nvim_set_option_value("modifiable", true, { buf = bufnr }) else vim.api.nvim_buf_set_option(bufnr, "modifiable", true) ---@diagnostic disable-line: deprecated end vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) self:render_hl(bufnr, hl_range_args) if vim.fn.has("nvim-0.10") == 1 then vim.api.nvim_set_option_value("modifiable", false, { buf = bufnr }) else vim.api.nvim_buf_set_option(bufnr, "modifiable", false) ---@diagnostic disable-line: deprecated end vim.fn.sign_unplace(SIGN_GROUP) for i, sign_name in pairs(signs) do vim.fn.sign_place(0, SIGN_GROUP, sign_name, bufnr, { lnum = i + 1 }) end vim.api.nvim_buf_clear_namespace(bufnr, namespace_extmarks_id, 0, -1) for i, extname in pairs(extmarks) do for _, mark in ipairs(extname) do vim.api.nvim_buf_set_extmark(bufnr, namespace_extmarks_id, i, -1, { virt_text = { { mark.str, mark.hl } }, virt_text_pos = "right_align", hl_mode = "combine", }) end end vim.api.nvim_buf_clear_namespace(bufnr, namespace_virtual_lines_id, 0, -1) for line_nr, vlines in pairs(virtual_lines) do vim.api.nvim_buf_set_extmark(bufnr, namespace_virtual_lines_id, line_nr, 0, { virt_lines = vlines, virt_lines_above = false, virt_lines_leftcol = true, }) end end ---@private ---@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 return end vim.api.nvim_buf_clear_namespace(bufnr, namespace_highlights_id, 0, -1) for _, args in ipairs(hl_range_args) do if vim.fn.has("nvim-0.11") == 1 and vim.hl and vim.hl.range then vim.hl.range(bufnr, namespace_highlights_id, args.higroup, args.start, args.finish, {}) else 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 function Renderer:draw() local bufnr = view.get_bufnr() if not bufnr or not vim.api.nvim_buf_is_loaded(bufnr) then return end local profile = log.profile_start("draw") local cursor = vim.api.nvim_win_get_cursor(view.get_winnr() or 0) local builder = Builder(self.explorer):build() self:_draw(bufnr, builder.lines, builder.hl_range_args, builder.signs, builder.extmarks, builder.virtual_lines) if cursor and #builder.lines >= cursor[1] then vim.api.nvim_win_set_cursor(view.get_winnr() or 0, cursor) end view.grow_from_content() log.profile_end(profile) events._dispatch_on_tree_rendered(bufnr, view.get_winnr()) end return Renderer ================================================ FILE: lua/nvim-tree/utils.lua ================================================ local M = { debouncers = {}, } M.is_unix = vim.fn.has("unix") == 1 M.is_macos = vim.fn.has("mac") == 1 or vim.fn.has("macunix") == 1 M.is_wsl = vim.fn.has("wsl") == 1 -- false for WSL M.is_windows = vim.fn.has("win32") == 1 or vim.fn.has("win32unix") == 1 ---@param haystack string ---@param needle string ---@return boolean function M.str_find(haystack, needle) return vim.fn.stridx(haystack, needle) ~= -1 end local path_separator = package.config:sub(1, 1) ---@param paths string[] ---@return string function M.path_join(paths) return table.concat(vim.tbl_map(M.path_remove_trailing, paths), path_separator) end ---@param path string ---@return fun(): string function M.path_split(path) return path:gmatch("[^" .. path_separator .. "]+" .. path_separator .. "?") end --- Get the basename of the given path. ---@param path string ---@return string function M.path_basename(path) path = M.path_remove_trailing(path) local i = path:match("^.*()" .. path_separator) if not i then return path end return path:sub(i + 1, #path) end --- Check if there are parentheses before brackets, it causes problems for windows. --- Refer to issue #2862 and #2961 for more details. local function has_parentheses_and_brackets(path) local _, i_parentheses = path:find("(", 1, true) local _, i_brackets = path:find("[", 1, true) if i_parentheses and i_brackets then return true end return false end --- Path normalizations for windows only local function win_norm_path(path) if path == nil then return path end local norm_path = path -- Normalize for issue #2862 and #2961 if has_parentheses_and_brackets(norm_path) then norm_path = norm_path:gsub("/", "\\") end -- Normalize the drive letter norm_path = norm_path:gsub("^%l:", function(drive) return drive:upper() end) return norm_path end --- Get a path relative to another path. ---@param path string ---@param relative_to string|nil ---@return string function M.path_relative(path, relative_to) if relative_to == nil then return path end local norm_path = path if M.is_windows then norm_path = win_norm_path(norm_path) end local _, r = norm_path:find(M.path_add_trailing(relative_to), 1, true) local p = norm_path if r then -- take the relative path starting after '/' -- if somehow given a completely matching path, -- returns "" p = norm_path:sub(r + 1) end return p end ---@param path string ---@return string function M.path_add_trailing(path) if path:sub(-1) == path_separator then return path end return path .. path_separator end ---@param path string ---@return string function M.path_remove_trailing(path) local p, _ = path:gsub(path_separator .. "$", "") return p end M.path_separator = path_separator ---@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 M.default_format_hidden_count = function(hidden_count, simple) local parts = {} local total_count = 0 for reason, count in pairs(hidden_count) do total_count = total_count + count if count > 0 then table.insert(parts, reason .. ": " .. tostring(count)) end end local hidden_count_string = table.concat(parts, ", ") -- if empty then is "" (empty string) if simple then hidden_count_string = "" end if total_count > 0 then return "(" .. tostring(total_count) .. (simple and " hidden" or " total ") .. hidden_count_string .. ")" end return nil end function M.rename_loaded_buffers(old_path, new_path) -- delete new if it exists for _, buf in pairs(vim.api.nvim_list_bufs()) do if vim.api.nvim_buf_is_loaded(buf) then local buf_name = vim.api.nvim_buf_get_name(buf) if buf_name == new_path then vim.api.nvim_buf_delete(buf, { force = true }) end end end -- rename old to new for _, buf in pairs(vim.api.nvim_list_bufs()) do if vim.api.nvim_buf_is_loaded(buf) then local buf_name = vim.api.nvim_buf_get_name(buf) local exact_match = buf_name == old_path local child_match = (buf_name:sub(1, #old_path) == old_path and buf_name:sub(#old_path + 1, #old_path + 1) == path_separator) if exact_match or child_match then vim.api.nvim_buf_set_name(buf, new_path .. buf_name:sub(#old_path + 1)) -- to avoid the 'overwrite existing file' error message on write for -- normal files local buftype if vim.fn.has("nvim-0.10") == 1 then buftype = vim.api.nvim_get_option_value("buftype", { buf = buf }) else buftype = vim.api.nvim_buf_get_option(buf, "buftype") ---@diagnostic disable-line: deprecated end if buftype == "" then vim.api.nvim_buf_call(buf, function() vim.cmd("silent! write!") vim.cmd("edit") end) end 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 ---@return boolean function M.file_exists(path) if not (M.is_windows or M.is_wsl) then local _, error = vim.loop.fs_stat(path) return error == nil end -- Windows is case-insensetive, but case-preserving -- If a file's name is being changed into itself -- with different casing, windows will falsely -- report that file is already existing, so a hand-rolled -- implementation of checking for existance is needed. -- Same holds for WSL, since it can sometimes -- access Windows files directly. -- For more details see (#3117). if is_windows_drive(path) then return vim.fn.isdirectory(path) == 1 end local parent = vim.fn.fnamemodify(path, ":h") local filename = vim.fn.fnamemodify(path, ":t") local handle = vim.loop.fs_scandir(parent) if not handle then -- File can not exist if its parent directory does not exist return false end while true do local name, _ = vim.loop.fs_scandir_next(handle) if not name then break end if name == filename then return true end end return false end ---@param path string ---@return string function M.canonical_path(path) if M.is_windows and path:match("^%a:") then return path:sub(1, 1):upper() .. path:sub(2) end return path end --- Escapes special characters in string for windows, refer to issue #2862 and #2961 for more details. local function escape_special_char_for_windows(path) if has_parentheses_and_brackets(path) then return path:gsub("\\", "/"):gsub("/ ", "\\ ") end return path:gsub("%(", "\\("):gsub("%)", "\\)") end --- Escapes special characters in string if windows else returns unmodified string. ---@param path string ---@return string|nil function M.escape_special_chars(path) if path == nil then return path end return M.is_windows and escape_special_char_for_windows(path) or path end local function round(value) -- Amount of digits to round to after floating point. local digits = 2 local round_number = 10 ^ digits return math.floor((value * round_number) + 0.5) / round_number end function M.format_bytes(bytes) local units = { "B", "K", "M", "G", "T", "P", "E", "Z", "Y" } local i = "i" -- bInary bytes = math.max(bytes, 0) local pow = math.floor((bytes and math.log(bytes) or 0) / math.log(1024)) pow = math.min(pow, #units) local value = round(bytes / (1024 ^ pow)) pow = pow + 1 -- units[pow] == nil when size == 0 B or size >= 1024 YiB if units[pow] == nil or pow == 1 then if bytes < 1024 then return bytes .. " " .. units[1] else -- Use the biggest adopted multiple of 2 instead of bytes. value = round(bytes / (1024 ^ (#units - 1))) -- For big numbers decimal part is not useful. return string.format("%.0f %s%s%s", value, units[#units], i, units[1]) end else return value .. " " .. units[pow] .. i .. units[1] end end function M.key_by(tbl, key) local keyed = {} for _, val in ipairs(tbl) do if val[key] then keyed[val[key]] = val end end return keyed end function M.bool_record(tbl, key) local keyed = {} for _, val in ipairs(tbl) do keyed[val[key]] = true end return keyed end local function timer_stop_close(timer) if timer:is_active() then timer:stop() end if not timer:is_closing() then timer:close() end end ---Execute callback timeout ms after the latest invocation with context. ---Waiting invocations for that context will be discarded. ---Invocation will be rescheduled while a callback is being executed. ---Caller must ensure that callback performs the same or functionally equivalent actions. --- ---@param context string identifies the callback to debounce ---@param timeout number ms to wait ---@param callback function to execute on completion function M.debounce(context, timeout, callback) -- all execution here is done in a synchronous context; no thread safety required M.debouncers[context] = M.debouncers[context] or {} local debouncer = M.debouncers[context] -- cancel waiting or executing timer if debouncer.timer then timer_stop_close(debouncer.timer) end local timer = vim.loop.new_timer() if not timer then return end debouncer.timer = timer timer:start(timeout, 0, function() timer_stop_close(timer) -- reschedule when callback is running if debouncer.executing then M.debounce(context, timeout, callback) return end -- call back at a safe time debouncer.executing = true vim.schedule(function() callback() debouncer.executing = false -- no other timer waiting if debouncer.timer == timer then M.debouncers[context] = nil end end) end) end ---@param path string ---@return integer|nil ---@return integer|nil function M.get_win_buf_from_path(path) for _, w in pairs(vim.api.nvim_tabpage_list_wins(0)) do local b = vim.api.nvim_win_get_buf(w) if vim.api.nvim_buf_get_name(b) == path then return w, b end end return nil, nil end function M.clear_prompt() if vim.opt.cmdheight._value ~= 0 then vim.cmd("normal! :") end end --- Return a new table with values from array ---@param array table ---@return table function M.array_shallow_clone(array) local to = {} for _, v in ipairs(array) do table.insert(to, v) end return to end --- Remove and return item from array if present. ---@param array table ---@param item any ---@return any|nil removed function M.array_remove(array, item) if not array then return nil end for i, v in ipairs(array) do if v == item then table.remove(array, i) return v end end end ---@param array table ---@return table function M.array_remove_nils(array) return vim.tbl_filter(function(v) return v ~= nil end, array) end --- Is the buffer named NvimTree_[0-9]+ a tree? filetype is "NvimTree" or not readable file. --- This is cheap, as the readable test should only ever be needed when resuming a vim session. ---@param bufnr number|nil may be 0 or nil for current ---@return boolean function M.is_nvim_tree_buf(bufnr) if bufnr == nil then bufnr = 0 end if vim.api.nvim_buf_is_valid(bufnr) then local bufname = vim.api.nvim_buf_get_name(bufnr) if vim.fn.fnamemodify(bufname, ":t"):match("^NvimTree_[0-9]+$") then if vim.bo[bufnr].filetype == "NvimTree" then return true elseif vim.fn.filereadable(bufname) == 0 then return true end end end return false end --- path is an executable file or directory ---@param absolute_path string ---@return boolean function M.is_executable(absolute_path) if M.is_windows or M.is_wsl then --- executable detection on windows is buggy and not performant hence it is disabled return false else return vim.loop.fs_access(absolute_path, "X") or false end end ---List of all option info/values ---@param opts vim.api.keyset.option passed directly to vim.api.nvim_get_option_info2 and vim.api.nvim_get_option_value ---@param was_set boolean filter was_set ---@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) if opts.buf and info.scope ~= "buf" then return false elseif opts.win and info.scope ~= "win" then return false else return true end 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 ---Filter out nodes that are descendants of other nodes in the list. ---When a directory is selected along with its children, only the directory needs to be operated on. ---@param nodes Node[] ---@return Node[] function M.filter_descendant_nodes(nodes) return vim.tbl_filter(function(node) local parent = node.parent while parent do if vim.tbl_contains(nodes, parent) then return false end parent = parent.parent end return true end, nodes) end ---Build confirmation prompt strings based on default_yes config. ---@param prompt_select string ---@param default_yes boolean ---@return string prompt_input ---@return string[] items_short ---@return string[] items_long function M.confirm_prompt(prompt_select, default_yes) if default_yes then return prompt_select .. " Y/n: ", { "", "n" }, { "Yes", "No" } else return prompt_select .. " y/N: ", { "", "y" }, { "No", "Yes" } end end ---Check if the current mode is visual or select (v, V, CTRL-V, s, S, CTRL-S). ---@return boolean function M.is_visual_mode() local mode = vim.api.nvim_get_mode().mode local visual_modes = { v = true, V = true, ["\22"] = true, -- \22 is CTRL-V s = true, S = true, ["\19"] = true, -- \19 is CTRL-S } return visual_modes[mode] == true or false end ---Exit visual mode synchronously. function M.exit_visual_mode() local esc = vim.api.nvim_replace_termcodes("", true, false, true) vim.api.nvim_feedkeys(esc, "nx", false) end ---Get the visual selection range nodes, exiting visual mode. ---@return Node[]? function M.get_visual_nodes() local explorer = require("nvim-tree.core").get_explorer() if not explorer then return nil end local start_line = vim.fn.line("v") local end_line = vim.fn.line(".") if start_line > end_line then start_line, end_line = end_line, start_line end local nodes = explorer:get_nodes_in_range(start_line, end_line) M.exit_visual_mode() return nodes end return M ================================================ FILE: lua/nvim-tree/view.lua ================================================ 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_LINES_EXCLUDED = { "root", } 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) bufnr = M.get_bufnr() for _, option in ipairs(BUFFER_OPTIONS) do vim.api.nvim_set_option_value(option.name, option.value, { buf = bufnr }) end vim.api.nvim_buf_set_name(M.get_bufnr(), "NvimTree_" .. tab) 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 return end end end function M.close_this_tab_only() close(vim.api.nvim_get_current_tabpage()) end function M.close_all_tabs() for tabpage, _ in pairs(M.View.tabpages) do close(tabpage) end end ---@param tabpage integer|nil function M.close(tabpage) if M.View.tab.sync.close then M.close_all_tabs() elseif tabpage then close(tabpage) else M.close_this_tab_only() end end ---@param options table|nil function M.open(options) if M.is_visible() then return end local profile = log.profile_start("view open") events._dispatch_on_tree_pre_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 M.View.root_excluded) 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 final_width = M.View.initial_width local max_width = get_width(M.View.max_width) if max_width == -1 then max_width = math.huge end local ns_id = vim.api.nvim_get_namespaces()["NvimTreeExtmarks"] for i, l in pairs(lines) do local line_nr = starts_at + i - 1 local line_width = 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 }) line_width = line_width + utils.extmarks_length(extmarks) + padding final_width = math.max(final_width, line_width) if final_width >= max_width then final_width = max_width break end end M.resize(final_width) 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 } 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 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 events._dispatch_on_tree_open() 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? nvim_tree.api.tree.winid.Opts ---@return number? 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 local lines_excluded = width.lines_excluded or DEFAULT_LINES_EXCLUDED M.View.root_excluded = vim.tbl_contains(lines_excluded, "root") 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.cursorlineopt = options.cursorlineopt 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 ================================================ FILE: lua/nvim-tree/watcher.lua ================================================ local notify = require("nvim-tree.notify") local log = require("nvim-tree.log") local utils = require("nvim-tree.utils") local Class = require("nvim-tree.classic") local MESSAGE_EMFILE = "fs.inotify.max_user_watches exceeded, see https://github.com/nvim-tree/nvim-tree.lua/wiki/Troubleshooting" local FS_EVENT_FLAGS = { -- inotify or equivalent will be used; fallback to stat has not yet been implemented stat = false, -- recursive is not functional in neovim's libuv implementation recursive = false, } local M = { config = {}, } ---Registry of all events ---@type Event[] local events = {} ---@class (exact) Event: nvim_tree.Class ---@field destroyed boolean ---@field private path string ---@field private fs_event uv.uv_fs_event_t? ---@field private listeners function[] local Event = Class:extend() ---@class Event ---@overload fun(args: EventArgs): Event ---@class (exact) EventArgs ---@field path string ---@protected ---@param args EventArgs function Event:new(args) self.destroyed = false self.path = args.path self.fs_event = nil self.listeners = {} end ---Static factory method ---Creates and starts an Event ---nil on failure to start ---@param args EventArgs ---@return Event? function Event:create(args) log.line("watcher", "Event:create '%s'", args.path) local event = Event(args) if event:start() then events[event.path] = event return event else return nil end end ---@return boolean function Event:start() log.line("watcher", "Event:start '%s'", self.path) local rc, _, name self.fs_event, _, name = vim.loop.new_fs_event() if not self.fs_event then self.fs_event = nil notify.warn(string.format("Could not initialize an fs_event watcher for path %s : %s", self.path, name)) return false end local event_cb = vim.schedule_wrap(function(err, filename) if err then log.line("watcher", "event_cb '%s' '%s' FAIL : %s", self.path, filename, err) -- do nothing if watchers have already been disabled if not M.config.filesystem_watchers.enable then return end -- EMFILE is catastrophic if name == "EMFILE" then M.disable_watchers(MESSAGE_EMFILE) return end local message = string.format("File system watcher failed (%s) for path %s, halting watcher.", err, self.path) if err == "EPERM" and (utils.is_windows or utils.is_wsl) then -- on directory removal windows will cascade the filesystem events out of order log.line("watcher", message) self:destroy() else self:destroy(message) end else log.line("watcher", "event_cb '%s' '%s'", self.path, filename) for _, listener in ipairs(self.listeners) do listener(filename) end end end) rc, _, name = self.fs_event:start(self.path, FS_EVENT_FLAGS, event_cb) if rc ~= 0 then if name == "EMFILE" then M.disable_watchers(MESSAGE_EMFILE) else notify.warn(string.format("Could not start the fs_event watcher for path %s : %s", self.path, name)) end return false end return true end ---@param listener function function Event:add(listener) table.insert(self.listeners, listener) end ---@param listener function function Event:remove(listener) utils.array_remove(self.listeners, listener) if #self.listeners == 0 then self:destroy() end end ---@param message string|nil function Event:destroy(message) log.line("watcher", "Event:destroy '%s'", self.path) if self.fs_event then if message then notify.warn(message) end local rc, _, name = self.fs_event:stop() if rc ~= 0 then notify.warn(string.format("Could not stop the fs_event watcher for path %s : %s", self.path, name)) end self.fs_event = nil end self.destroyed = true events[self.path] = nil end ---Registry of all watchers ---@type Watcher[] local watchers = {} ---@class (exact) Watcher: nvim_tree.Class ---@field data table user data ---@field destroyed boolean ---@field private path string ---@field private callback fun(watcher: Watcher) ---@field private files string[]? ---@field private listener fun(filename: string)? ---@field private event Event local Watcher = Class:extend() ---@class Watcher ---@overload fun(args: WatcherArgs): Watcher ---@class (exact) WatcherArgs ---@field path string ---@field files string[]|nil ---@field callback fun(watcher: Watcher) ---@field data table? user data ---@protected ---@param args WatcherArgs function Watcher:new(args) self.data = args.data self.destroyed = false self.path = args.path self.callback = args.callback self.files = args.files self.listener = nil end ---Static factory method ---Creates and starts a Watcher ---nil on failure to create Event ---@param args WatcherArgs ---@return Watcher|nil function Watcher:create(args) log.line("watcher", "Watcher:create '%s' %s", args.path, vim.inspect(args.files)) local event = events[args.path] or Event:create({ path = args.path }) if not event then return nil end local watcher = Watcher(args) watcher.event = event watcher:start() table.insert(watchers, watcher) return watcher end function Watcher:start() self.listener = function(filename) if not self.files or vim.tbl_contains(self.files, filename) then self.callback(self) end end self.event:add(self.listener) end function Watcher:destroy() log.line("watcher", "Watcher:destroy '%s'", self.path) self.event:remove(self.listener) utils.array_remove( watchers, self ) self.destroyed = true end M.Watcher = Watcher --- Permanently disable watchers and purge all state following a catastrophic error. ---@param msg string function M.disable_watchers(msg) notify.warn(string.format("Disabling watchers: %s", msg)) M.config.filesystem_watchers.enable = false require("nvim-tree").purge_all_state() end function M.purge_watchers() log.line("watcher", "purge_watchers") for _, w in ipairs(utils.array_shallow_clone(watchers)) do w:destroy() end for _, e in pairs(events) do e:destroy() end end --- Windows NT will present directories that cannot be enumerated. --- Detect these by attempting to start an event monitor. ---@param path string ---@return boolean function M.is_fs_event_capable(path) if not utils.is_windows then return true end local fs_event = vim.loop.new_fs_event() if not fs_event then return false end if fs_event:start(path, FS_EVENT_FLAGS, function() end) ~= 0 then return false end if fs_event:stop() ~= 0 then return false end return true end function M.setup(opts) M.config.filesystem_watchers = opts.filesystem_watchers end return M ================================================ FILE: lua/nvim-tree.lua ================================================ local api = require("nvim-tree.api") local log = require("nvim-tree.log") local view = require("nvim-tree.view") local utils = require("nvim-tree.utils") local actions = require("nvim-tree.actions") local core = require("nvim-tree.core") local notify = require("nvim-tree.notify") local config = require("nvim-tree.config") local M = { init_root = "", } --- Helper function to execute some explorer method safely ---@param fn string # key of explorer ---@param ... any|nil ---@return function|nil local function explorer_fn(fn, ...) local explorer = core.get_explorer() if explorer then return explorer[fn](explorer, ...) end end --- Update the tree root to a directory or the directory containing ---@param path string relative or absolute ---@param bufnr number|nil function M.change_root(path, bufnr) -- skip if current file is in ignore_list if type(bufnr) == "number" then local ft if vim.fn.has("nvim-0.10") == 1 then ft = vim.api.nvim_get_option_value("filetype", { buf = bufnr }) or "" else ft = vim.api.nvim_buf_get_option(bufnr, "filetype") or "" ---@diagnostic disable-line: deprecated end for _, value in pairs(config.g.update_focused_file.update_root.ignore_list) do if utils.str_find(path, value) or utils.str_find(ft, value) then return end end end -- don't find inexistent if vim.fn.filereadable(path) == 0 then return end local cwd = core.get_cwd() if cwd == nil then return end local vim_cwd = vim.fn.getcwd() -- test if in vim_cwd if utils.path_relative(path, vim_cwd) ~= path then if vim_cwd ~= cwd then explorer_fn("change_dir", vim_cwd) end return end -- test if in cwd if utils.path_relative(path, cwd) ~= path then return end -- otherwise test M.init_root if config.g.prefer_startup_root and utils.path_relative(path, M.init_root) ~= path then explorer_fn("change_dir", M.init_root) return end -- otherwise root_dirs for _, dir in pairs(config.g.root_dirs) do dir = vim.fn.fnamemodify(dir, ":p") if utils.path_relative(path, dir) ~= path then explorer_fn("change_dir", dir) return end end -- finally fall back to the folder containing the file explorer_fn("change_dir", vim.fn.fnamemodify(path, ":p:h")) end function M.tab_enter() if view.is_visible({ any_tabpage = true }) then local bufname = vim.api.nvim_buf_get_name(0) local ft if vim.fn.has("nvim-0.10") == 1 then ft = vim.api.nvim_get_option_value("filetype", { buf = 0 }) or "" else ft = vim.api.nvim_buf_get_option(0, "ft") ---@diagnostic disable-line: deprecated end for _, filter in ipairs(config.g.tab.sync.ignore) do if bufname:match(filter) ~= nil or ft:match(filter) ~= nil then return end end view.open({ focus_tree = false }) local explorer = core.get_explorer() if explorer then explorer.renderer:draw() end end end function M.open_on_directory() local should_proceed = config.g.hijack_directories.auto_open or view.is_visible() if not should_proceed then return end local buf = vim.api.nvim_get_current_buf() local bufname = vim.api.nvim_buf_get_name(buf) if vim.fn.isdirectory(bufname) ~= 1 then return end local explorer = core.get_explorer() if not explorer then core.init(bufname) end explorer_fn("force_dirchange", bufname, true, false) end local function manage_netrw() if config.g.hijack_netrw then vim.cmd("silent! autocmd! FileExplorer *") vim.cmd("autocmd VimEnter * ++once silent! autocmd! FileExplorer *") end if config.g.disable_netrw then vim.g.loaded_netrw = 1 vim.g.loaded_netrwPlugin = 1 end end local function setup_autocommands() local augroup_id = vim.api.nvim_create_augroup("NvimTree", { clear = true }) local function create_nvim_tree_autocmd(name, custom_opts) local default_opts = { group = augroup_id } vim.api.nvim_create_autocmd(name, vim.tbl_extend("force", default_opts, custom_opts)) 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 config.g.actions.open_file.eject then view._prevent_buffer_override() else view.abandon_current_window() end end, }) if config.g.tab.sync.open then create_nvim_tree_autocmd("TabEnter", { callback = vim.schedule_wrap(M.tab_enter) }) end if config.g.sync_root_with_cwd then create_nvim_tree_autocmd("DirChanged", { callback = function() actions.tree.change_dir.fn(vim.loop.cwd()) end, }) end if config.g.update_focused_file.enable then create_nvim_tree_autocmd("BufEnter", { callback = function(event) local exclude = config.g.update_focused_file.exclude if type(exclude) == "function" and exclude(event) then return end utils.debounce("BufEnter:find_file", config.g.view.debounce_delay, function() actions.tree.find_file.fn() end) end, }) end if config.g.hijack_directories.enable and (config.g.disable_netrw or config.g.hijack_netrw) then create_nvim_tree_autocmd({ "BufEnter", "BufNewFile" }, { callback = M.open_on_directory, nested = true }) end if config.g.view.centralize_selection then create_nvim_tree_autocmd("BufEnter", { pattern = "NvimTree_*", callback = function() vim.schedule(function() vim.api.nvim_buf_call(0, function() local is_term_mode = vim.api.nvim_get_mode().mode == "t" if is_term_mode then return end vim.cmd([[norm! zz]]) end) end) end, }) end if config.g.diagnostics.enable then create_nvim_tree_autocmd("DiagnosticChanged", { callback = function(ev) log.line("diagnostics", "DiagnosticChanged") require("nvim-tree.diagnostics").update_lsp(ev) end, }) create_nvim_tree_autocmd("User", { pattern = "CocDiagnosticChange", callback = function() log.line("diagnostics", "CocDiagnosticChange") require("nvim-tree.diagnostics").update_coc() end, }) end if config.g.view.float.enable and config.g.view.float.quit_on_focus_loss then create_nvim_tree_autocmd("WinLeave", { pattern = "NvimTree_*", callback = function() if utils.is_nvim_tree_buf(0) then view.close() end end, }) end -- Handles event dispatch when tree is closed by `:q` create_nvim_tree_autocmd("WinClosed", { pattern = "*", ---@param ev vim.api.keyset.create_autocmd.callback_args callback = function(ev) 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 function M.purge_all_state() view.close_all_tabs() view.abandon_all_windows() local explorer = core.get_explorer() if explorer then require("nvim-tree.git").purge_state() explorer:destroy() core.reset_explorer() end -- purge orphaned that were not destroyed by their nodes require("nvim-tree.watcher").purge_watchers() end ---@param config_user? nvim_tree.config user supplied subset of config function M.setup(config_user) if vim.fn.has("nvim-0.9") == 0 then notify.warn("nvim-tree.lua requires Neovim 0.9 or higher") return end M.init_root = vim.fn.getcwd() config.setup(config_user) manage_netrw() require("nvim-tree.notify").setup(config.g) require("nvim-tree.log").setup(config.g) if log.enabled("config") then log.line("config", "default config + user") log.raw("config", "%s\n", vim.inspect(config.g)) end require("nvim-tree.actions").setup(config.g) require("nvim-tree.keymap").setup(config.g) require("nvim-tree.appearance").setup() require("nvim-tree.diagnostics").setup(config.g) require("nvim-tree.explorer"):setup(config.g) require("nvim-tree.explorer.watch").setup(config.g) require("nvim-tree.git").setup(config.g) require("nvim-tree.git.utils").setup(config.g) require("nvim-tree.view").setup(config.g) require("nvim-tree.lib").setup(config.g) require("nvim-tree.renderer.components").setup(config.g) require("nvim-tree.buffers").setup(config.g) require("nvim-tree.help").setup(config.g) require("nvim-tree.watcher").setup(config.g) setup_autocommands() if vim.g.NvimTreeSetup == 1 then -- subsequent calls to setup M.purge_all_state() end vim.g.NvimTreeSetup = 1 vim.api.nvim_exec_autocmds("User", { pattern = "NvimTreeSetup" }) require("nvim-tree.api.impl").hydrate_post_setup(api) end vim.g.NvimTreeRequired = 1 vim.api.nvim_exec_autocmds("User", { pattern = "NvimTreeRequired" }) return M ================================================ FILE: plugin/nvim-tree.lua ================================================ require("nvim-tree.commands").setup() ================================================ FILE: release-please-config.json ================================================ { "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", "include-v-in-tag": true, "bootstrap-sha": "34780aca5bac0a58c163ea30719a276fead1bd95", "packages": { ".": { "package-name": "nvim-tree", "release-type": "simple" } } } ================================================ FILE: scripts/doc-comments.sh ================================================ #!/usr/bin/env bash out=$(grep -nr "^--- @" lua) if [ "$out" ]; then last_file="" while read -r line; do file="$(echo "$line" | cut -d: -f1)" if [[ "$file" != "$last_file" ]]; then echo "$file:" >&2 last_file="$file" fi echo "$line" | awk -F: '{ printf(" line %s: %s\n", $2, $3) }' >&2 done <<< "$out" exit 1 fi ================================================ FILE: scripts/help-defaults.sh ================================================ #!/usr/bin/env sh # run after changing default config or keymap.lua M.on_attach_default # scrapes and updates nvim-tree-lua.txt # run from repository root: scripts/help-defaults.sh OR make help-update set -e # # Operate on a temporary file as sed -i writes the file thousands of times. # WIP="/tmp/nvim-tree-lua.txt" cp "doc/nvim-tree-lua.txt" "${WIP}" # # Inject default config # begin="config-default-start" end="config-default-end" inject="config-default-injection-placeholder" # scrape config.default, indented at 2 sed -n -E "/${begin}/,/${end}/{ /${begin}/d; /${end}/d; p; }" lua/nvim-tree/config.lua > /tmp/config.default.2.lua # indent to match help sed -E "s/^ / /" /tmp/config.default.2.lua > /tmp/config.default.6.lua # inject then remove the placeholder sed -i -E "/${inject}/r /tmp/config.default.6.lua" "${WIP}" sed -i -E "/${inject}/d" "${WIP}" # # Inject default mappings # begin="BEGIN_ON_ATTACH_DEFAULT" end="END_ON_ATTACH_DEFAULT" # scrape ON_ATTACH_DEFAULT, indented at 2 sed -n -E "/${begin}/,/${end}/{ /${begin}/d; /${end}/d; p; }" lua/nvim-tree/keymap.lua > /tmp/ON_ATTACH_DEFAULT.lua # help lua sed -i -E "/${begin}/,/${end}/{ /${begin}/{p; r /tmp/ON_ATTACH_DEFAULT.lua }; /${end}/p; d; }" "${WIP}" # help human # extract mode, lhs, api, desc; handle both "n" and {"n", "x"} mode forms echo > /tmp/ON_ATTACH_DEFAULT.help sed -E ' s/^ *vim\.keymap\.set\(\{([^}]+)\}, *"([^"]+)",.*api(.*),.*opts\("([^"]*)".*/\1 \2 \3 \4/ t reformat s/^ *vim\.keymap\.set\("(.)", *"([^"]+)",.*api(.*),.*opts\("([^"]*)".*/\1 \2 \3 \4/ t reformat d :reformat s/"//g s/, //g ' /tmp/ON_ATTACH_DEFAULT.lua | while read -r mode lhs apipath desc do printf ' %-17.17s %-4.4s %-26.26s %s\n' "\`${lhs}\`" "${mode}" "${desc}" "|nvim_tree.api${apipath}()|" >> /tmp/ON_ATTACH_DEFAULT.help done echo >> /tmp/ON_ATTACH_DEFAULT.help begin="Show the mappings:" end="======" sed -i -E "/${begin}/,/${end}/{ /${begin}/{p; r /tmp/ON_ATTACH_DEFAULT.help }; /${end}/p; d; }" "${WIP}" # # complete # mv "${WIP}" "doc/nvim-tree-lua.txt" ================================================ FILE: scripts/luals-check.sh ================================================ #!/usr/bin/env sh # Performs a lua-language-server check on all lua files. # $VIMRUNTIME specifies neovim runtime path, defaults to "/usr/share/nvim/runtime" if unset. # # Call with codestyle-check param to enable only codestyle-check # # lua-language-server is inconsisent about which parameters must be absolute paths therefore we pass every path as absolute if [ $# -eq 1 ] && [ "${1}" != "codestyle-check" ] || [ $# -gt 1 ] ; then echo "usage: ${0} [codestyle-check]" 1>&2 exit 1 fi DIR_NVT="${PWD}" if [ ! -f "${DIR_NVT}/scripts/luals-check.sh" ]; then echo "Must be run from nvim-tree root" 1>&2 exit 1 fi if [ -z "${VIMRUNTIME}" ]; then export VIMRUNTIME="/usr/share/nvim/runtime" echo "Defaulting to VIMRUNTIME=${VIMRUNTIME}" fi if [ ! -d "${VIMRUNTIME}" ]; then echo "\$VIMRUNTIME=${VIMRUNTIME} not found" 1>&2 exit 1 fi DIR_OUT="${DIR_NVT}/luals-out" LUARC="${DIR_OUT}/luarc.json" RC=0 # clear previous output rm -rf "${DIR_OUT}" mkdir "${DIR_OUT}" # create the luarc.json for the requested check case "${1}" in "codestyle-check") jq \ '.diagnostics.neededFileStatus[] = "None" | .diagnostics.neededFileStatus."codestyle-check" = "Any"' \ "${DIR_NVT}/.luarc.json" > "${LUARC}" ;; *) cp "${DIR_NVT}/.luarc.json" "${LUARC}" ;; esac for SRC in lua scripts; do DIR_SRC="${DIR_NVT}/${SRC}" FILE_OUT="${DIR_OUT}/out.${SRC}.log" echo "Checking ${SRC}/" lua-language-server --check="${DIR_SRC}" --configpath="${LUARC}" --checklevel=Information --logpath="${DIR_OUT}" --loglevel=error 2>&1 | tee "${FILE_OUT}" if ! grep --quiet "Diagnosis completed, no problems found" "${FILE_OUT}"; then RC=1 fi done exit "${RC}" ================================================ FILE: scripts/setup-hooks.sh ================================================ #!/usr/bin/env bash ln -sf ../../.hooks/pre-commit.sh .git/hooks/pre-commit ================================================ FILE: scripts/vimdoc.sh ================================================ #!/usr/bin/env sh # Wrapper around Nvim make targets: # # make doc - gen_vimdoc.lua # Generates doc/nvim-tree-lua.txt # Uses nvim-tree sources defined in scripts/vimdoc_config.lua # Shims above into src/gen/gen_vimdoc.lua, replacing Nvim's config. # # make lintdoc - lintdoc.lua # Validates doc/nvim-tree-lua.txt # Desired: # - tags valid # - links valid # Also: # - brand spelling, notably Nvim and Lua # # There are some hardcoded expectations which we work around as commented. set -e if [ $# -ne 1 ] || [ "${1}" != "doc" ] && [ "${1}" != "lintdoc" ]; then echo "usage: ${0} " 1>&2 exit 1 fi DIR_NVIM_SRC_DEF="/tmp/src/neovim-stable" if [ ! -d "lua/nvim-tree" ]; then echo "Must be run from nvim-tree root" 1>&2 exit 1 fi if [ -z "${DIR_NVIM_SRC}" ] && [ -d "${DIR_NVIM_SRC_DEF}" ]; then export DIR_NVIM_SRC="${DIR_NVIM_SRC_DEF}" fi if [ ! -d "${DIR_NVIM_SRC}" ]; then cat << EOM Nvim source v0.11+ is required to run ${0} Unavailable: ${DIR_NVIM_SRC_DEF} or \$DIR_NVIM_SRC=${DIR_NVIM_SRC} Please: mkdir -p ${DIR_NVIM_SRC_DEF} curl -L 'https://github.com/neovim/neovim/archive/refs/tags/stable.tar.gz' | tar zx --directory $(dirname "${DIR_NVIM_SRC_DEF}") or use your own e.g. export DIR_NVIM_SRC="\${HOME}/src/neovim" EOM exit 1 fi cleanup() { # remove source link rm -fv "${DIR_NVIM_SRC}/runtime/lua/nvim_tree" # remove our config rm -fv "${DIR_NVIM_SRC}/src/gen/vimdoc_config.lua" rm -fv "${DIR_NVIM_SRC}/runtime/lua/placeholder.lua" # remove generated help rm -fv "${DIR_NVIM_SRC}/runtime/doc/nvim-tree-lua.txt" # revert generator if present if [ -f "${DIR_NVIM_SRC}/src/gen/gen_vimdoc.lua.org" ]; then mv -v "${DIR_NVIM_SRC}/src/gen/gen_vimdoc.lua.org" "${DIR_NVIM_SRC}/src/gen/gen_vimdoc.lua" fi } # clean up any previous failed runs cleanup # runtime/doc is hardcoded, copy the help in cp -v "doc/nvim-tree-lua.txt" "${DIR_NVIM_SRC}/runtime/doc" # setup doc generation if [ "${1}" = "doc" ]; then # runtime/lua is available, link our sources in there # gen_vimdoc.lua doesn't like dashes in lua module names # -> use nvim_tree instead of nvim-tree ln -sv "${PWD}/lua/nvim-tree" "${DIR_NVIM_SRC}/runtime/lua/nvim_tree" # modify gen_vimdoc.lua to use our config, backing up original cp "${DIR_NVIM_SRC}/src/gen/gen_vimdoc.lua" "${DIR_NVIM_SRC}/src/gen/gen_vimdoc.lua.org" sed -i -E 's/spairs\(config\)/spairs\(require("gen.vimdoc_config")\)/g' "${DIR_NVIM_SRC}/src/gen/gen_vimdoc.lua" # leave a generic placeholder to bridge between nvim.gen_vimdoc.Config echo "---@brief placeholder" > "${DIR_NVIM_SRC}/runtime/lua/placeholder.lua" # copy our config cp -v "scripts/vimdoc_config.lua" "${DIR_NVIM_SRC}/src/gen" fi # run from within Nvim source cd "${DIR_NVIM_SRC}" make "${1}" cd - # copy the generated help out cp -v "${DIR_NVIM_SRC}/runtime/doc/nvim-tree-lua.txt" "doc" # cleanup as everything succeeded cleanup ================================================ FILE: scripts/vimdoc_config.lua ================================================ --nvim-tree configuration for Nvim's gen_vimdoc.lua --Returned config is injected into the above. --Execute with `make doc`, see scripts/vimdoc.sh for details. --gen_vimdoc keys by filename: -- FIXME: Using f_base will confuse `_meta/protocol.lua` with `protocol.lua` --Hence we must ensure that filenames are unique within each nvim.gen_vimdoc.Config[] ---@class (exact) Src ---@field helptag string must be globally unique ---@field section string arbitrary ---@field path string relative to cwd local base = "runtime/lua/nvim_tree/" local placeholder = "runtime/lua/placeholder.lua" ---@type Src[] local srcs_config = { { helptag = "nvim-tree-config", section = "Config", path = base .. "_meta/config.lua", }, { helptag = "nvim-tree-config-sort", section = "Config: sort", path = base .. "_meta/config/sort.lua", }, { helptag = "nvim-tree-config-view", section = "Config: view", path = base .. "_meta/config/view.lua", }, { helptag = "nvim-tree-config-renderer", section = "Config: renderer", path = base .. "_meta/config/renderer.lua", }, { helptag = "nvim-tree-config-hijack-directories", section = "Config: hijack_directories", path = base .. "_meta/config/hijack_directories.lua", }, { helptag = "nvim-tree-config-update-focused-file", section = "Config: update_focused_file", path = base .. "_meta/config/update_focused_file.lua", }, { helptag = "nvim-tree-config-system-open", section = "Config: system_open", path = base .. "_meta/config/system_open.lua", }, { helptag = "nvim-tree-config-git", section = "Config: git", path = base .. "_meta/config/git.lua", }, { helptag = "nvim-tree-config-diagnostics", section = "Config: diagnostics", path = base .. "_meta/config/diagnostics.lua", }, { helptag = "nvim-tree-config-modified", section = "Config: modified", path = base .. "_meta/config/modified.lua", }, { helptag = "nvim-tree-config-filters", section = "Config: filters", path = base .. "_meta/config/filters.lua", }, { helptag = "nvim-tree-config-live-filter", section = "Config: live_filter", path = base .. "_meta/config/live_filter.lua", }, { helptag = "nvim-tree-config-filesystem-watchers", section = "Config: filesystem_watchers", path = base .. "_meta/config/filesystem_watchers.lua", }, { helptag = "nvim-tree-config-actions", section = "Config: actions", path = base .. "_meta/config/actions.lua", }, { helptag = "nvim-tree-config-trash", section = "Config: trash", path = base .. "_meta/config/trash.lua", }, { helptag = "nvim-tree-config-tab", section = "Config: tab", path = base .. "_meta/config/tab.lua", }, { helptag = "nvim-tree-config-notify", section = "Config: notify", path = base .. "_meta/config/notify.lua", }, { helptag = "nvim-tree-config-bookmarks", section = "Config: bookmarks", path = base .. "_meta/config/bookmarks.lua", }, { helptag = "nvim-tree-config-help", section = "Config: help", path = base .. "_meta/config/help.lua", }, { helptag = "nvim-tree-config-ui", section = "Config: ui", path = base .. "_meta/config/ui.lua", }, { helptag = "nvim-tree-config-experimental", section = "Config: experimental", path = base .. "_meta/config/experimental.lua", }, { helptag = "nvim-tree-config-log", section = "Config: log", path = base .. "_meta/config/log.lua", }, { helptag = "nvim-tree-config-default", section = "Config: Default", path = base .. "_meta/config/default.lua", }, { helptag = "nvim-tree-api", section = "PLACEHOLDER", path = placeholder, }, } ---@type Src[] local srcs_api = { { helptag = "nvim-tree-api", section = "API", path = base .. "api.lua", }, { helptag = "nvim-tree-api-appearance", section = "API: appearance", path = base .. "_meta/api/appearance.lua", }, { helptag = "nvim-tree-api-commands", section = "API: commands", path = base .. "_meta/api/commands.lua", }, { helptag = "nvim-tree-api-config", section = "API: config", path = base .. "_meta/api/config.lua", }, { helptag = "nvim-tree-api-events", section = "API: events", path = base .. "_meta/api/events.lua", }, { helptag = "nvim-tree-api-filter", section = "API: filter", path = base .. "_meta/api/filter.lua", }, { helptag = "nvim-tree-api-fs", section = "API: fs", path = base .. "_meta/api/fs.lua", }, { helptag = "nvim-tree-api-git", section = "API: git", path = base .. "_meta/api/git.lua", }, { helptag = "nvim-tree-api-map", section = "API: map", path = base .. "_meta/api/map.lua", }, { helptag = "nvim-tree-api-marks", section = "API: marks", path = base .. "_meta/api/marks.lua", }, { helptag = "nvim-tree-api-node", section = "API: node", path = base .. "_meta/api/node.lua", }, { helptag = "nvim-tree-api-tree", section = "API: tree", path = base .. "_meta/api/tree.lua", }, { helptag = "nvim-tree-class", section = "PLACEHOLDER", path = placeholder, }, } ---@type Src[] local srcs_class = { { helptag = "nvim-tree-class", section = "Class: Class", path = base .. "classic.lua", }, { helptag = "nvim-tree-class-decorator", section = "Class: Decorator", path = base .. "_meta/api/decorator.lua", }, { helptag = "nvim-tree-class-decorator-example", section = "Class: Decorator example", path = base .. "_meta/api/decorator_example.lua", }, } ---Map paths to file names ---File names are the unique key that gen_vimdoc.lua uses ---@param srcs Src[] ---@return string[] file names local function section_order(srcs) return vim.tbl_map(function(src) return vim.fn.fnamemodify(src.path, ":t") end, srcs) end ---Extract paths ---@param srcs Src[] ---@return string[] file names local function files(srcs) return vim.tbl_map(function(src) return src.path end, srcs) end ---Find a Src or error. ---Name is the (sometimes specifically hardcoded) mangled case filename with .lua stripped ---@param name string ---@param srcs Src[] ---@return Src? local function src_by_name(name, srcs) for _, s in ipairs(srcs) do if s.path:match(name:lower() .. ".lua$") then return s end end error(string.format("\n\nPath for lower, extension stripped file name='%s' not found in\nsrcs=%s\n", name, vim.inspect(srcs))) end -- generator doesn't strip _meta local function normalise_module(fun) fun.module = fun.module and fun.module:gsub("._meta", "", 1) or nil end ---HACK ---Problem: --- Generator generates fields for a class' methods. --- This is a problem as method fields don't have a module and aren't transformed. --- Method field fun only contains: classvar, desc, name and (function) type ---Solution: --- Collect a map of "class:method" to modules when the real method passes through fn_xform --- This works as the real method function is processed before the field method. ---@type table local modules_by_method = {} -- @type nvim.gen_vimdoc.Config[] return { -- Config { filename = "nvim-tree-lua.txt", section_order = section_order(srcs_config), files = files(srcs_config), section_fmt = function(name) return src_by_name(name, srcs_config).section end, helptag_fmt = function(name) return src_by_name(name, srcs_config).helptag end, }, -- API { filename = "nvim-tree-lua.txt", section_order = section_order(srcs_api), files = files(srcs_api), section_fmt = function(name) return src_by_name(name, srcs_api).section end, helptag_fmt = function(name) return src_by_name(name, srcs_api).helptag end, fn_xform = function(fun) if (fun.module) then normalise_module(fun) -- remove the module prefix from the left aligned function name -- default fn_helptag_fmt adds it back to the help tag local replaced fun.name, replaced = fun.name:gsub("^" .. fun.module .. "%.", "", 1) if (replaced ~= 1) then error(string.format("\n\nfun.name='%s' does not start with module\nfun=%s", fun.name, vim.inspect(fun))) end end end, }, -- Classes { filename = "nvim-tree-lua.txt", section_order = section_order(srcs_class), files = files(srcs_class), section_fmt = function(name) return src_by_name(name, srcs_class).section end, helptag_fmt = function(name) return src_by_name(name, srcs_class).helptag end, fn_xform = function(fun) if (fun.module) then normalise_module(fun) -- strip the class file from the module fun.module = fun.module:gsub("%.[^%.]*$", "", 1) -- record the module for the method modules_by_method[fun.classvar .. ":" .. fun.name] = fun.module end end, -- fn_helptag_fmt_common derived -- module prepended to classes -- module is fetched from modules_by_method when fun.module unavailable fn_helptag_fmt = function(fun) local fn_sfx = fun.table and "" or "()" local module = fun.module or modules_by_method[fun.classvar .. ":" .. fun.name] return string.format("%s.%s:%s%s", module, fun.classvar, fun.name, fn_sfx) end, }, }