Compare commits
287 Commits
compat-nvi
...
compat-nvi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
949913f186 | ||
|
|
a8d26bb088 | ||
|
|
623cecb809 | ||
|
|
95ed588211 | ||
|
|
899ed45602 | ||
|
|
0cd8ac4751 | ||
|
|
a2c75567ad | ||
|
|
8b4aaff783 | ||
|
|
7177d95ac0 | ||
|
|
b9aaf805a1 | ||
|
|
3c5d9dd31f | ||
|
|
c5dc80c36b | ||
|
|
e49fa4e529 | ||
|
|
69a07d169a | ||
|
|
f8489c9929 | ||
|
|
9d9c5711dc | ||
|
|
07149daa0c | ||
|
|
829e9f68e1 | ||
|
|
0b319a1b28 | ||
|
|
9f7bed5536 | ||
|
|
b17358ff4d | ||
|
|
99d713644d | ||
|
|
68a2a0971e | ||
|
|
cc18122be1 | ||
|
|
e38e061710 | ||
|
|
a65063cb0a | ||
|
|
c49499413a | ||
|
|
1837751efb | ||
|
|
059e4cadd6 | ||
|
|
9d241e5f58 | ||
|
|
cf908370fb | ||
|
|
f1f89f2062 | ||
|
|
bed442fd93 | ||
|
|
77dd666e36 | ||
|
|
e0e23f2d62 | ||
|
|
e204a7d819 | ||
|
|
bcb2a5a80d | ||
|
|
7e892767bd | ||
|
|
d91f885819 | ||
|
|
8cc369695b | ||
|
|
6d6a44626d | ||
|
|
bdc4ec6abd | ||
|
|
be2ccd4b1a | ||
|
|
a0f3e99b2d | ||
|
|
ed9db632a8 | ||
|
|
33ce8e3c73 | ||
|
|
6ca6f99e76 | ||
|
|
ada2c6441d | ||
|
|
cd2f7569db | ||
|
|
cbb5313f90 | ||
|
|
fba97517bb | ||
|
|
1044eba9e7 | ||
|
|
3845039c1a | ||
|
|
dd90bfa155 | ||
|
|
cb98892dea | ||
|
|
65c2ba8952 | ||
|
|
1be1e17be5 | ||
|
|
49c32c0dda | ||
|
|
23c0fe9a0a | ||
|
|
5a798b3be0 | ||
|
|
58055a0397 | ||
|
|
0122a71fce | ||
|
|
3170e33462 | ||
|
|
ea09ab497e | ||
|
|
e94f517798 | ||
|
|
2b970635d1 | ||
|
|
3a2f68b9d5 | ||
|
|
4e24505e2b | ||
|
|
48992fd3e8 | ||
|
|
c995ce0878 | ||
|
|
c446527056 | ||
|
|
55aa0062b9 | ||
|
|
187388b7f5 | ||
|
|
6ff828b25b | ||
|
|
c4ac723a83 | ||
|
|
23a564f1cd | ||
|
|
b07701f9da | ||
|
|
b01e7beaa6 | ||
|
|
c66cbdfc25 | ||
|
|
875d38e52c | ||
|
|
0db4977303 | ||
|
|
54afa503a9 | ||
|
|
3d58a9b2cf | ||
|
|
b4d704e88d | ||
|
|
4a01f90d11 | ||
|
|
79f631bc1d | ||
|
|
be2b4f58e6 | ||
|
|
c5536db0b7 | ||
|
|
7282f7de8a | ||
|
|
45d386a359 | ||
|
|
43fd138544 | ||
|
|
4aef454cd2 | ||
|
|
11b524899f | ||
|
|
9914780cba | ||
|
|
52b0c32152 | ||
|
|
0417d9148b | ||
|
|
540055be5f | ||
|
|
fbd421da71 | ||
|
|
ac8d259bad | ||
|
|
5cb87c037d | ||
|
|
3676e0b124 | ||
|
|
fb8735e96c | ||
|
|
3e49d9b748 | ||
|
|
9ba5366115 | ||
|
|
b83e06f7fe | ||
|
|
904110f8a9 | ||
|
|
e282420111 | ||
|
|
951e10a64e | ||
|
|
d753a1da9a | ||
|
|
011a7816b8 | ||
|
|
e8bf3d778a | ||
|
|
757951ba6b | ||
|
|
07f59e7450 | ||
|
|
4a725c0ca5 | ||
|
|
ce5d0a6b7d | ||
|
|
c272c88daf | ||
|
|
c3ea264947 | ||
|
|
259efeee62 | ||
|
|
e3353c4cb4 | ||
|
|
2bb15fd98f | ||
|
|
90dcf42bba | ||
|
|
049cdd3073 | ||
|
|
c5fba1ec18 | ||
|
|
81eb718394 | ||
|
|
9fd7b7ae29 | ||
|
|
d9edddb849 | ||
|
|
09a51266bc | ||
|
|
b314b3a699 | ||
|
|
261a5c380c | ||
|
|
a73d0d4800 | ||
|
|
ff6e7966f3 | ||
|
|
7323c81bd6 | ||
|
|
1685484738 | ||
|
|
cfc4692a3f | ||
|
|
451901ca9c | ||
|
|
9bbf95e616 | ||
|
|
665813b9e6 | ||
|
|
7fcb48c852 | ||
|
|
ac90664001 | ||
|
|
2928f8fe31 | ||
|
|
e632ac7c81 | ||
|
|
64cc3c17e1 | ||
|
|
5f30a7bee4 | ||
|
|
eff1db341c | ||
|
|
e5222970d9 | ||
|
|
e95bfbfc2d | ||
|
|
a0448184af | ||
|
|
86b9da5ca5 | ||
|
|
e7832785d2 | ||
|
|
d927e89aa9 | ||
|
|
08ab346f03 | ||
|
|
522bde4ea5 | ||
|
|
79434c2b3c | ||
|
|
1e3c578eeb | ||
|
|
630305c233 | ||
|
|
c964fa24d0 | ||
|
|
8dc2144e87 | ||
|
|
b754eb8359 | ||
|
|
f85b4d9952 | ||
|
|
203bb7e176 | ||
|
|
ecca8118f8 | ||
|
|
2edbe759cd | ||
|
|
ba5c18dc2b | ||
|
|
1018a83e10 | ||
|
|
1ee6a3ea65 | ||
|
|
2d629cab78 | ||
|
|
7cffe14743 | ||
|
|
1b667bc99e | ||
|
|
69aec67edb | ||
|
|
18447132fc | ||
|
|
21fadc1f38 | ||
|
|
ec530e73be | ||
|
|
16753d5e25 | ||
|
|
06e48c29c4 | ||
|
|
26512c369f | ||
|
|
208ce0b153 | ||
|
|
4900d66370 | ||
|
|
89becc7604 | ||
|
|
b32c88333f | ||
|
|
449b5bd0cb | ||
|
|
9a02dedd92 | ||
|
|
19425c5896 | ||
|
|
8632ac2739 | ||
|
|
80dc86e874 | ||
|
|
7087af83f3 | ||
|
|
c231933fcd | ||
|
|
0f96e32326 | ||
|
|
6a49a0301f | ||
|
|
078a9e5bf9 | ||
|
|
df92f1527f | ||
|
|
0fa2ec1950 | ||
|
|
26d0757bd9 | ||
|
|
ad1f3ef3bc | ||
|
|
8d0c93db4a | ||
|
|
2d2cbe63f4 | ||
|
|
831f1158c3 | ||
|
|
90bf14014e | ||
|
|
fd562ede63 | ||
|
|
95c57e034a | ||
|
|
c037c7ae84 | ||
|
|
fdcdb0ddf3 | ||
|
|
1e7019f91e | ||
|
|
63831d5179 | ||
|
|
b81ab199a5 | ||
|
|
d0ca2dab00 | ||
|
|
08db5a576d | ||
|
|
22044589fe | ||
|
|
4bd919a75f | ||
|
|
9d3602e8ea | ||
|
|
c84735483f | ||
|
|
eb6dde4733 | ||
|
|
418fc971fc | ||
|
|
269820e800 | ||
|
|
38fabe86cb | ||
|
|
f43b8af8f4 | ||
|
|
70bdf496ea | ||
|
|
40e515df87 | ||
|
|
28c4bb01f6 | ||
|
|
19dcacf06e | ||
|
|
736cc843e1 | ||
|
|
80d4f28383 | ||
|
|
21516f447b | ||
|
|
cbbc799e6c | ||
|
|
ec09b80c7b | ||
|
|
72858986f9 | ||
|
|
c18aa389a3 | ||
|
|
e401a4c957 | ||
|
|
7a795d78fa | ||
|
|
247f80b633 | ||
|
|
e6c1b4cd5b | ||
|
|
0c13bd76a8 | ||
|
|
b299a877ad | ||
|
|
65beb55ac7 | ||
|
|
0db63a350a | ||
|
|
7160e68d5a | ||
|
|
79258f1d67 | ||
|
|
104292c8f9 | ||
|
|
6548287e8b | ||
|
|
f262236107 | ||
|
|
3bc2207f4a | ||
|
|
d9aaa2f985 | ||
|
|
6b7b1b34fa | ||
|
|
1fc0eee946 | ||
|
|
e82a921baa | ||
|
|
b08003f546 | ||
|
|
84c2bd77ff | ||
|
|
aba394896b | ||
|
|
bdb6d4a254 | ||
|
|
9219831aa2 | ||
|
|
1caca62854 | ||
|
|
821f050fda | ||
|
|
3c936c7cb6 | ||
|
|
f6eef4a1f0 | ||
|
|
6f6eab14dc | ||
|
|
b0d27c09b6 | ||
|
|
a5793f1edb | ||
|
|
25921aa87a | ||
|
|
92ed3c487a | ||
|
|
3aeb59b075 | ||
|
|
a0f705995a | ||
|
|
2002b21be7 | ||
|
|
6b26628acf | ||
|
|
b1ecb75a6c | ||
|
|
8198fa01fc | ||
|
|
5e900c2f29 | ||
|
|
3806653d75 | ||
|
|
3a95c5a9cf | ||
|
|
0373680819 | ||
|
|
c3b7be8d19 | ||
|
|
e482bad61c | ||
|
|
84cb79e760 | ||
|
|
3ba383d591 | ||
|
|
6abc87b1d9 | ||
|
|
540c811cb2 | ||
|
|
b2ba6dea71 | ||
|
|
73ab312820 | ||
|
|
9d6f4c184b | ||
|
|
17d5bd64e8 | ||
|
|
9563a11ce0 | ||
|
|
6343813a35 | ||
|
|
99e32fea14 | ||
|
|
9d26594b6c | ||
|
|
7293f8dc70 | ||
|
|
d88d12f5bc | ||
|
|
aefa66c04d | ||
|
|
f8312cd06f | ||
|
|
46014449b6 |
13
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
13
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -5,7 +5,14 @@ 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.
|
||||
Is this a question?
|
||||
* Please start a new [Q&A discussion](https://github.com/nvim-tree/nvim-tree.lua/discussions/new) instead of raising a bug.
|
||||
|
||||
Before reporting:
|
||||
* search [existing issues](https://github.com/nvim-tree/nvim-tree.lua/issues)
|
||||
* ensure that nvim-tree is updated to the latest version
|
||||
|
||||
If you are experiencing performance issues, please [enable profiling](https://github.com/nvim-tree/nvim-tree.lua#performance-issues) and attach the logs.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Description"
|
||||
@@ -15,7 +22,7 @@ body:
|
||||
- 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)."
|
||||
description: "Output of `nvim --version`. Please see nvim-tree.lua [minimum required version](https://github.com/nvim-tree/nvim-tree.lua#notice)."
|
||||
placeholder: |
|
||||
NVIM v0.6.1
|
||||
Build type: Release
|
||||
@@ -42,7 +49,7 @@ body:
|
||||
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`
|
||||
(Right click) save [nvt-min.lua](https://raw.githubusercontent.com/nvim-tree/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.
|
||||
|
||||
|
||||
6
.github/ISSUE_TEMPLATE/feature_request.md
vendored
6
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -6,6 +6,12 @@ labels: feature request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
**Is this a question?**
|
||||
Please start a new [Q&A discussion](https://github.com/nvim-tree/nvim-tree.lua/discussions/new) instead of raising a feature request.
|
||||
|
||||
**Can this functionality be implemented utilising API?**
|
||||
nvim-tree exposes extensive API (see `:h nvim-tree-api`). Can it be used to achieve your goal? Is there a missing API that would make it possible?
|
||||
Given stable status of nvim-tree it's preferred to add new API than new functionality.
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/nvt-min.lua
vendored
4
.github/ISSUE_TEMPLATE/nvt-min.lua
vendored
@@ -6,8 +6,8 @@ local function load_plugins()
|
||||
require("packer").startup {
|
||||
{
|
||||
"wbthomason/packer.nvim",
|
||||
"kyazdani42/nvim-tree.lua",
|
||||
"kyazdani42/nvim-web-devicons",
|
||||
"nvim-tree/nvim-tree.lua",
|
||||
"nvim-tree/nvim-web-devicons",
|
||||
-- ADD PLUGINS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE
|
||||
},
|
||||
config = {
|
||||
|
||||
BIN
.github/screenshot.png
vendored
BIN
.github/screenshot.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 242 KiB |
BIN
.github/screenshot2.png
vendored
BIN
.github/screenshot2.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 615 KiB |
BIN
.github/screenshot3.png
vendored
BIN
.github/screenshot3.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 78 KiB |
BIN
.github/screenshot4.png
vendored
BIN
.github/screenshot4.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 565 KiB |
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -28,7 +28,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: JohnnyMorganz/stylua-action@1.0.0
|
||||
- uses: JohnnyMorganz/stylua-action@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: 0.15.1
|
||||
args: --color always --check lua/
|
||||
|
||||
19
.luarc.json
Normal file
19
.luarc.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json",
|
||||
"runtime.version" : "Lua 5.1",
|
||||
"diagnostics": {
|
||||
"globals": [
|
||||
"vim"
|
||||
],
|
||||
"disable": [
|
||||
"cast-local-type",
|
||||
"lowercase-global",
|
||||
"missing-parameter",
|
||||
"missing-return",
|
||||
"missing-return-value",
|
||||
"need-check-nil",
|
||||
"param-type-mismatch"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,13 +12,22 @@ luarocks install luacheck
|
||||
cargo install stylua
|
||||
```
|
||||
|
||||
You can setup the git hooks by running `scripts/setup-hooks.sh`.
|
||||
|
||||
## 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.
|
||||
Once you did, you should run the `scripts/update-help.sh`.
|
||||
|
||||
## 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.
|
||||
Once you did, you should run the `scripts/update-help.sh`.
|
||||
|
||||
Documentation for options should also be added, see how this is done after `nvim-tree.disable_netrw` in the `nvim-tree-lua.txt` file.
|
||||
|
||||
## Pull Request
|
||||
|
||||
Please reference any issues in the description e.g. "resolves #1234".
|
||||
|
||||
Please check "allow edits by maintainers" to allow nvim-tree developers to make small changes such as documentation tweaks.
|
||||
|
||||
439
README.md
439
README.md
@@ -1,392 +1,175 @@
|
||||
# A File Explorer For Neovim Written In Lua
|
||||
|
||||
[](https://github.com/kyazdani42/nvim-tree.lua/actions/workflows/ci.yml)
|
||||
[](https://github.com/nvim-tree/nvim-tree.lua/actions/workflows/ci.yml)
|
||||
|
||||
## Notice
|
||||
<img align="left" width="149" height="484" src="https://user-images.githubusercontent.com/17254073/195207026-f3434ba1-dc86-4c48-8ab3-b2efc3b85227.png">
|
||||
<img align="left" width="149" height="484" src="https://user-images.githubusercontent.com/17254073/195207023-7b709e35-7f10-416b-aafb-5bb61268c7d3.png">
|
||||
|
||||
This plugin requires [neovim >=0.6.0](https://github.com/neovim/neovim/wiki/Installing-Neovim).
|
||||
Automatic updates
|
||||
|
||||
If you have issues since the recent setup migration, check out [this guide](https://github.com/kyazdani42/nvim-tree.lua/issues/674)
|
||||
File type icons
|
||||
|
||||
Git integration
|
||||
|
||||
Diagnostics integration: LSP and COC
|
||||
|
||||
(Live) filtering
|
||||
|
||||
Cut, copy, paste, rename, delete, create
|
||||
|
||||
Highly customisable
|
||||
|
||||
<br clear="left"/>
|
||||
<br />
|
||||
|
||||
Take a look at the [wiki](https://github.com/nvim-tree/nvim-tree.lua/wiki) for Showcases, Tips, Recipes and more.
|
||||
|
||||
[Join us on matrix](https://matrix.to/#/#nvim-tree:matrix.org)
|
||||
|
||||
## Requirements
|
||||
|
||||
[neovim >=0.7.0](https://github.com/neovim/neovim/wiki/Installing-Neovim)
|
||||
|
||||
[nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons) is optional and used to display file icons. It requires a [patched font](https://www.nerdfonts.com/). Your terminal emulator must be configured to use that font, usually "Hack Nerd Font"
|
||||
|
||||
## 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'
|
||||
Plug 'nvim-tree/nvim-web-devicons' " optional, for file icons
|
||||
Plug 'nvim-tree/nvim-tree.lua'
|
||||
```
|
||||
|
||||
Install with [packer](https://github.com/wbthomason/packer.nvim):
|
||||
or 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)
|
||||
'nvim-tree/nvim-tree.lua',
|
||||
requires = {
|
||||
'nvim-tree/nvim-web-devicons', -- optional, for file icons
|
||||
},
|
||||
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
|
||||
```
|
||||
Setup should be run in a lua file or in a lua heredoc [:help lua-heredoc](https://neovim.io/doc/user/lua.html) if using in a vim file.
|
||||
|
||||
```lua
|
||||
-- init.lua
|
||||
-- examples for your init.lua
|
||||
|
||||
-- empty setup using defaults: add your own options
|
||||
require'nvim-tree'.setup {
|
||||
}
|
||||
-- disable netrw at the very start of your init.lua (strongly advised)
|
||||
vim.g.loaded_netrw = 1
|
||||
vim.g.loaded_netrwPlugin = 1
|
||||
|
||||
-- OR
|
||||
-- set termguicolors to enable highlight groups
|
||||
vim.opt.termguicolors = true
|
||||
|
||||
-- setup with all defaults
|
||||
-- each of these are documented in `:help nvim-tree.OPTION_NAME`
|
||||
-- nested options are documented by accessing them with `.` (eg: `:help nvim-tree.view.mappings.list`).
|
||||
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,
|
||||
-- empty setup using defaults
|
||||
require("nvim-tree").setup()
|
||||
|
||||
-- OR setup with some options
|
||||
require("nvim-tree").setup({
|
||||
sort_by = "case_sensitive",
|
||||
view = {
|
||||
width = 30,
|
||||
height = 30,
|
||||
hide_root_folder = false,
|
||||
side = "left",
|
||||
preserve_window_proportions = false,
|
||||
number = false,
|
||||
relativenumber = false,
|
||||
signcolumn = "yes",
|
||||
adaptive_size = true,
|
||||
mappings = {
|
||||
custom_only = false,
|
||||
list = {
|
||||
-- user mappings go here
|
||||
{ key = "u", action = "dir_up" },
|
||||
},
|
||||
},
|
||||
},
|
||||
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 = "",
|
||||
},
|
||||
group_empty = true,
|
||||
},
|
||||
filters = {
|
||||
dotfiles = false,
|
||||
custom = {},
|
||||
exclude = {},
|
||||
dotfiles = true,
|
||||
},
|
||||
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
|
||||
For complete list of available configuration options see [:help nvim-tree-setup](doc/nvim-tree-lua.txt)
|
||||
|
||||
### Default actions
|
||||
Each option is documented in `:help nvim-tree.OPTION_NAME`. Nested options can be accessed by appending `.`, for example [:help nvim-tree.view.mappings](doc/nvim-tree-lua.txt)
|
||||
|
||||
- `<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
|
||||
## Commands
|
||||
|
||||
### Settings
|
||||
See [:help nvim-tree-commands](doc/nvim-tree-lua.txt)
|
||||
|
||||
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
|
||||
Basic commands:
|
||||
|
||||
local tree_cb = require'nvim-tree.config'.nvim_tree_callback
|
||||
`:NvimTreeToggle` Open or close the tree. Takes an optional path argument.
|
||||
|
||||
local function print_node_path(node) {
|
||||
print(node.absolute_path)
|
||||
}
|
||||
`:NvimTreeFocus` Open the tree if it is closed, and then focus on the tree.
|
||||
|
||||
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
|
||||
}
|
||||
```
|
||||
`:NvimTreeFindFile` Move the cursor in the tree for the current buffer, opening folders if needed.
|
||||
|
||||
These are the default bindings:
|
||||
```lua
|
||||
`:NvimTreeCollapse` Collapses the nvim-tree recursively.
|
||||
|
||||
-- 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" }
|
||||
}
|
||||
```
|
||||
## Mappings
|
||||
|
||||
You can toggle the help UI by pressing `g?`.
|
||||
nvim-tree comes with number of mappings; for default mappings please see [:help nvim-tree-default-mappings](doc/nvim-tree-lua.txt), for way of configuring mappings see [:help nvim-tree-mappings](doc/nvim-tree-lua.txt)
|
||||
|
||||
## Tips & reminders
|
||||
`g?` toggles help, showing all the mappings and their actions.
|
||||
|
||||
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
|
||||
```
|
||||
## Roadmap
|
||||
|
||||
## Diagnostic Logging
|
||||
nvim-tree is stable and new major features will not be added. The focus is on existing user experience.
|
||||
|
||||
You may enable diagnostic logging to `$XDG_CACHE_HOME/nvim/nvim-tree.log`. See `:help nvim-tree.log`.
|
||||
Users are encouraged to add their own custom features via the public [API](#api).
|
||||
|
||||
## Performance Issues
|
||||
Development is focused on:
|
||||
* Bug fixes
|
||||
* Performance
|
||||
* Quality of Life improvements
|
||||
* API / Events
|
||||
* Enhancements to existing features
|
||||
|
||||
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.
|
||||
## API
|
||||
|
||||
```lua
|
||||
log = {
|
||||
enable = true,
|
||||
truncate = true,
|
||||
types = {
|
||||
git = true,
|
||||
profile = true,
|
||||
},
|
||||
},
|
||||
```
|
||||
nvim-tree exposes a public API. This is non breaking, with additions made as necessary.
|
||||
|
||||
Please attach `$XDG_CACHE_HOME/nvim/nvim-tree.log` if you raise an issue.
|
||||
Please raise a [feature request](https://github.com/nvim-tree/nvim-tree.lua/issues/new?assignees=&labels=feature+request&template=feature_request.md&title=) if the API is insufficent for your needs. [Contributions](#Contributing) are always welcome.
|
||||
|
||||
*Performance Tips:*
|
||||
[:help nvim-tree-api](doc/nvim-tree-lua.txt)
|
||||
|
||||
* 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.
|
||||
### Events
|
||||
|
||||
* Try manually running the git command (see the logs) in your shell e.g. `git --no-optional-locks status --porcelain=v1 --ignored=matching -u`.
|
||||
Users may subscribe to events that nvim-tree will dispatch in a variety of situations.
|
||||
|
||||
* 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.
|
||||
[:help nvim-tree-events](doc/nvim-tree-lua.txt)
|
||||
|
||||
* Try temporarily disabling git integration by setting `git.enable = false` in your setup.
|
||||
### Actions
|
||||
|
||||
Custom actions may be mapped which can invoke API or perform your own actions.
|
||||
|
||||
[:help nvim-tree-mappings](doc/nvim-tree-lua.txt)
|
||||
|
||||
## Contributing
|
||||
|
||||
PRs are always welcome. See [wiki](https://github.com/nvim-tree/nvim-tree.lua/wiki/Development) to get started.
|
||||
|
||||
See [bug](https://github.com/nvim-tree/nvim-tree.lua/issues?q=is%3Aissue+is%3Aopen+label%3Abug) and [PR Please](https://github.com/nvim-tree/nvim-tree.lua/issues?q=is%3Aopen+is%3Aissue+label%3A%22PR+please%22) issues if you are looking for some work to get you started.
|
||||
|
||||
### Help Wanted
|
||||
|
||||
Developers with the following environments:
|
||||
|
||||
* Apple macOS
|
||||
* Windows
|
||||
* WSL
|
||||
* msys
|
||||
* powershell
|
||||
|
||||
Help triaging, diagnosing and fixing issues specific to those environments is needed, as the nvim-tree developers do not have access to or expertise in these environments.
|
||||
|
||||
Let us know you're interested by commenting on issues and raising PRs.
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
See [Showcases](https://github.com/nvim-tree/nvim-tree.lua/wiki/Showcases) wiki page for examples of user's configurations with sources.
|
||||
|
||||
Please add your own!
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,36 +1,82 @@
|
||||
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 change_dir = require "nvim-tree.actions.root.change-dir"
|
||||
local legacy = require "nvim-tree.legacy"
|
||||
local core = require "nvim-tree.core"
|
||||
local reloaders = require "nvim-tree.actions.reloaders.reloaders"
|
||||
local copy_paste = require "nvim-tree.actions.fs.copy-paste"
|
||||
local collapse_all = require "nvim-tree.actions.tree-modifiers.collapse-all"
|
||||
local git = require "nvim-tree.git"
|
||||
local filters = require "nvim-tree.explorer.filters"
|
||||
|
||||
local _config = {}
|
||||
|
||||
local M = {}
|
||||
local M = {
|
||||
setup_called = false,
|
||||
init_root = "",
|
||||
}
|
||||
|
||||
function M.focus()
|
||||
M.open()
|
||||
view.focus()
|
||||
end
|
||||
|
||||
---@deprecated
|
||||
M.on_keypress = require("nvim-tree.actions").on_keypress
|
||||
function M.change_root(filepath, bufnr)
|
||||
-- skip if current file is in ignore_list
|
||||
local ft = vim.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
|
||||
|
||||
function M.toggle(find_file, no_focus)
|
||||
local cwd = core.get_cwd()
|
||||
local vim_cwd = vim.fn.getcwd()
|
||||
|
||||
-- test if in vim_cwd
|
||||
if utils.path_relative(filepath, vim_cwd) ~= filepath then
|
||||
if vim_cwd ~= cwd then
|
||||
change_dir.fn(vim_cwd)
|
||||
end
|
||||
return
|
||||
end
|
||||
-- test if in cwd
|
||||
if utils.path_relative(filepath, cwd) ~= filepath then
|
||||
return
|
||||
end
|
||||
|
||||
-- otherwise test M.init_root
|
||||
if _config.prefer_startup_root and utils.path_relative(filepath, M.init_root) ~= filepath then
|
||||
change_dir.fn(M.init_root)
|
||||
return
|
||||
end
|
||||
-- otherwise root_dirs
|
||||
for _, dir in pairs(_config.root_dirs) do
|
||||
dir = vim.fn.fnamemodify(dir, ":p")
|
||||
if utils.path_relative(filepath, dir) ~= filepath then
|
||||
change_dir.fn(dir)
|
||||
return
|
||||
end
|
||||
end
|
||||
-- finally fall back to the folder containing the file
|
||||
change_dir.fn(vim.fn.fnamemodify(filepath, ":p:h"))
|
||||
end
|
||||
|
||||
---@deprecated
|
||||
M.on_keypress = require("nvim-tree.actions.dispatch").dispatch
|
||||
|
||||
function M.toggle(find_file, no_focus, cwd, bang)
|
||||
if view.is_visible() then
|
||||
view.close()
|
||||
else
|
||||
local previous_buf = api.nvim_get_current_buf()
|
||||
M.open()
|
||||
local previous_buf = vim.api.nvim_get_current_buf()
|
||||
M.open(cwd)
|
||||
if _config.update_focused_file.enable or find_file then
|
||||
M.find_file(false, previous_buf)
|
||||
M.find_file(false, previous_buf, bang)
|
||||
end
|
||||
if no_focus then
|
||||
vim.cmd "noautocmd wincmd p"
|
||||
@@ -48,31 +94,37 @@ function M.open(cwd)
|
||||
end
|
||||
end
|
||||
|
||||
function M.open_replacing_current_buffer()
|
||||
function M.open_replacing_current_buffer(cwd)
|
||||
if view.is_visible() then
|
||||
return
|
||||
end
|
||||
|
||||
local buf = api.nvim_get_current_buf()
|
||||
local bufname = api.nvim_buf_get_name(buf)
|
||||
local buf = vim.api.nvim_get_current_buf()
|
||||
local bufname = vim.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 cwd == "" or cwd == nil then
|
||||
cwd = vim.fn.fnamemodify(bufname, ":p:h")
|
||||
end
|
||||
|
||||
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)
|
||||
require("nvim-tree.actions.finders.find-file").fn(bufname)
|
||||
end
|
||||
|
||||
function M.tab_change()
|
||||
function M.tab_enter()
|
||||
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
|
||||
local ft = vim.api.nvim_buf_get_option(0, "ft")
|
||||
for _, filter in ipairs(M.config.tab.sync.ignore) do
|
||||
if bufname:match(filter) ~= nil or ft:match(filter) ~= nil then
|
||||
return
|
||||
end
|
||||
end
|
||||
view.open { focus_tree = false }
|
||||
require("nvim-tree.renderer").draw()
|
||||
@@ -81,36 +133,26 @@ 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())
|
||||
local buf = vim.api.nvim_win_get_buf(win)
|
||||
return vim.api.nvim_buf_get_name(buf):match "NvimTree" ~= nil
|
||||
end, vim.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")
|
||||
local stat = vim.loop.fs_stat(fname)
|
||||
return stat and stat.type == "file" and vim.loop.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)
|
||||
function M.find_file(with_open, bufnr, bang)
|
||||
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)
|
||||
bufnr = bufnr or vim.api.nvim_get_current_buf()
|
||||
if not vim.api.nvim_buf_is_valid(bufnr) then
|
||||
return
|
||||
end
|
||||
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||
local filepath = utils.canonical_path(vim.fn.fnamemodify(bufname, ":p"))
|
||||
if not is_file_readable(filepath) then
|
||||
return
|
||||
@@ -120,12 +162,12 @@ function M.find_file(with_open, bufnr)
|
||||
M.open()
|
||||
end
|
||||
|
||||
-- if we don't schedule, it will search for NvimTree
|
||||
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)
|
||||
if bang or _config.update_focused_file.update_root then
|
||||
M.change_root(filepath, bufnr)
|
||||
end
|
||||
require("nvim-tree.actions.find-file").fn(filepath)
|
||||
require("nvim-tree.actions.finders.find-file").fn(filepath)
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -137,8 +179,8 @@ function M.open_on_directory()
|
||||
return
|
||||
end
|
||||
|
||||
local buf = api.nvim_get_current_buf()
|
||||
local bufname = api.nvim_buf_get_name(buf)
|
||||
local buf = vim.api.nvim_get_current_buf()
|
||||
local bufname = vim.api.nvim_buf_get_name(buf)
|
||||
if vim.fn.isdirectory(bufname) ~= 1 then
|
||||
return
|
||||
end
|
||||
@@ -148,6 +190,7 @@ end
|
||||
|
||||
function M.reset_highlight()
|
||||
colors.setup()
|
||||
view.reset_winhl()
|
||||
renderer.render_hl(view.get_bufnr())
|
||||
end
|
||||
|
||||
@@ -164,32 +207,32 @@ function M.place_cursor_on_node()
|
||||
return
|
||||
end
|
||||
|
||||
local line = api.nvim_get_current_line()
|
||||
local cursor = api.nvim_win_get_cursor(0)
|
||||
local line = vim.api.nvim_get_current_line()
|
||||
local cursor = vim.api.nvim_win_get_cursor(0)
|
||||
local idx = vim.fn.stridx(line, node.name)
|
||||
|
||||
if idx >= 0 then
|
||||
api.nvim_win_set_cursor(0, { cursor[1], idx })
|
||||
vim.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 bufnr = vim.api.nvim_get_current_buf()
|
||||
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||
local buftype = vim.api.nvim_buf_get_option(bufnr, "filetype")
|
||||
local ft_ignore = _config.ignore_ft_on_setup
|
||||
|
||||
local stats = luv.fs_stat(bufname)
|
||||
local stats = vim.loop.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)
|
||||
cwd = vim.fn.expand(vim.fn.fnameescape(bufname))
|
||||
-- INFO: could potentially conflict with rooter plugins
|
||||
vim.cmd("noautocmd cd " .. cwd)
|
||||
vim.cmd("noautocmd cd " .. vim.fn.fnameescape(cwd))
|
||||
end
|
||||
|
||||
local lines = not is_dir and api.nvim_buf_get_lines(bufnr, 0, -1, false) or {}
|
||||
local lines = not is_dir and vim.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
|
||||
@@ -220,7 +263,7 @@ function M.on_enter(netrw_disabled)
|
||||
-- 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])
|
||||
vim.api.nvim_set_current_win(existing_tree_wins[1])
|
||||
end
|
||||
|
||||
if should_open or should_hijack or existing_tree_wins[1] ~= nil then
|
||||
@@ -252,19 +295,29 @@ local function manage_netrw(disable_netrw, hijack_netrw)
|
||||
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)
|
||||
]]
|
||||
vim.api.nvim_create_user_command("NvimTreeOpen", function(res)
|
||||
M.open(res.args)
|
||||
end, { nargs = "?", complete = "dir" })
|
||||
vim.api.nvim_create_user_command("NvimTreeClose", view.close, { bar = true })
|
||||
vim.api.nvim_create_user_command("NvimTreeToggle", function(res)
|
||||
M.toggle(false, false, res.args)
|
||||
end, { nargs = "?", complete = "dir" })
|
||||
vim.api.nvim_create_user_command("NvimTreeFocus", M.focus, { bar = true })
|
||||
vim.api.nvim_create_user_command("NvimTreeRefresh", reloaders.reload_explorer, { bar = true })
|
||||
vim.api.nvim_create_user_command("NvimTreeClipboard", copy_paste.print_clipboard, { bar = true })
|
||||
vim.api.nvim_create_user_command("NvimTreeFindFile", function(res)
|
||||
M.find_file(true, nil, res.bang)
|
||||
end, { bang = true, bar = true })
|
||||
vim.api.nvim_create_user_command("NvimTreeFindFileToggle", function(res)
|
||||
M.toggle(true, false, res.args, res.bang)
|
||||
end, { bang = true, nargs = "?", complete = "dir" })
|
||||
vim.api.nvim_create_user_command("NvimTreeResize", function(res)
|
||||
M.resize(res.args)
|
||||
end, { nargs = 1, bar = true })
|
||||
vim.api.nvim_create_user_command("NvimTreeCollapse", collapse_all.fn, { bar = true })
|
||||
vim.api.nvim_create_user_command("NvimTreeCollapseKeepBuffers", function()
|
||||
collapse_all.fn(true)
|
||||
end, { bar = true })
|
||||
end
|
||||
|
||||
function M.change_dir(name)
|
||||
@@ -276,40 +329,136 @@ function M.change_dir(name)
|
||||
end
|
||||
|
||||
local function setup_autocommands(opts)
|
||||
vim.cmd "augroup NvimTree"
|
||||
vim.cmd "autocmd!"
|
||||
local augroup_id = vim.api.nvim_create_augroup("NvimTree", { clear = true })
|
||||
local function create_nvim_tree_autocmd(name, custom_opts)
|
||||
local default_opts = { group = augroup_id }
|
||||
vim.api.nvim_create_autocmd(name, vim.tbl_extend("force", default_opts, custom_opts))
|
||||
end
|
||||
|
||||
-- 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()"
|
||||
create_nvim_tree_autocmd("ColorScheme", { callback = M.reset_highlight })
|
||||
|
||||
if opts.open_on_tab then
|
||||
vim.cmd "au TabEnter * lua require'nvim-tree'.tab_change()"
|
||||
-- prevent new opened file from opening in the same window as nvim-tree
|
||||
create_nvim_tree_autocmd("BufWipeout", {
|
||||
pattern = "NvimTree_*",
|
||||
callback = function()
|
||||
if utils.is_nvim_tree_buf(0) then
|
||||
view._prevent_buffer_override()
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
local has_watchers = opts.filesystem_watchers.enable
|
||||
|
||||
if opts.auto_reload_on_write and not has_watchers then
|
||||
create_nvim_tree_autocmd("BufWritePost", { callback = reloaders.reload_explorer })
|
||||
end
|
||||
|
||||
create_nvim_tree_autocmd("BufReadPost", {
|
||||
callback = function()
|
||||
if filters.config.filter_no_buffer then
|
||||
reloaders.reload_explorer()
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
create_nvim_tree_autocmd("BufUnload", {
|
||||
callback = function(data)
|
||||
if filters.config.filter_no_buffer then
|
||||
reloaders.reload_explorer(nil, data.buf)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
if not has_watchers and opts.git.enable then
|
||||
create_nvim_tree_autocmd("User", {
|
||||
pattern = { "FugitiveChanged", "NeogitStatusRefreshed" },
|
||||
callback = reloaders.reload_git,
|
||||
})
|
||||
end
|
||||
|
||||
if opts.tab.sync.open then
|
||||
create_nvim_tree_autocmd("TabEnter", { callback = vim.schedule_wrap(M.tab_enter) })
|
||||
end
|
||||
if opts.hijack_cursor then
|
||||
vim.cmd "au CursorMoved NvimTree_* lua require'nvim-tree'.place_cursor_on_node()"
|
||||
create_nvim_tree_autocmd("CursorMoved", {
|
||||
pattern = "NvimTree_*",
|
||||
callback = function()
|
||||
if utils.is_nvim_tree_buf(0) then
|
||||
M.place_cursor_on_node()
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
if opts.update_cwd then
|
||||
vim.cmd "au DirChanged * lua require'nvim-tree'.change_dir(vim.loop.cwd())"
|
||||
if opts.sync_root_with_cwd then
|
||||
create_nvim_tree_autocmd("DirChanged", {
|
||||
callback = function()
|
||||
M.change_dir(vim.loop.cwd())
|
||||
end,
|
||||
})
|
||||
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()"
|
||||
create_nvim_tree_autocmd("BufEnter", {
|
||||
callback = function()
|
||||
M.find_file(false)
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
if opts.hijack_directories.enable then
|
||||
vim.cmd "au BufEnter,BufNewFile * lua require'nvim-tree'.open_on_directory()"
|
||||
create_nvim_tree_autocmd({ "BufEnter", "BufNewFile" }, { callback = M.open_on_directory })
|
||||
end
|
||||
|
||||
vim.cmd "augroup end"
|
||||
if opts.reload_on_bufenter and not has_watchers then
|
||||
create_nvim_tree_autocmd("BufEnter", {
|
||||
pattern = "NvimTree_*",
|
||||
callback = function()
|
||||
if utils.is_nvim_tree_buf(0) then
|
||||
reloaders.reload_explorer()
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
if opts.view.centralize_selection then
|
||||
create_nvim_tree_autocmd("BufEnter", {
|
||||
pattern = "NvimTree_*",
|
||||
callback = function()
|
||||
vim.schedule(function()
|
||||
vim.api.nvim_buf_call(0, function()
|
||||
vim.cmd [[norm! zz]]
|
||||
end)
|
||||
end)
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
if opts.diagnostics.enable then
|
||||
create_nvim_tree_autocmd("DiagnosticChanged", {
|
||||
callback = function()
|
||||
log.line("diagnostics", "DiagnosticChanged")
|
||||
require("nvim-tree.diagnostics").update()
|
||||
end,
|
||||
})
|
||||
create_nvim_tree_autocmd("User", {
|
||||
pattern = "CocDiagnosticChange",
|
||||
callback = function()
|
||||
log.line("diagnostics", "CocDiagnosticChange")
|
||||
require("nvim-tree.diagnostics").update()
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
if opts.view.float.enable and opts.view.float.quit_on_focus_loss then
|
||||
create_nvim_tree_autocmd("WinLeave", {
|
||||
pattern = "NvimTree_*",
|
||||
callback = function()
|
||||
if utils.is_nvim_tree_buf(0) then
|
||||
view.close()
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
|
||||
@@ -321,12 +470,19 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
|
||||
ignore_buffer_on_setup = false,
|
||||
open_on_setup = false,
|
||||
open_on_setup_file = false,
|
||||
open_on_tab = false,
|
||||
sort_by = "name",
|
||||
update_cwd = false,
|
||||
root_dirs = {},
|
||||
prefer_startup_root = false,
|
||||
sync_root_with_cwd = false,
|
||||
reload_on_bufenter = false,
|
||||
respect_buf_cwd = false,
|
||||
on_attach = "disable",
|
||||
remove_keymaps = false,
|
||||
select_prompts = false,
|
||||
view = {
|
||||
adaptive_size = false,
|
||||
centralize_selection = false,
|
||||
width = 30,
|
||||
height = 30,
|
||||
hide_root_folder = false,
|
||||
side = "left",
|
||||
preserve_window_proportions = false,
|
||||
@@ -339,20 +495,76 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
|
||||
-- user mappings go here
|
||||
},
|
||||
},
|
||||
float = {
|
||||
enable = false,
|
||||
quit_on_focus_loss = true,
|
||||
open_win_config = {
|
||||
relative = "editor",
|
||||
border = "rounded",
|
||||
width = 30,
|
||||
height = 30,
|
||||
row = 1,
|
||||
col = 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
renderer = {
|
||||
add_trailing = false,
|
||||
group_empty = false,
|
||||
highlight_git = false,
|
||||
full_name = false,
|
||||
highlight_opened_files = "none",
|
||||
root_folder_label = ":~:s?$?/..?",
|
||||
indent_width = 2,
|
||||
indent_markers = {
|
||||
enable = false,
|
||||
inline_arrows = true,
|
||||
icons = {
|
||||
corner = "└ ",
|
||||
edge = "│ ",
|
||||
none = " ",
|
||||
corner = "└",
|
||||
edge = "│",
|
||||
item = "│",
|
||||
bottom = "─",
|
||||
none = " ",
|
||||
},
|
||||
},
|
||||
icons = {
|
||||
webdev_colors = true,
|
||||
git_placement = "before",
|
||||
padding = " ",
|
||||
symlink_arrow = " ➛ ",
|
||||
show = {
|
||||
file = true,
|
||||
folder = true,
|
||||
folder_arrow = true,
|
||||
git = true,
|
||||
},
|
||||
glyphs = {
|
||||
default = "",
|
||||
symlink = "",
|
||||
bookmark = "",
|
||||
folder = {
|
||||
arrow_closed = "",
|
||||
arrow_open = "",
|
||||
default = "",
|
||||
open = "",
|
||||
empty = "",
|
||||
empty_open = "",
|
||||
symlink = "",
|
||||
symlink_open = "",
|
||||
},
|
||||
git = {
|
||||
unstaged = "✗",
|
||||
staged = "✓",
|
||||
unmerged = "",
|
||||
renamed = "➜",
|
||||
untracked = "★",
|
||||
deleted = "",
|
||||
ignored = "◌",
|
||||
},
|
||||
},
|
||||
},
|
||||
special_files = { "Cargo.toml", "Makefile", "README.md", "readme.md" },
|
||||
symlink_destination = true,
|
||||
},
|
||||
hijack_directories = {
|
||||
enable = true,
|
||||
@@ -360,7 +572,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
|
||||
},
|
||||
update_focused_file = {
|
||||
enable = false,
|
||||
update_cwd = false,
|
||||
update_root = false,
|
||||
ignore_list = {},
|
||||
},
|
||||
ignore_ft_on_setup = {},
|
||||
@@ -371,6 +583,12 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
|
||||
diagnostics = {
|
||||
enable = false,
|
||||
show_on_dirs = false,
|
||||
show_on_open_dirs = true,
|
||||
debounce_delay = 50,
|
||||
severity = {
|
||||
min = vim.diagnostic.severity.HINT,
|
||||
max = vim.diagnostic.severity.ERROR,
|
||||
},
|
||||
icons = {
|
||||
hint = "",
|
||||
info = "",
|
||||
@@ -380,12 +598,21 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
|
||||
},
|
||||
filters = {
|
||||
dotfiles = false,
|
||||
git_clean = false,
|
||||
no_buffer = false,
|
||||
custom = {},
|
||||
exclude = {},
|
||||
},
|
||||
filesystem_watchers = {
|
||||
enable = true,
|
||||
debounce_delay = 50,
|
||||
ignore_dirs = {},
|
||||
},
|
||||
git = {
|
||||
enable = true,
|
||||
ignore = true,
|
||||
show_on_dirs = true,
|
||||
show_on_open_dirs = true,
|
||||
timeout = 400,
|
||||
},
|
||||
actions = {
|
||||
@@ -395,9 +622,22 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
|
||||
global = false,
|
||||
restrict_above_cwd = false,
|
||||
},
|
||||
expand_all = {
|
||||
max_folder_discovery = 300,
|
||||
exclude = {},
|
||||
},
|
||||
file_popup = {
|
||||
open_win_config = {
|
||||
col = 1,
|
||||
row = 1,
|
||||
relative = "cursor",
|
||||
border = "shadow",
|
||||
style = "minimal",
|
||||
},
|
||||
},
|
||||
open_file = {
|
||||
quit_on_open = false,
|
||||
resize_window = false,
|
||||
resize_window = true,
|
||||
window_picker = {
|
||||
enable = true,
|
||||
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
|
||||
@@ -407,11 +647,28 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
|
||||
},
|
||||
},
|
||||
},
|
||||
remove_file = {
|
||||
close_window = true,
|
||||
},
|
||||
},
|
||||
trash = {
|
||||
cmd = "trash",
|
||||
cmd = "gio trash",
|
||||
require_confirm = true,
|
||||
},
|
||||
live_filter = {
|
||||
prefix = "[FILTER]: ",
|
||||
always_show_folders = true,
|
||||
},
|
||||
tab = {
|
||||
sync = {
|
||||
open = false,
|
||||
close = false,
|
||||
ignore = {},
|
||||
},
|
||||
},
|
||||
notify = {
|
||||
threshold = vim.log.levels.INFO,
|
||||
},
|
||||
log = {
|
||||
enable = false,
|
||||
truncate = false,
|
||||
@@ -419,9 +676,11 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
|
||||
all = false,
|
||||
config = false,
|
||||
copy_paste = false,
|
||||
dev = false,
|
||||
diagnostics = false,
|
||||
git = false,
|
||||
profile = false,
|
||||
watcher = false,
|
||||
},
|
||||
},
|
||||
} -- END_DEFAULT_OPTS
|
||||
@@ -430,9 +689,16 @@ local function merge_options(conf)
|
||||
return vim.tbl_deep_extend("force", DEFAULT_OPTS, conf or {})
|
||||
end
|
||||
|
||||
local FIELD_SKIP_VALIDATE = {
|
||||
open_win_config = true,
|
||||
}
|
||||
|
||||
local FIELD_OVERRIDE_TYPECHECK = {
|
||||
width = { string = true, ["function"] = true, number = true },
|
||||
height = { string = true, ["function"] = true, number = true },
|
||||
remove_keymaps = { boolean = true, table = true },
|
||||
on_attach = { ["function"] = true, string = true },
|
||||
sort_by = { ["function"] = true, string = true },
|
||||
root_folder_label = { ["function"] = true, string = true },
|
||||
}
|
||||
|
||||
local function validate_options(conf)
|
||||
@@ -445,25 +711,28 @@ local function validate_options(conf)
|
||||
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)
|
||||
if not FIELD_SKIP_VALIDATE[k] then
|
||||
local invalid
|
||||
local override_typecheck = FIELD_OVERRIDE_TYPECHECK[k] or {}
|
||||
if def[k] == nil then
|
||||
-- option does not exist
|
||||
invalid = string.format("[NvimTree] 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("[NvimTree] 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
|
||||
user[k] = nil
|
||||
else
|
||||
validate(v, def[k], prefix .. k .. ".")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -471,11 +740,18 @@ local function validate_options(conf)
|
||||
validate(conf, DEFAULT_OPTS, "")
|
||||
|
||||
if msg then
|
||||
utils.warn(msg)
|
||||
vim.notify_once(msg .. " | see :help nvim-tree-setup for available configuration options", vim.log.levels.WARN)
|
||||
end
|
||||
end
|
||||
|
||||
function M.setup(conf)
|
||||
if vim.fn.has "nvim-0.7" == 0 then
|
||||
vim.notify_once("nvim-tree.lua requires Neovim 0.7 or higher", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
|
||||
M.init_root = vim.fn.getcwd()
|
||||
|
||||
legacy.migrate_legacy_options(conf or {})
|
||||
|
||||
validate_options(conf)
|
||||
@@ -483,6 +759,8 @@ function M.setup(conf)
|
||||
local opts = merge_options(conf)
|
||||
local netrw_disabled = opts.disable_netrw or opts.hijack_netrw
|
||||
|
||||
_config.root_dirs = opts.root_dirs
|
||||
_config.prefer_startup_root = opts.prefer_startup_root
|
||||
_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
|
||||
@@ -494,12 +772,14 @@ function M.setup(conf)
|
||||
manage_netrw(opts.disable_netrw, opts.hijack_netrw)
|
||||
|
||||
M.config = opts
|
||||
require("nvim-tree.notify").setup(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.keymap").setup(opts)
|
||||
require("nvim-tree.colors").setup()
|
||||
require("nvim-tree.diagnostics").setup(opts)
|
||||
require("nvim-tree.explorer").setup(opts)
|
||||
@@ -507,10 +787,30 @@ function M.setup(conf)
|
||||
require("nvim-tree.view").setup(opts)
|
||||
require("nvim-tree.lib").setup(opts)
|
||||
require("nvim-tree.renderer").setup(opts)
|
||||
require("nvim-tree.live-filter").setup(opts)
|
||||
require("nvim-tree.marks").setup(opts)
|
||||
if M.config.renderer.icons.show.file and pcall(require, "nvim-web-devicons") then
|
||||
require("nvim-web-devicons").setup()
|
||||
end
|
||||
|
||||
setup_vim_commands()
|
||||
setup_autocommands(opts)
|
||||
|
||||
if not M.setup_called then
|
||||
-- first call to setup
|
||||
setup_vim_commands()
|
||||
else
|
||||
-- subsequent calls to setup
|
||||
require("nvim-tree.watcher").purge_watchers()
|
||||
view.close_all_tabs()
|
||||
view.abandon_all_windows()
|
||||
if core.get_explorer() ~= nil then
|
||||
git.purge_state()
|
||||
TreeExplorer = nil
|
||||
end
|
||||
end
|
||||
|
||||
M.setup_called = true
|
||||
|
||||
vim.schedule(function()
|
||||
M.on_enter(netrw_disabled)
|
||||
end)
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
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
|
||||
@@ -1,43 +0,0 @@
|
||||
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
|
||||
131
lua/nvim-tree/actions/dispatch.lua
Normal file
131
lua/nvim-tree/actions/dispatch.lua
Normal file
@@ -0,0 +1,131 @@
|
||||
local view = require "nvim-tree.view"
|
||||
local lib = require "nvim-tree.lib"
|
||||
|
||||
local M = {}
|
||||
|
||||
local Actions = {
|
||||
close = view.close,
|
||||
|
||||
-- Tree modifiers
|
||||
collapse_all = require("nvim-tree.actions.tree-modifiers.collapse-all").fn,
|
||||
expand_all = require("nvim-tree.actions.tree-modifiers.expand-all").fn,
|
||||
toggle_dotfiles = require("nvim-tree.actions.tree-modifiers.toggles").dotfiles,
|
||||
toggle_custom = require("nvim-tree.actions.tree-modifiers.toggles").custom,
|
||||
toggle_git_ignored = require("nvim-tree.actions.tree-modifiers.toggles").git_ignored,
|
||||
toggle_git_clean = require("nvim-tree.actions.tree-modifiers.toggles").git_clean,
|
||||
toggle_no_buffer = require("nvim-tree.actions.tree-modifiers.toggles").no_buffer,
|
||||
|
||||
-- Filesystem operations
|
||||
copy_absolute_path = require("nvim-tree.actions.fs.copy-paste").copy_absolute_path,
|
||||
copy_name = require("nvim-tree.actions.fs.copy-paste").copy_filename,
|
||||
copy_path = require("nvim-tree.actions.fs.copy-paste").copy_path,
|
||||
copy = require("nvim-tree.actions.fs.copy-paste").copy,
|
||||
create = require("nvim-tree.actions.fs.create-file").fn,
|
||||
cut = require("nvim-tree.actions.fs.copy-paste").cut,
|
||||
full_rename = require("nvim-tree.actions.fs.rename-file").fn ":p",
|
||||
paste = require("nvim-tree.actions.fs.copy-paste").paste,
|
||||
trash = require("nvim-tree.actions.fs.trash").fn,
|
||||
remove = require("nvim-tree.actions.fs.remove-file").fn,
|
||||
rename = require("nvim-tree.actions.fs.rename-file").fn ":t",
|
||||
rename_basename = require("nvim-tree.actions.fs.rename-file").fn ":t:r",
|
||||
|
||||
-- Movements in tree
|
||||
close_node = require("nvim-tree.actions.moves.parent").fn(true),
|
||||
first_sibling = require("nvim-tree.actions.moves.sibling").fn "first",
|
||||
last_sibling = require("nvim-tree.actions.moves.sibling").fn "last",
|
||||
next_diag_item = require("nvim-tree.actions.moves.item").fn("next", "diag"),
|
||||
next_git_item = require("nvim-tree.actions.moves.item").fn("next", "git"),
|
||||
next_sibling = require("nvim-tree.actions.moves.sibling").fn "next",
|
||||
parent_node = require("nvim-tree.actions.moves.parent").fn(false),
|
||||
prev_diag_item = require("nvim-tree.actions.moves.item").fn("prev", "diag"),
|
||||
prev_git_item = require("nvim-tree.actions.moves.item").fn("prev", "git"),
|
||||
prev_sibling = require("nvim-tree.actions.moves.sibling").fn "prev",
|
||||
|
||||
-- Other types
|
||||
refresh = require("nvim-tree.actions.reloaders.reloaders").reload_explorer,
|
||||
dir_up = require("nvim-tree.actions.root.dir-up").fn,
|
||||
search_node = require("nvim-tree.actions.finders.search-node").fn,
|
||||
run_file_command = require("nvim-tree.actions.node.run-command").run_file_command,
|
||||
toggle_file_info = require("nvim-tree.actions.node.file-popup").toggle_file_info,
|
||||
system_open = require("nvim-tree.actions.node.system-open").fn,
|
||||
toggle_mark = require("nvim-tree.marks").toggle_mark,
|
||||
bulk_move = require("nvim-tree.marks.bulk-move").bulk_move,
|
||||
}
|
||||
|
||||
local function handle_action_on_help_ui(action)
|
||||
if action == "close" or action == "toggle_help" then
|
||||
require("nvim-tree.actions.tree-modifiers.toggles").help()
|
||||
end
|
||||
end
|
||||
|
||||
local function handle_filter_actions(action)
|
||||
if action == "live_filter" then
|
||||
require("nvim-tree.live-filter").start_filtering()
|
||||
elseif action == "clear_live_filter" then
|
||||
require("nvim-tree.live-filter").clear_filter()
|
||||
end
|
||||
end
|
||||
|
||||
local function change_dir_action(node)
|
||||
if node.name == ".." then
|
||||
require("nvim-tree.actions.root.change-dir").fn ".."
|
||||
elseif node.nodes ~= nil then
|
||||
require("nvim-tree.actions.root.change-dir").fn(lib.get_last_group_node(node).absolute_path)
|
||||
end
|
||||
end
|
||||
|
||||
local function open_file(action, node)
|
||||
local path = node.absolute_path
|
||||
if node.link_to and not node.nodes then
|
||||
path = node.link_to
|
||||
end
|
||||
require("nvim-tree.actions.node.open-file").fn(action, path)
|
||||
end
|
||||
|
||||
local function handle_tree_actions(action)
|
||||
local node = lib.get_node_at_cursor()
|
||||
if not node then
|
||||
return
|
||||
end
|
||||
|
||||
local custom_function = M.custom_keypress_funcs[action]
|
||||
local defined_action = Actions[action]
|
||||
|
||||
if type(custom_function) == "function" then
|
||||
return custom_function(node)
|
||||
elseif defined_action then
|
||||
return defined_action(node)
|
||||
end
|
||||
|
||||
local is_parent = node.name == ".."
|
||||
|
||||
if action == "preview" and is_parent then
|
||||
return
|
||||
end
|
||||
|
||||
if action == "cd" or is_parent then
|
||||
return change_dir_action(node)
|
||||
end
|
||||
|
||||
if node.nodes then
|
||||
lib.expand_or_collapse(node)
|
||||
else
|
||||
open_file(action, node)
|
||||
end
|
||||
end
|
||||
|
||||
function M.dispatch(action)
|
||||
if view.is_help_ui() or action == "toggle_help" then
|
||||
handle_action_on_help_ui(action)
|
||||
elseif action == "live_filter" or action == "clear_live_filter" then
|
||||
handle_filter_actions(action)
|
||||
else
|
||||
handle_tree_actions(action)
|
||||
end
|
||||
end
|
||||
|
||||
function M.setup(custom_keypress_funcs)
|
||||
M.custom_keypress_funcs = custom_keypress_funcs
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -1,77 +0,0 @@
|
||||
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
|
||||
77
lua/nvim-tree/actions/finders/find-file.lua
Normal file
77
lua/nvim-tree/actions/finders/find-file.lua
Normal file
@@ -0,0 +1,77 @@
|
||||
local log = require "nvim-tree.log"
|
||||
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 reload = require "nvim-tree.explorer.reload"
|
||||
local Iterator = require "nvim-tree.iterators.node-iterator"
|
||||
|
||||
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 not core.get_explorer() then
|
||||
return
|
||||
end
|
||||
|
||||
-- always match against the real path
|
||||
local fname_real = vim.loop.fs_realpath(fname)
|
||||
if not fname_real then
|
||||
return
|
||||
end
|
||||
|
||||
if running[fname_real] then
|
||||
return
|
||||
end
|
||||
running[fname_real] = true
|
||||
|
||||
local ps = log.profile_start("find file %s", fname_real)
|
||||
|
||||
-- we cannot wait for watchers
|
||||
reload.refresh_nodes_for_path(vim.fn.fnamemodify(fname_real, ":h"))
|
||||
|
||||
local line = core.get_nodes_starting_line()
|
||||
|
||||
local absolute_paths_searched = {}
|
||||
|
||||
local found = Iterator.builder(core.get_explorer().nodes)
|
||||
:matcher(function(node)
|
||||
return node.absolute_path == fname_real or node.link_to == fname_real
|
||||
end)
|
||||
:applier(function(node)
|
||||
line = line + 1
|
||||
|
||||
if vim.tbl_contains(absolute_paths_searched, node.absolute_path) then
|
||||
return
|
||||
end
|
||||
table.insert(absolute_paths_searched, node.absolute_path)
|
||||
|
||||
local abs_match = vim.startswith(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)
|
||||
|
||||
if abs_match or link_match then
|
||||
node.open = true
|
||||
if #node.nodes == 0 then
|
||||
core.get_explorer():expand(node)
|
||||
end
|
||||
end
|
||||
end)
|
||||
:recursor(function(node)
|
||||
return node.open and node.nodes
|
||||
end)
|
||||
:iterate()
|
||||
|
||||
if found and view.is_visible() then
|
||||
renderer.draw()
|
||||
view.set_cursor { line, 0 }
|
||||
end
|
||||
|
||||
running[fname_real] = false
|
||||
|
||||
log.profile_end(ps, "find file %s", fname_real)
|
||||
end
|
||||
|
||||
return M
|
||||
91
lua/nvim-tree/actions/finders/search-node.lua
Normal file
91
lua/nvim-tree/actions/finders/search-node.lua
Normal file
@@ -0,0 +1,91 @@
|
||||
local core = require "nvim-tree.core"
|
||||
local filters = require "nvim-tree.explorer.filters"
|
||||
local find_file = require("nvim-tree.actions.finders.find-file").fn
|
||||
|
||||
local M = {}
|
||||
|
||||
local function search(search_dir, input_path)
|
||||
local realpaths_searched = {}
|
||||
|
||||
if not search_dir then
|
||||
return
|
||||
end
|
||||
|
||||
local function iter(dir)
|
||||
local realpath, path, name, stat, handle, _
|
||||
|
||||
local filter_status = filters.prepare()
|
||||
|
||||
handle, _ = vim.loop.fs_scandir(dir)
|
||||
if not handle then
|
||||
return
|
||||
end
|
||||
|
||||
realpath, _ = vim.loop.fs_realpath(dir)
|
||||
if not realpath or vim.tbl_contains(realpaths_searched, realpath) then
|
||||
return
|
||||
end
|
||||
table.insert(realpaths_searched, realpath)
|
||||
|
||||
name, _ = vim.loop.fs_scandir_next(handle)
|
||||
while name do
|
||||
path = dir .. "/" .. name
|
||||
|
||||
stat, _ = vim.loop.fs_stat(path)
|
||||
if not stat then
|
||||
break
|
||||
end
|
||||
|
||||
if not filters.should_filter(path, filter_status) then
|
||||
if string.find(path, "/" .. input_path .. "$") then
|
||||
return path
|
||||
end
|
||||
|
||||
if stat.type == "directory" then
|
||||
path = iter(path)
|
||||
if path then
|
||||
return path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
name, _ = vim.loop.fs_scandir_next(handle)
|
||||
end
|
||||
end
|
||||
|
||||
return iter(search_dir)
|
||||
end
|
||||
|
||||
function M.fn()
|
||||
if not core.get_explorer() then
|
||||
return
|
||||
end
|
||||
|
||||
-- temporarily set &path
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
local path_existed, path_opt = pcall(vim.api.nvim_buf_get_option, bufnr, "path")
|
||||
vim.api.nvim_buf_set_option(bufnr, "path", core.get_cwd() .. "/**")
|
||||
|
||||
vim.ui.input({ prompt = "Search: ", completion = "file_in_path" }, function(input_path)
|
||||
if not input_path or input_path == "" then
|
||||
return
|
||||
end
|
||||
-- reset &path
|
||||
if path_existed then
|
||||
vim.api.nvim_buf_set_option(bufnr, "path", path_opt)
|
||||
else
|
||||
vim.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)
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -1,10 +1,9 @@
|
||||
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 events = require "nvim-tree.events"
|
||||
local notify = require "nvim-tree.notify"
|
||||
|
||||
local M = {}
|
||||
|
||||
@@ -17,7 +16,7 @@ local function do_copy(source, destination)
|
||||
local source_stats, handle
|
||||
local success, errmsg
|
||||
|
||||
source_stats, errmsg = uv.fs_stat(source)
|
||||
source_stats, errmsg = vim.loop.fs_stat(source)
|
||||
if not source_stats then
|
||||
log.line("copy_paste", "do_copy fs_stat '%s' failed '%s'", source, errmsg)
|
||||
return false, errmsg
|
||||
@@ -31,14 +30,14 @@ local function do_copy(source, destination)
|
||||
end
|
||||
|
||||
if source_stats.type == "file" then
|
||||
success, errmsg = uv.fs_copyfile(source, destination)
|
||||
success, errmsg = vim.loop.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)
|
||||
handle, errmsg = vim.loop.fs_scandir(source)
|
||||
if type(handle) == "string" then
|
||||
return false, handle
|
||||
elseif not handle then
|
||||
@@ -46,14 +45,14 @@ local function do_copy(source, destination)
|
||||
return false, errmsg
|
||||
end
|
||||
|
||||
success, errmsg = uv.fs_mkdir(destination, source_stats.mode)
|
||||
success, errmsg = vim.loop.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)
|
||||
local name, _ = vim.loop.fs_scandir_next(handle)
|
||||
if not name then
|
||||
break
|
||||
end
|
||||
@@ -80,35 +79,39 @@ local function do_single_paste(source, dest, action_type, action_fn)
|
||||
|
||||
log.line("copy_paste", "do_single_paste '%s' -> '%s'", source, dest)
|
||||
|
||||
dest_stats, errmsg, errcode = uv.fs_stat(dest)
|
||||
dest_stats, errmsg, errcode = vim.loop.fs_stat(dest)
|
||||
if not dest_stats and errcode ~= "ENOENT" then
|
||||
a.nvim_err_writeln("Could not " .. action_type .. " " .. source .. " - " .. (errmsg or "???"))
|
||||
notify.error("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
|
||||
local function on_process()
|
||||
success, errmsg = action_fn(source, dest)
|
||||
if not success then
|
||||
a.nvim_err_writeln("Could not " .. action_type .. " " .. source .. " - " .. (errmsg or "???"))
|
||||
notify.error("Could not " .. action_type .. " " .. source .. " - " .. (errmsg or "???"))
|
||||
return false, errmsg
|
||||
end
|
||||
end
|
||||
|
||||
if dest_stats then
|
||||
local prompt_select = "Overwrite " .. dest .. " ?"
|
||||
local prompt_input = prompt_select .. " y/n/r(ename): "
|
||||
lib.prompt(prompt_input, prompt_select, { "y", "n", "r" }, { "Yes", "No", "Rename" }, function(item_short)
|
||||
utils.clear_prompt()
|
||||
if item_short == "y" then
|
||||
on_process()
|
||||
elseif item_short == "r" then
|
||||
vim.ui.input({ prompt = "Rename to ", default = dest, completion = "dir" }, function(new_dest)
|
||||
utils.clear_prompt()
|
||||
if new_dest then
|
||||
do_single_paste(source, new_dest, action_type, action_fn)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
else
|
||||
on_process()
|
||||
end
|
||||
end
|
||||
|
||||
local function add_to_clipboard(node, clip)
|
||||
@@ -119,11 +122,17 @@ local function add_to_clipboard(node, clip)
|
||||
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")
|
||||
return notify.info(node.absolute_path .. " removed to clipboard.")
|
||||
end
|
||||
end
|
||||
table.insert(clip, node)
|
||||
a.nvim_out_write(node.absolute_path .. " added to clipboard.\n")
|
||||
notify.info(node.absolute_path .. " added to clipboard.")
|
||||
end
|
||||
|
||||
function M.clear_clipboard()
|
||||
clipboard.move = {}
|
||||
clipboard.copy = {}
|
||||
utils.notify.info "Clipboard has been emptied."
|
||||
end
|
||||
|
||||
function M.copy(node)
|
||||
@@ -137,7 +146,7 @@ end
|
||||
local function do_paste(node, action_type, action_fn)
|
||||
node = lib.get_last_group_node(node)
|
||||
if node.name == ".." then
|
||||
return
|
||||
node = core.get_explorer()
|
||||
end
|
||||
local clip = clipboard[action_type]
|
||||
if #clip == 0 then
|
||||
@@ -145,18 +154,15 @@ local function do_paste(node, action_type, action_fn)
|
||||
end
|
||||
|
||||
local destination = node.absolute_path
|
||||
local stats, errmsg, errcode = uv.fs_stat(destination)
|
||||
local stats, errmsg, errcode = vim.loop.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 "???"))
|
||||
notify.error("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
|
||||
@@ -165,7 +171,9 @@ local function do_paste(node, action_type, action_fn)
|
||||
end
|
||||
|
||||
clipboard[action_type] = {}
|
||||
return require("nvim-tree.actions.reloaders").reload_explorer()
|
||||
if M.enable_reload then
|
||||
return require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
|
||||
end
|
||||
end
|
||||
|
||||
local function do_cut(source, destination)
|
||||
@@ -176,12 +184,14 @@ local function do_cut(source, destination)
|
||||
return true
|
||||
end
|
||||
|
||||
local success, errmsg = uv.fs_rename(source, destination)
|
||||
events._dispatch_will_rename_node(source, destination)
|
||||
local success, errmsg = vim.loop.fs_rename(source, destination)
|
||||
if not success then
|
||||
log.line("copy_paste", "do_cut fs_rename failed '%s'", errmsg)
|
||||
return false, errmsg
|
||||
end
|
||||
utils.rename_loaded_buffers(source, destination)
|
||||
events._dispatch_node_renamed(source, destination)
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -208,18 +218,18 @@ function M.print_clipboard()
|
||||
end
|
||||
end
|
||||
|
||||
return a.nvim_out_write(table.concat(content, "\n") .. "\n")
|
||||
return notify.info(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))
|
||||
return notify.info(string.format("Copied %s to system clipboard!", 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))
|
||||
return notify.info(string.format("Copied %s to neovim clipboard!", content))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -242,6 +252,7 @@ end
|
||||
|
||||
function M.setup(opts)
|
||||
M.use_system_clipboard = opts.actions.use_system_clipboard
|
||||
M.enable_reload = not opts.filesystem_watchers.enable
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -1,36 +1,36 @@
|
||||
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 notify = require "nvim-tree.notify"
|
||||
|
||||
local find_file = require("nvim-tree.actions.finders.find-file").fn
|
||||
|
||||
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 }
|
||||
local function create_and_notify(file)
|
||||
local ok, fd = pcall(vim.loop.fs_open, file, "w", 420)
|
||||
if not ok then
|
||||
notify.error("Couldn't create file " .. file)
|
||||
return
|
||||
end
|
||||
vim.loop.fs_close(fd)
|
||||
events._dispatch_file_created(file)
|
||||
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
|
||||
local prompt_select = "Overwrite " .. file .. " ?"
|
||||
local prompt_input = prompt_select .. " y/n: "
|
||||
lib.prompt(prompt_input, prompt_select, { "y", "n" }, { "Yes", "No" }, function(item_short)
|
||||
utils.clear_prompt()
|
||||
if item_short == "y" then
|
||||
create_and_notify(file)
|
||||
end
|
||||
end)
|
||||
else
|
||||
create_and_notify(file)
|
||||
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)
|
||||
@@ -42,8 +42,7 @@ local function get_num_nodes(iter)
|
||||
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
|
||||
if node.nodes ~= nil then
|
||||
return utils.path_add_trailing(node.absolute_path)
|
||||
end
|
||||
local node_name_size = #(node.name or "")
|
||||
@@ -51,8 +50,8 @@ local function get_containing_folder(node)
|
||||
end
|
||||
|
||||
function M.fn(node)
|
||||
node = lib.get_last_group_node(node)
|
||||
if node.name == ".." then
|
||||
node = node and lib.get_last_group_node(node)
|
||||
if not node or node.name == ".." then
|
||||
node = {
|
||||
absolute_path = core.get_cwd(),
|
||||
nodes = core.get_explorer().nodes,
|
||||
@@ -65,14 +64,13 @@ function M.fn(node)
|
||||
local input_opts = { prompt = "Create file ", default = containing_folder, completion = "file" }
|
||||
|
||||
vim.ui.input(input_opts, function(new_file_path)
|
||||
utils.clear_prompt()
|
||||
if not new_file_path or new_file_path == containing_folder then
|
||||
return
|
||||
end
|
||||
|
||||
utils.clear_prompt()
|
||||
|
||||
if utils.file_exists(new_file_path) then
|
||||
utils.warn "Cannot create: file already exists"
|
||||
notify.warn "Cannot create: file already exists"
|
||||
return
|
||||
end
|
||||
|
||||
@@ -95,21 +93,26 @@ function M.fn(node)
|
||||
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)
|
||||
local success = vim.loop.fs_mkdir(path_to_create, 493)
|
||||
if not success then
|
||||
a.nvim_err_writeln("Could not create folder " .. path_to_create)
|
||||
notify.error("Could not create folder " .. path_to_create)
|
||||
is_error = true
|
||||
break
|
||||
end
|
||||
events._dispatch_folder_created(new_file_path)
|
||||
end
|
||||
end
|
||||
if not is_error then
|
||||
a.nvim_out_write(new_file_path .. " was properly created\n")
|
||||
notify.info(new_file_path .. " was properly created")
|
||||
end
|
||||
events._dispatch_folder_created(new_file_path)
|
||||
require("nvim-tree.actions.reloaders").reload_explorer()
|
||||
focus_file(new_file_path)
|
||||
|
||||
-- synchronously refreshes as we can't wait for the watchers
|
||||
find_file(utils.path_remove_trailing(new_file_path))
|
||||
end)
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
M.enable_reload = not opts.filesystem_watchers.enable
|
||||
end
|
||||
|
||||
return M
|
||||
108
lua/nvim-tree/actions/fs/remove-file.lua
Normal file
108
lua/nvim-tree/actions/fs/remove-file.lua
Normal file
@@ -0,0 +1,108 @@
|
||||
local utils = require "nvim-tree.utils"
|
||||
local events = require "nvim-tree.events"
|
||||
local view = require "nvim-tree.view"
|
||||
local lib = require "nvim-tree.lib"
|
||||
local notify = require "nvim-tree.notify"
|
||||
|
||||
local M = {}
|
||||
|
||||
local function close_windows(windows)
|
||||
if view.View.float.enable and #vim.api.nvim_list_wins() == 1 then
|
||||
return
|
||||
end
|
||||
|
||||
for _, window in ipairs(windows) do
|
||||
if vim.api.nvim_win_is_valid(window) then
|
||||
vim.api.nvim_win_close(window, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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 or view.View.float.enable) then
|
||||
local winnr = vim.api.nvim_get_current_win()
|
||||
vim.api.nvim_set_current_win(buf.windows[1])
|
||||
vim.cmd ":bn"
|
||||
if not view.View.float.enable then
|
||||
vim.api.nvim_set_current_win(winnr)
|
||||
end
|
||||
end
|
||||
vim.api.nvim_buf_delete(buf.bufnr, { force = true })
|
||||
if M.close_window then
|
||||
close_windows(buf.windows)
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function remove_dir(cwd)
|
||||
local handle = vim.loop.fs_scandir(cwd)
|
||||
if type(handle) == "string" then
|
||||
return notify.error(handle)
|
||||
end
|
||||
|
||||
while true do
|
||||
local name, t = vim.loop.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 = vim.loop.fs_unlink(new_cwd)
|
||||
if not success then
|
||||
return false
|
||||
end
|
||||
clear_buffer(new_cwd)
|
||||
end
|
||||
end
|
||||
|
||||
return vim.loop.fs_rmdir(cwd)
|
||||
end
|
||||
|
||||
function M.fn(node)
|
||||
if node.name == ".." then
|
||||
return
|
||||
end
|
||||
local prompt_select = "Remove " .. node.name .. " ?"
|
||||
local prompt_input = prompt_select .. " y/n: "
|
||||
lib.prompt(prompt_input, prompt_select, { "y", "n" }, { "Yes", "No" }, function(item_short)
|
||||
utils.clear_prompt()
|
||||
if item_short == "y" then
|
||||
if node.nodes ~= nil and not node.link_to then
|
||||
local success = remove_dir(node.absolute_path)
|
||||
if not success then
|
||||
return notify.error("Could not remove " .. node.name)
|
||||
end
|
||||
events._dispatch_folder_removed(node.absolute_path)
|
||||
else
|
||||
local success = vim.loop.fs_unlink(node.absolute_path)
|
||||
if not success then
|
||||
return notify.error("Could not remove " .. node.name)
|
||||
end
|
||||
events._dispatch_file_removed(node.absolute_path)
|
||||
clear_buffer(node.absolute_path)
|
||||
end
|
||||
notify.info(node.absolute_path .. " was properly removed.")
|
||||
if M.enable_reload then
|
||||
require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
M.enable_reload = not opts.filesystem_watchers.enable
|
||||
M.close_window = opts.actions.remove_file.close_window
|
||||
end
|
||||
|
||||
return M
|
||||
92
lua/nvim-tree/actions/fs/rename-file.lua
Normal file
92
lua/nvim-tree/actions/fs/rename-file.lua
Normal file
@@ -0,0 +1,92 @@
|
||||
local lib = require "nvim-tree.lib"
|
||||
local utils = require "nvim-tree.utils"
|
||||
local events = require "nvim-tree.events"
|
||||
local notify = require "nvim-tree.notify"
|
||||
|
||||
local M = {}
|
||||
|
||||
local ALLOWED_MODIFIERS = {
|
||||
[":p"] = true,
|
||||
[":t"] = true,
|
||||
[":t:r"] = true,
|
||||
}
|
||||
|
||||
local function err_fmt(from, to, reason)
|
||||
return string.format("Cannot rename %s -> %s: %s", from, to, reason)
|
||||
end
|
||||
|
||||
function M.rename(node, to)
|
||||
if utils.file_exists(to) then
|
||||
notify.warn(err_fmt(node.absolute_path, to, "file already exists"))
|
||||
return
|
||||
end
|
||||
|
||||
events._dispatch_will_rename_node(node.absolute_path, to)
|
||||
local success, err = vim.loop.fs_rename(node.absolute_path, to)
|
||||
if not success then
|
||||
return notify.warn(err_fmt(node.absolute_path, to, err))
|
||||
end
|
||||
notify.info(node.absolute_path .. " ➜ " .. to)
|
||||
utils.rename_loaded_buffers(node.absolute_path, to)
|
||||
events._dispatch_node_renamed(node.absolute_path, to)
|
||||
end
|
||||
|
||||
function M.fn(default_modifier)
|
||||
default_modifier = default_modifier or ":t"
|
||||
|
||||
return function(node, modifier)
|
||||
if type(node) ~= "table" then
|
||||
node = lib.get_node_at_cursor()
|
||||
end
|
||||
|
||||
if type(modifier) ~= "string" then
|
||||
modifier = default_modifier
|
||||
end
|
||||
|
||||
-- support for only specific modifiers have been implemented
|
||||
if not ALLOWED_MODIFIERS[modifier] then
|
||||
return notify.warn(
|
||||
"Modifier " .. vim.inspect(modifier) .. " is not in allowed list : " .. table.concat(ALLOWED_MODIFIERS, ",")
|
||||
)
|
||||
end
|
||||
|
||||
node = lib.get_last_group_node(node)
|
||||
if node.name == ".." then
|
||||
return
|
||||
end
|
||||
|
||||
local namelen = node.name:len()
|
||||
local directory = node.absolute_path:sub(0, namelen * -1 - 1)
|
||||
local default_path
|
||||
local prepend = ""
|
||||
local append = ""
|
||||
default_path = vim.fn.fnamemodify(node.absolute_path, modifier)
|
||||
if modifier:sub(0, 2) == ":t" then
|
||||
prepend = directory
|
||||
end
|
||||
if modifier == ":t:r" then
|
||||
local extension = vim.fn.fnamemodify(node.name, ":e")
|
||||
append = extension:len() == 0 and "" or "." .. extension
|
||||
end
|
||||
|
||||
local input_opts = { prompt = "Rename to ", default = default_path, completion = "file" }
|
||||
|
||||
vim.ui.input(input_opts, function(new_file_path)
|
||||
utils.clear_prompt()
|
||||
if not new_file_path then
|
||||
return
|
||||
end
|
||||
|
||||
M.rename(node, prepend .. new_file_path .. append)
|
||||
if M.enable_reload then
|
||||
require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
M.enable_reload = not opts.filesystem_watchers.enable
|
||||
end
|
||||
|
||||
return M
|
||||
115
lua/nvim-tree/actions/fs/trash.lua
Normal file
115
lua/nvim-tree/actions/fs/trash.lua
Normal file
@@ -0,0 +1,115 @@
|
||||
local lib = require "nvim-tree.lib"
|
||||
local notify = require "nvim-tree.notify"
|
||||
|
||||
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 = vim.api.nvim_get_current_win()
|
||||
vim.api.nvim_set_current_win(buf.windows[1])
|
||||
vim.cmd ":bn"
|
||||
vim.api.nvim_set_current_win(winnr)
|
||||
end
|
||||
vim.api.nvim_buf_delete(buf.bufnr, {})
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
notify.warn "Trash is currently a UNIX only feature!"
|
||||
return
|
||||
end
|
||||
|
||||
local binary = M.config.trash.cmd:gsub(" .*$", "")
|
||||
if vim.fn.executable(binary) == 0 then
|
||||
notify.warn(binary .. " is not executable.")
|
||||
return
|
||||
end
|
||||
|
||||
local err_msg = ""
|
||||
local function on_stderr(_, data)
|
||||
err_msg = err_msg .. (data and table.concat(data, " "))
|
||||
end
|
||||
|
||||
-- trashes a path (file or folder)
|
||||
local function trash_path(on_exit)
|
||||
vim.fn.jobstart(M.config.trash.cmd .. ' "' .. node.absolute_path .. '"', {
|
||||
detach = true,
|
||||
on_exit = on_exit,
|
||||
on_stderr = on_stderr,
|
||||
})
|
||||
end
|
||||
|
||||
local function do_trash()
|
||||
if node.nodes ~= nil and not node.link_to then
|
||||
trash_path(function(_, rc)
|
||||
if rc ~= 0 then
|
||||
notify.warn("trash failed: " .. err_msg .. "; please see :help nvim-tree.trash")
|
||||
return
|
||||
end
|
||||
events._dispatch_folder_removed(node.absolute_path)
|
||||
if M.enable_reload then
|
||||
require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
|
||||
end
|
||||
end)
|
||||
else
|
||||
trash_path(function(_, rc)
|
||||
if rc ~= 0 then
|
||||
notify.warn("trash failed: " .. err_msg .. "; please see :help nvim-tree.trash")
|
||||
return
|
||||
end
|
||||
events._dispatch_file_removed(node.absolute_path)
|
||||
clear_buffer(node.absolute_path)
|
||||
if M.enable_reload then
|
||||
require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
if M.config.trash.require_confirm then
|
||||
local prompt_select = "Trash " .. node.name .. " ?"
|
||||
local prompt_input = prompt_select .. " y/n: "
|
||||
lib.prompt(prompt_input, prompt_select, { "y", "n" }, { "Yes", "No" }, function(item_short)
|
||||
utils.clear_prompt()
|
||||
if item_short == "y" then
|
||||
do_trash()
|
||||
end
|
||||
end)
|
||||
else
|
||||
do_trash()
|
||||
end
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
M.config.trash = opts.trash or {}
|
||||
M.enable_reload = not opts.filesystem_watchers.enable
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -1,145 +1,290 @@
|
||||
local a = vim.api
|
||||
-- @deprecated: new implementation in nvim-tree.keymap. Please do not edit this file.
|
||||
|
||||
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 notify = require "nvim-tree.notify"
|
||||
|
||||
-- BEGIN_DEFAULT_MAPPINGS
|
||||
local DEFAULT_MAPPINGS = {
|
||||
{
|
||||
key = { "<CR>", "o", "<2-LeftMouse>" },
|
||||
action = "edit",
|
||||
desc = "open a file or folder; root will cd to the above directory",
|
||||
},
|
||||
{
|
||||
key = "<C-e>",
|
||||
action = "edit_in_place",
|
||||
desc = "edit the file in place, effectively replacing the tree explorer",
|
||||
},
|
||||
{
|
||||
key = "O",
|
||||
action = "edit_no_picker",
|
||||
desc = "same as (edit) with no window picker",
|
||||
},
|
||||
{
|
||||
key = { "<C-]>", "<2-RightMouse>" },
|
||||
action = "cd",
|
||||
desc = "cd in the directory under the cursor",
|
||||
},
|
||||
{
|
||||
key = "<C-v>",
|
||||
action = "vsplit",
|
||||
desc = "open the file in a vertical split",
|
||||
},
|
||||
{
|
||||
key = "<C-x>",
|
||||
action = "split",
|
||||
desc = "open the file in a horizontal split",
|
||||
},
|
||||
{
|
||||
key = "<C-t>",
|
||||
action = "tabnew",
|
||||
desc = "open the file in a new tab",
|
||||
},
|
||||
{
|
||||
key = "<",
|
||||
action = "prev_sibling",
|
||||
desc = "navigate to the previous sibling of current file/directory",
|
||||
},
|
||||
{
|
||||
key = ">",
|
||||
action = "next_sibling",
|
||||
desc = "navigate to the next sibling of current file/directory",
|
||||
},
|
||||
{
|
||||
key = "P",
|
||||
action = "parent_node",
|
||||
desc = "move cursor to the parent directory",
|
||||
},
|
||||
{
|
||||
key = "<BS>",
|
||||
action = "close_node",
|
||||
desc = "close current opened directory or parent",
|
||||
},
|
||||
{
|
||||
key = "<Tab>",
|
||||
action = "preview",
|
||||
desc = "open the file as a preview (keeps the cursor in the tree)",
|
||||
},
|
||||
{
|
||||
key = "K",
|
||||
action = "first_sibling",
|
||||
desc = "navigate to the first sibling of current file/directory",
|
||||
},
|
||||
{
|
||||
key = "J",
|
||||
action = "last_sibling",
|
||||
desc = "navigate to the last sibling of current file/directory",
|
||||
},
|
||||
{
|
||||
key = "C",
|
||||
action = "toggle_git_clean",
|
||||
desc = "toggle visibility of git clean via |filters.git_clean| option",
|
||||
},
|
||||
{
|
||||
key = "I",
|
||||
action = "toggle_git_ignored",
|
||||
desc = "toggle visibility of files/folders hidden via |git.ignore| option",
|
||||
},
|
||||
{
|
||||
key = "H",
|
||||
action = "toggle_dotfiles",
|
||||
desc = "toggle visibility of dotfiles via |filters.dotfiles| option",
|
||||
},
|
||||
{
|
||||
key = "B",
|
||||
action = "toggle_no_buffer",
|
||||
desc = "toggle visibility of files/folders hidden via |filters.no_buffer| option",
|
||||
},
|
||||
{
|
||||
key = "U",
|
||||
action = "toggle_custom",
|
||||
desc = "toggle visibility of files/folders hidden via |filters.custom| option",
|
||||
},
|
||||
{
|
||||
key = "R",
|
||||
action = "refresh",
|
||||
desc = "refresh the tree",
|
||||
},
|
||||
{
|
||||
key = "a",
|
||||
action = "create",
|
||||
desc = "add a file; leaving a trailing `/` will add a directory",
|
||||
},
|
||||
{
|
||||
key = "d",
|
||||
action = "remove",
|
||||
desc = "delete a file (will prompt for confirmation)",
|
||||
},
|
||||
{
|
||||
key = "D",
|
||||
action = "trash",
|
||||
desc = "trash a file via |trash| option",
|
||||
},
|
||||
{
|
||||
key = "r",
|
||||
action = "rename",
|
||||
desc = "rename a file",
|
||||
},
|
||||
{
|
||||
key = "<C-r>",
|
||||
action = "full_rename",
|
||||
desc = "rename a file and omit the filename on input",
|
||||
},
|
||||
{
|
||||
key = "e",
|
||||
action = "rename_basename",
|
||||
desc = "rename a file with filename-modifiers ':t:r' without changing extension",
|
||||
},
|
||||
{
|
||||
key = "x",
|
||||
action = "cut",
|
||||
desc = "add/remove file/directory to cut clipboard",
|
||||
},
|
||||
{
|
||||
key = "c",
|
||||
action = "copy",
|
||||
desc = "add/remove file/directory to copy clipboard",
|
||||
},
|
||||
{
|
||||
key = "p",
|
||||
action = "paste",
|
||||
desc = "paste from clipboard; cut clipboard has precedence over copy; will prompt for confirmation",
|
||||
},
|
||||
{
|
||||
key = "y",
|
||||
action = "copy_name",
|
||||
desc = "copy name to system clipboard",
|
||||
},
|
||||
{
|
||||
key = "Y",
|
||||
action = "copy_path",
|
||||
desc = "copy relative path to system clipboard",
|
||||
},
|
||||
{
|
||||
key = "gy",
|
||||
action = "copy_absolute_path",
|
||||
desc = "copy absolute path to system clipboard",
|
||||
},
|
||||
{
|
||||
key = "[e",
|
||||
action = "prev_diag_item",
|
||||
desc = "go to next diagnostic item",
|
||||
},
|
||||
{
|
||||
key = "[c",
|
||||
action = "prev_git_item",
|
||||
desc = "go to next git item",
|
||||
},
|
||||
{
|
||||
key = "]e",
|
||||
action = "next_diag_item",
|
||||
desc = "go to prev diagnostic item",
|
||||
},
|
||||
{
|
||||
key = "]c",
|
||||
action = "next_git_item",
|
||||
desc = "go to prev git item",
|
||||
},
|
||||
{
|
||||
key = "-",
|
||||
action = "dir_up",
|
||||
desc = "navigate up to the parent directory of the current file/directory",
|
||||
},
|
||||
{
|
||||
key = "s",
|
||||
action = "system_open",
|
||||
desc = "open a file with default system application or a folder with default file manager, using |system_open| option",
|
||||
},
|
||||
{
|
||||
key = "f",
|
||||
action = "live_filter",
|
||||
desc = "live filter nodes dynamically based on regex matching.",
|
||||
},
|
||||
{
|
||||
key = "F",
|
||||
action = "clear_live_filter",
|
||||
desc = "clear live filter",
|
||||
},
|
||||
{
|
||||
key = "q",
|
||||
action = "close",
|
||||
desc = "close tree window",
|
||||
},
|
||||
{
|
||||
key = "W",
|
||||
action = "collapse_all",
|
||||
desc = "collapse the whole tree",
|
||||
},
|
||||
{
|
||||
key = "E",
|
||||
action = "expand_all",
|
||||
desc = "expand the whole tree, stopping after expanding |actions.expand_all.max_folder_discovery| folders; this might hang neovim for a while if running on a big folder",
|
||||
},
|
||||
{
|
||||
key = "S",
|
||||
action = "search_node",
|
||||
desc = "prompt the user to enter a path and then expands the tree to match the path",
|
||||
},
|
||||
{
|
||||
key = ".",
|
||||
action = "run_file_command",
|
||||
desc = "enter vim command mode with the file the cursor is on",
|
||||
},
|
||||
{
|
||||
key = "<C-k>",
|
||||
action = "toggle_file_info",
|
||||
desc = "toggle a popup with file infos about the file under the cursor",
|
||||
},
|
||||
{
|
||||
key = "g?",
|
||||
action = "toggle_help",
|
||||
desc = "toggle help",
|
||||
},
|
||||
{
|
||||
key = "m",
|
||||
action = "toggle_mark",
|
||||
desc = "Toggle node in bookmarks",
|
||||
},
|
||||
{
|
||||
key = "bmv",
|
||||
action = "bulk_move",
|
||||
desc = "Move all bookmarked nodes into specified location",
|
||||
},
|
||||
}
|
||||
-- END_DEFAULT_MAPPINGS
|
||||
|
||||
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" },
|
||||
},
|
||||
mappings = {},
|
||||
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
|
||||
local function set_map_for(bufnr)
|
||||
local opts = { noremap = true, silent = true, nowait = true, buffer = bufnr }
|
||||
return function(mode, rhs)
|
||||
return function(lhs)
|
||||
vim.keymap.set(mode or "n", lhs, rhs, opts)
|
||||
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
|
||||
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)
|
||||
local function run_dispatch(action)
|
||||
return function()
|
||||
require("nvim-tree.actions.dispatch").dispatch(action)
|
||||
end
|
||||
end
|
||||
|
||||
function M.apply_mappings(bufnr)
|
||||
local setter_for = set_map_for(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 })
|
||||
local rhs = b.cb or run_dispatch(b.action)
|
||||
if rhs then
|
||||
local setter = setter_for(b.mode, rhs)
|
||||
|
||||
local keys = type(b.key) == "table" and b.key or { b.key }
|
||||
for _, key in pairs(keys) do
|
||||
setter(key)
|
||||
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
|
||||
@@ -175,7 +320,7 @@ local function merge_mappings(user_mappings)
|
||||
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"
|
||||
notify.warn "action can't be empty if action_cb provided"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -216,17 +361,59 @@ local function copy_mappings(user_mappings)
|
||||
return user_mappings
|
||||
end
|
||||
|
||||
local function cleanup_existing_mappings()
|
||||
local bufnr = view.get_bufnr()
|
||||
if bufnr == nil or not vim.api.nvim_buf_is_valid(bufnr) then
|
||||
return
|
||||
end
|
||||
|
||||
for _, b in pairs(M.mappings) do
|
||||
local keys = type(b.key) == "table" and b.key or { b.key }
|
||||
for _, key in pairs(keys) do
|
||||
vim.keymap.del(b.mode or "n", key, { buffer = bufnr })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function filter_mappings(mappings, keys)
|
||||
if type(keys) == "boolean" and keys then
|
||||
return {}
|
||||
elseif type(keys) == "table" then
|
||||
return vim.tbl_filter(function(m)
|
||||
if type(m.key) == "table" then
|
||||
m.key = vim.tbl_filter(function(k)
|
||||
return not vim.tbl_contains(keys, k)
|
||||
end, m.key)
|
||||
return #m.key > 0
|
||||
else
|
||||
return not vim.tbl_contains(keys, m.key)
|
||||
end
|
||||
end, vim.deepcopy(mappings))
|
||||
else
|
||||
return vim.deepcopy(mappings)
|
||||
end
|
||||
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)
|
||||
require("nvim-tree.actions.fs.trash").setup(opts)
|
||||
require("nvim-tree.actions.node.system-open").setup(opts)
|
||||
require("nvim-tree.actions.node.file-popup").setup(opts)
|
||||
require("nvim-tree.actions.node.open-file").setup(opts)
|
||||
require("nvim-tree.actions.root.change-dir").setup(opts)
|
||||
require("nvim-tree.actions.fs.create-file").setup(opts)
|
||||
require("nvim-tree.actions.fs.rename-file").setup(opts)
|
||||
require("nvim-tree.actions.fs.remove-file").setup(opts)
|
||||
require("nvim-tree.actions.fs.copy-paste").setup(opts)
|
||||
require("nvim-tree.actions.tree-modifiers.expand-all").setup(opts)
|
||||
|
||||
cleanup_existing_mappings()
|
||||
|
||||
M.mappings = filter_mappings(DEFAULT_MAPPINGS, opts.remove_keymaps)
|
||||
|
||||
local user_map_config = (opts.view or {}).mappings or {}
|
||||
local options = vim.tbl_deep_extend("force", DEFAULT_MAPPING_CONFIG, user_map_config)
|
||||
@@ -236,6 +423,8 @@ function M.setup(opts)
|
||||
M.mappings = merge_mappings(options.list)
|
||||
end
|
||||
|
||||
require("nvim-tree.actions.dispatch").setup(M.custom_keypress_funcs)
|
||||
|
||||
log.line("config", "active mappings")
|
||||
log.raw("config", "%s\n", vim.inspect(M.mappings))
|
||||
end
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
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 = utils.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
|
||||
56
lua/nvim-tree/actions/moves/item.lua
Normal file
56
lua/nvim-tree/actions/moves/item.lua
Normal file
@@ -0,0 +1,56 @@
|
||||
local utils = require "nvim-tree.utils"
|
||||
local view = require "nvim-tree.view"
|
||||
local core = require "nvim-tree.core"
|
||||
local lib = require "nvim-tree.lib"
|
||||
local explorer_common = require "nvim-tree.explorer.common"
|
||||
|
||||
local M = {}
|
||||
|
||||
function M.fn(where, what)
|
||||
return function()
|
||||
local node_cur = lib.get_node_at_cursor()
|
||||
local nodes_by_line = utils.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
|
||||
local valid = false
|
||||
if what == "git" then
|
||||
valid = explorer_common.shows_git_status(node)
|
||||
elseif what == "diag" then
|
||||
valid = node.diag_status ~= nil
|
||||
end
|
||||
|
||||
if not first and valid then
|
||||
first = line
|
||||
end
|
||||
|
||||
if node == node_cur then
|
||||
cur = line
|
||||
elseif valid 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
|
||||
41
lua/nvim-tree/actions/moves/parent.lua
Normal file
41
lua/nvim-tree/actions/moves/parent.lua
Normal file
@@ -0,0 +1,41 @@
|
||||
local renderer = require "nvim-tree.renderer"
|
||||
local view = require "nvim-tree.view"
|
||||
local utils = require "nvim-tree.utils"
|
||||
local core = require "nvim-tree.core"
|
||||
|
||||
local M = {}
|
||||
|
||||
function M.fn(should_close)
|
||||
should_close = should_close or false
|
||||
|
||||
return function(node)
|
||||
if should_close and node.open then
|
||||
node.open = false
|
||||
return renderer.draw()
|
||||
end
|
||||
|
||||
local parent = node.parent
|
||||
|
||||
if renderer.config.group_empty and parent then
|
||||
while parent.parent and parent.parent.group_next do
|
||||
parent = parent.parent
|
||||
end
|
||||
end
|
||||
|
||||
if not parent or not parent.parent 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
|
||||
|
||||
return M
|
||||
51
lua/nvim-tree/actions/moves/sibling.lua
Normal file
51
lua/nvim-tree/actions/moves/sibling.lua
Normal file
@@ -0,0 +1,51 @@
|
||||
local utils = require "nvim-tree.utils"
|
||||
local core = require "nvim-tree.core"
|
||||
local Iterator = require "nvim-tree.iterators.node-iterator"
|
||||
|
||||
local M = {}
|
||||
|
||||
function M.fn(direction)
|
||||
return function(node)
|
||||
if node.name == ".." or not direction then
|
||||
return
|
||||
end
|
||||
|
||||
local first, last, next, prev = nil, nil, nil, nil
|
||||
local found = false
|
||||
local parent = node.parent or core.get_explorer()
|
||||
Iterator.builder(parent.nodes)
|
||||
:recursor(function()
|
||||
return nil
|
||||
end)
|
||||
:applier(function(n)
|
||||
first = first or n
|
||||
last = n
|
||||
if n.absolute_path == node.absolute_path then
|
||||
found = true
|
||||
return
|
||||
end
|
||||
prev = not found and n or prev
|
||||
if found and not next then
|
||||
next = n
|
||||
end
|
||||
end)
|
||||
:iterate()
|
||||
|
||||
local target_node
|
||||
if direction == "first" then
|
||||
target_node = first
|
||||
elseif direction == "last" then
|
||||
target_node = last
|
||||
elseif direction == "next" then
|
||||
target_node = next or first
|
||||
else
|
||||
target_node = prev or last
|
||||
end
|
||||
|
||||
if target_node then
|
||||
utils.focus_file(target_node.absolute_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -1,5 +1,4 @@
|
||||
local utils = require "nvim-tree.utils"
|
||||
local a = vim.api
|
||||
|
||||
local M = {}
|
||||
|
||||
@@ -28,28 +27,25 @@ local function setup_window(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",
|
||||
local open_win_config = vim.tbl_extend("force", M.open_win_config, {
|
||||
width = max_width + 1,
|
||||
height = #lines,
|
||||
border = "shadow",
|
||||
noautocmd = true,
|
||||
style = "minimal",
|
||||
zindex = 60,
|
||||
})
|
||||
local winnr = vim.api.nvim_open_win(0, false, open_win_config)
|
||||
current_popup = {
|
||||
winnr = winnr,
|
||||
file_path = node.absolute_path,
|
||||
}
|
||||
local bufnr = a.nvim_create_buf(false, true)
|
||||
a.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
||||
a.nvim_win_set_buf(winnr, bufnr)
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
||||
vim.api.nvim_win_set_buf(winnr, bufnr)
|
||||
end
|
||||
|
||||
function M.close_popup()
|
||||
if current_popup ~= nil then
|
||||
a.nvim_win_close(current_popup.winnr, { force = true })
|
||||
vim.api.nvim_win_close(current_popup.winnr, { force = true })
|
||||
vim.cmd "augroup NvimTreeRemoveFilePopup | au! CursorMoved | augroup END"
|
||||
|
||||
current_popup = nil
|
||||
@@ -72,11 +68,14 @@ function M.toggle_file_info(node)
|
||||
|
||||
setup_window(node)
|
||||
|
||||
vim.cmd [[
|
||||
augroup NvimTreeRemoveFilePopup
|
||||
au CursorMoved * lua require'nvim-tree.actions.file-popup'.close_popup()
|
||||
augroup END
|
||||
]]
|
||||
vim.api.nvim_create_autocmd("CursorMoved", {
|
||||
group = vim.api.nvim_create_augroup("NvimTreeRemoveFilePopup", {}),
|
||||
callback = M.close_popup,
|
||||
})
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
M.open_win_config = opts.actions.file_popup.open_win_config
|
||||
end
|
||||
|
||||
return M
|
||||
322
lua/nvim-tree/actions/node/open-file.lua
Normal file
322
lua/nvim-tree/actions/node/open-file.lua
Normal file
@@ -0,0 +1,322 @@
|
||||
-- Copyright 2019 Yazdani Kiyan under MIT License
|
||||
local lib = require "nvim-tree.lib"
|
||||
local utils = require "nvim-tree.utils"
|
||||
local view = require "nvim-tree.view"
|
||||
|
||||
local M = {}
|
||||
|
||||
local function get_user_input_char()
|
||||
local c = vim.fn.getchar()
|
||||
while type(c) ~= "number" do
|
||||
c = vim.fn.getchar()
|
||||
end
|
||||
return vim.fn.nr2char(c)
|
||||
end
|
||||
|
||||
---Get all windows in the current tabpage that aren't NvimTree.
|
||||
---@return table with valid win_ids
|
||||
local function usable_win_ids()
|
||||
local tabpage = vim.api.nvim_get_current_tabpage()
|
||||
local win_ids = vim.api.nvim_tabpage_list_wins(tabpage)
|
||||
local tree_winid = view.get_winnr(tabpage)
|
||||
|
||||
return vim.tbl_filter(function(id)
|
||||
local bufid = vim.api.nvim_win_get_buf(id)
|
||||
for option, v in pairs(M.window_picker.exclude) do
|
||||
local ok, option_value = pcall(vim.api.nvim_buf_get_option, bufid, option)
|
||||
if ok and vim.tbl_contains(v, option_value) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local win_config = vim.api.nvim_win_get_config(id)
|
||||
return id ~= tree_winid and win_config.focusable and not win_config.external
|
||||
end, win_ids)
|
||||
end
|
||||
|
||||
---Find the first window in the tab that is not NvimTree.
|
||||
---@return integer -1 if none available
|
||||
local function first_win_id()
|
||||
local selectable = usable_win_ids()
|
||||
if #selectable > 0 then
|
||||
return selectable[1]
|
||||
else
|
||||
return -1
|
||||
end
|
||||
end
|
||||
|
||||
---Get user to pick a window in the tab that is not NvimTree.
|
||||
---@return integer|nil -- If a valid window was picked, return its id. If an
|
||||
--- invalid window was picked / user canceled, return nil. If there are
|
||||
--- no selectable windows, return -1.
|
||||
local function pick_win_id()
|
||||
local selectable = usable_win_ids()
|
||||
|
||||
-- If there are no selectable windows: return. If there's only 1, return it without picking.
|
||||
if #selectable == 0 then
|
||||
return -1
|
||||
end
|
||||
if #selectable == 1 then
|
||||
return selectable[1]
|
||||
end
|
||||
|
||||
local i = 1
|
||||
local win_opts = {}
|
||||
local win_map = {}
|
||||
local laststatus = vim.o.laststatus
|
||||
vim.o.laststatus = 2
|
||||
|
||||
local tabpage = vim.api.nvim_get_current_tabpage()
|
||||
local win_ids = vim.api.nvim_tabpage_list_wins(tabpage)
|
||||
|
||||
local not_selectable = vim.tbl_filter(function(id)
|
||||
return not vim.tbl_contains(selectable, id)
|
||||
end, win_ids)
|
||||
|
||||
if laststatus == 3 then
|
||||
for _, win_id in ipairs(not_selectable) do
|
||||
local ok_status, statusline = pcall(vim.api.nvim_win_get_option, win_id, "statusline")
|
||||
local ok_hl, winhl = pcall(vim.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
|
||||
vim.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(vim.api.nvim_win_get_option, id, "statusline")
|
||||
local ok_hl, winhl = pcall(vim.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
|
||||
|
||||
vim.api.nvim_win_set_option(id, "statusline", "%=" .. char .. "%=")
|
||||
vim.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"
|
||||
if vim.opt.cmdheight._value ~= 0 then
|
||||
print "Pick window: "
|
||||
end
|
||||
local _, resp = pcall(get_user_input_char)
|
||||
resp = (resp or ""):upper()
|
||||
utils.clear_prompt()
|
||||
|
||||
-- Restore window options
|
||||
for _, id in ipairs(selectable) do
|
||||
for opt, value in pairs(win_opts[id]) do
|
||||
vim.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
|
||||
vim.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()
|
||||
end
|
||||
vim.cmd("tabe " .. vim.fn.fnameescape(filename))
|
||||
end
|
||||
|
||||
local function on_preview(buf_loaded)
|
||||
if not buf_loaded then
|
||||
vim.bo.bufhidden = "delete"
|
||||
|
||||
vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, {
|
||||
group = vim.api.nvim_create_augroup("RemoveBufHidden", {}),
|
||||
buffer = vim.api.nvim_get_current_buf(),
|
||||
callback = function()
|
||||
vim.bo.bufhidden = ""
|
||||
end,
|
||||
once = true,
|
||||
})
|
||||
end
|
||||
view.focus()
|
||||
end
|
||||
|
||||
local function get_target_winid(mode, win_ids)
|
||||
local target_winid
|
||||
if not M.window_picker.enable or mode == "edit_no_picker" then
|
||||
target_winid = lib.target_winid
|
||||
|
||||
-- first available window
|
||||
if not vim.tbl_contains(win_ids, target_winid) then
|
||||
target_winid = first_win_id()
|
||||
end
|
||||
else
|
||||
-- pick a window
|
||||
target_winid = pick_win_id()
|
||||
if target_winid == nil then
|
||||
-- pick failed/cancelled
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if target_winid == -1 then
|
||||
target_winid = lib.target_winid
|
||||
end
|
||||
return target_winid
|
||||
end
|
||||
|
||||
-- This is only to avoid the BufEnter for nvim-tree to trigger
|
||||
-- which would cause find-file to run on an invalid file.
|
||||
local function set_current_win_no_autocmd(winid, autocmd)
|
||||
local eventignore = vim.opt.eventignore:get()
|
||||
vim.opt.eventignore:append(autocmd)
|
||||
vim.api.nvim_set_current_win(winid)
|
||||
vim.opt.eventignore = eventignore
|
||||
end
|
||||
|
||||
local function open_in_new_window(filename, mode, win_ids)
|
||||
if type(mode) ~= "string" then
|
||||
mode = ""
|
||||
end
|
||||
|
||||
local target_winid = get_target_winid(mode, win_ids)
|
||||
if not target_winid then
|
||||
return
|
||||
end
|
||||
|
||||
local create_new_window = #vim.api.nvim_list_wins() == 1
|
||||
local new_window_side = (view.View.side == "right") and "aboveleft" or "belowright"
|
||||
|
||||
-- Target is invalid or window does not exist in current tabpage: create new window
|
||||
if not vim.tbl_contains(win_ids, target_winid) then
|
||||
vim.cmd(new_window_side .. " vsplit")
|
||||
target_winid = vim.api.nvim_get_current_win()
|
||||
lib.target_winid = target_winid
|
||||
|
||||
-- No need to split, as we created a new window.
|
||||
create_new_window = false
|
||||
if mode:match "split$" then
|
||||
mode = "edit"
|
||||
end
|
||||
elseif not vim.o.hidden then
|
||||
-- If `hidden` is not enabled, check if buffer in target window is
|
||||
-- modified, and create new split if it is.
|
||||
local target_bufid = vim.api.nvim_win_get_buf(target_winid)
|
||||
if vim.api.nvim_buf_get_option(target_bufid, "modified") then
|
||||
mode = "vsplit"
|
||||
end
|
||||
end
|
||||
|
||||
local fname = vim.fn.fnameescape(filename)
|
||||
|
||||
local cmd
|
||||
if create_new_window then
|
||||
cmd = string.format("%s vsplit %s", new_window_side, fname)
|
||||
elseif mode:match "split$" then
|
||||
cmd = string.format("%s %s", mode, fname)
|
||||
else
|
||||
cmd = string.format("edit %s", fname)
|
||||
end
|
||||
|
||||
if mode == "preview" and view.View.float.enable then
|
||||
-- ignore "WinLeave" autocmd on preview
|
||||
-- because the registered "WinLeave"
|
||||
-- will kill the floating window immediately
|
||||
set_current_win_no_autocmd(target_winid, { "WinLeave", "BufEnter" })
|
||||
else
|
||||
set_current_win_no_autocmd(target_winid, { "BufEnter" })
|
||||
end
|
||||
|
||||
pcall(vim.cmd, cmd)
|
||||
lib.set_target_win()
|
||||
end
|
||||
|
||||
local function is_already_loaded(filename)
|
||||
for _, buf_id in ipairs(vim.api.nvim_list_bufs()) do
|
||||
if vim.api.nvim_buf_is_loaded(buf_id) and filename == vim.api.nvim_buf_get_name(buf_id) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function edit_in_current_buf(filename)
|
||||
require("nvim-tree.view").abandon_current_window()
|
||||
vim.cmd("edit " .. vim.fn.fnameescape(filename))
|
||||
end
|
||||
|
||||
function M.fn(mode, filename)
|
||||
if type(mode) ~= "string" then
|
||||
mode = ""
|
||||
end
|
||||
|
||||
if mode == "tabnew" then
|
||||
return open_file_in_tab(filename)
|
||||
end
|
||||
|
||||
if mode == "edit_in_place" then
|
||||
return edit_in_current_buf(filename)
|
||||
end
|
||||
|
||||
local tabpage = vim.api.nvim_get_current_tabpage()
|
||||
local win_ids = vim.api.nvim_tabpage_list_wins(tabpage)
|
||||
local buf_loaded = is_already_loaded(filename)
|
||||
|
||||
local found_win = utils.get_win_buf_from_path(filename)
|
||||
if found_win and mode == "preview" then
|
||||
return
|
||||
end
|
||||
|
||||
if not found_win then
|
||||
open_in_new_window(filename, mode, win_ids)
|
||||
else
|
||||
vim.api.nvim_set_current_win(found_win)
|
||||
vim.bo.bufhidden = ""
|
||||
end
|
||||
|
||||
if M.resize_window then
|
||||
view.resize()
|
||||
end
|
||||
|
||||
if mode == "preview" then
|
||||
return on_preview(buf_loaded)
|
||||
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
|
||||
@@ -1,4 +1,4 @@
|
||||
local uv = vim.loop
|
||||
local notify = require "nvim-tree.notify"
|
||||
|
||||
local M = {
|
||||
config = {
|
||||
@@ -10,7 +10,7 @@ local M = {
|
||||
|
||||
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."
|
||||
require("nvim-tree.utils").notify.warn "Cannot open file with system application. Unrecognized platform."
|
||||
return
|
||||
end
|
||||
|
||||
@@ -18,10 +18,10 @@ function M.fn(node)
|
||||
cmd = M.config.system_open.cmd,
|
||||
args = M.config.system_open.args,
|
||||
errors = "\n",
|
||||
stderr = uv.new_pipe(false),
|
||||
stderr = vim.loop.new_pipe(false),
|
||||
}
|
||||
table.insert(process.args, node.link_to or node.absolute_path)
|
||||
process.handle, process.pid = uv.spawn(
|
||||
process.handle, process.pid = vim.loop.spawn(
|
||||
process.cmd,
|
||||
{ args = process.args, stdio = { nil, nil, process.stderr }, detached = true },
|
||||
function(code)
|
||||
@@ -29,17 +29,16 @@ function M.fn(node)
|
||||
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)
|
||||
notify.warn(string.format("system_open failed with return code %d: %s", code, process.errors))
|
||||
end
|
||||
end
|
||||
)
|
||||
table.remove(process.args)
|
||||
if not process.handle then
|
||||
error("\n" .. process.pid .. "\nNvimTree system_open: failed to spawn process using '" .. process.cmd .. "'.")
|
||||
notify.warn(string.format("system_open failed to spawn command '%s': %s", process.cmd, process.pid))
|
||||
return
|
||||
end
|
||||
uv.read_start(process.stderr, function(err, data)
|
||||
vim.loop.read_start(process.stderr, function(err, data)
|
||||
if err then
|
||||
return
|
||||
end
|
||||
@@ -47,11 +46,11 @@ function M.fn(node)
|
||||
process.errors = process.errors .. data
|
||||
end
|
||||
end)
|
||||
uv.unref(process.handle)
|
||||
vim.loop.unref(process.handle)
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
M.config.system_open = opts or {}
|
||||
M.config.system_open = opts.system_open or {}
|
||||
|
||||
if #M.config.system_open.cmd == 0 then
|
||||
if M.config.is_windows then
|
||||
@@ -1,286 +0,0 @@
|
||||
-- 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
|
||||
@@ -6,19 +6,19 @@ local core = require "nvim-tree.core"
|
||||
|
||||
local M = {}
|
||||
|
||||
local function refresh_nodes(node, projects)
|
||||
local function refresh_nodes(node, projects, unloaded_bufnr)
|
||||
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 {})
|
||||
explorer_module.reload(node, projects[project_root] or {}, unloaded_bufnr)
|
||||
for _, _node in ipairs(node.nodes) do
|
||||
if _node.nodes and _node.open then
|
||||
refresh_nodes(_node, projects)
|
||||
refresh_nodes(_node, projects, unloaded_bufnr)
|
||||
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 project_root = git.get_project_root(parent_node.absolute_path)
|
||||
local status = projects[project_root] or {}
|
||||
for _, node in ipairs(parent_node.nodes) do
|
||||
if node.nodes then
|
||||
@@ -33,14 +33,16 @@ function M.reload_node_status(parent_node, projects)
|
||||
end
|
||||
|
||||
local event_running = false
|
||||
function M.reload_explorer()
|
||||
---@param _ table unused node passed by action
|
||||
---@param unloaded_bufnr number optional bufnr recently unloaded via BufUnload event
|
||||
function M.reload_explorer(_, unloaded_bufnr)
|
||||
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)
|
||||
refresh_nodes(core.get_explorer(), projects, unloaded_bufnr)
|
||||
if view.is_visible() then
|
||||
renderer.draw()
|
||||
end
|
||||
@@ -1,91 +0,0 @@
|
||||
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
|
||||
@@ -1,45 +0,0 @@
|
||||
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
|
||||
83
lua/nvim-tree/actions/root/change-dir.lua
Normal file
83
lua/nvim-tree/actions/root/change-dir.lua
Normal file
@@ -0,0 +1,83 @@
|
||||
local log = require "nvim-tree.log"
|
||||
local utils = require "nvim-tree.utils"
|
||||
local core = require "nvim-tree.core"
|
||||
|
||||
local M = {
|
||||
current_tab = vim.api.nvim_get_current_tabpage(),
|
||||
}
|
||||
|
||||
local function clean_input_cwd(name)
|
||||
name = vim.fn.fnameescape(name)
|
||||
local root_parent_cwd = vim.fn.fnamemodify(utils.path_remove_trailing(core.get_cwd()), ":h")
|
||||
if name == ".." and root_parent_cwd then
|
||||
return vim.fn.expand(root_parent_cwd)
|
||||
else
|
||||
return vim.fn.expand(name)
|
||||
end
|
||||
end
|
||||
|
||||
local function is_window_event(new_tabpage)
|
||||
local is_event_scope_window = vim.v.event.scope == "window" or vim.v.event.changed_window
|
||||
return is_event_scope_window and new_tabpage == M.current_tab
|
||||
end
|
||||
|
||||
local function prevent_cwd_change(foldername)
|
||||
local is_same_cwd = foldername == core.get_cwd()
|
||||
local is_restricted_above = M.options.restrict_above_cwd and foldername < vim.fn.getcwd(-1, -1)
|
||||
return is_same_cwd or is_restricted_above
|
||||
end
|
||||
|
||||
function M.fn(input_cwd, with_open)
|
||||
if not core.get_explorer() then
|
||||
return
|
||||
end
|
||||
|
||||
local new_tabpage = vim.api.nvim_get_current_tabpage()
|
||||
if is_window_event(new_tabpage) then
|
||||
return
|
||||
end
|
||||
|
||||
local foldername = clean_input_cwd(input_cwd)
|
||||
if prevent_cwd_change(foldername) then
|
||||
return
|
||||
end
|
||||
|
||||
M.current_tab = new_tabpage
|
||||
M.force_dirchange(foldername, with_open)
|
||||
end
|
||||
|
||||
local function cd(global, path)
|
||||
vim.cmd((global and "cd " or "lcd ") .. vim.fn.fnameescape(path))
|
||||
end
|
||||
|
||||
local function should_change_dir()
|
||||
return M.options.enable and vim.tbl_isempty(vim.v.event)
|
||||
end
|
||||
|
||||
local function add_profiling_to(f)
|
||||
return function(foldername, should_open_view)
|
||||
local ps = log.profile_start("change dir %s", foldername)
|
||||
f(foldername, should_open_view)
|
||||
log.profile_end(ps, "change dir %s", foldername)
|
||||
end
|
||||
end
|
||||
|
||||
M.force_dirchange = add_profiling_to(function(foldername, should_open_view)
|
||||
if should_change_dir() then
|
||||
cd(M.options.global, foldername)
|
||||
end
|
||||
|
||||
core.init(foldername)
|
||||
|
||||
if should_open_view then
|
||||
require("nvim-tree.lib").open()
|
||||
else
|
||||
require("nvim-tree.renderer").draw()
|
||||
end
|
||||
end)
|
||||
|
||||
function M.setup(options)
|
||||
M.options = options.actions.change_dir
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -5,11 +5,11 @@ local M = {}
|
||||
|
||||
function M.fn(node)
|
||||
if not node or node.name == ".." then
|
||||
return require("nvim-tree.actions.change-dir").fn ".."
|
||||
return require("nvim-tree.actions.root.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)
|
||||
require("nvim-tree.actions.root.change-dir").fn(newdir)
|
||||
return require("nvim-tree.actions.finders.find-file").fn(node.absolute_path)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
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
|
||||
@@ -1,90 +0,0 @@
|
||||
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
|
||||
46
lua/nvim-tree/actions/tree-modifiers/collapse-all.lua
Normal file
46
lua/nvim-tree/actions/tree-modifiers/collapse-all.lua
Normal file
@@ -0,0 +1,46 @@
|
||||
local renderer = require "nvim-tree.renderer"
|
||||
local utils = require "nvim-tree.utils"
|
||||
local core = require "nvim-tree.core"
|
||||
local Iterator = require "nvim-tree.iterators.node-iterator"
|
||||
|
||||
local M = {}
|
||||
|
||||
local function buf_match()
|
||||
local buffer_paths = vim.tbl_map(function(buffer)
|
||||
return vim.api.nvim_buf_get_name(buffer)
|
||||
end, vim.api.nvim_list_bufs())
|
||||
|
||||
return function(path)
|
||||
for _, buffer_path in ipairs(buffer_paths) do
|
||||
local matches = utils.str_find(buffer_path, path)
|
||||
if matches then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function M.fn(keep_buffers)
|
||||
if not core.get_explorer() then
|
||||
return
|
||||
end
|
||||
|
||||
local matches = buf_match()
|
||||
|
||||
Iterator.builder(core.get_explorer().nodes)
|
||||
:hidden()
|
||||
:applier(function(node)
|
||||
if node.nodes ~= nil then
|
||||
node.open = keep_buffers == true and matches(node.absolute_path)
|
||||
end
|
||||
end)
|
||||
:recursor(function(n)
|
||||
return n.nodes
|
||||
end)
|
||||
:iterate()
|
||||
|
||||
renderer.draw()
|
||||
end
|
||||
|
||||
return M
|
||||
71
lua/nvim-tree/actions/tree-modifiers/expand-all.lua
Normal file
71
lua/nvim-tree/actions/tree-modifiers/expand-all.lua
Normal file
@@ -0,0 +1,71 @@
|
||||
local core = require "nvim-tree.core"
|
||||
local renderer = require "nvim-tree.renderer"
|
||||
local Iterator = require "nvim-tree.iterators.node-iterator"
|
||||
local notify = require "nvim-tree.notify"
|
||||
|
||||
local M = {}
|
||||
|
||||
local function to_lookup_table(list)
|
||||
local table = {}
|
||||
for _, element in ipairs(list) do
|
||||
table[element] = true
|
||||
end
|
||||
|
||||
return table
|
||||
end
|
||||
|
||||
local function expand(node)
|
||||
node.open = true
|
||||
if #node.nodes == 0 then
|
||||
core.get_explorer():expand(node)
|
||||
end
|
||||
end
|
||||
|
||||
local function should_expand(expansion_count, node)
|
||||
local should_halt = expansion_count >= M.MAX_FOLDER_DISCOVERY
|
||||
local should_exclude = M.EXCLUDE[node.name]
|
||||
return not should_halt and node.nodes and not node.open and not should_exclude
|
||||
end
|
||||
|
||||
local function gen_iterator()
|
||||
local expansion_count = 0
|
||||
|
||||
return function(parent)
|
||||
if parent.parent and parent.nodes and not parent.open then
|
||||
expansion_count = expansion_count + 1
|
||||
expand(parent)
|
||||
end
|
||||
|
||||
Iterator.builder(parent.nodes)
|
||||
:hidden()
|
||||
:applier(function(node)
|
||||
if should_expand(expansion_count, node) then
|
||||
expansion_count = expansion_count + 1
|
||||
expand(node)
|
||||
end
|
||||
end)
|
||||
:recursor(function(node)
|
||||
return expansion_count < M.MAX_FOLDER_DISCOVERY and node.open and node.nodes
|
||||
end)
|
||||
:iterate()
|
||||
|
||||
if expansion_count >= M.MAX_FOLDER_DISCOVERY then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M.fn(base_node)
|
||||
local node = base_node.nodes and base_node or core.get_explorer()
|
||||
if gen_iterator()(node) then
|
||||
notify.warn("expansion iteration was halted after " .. M.MAX_FOLDER_DISCOVERY .. " discovered folders")
|
||||
end
|
||||
renderer.draw()
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
M.MAX_FOLDER_DISCOVERY = opts.actions.expand_all.max_folder_discovery
|
||||
M.EXCLUDE = to_lookup_table(opts.actions.expand_all.exclude)
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -1,7 +1,7 @@
|
||||
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 reloaders = require "nvim-tree.actions.reloaders.reloaders"
|
||||
|
||||
local M = {}
|
||||
|
||||
@@ -15,6 +15,16 @@ function M.git_ignored()
|
||||
return reloaders.reload_explorer()
|
||||
end
|
||||
|
||||
function M.git_clean()
|
||||
filters.config.filter_git_clean = not filters.config.filter_git_clean
|
||||
return reloaders.reload_explorer()
|
||||
end
|
||||
|
||||
function M.no_buffer()
|
||||
filters.config.filter_no_buffer = not filters.config.filter_no_buffer
|
||||
return reloaders.reload_explorer()
|
||||
end
|
||||
|
||||
function M.dotfiles()
|
||||
filters.config.filter_dotfiles = not filters.config.filter_dotfiles
|
||||
return reloaders.reload_explorer()
|
||||
130
lua/nvim-tree/api.lua
Normal file
130
lua/nvim-tree/api.lua
Normal file
@@ -0,0 +1,130 @@
|
||||
local Api = {
|
||||
tree = {},
|
||||
node = { navigate = { sibling = {}, git = {}, diagnostics = {} }, run = {}, open = {} },
|
||||
events = {},
|
||||
marks = { bulk = {}, navigate = {} },
|
||||
fs = { copy = {} },
|
||||
git = {},
|
||||
live_filter = {},
|
||||
}
|
||||
|
||||
local function inject_node(f)
|
||||
return function(node, ...)
|
||||
node = node or require("nvim-tree.lib").get_node_at_cursor()
|
||||
f(node, ...)
|
||||
end
|
||||
end
|
||||
|
||||
Api.tree.open = require("nvim-tree").open
|
||||
Api.tree.toggle = require("nvim-tree").toggle
|
||||
Api.tree.close = require("nvim-tree.view").close
|
||||
Api.tree.close_in_this_tab = require("nvim-tree.view").close_this_tab_only
|
||||
Api.tree.close_in_all_tabs = require("nvim-tree.view").close_all_tabs
|
||||
Api.tree.focus = require("nvim-tree").focus
|
||||
Api.tree.reload = require("nvim-tree.actions.reloaders.reloaders").reload_explorer
|
||||
Api.tree.change_root = require("nvim-tree").change_dir
|
||||
Api.tree.change_root_to_node = inject_node(function(node)
|
||||
if node.name == ".." then
|
||||
require("nvim-tree.actions.root.change-dir").fn ".."
|
||||
elseif node.nodes ~= nil then
|
||||
require("nvim-tree.actions.root.change-dir").fn(require("nvim-tree.lib").get_last_group_node(node).absolute_path)
|
||||
end
|
||||
end)
|
||||
Api.tree.change_root_to_parent = inject_node(require("nvim-tree.actions.root.dir-up").fn)
|
||||
Api.tree.get_node_under_cursor = require("nvim-tree.lib").get_node_at_cursor
|
||||
Api.tree.get_nodes = require("nvim-tree.lib").get_nodes
|
||||
Api.tree.find_file = require("nvim-tree.actions.finders.find-file").fn
|
||||
Api.tree.search_node = require("nvim-tree.actions.finders.search-node").fn
|
||||
Api.tree.collapse_all = require("nvim-tree.actions.tree-modifiers.collapse-all").fn
|
||||
Api.tree.expand_all = inject_node(require("nvim-tree.actions.tree-modifiers.expand-all").fn)
|
||||
Api.tree.toggle_gitignore_filter = require("nvim-tree.actions.tree-modifiers.toggles").git_ignored
|
||||
Api.tree.toggle_git_clean_filter = require("nvim-tree.actions.tree-modifiers.toggles").git_clean
|
||||
Api.tree.toggle_no_buffer_filter = require("nvim-tree.actions.tree-modifiers.toggles").no_buffer
|
||||
Api.tree.toggle_custom_filter = require("nvim-tree.actions.tree-modifiers.toggles").custom
|
||||
Api.tree.toggle_hidden_filter = require("nvim-tree.actions.tree-modifiers.toggles").dotfiles
|
||||
Api.tree.toggle_help = require("nvim-tree.actions.tree-modifiers.toggles").help
|
||||
|
||||
Api.fs.create = inject_node(require("nvim-tree.actions.fs.create-file").fn)
|
||||
Api.fs.remove = inject_node(require("nvim-tree.actions.fs.remove-file").fn)
|
||||
Api.fs.trash = inject_node(require("nvim-tree.actions.fs.trash").fn)
|
||||
Api.fs.rename_node = inject_node(require("nvim-tree.actions.fs.rename-file").fn ":t")
|
||||
Api.fs.rename = inject_node(require("nvim-tree.actions.fs.rename-file").fn ":t")
|
||||
Api.fs.rename_sub = inject_node(require("nvim-tree.actions.fs.rename-file").fn ":p")
|
||||
Api.fs.rename_basename = inject_node(require("nvim-tree.actions.fs.rename-file").fn ":t:r")
|
||||
Api.fs.cut = inject_node(require("nvim-tree.actions.fs.copy-paste").cut)
|
||||
Api.fs.paste = inject_node(require("nvim-tree.actions.fs.copy-paste").paste)
|
||||
Api.fs.clear_clipboard = require("nvim-tree.actions.fs.copy-paste").clear_clipboard
|
||||
Api.fs.print_clipboard = require("nvim-tree.actions.fs.copy-paste").print_clipboard
|
||||
Api.fs.copy.node = inject_node(require("nvim-tree.actions.fs.copy-paste").copy)
|
||||
Api.fs.copy.absolute_path = inject_node(require("nvim-tree.actions.fs.copy-paste").copy_absolute_path)
|
||||
Api.fs.copy.filename = inject_node(require("nvim-tree.actions.fs.copy-paste").copy_filename)
|
||||
Api.fs.copy.relative_path = inject_node(require("nvim-tree.actions.fs.copy-paste").copy_path)
|
||||
|
||||
local function edit(mode, node)
|
||||
local path = node.absolute_path
|
||||
if node.link_to and not node.nodes then
|
||||
path = node.link_to
|
||||
end
|
||||
require("nvim-tree.actions.node.open-file").fn(mode, path)
|
||||
end
|
||||
|
||||
local function open_or_expand_or_dir_up(mode)
|
||||
return function(node)
|
||||
if node.name == ".." then
|
||||
require("nvim-tree.actions.root.change-dir").fn ".."
|
||||
elseif node.nodes then
|
||||
require("nvim-tree.lib").expand_or_collapse(node)
|
||||
else
|
||||
edit(mode, node)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function open_preview(node)
|
||||
if node.nodes or node.name == ".." then
|
||||
return
|
||||
end
|
||||
|
||||
edit("preview", node)
|
||||
end
|
||||
|
||||
Api.node.open.edit = inject_node(open_or_expand_or_dir_up "edit")
|
||||
Api.node.open.replace_tree_buffer = inject_node(open_or_expand_or_dir_up "edit_in_place")
|
||||
Api.node.open.no_window_picker = inject_node(open_or_expand_or_dir_up "edit_no_picker")
|
||||
Api.node.open.vertical = inject_node(open_or_expand_or_dir_up "vsplit")
|
||||
Api.node.open.horizontal = inject_node(open_or_expand_or_dir_up "split")
|
||||
Api.node.open.tab = inject_node(open_or_expand_or_dir_up "tabnew")
|
||||
Api.node.open.preview = inject_node(open_preview)
|
||||
|
||||
Api.node.show_info_popup = inject_node(require("nvim-tree.actions.node.file-popup").toggle_file_info)
|
||||
Api.node.run.cmd = inject_node(require("nvim-tree.actions.node.run-command").run_file_command)
|
||||
Api.node.run.system = inject_node(require("nvim-tree.actions.node.system-open").fn)
|
||||
Api.node.navigate.sibling.next = inject_node(require("nvim-tree.actions.moves.sibling").fn "next")
|
||||
Api.node.navigate.sibling.prev = inject_node(require("nvim-tree.actions.moves.sibling").fn "prev")
|
||||
Api.node.navigate.sibling.first = inject_node(require("nvim-tree.actions.moves.sibling").fn "first")
|
||||
Api.node.navigate.sibling.last = inject_node(require("nvim-tree.actions.moves.sibling").fn "last")
|
||||
Api.node.navigate.parent = inject_node(require("nvim-tree.actions.moves.parent").fn(false))
|
||||
Api.node.navigate.parent_close = inject_node(require("nvim-tree.actions.moves.parent").fn(true))
|
||||
Api.node.navigate.git.next = inject_node(require("nvim-tree.actions.moves.item").fn("next", "git"))
|
||||
Api.node.navigate.git.prev = inject_node(require("nvim-tree.actions.moves.item").fn("prev", "git"))
|
||||
Api.node.navigate.diagnostics.next = inject_node(require("nvim-tree.actions.moves.item").fn("next", "diag"))
|
||||
Api.node.navigate.diagnostics.prev = inject_node(require("nvim-tree.actions.moves.item").fn("prev", "diag"))
|
||||
|
||||
Api.git.reload = require("nvim-tree.actions.reloaders.reloaders").reload_git
|
||||
|
||||
Api.events.subscribe = require("nvim-tree.events").subscribe
|
||||
Api.events.Event = require("nvim-tree.events").Event
|
||||
|
||||
Api.live_filter.start = require("nvim-tree.live-filter").start_filtering
|
||||
Api.live_filter.clear = require("nvim-tree.live-filter").clear_filter
|
||||
|
||||
Api.marks.get = inject_node(require("nvim-tree.marks").get_mark)
|
||||
Api.marks.list = require("nvim-tree.marks").get_marks
|
||||
Api.marks.toggle = inject_node(require("nvim-tree.marks").toggle_mark)
|
||||
Api.marks.clear = require("nvim-tree.marks").clear_marks
|
||||
Api.marks.bulk.move = require("nvim-tree.marks.bulk-move").bulk_move
|
||||
Api.marks.navigate.next = require("nvim-tree.marks.navigation").next
|
||||
Api.marks.navigate.prev = require("nvim-tree.marks.navigation").prev
|
||||
Api.marks.navigate.select = require("nvim-tree.marks.navigation").select
|
||||
|
||||
return Api
|
||||
@@ -1,6 +1,3 @@
|
||||
local api = vim.api
|
||||
local icons = require "nvim-tree.renderer.icon-config"
|
||||
|
||||
local M = {}
|
||||
|
||||
local function get_color_from_hl(hl_name, fallback)
|
||||
@@ -52,6 +49,10 @@ local function get_hl_groups()
|
||||
GitNew = { fg = colors.yellow },
|
||||
|
||||
WindowPicker = { gui = "bold", fg = "#ededed", bg = "#4493c8" },
|
||||
LiveFilterPrefix = { gui = "bold", fg = colors.purple },
|
||||
LiveFilterValue = { gui = "bold", fg = "#fff" },
|
||||
|
||||
Bookmark = { fg = colors.green },
|
||||
}
|
||||
end
|
||||
|
||||
@@ -60,9 +61,13 @@ local function get_links()
|
||||
FolderName = "Directory",
|
||||
EmptyFolderName = "Directory",
|
||||
OpenedFolderName = "Directory",
|
||||
OpenedFolderIcon = "NvimTreeFolderIcon",
|
||||
ClosedFolderIcon = "NvimTreeFolderIcon",
|
||||
Normal = "Normal",
|
||||
NormalNC = "NvimTreeNormal",
|
||||
EndOfBuffer = "EndOfBuffer",
|
||||
CursorLineNr = "CursorLineNr",
|
||||
LineNr = "LineNr",
|
||||
CursorLine = "CursorLine",
|
||||
VertSplit = "VertSplit",
|
||||
WinSeparator = "NvimTreeVertSplit",
|
||||
@@ -73,6 +78,7 @@ local function get_links()
|
||||
FileMerge = "NvimTreeGitMerge",
|
||||
FileStaged = "NvimTreeGitStaged",
|
||||
FileDeleted = "NvimTreeGitDeleted",
|
||||
FileIgnored = "NvimTreeGitIgnored",
|
||||
Popup = "Normal",
|
||||
GitIgnored = "Comment",
|
||||
StatusLine = "StatusLine",
|
||||
@@ -82,20 +88,17 @@ local function get_links()
|
||||
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)
|
||||
vim.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)
|
||||
vim.api.nvim_command("hi def link NvimTree" .. k .. " " .. d)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ 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)
|
||||
return string.format("<cmd>lua require'nvim-tree.actions.dispatch'.dispatch('%s')<CR>", callback_name)
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
local events = require "nvim-tree.events"
|
||||
local explorer = require "nvim-tree.explorer"
|
||||
local live_filter = require "nvim-tree.live-filter"
|
||||
local view = require "nvim-tree.view"
|
||||
local log = require "nvim-tree.log"
|
||||
|
||||
local M = {}
|
||||
|
||||
@@ -8,11 +10,18 @@ TreeExplorer = nil
|
||||
local first_init_done = false
|
||||
|
||||
function M.init(foldername)
|
||||
local pn = string.format("core init %s", foldername)
|
||||
local ps = log.profile_start(pn)
|
||||
|
||||
if TreeExplorer then
|
||||
TreeExplorer:destroy()
|
||||
end
|
||||
TreeExplorer = explorer.Explorer.new(foldername)
|
||||
if not first_init_done then
|
||||
events._dispatch_ready()
|
||||
first_init_done = true
|
||||
end
|
||||
log.profile_end(ps, pn)
|
||||
end
|
||||
|
||||
function M.get_explorer()
|
||||
@@ -20,7 +29,7 @@ function M.get_explorer()
|
||||
end
|
||||
|
||||
function M.get_cwd()
|
||||
return TreeExplorer.cwd
|
||||
return TreeExplorer.absolute_path
|
||||
end
|
||||
|
||||
function M.get_nodes_starting_line()
|
||||
@@ -28,6 +37,9 @@ function M.get_nodes_starting_line()
|
||||
if view.is_root_folder_visible(M.get_cwd()) then
|
||||
offset = offset + 1
|
||||
end
|
||||
if live_filter.filter then
|
||||
return offset + 1
|
||||
end
|
||||
return offset
|
||||
end
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
local a = vim.api
|
||||
local utils = require "nvim-tree.utils"
|
||||
local view = require "nvim-tree.view"
|
||||
local core = require "nvim-tree.core"
|
||||
@@ -8,16 +7,6 @@ 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" },
|
||||
@@ -28,39 +17,23 @@ local sign_names = {
|
||||
|
||||
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
|
||||
if not vim.api.nvim_buf_is_valid(buf) or not vim.api.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 })
|
||||
vim.fn.sign_place(0, GROUP, sign_name, buf, { lnum = linenr, priority = 2 })
|
||||
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
|
||||
for _, diagnostic in ipairs(vim.diagnostic.get(nil, { severity = M.severity })) do
|
||||
local buf = diagnostic.bufnr
|
||||
if vim.api.nvim_buf_is_valid(buf) then
|
||||
local bufname = vim.api.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
|
||||
@@ -116,36 +89,45 @@ 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")
|
||||
utils.debounce("diagnostics", M.debounce_delay, function()
|
||||
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
|
||||
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 nodes_by_line = utils.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line())
|
||||
for line, node in pairs(nodes_by_line) do
|
||||
local nodepath = utils.canonical_path(node.absolute_path)
|
||||
log.line("diagnostics", " %d checking nodepath '%s'", line, nodepath)
|
||||
if M.show_on_dirs and vim.startswith(bufpath, nodepath) then
|
||||
log.line("diagnostics", " matched fold node '%s'", node.absolute_path)
|
||||
add_sign(line, severity)
|
||||
elseif nodepath == bufpath then
|
||||
log.line("diagnostics", " matched file node '%s'", node.absolute_path)
|
||||
add_sign(line, severity)
|
||||
M.clear()
|
||||
|
||||
local nodes_by_line = utils.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line())
|
||||
for _, node in pairs(nodes_by_line) do
|
||||
node.diag_status = nil
|
||||
end
|
||||
|
||||
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
|
||||
for line, node in pairs(nodes_by_line) do
|
||||
local nodepath = utils.canonical_path(node.absolute_path)
|
||||
log.line("diagnostics", " %d checking nodepath '%s'", line, nodepath)
|
||||
if M.show_on_dirs and vim.startswith(bufpath, nodepath) and (not node.open or M.show_on_open_dirs) then
|
||||
log.line("diagnostics", " matched fold node '%s'", node.absolute_path)
|
||||
node.diag_status = severity
|
||||
add_sign(line, severity)
|
||||
elseif nodepath == bufpath then
|
||||
log.line("diagnostics", " matched file node '%s'", node.absolute_path)
|
||||
node.diag_status = severity
|
||||
add_sign(line, severity)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
log.profile_end(ps, "diagnostics update")
|
||||
log.profile_end(ps, "diagnostics update")
|
||||
end)
|
||||
end
|
||||
|
||||
local links = {
|
||||
@@ -157,7 +139,15 @@ local links = {
|
||||
|
||||
function M.setup(opts)
|
||||
M.enable = opts.diagnostics.enable
|
||||
M.debounce_delay = opts.diagnostics.debounce_delay
|
||||
M.severity = opts.diagnostics.severity
|
||||
|
||||
if M.enable then
|
||||
log.line("diagnostics", "setup")
|
||||
end
|
||||
|
||||
M.show_on_dirs = opts.diagnostics.show_on_dirs
|
||||
M.show_on_open_dirs = opts.diagnostics.show_on_open_dirs
|
||||
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] })
|
||||
@@ -166,12 +156,6 @@ function M.setup(opts)
|
||||
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
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
local notify = require "nvim-tree.notify"
|
||||
|
||||
local M = {}
|
||||
|
||||
local global_handlers = {}
|
||||
|
||||
local Event = {
|
||||
M.Event = {
|
||||
Ready = "Ready",
|
||||
WillRenameNode = "WillRenameNode",
|
||||
NodeRenamed = "NodeRenamed",
|
||||
TreeOpen = "TreeOpen",
|
||||
TreeClose = "TreeClose",
|
||||
@@ -11,13 +14,14 @@ local Event = {
|
||||
FileRemoved = "FileRemoved",
|
||||
FolderCreated = "FolderCreated",
|
||||
FolderRemoved = "FolderRemoved",
|
||||
Resize = "Resize",
|
||||
}
|
||||
|
||||
local function get_handlers(event_name)
|
||||
return global_handlers[event_name] or {}
|
||||
end
|
||||
|
||||
local function register_handler(event_name, handler)
|
||||
function M.subscribe(event_name, handler)
|
||||
local handlers = get_handlers(event_name)
|
||||
table.insert(handlers, handler)
|
||||
global_handlers[event_name] = handlers
|
||||
@@ -27,103 +31,104 @@ 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))
|
||||
notify.error("Handler for event " .. event_name .. " errored. " .. vim.inspect(error))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--@private
|
||||
function M._dispatch_ready()
|
||||
dispatch(Event.Ready)
|
||||
dispatch(M.Event.Ready)
|
||||
end
|
||||
|
||||
--@private
|
||||
function M._dispatch_will_rename_node(old_name, new_name)
|
||||
dispatch(M.Event.WillRenameNode, { old_name = old_name, new_name = new_name })
|
||||
end
|
||||
|
||||
--@private
|
||||
function M._dispatch_node_renamed(old_name, new_name)
|
||||
dispatch(Event.NodeRenamed, { old_name = old_name, new_name = new_name })
|
||||
dispatch(M.Event.NodeRenamed, { old_name = old_name, new_name = new_name })
|
||||
end
|
||||
|
||||
--@private
|
||||
function M._dispatch_file_removed(fname)
|
||||
dispatch(Event.FileRemoved, { fname = fname })
|
||||
dispatch(M.Event.FileRemoved, { fname = fname })
|
||||
end
|
||||
|
||||
--@private
|
||||
function M._dispatch_file_created(fname)
|
||||
dispatch(Event.FileCreated, { fname = fname })
|
||||
dispatch(M.Event.FileCreated, { fname = fname })
|
||||
end
|
||||
|
||||
--@private
|
||||
function M._dispatch_folder_created(folder_name)
|
||||
dispatch(Event.FolderCreated, { folder_name = folder_name })
|
||||
dispatch(M.Event.FolderCreated, { folder_name = folder_name })
|
||||
end
|
||||
|
||||
--@private
|
||||
function M._dispatch_folder_removed(folder_name)
|
||||
dispatch(Event.FolderRemoved, { folder_name = folder_name })
|
||||
dispatch(M.Event.FolderRemoved, { folder_name = folder_name })
|
||||
end
|
||||
|
||||
--@private
|
||||
function M._dispatch_on_tree_open()
|
||||
dispatch(Event.TreeOpen, nil)
|
||||
dispatch(M.Event.TreeOpen, nil)
|
||||
end
|
||||
|
||||
--@private
|
||||
function M._dispatch_on_tree_close()
|
||||
dispatch(Event.TreeClose, nil)
|
||||
dispatch(M.Event.TreeClose, nil)
|
||||
end
|
||||
|
||||
--Registers a handler for the Ready event.
|
||||
--@param handler (function) Handler with the signature `function()`
|
||||
--@private
|
||||
function M._dispatch_on_tree_resize(size)
|
||||
dispatch(M.Event.Resize, size)
|
||||
end
|
||||
|
||||
--- @deprecated
|
||||
function M.on_nvim_tree_ready(handler)
|
||||
register_handler(Event.Ready, handler)
|
||||
M.subscribe(M.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.
|
||||
--- @deprecated
|
||||
function M.on_node_renamed(handler)
|
||||
register_handler(Event.NodeRenamed, handler)
|
||||
M.subscribe(M.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.
|
||||
--- @deprecated
|
||||
function M.on_file_created(handler)
|
||||
register_handler(Event.FileCreated, handler)
|
||||
M.subscribe(M.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.
|
||||
--- @deprecated
|
||||
function M.on_file_removed(handler)
|
||||
register_handler(Event.FileRemoved, handler)
|
||||
M.subscribe(M.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.
|
||||
--- @deprecated
|
||||
function M.on_folder_created(handler)
|
||||
register_handler(Event.FolderCreated, handler)
|
||||
M.subscribe(M.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.
|
||||
--- @deprecated
|
||||
function M.on_folder_removed(handler)
|
||||
register_handler(Event.FolderRemoved, handler)
|
||||
M.subscribe(M.Event.FolderRemoved, handler)
|
||||
end
|
||||
|
||||
--Registers a handler for the TreeOpen event.
|
||||
--@param handler (function) Handler with the signature function(payload)
|
||||
--- @deprecated
|
||||
function M.on_tree_open(handler)
|
||||
register_handler(Event.TreeOpen, handler)
|
||||
M.subscribe(M.Event.TreeOpen, handler)
|
||||
end
|
||||
|
||||
--Registers a handler for the TreeClose event.
|
||||
--@param handler (function) Handler with the signature function(payload)
|
||||
--- @deprecated
|
||||
function M.on_tree_close(handler)
|
||||
register_handler(Event.TreeClose, handler)
|
||||
M.subscribe(M.Event.TreeClose, handler)
|
||||
end
|
||||
|
||||
--- @deprecated
|
||||
function M.on_tree_resize(handler)
|
||||
M.subscribe(M.Event.Resize, handler)
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
local uv = vim.loop
|
||||
|
||||
local M = {}
|
||||
|
||||
local function 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
|
||||
if file_status then
|
||||
return file_status
|
||||
end
|
||||
|
||||
return status.dirs and status.dirs[absolute_path]
|
||||
end
|
||||
|
||||
local function get_git_status(parent_ignored, status, absolute_path)
|
||||
@@ -16,7 +18,7 @@ local function get_git_status(parent_ignored, status, absolute_path)
|
||||
end
|
||||
|
||||
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")
|
||||
return #node.nodes == 1 and node.nodes[1].nodes and vim.loop.fs_access(node.nodes[1].absolute_path, "R")
|
||||
end
|
||||
|
||||
function M.update_git_status(node, parent_ignored, status)
|
||||
@@ -37,4 +39,36 @@ function M.update_git_status(node, parent_ignored, status)
|
||||
end
|
||||
end
|
||||
|
||||
function M.shows_git_status(node)
|
||||
if not node.git_status then
|
||||
-- status doesn't exist
|
||||
return false
|
||||
elseif not node.nodes then
|
||||
-- status exist and is a file
|
||||
return true
|
||||
elseif not node.open then
|
||||
-- status exist, is a closed dir
|
||||
return M.config.git.show_on_dirs
|
||||
else
|
||||
-- status exist, is a open dir
|
||||
return M.config.git.show_on_dirs and M.config.git.show_on_open_dirs
|
||||
end
|
||||
end
|
||||
|
||||
function M.node_destroy(node)
|
||||
if not node then
|
||||
return
|
||||
end
|
||||
|
||||
if node.watcher then
|
||||
node.watcher:destroy()
|
||||
end
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
M.config = {
|
||||
git = opts.git,
|
||||
}
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -1,82 +1,92 @@
|
||||
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 live_filter = require "nvim-tree.live-filter"
|
||||
local notify = require "nvim-tree.notify"
|
||||
local log = require "nvim-tree.log"
|
||||
|
||||
local M = {}
|
||||
|
||||
local function get_type_from(type_, cwd)
|
||||
return type_ or (uv.fs_stat(cwd) or {}).type
|
||||
return type_ or (vim.loop.fs_stat(cwd) or {}).type
|
||||
end
|
||||
|
||||
local function populate_children(handle, cwd, node, status)
|
||||
local function populate_children(handle, cwd, node, git_status)
|
||||
local node_ignored = node.git_status == "!!"
|
||||
local nodes_by_path = utils.bool_record(node.nodes, "absolute_path")
|
||||
local filter_status = filters.prepare(git_status)
|
||||
while true do
|
||||
local name, t = uv.fs_scandir_next(handle)
|
||||
local name, t = vim.loop.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 not filters.should_filter(abs, filter_status) and not nodes_by_path[abs] then
|
||||
local child = nil
|
||||
if t == "directory" and uv.fs_access(abs, "R") then
|
||||
child = builders.folder(node, abs, name, status, node_ignored)
|
||||
if t == "directory" and vim.loop.fs_access(abs, "R") then
|
||||
child = builders.folder(node, abs, name)
|
||||
elseif t == "file" then
|
||||
child = builders.file(node, abs, name, status, node_ignored)
|
||||
child = builders.file(node, abs, name)
|
||||
elseif t == "link" then
|
||||
local link = builders.link(node, abs, name, status, node_ignored)
|
||||
local link = builders.link(node, abs, name)
|
||||
if link.link_to ~= nil then
|
||||
child = link
|
||||
end
|
||||
end
|
||||
if child then
|
||||
table.insert(node.nodes, child)
|
||||
common.update_git_status(child, node_ignored, status)
|
||||
nodes_by_path[child.absolute_path] = true
|
||||
common.update_git_status(child, node_ignored, git_status)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function get_dir_handle(cwd)
|
||||
local handle = uv.fs_scandir(cwd)
|
||||
local handle = vim.loop.fs_scandir(cwd)
|
||||
if type(handle) == "string" then
|
||||
api.nvim_err_writeln(handle)
|
||||
notify.error(handle)
|
||||
return
|
||||
end
|
||||
return handle
|
||||
end
|
||||
|
||||
function M.explore(node, status)
|
||||
local cwd = node.cwd or node.link_to or node.absolute_path
|
||||
local cwd = node.link_to or node.absolute_path
|
||||
local handle = get_dir_handle(cwd)
|
||||
if not handle then
|
||||
return
|
||||
end
|
||||
|
||||
local pn = string.format("explore init %s", node.absolute_path)
|
||||
local ps = log.profile_start(pn)
|
||||
|
||||
populate_children(handle, cwd, node, status)
|
||||
|
||||
local is_root = node.cwd ~= nil
|
||||
local is_root = not node.parent
|
||||
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
|
||||
if M.config.group_empty 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 {}
|
||||
|
||||
log.profile_end(ps, pn)
|
||||
return ns
|
||||
end
|
||||
|
||||
sorters.merge_sort(node.nodes, sorters.node_comparator)
|
||||
live_filter.apply_filter(node)
|
||||
|
||||
log.profile_end(ps, pn)
|
||||
return node.nodes
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
M.config = opts.renderer
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -14,26 +14,63 @@ local function is_excluded(path)
|
||||
return false
|
||||
end
|
||||
|
||||
---Check if the given path should be ignored.
|
||||
---Check if the given path is git clean/ignored
|
||||
---@param path string Absolute path
|
||||
---@param git_status table from prepare
|
||||
---@return boolean
|
||||
function M.should_ignore(path)
|
||||
local basename = utils.path_basename(path)
|
||||
|
||||
if is_excluded(path) then
|
||||
local function git(path, git_status)
|
||||
if type(git_status) ~= "table" or type(git_status.files) ~= "table" or type(git_status.dirs) ~= "table" then
|
||||
return false
|
||||
end
|
||||
|
||||
if M.config.filter_dotfiles then
|
||||
if basename:sub(1, 1) == "." then
|
||||
return true
|
||||
-- default status to clean
|
||||
local status = git_status.files[path] or git_status.dirs[path] or " "
|
||||
|
||||
-- filter ignored; overrides clean as they are effectively dirty
|
||||
if M.config.filter_git_ignored and status == "!!" then
|
||||
return true
|
||||
end
|
||||
|
||||
-- filter clean
|
||||
if M.config.filter_git_clean and status == " " then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
---Check if the given path has no listed buffer
|
||||
---@param path string Absolute path
|
||||
---@param bufinfo table vim.fn.getbufinfo { buflisted = 1 }
|
||||
---@param unloaded_bufnr number optional bufnr recently unloaded via BufUnload event
|
||||
---@return boolean
|
||||
local function buf(path, bufinfo, unloaded_bufnr)
|
||||
if not M.config.filter_no_buffer or type(bufinfo) ~= "table" then
|
||||
return false
|
||||
end
|
||||
|
||||
-- filter files with no open buffer and directories containing no open buffers
|
||||
for _, b in ipairs(bufinfo) do
|
||||
if b.name == path or b.name:find(path .. "/", 1, true) and b.bufnr ~= unloaded_bufnr then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function dotfile(path)
|
||||
return M.config.filter_dotfiles and utils.path_basename(path):sub(1, 1) == "."
|
||||
end
|
||||
|
||||
local function custom(path)
|
||||
if not M.config.filter_custom then
|
||||
return false
|
||||
end
|
||||
|
||||
local basename = utils.path_basename(path)
|
||||
|
||||
-- filter custom regexes
|
||||
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
|
||||
@@ -51,10 +88,41 @@ function M.should_ignore(path)
|
||||
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)
|
||||
---Prepare arguments for should_filter. This is done prior to should_filter for efficiency reasons.
|
||||
---@param git_status table results of git.load_project_status(...)
|
||||
---@param unloaded_bufnr number optional bufnr recently unloaded via BufUnload event
|
||||
---@return table
|
||||
--- git_status: reference
|
||||
--- unloaded_bufnr: copy
|
||||
--- bufinfo: empty unless no_buffer set: vim.fn.getbufinfo { buflisted = 1 }
|
||||
function M.prepare(git_status, unloaded_bufnr)
|
||||
local status = {
|
||||
git_status = git_status or {},
|
||||
unloaded_bufnr = unloaded_bufnr,
|
||||
bufinfo = {},
|
||||
}
|
||||
|
||||
if M.config.filter_no_buffer then
|
||||
status.bufinfo = vim.fn.getbufinfo { buflisted = 1 }
|
||||
end
|
||||
|
||||
return status
|
||||
end
|
||||
|
||||
---Check if the given path should be filtered.
|
||||
---@param path string Absolute path
|
||||
---@param status table from prepare
|
||||
---@return boolean
|
||||
function M.should_filter(path, status)
|
||||
-- exclusions override all filters
|
||||
if is_excluded(path) then
|
||||
return false
|
||||
end
|
||||
|
||||
return git(path, status.git_status)
|
||||
or buf(path, status.bufinfo, status.unloaded_bufnr)
|
||||
or dotfile(path)
|
||||
or custom(path)
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
@@ -62,8 +130,11 @@ function M.setup(opts)
|
||||
filter_custom = true,
|
||||
filter_dotfiles = opts.filters.dotfiles,
|
||||
filter_git_ignored = opts.git.ignore,
|
||||
filter_git_clean = opts.filters.git_clean,
|
||||
filter_no_buffer = opts.filters.no_buffer,
|
||||
}
|
||||
|
||||
M.ignore_list = {}
|
||||
M.exclude_list = opts.filters.exclude
|
||||
|
||||
local custom_filter = opts.filters.custom
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
local uv = vim.loop
|
||||
|
||||
local git = require "nvim-tree.git"
|
||||
local watch = require "nvim-tree.explorer.watch"
|
||||
local common = require "nvim-tree.explorer.common"
|
||||
|
||||
local M = {}
|
||||
|
||||
@@ -11,17 +11,19 @@ local Explorer = {}
|
||||
Explorer.__index = Explorer
|
||||
|
||||
function Explorer.new(cwd)
|
||||
cwd = uv.fs_realpath(cwd or uv.cwd())
|
||||
cwd = vim.loop.fs_realpath(cwd or vim.loop.cwd())
|
||||
local explorer = setmetatable({
|
||||
cwd = cwd,
|
||||
absolute_path = cwd,
|
||||
nodes = {},
|
||||
open = true,
|
||||
}, Explorer)
|
||||
explorer.watcher = watch.create_watcher(explorer)
|
||||
explorer:_load(explorer)
|
||||
return explorer
|
||||
end
|
||||
|
||||
function Explorer:_load(node)
|
||||
local cwd = node.cwd or node.link_to or node.absolute_path
|
||||
local cwd = node.link_to or node.absolute_path
|
||||
local git_statuses = git.load_project_status(cwd)
|
||||
M.explore(node, git_statuses)
|
||||
end
|
||||
@@ -30,9 +32,25 @@ function Explorer:expand(node)
|
||||
self:_load(node)
|
||||
end
|
||||
|
||||
function Explorer:destroy()
|
||||
local function iterate(node)
|
||||
common.node_destroy(node)
|
||||
if node.nodes then
|
||||
for _, child in pairs(node.nodes) do
|
||||
iterate(child)
|
||||
end
|
||||
end
|
||||
end
|
||||
iterate(self)
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
require("nvim-tree.explorer.common").setup(opts)
|
||||
require("nvim-tree.explorer.explore").setup(opts)
|
||||
require("nvim-tree.explorer.filters").setup(opts)
|
||||
require("nvim-tree.explorer.sorters").setup(opts)
|
||||
require("nvim-tree.explorer.reload").setup(opts)
|
||||
require("nvim-tree.explorer.watch").setup(opts)
|
||||
end
|
||||
|
||||
M.Explorer = Explorer
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
local uv = vim.loop
|
||||
local utils = require "nvim-tree.utils"
|
||||
local watch = require "nvim-tree.explorer.watch"
|
||||
|
||||
local M = {
|
||||
is_windows = vim.fn.has "win32" == 1,
|
||||
is_wsl = vim.fn.has "wsl" == 1,
|
||||
}
|
||||
|
||||
function M.folder(parent, absolute_path, name)
|
||||
local handle = uv.fs_scandir(absolute_path)
|
||||
local has_children = handle and uv.fs_scandir_next(handle) ~= nil
|
||||
local handle = vim.loop.fs_scandir(absolute_path)
|
||||
local has_children = handle and vim.loop.fs_scandir_next(handle) ~= nil
|
||||
|
||||
return {
|
||||
local node = {
|
||||
type = "directory",
|
||||
absolute_path = absolute_path,
|
||||
fs_stat = uv.fs_stat(absolute_path),
|
||||
fs_stat = vim.loop.fs_stat(absolute_path),
|
||||
group_next = nil, -- If node is grouped, this points to the next child dir/link node
|
||||
has_children = has_children,
|
||||
name = name,
|
||||
@@ -19,23 +21,38 @@ function M.folder(parent, absolute_path, name)
|
||||
open = false,
|
||||
parent = parent,
|
||||
}
|
||||
|
||||
node.watcher = watch.create_watcher(node)
|
||||
|
||||
return node
|
||||
end
|
||||
|
||||
local function is_executable(absolute_path, ext)
|
||||
function M.is_executable(parent, absolute_path, ext)
|
||||
if M.is_windows then
|
||||
return utils.is_windows_exe(ext)
|
||||
elseif M.is_wsl then
|
||||
if parent.is_wsl_windows_fs_path == nil then
|
||||
-- Evaluate lazily when needed and do so only once for each parent
|
||||
-- as 'wslpath' calls can get expensive in highly populated directories.
|
||||
parent.is_wsl_windows_fs_path = utils.is_wsl_windows_fs_path(absolute_path)
|
||||
end
|
||||
|
||||
if parent.is_wsl_windows_fs_path then
|
||||
return utils.is_wsl_windows_fs_exe(ext)
|
||||
end
|
||||
end
|
||||
return uv.fs_access(absolute_path, "X")
|
||||
return vim.loop.fs_access(absolute_path, "X")
|
||||
end
|
||||
|
||||
function M.file(parent, absolute_path, name)
|
||||
local ext = string.match(name, ".?[^.]+%.(.*)") or ""
|
||||
|
||||
return {
|
||||
type = "file",
|
||||
absolute_path = absolute_path,
|
||||
executable = is_executable(absolute_path, ext),
|
||||
executable = M.is_executable(parent, absolute_path, ext),
|
||||
extension = ext,
|
||||
fs_stat = uv.fs_stat(absolute_path),
|
||||
fs_stat = vim.loop.fs_stat(absolute_path),
|
||||
name = name,
|
||||
parent = parent,
|
||||
}
|
||||
@@ -47,19 +64,23 @@ end
|
||||
-- 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)
|
||||
--- 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)
|
||||
--- I dont know if this is needed, because in my understanding, there isn't hard links in windows, but just to be sure i changed it.
|
||||
local link_to = vim.loop.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
|
||||
|
||||
local is_dir_link = (link_to ~= nil) and vim.loop.fs_stat(link_to).type == "directory"
|
||||
|
||||
if is_dir_link then
|
||||
local handle = vim.loop.fs_scandir(link_to)
|
||||
has_children = handle and vim.loop.fs_scandir_next(handle) ~= nil
|
||||
open = false
|
||||
nodes = {}
|
||||
end
|
||||
|
||||
return {
|
||||
local node = {
|
||||
type = "link",
|
||||
absolute_path = absolute_path,
|
||||
fs_stat = uv.fs_stat(absolute_path),
|
||||
fs_stat = vim.loop.fs_stat(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,
|
||||
@@ -68,6 +89,12 @@ function M.link(parent, absolute_path, name)
|
||||
open = open,
|
||||
parent = parent,
|
||||
}
|
||||
|
||||
if is_dir_link then
|
||||
node.watcher = watch.create_watcher(node)
|
||||
end
|
||||
|
||||
return node
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
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 live_filter = require "nvim-tree.live-filter"
|
||||
local notify = require "nvim-tree.notify"
|
||||
local git = require "nvim-tree.git"
|
||||
local log = require "nvim-tree.log"
|
||||
|
||||
local NodeIterator = require "nvim-tree.iterators.node-iterator"
|
||||
|
||||
local M = {}
|
||||
|
||||
@@ -18,14 +21,31 @@ local function update_status(nodes_by_path, node_ignored, status)
|
||||
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)
|
||||
local function reload_and_get_git_project(path)
|
||||
local project_root = git.get_project_root(path)
|
||||
git.reload_project(project_root, path)
|
||||
return project_root, git.get_project(project_root) or {}
|
||||
end
|
||||
|
||||
local function update_parent_statuses(node, project, root)
|
||||
while project and node and node.absolute_path ~= root do
|
||||
common.update_git_status(node, false, project)
|
||||
node = node.parent
|
||||
end
|
||||
end
|
||||
|
||||
function M.reload(node, git_status, unloaded_bufnr)
|
||||
local cwd = node.link_to or node.absolute_path
|
||||
local handle = vim.loop.fs_scandir(cwd)
|
||||
if type(handle) == "string" then
|
||||
api.nvim_err_writeln(handle)
|
||||
notify.error(handle)
|
||||
return
|
||||
end
|
||||
|
||||
local ps = log.profile_start("reload %s", node.absolute_path)
|
||||
|
||||
local filter_status = filters.prepare(git_status, unloaded_bufnr)
|
||||
|
||||
if node.group_next then
|
||||
node.nodes = { node.group_next }
|
||||
node.group_next = nil
|
||||
@@ -36,48 +56,142 @@ function M.reload(node, status)
|
||||
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
|
||||
local ok, name, t = pcall(vim.loop.fs_scandir_next, handle)
|
||||
if not ok or not name then
|
||||
break
|
||||
end
|
||||
|
||||
local stat
|
||||
local function fs_stat_cached(path)
|
||||
if stat ~= nil then
|
||||
return stat
|
||||
end
|
||||
|
||||
stat = vim.loop.fs_stat(path)
|
||||
return stat
|
||||
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
|
||||
t = t or (fs_stat_cached(abs) or {}).type
|
||||
if not filters.should_filter(abs, filter_status) then
|
||||
child_names[abs] = true
|
||||
|
||||
-- Recreate node if type changes.
|
||||
if nodes_by_path[abs] then
|
||||
local n = nodes_by_path[abs]
|
||||
|
||||
if n.type ~= t then
|
||||
utils.array_remove(node.nodes, n)
|
||||
common.node_destroy(n)
|
||||
nodes_by_path[abs] = nil
|
||||
end
|
||||
end
|
||||
|
||||
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))
|
||||
if t == "directory" and vim.loop.fs_access(abs, "R") then
|
||||
local folder = builders.folder(node, abs, name)
|
||||
nodes_by_path[abs] = folder
|
||||
table.insert(node.nodes, folder)
|
||||
elseif t == "file" then
|
||||
table.insert(node.nodes, builders.file(node, abs, name, status, node_ignored))
|
||||
local file = builders.file(node, abs, name)
|
||||
nodes_by_path[abs] = file
|
||||
table.insert(node.nodes, file)
|
||||
elseif t == "link" then
|
||||
local link = builders.link(node, abs, name, status, node_ignored)
|
||||
local link = builders.link(node, abs, name)
|
||||
if link.link_to ~= nil then
|
||||
nodes_by_path[abs] = link
|
||||
table.insert(node.nodes, link)
|
||||
end
|
||||
end
|
||||
else
|
||||
local n = nodes_by_path[abs]
|
||||
if n then
|
||||
n.executable = builders.is_executable(n.parent, abs, n.extension or "")
|
||||
n.fs_stat = fs_stat_cached(abs)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
node.nodes = vim.tbl_map(
|
||||
update_status(nodes_by_path, node_ignored, status),
|
||||
update_status(nodes_by_path, node_ignored, git_status),
|
||||
vim.tbl_filter(function(n)
|
||||
return child_names[n.absolute_path]
|
||||
if child_names[n.absolute_path] then
|
||||
return child_names[n.absolute_path]
|
||||
else
|
||||
common.node_destroy(n)
|
||||
return nil
|
||||
end
|
||||
end, node.nodes)
|
||||
)
|
||||
|
||||
local is_root = node.cwd ~= nil
|
||||
local is_root = not node.parent
|
||||
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
|
||||
if M.config.group_empty and not is_root and child_folder_only then
|
||||
node.group_next = child_folder_only
|
||||
local ns = M.reload(child_folder_only, status)
|
||||
local ns = M.reload(child_folder_only, git_status)
|
||||
node.nodes = ns or {}
|
||||
log.profile_end(ps, "reload %s", node.absolute_path)
|
||||
return ns
|
||||
end
|
||||
|
||||
sorters.merge_sort(node.nodes, sorters.node_comparator)
|
||||
live_filter.apply_filter(node)
|
||||
log.profile_end(ps, "reload %s", node.absolute_path)
|
||||
return node.nodes
|
||||
end
|
||||
|
||||
---Refresh contents and git status for a single node
|
||||
---@param node table
|
||||
function M.refresh_node(node)
|
||||
if type(node) ~= "table" then
|
||||
return
|
||||
end
|
||||
|
||||
local parent_node = utils.get_parent_of_group(node)
|
||||
|
||||
local project_root, project = reload_and_get_git_project(node.absolute_path)
|
||||
|
||||
require("nvim-tree.explorer.reload").reload(parent_node, project)
|
||||
|
||||
update_parent_statuses(parent_node, project, project_root)
|
||||
end
|
||||
|
||||
---Refresh contents and git status for all nodes to a path: actual directory and links
|
||||
---@param path string absolute path
|
||||
function M.refresh_nodes_for_path(path)
|
||||
local explorer = require("nvim-tree.core").get_explorer()
|
||||
if not explorer then
|
||||
return
|
||||
end
|
||||
|
||||
local pn = string.format("refresh_nodes_for_path %s", path)
|
||||
local ps = log.profile_start(pn)
|
||||
|
||||
NodeIterator.builder({ explorer })
|
||||
:hidden()
|
||||
:recursor(function(node)
|
||||
if node.group_next then
|
||||
return { node.group_next }
|
||||
end
|
||||
if node.nodes then
|
||||
return node.nodes
|
||||
end
|
||||
end)
|
||||
:applier(function(node)
|
||||
local abs_contains = node.absolute_path and path:match("^" .. node.absolute_path)
|
||||
local link_contains = node.link_to and path:match("^" .. node.link_to)
|
||||
if abs_contains or link_contains then
|
||||
M.refresh_node(node)
|
||||
end
|
||||
end)
|
||||
:iterate()
|
||||
|
||||
log.profile_end(ps, pn)
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
M.config = opts.renderer
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -66,13 +66,52 @@ end
|
||||
---@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
|
||||
if type(M.sort_by) == "function" then
|
||||
local t_user = {}
|
||||
local origin_index = {}
|
||||
|
||||
split_merge(t, 1, #t, comparator)
|
||||
for _, n in ipairs(t) do
|
||||
table.insert(t_user, {
|
||||
absolute_path = n.absolute_path,
|
||||
executable = n.executable,
|
||||
extension = n.extension,
|
||||
link_to = n.link_to,
|
||||
name = n.name,
|
||||
type = n.type,
|
||||
})
|
||||
table.insert(origin_index, n)
|
||||
end
|
||||
|
||||
M.sort_by(t_user)
|
||||
|
||||
-- do merge sort for prevent memory exceed
|
||||
local user_index = {}
|
||||
for i, v in ipairs(t_user) do
|
||||
if type(v.absolute_path) == "string" and user_index[v.absolute_path] == nil then
|
||||
user_index[v.absolute_path] = i
|
||||
end
|
||||
end
|
||||
|
||||
-- if missing value found, then using origin_index
|
||||
local mini_comparator = function(a, b)
|
||||
local a_index = user_index[a.absolute_path] or origin_index[a.absolute_path]
|
||||
local b_index = user_index[b.absolute_path] or origin_index[b.absolute_path]
|
||||
|
||||
if type(a_index) == "number" and type(b_index) == "number" then
|
||||
return a_index <= b_index
|
||||
end
|
||||
return (a_index or 0) <= (b_index or 0)
|
||||
end
|
||||
|
||||
split_merge(t, 1, #t, mini_comparator) -- sort by user order
|
||||
else
|
||||
if not comparator then
|
||||
comparator = function(left, right)
|
||||
return left < right
|
||||
end
|
||||
end
|
||||
split_merge(t, 1, #t, comparator)
|
||||
end
|
||||
end
|
||||
|
||||
local function node_comparator_name_ignorecase_or_not(a, b, ignorecase)
|
||||
@@ -124,12 +163,40 @@ function M.node_comparator_modification_time(a, b)
|
||||
return last_modified_b <= last_modified_a
|
||||
end
|
||||
|
||||
function M.node_comparator_extension(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
|
||||
|
||||
if not (a.extension and b.extension) then
|
||||
return true
|
||||
end
|
||||
|
||||
if a.extension and not b.extension then
|
||||
return true
|
||||
elseif not a.extension and b.extension then
|
||||
return false
|
||||
end
|
||||
|
||||
return a.extension:lower() <= b.extension:lower()
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
M.sort_by = opts.sort_by
|
||||
if M.sort_by == "modification_time" then
|
||||
if M.sort_by and type(M.sort_by) == "function" then
|
||||
M.node_comparator = M.sort_by
|
||||
elseif 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
|
||||
elseif M.sort_by == "extension" then
|
||||
M.node_comparator = M.node_comparator_extension
|
||||
else
|
||||
M.node_comparator = M.node_comparator_name_ignorecase
|
||||
end
|
||||
|
||||
77
lua/nvim-tree/explorer/watch.lua
Normal file
77
lua/nvim-tree/explorer/watch.lua
Normal file
@@ -0,0 +1,77 @@
|
||||
local log = require "nvim-tree.log"
|
||||
local utils = require "nvim-tree.utils"
|
||||
local Watcher = require("nvim-tree.watcher").Watcher
|
||||
|
||||
local M = {}
|
||||
|
||||
local function is_git(path)
|
||||
return vim.fn.fnamemodify(path, ":t") == ".git"
|
||||
end
|
||||
|
||||
local IGNORED_PATHS = {
|
||||
-- disable watchers on kernel filesystems
|
||||
-- which have a lot of unwanted events
|
||||
"/sys",
|
||||
"/proc",
|
||||
"/dev",
|
||||
}
|
||||
|
||||
local function is_folder_ignored(path)
|
||||
for _, folder in ipairs(IGNORED_PATHS) do
|
||||
if vim.startswith(path, folder) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
for _, ignore_dir in ipairs(M.ignore_dirs) do
|
||||
if vim.fn.match(path, ignore_dir) ~= -1 then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function M.create_watcher(node)
|
||||
if not M.enabled or type(node) ~= "table" then
|
||||
return nil
|
||||
end
|
||||
|
||||
local path
|
||||
if node.type == "link" then
|
||||
path = node.link_to
|
||||
else
|
||||
path = node.absolute_path
|
||||
end
|
||||
|
||||
if is_git(path) or is_folder_ignored(path) then
|
||||
return nil
|
||||
end
|
||||
|
||||
local function callback(watcher)
|
||||
log.line("watcher", "node event scheduled refresh %s", watcher.context)
|
||||
utils.debounce(watcher.context, M.debounce_delay, function()
|
||||
if node.link_to then
|
||||
log.line("watcher", "node event executing refresh '%s' -> '%s'", node.link_to, node.absolute_path)
|
||||
else
|
||||
log.line("watcher", "node event executing refresh '%s'", node.absolute_path)
|
||||
end
|
||||
require("nvim-tree.explorer.reload").refresh_node(node)
|
||||
require("nvim-tree.renderer").draw()
|
||||
end)
|
||||
end
|
||||
|
||||
M.uid = M.uid + 1
|
||||
return Watcher:new(path, nil, callback, {
|
||||
context = "explorer:watch:" .. path .. ":" .. M.uid,
|
||||
})
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
M.enabled = opts.filesystem_watchers.enable
|
||||
M.debounce_delay = opts.filesystem_watchers.debounce_delay
|
||||
M.ignore_dirs = opts.filesystem_watchers.ignore_dirs
|
||||
M.uid = 0
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -1,35 +1,79 @@
|
||||
local log = require "nvim-tree.log"
|
||||
local utils = require "nvim-tree.utils"
|
||||
local git_utils = require "nvim-tree.git.utils"
|
||||
local Runner = require "nvim-tree.git.runner"
|
||||
local Watcher = require("nvim-tree.watcher").Watcher
|
||||
local Iterator = require "nvim-tree.iterators.node-iterator"
|
||||
|
||||
local M = {
|
||||
config = nil,
|
||||
config = {},
|
||||
projects = {},
|
||||
cwd_to_project_root = {},
|
||||
}
|
||||
|
||||
-- Files under .git that should result in a reload when changed.
|
||||
-- Utilities (like watchman) can also write to this directory (often) and aren't useful for us.
|
||||
local WATCHED_FILES = {
|
||||
"FETCH_HEAD", -- remote ref
|
||||
"HEAD", -- local ref
|
||||
"HEAD.lock", -- HEAD will not always be updated e.g. revert
|
||||
"config", -- user config
|
||||
"index", -- staging area
|
||||
}
|
||||
|
||||
function M.reload()
|
||||
if not M.config.enable then
|
||||
if not M.config.git.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),
|
||||
}
|
||||
M.reload_project(project_root)
|
||||
end
|
||||
|
||||
return M.projects
|
||||
end
|
||||
|
||||
function M.reload_project(project_root, path)
|
||||
local project = M.projects[project_root]
|
||||
if not project or not M.config.git.enable then
|
||||
return
|
||||
end
|
||||
|
||||
if path and path:find(project_root, 1, true) ~= 1 then
|
||||
return
|
||||
end
|
||||
|
||||
local git_status = Runner.run {
|
||||
project_root = project_root,
|
||||
path = path,
|
||||
list_untracked = git_utils.should_show_untracked(project_root),
|
||||
list_ignored = true,
|
||||
timeout = M.config.git.timeout,
|
||||
}
|
||||
|
||||
if path then
|
||||
for p in pairs(project.files) do
|
||||
if p:find(path, 1, true) == 1 then
|
||||
project.files[p] = nil
|
||||
end
|
||||
end
|
||||
project.files = vim.tbl_deep_extend("force", project.files, git_status)
|
||||
else
|
||||
project.files = git_status
|
||||
end
|
||||
|
||||
project.dirs = git_utils.file_status_to_dir_status(project.files, project_root)
|
||||
end
|
||||
|
||||
function M.get_project(project_root)
|
||||
return M.projects[project_root]
|
||||
end
|
||||
|
||||
function M.get_project_root(cwd)
|
||||
if not M.config.git.enable then
|
||||
return nil
|
||||
end
|
||||
|
||||
if M.cwd_to_project_root[cwd] then
|
||||
return M.cwd_to_project_root[cwd]
|
||||
end
|
||||
@@ -38,12 +82,51 @@ function M.get_project_root(cwd)
|
||||
return nil
|
||||
end
|
||||
|
||||
local project_root = git_utils.get_toplevel(cwd)
|
||||
return project_root
|
||||
local stat, _ = vim.loop.fs_stat(cwd)
|
||||
if not stat or stat.type ~= "directory" then
|
||||
return nil
|
||||
end
|
||||
|
||||
M.cwd_to_project_root[cwd] = git_utils.get_toplevel(cwd)
|
||||
return M.cwd_to_project_root[cwd]
|
||||
end
|
||||
|
||||
local function reload_tree_at(project_root)
|
||||
if not M.config.git.enable then
|
||||
return nil
|
||||
end
|
||||
|
||||
log.line("watcher", "git event executing '%s'", project_root)
|
||||
local root_node = utils.get_node_from_path(project_root)
|
||||
if not root_node then
|
||||
return
|
||||
end
|
||||
|
||||
M.reload_project(project_root)
|
||||
local project = M.get_project(project_root)
|
||||
|
||||
local project_files = project.files and project.files or {}
|
||||
local project_dirs = project.dirs and project.dirs or {}
|
||||
|
||||
Iterator.builder(root_node.nodes)
|
||||
:hidden()
|
||||
:applier(function(node)
|
||||
local parent_ignored = node.parent.git_status == "!!"
|
||||
node.git_status = project_dirs[node.absolute_path] or project_files[node.absolute_path]
|
||||
if not node.git_status and parent_ignored then
|
||||
node.git_status = "!!"
|
||||
end
|
||||
end)
|
||||
:recursor(function(node)
|
||||
return node.nodes and #node.nodes > 0 and node.nodes
|
||||
end)
|
||||
:iterate()
|
||||
|
||||
require("nvim-tree.renderer").draw()
|
||||
end
|
||||
|
||||
function M.load_project_status(cwd)
|
||||
if not M.config.enable then
|
||||
if not M.config.git.enable then
|
||||
return {}
|
||||
end
|
||||
|
||||
@@ -62,17 +145,42 @@ function M.load_project_status(cwd)
|
||||
project_root = project_root,
|
||||
list_untracked = git_utils.should_show_untracked(project_root),
|
||||
list_ignored = true,
|
||||
timeout = M.config.timeout,
|
||||
timeout = M.config.git.timeout,
|
||||
}
|
||||
|
||||
local watcher = nil
|
||||
if M.config.filesystem_watchers.enable then
|
||||
log.line("watcher", "git start")
|
||||
|
||||
local callback = function(w)
|
||||
log.line("watcher", "git event scheduled '%s'", w.project_root)
|
||||
utils.debounce("git:watcher:" .. w.project_root, M.config.filesystem_watchers.debounce_delay, function()
|
||||
reload_tree_at(w.project_root)
|
||||
end)
|
||||
end
|
||||
|
||||
watcher = Watcher:new(utils.path_join { project_root, ".git" }, WATCHED_FILES, callback, {
|
||||
project_root = project_root,
|
||||
})
|
||||
end
|
||||
|
||||
M.projects[project_root] = {
|
||||
files = git_status,
|
||||
dirs = git_utils.file_status_to_dir_status(git_status, project_root),
|
||||
watcher = watcher,
|
||||
}
|
||||
return M.projects[project_root]
|
||||
end
|
||||
|
||||
function M.purge_state()
|
||||
log.line("git", "purge_state")
|
||||
M.projects = {}
|
||||
M.cwd_to_project_root = {}
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
M.config = opts.git
|
||||
M.config.git = opts.git
|
||||
M.config.filesystem_watchers = opts.filesystem_watchers
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
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('"$', "")
|
||||
function Runner:_parse_status_output(status, path)
|
||||
-- replacing slashes if on windows
|
||||
if vim.fn.has "win32" == 1 then
|
||||
path = path:gsub("/", "\\")
|
||||
@@ -16,15 +12,26 @@ function Runner:_parse_status_output(line)
|
||||
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
|
||||
local skip_next_line = false
|
||||
for line in prev:gmatch "[^\n]*\n" do
|
||||
i = i + self:_parse_status_output(line)
|
||||
if skip_next_line then
|
||||
skip_next_line = false
|
||||
else
|
||||
local status = line:sub(1, 2)
|
||||
local path = line:sub(4, -2)
|
||||
if utils.str_find(status, "R") then
|
||||
-- skip next line if it is a rename entry
|
||||
skip_next_line = true
|
||||
end
|
||||
self:_parse_status_output(status, path)
|
||||
end
|
||||
i = i + #line
|
||||
end
|
||||
|
||||
return prev:sub(i, -1)
|
||||
@@ -45,7 +52,7 @@ 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 },
|
||||
args = { "--no-optional-locks", "status", "--porcelain=v1", "-z", ignored, untracked, self.path },
|
||||
cwd = self.project_root,
|
||||
stdio = { nil, stdout_handle, stderr_handle },
|
||||
}
|
||||
@@ -54,14 +61,15 @@ end
|
||||
function Runner:_log_raw_output(output)
|
||||
if output and type(output) == "string" then
|
||||
log.raw("git", "%s", output)
|
||||
log.line("git", "done")
|
||||
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 stdout = vim.loop.new_pipe(false)
|
||||
local stderr = vim.loop.new_pipe(false)
|
||||
local timer = vim.loop.new_timer()
|
||||
|
||||
local function on_finish(rc)
|
||||
self.rc = rc or 0
|
||||
@@ -78,14 +86,14 @@ function Runner:_run_git_job()
|
||||
handle:close()
|
||||
end
|
||||
|
||||
pcall(uv.kill, pid)
|
||||
pcall(vim.loop.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, " "))
|
||||
log.line("git", "git %s", table.concat(utils.array_remove_nils(opts.args), " "))
|
||||
|
||||
handle, pid = uv.spawn(
|
||||
handle, pid = vim.loop.spawn(
|
||||
"git",
|
||||
opts,
|
||||
vim.schedule_wrap(function(rc)
|
||||
@@ -106,6 +114,9 @@ function Runner:_run_git_job()
|
||||
if err then
|
||||
return
|
||||
end
|
||||
if data then
|
||||
data = data:gsub("%z", "\n")
|
||||
end
|
||||
self:_log_raw_output(data)
|
||||
output_leftover = self:_handle_incoming_data(output_leftover, data)
|
||||
end
|
||||
@@ -114,24 +125,26 @@ function Runner:_run_git_job()
|
||||
self:_log_raw_output(data)
|
||||
end
|
||||
|
||||
uv.read_start(stdout, vim.schedule_wrap(manage_stdout))
|
||||
uv.read_start(stderr, vim.schedule_wrap(manage_stderr))
|
||||
vim.loop.read_start(stdout, vim.schedule_wrap(manage_stdout))
|
||||
vim.loop.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 ps = log.profile_start("git job %s %s", opts.project_root, opts.path)
|
||||
|
||||
local self = setmetatable({
|
||||
project_root = opts.project_root,
|
||||
path = opts.path,
|
||||
list_untracked = opts.list_untracked,
|
||||
list_ignored = opts.list_ignored,
|
||||
timeout = opts.timeout or 400,
|
||||
@@ -142,14 +155,14 @@ function Runner.run(opts)
|
||||
self:_run_git_job()
|
||||
self:_wait()
|
||||
|
||||
log.profile_end(ps, "git job %s", opts.project_root)
|
||||
log.profile_end(ps, "git job %s %s", opts.project_root, opts.path)
|
||||
|
||||
if self.rc == -1 then
|
||||
log.line("git", "job timed out")
|
||||
log.line("git", "job timed out %s %s", opts.project_root, opts.path)
|
||||
elseif self.rc ~= 0 then
|
||||
log.line("git", "job failed with return code %d", self.rc)
|
||||
log.line("git", "job fail rc %d %s %s", self.rc, opts.project_root, opts.path)
|
||||
else
|
||||
log.line("git", "job success")
|
||||
log.line("git", "job success %s %s", opts.project_root, opts.path)
|
||||
end
|
||||
|
||||
return self.output
|
||||
|
||||
@@ -1,15 +1,32 @@
|
||||
local M = {}
|
||||
local log = require "nvim-tree.log"
|
||||
|
||||
local has_cygpath = vim.fn.executable "cygpath" == 1
|
||||
|
||||
function M.get_toplevel(cwd)
|
||||
local cmd = "git -C " .. vim.fn.shellescape(cwd) .. " rev-parse --show-toplevel"
|
||||
local ps = log.profile_start("git toplevel %s", cwd)
|
||||
|
||||
local cmd = { "git", "-C", cwd, "rev-parse", "--show-toplevel" }
|
||||
log.line("git", "%s", vim.inspect(cmd))
|
||||
|
||||
local toplevel = vim.fn.system(cmd)
|
||||
|
||||
if not toplevel or #toplevel == 0 or toplevel:match "fatal" then
|
||||
log.raw("git", toplevel)
|
||||
log.profile_end(ps, "git toplevel %s", cwd)
|
||||
|
||||
if vim.v.shell_error ~= 0 or 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
|
||||
-- msys2 git support
|
||||
if has_cygpath then
|
||||
toplevel = vim.fn.system("cygpath -w " .. vim.fn.shellescape(toplevel))
|
||||
if vim.v.shell_error ~= 0 then
|
||||
return nil
|
||||
end
|
||||
end
|
||||
toplevel = toplevel:gsub("/", "\\")
|
||||
end
|
||||
|
||||
@@ -24,9 +41,17 @@ function M.should_show_untracked(cwd)
|
||||
return untracked[cwd]
|
||||
end
|
||||
|
||||
local cmd = "git -C " .. cwd .. " config --type=bool status.showUntrackedFiles"
|
||||
local ps = log.profile_start("git untracked %s", cwd)
|
||||
|
||||
local cmd = { "git", "-C", cwd, "config", "status.showUntrackedFiles" }
|
||||
log.line("git", vim.inspect(cmd))
|
||||
|
||||
local has_untracked = vim.fn.system(cmd)
|
||||
untracked[cwd] = vim.trim(has_untracked) ~= "false"
|
||||
|
||||
log.raw("git", has_untracked)
|
||||
log.profile_end(ps, "git untracked %s", cwd)
|
||||
|
||||
untracked[cwd] = vim.trim(has_untracked) ~= "no"
|
||||
return untracked[cwd]
|
||||
end
|
||||
|
||||
|
||||
65
lua/nvim-tree/iterators/node-iterator.lua
Normal file
65
lua/nvim-tree/iterators/node-iterator.lua
Normal file
@@ -0,0 +1,65 @@
|
||||
local NodeIterator = {}
|
||||
NodeIterator.__index = NodeIterator
|
||||
|
||||
function NodeIterator.builder(nodes)
|
||||
return setmetatable({
|
||||
nodes = nodes,
|
||||
_filter_hidden = function(node)
|
||||
return not node.hidden
|
||||
end,
|
||||
_apply_fn_on_node = function(_) end,
|
||||
_match = function(_) end,
|
||||
_recurse_with = function(node)
|
||||
return node.nodes
|
||||
end,
|
||||
}, NodeIterator)
|
||||
end
|
||||
|
||||
function NodeIterator:hidden()
|
||||
self._filter_hidden = function(_)
|
||||
return true
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function NodeIterator:matcher(f)
|
||||
self._match = f
|
||||
return self
|
||||
end
|
||||
|
||||
function NodeIterator:applier(f)
|
||||
self._apply_fn_on_node = f
|
||||
return self
|
||||
end
|
||||
|
||||
function NodeIterator:recursor(f)
|
||||
self._recurse_with = f
|
||||
return self
|
||||
end
|
||||
|
||||
function NodeIterator:iterate()
|
||||
local iteration_count = 0
|
||||
local function iter(nodes)
|
||||
for _, node in ipairs(nodes) do
|
||||
if self._filter_hidden(node) then
|
||||
iteration_count = iteration_count + 1
|
||||
if self._match(node) then
|
||||
return node, iteration_count
|
||||
end
|
||||
self._apply_fn_on_node(node, iteration_count)
|
||||
local children = self._recurse_with(node)
|
||||
if children then
|
||||
local n = iter(children)
|
||||
if n then
|
||||
return n, iteration_count
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil, 0
|
||||
end
|
||||
|
||||
return iter(self.nodes)
|
||||
end
|
||||
|
||||
return NodeIterator
|
||||
290
lua/nvim-tree/keymap.lua
Normal file
290
lua/nvim-tree/keymap.lua
Normal file
@@ -0,0 +1,290 @@
|
||||
local Api = require "nvim-tree.api"
|
||||
|
||||
local M = {}
|
||||
|
||||
local DEFAULT_KEYMAPS = {
|
||||
{
|
||||
key = { "<CR>", "o", "<2-LeftMouse>" },
|
||||
callback = Api.node.open.edit,
|
||||
desc = "open a file or folder; root will cd to the above directory",
|
||||
},
|
||||
{
|
||||
key = "<C-e>",
|
||||
callback = Api.node.open.replace_tree_buffer,
|
||||
desc = "edit the file in place, effectively replacing the tree explorer",
|
||||
},
|
||||
{
|
||||
key = "O",
|
||||
callback = Api.node.open.no_window_picker,
|
||||
desc = "same as (edit) with no window picker",
|
||||
},
|
||||
{
|
||||
key = { "<C-]>", "<2-RightMouse>" },
|
||||
callback = Api.tree.change_root_to_node,
|
||||
desc = "cd in the directory under the cursor",
|
||||
},
|
||||
{
|
||||
key = "<C-v>",
|
||||
callback = Api.node.open.vertical,
|
||||
desc = "open the file in a vertical split",
|
||||
},
|
||||
{
|
||||
key = "<C-x>",
|
||||
callback = Api.node.open.horizontal,
|
||||
desc = "open the file in a horizontal split",
|
||||
},
|
||||
{
|
||||
key = "<C-t>",
|
||||
callback = Api.node.open.tab,
|
||||
desc = "open the file in a new tab",
|
||||
},
|
||||
{
|
||||
key = "<",
|
||||
callback = Api.node.navigate.sibling.prev,
|
||||
desc = "navigate to the previous sibling of current file/directory",
|
||||
},
|
||||
{
|
||||
key = ">",
|
||||
callback = Api.node.navigate.sibling.next,
|
||||
desc = "navigate to the next sibling of current file/directory",
|
||||
},
|
||||
{
|
||||
key = "P",
|
||||
callback = Api.node.navigate.parent,
|
||||
desc = "move cursor to the parent directory",
|
||||
},
|
||||
{
|
||||
key = "<BS>",
|
||||
callback = Api.node.navigate.parent_close,
|
||||
desc = "close current opened directory or parent",
|
||||
},
|
||||
{
|
||||
key = "<Tab>",
|
||||
callback = Api.node.open.preview,
|
||||
desc = "open the file as a preview (keeps the cursor in the tree)",
|
||||
},
|
||||
{
|
||||
key = "K",
|
||||
callback = Api.node.navigate.sibling.first,
|
||||
desc = "navigate to the first sibling of current file/directory",
|
||||
},
|
||||
{
|
||||
key = "J",
|
||||
callback = Api.node.navigate.sibling.last,
|
||||
desc = "navigate to the last sibling of current file/directory",
|
||||
},
|
||||
{
|
||||
key = "I",
|
||||
callback = Api.tree.toggle_gitignore_filter,
|
||||
desc = "toggle visibility of files/folders hidden via |git.ignore| option",
|
||||
},
|
||||
{
|
||||
key = "H",
|
||||
callback = Api.tree.toggle_hidden_filter,
|
||||
desc = "toggle visibility of dotfiles via |filters.dotfiles| option",
|
||||
},
|
||||
{
|
||||
key = "U",
|
||||
callback = Api.tree.toggle_custom_filter,
|
||||
desc = "toggle visibility of files/folders hidden via |filters.custom| option",
|
||||
},
|
||||
{
|
||||
key = "R",
|
||||
callback = Api.tree.reload,
|
||||
desc = "refresh the tree",
|
||||
},
|
||||
{
|
||||
key = "a",
|
||||
callback = Api.fs.create,
|
||||
desc = "add a file; leaving a trailing `/` will add a directory",
|
||||
},
|
||||
{
|
||||
key = "d",
|
||||
callback = Api.fs.remove,
|
||||
desc = "delete a file (will prompt for confirmation)",
|
||||
},
|
||||
{
|
||||
key = "D",
|
||||
callback = Api.fs.trash,
|
||||
desc = "trash a file via |trash| option",
|
||||
},
|
||||
{
|
||||
key = "r",
|
||||
callback = Api.fs.rename,
|
||||
desc = "rename a file",
|
||||
},
|
||||
{
|
||||
key = "<C-r>",
|
||||
callback = Api.fs.rename_sub,
|
||||
desc = "rename a file and omit the filename on input",
|
||||
},
|
||||
{
|
||||
key = "x",
|
||||
callback = Api.fs.cut,
|
||||
desc = "add/remove file/directory to cut clipboard",
|
||||
},
|
||||
{
|
||||
key = "c",
|
||||
callback = Api.fs.copy.node,
|
||||
desc = "add/remove file/directory to copy clipboard",
|
||||
},
|
||||
{
|
||||
key = "p",
|
||||
callback = Api.fs.paste,
|
||||
desc = "paste from clipboard; cut clipboard has precedence over copy; will prompt for confirmation",
|
||||
},
|
||||
{
|
||||
key = "y",
|
||||
callback = Api.fs.copy.filename,
|
||||
desc = "copy name to system clipboard",
|
||||
},
|
||||
{
|
||||
key = "Y",
|
||||
callback = Api.fs.copy.relative_path,
|
||||
desc = "copy relative path to system clipboard",
|
||||
},
|
||||
{
|
||||
key = "gy",
|
||||
callback = Api.fs.copy.absolute_path,
|
||||
desc = "copy absolute path to system clipboard",
|
||||
},
|
||||
{
|
||||
key = "]e",
|
||||
callback = Api.node.navigate.diagnostics.next,
|
||||
desc = "go to next diagnostic item",
|
||||
},
|
||||
{
|
||||
key = "]c",
|
||||
callback = Api.node.navigate.git.next,
|
||||
desc = "go to next git item",
|
||||
},
|
||||
{
|
||||
key = "[e",
|
||||
callback = Api.node.navigate.diagnostics.prev,
|
||||
desc = "go to prev diagnostic item",
|
||||
},
|
||||
{
|
||||
key = "[c",
|
||||
callback = Api.node.navigate.git.prev,
|
||||
desc = "go to prev git item",
|
||||
},
|
||||
{
|
||||
key = "-",
|
||||
callback = Api.tree.change_root_to_parent,
|
||||
desc = "navigate up to the parent directory of the current file/directory",
|
||||
},
|
||||
{
|
||||
key = "s",
|
||||
callback = Api.node.run.system,
|
||||
desc = "open a file with default system application or a folder with default file manager, using |system_open| option",
|
||||
},
|
||||
{
|
||||
key = "f",
|
||||
callback = Api.live_filter.start,
|
||||
desc = "live filter nodes dynamically based on regex matching.",
|
||||
},
|
||||
{
|
||||
key = "F",
|
||||
callback = Api.live_filter.clear,
|
||||
desc = "clear live filter",
|
||||
},
|
||||
{
|
||||
key = "q",
|
||||
callback = Api.tree.close,
|
||||
desc = "close tree window",
|
||||
},
|
||||
{
|
||||
key = "W",
|
||||
callback = Api.tree.collapse_all,
|
||||
desc = "collapse the whole tree",
|
||||
},
|
||||
{
|
||||
key = "E",
|
||||
callback = Api.tree.expand_all,
|
||||
desc = "expand the whole tree, stopping after expanding |callbacks.expand_all.max_folder_discovery| folders; this might hang neovim for a while if running on a big folder",
|
||||
},
|
||||
{
|
||||
key = "S",
|
||||
callback = Api.tree.search_node,
|
||||
desc = "prompt the user to enter a path and then expands the tree to match the path",
|
||||
},
|
||||
{
|
||||
key = ".",
|
||||
callback = Api.node.run.cmd,
|
||||
desc = "enter vim command mode with the file the cursor is on",
|
||||
},
|
||||
{
|
||||
key = "<C-k>",
|
||||
callback = Api.node.show_info_popup,
|
||||
desc = "toggle a popup with file infos about the file under the cursor",
|
||||
},
|
||||
{
|
||||
key = "g?",
|
||||
callback = Api.tree.toggle_help,
|
||||
desc = "toggle help",
|
||||
},
|
||||
{
|
||||
key = "m",
|
||||
callback = Api.marks.toggle,
|
||||
desc = "Toggle node in bookmarks",
|
||||
},
|
||||
{
|
||||
key = "bmv",
|
||||
callback = Api.marks.bulk.move,
|
||||
desc = "Move all bookmarked nodes into specified location",
|
||||
},
|
||||
}
|
||||
|
||||
function M.set_keymaps(bufnr)
|
||||
local opts = { noremap = true, silent = true, nowait = true, buffer = bufnr }
|
||||
for _, km in ipairs(M.keymaps) do
|
||||
local keys = type(km.key) == "table" and km.key or { km.key }
|
||||
for _, key in ipairs(keys) do
|
||||
vim.keymap.set("n", key, km.callback, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function filter_default_mappings(keys_to_disable)
|
||||
local new_map = {}
|
||||
for _, m in pairs(DEFAULT_KEYMAPS) do
|
||||
local keys = type(m.key) == "table" and m.key or { m.key }
|
||||
local reminding_keys = {}
|
||||
for _, key in pairs(keys) do
|
||||
local found = false
|
||||
for _, key_to_disable in pairs(keys_to_disable) do
|
||||
if key_to_disable == key then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
table.insert(reminding_keys, key)
|
||||
end
|
||||
end
|
||||
if #reminding_keys > 0 then
|
||||
local map = vim.deepcopy(m)
|
||||
map.key = reminding_keys
|
||||
table.insert(new_map, map)
|
||||
end
|
||||
end
|
||||
return new_map
|
||||
end
|
||||
|
||||
local function get_keymaps(keys_to_disable)
|
||||
if keys_to_disable == true then
|
||||
return {}
|
||||
end
|
||||
|
||||
if type(keys_to_disable) == "table" and #keys_to_disable > 0 then
|
||||
return filter_default_mappings(keys_to_disable)
|
||||
end
|
||||
|
||||
return DEFAULT_KEYMAPS
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
M.keymaps = get_keymaps(opts.remove_keymaps)
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -1,183 +1,8 @@
|
||||
local utils = require "nvim-tree.utils"
|
||||
local notify = require "nvim-tree.notify"
|
||||
|
||||
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
|
||||
@@ -188,48 +13,37 @@ local function refactored(opts)
|
||||
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
|
||||
-- 2022/06/20
|
||||
utils.move_missing_val(opts, "update_focused_file", "update_cwd", opts, "update_focused_file", "update_root")
|
||||
utils.move_missing_val(opts, "", "update_cwd", opts, "", "sync_root_with_cwd")
|
||||
|
||||
-- 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
|
||||
-- 2022/11/07
|
||||
utils.move_missing_val(opts, "", "open_on_tab", opts, "tab.sync", "open", false)
|
||||
utils.move_missing_val(opts, "", "open_on_tab", opts, "tab.sync", "close")
|
||||
utils.move_missing_val(opts, "", "ignore_buf_on_tab_change", opts, "tab.sync", "ignore")
|
||||
|
||||
-- 2022/11/22
|
||||
utils.move_missing_val(opts, "renderer", "root_folder_modifier", opts, "renderer", "root_folder_label")
|
||||
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)"
|
||||
notify.warn "auto close feature has been removed, see note in the README (tips & reminder section)"
|
||||
opts.auto_close = nil
|
||||
end
|
||||
|
||||
if opts.focus_empty_on_setup then
|
||||
notify.warn "focus_empty_on_setup has been removed and will be replaced by a new startup configuration. Please remove this option. See https://bit.ly/3yJch2T"
|
||||
opts.focus_empty_on_setup = nil
|
||||
end
|
||||
|
||||
if opts.create_in_closed_folder then
|
||||
notify.warn "create_in_closed_folder has been removed and is now the default behaviour. You may use api.fs.create to add a file under your desired node."
|
||||
end
|
||||
opts.create_in_closed_folder = nil
|
||||
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)
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
local api = vim.api
|
||||
|
||||
local renderer = require "nvim-tree.renderer"
|
||||
local view = require "nvim-tree.view"
|
||||
local core = require "nvim-tree.core"
|
||||
@@ -13,23 +11,64 @@ 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 cursor = vim.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 = utils.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 utils.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line())[line]
|
||||
end
|
||||
|
||||
if line == 1 and view.is_root_folder_visible(core.get_cwd()) then
|
||||
return { name = ".." }
|
||||
end
|
||||
|
||||
return utils.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line())[line]
|
||||
end
|
||||
|
||||
---Create a sanitized partial copy of a node, populating children recursively.
|
||||
---@param node table
|
||||
---@return table|nil cloned node
|
||||
local function clone_node(node)
|
||||
if not node then
|
||||
node = core.get_explorer()
|
||||
if not node then
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local n = {
|
||||
absolute_path = node.absolute_path,
|
||||
executable = node.executable,
|
||||
extension = node.extension,
|
||||
git_status = node.git_status,
|
||||
has_children = node.has_children,
|
||||
hidden = node.hidden,
|
||||
link_to = node.link_to,
|
||||
name = node.name,
|
||||
open = node.open,
|
||||
type = node.type,
|
||||
}
|
||||
|
||||
if type(node.nodes) == "table" then
|
||||
n.nodes = {}
|
||||
for _, child in ipairs(node.nodes) do
|
||||
table.insert(n.nodes, clone_node(child))
|
||||
end
|
||||
end
|
||||
|
||||
return n
|
||||
end
|
||||
|
||||
---Api.tree.get_nodes
|
||||
function M.get_nodes()
|
||||
return clone_node(core.get_explorer())
|
||||
end
|
||||
|
||||
-- If node is grouped, return the last node in the group. Otherwise, return the given node.
|
||||
@@ -55,7 +94,7 @@ function M.expand_or_collapse(node)
|
||||
end
|
||||
|
||||
function M.set_target_win()
|
||||
local id = api.nvim_get_current_win()
|
||||
local id = vim.api.nvim_get_current_win()
|
||||
local tree_id = view.get_winnr()
|
||||
if tree_id and id == tree_id then
|
||||
M.target_winid = 0
|
||||
@@ -66,9 +105,8 @@ function M.set_target_win()
|
||||
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)
|
||||
if M.respect_buf_cwd and cwd ~= core.get_cwd() then
|
||||
require("nvim-tree.actions.root.change-dir").fn(cwd)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -80,10 +118,10 @@ local function open_view_and_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 bufnr = vim.api.nvim_get_current_buf()
|
||||
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||
local bufmodified = vim.api.nvim_buf_get_option(bufnr, "modified")
|
||||
local ft = vim.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
|
||||
@@ -91,12 +129,34 @@ local function should_hijack_current_buf()
|
||||
return should_hijack_dir or should_hijack_unnamed
|
||||
end
|
||||
|
||||
function M.prompt(prompt_input, prompt_select, items_short, items_long, callback)
|
||||
local function format_item(short)
|
||||
for i, s in ipairs(items_short) do
|
||||
if short == s then
|
||||
return items_long[i]
|
||||
end
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
if M.select_prompts then
|
||||
vim.ui.select(items_short, { prompt = prompt_select, format_item = format_item }, function(item_short)
|
||||
callback(item_short)
|
||||
end)
|
||||
else
|
||||
vim.ui.input({ prompt = prompt_input }, function(item_short)
|
||||
callback(item_short)
|
||||
end)
|
||||
end
|
||||
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.close_this_tab_only()
|
||||
view.open_in_current_win()
|
||||
renderer.draw()
|
||||
else
|
||||
@@ -105,22 +165,24 @@ function M.open(cwd)
|
||||
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
|
||||
-- @deprecated: use nvim-tree.actions.tree-modifiers.collapse-all.fn
|
||||
M.collapse_all = require("nvim-tree.actions.tree-modifiers.collapse-all").fn
|
||||
-- @deprecated: use nvim-tree.actions.root.dir-up.fn
|
||||
M.dir_up = require("nvim-tree.actions.root.dir-up").fn
|
||||
-- @deprecated: use nvim-tree.actions.root.change-dir.fn
|
||||
M.change_dir = require("nvim-tree.actions.root.change-dir").fn
|
||||
-- @deprecated: use nvim-tree.actions.reloaders.reloaders.reload_explorer
|
||||
M.refresh_tree = require("nvim-tree.actions.reloaders.reloaders").reload_explorer
|
||||
-- @deprecated: use nvim-tree.actions.reloaders.reloaders.reload_git
|
||||
M.reload_git = require("nvim-tree.actions.reloaders.reloaders").reload_git
|
||||
-- @deprecated: use nvim-tree.actions.finders.find-file.fn
|
||||
M.set_index_and_redraw = require("nvim-tree.actions.finders.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
|
||||
M.respect_buf_cwd = opts.respect_buf_cwd
|
||||
M.select_prompts = opts.select_prompts
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
158
lua/nvim-tree/live-filter.lua
Normal file
158
lua/nvim-tree/live-filter.lua
Normal file
@@ -0,0 +1,158 @@
|
||||
local view = require "nvim-tree.view"
|
||||
local utils = require "nvim-tree.utils"
|
||||
local Iterator = require "nvim-tree.iterators.node-iterator"
|
||||
|
||||
local M = {
|
||||
filter = nil,
|
||||
}
|
||||
|
||||
local function redraw()
|
||||
require("nvim-tree.renderer").draw()
|
||||
end
|
||||
|
||||
local function reset_filter(node_)
|
||||
node_ = node_ or TreeExplorer
|
||||
Iterator.builder(node_.nodes)
|
||||
:hidden()
|
||||
:applier(function(node)
|
||||
node.hidden = false
|
||||
end)
|
||||
:iterate()
|
||||
end
|
||||
|
||||
local overlay_bufnr = nil
|
||||
local overlay_winnr = nil
|
||||
|
||||
local function remove_overlay()
|
||||
if view.View.float.enable and view.View.float.quit_on_focus_loss then
|
||||
-- return to normal nvim-tree float behaviour when filter window is closed
|
||||
vim.api.nvim_create_autocmd("WinLeave", {
|
||||
pattern = "NvimTree_*",
|
||||
group = vim.api.nvim_create_augroup("NvimTree", { clear = false }),
|
||||
callback = function()
|
||||
if utils.is_nvim_tree_buf(0) then
|
||||
view.close()
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
vim.api.nvim_win_close(overlay_winnr, { force = true })
|
||||
overlay_bufnr = nil
|
||||
overlay_winnr = nil
|
||||
|
||||
if M.filter == "" then
|
||||
M.clear_filter()
|
||||
end
|
||||
end
|
||||
|
||||
local function matches(node)
|
||||
local path = node.absolute_path
|
||||
local name = vim.fn.fnamemodify(path, ":t")
|
||||
return vim.regex(M.filter):match_str(name) ~= nil
|
||||
end
|
||||
|
||||
function M.apply_filter(node_)
|
||||
if not M.filter or M.filter == "" then
|
||||
reset_filter(node_)
|
||||
return
|
||||
end
|
||||
|
||||
-- TODO(kiyan): this iterator cannot yet be refactored with the Iterator module
|
||||
-- since the node mapper is based on its children
|
||||
local function iterate(node)
|
||||
local filtered_nodes = 0
|
||||
local nodes = node.group_next and { node.group_next } or node.nodes
|
||||
|
||||
if nodes then
|
||||
for _, n in pairs(nodes) do
|
||||
iterate(n)
|
||||
if n.hidden then
|
||||
filtered_nodes = filtered_nodes + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local has_nodes = nodes and (M.always_show_folders or #nodes > filtered_nodes)
|
||||
local ok, is_match = pcall(matches, node)
|
||||
node.hidden = not (has_nodes or (ok and is_match))
|
||||
end
|
||||
|
||||
iterate(node_ or TreeExplorer)
|
||||
end
|
||||
|
||||
local function record_char()
|
||||
vim.schedule(function()
|
||||
M.filter = vim.api.nvim_buf_get_lines(overlay_bufnr, 0, -1, false)[1]
|
||||
M.apply_filter()
|
||||
redraw()
|
||||
end)
|
||||
end
|
||||
|
||||
local function configure_buffer_overlay()
|
||||
overlay_bufnr = vim.api.nvim_create_buf(false, true)
|
||||
|
||||
vim.api.nvim_buf_attach(overlay_bufnr, true, {
|
||||
on_lines = record_char,
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd("InsertLeave", {
|
||||
callback = remove_overlay,
|
||||
once = true,
|
||||
})
|
||||
|
||||
vim.api.nvim_buf_set_keymap(overlay_bufnr, "i", "<CR>", "<cmd>stopinsert<CR>", {})
|
||||
end
|
||||
|
||||
local function create_overlay()
|
||||
local min_width = 20
|
||||
if view.View.float.enable then
|
||||
-- don't close nvim-tree float when focus is changed to filter window
|
||||
vim.api.nvim_clear_autocmds {
|
||||
event = "WinLeave",
|
||||
pattern = "NvimTree_*",
|
||||
group = vim.api.nvim_create_augroup("NvimTree", { clear = false }),
|
||||
}
|
||||
|
||||
min_width = min_width - 2
|
||||
end
|
||||
|
||||
configure_buffer_overlay()
|
||||
overlay_winnr = vim.api.nvim_open_win(overlay_bufnr, true, {
|
||||
col = 1,
|
||||
row = 0,
|
||||
relative = "cursor",
|
||||
width = math.max(min_width, vim.api.nvim_win_get_width(view.get_winnr()) - #M.prefix - 2),
|
||||
height = 1,
|
||||
border = "none",
|
||||
style = "minimal",
|
||||
})
|
||||
vim.api.nvim_buf_set_option(overlay_bufnr, "modifiable", true)
|
||||
vim.api.nvim_buf_set_lines(overlay_bufnr, 0, -1, false, { M.filter })
|
||||
vim.cmd "startinsert"
|
||||
vim.api.nvim_win_set_cursor(overlay_winnr, { 1, #M.filter + 1 })
|
||||
end
|
||||
|
||||
function M.start_filtering()
|
||||
M.filter = M.filter or ""
|
||||
|
||||
redraw()
|
||||
local row = require("nvim-tree.core").get_nodes_starting_line() - 1
|
||||
local col = #M.prefix > 0 and #M.prefix - 1 or 1
|
||||
view.set_cursor { row, col }
|
||||
-- needs scheduling to let the cursor move before initializing the window
|
||||
vim.schedule(create_overlay)
|
||||
end
|
||||
|
||||
function M.clear_filter()
|
||||
M.filter = nil
|
||||
reset_filter()
|
||||
redraw()
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
M.prefix = opts.live_filter.prefix
|
||||
M.always_show_folders = opts.live_filter.always_show_folders
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -1,5 +1,3 @@
|
||||
local uv = vim.loop
|
||||
|
||||
local M = {
|
||||
config = nil,
|
||||
path = nil,
|
||||
@@ -29,7 +27,7 @@ function M.profile_start(fmt, ...)
|
||||
return
|
||||
end
|
||||
M.line("profile", "START " .. (fmt or "???"), ...)
|
||||
return uv.hrtime()
|
||||
return vim.loop.hrtime()
|
||||
end
|
||||
|
||||
--- Write to log file via M.line
|
||||
@@ -39,7 +37,7 @@ 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
|
||||
local millis = start and math.modf((vim.loop.hrtime() - start) / 1000000) or -1
|
||||
M.line("profile", "END " .. (fmt or "???") .. " " .. millis .. "ms", ...)
|
||||
end
|
||||
|
||||
@@ -56,7 +54,7 @@ function M.setup(opts)
|
||||
if M.config.truncate then
|
||||
os.remove(M.path)
|
||||
end
|
||||
print("nvim-tree.lua logging to " .. M.path)
|
||||
require("nvim-tree.notify").debug("nvim-tree.lua logging to " .. M.path)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
42
lua/nvim-tree/marks/bulk-move.lua
Normal file
42
lua/nvim-tree/marks/bulk-move.lua
Normal file
@@ -0,0 +1,42 @@
|
||||
local Marks = require "nvim-tree.marks"
|
||||
local Core = require "nvim-tree.core"
|
||||
local utils = require "nvim-tree.utils"
|
||||
local FsRename = require "nvim-tree.actions.fs.rename-file"
|
||||
local notify = require "nvim-tree.notify"
|
||||
|
||||
local M = {}
|
||||
|
||||
function M.bulk_move()
|
||||
if #Marks.get_marks() == 0 then
|
||||
notify.warn "no bookmark to perform bulk move on, aborting."
|
||||
return
|
||||
end
|
||||
|
||||
vim.ui.input({ prompt = "Move to: ", default = Core.get_cwd(), completion = "dir" }, function(location)
|
||||
utils.clear_prompt()
|
||||
if not location or location == "" then
|
||||
return
|
||||
end
|
||||
if vim.fn.filewritable(location) ~= 2 then
|
||||
notify.warn(location .. " is not writable, cannot move.")
|
||||
return
|
||||
end
|
||||
|
||||
local marks = Marks.get_marks()
|
||||
for _, node in pairs(marks) do
|
||||
local head = vim.fn.fnamemodify(node.absolute_path, ":t")
|
||||
local to = utils.path_join { location, head }
|
||||
FsRename.rename(node, to)
|
||||
end
|
||||
|
||||
if M.enable_reload then
|
||||
require("nvim-tree.actions.reloaders.reloaders").reload_explorer()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
M.enable_reload = not opts.filesystem_watchers.enable
|
||||
end
|
||||
|
||||
return M
|
||||
81
lua/nvim-tree/marks/init.lua
Normal file
81
lua/nvim-tree/marks/init.lua
Normal file
@@ -0,0 +1,81 @@
|
||||
local view = require "nvim-tree.view"
|
||||
local Iterator = require "nvim-tree.iterators.node-iterator"
|
||||
local core = require "nvim-tree.core"
|
||||
|
||||
local NvimTreeMarks = {}
|
||||
|
||||
local M = {}
|
||||
|
||||
local function add_mark(node)
|
||||
NvimTreeMarks[node.absolute_path] = node
|
||||
M.draw()
|
||||
end
|
||||
|
||||
local function remove_mark(node)
|
||||
NvimTreeMarks[node.absolute_path] = nil
|
||||
M.draw()
|
||||
end
|
||||
|
||||
function M.toggle_mark(node)
|
||||
if node.absolute_path == nil then
|
||||
return
|
||||
end
|
||||
|
||||
if M.get_mark(node) then
|
||||
remove_mark(node)
|
||||
else
|
||||
add_mark(node)
|
||||
end
|
||||
end
|
||||
|
||||
function M.clear_marks()
|
||||
NvimTreeMarks = {}
|
||||
M.draw()
|
||||
end
|
||||
|
||||
function M.get_mark(node)
|
||||
return NvimTreeMarks[node.absolute_path]
|
||||
end
|
||||
|
||||
function M.get_marks()
|
||||
local list = {}
|
||||
for _, node in pairs(NvimTreeMarks) do
|
||||
table.insert(list, node)
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
local GROUP = "NvimTreeMarkSigns"
|
||||
local SIGN_NAME = "NvimTreeMark"
|
||||
|
||||
function M.clear()
|
||||
vim.fn.sign_unplace(GROUP)
|
||||
end
|
||||
|
||||
function M.draw()
|
||||
if not view.is_visible() then
|
||||
return
|
||||
end
|
||||
|
||||
M.clear()
|
||||
|
||||
local buf = view.get_bufnr()
|
||||
local add = core.get_nodes_starting_line() - 1
|
||||
Iterator.builder(core.get_explorer().nodes)
|
||||
:recursor(function(node)
|
||||
return node.open and node.nodes
|
||||
end)
|
||||
:applier(function(node, idx)
|
||||
if M.get_mark(node) then
|
||||
vim.fn.sign_place(0, GROUP, SIGN_NAME, buf, { lnum = idx + add, priority = 3 })
|
||||
end
|
||||
end)
|
||||
:iterate()
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
vim.fn.sign_define(SIGN_NAME, { text = opts.renderer.icons.glyphs.bookmark, texthl = "NvimTreeBookmark" })
|
||||
require("nvim-tree.marks.bulk-move").setup(opts)
|
||||
end
|
||||
|
||||
return M
|
||||
93
lua/nvim-tree/marks/navigation.lua
Normal file
93
lua/nvim-tree/marks/navigation.lua
Normal file
@@ -0,0 +1,93 @@
|
||||
local Iterator = require "nvim-tree.iterators.node-iterator"
|
||||
local core = require "nvim-tree.core"
|
||||
local Marks = require "nvim-tree.marks"
|
||||
local open_file = require "nvim-tree.actions.node.open-file"
|
||||
local utils = require "nvim-tree.utils"
|
||||
local lib = require "nvim-tree.lib"
|
||||
|
||||
local function get_nearest(node, where)
|
||||
local first, prev, next, last = nil, nil, nil, nil
|
||||
local found = false
|
||||
|
||||
Iterator.builder(core.get_explorer().nodes)
|
||||
:recursor(function(n)
|
||||
return n.open and n.nodes
|
||||
end)
|
||||
:applier(function(n)
|
||||
if n.absolute_path == node.absolute_path then
|
||||
found = true
|
||||
return
|
||||
end
|
||||
|
||||
if not Marks.get_mark(n) then
|
||||
return
|
||||
end
|
||||
|
||||
last = n
|
||||
first = first or n
|
||||
|
||||
if found and not next then
|
||||
next = n
|
||||
end
|
||||
|
||||
if not found then
|
||||
prev = n
|
||||
end
|
||||
end)
|
||||
:iterate()
|
||||
|
||||
if not found then
|
||||
return
|
||||
end
|
||||
|
||||
if where == "next" then
|
||||
return next or first
|
||||
else
|
||||
return prev or last
|
||||
end
|
||||
end
|
||||
|
||||
local function get(where, node)
|
||||
if node then
|
||||
return get_nearest(node, where)
|
||||
end
|
||||
end
|
||||
|
||||
local function open_or_focus(node)
|
||||
if node and not node.nodes and not utils.get_win_buf_from_path(node.absolute_path) then
|
||||
open_file.fn("edit", node.absolute_path)
|
||||
elseif node then
|
||||
utils.focus_file(node.absolute_path)
|
||||
end
|
||||
end
|
||||
|
||||
local function navigate_to(where)
|
||||
return function()
|
||||
local node = lib.get_node_at_cursor()
|
||||
local next = get(where, node)
|
||||
open_or_focus(next)
|
||||
end
|
||||
end
|
||||
|
||||
local M = {}
|
||||
|
||||
M.next = navigate_to "next"
|
||||
M.prev = navigate_to "prev"
|
||||
|
||||
function M.select()
|
||||
local list = vim.tbl_map(function(n)
|
||||
return n.absolute_path
|
||||
end, Marks.get_marks())
|
||||
|
||||
vim.ui.select(list, {
|
||||
prompt = "Select a file to open or a folder to focus",
|
||||
}, function(choice)
|
||||
if not choice or choice == "" then
|
||||
return
|
||||
end
|
||||
local node = Marks.get_mark { absolute_path = choice }
|
||||
open_or_focus(node)
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
||||
44
lua/nvim-tree/notify.lua
Normal file
44
lua/nvim-tree/notify.lua
Normal file
@@ -0,0 +1,44 @@
|
||||
local M = {}
|
||||
|
||||
local config = {
|
||||
threshold = vim.log.levels.INFO,
|
||||
}
|
||||
|
||||
local modes = {
|
||||
{ name = "trace", level = vim.log.levels.TRACE },
|
||||
{ name = "debug", level = vim.log.levels.DEBUG },
|
||||
{ name = "info", level = vim.log.levels.INFO },
|
||||
{ name = "warn", level = vim.log.levels.WARN },
|
||||
{ name = "error", level = vim.log.levels.ERROR },
|
||||
}
|
||||
|
||||
do
|
||||
local has_notify, notify_plugin = pcall(require, "notify")
|
||||
|
||||
local dispatch = function(level, msg)
|
||||
if level < config.threshold then
|
||||
return
|
||||
end
|
||||
|
||||
vim.schedule(function()
|
||||
if has_notify and notify_plugin then
|
||||
notify_plugin(msg, level, { title = "NvimTree" })
|
||||
else
|
||||
vim.notify("[NvimTree] " .. msg, level)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
for _, x in ipairs(modes) do
|
||||
M[x.name] = function(msg)
|
||||
return dispatch(x.level, msg)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
opts = opts or {}
|
||||
config.threshold = opts.notify.threshold or vim.log.levels.INFO
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -1,4 +1,5 @@
|
||||
local utils = require "nvim-tree.utils"
|
||||
local core = require "nvim-tree.core"
|
||||
|
||||
local git = require "nvim-tree.renderer.components.git"
|
||||
local pad = require "nvim-tree.renderer.components.padding"
|
||||
@@ -7,24 +8,22 @@ local icons = require "nvim-tree.renderer.components.icons"
|
||||
local Builder = {}
|
||||
Builder.__index = Builder
|
||||
|
||||
local DEFAULT_ROOT_FOLDER_LABEL = ":~:s?$?/..?"
|
||||
|
||||
function Builder.new(root_cwd)
|
||||
return setmetatable({
|
||||
index = 0,
|
||||
depth = nil,
|
||||
depth = 0,
|
||||
highlights = {},
|
||||
lines = {},
|
||||
markers = {},
|
||||
signs = {},
|
||||
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 ":~"
|
||||
function Builder:configure_root_label(root_folder_label)
|
||||
self.root_folder_label = root_folder_label or DEFAULT_ROOT_FOLDER_LABEL
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -33,8 +32,8 @@ function Builder:configure_trailing_slash(with_trailing)
|
||||
return self
|
||||
end
|
||||
|
||||
function Builder:configure_special_map(special_map)
|
||||
self.special_map = special_map
|
||||
function Builder:configure_special_files(special_files)
|
||||
self.special_files = special_files
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -43,14 +42,14 @@ function Builder:configure_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
|
||||
function Builder:configure_filter(filter, prefix)
|
||||
self.filter_prefix = prefix
|
||||
self.filter = filter
|
||||
return self
|
||||
end
|
||||
|
||||
function Builder:configure_opened_file_highlighting(highlight_opened_files)
|
||||
self.highlight_opened_files = highlight_opened_files
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -61,9 +60,16 @@ function Builder:configure_git_icons_padding(padding)
|
||||
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
|
||||
if where == "signcolumn" then
|
||||
vim.fn.sign_unplace(git.SIGN_GROUP)
|
||||
self.is_git_sign = true
|
||||
end
|
||||
self.is_git_after = where == "after" and not self.is_git_sign
|
||||
return self
|
||||
end
|
||||
|
||||
function Builder:configure_symlink_destination(show)
|
||||
self.symlink_destination = show
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -91,10 +97,11 @@ function Builder:_unwrap_git_data(git_icons_and_hl_groups, offset)
|
||||
end
|
||||
|
||||
local icon = ""
|
||||
for _, v in ipairs(git_icons_and_hl_groups) do
|
||||
for i, 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
|
||||
local remove_padding = self.is_git_after and i == #git_icons_and_hl_groups
|
||||
icon = icon .. v.icon .. (remove_padding and "" or self.git_icon_padding)
|
||||
end
|
||||
end
|
||||
return icon
|
||||
@@ -108,17 +115,27 @@ function Builder:_build_folder(node, padding, git_hl, git_icons_tbl)
|
||||
local icon = icons.get_folder_icon(node.open, node.link_to ~= nil, has_children)
|
||||
|
||||
local foldername = name .. self.trailing_slash
|
||||
if node.link_to and self.symlink_destination then
|
||||
local arrow = icons.i.symlink_arrow
|
||||
local link_to = utils.path_relative(node.link_to, core.get_cwd())
|
||||
foldername = foldername .. arrow .. link_to
|
||||
end
|
||||
|
||||
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 fname_starts_at = offset + #icon + (self.is_git_after and 0 or #git_icons)
|
||||
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)
|
||||
if node.open then
|
||||
self:_insert_highlight("NvimTreeOpenedFolderIcon", offset, offset + #icon)
|
||||
else
|
||||
self:_insert_highlight("NvimTreeClosedFolderIcon", offset, offset + #icon)
|
||||
end
|
||||
end
|
||||
|
||||
local foldername_hl = "NvimTreeFolderName"
|
||||
if self.special_map[node.absolute_path] then
|
||||
if vim.tbl_contains(self.special_files, node.absolute_path) or vim.tbl_contains(self.special_files, node.name) then
|
||||
foldername_hl = "NvimTreeSpecialFolderName"
|
||||
elseif node.open then
|
||||
foldername_hl = "NvimTreeOpenedFolderName"
|
||||
@@ -134,12 +151,13 @@ function Builder:_build_folder(node, padding, git_hl, git_icons_tbl)
|
||||
end
|
||||
|
||||
function Builder:_format_line(before, after, git_icons)
|
||||
git_icons = self.is_git_after and git_icons and " " .. git_icons or git_icons
|
||||
return string.format(
|
||||
"%s%s%s%s",
|
||||
before,
|
||||
#git_icons > 0 and not self.is_git_after and git_icons .. self.git_icon_padding or "",
|
||||
self.is_git_after and "" or git_icons,
|
||||
after,
|
||||
#git_icons > 0 and self.is_git_after and self.git_icon_padding .. git_icons or ""
|
||||
self.is_git_after and git_icons or ""
|
||||
)
|
||||
end
|
||||
|
||||
@@ -148,7 +166,11 @@ function Builder:_build_symlink(node, padding, git_highlight, git_icons_tbl)
|
||||
|
||||
local icon = icons.i.symlink
|
||||
local arrow = icons.i.symlink_arrow
|
||||
local symlink_formatted = node.name .. arrow .. node.link_to
|
||||
local symlink_formatted = node.name
|
||||
if self.symlink_destination then
|
||||
local link_to = utils.path_relative(node.link_to, core.get_cwd())
|
||||
symlink_formatted = symlink_formatted .. arrow .. link_to
|
||||
end
|
||||
|
||||
local link_highlight = git_highlight or "NvimTreeSymlink"
|
||||
|
||||
@@ -172,12 +194,12 @@ function Builder:_highlight_opened_files(node, offset, icon_length, git_icons_le
|
||||
local from = offset
|
||||
local to = offset
|
||||
|
||||
if self.open_file_highlight == "icon" then
|
||||
if self.highlight_opened_files == "icon" then
|
||||
to = from + icon_length
|
||||
elseif self.open_file_highlight == "name" then
|
||||
elseif self.highlight_opened_files == "name" then
|
||||
from = offset + icon_length + git_icons_length
|
||||
to = from + #node.name
|
||||
elseif self.open_file_highlight == "all" then
|
||||
elseif self.highlight_opened_files == "all" then
|
||||
to = from + icon_length + git_icons_length + #node.name
|
||||
end
|
||||
|
||||
@@ -198,7 +220,7 @@ function Builder:_build_file(node, padding, git_highlight, git_icons_tbl)
|
||||
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
|
||||
if vim.tbl_contains(self.special_files, node.absolute_path) or vim.tbl_contains(self.special_files, node.name) then
|
||||
self:_insert_highlight("NvimTreeSpecialFile", col_start, col_end)
|
||||
elseif node.executable then
|
||||
self:_insert_highlight("NvimTreeExecFile", col_start, col_end)
|
||||
@@ -206,7 +228,7 @@ function Builder:_build_file(node, padding, git_highlight, git_icons_tbl)
|
||||
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
|
||||
local should_highlight_opened_files = self.highlight_opened_files 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
|
||||
@@ -216,16 +238,22 @@ function Builder:_build_file(node, padding, git_highlight, git_icons_tbl)
|
||||
end
|
||||
end
|
||||
|
||||
function Builder:_build_line(tree, node, idx)
|
||||
local padding = pad.get_padding(self.depth, idx, tree, node, self.markers)
|
||||
function Builder:_build_line(node, idx, num_children)
|
||||
local padding = pad.get_padding(self.depth, idx, num_children, node, self.markers)
|
||||
|
||||
if self.depth > 0 then
|
||||
if string.len(padding) > 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)
|
||||
|
||||
if self.is_git_sign and git_icons_tbl and #git_icons_tbl > 0 then
|
||||
local git_info = git_icons_tbl[1]
|
||||
table.insert(self.signs, { sign = git_info.hl, lnum = self.index + 1 })
|
||||
git_icons_tbl = {}
|
||||
end
|
||||
|
||||
local is_folder = node.nodes ~= nil
|
||||
local is_symlink = node.link_to ~= nil
|
||||
|
||||
@@ -239,38 +267,73 @@ function Builder:_build_line(tree, node, idx)
|
||||
self.index = self.index + 1
|
||||
|
||||
if node.open then
|
||||
self.depth = self.depth + 2
|
||||
self.depth = self.depth + 1
|
||||
self:build(node)
|
||||
self.depth = self.depth - 2
|
||||
self.depth = self.depth - 1
|
||||
end
|
||||
end
|
||||
|
||||
function Builder:_get_nodes_number(nodes)
|
||||
if not self.filter then
|
||||
return #nodes
|
||||
end
|
||||
|
||||
local i = 0
|
||||
for _, n in pairs(nodes) do
|
||||
if not n.hidden then
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
return i
|
||||
end
|
||||
|
||||
function Builder:build(tree)
|
||||
for idx, node in ipairs(tree.nodes) do
|
||||
self:_build_line(tree, node, idx)
|
||||
local num_children = self:_get_nodes_number(tree.nodes)
|
||||
local idx = 1
|
||||
for _, node in ipairs(tree.nodes) do
|
||||
if not node.hidden then
|
||||
self:_build_line(node, idx, num_children)
|
||||
idx = idx + 1
|
||||
end
|
||||
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, ".." }
|
||||
local function format_root_name(root_cwd, root_label)
|
||||
if type(root_label) == "function" then
|
||||
local label = root_label(root_cwd)
|
||||
if type(label) == "string" then
|
||||
return label
|
||||
else
|
||||
root_label = DEFAULT_ROOT_FOLDER_LABEL
|
||||
end
|
||||
end
|
||||
return utils.path_remove_trailing(vim.fn.fnamemodify(root_cwd, root_label))
|
||||
end
|
||||
|
||||
function Builder:build_header(show_header)
|
||||
if show_header then
|
||||
local root_name = format_root_name(self.root_cwd, self.root_folder_modifier)
|
||||
local root_name = format_root_name(self.root_cwd, self.root_folder_label)
|
||||
self:_insert_line(root_name)
|
||||
self:_insert_highlight("NvimTreeRootFolder", 0, string.len(root_name))
|
||||
self.index = 1
|
||||
end
|
||||
|
||||
if self.filter then
|
||||
local filter_line = self.filter_prefix .. "/" .. self.filter .. "/"
|
||||
self:_insert_line(filter_line)
|
||||
local prefix_length = string.len(self.filter_prefix)
|
||||
self:_insert_highlight("NvimTreeLiveFilterPrefix", 0, prefix_length)
|
||||
self:_insert_highlight("NvimTreeLiveFilterValue", prefix_length, string.len(filter_line))
|
||||
self.index = self.index + 1
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function Builder:unwrap()
|
||||
return self.lines, self.highlights
|
||||
return self.lines, self.highlights, self.signs
|
||||
end
|
||||
|
||||
return Builder
|
||||
|
||||
111
lua/nvim-tree/renderer/components/full-name.lua
Normal file
111
lua/nvim-tree/renderer/components/full-name.lua
Normal file
@@ -0,0 +1,111 @@
|
||||
local M = {}
|
||||
|
||||
local utils = require "nvim-tree.utils"
|
||||
|
||||
local function hide(win)
|
||||
if win then
|
||||
if vim.api.nvim_win_is_valid(win) then
|
||||
vim.api.nvim_win_close(win, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- reduce signcolumn/foldcolumn from window width
|
||||
local function effective_win_width()
|
||||
local win_width = vim.fn.winwidth(0)
|
||||
|
||||
-- return zero if the window cannot be found
|
||||
local win_id = vim.fn.win_getid()
|
||||
|
||||
if win_id == 0 then
|
||||
return win_width
|
||||
end
|
||||
|
||||
-- if the window does not exist the result is an empty list
|
||||
local win_info = vim.fn.getwininfo(win_id)
|
||||
|
||||
-- check if result table is empty
|
||||
if next(win_info) == nil then
|
||||
return win_width
|
||||
end
|
||||
|
||||
return win_width - win_info[1].textoff
|
||||
end
|
||||
|
||||
local function show()
|
||||
local line_nr = vim.api.nvim_win_get_cursor(0)[1]
|
||||
if line_nr == 1 and require("nvim-tree.view").is_root_folder_visible() then
|
||||
return
|
||||
end
|
||||
if vim.wo.wrap then
|
||||
return
|
||||
end
|
||||
-- only work for left tree
|
||||
if vim.api.nvim_win_get_position(0)[2] ~= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local line = vim.fn.getline "."
|
||||
local leftcol = vim.fn.winsaveview().leftcol
|
||||
-- hide full name if left column of node in nvim-tree win is not zero
|
||||
if leftcol ~= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local text_width = vim.fn.strdisplaywidth(vim.fn.substitute(line, "[^[:print:]]*$", "", "g"))
|
||||
local win_width = effective_win_width()
|
||||
|
||||
if text_width < win_width then
|
||||
return
|
||||
end
|
||||
|
||||
M.popup_win = vim.api.nvim_open_win(vim.api.nvim_create_buf(false, false), false, {
|
||||
relative = "win",
|
||||
bufpos = { vim.fn.line "." - 2, 0 },
|
||||
width = math.min(text_width, vim.o.columns - 2),
|
||||
height = 1,
|
||||
noautocmd = true,
|
||||
style = "minimal",
|
||||
})
|
||||
|
||||
local ns_id = vim.api.nvim_get_namespaces()["NvimTreeHighlights"]
|
||||
local extmarks = vim.api.nvim_buf_get_extmarks(0, ns_id, { line_nr - 1, 0 }, { line_nr - 1, -1 }, { details = 1 })
|
||||
vim.api.nvim_win_call(M.popup_win, function()
|
||||
vim.fn.setbufline("%", 1, line)
|
||||
for _, extmark in ipairs(extmarks) do
|
||||
local hl = extmark[4]
|
||||
vim.api.nvim_buf_add_highlight(0, ns_id, hl.hl_group, 0, extmark[3], hl.end_col)
|
||||
end
|
||||
vim.cmd [[ setlocal nowrap cursorline noswapfile nobuflisted buftype=nofile bufhidden=hide ]]
|
||||
end)
|
||||
end
|
||||
|
||||
M.setup = function(opts)
|
||||
M.config = opts.renderer
|
||||
if not M.config.full_name then
|
||||
return
|
||||
end
|
||||
|
||||
local group = vim.api.nvim_create_augroup("nvim_tree_floating_node", { clear = true })
|
||||
vim.api.nvim_create_autocmd({ "BufLeave", "CursorMoved" }, {
|
||||
group = group,
|
||||
pattern = { "NvimTree_*" },
|
||||
callback = function()
|
||||
if utils.is_nvim_tree_buf(0) then
|
||||
hide(M.popup_win)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd({ "CursorMoved" }, {
|
||||
group = group,
|
||||
pattern = { "NvimTree_*" },
|
||||
callback = function()
|
||||
if utils.is_nvim_tree_buf(0) then
|
||||
show()
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -1,10 +1,11 @@
|
||||
local _icons = require "nvim-tree.renderer.icon-config"
|
||||
local utils = require "nvim-tree.utils"
|
||||
local notify = require "nvim-tree.notify"
|
||||
local explorer_common = require "nvim-tree.explorer.common"
|
||||
|
||||
local M = {}
|
||||
local M = {
|
||||
SIGN_GROUP = "NvimTreeGitSigns",
|
||||
}
|
||||
|
||||
local function build_icons_table()
|
||||
local i = M.icon_state.icons.git_icons
|
||||
local function build_icons_table(i)
|
||||
return {
|
||||
["M "] = { { icon = i.staged, hl = "NvimTreeGitStaged" } },
|
||||
[" M"] = { { icon = i.unstaged, hl = "NvimTreeGitDirty" } },
|
||||
@@ -68,22 +69,22 @@ end
|
||||
local function nil_() end
|
||||
|
||||
local function warn_status(git_status)
|
||||
utils.warn(
|
||||
notify.warn(
|
||||
'Unrecognized git state "'
|
||||
.. git_status
|
||||
.. '". Please open up an issue on https://github.com/kyazdani42/nvim-tree.lua/issues with this message.'
|
||||
.. '". Please open up an issue on https://github.com/nvim-tree/nvim-tree.lua/issues with this message.'
|
||||
)
|
||||
end
|
||||
|
||||
local function get_icons_(node)
|
||||
local git_status = node.git_status
|
||||
if not git_status then
|
||||
if not explorer_common.shows_git_status(node) then
|
||||
return nil
|
||||
end
|
||||
|
||||
local git_status = node.git_status
|
||||
local icons = M.git_icons[git_status]
|
||||
if not icons then
|
||||
if vim.g.nvim_tree_git_hl ~= 1 then
|
||||
if not M.config.highlight_git then
|
||||
warn_status(git_status)
|
||||
end
|
||||
return nil
|
||||
@@ -99,6 +100,7 @@ local git_hl = {
|
||||
["AD"] = "NvimTreeFileStaged",
|
||||
["MD"] = "NvimTreeFileStaged",
|
||||
["T "] = "NvimTreeFileStaged",
|
||||
["TT"] = "NvimTreeFileStaged",
|
||||
[" M"] = "NvimTreeFileDirty",
|
||||
["CM"] = "NvimTreeFileDirty",
|
||||
[" C"] = "NvimTreeFileDirty",
|
||||
@@ -120,39 +122,49 @@ local git_hl = {
|
||||
["R "] = "NvimTreeFileRenamed",
|
||||
["RM"] = "NvimTreeFileRenamed",
|
||||
[" R"] = "NvimTreeFileRenamed",
|
||||
["!!"] = "NvimTreeGitIgnored",
|
||||
["!!"] = "NvimTreeFileIgnored",
|
||||
[" A"] = "none",
|
||||
}
|
||||
|
||||
function M.setup_signs(i)
|
||||
vim.fn.sign_define("NvimTreeGitDirty", { text = i.unstaged, texthl = "NvimTreeGitDirty" })
|
||||
vim.fn.sign_define("NvimTreeGitStaged", { text = i.staged, texthl = "NvimTreeGitStaged" })
|
||||
vim.fn.sign_define("NvimTreeGitMerge", { text = i.unmerged, texthl = "NvimTreeGitMerge" })
|
||||
vim.fn.sign_define("NvimTreeGitRenamed", { text = i.renamed, texthl = "NvimTreeGitRenamed" })
|
||||
vim.fn.sign_define("NvimTreeGitNew", { text = i.untracked, texthl = "NvimTreeGitNew" })
|
||||
vim.fn.sign_define("NvimTreeGitDeleted", { text = i.deleted, texthl = "NvimTreeGitDeleted" })
|
||||
vim.fn.sign_define("NvimTreeGitIgnored", { text = i.ignored, texthl = "NvimTreeGitIgnored" })
|
||||
end
|
||||
|
||||
local function get_highlight_(node)
|
||||
local git_status = node.git_status
|
||||
if not git_status then
|
||||
if not explorer_common.shows_git_status(node) then
|
||||
return
|
||||
end
|
||||
|
||||
return git_hl[git_status]
|
||||
end
|
||||
|
||||
M.get_icons = nil_
|
||||
M.get_highlight = nil_
|
||||
function M.setup(opts)
|
||||
M.config = opts.renderer
|
||||
|
||||
M.icon_state = _icons.get_config()
|
||||
M.git_icons = build_icons_table()
|
||||
M.git_icons = build_icons_table(opts.renderer.icons.glyphs.git)
|
||||
|
||||
function M.reload()
|
||||
M.icon_state = _icons.get_config()
|
||||
M.git_icons = build_icons_table()
|
||||
M.setup_signs(opts.renderer.icons.glyphs.git)
|
||||
|
||||
if M.icon_state.show_git_icon then
|
||||
if opts.renderer.icons.show.git then
|
||||
M.get_icons = get_icons_
|
||||
else
|
||||
M.get_icons = nil_
|
||||
end
|
||||
if vim.g.nvim_tree_git_hl == 1 then
|
||||
|
||||
if opts.renderer.highlight_git then
|
||||
M.get_highlight = get_highlight_
|
||||
else
|
||||
M.get_highlight = nil_
|
||||
end
|
||||
|
||||
M.git_show_on_open_dirs = opts.git.show_on_open_dirs
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
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 " ➛ "
|
||||
M.i.symlink = #M.config.glyphs.symlink > 0 and M.config.glyphs.symlink .. M.config.padding or ""
|
||||
M.i.symlink_arrow = M.config.symlink_arrow
|
||||
end
|
||||
|
||||
local function empty()
|
||||
@@ -14,30 +12,30 @@ 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
|
||||
n = M.config.glyphs.folder.symlink_open
|
||||
elseif is_symlink then
|
||||
n = M.icons.folder_icons.symlink
|
||||
n = M.config.glyphs.folder.symlink
|
||||
elseif open then
|
||||
if has_children then
|
||||
n = M.icons.folder_icons.open
|
||||
n = M.config.glyphs.folder.open
|
||||
else
|
||||
n = M.icons.folder_icons.empty_open
|
||||
n = M.config.glyphs.folder.empty_open
|
||||
end
|
||||
else
|
||||
if has_children then
|
||||
n = M.icons.folder_icons.default
|
||||
n = M.config.glyphs.folder.default
|
||||
else
|
||||
n = M.icons.folder_icons.empty
|
||||
n = M.config.glyphs.folder.empty
|
||||
end
|
||||
end
|
||||
return n .. M.padding
|
||||
return n .. M.config.padding
|
||||
end
|
||||
|
||||
local function get_file_icon_default()
|
||||
local hl_group = "NvimTreeFileIcon"
|
||||
local icon = M.icons.default
|
||||
local icon = M.config.glyphs.default
|
||||
if #icon > 0 then
|
||||
return icon .. M.padding, hl_group
|
||||
return icon .. M.config.padding, hl_group
|
||||
else
|
||||
return ""
|
||||
end
|
||||
@@ -45,11 +43,11 @@ 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
|
||||
if not M.config.webdev_colors then
|
||||
hl_group = "NvimTreeFileIcon"
|
||||
end
|
||||
if icon and hl_group ~= "DevIconDefault" then
|
||||
return icon .. M.padding, hl_group
|
||||
return icon .. M.config.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, "%.(.*)"))
|
||||
@@ -59,7 +57,7 @@ local function get_file_icon_webdev(fname, extension)
|
||||
end
|
||||
|
||||
local function config_file_icon()
|
||||
if M.configs.show_file_icon then
|
||||
if M.config.show.file then
|
||||
if M.devicons then
|
||||
M.get_file_icon = get_file_icon_webdev
|
||||
else
|
||||
@@ -71,23 +69,23 @@ local function config_file_icon()
|
||||
end
|
||||
|
||||
local function config_folder_icon()
|
||||
if M.configs.show_folder_icon then
|
||||
if M.config.show.folder 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
|
||||
|
||||
function M.reset_config()
|
||||
config_symlinks()
|
||||
config_file_icon()
|
||||
config_folder_icon()
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
M.config = opts.renderer.icons
|
||||
|
||||
M.devicons = pcall(require, "nvim-web-devicons") and require "nvim-web-devicons" or nil
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -1,53 +1,112 @@
|
||||
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 .. " "
|
||||
local function check_siblings_for_folder(node, with_arrows)
|
||||
if with_arrows then
|
||||
local has_files = false
|
||||
local has_folders = false
|
||||
for _, n in pairs(node.parent.nodes) do
|
||||
if n.nodes and node.absolute_path ~= n.absolute_path then
|
||||
has_folders = true
|
||||
end
|
||||
if not n.nodes then
|
||||
has_files = true
|
||||
end
|
||||
if has_files and has_folders then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return string.rep(" ", depth)
|
||||
end
|
||||
return false
|
||||
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
|
||||
local function get_padding_indent_markers(depth, idx, nodes_number, markers, with_arrows, inline_arrows, node)
|
||||
local base_padding = with_arrows and (not node.nodes or depth > 0) and " " or ""
|
||||
local padding = (inline_arrows or depth == 0) and base_padding or ""
|
||||
|
||||
if depth > 0 then
|
||||
local has_folder_sibling = check_siblings_for_folder(node, with_arrows)
|
||||
local indent = string.rep(" ", M.config.indent_width - 1)
|
||||
markers[depth] = idx ~= nodes_number
|
||||
for i = 1, depth do
|
||||
local glyph
|
||||
if idx == nodes_number and i == depth then
|
||||
local bottom_width = M.config.indent_width
|
||||
- 2
|
||||
+ (with_arrows and not inline_arrows and has_folder_sibling and 2 or 0)
|
||||
glyph = M.config.indent_markers.icons.corner
|
||||
.. string.rep(M.config.indent_markers.icons.bottom, bottom_width)
|
||||
.. (M.config.indent_width > 1 and " " or "")
|
||||
elseif markers[i] and i == depth then
|
||||
glyph = M.config.indent_markers.icons.item .. indent
|
||||
elseif markers[i] then
|
||||
padding = padding .. M.config.indent_markers.icons.edge
|
||||
glyph = M.config.indent_markers.icons.edge .. indent
|
||||
else
|
||||
padding = padding .. M.config.indent_markers.icons.none
|
||||
glyph = M.config.indent_markers.icons.none .. indent
|
||||
end
|
||||
|
||||
if not with_arrows or (inline_arrows and (depth ~= i or not node.nodes)) then
|
||||
padding = padding .. glyph
|
||||
elseif inline_arrows then
|
||||
padding = padding
|
||||
elseif idx ~= nodes_number and depth == i and not node.nodes and has_folder_sibling then
|
||||
padding = padding .. base_padding .. glyph .. base_padding
|
||||
else
|
||||
padding = padding .. base_padding .. glyph
|
||||
end
|
||||
end
|
||||
end
|
||||
return padding
|
||||
end
|
||||
|
||||
function M.reload_padding_function()
|
||||
local icon_state = require("nvim-tree.renderer.icon-config").get_config()
|
||||
local function get_padding_arrows(node, indent)
|
||||
if node.nodes then
|
||||
return M.config.icons.glyphs.folder[node.open and "arrow_open" or "arrow_closed"] .. " "
|
||||
elseif indent then
|
||||
return " "
|
||||
else
|
||||
return ""
|
||||
end
|
||||
end
|
||||
|
||||
if icon_state.show_folder_icon and icon_state.show_folder_arrows then
|
||||
M.get_padding = get_padding_arrows(icon_state)
|
||||
function M.get_padding(depth, idx, nodes_number, node, markers)
|
||||
local padding = ""
|
||||
|
||||
local show_arrows = M.config.icons.show.folder_arrow
|
||||
local show_markers = M.config.indent_markers.enable
|
||||
local inline_arrows = M.config.indent_markers.inline_arrows
|
||||
local indent_width = M.config.indent_width
|
||||
|
||||
if show_markers then
|
||||
padding = padding .. get_padding_indent_markers(depth, idx, nodes_number, markers, show_arrows, inline_arrows, node)
|
||||
else
|
||||
padding = padding .. string.rep(" ", depth * indent_width)
|
||||
end
|
||||
|
||||
if M.config.indent_markers.enable then
|
||||
M.get_padding = get_padding_indent_markers
|
||||
if show_arrows then
|
||||
padding = padding .. get_padding_arrows(node, not show_markers)
|
||||
end
|
||||
|
||||
return padding
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
M.config = {
|
||||
indent_markers = opts.renderer.indent_markers,
|
||||
}
|
||||
M.config = opts.renderer
|
||||
|
||||
if M.config.indent_width < 1 then
|
||||
M.config.indent_width = 1
|
||||
end
|
||||
|
||||
local function check_marker(symbol)
|
||||
if #symbol == 0 then
|
||||
return " "
|
||||
end
|
||||
-- return the first character from the UTF-8 encoded string; we may use utf8.codes from Lua 5.3 when available
|
||||
return symbol:match "[%z\1-\127\194-\244][\128-\191]*"
|
||||
end
|
||||
|
||||
for k, v in pairs(M.config.indent_markers.icons) do
|
||||
M.config.indent_markers.icons[k] = check_marker(v)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
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
|
||||
@@ -5,41 +5,40 @@ local view = require "nvim-tree.view"
|
||||
|
||||
local _padding = require "nvim-tree.renderer.components.padding"
|
||||
local icon_component = require "nvim-tree.renderer.components.icons"
|
||||
local full_name = require "nvim-tree.renderer.components.full-name"
|
||||
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 live_filter = require "nvim-tree.live-filter"
|
||||
local marks = require "nvim-tree.marks"
|
||||
|
||||
local M = {
|
||||
last_highlights = {},
|
||||
}
|
||||
|
||||
local namespace_id = api.nvim_create_namespace "NvimTreeHighlights"
|
||||
local namespace_id = vim.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)
|
||||
local function _draw(bufnr, lines, hl, signs)
|
||||
vim.api.nvim_buf_set_option(bufnr, "modifiable", true)
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
||||
M.render_hl(bufnr, hl)
|
||||
api.nvim_buf_set_option(bufnr, "modifiable", false)
|
||||
vim.api.nvim_buf_set_option(bufnr, "modifiable", false)
|
||||
vim.fn.sign_unplace(git.SIGN_GROUP)
|
||||
for _, sign in pairs(signs) do
|
||||
vim.fn.sign_place(0, git.SIGN_GROUP, sign.sign, bufnr, { lnum = sign.lnum, priority = 1 })
|
||||
end
|
||||
end
|
||||
|
||||
function M.render_hl(bufnr, hl)
|
||||
if not bufnr or not api.nvim_buf_is_loaded(bufnr) then
|
||||
if not bufnr or not vim.api.nvim_buf_is_loaded(bufnr) then
|
||||
return
|
||||
end
|
||||
api.nvim_buf_clear_namespace(bufnr, namespace_id, 0, -1)
|
||||
vim.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])
|
||||
vim.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,
|
||||
@@ -47,70 +46,65 @@ local picture_map = {
|
||||
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
|
||||
if not core.get_explorer() or not bufnr or not vim.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 cursor = vim.api.nvim_win_get_cursor(view.get_winnr())
|
||||
icon_component.reset_config()
|
||||
|
||||
local lines, hl
|
||||
local signs = {}
|
||||
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())
|
||||
lines, hl, signs = Builder.new(core.get_cwd())
|
||||
:configure_root_label(M.config.root_folder_label)
|
||||
:configure_trailing_slash(M.config.add_trailing)
|
||||
:configure_special_files(M.config.special_files)
|
||||
: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_opened_file_highlighting(M.config.highlight_opened_files)
|
||||
:configure_git_icons_padding(M.config.icons.padding)
|
||||
:configure_git_icons_placement(M.config.icons.git_placement)
|
||||
:configure_symlink_destination(M.config.symlink_destination)
|
||||
:configure_filter(live_filter.filter, live_filter.prefix)
|
||||
:build_header(view.is_root_folder_visible(core.get_cwd()))
|
||||
:build(core.get_explorer())
|
||||
:unwrap()
|
||||
end
|
||||
|
||||
_draw(bufnr, lines, hl)
|
||||
_draw(bufnr, lines, hl, signs)
|
||||
|
||||
M.last_highlights = hl
|
||||
|
||||
if cursor and #lines >= cursor[1] then
|
||||
api.nvim_win_set_cursor(view.get_winnr(), cursor)
|
||||
vim.api.nvim_win_set_cursor(view.get_winnr(), cursor)
|
||||
end
|
||||
|
||||
if view.is_help_ui() then
|
||||
diagnostics.clear()
|
||||
marks.clear()
|
||||
else
|
||||
diagnostics.update()
|
||||
marks.draw()
|
||||
end
|
||||
|
||||
view.grow_from_content()
|
||||
|
||||
log.profile_end(ps, "draw")
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
M.config = {
|
||||
indent_markers = opts.renderer.indent_markers,
|
||||
icons = opts.renderer.icons,
|
||||
}
|
||||
M.config = opts.renderer
|
||||
|
||||
_padding.setup(opts)
|
||||
full_name.setup(opts)
|
||||
git.setup(opts)
|
||||
icon_component.setup(opts)
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -1,41 +1,32 @@
|
||||
local has_notify, notify = pcall(require, "notify")
|
||||
local Iterator = require "nvim-tree.iterators.node-iterator"
|
||||
local notify = require "nvim-tree.notify"
|
||||
|
||||
local a = vim.api
|
||||
local uv = vim.loop
|
||||
|
||||
local M = {}
|
||||
local M = {
|
||||
debouncers = {},
|
||||
}
|
||||
|
||||
M.is_windows = vim.fn.has "win32" == 1 or vim.fn.has "win32unix" == 1
|
||||
M.is_wsl = vim.fn.has "wsl" == 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)
|
||||
local fd = vim.loop.fs_open(path, "r", 438)
|
||||
if not fd then
|
||||
return ""
|
||||
end
|
||||
local stat = uv.fs_fstat(fd)
|
||||
local stat = vim.loop.fs_fstat(fd)
|
||||
if not stat then
|
||||
return ""
|
||||
end
|
||||
local data = uv.fs_read(fd, stat.size, 0)
|
||||
uv.fs_close(fd)
|
||||
local data = vim.loop.fs_read(fd, stat.size, 0)
|
||||
vim.loop.fs_close(fd)
|
||||
return data or ""
|
||||
end
|
||||
|
||||
@@ -84,45 +75,61 @@ 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
|
||||
-- get the node and index of the node from the tree that matches the predicate.
|
||||
-- The explored nodes are those displayed on the view.
|
||||
-- @param 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
|
||||
local node, i = Iterator.builder(nodes)
|
||||
:matcher(fn)
|
||||
:recursor(function(node)
|
||||
return node.open and #node.nodes > 0 and node.nodes
|
||||
end)
|
||||
:iterate()
|
||||
i = require("nvim-tree.view").is_root_folder_visible() and i or i - 1
|
||||
i = require("nvim-tree.live-filter").filter and i + 1 or i
|
||||
return node, i
|
||||
end
|
||||
|
||||
-- get the node in the tree state depending on the absolute path of the node
|
||||
-- (grouped or hidden too)
|
||||
function M.get_node_from_path(path)
|
||||
local explorer = require("nvim-tree.core").get_explorer()
|
||||
|
||||
-- tree may not yet be loaded
|
||||
if not explorer then
|
||||
return
|
||||
end
|
||||
|
||||
if explorer.absolute_path == path then
|
||||
return explorer
|
||||
end
|
||||
|
||||
return Iterator.builder(explorer.nodes)
|
||||
:hidden()
|
||||
:matcher(function(node)
|
||||
return node.absolute_path == path or node.link_to == path
|
||||
end)
|
||||
:recursor(function(node)
|
||||
if node.group_next then
|
||||
return { node.group_next }
|
||||
end
|
||||
if node.nodes then
|
||||
return node.nodes
|
||||
end
|
||||
end)
|
||||
:iterate()
|
||||
end
|
||||
|
||||
-- get the highest parent of grouped nodes
|
||||
function M.get_parent_of_group(node_)
|
||||
local node = node_
|
||||
while node.parent and node.parent.group_next do
|
||||
node = node.parent
|
||||
end
|
||||
return node
|
||||
end
|
||||
|
||||
-- return visible nodes indexed by line
|
||||
-- @param nodes_all list of node
|
||||
-- @param line_start first index
|
||||
@@ -130,51 +137,93 @@ end
|
||||
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
|
||||
|
||||
Iterator.builder(nodes_all)
|
||||
:applier(function(node)
|
||||
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)
|
||||
end)
|
||||
:recursor(function(node)
|
||||
return node.open == true and node.nodes
|
||||
end)
|
||||
:iterate()
|
||||
|
||||
return nodes_by_line
|
||||
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
|
||||
function M.is_windows_exe(ext)
|
||||
if not M.pathexts then
|
||||
if not vim.env.PATHEXT then
|
||||
return false
|
||||
end
|
||||
|
||||
local wexe = vim.split(vim.env.PATHEXT:gsub("%.", ""), ";")
|
||||
M.pathexts = {}
|
||||
for _, v in pairs(wexe) do
|
||||
M.pathexts[v] = true
|
||||
end
|
||||
end
|
||||
|
||||
return M.pathexts[ext:upper()]
|
||||
end
|
||||
|
||||
function M.is_windows_exe(ext)
|
||||
return pathexts[ext:upper()]
|
||||
--- Check whether path maps to Windows filesystem mounted by WSL
|
||||
-- @param path string
|
||||
-- @return boolean
|
||||
function M.is_wsl_windows_fs_path(path)
|
||||
-- Run 'wslpath' command to try translating WSL path to Windows path.
|
||||
-- Consume stderr output as well because 'wslpath' can produce permission
|
||||
-- errors on some files (e.g. temporary files in root of system drive).
|
||||
local handle = io.popen('wslpath -w "' .. path .. '" 2>/dev/null')
|
||||
if handle then
|
||||
local output = handle:read "*a"
|
||||
handle:close()
|
||||
|
||||
return string.find(output, "^\\\\wsl$\\") == nil
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Check whether extension is Windows executable under WSL
|
||||
-- @param ext string
|
||||
-- @return boolean
|
||||
function M.is_wsl_windows_fs_exe(ext)
|
||||
if not vim.env.PATHEXT then
|
||||
-- Extract executable extensions from within WSL.
|
||||
-- Redirect stderr to null to silence warnings when
|
||||
-- Windows command is executed from Linux filesystem:
|
||||
-- > CMD.EXE was started with the above path as the current directory.
|
||||
-- > UNC paths are not supported. Defaulting to Windows directory.
|
||||
local handle = io.popen 'cmd.exe /c "echo %PATHEXT%" 2>/dev/null'
|
||||
if handle then
|
||||
vim.env.PATHEXT = handle:read "*a"
|
||||
handle:close()
|
||||
end
|
||||
end
|
||||
|
||||
return M.is_windows_exe(ext)
|
||||
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)
|
||||
for _, buf in pairs(vim.api.nvim_list_bufs()) do
|
||||
if vim.api.nvim_buf_is_loaded(buf) then
|
||||
local buf_name = vim.api.nvim_buf_get_name(buf)
|
||||
local exact_match = buf_name == old_path
|
||||
local child_match = (
|
||||
buf_name:sub(1, #old_path) == old_path and buf_name:sub(#old_path + 1, #old_path + 1) == path_separator
|
||||
)
|
||||
if exact_match or child_match then
|
||||
a.nvim_buf_set_name(buf, new_path .. buf_name:sub(#old_path + 1))
|
||||
vim.api.nvim_buf_set_name(buf, new_path .. buf_name:sub(#old_path + 1))
|
||||
-- to avoid the 'overwrite existing file' error message on write for
|
||||
-- normal files
|
||||
if a.nvim_buf_get_option(buf, "buftype") == "" then
|
||||
a.nvim_buf_call(buf, function()
|
||||
if vim.api.nvim_buf_get_option(buf, "buftype") == "" then
|
||||
vim.api.nvim_buf_call(buf, function()
|
||||
vim.cmd "silent! write!"
|
||||
vim.cmd "edit"
|
||||
end)
|
||||
end
|
||||
end
|
||||
@@ -200,15 +249,15 @@ end
|
||||
|
||||
-- Create empty sub-tables if not present
|
||||
-- @param tbl to create empty inside of
|
||||
-- @param sub dot separated string of sub-tables
|
||||
-- @param path dot separated string of sub-tables
|
||||
-- @return deepest sub-table
|
||||
function M.table_create_missing(tbl, sub)
|
||||
function M.table_create_missing(tbl, path)
|
||||
if tbl == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
local t = tbl
|
||||
for s in string.gmatch(sub, "([^%.]+)%.*") do
|
||||
for s in string.gmatch(path, "([^%.]+)%.*") do
|
||||
if t[s] == nil then
|
||||
t[s] = {}
|
||||
end
|
||||
@@ -218,6 +267,57 @@ function M.table_create_missing(tbl, sub)
|
||||
return t
|
||||
end
|
||||
|
||||
--- Move a value from src to dst if value is nil on dst.
|
||||
--- Remove value from src
|
||||
--- @param src table to copy from
|
||||
--- @param src_path string dot separated string of sub-tables
|
||||
--- @param src_pos string value pos
|
||||
--- @param dst table to copy to
|
||||
--- @param dst_path string dot separated string of sub-tables, created when missing
|
||||
--- @param dst_pos string value pos
|
||||
--- @param remove boolean default true
|
||||
function M.move_missing_val(src, src_path, src_pos, dst, dst_path, dst_pos, remove)
|
||||
if remove == nil then
|
||||
remove = true
|
||||
end
|
||||
|
||||
local ok, err = pcall(vim.validate, {
|
||||
src = { src, "table" },
|
||||
src_path = { src_path, "string" },
|
||||
src_pos = { src_pos, "string" },
|
||||
dst = { dst, "table" },
|
||||
dst_path = { dst_path, "string" },
|
||||
dst_pos = { dst_pos, "string" },
|
||||
remove = { remove, "boolean" },
|
||||
})
|
||||
if not ok then
|
||||
notify.warn("move_missing_val: " .. (err or "invalid arguments"))
|
||||
return
|
||||
end
|
||||
|
||||
for pos in string.gmatch(src_path, "([^%.]+)%.*") do
|
||||
if src[pos] and type(src[pos]) == "table" then
|
||||
src = src[pos]
|
||||
else
|
||||
src = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
local src_val = src and src[src_pos]
|
||||
if src_val == nil then
|
||||
return
|
||||
end
|
||||
|
||||
dst = M.table_create_missing(dst, dst_path)
|
||||
if dst[dst_pos] == nil then
|
||||
dst[dst_pos] = src_val
|
||||
end
|
||||
|
||||
if remove then
|
||||
src[src_pos] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function M.format_bytes(bytes)
|
||||
local units = { "B", "K", "M", "G", "T" }
|
||||
|
||||
@@ -241,4 +341,140 @@ function M.key_by(tbl, key)
|
||||
return keyed
|
||||
end
|
||||
|
||||
function M.bool_record(tbl, key)
|
||||
local keyed = {}
|
||||
for _, val in ipairs(tbl) do
|
||||
keyed[val[key]] = true
|
||||
end
|
||||
return keyed
|
||||
end
|
||||
|
||||
local function timer_stop_close(timer)
|
||||
if timer:is_active() then
|
||||
timer:stop()
|
||||
end
|
||||
if not timer:is_closing() then
|
||||
timer:close()
|
||||
end
|
||||
end
|
||||
|
||||
---Execute callback timeout ms after the latest invocation with context.
|
||||
---Waiting invocations for that context will be discarded.
|
||||
---Invocation will be rescheduled while a callback is being executed.
|
||||
---Caller must ensure that callback performs the same or functionally equivalent actions.
|
||||
---
|
||||
---@param context string identifies the callback to debounce
|
||||
---@param timeout number ms to wait
|
||||
---@param callback function to execute on completion
|
||||
function M.debounce(context, timeout, callback)
|
||||
-- all execution here is done in a synchronous context; no thread safety required
|
||||
|
||||
M.debouncers[context] = M.debouncers[context] or {}
|
||||
local debouncer = M.debouncers[context]
|
||||
|
||||
-- cancel waiting or executing timer
|
||||
if debouncer.timer then
|
||||
timer_stop_close(debouncer.timer)
|
||||
end
|
||||
|
||||
local timer = vim.loop.new_timer()
|
||||
debouncer.timer = timer
|
||||
timer:start(timeout, 0, function()
|
||||
timer_stop_close(timer)
|
||||
|
||||
-- reschedule when callback is running
|
||||
if debouncer.executing then
|
||||
M.debounce(context, timeout, callback)
|
||||
return
|
||||
end
|
||||
|
||||
-- call back at a safe time
|
||||
debouncer.executing = true
|
||||
vim.schedule(function()
|
||||
callback()
|
||||
debouncer.executing = false
|
||||
|
||||
-- no other timer waiting
|
||||
if debouncer.timer == timer then
|
||||
M.debouncers[context] = nil
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
function M.focus_file(path)
|
||||
local _, i = M.find_node(require("nvim-tree.core").get_explorer().nodes, function(node)
|
||||
return node.absolute_path == path
|
||||
end)
|
||||
require("nvim-tree.view").set_cursor { i + 1, 1 }
|
||||
end
|
||||
|
||||
function M.get_win_buf_from_path(path)
|
||||
for _, w in pairs(vim.api.nvim_tabpage_list_wins(0)) do
|
||||
local b = vim.api.nvim_win_get_buf(w)
|
||||
if vim.api.nvim_buf_get_name(b) == path then
|
||||
return w, b
|
||||
end
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
function M.clear_prompt()
|
||||
if vim.opt.cmdheight._value ~= 0 then
|
||||
vim.cmd "normal! :"
|
||||
end
|
||||
end
|
||||
|
||||
-- return a new table with values from array
|
||||
function M.array_shallow_clone(array)
|
||||
local to = {}
|
||||
for _, v in ipairs(array) do
|
||||
table.insert(to, v)
|
||||
end
|
||||
return to
|
||||
end
|
||||
|
||||
-- remove item from array if it exists
|
||||
function M.array_remove(array, item)
|
||||
for i, v in ipairs(array) do
|
||||
if v == item then
|
||||
table.remove(array, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M.array_remove_nils(array)
|
||||
return vim.tbl_filter(function(v)
|
||||
return v ~= nil
|
||||
end, array)
|
||||
end
|
||||
|
||||
function M.inject_node(f)
|
||||
return function()
|
||||
f(require("nvim-tree.lib").get_node_at_cursor())
|
||||
end
|
||||
end
|
||||
|
||||
---Is the buffer named NvimTree_[0-9]+ a tree? filetype is "NvimTree" or not readable file.
|
||||
---This is cheap, as the readable test should only ever be needed when resuming a vim session.
|
||||
---@param bufnr number may be 0 or nil for current
|
||||
---@return boolean
|
||||
function M.is_nvim_tree_buf(bufnr)
|
||||
if bufnr == nil then
|
||||
bufnr = 0
|
||||
end
|
||||
if vim.fn.bufexists(bufnr) then
|
||||
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||
if vim.fn.fnamemodify(bufname, ":t"):match "^NvimTree_[0-9]+$" then
|
||||
if vim.bo[bufnr].filetype == "NvimTree" then
|
||||
return true
|
||||
elseif vim.fn.filereadable(bufname) == 0 then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
local a = vim.api
|
||||
|
||||
local M = {}
|
||||
|
||||
local events = require "nvim-tree.events"
|
||||
local utils = require "nvim-tree.utils"
|
||||
local log = require "nvim-tree.log"
|
||||
|
||||
local function get_win_sep_hl()
|
||||
-- #1221 WinSeparator not present in nvim 0.6.1 and some builds of 0.7.0
|
||||
local has_win_sep = pcall(vim.cmd, "silent hi WinSeparator")
|
||||
return has_win_sep and "WinSeparator:NvimTreeWinSeparator" or "VertSplit:NvimTreeWinSeparator"
|
||||
end
|
||||
|
||||
M.View = {
|
||||
adaptive_size = false,
|
||||
centralize_selection = false,
|
||||
tabpages = {},
|
||||
cursors = {},
|
||||
hide_root_folder = false,
|
||||
@@ -20,15 +28,17 @@ M.View = {
|
||||
foldmethod = "manual",
|
||||
foldcolumn = "0",
|
||||
cursorcolumn = false,
|
||||
cursorlineopt = "line",
|
||||
cursorline = true,
|
||||
cursorlineopt = "both",
|
||||
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",
|
||||
"CursorLineNr:NvimTreeCursorLineNr",
|
||||
"LineNr:NvimTreeLineNr",
|
||||
get_win_sep_hl(),
|
||||
"StatusLine:NvimTreeStatusLine",
|
||||
"StatusLineNC:NvimTreeStatuslineNC",
|
||||
"SignColumn:NvimTreeSignColumn",
|
||||
@@ -67,9 +77,9 @@ local function matches_bufnr(bufnr)
|
||||
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 })
|
||||
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
|
||||
if not matches_bufnr(bufnr) and utils.is_nvim_tree_buf(bufnr) then
|
||||
pcall(vim.api.nvim_buf_delete, bufnr, { force = true })
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -77,20 +87,24 @@ 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)
|
||||
local tab = vim.api.nvim_get_current_tabpage()
|
||||
BUFNR_PER_TAB[tab] = bufnr or vim.api.nvim_create_buf(false, false)
|
||||
vim.api.nvim_buf_set_name(M.get_bufnr(), "NvimTree_" .. tab)
|
||||
|
||||
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())
|
||||
if type(M.on_attach) == "function" then
|
||||
require("nvim-tree.keymap").set_keymaps(M.get_bufnr())
|
||||
M.on_attach(M.get_bufnr())
|
||||
else
|
||||
require("nvim-tree.actions").apply_mappings(M.get_bufnr())
|
||||
end
|
||||
end
|
||||
|
||||
local function get_size()
|
||||
local width_or_height = M.is_vertical() and "width" or "height"
|
||||
local size = M.View[width_or_height]
|
||||
local size = M.View.width
|
||||
if type(size) == "number" then
|
||||
return size
|
||||
elseif type(size) == "function" then
|
||||
@@ -104,52 +118,62 @@ 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()
|
||||
local winnr = vim.api.nvim_get_current_win()
|
||||
M.View.tabpages[tabpage] = vim.tbl_extend("force", M.View.tabpages[tabpage] or tabinitial, { winnr = winnr })
|
||||
end
|
||||
|
||||
local function 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)
|
||||
vim.opt_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())
|
||||
local function open_win_config()
|
||||
if type(M.View.float.open_win_config) == "function" then
|
||||
return M.View.float.open_win_config()
|
||||
else
|
||||
return M.View.float.open_win_config
|
||||
end
|
||||
end
|
||||
|
||||
local function open_window()
|
||||
if M.View.float.enable then
|
||||
vim.api.nvim_open_win(0, true, open_win_config())
|
||||
else
|
||||
vim.api.nvim_command "vsp"
|
||||
M.reposition_window()
|
||||
end
|
||||
setup_tabpage(vim.api.nvim_get_current_tabpage())
|
||||
set_window_options_and_buffer()
|
||||
end
|
||||
|
||||
local function is_buf_displayed(buf)
|
||||
return vim.api.nvim_buf_is_valid(buf) and vim.fn.buflisted(buf) == 1
|
||||
end
|
||||
|
||||
local function get_alt_or_next_buf()
|
||||
local alt_buf = vim.fn.bufnr "#"
|
||||
if is_buf_displayed(alt_buf) then
|
||||
return alt_buf
|
||||
end
|
||||
|
||||
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
|
||||
if is_buf_displayed(buf) then
|
||||
return buf
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function switch_buf_if_last_buf()
|
||||
if #a.nvim_list_wins() == 1 then
|
||||
if #get_existing_buffers() > 0 then
|
||||
vim.cmd "sbnext"
|
||||
if #vim.api.nvim_list_wins() == 1 then
|
||||
local buf = get_alt_or_next_buf()
|
||||
if buf then
|
||||
vim.cmd("sb" .. buf)
|
||||
else
|
||||
vim.cmd "new"
|
||||
end
|
||||
@@ -157,25 +181,27 @@ local function switch_buf_if_last_buf()
|
||||
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())
|
||||
local function save_tab_state(tabnr)
|
||||
local tabpage = tabnr or vim.api.nvim_get_current_tabpage()
|
||||
M.View.cursors[tabpage] = vim.api.nvim_win_get_cursor(M.get_winnr(tabpage))
|
||||
end
|
||||
|
||||
function M.close()
|
||||
if not M.is_visible() then
|
||||
local function close(tabpage)
|
||||
if not M.is_visible { tabpage = tabpage } then
|
||||
return
|
||||
end
|
||||
save_tab_state()
|
||||
save_tab_state(tabpage)
|
||||
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 tree_win = M.get_winnr(tabpage)
|
||||
local current_win = vim.api.nvim_get_current_win()
|
||||
for _, win in pairs(vim.api.nvim_tabpage_list_wins(tabpage)) do
|
||||
if vim.api.nvim_win_get_config(win).relative == "" then
|
||||
local prev_win = vim.fn.winnr "#" -- this tab only
|
||||
if tree_win == current_win and prev_win > 0 then
|
||||
a.nvim_set_current_win(vim.fn.win_getid(prev_win))
|
||||
vim.api.nvim_set_current_win(vim.fn.win_getid(prev_win))
|
||||
end
|
||||
if vim.api.nvim_win_is_valid(tree_win) then
|
||||
vim.api.nvim_win_close(tree_win, true)
|
||||
end
|
||||
events._dispatch_on_tree_close()
|
||||
return
|
||||
@@ -183,14 +209,34 @@ function M.close()
|
||||
end
|
||||
end
|
||||
|
||||
function M.close_this_tab_only()
|
||||
close(vim.api.nvim_get_current_tabpage())
|
||||
end
|
||||
|
||||
function M.close_all_tabs()
|
||||
for tabpage, _ in pairs(M.View.tabpages) do
|
||||
close(tabpage)
|
||||
end
|
||||
end
|
||||
|
||||
function M.close()
|
||||
if M.View.tab.sync.close then
|
||||
M.close_all_tabs()
|
||||
else
|
||||
M.close_this_tab_only()
|
||||
end
|
||||
end
|
||||
|
||||
function M.open(options)
|
||||
if M.is_visible() then
|
||||
return
|
||||
end
|
||||
|
||||
local pn = string.format "view open"
|
||||
local ps = log.profile_start(pn)
|
||||
|
||||
create_buffer()
|
||||
open_window()
|
||||
set_window_options_and_buffer()
|
||||
M.resize()
|
||||
|
||||
local opts = options or { focus_tree = true }
|
||||
@@ -198,9 +244,36 @@ function M.open(options)
|
||||
vim.cmd "wincmd p"
|
||||
end
|
||||
events._dispatch_on_tree_open()
|
||||
|
||||
log.profile_end(ps, pn)
|
||||
end
|
||||
|
||||
local function grow()
|
||||
local starts_at = M.is_root_folder_visible(require("nvim-tree.core").get_cwd()) and 1 or 0
|
||||
local lines = vim.api.nvim_buf_get_lines(M.get_bufnr(), starts_at, -1, false)
|
||||
local max_length = M.View.initial_width
|
||||
for _, l in pairs(lines) do
|
||||
local count = vim.fn.strchars(l) + 3 -- plus some padding
|
||||
if max_length < count then
|
||||
max_length = count
|
||||
end
|
||||
end
|
||||
M.resize(max_length)
|
||||
end
|
||||
|
||||
function M.grow_from_content()
|
||||
if M.View.adaptive_size then
|
||||
grow()
|
||||
end
|
||||
end
|
||||
|
||||
function M.resize(size)
|
||||
if M.View.float.enable and not M.View.adaptive_size then
|
||||
-- if the floating windows's adaptive size is not desired, then the
|
||||
-- float size should be defined in view.float.open_win_config
|
||||
return
|
||||
end
|
||||
|
||||
if type(size) == "string" then
|
||||
size = vim.trim(size)
|
||||
local first_char = size:sub(1, 1)
|
||||
@@ -224,11 +297,10 @@ function M.resize(size)
|
||||
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
|
||||
local new_size = get_size()
|
||||
vim.api.nvim_win_set_width(M.get_winnr(), new_size)
|
||||
|
||||
events._dispatch_on_tree_resize(new_size)
|
||||
|
||||
if not M.View.preserve_window_proportions then
|
||||
vim.cmd ":wincmd ="
|
||||
@@ -237,19 +309,19 @@ end
|
||||
|
||||
function M.reposition_window()
|
||||
local move_to = move_tbl[M.View.side]
|
||||
a.nvim_command("wincmd " .. move_to)
|
||||
vim.api.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()
|
||||
local current_tab = vim.api.nvim_get_current_tabpage()
|
||||
M.View.tabpages[current_tab].winnr = vim.api.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())
|
||||
create_buffer(opts.hijack_current_buf and vim.api.nvim_get_current_buf())
|
||||
setup_tabpage(vim.api.nvim_get_current_tabpage())
|
||||
set_current_win()
|
||||
set_window_options_and_buffer()
|
||||
if opts.resize then
|
||||
@@ -259,36 +331,53 @@ function M.open_in_current_win(opts)
|
||||
end
|
||||
|
||||
function M.abandon_current_window()
|
||||
local tab = a.nvim_get_current_tabpage()
|
||||
local tab = vim.api.nvim_get_current_tabpage()
|
||||
BUFNR_PER_TAB[tab] = nil
|
||||
M.View.tabpages[tab].winnr = nil
|
||||
if M.View.tabpages[tab] then
|
||||
M.View.tabpages[tab].winnr = nil
|
||||
end
|
||||
end
|
||||
|
||||
function M.abandon_all_windows()
|
||||
for tab, _ in pairs(vim.api.nvim_list_tabpages()) do
|
||||
BUFNR_PER_TAB[tab] = nil
|
||||
if M.View.tabpages[tab] then
|
||||
M.View.tabpages[tab].winnr = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M.is_visible(opts)
|
||||
if opts and opts.tabpage then
|
||||
if M.View.tabpages[opts.tabpage] == nil then
|
||||
return false
|
||||
end
|
||||
local winnr = M.View.tabpages[opts.tabpage].winnr
|
||||
return winnr and vim.api.nvim_win_is_valid(winnr)
|
||||
end
|
||||
|
||||
if opts and opts.any_tabpage then
|
||||
for _, v in pairs(M.View.tabpages) do
|
||||
if a.nvim_win_is_valid(v.winnr) then
|
||||
if v.winnr and vim.api.nvim_win_is_valid(v.winnr) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return M.get_winnr() ~= nil and a.nvim_win_is_valid(M.get_winnr())
|
||||
return M.get_winnr() ~= nil and vim.api.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()
|
||||
pcall(vim.api.nvim_win_set_cursor, M.get_winnr(), opts)
|
||||
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
|
||||
if vim.api.nvim_win_get_tabpage(wnr or 0) ~= vim.api.nvim_win_get_tabpage(0) then
|
||||
M.close()
|
||||
M.open()
|
||||
wnr = M.get_winnr()
|
||||
@@ -296,16 +385,12 @@ function M.focus(winnr, open_if_closed)
|
||||
M.open()
|
||||
end
|
||||
|
||||
a.nvim_set_current_win(wnr)
|
||||
end
|
||||
|
||||
function M.is_vertical()
|
||||
return M.View.side == "left" or M.View.side == "right"
|
||||
vim.api.nvim_set_current_win(wnr)
|
||||
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()
|
||||
local tabpage = vim.api.nvim_get_current_tabpage()
|
||||
M.set_cursor(M.View.cursors[tabpage])
|
||||
end
|
||||
|
||||
@@ -313,7 +398,7 @@ end
|
||||
---@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()
|
||||
tabpage = tabpage or vim.api.nvim_get_current_tabpage()
|
||||
local tabinfo = M.View.tabpages[tabpage]
|
||||
if tabinfo ~= nil then
|
||||
return tabinfo.winnr
|
||||
@@ -323,14 +408,14 @@ end
|
||||
--- Returns the current nvim tree bufnr
|
||||
---@return number
|
||||
function M.get_bufnr()
|
||||
return BUFNR_PER_TAB[a.nvim_get_current_tabpage()]
|
||||
return BUFNR_PER_TAB[vim.api.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()
|
||||
tabpage = tabpage or vim.api.nvim_get_current_tabpage()
|
||||
local tabinfo = M.View.tabpages[tabpage]
|
||||
if tabinfo ~= nil then
|
||||
return tabinfo.help
|
||||
@@ -338,12 +423,12 @@ function M.is_help_ui(tabpage)
|
||||
end
|
||||
|
||||
function M.toggle_help(tabpage)
|
||||
tabpage = tabpage or a.nvim_get_current_tabpage()
|
||||
tabpage = tabpage or vim.api.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)
|
||||
return bufnr and vim.api.nvim_buf_is_valid(bufnr) and vim.api.nvim_buf_is_loaded(bufnr)
|
||||
end
|
||||
|
||||
function M._prevent_buffer_override()
|
||||
@@ -354,9 +439,11 @@ function M._prevent_buffer_override()
|
||||
-- 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)
|
||||
local curwin = vim.api.nvim_get_current_win()
|
||||
local curwinconfig = vim.api.nvim_win_get_config(curwin)
|
||||
local curbuf = vim.api.nvim_win_get_buf(curwin)
|
||||
local bufname = vim.api.nvim_buf_get_name(curbuf)
|
||||
|
||||
if not bufname:match "NvimTree" then
|
||||
for i, tabpage in ipairs(M.View.tabpages) do
|
||||
if tabpage.winnr == view_winnr then
|
||||
@@ -375,8 +462,15 @@ function M._prevent_buffer_override()
|
||||
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)
|
||||
pcall(vim.api.nvim_win_close, curwin, { force = true })
|
||||
|
||||
-- to handle opening a file using :e when nvim-tree is on floating mode
|
||||
-- falling back to the current window instead of creating a new one
|
||||
if curwinconfig.relative ~= "" then
|
||||
require("nvim-tree.actions.node.open-file").fn("edit_in_place", bufname)
|
||||
else
|
||||
require("nvim-tree.actions.node.open-file").fn("edit", bufname)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -384,16 +478,29 @@ function M.is_root_folder_visible(cwd)
|
||||
return cwd ~= "/" and not M.View.hide_root_folder
|
||||
end
|
||||
|
||||
-- used on ColorScheme event
|
||||
function M.reset_winhl()
|
||||
if M.get_winnr() and vim.api.nvim_win_is_valid(M.get_winnr()) then
|
||||
vim.wo[M.get_winnr()].winhl = M.View.winopts.winhl
|
||||
end
|
||||
end
|
||||
|
||||
function M.setup(opts)
|
||||
local options = opts.view or {}
|
||||
M.View.side = options.side
|
||||
M.View.adaptive_size = options.adaptive_size
|
||||
M.View.centralize_selection = options.centralize_selection
|
||||
M.View.side = (options.side == "right") and "right" or "left"
|
||||
M.View.width = options.width
|
||||
M.View.height = options.height
|
||||
M.View.initial_width = get_size()
|
||||
M.View.hide_root_folder = options.hide_root_folder
|
||||
M.View.tab = opts.tab
|
||||
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
|
||||
M.View.float = options.float
|
||||
M.on_attach = opts.on_attach
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
158
lua/nvim-tree/watcher.lua
Normal file
158
lua/nvim-tree/watcher.lua
Normal file
@@ -0,0 +1,158 @@
|
||||
local notify = require "nvim-tree.notify"
|
||||
|
||||
local log = require "nvim-tree.log"
|
||||
local utils = require "nvim-tree.utils"
|
||||
|
||||
local M = {}
|
||||
|
||||
local Event = {
|
||||
_events = {},
|
||||
}
|
||||
Event.__index = Event
|
||||
|
||||
local Watcher = {
|
||||
_watchers = {},
|
||||
}
|
||||
Watcher.__index = Watcher
|
||||
|
||||
local FS_EVENT_FLAGS = {
|
||||
-- inotify or equivalent will be used; fallback to stat has not yet been implemented
|
||||
stat = false,
|
||||
-- recursive is not functional in neovim's libuv implementation
|
||||
recursive = false,
|
||||
}
|
||||
|
||||
function Event:new(path)
|
||||
log.line("watcher", "Event:new '%s'", path)
|
||||
|
||||
local e = setmetatable({
|
||||
_path = path,
|
||||
_fs_event = nil,
|
||||
_listeners = {},
|
||||
}, Event)
|
||||
|
||||
if e:start() then
|
||||
Event._events[path] = e
|
||||
return e
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
function Event:start()
|
||||
log.line("watcher", "Event:start '%s'", self._path)
|
||||
|
||||
local rc, _, name
|
||||
|
||||
self._fs_event, _, name = vim.loop.new_fs_event()
|
||||
if not self._fs_event then
|
||||
self._fs_event = nil
|
||||
notify.warn(string.format("Could not initialize an fs_event watcher for path %s : %s", self._path, name))
|
||||
return false
|
||||
end
|
||||
|
||||
local event_cb = vim.schedule_wrap(function(err, filename)
|
||||
if err then
|
||||
log.line("watcher", "event_cb '%s' '%s' FAIL : %s", self._path, filename, err)
|
||||
self:destroy(string.format("File system watcher failed (%s) for path %s, halting watcher.", err, self._path))
|
||||
else
|
||||
log.line("watcher", "event_cb '%s' '%s'", self._path, filename)
|
||||
for _, listener in ipairs(self._listeners) do
|
||||
listener(filename)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
rc, _, name = self._fs_event:start(self._path, FS_EVENT_FLAGS, event_cb)
|
||||
if rc ~= 0 then
|
||||
notify.warn(string.format("Could not start the fs_event watcher for path %s : %s", self._path, name))
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function Event:add(listener)
|
||||
table.insert(self._listeners, listener)
|
||||
end
|
||||
|
||||
function Event:remove(listener)
|
||||
utils.array_remove(self._listeners, listener)
|
||||
if #self._listeners == 0 then
|
||||
self:destroy()
|
||||
end
|
||||
end
|
||||
|
||||
function Event:destroy(message)
|
||||
log.line("watcher", "Event:destroy '%s'", self._path)
|
||||
|
||||
if self._fs_event then
|
||||
if message then
|
||||
notify.warn(message)
|
||||
end
|
||||
|
||||
local rc, _, name = self._fs_event:stop()
|
||||
if rc ~= 0 then
|
||||
notify.warn(string.format("Could not stop the fs_event watcher for path %s : %s", self._path, name))
|
||||
end
|
||||
self._fs_event = nil
|
||||
end
|
||||
|
||||
Event._events[self._path] = nil
|
||||
end
|
||||
|
||||
function Watcher:new(path, files, callback, data)
|
||||
log.line("watcher", "Watcher:new '%s' %s", path, vim.inspect(files))
|
||||
|
||||
local w = setmetatable(data, Watcher)
|
||||
|
||||
w._event = Event._events[path] or Event:new(path)
|
||||
w._listener = nil
|
||||
w._path = path
|
||||
w._files = files
|
||||
w._callback = callback
|
||||
|
||||
if not w._event then
|
||||
return nil
|
||||
end
|
||||
|
||||
w:start()
|
||||
|
||||
table.insert(Watcher._watchers, w)
|
||||
|
||||
return w
|
||||
end
|
||||
|
||||
function Watcher:start()
|
||||
self._listener = function(filename)
|
||||
if not self._files or vim.tbl_contains(self._files, filename) then
|
||||
self._callback(self)
|
||||
end
|
||||
end
|
||||
|
||||
self._event:add(self._listener)
|
||||
end
|
||||
|
||||
function Watcher:destroy()
|
||||
log.line("watcher", "Watcher:destroy '%s'", self._path)
|
||||
|
||||
self._event:remove(self._listener)
|
||||
|
||||
utils.array_remove(Watcher._watchers, self)
|
||||
end
|
||||
|
||||
M.Watcher = Watcher
|
||||
|
||||
function M.purge_watchers()
|
||||
log.line("watcher", "purge_watchers")
|
||||
|
||||
for _, w in ipairs(utils.array_shallow_clone(Watcher._watchers)) do
|
||||
w:destroy()
|
||||
end
|
||||
|
||||
for _, e in pairs(Event._events) do
|
||||
e:destroy()
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
84
scripts/generate_default_mappings.lua
Normal file
84
scripts/generate_default_mappings.lua
Normal file
@@ -0,0 +1,84 @@
|
||||
-- luacheck:ignore 113
|
||||
---@diagnostic disable: undefined-global
|
||||
|
||||
-- write DEFAULT_MAPPINGS in various formats
|
||||
|
||||
local max_key_help = 0
|
||||
local max_key_lua = 0
|
||||
local max_action_help = 0
|
||||
local outs_help = {}
|
||||
local outs_lua = {}
|
||||
|
||||
for _, m in pairs(DEFAULT_MAPPINGS) do
|
||||
local out
|
||||
if type(m.key) == "table" then
|
||||
local first = true
|
||||
local keys_lua = "key = {"
|
||||
for _, sub_key in pairs(m.key) do
|
||||
-- lua
|
||||
keys_lua = string.format('%s%s "%s"', keys_lua, first and "" or ",", sub_key)
|
||||
|
||||
-- help
|
||||
out = {}
|
||||
if first then
|
||||
out.action = m.action
|
||||
out.desc = m.desc
|
||||
first = false
|
||||
else
|
||||
out.action = ""
|
||||
out.desc = ""
|
||||
end
|
||||
out.key = string.format("`%s`", sub_key)
|
||||
max_action_help = math.max(#out.action, max_action_help)
|
||||
max_key_help = math.max(#out.key, max_key_help)
|
||||
table.insert(outs_help, out)
|
||||
end
|
||||
|
||||
-- lua
|
||||
out = {}
|
||||
out.key = string.format("%s },", keys_lua)
|
||||
table.insert(outs_lua, out)
|
||||
else
|
||||
-- help
|
||||
out = {}
|
||||
out.action = m.action
|
||||
out.desc = m.desc
|
||||
out.key = string.format("`%s`", m.key)
|
||||
table.insert(outs_help, out)
|
||||
max_action_help = math.max(#out.action, max_action_help)
|
||||
max_key_help = math.max(#out.key, max_key_help)
|
||||
|
||||
-- lua
|
||||
out = {}
|
||||
out.key = string.format('key = "%s",', m.key)
|
||||
table.insert(outs_lua, out)
|
||||
end
|
||||
|
||||
--lua
|
||||
out.action = string.format('action = "%s"', m.action)
|
||||
max_key_lua = math.max(#out.key, max_key_lua)
|
||||
end
|
||||
|
||||
-- help
|
||||
local file = io.open("/tmp/DEFAULT_MAPPINGS.help", "w")
|
||||
io.output(file)
|
||||
io.write "\n"
|
||||
local fmt = string.format("%%-%d.%ds %%-%d.%ds %%s\n", max_key_help, max_key_help, max_action_help, max_action_help)
|
||||
for _, m in pairs(outs_help) do
|
||||
if m.action == "" then
|
||||
io.write(string.format("%s\n", m.key))
|
||||
else
|
||||
io.write(string.format(fmt, m.key, m.action, m.desc))
|
||||
end
|
||||
end
|
||||
io.write "\n"
|
||||
io.close(file)
|
||||
|
||||
-- lua
|
||||
file = io.open("/tmp/DEFAULT_MAPPINGS.lua", "w")
|
||||
io.output(file)
|
||||
fmt = string.format(" { %%-%d.%ds %%s },\n", max_key_lua, max_key_lua)
|
||||
for _, m in pairs(outs_lua) do
|
||||
io.write(string.format(fmt, m.key, m.action))
|
||||
end
|
||||
io.close(file)
|
||||
33
scripts/update-help.sh
Executable file
33
scripts/update-help.sh
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/bin/sh
|
||||
|
||||
# run after changing nvim-tree.lua DEFAULT_OPTS or nvim-tree/actions/init.lua M.mappings
|
||||
# scrapes and updates nvim-tree-lua.txt
|
||||
# run from repository root: scripts/update-default-opts.sh
|
||||
|
||||
|
||||
begin="BEGIN_DEFAULT_OPTS"
|
||||
end="END_DEFAULT_OPTS"
|
||||
|
||||
# scrape DEFAULT_OPTS, 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
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
begin="BEGIN_DEFAULT_MAPPINGS"
|
||||
end="END_DEFAULT_MAPPINGS"
|
||||
|
||||
# generate various DEFAULT_MAPPINGS
|
||||
sed -n -e "/${begin}/,/${end}/{ /${begin}/d; /${end}/d; p; }" lua/nvim-tree/actions/init.lua > /tmp/DEFAULT_MAPPINGS.M.lua
|
||||
cat /tmp/DEFAULT_MAPPINGS.M.lua scripts/generate_default_mappings.lua | lua
|
||||
|
||||
# help
|
||||
sed -i -e "/${begin}/,/${end}/{ /${begin}/{p; r /tmp/DEFAULT_MAPPINGS.lua
|
||||
}; /${end}/p; d }" doc/nvim-tree-lua.txt
|
||||
sed -i -e "/^DEFAULT MAPPINGS/,/^>$/{ /^DEFAULT MAPPINGS/{p; r /tmp/DEFAULT_MAPPINGS.help
|
||||
}; /^>$/p; d }" doc/nvim-tree-lua.txt
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/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
|
||||
|
||||
Reference in New Issue
Block a user