1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-04-13 12:30:40 +08:00

feat(filetree): add nvim-tree.lua

This commit is contained in:
Wang Shidong 2022-05-19 09:03:59 +08:00 committed by GitHub
parent fba36bf5a0
commit 62a15d02a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 7205 additions and 1 deletions

View File

@ -1307,6 +1307,7 @@ let g:spacevim_smartcloseignoreft = [
\ 'tagbar',
\ 'vimfiler',
\ 'defx',
\ 'NvimTree',
\ 'SpaceVimRunner',
\ 'SpaceVimREPL',
\ 'SpaceVimQuickFix',
@ -1732,6 +1733,12 @@ function! SpaceVim#welcome() abort
elseif exists(':NERDTree') == 2
NERDTree
wincmd p
elseif exists(':NvimTreeOpen') == 2
NvimTreeOpen
" the statusline of nvimtree is not udpated when open nvim tree in
" welcome function
doautocmd WinEnter
wincmd p
endif
endif
endfunction

View File

@ -84,6 +84,8 @@ function! SpaceVim#layers#core#plugins() abort
call add(plugins, [g:_spacevim_root_dir . 'bundle/defx-git',{'merged' : 0, 'loadconf' : 1}])
call add(plugins, [g:_spacevim_root_dir . 'bundle/defx-icons',{'merged' : 0}])
call add(plugins, [g:_spacevim_root_dir . 'bundle/defx-sftp',{'merged' : 0}])
elseif g:spacevim_filemanager ==# 'nvim-tree'
call add(plugins, [g:_spacevim_root_dir . 'bundle/nvim-tree.lua',{'merged' : 0, 'loadconf' : 1}])
endif
if !g:spacevim_vimcompatible
@ -313,6 +315,11 @@ function! SpaceVim#layers#core#config() abort
call SpaceVim#mapping#space#def('nnoremap', ['f', 'T'], 'Defx -no-toggle', 'show-file-tree', 1)
call SpaceVim#mapping#space#def('nnoremap', ['f', 'o'], "Defx -no-toggle -search=`expand('%:p')` `stridx(expand('%:p'), getcwd()) < 0? expand('%:p:h'): getcwd()`", 'open-file-tree', 1)
call SpaceVim#mapping#space#def('nnoremap', ['b', 't'], 'exe "Defx -no-toggle " . fnameescape(expand("%:p:h"))', 'show-file-tree-at-buffer-dir', 1)
elseif g:spacevim_filemanager ==# 'nvim-tree'
call SpaceVim#mapping#space#def('nnoremap', ['f', 't'], 'NvimTreeToggle', 'toggle-file-tree', 1)
call SpaceVim#mapping#space#def('nnoremap', ['f', 'T'], 'NvimTree', 'show-file-tree', 1)
call SpaceVim#mapping#space#def('nnoremap', ['f', 'o'], "NvimTreeFindFile", 'open-file-tree', 1)
call SpaceVim#mapping#space#def('nnoremap', ['b', 't'], 'exe "NvimTreeOpen " . fnameescape(expand("%:p:h"))', 'show-file-tree-at-buffer-dir', 1)
endif
call SpaceVim#mapping#space#def('nnoremap', ['f', 'y'], 'call SpaceVim#util#CopyToClipboard()', 'show-and-copy-buffer-filename', 1)
nnoremap <silent> <Plug>YankGitRemoteURL :call SpaceVim#util#CopyToClipboard(2)<Cr>

View File

@ -425,6 +425,13 @@ function! SpaceVim#layers#core#statusline#get(...) abort
\ . ' defx '
\ . '%#SpaceVim_statusline_b_SpaceVim_statusline_c#'
\ . s:lsep . ' '
elseif &filetype ==# 'NvimTree'
return '%#SpaceVim_statusline_ia#' . s:winnr(1)
\ . '%#SpaceVim_statusline_ia_SpaceVim_statusline_b#' . s:lsep
\ . '%#SpaceVim_statusline_b#'
\ . ' NvimTree '
\ . '%#SpaceVim_statusline_b_SpaceVim_statusline_c#'
\ . s:lsep . ' '
elseif &filetype ==# 'Fuzzy'
return '%#SpaceVim_statusline_a_bold# Fuzzy %#SpaceVim_statusline_a_SpaceVim_statusline_b#' . s:lsep
\ . '%#SpaceVim_statusline_b# %{fuzzy#statusline()} %#SpaceVim_statusline_b_SpaceVim_statusline_c#' . s:lsep

View File

@ -24,7 +24,7 @@ let s:default = {
\ 'min_size' : 3,
\ 'width' : 1,
\ 'right_offset' : 1,
\ 'excluded_filetypes' : ['startify', 'leaderf'],
\ 'excluded_filetypes' : ['startify', 'leaderf', 'NvimTree'],
\ 'shape' : {
\ 'head' : '▲',
\ 'body' : '█',

View File

@ -25,5 +25,6 @@ In `bundle/` directory, there are two kinds of plugins: forked plugins without c
- [deoplete](https://github.com/Shougo/deoplete.nvim/tree/1c40f648d2b00e70beb4c473b7c0e32b633bd9ae)
- [vim-scala@7657218](https://github.com/derekwyatt/vim-scala/tree/7657218f14837395a4e6759f15289bad6febd1b4)
- [neosnippet.vim@5973e80](https://github.com/Shougo/neosnippet.vim/tree/5973e801e7ad38a01e888cb794d74e076a35ea9b)
- [nvim-tree.lua](https://github.com/kyazdani42/nvim-tree.lua/tree/9049f364cc3ceaff07ab130e1d35aec9e4124563)
- [telescope.nvim@39b12d8](https://github.com/nvim-telescope/telescope.nvim/tree/39b12d84e86f5054e2ed98829b367598ae53ab41)
- [telescope-ctags-outline.nvim](https://github.com/fcying/telescope-ctags-outline.nvim)

View File

@ -0,0 +1,9 @@
root = true
[*]
insert_final_newline = true
end_of_line = lf
[*.lua]
indent_style = space
indent_size = 2

View File

@ -0,0 +1,3 @@
# These are supported funding model platforms
github: kyazdani42

View File

@ -0,0 +1,71 @@
name: Bug report
description: Report a problem with nvim-tree
labels: [bug]
body:
- type: markdown
attributes:
value: |
Before reporting: search [existing issues](https://github.com/kyazdani42/nvim-tree.lua/issues) and make sure that nvim-tree is updated to the latest version. If you are experiencing performance issues, please [enable profiling](https://github.com/kyazdani42/nvim-tree.lua#performance-issues) and attach the logs.
- 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/kyazdani42/nvim-tree.lua#notice)."
placeholder: |
NVIM v0.6.1
Build type&#58 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: "nvim-tree version"
description: "`cd <your-package-directory>/nvim-tree.lua ; git log --format='%h' -n 1`"
placeholder: |
nvim-tree branch, commit or tag number
validations:
required: true
- type: textarea
attributes:
label: "Minimal config"
description: "Minimal(!) configuration necessary to reproduce the issue.
(Right click) save [nvt-min.lua](https://raw.githubusercontent.com/kyazdani42/nvim-tree.lua/master/.github/ISSUE_TEMPLATE/nvt-min.lua) to `/tmp` and run using `nvim -nu /tmp/nvt-min.lua`
If _absolutely_ necessary, add plugins and modify the nvim-tree setup at the indicated lines.
Paste the contents of your `/tmp/nvt-min.lua` here."
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)."

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: feature request
assignees: ''
---
**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.

View File

@ -0,0 +1,34 @@
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",
"kyazdani42/nvim-tree.lua",
"kyazdani42/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

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 KiB

View File

@ -0,0 +1,34 @@
name: CI
on:
pull_request:
branches:
- '*'
push:
branches:
- master
jobs:
luacheck:
name: luacheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Prepare
run: |
sudo apt-get update
sudo add-apt-repository universe
sudo apt install luarocks -y
sudo luarocks install luacheck
- name: Run luacheck
run: luacheck .
stylua:
name: stylua
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: JohnnyMorganz/stylua-action@1.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --color always --check lua/

View File

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

View File

@ -0,0 +1,14 @@
-- vim: ft=lua tw=80
-- Don't report unused self arguments of methods.
self = false
ignore = {
"631", -- max_line_length
}
-- Global objects defined by the C code
globals = {
"vim",
"TreeExplorer"
}

View File

@ -0,0 +1,6 @@
column_width = 120
line_endings = "Unix"
indent_type = "Spaces"
indent_width = 2
quote_style = "AutoPreferDouble"
call_parentheses = "None"

View File

@ -0,0 +1,24 @@
# Contributing to `nvim-tree.lua`
Thank you for contributing.
## Styling and formatting
Code is formatted using luacheck, and linted using stylua.
You can install these with:
```bash
luarocks install luacheck
cargo install stylua
```
## Adding new actions
To add a new action, add a file in `actions/name-of-the-action.lua`. You should export a `setup` function if some configuration is needed.
## Documentation
When adding new options, you should declare the defaults in the main `nvim-tree.lua` file.
Once you did, you should run the `update-default-opts.sh` script which will update the default documentation in the README and the help file.
Documentation for options should also be added, see how this is done after `nvim-tree.disable_netrw` in the `nvim-tree-lua.txt` file.

View File

@ -0,0 +1,15 @@
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 <http://www.gnu.org/licenses/>.

View File

@ -0,0 +1,391 @@
# A File Explorer For Neovim Written In Lua
[![CI](https://github.com/kyazdani42/nvim-tree.lua/actions/workflows/ci.yml/badge.svg)](https://github.com/kyazdani42/nvim-tree.lua/actions/workflows/ci.yml)
## Notice
This plugin requires [neovim >=0.6.0](https://github.com/neovim/neovim/wiki/Installing-Neovim).
If you have issues since the recent setup migration, check out [this guide](https://github.com/kyazdani42/nvim-tree.lua/issues/674)
## Install
Install with [vim-plug](https://github.com/junegunn/vim-plug):
```vim
" requires
Plug 'kyazdani42/nvim-web-devicons' " for file icons
Plug 'kyazdani42/nvim-tree.lua'
```
Install with [packer](https://github.com/wbthomason/packer.nvim):
```lua
use {
'kyazdani42/nvim-tree.lua',
requires = {
'kyazdani42/nvim-web-devicons', -- optional, for file icon
},
tag = 'nightly' -- optional, updated every week. (see issue #1193)
}
```
## Setup
Options are currently being migrated into the setup function, you need to run `require'nvim-tree'.setup()` in your personal configurations.
Setup should be run in a lua file or in a lua heredoc (`:help lua-heredoc`) if using in a vim file.
Note that options under the `g:` command should be set **BEFORE** running the setup function.
These are being migrated to the setup function incrementally, check [this issue](https://github.com/kyazdani42/nvim-tree.lua/issues/674) if you encounter any problems related to configs not working after update.
```vim
" vimrc
let g:nvim_tree_git_hl = 1 "0 by default, will enable file highlight for git attributes (can be used without the icons).
let g:nvim_tree_highlight_opened_files = 1 "0 by default, will enable folder and file icon highlight for opened files/directories.
let g:nvim_tree_root_folder_modifier = ':~' "This is the default. See :help filename-modifiers for more options
let g:nvim_tree_add_trailing = 1 "0 by default, append a trailing slash to folder names
let g:nvim_tree_group_empty = 1 " 0 by default, compact folders that only contain a single folder into one node in the file tree
let g:nvim_tree_icon_padding = ' ' "one space by default, used for rendering the space between the icon and the filename. Use with caution, it could break rendering if you set an empty string depending on your font.
let g:nvim_tree_symlink_arrow = ' >> ' " defaults to ' ➛ '. used as a separator between symlinks' source and target.
let g:nvim_tree_respect_buf_cwd = 1 "0 by default, will change cwd of nvim-tree to that of new buffer's when opening nvim-tree.
let g:nvim_tree_create_in_closed_folder = 1 "0 by default, When creating files, sets the path of a file when cursor is on a closed folder to the parent folder when 0, and inside the folder when 1.
let g:nvim_tree_special_files = { 'README.md': 1, 'Makefile': 1, 'MAKEFILE': 1 } " List of filenames that gets highlighted with NvimTreeSpecialFile
let g:nvim_tree_show_icons = {
\ 'git': 1,
\ 'folders': 0,
\ 'files': 0,
\ 'folder_arrows': 0,
\ }
"If 0, do not show the icons for one of 'git' 'folder' and 'files'
"1 by default, notice that if 'files' is 1, it will only display
"if nvim-web-devicons is installed and on your runtimepath.
"if folder is 1, you can also tell folder_arrows 1 to show small arrows next to the folder icons.
"but this will not work when you set renderer.indent_markers.enable (because of UI conflict)
" default will show icon by default if no icon is provided
" default shows no icon by default
let g:nvim_tree_icons = {
\ 'default': "",
\ 'symlink': "",
\ 'git': {
\ 'unstaged': "✗",
\ 'staged': "✓",
\ 'unmerged': "",
\ 'renamed': "➜",
\ 'untracked': "★",
\ 'deleted': "",
\ 'ignored': "◌"
\ },
\ 'folder': {
\ 'arrow_open': "",
\ 'arrow_closed': "",
\ 'default': "",
\ 'open': "",
\ 'empty': "",
\ 'empty_open': "",
\ 'symlink': "",
\ 'symlink_open': "",
\ }
\ }
nnoremap <C-n> :NvimTreeToggle<CR>
nnoremap <leader>r :NvimTreeRefresh<CR>
nnoremap <leader>n :NvimTreeFindFile<CR>
" More available functions:
" NvimTreeOpen
" NvimTreeClose
" NvimTreeFocus
" NvimTreeFindFileToggle
" NvimTreeResize
" NvimTreeCollapse
" NvimTreeCollapseKeepBuffers
set termguicolors " this variable must be enabled for colors to be applied properly
" a list of groups can be found at `:help nvim_tree_highlight`
highlight NvimTreeFolderIcon guibg=blue
```
```lua
-- init.lua
-- empty setup using defaults: add your own options
require'nvim-tree'.setup {
}
-- OR
-- setup with all defaults
-- each of these are documented in `:help nvim-tree.OPTION_NAME`
require'nvim-tree'.setup { -- BEGIN_DEFAULT_OPTS
auto_reload_on_write = true,
disable_netrw = false,
hijack_cursor = false,
hijack_netrw = true,
hijack_unnamed_buffer_when_opening = false,
ignore_buffer_on_setup = false,
open_on_setup = false,
open_on_setup_file = false,
open_on_tab = false,
sort_by = "name",
update_cwd = false,
view = {
width = 30,
height = 30,
hide_root_folder = false,
side = "left",
preserve_window_proportions = false,
number = false,
relativenumber = false,
signcolumn = "yes",
mappings = {
custom_only = false,
list = {
-- user mappings go here
},
},
},
renderer = {
indent_markers = {
enable = false,
icons = {
corner = "└ ",
edge = "│ ",
none = " ",
},
},
icons = {
webdev_colors = true,
git_placement = "before",
}
},
hijack_directories = {
enable = true,
auto_open = true,
},
update_focused_file = {
enable = false,
update_cwd = false,
ignore_list = {},
},
ignore_ft_on_setup = {},
system_open = {
cmd = "",
args = {},
},
diagnostics = {
enable = false,
show_on_dirs = false,
icons = {
hint = "",
info = "",
warning = "",
error = "",
},
},
filters = {
dotfiles = false,
custom = {},
exclude = {},
},
git = {
enable = true,
ignore = true,
timeout = 400,
},
actions = {
use_system_clipboard = true,
change_dir = {
enable = true,
global = false,
restrict_above_cwd = false,
},
open_file = {
quit_on_open = false,
resize_window = false,
window_picker = {
enable = true,
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
exclude = {
filetype = { "notify", "packer", "qf", "diff", "fugitive", "fugitiveblame" },
buftype = { "nofile", "terminal", "help" },
},
},
},
},
trash = {
cmd = "trash",
require_confirm = true,
},
log = {
enable = false,
truncate = false,
types = {
all = false,
config = false,
copy_paste = false,
diagnostics = false,
git = false,
profile = false,
},
},
} -- END_DEFAULT_OPTS
```
## Key Bindings
### Default actions
- `<CR>` or `o` on the root folder will cd in the above directory
- `<C-]>` will cd in the directory under the cursor
- `<BS>` will close current opened directory or parent
- type `a` to add a file. Adding a directory requires leaving a leading `/` at the end of the path.
> you can add multiple directories by doing foo/bar/baz/f and it will add foo bar and baz directories and f as a file
- type `r` to rename a file
- type `<C-r>` to rename a file and omit the filename on input
- type `x` to add/remove file/directory to cut clipboard
- type `c` to add/remove file/directory to copy clipboard
- type `y` will copy name to system clipboard
- type `Y` will copy relative path to system clipboard
- type `gy` will copy absolute path to system clipboard
- type `p` to paste from clipboard. Cut clipboard has precedence over copy (will prompt for confirmation)
- type `d` to delete a file (will prompt for confirmation)
- type `D` to trash a file (configured in setup())
- type `]c` to go to next git item
- type `[c` to go to prev git item
- type `-` to navigate up to the parent directory of the current file/directory
- type `s` to open a file with default system application or a folder with default file manager (if you want to change the command used to do it see `:h nvim-tree.setup` under `system_open`)
- if the file is a directory, `<CR>` will open the directory otherwise it will open the file in the buffer near the tree
- if the file is a symlink, `<CR>` will follow the symlink (if the target is a file)
- `<C-v>` will open the file in a vertical split
- `<C-x>` will open the file in a horizontal split
- `<C-t>` will open the file in a new tab
- `<Tab>` will open the file as a preview (keeps the cursor in the tree)
- `I` will toggle visibility of hidden folders / files
- `H` will toggle visibility of dotfiles (files/folders starting with a `.`)
- `R` will refresh the tree
- Double left click acts like `<CR>`
- Double right click acts like `<C-]>`
- `W` will collapse the whole tree
- `S` will prompt the user to enter a path and then expands the tree to match the path
- `.` will enter vim command mode with the file the cursor is on
- `C-k` will toggle a popup with file infos about the file under the cursor
### Settings
The `list` option in `view.mappings.list` is a table of
```lua
-- key can be either a string or a table of string (lhs)
-- action is the name of the action, set to `""` to remove default action
-- action_cb is the function that will be called, it receives the node as a parameter. Optional for default actions
-- mode is normal by default
local tree_cb = require'nvim-tree.config'.nvim_tree_callback
local function print_node_path(node) {
print(node.absolute_path)
}
local list = {
{ key = {"<CR>", "o" }, action = "edit", mode = "n"},
{ key = "p", action = "print_path", action_cb = print_node_path },
{ key = "s", cb = tree_cb("vsplit") }, --tree_cb and the cb property are deprecated
{ key = "<2-RightMouse>", action = "" }, -- will remove default cd action
}
```
These are the default bindings:
```lua
-- default mappings
local list = {
{ key = {"<CR>", "o", "<2-LeftMouse>"}, action = "edit" },
{ key = "<C-e>", action = "edit_in_place" },
{ key = {"O"}, action = "edit_no_picker" },
{ key = {"<2-RightMouse>", "<C-]>"}, action = "cd" },
{ key = "<C-v>", action = "vsplit" },
{ key = "<C-x>", action = "split" },
{ key = "<C-t>", action = "tabnew" },
{ key = "<", action = "prev_sibling" },
{ key = ">", action = "next_sibling" },
{ key = "P", action = "parent_node" },
{ key = "<BS>", action = "close_node" },
{ key = "<Tab>", action = "preview" },
{ key = "K", action = "first_sibling" },
{ key = "J", action = "last_sibling" },
{ key = "I", action = "toggle_git_ignored" },
{ key = "H", action = "toggle_dotfiles" },
{ key = "R", action = "refresh" },
{ key = "a", action = "create" },
{ key = "d", action = "remove" },
{ key = "D", action = "trash" },
{ key = "r", action = "rename" },
{ key = "<C-r>", action = "full_rename" },
{ key = "x", action = "cut" },
{ key = "c", action = "copy" },
{ key = "p", action = "paste" },
{ key = "y", action = "copy_name" },
{ key = "Y", action = "copy_path" },
{ key = "gy", action = "copy_absolute_path" },
{ key = "[c", action = "prev_git_item" },
{ key = "]c", action = "next_git_item" },
{ key = "-", action = "dir_up" },
{ key = "s", action = "system_open" },
{ key = "q", action = "close" },
{ key = "g?", action = "toggle_help" },
{ key = "W", action = "collapse_all" },
{ key = "S", action = "search_node" },
{ key = "<C-k>", action = "toggle_file_info" },
{ key = ".", action = "run_file_command" }
}
```
You can toggle the help UI by pressing `g?`.
## Tips & reminders
1. You can add a directory by adding a `/` at the end of the paths, entering multiple directories `BASE/foo/bar/baz` will add directory foo, then bar and add a file baz to it.
2. You can update window options for the tree by setting `require"nvim-tree.view".View.winopts.MY_OPTION = MY_OPTION_VALUE`
3. `toggle` has a second parameter which allows to toggle without focusing the explorer (`require"nvim-tree".toggle(false, true)`).
4. You can allow nvim-tree to behave like vinegar (see `:help nvim-tree-vinegar`).
5. If you `:set nosplitright`, the files will open on the left side of the tree, placing the tree window in the right side of the file you opened.
6. You can automatically close the tab/vim when nvim-tree is the last window in the tab. WARNING: other plugins or automation may interfere with this:
```vim
autocmd BufEnter * ++nested if winnr('$') == 1 && bufname() == 'NvimTree_' . tabpagenr() | quit | endif
```
## Diagnostic Logging
You may enable diagnostic logging to `$XDG_CACHE_HOME/nvim/nvim-tree.log`. See `:help nvim-tree.log`.
## Performance Issues
If you are experiencing performance issues with nvim-tree.lua, you can enable profiling in the logs. It is advisable to enable git logging at the same time, as that can be a source of performance problems.
```lua
log = {
enable = true,
truncate = true,
types = {
git = true,
profile = true,
},
},
```
Please attach `$XDG_CACHE_HOME/nvim/nvim-tree.log` if you raise an issue.
*Performance Tips:*
* If you are using fish as an editor shell (which might be fixed in the future), try set `shell=/bin/bash` in your vim config.
* Try manually running the git command (see the logs) in your shell e.g. `git --no-optional-locks status --porcelain=v1 --ignored=matching -u`.
* Huge git repositories may timeout after the default `git.timeout` of 400ms. Try increasing that in your setup if you see `[git] job timed out` in the logs.
* Try temporarily disabling git integration by setting `git.enable = false` in your setup.
## Screenshots
![alt text](.github/screenshot.png?raw=true "kyazdani42 tree")
![alt text](.github/screenshot2.png?raw=true "akin909 tree")
![alt text](.github/screenshot3.png?raw=true "stsewd tree")
![alt text](.github/screenshot4.png?raw=true "reyhankaplan tree")

1
bundle/nvim-tree.lua/doc/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
tags

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,519 @@
local luv = vim.loop
local api = vim.api
local lib = require "nvim-tree.lib"
local log = require "nvim-tree.log"
local colors = require "nvim-tree.colors"
local renderer = require "nvim-tree.renderer"
local view = require "nvim-tree.view"
local utils = require "nvim-tree.utils"
local change_dir = require "nvim-tree.actions.change-dir"
local legacy = require "nvim-tree.legacy"
local core = require "nvim-tree.core"
local _config = {}
local M = {}
function M.focus()
M.open()
view.focus()
end
---@deprecated
M.on_keypress = require("nvim-tree.actions").on_keypress
function M.toggle(find_file, no_focus)
if view.is_visible() then
view.close()
else
local previous_buf = api.nvim_get_current_buf()
M.open()
if _config.update_focused_file.enable or find_file then
M.find_file(false, previous_buf)
end
if no_focus then
vim.cmd "noautocmd wincmd p"
end
end
end
function M.open(cwd)
cwd = cwd ~= "" and cwd or nil
if view.is_visible() then
lib.set_target_win()
view.focus()
else
lib.open(cwd)
end
end
function M.open_replacing_current_buffer()
if view.is_visible() then
return
end
local buf = api.nvim_get_current_buf()
local bufname = api.nvim_buf_get_name(buf)
if bufname == "" or vim.loop.fs_stat(bufname) == nil then
return
end
local cwd = vim.fn.fnamemodify(bufname, ":p:h")
if not core.get_explorer() or cwd ~= core.get_cwd() then
core.init(cwd)
end
view.open_in_current_win { hijack_current_buf = false, resize = false }
require("nvim-tree.renderer").draw()
require("nvim-tree.actions.find-file").fn(bufname)
end
function M.tab_change()
if view.is_visible { any_tabpage = true } then
local bufname = vim.api.nvim_buf_get_name(0)
if bufname:match "Neogit" ~= nil or bufname:match "--graph" ~= nil then
return
end
view.open { focus_tree = false }
require("nvim-tree.renderer").draw()
end
end
local function find_existing_windows()
return vim.tbl_filter(function(win)
local buf = api.nvim_win_get_buf(win)
return api.nvim_buf_get_name(buf):match "NvimTree" ~= nil
end, api.nvim_list_wins())
end
local function is_file_readable(fname)
local stat = luv.fs_stat(fname)
return stat and stat.type == "file" and luv.fs_access(fname, "R")
end
local function update_base_dir_with_filepath(filepath, bufnr)
local ft = api.nvim_buf_get_option(bufnr, "filetype") or ""
for _, value in pairs(_config.update_focused_file.ignore_list) do
if utils.str_find(filepath, value) or utils.str_find(ft, value) then
return
end
end
if not vim.startswith(filepath, core.get_explorer().cwd) then
change_dir.fn(vim.fn.fnamemodify(filepath, ":p:h"))
end
end
function M.find_file(with_open, bufnr)
if not with_open and not core.get_explorer() then
return
end
bufnr = bufnr or api.nvim_get_current_buf()
local bufname = api.nvim_buf_get_name(bufnr)
local filepath = utils.canonical_path(vim.fn.fnamemodify(bufname, ":p"))
if not is_file_readable(filepath) then
return
end
if with_open then
M.open()
end
vim.schedule(function()
-- if we don't schedule, it will search for NvimTree
if _config.update_focused_file.update_cwd then
update_base_dir_with_filepath(filepath, bufnr)
end
require("nvim-tree.actions.find-file").fn(filepath)
end)
end
M.resize = view.resize
function M.open_on_directory()
local should_proceed = M.initialized and (_config.hijack_directories.auto_open or view.is_visible())
if not should_proceed then
return
end
local buf = api.nvim_get_current_buf()
local bufname = api.nvim_buf_get_name(buf)
if vim.fn.isdirectory(bufname) ~= 1 then
return
end
change_dir.force_dirchange(bufname, true)
end
function M.reset_highlight()
colors.setup()
renderer.render_hl(view.get_bufnr())
end
local prev_line
function M.place_cursor_on_node()
local l = vim.api.nvim_win_get_cursor(0)[1]
if l == prev_line then
return
end
prev_line = l
local node = lib.get_node_at_cursor()
if not node or node.name == ".." then
return
end
local line = api.nvim_get_current_line()
local cursor = api.nvim_win_get_cursor(0)
local idx = vim.fn.stridx(line, node.name)
if idx >= 0 then
api.nvim_win_set_cursor(0, { cursor[1], idx })
end
end
function M.on_enter(netrw_disabled)
local bufnr = api.nvim_get_current_buf()
local bufname = api.nvim_buf_get_name(bufnr)
local buftype = api.nvim_buf_get_option(bufnr, "filetype")
local ft_ignore = _config.ignore_ft_on_setup
local stats = luv.fs_stat(bufname)
local is_dir = stats and stats.type == "directory"
local is_file = stats and stats.type == "file"
local cwd
if is_dir then
cwd = vim.fn.expand(bufname)
-- INFO: could potentially conflict with rooter plugins
vim.cmd("noautocmd cd " .. cwd)
end
local lines = not is_dir and api.nvim_buf_get_lines(bufnr, 0, -1, false) or {}
local buf_has_content = #lines > 1 or (#lines == 1 and lines[1] ~= "")
local buf_is_dir = is_dir and netrw_disabled
local buf_is_empty = bufname == "" and not buf_has_content
local should_be_preserved = vim.tbl_contains(ft_ignore, buftype)
local should_open = false
local should_focus_other_window = false
local should_find = false
if (_config.open_on_setup or _config.open_on_setup_file) and not should_be_preserved then
if buf_is_dir or buf_is_empty then
should_open = true
elseif is_file and _config.open_on_setup_file then
should_open = true
should_focus_other_window = true
should_find = _config.update_focused_file.enable
elseif _config.ignore_buffer_on_setup then
should_open = true
should_focus_other_window = true
end
end
local should_hijack = _config.hijack_directories.enable
and _config.hijack_directories.auto_open
and is_dir
and not should_be_preserved
-- Session that left a NvimTree Buffer opened, reopen with it
local existing_tree_wins = find_existing_windows()
if existing_tree_wins[1] then
api.nvim_set_current_win(existing_tree_wins[1])
end
if should_open or should_hijack or existing_tree_wins[1] ~= nil then
lib.open(cwd)
if should_focus_other_window then
vim.cmd "noautocmd wincmd p"
if should_find then
M.find_file(false)
end
end
end
M.initialized = true
end
function M.get_config()
return M.config
end
local function manage_netrw(disable_netrw, hijack_netrw)
if hijack_netrw then
vim.cmd "silent! autocmd! FileExplorer *"
vim.cmd "autocmd VimEnter * ++once silent! autocmd! FileExplorer *"
end
if disable_netrw then
vim.g.loaded_netrw = 1
vim.g.loaded_netrwPlugin = 1
end
end
local function setup_vim_commands()
vim.cmd [[
command! -nargs=? -complete=dir NvimTreeOpen lua require'nvim-tree'.open("<args>")
command! NvimTreeClose lua require'nvim-tree.view'.close()
command! NvimTreeToggle lua require'nvim-tree'.toggle(false)
command! NvimTreeFocus lua require'nvim-tree'.focus()
command! NvimTreeRefresh lua require'nvim-tree.actions.reloaders'.reload_explorer()
command! NvimTreeClipboard lua require'nvim-tree.actions.copy-paste'.print_clipboard()
command! NvimTreeFindFile lua require'nvim-tree'.find_file(true)
command! NvimTreeFindFileToggle lua require'nvim-tree'.toggle(true)
command! -nargs=1 NvimTreeResize lua require'nvim-tree'.resize("<args>")
command! NvimTreeCollapse lua require'nvim-tree.actions.collapse-all'.fn()
command! NvimTreeCollapseKeepBuffers lua require'nvim-tree.actions.collapse-all'.fn(true)
]]
end
function M.change_dir(name)
change_dir.fn(name)
if _config.update_focused_file.enable then
M.find_file(false)
end
end
local function setup_autocommands(opts)
vim.cmd "augroup NvimTree"
vim.cmd "autocmd!"
-- reset highlights when colorscheme is changed
vim.cmd "au ColorScheme * lua require'nvim-tree'.reset_highlight()"
if opts.auto_reload_on_write then
vim.cmd "au BufWritePost * lua require'nvim-tree.actions.reloaders'.reload_explorer()"
end
vim.cmd "au User FugitiveChanged,NeogitStatusRefreshed lua require'nvim-tree.actions.reloaders'.reload_git()"
if opts.open_on_tab then
vim.cmd "au TabEnter * lua require'nvim-tree'.tab_change()"
end
if opts.hijack_cursor then
vim.cmd "au CursorMoved NvimTree_* lua require'nvim-tree'.place_cursor_on_node()"
end
if opts.update_cwd then
vim.cmd "au DirChanged * lua require'nvim-tree'.change_dir(vim.loop.cwd())"
end
if opts.update_focused_file.enable then
vim.cmd "au BufEnter * lua require'nvim-tree'.find_file(false)"
end
if not opts.actions.open_file.quit_on_open then
vim.cmd "au BufWipeout NvimTree_* lua require'nvim-tree.view'._prevent_buffer_override()"
else
vim.cmd "au BufWipeout NvimTree_* lua require'nvim-tree.view'.abandon_current_window()"
end
if opts.hijack_directories.enable then
vim.cmd "au BufEnter,BufNewFile * lua require'nvim-tree'.open_on_directory()"
end
vim.cmd "augroup end"
end
local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
auto_reload_on_write = true,
disable_netrw = false,
hijack_cursor = false,
hijack_netrw = true,
hijack_unnamed_buffer_when_opening = false,
ignore_buffer_on_setup = false,
open_on_setup = false,
open_on_setup_file = false,
open_on_tab = false,
sort_by = "name",
update_cwd = false,
view = {
width = 30,
height = 30,
hide_root_folder = false,
side = "left",
preserve_window_proportions = false,
number = false,
relativenumber = false,
signcolumn = "yes",
mappings = {
custom_only = false,
list = {
-- user mappings go here
},
},
},
renderer = {
indent_markers = {
enable = false,
icons = {
corner = "",
edge = "",
none = " ",
},
},
icons = {
webdev_colors = true,
git_placement = "before",
},
},
hijack_directories = {
enable = true,
auto_open = true,
},
update_focused_file = {
enable = false,
update_cwd = false,
ignore_list = {},
},
ignore_ft_on_setup = {},
system_open = {
cmd = "",
args = {},
},
diagnostics = {
enable = false,
show_on_dirs = false,
icons = {
hint = "",
info = "",
warning = "",
error = "",
},
},
filters = {
dotfiles = false,
custom = {},
exclude = {},
},
git = {
enable = true,
ignore = true,
timeout = 400,
},
actions = {
use_system_clipboard = true,
change_dir = {
enable = true,
global = false,
restrict_above_cwd = false,
},
open_file = {
quit_on_open = false,
resize_window = false,
window_picker = {
enable = true,
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
exclude = {
filetype = { "notify", "packer", "qf", "diff", "fugitive", "fugitiveblame" },
buftype = { "nofile", "terminal", "help" },
},
},
},
},
trash = {
cmd = "trash",
require_confirm = true,
},
log = {
enable = false,
truncate = false,
types = {
all = false,
config = false,
copy_paste = false,
diagnostics = false,
git = false,
profile = false,
},
},
} -- END_DEFAULT_OPTS
local function merge_options(conf)
return vim.tbl_deep_extend("force", DEFAULT_OPTS, conf or {})
end
local FIELD_OVERRIDE_TYPECHECK = {
width = { string = true, ["function"] = true, number = true },
height = { string = true, ["function"] = true, number = true },
}
local function validate_options(conf)
local msg
local function validate(user, def, prefix)
-- only compare tables with contents that are not integer indexed
if type(user) ~= "table" or type(def) ~= "table" or not next(def) or type(next(def)) == "number" then
return
end
for k, v in pairs(user) do
local invalid
local override_typecheck = FIELD_OVERRIDE_TYPECHECK[k] or {}
if def[k] == nil then
-- option does not exist
invalid = string.format("unknown option: %s%s", prefix, k)
elseif type(v) ~= type(def[k]) and not override_typecheck[type(v)] then
-- option is of the wrong type and is not a function
invalid = string.format("invalid option: %s%s expected: %s actual: %s", prefix, k, type(def[k]), type(v))
end
if invalid then
if msg then
msg = string.format("%s | %s", msg, invalid)
else
msg = string.format("%s", invalid)
end
user[k] = nil
else
validate(v, def[k], prefix .. k .. ".")
end
end
end
validate(conf, DEFAULT_OPTS, "")
if msg then
utils.warn(msg)
end
end
function M.setup(conf)
legacy.migrate_legacy_options(conf or {})
validate_options(conf)
local opts = merge_options(conf)
local netrw_disabled = opts.disable_netrw or opts.hijack_netrw
_config.update_focused_file = opts.update_focused_file
_config.open_on_setup = opts.open_on_setup
_config.open_on_setup_file = opts.open_on_setup_file
_config.ignore_buffer_on_setup = opts.ignore_buffer_on_setup
_config.ignore_ft_on_setup = opts.ignore_ft_on_setup
_config.hijack_directories = opts.hijack_directories
_config.hijack_directories.enable = _config.hijack_directories.enable and netrw_disabled
manage_netrw(opts.disable_netrw, opts.hijack_netrw)
M.config = opts
require("nvim-tree.log").setup(opts)
log.line("config", "default config + user")
log.raw("config", "%s\n", vim.inspect(opts))
require("nvim-tree.actions").setup(opts)
require("nvim-tree.colors").setup()
require("nvim-tree.diagnostics").setup(opts)
require("nvim-tree.explorer").setup(opts)
require("nvim-tree.git").setup(opts)
require("nvim-tree.view").setup(opts)
require("nvim-tree.lib").setup(opts)
require("nvim-tree.renderer").setup(opts)
setup_vim_commands()
setup_autocommands(opts)
vim.schedule(function()
M.on_enter(netrw_disabled)
end)
end
return M

View File

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

View File

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

View File

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

View File

@ -0,0 +1,115 @@
local a = vim.api
local uv = vim.loop
local utils = require "nvim-tree.utils"
local events = require "nvim-tree.events"
local lib = require "nvim-tree.lib"
local core = require "nvim-tree.core"
local M = {}
local function focus_file(file)
local _, i = utils.find_node(core.get_explorer().nodes, function(node)
return node.absolute_path == file
end)
require("nvim-tree.view").set_cursor { i + 1, 1 }
end
local function create_file(file)
if utils.file_exists(file) then
print(file .. " already exists. Overwrite? y/n")
local ans = utils.get_user_input_char()
utils.clear_prompt()
if ans ~= "y" then
return
end
end
local ok, fd = pcall(uv.fs_open, file, "w", 420)
if not ok then
a.nvim_err_writeln("Couldn't create file " .. file)
return
end
uv.fs_close(fd)
events._dispatch_file_created(file)
end
local function get_num_nodes(iter)
local i = 0
for _ in iter do
i = i + 1
end
return i
end
local function get_containing_folder(node)
local is_open = vim.g.nvim_tree_create_in_closed_folder == 1 or node.open
if node.nodes ~= nil and is_open then
return utils.path_add_trailing(node.absolute_path)
end
local node_name_size = #(node.name or "")
return node.absolute_path:sub(0, -node_name_size - 1)
end
function M.fn(node)
node = lib.get_last_group_node(node)
if node.name == ".." then
node = {
absolute_path = core.get_cwd(),
nodes = core.get_explorer().nodes,
open = true,
}
end
local containing_folder = get_containing_folder(node)
local input_opts = { prompt = "Create file ", default = containing_folder, completion = "file" }
vim.ui.input(input_opts, function(new_file_path)
if not new_file_path or new_file_path == containing_folder then
return
end
utils.clear_prompt()
if utils.file_exists(new_file_path) then
utils.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_file(path_to_create)
elseif not utils.file_exists(path_to_create) then
local success = uv.fs_mkdir(path_to_create, 493)
if not success then
a.nvim_err_writeln("Could not create folder " .. path_to_create)
is_error = true
break
end
end
end
if not is_error then
a.nvim_out_write(new_file_path .. " was properly created\n")
end
events._dispatch_folder_created(new_file_path)
require("nvim-tree.actions.reloaders").reload_explorer()
focus_file(new_file_path)
end)
end
return M

View File

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

View File

@ -0,0 +1,82 @@
local utils = require "nvim-tree.utils"
local a = vim.api
local M = {}
local function get_formatted_lines(node)
local stats = node.fs_stat
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
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 winnr = a.nvim_open_win(0, false, {
col = 1,
row = 1,
relative = "cursor",
width = max_width + 1,
height = #lines,
border = "shadow",
noautocmd = true,
style = "minimal",
})
current_popup = {
winnr = winnr,
file_path = node.absolute_path,
}
local bufnr = a.nvim_create_buf(false, true)
a.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
a.nvim_win_set_buf(winnr, bufnr)
end
function M.close_popup()
if current_popup ~= nil then
a.nvim_win_close(current_popup.winnr, { force = true })
vim.cmd "augroup NvimTreeRemoveFilePopup | au! CursorMoved | augroup END"
current_popup = nil
end
end
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.cmd [[
augroup NvimTreeRemoveFilePopup
au CursorMoved * lua require'nvim-tree.actions.file-popup'.close_popup()
augroup END
]]
end
return M

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,62 @@
local git = require "nvim-tree.git"
local view = require "nvim-tree.view"
local renderer = require "nvim-tree.renderer"
local explorer_module = require "nvim-tree.explorer"
local core = require "nvim-tree.core"
local M = {}
local function refresh_nodes(node, projects)
local cwd = node.cwd or node.link_to or node.absolute_path
local project_root = git.get_project_root(cwd)
explorer_module.reload(node, projects[project_root] or {})
for _, _node in ipairs(node.nodes) do
if _node.nodes and _node.open then
refresh_nodes(_node, projects)
end
end
end
function M.reload_node_status(parent_node, projects)
local project_root = git.get_project_root(parent_node.absolute_path or parent_node.cwd)
local status = projects[project_root] or {}
for _, node in ipairs(parent_node.nodes) do
if node.nodes then
node.git_status = status.dirs and status.dirs[node.absolute_path]
else
node.git_status = status.files and status.files[node.absolute_path]
end
if node.nodes and #node.nodes > 0 then
M.reload_node_status(node, projects)
end
end
end
local event_running = false
function M.reload_explorer()
if event_running or not core.get_explorer() or vim.v.exiting ~= vim.NIL then
return
end
event_running = true
local projects = git.reload()
refresh_nodes(core.get_explorer(), projects)
if view.is_visible() then
renderer.draw()
end
event_running = false
end
function M.reload_git()
if not core.get_explorer() or not git.config.enable or event_running then
return
end
event_running = true
local projects = git.reload()
M.reload_node_status(core.get_explorer(), projects)
renderer.draw()
event_running = false
end
return M

View File

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

View File

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

View File

@ -0,0 +1,22 @@
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)
local function get_node_path(node)
if node.name == ".." then
return utils.path_remove_trailing(core.get_cwd())
else
return node.absolute_path
end
end
function M.run_file_command(node)
local node_path = get_node_path(node)
vim.api.nvim_input(": " .. node_path .. "<Home>")
end
return M

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,34 @@
local events = require "nvim-tree.events"
local explorer = require "nvim-tree.explorer"
local view = require "nvim-tree.view"
local M = {}
TreeExplorer = nil
local first_init_done = false
function M.init(foldername)
TreeExplorer = explorer.Explorer.new(foldername)
if not first_init_done then
events._dispatch_ready()
first_init_done = true
end
end
function M.get_explorer()
return TreeExplorer
end
function M.get_cwd()
return TreeExplorer.cwd
end
function M.get_nodes_starting_line()
local offset = 1
if view.is_root_folder_visible(M.get_cwd()) then
offset = offset + 1
end
return offset
end
return M

View File

@ -0,0 +1,178 @@
local a = vim.api
local utils = require "nvim-tree.utils"
local view = require "nvim-tree.view"
local core = require "nvim-tree.core"
local log = require "nvim-tree.log"
local M = {}
local GROUP = "NvimTreeDiagnosticSigns"
local function get_lowest_severity(diagnostics)
local severity = math.huge
for _, v in ipairs(diagnostics) do
if v.severity < severity then
severity = v.severity
end
end
return severity
end
local severity_levels = { Error = 1, Warning = 2, Information = 3, Hint = 4 }
local sign_names = {
{ "NvimTreeSignError", "NvimTreeLspDiagnosticsError" },
{ "NvimTreeSignWarning", "NvimTreeLspDiagnosticsWarning" },
{ "NvimTreeSignInformation", "NvimTreeLspDiagnosticsInformation" },
{ "NvimTreeSignHint", "NvimTreeLspDiagnosticsHint" },
}
local function add_sign(linenr, severity)
local buf = view.get_bufnr()
if not a.nvim_buf_is_valid(buf) or not a.nvim_buf_is_loaded(buf) then
return
end
local sign_name = sign_names[severity][1]
vim.fn.sign_place(1, GROUP, sign_name, buf, { lnum = linenr + 1 })
end
local function from_nvim_lsp()
local buffer_severity = {}
-- vim.lsp.diagnostic.get_all was deprecated in nvim 0.7 and replaced with vim.diagnostic.get
-- This conditional can be removed when the minimum required version of nvim is changed to 0.7.
if vim.diagnostic then
-- nvim version >= 0.7
for _, diagnostic in ipairs(vim.diagnostic.get()) do
local buf = diagnostic.bufnr
if a.nvim_buf_is_valid(buf) then
local bufname = a.nvim_buf_get_name(buf)
local lowest_severity = buffer_severity[bufname]
if not lowest_severity or diagnostic.severity < lowest_severity then
buffer_severity[bufname] = diagnostic.severity
end
end
end
else
-- nvim version < 0.7
for buf, diagnostics in pairs(vim.lsp.diagnostic.get_all()) do
if a.nvim_buf_is_valid(buf) then
local bufname = a.nvim_buf_get_name(buf)
if not buffer_severity[bufname] then
local severity = get_lowest_severity(diagnostics)
buffer_severity[bufname] = severity
end
end
end
end
return buffer_severity
end
local function from_coc()
if vim.g.coc_service_initialized ~= 1 then
return {}
end
local diagnostic_list = vim.fn.CocAction "diagnosticList"
if type(diagnostic_list) ~= "table" or vim.tbl_isempty(diagnostic_list) then
return {}
end
local buffer_severity = {}
local diagnostics = {}
for _, diagnostic in ipairs(diagnostic_list) do
local bufname = diagnostic.file
local severity = severity_levels[diagnostic.severity]
local severity_list = diagnostics[bufname] or {}
table.insert(severity_list, severity)
diagnostics[bufname] = severity_list
end
for bufname, severity_list in pairs(diagnostics) do
if not buffer_severity[bufname] then
local severity = math.min(unpack(severity_list))
buffer_severity[bufname] = severity
end
end
return buffer_severity
end
local function is_using_coc()
return vim.g.coc_service_initialized == 1
end
function M.clear()
if not M.enable or not view.is_buf_valid(view.get_bufnr()) then
return
end
vim.fn.sign_unplace(GROUP)
end
function M.update()
if not M.enable or not core.get_explorer() or not view.is_buf_valid(view.get_bufnr()) then
return
end
local ps = log.profile_start "diagnostics update"
log.line("diagnostics", "update")
local buffer_severity
if is_using_coc() then
buffer_severity = from_coc()
else
buffer_severity = from_nvim_lsp()
end
M.clear()
for bufname, severity in pairs(buffer_severity) do
local bufpath = utils.canonical_path(bufname)
log.line("diagnostics", " bufpath '%s' severity %d", bufpath, severity)
if 0 < severity and severity < 5 then
local node, line = utils.find_node(core.get_explorer().nodes, function(node)
local nodepath = utils.canonical_path(node.absolute_path)
log.line("diagnostics", " checking nodepath '%s'", nodepath)
if M.show_on_dirs and not node.open then
return vim.startswith(bufpath, nodepath)
else
return nodepath == bufpath
end
end)
if node then
log.line("diagnostics", " matched node '%s'", node.absolute_path)
add_sign(line, severity)
end
end
end
log.profile_end(ps, "diagnostics update")
end
local links = {
NvimTreeLspDiagnosticsError = "DiagnosticError",
NvimTreeLspDiagnosticsWarning = "DiagnosticWarn",
NvimTreeLspDiagnosticsInformation = "DiagnosticInfo",
NvimTreeLspDiagnosticsHint = "DiagnosticHint",
}
function M.setup(opts)
M.enable = opts.diagnostics.enable
M.show_on_dirs = opts.diagnostics.show_on_dirs
vim.fn.sign_define(sign_names[1][1], { text = opts.diagnostics.icons.error, texthl = sign_names[1][2] })
vim.fn.sign_define(sign_names[2][1], { text = opts.diagnostics.icons.warning, texthl = sign_names[2][2] })
vim.fn.sign_define(sign_names[3][1], { text = opts.diagnostics.icons.info, texthl = sign_names[3][2] })
vim.fn.sign_define(sign_names[4][1], { text = opts.diagnostics.icons.hint, texthl = sign_names[4][2] })
for lhs, rhs in pairs(links) do
vim.cmd("hi def link " .. lhs .. " " .. rhs)
end
if M.enable then
log.line("diagnostics", "setup")
vim.cmd "au DiagnosticChanged * lua require'nvim-tree.diagnostics'.update()"
vim.cmd "au User CocDiagnosticChange lua require'nvim-tree.diagnostics'.update()"
end
end
return M

View File

@ -0,0 +1,129 @@
local M = {}
local global_handlers = {}
local Event = {
Ready = "Ready",
NodeRenamed = "NodeRenamed",
TreeOpen = "TreeOpen",
TreeClose = "TreeClose",
FileCreated = "FileCreated",
FileRemoved = "FileRemoved",
FolderCreated = "FolderCreated",
FolderRemoved = "FolderRemoved",
}
local function get_handlers(event_name)
return global_handlers[event_name] or {}
end
local function register_handler(event_name, handler)
local handlers = get_handlers(event_name)
table.insert(handlers, handler)
global_handlers[event_name] = handlers
end
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
vim.api.nvim_err_writeln("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_node_renamed(old_name, new_name)
dispatch(Event.NodeRenamed, { old_name = old_name, new_name = new_name })
end
--@private
function M._dispatch_file_removed(fname)
dispatch(Event.FileRemoved, { 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_open()
dispatch(Event.TreeOpen, nil)
end
--@private
function M._dispatch_on_tree_close()
dispatch(Event.TreeClose, nil)
end
--Registers a handler for the Ready event.
--@param handler (function) Handler with the signature `function()`
function M.on_nvim_tree_ready(handler)
register_handler(Event.Ready, handler)
end
--Registers a handler for the NodeRenamed event.
--@param handler (function) Handler with the signature function(payload), where payload is a table containing:
-- - old_name (string) Absolute path to the old node location.
-- - new_name (string) Absolute path to the new node location.
function M.on_node_renamed(handler)
register_handler(Event.NodeRenamed, handler)
end
--Registers a handler for the FileCreated event.
--@param handler (function) Handler with the signature function(payload), where payload is a table containing:
-- - fname (string) Absolute path to the created file.
function M.on_file_created(handler)
register_handler(Event.FileCreated, handler)
end
--Registers a handler for the FileRemoved event.
--@param handler (function) Handler with the signature function(payload), where payload is a table containing:
-- - fname (string) Absolute path to the removed file.
function M.on_file_removed(handler)
register_handler(Event.FileRemoved, handler)
end
--Registers a handler for the FolderCreated event.
--@param handler (function) Handler with the signature function(payload), where payload is a table containing:
-- - folder_name (string) Absolute path to the created folder.
function M.on_folder_created(handler)
register_handler(Event.FolderCreated, handler)
end
--Registers a handler for the FolderRemoved event.
--@param handler (function) Handler with the signature function(payload), where payload is a table containing:
-- - folder_name (string) Absolute path to the removed folder.
function M.on_folder_removed(handler)
register_handler(Event.FolderRemoved, handler)
end
--Registers a handler for the TreeOpen event.
--@param handler (function) Handler with the signature function(payload)
function M.on_tree_open(handler)
register_handler(Event.TreeOpen, handler)
end
--Registers a handler for the TreeClose event.
--@param handler (function) Handler with the signature function(payload)
function M.on_tree_close(handler)
register_handler(Event.TreeClose, handler)
end
return M

View File

@ -0,0 +1,9 @@
local uv = vim.loop
local M = {}
function M.has_one_child_folder(node)
return #node.nodes == 1 and node.nodes[1].nodes and uv.fs_access(node.nodes[1].absolute_path, "R")
end
return M

View File

@ -0,0 +1,77 @@
local api = vim.api
local uv = vim.loop
local utils = require "nvim-tree.utils"
local builders = require "nvim-tree.explorer.node-builders"
local common = require "nvim-tree.explorer.common"
local sorters = require "nvim-tree.explorer.sorters"
local filters = require "nvim-tree.explorer.filters"
local M = {}
local function get_type_from(type_, cwd)
return type_ or (uv.fs_stat(cwd) or {}).type
end
local function populate_children(handle, cwd, node, status)
local node_ignored = node.git_status == "!!"
while true do
local name, t = uv.fs_scandir_next(handle)
if not name then
break
end
local nodes_by_path = utils.key_by(node.nodes, "absolute_path")
local abs = utils.path_join { cwd, name }
t = get_type_from(t, abs)
if
not filters.should_ignore(abs)
and not filters.should_ignore_git(abs, status.files)
and not nodes_by_path[abs]
then
if t == "directory" and uv.fs_access(abs, "R") then
table.insert(node.nodes, builders.folder(node, abs, name, status, node_ignored))
elseif t == "file" then
table.insert(node.nodes, builders.file(node, abs, name, status, node_ignored))
elseif t == "link" then
local link = builders.link(node, abs, name, status, node_ignored)
if link.link_to ~= nil then
table.insert(node.nodes, link)
end
end
end
end
end
local function get_dir_handle(cwd)
local handle = uv.fs_scandir(cwd)
if type(handle) == "string" then
api.nvim_err_writeln(handle)
return
end
return handle
end
function M.explore(node, status)
local cwd = node.cwd or node.link_to or node.absolute_path
local handle = get_dir_handle(cwd)
if not handle then
return
end
populate_children(handle, cwd, node, status)
local is_root = node.cwd ~= nil
local child_folder_only = common.has_one_child_folder(node) and node.nodes[1]
if vim.g.nvim_tree_group_empty == 1 and not is_root and child_folder_only then
node.group_next = child_folder_only
local ns = M.explore(child_folder_only, status)
node.nodes = ns or {}
return ns
end
sorters.merge_sort(node.nodes, sorters.node_comparator)
return node.nodes
end
return M

View File

@ -0,0 +1,77 @@
local utils = require "nvim-tree.utils"
local M = {
ignore_list = {},
exclude_list = {},
}
local function is_excluded(path)
for _, node in ipairs(M.exclude_list) do
if path:match(node) then
return true
end
end
return false
end
---Check if the given path should be ignored.
---@param path string Absolute path
---@return boolean
function M.should_ignore(path)
local basename = utils.path_basename(path)
if is_excluded(path) then
return false
end
if M.config.filter_dotfiles then
if basename:sub(1, 1) == "." then
return true
end
end
if not M.config.filter_custom then
return false
end
local relpath = utils.path_relative(path, vim.loop.cwd())
for pat, _ in pairs(M.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 M.ignore_list["*" .. string.sub(path, idx)] == true then
return true
end
end
return false
end
function M.should_ignore_git(path, status)
return M.config.filter_git_ignored
and (M.config.filter_git_ignored and status and status[path] == "!!")
and not is_excluded(path)
end
function M.setup(opts)
M.config = {
filter_custom = true,
filter_dotfiles = opts.filters.dotfiles,
filter_git_ignored = opts.git.ignore,
}
M.exclude_list = opts.filters.exclude
local custom_filter = opts.filters.custom
if custom_filter and #custom_filter > 0 then
for _, filter_name in pairs(custom_filter) do
M.ignore_list[filter_name] = true
end
end
end
return M

View File

@ -0,0 +1,40 @@
local uv = vim.loop
local git = require "nvim-tree.git"
local M = {}
M.explore = require("nvim-tree.explorer.explore").explore
M.reload = require("nvim-tree.explorer.reload").reload
local Explorer = {}
Explorer.__index = Explorer
function Explorer.new(cwd)
cwd = uv.fs_realpath(cwd or uv.cwd())
local explorer = setmetatable({
cwd = cwd,
nodes = {},
}, Explorer)
explorer:_load(explorer)
return explorer
end
function Explorer:_load(node)
local cwd = node.cwd or node.link_to or node.absolute_path
local git_statuses = git.load_project_status(cwd)
M.explore(node, git_statuses)
end
function Explorer:expand(node)
self:_load(node)
end
function M.setup(opts)
require("nvim-tree.explorer.filters").setup(opts)
require("nvim-tree.explorer.sorters").setup(opts)
end
M.Explorer = Explorer
return M

View File

@ -0,0 +1,89 @@
local uv = vim.loop
local utils = require "nvim-tree.utils"
local M = {
is_windows = vim.fn.has "win32" == 1,
}
function M.get_dir_git_status(parent_ignored, status, absolute_path)
if parent_ignored then
return "!!"
end
local dir_status = status.dirs and status.dirs[absolute_path]
local file_status = status.files and status.files[absolute_path]
return dir_status or file_status
end
function M.get_git_status(parent_ignored, status, absolute_path)
return parent_ignored and "!!" or status.files and status.files[absolute_path]
end
function M.folder(parent, absolute_path, name, status, parent_ignored)
local handle = uv.fs_scandir(absolute_path)
local has_children = handle and uv.fs_scandir_next(handle) ~= nil
return {
absolute_path = absolute_path,
fs_stat = uv.fs_stat(absolute_path),
git_status = M.get_dir_git_status(parent_ignored, status, absolute_path),
group_next = nil, -- If node is grouped, this points to the next child dir/link node
has_children = has_children,
name = name,
nodes = {},
open = false,
parent = parent,
}
end
local function is_executable(absolute_path, ext)
if M.is_windows then
return utils.is_windows_exe(ext)
end
return uv.fs_access(absolute_path, "X")
end
function M.file(parent, absolute_path, name, status, parent_ignored)
local ext = string.match(name, ".?[^.]+%.(.*)") or ""
return {
absolute_path = absolute_path,
executable = is_executable(absolute_path, ext),
extension = ext,
fs_stat = uv.fs_stat(absolute_path),
git_status = M.get_git_status(parent_ignored, status, absolute_path),
name = name,
parent = parent,
}
end
-- TODO-INFO: sometimes fs_realpath returns nil
-- I expect this be a bug in glibc, because it fails to retrieve the path for some
-- links (for instance libr2.so in /usr/lib) and thus even with a C program realpath fails
-- when it has no real reason to. Maybe there is a reason, but errno is definitely wrong.
-- So we need to check for link_to ~= nil when adding new links to the main tree
function M.link(parent, absolute_path, name, status, parent_ignored)
--- I dont know if this is needed, because in my understanding, there isnt hard links in windows, but just to be sure i changed it.
local link_to = uv.fs_realpath(absolute_path)
local open, nodes, has_children
if (link_to ~= nil) and uv.fs_stat(link_to).type == "directory" then
local handle = uv.fs_scandir(link_to)
has_children = handle and uv.fs_scandir_next(handle) ~= nil
open = false
nodes = {}
end
return {
absolute_path = absolute_path,
fs_stat = uv.fs_stat(absolute_path),
git_status = M.get_git_status(parent_ignored, status, absolute_path),
group_next = nil, -- If node is grouped, this points to the next child dir/link node
has_children = has_children,
link_to = link_to,
name = name,
nodes = nodes,
open = open,
parent = parent,
}
end
return M

View File

@ -0,0 +1,87 @@
local api = vim.api
local uv = vim.loop
local utils = require "nvim-tree.utils"
local builders = require "nvim-tree.explorer.node-builders"
local common = require "nvim-tree.explorer.common"
local filters = require "nvim-tree.explorer.filters"
local sorters = require "nvim-tree.explorer.sorters"
local M = {}
local function update_status(nodes_by_path, node_ignored, status)
return function(node)
if nodes_by_path[node.absolute_path] then
if node.nodes then
node.git_status = builders.get_dir_git_status(node_ignored, status, node.absolute_path)
else
node.git_status = builders.get_git_status(node_ignored, status, node.absolute_path)
end
end
return node
end
end
function M.reload(node, status)
local cwd = node.cwd or node.link_to or node.absolute_path
local handle = uv.fs_scandir(cwd)
if type(handle) == "string" then
api.nvim_err_writeln(handle)
return
end
if node.group_next then
node.nodes = { node.group_next }
node.group_next = nil
end
local child_names = {}
local node_ignored = node.git_status == "!!"
local nodes_by_path = utils.key_by(node.nodes, "absolute_path")
while true do
local name, t = uv.fs_scandir_next(handle)
if not name then
break
end
local abs = utils.path_join { cwd, name }
t = t or (uv.fs_stat(abs) or {}).type
if not filters.should_ignore(abs) and not filters.should_ignore_git(abs, status.files) then
child_names[abs] = true
if not nodes_by_path[abs] then
if t == "directory" and uv.fs_access(abs, "R") then
table.insert(node.nodes, builders.folder(node, abs, name, status, node_ignored))
elseif t == "file" then
table.insert(node.nodes, builders.file(node, abs, name, status, node_ignored))
elseif t == "link" then
local link = builders.link(node, abs, name, status, node_ignored)
if link.link_to ~= nil then
table.insert(node.nodes, link)
end
end
end
end
end
node.nodes = vim.tbl_map(
update_status(nodes_by_path, node_ignored, status),
vim.tbl_filter(function(n)
return child_names[n.absolute_path]
end, node.nodes)
)
local is_root = node.cwd ~= nil
local child_folder_only = common.has_one_child_folder(node) and node.nodes[1]
if vim.g.nvim_tree_group_empty == 1 and not is_root and child_folder_only then
node.group_next = child_folder_only
local ns = M.reload(child_folder_only, status)
node.nodes = ns or {}
return ns
end
sorters.merge_sort(node.nodes, sorters.node_comparator)
return node.nodes
end
return M

View File

@ -0,0 +1,138 @@
local M = {
sort_by = nil,
node_comparator = nil,
}
---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
local function 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(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
local function split_merge(t, first, last, comparator)
if (last - first) < 1 then
return
end
local mid = math.floor((first + last) / 2)
split_merge(t, first, mid, comparator)
split_merge(t, mid + 1, last, comparator)
merge(t, first, mid, last, comparator)
end
---Perform a merge sort on a given list.
---@param t any[]
---@param comparator function|nil
function M.merge_sort(t, comparator)
if not comparator then
comparator = function(left, right)
return left < right
end
end
split_merge(t, 1, #t, comparator)
end
local function node_comparator_name_ignorecase_or_not(a, b, ignorecase)
if not (a and b) then
return true
end
if a.nodes and not b.nodes then
return true
elseif not a.nodes and b.nodes then
return false
end
if ignorecase then
return a.name:lower() <= b.name:lower()
else
return a.name <= b.name
end
end
function M.node_comparator_name_case_sensisive(a, b)
return node_comparator_name_ignorecase_or_not(a, b, false)
end
function M.node_comparator_name_ignorecase(a, b)
return node_comparator_name_ignorecase_or_not(a, b, true)
end
function M.node_comparator_modification_time(a, b)
if not (a and b) then
return true
end
if a.nodes and not b.nodes then
return true
elseif not a.nodes and b.nodes then
return false
end
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
function M.setup(opts)
M.sort_by = opts.sort_by
if M.sort_by == "modification_time" then
M.node_comparator = M.node_comparator_modification_time
elseif M.sort_by == "case_sensitive" then
M.node_comparator = M.node_comparator_name_case_sensisive
else
M.node_comparator = M.node_comparator_name_ignorecase
end
end
return M

View File

@ -0,0 +1,78 @@
local git_utils = require "nvim-tree.git.utils"
local Runner = require "nvim-tree.git.runner"
local M = {
config = nil,
projects = {},
cwd_to_project_root = {},
}
function M.reload()
if not M.config.enable then
return {}
end
for project_root in pairs(M.projects) do
M.projects[project_root] = {}
local git_status = Runner.run {
project_root = project_root,
list_untracked = git_utils.should_show_untracked(project_root),
list_ignored = true,
timeout = M.config.timeout,
}
M.projects[project_root] = {
files = git_status,
dirs = git_utils.file_status_to_dir_status(git_status, project_root),
}
end
return M.projects
end
function M.get_project_root(cwd)
if M.cwd_to_project_root[cwd] then
return M.cwd_to_project_root[cwd]
end
if M.cwd_to_project_root[cwd] == false then
return nil
end
local project_root = git_utils.get_toplevel(cwd)
return project_root
end
function M.load_project_status(cwd)
if not M.config.enable then
return {}
end
local project_root = M.get_project_root(cwd)
if not project_root then
M.cwd_to_project_root[cwd] = false
return {}
end
local status = M.projects[project_root]
if status then
return status
end
local git_status = Runner.run {
project_root = project_root,
list_untracked = git_utils.should_show_untracked(project_root),
list_ignored = true,
timeout = M.config.timeout,
}
M.projects[project_root] = {
files = git_status,
dirs = git_utils.file_status_to_dir_status(git_status, project_root),
}
return M.projects[project_root]
end
function M.setup(opts)
M.config = opts.git
end
return M

View File

@ -0,0 +1,158 @@
local uv = vim.loop
local log = require "nvim-tree.log"
local utils = require "nvim-tree.utils"
local Runner = {}
Runner.__index = Runner
function Runner:_parse_status_output(line)
local status = line:sub(1, 2)
-- removing `"` when git is returning special file status containing spaces
local path = line:sub(4, -2):gsub('^"', ""):gsub('"$', "")
-- replacing slashes if on windows
if vim.fn.has "win32" == 1 then
path = path:gsub("/", "\\")
end
if #status > 0 and #path > 0 then
self.output[utils.path_remove_trailing(utils.path_join { self.project_root, path })] = status
end
return #line
end
function Runner:_handle_incoming_data(prev_output, incoming)
if incoming and utils.str_find(incoming, "\n") then
local prev = prev_output .. incoming
local i = 1
for line in prev:gmatch "[^\n]*\n" do
i = i + self:_parse_status_output(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 nil
end
function Runner:_getopts(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", ignored, untracked },
cwd = self.project_root,
stdio = { nil, stdout_handle, stderr_handle },
}
end
function Runner:_log_raw_output(output)
if output and type(output) == "string" then
log.raw("git", "%s", output)
end
end
function Runner:_run_git_job()
local handle, pid
local stdout = uv.new_pipe(false)
local stderr = uv.new_pipe(false)
local timer = uv.new_timer()
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
return
end
timer:stop()
timer:close()
stdout:read_stop()
stderr:read_stop()
stdout:close()
stderr:close()
if handle then
handle:close()
end
pcall(uv.kill, pid)
end
local opts = self:_getopts(stdout, stderr)
log.line("git", "running job with timeout %dms", self.timeout)
log.line("git", "git %s", table.concat(opts.args, " "))
handle, pid = uv.spawn(
"git",
opts,
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
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
uv.read_start(stdout, vim.schedule_wrap(manage_stdout))
uv.read_start(stderr, vim.schedule_wrap(manage_stderr))
end
function Runner:_wait()
local function is_done()
return self.rc ~= nil
end
while not vim.wait(30, is_done) do
end
end
-- This module runs a git process, which will be killed if it takes more than timeout which defaults to 400ms
function Runner.run(opts)
local ps = log.profile_start("git job %s", opts.project_root)
local self = setmetatable({
project_root = opts.project_root,
list_untracked = opts.list_untracked,
list_ignored = opts.list_ignored,
timeout = opts.timeout or 400,
output = {},
rc = nil, -- -1 indicates timeout
}, Runner)
self:_run_git_job()
self:_wait()
log.profile_end(ps, "git job %s", opts.project_root)
if self.rc == -1 then
log.line("git", "job timed out")
elseif self.rc ~= 0 then
log.line("git", "job failed with return code %d", self.rc)
else
log.line("git", "job success")
end
return self.output
end
return Runner

View File

@ -0,0 +1,53 @@
local M = {}
function M.get_toplevel(cwd)
local cmd = "git -C " .. vim.fn.shellescape(cwd) .. " rev-parse --show-toplevel"
local toplevel = vim.fn.system(cmd)
if not toplevel or #toplevel == 0 or toplevel:match "fatal" then
return nil
end
-- git always returns path with forward slashes
if vim.fn.has "win32" == 1 then
toplevel = toplevel:gsub("/", "\\")
end
-- remove newline
return toplevel:sub(0, -2)
end
local untracked = {}
function M.should_show_untracked(cwd)
if untracked[cwd] ~= nil then
return untracked[cwd]
end
local cmd = "git -C " .. cwd .. " config --type=bool status.showUntrackedFiles"
local has_untracked = vim.fn.system(cmd)
untracked[cwd] = vim.trim(has_untracked) ~= "false"
return untracked[cwd]
end
function M.file_status_to_dir_status(status, cwd)
local dirs = {}
for p, s in pairs(status) do
if s ~= "!!" then
local modified = vim.fn.fnamemodify(p, ":h")
dirs[modified] = s
end
end
for dirname, s in pairs(dirs) do
local modified = dirname
while modified ~= cwd and modified ~= "/" do
modified = vim.fn.fnamemodify(modified, ":h")
dirs[modified] = s
end
end
return dirs
end
return M

View File

@ -0,0 +1,240 @@
local utils = require "nvim-tree.utils"
local M = {}
-- TODO update bit.ly/3vIpEOJ when adding a migration
-- migrate the g: to o if the user has not specified that when calling setup
local g_migrations = {
nvim_tree_disable_netrw = function(o)
if o.disable_netrw == nil then
o.disable_netrw = vim.g.nvim_tree_disable_netrw ~= 0
end
end,
nvim_tree_hijack_netrw = function(o)
if o.hijack_netrw == nil then
o.hijack_netrw = vim.g.nvim_tree_hijack_netrw ~= 0
end
end,
nvim_tree_auto_open = function(o)
if o.open_on_setup == nil then
o.open_on_setup = vim.g.nvim_tree_auto_open ~= 0
end
end,
nvim_tree_tab_open = function(o)
if o.open_on_tab == nil then
o.open_on_tab = vim.g.nvim_tree_tab_open ~= 0
end
end,
nvim_tree_update_cwd = function(o)
if o.update_cwd == nil then
o.update_cwd = vim.g.nvim_tree_update_cwd ~= 0
end
end,
nvim_tree_hijack_cursor = function(o)
if o.hijack_cursor == nil then
o.hijack_cursor = vim.g.nvim_tree_hijack_cursor ~= 0
end
end,
nvim_tree_system_open_command = function(o)
utils.table_create_missing(o, "system_open")
if o.system_open.cmd == nil then
o.system_open.cmd = vim.g.nvim_tree_system_open_command
end
end,
nvim_tree_system_open_command_args = function(o)
utils.table_create_missing(o, "system_open")
if o.system_open.args == nil then
o.system_open.args = vim.g.nvim_tree_system_open_command_args
end
end,
nvim_tree_follow = function(o)
utils.table_create_missing(o, "update_focused_file")
if o.update_focused_file.enable == nil then
o.update_focused_file.enable = vim.g.nvim_tree_follow ~= 0
end
end,
nvim_tree_follow_update_path = function(o)
utils.table_create_missing(o, "update_focused_file")
if o.update_focused_file.update_cwd == nil then
o.update_focused_file.update_cwd = vim.g.nvim_tree_follow_update_path ~= 0
end
end,
nvim_tree_lsp_diagnostics = function(o)
utils.table_create_missing(o, "diagnostics")
if o.diagnostics.enable == nil then
o.diagnostics.enable = vim.g.nvim_tree_lsp_diagnostics ~= 0
if o.diagnostics.show_on_dirs == nil then
o.diagnostics.show_on_dirs = vim.g.nvim_tree_lsp_diagnostics ~= 0
end
end
end,
nvim_tree_auto_resize = function(o)
utils.table_create_missing(o, "actions.open_file")
if o.actions.open_file.resize_window == nil then
o.actions.open_file.resize_window = vim.g.nvim_tree_auto_resize ~= 0
end
end,
nvim_tree_bindings = function(o)
utils.table_create_missing(o, "view.mappings")
if o.view.mappings.list == nil then
o.view.mappings.list = vim.g.nvim_tree_bindings
end
end,
nvim_tree_disable_keybindings = function(o)
utils.table_create_missing(o, "view.mappings")
if o.view.mappings.custom_only == nil then
if vim.g.nvim_tree_disable_keybindings ~= 0 then
o.view.mappings.custom_only = true
-- specify one mapping so that defaults do not apply
o.view.mappings.list = {
{ key = "g?", action = "" },
}
end
end
end,
nvim_tree_disable_default_keybindings = function(o)
utils.table_create_missing(o, "view.mappings")
if o.view.mappings.custom_only == nil then
o.view.mappings.custom_only = vim.g.nvim_tree_disable_default_keybindings ~= 0
end
end,
nvim_tree_hide_dotfiles = function(o)
utils.table_create_missing(o, "filters")
if o.filters.dotfiles == nil then
o.filters.dotfiles = vim.g.nvim_tree_hide_dotfiles ~= 0
end
end,
nvim_tree_ignore = function(o)
utils.table_create_missing(o, "filters")
if o.filters.custom == nil then
o.filters.custom = vim.g.nvim_tree_ignore
end
end,
nvim_tree_gitignore = function(o)
utils.table_create_missing(o, "git")
if o.git.ignore == nil then
o.git.ignore = vim.g.nvim_tree_gitignore ~= 0
end
end,
nvim_tree_disable_window_picker = function(o)
utils.table_create_missing(o, "actions.open_file.window_picker")
if o.actions.open_file.window_picker.enable == nil then
o.actions.open_file.window_picker.enable = vim.g.nvim_tree_disable_window_picker == 0
end
end,
nvim_tree_window_picker_chars = function(o)
utils.table_create_missing(o, "actions.open_file.window_picker")
if o.actions.open_file.window_picker.chars == nil then
o.actions.open_file.window_picker.chars = vim.g.nvim_tree_window_picker_chars
end
end,
nvim_tree_window_picker_exclude = function(o)
utils.table_create_missing(o, "actions.open_file.window_picker")
if o.actions.open_file.window_picker.exclude == nil then
o.actions.open_file.window_picker.exclude = vim.g.nvim_tree_window_picker_exclude
end
end,
nvim_tree_quit_on_open = function(o)
utils.table_create_missing(o, "actions.open_file")
if o.actions.open_file.quit_on_open == nil then
o.actions.open_file.quit_on_open = vim.g.nvim_tree_quit_on_open == 1
end
end,
nvim_tree_change_dir_global = function(o)
utils.table_create_missing(o, "actions.change_dir")
if o.actions.change_dir.global == nil then
o.actions.change_dir.global = vim.g.nvim_tree_change_dir_global == 1
end
end,
nvim_tree_indent_markers = function(o)
utils.table_create_missing(o, "renderer.indent_markers")
if o.renderer.indent_markers.enable == nil then
o.renderer.indent_markers.enable = vim.g.nvim_tree_indent_markers == 1
end
end,
}
local function refactored(opts)
-- mapping actions
if opts.view and opts.view.mappings and opts.view.mappings.list then
for _, m in pairs(opts.view.mappings.list) do
if m.action == "toggle_ignored" then
m.action = "toggle_git_ignored"
end
end
end
-- update_to_buf_dir -> hijack_directories
if opts.update_to_buf_dir ~= nil then
utils.table_create_missing(opts, "hijack_directories")
if opts.hijack_directories.enable == nil then
opts.hijack_directories.enable = opts.update_to_buf_dir.enable
end
if opts.hijack_directories.auto_open == nil then
opts.hijack_directories.auto_open = opts.update_to_buf_dir.auto_open
end
opts.update_to_buf_dir = nil
end
-- view.auto_resize -> actions.open_file.resize_window
if opts.view and opts.view.auto_resize ~= nil then
utils.table_create_missing(opts, "actions.open_file")
if opts.actions.open_file.resize_window == nil then
opts.actions.open_file.resize_window = opts.view.auto_resize
end
opts.view.auto_resize = nil
end
end
local function removed(opts)
if opts.auto_close then
utils.warn "auto close feature has been removed, see note in the README (tips & reminder section)"
opts.auto_close = nil
end
end
function M.migrate_legacy_options(opts)
-- g: options
local msg
for g, m in pairs(g_migrations) do
if vim.fn.exists("g:" .. g) ~= 0 then
m(opts)
msg = (msg and msg .. ", " or "Following options were moved to setup, see bit.ly/3vIpEOJ: ") .. g
end
end
if msg then
utils.warn(msg)
end
-- silently move
refactored(opts)
-- warn and delete
removed(opts)
end
return M

View File

@ -0,0 +1,144 @@
local api = vim.api
local renderer = require "nvim-tree.renderer"
local view = require "nvim-tree.view"
local core = require "nvim-tree.core"
local M = {
target_winid = nil,
}
function M.get_nodes_by_line(nodes_all, line_start)
local nodes_by_line = {}
local line = line_start
local function iter(nodes)
for _, node in ipairs(nodes) do
nodes_by_line[line] = node
line = line + 1
if node.open == true then
local child = iter(node.nodes)
if child ~= nil then
return child
end
end
end
end
iter(nodes_all)
return nodes_by_line
end
function M.get_node_at_cursor()
if not core.get_explorer() then
return
end
local winnr = view.get_winnr()
if not winnr then
return
end
local cursor = api.nvim_win_get_cursor(view.get_winnr())
local line = cursor[1]
if view.is_help_ui() then
local help_lines = require("nvim-tree.renderer.help").compute_lines()
local help_text = M.get_nodes_by_line(help_lines, 1)[line]
return { name = help_text }
else
if line == 1 and core.get_explorer().cwd ~= "/" and view.is_root_folder_visible() then
return { name = ".." }
end
return M.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line())[line]
end
end
-- If node is grouped, return the last node in the group. Otherwise, return the given node.
function M.get_last_group_node(node)
local next = node
while next.group_next do
next = next.group_next
end
return next
end
function M.expand_or_collapse(node)
node.open = not node.open
if node.has_children then
node.has_children = false
end
if #node.nodes == 0 then
core.get_explorer():expand(node)
end
renderer.draw()
end
function M.set_target_win()
local id = 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
local function handle_buf_cwd(cwd)
local respect_buf_cwd = vim.g.nvim_tree_respect_buf_cwd or 0
if respect_buf_cwd == 1 and cwd ~= core.get_explorer().cwd then
require("nvim-tree.actions.change-dir").fn(cwd)
end
end
local function open_view_and_draw()
local cwd = vim.fn.getcwd()
view.open()
handle_buf_cwd(cwd)
renderer.draw()
end
local function should_hijack_current_buf()
local bufnr = api.nvim_get_current_buf()
local bufname = api.nvim_buf_get_name(bufnr)
local bufmodified = api.nvim_buf_get_option(bufnr, "modified")
local ft = api.nvim_buf_get_option(bufnr, "ft")
local 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
function M.open(cwd)
M.set_target_win()
if not core.get_explorer() or cwd then
core.init(cwd or vim.loop.cwd())
end
if should_hijack_current_buf() then
view.open_in_current_win()
renderer.draw()
else
open_view_and_draw()
end
view.restore_tab_state()
end
-- @deprecated: use nvim-tree.actions.collapse-all.fn
M.collapse_all = require("nvim-tree.actions.collapse-all").fn
-- @deprecated: use nvim-tree.actions.dir-up.fn
M.dir_up = require("nvim-tree.actions.dir-up").fn
-- @deprecated: use nvim-tree.actions.change-dir.fn
M.change_dir = require("nvim-tree.actions.change-dir").fn
-- @deprecated: use nvim-tree.actions.reloaders.reload_explorer
M.refresh_tree = require("nvim-tree.actions.reloaders").reload_explorer
-- @deprecated: use nvim-tree.actions.reloaders.reload_git
M.reload_git = require("nvim-tree.actions.reloaders").reload_git
-- @deprecated: use nvim-tree.actions.find-file.fn
M.set_index_and_redraw = require("nvim-tree.actions.find-file").fn
function M.setup(opts)
M.hijack_unnamed_buffer_when_opening = opts.hijack_unnamed_buffer_when_opening
M.hijack_directories = opts.hijack_directories
end
return M

View File

@ -0,0 +1,63 @@
local uv = vim.loop
local M = {
config = nil,
path = nil,
}
--- Write to log file
--- @param typ string as per log.types config
--- @param fmt string for string.format
--- @vararg any arguments for string.format
function M.raw(typ, fmt, ...)
if not M.path or not M.config.types[typ] and not M.config.types.all then
return
end
local line = string.format(fmt, ...)
local file = io.open(M.path, "a")
io.output(file)
io.write(line)
io.close(file)
end
--- Write to log file via M.line
--- START is prefixed
--- @return number nanos to pass to profile_end
function M.profile_start(fmt, ...)
if not M.path or not M.config.types.profile and not M.config.types.all then
return
end
M.line("profile", "START " .. (fmt or "???"), ...)
return uv.hrtime()
end
--- Write to log file via M.line
--- END is prefixed and duration in seconds is suffixed
--- @param start number nanos returned from profile_start
function M.profile_end(start, fmt, ...)
if not M.path or not M.config.types.profile and not M.config.types.all then
return
end
local millis = start and math.modf((uv.hrtime() - start) / 1000000) or -1
M.line("profile", "END " .. (fmt or "???") .. " " .. millis .. "ms", ...)
end
-- Write to log file via M.raw
-- time and typ are prefixed and a trailing newline is added
function M.line(typ, fmt, ...)
M.raw(typ, string.format("[%s] [%s] %s\n", os.date "%Y-%m-%d %H:%M:%S", typ, fmt), ...)
end
function M.setup(opts)
M.config = opts.log
if M.config and M.config.enable and M.config.types then
M.path = string.format("%s/nvim-tree.log", vim.fn.stdpath "cache", os.date "%H:%M:%S", vim.env.USER)
if M.config.truncate then
os.remove(M.path)
end
print("nvim-tree.lua logging to " .. M.path)
end
end
return M

View File

@ -0,0 +1,276 @@
local utils = require "nvim-tree.utils"
local git = require "nvim-tree.renderer.components.git"
local pad = require "nvim-tree.renderer.components.padding"
local icons = require "nvim-tree.renderer.components.icons"
local Builder = {}
Builder.__index = Builder
function Builder.new(root_cwd)
return setmetatable({
index = 0,
depth = nil,
highlights = {},
lines = {},
markers = {},
root_cwd = root_cwd,
}, Builder)
end
function Builder:configure_initial_depth(show_arrows)
self.depth = show_arrows and 2 or 0
return self
end
function Builder:configure_root_modifier(root_folder_modifier)
self.root_folder_modifier = root_folder_modifier or ":~"
return self
end
function Builder:configure_trailing_slash(with_trailing)
self.trailing_slash = with_trailing and "/" or ""
return self
end
function Builder:configure_special_map(special_map)
self.special_map = special_map
return self
end
function Builder:configure_picture_map(picture_map)
self.picture_map = picture_map
return self
end
function Builder:configure_opened_file_highlighting(level)
if level == 1 then
self.open_file_highlight = "icon"
elseif level == 2 then
self.open_file_highlight = "name"
elseif level == 3 then
self.open_file_highlight = "all"
end
return self
end
function Builder:configure_git_icons_padding(padding)
self.git_icon_padding = padding or " "
return self
end
function Builder:configure_git_icons_placement(where)
where = where or "before"
self.is_git_before = where == "before"
self.is_git_after = not self.is_git_before
return self
end
function Builder:_insert_highlight(group, start, end_)
table.insert(self.highlights, { group, self.index, start, end_ or -1 })
end
function Builder:_insert_line(line)
table.insert(self.lines, line)
end
local function get_folder_name(node)
local name = node.name
local next = node.group_next
while next do
name = name .. "/" .. next.name
next = next.group_next
end
return name
end
function Builder:_unwrap_git_data(git_icons_and_hl_groups, offset)
if not git_icons_and_hl_groups then
return ""
end
local icon = ""
for _, v in ipairs(git_icons_and_hl_groups) do
if #v.icon > 0 then
self:_insert_highlight(v.hl, offset + #icon, offset + #icon + #v.icon)
icon = icon .. v.icon .. self.git_icon_padding
end
end
return icon
end
function Builder:_build_folder(node, padding, git_hl, git_icons_tbl)
local offset = string.len(padding)
local name = get_folder_name(node)
local has_children = #node.nodes ~= 0 or node.has_children
local icon = icons.get_folder_icon(node.open, node.link_to ~= nil, has_children)
local foldername = name .. self.trailing_slash
local git_icons = self:_unwrap_git_data(git_icons_tbl, offset + #icon + (self.is_git_after and #foldername + 1 or 0))
local fname_starts_at = offset + #icon + (self.is_git_before and #git_icons or 0)
local line = self:_format_line(padding .. icon, foldername, git_icons)
self:_insert_line(line)
if #icon > 0 then
self:_insert_highlight("NvimTreeFolderIcon", offset, offset + #icon)
end
local foldername_hl = "NvimTreeFolderName"
if self.special_map[node.absolute_path] then
foldername_hl = "NvimTreeSpecialFolderName"
elseif node.open then
foldername_hl = "NvimTreeOpenedFolderName"
elseif not has_children then
foldername_hl = "NvimTreeEmptyFolderName"
end
self:_insert_highlight(foldername_hl, fname_starts_at, fname_starts_at + #foldername)
if git_hl then
self:_insert_highlight(git_hl, fname_starts_at, fname_starts_at + #foldername)
end
end
function Builder:_format_line(before, after, git_icons)
return string.format(
"%s%s%s %s",
before,
self.is_git_after and "" or git_icons,
after,
self.is_git_after and git_icons or ""
)
end
function Builder:_build_symlink(node, padding, git_highlight, git_icons_tbl)
local offset = string.len(padding)
local icon = icons.i.symlink
local arrow = icons.i.symlink_arrow
local symlink_formatted = node.name .. arrow .. node.link_to
local link_highlight = git_highlight or "NvimTreeSymlink"
local git_icons_starts_at = offset + #icon + (self.is_git_after and #symlink_formatted + 1 or 0)
local git_icons = self:_unwrap_git_data(git_icons_tbl, git_icons_starts_at)
local line = self:_format_line(padding .. icon, symlink_formatted, git_icons)
self:_insert_highlight(link_highlight, offset + (self.is_git_after and 0 or #git_icons), string.len(line))
self:_insert_line(line)
end
function Builder:_build_file_icon(node, offset)
local icon, hl_group = icons.get_file_icon(node.name, node.extension)
if hl_group then
self:_insert_highlight(hl_group, offset, offset + #icon)
end
return icon, false
end
function Builder:_highlight_opened_files(node, offset, icon_length, git_icons_length)
local from = offset
local to = offset
if self.open_file_highlight == "icon" then
to = from + icon_length
elseif self.open_file_highlight == "name" then
from = offset + icon_length + git_icons_length
to = from + #node.name
elseif self.open_file_highlight == "all" then
to = from + icon_length + git_icons_length + #node.name
end
self:_insert_highlight("NvimTreeOpenedFile", from, to)
end
function Builder:_build_file(node, padding, git_highlight, git_icons_tbl)
local offset = string.len(padding)
local icon = self:_build_file_icon(node, offset)
local git_icons_starts_at = offset + #icon + (self.is_git_after and #node.name + 1 or 0)
local git_icons = self:_unwrap_git_data(git_icons_tbl, git_icons_starts_at)
self:_insert_line(self:_format_line(padding .. icon, node.name, git_icons))
local git_icons_length = self.is_git_after and 0 or #git_icons
local col_start = offset + #icon + git_icons_length
local col_end = col_start + #node.name
if self.special_map[node.absolute_path] or self.special_map[node.name] then
self:_insert_highlight("NvimTreeSpecialFile", col_start, col_end)
elseif node.executable then
self:_insert_highlight("NvimTreeExecFile", col_start, col_end)
elseif self.picture_map[node.extension] then
self:_insert_highlight("NvimTreeImageFile", col_start, col_end)
end
local should_highlight_opened_files = self.open_file_highlight and vim.fn.bufloaded(node.absolute_path) > 0
if should_highlight_opened_files then
self:_highlight_opened_files(node, offset, #icon, git_icons_length)
end
if git_highlight then
self:_insert_highlight(git_highlight, col_start, col_end)
end
end
function Builder:_build_line(tree, node, idx)
local padding = pad.get_padding(self.depth, idx, tree, node, self.markers)
if self.depth > 0 then
self:_insert_highlight("NvimTreeIndentMarker", 0, string.len(padding))
end
local git_highlight = git.get_highlight(node)
local git_icons_tbl = git.get_icons(node)
local is_folder = node.nodes ~= nil
local is_symlink = node.link_to ~= nil
if is_folder then
self:_build_folder(node, padding, git_highlight, git_icons_tbl)
elseif is_symlink then
self:_build_symlink(node, padding, git_highlight, git_icons_tbl)
else
self:_build_file(node, padding, git_highlight, git_icons_tbl)
end
self.index = self.index + 1
if node.open then
self.depth = self.depth + 2
self:build(node)
self.depth = self.depth - 2
end
end
function Builder:build(tree)
for idx, node in ipairs(tree.nodes) do
self:_build_line(tree, node, idx)
end
return self
end
local function format_root_name(root_cwd, modifier)
local base_root = utils.path_remove_trailing(vim.fn.fnamemodify(root_cwd, modifier))
return utils.path_join { base_root, ".." }
end
function Builder:build_header(show_header)
if show_header then
local root_name = format_root_name(self.root_cwd, self.root_folder_modifier)
self:_insert_line(root_name)
self:_insert_highlight("NvimTreeRootFolder", 0, string.len(root_name))
self.index = 1
end
return self
end
function Builder:unwrap()
return self.lines, self.highlights
end
return Builder

View File

@ -0,0 +1,158 @@
local _icons = require "nvim-tree.renderer.icon-config"
local utils = require "nvim-tree.utils"
local M = {}
local function build_icons_table()
local i = M.icon_state.icons.git_icons
return {
["M "] = { { icon = i.staged, hl = "NvimTreeGitStaged" } },
[" M"] = { { icon = i.unstaged, hl = "NvimTreeGitDirty" } },
["C "] = { { icon = i.staged, hl = "NvimTreeGitStaged" } },
[" C"] = { { icon = i.unstaged, hl = "NvimTreeGitDirty" } },
["CM"] = { { icon = i.unstaged, hl = "NvimTreeGitDirty" } },
[" T"] = { { icon = i.unstaged, hl = "NvimTreeGitDirty" } },
["T "] = { { icon = i.staged, hl = "NvimTreeGitStaged" } },
["MM"] = {
{ icon = i.staged, hl = "NvimTreeGitStaged" },
{ icon = i.unstaged, hl = "NvimTreeGitDirty" },
},
["MD"] = {
{ icon = i.staged, hl = "NvimTreeGitStaged" },
},
["A "] = {
{ icon = i.staged, hl = "NvimTreeGitStaged" },
},
["AD"] = {
{ icon = i.staged, hl = "NvimTreeGitStaged" },
},
[" A"] = {
{ icon = i.untracked, hl = "NvimTreeGitNew" },
},
-- not sure about this one
["AA"] = {
{ icon = i.unmerged, hl = "NvimTreeGitMerge" },
{ icon = i.untracked, hl = "NvimTreeGitNew" },
},
["AU"] = {
{ icon = i.unmerged, hl = "NvimTreeGitMerge" },
{ icon = i.untracked, hl = "NvimTreeGitNew" },
},
["AM"] = {
{ icon = i.staged, hl = "NvimTreeGitStaged" },
{ icon = i.unstaged, hl = "NvimTreeGitDirty" },
},
["??"] = { { icon = i.untracked, hl = "NvimTreeGitNew" } },
["R "] = { { icon = i.renamed, hl = "NvimTreeGitRenamed" } },
[" R"] = { { icon = i.renamed, hl = "NvimTreeGitRenamed" } },
["RM"] = {
{ icon = i.unstaged, hl = "NvimTreeGitDirty" },
{ icon = i.renamed, hl = "NvimTreeGitRenamed" },
},
["UU"] = { { icon = i.unmerged, hl = "NvimTreeGitMerge" } },
["UD"] = { { icon = i.unmerged, hl = "NvimTreeGitMerge" } },
["UA"] = { { icon = i.unmerged, hl = "NvimTreeGitMerge" } },
[" D"] = { { icon = i.deleted, hl = "NvimTreeGitDeleted" } },
["D "] = { { icon = i.deleted, hl = "NvimTreeGitDeleted" } },
["RD"] = { { icon = i.deleted, hl = "NvimTreeGitDeleted" } },
["DD"] = { { icon = i.deleted, hl = "NvimTreeGitDeleted" } },
["DU"] = {
{ icon = i.deleted, hl = "NvimTreeGitDeleted" },
{ icon = i.unmerged, hl = "NvimTreeGitMerge" },
},
["!!"] = { { icon = i.ignored, hl = "NvimTreeGitIgnored" } },
dirty = { { icon = i.unstaged, hl = "NvimTreeGitDirty" } },
}
end
local function nil_() end
local function warn_status(git_status)
utils.warn(
'Unrecognized git state "'
.. git_status
.. '". Please open up an issue on https://github.com/kyazdani42/nvim-tree.lua/issues with this message.'
)
end
local function get_icons_(node)
local git_status = node.git_status
if not git_status then
return nil
end
local icons = M.git_icons[git_status]
if not icons then
if vim.g.nvim_tree_git_hl ~= 1 then
warn_status(git_status)
end
return nil
end
return icons
end
local git_hl = {
["M "] = "NvimTreeFileStaged",
["C "] = "NvimTreeFileStaged",
["AA"] = "NvimTreeFileStaged",
["AD"] = "NvimTreeFileStaged",
["MD"] = "NvimTreeFileStaged",
["T "] = "NvimTreeFileStaged",
[" M"] = "NvimTreeFileDirty",
["CM"] = "NvimTreeFileDirty",
[" C"] = "NvimTreeFileDirty",
[" T"] = "NvimTreeFileDirty",
["MM"] = "NvimTreeFileDirty",
["AM"] = "NvimTreeFileDirty",
dirty = "NvimTreeFileDirty",
["A "] = "NvimTreeFileNew",
["??"] = "NvimTreeFileNew",
["AU"] = "NvimTreeFileMerge",
["UU"] = "NvimTreeFileMerge",
["UD"] = "NvimTreeFileMerge",
["DU"] = "NvimTreeFileMerge",
["UA"] = "NvimTreeFileMerge",
[" D"] = "NvimTreeFileDeleted",
["DD"] = "NvimTreeFileDeleted",
["RD"] = "NvimTreeFileDeleted",
["D "] = "NvimTreeFileDeleted",
["R "] = "NvimTreeFileRenamed",
["RM"] = "NvimTreeFileRenamed",
[" R"] = "NvimTreeFileRenamed",
["!!"] = "NvimTreeGitIgnored",
[" A"] = "none",
}
local function get_highlight_(node)
local git_status = node.git_status
if not git_status then
return
end
return git_hl[git_status]
end
M.get_icons = nil_
M.get_highlight = nil_
M.icon_state = _icons.get_config()
M.git_icons = build_icons_table()
function M.reload()
M.icon_state = _icons.get_config()
M.git_icons = build_icons_table()
if M.icon_state.show_git_icon then
M.get_icons = get_icons_
else
M.get_icons = nil_
end
if vim.g.nvim_tree_git_hl == 1 then
M.get_highlight = get_highlight_
else
M.get_highlight = nil_
end
end
return M

View File

@ -0,0 +1,93 @@
local icon_config = require "nvim-tree.renderer.icon-config"
local M = { i = {} }
local function config_symlinks()
M.i.symlink = #M.icons.symlink > 0 and M.icons.symlink .. M.padding or ""
M.i.symlink_arrow = vim.g.nvim_tree_symlink_arrow or ""
end
local function empty()
return ""
end
local function get_folder_icon(open, is_symlink, has_children)
local n
if is_symlink and open then
n = M.icons.folder_icons.symlink_open
elseif is_symlink then
n = M.icons.folder_icons.symlink
elseif open then
if has_children then
n = M.icons.folder_icons.open
else
n = M.icons.folder_icons.empty_open
end
else
if has_children then
n = M.icons.folder_icons.default
else
n = M.icons.folder_icons.empty
end
end
return n .. M.padding
end
local function get_file_icon_default()
local hl_group = "NvimTreeFileIcon"
local icon = M.icons.default
if #icon > 0 then
return icon .. M.padding, hl_group
else
return ""
end
end
local function get_file_icon_webdev(fname, extension)
local icon, hl_group = M.devicons.get_icon(fname, extension)
if not M.webdev_colors then
hl_group = "NvimTreeFileIcon"
end
if icon and hl_group ~= "DevIconDefault" then
return icon .. M.padding, hl_group
elseif string.match(extension, "%.(.*)") then
-- If there are more extensions to the file, try to grab the icon for them recursively
return get_file_icon_webdev(fname, string.match(extension, "%.(.*)"))
else
return get_file_icon_default()
end
end
local function config_file_icon()
if M.configs.show_file_icon then
if M.devicons then
M.get_file_icon = get_file_icon_webdev
else
M.get_file_icon = get_file_icon_default
end
else
M.get_file_icon = empty
end
end
local function config_folder_icon()
if M.configs.show_folder_icon then
M.get_folder_icon = get_folder_icon
else
M.get_folder_icon = empty
end
end
function M.reset_config(webdev_colors)
M.configs = icon_config.get_config()
M.icons = M.configs.icons
M.padding = vim.g.nvim_tree_icon_padding or " "
M.devicons = M.configs.has_devicons and require "nvim-web-devicons" or nil
M.webdev_colors = webdev_colors
config_symlinks()
config_file_icon()
config_folder_icon()
end
return M

View File

@ -0,0 +1,53 @@
local M = {}
function M.get_padding(depth)
return string.rep(" ", depth)
end
local function get_padding_arrows(icon_state)
return function(depth, _, _, node)
if node.nodes then
local icon = icon_state.icons.folder_icons[node.open and "arrow_open" or "arrow_closed"]
return string.rep(" ", depth - 2) .. icon .. " "
end
return string.rep(" ", depth)
end
end
local function get_padding_indent_markers(depth, idx, tree, _, markers)
local padding = ""
if depth ~= 0 then
local rdepth = depth / 2
markers[rdepth] = idx ~= #tree.nodes
for i = 1, rdepth do
if idx == #tree.nodes and i == rdepth then
padding = padding .. M.config.indent_markers.icons.corner
elseif markers[i] then
padding = padding .. M.config.indent_markers.icons.edge
else
padding = padding .. M.config.indent_markers.icons.none
end
end
end
return padding
end
function M.reload_padding_function()
local icon_state = require("nvim-tree.renderer.icon-config").get_config()
if icon_state.show_folder_icon and icon_state.show_folder_arrows then
M.get_padding = get_padding_arrows(icon_state)
end
if M.config.indent_markers.enable then
M.get_padding = get_padding_indent_markers
end
end
function M.setup(opts)
M.config = {
indent_markers = opts.renderer.indent_markers,
}
end
return M

View File

@ -0,0 +1,50 @@
local M = {}
function M.compute_lines()
local help_lines = { "HELP" }
local help_hl = { { "NvimTreeRootFolder", 0, 0, #help_lines[1] } }
local mappings = vim.tbl_filter(function(v)
return (v.cb ~= nil and v.cb ~= "") or (v.action ~= nil and v.action ~= "")
end, require("nvim-tree.actions").mappings)
local processed = {}
for _, b in pairs(mappings) do
local cb = b.cb
local key = b.key
local name
if cb and cb:sub(1, 35) == require("nvim-tree.config").nvim_tree_callback("test"):sub(1, 35) then
name = cb:match "'[^']+'[^']*$"
name = name:match "'[^']+'"
elseif b.action then
name = b.action
else
name = (b.name ~= nil) and b.name or cb
name = '"' .. name .. '"'
end
table.insert(processed, { key, name, true })
end
table.sort(processed, function(a, b)
return (a[3] == b[3] and (a[2] < b[2] or (a[2] == b[2] and #a[1] < #b[1]))) or (a[3] and not b[3])
end)
local num = 0
for _, val in pairs(processed) do
local keys = type(val[1]) == "string" and { val[1] } or val[1]
local map_name = val[2]
local builtin = val[3]
for _, key in pairs(keys) do
num = num + 1
local bind_string = string.format("%6s : %s", key, map_name)
table.insert(help_lines, bind_string)
local hl_len = math.max(6, string.len(key)) + 2
table.insert(help_hl, { "NvimTreeFolderName", num, 0, hl_len })
if not builtin then
table.insert(help_hl, { "NvimTreeFileRenamed", num, hl_len, -1 })
end
end
end
return help_lines, help_hl
end
return M

View File

@ -0,0 +1,61 @@
local M = {}
function M.get_config()
local show_icons = vim.g.nvim_tree_show_icons or { git = 1, folders = 1, files = 1, folder_arrows = 1 }
local icons = {
default = "",
symlink = "",
git_icons = {
unstaged = "",
staged = "",
unmerged = "",
renamed = "",
untracked = "",
deleted = "",
ignored = "",
},
folder_icons = {
arrow_closed = "",
arrow_open = "",
default = "",
open = "",
empty = "",
empty_open = "",
symlink = "",
symlink_open = "",
},
}
local user_icons = vim.g.nvim_tree_icons
if user_icons then
if user_icons.default then
icons.default = user_icons.default
icons.symlink = user_icons.default
end
if user_icons.symlink then
icons.symlink = user_icons.symlink
end
for key, val in pairs(user_icons.git or {}) do
if icons.git_icons[key] then
icons.git_icons[key] = val
end
end
for key, val in pairs(user_icons.folder or {}) do
if icons.folder_icons[key] then
icons.folder_icons[key] = val
end
end
end
local has_devicons = pcall(require, "nvim-web-devicons")
return {
show_file_icon = show_icons.files == 1,
show_folder_icon = show_icons.folders == 1,
show_git_icon = show_icons.git == 1,
show_folder_arrows = show_icons.folder_arrows == 1,
has_devicons = has_devicons,
icons = icons,
}
end
return M

View File

@ -0,0 +1,116 @@
local core = require "nvim-tree.core"
local diagnostics = require "nvim-tree.diagnostics"
local log = require "nvim-tree.log"
local view = require "nvim-tree.view"
local _padding = require "nvim-tree.renderer.components.padding"
local icon_component = require "nvim-tree.renderer.components.icons"
local help = require "nvim-tree.renderer.help"
local git = require "nvim-tree.renderer.components.git"
local Builder = require "nvim-tree.renderer.builder"
local api = vim.api
local M = {
last_highlights = {},
}
local namespace_id = api.nvim_create_namespace "NvimTreeHighlights"
local function _draw(bufnr, lines, hl)
api.nvim_buf_set_option(bufnr, "modifiable", true)
api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
M.render_hl(bufnr, hl)
api.nvim_buf_set_option(bufnr, "modifiable", false)
end
function M.render_hl(bufnr, hl)
if not bufnr or not api.nvim_buf_is_loaded(bufnr) then
return
end
api.nvim_buf_clear_namespace(bufnr, namespace_id, 0, -1)
for _, data in ipairs(hl or M.last_highlights) do
api.nvim_buf_add_highlight(bufnr, namespace_id, data[1], data[2], data[3], data[4])
end
end
local function should_show_arrows()
return not M.config.indent_markers.enable
and icon_component.configs.show_folder_icon
and icon_component.configs.show_folder_arrows
end
local picture_map = {
jpg = true,
jpeg = true,
png = true,
gif = true,
}
local function get_special_files_map()
return vim.g.nvim_tree_special_files
or {
["Cargo.toml"] = true,
Makefile = true,
["README.md"] = true,
["readme.md"] = true,
}
end
function M.draw()
local bufnr = view.get_bufnr()
if not core.get_explorer() or not bufnr or not api.nvim_buf_is_loaded(bufnr) then
return
end
local ps = log.profile_start "draw"
local cursor = api.nvim_win_get_cursor(view.get_winnr())
_padding.reload_padding_function()
icon_component.reset_config(M.config.icons.webdev_colors)
git.reload()
local lines, hl
if view.is_help_ui() then
lines, hl = help.compute_lines()
else
lines, hl = Builder.new(core.get_cwd())
:configure_initial_depth(should_show_arrows())
:configure_root_modifier(vim.g.nvim_tree_root_folder_modifier)
:configure_trailing_slash(vim.g.nvim_tree_add_trailing == 1)
:configure_special_map(get_special_files_map())
:configure_picture_map(picture_map)
:configure_opened_file_highlighting(vim.g.nvim_tree_highlight_opened_files)
:configure_git_icons_padding(vim.g.nvim_tree_icon_padding)
:configure_git_icons_placement(M.config.icons.git_placement)
:build_header(view.is_root_folder_visible(core.get_cwd()))
:build(core.get_explorer())
:unwrap()
end
_draw(bufnr, lines, hl)
M.last_highlights = hl
if cursor and #lines >= cursor[1] then
api.nvim_win_set_cursor(view.get_winnr(), cursor)
end
if view.is_help_ui() then
diagnostics.clear()
else
diagnostics.update()
end
log.profile_end(ps, "draw")
end
function M.setup(opts)
M.config = {
indent_markers = opts.renderer.indent_markers,
icons = opts.renderer.icons,
}
_padding.setup(opts)
end
return M

View File

@ -0,0 +1,221 @@
local has_notify, notify = pcall(require, "notify")
local a = vim.api
local uv = vim.loop
local M = {}
M.is_windows = vim.fn.has "win32" == 1 or vim.fn.has "win32unix" == 1
function M.path_to_matching_str(path)
return path:gsub("(%-)", "(%%-)"):gsub("(%.)", "(%%.)"):gsub("(%_)", "(%%_)")
end
function M.warn(msg)
vim.schedule(function()
if has_notify then
notify(msg, vim.log.levels.WARN, { title = "NvimTree" })
else
vim.notify("[NvimTree] " .. msg, vim.log.levels.WARN)
end
end)
end
function M.str_find(haystack, needle)
return vim.fn.stridx(haystack, needle) ~= -1
end
function M.read_file(path)
local fd = uv.fs_open(path, "r", 438)
if not fd then
return ""
end
local stat = uv.fs_fstat(fd)
if not stat then
return ""
end
local data = uv.fs_read(fd, stat.size, 0)
uv.fs_close(fd)
return data or ""
end
local path_separator = package.config:sub(1, 1)
function M.path_join(paths)
return table.concat(vim.tbl_map(M.path_remove_trailing, paths), path_separator)
end
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
---Get a path relative to another path.
---@param path string
---@param relative_to string
---@return string
function M.path_relative(path, relative_to)
local p, _ = path:gsub("^" .. M.path_to_matching_str(M.path_add_trailing(relative_to)), "")
return p
end
function M.path_add_trailing(path)
if path:sub(-1) == path_separator then
return path
end
return path .. path_separator
end
function M.path_remove_trailing(path)
local p, _ = path:gsub(path_separator .. "$", "")
return p
end
M.path_separator = path_separator
function M.clear_prompt()
vim.api.nvim_command "normal! :"
end
function M.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 the node from the tree that matches the predicate
-- @param nodes list of node
-- @param fn function(node): boolean
function M.find_node(nodes, fn)
local function iter(nodes_, fn_)
local i = 1
for _, node in ipairs(nodes_) do
if fn_(node) then
return node, i
end
if node.open and #node.nodes > 0 then
local n, idx = iter(node.nodes, fn_)
i = i + idx
if n then
return n, i
end
else
i = i + 1
end
end
return nil, i
end
local node, i = iter(nodes, fn)
i = require("nvim-tree.view").View.hide_root_folder and i - 1 or i
return node, i
end
---Matching executable files in Windows.
---@param ext string
---@return boolean
local PATHEXT = vim.env.PATHEXT or ""
local wexe = vim.split(PATHEXT:gsub("%.", ""), ";")
local pathexts = {}
for _, v in pairs(wexe) do
pathexts[v] = true
end
function M.is_windows_exe(ext)
return pathexts[ext:upper()]
end
function M.rename_loaded_buffers(old_path, new_path)
for _, buf in pairs(a.nvim_list_bufs()) do
if a.nvim_buf_is_loaded(buf) then
local buf_name = a.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
a.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
if a.nvim_buf_get_option(buf, "buftype") == "" then
a.nvim_buf_call(buf, function()
vim.cmd "silent! write!"
end)
end
end
end
end
end
--- @param path string path to file or directory
--- @return boolean
function M.file_exists(path)
local _, error = vim.loop.fs_stat(path)
return error == nil
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
-- Create empty sub-tables if not present
-- @param tbl to create empty inside of
-- @param sub dot separated string of sub-tables
-- @return deepest sub-table
function M.table_create_missing(tbl, sub)
if tbl == nil then
return nil
end
local t = tbl
for s in string.gmatch(sub, "([^%.]+)%.*") do
if t[s] == nil then
t[s] = {}
end
t = t[s]
end
return t
end
function M.format_bytes(bytes)
local units = { "B", "K", "M", "G", "T" }
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 = bytes / (1024 ^ pow)
value = math.floor((value * 10) + 0.5) / 10
pow = pow + 1
return (units[pow] == nil) and (bytes .. "B") or (value .. units[pow])
end
function M.key_by(tbl, key)
local keyed = {}
for _, val in ipairs(tbl) do
keyed[val[key]] = val
end
return keyed
end
return M

View File

@ -0,0 +1,399 @@
local a = vim.api
local M = {}
local events = require "nvim-tree.events"
M.View = {
tabpages = {},
cursors = {},
hide_root_folder = false,
winopts = {
relativenumber = false,
number = false,
list = false,
foldenable = false,
winfixwidth = true,
winfixheight = true,
spell = false,
signcolumn = "yes",
foldmethod = "manual",
foldcolumn = "0",
cursorcolumn = false,
cursorlineopt = "line",
colorcolumn = "0",
wrap = false,
winhl = table.concat({
"EndOfBuffer:NvimTreeEndOfBuffer",
"Normal:NvimTreeNormal",
"CursorLine:NvimTreeCursorLine",
-- #1221 WinSeparator not present in nvim 0.6.1 and some builds of 0.7.0
pcall(vim.cmd, "silent hi WinSeparator") and "WinSeparator:NvimTreeWinSeparator" or "VertSplit:NvimTreeWinSeparator",
"StatusLine:NvimTreeStatusLine",
"StatusLineNC:NvimTreeStatuslineNC",
"SignColumn:NvimTreeSignColumn",
"NormalNC:NvimTreeNormalNC",
}, ","),
},
}
-- The initial state of a tab
local tabinitial = {
-- True if help is displayed
help = false,
-- The position of the cursor { line, column }
cursor = { 0, 0 },
-- The NvimTree window number
winnr = nil,
}
local BUFNR_PER_TAB = {}
local BUFFER_OPTIONS = {
swapfile = false,
buftype = "nofile",
modifiable = false,
filetype = "NvimTree",
bufhidden = "wipe",
buflisted = false,
}
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(a.nvim_list_bufs()) do
if not matches_bufnr(bufnr) and a.nvim_buf_get_name(bufnr):match "NvimTree" ~= nil then
return pcall(a.nvim_buf_delete, bufnr, { force = true })
end
end
end
local function create_buffer(bufnr)
wipe_rogue_buffer()
local tab = a.nvim_get_current_tabpage()
BUFNR_PER_TAB[tab] = bufnr or a.nvim_create_buf(false, false)
a.nvim_buf_set_name(M.get_bufnr(), "NvimTree_" .. tab)
for option, value in pairs(BUFFER_OPTIONS) do
vim.bo[M.get_bufnr()][option] = value
end
require("nvim-tree.actions").apply_mappings(M.get_bufnr())
end
local function get_size()
local width_or_height = M.is_vertical() and "width" or "height"
local size = M.View[width_or_height]
if type(size) == "number" then
return size
elseif type(size) == "function" then
return size()
end
local size_as_number = tonumber(size:sub(0, -2))
local percent_as_decimal = size_as_number / 100
return math.floor(vim.o.columns * percent_as_decimal)
end
local move_tbl = {
left = "H",
right = "L",
bottom = "J",
top = "K",
}
-- TODO: remove this once they fix https://github.com/neovim/neovim/issues/14670
local function set_local(opt, value)
local cmd
if value == true then
cmd = string.format("setlocal %s", opt)
elseif value == false then
cmd = string.format("setlocal no%s", opt)
else
cmd = string.format("setlocal %s=%s", opt, value)
end
vim.cmd(cmd)
end
-- setup_tabpage sets up the initial state of a tab
local function setup_tabpage(tabpage)
local winnr = a.nvim_get_current_win()
M.View.tabpages[tabpage] = vim.tbl_extend("force", M.View.tabpages[tabpage] or tabinitial, { winnr = winnr })
end
local function open_window()
a.nvim_command "vsp"
M.reposition_window()
setup_tabpage(a.nvim_get_current_tabpage())
end
local function set_window_options_and_buffer()
pcall(vim.cmd, "buffer " .. M.get_bufnr())
for k, v in pairs(M.View.winopts) do
set_local(k, v)
end
end
local function get_existing_buffers()
return vim.tbl_filter(function(buf)
return a.nvim_buf_is_valid(buf) and vim.fn.buflisted(buf) == 1
end, a.nvim_list_bufs())
end
local function switch_buf_if_last_buf()
if #a.nvim_list_wins() == 1 then
if #get_existing_buffers() > 0 then
vim.cmd "sbnext"
else
vim.cmd "new"
end
end
end
-- save_tab_state saves any state that should be preserved across redraws.
local function save_tab_state()
local tabpage = a.nvim_get_current_tabpage()
M.View.cursors[tabpage] = a.nvim_win_get_cursor(M.get_winnr())
end
function M.close()
if not M.is_visible() then
return
end
save_tab_state()
switch_buf_if_last_buf()
local tree_win = M.get_winnr()
local current_win = a.nvim_get_current_win()
for _, win in pairs(a.nvim_list_wins()) do
if tree_win ~= win and a.nvim_win_get_config(win).relative == "" then
a.nvim_win_close(tree_win, true)
local prev_win = vim.fn.winnr "#" -- this tab only
if tree_win == current_win and prev_win > 0 then
a.nvim_set_current_win(vim.fn.win_getid(prev_win))
end
events._dispatch_on_tree_close()
return
end
end
end
function M.open(options)
if M.is_visible() then
return
end
create_buffer()
open_window()
set_window_options_and_buffer()
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()
end
function M.resize(size)
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
if M.is_vertical() then
a.nvim_win_set_width(M.get_winnr(), get_size())
else
a.nvim_win_set_height(M.get_winnr(), get_size())
end
if not M.View.preserve_window_proportions then
vim.cmd ":wincmd ="
end
end
function M.reposition_window()
local move_to = move_tbl[M.View.side]
a.nvim_command("wincmd " .. move_to)
M.resize()
end
local function set_current_win()
local current_tab = a.nvim_get_current_tabpage()
M.View.tabpages[current_tab].winnr = a.nvim_get_current_win()
end
function M.open_in_current_win(opts)
opts = opts or { hijack_current_buf = true, resize = true }
create_buffer(opts.hijack_current_buf and a.nvim_get_current_buf())
setup_tabpage(a.nvim_get_current_tabpage())
set_current_win()
set_window_options_and_buffer()
if opts.resize then
M.reposition_window()
M.resize()
end
end
function M.abandon_current_window()
local tab = a.nvim_get_current_tabpage()
BUFNR_PER_TAB[tab] = nil
M.View.tabpages[tab].winnr = nil
end
function M.is_visible(opts)
if opts and opts.any_tabpage then
for _, v in pairs(M.View.tabpages) do
if a.nvim_win_is_valid(v.winnr) then
return true
end
end
return false
end
return M.get_winnr() ~= nil and a.nvim_win_is_valid(M.get_winnr())
end
function M.set_cursor(opts)
if M.is_visible() then
pcall(a.nvim_win_set_cursor, M.get_winnr(), opts)
-- patch until https://github.com/neovim/neovim/issues/17395 is fixed
require("nvim-tree.renderer").draw()
end
end
function M.focus(winnr, open_if_closed)
local wnr = winnr or M.get_winnr()
if a.nvim_win_get_tabpage(wnr or 0) ~= a.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
a.nvim_set_current_win(wnr)
end
function M.is_vertical()
return M.View.side == "left" or M.View.side == "right"
end
--- Restores the state of a NvimTree window if it was initialized before.
function M.restore_tab_state()
local tabpage = a.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: (optional) the number of the chosen tabpage. Defaults to current tabpage.
---@return number
function M.get_winnr(tabpage)
tabpage = tabpage or a.nvim_get_current_tabpage()
local tabinfo = M.View.tabpages[tabpage]
if tabinfo ~= nil then
return tabinfo.winnr
end
end
--- Returns the current nvim tree bufnr
---@return number
function M.get_bufnr()
return BUFNR_PER_TAB[a.nvim_get_current_tabpage()]
end
--- Checks if nvim-tree is displaying the help ui within the tabpage specified
---@param tabpage number: (optional) the number of the chosen tabpage. Defaults to current tabpage.
---@return number
function M.is_help_ui(tabpage)
tabpage = tabpage or a.nvim_get_current_tabpage()
local tabinfo = M.View.tabpages[tabpage]
if tabinfo ~= nil then
return tabinfo.help
end
end
function M.toggle_help(tabpage)
tabpage = tabpage or a.nvim_get_current_tabpage()
M.View.tabpages[tabpage].help = not M.View.tabpages[tabpage].help
end
function M.is_buf_valid(bufnr)
return bufnr and a.nvim_buf_is_valid(bufnr) and a.nvim_buf_is_loaded(bufnr)
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 = a.nvim_get_current_win()
local curbuf = a.nvim_win_get_buf(curwin)
local bufname = a.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 }
require("nvim-tree.renderer").draw()
a.nvim_win_close(curwin, { force = true })
require("nvim-tree.actions.open-file").fn("edit", bufname)
end)
end
function M.is_root_folder_visible(cwd)
return cwd ~= "/" and not M.View.hide_root_folder
end
function M.setup(opts)
local options = opts.view or {}
M.View.side = options.side
M.View.width = options.width
M.View.height = options.height
M.View.hide_root_folder = options.hide_root_folder
M.View.preserve_window_proportions = options.preserve_window_proportions
M.View.winopts.number = options.number
M.View.winopts.relativenumber = options.relativenumber
M.View.winopts.signcolumn = options.signcolumn
end
return M

View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
ln -sf ../../.hooks/pre-commit.sh .git/hooks/pre-commit

View File

@ -0,0 +1,21 @@
#!/bin/sh
# run after changing nvim-tree.lua DEFAULT_OPTS: scrapes and updates README.md, nvim-tree-lua.txt
begin="BEGIN_DEFAULT_OPTS"
end="END_DEFAULT_OPTS"
# scrape, indented at 2
sed -n -e "/${begin}/,/${end}/{ /${begin}/d; /${end}/d; p; }" lua/nvim-tree.lua > /tmp/DEFAULT_OPTS.2.lua
# indent some more
sed -e "s/^ / /" /tmp/DEFAULT_OPTS.2.lua > /tmp/DEFAULT_OPTS.6.lua
# README.md indented at 2
sed -i -e "/${begin}/,/${end}/{ /${begin}/{p; r /tmp/DEFAULT_OPTS.2.lua
}; /${end}/p; d }" README.md
# help, indented at 6
sed -i -e "/${begin}/,/${end}/{ /${begin}/{p; r /tmp/DEFAULT_OPTS.6.lua
}; /${end}/p; d }" doc/nvim-tree-lua.txt

View File

@ -0,0 +1,42 @@
let s:WIN = SpaceVim#api#import('vim#window')
nnoremap <silent> <F3> <cmd>NvimTreeToggle<CR>
" we can not use this option to disable default key bindings
" let g:nvim_tree_disable_default_keybindings = 1
augroup vfinit
au!
autocmd FileType NvimTree call s:nvim_tree_init()
autocmd BufEnter * nested if
\ (!has('vim_starting') && s:WIN.win_count() == 1 && g:_spacevim_autoclose_filetree
\ && &filetype ==# 'NvimTree') |
\ call s:close_last_filetree() | endif
augroup END
function! s:close_last_filetree() abort
call SpaceVim#layers#shell#close_terminal()
q
endfunction
function! s:nvim_tree_init() abort
nnoremap <silent><buffer> . :<C-u>lua require'nvim-tree.actions'.on_keypress('toggle_dotfiles')<Cr>
endfunction
lua <<EOF
-- init.lua
-- empty setup using defaults: add your own options
require'nvim-tree'.setup {
view = {
width = vim.api.nvim_eval('g:spacevim_sidebar_width'),
height = 30,
hide_root_folder = false,
side = "right",
preserve_window_proportions = false,
number = false,
relativenumber = false,
signcolumn = "yes",
mappings = {
custom_only = true,
list = {
-- user mappings go here
},
},
},
}
EOF