Compare commits
703 Commits
compat-nvi
...
nvim-tree-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7639482a1 | ||
|
|
2ee1c5e17f | ||
|
|
3fc8de198c | ||
|
|
610a1c189b | ||
|
|
c22124b374 | ||
|
|
2156bc08c9 | ||
|
|
82ab19ebf7 | ||
|
|
120ba58254 | ||
|
|
00dff482f9 | ||
|
|
8f974879a0 | ||
|
|
14039337a5 | ||
|
|
e4bc05b415 | ||
|
|
3cddd28177 | ||
|
|
6e5a204ca6 | ||
|
|
f3efc25e56 | ||
|
|
8760d76c1d | ||
|
|
077af9f990 | ||
|
|
68be6df2fc | ||
|
|
63c7ad9037 | ||
|
|
9b82ff9bba | ||
|
|
2a268f631d | ||
|
|
f5f6789299 | ||
|
|
ce09bfb95f | ||
|
|
0fede9f813 | ||
|
|
1c9553a19f | ||
|
|
ca0904e4c5 | ||
|
|
5ad87620ec | ||
|
|
50e919426a | ||
|
|
010ae0365a | ||
|
|
38aac09151 | ||
|
|
c9104a5d07 | ||
|
|
4a9e82d10a | ||
|
|
4520c0355c | ||
|
|
1ae1c33ce1 | ||
|
|
9650e735ba | ||
|
|
59a8a6ae5e | ||
|
|
0429f286b3 | ||
|
|
8405ecfbd6 | ||
|
|
0ae9ad4ded | ||
|
|
e7cdecc636 | ||
|
|
b18ce8be8f | ||
|
|
03ae60313b | ||
|
|
45a93d9979 | ||
|
|
bd4881660b | ||
|
|
cd9c6db77f | ||
|
|
03f737e574 | ||
|
|
a4dd5ad5c8 | ||
|
|
b652dbd0e0 | ||
|
|
d41b4ca013 | ||
|
|
2d6e64dd8c | ||
|
|
cb57691536 | ||
|
|
70d7377c3f | ||
|
|
ea55ef1203 | ||
|
|
43c3c36c7a | ||
|
|
d43ab67d0e | ||
|
|
6fbcb5a892 | ||
|
|
e962e97cab | ||
|
|
42340952af | ||
|
|
210478677c | ||
|
|
ad0b95dee5 | ||
|
|
466fbed3e4 | ||
|
|
15942df2bb | ||
|
|
e25eb7fa83 | ||
|
|
48d0e82f94 | ||
|
|
82ba116bbd | ||
|
|
908478a0e0 | ||
|
|
1aa9852cad | ||
|
|
4e396b2624 | ||
|
|
48a9290757 | ||
|
|
b2640685a8 | ||
|
|
1d629a5d3f | ||
|
|
f9ff00bc06 | ||
|
|
abfd1d1b67 | ||
|
|
2ede0de67b | ||
|
|
12a9a995a4 | ||
|
|
d1957d3472 | ||
|
|
869c064721 | ||
|
|
74e94625b1 | ||
|
|
8b2c5c678b | ||
|
|
2086e564c4 | ||
|
|
1cac8005df | ||
|
|
8704b6f7fc | ||
|
|
26632f496e | ||
|
|
5a87ffe35c | ||
|
|
517e4fbb9e | ||
|
|
4c8ddee453 | ||
|
|
4215f33da5 | ||
|
|
2bc725a3eb | ||
|
|
340d3a9795 | ||
|
|
edd4e25fd4 | ||
|
|
78c4c083ed | ||
|
|
acffab931a | ||
|
|
64f61e4c91 | ||
|
|
347e1eb352 | ||
|
|
76db7ed0da | ||
|
|
5a18b98274 | ||
|
|
62008e5cf2 | ||
|
|
ae8e46e8fa | ||
|
|
81eb8d5192 | ||
|
|
d8d3a1590a | ||
|
|
ddd1d6eb21 | ||
|
|
0aca0920f4 | ||
|
|
308f2fcec2 | ||
|
|
2d97059661 | ||
|
|
e508bdc418 | ||
|
|
e20966ae55 | ||
|
|
85c502e907 | ||
|
|
b1bbd4f7dc | ||
|
|
f7c09bd72e | ||
|
|
707b24af91 | ||
|
|
76b98109f6 | ||
|
|
cfea5bd080 | ||
|
|
1fd9c98960 | ||
|
|
164f11db4f | ||
|
|
8f2a50f1cd | ||
|
|
c64becf80c | ||
|
|
3c4267eb50 | ||
|
|
041dbd18f4 | ||
|
|
efafd73efa | ||
|
|
d52fdeb0a3 | ||
|
|
7efaa339d3 | ||
|
|
030defdb65 | ||
|
|
d35a8d5ec6 | ||
|
|
d16246a757 | ||
|
|
863cf832ce | ||
|
|
c7d4650c38 | ||
|
|
4a87b8b46b | ||
|
|
2dbe4ea2b5 | ||
|
|
39e6fef85a | ||
|
|
b278fc25ae | ||
|
|
8cbb1db8e9 | ||
|
|
f39f7b6fcd | ||
|
|
e9ac136a3a | ||
|
|
d9cb432d2c | ||
|
|
fbee8a69a4 | ||
|
|
7bdb220d0f | ||
|
|
48b1d8638f | ||
|
|
75ff64e666 | ||
|
|
74525ac047 | ||
|
|
e9c5abe073 | ||
|
|
f24afa2cef | ||
|
|
b8c3a23e76 | ||
|
|
9f6d3fac82 | ||
|
|
78a5836092 | ||
|
|
86810e5c0b | ||
|
|
5d13cc8205 | ||
|
|
6a99f5af78 | ||
|
|
f1b3e6a7eb | ||
|
|
b6b86e1f3e | ||
|
|
fac4900bd1 | ||
|
|
f779abaf2a | ||
|
|
dc839a72a6 | ||
|
|
02ae52357b | ||
|
|
96a783fbd6 | ||
|
|
50f30bcd8c | ||
|
|
8f92e1edd3 | ||
|
|
141c0f97c3 | ||
|
|
34780aca5b | ||
|
|
4891d6cec3 | ||
|
|
90cff8e468 | ||
|
|
0a7c24b675 | ||
|
|
27e66c2ea8 | ||
|
|
2fed5e1010 | ||
|
|
13f967f8e7 | ||
|
|
7d1760f892 | ||
|
|
7e5c673180 | ||
|
|
05f55c1fd6 | ||
|
|
d5cc938ab0 | ||
|
|
5e4475d8bf | ||
|
|
5231562caf | ||
|
|
db796fc74e | ||
|
|
fa00b57873 | ||
|
|
8c534822a7 | ||
|
|
28cf0cd678 | ||
|
|
fb89297347 | ||
|
|
b67a773609 | ||
|
|
46e1f776f0 | ||
|
|
8f9169a059 | ||
|
|
633811c53d | ||
|
|
52a1c99bf0 | ||
|
|
80cfeadf17 | ||
|
|
874ae6e944 | ||
|
|
74ddb8f2bb | ||
|
|
a2aaf8b430 | ||
|
|
4ee6366ff1 | ||
|
|
0a99c4a23e | ||
|
|
c763861afb | ||
|
|
20a0707e0f | ||
|
|
24bb0ed806 | ||
|
|
7e3c0bee7b | ||
|
|
7630cf4a92 | ||
|
|
df38f1f30d | ||
|
|
c1568568b3 | ||
|
|
7c5c074354 | ||
|
|
78a9ca5ed6 | ||
|
|
c2194e940c | ||
|
|
a31dfada1d | ||
|
|
83b699533b | ||
|
|
db8145c27d | ||
|
|
8b4dbc57e4 | ||
|
|
40b9b887d0 | ||
|
|
aaee4cd896 | ||
|
|
0882354517 | ||
|
|
57078f9240 | ||
|
|
4054fc4be3 | ||
|
|
e64a498a5e | ||
|
|
53b0bcaada | ||
|
|
94e572e141 | ||
|
|
85abe29396 | ||
|
|
d8e495b235 | ||
|
|
e153d9f599 | ||
|
|
7dcda5d3b6 | ||
|
|
113e0950c8 | ||
|
|
934469b9b6 | ||
|
|
ce3495bd4c | ||
|
|
07eb5b4059 | ||
|
|
d49a284236 | ||
|
|
ea147418e0 | ||
|
|
914a6868cb | ||
|
|
a3aa3b47ea | ||
|
|
f742b86852 | ||
|
|
7f7665a17b | ||
|
|
94c7c810af | ||
|
|
b7f6600bc2 | ||
|
|
8f48426c88 | ||
|
|
33c3bc562b | ||
|
|
b856d0a0c3 | ||
|
|
51f02366de | ||
|
|
ec33d4befa | ||
|
|
5897b3622f | ||
|
|
277632fbd9 | ||
|
|
a2b6e5ad2a | ||
|
|
323f65cb9c | ||
|
|
28c3980b25 | ||
|
|
00741206c2 | ||
|
|
b144b33390 | ||
|
|
d11d701857 | ||
|
|
807dc05156 | ||
|
|
920868dba1 | ||
|
|
7c4c7e4e98 | ||
|
|
4e36850811 | ||
|
|
dea82ae207 | ||
|
|
18c7a31198 | ||
|
|
ace64228ad | ||
|
|
116b88564f | ||
|
|
0a54dcb76b | ||
|
|
904f95cd9d | ||
|
|
0042886db0 | ||
|
|
6c3ddcbc10 | ||
|
|
0a89dcb464 | ||
|
|
4bd30f0137 | ||
|
|
75c05742bc | ||
|
|
c1466f991a | ||
|
|
273c1700eb | ||
|
|
3b62c6bf2c | ||
|
|
697bfaccac | ||
|
|
a6daf50b9d | ||
|
|
ef305a888b | ||
|
|
a708bd2413 | ||
|
|
04b2c1e08c | ||
|
|
3d2fd90b28 | ||
|
|
4af572246c | ||
|
|
d17389ce53 | ||
|
|
1fe32286db | ||
|
|
3cc698b35b | ||
|
|
7aff29d755 | ||
|
|
c3c6544ee0 | ||
|
|
85ece277bc | ||
|
|
bdceaf5096 | ||
|
|
d4f6d33496 | ||
|
|
f873625d06 | ||
|
|
f5804ce94e | ||
|
|
e0c7eb5044 | ||
|
|
034511714b | ||
|
|
8d82c4dbe1 | ||
|
|
576d4c1b03 | ||
|
|
f5d970d450 | ||
|
|
63061404f4 | ||
|
|
73ab6651db | ||
|
|
58d1014324 | ||
|
|
e2a4c9d09d | ||
|
|
164eb10cbd | ||
|
|
d5d6950a0d | ||
|
|
9ef6c3cd88 | ||
|
|
b1e074d2b5 | ||
|
|
736c7ff590 | ||
|
|
b6574056b5 | ||
|
|
89816ace70 | ||
|
|
498e8793bb | ||
|
|
270c95556c | ||
|
|
98b76ff0a2 | ||
|
|
53295de04d | ||
|
|
0df384b6c0 | ||
|
|
9c60947926 | ||
|
|
d8b154c5f0 | ||
|
|
74996b8626 | ||
|
|
1f5bbc1efd | ||
|
|
bb375fb203 | ||
|
|
061a05bfd9 | ||
|
|
71e1c5bdc2 | ||
|
|
13c714681f | ||
|
|
d68b00a63e | ||
|
|
967865cdb7 | ||
|
|
517dee64c1 | ||
|
|
f8bb6b4c76 | ||
|
|
0db85a7024 | ||
|
|
a774fa186c | ||
|
|
5b5373254f | ||
|
|
e99616bebe | ||
|
|
5cc18d4f2f | ||
|
|
5aa318c159 | ||
|
|
8f392fa763 | ||
|
|
a8a4834e1a | ||
|
|
6ad5c26f4d | ||
|
|
56cdb5827d | ||
|
|
f3dbddf8b3 | ||
|
|
68f485b454 | ||
|
|
0a144ba50a | ||
|
|
b601b5aa25 | ||
|
|
e16083cb01 | ||
|
|
48d53a5934 | ||
|
|
920d5c8f7e | ||
|
|
086bf310bd | ||
|
|
d1410cb089 | ||
|
|
94e3b09900 | ||
|
|
0ef3d4613f | ||
|
|
7ad1c204c4 | ||
|
|
0c9bfe7225 | ||
|
|
7cd722ff3a | ||
|
|
3e9509ec1b | ||
|
|
45400cd7e0 | ||
|
|
a38f9a55a4 | ||
|
|
6319ad9405 | ||
|
|
886d852f6e | ||
|
|
6515a1e1a9 | ||
|
|
874b7be5d0 | ||
|
|
31d8e24460 | ||
|
|
aa9971768a | ||
|
|
a50723e35f | ||
|
|
4f036342f1 | ||
|
|
1d79a64a88 | ||
|
|
fe980baa94 | ||
|
|
f0a1c6ae2e | ||
|
|
1830e5e8a4 | ||
|
|
6e4b3b1868 | ||
|
|
851ed88f29 | ||
|
|
6001523c0a | ||
|
|
bbb6d48910 | ||
|
|
1b453441f4 | ||
|
|
6117582578 | ||
|
|
362ecbeed6 | ||
|
|
59bcb01d3b | ||
|
|
74959750f7 | ||
|
|
9c97e6449b | ||
|
|
66c15afd13 | ||
|
|
04f99f14b5 | ||
|
|
bcb969c869 | ||
|
|
08a0aa1a3b | ||
|
|
4222bb875d | ||
|
|
8b8d457e07 | ||
|
|
ba1778e061 | ||
|
|
36e29c3a95 | ||
|
|
b712b82b0c | ||
|
|
02fdc262eb | ||
|
|
59e65d88db | ||
|
|
7eb33d2a6d | ||
|
|
e0166d1469 | ||
|
|
8505b6ecd8 | ||
|
|
215b29bfad | ||
|
|
f3b73725c5 | ||
|
|
9fcd50d3e1 | ||
|
|
fb775b3353 | ||
|
|
e14989c0ea | ||
|
|
8567841b87 | ||
|
|
e05ed6a60f | ||
|
|
55028e30d7 | ||
|
|
9e87ee2d6e | ||
|
|
7944e479c1 | ||
|
|
f1c2d6d372 | ||
|
|
16f2806d59 | ||
|
|
96506fee49 | ||
|
|
13adc94e8e | ||
|
|
e8a89db1bb | ||
|
|
1b13a49f91 | ||
|
|
1f0fc8d6e8 | ||
|
|
3ce0a8e770 | ||
|
|
ccb6d8a518 | ||
|
|
5b554a9e2d | ||
|
|
3a42468a58 | ||
|
|
f2ee30998e | ||
|
|
92a0802b88 | ||
|
|
f43f3110a5 | ||
|
|
bac962caf4 | ||
|
|
cdbd7daf29 | ||
|
|
951b6e7e55 | ||
|
|
e322fbb80b | ||
|
|
9d27c9ebea | ||
|
|
013b4982d9 | ||
|
|
dcc344cc72 | ||
|
|
9ad93b6ac0 | ||
|
|
3c4958ab3d | ||
|
|
9e4c39572f | ||
|
|
fee6801393 | ||
|
|
3000797e53 | ||
|
|
e14c2895b4 | ||
|
|
4fc74ca321 | ||
|
|
547db6e929 | ||
|
|
d949af7245 | ||
|
|
e0cfbbb93d | ||
|
|
29788cc32a | ||
|
|
89c79cb33b | ||
|
|
87409bb4af | ||
|
|
d85b6718ce | ||
|
|
18272f8df3 | ||
|
|
e8ea62c198 | ||
|
|
cdb40dc42e | ||
|
|
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 |
@@ -4,6 +4,18 @@ root = true
|
|||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
|
|
||||||
|
[nvim-tree-lua.txt]
|
||||||
|
max_line_length = 78
|
||||||
|
|
||||||
[*.lua]
|
[*.lua]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
max_line_length = 140
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
|
# EmmyLuaCodeStyle specific, see
|
||||||
|
# https://github.com/CppCXY/EmmyLuaCodeStyle/blob/master/lua.template.editorconfig
|
||||||
|
continuation_indent = 2
|
||||||
|
quote_style = double
|
||||||
|
call_arg_parentheses = always
|
||||||
|
space_before_closure_open_parenthesis = false
|
||||||
|
align_continuous_similar_call_args = true
|
||||||
|
|||||||
27
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
27
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -5,7 +5,16 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
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.
|
||||||
|
|
||||||
|
Please note that nvim-tree team members do not have access to nor expertise with Windows. You will need to be an active participant during resolution.
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: "Description"
|
label: "Description"
|
||||||
@@ -15,7 +24,7 @@ body:
|
|||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: "Neovim version"
|
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: |
|
placeholder: |
|
||||||
NVIM v0.6.1
|
NVIM v0.6.1
|
||||||
Build type: Release
|
Build type: Release
|
||||||
@@ -29,6 +38,12 @@ body:
|
|||||||
placeholder: "Linux 5.16.11-arch1-1, macOS 11.5, Windows 10"
|
placeholder: "Linux 5.16.11-arch1-1, macOS 11.5, Windows 10"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: "Windows variant"
|
||||||
|
placeholder: "WSL, PowerShell, cygwin, msys"
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: "nvim-tree version"
|
label: "nvim-tree version"
|
||||||
@@ -39,14 +54,14 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: "Minimal config"
|
label: "Clean room replication"
|
||||||
description: "Minimal(!) configuration necessary to reproduce the issue.
|
description: "Minimal(!) configuration necessary to reproduce the issue.
|
||||||
|
|
||||||
(Right click) save [nvt-min.lua](https://raw.githubusercontent.com/kyazdani42/nvim-tree.lua/master/.github/ISSUE_TEMPLATE/nvt-min.lua) to `/tmp` and run using `nvim -nu /tmp/nvt-min.lua`
|
If not provided it is very unlikely that the nvim-tree team will be able to address your issue.
|
||||||
|
|
||||||
If _absolutely_ necessary, add plugins and modify the nvim-tree setup at the indicated lines.
|
See [wiki: Clean Room Replication](https://github.com/nvim-tree/nvim-tree.lua/wiki/Troubleshooting#clean-room-replication) for instructions and paste the contents of your `/tmp/nvt-min.lua` here.
|
||||||
|
|
||||||
Paste the contents of your `/tmp/nvt-min.lua` here."
|
Please do NOT post a configuration that uses other plugin managers such as lazy, see [wiki: Lazy Loading](https://github.com/nvim-tree/nvim-tree.lua/wiki/Installation#lazy-loading)"
|
||||||
render: lua
|
render: lua
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
6
.github/ISSUE_TEMPLATE/feature_request.md
vendored
6
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -6,6 +6,12 @@ labels: feature request
|
|||||||
assignees: ''
|
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.**
|
**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 [...]
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|||||||
37
.github/ISSUE_TEMPLATE/nvt-min.lua
vendored
37
.github/ISSUE_TEMPLATE/nvt-min.lua
vendored
@@ -1,13 +1,16 @@
|
|||||||
vim.cmd [[set runtimepath=$VIMRUNTIME]]
|
vim.g.loaded_netrw = 1
|
||||||
vim.cmd [[set packpath=/tmp/nvt-min/site]]
|
vim.g.loaded_netrwPlugin = 1
|
||||||
|
|
||||||
|
vim.cmd([[set runtimepath=$VIMRUNTIME]])
|
||||||
|
vim.cmd([[set packpath=/tmp/nvt-min/site]])
|
||||||
local package_root = "/tmp/nvt-min/site/pack"
|
local package_root = "/tmp/nvt-min/site/pack"
|
||||||
local install_path = package_root .. "/packer/start/packer.nvim"
|
local install_path = package_root .. "/packer/start/packer.nvim"
|
||||||
local function load_plugins()
|
local function load_plugins()
|
||||||
require("packer").startup {
|
require("packer").startup({
|
||||||
{
|
{
|
||||||
"wbthomason/packer.nvim",
|
"wbthomason/packer.nvim",
|
||||||
"kyazdani42/nvim-tree.lua",
|
"nvim-tree/nvim-tree.lua",
|
||||||
"kyazdani42/nvim-web-devicons",
|
"nvim-tree/nvim-web-devicons",
|
||||||
-- ADD PLUGINS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE
|
-- ADD PLUGINS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE
|
||||||
},
|
},
|
||||||
config = {
|
config = {
|
||||||
@@ -15,20 +18,34 @@ local function load_plugins()
|
|||||||
compile_path = install_path .. "/plugin/packer_compiled.lua",
|
compile_path = install_path .. "/plugin/packer_compiled.lua",
|
||||||
display = { non_interactive = true },
|
display = { non_interactive = true },
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
end
|
end
|
||||||
if vim.fn.isdirectory(install_path) == 0 then
|
if vim.fn.isdirectory(install_path) == 0 then
|
||||||
print "Installing nvim-tree and dependencies."
|
print("Installing nvim-tree and dependencies.")
|
||||||
vim.fn.system { "git", "clone", "--depth=1", "https://github.com/wbthomason/packer.nvim", install_path }
|
vim.fn.system({ "git", "clone", "--depth=1", "https://github.com/wbthomason/packer.nvim", install_path })
|
||||||
end
|
end
|
||||||
load_plugins()
|
load_plugins()
|
||||||
require("packer").sync()
|
require("packer").sync()
|
||||||
vim.cmd [[autocmd User PackerComplete ++once echo "Ready!" | lua setup()]]
|
vim.cmd([[autocmd User PackerComplete ++once echo "Ready!" | lua setup()]])
|
||||||
vim.opt.termguicolors = true
|
vim.opt.termguicolors = true
|
||||||
vim.opt.cursorline = true
|
vim.opt.cursorline = true
|
||||||
|
|
||||||
-- MODIFY NVIM-TREE SETTINGS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE
|
-- MODIFY NVIM-TREE SETTINGS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE
|
||||||
_G.setup = function()
|
_G.setup = function()
|
||||||
require("nvim-tree").setup {}
|
require("nvim-tree").setup({})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- UNCOMMENT this block for diagnostics issues, substituting pattern and cmd as appropriate.
|
||||||
|
-- Requires diagnostics.enable = true in setup.
|
||||||
|
--[[
|
||||||
|
vim.api.nvim_create_autocmd("FileType", {
|
||||||
|
pattern = "lua",
|
||||||
|
callback = function()
|
||||||
|
vim.lsp.start {
|
||||||
|
name = "my-luals",
|
||||||
|
cmd = { "lua-language-server" },
|
||||||
|
root_dir = vim.loop.cwd(),
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
]]
|
||||||
|
|||||||
8
.github/dependabot.yml
vendored
Normal file
8
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
reviewers:
|
||||||
|
- "gegoune"
|
||||||
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 |
106
.github/workflows/ci.yml
vendored
106
.github/workflows/ci.yml
vendored
@@ -2,33 +2,93 @@ name: CI
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
|
||||||
- '*'
|
|
||||||
push:
|
push:
|
||||||
branches:
|
branches: [master]
|
||||||
- master
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
luacheck:
|
lint:
|
||||||
name: luacheck
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Prepare
|
concurrency:
|
||||||
run: |
|
group: ${{ github.workflow }}-${{ matrix.lua_version }}-${{ github.head_ref || github.ref_name }}
|
||||||
sudo apt-get update
|
cancel-in-progress: true
|
||||||
sudo add-apt-repository universe
|
|
||||||
sudo apt install luarocks -y
|
strategy:
|
||||||
sudo luarocks install luacheck
|
matrix:
|
||||||
- name: Run luacheck
|
lua_version: [ 5.1 ]
|
||||||
run: luacheck .
|
|
||||||
stylua:
|
|
||||||
name: stylua
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- uses: JohnnyMorganz/stylua-action@1.0.0
|
|
||||||
|
- uses: leafo/gh-actions-lua@v10
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
luaVersion: ${{ matrix.lua_version }}
|
||||||
args: --color always --check lua/
|
|
||||||
|
- uses: leafo/gh-actions-luarocks@v4
|
||||||
|
|
||||||
|
- run: luarocks install luacheck 1.1.1
|
||||||
|
|
||||||
|
- run: make lint
|
||||||
|
|
||||||
|
style:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ matrix.emmy_lua_code_style_version }}-${{ github.head_ref || github.ref_name }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
emmy_lua_code_style_version: [ 1.5.6 ]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: install emmy_lua_code_style
|
||||||
|
run: |
|
||||||
|
mkdir -p CodeFormat
|
||||||
|
curl -L "https://github.com/CppCXY/EmmyLuaCodeStyle/releases/download/${{ matrix.emmy_lua_code_style_version }}/linux-x64.tar.gz" | tar zx --directory CodeFormat
|
||||||
|
|
||||||
|
- run: echo "CodeFormat/linux-x64/bin" >> "$GITHUB_PATH"
|
||||||
|
|
||||||
|
- run: make style
|
||||||
|
|
||||||
|
- run: make style-doc
|
||||||
|
|
||||||
|
check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ matrix.nvim_version }}-${{ matrix.luals_version }}-${{ github.head_ref || github.ref_name }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
nvim_version: [ stable, nightly ]
|
||||||
|
luals_version: [ 3.11.0 ]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: rhysd/action-setup-vim@v1
|
||||||
|
with:
|
||||||
|
neovim: true
|
||||||
|
version: ${{ matrix.nvim_version }}
|
||||||
|
|
||||||
|
- name: install luals
|
||||||
|
run: |
|
||||||
|
mkdir -p luals
|
||||||
|
curl -L "https://github.com/LuaLS/lua-language-server/releases/download/${{ matrix.luals_version }}/lua-language-server-${{ matrix.luals_version }}-linux-x64.tar.gz" | tar zx --directory luals
|
||||||
|
|
||||||
|
- run: echo "luals/bin" >> "$GITHUB_PATH"
|
||||||
|
|
||||||
|
- name: make check
|
||||||
|
env:
|
||||||
|
VIMRUNTIME: /home/runner/nvim-${{ matrix.nvim_version }}/share/nvim/runtime
|
||||||
|
run: make check
|
||||||
|
|
||||||
|
- run: make help-check
|
||||||
|
|||||||
37
.github/workflows/luarocks-release.yml
vendored
Normal file
37
.github/workflows/luarocks-release.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: Luarocks Release
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||||
|
workflow_dispatch:
|
||||||
|
jobs:
|
||||||
|
luarocks-upload:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: LuaRocks Upload
|
||||||
|
uses: nvim-neorocks/luarocks-tag-release@v7
|
||||||
|
env:
|
||||||
|
LUAROCKS_API_KEY: ${{ secrets.LUAROCKS_API_KEY }}
|
||||||
|
with:
|
||||||
|
summary: A File Explorer For Neovim
|
||||||
|
detailed_description: |
|
||||||
|
Automatic updates
|
||||||
|
|
||||||
|
File type icons
|
||||||
|
|
||||||
|
Git integration
|
||||||
|
|
||||||
|
Diagnostics integration - LSP and COC
|
||||||
|
|
||||||
|
(Live) filtering
|
||||||
|
|
||||||
|
Cut, copy, paste, rename, delete, create etc.
|
||||||
|
|
||||||
|
Highly customisable
|
||||||
|
|
||||||
|
Rich API
|
||||||
|
license: "GPL-3.0"
|
||||||
|
labels: neovim
|
||||||
|
dependencies: |
|
||||||
|
nvim-web-devicons
|
||||||
37
.github/workflows/release-please.yml
vendored
Normal file
37
.github/workflows/release-please.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
workflow_dispatch:
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
name: release-please
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
jobs:
|
||||||
|
release-please:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: google-github-actions/release-please-action@v4
|
||||||
|
id: release
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: tag major and minor versions
|
||||||
|
if: ${{ steps.release.outputs.release_created }}
|
||||||
|
run: |
|
||||||
|
git config user.name github-actions[bot]
|
||||||
|
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
|
||||||
|
git remote add gh-token "https://${{ secrets.GITHUB_TOKEN }}@github.com/google-github-actions/release-please-action.git"
|
||||||
|
git tag -d v${{ steps.release.outputs.major }} || true
|
||||||
|
git tag -d v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} || true
|
||||||
|
git tag -d v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}.${{ steps.release.outputs.patch }} || true
|
||||||
|
git push origin :v${{ steps.release.outputs.major }} || true
|
||||||
|
git push origin :v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} || true
|
||||||
|
git push origin :v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}.${{ steps.release.outputs.patch }} || true
|
||||||
|
git tag -a v${{ steps.release.outputs.major }} -m "Release v${{ steps.release.outputs.major }}"
|
||||||
|
git tag -a v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }} -m "Release v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}"
|
||||||
|
git tag -a v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}.${{ steps.release.outputs.patch }} -m "Release v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}.${{ steps.release.outputs.patch }}"
|
||||||
|
git push origin v${{ steps.release.outputs.major }}
|
||||||
|
git push origin v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}
|
||||||
|
git push origin v${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}.${{ steps.release.outputs.patch }}
|
||||||
19
.github/workflows/semantic-pr-subject.yml
vendored
Normal file
19
.github/workflows/semantic-pr-subject.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name: Semantic Pull Request Subject
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- reopened
|
||||||
|
- edited
|
||||||
|
- synchronize
|
||||||
|
workflow_dispatch:
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
jobs:
|
||||||
|
semantic-pr-subject:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: amannn/action-semantic-pull-request@v5.5.3
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/luals-out/
|
||||||
|
/luals/
|
||||||
|
# backup vim files
|
||||||
|
*~
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env bash
|
#!/bin/sh
|
||||||
|
|
||||||
stylua . --check || exit 1
|
make
|
||||||
luacheck . || exit 1
|
|
||||||
|
|||||||
11
.luacheckrc
11
.luacheckrc
@@ -1,14 +1,15 @@
|
|||||||
-- vim: ft=lua tw=80
|
local M = {}
|
||||||
|
|
||||||
-- Don't report unused self arguments of methods.
|
-- Don't report unused self arguments of methods.
|
||||||
self = false
|
M.self = false
|
||||||
|
|
||||||
ignore = {
|
M.ignore = {
|
||||||
"631", -- max_line_length
|
"631", -- max_line_length
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Global objects defined by the C code
|
-- Global objects defined by the C code
|
||||||
globals = {
|
M.globals = {
|
||||||
"vim",
|
"vim",
|
||||||
"TreeExplorer"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return M
|
||||||
|
|||||||
78
.luarc.json
Normal file
78
.luarc.json
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json",
|
||||||
|
"runtime.version.luals-check-only": "Lua 5.1",
|
||||||
|
"workspace": {
|
||||||
|
"library": [
|
||||||
|
"$VIMRUNTIME/lua/vim",
|
||||||
|
"${3rd}/luv/library"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"diagnostics": {
|
||||||
|
"libraryFiles": "Disable",
|
||||||
|
"globals": [],
|
||||||
|
"neededFileStatus": {
|
||||||
|
"ambiguity-1": "Any",
|
||||||
|
"assign-type-mismatch": "Any",
|
||||||
|
"await-in-sync": "Any",
|
||||||
|
"cast-local-type": "Any",
|
||||||
|
"cast-type-mismatch": "Any",
|
||||||
|
"circle-doc-class": "Any",
|
||||||
|
"close-non-object": "Any",
|
||||||
|
"code-after-break": "Any",
|
||||||
|
"codestyle-check": "None",
|
||||||
|
"count-down-loop": "Any",
|
||||||
|
"deprecated": "Any",
|
||||||
|
"different-requires": "Any",
|
||||||
|
"discard-returns": "Any",
|
||||||
|
"doc-field-no-class": "Any",
|
||||||
|
"duplicate-doc-alias": "Any",
|
||||||
|
"duplicate-doc-field": "Any",
|
||||||
|
"duplicate-doc-param": "Any",
|
||||||
|
"duplicate-index": "Any",
|
||||||
|
"duplicate-set-field": "Any",
|
||||||
|
"empty-block": "Any",
|
||||||
|
"global-element": "Any",
|
||||||
|
"global-in-nil-env": "Any",
|
||||||
|
"incomplete-signature-doc": "Any",
|
||||||
|
"inject-field": "Any",
|
||||||
|
"invisible": "Any",
|
||||||
|
"lowercase-global": "Any",
|
||||||
|
"missing-fields": "Any",
|
||||||
|
"missing-global-doc": "Any",
|
||||||
|
"missing-local-export-doc": "Any",
|
||||||
|
"missing-parameter": "Any",
|
||||||
|
"missing-return": "Any",
|
||||||
|
"missing-return-value": "Any",
|
||||||
|
"name-style-check": "None",
|
||||||
|
"need-check-nil": "Any",
|
||||||
|
"newfield-call": "Any",
|
||||||
|
"newline-call": "Any",
|
||||||
|
"no-unknown": "None",
|
||||||
|
"not-yieldable": "Any",
|
||||||
|
"param-type-mismatch": "Any",
|
||||||
|
"redefined-local": "Any",
|
||||||
|
"redundant-parameter": "Any",
|
||||||
|
"redundant-return": "Any",
|
||||||
|
"redundant-return-value": "Any",
|
||||||
|
"redundant-value": "Any",
|
||||||
|
"return-type-mismatch": "Any",
|
||||||
|
"spell-check": "None",
|
||||||
|
"trailing-space": "Any",
|
||||||
|
"unbalanced-assignments": "Any",
|
||||||
|
"undefined-doc-class": "Any",
|
||||||
|
"undefined-doc-name": "Any",
|
||||||
|
"undefined-doc-param": "Any",
|
||||||
|
"undefined-env-child": "Any",
|
||||||
|
"undefined-field": "None",
|
||||||
|
"undefined-global": "Any",
|
||||||
|
"unknown-cast-variable": "Any",
|
||||||
|
"unknown-diag-code": "Any",
|
||||||
|
"unknown-operator": "Any",
|
||||||
|
"unreachable-code": "Any",
|
||||||
|
"unused-function": "Any",
|
||||||
|
"unused-label": "Any",
|
||||||
|
"unused-local": "Any",
|
||||||
|
"unused-vararg": "Any"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
.release-please-manifest.json
Normal file
3
.release-please-manifest.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
".": "1.8.0"
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
column_width = 120
|
|
||||||
line_endings = "Unix"
|
|
||||||
indent_type = "Spaces"
|
|
||||||
indent_width = 2
|
|
||||||
quote_style = "AutoPreferDouble"
|
|
||||||
call_parentheses = "None"
|
|
||||||
233
CHANGELOG.md
Normal file
233
CHANGELOG.md
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [1.8.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.7.1...nvim-tree-v1.8.0) (2024-11-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **#2819:** add actions.open_file.relative_path, default enabled, following successful experiment ([#2995](https://github.com/nvim-tree/nvim-tree.lua/issues/2995)) ([2ee1c5e](https://github.com/nvim-tree/nvim-tree.lua/commit/2ee1c5e17fdfbf5013af31b1410e4a5f28f4cadd))
|
||||||
|
* **#2938:** add default filesystem_watchers.ignore_dirs = { "/.ccls-cache", "/build", "/node_modules", "/target", } ([#2940](https://github.com/nvim-tree/nvim-tree.lua/issues/2940)) ([010ae03](https://github.com/nvim-tree/nvim-tree.lua/commit/010ae0365aafd6275c478d932515d2e8e897b7bb))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **#2945:** stack overflow on api.git.reload or fugitive event with watchers disabled ([#2949](https://github.com/nvim-tree/nvim-tree.lua/issues/2949)) ([5ad8762](https://github.com/nvim-tree/nvim-tree.lua/commit/5ad87620ec9d1190d15c88171a3f0122bc16b0fe))
|
||||||
|
* **#2947:** root is never a dotfile, so that it doesn't propagate to children ([#2958](https://github.com/nvim-tree/nvim-tree.lua/issues/2958)) ([f5f6789](https://github.com/nvim-tree/nvim-tree.lua/commit/f5f67892996b280ae78b1b0a2d07c4fa29ae0905))
|
||||||
|
* **#2951:** highlights incorrect following cancelled pick ([#2952](https://github.com/nvim-tree/nvim-tree.lua/issues/2952)) ([1c9553a](https://github.com/nvim-tree/nvim-tree.lua/commit/1c9553a19f70df3dcb171546a3d5e034531ef093))
|
||||||
|
* **#2954:** resolve occasional tree flashing on diagnostics, set tree buffer options in deterministic order ([#2980](https://github.com/nvim-tree/nvim-tree.lua/issues/2980)) ([82ab19e](https://github.com/nvim-tree/nvim-tree.lua/commit/82ab19ebf79c1839d7351f2fed213d1af13a598e))
|
||||||
|
* **#2961:** windows: escape brackets and parentheses when opening file ([#2962](https://github.com/nvim-tree/nvim-tree.lua/issues/2962)) ([63c7ad9](https://github.com/nvim-tree/nvim-tree.lua/commit/63c7ad9037fb7334682dd0b3a177cee25c5c8a0f))
|
||||||
|
* **#2969:** After a rename, the node loses selection ([#2974](https://github.com/nvim-tree/nvim-tree.lua/issues/2974)) ([1403933](https://github.com/nvim-tree/nvim-tree.lua/commit/14039337a563f4efd72831888f332a15585f0ea1))
|
||||||
|
* **#2972:** error on :colorscheme ([#2973](https://github.com/nvim-tree/nvim-tree.lua/issues/2973)) ([6e5a204](https://github.com/nvim-tree/nvim-tree.lua/commit/6e5a204ca659bb8f2a564df75df2739edec03cb0))
|
||||||
|
* **#2976:** use vim.loop to preserve neovim 0.9 compatibility ([#2977](https://github.com/nvim-tree/nvim-tree.lua/issues/2977)) ([00dff48](https://github.com/nvim-tree/nvim-tree.lua/commit/00dff482f9a8fb806a54fd980359adc6cd45d435))
|
||||||
|
* **#2978:** grouped folder not showing closed icon ([#2979](https://github.com/nvim-tree/nvim-tree.lua/issues/2979)) ([120ba58](https://github.com/nvim-tree/nvim-tree.lua/commit/120ba58254835d412bbc91cffe847e9be835fadd))
|
||||||
|
* **#2981:** windows: root changed when navigating with LSP ([#2982](https://github.com/nvim-tree/nvim-tree.lua/issues/2982)) ([c22124b](https://github.com/nvim-tree/nvim-tree.lua/commit/c22124b37409bee6d1a0da77f4f3a1526f7a204d))
|
||||||
|
* symlink file icons rendered when renderer.icons.show.file = false, folder.symlink* was incorrectly rendered as folder.default|open ([#2983](https://github.com/nvim-tree/nvim-tree.lua/issues/2983)) ([2156bc0](https://github.com/nvim-tree/nvim-tree.lua/commit/2156bc08c982d3c4b4cfc2b8fd7faeff58a88e10))
|
||||||
|
|
||||||
|
## [1.7.1](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.7.0...nvim-tree-v1.7.1) (2024-09-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **#2794:** sshfs compatibility ([#2922](https://github.com/nvim-tree/nvim-tree.lua/issues/2922)) ([9650e73](https://github.com/nvim-tree/nvim-tree.lua/commit/9650e735baad0d39505f4cb4867a60f02858536a))
|
||||||
|
* **#2928:** nil explorer in parent move action ([#2929](https://github.com/nvim-tree/nvim-tree.lua/issues/2929)) ([0429f28](https://github.com/nvim-tree/nvim-tree.lua/commit/0429f286b350c65118d66b646775bf187936fa47))
|
||||||
|
* **#2930:** empty groups expanded on reload ([#2935](https://github.com/nvim-tree/nvim-tree.lua/issues/2935)) ([4520c03](https://github.com/nvim-tree/nvim-tree.lua/commit/4520c0355cc561830ee2cf90dc37a2a75abf7995))
|
||||||
|
* invalid explorer on open ([#2927](https://github.com/nvim-tree/nvim-tree.lua/issues/2927)) ([59a8a6a](https://github.com/nvim-tree/nvim-tree.lua/commit/59a8a6ae5e9d3eae99d08ab655d12fd51d5d17f3))
|
||||||
|
|
||||||
|
|
||||||
|
### Reverts
|
||||||
|
|
||||||
|
* **#2794:** sshfs compatibility ([#2920](https://github.com/nvim-tree/nvim-tree.lua/issues/2920)) ([8405ecf](https://github.com/nvim-tree/nvim-tree.lua/commit/8405ecfbd6bb08a94ffc9c68fef211eea56e8a3b))
|
||||||
|
|
||||||
|
## [1.7.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.6.1...nvim-tree-v1.7.0) (2024-09-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **#2430:** use vim.ui.open as default system_open, for neovim 0.10+ ([#2912](https://github.com/nvim-tree/nvim-tree.lua/issues/2912)) ([03f737e](https://github.com/nvim-tree/nvim-tree.lua/commit/03f737e5744a2b3ebb4b086f7636a3399224ec0c))
|
||||||
|
* help closes on <Esc> and api.tree.toggle_help mappings ([#2909](https://github.com/nvim-tree/nvim-tree.lua/issues/2909)) ([b652dbd](https://github.com/nvim-tree/nvim-tree.lua/commit/b652dbd0e0489c5fbb81fbededf0d99029cd2f38))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **#2862:** windows path replaces backslashes with forward slashes ([#2903](https://github.com/nvim-tree/nvim-tree.lua/issues/2903)) ([45a93d9](https://github.com/nvim-tree/nvim-tree.lua/commit/45a93d99794fff3064141d5b3a50db98ce352697))
|
||||||
|
* **#2906:** resource leak on populate children ([#2907](https://github.com/nvim-tree/nvim-tree.lua/issues/2907)) ([a4dd5ad](https://github.com/nvim-tree/nvim-tree.lua/commit/a4dd5ad5c8f9349142291d24e0e6466995594b9a))
|
||||||
|
* **#2917:** fix root copy paths: Y, ge, gy, y ([#2918](https://github.com/nvim-tree/nvim-tree.lua/issues/2918)) ([b18ce8b](https://github.com/nvim-tree/nvim-tree.lua/commit/b18ce8be8f162eee0bc37addcfe17d7d019fcec7))
|
||||||
|
* safely close last tree window ([#2913](https://github.com/nvim-tree/nvim-tree.lua/issues/2913)) ([bd48816](https://github.com/nvim-tree/nvim-tree.lua/commit/bd4881660bf0ddfa6acb21259f856ba3dcb26a93))
|
||||||
|
* safely close tree window with pcall and debug logging ([bd48816](https://github.com/nvim-tree/nvim-tree.lua/commit/bd4881660bf0ddfa6acb21259f856ba3dcb26a93))
|
||||||
|
|
||||||
|
## [1.6.1](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.6.0...nvim-tree-v1.6.1) (2024-09-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **#2794:** sshfs compatibility ([#2893](https://github.com/nvim-tree/nvim-tree.lua/issues/2893)) ([2d6e64d](https://github.com/nvim-tree/nvim-tree.lua/commit/2d6e64dd8c45a86f312552b7a47eef2c8623a25c))
|
||||||
|
* **#2868:** windows: do not visit unenumerable directories such as Application Data ([#2874](https://github.com/nvim-tree/nvim-tree.lua/issues/2874)) ([2104786](https://github.com/nvim-tree/nvim-tree.lua/commit/210478677cb9d672c4265deb0e9b59d58b675bd4))
|
||||||
|
* **#2878:** nowrapscan prevents move from root ([#2880](https://github.com/nvim-tree/nvim-tree.lua/issues/2880)) ([4234095](https://github.com/nvim-tree/nvim-tree.lua/commit/42340952af598a08ab80579d067b6da72a9e6d29))
|
||||||
|
* **#2879:** remove unnecessary tree window width setting to prevent unnecessary :wincmd = ([#2881](https://github.com/nvim-tree/nvim-tree.lua/issues/2881)) ([d43ab67](https://github.com/nvim-tree/nvim-tree.lua/commit/d43ab67d0eb4317961c5e9d15fffe908519debe0))
|
||||||
|
|
||||||
|
## [1.6.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.5.0...nvim-tree-v1.6.0) (2024-08-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **#2225:** add renderer.hidden_display to show a summary of hidden files below the tree ([#2856](https://github.com/nvim-tree/nvim-tree.lua/issues/2856)) ([e25eb7f](https://github.com/nvim-tree/nvim-tree.lua/commit/e25eb7fa83f7614bb23d762e91d2de44fcd7103b))
|
||||||
|
* **#2349:** add "right_align" option for renderer.icons.*_placement ([#2839](https://github.com/nvim-tree/nvim-tree.lua/issues/2839)) ([1d629a5](https://github.com/nvim-tree/nvim-tree.lua/commit/1d629a5d3f7d83d516494c221a2cfc079f43bc47))
|
||||||
|
* **#2349:** add "right_align" option for renderer.icons.*_placement ([#2846](https://github.com/nvim-tree/nvim-tree.lua/issues/2846)) ([48d0e82](https://github.com/nvim-tree/nvim-tree.lua/commit/48d0e82f9434691cc50d970898142a8c084a49d6))
|
||||||
|
* add renderer.highlight_hidden, renderer.icons.show.hidden and renderer.icons.hidden_placement for dotfile icons/highlights ([#2840](https://github.com/nvim-tree/nvim-tree.lua/issues/2840)) ([48a9290](https://github.com/nvim-tree/nvim-tree.lua/commit/48a92907575df1dbd7242975a04e98169cb3a115))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **#2859:** make sure window still exists when restoring options ([#2863](https://github.com/nvim-tree/nvim-tree.lua/issues/2863)) ([466fbed](https://github.com/nvim-tree/nvim-tree.lua/commit/466fbed3e4b61fcc23a48fe99de7bfa264a9fee8))
|
||||||
|
|
||||||
|
## [1.5.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.4.0...nvim-tree-v1.5.0) (2024-07-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **#2127:** add experimental.actions.open_file.relative_path to open files with a relative path rather than absolute ([#2805](https://github.com/nvim-tree/nvim-tree.lua/issues/2805)) ([869c064](https://github.com/nvim-tree/nvim-tree.lua/commit/869c064721a6c2091f22c3541e8f0ff958361771))
|
||||||
|
* **#2598:** add api.tree.resize ([#2811](https://github.com/nvim-tree/nvim-tree.lua/issues/2811)) ([2ede0de](https://github.com/nvim-tree/nvim-tree.lua/commit/2ede0de67b47e89e2b4cb488ea3f58b8f5a8c90a))
|
||||||
|
* **#2799:** `filesystem_watchers.ignore_dirs` and `git.disable_for_dirs` may be functions ([#2800](https://github.com/nvim-tree/nvim-tree.lua/issues/2800)) ([8b2c5c6](https://github.com/nvim-tree/nvim-tree.lua/commit/8b2c5c678be4b49dff6a2df794877000113fd77b))
|
||||||
|
* **#2799:** filesystem_watchers.ignore_dirs and git.disable_for_dirs may be functions ([8b2c5c6](https://github.com/nvim-tree/nvim-tree.lua/commit/8b2c5c678be4b49dff6a2df794877000113fd77b))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **#2813:** macos: enable file renaming with changed capitalization ([#2814](https://github.com/nvim-tree/nvim-tree.lua/issues/2814)) ([abfd1d1](https://github.com/nvim-tree/nvim-tree.lua/commit/abfd1d1b6772540364743531cc0331e08a0027a9))
|
||||||
|
* **#2819:** experimental.actions.open_file.relative_path issue following change directory ([#2820](https://github.com/nvim-tree/nvim-tree.lua/issues/2820)) ([12a9a99](https://github.com/nvim-tree/nvim-tree.lua/commit/12a9a995a455d2c2466e47140663275365a5d2fc))
|
||||||
|
|
||||||
|
## [1.4.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.3.3...nvim-tree-v1.4.0) (2024-06-09)
|
||||||
|
|
||||||
|
### Notice
|
||||||
|
|
||||||
|
* Neovim 0.9 is now the minimum supported version; please upgrade to neovim release version 0.9 or 0.10.
|
||||||
|
|
||||||
|
### Reverts
|
||||||
|
|
||||||
|
* **#2781:** "refactor: replace deprecated use of vim.diagnostic.is_disabled()" ([#2784](https://github.com/nvim-tree/nvim-tree.lua/issues/2784)) ([517e4fb](https://github.com/nvim-tree/nvim-tree.lua/commit/517e4fbb9ef3c0986da7047f44b4b91a2400f93c))
|
||||||
|
|
||||||
|
|
||||||
|
### Miscellaneous Chores
|
||||||
|
|
||||||
|
* release 1.4.0 ([1cac800](https://github.com/nvim-tree/nvim-tree.lua/commit/1cac8005df6da484c97499247754afa59fef92db))
|
||||||
|
|
||||||
|
## [1.3.3](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.3.2...nvim-tree-v1.3.3) (2024-05-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* nil access exception with git integration when changing branches ([#2774](https://github.com/nvim-tree/nvim-tree.lua/issues/2774)) ([340d3a9](https://github.com/nvim-tree/nvim-tree.lua/commit/340d3a9795e06bdd1814228de398cd510f9bfbb0))
|
||||||
|
|
||||||
|
## [1.3.2](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.3.1...nvim-tree-v1.3.2) (2024-05-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **#2758:** use nvim-webdevicons default file icon, not renderer.icons.glyphs.default, as per :help ([#2759](https://github.com/nvim-tree/nvim-tree.lua/issues/2759)) ([347e1eb](https://github.com/nvim-tree/nvim-tree.lua/commit/347e1eb35264677f66a79466bb5e3d111968e12c))
|
||||||
|
* **#2758:** use nvim-webdevicons default for default files ([347e1eb](https://github.com/nvim-tree/nvim-tree.lua/commit/347e1eb35264677f66a79466bb5e3d111968e12c))
|
||||||
|
* **#925:** handle newlines in file names ([#2754](https://github.com/nvim-tree/nvim-tree.lua/issues/2754)) ([64f61e4](https://github.com/nvim-tree/nvim-tree.lua/commit/64f61e4c913047a045ff90bd188dd3b54ee443cf))
|
||||||
|
|
||||||
|
## [1.3.1](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.3.0...nvim-tree-v1.3.1) (2024-04-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **#2535:** TextYankPost event sends vim.v.event ([#2734](https://github.com/nvim-tree/nvim-tree.lua/issues/2734)) ([d8d3a15](https://github.com/nvim-tree/nvim-tree.lua/commit/d8d3a1590a05b2d8b5eb26e2ed1c6052b1b47a77))
|
||||||
|
* **#2733:** escape trash path ([#2735](https://github.com/nvim-tree/nvim-tree.lua/issues/2735)) ([81eb8d5](https://github.com/nvim-tree/nvim-tree.lua/commit/81eb8d519233c105f30dc0a278607e62b20502fd))
|
||||||
|
|
||||||
|
## [1.3.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.2.0...nvim-tree-v1.3.0) (2024-03-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add update_focused_file.exclude ([#2673](https://github.com/nvim-tree/nvim-tree.lua/issues/2673)) ([e20966a](https://github.com/nvim-tree/nvim-tree.lua/commit/e20966ae558524f8d6f93dc37f5d2a8605f893e2))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **#2658:** change SpellCap groups to reduce confusion: ExecFile->Question, ImageFile->Question, SpecialFile->Title, Symlink->Underlined; add all other highlight groups to :NvimTreeHiTest ([#2732](https://github.com/nvim-tree/nvim-tree.lua/issues/2732)) ([0aca092](https://github.com/nvim-tree/nvim-tree.lua/commit/0aca0920f44b12a8383134bcb52da9faec123608))
|
||||||
|
* bookmark filter shows marked directory children ([#2719](https://github.com/nvim-tree/nvim-tree.lua/issues/2719)) ([2d97059](https://github.com/nvim-tree/nvim-tree.lua/commit/2d97059661c83787372c8c003e743c984ba3ac50))
|
||||||
|
|
||||||
|
## [1.2.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.1.1...nvim-tree-v1.2.0) (2024-03-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add api.tree.toggle_enable_filters ([#2706](https://github.com/nvim-tree/nvim-tree.lua/issues/2706)) ([f7c09bd](https://github.com/nvim-tree/nvim-tree.lua/commit/f7c09bd72e50e1795bd3afb9e2a2b157b4bfb3c3))
|
||||||
|
|
||||||
|
## [1.1.1](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.1.0...nvim-tree-v1.1.1) (2024-03-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **#2395:** marks.bulk.move defaults to directory at cursor ([#2688](https://github.com/nvim-tree/nvim-tree.lua/issues/2688)) ([cfea5bd](https://github.com/nvim-tree/nvim-tree.lua/commit/cfea5bd0806aab41bef6014c6cf5a510910ddbdb))
|
||||||
|
* **#2705:** change NvimTreeWindowPicker cterm background from Cyan to more visible DarkBlue ([#2708](https://github.com/nvim-tree/nvim-tree.lua/issues/2708)) ([1fd9c98](https://github.com/nvim-tree/nvim-tree.lua/commit/1fd9c98960463d2d5d400916c0633b2df016941d))
|
||||||
|
* bookmark filter should include parent directory ([#2704](https://github.com/nvim-tree/nvim-tree.lua/issues/2704)) ([76b9810](https://github.com/nvim-tree/nvim-tree.lua/commit/76b98109f62caa12b2f1dff472060b2233ea2e90))
|
||||||
|
|
||||||
|
## [1.1.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.0.0...nvim-tree-v1.1.0) (2024-03-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **#2630:** file renames can now create directories ([#2657](https://github.com/nvim-tree/nvim-tree.lua/issues/2657)) ([efafd73](https://github.com/nvim-tree/nvim-tree.lua/commit/efafd73efa9bc8c26282aed563ba0f01c7465b06))
|
||||||
|
* add api.fs.copy.basename, default mapping ge ([#2698](https://github.com/nvim-tree/nvim-tree.lua/issues/2698)) ([8f2a50f](https://github.com/nvim-tree/nvim-tree.lua/commit/8f2a50f1cd0c64003042364cf317c8788eaa6c8c))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **#2695:** git toplevel guard against missing paths ([#2696](https://github.com/nvim-tree/nvim-tree.lua/issues/2696)) ([3c4267e](https://github.com/nvim-tree/nvim-tree.lua/commit/3c4267eb5045fa86b67fe40c0c63d31efc801e77))
|
||||||
|
* searchcount exception on invalid search regex ([#2693](https://github.com/nvim-tree/nvim-tree.lua/issues/2693)) ([041dbd1](https://github.com/nvim-tree/nvim-tree.lua/commit/041dbd18f440207ad161503a384e7c82d575db66))
|
||||||
|
|
||||||
|
## [1.0.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v0.100.0...nvim-tree-v1.0.0) (2024-02-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **#2654:** filters.custom may be a function ([#2655](https://github.com/nvim-tree/nvim-tree.lua/issues/2655)) ([4a87b8b](https://github.com/nvim-tree/nvim-tree.lua/commit/4a87b8b46b4a30107971871df3cb7f4c30fdd5d0))
|
||||||
|
|
||||||
|
|
||||||
|
### Miscellaneous Chores
|
||||||
|
|
||||||
|
* release 1.0.0 ([#2678](https://github.com/nvim-tree/nvim-tree.lua/issues/2678)) ([d16246a](https://github.com/nvim-tree/nvim-tree.lua/commit/d16246a7575538f77e9246520449b99333c469f7))
|
||||||
|
|
||||||
|
## [0.100.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v0.99.0...nvim-tree-v0.100.0) (2024-02-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **#1389:** api: recursive node navigation for git and diagnostics ([#2525](https://github.com/nvim-tree/nvim-tree.lua/issues/2525)) ([5d13cc8](https://github.com/nvim-tree/nvim-tree.lua/commit/5d13cc8205bce4963866f73c50f6fdc18a515ffe))
|
||||||
|
* **#2415:** add :NvimTreeHiTest ([#2664](https://github.com/nvim-tree/nvim-tree.lua/issues/2664)) ([b278fc2](https://github.com/nvim-tree/nvim-tree.lua/commit/b278fc25ae0fc95e4808eb5618f07fc2522fd2b3))
|
||||||
|
* **#2415:** colour and highlight overhaul, see :help nvim-tree-highlight-overhaul ([#2455](https://github.com/nvim-tree/nvim-tree.lua/issues/2455)) ([e9c5abe](https://github.com/nvim-tree/nvim-tree.lua/commit/e9c5abe073a973f54d3ca10bfe30f253569f4405))
|
||||||
|
* add node.open.toggle_group_empty, default mapping L ([#2647](https://github.com/nvim-tree/nvim-tree.lua/issues/2647)) ([8cbb1db](https://github.com/nvim-tree/nvim-tree.lua/commit/8cbb1db8e90b62fc56f379992e622e9f919792ce))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **#2415:** disambiguate highlight groups, see :help nvim-tree-highlight-overhaul ([#2639](https://github.com/nvim-tree/nvim-tree.lua/issues/2639)) ([d9cb432](https://github.com/nvim-tree/nvim-tree.lua/commit/d9cb432d2c8d8fa9267ddbd7535d76fe4df89360))
|
||||||
|
* **#2415:** fix NvimTreeIndentMarker highlight group: FileIcon->FolderIcon ([e9ac136](https://github.com/nvim-tree/nvim-tree.lua/commit/e9ac136a3ab996aa8e4253253521dcf2cb66b81b))
|
||||||
|
* **#2415:** highlight help header and mappings ([#2669](https://github.com/nvim-tree/nvim-tree.lua/issues/2669)) ([39e6fef](https://github.com/nvim-tree/nvim-tree.lua/commit/39e6fef85ac3bb29532b877aa7c9c34911c661af))
|
||||||
|
* **#2415:** nvim 0.8 highlight overhaul support, limited to only show highest highlight precedence ([#2642](https://github.com/nvim-tree/nvim-tree.lua/issues/2642)) ([f39f7b6](https://github.com/nvim-tree/nvim-tree.lua/commit/f39f7b6fcd3865ac2146de4cb4045286308f2935))
|
||||||
|
* **#2415:** NvimTreeIndentMarker highlight group: FileIcon->FolderIcon ([#2656](https://github.com/nvim-tree/nvim-tree.lua/issues/2656)) ([e9ac136](https://github.com/nvim-tree/nvim-tree.lua/commit/e9ac136a3ab996aa8e4253253521dcf2cb66b81b))
|
||||||
|
* **#2624:** open file from docked floating window ([#2627](https://github.com/nvim-tree/nvim-tree.lua/issues/2627)) ([f24afa2](https://github.com/nvim-tree/nvim-tree.lua/commit/f24afa2cef551122b8bd53bb2e4a7df42343ce2e))
|
||||||
|
* **#2632:** occasional error stack when locating nvim-tree window ([#2633](https://github.com/nvim-tree/nvim-tree.lua/issues/2633)) ([48b1d86](https://github.com/nvim-tree/nvim-tree.lua/commit/48b1d8638fa3726236ae22e0e48a74ac8ea6592a))
|
||||||
|
* **#2637:** show buffer modified icons and highlights ([#2638](https://github.com/nvim-tree/nvim-tree.lua/issues/2638)) ([7bdb220](https://github.com/nvim-tree/nvim-tree.lua/commit/7bdb220d0fe604a77361e92cdbc7af1b8a412126))
|
||||||
|
* **#2643:** correctly apply linked highlight groups in tree window ([#2653](https://github.com/nvim-tree/nvim-tree.lua/issues/2653)) ([fbee8a6](https://github.com/nvim-tree/nvim-tree.lua/commit/fbee8a69a46f558d29ab84e96301425b0501c668))
|
||||||
|
* allow highlight overrides for DEFAULT_DEFS: NvimTreeFolderIcon, NvimTreeWindowPicker ([#2636](https://github.com/nvim-tree/nvim-tree.lua/issues/2636)) ([74525ac](https://github.com/nvim-tree/nvim-tree.lua/commit/74525ac04760bf0d9fec2bf51474d2b05f36048e))
|
||||||
|
* bad column offset when using full_name ([#2629](https://github.com/nvim-tree/nvim-tree.lua/issues/2629)) ([75ff64e](https://github.com/nvim-tree/nvim-tree.lua/commit/75ff64e6663fc3b23c72dca32b2f838acefe7c8a))
|
||||||
|
* passing nil as window handle in view.get_winnr ([48b1d86](https://github.com/nvim-tree/nvim-tree.lua/commit/48b1d8638fa3726236ae22e0e48a74ac8ea6592a))
|
||||||
|
|
||||||
|
## 0.99.0 (2024-01-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **#1850:** add "no bookmark" filter ([#2571](https://github.com/nvim-tree/nvim-tree.lua/issues/2571)) ([8f92e1e](https://github.com/nvim-tree/nvim-tree.lua/commit/8f92e1edd399f839a23776dcc6eee4ba18030370))
|
||||||
|
* add kind param to vim.ui.select function calls ([#2602](https://github.com/nvim-tree/nvim-tree.lua/issues/2602)) ([dc839a7](https://github.com/nvim-tree/nvim-tree.lua/commit/dc839a72a6496ce22ebd3dd959115cf97c1b20a0))
|
||||||
|
* add option to skip gitignored files on git navigation ([#2583](https://github.com/nvim-tree/nvim-tree.lua/issues/2583)) ([50f30bc](https://github.com/nvim-tree/nvim-tree.lua/commit/50f30bcd8c62ac4a83d133d738f268279f2c2ce2))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **#2519:** Diagnostics Not Updated When Tree Not Visible ([#2597](https://github.com/nvim-tree/nvim-tree.lua/issues/2597)) ([96a783f](https://github.com/nvim-tree/nvim-tree.lua/commit/96a783fbd606a458bcce2ef8041240a8b94510ce))
|
||||||
|
* **#2609:** help toggle ([#2611](https://github.com/nvim-tree/nvim-tree.lua/issues/2611)) ([fac4900](https://github.com/nvim-tree/nvim-tree.lua/commit/fac4900bd18a9fa15be3d104645d9bdef7b3dcec))
|
||||||
|
* hijack_cursor on update focused file and vim search ([#2600](https://github.com/nvim-tree/nvim-tree.lua/issues/2600)) ([02ae523](https://github.com/nvim-tree/nvim-tree.lua/commit/02ae52357ba4da77a4c120390791584a81d15340))
|
||||||
126
CONTRIBUTING.md
126
CONTRIBUTING.md
@@ -2,23 +2,129 @@
|
|||||||
|
|
||||||
Thank you for contributing.
|
Thank you for contributing.
|
||||||
|
|
||||||
## Styling and formatting
|
See [wiki: Development](https://github.com/nvim-tree/nvim-tree.lua/wiki/Development) for environment setup, tips and tools.
|
||||||
|
|
||||||
Code is formatted using luacheck, and linted using stylua.
|
# Tools
|
||||||
You can install these with:
|
|
||||||
|
|
||||||
```bash
|
Following are used during CI and strongly recommended during local development.
|
||||||
luarocks install luacheck
|
|
||||||
cargo install stylua
|
Language server: [luals](https://luals.github.io)
|
||||||
|
|
||||||
|
Lint: [luacheck](https://github.com/lunarmodules/luacheck/)
|
||||||
|
|
||||||
|
Style: [EmmyLuaCodeStyle](https://github.com/CppCXY/EmmyLuaCodeStyle): `CodeCheck`
|
||||||
|
|
||||||
|
nvim-tree.lua migrated from stylua to EmmyLuaCodeStyle ~2024/10. `vim.lsp.buf.format()` may be used as it is the default formatter for luals
|
||||||
|
|
||||||
|
You can install them via you OS package manager e.g. `pacman`, `brew` or other via other package managers such as `cargo` or `luarocks`
|
||||||
|
|
||||||
|
# Quality
|
||||||
|
|
||||||
|
The following quality checks are mandatory and are performed during CI. They run on the entire `lua` directory and return 1 on any failure.
|
||||||
|
|
||||||
|
You can run them all via `make` or `make all`
|
||||||
|
|
||||||
|
You can setup git hooks to run all checks by running `scripts/setup-hooks.sh`
|
||||||
|
|
||||||
|
## lint
|
||||||
|
|
||||||
|
1. Runs luacheck quietly using `.luacheck` settings
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make lint
|
||||||
```
|
```
|
||||||
|
|
||||||
## Adding new actions
|
## style
|
||||||
|
|
||||||
|
1. Runs CodeCheck using `.editorconfig` settings
|
||||||
|
1. Runs `scripts/doc-comments.sh` to validate annotated documentation
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make style
|
||||||
|
```
|
||||||
|
|
||||||
|
You can automatically fix `CodeCheck` issues via:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make style-fix
|
||||||
|
```
|
||||||
|
|
||||||
|
## check
|
||||||
|
|
||||||
|
1. Runs the checks that the LSP lua language server runs inside nvim using `.luarc.json` via `scripts/luals-check.sh`
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make check
|
||||||
|
```
|
||||||
|
|
||||||
|
Assumes `$VIMRUNTIME` is `/usr/share/nvim/runtime`. Adjust as necessary e.g.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
VIMRUNTIME="/my/path/to/runtime" make check
|
||||||
|
```
|
||||||
|
|
||||||
|
If `lua-language-server` is not available or `--check` doesn't function (e.g. Arch Linux 3.9.1-1) you can manually install it as per `ci.yml` e.g.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir luals
|
||||||
|
curl -L "https://github.com/LuaLS/lua-language-server/releases/download/3.9.1/lua-language-server-3.9.1-linux-x64.tar.gz" | tar zx --directory luals
|
||||||
|
|
||||||
|
PATH="luals/bin:${PATH}" make check
|
||||||
|
```
|
||||||
|
|
||||||
|
# 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.
|
To add a new action, add a file in `actions/name-of-the-action.lua`. You should export a `setup` function if some configuration is needed.
|
||||||
|
|
||||||
## Documentation
|
Once you did, you should run `make help-update`
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
|
||||||
|
## Opts
|
||||||
|
|
||||||
When adding new options, you should declare the defaults in the main `nvim-tree.lua` file.
|
When adding new options, you should declare the defaults in the main `nvim-tree.lua` file.
|
||||||
Once you did, you should run the `update-default-opts.sh` script which will update the default documentation in the README and the help file.
|
|
||||||
|
|
||||||
Documentation for options should also be added, see how this is done after `nvim-tree.disable_netrw` in the `nvim-tree-lua.txt` file.
|
Documentation for options should also be added to `nvim-tree-opts` in `doc/nvim-tree-lua.txt`
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
When adding or changing API please update :help nvim-tree-api
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
|
||||||
|
Please note that nvim-tree team members do not have access to nor expertise with Windows.
|
||||||
|
|
||||||
|
You will need to be an active participant during development and raise a PR to resolve any issues that may arise.
|
||||||
|
|
||||||
|
Please ensure that windows specific features and fixes are behind the appropriate feature flag, see [wiki: OS Feature Flags](https://github.com/nvim-tree/nvim-tree.lua/wiki/Development#os-feature-flags)
|
||||||
|
|
||||||
|
# Pull Request
|
||||||
|
|
||||||
|
Please reference any issues in the description e.g. "resolves #1234", which will be closed upon merge.
|
||||||
|
|
||||||
|
Please check "allow edits by maintainers" to allow nvim-tree developers to make small changes such as documentation tweaks.
|
||||||
|
|
||||||
|
## Subject
|
||||||
|
|
||||||
|
The merge commit message will be the subject of the PR.
|
||||||
|
|
||||||
|
A [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0) subject will be validated by the Semantic Pull Request Subject CI job. Reference the issue to be used in the release notes e.g.
|
||||||
|
|
||||||
|
`fix(#2395): marks.bulk.move defaults to directory at cursor`
|
||||||
|
|
||||||
|
Available types:
|
||||||
|
* feat: A new feature
|
||||||
|
* fix: A bug fix
|
||||||
|
* docs: Documentation only changes
|
||||||
|
* style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
|
||||||
|
* refactor: A code change that neither fixes a bug nor adds a feature
|
||||||
|
* perf: A code change that improves performance
|
||||||
|
* test: Adding missing tests or correcting existing tests
|
||||||
|
* build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
|
||||||
|
* ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
|
||||||
|
* chore: Other changes that don't modify src or test files
|
||||||
|
* revert: Reverts a previous commit
|
||||||
|
|
||||||
|
If in doubt, look at previous commits.
|
||||||
|
|
||||||
|
See also [The Conventional Commits ultimate cheatsheet](https://gist.github.com/gabrielecanepa/fa6cca1a8ae96f77896fe70ddee65527)
|
||||||
|
|||||||
48
Makefile
Normal file
48
Makefile
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
all: lint style check
|
||||||
|
|
||||||
|
#
|
||||||
|
# mandatory checks
|
||||||
|
#
|
||||||
|
lint: luacheck
|
||||||
|
|
||||||
|
style: style-check style-doc
|
||||||
|
|
||||||
|
check: luals
|
||||||
|
|
||||||
|
#
|
||||||
|
# subtasks
|
||||||
|
#
|
||||||
|
luacheck:
|
||||||
|
luacheck -q lua
|
||||||
|
|
||||||
|
# --diagnosis-as-error does not function for workspace, hence we post-process the output
|
||||||
|
style-check:
|
||||||
|
CodeFormat check --config .editorconfig --diagnosis-as-error --workspace lua
|
||||||
|
|
||||||
|
style-doc:
|
||||||
|
scripts/doc-comments.sh
|
||||||
|
|
||||||
|
luals:
|
||||||
|
@scripts/luals-check.sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# fixes
|
||||||
|
#
|
||||||
|
style-fix:
|
||||||
|
CodeFormat format --config .editorconfig --workspace lua
|
||||||
|
|
||||||
|
#
|
||||||
|
# utility
|
||||||
|
#
|
||||||
|
help-update:
|
||||||
|
scripts/help-update.sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# CI
|
||||||
|
#
|
||||||
|
help-check: help-update
|
||||||
|
git diff --exit-code doc/nvim-tree-lua.txt
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: all lint style check luacheck style-check style-doc luals style-fix help-update help-check
|
||||||
|
|
||||||
480
README.md
480
README.md
@@ -1,392 +1,186 @@
|
|||||||
# A File Explorer For Neovim Written In Lua
|
# 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="199" height="598" src="https://user-images.githubusercontent.com/1505378/232662694-8dc494e0-24da-497a-8541-29344293378c.png">
|
||||||
|
<img align="left" width="199" height="598" src="https://user-images.githubusercontent.com/1505378/232662698-2f321315-c67a-486b-85d8-8c391de52392.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.
|
||||||
|
|
||||||
|
Questions and general support: [Discussions](https://github.com/nvim-tree/nvim-tree.lua/discussions)
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
[neovim >=0.9.0](https://github.com/neovim/neovim/wiki/Installing-Neovim)
|
||||||
|
|
||||||
|
[nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons) is optional and used to display file icons. It requires a [patched font](https://www.nerdfonts.com/). Your terminal emulator must be configured to use that font, usually "Hack Nerd Font"
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
Install with [vim-plug](https://github.com/junegunn/vim-plug):
|
Please install via your preferred package manager. See [Installation](https://github.com/nvim-tree/nvim-tree.lua/wiki/Installation) for specific package manager instructions.
|
||||||
|
|
||||||
```vim
|
`nvim-tree/nvim-tree.lua`
|
||||||
" requires
|
|
||||||
Plug 'kyazdani42/nvim-web-devicons' " for file icons
|
|
||||||
Plug 'kyazdani42/nvim-tree.lua'
|
|
||||||
```
|
|
||||||
|
|
||||||
Install with [packer](https://github.com/wbthomason/packer.nvim):
|
Major or minor versions may be specified via tags: `v<MAJOR>` e.g. `v1` or `v<MAJOR>.<MINOR>` e.g. `v1.23`
|
||||||
|
|
||||||
|
`nvim-tree/nvim-web-devicons` optional, for file icons
|
||||||
|
|
||||||
|
Disabling [netrw](https://neovim.io/doc/user/pi_netrw.html) is strongly advised, see [:help nvim-tree-netrw](doc/nvim-tree-lua.txt)
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
Setup the plugin in your `init.lua`
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
use {
|
-- disable netrw at the very start of your init.lua
|
||||||
'kyazdani42/nvim-tree.lua',
|
vim.g.loaded_netrw = 1
|
||||||
requires = {
|
vim.g.loaded_netrwPlugin = 1
|
||||||
'kyazdani42/nvim-web-devicons', -- optional, for file icon
|
|
||||||
},
|
|
||||||
tag = 'nightly' -- optional, updated every week. (see issue #1193)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Setup
|
-- optionally enable 24-bit colour
|
||||||
|
vim.opt.termguicolors = true
|
||||||
|
|
||||||
Options are currently being migrated into the setup function, you need to run `require'nvim-tree'.setup()` in your personal configurations.
|
-- empty setup using defaults
|
||||||
Setup should be run in a lua file or in a lua heredoc (`:help lua-heredoc`) if using in a vim file.
|
require("nvim-tree").setup()
|
||||||
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
|
-- OR setup with some options
|
||||||
" default shows no icon by default
|
require("nvim-tree").setup({
|
||||||
let g:nvim_tree_icons = {
|
sort = {
|
||||||
\ 'default': "",
|
sorter = "case_sensitive",
|
||||||
\ 'symlink': "",
|
},
|
||||||
\ 'git': {
|
|
||||||
\ 'unstaged': "✗",
|
|
||||||
\ 'staged': "✓",
|
|
||||||
\ 'unmerged': "",
|
|
||||||
\ 'renamed': "➜",
|
|
||||||
\ 'untracked': "★",
|
|
||||||
\ 'deleted': "",
|
|
||||||
\ 'ignored': "◌"
|
|
||||||
\ },
|
|
||||||
\ 'folder': {
|
|
||||||
\ 'arrow_open': "",
|
|
||||||
\ 'arrow_closed': "",
|
|
||||||
\ 'default': "",
|
|
||||||
\ 'open': "",
|
|
||||||
\ 'empty': "",
|
|
||||||
\ 'empty_open': "",
|
|
||||||
\ 'symlink': "",
|
|
||||||
\ 'symlink_open': "",
|
|
||||||
\ }
|
|
||||||
\ }
|
|
||||||
|
|
||||||
nnoremap <C-n> :NvimTreeToggle<CR>
|
|
||||||
nnoremap <leader>r :NvimTreeRefresh<CR>
|
|
||||||
nnoremap <leader>n :NvimTreeFindFile<CR>
|
|
||||||
" More available functions:
|
|
||||||
" NvimTreeOpen
|
|
||||||
" NvimTreeClose
|
|
||||||
" NvimTreeFocus
|
|
||||||
" NvimTreeFindFileToggle
|
|
||||||
" NvimTreeResize
|
|
||||||
" NvimTreeCollapse
|
|
||||||
" NvimTreeCollapseKeepBuffers
|
|
||||||
|
|
||||||
set termguicolors " this variable must be enabled for colors to be applied properly
|
|
||||||
|
|
||||||
" a list of groups can be found at `:help nvim_tree_highlight`
|
|
||||||
highlight NvimTreeFolderIcon guibg=blue
|
|
||||||
```
|
|
||||||
|
|
||||||
```lua
|
|
||||||
-- init.lua
|
|
||||||
|
|
||||||
-- empty setup using defaults: add your own options
|
|
||||||
require'nvim-tree'.setup {
|
|
||||||
}
|
|
||||||
|
|
||||||
-- OR
|
|
||||||
|
|
||||||
-- setup with all defaults
|
|
||||||
-- each of these are documented in `:help nvim-tree.OPTION_NAME`
|
|
||||||
-- 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,
|
|
||||||
view = {
|
view = {
|
||||||
width = 30,
|
width = 30,
|
||||||
height = 30,
|
|
||||||
hide_root_folder = false,
|
|
||||||
side = "left",
|
|
||||||
preserve_window_proportions = false,
|
|
||||||
number = false,
|
|
||||||
relativenumber = false,
|
|
||||||
signcolumn = "yes",
|
|
||||||
mappings = {
|
|
||||||
custom_only = false,
|
|
||||||
list = {
|
|
||||||
-- user mappings go here
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
renderer = {
|
renderer = {
|
||||||
indent_markers = {
|
group_empty = true,
|
||||||
enable = false,
|
|
||||||
icons = {
|
|
||||||
corner = "└ ",
|
|
||||||
edge = "│ ",
|
|
||||||
none = " ",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
icons = {
|
|
||||||
webdev_colors = true,
|
|
||||||
git_placement = "before",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hijack_directories = {
|
|
||||||
enable = true,
|
|
||||||
auto_open = true,
|
|
||||||
},
|
|
||||||
update_focused_file = {
|
|
||||||
enable = false,
|
|
||||||
update_cwd = false,
|
|
||||||
ignore_list = {},
|
|
||||||
},
|
|
||||||
ignore_ft_on_setup = {},
|
|
||||||
system_open = {
|
|
||||||
cmd = "",
|
|
||||||
args = {},
|
|
||||||
},
|
|
||||||
diagnostics = {
|
|
||||||
enable = false,
|
|
||||||
show_on_dirs = false,
|
|
||||||
icons = {
|
|
||||||
hint = "",
|
|
||||||
info = "",
|
|
||||||
warning = "",
|
|
||||||
error = "",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
filters = {
|
filters = {
|
||||||
dotfiles = false,
|
dotfiles = true,
|
||||||
custom = {},
|
|
||||||
exclude = {},
|
|
||||||
},
|
},
|
||||||
git = {
|
})
|
||||||
enable = true,
|
|
||||||
ignore = true,
|
|
||||||
timeout = 400,
|
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
use_system_clipboard = true,
|
|
||||||
change_dir = {
|
|
||||||
enable = true,
|
|
||||||
global = false,
|
|
||||||
restrict_above_cwd = false,
|
|
||||||
},
|
|
||||||
open_file = {
|
|
||||||
quit_on_open = false,
|
|
||||||
resize_window = false,
|
|
||||||
window_picker = {
|
|
||||||
enable = true,
|
|
||||||
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
|
|
||||||
exclude = {
|
|
||||||
filetype = { "notify", "packer", "qf", "diff", "fugitive", "fugitiveblame" },
|
|
||||||
buftype = { "nofile", "terminal", "help" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
trash = {
|
|
||||||
cmd = "trash",
|
|
||||||
require_confirm = true,
|
|
||||||
},
|
|
||||||
log = {
|
|
||||||
enable = false,
|
|
||||||
truncate = false,
|
|
||||||
types = {
|
|
||||||
all = false,
|
|
||||||
config = false,
|
|
||||||
copy_paste = false,
|
|
||||||
diagnostics = false,
|
|
||||||
git = false,
|
|
||||||
profile = false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} -- END_DEFAULT_OPTS
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Key Bindings
|
### Help
|
||||||
|
|
||||||
### Default actions
|
Open the tree: `:NvimTreeOpen`
|
||||||
|
|
||||||
- `<CR>` or `o` on the root folder will cd in the above directory
|
Show the mappings: `g?`
|
||||||
- `<C-]>` will cd in the directory under the cursor
|
|
||||||
- `<BS>` will close current opened directory or parent
|
|
||||||
- type `a` to add a file. Adding a directory requires leaving a leading `/` at the end of the path.
|
|
||||||
> you can add multiple directories by doing foo/bar/baz/f and it will add foo bar and baz directories and f as a file
|
|
||||||
- type `r` to rename a file
|
|
||||||
- type `<C-r>` to rename a file and omit the filename on input
|
|
||||||
- type `x` to add/remove file/directory to cut clipboard
|
|
||||||
- type `c` to add/remove file/directory to copy clipboard
|
|
||||||
- type `y` will copy name to system clipboard
|
|
||||||
- type `Y` will copy relative path to system clipboard
|
|
||||||
- type `gy` will copy absolute path to system clipboard
|
|
||||||
- type `p` to paste from clipboard. Cut clipboard has precedence over copy (will prompt for confirmation)
|
|
||||||
- type `d` to delete a file (will prompt for confirmation)
|
|
||||||
- type `D` to trash a file (configured in setup())
|
|
||||||
- type `]c` to go to next git item
|
|
||||||
- type `[c` to go to prev git item
|
|
||||||
- type `-` to navigate up to the parent directory of the current file/directory
|
|
||||||
- type `s` to open a file with default system application or a folder with default file manager (if you want to change the command used to do it see `:h nvim-tree.setup` under `system_open`)
|
|
||||||
- if the file is a directory, `<CR>` will open the directory otherwise it will open the file in the buffer near the tree
|
|
||||||
- if the file is a symlink, `<CR>` will follow the symlink (if the target is a file)
|
|
||||||
- `<C-v>` will open the file in a vertical split
|
|
||||||
- `<C-x>` will open the file in a horizontal split
|
|
||||||
- `<C-t>` will open the file in a new tab
|
|
||||||
- `<Tab>` will open the file as a preview (keeps the cursor in the tree)
|
|
||||||
- `I` will toggle visibility of hidden folders / files
|
|
||||||
- `H` will toggle visibility of dotfiles (files/folders starting with a `.`)
|
|
||||||
- `R` will refresh the tree
|
|
||||||
- Double left click acts like `<CR>`
|
|
||||||
- Double right click acts like `<C-]>`
|
|
||||||
- `W` will collapse the whole tree
|
|
||||||
- `S` will prompt the user to enter a path and then expands the tree to match the path
|
|
||||||
- `.` will enter vim command mode with the file the cursor is on
|
|
||||||
- `C-k` will toggle a popup with file infos about the file under the cursor
|
|
||||||
|
|
||||||
### Settings
|
### Custom Mappings
|
||||||
|
|
||||||
|
[:help nvim-tree-mappings-default](doc/nvim-tree-lua.txt) are applied by default however you may customise via |nvim-tree.on_attach| e.g.
|
||||||
|
|
||||||
The `list` option in `view.mappings.list` is a table of
|
|
||||||
```lua
|
```lua
|
||||||
-- key can be either a string or a table of string (lhs)
|
local function my_on_attach(bufnr)
|
||||||
-- action is the name of the action, set to `""` to remove default action
|
local api = require "nvim-tree.api"
|
||||||
-- action_cb is the function that will be called, it receives the node as a parameter. Optional for default actions
|
|
||||||
-- mode is normal by default
|
|
||||||
|
|
||||||
local tree_cb = require'nvim-tree.config'.nvim_tree_callback
|
local function opts(desc)
|
||||||
|
return { desc = "nvim-tree: " .. desc, buffer = bufnr, noremap = true, silent = true, nowait = true }
|
||||||
|
end
|
||||||
|
|
||||||
local function print_node_path(node) {
|
-- default mappings
|
||||||
print(node.absolute_path)
|
api.config.mappings.default_on_attach(bufnr)
|
||||||
}
|
|
||||||
|
|
||||||
local list = {
|
-- custom mappings
|
||||||
{ key = {"<CR>", "o" }, action = "edit", mode = "n"},
|
vim.keymap.set('n', '<C-t>', api.tree.change_root_to_parent, opts('Up'))
|
||||||
{ key = "p", action = "print_path", action_cb = print_node_path },
|
vim.keymap.set('n', '?', api.tree.toggle_help, opts('Help'))
|
||||||
{ key = "s", cb = tree_cb("vsplit") }, --tree_cb and the cb property are deprecated
|
end
|
||||||
{ key = "<2-RightMouse>", action = "" }, -- will remove default cd action
|
|
||||||
|
-- pass to setup along with your other options
|
||||||
|
require("nvim-tree").setup {
|
||||||
|
---
|
||||||
|
on_attach = my_on_attach,
|
||||||
|
---
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
These are the default bindings:
|
### Highlight
|
||||||
```lua
|
|
||||||
|
|
||||||
-- default mappings
|
Run `:NvimTreeHiTest` to show all the highlights that nvim-tree uses.
|
||||||
local list = {
|
|
||||||
{ key = {"<CR>", "o", "<2-LeftMouse>"}, action = "edit" },
|
|
||||||
{ key = "<C-e>", action = "edit_in_place" },
|
|
||||||
{ key = {"O"}, action = "edit_no_picker" },
|
|
||||||
{ key = {"<2-RightMouse>", "<C-]>"}, action = "cd" },
|
|
||||||
{ key = "<C-v>", action = "vsplit" },
|
|
||||||
{ key = "<C-x>", action = "split" },
|
|
||||||
{ key = "<C-t>", action = "tabnew" },
|
|
||||||
{ key = "<", action = "prev_sibling" },
|
|
||||||
{ key = ">", action = "next_sibling" },
|
|
||||||
{ key = "P", action = "parent_node" },
|
|
||||||
{ key = "<BS>", action = "close_node" },
|
|
||||||
{ key = "<Tab>", action = "preview" },
|
|
||||||
{ key = "K", action = "first_sibling" },
|
|
||||||
{ key = "J", action = "last_sibling" },
|
|
||||||
{ key = "I", action = "toggle_git_ignored" },
|
|
||||||
{ key = "H", action = "toggle_dotfiles" },
|
|
||||||
{ key = "R", action = "refresh" },
|
|
||||||
{ key = "a", action = "create" },
|
|
||||||
{ key = "d", action = "remove" },
|
|
||||||
{ key = "D", action = "trash" },
|
|
||||||
{ key = "r", action = "rename" },
|
|
||||||
{ key = "<C-r>", action = "full_rename" },
|
|
||||||
{ key = "x", action = "cut" },
|
|
||||||
{ key = "c", action = "copy" },
|
|
||||||
{ key = "p", action = "paste" },
|
|
||||||
{ key = "y", action = "copy_name" },
|
|
||||||
{ key = "Y", action = "copy_path" },
|
|
||||||
{ key = "gy", action = "copy_absolute_path" },
|
|
||||||
{ key = "[c", action = "prev_git_item" },
|
|
||||||
{ key = "]c", action = "next_git_item" },
|
|
||||||
{ key = "-", action = "dir_up" },
|
|
||||||
{ key = "s", action = "system_open" },
|
|
||||||
{ key = "q", action = "close" },
|
|
||||||
{ key = "g?", action = "toggle_help" },
|
|
||||||
{ key = "W", action = "collapse_all" },
|
|
||||||
{ key = "S", action = "search_node" },
|
|
||||||
{ key = "<C-k>", action = "toggle_file_info" },
|
|
||||||
{ key = ".", action = "run_file_command" }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You can toggle the help UI by pressing `g?`.
|
They can be customised before or after setup is called and will be immediately
|
||||||
|
applied at runtime. e.g.
|
||||||
## Tips & reminders
|
|
||||||
|
|
||||||
1. You can add a directory by adding a `/` at the end of the paths, entering multiple directories `BASE/foo/bar/baz` will add directory foo, then bar and add a file baz to it.
|
|
||||||
2. You can update window options for the tree by setting `require"nvim-tree.view".View.winopts.MY_OPTION = MY_OPTION_VALUE`
|
|
||||||
3. `toggle` has a second parameter which allows to toggle without focusing the explorer (`require"nvim-tree".toggle(false, true)`).
|
|
||||||
4. You can allow nvim-tree to behave like vinegar (see `:help nvim-tree-vinegar`).
|
|
||||||
5. If you `:set nosplitright`, the files will open on the left side of the tree, placing the tree window in the right side of the file you opened.
|
|
||||||
6. You can automatically close the tab/vim when nvim-tree is the last window in the tab. WARNING: other plugins or automation may interfere with this:
|
|
||||||
```vim
|
|
||||||
autocmd BufEnter * ++nested if winnr('$') == 1 && bufname() == 'NvimTree_' . tabpagenr() | quit | endif
|
|
||||||
```
|
|
||||||
|
|
||||||
## Diagnostic Logging
|
|
||||||
|
|
||||||
You may enable diagnostic logging to `$XDG_CACHE_HOME/nvim/nvim-tree.log`. See `:help nvim-tree.log`.
|
|
||||||
|
|
||||||
## Performance Issues
|
|
||||||
|
|
||||||
If you are experiencing performance issues with nvim-tree.lua, you can enable profiling in the logs. It is advisable to enable git logging at the same time, as that can be a source of performance problems.
|
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
log = {
|
vim.cmd([[
|
||||||
enable = true,
|
:hi NvimTreeExecFile guifg=#ffa0a0
|
||||||
truncate = true,
|
:hi NvimTreeSpecialFile guifg=#ff80ff gui=underline
|
||||||
types = {
|
:hi NvimTreeSymlink guifg=Yellow gui=italic
|
||||||
git = true,
|
:hi link NvimTreeImageFile Title
|
||||||
profile = true,
|
]])
|
||||||
},
|
|
||||||
},
|
|
||||||
```
|
```
|
||||||
|
See [:help nvim-tree-highlight](doc/nvim-tree-lua.txt) for details.
|
||||||
|
|
||||||
Please attach `$XDG_CACHE_HOME/nvim/nvim-tree.log` if you raise an issue.
|
## Commands
|
||||||
|
|
||||||
*Performance Tips:*
|
See [:help nvim-tree-commands](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.
|
Basic commands:
|
||||||
|
|
||||||
* Try manually running the git command (see the logs) in your shell e.g. `git --no-optional-locks status --porcelain=v1 --ignored=matching -u`.
|
`:NvimTreeToggle` Open or close the tree. Takes an optional path argument.
|
||||||
|
|
||||||
* 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.
|
`:NvimTreeFocus` Open the tree if it is closed, and then focus on the tree.
|
||||||
|
|
||||||
* Try temporarily disabling git integration by setting `git.enable = false` in your setup.
|
`:NvimTreeFindFile` Move the cursor in the tree for the current buffer, opening folders if needed.
|
||||||
|
|
||||||
|
`:NvimTreeCollapse` Collapses the nvim-tree recursively.
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
nvim-tree is stable and new major features will not be added. The focus is on existing user experience.
|
||||||
|
|
||||||
|
Users are encouraged to add their own custom features via the public [API](#api).
|
||||||
|
|
||||||
|
Development is focused on:
|
||||||
|
* Bug fixes
|
||||||
|
* Performance
|
||||||
|
* Quality of Life improvements
|
||||||
|
* API / Events
|
||||||
|
* Enhancements to existing features
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
nvim-tree exposes a public API. This is non breaking, with additions made as necessary. See [:help nvim-tree-api](doc/nvim-tree-lua.txt)
|
||||||
|
|
||||||
|
See wiki [Recipes](https://github.com/nvim-tree/nvim-tree.lua/wiki/Recipes) and [Tips](https://github.com/nvim-tree/nvim-tree.lua/wiki/Tips) for ideas and inspiration.
|
||||||
|
|
||||||
|
Please raise a [feature request](https://github.com/nvim-tree/nvim-tree.lua/issues/new?assignees=&labels=feature+request&template=feature_request.md&title=) if the API is insufficient for your needs. [Contributions](#Contributing) are always welcome.
|
||||||
|
|
||||||
|
You may also subscribe to events that nvim-tree will dispatch in a variety of situations, see [:help nvim-tree-events](doc/nvim-tree-lua.txt)
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
PRs are always welcome. See [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.
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||

|
See [Showcases](https://github.com/nvim-tree/nvim-tree.lua/wiki/Showcases) wiki page for examples of user's configurations with sources.
|
||||||

|
|
||||||

|
Please add your own!
|
||||||

|
|
||||||
|
## Team
|
||||||
|
|
||||||
|
* [@alex-courtis](https://github.com/alex-courtis) Arch Linux
|
||||||
|
* [@gegoune](https://github.com/gegoune) macOS
|
||||||
|
* [@Akmadan23](https://github.com/Akmadan23) Linux
|
||||||
|
* [@dependabot[bot]](https://github.com/apps/dependabot) Ubuntu Linux
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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
|
|
||||||
@@ -1,247 +0,0 @@
|
|||||||
local a = vim.api
|
|
||||||
local uv = vim.loop
|
|
||||||
|
|
||||||
local lib = require "nvim-tree.lib"
|
|
||||||
local log = require "nvim-tree.log"
|
|
||||||
local utils = require "nvim-tree.utils"
|
|
||||||
local core = require "nvim-tree.core"
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
local clipboard = {
|
|
||||||
move = {},
|
|
||||||
copy = {},
|
|
||||||
}
|
|
||||||
|
|
||||||
local function do_copy(source, destination)
|
|
||||||
local source_stats, handle
|
|
||||||
local success, errmsg
|
|
||||||
|
|
||||||
source_stats, errmsg = uv.fs_stat(source)
|
|
||||||
if not source_stats then
|
|
||||||
log.line("copy_paste", "do_copy fs_stat '%s' failed '%s'", source, errmsg)
|
|
||||||
return false, errmsg
|
|
||||||
end
|
|
||||||
|
|
||||||
log.line("copy_paste", "do_copy %s '%s' -> '%s'", source_stats.type, source, destination)
|
|
||||||
|
|
||||||
if source == destination then
|
|
||||||
log.line("copy_paste", "do_copy source and destination are the same, exiting early")
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
if source_stats.type == "file" then
|
|
||||||
success, errmsg = uv.fs_copyfile(source, destination)
|
|
||||||
if not success then
|
|
||||||
log.line("copy_paste", "do_copy fs_copyfile failed '%s'", errmsg)
|
|
||||||
return false, errmsg
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
elseif source_stats.type == "directory" then
|
|
||||||
handle, errmsg = uv.fs_scandir(source)
|
|
||||||
if type(handle) == "string" then
|
|
||||||
return false, handle
|
|
||||||
elseif not handle then
|
|
||||||
log.line("copy_paste", "do_copy fs_scandir '%s' failed '%s'", source, errmsg)
|
|
||||||
return false, errmsg
|
|
||||||
end
|
|
||||||
|
|
||||||
success, errmsg = uv.fs_mkdir(destination, source_stats.mode)
|
|
||||||
if not success then
|
|
||||||
log.line("copy_paste", "do_copy fs_mkdir '%s' failed '%s'", destination, errmsg)
|
|
||||||
return false, errmsg
|
|
||||||
end
|
|
||||||
|
|
||||||
while true do
|
|
||||||
local name, _ = uv.fs_scandir_next(handle)
|
|
||||||
if not name then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
local new_name = utils.path_join { source, name }
|
|
||||||
local new_destination = utils.path_join { destination, name }
|
|
||||||
success, errmsg = do_copy(new_name, new_destination)
|
|
||||||
if not success then
|
|
||||||
return false, errmsg
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
errmsg = string.format("'%s' illegal file type '%s'", source, source_stats.type)
|
|
||||||
log.line("copy_paste", "do_copy %s", errmsg)
|
|
||||||
return false, errmsg
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local function do_single_paste(source, dest, action_type, action_fn)
|
|
||||||
local dest_stats
|
|
||||||
local success, errmsg, errcode
|
|
||||||
|
|
||||||
log.line("copy_paste", "do_single_paste '%s' -> '%s'", source, dest)
|
|
||||||
|
|
||||||
dest_stats, errmsg, errcode = uv.fs_stat(dest)
|
|
||||||
if not dest_stats and errcode ~= "ENOENT" then
|
|
||||||
a.nvim_err_writeln("Could not " .. action_type .. " " .. source .. " - " .. (errmsg or "???"))
|
|
||||||
return false, errmsg
|
|
||||||
end
|
|
||||||
|
|
||||||
local should_process = true
|
|
||||||
local should_rename = false
|
|
||||||
|
|
||||||
if dest_stats then
|
|
||||||
print(dest .. " already exists. Overwrite? y/n/r(ename)")
|
|
||||||
local ans = utils.get_user_input_char()
|
|
||||||
utils.clear_prompt()
|
|
||||||
should_process = ans:match "^y"
|
|
||||||
should_rename = ans:match "^r"
|
|
||||||
end
|
|
||||||
|
|
||||||
if should_rename then
|
|
||||||
local new_dest = vim.fn.input("New name: ", dest)
|
|
||||||
return do_single_paste(source, new_dest, action_type, action_fn)
|
|
||||||
end
|
|
||||||
|
|
||||||
if should_process then
|
|
||||||
success, errmsg = action_fn(source, dest)
|
|
||||||
if not success then
|
|
||||||
a.nvim_err_writeln("Could not " .. action_type .. " " .. source .. " - " .. (errmsg or "???"))
|
|
||||||
return false, errmsg
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function add_to_clipboard(node, clip)
|
|
||||||
if node.name == ".." then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
for idx, _node in ipairs(clip) do
|
|
||||||
if _node.absolute_path == node.absolute_path then
|
|
||||||
table.remove(clip, idx)
|
|
||||||
return a.nvim_out_write(node.absolute_path .. " removed to clipboard.\n")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
table.insert(clip, node)
|
|
||||||
a.nvim_out_write(node.absolute_path .. " added to clipboard.\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.copy(node)
|
|
||||||
add_to_clipboard(node, clipboard.copy)
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.cut(node)
|
|
||||||
add_to_clipboard(node, clipboard.move)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function do_paste(node, action_type, action_fn)
|
|
||||||
node = lib.get_last_group_node(node)
|
|
||||||
if node.name == ".." then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local clip = clipboard[action_type]
|
|
||||||
if #clip == 0 then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local destination = node.absolute_path
|
|
||||||
local stats, errmsg, errcode = uv.fs_stat(destination)
|
|
||||||
if not stats and errcode ~= "ENOENT" then
|
|
||||||
log.line("copy_paste", "do_paste fs_stat '%s' failed '%s'", destination, errmsg)
|
|
||||||
a.nvim_err_writeln("Could not " .. action_type .. " " .. destination .. " - " .. (errmsg or "???"))
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local is_dir = stats and stats.type == "directory"
|
|
||||||
|
|
||||||
if not is_dir then
|
|
||||||
destination = vim.fn.fnamemodify(destination, ":p:h")
|
|
||||||
elseif not node.open then
|
|
||||||
destination = vim.fn.fnamemodify(destination, ":p:h:h")
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, _node in ipairs(clip) do
|
|
||||||
local dest = utils.path_join { destination, _node.name }
|
|
||||||
do_single_paste(_node.absolute_path, dest, action_type, action_fn)
|
|
||||||
end
|
|
||||||
|
|
||||||
clipboard[action_type] = {}
|
|
||||||
return require("nvim-tree.actions.reloaders").reload_explorer()
|
|
||||||
end
|
|
||||||
|
|
||||||
local function do_cut(source, destination)
|
|
||||||
log.line("copy_paste", "do_cut '%s' -> '%s'", source, destination)
|
|
||||||
|
|
||||||
if source == destination then
|
|
||||||
log.line("copy_paste", "do_cut source and destination are the same, exiting early")
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local success, errmsg = uv.fs_rename(source, destination)
|
|
||||||
if not success then
|
|
||||||
log.line("copy_paste", "do_cut fs_rename failed '%s'", errmsg)
|
|
||||||
return false, errmsg
|
|
||||||
end
|
|
||||||
utils.rename_loaded_buffers(source, destination)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.paste(node)
|
|
||||||
if clipboard.move[1] ~= nil then
|
|
||||||
return do_paste(node, "move", do_cut)
|
|
||||||
end
|
|
||||||
|
|
||||||
return do_paste(node, "copy", do_copy)
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.print_clipboard()
|
|
||||||
local content = {}
|
|
||||||
if #clipboard.move > 0 then
|
|
||||||
table.insert(content, "Cut")
|
|
||||||
for _, item in pairs(clipboard.move) do
|
|
||||||
table.insert(content, " * " .. item.absolute_path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if #clipboard.copy > 0 then
|
|
||||||
table.insert(content, "Copy")
|
|
||||||
for _, item in pairs(clipboard.copy) do
|
|
||||||
table.insert(content, " * " .. item.absolute_path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return a.nvim_out_write(table.concat(content, "\n") .. "\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
local function copy_to_clipboard(content)
|
|
||||||
if M.use_system_clipboard == true then
|
|
||||||
vim.fn.setreg("+", content)
|
|
||||||
vim.fn.setreg('"', content)
|
|
||||||
return a.nvim_out_write(string.format("Copied %s to system clipboard! \n", content))
|
|
||||||
else
|
|
||||||
vim.fn.setreg('"', content)
|
|
||||||
vim.fn.setreg("1", content)
|
|
||||||
return a.nvim_out_write(string.format("Copied %s to neovim clipboard \n", content))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.copy_filename(node)
|
|
||||||
return copy_to_clipboard(node.name)
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.copy_path(node)
|
|
||||||
local absolute_path = node.absolute_path
|
|
||||||
local relative_path = utils.path_relative(absolute_path, core.get_cwd())
|
|
||||||
local content = node.nodes ~= nil and utils.path_add_trailing(relative_path) or relative_path
|
|
||||||
return copy_to_clipboard(content)
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.copy_absolute_path(node)
|
|
||||||
local absolute_path = node.absolute_path
|
|
||||||
local content = node.nodes ~= nil and utils.path_add_trailing(absolute_path) or absolute_path
|
|
||||||
return copy_to_clipboard(content)
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.setup(opts)
|
|
||||||
M.use_system_clipboard = opts.actions.use_system_clipboard
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
local a = vim.api
|
|
||||||
local uv = vim.loop
|
|
||||||
|
|
||||||
local utils = require "nvim-tree.utils"
|
|
||||||
local events = require "nvim-tree.events"
|
|
||||||
local lib = require "nvim-tree.lib"
|
|
||||||
local core = require "nvim-tree.core"
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
local function focus_file(file)
|
|
||||||
local _, i = utils.find_node(core.get_explorer().nodes, function(node)
|
|
||||||
return node.absolute_path == file
|
|
||||||
end)
|
|
||||||
require("nvim-tree.view").set_cursor { i + 1, 1 }
|
|
||||||
end
|
|
||||||
|
|
||||||
local function create_file(file)
|
|
||||||
if utils.file_exists(file) then
|
|
||||||
print(file .. " already exists. Overwrite? y/n")
|
|
||||||
local ans = utils.get_user_input_char()
|
|
||||||
utils.clear_prompt()
|
|
||||||
if ans ~= "y" then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local ok, fd = pcall(uv.fs_open, file, "w", 420)
|
|
||||||
if not ok then
|
|
||||||
a.nvim_err_writeln("Couldn't create file " .. file)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
uv.fs_close(fd)
|
|
||||||
events._dispatch_file_created(file)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_num_nodes(iter)
|
|
||||||
local i = 0
|
|
||||||
for _ in iter do
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
return i
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_containing_folder(node)
|
|
||||||
local is_open = vim.g.nvim_tree_create_in_closed_folder == 1 or node.open
|
|
||||||
if node.nodes ~= nil and is_open then
|
|
||||||
return utils.path_add_trailing(node.absolute_path)
|
|
||||||
end
|
|
||||||
local node_name_size = #(node.name or "")
|
|
||||||
return node.absolute_path:sub(0, -node_name_size - 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.fn(node)
|
|
||||||
node = lib.get_last_group_node(node)
|
|
||||||
if node.name == ".." then
|
|
||||||
node = {
|
|
||||||
absolute_path = core.get_cwd(),
|
|
||||||
nodes = core.get_explorer().nodes,
|
|
||||||
open = true,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local containing_folder = get_containing_folder(node)
|
|
||||||
|
|
||||||
local input_opts = { prompt = "Create file ", default = containing_folder, completion = "file" }
|
|
||||||
|
|
||||||
vim.ui.input(input_opts, function(new_file_path)
|
|
||||||
if not new_file_path or new_file_path == containing_folder then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
utils.clear_prompt()
|
|
||||||
|
|
||||||
if utils.file_exists(new_file_path) then
|
|
||||||
utils.warn "Cannot create: file already exists"
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- create a folder for each path element if the folder does not exist
|
|
||||||
-- if the answer ends with a /, create a file for the last path element
|
|
||||||
local is_last_path_file = not new_file_path:match(utils.path_separator .. "$")
|
|
||||||
local path_to_create = ""
|
|
||||||
local idx = 0
|
|
||||||
|
|
||||||
local num_nodes = get_num_nodes(utils.path_split(utils.path_remove_trailing(new_file_path)))
|
|
||||||
local is_error = false
|
|
||||||
for path in utils.path_split(new_file_path) do
|
|
||||||
idx = idx + 1
|
|
||||||
local p = utils.path_remove_trailing(path)
|
|
||||||
if #path_to_create == 0 and vim.fn.has "win32" == 1 then
|
|
||||||
path_to_create = utils.path_join { p, path_to_create }
|
|
||||||
else
|
|
||||||
path_to_create = utils.path_join { path_to_create, p }
|
|
||||||
end
|
|
||||||
if is_last_path_file and idx == num_nodes then
|
|
||||||
create_file(path_to_create)
|
|
||||||
elseif not utils.file_exists(path_to_create) then
|
|
||||||
local success = uv.fs_mkdir(path_to_create, 493)
|
|
||||||
if not success then
|
|
||||||
a.nvim_err_writeln("Could not create folder " .. path_to_create)
|
|
||||||
is_error = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not is_error then
|
|
||||||
a.nvim_out_write(new_file_path .. " was properly created\n")
|
|
||||||
end
|
|
||||||
events._dispatch_folder_created(new_file_path)
|
|
||||||
require("nvim-tree.actions.reloaders").reload_explorer()
|
|
||||||
focus_file(new_file_path)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
local utils = require "nvim-tree.utils"
|
|
||||||
local core = require "nvim-tree.core"
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
function M.fn(node)
|
|
||||||
if not node or node.name == ".." then
|
|
||||||
return require("nvim-tree.actions.change-dir").fn ".."
|
|
||||||
else
|
|
||||||
local newdir = vim.fn.fnamemodify(utils.path_remove_trailing(core.get_cwd()), ":h")
|
|
||||||
require("nvim-tree.actions.change-dir").fn(newdir)
|
|
||||||
return require("nvim-tree.actions.find-file").fn(node.absolute_path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -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
|
|
||||||
97
lua/nvim-tree/actions/finders/find-file.lua
Normal file
97
lua/nvim-tree/actions/finders/find-file.lua
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
local log = require("nvim-tree.log")
|
||||||
|
local view = require("nvim-tree.view")
|
||||||
|
local utils = require("nvim-tree.utils")
|
||||||
|
local core = require("nvim-tree.core")
|
||||||
|
|
||||||
|
local DirectoryNode = require("nvim-tree.node.directory")
|
||||||
|
local Iterator = require("nvim-tree.iterators.node-iterator")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local running = {}
|
||||||
|
|
||||||
|
---Find a path in the tree, expand it and focus it
|
||||||
|
---@param path string relative or absolute
|
||||||
|
function M.fn(path)
|
||||||
|
local explorer = core.get_explorer()
|
||||||
|
if not explorer or not view.is_visible() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- always match against the real path
|
||||||
|
local path_real = vim.loop.fs_realpath(path)
|
||||||
|
if not path_real then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if running[path_real] then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
running[path_real] = true
|
||||||
|
|
||||||
|
local profile = log.profile_start("find file %s", path_real)
|
||||||
|
|
||||||
|
-- refresh the contents of all parents, expanding groups as needed
|
||||||
|
if utils.get_node_from_path(path_real) == nil then
|
||||||
|
explorer:refresh_parent_nodes_for_path(vim.fn.fnamemodify(path_real, ":h"))
|
||||||
|
end
|
||||||
|
|
||||||
|
local line = core.get_nodes_starting_line()
|
||||||
|
|
||||||
|
local absolute_paths_searched = {}
|
||||||
|
|
||||||
|
local found = Iterator.builder(core.get_explorer().nodes)
|
||||||
|
:matcher(function(node)
|
||||||
|
return node.absolute_path == path_real or node.link_to == path_real
|
||||||
|
end)
|
||||||
|
:applier(function(node)
|
||||||
|
local incremented_line = false
|
||||||
|
if not node.group_next then
|
||||||
|
line = line + 1
|
||||||
|
incremented_line = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if vim.tbl_contains(absolute_paths_searched, node.absolute_path) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
table.insert(absolute_paths_searched, node.absolute_path)
|
||||||
|
|
||||||
|
local abs_match = vim.startswith(path_real, node.absolute_path .. utils.path_separator)
|
||||||
|
local link_match = node.link_to and vim.startswith(path_real, node.link_to .. utils.path_separator)
|
||||||
|
|
||||||
|
if abs_match or link_match then
|
||||||
|
local dir = node:as(DirectoryNode)
|
||||||
|
if dir then
|
||||||
|
if not dir.group_next then
|
||||||
|
dir.open = true
|
||||||
|
end
|
||||||
|
if #dir.nodes == 0 then
|
||||||
|
core.get_explorer():expand(dir)
|
||||||
|
if dir.group_next and incremented_line then
|
||||||
|
line = line - 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
:recursor(function(node)
|
||||||
|
node = node and node:as(DirectoryNode)
|
||||||
|
if node then
|
||||||
|
return node.group_next and { node.group_next } or (node.open and #node.nodes > 0 and node.nodes)
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
:iterate()
|
||||||
|
|
||||||
|
if found and view.is_visible() then
|
||||||
|
explorer.renderer:draw()
|
||||||
|
view.set_cursor({ line, 0 })
|
||||||
|
end
|
||||||
|
|
||||||
|
running[path_real] = false
|
||||||
|
|
||||||
|
log.profile_end(profile)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
6
lua/nvim-tree/actions/finders/init.lua
Normal file
6
lua/nvim-tree/actions/finders/init.lua
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
M.find_file = require("nvim-tree.actions.finders.find-file")
|
||||||
|
M.search_node = require("nvim-tree.actions.finders.search-node")
|
||||||
|
|
||||||
|
return M
|
||||||
116
lua/nvim-tree/actions/finders/search-node.lua
Normal file
116
lua/nvim-tree/actions/finders/search-node.lua
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
local core = require("nvim-tree.core")
|
||||||
|
local find_file = require("nvim-tree.actions.finders.find-file").fn
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@param search_dir string|nil
|
||||||
|
---@param input_path string
|
||||||
|
---@return string|nil
|
||||||
|
local function search(search_dir, input_path)
|
||||||
|
local realpaths_searched = {}
|
||||||
|
local explorer = core.get_explorer()
|
||||||
|
|
||||||
|
if not explorer then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not search_dir then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param dir string
|
||||||
|
---@return string|nil
|
||||||
|
local function iter(dir)
|
||||||
|
local realpath, path, name, stat, handle, _
|
||||||
|
|
||||||
|
local filter_status = explorer.filters:prepare()
|
||||||
|
|
||||||
|
handle, _ = vim.loop.fs_scandir(dir)
|
||||||
|
if not handle then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
realpath, _ = vim.loop.fs_realpath(dir)
|
||||||
|
if not realpath or vim.tbl_contains(realpaths_searched, realpath) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
table.insert(realpaths_searched, realpath)
|
||||||
|
|
||||||
|
name, _ = vim.loop.fs_scandir_next(handle)
|
||||||
|
while name do
|
||||||
|
path = dir .. "/" .. name
|
||||||
|
|
||||||
|
---@type uv.fs_stat.result|nil
|
||||||
|
stat, _ = vim.loop.fs_stat(path)
|
||||||
|
if not stat then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
if not explorer.filters:should_filter(path, stat, filter_status) then
|
||||||
|
if string.find(path, "/" .. input_path .. "$") then
|
||||||
|
return path
|
||||||
|
end
|
||||||
|
|
||||||
|
if stat.type == "directory" then
|
||||||
|
path = iter(path)
|
||||||
|
if path then
|
||||||
|
return path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
name, _ = vim.loop.fs_scandir_next(handle)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return iter(search_dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.fn()
|
||||||
|
if not core.get_explorer() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- temporarily set &path
|
||||||
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
|
|
||||||
|
local path_existed, path_opt
|
||||||
|
if vim.fn.has("nvim-0.10") == 1 then
|
||||||
|
path_existed, path_opt = pcall(vim.api.nvim_get_option_value, "path", { buf = bufnr })
|
||||||
|
vim.api.nvim_set_option_value("path", core.get_cwd() .. "/**", { buf = bufnr })
|
||||||
|
else
|
||||||
|
path_existed, path_opt = pcall(vim.api.nvim_buf_get_option, bufnr, "path") ---@diagnostic disable-line: deprecated
|
||||||
|
vim.api.nvim_buf_set_option(bufnr, "path", core.get_cwd() .. "/**") ---@diagnostic disable-line: deprecated
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.ui.input({ prompt = "Search: ", completion = "file_in_path" }, function(input_path)
|
||||||
|
if not input_path or input_path == "" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
-- reset &path
|
||||||
|
if path_existed then
|
||||||
|
if vim.fn.has("nvim-0.10") == 1 then
|
||||||
|
vim.api.nvim_set_option_value("path", path_opt, { buf = bufnr })
|
||||||
|
else
|
||||||
|
vim.api.nvim_buf_set_option(bufnr, "path", path_opt) ---@diagnostic disable-line: deprecated
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if vim.fn.has("nvim-0.10") == 1 then
|
||||||
|
vim.api.nvim_set_option_value("path", nil, { buf = bufnr })
|
||||||
|
else
|
||||||
|
vim.api.nvim_buf_set_option(bufnr, "path", nil) ---@diagnostic disable-line: deprecated
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- strip trailing slash
|
||||||
|
input_path = string.gsub(input_path, "/$", "")
|
||||||
|
|
||||||
|
-- search under cwd
|
||||||
|
local found = search(core.get_cwd(), input_path)
|
||||||
|
if found then
|
||||||
|
find_file(found)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
391
lua/nvim-tree/actions/fs/clipboard.lua
Normal file
391
lua/nvim-tree/actions/fs/clipboard.lua
Normal file
@@ -0,0 +1,391 @@
|
|||||||
|
local lib = require("nvim-tree.lib")
|
||||||
|
local log = require("nvim-tree.log")
|
||||||
|
local utils = require("nvim-tree.utils")
|
||||||
|
local core = require("nvim-tree.core")
|
||||||
|
local events = require("nvim-tree.events")
|
||||||
|
local notify = require("nvim-tree.notify")
|
||||||
|
|
||||||
|
local find_file = require("nvim-tree.actions.finders.find-file").fn
|
||||||
|
|
||||||
|
local Class = require("nvim-tree.classic")
|
||||||
|
local DirectoryNode = require("nvim-tree.node.directory")
|
||||||
|
|
||||||
|
---@alias ClipboardAction "copy" | "cut"
|
||||||
|
---@alias ClipboardData table<ClipboardAction, Node[]>
|
||||||
|
|
||||||
|
---@alias ClipboardActionFn fun(source: string, dest: string): boolean, string?
|
||||||
|
|
||||||
|
---@class (exact) Clipboard: Class
|
||||||
|
---@field private explorer Explorer
|
||||||
|
---@field private data ClipboardData
|
||||||
|
---@field private clipboard_name string
|
||||||
|
---@field private reg string
|
||||||
|
local Clipboard = Class:extend()
|
||||||
|
|
||||||
|
---@class Clipboard
|
||||||
|
---@overload fun(args: ClipboardArgs): Clipboard
|
||||||
|
|
||||||
|
---@class (exact) ClipboardArgs
|
||||||
|
---@field explorer Explorer
|
||||||
|
|
||||||
|
---@protected
|
||||||
|
---@param args ClipboardArgs
|
||||||
|
function Clipboard:new(args)
|
||||||
|
self.explorer = args.explorer
|
||||||
|
|
||||||
|
self.data = {
|
||||||
|
copy = {},
|
||||||
|
cut = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
self.clipboard_name = self.explorer.opts.actions.use_system_clipboard and "system" or "neovim"
|
||||||
|
self.reg = self.explorer.opts.actions.use_system_clipboard and "+" or "1"
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param source string
|
||||||
|
---@param destination string
|
||||||
|
---@return boolean
|
||||||
|
---@return string|nil
|
||||||
|
local function do_copy(source, destination)
|
||||||
|
local source_stats, err = vim.loop.fs_stat(source)
|
||||||
|
|
||||||
|
if not source_stats then
|
||||||
|
log.line("copy_paste", "do_copy fs_stat '%s' failed '%s'", source, err)
|
||||||
|
return false, err
|
||||||
|
end
|
||||||
|
|
||||||
|
log.line("copy_paste", "do_copy %s '%s' -> '%s'", source_stats.type, source, destination)
|
||||||
|
|
||||||
|
if source == destination then
|
||||||
|
log.line("copy_paste", "do_copy source and destination are the same, exiting early")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if source_stats.type == "file" then
|
||||||
|
local success
|
||||||
|
success, err = vim.loop.fs_copyfile(source, destination)
|
||||||
|
if not success then
|
||||||
|
log.line("copy_paste", "do_copy fs_copyfile failed '%s'", err)
|
||||||
|
return false, err
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
elseif source_stats.type == "directory" then
|
||||||
|
local handle
|
||||||
|
handle, err = vim.loop.fs_scandir(source)
|
||||||
|
if type(handle) == "string" then
|
||||||
|
return false, handle
|
||||||
|
elseif not handle then
|
||||||
|
log.line("copy_paste", "do_copy fs_scandir '%s' failed '%s'", source, err)
|
||||||
|
return false, err
|
||||||
|
end
|
||||||
|
|
||||||
|
local success
|
||||||
|
success, err = vim.loop.fs_mkdir(destination, source_stats.mode)
|
||||||
|
if not success then
|
||||||
|
log.line("copy_paste", "do_copy fs_mkdir '%s' failed '%s'", destination, err)
|
||||||
|
return false, err
|
||||||
|
end
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local name, _ = vim.loop.fs_scandir_next(handle)
|
||||||
|
if not name then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local new_name = utils.path_join({ source, name })
|
||||||
|
local new_destination = utils.path_join({ destination, name })
|
||||||
|
success, err = do_copy(new_name, new_destination)
|
||||||
|
if not success then
|
||||||
|
return false, err
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
err = string.format("'%s' illegal file type '%s'", source, source_stats.type)
|
||||||
|
log.line("copy_paste", "do_copy %s", err)
|
||||||
|
return false, err
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param source string
|
||||||
|
---@param dest string
|
||||||
|
---@param action ClipboardAction
|
||||||
|
---@param action_fn ClipboardActionFn
|
||||||
|
---@return boolean|nil -- success
|
||||||
|
---@return string|nil -- error message
|
||||||
|
local function do_single_paste(source, dest, action, action_fn)
|
||||||
|
local notify_source = notify.render_path(source)
|
||||||
|
|
||||||
|
log.line("copy_paste", "do_single_paste '%s' -> '%s'", source, dest)
|
||||||
|
|
||||||
|
local dest_stats, err, err_name = vim.loop.fs_stat(dest)
|
||||||
|
if not dest_stats and err_name ~= "ENOENT" then
|
||||||
|
notify.error("Could not " .. action .. " " .. notify_source .. " - " .. (err or "???"))
|
||||||
|
return false, err
|
||||||
|
end
|
||||||
|
|
||||||
|
local function on_process()
|
||||||
|
local success, error = action_fn(source, dest)
|
||||||
|
if not success then
|
||||||
|
notify.error("Could not " .. action .. " " .. notify_source .. " - " .. (error or "???"))
|
||||||
|
return false, error
|
||||||
|
end
|
||||||
|
|
||||||
|
find_file(utils.path_remove_trailing(dest))
|
||||||
|
end
|
||||||
|
|
||||||
|
if dest_stats then
|
||||||
|
local input_opts = {
|
||||||
|
prompt = "Rename to ",
|
||||||
|
default = dest,
|
||||||
|
completion = "dir",
|
||||||
|
}
|
||||||
|
|
||||||
|
if source == dest then
|
||||||
|
vim.ui.input(input_opts, function(new_dest)
|
||||||
|
utils.clear_prompt()
|
||||||
|
if new_dest then
|
||||||
|
do_single_paste(source, new_dest, action, action_fn)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
local prompt_select = "Overwrite " .. dest .. " ?"
|
||||||
|
local prompt_input = prompt_select .. " R(ename)/y/n: "
|
||||||
|
lib.prompt(prompt_input, prompt_select, { "", "y", "n" }, { "Rename", "Yes", "No" }, "nvimtree_overwrite_rename", function(item_short)
|
||||||
|
utils.clear_prompt()
|
||||||
|
if item_short == "y" then
|
||||||
|
on_process()
|
||||||
|
elseif item_short == "" or item_short == "r" then
|
||||||
|
vim.ui.input(input_opts, function(new_dest)
|
||||||
|
utils.clear_prompt()
|
||||||
|
if new_dest then
|
||||||
|
do_single_paste(source, new_dest, action, action_fn)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
on_process()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
---@param clip ClipboardData
|
||||||
|
local function toggle(node, clip)
|
||||||
|
if node.name == ".." then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local notify_node = notify.render_path(node.absolute_path)
|
||||||
|
|
||||||
|
if utils.array_remove(clip, node) then
|
||||||
|
notify.info(notify_node .. " removed from clipboard.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(clip, node)
|
||||||
|
notify.info(notify_node .. " added to clipboard.")
|
||||||
|
end
|
||||||
|
|
||||||
|
---Clear copied and cut
|
||||||
|
function Clipboard:clear_clipboard()
|
||||||
|
self.data.copy = {}
|
||||||
|
self.data.cut = {}
|
||||||
|
notify.info("Clipboard has been emptied.")
|
||||||
|
self.explorer.renderer:draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
---Copy one node
|
||||||
|
---@param node Node
|
||||||
|
function Clipboard:copy(node)
|
||||||
|
utils.array_remove(self.data.cut, node)
|
||||||
|
toggle(node, self.data.copy)
|
||||||
|
self.explorer.renderer:draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
---Cut one node
|
||||||
|
---@param node Node
|
||||||
|
function Clipboard:cut(node)
|
||||||
|
utils.array_remove(self.data.copy, node)
|
||||||
|
toggle(node, self.data.cut)
|
||||||
|
self.explorer.renderer:draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
---Paste cut or cop
|
||||||
|
---@private
|
||||||
|
---@param node Node
|
||||||
|
---@param action ClipboardAction
|
||||||
|
---@param action_fn ClipboardActionFn
|
||||||
|
function Clipboard:do_paste(node, action, action_fn)
|
||||||
|
if node.name == ".." then
|
||||||
|
node = self.explorer
|
||||||
|
else
|
||||||
|
local dir = node:as(DirectoryNode)
|
||||||
|
if dir then
|
||||||
|
node = dir:last_group_node()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local clip = self.data[action]
|
||||||
|
if #clip == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local destination = node.absolute_path
|
||||||
|
local stats, err, err_name = vim.loop.fs_stat(destination)
|
||||||
|
if not stats and err_name ~= "ENOENT" then
|
||||||
|
log.line("copy_paste", "do_paste fs_stat '%s' failed '%s'", destination, err)
|
||||||
|
notify.error("Could not " .. action .. " " .. notify.render_path(destination) .. " - " .. (err or "???"))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local is_dir = stats and stats.type == "directory"
|
||||||
|
if not is_dir then
|
||||||
|
destination = vim.fn.fnamemodify(destination, ":p:h")
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, _node in ipairs(clip) do
|
||||||
|
local dest = utils.path_join({ destination, _node.name })
|
||||||
|
do_single_paste(_node.absolute_path, dest, action, action_fn)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.data[action] = {}
|
||||||
|
if not self.explorer.opts.filesystem_watchers.enable then
|
||||||
|
self.explorer:reload_explorer()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param source string
|
||||||
|
---@param destination string
|
||||||
|
---@return boolean
|
||||||
|
---@return string?
|
||||||
|
local function do_cut(source, destination)
|
||||||
|
log.line("copy_paste", "do_cut '%s' -> '%s'", source, destination)
|
||||||
|
|
||||||
|
if source == destination then
|
||||||
|
log.line("copy_paste", "do_cut source and destination are the same, exiting early")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
events._dispatch_will_rename_node(source, destination)
|
||||||
|
local success, errmsg = vim.loop.fs_rename(source, destination)
|
||||||
|
if not success then
|
||||||
|
log.line("copy_paste", "do_cut fs_rename failed '%s'", errmsg)
|
||||||
|
return false, errmsg
|
||||||
|
end
|
||||||
|
utils.rename_loaded_buffers(source, destination)
|
||||||
|
events._dispatch_node_renamed(source, destination)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
---Paste cut (if present) or copy (if present)
|
||||||
|
---@param node Node
|
||||||
|
function Clipboard:paste(node)
|
||||||
|
if self.data.cut[1] ~= nil then
|
||||||
|
self:do_paste(node, "cut", do_cut)
|
||||||
|
elseif self.data.copy[1] ~= nil then
|
||||||
|
self:do_paste(node, "copy", do_copy)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Clipboard:print_clipboard()
|
||||||
|
local content = {}
|
||||||
|
if #self.data.cut > 0 then
|
||||||
|
table.insert(content, "Cut")
|
||||||
|
for _, node in pairs(self.data.cut) do
|
||||||
|
table.insert(content, " * " .. (notify.render_path(node.absolute_path)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #self.data.copy > 0 then
|
||||||
|
table.insert(content, "Copy")
|
||||||
|
for _, node in pairs(self.data.copy) do
|
||||||
|
table.insert(content, " * " .. (notify.render_path(node.absolute_path)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
notify.info(table.concat(content, "\n") .. "\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param content string
|
||||||
|
function Clipboard:copy_to_reg(content)
|
||||||
|
-- manually firing TextYankPost does not set vim.v.event
|
||||||
|
-- workaround: create a scratch buffer with the clipboard contents and send a yank command
|
||||||
|
local temp_buf = vim.api.nvim_create_buf(false, true)
|
||||||
|
vim.api.nvim_buf_set_text(temp_buf, 0, 0, 0, 0, { content })
|
||||||
|
vim.api.nvim_buf_call(temp_buf, function()
|
||||||
|
vim.cmd(string.format('normal! "%sy$', self.reg))
|
||||||
|
end)
|
||||||
|
vim.api.nvim_buf_delete(temp_buf, {})
|
||||||
|
|
||||||
|
notify.info(string.format("Copied %s to %s clipboard!", content, self.clipboard_name))
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
function Clipboard:copy_filename(node)
|
||||||
|
if node.name == ".." then
|
||||||
|
-- root
|
||||||
|
self:copy_to_reg(vim.fn.fnamemodify(self.explorer.absolute_path, ":t"))
|
||||||
|
else
|
||||||
|
-- node
|
||||||
|
self:copy_to_reg(node.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
function Clipboard:copy_basename(node)
|
||||||
|
if node.name == ".." then
|
||||||
|
-- root
|
||||||
|
self:copy_to_reg(vim.fn.fnamemodify(self.explorer.absolute_path, ":t:r"))
|
||||||
|
else
|
||||||
|
-- node
|
||||||
|
self:copy_to_reg(vim.fn.fnamemodify(node.name, ":r"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
function Clipboard:copy_path(node)
|
||||||
|
if node.name == ".." then
|
||||||
|
-- root
|
||||||
|
self:copy_to_reg(utils.path_add_trailing(""))
|
||||||
|
else
|
||||||
|
-- node
|
||||||
|
local absolute_path = node.absolute_path
|
||||||
|
local cwd = core.get_cwd()
|
||||||
|
if cwd == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local relative_path = utils.path_relative(absolute_path, cwd)
|
||||||
|
if node:is(DirectoryNode) then
|
||||||
|
self:copy_to_reg(utils.path_add_trailing(relative_path))
|
||||||
|
else
|
||||||
|
self:copy_to_reg(relative_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
function Clipboard:copy_absolute_path(node)
|
||||||
|
if node.name == ".." then
|
||||||
|
node = self.explorer
|
||||||
|
end
|
||||||
|
|
||||||
|
local absolute_path = node.absolute_path
|
||||||
|
local content = node.nodes ~= nil and utils.path_add_trailing(absolute_path) or absolute_path
|
||||||
|
self:copy_to_reg(content)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Node is cut. Will not be copied.
|
||||||
|
---@param node Node
|
||||||
|
---@return boolean
|
||||||
|
function Clipboard:is_cut(node)
|
||||||
|
return vim.tbl_contains(self.data.cut, node)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Node is copied. Will not be cut.
|
||||||
|
---@param node Node
|
||||||
|
---@return boolean
|
||||||
|
function Clipboard:is_copied(node)
|
||||||
|
return vim.tbl_contains(self.data.copy, node)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Clipboard
|
||||||
105
lua/nvim-tree/actions/fs/create-file.lua
Normal file
105
lua/nvim-tree/actions/fs/create-file.lua
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
local utils = require("nvim-tree.utils")
|
||||||
|
local events = require("nvim-tree.events")
|
||||||
|
local core = require("nvim-tree.core")
|
||||||
|
local notify = require("nvim-tree.notify")
|
||||||
|
|
||||||
|
local find_file = require("nvim-tree.actions.finders.find-file").fn
|
||||||
|
|
||||||
|
local FileNode = require("nvim-tree.node.file")
|
||||||
|
local DirectoryNode = require("nvim-tree.node.directory")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@param file string
|
||||||
|
local function create_and_notify(file)
|
||||||
|
events._dispatch_will_create_file(file)
|
||||||
|
local ok, fd = pcall(vim.loop.fs_open, file, "w", 420)
|
||||||
|
if not ok or type(fd) ~= "number" then
|
||||||
|
notify.error("Couldn't create file " .. notify.render_path(file))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
vim.loop.fs_close(fd)
|
||||||
|
events._dispatch_file_created(file)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param iter function iterable
|
||||||
|
---@return integer
|
||||||
|
local function get_num_nodes(iter)
|
||||||
|
local i = 0
|
||||||
|
for _ in iter do
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node?
|
||||||
|
function M.fn(node)
|
||||||
|
node = node or core.get_explorer()
|
||||||
|
if not node then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local dir = node:is(FileNode) and node.parent or node:as(DirectoryNode)
|
||||||
|
if not dir then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
dir = dir:last_group_node()
|
||||||
|
|
||||||
|
local containing_folder = utils.path_add_trailing(dir.absolute_path)
|
||||||
|
|
||||||
|
local input_opts = {
|
||||||
|
prompt = "Create file ",
|
||||||
|
default = containing_folder,
|
||||||
|
completion = "file",
|
||||||
|
}
|
||||||
|
|
||||||
|
vim.ui.input(input_opts, function(new_file_path)
|
||||||
|
utils.clear_prompt()
|
||||||
|
if not new_file_path or new_file_path == containing_folder then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if utils.file_exists(new_file_path) then
|
||||||
|
notify.warn("Cannot create: file already exists")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- create a folder for each path element if the folder does not exist
|
||||||
|
-- if the answer ends with a /, create a file for the last path element
|
||||||
|
local is_last_path_file = not new_file_path:match(utils.path_separator .. "$")
|
||||||
|
local path_to_create = ""
|
||||||
|
local idx = 0
|
||||||
|
|
||||||
|
local num_nodes = get_num_nodes(utils.path_split(utils.path_remove_trailing(new_file_path)))
|
||||||
|
local is_error = false
|
||||||
|
for path in utils.path_split(new_file_path) do
|
||||||
|
idx = idx + 1
|
||||||
|
local p = utils.path_remove_trailing(path)
|
||||||
|
if #path_to_create == 0 and vim.fn.has("win32") == 1 then
|
||||||
|
path_to_create = utils.path_join({ p, path_to_create })
|
||||||
|
else
|
||||||
|
path_to_create = utils.path_join({ path_to_create, p })
|
||||||
|
end
|
||||||
|
if is_last_path_file and idx == num_nodes then
|
||||||
|
create_and_notify(path_to_create)
|
||||||
|
elseif not utils.file_exists(path_to_create) then
|
||||||
|
local success = vim.loop.fs_mkdir(path_to_create, 493)
|
||||||
|
if not success then
|
||||||
|
notify.error("Could not create folder " .. notify.render_path(path_to_create))
|
||||||
|
is_error = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
events._dispatch_folder_created(new_file_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not is_error then
|
||||||
|
notify.info(notify.render_path(new_file_path) .. " was properly created")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- synchronously refreshes as we can't wait for the watchers
|
||||||
|
find_file(utils.path_remove_trailing(new_file_path))
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
14
lua/nvim-tree/actions/fs/init.lua
Normal file
14
lua/nvim-tree/actions/fs/init.lua
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
M.create_file = require("nvim-tree.actions.fs.create-file")
|
||||||
|
M.remove_file = require("nvim-tree.actions.fs.remove-file")
|
||||||
|
M.rename_file = require("nvim-tree.actions.fs.rename-file")
|
||||||
|
M.trash = require("nvim-tree.actions.fs.trash")
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.remove_file.setup(opts)
|
||||||
|
M.rename_file.setup(opts)
|
||||||
|
M.trash.setup(opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
160
lua/nvim-tree/actions/fs/remove-file.lua
Normal file
160
lua/nvim-tree/actions/fs/remove-file.lua
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
local core = require("nvim-tree.core")
|
||||||
|
local utils = require("nvim-tree.utils")
|
||||||
|
local events = require("nvim-tree.events")
|
||||||
|
local view = require("nvim-tree.view")
|
||||||
|
local lib = require("nvim-tree.lib")
|
||||||
|
local notify = require("nvim-tree.notify")
|
||||||
|
|
||||||
|
local DirectoryLinkNode = require("nvim-tree.node.directory-link")
|
||||||
|
local DirectoryNode = require("nvim-tree.node.directory")
|
||||||
|
|
||||||
|
local M = {
|
||||||
|
config = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
---@param windows integer[]
|
||||||
|
local function close_windows(windows)
|
||||||
|
-- Prevent from closing when the win count equals 1 or 2,
|
||||||
|
-- where the win to remove could be the last opened.
|
||||||
|
-- For details see #2503.
|
||||||
|
if view.View.float.enable and #vim.api.nvim_list_wins() < 3 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
|
||||||
|
|
||||||
|
---@param absolute_path string
|
||||||
|
local function clear_buffer(absolute_path)
|
||||||
|
local bufs = vim.fn.getbufinfo({ bufloaded = 1, buflisted = 1 })
|
||||||
|
for _, buf in pairs(bufs) do
|
||||||
|
if buf.name == absolute_path then
|
||||||
|
local tree_winnr = vim.api.nvim_get_current_win()
|
||||||
|
if buf.hidden == 0 and (#bufs > 1 or view.View.float.enable) then
|
||||||
|
vim.api.nvim_set_current_win(buf.windows[1])
|
||||||
|
vim.cmd(":bn")
|
||||||
|
end
|
||||||
|
vim.api.nvim_buf_delete(buf.bufnr, { force = true })
|
||||||
|
if not view.View.float.quit_on_focus_loss then
|
||||||
|
vim.api.nvim_set_current_win(tree_winnr)
|
||||||
|
end
|
||||||
|
if M.config.actions.remove_file.close_window then
|
||||||
|
close_windows(buf.windows)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param cwd string
|
||||||
|
---@return boolean|nil
|
||||||
|
local function remove_dir(cwd)
|
||||||
|
local handle, err = vim.loop.fs_scandir(cwd)
|
||||||
|
if not handle then
|
||||||
|
notify.error(err)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local name, _ = vim.loop.fs_scandir_next(handle)
|
||||||
|
if not name then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local new_cwd = utils.path_join({ cwd, name })
|
||||||
|
|
||||||
|
-- Type must come from fs_stat and not fs_scandir_next to maintain sshfs compatibility
|
||||||
|
local stat = vim.loop.fs_stat(new_cwd)
|
||||||
|
local type = stat and stat.type or nil
|
||||||
|
|
||||||
|
if type == "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
|
||||||
|
|
||||||
|
--- Remove a node, notify errors, dispatch events
|
||||||
|
---@param node Node
|
||||||
|
function M.remove(node)
|
||||||
|
local notify_node = notify.render_path(node.absolute_path)
|
||||||
|
if node:is(DirectoryNode) and not node:is(DirectoryLinkNode) then
|
||||||
|
local success = remove_dir(node.absolute_path)
|
||||||
|
if not success then
|
||||||
|
notify.error("Could not remove " .. notify_node)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
events._dispatch_folder_removed(node.absolute_path)
|
||||||
|
else
|
||||||
|
events._dispatch_will_remove_file(node.absolute_path)
|
||||||
|
local success = vim.loop.fs_unlink(node.absolute_path)
|
||||||
|
if not success then
|
||||||
|
notify.error("Could not remove " .. notify_node)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
events._dispatch_file_removed(node.absolute_path)
|
||||||
|
clear_buffer(node.absolute_path)
|
||||||
|
end
|
||||||
|
notify.info(notify_node .. " was properly removed.")
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
function M.fn(node)
|
||||||
|
if node.name == ".." then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local function do_remove()
|
||||||
|
M.remove(node)
|
||||||
|
local explorer = core.get_explorer()
|
||||||
|
if not M.config.filesystem_watchers.enable and explorer then
|
||||||
|
explorer:reload_explorer()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if M.config.ui.confirm.remove then
|
||||||
|
local prompt_select = "Remove " .. node.name .. "?"
|
||||||
|
local prompt_input, items_short, items_long
|
||||||
|
|
||||||
|
if M.config.ui.confirm.default_yes then
|
||||||
|
prompt_input = prompt_select .. " Y/n: "
|
||||||
|
items_short = { "", "n" }
|
||||||
|
items_long = { "Yes", "No" }
|
||||||
|
else
|
||||||
|
prompt_input = prompt_select .. " y/N: "
|
||||||
|
items_short = { "", "y" }
|
||||||
|
items_long = { "No", "Yes" }
|
||||||
|
end
|
||||||
|
|
||||||
|
lib.prompt(prompt_input, prompt_select, items_short, items_long, "nvimtree_remove", function(item_short)
|
||||||
|
utils.clear_prompt()
|
||||||
|
if item_short == "y" or item_short == (M.config.ui.confirm.default_yes and "") then
|
||||||
|
do_remove()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
do_remove()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.config.ui = opts.ui
|
||||||
|
M.config.actions = opts.actions
|
||||||
|
M.config.filesystem_watchers = opts.filesystem_watchers
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
181
lua/nvim-tree/actions/fs/rename-file.lua
Normal file
181
lua/nvim-tree/actions/fs/rename-file.lua
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
local core = require("nvim-tree.core")
|
||||||
|
local utils = require("nvim-tree.utils")
|
||||||
|
local events = require("nvim-tree.events")
|
||||||
|
local notify = require("nvim-tree.notify")
|
||||||
|
|
||||||
|
local find_file = require("nvim-tree.actions.finders.find-file").fn
|
||||||
|
|
||||||
|
local DirectoryNode = require("nvim-tree.node.directory")
|
||||||
|
|
||||||
|
local M = {
|
||||||
|
config = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
---@param iter function iterable
|
||||||
|
---@return integer
|
||||||
|
local function get_num_nodes(iter)
|
||||||
|
local i = 0
|
||||||
|
for _ in iter do
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
|
||||||
|
local ALLOWED_MODIFIERS = {
|
||||||
|
[":p"] = true,
|
||||||
|
[":p:h"] = true,
|
||||||
|
[":t"] = true,
|
||||||
|
[":t:r"] = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function err_fmt(from, to, reason)
|
||||||
|
return string.format("Cannot rename %s -> %s: %s", from, to, reason)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function rename_file_exists(node, to)
|
||||||
|
if not utils.is_macos then
|
||||||
|
return utils.file_exists(to)
|
||||||
|
end
|
||||||
|
|
||||||
|
if string.lower(node) == string.lower(to) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return utils.file_exists(to)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
---@param to string
|
||||||
|
function M.rename(node, to)
|
||||||
|
local notify_from = notify.render_path(node.absolute_path)
|
||||||
|
local notify_to = notify.render_path(to)
|
||||||
|
|
||||||
|
if rename_file_exists(notify_from, notify_to) then
|
||||||
|
notify.warn(err_fmt(notify_from, notify_to, "file already exists"))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- create a folder for each path element if the folder does not exist
|
||||||
|
local idx = 0
|
||||||
|
local path_to_create = ""
|
||||||
|
|
||||||
|
local num_nodes = get_num_nodes(utils.path_split(utils.path_remove_trailing(to)))
|
||||||
|
local is_error = false
|
||||||
|
for path in utils.path_split(to) do
|
||||||
|
idx = idx + 1
|
||||||
|
|
||||||
|
local p = utils.path_remove_trailing(path)
|
||||||
|
if #path_to_create == 0 and vim.fn.has("win32") == 1 then
|
||||||
|
path_to_create = utils.path_join({ p, path_to_create })
|
||||||
|
else
|
||||||
|
path_to_create = utils.path_join({ path_to_create, p })
|
||||||
|
end
|
||||||
|
|
||||||
|
if idx == num_nodes then
|
||||||
|
events._dispatch_will_rename_node(node.absolute_path, to)
|
||||||
|
local success, err = vim.loop.fs_rename(node.absolute_path, to)
|
||||||
|
|
||||||
|
if not success then
|
||||||
|
notify.warn(err_fmt(notify_from, notify_to, err))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
elseif not rename_file_exists(notify_from, path_to_create) then
|
||||||
|
local success = vim.loop.fs_mkdir(path_to_create, 493)
|
||||||
|
if not success then
|
||||||
|
notify.error("Could not create folder " .. notify.render_path(path_to_create))
|
||||||
|
is_error = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
is_error = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not is_error then
|
||||||
|
notify.info(string.format("%s -> %s", notify_from, notify_to))
|
||||||
|
utils.rename_loaded_buffers(node.absolute_path, to)
|
||||||
|
events._dispatch_node_renamed(node.absolute_path, to)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param default_modifier string|nil
|
||||||
|
---@return fun(node: Node, modifier: string)
|
||||||
|
function M.fn(default_modifier)
|
||||||
|
default_modifier = default_modifier or ":t"
|
||||||
|
|
||||||
|
return function(node, modifier)
|
||||||
|
local explorer = core.get_explorer()
|
||||||
|
if not explorer then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(node) ~= "table" then
|
||||||
|
node = explorer:get_node_at_cursor()
|
||||||
|
end
|
||||||
|
if not node then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(modifier) ~= "string" then
|
||||||
|
modifier = default_modifier
|
||||||
|
end
|
||||||
|
|
||||||
|
-- support for only specific modifiers have been implemented
|
||||||
|
if not ALLOWED_MODIFIERS[modifier] then
|
||||||
|
notify.warn("Modifier " .. vim.inspect(modifier) .. " is not in allowed list : " .. table.concat(ALLOWED_MODIFIERS, ","))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local dir = node:as(DirectoryNode)
|
||||||
|
if dir then
|
||||||
|
node = dir:last_group_node()
|
||||||
|
end
|
||||||
|
if node.name == ".." then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local namelen = node.name:len()
|
||||||
|
local directory = node.absolute_path:sub(0, namelen * -1 - 1)
|
||||||
|
local default_path
|
||||||
|
local prepend = ""
|
||||||
|
local append = ""
|
||||||
|
default_path = vim.fn.fnamemodify(node.absolute_path, modifier)
|
||||||
|
if modifier:sub(0, 2) == ":t" then
|
||||||
|
prepend = directory
|
||||||
|
end
|
||||||
|
if modifier == ":t:r" then
|
||||||
|
local extension = vim.fn.fnamemodify(node.name, ":e")
|
||||||
|
append = extension:len() == 0 and "" or "." .. extension
|
||||||
|
end
|
||||||
|
if modifier == ":p:h" then
|
||||||
|
default_path = default_path .. "/"
|
||||||
|
end
|
||||||
|
|
||||||
|
local input_opts = {
|
||||||
|
prompt = "Rename to ",
|
||||||
|
default = default_path,
|
||||||
|
completion = "file",
|
||||||
|
}
|
||||||
|
|
||||||
|
vim.ui.input(input_opts, function(new_file_path)
|
||||||
|
utils.clear_prompt()
|
||||||
|
if not new_file_path then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local full_new_path = prepend .. new_file_path .. append
|
||||||
|
|
||||||
|
M.rename(node, full_new_path)
|
||||||
|
if not M.config.filesystem_watchers.enable then
|
||||||
|
explorer:reload_explorer()
|
||||||
|
end
|
||||||
|
|
||||||
|
find_file(utils.path_remove_trailing(full_new_path))
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.config.filesystem_watchers = opts.filesystem_watchers
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
128
lua/nvim-tree/actions/fs/trash.lua
Normal file
128
lua/nvim-tree/actions/fs/trash.lua
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
local core = require("nvim-tree.core")
|
||||||
|
local lib = require("nvim-tree.lib")
|
||||||
|
local notify = require("nvim-tree.notify")
|
||||||
|
|
||||||
|
local DirectoryLinkNode = require("nvim-tree.node.directory-link")
|
||||||
|
local DirectoryNode = require("nvim-tree.node.directory")
|
||||||
|
|
||||||
|
local M = {
|
||||||
|
config = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
local utils = require("nvim-tree.utils")
|
||||||
|
local events = require("nvim-tree.events")
|
||||||
|
|
||||||
|
---@param absolute_path string
|
||||||
|
local function clear_buffer(absolute_path)
|
||||||
|
local bufs = vim.fn.getbufinfo({ bufloaded = 1, buflisted = 1 })
|
||||||
|
for _, buf in pairs(bufs) do
|
||||||
|
if buf.name == absolute_path then
|
||||||
|
if buf.hidden == 0 and #bufs > 1 then
|
||||||
|
local winnr = vim.api.nvim_get_current_win()
|
||||||
|
vim.api.nvim_set_current_win(buf.windows[1])
|
||||||
|
vim.cmd(":bn")
|
||||||
|
vim.api.nvim_set_current_win(winnr)
|
||||||
|
end
|
||||||
|
vim.api.nvim_buf_delete(buf.bufnr, {})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
function M.remove(node)
|
||||||
|
local binary = M.config.trash.cmd:gsub(" .*$", "")
|
||||||
|
if vim.fn.executable(binary) == 0 then
|
||||||
|
notify.warn(string.format("trash.cmd '%s' is not an executable.", M.config.trash.cmd))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local err_msg = ""
|
||||||
|
local function on_stderr(_, data)
|
||||||
|
err_msg = err_msg .. (data and table.concat(data, " "))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- trashes a path (file or folder)
|
||||||
|
local function trash_path(on_exit)
|
||||||
|
local need_sync_wait = utils.is_windows
|
||||||
|
local job = vim.fn.jobstart(M.config.trash.cmd .. " " .. vim.fn.shellescape(node.absolute_path), {
|
||||||
|
detach = not need_sync_wait,
|
||||||
|
on_exit = on_exit,
|
||||||
|
on_stderr = on_stderr,
|
||||||
|
})
|
||||||
|
if need_sync_wait then
|
||||||
|
vim.fn.jobwait({ job })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local explorer = core.get_explorer()
|
||||||
|
|
||||||
|
if node:is(DirectoryNode) and not node:is(DirectoryLinkNode) then
|
||||||
|
trash_path(function(_, rc)
|
||||||
|
if rc ~= 0 then
|
||||||
|
notify.warn("trash failed: " .. err_msg .. "; please see :help nvim-tree.trash")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
events._dispatch_folder_removed(node.absolute_path)
|
||||||
|
if not M.config.filesystem_watchers.enable and explorer then
|
||||||
|
explorer:reload_explorer()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
events._dispatch_will_remove_file(node.absolute_path)
|
||||||
|
trash_path(function(_, rc)
|
||||||
|
if rc ~= 0 then
|
||||||
|
notify.warn("trash failed: " .. err_msg .. "; please see :help nvim-tree.trash")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
events._dispatch_file_removed(node.absolute_path)
|
||||||
|
clear_buffer(node.absolute_path)
|
||||||
|
if not M.config.filesystem_watchers.enable and explorer then
|
||||||
|
explorer:reload_explorer()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
function M.fn(node)
|
||||||
|
if node.name == ".." then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local function do_trash()
|
||||||
|
M.remove(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
if M.config.ui.confirm.trash then
|
||||||
|
local prompt_select = "Trash " .. node.name .. "?"
|
||||||
|
local prompt_input, items_short, items_long
|
||||||
|
|
||||||
|
if M.config.ui.confirm.default_yes then
|
||||||
|
prompt_input = prompt_select .. " Y/n: "
|
||||||
|
items_short = { "", "n" }
|
||||||
|
items_long = { "Yes", "No" }
|
||||||
|
else
|
||||||
|
prompt_input = prompt_select .. " y/N: "
|
||||||
|
items_short = { "", "y" }
|
||||||
|
items_long = { "No", "Yes" }
|
||||||
|
end
|
||||||
|
|
||||||
|
lib.prompt(prompt_input, prompt_select, items_short, items_long, "nvimtree_trash", function(item_short)
|
||||||
|
utils.clear_prompt()
|
||||||
|
if item_short == "y" or item_short == (M.config.ui.confirm.default_yes and "") then
|
||||||
|
do_trash()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
do_trash()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.config.ui = opts.ui
|
||||||
|
M.config.trash = opts.trash
|
||||||
|
M.config.filesystem_watchers = opts.filesystem_watchers
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -1,243 +1,17 @@
|
|||||||
local a = vim.api
|
local M = {}
|
||||||
|
|
||||||
local lib = require "nvim-tree.lib"
|
M.finders = require("nvim-tree.actions.finders")
|
||||||
local log = require "nvim-tree.log"
|
M.fs = require("nvim-tree.actions.fs")
|
||||||
local view = require "nvim-tree.view"
|
M.moves = require("nvim-tree.actions.moves")
|
||||||
local util = require "nvim-tree.utils"
|
M.node = require("nvim-tree.actions.node")
|
||||||
local nvim_tree_callback = require("nvim-tree.config").nvim_tree_callback
|
M.root = require("nvim-tree.actions.root")
|
||||||
|
M.tree = require("nvim-tree.actions.tree")
|
||||||
local M = {
|
|
||||||
mappings = {
|
|
||||||
{ key = { "<CR>", "o", "<2-LeftMouse>" }, action = "edit" },
|
|
||||||
{ key = "<C-e>", action = "edit_in_place" },
|
|
||||||
{ key = "O", action = "edit_no_picker" },
|
|
||||||
{ key = { "<2-RightMouse>", "<C-]>" }, action = "cd" },
|
|
||||||
{ key = "<C-v>", action = "vsplit" },
|
|
||||||
{ key = "<C-x>", action = "split" },
|
|
||||||
{ key = "<C-t>", action = "tabnew" },
|
|
||||||
{ key = "<", action = "prev_sibling" },
|
|
||||||
{ key = ">", action = "next_sibling" },
|
|
||||||
{ key = "P", action = "parent_node" },
|
|
||||||
{ key = "<BS>", action = "close_node" },
|
|
||||||
{ key = "<Tab>", action = "preview" },
|
|
||||||
{ key = "K", action = "first_sibling" },
|
|
||||||
{ key = "J", action = "last_sibling" },
|
|
||||||
{ key = "I", action = "toggle_git_ignored" },
|
|
||||||
{ key = "H", action = "toggle_dotfiles" },
|
|
||||||
{ key = "R", action = "refresh" },
|
|
||||||
{ key = "a", action = "create" },
|
|
||||||
{ key = "d", action = "remove" },
|
|
||||||
{ key = "D", action = "trash" },
|
|
||||||
{ key = "r", action = "rename" },
|
|
||||||
{ key = "<C-r>", action = "full_rename" },
|
|
||||||
{ key = "x", action = "cut" },
|
|
||||||
{ key = "c", action = "copy" },
|
|
||||||
{ key = "p", action = "paste" },
|
|
||||||
{ key = "y", action = "copy_name" },
|
|
||||||
{ key = "Y", action = "copy_path" },
|
|
||||||
{ key = "gy", action = "copy_absolute_path" },
|
|
||||||
{ key = "[c", action = "prev_git_item" },
|
|
||||||
{ key = "]c", action = "next_git_item" },
|
|
||||||
{ key = "-", action = "dir_up" },
|
|
||||||
{ key = "s", action = "system_open" },
|
|
||||||
{ key = "q", action = "close" },
|
|
||||||
{ key = "g?", action = "toggle_help" },
|
|
||||||
{ key = "W", action = "collapse_all" },
|
|
||||||
{ key = "S", action = "search_node" },
|
|
||||||
{ key = ".", action = "run_file_command" },
|
|
||||||
{ key = "<C-k>", action = "toggle_file_info" },
|
|
||||||
{ key = "U", action = "toggle_custom" },
|
|
||||||
},
|
|
||||||
custom_keypress_funcs = {},
|
|
||||||
}
|
|
||||||
|
|
||||||
local keypress_funcs = {
|
|
||||||
close = view.close,
|
|
||||||
close_node = require("nvim-tree.actions.movements").parent_node(true),
|
|
||||||
collapse_all = require("nvim-tree.actions.collapse-all").fn,
|
|
||||||
copy_absolute_path = require("nvim-tree.actions.copy-paste").copy_absolute_path,
|
|
||||||
copy_name = require("nvim-tree.actions.copy-paste").copy_filename,
|
|
||||||
copy_path = require("nvim-tree.actions.copy-paste").copy_path,
|
|
||||||
copy = require("nvim-tree.actions.copy-paste").copy,
|
|
||||||
create = require("nvim-tree.actions.create-file").fn,
|
|
||||||
cut = require("nvim-tree.actions.copy-paste").cut,
|
|
||||||
dir_up = require("nvim-tree.actions.dir-up").fn,
|
|
||||||
first_sibling = require("nvim-tree.actions.movements").sibling(-math.huge),
|
|
||||||
full_rename = require("nvim-tree.actions.rename-file").fn(true),
|
|
||||||
last_sibling = require("nvim-tree.actions.movements").sibling(math.huge),
|
|
||||||
next_git_item = require("nvim-tree.actions.movements").find_git_item "next",
|
|
||||||
next_sibling = require("nvim-tree.actions.movements").sibling(1),
|
|
||||||
parent_node = require("nvim-tree.actions.movements").parent_node(false),
|
|
||||||
paste = require("nvim-tree.actions.copy-paste").paste,
|
|
||||||
prev_git_item = require("nvim-tree.actions.movements").find_git_item "prev",
|
|
||||||
prev_sibling = require("nvim-tree.actions.movements").sibling(-1),
|
|
||||||
refresh = require("nvim-tree.actions.reloaders").reload_explorer,
|
|
||||||
remove = require("nvim-tree.actions.remove-file").fn,
|
|
||||||
rename = require("nvim-tree.actions.rename-file").fn(false),
|
|
||||||
run_file_command = require("nvim-tree.actions.run-command").run_file_command,
|
|
||||||
search_node = require("nvim-tree.actions.search-node").fn,
|
|
||||||
toggle_file_info = require("nvim-tree.actions.file-popup").toggle_file_info,
|
|
||||||
system_open = require("nvim-tree.actions.system-open").fn,
|
|
||||||
toggle_dotfiles = require("nvim-tree.actions.toggles").dotfiles,
|
|
||||||
toggle_help = require("nvim-tree.actions.toggles").help,
|
|
||||||
toggle_custom = require("nvim-tree.actions.toggles").custom,
|
|
||||||
toggle_git_ignored = require("nvim-tree.actions.toggles").git_ignored,
|
|
||||||
trash = require("nvim-tree.actions.trash").fn,
|
|
||||||
}
|
|
||||||
|
|
||||||
function M.on_keypress(action)
|
|
||||||
if view.is_help_ui() and action == "close" then
|
|
||||||
action = "toggle_help"
|
|
||||||
end
|
|
||||||
if view.is_help_ui() and action ~= "toggle_help" then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local node = lib.get_node_at_cursor()
|
|
||||||
if not node then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local custom_function = M.custom_keypress_funcs[action]
|
|
||||||
local default_function = keypress_funcs[action]
|
|
||||||
|
|
||||||
if type(custom_function) == "function" then
|
|
||||||
return custom_function(node)
|
|
||||||
elseif default_function then
|
|
||||||
return default_function(node)
|
|
||||||
end
|
|
||||||
|
|
||||||
if action == "preview" then
|
|
||||||
if node.name == ".." then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if not node.nodes then
|
|
||||||
return require("nvim-tree.actions.open-file").fn("preview", node.absolute_path)
|
|
||||||
end
|
|
||||||
elseif node.name == ".." then
|
|
||||||
return require("nvim-tree.actions.change-dir").fn ".."
|
|
||||||
elseif action == "cd" then
|
|
||||||
if node.nodes ~= nil then
|
|
||||||
require("nvim-tree.actions.change-dir").fn(lib.get_last_group_node(node).absolute_path)
|
|
||||||
end
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if node.link_to and not node.nodes then
|
|
||||||
require("nvim-tree.actions.open-file").fn(action, node.link_to)
|
|
||||||
elseif node.nodes ~= nil then
|
|
||||||
lib.expand_or_collapse(node)
|
|
||||||
else
|
|
||||||
require("nvim-tree.actions.open-file").fn(action, node.absolute_path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.apply_mappings(bufnr)
|
|
||||||
for _, b in pairs(M.mappings) do
|
|
||||||
local mapping_rhs = b.cb or nvim_tree_callback(b.action)
|
|
||||||
if type(b.key) == "table" then
|
|
||||||
for _, key in pairs(b.key) do
|
|
||||||
a.nvim_buf_set_keymap(bufnr, b.mode or "n", key, mapping_rhs, { noremap = true, silent = true, nowait = true })
|
|
||||||
end
|
|
||||||
elseif mapping_rhs then
|
|
||||||
a.nvim_buf_set_keymap(bufnr, b.mode or "n", b.key, mapping_rhs, { noremap = true, silent = true, nowait = true })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function merge_mappings(user_mappings)
|
|
||||||
if #user_mappings == 0 then
|
|
||||||
return M.mappings
|
|
||||||
end
|
|
||||||
|
|
||||||
local function is_empty(s)
|
|
||||||
return s == ""
|
|
||||||
end
|
|
||||||
|
|
||||||
local user_keys = {}
|
|
||||||
local removed_keys = {}
|
|
||||||
-- remove default mappings if action is a empty string
|
|
||||||
for _, map in pairs(user_mappings) do
|
|
||||||
if type(map.key) == "table" then
|
|
||||||
for _, key in pairs(map.key) do
|
|
||||||
table.insert(user_keys, key)
|
|
||||||
if is_empty(map.action) then
|
|
||||||
table.insert(removed_keys, key)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
table.insert(user_keys, map.key)
|
|
||||||
if is_empty(map.action) then
|
|
||||||
table.insert(removed_keys, map.key)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if map.action and type(map.action_cb) == "function" then
|
|
||||||
if not is_empty(map.action) then
|
|
||||||
M.custom_keypress_funcs[map.action] = map.action_cb
|
|
||||||
else
|
|
||||||
util.warn "action can't be empty if action_cb provided"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local default_map = vim.tbl_filter(function(map)
|
|
||||||
if type(map.key) == "table" then
|
|
||||||
local filtered_keys = {}
|
|
||||||
for _, key in pairs(map.key) do
|
|
||||||
if not vim.tbl_contains(user_keys, key) and not vim.tbl_contains(removed_keys, key) then
|
|
||||||
table.insert(filtered_keys, key)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
map.key = filtered_keys
|
|
||||||
return not vim.tbl_isempty(map.key)
|
|
||||||
else
|
|
||||||
return not vim.tbl_contains(user_keys, map.key) and not vim.tbl_contains(removed_keys, map.key)
|
|
||||||
end
|
|
||||||
end, M.mappings)
|
|
||||||
|
|
||||||
local user_map = vim.tbl_filter(function(map)
|
|
||||||
return not is_empty(map.action)
|
|
||||||
end, user_mappings)
|
|
||||||
|
|
||||||
return vim.fn.extend(default_map, user_map)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function copy_mappings(user_mappings)
|
|
||||||
if #user_mappings == 0 then
|
|
||||||
return M.mappings
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, map in pairs(user_mappings) do
|
|
||||||
if map.action and type(map.action_cb) == "function" then
|
|
||||||
M.custom_keypress_funcs[map.action] = map.action_cb
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return user_mappings
|
|
||||||
end
|
|
||||||
|
|
||||||
local DEFAULT_MAPPING_CONFIG = {
|
|
||||||
custom_only = false,
|
|
||||||
list = {},
|
|
||||||
}
|
|
||||||
|
|
||||||
function M.setup(opts)
|
function M.setup(opts)
|
||||||
require("nvim-tree.actions.system-open").setup(opts.system_open)
|
M.fs.setup(opts)
|
||||||
require("nvim-tree.actions.trash").setup(opts.trash)
|
M.node.setup(opts)
|
||||||
require("nvim-tree.actions.open-file").setup(opts)
|
M.root.setup(opts)
|
||||||
require("nvim-tree.actions.change-dir").setup(opts)
|
M.tree.setup(opts)
|
||||||
require("nvim-tree.actions.copy-paste").setup(opts)
|
|
||||||
|
|
||||||
local user_map_config = (opts.view or {}).mappings or {}
|
|
||||||
local options = vim.tbl_deep_extend("force", DEFAULT_MAPPING_CONFIG, user_map_config)
|
|
||||||
if options.custom_only then
|
|
||||||
M.mappings = copy_mappings(options.list)
|
|
||||||
else
|
|
||||||
M.mappings = merge_mappings(options.list)
|
|
||||||
end
|
|
||||||
|
|
||||||
log.line("config", "active mappings")
|
|
||||||
log.raw("config", "%s\n", vim.inspect(M.mappings))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -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
|
|
||||||
7
lua/nvim-tree/actions/moves/init.lua
Normal file
7
lua/nvim-tree/actions/moves/init.lua
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
M.item = require("nvim-tree.actions.moves.item")
|
||||||
|
M.parent = require("nvim-tree.actions.moves.parent")
|
||||||
|
M.sibling = require("nvim-tree.actions.moves.sibling")
|
||||||
|
|
||||||
|
return M
|
||||||
247
lua/nvim-tree/actions/moves/item.lua
Normal file
247
lua/nvim-tree/actions/moves/item.lua
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
local utils = require("nvim-tree.utils")
|
||||||
|
local view = require("nvim-tree.view")
|
||||||
|
local core = require("nvim-tree.core")
|
||||||
|
local diagnostics = require("nvim-tree.diagnostics")
|
||||||
|
|
||||||
|
local FileNode = require("nvim-tree.node.file")
|
||||||
|
local DirectoryNode = require("nvim-tree.node.directory")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
local MAX_DEPTH = 100
|
||||||
|
|
||||||
|
---Return the status of the node or nil if no status, depending on the type of
|
||||||
|
---status.
|
||||||
|
---@param node Node to inspect
|
||||||
|
---@param what string? type of status
|
||||||
|
---@param skip_gitignored boolean? default false
|
||||||
|
---@return boolean
|
||||||
|
local function status_is_valid(node, what, skip_gitignored)
|
||||||
|
if what == "git" then
|
||||||
|
local git_xy = node:get_git_xy()
|
||||||
|
return git_xy ~= nil and (not skip_gitignored or git_xy[1] ~= "!!")
|
||||||
|
elseif what == "diag" then
|
||||||
|
local diag_status = diagnostics.get_diag_status(node)
|
||||||
|
return diag_status ~= nil and diag_status.value ~= nil
|
||||||
|
elseif what == "opened" then
|
||||||
|
return vim.fn.bufloaded(node.absolute_path) ~= 0
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
---Move to the next node that has a valid status. If none found, don't move.
|
||||||
|
---@param explorer Explorer
|
||||||
|
---@param where string? where to move (forwards or backwards)
|
||||||
|
---@param what string? type of status
|
||||||
|
---@param skip_gitignored boolean? default false
|
||||||
|
local function move(explorer, where, what, skip_gitignored)
|
||||||
|
local first_node_line = core.get_nodes_starting_line()
|
||||||
|
local nodes_by_line = utils.get_nodes_by_line(explorer.nodes, first_node_line)
|
||||||
|
local iter_start, iter_end, iter_step, cur, first, nex
|
||||||
|
|
||||||
|
local cursor = explorer:get_cursor_position()
|
||||||
|
if cursor and cursor[1] < first_node_line then
|
||||||
|
cur = cursor[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
if where == "next" then
|
||||||
|
iter_start, iter_end, iter_step = first_node_line, #nodes_by_line, 1
|
||||||
|
elseif where == "prev" then
|
||||||
|
iter_start, iter_end, iter_step = #nodes_by_line, first_node_line, -1
|
||||||
|
end
|
||||||
|
|
||||||
|
for line = iter_start, iter_end, iter_step do
|
||||||
|
local node = nodes_by_line[line]
|
||||||
|
local valid = status_is_valid(node, what, skip_gitignored)
|
||||||
|
|
||||||
|
if not first and valid then
|
||||||
|
first = line
|
||||||
|
end
|
||||||
|
|
||||||
|
if cursor and line == cursor[1] then
|
||||||
|
cur = line
|
||||||
|
elseif valid and cur then
|
||||||
|
nex = line
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if nex then
|
||||||
|
view.set_cursor({ nex, 0 })
|
||||||
|
elseif vim.o.wrapscan and first then
|
||||||
|
view.set_cursor({ first, 0 })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node DirectoryNode
|
||||||
|
local function expand_node(node)
|
||||||
|
if not node.open then
|
||||||
|
-- Expand the node.
|
||||||
|
-- Should never collapse since we checked open.
|
||||||
|
node:expand_or_collapse(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Move to the next node recursively.
|
||||||
|
---@param explorer Explorer
|
||||||
|
---@param what string? type of status
|
||||||
|
---@param skip_gitignored? boolean default false
|
||||||
|
local function move_next_recursive(explorer, what, skip_gitignored)
|
||||||
|
-- If the current node:
|
||||||
|
-- * is a directory
|
||||||
|
-- * and is not the root node
|
||||||
|
-- * and has a git/diag status
|
||||||
|
-- * and is not opened
|
||||||
|
-- expand it.
|
||||||
|
local node_init = explorer:get_node_at_cursor()
|
||||||
|
if not node_init then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local valid = false
|
||||||
|
if node_init.name ~= ".." then -- root node cannot have a status
|
||||||
|
valid = status_is_valid(node_init, what, skip_gitignored)
|
||||||
|
end
|
||||||
|
local node_dir = node_init:as(DirectoryNode)
|
||||||
|
if node_dir and valid and not node_dir.open then
|
||||||
|
node_dir:expand_or_collapse(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
move(explorer, "next", what, skip_gitignored)
|
||||||
|
|
||||||
|
local node_cur = explorer:get_node_at_cursor()
|
||||||
|
if not node_cur then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If we haven't moved at all at this point, return.
|
||||||
|
if node_init == node_cur then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- i is used to limit iterations.
|
||||||
|
local i = 0
|
||||||
|
local dir_cur = node_cur:as(DirectoryNode)
|
||||||
|
while dir_cur and i < MAX_DEPTH do
|
||||||
|
expand_node(dir_cur)
|
||||||
|
|
||||||
|
move(explorer, "next", what, skip_gitignored)
|
||||||
|
|
||||||
|
-- Save current node.
|
||||||
|
node_cur = explorer:get_node_at_cursor()
|
||||||
|
dir_cur = node_cur and node_cur:as(DirectoryNode)
|
||||||
|
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Move to the previous node recursively.
|
||||||
|
---
|
||||||
|
--- move_prev_recursive:
|
||||||
|
---
|
||||||
|
--- 1) Save current as node_init.
|
||||||
|
-- 2) Call a non-recursive prev.
|
||||||
|
--- 3) If current node is node_init's parent, call move_prev_recursive.
|
||||||
|
--- 4) Else:
|
||||||
|
--- 4.1) If current node is nil, is node_init (we didn't move), or is a file, return.
|
||||||
|
--- 4.2) The current file is a directory, expand it.
|
||||||
|
--- 4.3) Find node_init in current window, and move to it (if not found, return).
|
||||||
|
--- If node_init is the root node (name = ".."), directly move to position 1.
|
||||||
|
--- 4.4) Call a non-recursive prev.
|
||||||
|
--- 4.5) Save the current node and start back from 4.1.
|
||||||
|
---
|
||||||
|
---@param explorer Explorer
|
||||||
|
---@param what string? type of status
|
||||||
|
---@param skip_gitignored boolean? default false
|
||||||
|
local function move_prev_recursive(explorer, what, skip_gitignored)
|
||||||
|
local node_init, node_cur
|
||||||
|
|
||||||
|
-- 1)
|
||||||
|
node_init = explorer:get_node_at_cursor()
|
||||||
|
if node_init == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 2)
|
||||||
|
move(explorer, "prev", what, skip_gitignored)
|
||||||
|
|
||||||
|
node_cur = explorer:get_node_at_cursor()
|
||||||
|
if node_cur == node_init.parent then
|
||||||
|
-- 3)
|
||||||
|
move_prev_recursive(explorer, what, skip_gitignored)
|
||||||
|
else
|
||||||
|
-- i is used to limit iterations.
|
||||||
|
local i = 0
|
||||||
|
while i < MAX_DEPTH do
|
||||||
|
-- 4.1)
|
||||||
|
if
|
||||||
|
node_cur == nil
|
||||||
|
or node_cur == node_init -- we didn't move
|
||||||
|
or node_cur:is(FileNode) -- node is a file
|
||||||
|
then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 4.2)
|
||||||
|
local node_dir = node_cur:as(DirectoryNode)
|
||||||
|
if node_dir then
|
||||||
|
expand_node(node_dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 4.3)
|
||||||
|
if node_init.name == ".." then -- root node
|
||||||
|
view.set_cursor({ 1, 0 }) -- move to root node (position 1)
|
||||||
|
else
|
||||||
|
local node_init_line = utils.find_node_line(node_init)
|
||||||
|
if node_init_line < 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
view.set_cursor({ node_init_line, 0 })
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 4.4)
|
||||||
|
move(explorer, "prev", what, skip_gitignored)
|
||||||
|
|
||||||
|
-- 4.5)
|
||||||
|
node_cur = explorer:get_node_at_cursor()
|
||||||
|
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@class NavigationItemOpts
|
||||||
|
---@field where string?
|
||||||
|
---@field what string?
|
||||||
|
---@field skip_gitignored boolean?
|
||||||
|
---@field recurse boolean?
|
||||||
|
|
||||||
|
---@param opts NavigationItemOpts
|
||||||
|
---@return fun()
|
||||||
|
function M.fn(opts)
|
||||||
|
return function()
|
||||||
|
local explorer = core.get_explorer()
|
||||||
|
if not explorer then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local recurse = false
|
||||||
|
|
||||||
|
-- recurse only valid for git and diag moves.
|
||||||
|
if (opts.what == "git" or opts.what == "diag") and opts.recurse ~= nil then
|
||||||
|
recurse = opts.recurse
|
||||||
|
end
|
||||||
|
|
||||||
|
if not recurse then
|
||||||
|
move(explorer, opts.where, opts.what, opts.skip_gitignored)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.where == "next" then
|
||||||
|
move_next_recursive(explorer, opts.what, opts.skip_gitignored)
|
||||||
|
elseif opts.where == "prev" then
|
||||||
|
move_prev_recursive(explorer, opts.what, opts.skip_gitignored)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
44
lua/nvim-tree/actions/moves/parent.lua
Normal file
44
lua/nvim-tree/actions/moves/parent.lua
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
local view = require("nvim-tree.view")
|
||||||
|
local utils = require("nvim-tree.utils")
|
||||||
|
|
||||||
|
local DirectoryNode = require("nvim-tree.node.directory")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@param should_close boolean|nil
|
||||||
|
---@return fun(node: Node): boolean|nil
|
||||||
|
function M.fn(should_close)
|
||||||
|
should_close = should_close or false
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
return function(node)
|
||||||
|
local dir = node:as(DirectoryNode)
|
||||||
|
if dir then
|
||||||
|
dir = dir:last_group_node()
|
||||||
|
if should_close and dir.open then
|
||||||
|
dir.open = false
|
||||||
|
dir.explorer.renderer:draw()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local parent = (node:get_parent_of_group() or node).parent
|
||||||
|
|
||||||
|
if not parent or not parent.parent then
|
||||||
|
view.set_cursor({ 1, 0 })
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local _, line = utils.find_node(parent.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
|
||||||
|
parent.explorer.renderer:draw()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
53
lua/nvim-tree/actions/moves/sibling.lua
Normal file
53
lua/nvim-tree/actions/moves/sibling.lua
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
local utils = require("nvim-tree.utils")
|
||||||
|
local core = require("nvim-tree.core")
|
||||||
|
local Iterator = require("nvim-tree.iterators.node-iterator")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@param direction string
|
||||||
|
---@return fun(node: Node): nil
|
||||||
|
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 and parent.nodes or {})
|
||||||
|
:recursor(function()
|
||||||
|
return nil
|
||||||
|
end)
|
||||||
|
:applier(function(n)
|
||||||
|
first = first or n
|
||||||
|
last = n
|
||||||
|
if n.absolute_path == node.absolute_path then
|
||||||
|
found = true
|
||||||
|
return
|
||||||
|
end
|
||||||
|
prev = not found and n or prev
|
||||||
|
if found and not next then
|
||||||
|
next = n
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
:iterate()
|
||||||
|
|
||||||
|
local target_node
|
||||||
|
if direction == "first" then
|
||||||
|
target_node = first
|
||||||
|
elseif direction == "last" then
|
||||||
|
target_node = last
|
||||||
|
elseif direction == "next" then
|
||||||
|
target_node = next or first
|
||||||
|
else
|
||||||
|
target_node = prev or last
|
||||||
|
end
|
||||||
|
|
||||||
|
if target_node then
|
||||||
|
utils.focus_file(target_node.absolute_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -1,10 +1,19 @@
|
|||||||
local utils = require "nvim-tree.utils"
|
local utils = require("nvim-tree.utils")
|
||||||
local a = vim.api
|
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
---@return table
|
||||||
local function get_formatted_lines(node)
|
local function get_formatted_lines(node)
|
||||||
local stats = node.fs_stat
|
local stats = node.fs_stat
|
||||||
|
if stats == nil then
|
||||||
|
return {
|
||||||
|
"",
|
||||||
|
" Can't retrieve file information",
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
local fpath = " fullpath: " .. node.absolute_path
|
local fpath = " fullpath: " .. node.absolute_path
|
||||||
local created_at = " created: " .. os.date("%x %X", stats.birthtime.sec)
|
local created_at = " created: " .. os.date("%x %X", stats.birthtime.sec)
|
||||||
local modified_at = " modified: " .. os.date("%x %X", stats.mtime.sec)
|
local modified_at = " modified: " .. os.date("%x %X", stats.mtime.sec)
|
||||||
@@ -22,40 +31,39 @@ end
|
|||||||
|
|
||||||
local current_popup = nil
|
local current_popup = nil
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
local function setup_window(node)
|
local function setup_window(node)
|
||||||
local lines = get_formatted_lines(node)
|
local lines = get_formatted_lines(node)
|
||||||
|
|
||||||
local max_width = vim.fn.max(vim.tbl_map(function(n)
|
local max_width = vim.fn.max(vim.tbl_map(function(n)
|
||||||
return #n
|
return #n
|
||||||
end, lines))
|
end, lines))
|
||||||
local winnr = a.nvim_open_win(0, false, {
|
local open_win_config = vim.tbl_extend("force", M.open_win_config, {
|
||||||
col = 1,
|
|
||||||
row = 1,
|
|
||||||
relative = "cursor",
|
|
||||||
width = max_width + 1,
|
width = max_width + 1,
|
||||||
height = #lines,
|
height = #lines,
|
||||||
border = "shadow",
|
|
||||||
noautocmd = true,
|
noautocmd = true,
|
||||||
style = "minimal",
|
zindex = 60,
|
||||||
})
|
})
|
||||||
|
local winnr = vim.api.nvim_open_win(0, false, open_win_config)
|
||||||
current_popup = {
|
current_popup = {
|
||||||
winnr = winnr,
|
winnr = winnr,
|
||||||
file_path = node.absolute_path,
|
file_path = node.absolute_path,
|
||||||
}
|
}
|
||||||
local bufnr = a.nvim_create_buf(false, true)
|
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
a.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
||||||
a.nvim_win_set_buf(winnr, bufnr)
|
vim.api.nvim_win_set_buf(winnr, bufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.close_popup()
|
function M.close_popup()
|
||||||
if current_popup ~= nil then
|
if current_popup ~= nil then
|
||||||
a.nvim_win_close(current_popup.winnr, { force = true })
|
vim.api.nvim_win_close(current_popup.winnr, true)
|
||||||
vim.cmd "augroup NvimTreeRemoveFilePopup | au! CursorMoved | augroup END"
|
vim.cmd("augroup NvimTreeRemoveFilePopup | au! CursorMoved | augroup END")
|
||||||
|
|
||||||
current_popup = nil
|
current_popup = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
function M.toggle_file_info(node)
|
function M.toggle_file_info(node)
|
||||||
if node.name == ".." then
|
if node.name == ".." then
|
||||||
return
|
return
|
||||||
@@ -72,11 +80,14 @@ function M.toggle_file_info(node)
|
|||||||
|
|
||||||
setup_window(node)
|
setup_window(node)
|
||||||
|
|
||||||
vim.cmd [[
|
vim.api.nvim_create_autocmd("CursorMoved", {
|
||||||
augroup NvimTreeRemoveFilePopup
|
group = vim.api.nvim_create_augroup("NvimTreeRemoveFilePopup", {}),
|
||||||
au CursorMoved * lua require'nvim-tree.actions.file-popup'.close_popup()
|
callback = M.close_popup,
|
||||||
augroup END
|
})
|
||||||
]]
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.open_win_config = opts.actions.file_popup.open_win_config
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
14
lua/nvim-tree/actions/node/init.lua
Normal file
14
lua/nvim-tree/actions/node/init.lua
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
M.file_popup = require("nvim-tree.actions.node.file-popup")
|
||||||
|
M.open_file = require("nvim-tree.actions.node.open-file")
|
||||||
|
M.run_command = require("nvim-tree.actions.node.run-command")
|
||||||
|
M.system_open = require("nvim-tree.actions.node.system-open")
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
require("nvim-tree.actions.node.system-open").setup(opts)
|
||||||
|
require("nvim-tree.actions.node.file-popup").setup(opts)
|
||||||
|
require("nvim-tree.actions.node.open-file").setup(opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
431
lua/nvim-tree/actions/node/open-file.lua
Normal file
431
lua/nvim-tree/actions/node/open-file.lua
Normal file
@@ -0,0 +1,431 @@
|
|||||||
|
-- Copyright 2019 Yazdani Kiyan under MIT License
|
||||||
|
local lib = require("nvim-tree.lib")
|
||||||
|
local notify = require("nvim-tree.notify")
|
||||||
|
local utils = require("nvim-tree.utils")
|
||||||
|
local view = require("nvim-tree.view")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---Get single char from user input
|
||||||
|
---@return string
|
||||||
|
local function get_user_input_char()
|
||||||
|
local c = vim.fn.getchar()
|
||||||
|
while type(c) ~= "number" do
|
||||||
|
c = vim.fn.getchar()
|
||||||
|
end
|
||||||
|
return vim.fn.nr2char(c)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Get all windows in the current tabpage that aren't NvimTree.
|
||||||
|
---@return table with valid win_ids
|
||||||
|
local function usable_win_ids()
|
||||||
|
local tabpage = vim.api.nvim_get_current_tabpage()
|
||||||
|
local win_ids = vim.api.nvim_tabpage_list_wins(tabpage)
|
||||||
|
local tree_winid = view.get_winnr(tabpage)
|
||||||
|
|
||||||
|
return vim.tbl_filter(function(id)
|
||||||
|
local bufid = vim.api.nvim_win_get_buf(id)
|
||||||
|
for option, v in pairs(M.window_picker.exclude) do
|
||||||
|
local ok, option_value
|
||||||
|
if vim.fn.has("nvim-0.10") == 1 then
|
||||||
|
ok, option_value = pcall(vim.api.nvim_get_option_value, option, { buf = bufid })
|
||||||
|
else
|
||||||
|
ok, option_value = pcall(vim.api.nvim_buf_get_option, bufid, option) ---@diagnostic disable-line: deprecated
|
||||||
|
end
|
||||||
|
|
||||||
|
if ok and vim.tbl_contains(v, option_value) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local win_config = vim.api.nvim_win_get_config(id)
|
||||||
|
return id ~= tree_winid and win_config.focusable and not win_config.external or false
|
||||||
|
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
|
||||||
|
|
||||||
|
if #M.window_picker.chars < #selectable then
|
||||||
|
notify.error(string.format("More windows (%d) than actions.open_file.window_picker.chars (%d).", #selectable, #M.window_picker.chars))
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local i = 1
|
||||||
|
local win_opts_selectable = {}
|
||||||
|
local win_opts_unselectable = {}
|
||||||
|
local win_map = {}
|
||||||
|
local laststatus = vim.o.laststatus
|
||||||
|
vim.o.laststatus = 2
|
||||||
|
|
||||||
|
local tabpage = vim.api.nvim_get_current_tabpage()
|
||||||
|
local win_ids = vim.api.nvim_tabpage_list_wins(tabpage)
|
||||||
|
|
||||||
|
local not_selectable = vim.tbl_filter(function(id)
|
||||||
|
return not vim.tbl_contains(selectable, id)
|
||||||
|
end, win_ids)
|
||||||
|
|
||||||
|
if laststatus == 3 then
|
||||||
|
for _, win_id in ipairs(not_selectable) do
|
||||||
|
local ok_status, statusline
|
||||||
|
|
||||||
|
if vim.fn.has("nvim-0.10") == 1 then
|
||||||
|
ok_status, statusline = pcall(vim.api.nvim_get_option_value, "statusline", { win = win_id })
|
||||||
|
else
|
||||||
|
ok_status, statusline = pcall(vim.api.nvim_win_get_option, win_id, "statusline") ---@diagnostic disable-line: deprecated
|
||||||
|
end
|
||||||
|
|
||||||
|
win_opts_unselectable[win_id] = {
|
||||||
|
statusline = ok_status and statusline or "",
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Clear statusline for windows not selectable
|
||||||
|
if vim.fn.has("nvim-0.10") == 1 then
|
||||||
|
vim.api.nvim_set_option_value("statusline", " ", { win = win_id })
|
||||||
|
else
|
||||||
|
vim.api.nvim_win_set_option(win_id, "statusline", " ") ---@diagnostic disable-line: deprecated
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Setup UI
|
||||||
|
for _, id in ipairs(selectable) do
|
||||||
|
local char = M.window_picker.chars:sub(i, i)
|
||||||
|
|
||||||
|
local ok_status, statusline, ok_hl, winhl
|
||||||
|
if vim.fn.has("nvim-0.10") == 1 then
|
||||||
|
ok_status, statusline = pcall(vim.api.nvim_get_option_value, "statusline", { win = id })
|
||||||
|
ok_hl, winhl = pcall(vim.api.nvim_get_option_value, "winhl", { win = id })
|
||||||
|
else
|
||||||
|
ok_status, statusline = pcall(vim.api.nvim_win_get_option, id, "statusline") ---@diagnostic disable-line: deprecated
|
||||||
|
ok_hl, winhl = pcall(vim.api.nvim_win_get_option, id, "winhl") ---@diagnostic disable-line: deprecated
|
||||||
|
end
|
||||||
|
|
||||||
|
win_opts_selectable[id] = {
|
||||||
|
statusline = ok_status and statusline or "",
|
||||||
|
winhl = ok_hl and winhl or "",
|
||||||
|
}
|
||||||
|
win_map[char] = id
|
||||||
|
|
||||||
|
if vim.fn.has("nvim-0.10") == 1 then
|
||||||
|
vim.api.nvim_set_option_value("statusline", "%=" .. char .. "%=", { win = id })
|
||||||
|
vim.api.nvim_set_option_value("winhl", "StatusLine:NvimTreeWindowPicker,StatusLineNC:NvimTreeWindowPicker", { win = id })
|
||||||
|
else
|
||||||
|
vim.api.nvim_win_set_option(id, "statusline", "%=" .. char .. "%=") ---@diagnostic disable-line: deprecated
|
||||||
|
vim.api.nvim_win_set_option(id, "winhl", "StatusLine:NvimTreeWindowPicker,StatusLineNC:NvimTreeWindowPicker") ---@diagnostic disable-line: deprecated
|
||||||
|
end
|
||||||
|
|
||||||
|
i = i + 1
|
||||||
|
if i > #M.window_picker.chars then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.cmd("redraw")
|
||||||
|
if vim.opt.cmdheight._value ~= 0 then
|
||||||
|
print("Pick window: ")
|
||||||
|
end
|
||||||
|
local _, resp = pcall(get_user_input_char)
|
||||||
|
resp = (resp or ""):upper()
|
||||||
|
utils.clear_prompt()
|
||||||
|
|
||||||
|
-- Restore window options
|
||||||
|
for _, id in ipairs(selectable) do
|
||||||
|
for opt, value in pairs(win_opts_selectable[id]) do
|
||||||
|
if vim.fn.has("nvim-0.10") == 1 then
|
||||||
|
vim.api.nvim_set_option_value(opt, value, { win = id })
|
||||||
|
else
|
||||||
|
vim.api.nvim_win_set_option(id, opt, value) ---@diagnostic disable-line: deprecated
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if laststatus == 3 then
|
||||||
|
for _, id in ipairs(not_selectable) do
|
||||||
|
-- Ensure window still exists at this point
|
||||||
|
if vim.api.nvim_win_is_valid(id) then
|
||||||
|
for opt, value in pairs(win_opts_unselectable[id]) do
|
||||||
|
if vim.fn.has("nvim-0.10") == 1 then
|
||||||
|
vim.api.nvim_set_option_value(opt, value, { win = id })
|
||||||
|
else
|
||||||
|
vim.api.nvim_win_set_option(id, opt, value) ---@diagnostic disable-line: deprecated
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.o.laststatus = laststatus
|
||||||
|
|
||||||
|
if not vim.tbl_contains(vim.split(M.window_picker.chars, ""), resp) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
return win_map[resp]
|
||||||
|
end
|
||||||
|
|
||||||
|
local function open_file_in_tab(filename)
|
||||||
|
if M.quit_on_open then
|
||||||
|
view.close()
|
||||||
|
end
|
||||||
|
if M.relative_path then
|
||||||
|
filename = utils.path_relative(filename, vim.fn.getcwd())
|
||||||
|
end
|
||||||
|
vim.cmd("tabe " .. vim.fn.fnameescape(filename))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function drop(filename)
|
||||||
|
if M.quit_on_open then
|
||||||
|
view.close()
|
||||||
|
end
|
||||||
|
if M.relative_path then
|
||||||
|
filename = utils.path_relative(filename, vim.fn.getcwd())
|
||||||
|
end
|
||||||
|
vim.cmd("drop " .. vim.fn.fnameescape(filename))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function tab_drop(filename)
|
||||||
|
if M.quit_on_open then
|
||||||
|
view.close()
|
||||||
|
end
|
||||||
|
if M.relative_path then
|
||||||
|
filename = utils.path_relative(filename, vim.fn.getcwd())
|
||||||
|
end
|
||||||
|
vim.cmd("tab :drop " .. vim.fn.fnameescape(filename))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function on_preview(buf_loaded)
|
||||||
|
if not buf_loaded then
|
||||||
|
vim.bo.bufhidden = "delete"
|
||||||
|
|
||||||
|
vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, {
|
||||||
|
group = vim.api.nvim_create_augroup("RemoveBufHidden", {}),
|
||||||
|
buffer = vim.api.nvim_get_current_buf(),
|
||||||
|
callback = function()
|
||||||
|
vim.bo.bufhidden = ""
|
||||||
|
end,
|
||||||
|
once = true,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
view.focus()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_target_winid(mode)
|
||||||
|
local target_winid
|
||||||
|
if not M.window_picker.enable or mode == "edit_no_picker" or mode == "preview_no_picker" then
|
||||||
|
target_winid = lib.target_winid
|
||||||
|
|
||||||
|
-- first available window
|
||||||
|
if not vim.tbl_contains(vim.api.nvim_tabpage_list_wins(0), target_winid) then
|
||||||
|
target_winid = first_win_id()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- pick a window
|
||||||
|
if type(M.window_picker.picker) == "function" then
|
||||||
|
target_winid = M.window_picker.picker()
|
||||||
|
else
|
||||||
|
target_winid = pick_win_id()
|
||||||
|
end
|
||||||
|
if target_winid == nil then
|
||||||
|
-- pick failed/cancelled
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if target_winid == -1 then
|
||||||
|
target_winid = lib.target_winid
|
||||||
|
end
|
||||||
|
return target_winid
|
||||||
|
end
|
||||||
|
|
||||||
|
-- This is only to avoid the BufEnter for nvim-tree to trigger
|
||||||
|
-- which would cause find-file to run on an invalid file.
|
||||||
|
local function set_current_win_no_autocmd(winid, autocmd)
|
||||||
|
local eventignore = vim.opt.eventignore:get()
|
||||||
|
vim.opt.eventignore:append(autocmd)
|
||||||
|
vim.api.nvim_set_current_win(winid)
|
||||||
|
vim.opt.eventignore = eventignore
|
||||||
|
end
|
||||||
|
|
||||||
|
local function open_in_new_window(filename, mode)
|
||||||
|
if type(mode) ~= "string" then
|
||||||
|
mode = ""
|
||||||
|
end
|
||||||
|
|
||||||
|
local target_winid = get_target_winid(mode)
|
||||||
|
if not target_winid then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- non-floating, non-nvim-tree windows
|
||||||
|
local win_ids = vim.tbl_filter(function(id)
|
||||||
|
local config = vim.api.nvim_win_get_config(id)
|
||||||
|
local bufnr = vim.api.nvim_win_get_buf(id)
|
||||||
|
return config and config.relative == "" or utils.is_nvim_tree_buf(bufnr)
|
||||||
|
end, vim.api.nvim_list_wins())
|
||||||
|
|
||||||
|
local create_new_window = #win_ids == 1 -- This implies that the nvim-tree window is the only one
|
||||||
|
local new_window_side = (view.View.side == "right") and "aboveleft" or "belowright"
|
||||||
|
|
||||||
|
-- Target is invalid: create new window
|
||||||
|
if not vim.tbl_contains(win_ids, target_winid) then
|
||||||
|
vim.cmd(new_window_side .. " vsplit")
|
||||||
|
target_winid = vim.api.nvim_get_current_win()
|
||||||
|
lib.target_winid = target_winid
|
||||||
|
|
||||||
|
-- No need to split, as we created a new window.
|
||||||
|
create_new_window = false
|
||||||
|
if mode:match("split$") then
|
||||||
|
mode = "edit"
|
||||||
|
end
|
||||||
|
elseif not vim.o.hidden then
|
||||||
|
-- If `hidden` is not enabled, check if buffer in target window is
|
||||||
|
-- modified, and create new split if it is.
|
||||||
|
local target_bufid = vim.api.nvim_win_get_buf(target_winid)
|
||||||
|
|
||||||
|
local modified
|
||||||
|
if vim.fn.has("nvim-0.10") == 1 then
|
||||||
|
modified = vim.api.nvim_get_option_value("modified", { buf = target_bufid })
|
||||||
|
else
|
||||||
|
modified = vim.api.nvim_buf_get_option(target_bufid, "modified") ---@diagnostic disable-line: deprecated
|
||||||
|
end
|
||||||
|
|
||||||
|
if modified then
|
||||||
|
if not mode:match("split$") then
|
||||||
|
mode = "vsplit"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if (mode == "preview" or mode == "preview_no_picker") and view.View.float.enable then
|
||||||
|
-- ignore "WinLeave" autocmd on preview
|
||||||
|
-- because the registered "WinLeave"
|
||||||
|
-- will kill the floating window immediately
|
||||||
|
set_current_win_no_autocmd(target_winid, { "WinLeave", "BufEnter" })
|
||||||
|
else
|
||||||
|
set_current_win_no_autocmd(target_winid, { "BufEnter" })
|
||||||
|
end
|
||||||
|
|
||||||
|
local fname
|
||||||
|
if M.relative_path then
|
||||||
|
fname = utils.escape_special_chars(vim.fn.fnameescape(utils.path_relative(filename, vim.fn.getcwd())))
|
||||||
|
else
|
||||||
|
fname = utils.escape_special_chars(vim.fn.fnameescape(filename))
|
||||||
|
end
|
||||||
|
|
||||||
|
local command
|
||||||
|
if create_new_window then
|
||||||
|
-- generated from vim.api.nvim_parse_cmd("belowright vsplit foo", {})
|
||||||
|
command = { cmd = "vsplit", mods = { split = new_window_side }, args = { fname } }
|
||||||
|
elseif mode:match("split$") then
|
||||||
|
command = { cmd = mode, args = { fname } }
|
||||||
|
else
|
||||||
|
command = { cmd = "edit", args = { fname } }
|
||||||
|
end
|
||||||
|
|
||||||
|
pcall(vim.api.nvim_cmd, command, { output = false })
|
||||||
|
lib.set_target_win()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function is_already_loaded(filename)
|
||||||
|
for _, buf_id in ipairs(vim.api.nvim_list_bufs()) do
|
||||||
|
if vim.api.nvim_buf_is_loaded(buf_id) and filename == vim.api.nvim_buf_get_name(buf_id) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function edit_in_current_buf(filename)
|
||||||
|
require("nvim-tree.view").abandon_current_window()
|
||||||
|
if M.relative_path then
|
||||||
|
filename = utils.path_relative(filename, vim.fn.getcwd())
|
||||||
|
end
|
||||||
|
vim.cmd("keepalt keepjumps edit " .. vim.fn.fnameescape(filename))
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param mode string
|
||||||
|
---@param filename string
|
||||||
|
---@return nil
|
||||||
|
function M.fn(mode, filename)
|
||||||
|
if type(mode) ~= "string" then
|
||||||
|
mode = ""
|
||||||
|
end
|
||||||
|
|
||||||
|
if mode == "tabnew" then
|
||||||
|
return open_file_in_tab(filename)
|
||||||
|
end
|
||||||
|
|
||||||
|
if mode == "drop" then
|
||||||
|
return drop(filename)
|
||||||
|
end
|
||||||
|
|
||||||
|
if mode == "tab_drop" then
|
||||||
|
return tab_drop(filename)
|
||||||
|
end
|
||||||
|
|
||||||
|
if mode == "edit_in_place" then
|
||||||
|
return edit_in_current_buf(filename)
|
||||||
|
end
|
||||||
|
|
||||||
|
local buf_loaded = is_already_loaded(filename)
|
||||||
|
|
||||||
|
local found_win = utils.get_win_buf_from_path(filename)
|
||||||
|
if found_win and (mode == "preview" or mode == "preview_no_picker") then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not found_win then
|
||||||
|
open_in_new_window(filename, mode)
|
||||||
|
else
|
||||||
|
vim.api.nvim_set_current_win(found_win)
|
||||||
|
vim.bo.bufhidden = ""
|
||||||
|
end
|
||||||
|
|
||||||
|
if M.resize_window then
|
||||||
|
view.resize()
|
||||||
|
end
|
||||||
|
|
||||||
|
if mode == "preview" or mode == "preview_no_picker" then
|
||||||
|
return on_preview(buf_loaded)
|
||||||
|
end
|
||||||
|
|
||||||
|
if M.quit_on_open then
|
||||||
|
view.close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.quit_on_open = opts.actions.open_file.quit_on_open
|
||||||
|
M.resize_window = opts.actions.open_file.resize_window
|
||||||
|
M.relative_path = opts.actions.open_file.relative_path
|
||||||
|
if opts.actions.open_file.window_picker.chars then
|
||||||
|
opts.actions.open_file.window_picker.chars = tostring(opts.actions.open_file.window_picker.chars):upper()
|
||||||
|
end
|
||||||
|
M.window_picker = opts.actions.open_file.window_picker
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -1,19 +1,23 @@
|
|||||||
local utils = require "nvim-tree.utils"
|
local utils = require("nvim-tree.utils")
|
||||||
local core = require "nvim-tree.core"
|
local core = require("nvim-tree.core")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
---Retrieves the absolute path to the node.
|
---Retrieves the absolute path to the node.
|
||||||
---Safely handles the node representing the current directory
|
---Safely handles the node representing the current directory
|
||||||
---(the topmost node in the nvim-tree window)
|
---(the topmost node in the nvim-tree window)
|
||||||
|
---@param node Node
|
||||||
|
---@return string
|
||||||
local function get_node_path(node)
|
local function get_node_path(node)
|
||||||
if node.name == ".." then
|
local cwd = core.get_cwd()
|
||||||
return utils.path_remove_trailing(core.get_cwd())
|
if node.name == ".." and cwd then
|
||||||
|
return utils.path_remove_trailing(cwd)
|
||||||
else
|
else
|
||||||
return node.absolute_path
|
return node.absolute_path
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
function M.run_file_command(node)
|
function M.run_file_command(node)
|
||||||
local node_path = get_node_path(node)
|
local node_path = get_node_path(node)
|
||||||
vim.api.nvim_input(": " .. node_path .. "<Home>")
|
vim.api.nvim_input(": " .. node_path .. "<Home>")
|
||||||
91
lua/nvim-tree/actions/node/system-open.lua
Normal file
91
lua/nvim-tree/actions/node/system-open.lua
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
local notify = require("nvim-tree.notify")
|
||||||
|
local utils = require("nvim-tree.utils")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
local function user(node)
|
||||||
|
if #M.config.system_open.cmd == 0 then
|
||||||
|
require("nvim-tree.utils").notify.warn("Cannot open file with system application. Unrecognized platform.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local process = {
|
||||||
|
cmd = M.config.system_open.cmd,
|
||||||
|
args = M.config.system_open.args,
|
||||||
|
errors = "\n",
|
||||||
|
stderr = vim.loop.new_pipe(false),
|
||||||
|
}
|
||||||
|
table.insert(process.args, node.link_to or node.absolute_path)
|
||||||
|
|
||||||
|
local opts = {
|
||||||
|
args = process.args,
|
||||||
|
stdio = { nil, nil, process.stderr },
|
||||||
|
detached = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
process.handle, process.pid = vim.loop.spawn(process.cmd, opts, function(code)
|
||||||
|
process.stderr:read_stop()
|
||||||
|
process.stderr:close()
|
||||||
|
process.handle:close()
|
||||||
|
if code ~= 0 then
|
||||||
|
notify.warn(string.format("system_open failed with return code %d: %s", code, process.errors))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
table.remove(process.args)
|
||||||
|
if not process.handle then
|
||||||
|
notify.warn(string.format("system_open failed to spawn command '%s': %s", process.cmd, process.pid))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
vim.loop.read_start(process.stderr, function(err, data)
|
||||||
|
if err then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if data then
|
||||||
|
process.errors = process.errors .. data
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
vim.loop.unref(process.handle)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
local function native(node)
|
||||||
|
local _, err = vim.ui.open(node.link_to or node.absolute_path)
|
||||||
|
|
||||||
|
-- err only provided on opener executable not found hence logging path is not useful
|
||||||
|
if err then
|
||||||
|
notify.warn(err)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
function M.fn(node)
|
||||||
|
M.open(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO #2430 always use native once 0.10 is the minimum neovim version
|
||||||
|
function M.setup(opts)
|
||||||
|
M.config = {}
|
||||||
|
M.config.system_open = opts.system_open or {}
|
||||||
|
|
||||||
|
if vim.fn.has("nvim-0.10") == 1 and #M.config.system_open.cmd == 0 then
|
||||||
|
M.open = native
|
||||||
|
else
|
||||||
|
M.open = user
|
||||||
|
if #M.config.system_open.cmd == 0 then
|
||||||
|
if utils.is_windows then
|
||||||
|
M.config.system_open = {
|
||||||
|
cmd = "cmd",
|
||||||
|
args = { "/c", "start", '""' },
|
||||||
|
}
|
||||||
|
elseif utils.is_macos then
|
||||||
|
M.config.system_open.cmd = "open"
|
||||||
|
elseif utils.is_unix then
|
||||||
|
M.config.system_open.cmd = "xdg-open"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -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
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
local git = require "nvim-tree.git"
|
|
||||||
local view = require "nvim-tree.view"
|
|
||||||
local renderer = require "nvim-tree.renderer"
|
|
||||||
local explorer_module = require "nvim-tree.explorer"
|
|
||||||
local core = require "nvim-tree.core"
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
local function refresh_nodes(node, projects)
|
|
||||||
local cwd = node.cwd or node.link_to or node.absolute_path
|
|
||||||
local project_root = git.get_project_root(cwd)
|
|
||||||
explorer_module.reload(node, projects[project_root] or {})
|
|
||||||
for _, _node in ipairs(node.nodes) do
|
|
||||||
if _node.nodes and _node.open then
|
|
||||||
refresh_nodes(_node, projects)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.reload_node_status(parent_node, projects)
|
|
||||||
local project_root = git.get_project_root(parent_node.absolute_path or parent_node.cwd)
|
|
||||||
local status = projects[project_root] or {}
|
|
||||||
for _, node in ipairs(parent_node.nodes) do
|
|
||||||
if node.nodes then
|
|
||||||
node.git_status = status.dirs and status.dirs[node.absolute_path]
|
|
||||||
else
|
|
||||||
node.git_status = status.files and status.files[node.absolute_path]
|
|
||||||
end
|
|
||||||
if node.nodes and #node.nodes > 0 then
|
|
||||||
M.reload_node_status(node, projects)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local event_running = false
|
|
||||||
function M.reload_explorer()
|
|
||||||
if event_running or not core.get_explorer() or vim.v.exiting ~= vim.NIL then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
event_running = true
|
|
||||||
|
|
||||||
local projects = git.reload()
|
|
||||||
refresh_nodes(core.get_explorer(), projects)
|
|
||||||
if view.is_visible() then
|
|
||||||
renderer.draw()
|
|
||||||
end
|
|
||||||
event_running = false
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.reload_git()
|
|
||||||
if not core.get_explorer() or not git.config.enable or event_running then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
event_running = true
|
|
||||||
|
|
||||||
local projects = git.reload()
|
|
||||||
M.reload_node_status(core.get_explorer(), projects)
|
|
||||||
renderer.draw()
|
|
||||||
event_running = false
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -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
|
|
||||||
105
lua/nvim-tree/actions/root/change-dir.lua
Normal file
105
lua/nvim-tree/actions/root/change-dir.lua
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
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(),
|
||||||
|
}
|
||||||
|
|
||||||
|
---@param name string
|
||||||
|
---@return string|nil
|
||||||
|
local function clean_input_cwd(name)
|
||||||
|
name = vim.fn.fnameescape(name)
|
||||||
|
local cwd = core.get_cwd()
|
||||||
|
if cwd == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local root_parent_cwd = vim.fn.fnamemodify(utils.path_remove_trailing(cwd), ":h")
|
||||||
|
if name == ".." and root_parent_cwd then
|
||||||
|
return vim.fn.expand(root_parent_cwd)
|
||||||
|
else
|
||||||
|
return vim.fn.expand(name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param new_tabpage integer
|
||||||
|
---@return boolean
|
||||||
|
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
|
||||||
|
|
||||||
|
---@param foldername string
|
||||||
|
---@return boolean
|
||||||
|
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
|
||||||
|
|
||||||
|
---@param input_cwd string
|
||||||
|
---@param with_open boolean|nil
|
||||||
|
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 foldername == nil or prevent_cwd_change(foldername) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
M.current_tab = new_tabpage
|
||||||
|
M.force_dirchange(foldername, with_open)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param global boolean
|
||||||
|
---@param path string
|
||||||
|
local function cd(global, path)
|
||||||
|
vim.cmd((global and "cd " or "lcd ") .. vim.fn.fnameescape(path))
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return boolean
|
||||||
|
local function should_change_dir()
|
||||||
|
return M.options.enable and vim.tbl_isempty(vim.v.event)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param f function
|
||||||
|
---@return fun(foldername: string, should_open_view: boolean|nil)
|
||||||
|
local function add_profiling_to(f)
|
||||||
|
return function(foldername, should_open_view)
|
||||||
|
local profile = log.profile_start("change dir %s", foldername)
|
||||||
|
f(foldername, should_open_view)
|
||||||
|
log.profile_end(profile)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.force_dirchange = add_profiling_to(function(foldername, should_open_view)
|
||||||
|
local valid_dir = vim.fn.isdirectory(foldername) == 1 -- prevent problems on non existing dirs
|
||||||
|
if valid_dir then
|
||||||
|
if should_change_dir() then
|
||||||
|
cd(M.options.global, foldername)
|
||||||
|
end
|
||||||
|
core.init(foldername)
|
||||||
|
end
|
||||||
|
|
||||||
|
if should_open_view then
|
||||||
|
require("nvim-tree.lib").open()
|
||||||
|
else
|
||||||
|
local explorer = core.get_explorer()
|
||||||
|
if explorer then
|
||||||
|
explorer.renderer:draw()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
function M.setup(options)
|
||||||
|
M.options = options.actions.change_dir
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
22
lua/nvim-tree/actions/root/dir-up.lua
Normal file
22
lua/nvim-tree/actions/root/dir-up.lua
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
local utils = require("nvim-tree.utils")
|
||||||
|
local core = require("nvim-tree.core")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
function M.fn(node)
|
||||||
|
if not node or node.name == ".." then
|
||||||
|
require("nvim-tree.actions.root.change-dir").fn("..")
|
||||||
|
else
|
||||||
|
local cwd = core.get_cwd()
|
||||||
|
if cwd == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local newdir = vim.fn.fnamemodify(utils.path_remove_trailing(cwd), ":h")
|
||||||
|
require("nvim-tree.actions.root.change-dir").fn(newdir)
|
||||||
|
require("nvim-tree.actions.finders.find-file").fn(node.absolute_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
10
lua/nvim-tree/actions/root/init.lua
Normal file
10
lua/nvim-tree/actions/root/init.lua
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
M.change_dir = require("nvim-tree.actions.root.change-dir")
|
||||||
|
M.dir_up = require("nvim-tree.actions.root.dir-up")
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.change_dir.setup(opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -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,70 +0,0 @@
|
|||||||
local uv = vim.loop
|
|
||||||
|
|
||||||
local M = {
|
|
||||||
config = {
|
|
||||||
is_windows = vim.fn.has "win32" == 1 or vim.fn.has "win32unix" == 1,
|
|
||||||
is_macos = vim.fn.has "mac" == 1 or vim.fn.has "macunix" == 1,
|
|
||||||
is_unix = vim.fn.has "unix" == 1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
function M.fn(node)
|
|
||||||
if #M.config.system_open.cmd == 0 then
|
|
||||||
require("nvim-tree.utils").warn "Cannot open file with system application. Unrecognized platform."
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local process = {
|
|
||||||
cmd = M.config.system_open.cmd,
|
|
||||||
args = M.config.system_open.args,
|
|
||||||
errors = "\n",
|
|
||||||
stderr = uv.new_pipe(false),
|
|
||||||
}
|
|
||||||
table.insert(process.args, node.link_to or node.absolute_path)
|
|
||||||
process.handle, process.pid = uv.spawn(
|
|
||||||
process.cmd,
|
|
||||||
{ args = process.args, stdio = { nil, nil, process.stderr }, detached = true },
|
|
||||||
function(code)
|
|
||||||
process.stderr:read_stop()
|
|
||||||
process.stderr:close()
|
|
||||||
process.handle:close()
|
|
||||||
if code ~= 0 then
|
|
||||||
process.errors = process.errors .. string.format("NvimTree system_open: return code %d.", code)
|
|
||||||
error(process.errors)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
)
|
|
||||||
table.remove(process.args)
|
|
||||||
if not process.handle then
|
|
||||||
error("\n" .. process.pid .. "\nNvimTree system_open: failed to spawn process using '" .. process.cmd .. "'.")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
uv.read_start(process.stderr, function(err, data)
|
|
||||||
if err then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if data then
|
|
||||||
process.errors = process.errors .. data
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
uv.unref(process.handle)
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.setup(opts)
|
|
||||||
M.config.system_open = opts or {}
|
|
||||||
|
|
||||||
if #M.config.system_open.cmd == 0 then
|
|
||||||
if M.config.is_windows then
|
|
||||||
M.config.system_open = {
|
|
||||||
cmd = "cmd",
|
|
||||||
args = { "/c", "start", '""' },
|
|
||||||
}
|
|
||||||
elseif M.config.is_macos then
|
|
||||||
M.config.system_open.cmd = "open"
|
|
||||||
elseif M.config.is_unix then
|
|
||||||
M.config.system_open.cmd = "xdg-open"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
local view = require "nvim-tree.view"
|
|
||||||
local filters = require "nvim-tree.explorer.filters"
|
|
||||||
local renderer = require "nvim-tree.renderer"
|
|
||||||
local reloaders = require "nvim-tree.actions.reloaders"
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
function M.custom()
|
|
||||||
filters.config.filter_custom = not filters.config.filter_custom
|
|
||||||
return reloaders.reload_explorer()
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.git_ignored()
|
|
||||||
filters.config.filter_git_ignored = not filters.config.filter_git_ignored
|
|
||||||
return reloaders.reload_explorer()
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.dotfiles()
|
|
||||||
filters.config.filter_dotfiles = not filters.config.filter_dotfiles
|
|
||||||
return reloaders.reload_explorer()
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.help()
|
|
||||||
view.toggle_help()
|
|
||||||
renderer.draw()
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -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
|
|
||||||
71
lua/nvim-tree/actions/tree/find-file.lua
Normal file
71
lua/nvim-tree/actions/tree/find-file.lua
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
local core = require("nvim-tree.core")
|
||||||
|
local lib = require("nvim-tree.lib")
|
||||||
|
local view = require("nvim-tree.view")
|
||||||
|
local finders_find_file = require("nvim-tree.actions.finders.find-file")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
--- Find file or buffer
|
||||||
|
---@param opts ApiTreeFindFileOpts|nil|boolean legacy -> opts.buf
|
||||||
|
function M.fn(opts)
|
||||||
|
-- legacy arguments
|
||||||
|
if type(opts) == "string" then
|
||||||
|
opts = {
|
||||||
|
buf = opts,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
opts = opts or {}
|
||||||
|
|
||||||
|
-- do nothing if closed and open not requested
|
||||||
|
if not opts.open and not core.get_explorer() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local bufnr, path
|
||||||
|
|
||||||
|
-- (optional) buffer number and path
|
||||||
|
local opts_buf = opts.buf
|
||||||
|
if type(opts_buf) == "nil" then
|
||||||
|
bufnr = vim.api.nvim_get_current_buf()
|
||||||
|
path = vim.api.nvim_buf_get_name(bufnr)
|
||||||
|
elseif type(opts_buf) == "number" then
|
||||||
|
if not vim.api.nvim_buf_is_valid(opts_buf) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
bufnr = opts_buf
|
||||||
|
path = vim.api.nvim_buf_get_name(bufnr)
|
||||||
|
elseif type(opts_buf) == "string" then
|
||||||
|
bufnr = nil
|
||||||
|
path = tostring(opts_buf)
|
||||||
|
else
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if view.is_visible() then
|
||||||
|
-- focus
|
||||||
|
if opts.focus then
|
||||||
|
lib.set_target_win()
|
||||||
|
view.focus()
|
||||||
|
end
|
||||||
|
elseif opts.open then
|
||||||
|
-- open
|
||||||
|
lib.open({ current_window = opts.current_window, winid = opts.winid })
|
||||||
|
if not opts.focus then
|
||||||
|
vim.cmd("noautocmd wincmd p")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- update root
|
||||||
|
if opts.update_root or M.config.update_focused_file.update_root.enable then
|
||||||
|
require("nvim-tree").change_root(path, bufnr)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- find
|
||||||
|
finders_find_file.fn(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.config = opts or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
17
lua/nvim-tree/actions/tree/init.lua
Normal file
17
lua/nvim-tree/actions/tree/init.lua
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
M.find_file = require("nvim-tree.actions.tree.find-file")
|
||||||
|
M.modifiers = require("nvim-tree.actions.tree.modifiers")
|
||||||
|
M.open = require("nvim-tree.actions.tree.open")
|
||||||
|
M.toggle = require("nvim-tree.actions.tree.toggle")
|
||||||
|
M.resize = require("nvim-tree.actions.tree.resize")
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.find_file.setup(opts)
|
||||||
|
M.modifiers.setup(opts)
|
||||||
|
M.open.setup(opts)
|
||||||
|
M.toggle.setup(opts)
|
||||||
|
M.resize.setup(opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
57
lua/nvim-tree/actions/tree/modifiers/collapse-all.lua
Normal file
57
lua/nvim-tree/actions/tree/modifiers/collapse-all.lua
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
local utils = require("nvim-tree.utils")
|
||||||
|
local core = require("nvim-tree.core")
|
||||||
|
local Iterator = require("nvim-tree.iterators.node-iterator")
|
||||||
|
|
||||||
|
local DirectoryNode = require("nvim-tree.node.directory")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@return fun(path: string): boolean
|
||||||
|
local function buf_match()
|
||||||
|
local buffer_paths = vim.tbl_map(function(buffer)
|
||||||
|
return vim.api.nvim_buf_get_name(buffer)
|
||||||
|
end, vim.api.nvim_list_bufs())
|
||||||
|
|
||||||
|
return function(path)
|
||||||
|
for _, buffer_path in ipairs(buffer_paths) do
|
||||||
|
local matches = utils.str_find(buffer_path, path)
|
||||||
|
if matches then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param keep_buffers boolean
|
||||||
|
function M.fn(keep_buffers)
|
||||||
|
local explorer = core.get_explorer()
|
||||||
|
if not explorer then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local node = explorer:get_node_at_cursor()
|
||||||
|
if not node then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local matches = buf_match()
|
||||||
|
|
||||||
|
Iterator.builder(explorer.nodes)
|
||||||
|
:hidden()
|
||||||
|
:applier(function(n)
|
||||||
|
local dir = n:as(DirectoryNode)
|
||||||
|
if dir then
|
||||||
|
dir.open = keep_buffers and matches(dir.absolute_path)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
:recursor(function(n)
|
||||||
|
return n.group_next and { n.group_next } or n.nodes
|
||||||
|
end)
|
||||||
|
:iterate()
|
||||||
|
|
||||||
|
explorer.renderer:draw()
|
||||||
|
utils.focus_node_or_parent(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
95
lua/nvim-tree/actions/tree/modifiers/expand-all.lua
Normal file
95
lua/nvim-tree/actions/tree/modifiers/expand-all.lua
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
local core = require("nvim-tree.core")
|
||||||
|
local Iterator = require("nvim-tree.iterators.node-iterator")
|
||||||
|
local notify = require("nvim-tree.notify")
|
||||||
|
|
||||||
|
local DirectoryNode = require("nvim-tree.node.directory")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@param list string[]
|
||||||
|
---@return table
|
||||||
|
local function to_lookup_table(list)
|
||||||
|
local table = {}
|
||||||
|
for _, element in ipairs(list) do
|
||||||
|
table[element] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
return table
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node DirectoryNode
|
||||||
|
local function expand(node)
|
||||||
|
node = node:last_group_node()
|
||||||
|
node.open = true
|
||||||
|
if #node.nodes == 0 then
|
||||||
|
core.get_explorer():expand(node)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param expansion_count integer
|
||||||
|
---@param node Node
|
||||||
|
---@return boolean
|
||||||
|
local function should_expand(expansion_count, node)
|
||||||
|
local dir = node:as(DirectoryNode)
|
||||||
|
if not dir then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local should_halt = expansion_count >= M.MAX_FOLDER_DISCOVERY
|
||||||
|
local should_exclude = M.EXCLUDE[dir.name]
|
||||||
|
return not should_halt and not dir.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
|
||||||
|
node = node:as(DirectoryNode)
|
||||||
|
if node then
|
||||||
|
expand(node)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
:recursor(function(node)
|
||||||
|
return expansion_count < M.MAX_FOLDER_DISCOVERY and (node.group_next and { node.group_next } or (node.open and node.nodes))
|
||||||
|
end)
|
||||||
|
:iterate()
|
||||||
|
|
||||||
|
if expansion_count >= M.MAX_FOLDER_DISCOVERY then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Expand the directory node or the root
|
||||||
|
---@param node Node
|
||||||
|
function M.fn(node)
|
||||||
|
local explorer = core.get_explorer()
|
||||||
|
local parent = node:as(DirectoryNode) or explorer
|
||||||
|
if not parent then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if gen_iterator()(parent) then
|
||||||
|
notify.warn("expansion iteration was halted after " .. M.MAX_FOLDER_DISCOVERY .. " discovered folders")
|
||||||
|
end
|
||||||
|
if explorer then
|
||||||
|
explorer.renderer:draw()
|
||||||
|
end
|
||||||
|
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
|
||||||
10
lua/nvim-tree/actions/tree/modifiers/init.lua
Normal file
10
lua/nvim-tree/actions/tree/modifiers/init.lua
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
M.collapse_all = require("nvim-tree.actions.tree.modifiers.collapse-all")
|
||||||
|
M.expand_all = require("nvim-tree.actions.tree.modifiers.expand-all")
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.expand_all.setup(opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
55
lua/nvim-tree/actions/tree/open.lua
Normal file
55
lua/nvim-tree/actions/tree/open.lua
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
local lib = require("nvim-tree.lib")
|
||||||
|
local view = require("nvim-tree.view")
|
||||||
|
local finders_find_file = require("nvim-tree.actions.finders.find-file")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---Open the tree, focusing if already open.
|
||||||
|
---@param opts ApiTreeOpenOpts|nil|string legacy -> opts.path
|
||||||
|
function M.fn(opts)
|
||||||
|
-- legacy arguments
|
||||||
|
if type(opts) == "string" then
|
||||||
|
opts = {
|
||||||
|
path = opts,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
opts = opts or {}
|
||||||
|
|
||||||
|
local previous_buf = vim.api.nvim_get_current_buf()
|
||||||
|
local previous_path = vim.api.nvim_buf_get_name(previous_buf)
|
||||||
|
|
||||||
|
-- sanitise path
|
||||||
|
if type(opts.path) ~= "string" or vim.fn.isdirectory(opts.path) == 0 then
|
||||||
|
opts.path = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if view.is_visible() then
|
||||||
|
-- focus
|
||||||
|
lib.set_target_win()
|
||||||
|
view.focus()
|
||||||
|
else
|
||||||
|
-- open
|
||||||
|
lib.open({
|
||||||
|
path = opts.path,
|
||||||
|
current_window = opts.current_window,
|
||||||
|
winid = opts.winid,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- find file
|
||||||
|
if M.config.update_focused_file.enable or opts.find_file then
|
||||||
|
-- update root
|
||||||
|
if opts.update_root then
|
||||||
|
require("nvim-tree").change_root(previous_path, previous_buf)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- find
|
||||||
|
finders_find_file.fn(previous_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.config = opts or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
51
lua/nvim-tree/actions/tree/resize.lua
Normal file
51
lua/nvim-tree/actions/tree/resize.lua
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
local view = require("nvim-tree.view")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---Resize the tree, persisting the new size.
|
||||||
|
---@param opts ApiTreeResizeOpts|nil
|
||||||
|
function M.fn(opts)
|
||||||
|
if opts == nil then
|
||||||
|
-- reset to config values
|
||||||
|
view.configure_width()
|
||||||
|
view.resize()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local options = opts or {}
|
||||||
|
local width_cfg = options.width
|
||||||
|
|
||||||
|
if width_cfg ~= nil then
|
||||||
|
view.configure_width(width_cfg)
|
||||||
|
view.resize()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not view.is_width_determined() then
|
||||||
|
-- {absolute} and {relative} do nothing when {width} is a function.
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local absolute = options.absolute
|
||||||
|
if type(absolute) == "number" then
|
||||||
|
view.resize(absolute)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local relative = options.relative
|
||||||
|
if type(relative) == "number" then
|
||||||
|
local relative_size = tostring(relative)
|
||||||
|
if relative > 0 then
|
||||||
|
relative_size = "+" .. relative_size
|
||||||
|
end
|
||||||
|
|
||||||
|
view.resize(relative_size)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.config = opts or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
76
lua/nvim-tree/actions/tree/toggle.lua
Normal file
76
lua/nvim-tree/actions/tree/toggle.lua
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
local lib = require("nvim-tree.lib")
|
||||||
|
local view = require("nvim-tree.view")
|
||||||
|
local finders_find_file = require("nvim-tree.actions.finders.find-file")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---Toggle the tree.
|
||||||
|
---@param opts ApiTreeToggleOpts|nil|boolean legacy -> opts.find_file
|
||||||
|
---@param no_focus string|nil legacy -> opts.focus
|
||||||
|
---@param cwd boolean|nil legacy -> opts.path
|
||||||
|
---@param bang boolean|nil legacy -> opts.update_root
|
||||||
|
function M.fn(opts, no_focus, cwd, bang)
|
||||||
|
-- legacy arguments
|
||||||
|
if type(opts) == "boolean" then
|
||||||
|
opts = {
|
||||||
|
find_file = opts,
|
||||||
|
}
|
||||||
|
if type(cwd) == "string" then
|
||||||
|
opts.path = cwd
|
||||||
|
end
|
||||||
|
if type(no_focus) == "boolean" then
|
||||||
|
opts.focus = not no_focus
|
||||||
|
end
|
||||||
|
if type(bang) == "boolean" then
|
||||||
|
opts.update_root = bang
|
||||||
|
end
|
||||||
|
end
|
||||||
|
opts = opts or {}
|
||||||
|
|
||||||
|
-- defaults
|
||||||
|
if opts.focus == nil then
|
||||||
|
opts.focus = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local previous_buf = vim.api.nvim_get_current_buf()
|
||||||
|
local previous_path = vim.api.nvim_buf_get_name(previous_buf)
|
||||||
|
|
||||||
|
-- sanitise path
|
||||||
|
if type(opts.path) ~= "string" or vim.fn.isdirectory(opts.path) == 0 then
|
||||||
|
opts.path = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if view.is_visible() then
|
||||||
|
-- close
|
||||||
|
view.close()
|
||||||
|
else
|
||||||
|
-- open
|
||||||
|
lib.open({
|
||||||
|
path = opts.path,
|
||||||
|
current_window = opts.current_window,
|
||||||
|
winid = opts.winid,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- find file
|
||||||
|
if M.config.update_focused_file.enable or opts.find_file then
|
||||||
|
-- update root
|
||||||
|
if opts.update_root then
|
||||||
|
require("nvim-tree").change_root(previous_path, previous_buf)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- find
|
||||||
|
finders_find_file.fn(previous_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- restore focus
|
||||||
|
if not opts.focus then
|
||||||
|
vim.cmd("noautocmd wincmd p")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.config = opts or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
314
lua/nvim-tree/api.lua
Normal file
314
lua/nvim-tree/api.lua
Normal file
@@ -0,0 +1,314 @@
|
|||||||
|
local core = require("nvim-tree.core")
|
||||||
|
local view = require("nvim-tree.view")
|
||||||
|
local utils = require("nvim-tree.utils")
|
||||||
|
local actions = require("nvim-tree.actions")
|
||||||
|
local appearance_hi_test = require("nvim-tree.appearance.hi-test")
|
||||||
|
local events = require("nvim-tree.events")
|
||||||
|
local help = require("nvim-tree.help")
|
||||||
|
local keymap = require("nvim-tree.keymap")
|
||||||
|
local notify = require("nvim-tree.notify")
|
||||||
|
|
||||||
|
local DirectoryNode = require("nvim-tree.node.directory")
|
||||||
|
local FileLinkNode = require("nvim-tree.node.file-link")
|
||||||
|
local RootNode = require("nvim-tree.node.root")
|
||||||
|
|
||||||
|
local Api = {
|
||||||
|
tree = {},
|
||||||
|
node = {
|
||||||
|
navigate = {
|
||||||
|
sibling = {},
|
||||||
|
git = {},
|
||||||
|
diagnostics = {},
|
||||||
|
opened = {},
|
||||||
|
},
|
||||||
|
run = {},
|
||||||
|
open = {},
|
||||||
|
},
|
||||||
|
events = {},
|
||||||
|
marks = {
|
||||||
|
bulk = {},
|
||||||
|
navigate = {},
|
||||||
|
},
|
||||||
|
fs = {
|
||||||
|
copy = {},
|
||||||
|
},
|
||||||
|
git = {},
|
||||||
|
live_filter = {},
|
||||||
|
config = {
|
||||||
|
mappings = {},
|
||||||
|
},
|
||||||
|
commands = {},
|
||||||
|
diagnostics = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
---Print error when setup not called.
|
||||||
|
---@param fn fun(...): any
|
||||||
|
---@return fun(...): any
|
||||||
|
local function wrap(fn)
|
||||||
|
return function(...)
|
||||||
|
if vim.g.NvimTreeSetup == 1 then
|
||||||
|
return fn(...)
|
||||||
|
else
|
||||||
|
notify.error("nvim-tree setup not called")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Invoke a method on the singleton explorer.
|
||||||
|
---Print error when setup not called.
|
||||||
|
---@param explorer_method string explorer method name
|
||||||
|
---@return fun(...): any
|
||||||
|
local function wrap_explorer(explorer_method)
|
||||||
|
return wrap(function(...)
|
||||||
|
local explorer = core.get_explorer()
|
||||||
|
if explorer then
|
||||||
|
return explorer[explorer_method](explorer, ...)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Inject the node as the first argument if present otherwise do nothing.
|
||||||
|
---@param fn fun(node: Node, ...): any
|
||||||
|
---@return fun(node: Node, ...): any
|
||||||
|
local function wrap_node(fn)
|
||||||
|
return function(node, ...)
|
||||||
|
node = node or wrap_explorer("get_node_at_cursor")()
|
||||||
|
if node then
|
||||||
|
return fn(node, ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Inject the node or nil as the first argument if absent.
|
||||||
|
---@param fn fun(node: Node, ...): any
|
||||||
|
---@return fun(node: Node, ...): any
|
||||||
|
local function wrap_node_or_nil(fn)
|
||||||
|
return function(node, ...)
|
||||||
|
node = node or wrap_explorer("get_node_at_cursor")()
|
||||||
|
return fn(node, ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Invoke a member's method on the singleton explorer.
|
||||||
|
---Print error when setup not called.
|
||||||
|
---@param explorer_member string explorer member name
|
||||||
|
---@param member_method string method name to invoke on member
|
||||||
|
---@param ... any passed to method
|
||||||
|
---@return fun(...): any
|
||||||
|
local function wrap_explorer_member_args(explorer_member, member_method, ...)
|
||||||
|
local method_args = ...
|
||||||
|
return wrap(function(...)
|
||||||
|
local explorer = core.get_explorer()
|
||||||
|
if explorer then
|
||||||
|
return explorer[explorer_member][member_method](explorer[explorer_member], method_args, ...)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Invoke a member's method on the singleton explorer.
|
||||||
|
---Print error when setup not called.
|
||||||
|
---@param explorer_member string explorer member name
|
||||||
|
---@param member_method string method name to invoke on member
|
||||||
|
---@return fun(...): any
|
||||||
|
local function wrap_explorer_member(explorer_member, member_method)
|
||||||
|
return wrap(function(...)
|
||||||
|
local explorer = core.get_explorer()
|
||||||
|
if explorer then
|
||||||
|
return explorer[explorer_member][member_method](explorer[explorer_member], ...)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@class ApiTreeOpenOpts
|
||||||
|
---@field path string|nil path
|
||||||
|
---@field current_window boolean|nil default false
|
||||||
|
---@field winid number|nil
|
||||||
|
---@field find_file boolean|nil default false
|
||||||
|
---@field update_root boolean|nil default false
|
||||||
|
|
||||||
|
Api.tree.open = wrap(actions.tree.open.fn)
|
||||||
|
Api.tree.focus = Api.tree.open
|
||||||
|
|
||||||
|
---@class ApiTreeToggleOpts
|
||||||
|
---@field path string|nil
|
||||||
|
---@field current_window boolean|nil default false
|
||||||
|
---@field winid number|nil
|
||||||
|
---@field find_file boolean|nil default false
|
||||||
|
---@field update_root boolean|nil default false
|
||||||
|
---@field focus boolean|nil default true
|
||||||
|
|
||||||
|
Api.tree.toggle = wrap(actions.tree.toggle.fn)
|
||||||
|
Api.tree.close = wrap(view.close)
|
||||||
|
Api.tree.close_in_this_tab = wrap(view.close_this_tab_only)
|
||||||
|
Api.tree.close_in_all_tabs = wrap(view.close_all_tabs)
|
||||||
|
Api.tree.reload = wrap_explorer("reload_explorer")
|
||||||
|
|
||||||
|
---@class ApiTreeResizeOpts
|
||||||
|
---@field width string|function|number|table|nil
|
||||||
|
---@field absolute number|nil
|
||||||
|
---@field relative number|nil
|
||||||
|
|
||||||
|
Api.tree.resize = wrap(actions.tree.resize.fn)
|
||||||
|
|
||||||
|
Api.tree.change_root = wrap(function(...)
|
||||||
|
require("nvim-tree").change_dir(...)
|
||||||
|
end)
|
||||||
|
|
||||||
|
Api.tree.change_root_to_node = wrap_node(function(node)
|
||||||
|
if node.name == ".." or node:is(RootNode) then
|
||||||
|
actions.root.change_dir.fn("..")
|
||||||
|
else
|
||||||
|
node = node:as(DirectoryNode)
|
||||||
|
if node then
|
||||||
|
actions.root.change_dir.fn(node:last_group_node().absolute_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
Api.tree.change_root_to_parent = wrap_node(actions.root.dir_up.fn)
|
||||||
|
Api.tree.get_node_under_cursor = wrap_explorer("get_node_at_cursor")
|
||||||
|
Api.tree.get_nodes = wrap_explorer("get_nodes")
|
||||||
|
|
||||||
|
---@class ApiTreeFindFileOpts
|
||||||
|
---@field buf string|number|nil
|
||||||
|
---@field open boolean|nil default false
|
||||||
|
---@field current_window boolean|nil default false
|
||||||
|
---@field winid number|nil
|
||||||
|
---@field update_root boolean|nil default false
|
||||||
|
---@field focus boolean|nil default false
|
||||||
|
|
||||||
|
Api.tree.find_file = wrap(actions.tree.find_file.fn)
|
||||||
|
Api.tree.search_node = wrap(actions.finders.search_node.fn)
|
||||||
|
Api.tree.collapse_all = wrap(actions.tree.modifiers.collapse_all.fn)
|
||||||
|
Api.tree.expand_all = wrap_node(actions.tree.modifiers.expand_all.fn)
|
||||||
|
Api.tree.toggle_enable_filters = wrap_explorer_member("filters", "toggle")
|
||||||
|
Api.tree.toggle_gitignore_filter = wrap_explorer_member_args("filters", "toggle", "git_ignored")
|
||||||
|
Api.tree.toggle_git_clean_filter = wrap_explorer_member_args("filters", "toggle", "git_clean")
|
||||||
|
Api.tree.toggle_no_buffer_filter = wrap_explorer_member_args("filters", "toggle", "no_buffer")
|
||||||
|
Api.tree.toggle_custom_filter = wrap_explorer_member_args("filters", "toggle", "custom")
|
||||||
|
Api.tree.toggle_hidden_filter = wrap_explorer_member_args("filters", "toggle", "dotfiles")
|
||||||
|
Api.tree.toggle_no_bookmark_filter = wrap_explorer_member_args("filters", "toggle", "no_bookmark")
|
||||||
|
Api.tree.toggle_help = wrap(help.toggle)
|
||||||
|
Api.tree.is_tree_buf = wrap(utils.is_nvim_tree_buf)
|
||||||
|
|
||||||
|
---@class ApiTreeIsVisibleOpts
|
||||||
|
---@field tabpage number|nil
|
||||||
|
---@field any_tabpage boolean|nil default false
|
||||||
|
|
||||||
|
Api.tree.is_visible = wrap(view.is_visible)
|
||||||
|
|
||||||
|
---@class ApiTreeWinIdOpts
|
||||||
|
---@field tabpage number|nil default nil
|
||||||
|
|
||||||
|
Api.tree.winid = wrap(view.winid)
|
||||||
|
|
||||||
|
Api.fs.create = wrap_node_or_nil(actions.fs.create_file.fn)
|
||||||
|
Api.fs.remove = wrap_node(actions.fs.remove_file.fn)
|
||||||
|
Api.fs.trash = wrap_node(actions.fs.trash.fn)
|
||||||
|
Api.fs.rename_node = wrap_node(actions.fs.rename_file.fn(":t"))
|
||||||
|
Api.fs.rename = wrap_node(actions.fs.rename_file.fn(":t"))
|
||||||
|
Api.fs.rename_sub = wrap_node(actions.fs.rename_file.fn(":p:h"))
|
||||||
|
Api.fs.rename_basename = wrap_node(actions.fs.rename_file.fn(":t:r"))
|
||||||
|
Api.fs.rename_full = wrap_node(actions.fs.rename_file.fn(":p"))
|
||||||
|
Api.fs.cut = wrap_node(wrap_explorer_member("clipboard", "cut"))
|
||||||
|
Api.fs.paste = wrap_node(wrap_explorer_member("clipboard", "paste"))
|
||||||
|
Api.fs.clear_clipboard = wrap_explorer_member("clipboard", "clear_clipboard")
|
||||||
|
Api.fs.print_clipboard = wrap_explorer_member("clipboard", "print_clipboard")
|
||||||
|
Api.fs.copy.node = wrap_node(wrap_explorer_member("clipboard", "copy"))
|
||||||
|
Api.fs.copy.absolute_path = wrap_node(wrap_explorer_member("clipboard", "copy_absolute_path"))
|
||||||
|
Api.fs.copy.filename = wrap_node(wrap_explorer_member("clipboard", "copy_filename"))
|
||||||
|
Api.fs.copy.basename = wrap_node(wrap_explorer_member("clipboard", "copy_basename"))
|
||||||
|
Api.fs.copy.relative_path = wrap_node(wrap_explorer_member("clipboard", "copy_path"))
|
||||||
|
|
||||||
|
---@param mode string
|
||||||
|
---@param node Node
|
||||||
|
local function edit(mode, node)
|
||||||
|
local file_link = node:as(FileLinkNode)
|
||||||
|
local path = file_link and file_link.link_to or node.absolute_path
|
||||||
|
actions.node.open_file.fn(mode, path)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param mode string
|
||||||
|
---@param toggle_group boolean?
|
||||||
|
---@return fun(node: Node)
|
||||||
|
local function open_or_expand_or_dir_up(mode, toggle_group)
|
||||||
|
---@param node Node
|
||||||
|
return function(node)
|
||||||
|
local root = node:as(RootNode)
|
||||||
|
local dir = node:as(DirectoryNode)
|
||||||
|
|
||||||
|
if root or node.name == ".." then
|
||||||
|
actions.root.change_dir.fn("..")
|
||||||
|
elseif dir then
|
||||||
|
dir:expand_or_collapse(toggle_group)
|
||||||
|
elseif not toggle_group then
|
||||||
|
edit(mode, node)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Api.node.open.edit = wrap_node(open_or_expand_or_dir_up("edit"))
|
||||||
|
Api.node.open.drop = wrap_node(open_or_expand_or_dir_up("drop"))
|
||||||
|
Api.node.open.tab_drop = wrap_node(open_or_expand_or_dir_up("tab_drop"))
|
||||||
|
Api.node.open.replace_tree_buffer = wrap_node(open_or_expand_or_dir_up("edit_in_place"))
|
||||||
|
Api.node.open.no_window_picker = wrap_node(open_or_expand_or_dir_up("edit_no_picker"))
|
||||||
|
Api.node.open.vertical = wrap_node(open_or_expand_or_dir_up("vsplit"))
|
||||||
|
Api.node.open.horizontal = wrap_node(open_or_expand_or_dir_up("split"))
|
||||||
|
Api.node.open.tab = wrap_node(open_or_expand_or_dir_up("tabnew"))
|
||||||
|
Api.node.open.toggle_group_empty = wrap_node(open_or_expand_or_dir_up("toggle_group_empty", true))
|
||||||
|
Api.node.open.preview = wrap_node(open_or_expand_or_dir_up("preview"))
|
||||||
|
Api.node.open.preview_no_picker = wrap_node(open_or_expand_or_dir_up("preview_no_picker"))
|
||||||
|
|
||||||
|
Api.node.show_info_popup = wrap_node(actions.node.file_popup.toggle_file_info)
|
||||||
|
Api.node.run.cmd = wrap_node(actions.node.run_command.run_file_command)
|
||||||
|
Api.node.run.system = wrap_node(actions.node.system_open.fn)
|
||||||
|
|
||||||
|
Api.node.navigate.sibling.next = wrap_node(actions.moves.sibling.fn("next"))
|
||||||
|
Api.node.navigate.sibling.prev = wrap_node(actions.moves.sibling.fn("prev"))
|
||||||
|
Api.node.navigate.sibling.first = wrap_node(actions.moves.sibling.fn("first"))
|
||||||
|
Api.node.navigate.sibling.last = wrap_node(actions.moves.sibling.fn("last"))
|
||||||
|
Api.node.navigate.parent = wrap_node(actions.moves.parent.fn(false))
|
||||||
|
Api.node.navigate.parent_close = wrap_node(actions.moves.parent.fn(true))
|
||||||
|
Api.node.navigate.git.next = wrap_node(actions.moves.item.fn({ where = "next", what = "git" }))
|
||||||
|
Api.node.navigate.git.next_skip_gitignored = wrap_node(actions.moves.item.fn({ where = "next", what = "git", skip_gitignored = true }))
|
||||||
|
Api.node.navigate.git.next_recursive = wrap_node(actions.moves.item.fn({ where = "next", what = "git", recurse = true }))
|
||||||
|
Api.node.navigate.git.prev = wrap_node(actions.moves.item.fn({ where = "prev", what = "git" }))
|
||||||
|
Api.node.navigate.git.prev_skip_gitignored = wrap_node(actions.moves.item.fn({ where = "prev", what = "git", skip_gitignored = true }))
|
||||||
|
Api.node.navigate.git.prev_recursive = wrap_node(actions.moves.item.fn({ where = "prev", what = "git", recurse = true }))
|
||||||
|
Api.node.navigate.diagnostics.next = wrap_node(actions.moves.item.fn({ where = "next", what = "diag" }))
|
||||||
|
Api.node.navigate.diagnostics.next_recursive = wrap_node(actions.moves.item.fn({ where = "next", what = "diag", recurse = true }))
|
||||||
|
Api.node.navigate.diagnostics.prev = wrap_node(actions.moves.item.fn({ where = "prev", what = "diag" }))
|
||||||
|
Api.node.navigate.diagnostics.prev_recursive = wrap_node(actions.moves.item.fn({ where = "prev", what = "diag", recurse = true }))
|
||||||
|
Api.node.navigate.opened.next = wrap_node(actions.moves.item.fn({ where = "next", what = "opened" }))
|
||||||
|
Api.node.navigate.opened.prev = wrap_node(actions.moves.item.fn({ where = "prev", what = "opened" }))
|
||||||
|
|
||||||
|
Api.git.reload = wrap_explorer("reload_git")
|
||||||
|
|
||||||
|
Api.events.subscribe = events.subscribe
|
||||||
|
Api.events.Event = events.Event
|
||||||
|
|
||||||
|
Api.live_filter.start = wrap_explorer_member("live_filter", "start_filtering")
|
||||||
|
Api.live_filter.clear = wrap_explorer_member("live_filter", "clear_filter")
|
||||||
|
|
||||||
|
Api.marks.get = wrap_node(wrap_explorer_member("marks", "get"))
|
||||||
|
Api.marks.list = wrap_explorer_member("marks", "list")
|
||||||
|
Api.marks.toggle = wrap_node(wrap_explorer_member("marks", "toggle"))
|
||||||
|
Api.marks.clear = wrap_explorer_member("marks", "clear")
|
||||||
|
Api.marks.bulk.delete = wrap_explorer_member("marks", "bulk_delete")
|
||||||
|
Api.marks.bulk.trash = wrap_explorer_member("marks", "bulk_trash")
|
||||||
|
Api.marks.bulk.move = wrap_explorer_member("marks", "bulk_move")
|
||||||
|
Api.marks.navigate.next = wrap_explorer_member("marks", "navigate_next")
|
||||||
|
Api.marks.navigate.prev = wrap_explorer_member("marks", "navigate_prev")
|
||||||
|
Api.marks.navigate.select = wrap_explorer_member("marks", "navigate_select")
|
||||||
|
|
||||||
|
Api.config.mappings.get_keymap = wrap(keymap.get_keymap)
|
||||||
|
Api.config.mappings.get_keymap_default = wrap(keymap.get_keymap_default)
|
||||||
|
Api.config.mappings.default_on_attach = keymap.default_on_attach
|
||||||
|
|
||||||
|
Api.diagnostics.hi_test = wrap(appearance_hi_test)
|
||||||
|
|
||||||
|
Api.commands.get = wrap(function()
|
||||||
|
return require("nvim-tree.commands").get()
|
||||||
|
end)
|
||||||
|
|
||||||
|
return Api
|
||||||
140
lua/nvim-tree/appearance/hi-test.lua
Normal file
140
lua/nvim-tree/appearance/hi-test.lua
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
local appearance = require("nvim-tree.appearance")
|
||||||
|
|
||||||
|
local Class = require("nvim-tree.classic")
|
||||||
|
|
||||||
|
-- others with name and links less than this arbitrary value are short
|
||||||
|
local SHORT_LEN = 50
|
||||||
|
|
||||||
|
---@class (exact) HighlightDisplay: Class for :NvimTreeHiTest
|
||||||
|
---@field group string nvim-tree highlight group name
|
||||||
|
---@field links string link chain to a concretely defined group
|
||||||
|
---@field def string :hi concrete definition after following any links
|
||||||
|
local HighlightDisplay = Class:extend()
|
||||||
|
|
||||||
|
---@class HighlightDisplay
|
||||||
|
---@overload fun(args: HighlightDisplayArgs): HighlightDisplay
|
||||||
|
|
||||||
|
---@class (exact) HighlightDisplayArgs
|
||||||
|
---@field group string nvim-tree highlight group name
|
||||||
|
|
||||||
|
---@protected
|
||||||
|
---@param args HighlightDisplayArgs
|
||||||
|
function HighlightDisplay:new(args)
|
||||||
|
self.group = args.group
|
||||||
|
|
||||||
|
local concrete = self.group
|
||||||
|
|
||||||
|
-- maybe follow links
|
||||||
|
local links = {}
|
||||||
|
local link = vim.api.nvim_get_hl(0, { name = self.group }).link
|
||||||
|
while link do
|
||||||
|
table.insert(links, link)
|
||||||
|
concrete = link
|
||||||
|
link = vim.api.nvim_get_hl(0, { name = link }).link
|
||||||
|
end
|
||||||
|
self.links = table.concat(links, " ")
|
||||||
|
|
||||||
|
-- concrete definition
|
||||||
|
local ok, res = pcall(vim.api.nvim_cmd, { cmd = "highlight", args = { concrete } }, { output = true })
|
||||||
|
if ok and type(res) == "string" then
|
||||||
|
self.def = res:gsub(".*xxx *", "")
|
||||||
|
else
|
||||||
|
self.def = ""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Render one group.
|
||||||
|
---@param bufnr number to render in
|
||||||
|
---@param fmt string format string for group, links, def
|
||||||
|
---@param l number line number to render at
|
||||||
|
---@return number l next line number
|
||||||
|
function HighlightDisplay:render(bufnr, fmt, l)
|
||||||
|
local text = string.format(fmt, self.group, self.links, self.def)
|
||||||
|
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, l, -1, true, { text })
|
||||||
|
vim.api.nvim_buf_add_highlight(bufnr, -1, self.group, l, 0, #self.group)
|
||||||
|
|
||||||
|
return l + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
---Render many groups.
|
||||||
|
---@param header string before with underline line
|
||||||
|
---@param displays HighlightDisplay[] highlight group
|
||||||
|
---@param bufnr number to render in
|
||||||
|
---@param l number line number to start at
|
||||||
|
---@return number l next line number
|
||||||
|
local function render_displays(header, displays, bufnr, l)
|
||||||
|
local max_group_len = 0
|
||||||
|
local max_links_len = 0
|
||||||
|
|
||||||
|
-- build all highlight groups, using name only
|
||||||
|
for _, display in ipairs(displays) do
|
||||||
|
max_group_len = math.max(max_group_len, #display.group)
|
||||||
|
max_links_len = math.max(max_links_len, #display.links)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- header
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, l, -1, true, { header, (header:gsub(".", "-")) })
|
||||||
|
l = l + 2
|
||||||
|
|
||||||
|
-- render and highlight
|
||||||
|
local fmt = string.format("%%-%d.%ds %%-%d.%ds %%s", max_group_len, max_group_len, max_links_len, max_links_len)
|
||||||
|
for _, display in ipairs(displays) do
|
||||||
|
l = display:render(bufnr, fmt, l)
|
||||||
|
end
|
||||||
|
|
||||||
|
return l
|
||||||
|
end
|
||||||
|
|
||||||
|
---Run a test similar to :so $VIMRUNTIME/syntax/hitest.vim
|
||||||
|
---Display all nvim-tree and neovim highlight groups, their link chain and actual definition
|
||||||
|
return function()
|
||||||
|
-- create a buffer
|
||||||
|
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
|
|
||||||
|
local l = 0
|
||||||
|
|
||||||
|
-- nvim-tree groups, ordered
|
||||||
|
local displays = {}
|
||||||
|
for _, highlight_group in ipairs(appearance.HIGHLIGHT_GROUPS) do
|
||||||
|
local display = HighlightDisplay({ group = highlight_group.group })
|
||||||
|
table.insert(displays, display)
|
||||||
|
end
|
||||||
|
l = render_displays("nvim-tree", displays, bufnr, l)
|
||||||
|
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, l, -1, true, { "" })
|
||||||
|
l = l + 1
|
||||||
|
|
||||||
|
-- built in groups, ordered opaquely by nvim
|
||||||
|
local displays_short, displays_long = {}, {}
|
||||||
|
local ok, out = pcall(vim.api.nvim_cmd, { cmd = "highlight" }, { output = true })
|
||||||
|
if ok then
|
||||||
|
for group in string.gmatch(out, "(%w*)%s+xxx") do
|
||||||
|
if group:find("NvimTree", 1, true) ~= 1 then
|
||||||
|
local display = HighlightDisplay({ group = group })
|
||||||
|
if #display.group + #display.links > SHORT_LEN then
|
||||||
|
table.insert(displays_long, display)
|
||||||
|
else
|
||||||
|
table.insert(displays_short, display)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- short ones first
|
||||||
|
l = render_displays("other, short", displays_short, bufnr, l)
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, l, -1, true, { "" })
|
||||||
|
l = l + 1
|
||||||
|
|
||||||
|
-- long
|
||||||
|
render_displays("other, long", displays_long, bufnr, l)
|
||||||
|
|
||||||
|
-- finalise and focus the buffer
|
||||||
|
if vim.fn.has("nvim-0.10") == 1 then
|
||||||
|
vim.api.nvim_set_option_value("modifiable", false, { buf = bufnr })
|
||||||
|
else
|
||||||
|
vim.api.nvim_buf_set_option(bufnr, "modifiable", false) ---@diagnostic disable-line: deprecated
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.cmd.buffer(bufnr)
|
||||||
|
end
|
||||||
210
lua/nvim-tree/appearance/init.lua
Normal file
210
lua/nvim-tree/appearance/init.lua
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@class HighlightGroup
|
||||||
|
---@field group string
|
||||||
|
---@field link string|nil
|
||||||
|
---@field def string|nil
|
||||||
|
|
||||||
|
---@type HighlightGroup[]
|
||||||
|
-- All highlight groups: linked or directly defined.
|
||||||
|
-- Please add new groups to help and preserve order.
|
||||||
|
-- Please avoid directly defined groups to preserve accessibility for TUI.
|
||||||
|
M.HIGHLIGHT_GROUPS = {
|
||||||
|
|
||||||
|
-- Standard
|
||||||
|
{ group = "NvimTreeNormal", link = "Normal" },
|
||||||
|
{ group = "NvimTreeNormalFloat", link = "NormalFloat" },
|
||||||
|
{ group = "NvimTreeNormalFloatBorder", link = "FloatBorder" },
|
||||||
|
{ group = "NvimTreeNormalNC", link = "NvimTreeNormal" },
|
||||||
|
|
||||||
|
{ group = "NvimTreeLineNr", link = "LineNr" },
|
||||||
|
{ group = "NvimTreeWinSeparator", link = "WinSeparator" },
|
||||||
|
{ group = "NvimTreeEndOfBuffer", link = "EndOfBuffer" },
|
||||||
|
{ group = "NvimTreePopup", link = "Normal" },
|
||||||
|
{ group = "NvimTreeSignColumn", link = "NvimTreeNormal" },
|
||||||
|
|
||||||
|
{ group = "NvimTreeCursorColumn", link = "CursorColumn" },
|
||||||
|
{ group = "NvimTreeCursorLine", link = "CursorLine" },
|
||||||
|
{ group = "NvimTreeCursorLineNr", link = "CursorLineNr" },
|
||||||
|
|
||||||
|
{ group = "NvimTreeStatusLine", link = "StatusLine" },
|
||||||
|
{ group = "NvimTreeStatusLineNC", link = "StatusLineNC" },
|
||||||
|
|
||||||
|
-- File Text
|
||||||
|
{ group = "NvimTreeExecFile", link = "Question" },
|
||||||
|
{ group = "NvimTreeImageFile", link = "Question" },
|
||||||
|
{ group = "NvimTreeSpecialFile", link = "Title" },
|
||||||
|
{ group = "NvimTreeSymlink", link = "Underlined" },
|
||||||
|
|
||||||
|
-- Folder Text
|
||||||
|
{ group = "NvimTreeRootFolder", link = "Title" },
|
||||||
|
{ group = "NvimTreeFolderName", link = "Directory" },
|
||||||
|
{ group = "NvimTreeEmptyFolderName", link = "Directory" },
|
||||||
|
{ group = "NvimTreeOpenedFolderName", link = "Directory" },
|
||||||
|
{ group = "NvimTreeSymlinkFolderName", link = "Directory" },
|
||||||
|
|
||||||
|
-- File Icons
|
||||||
|
{ group = "NvimTreeFileIcon", link = "NvimTreeNormal" },
|
||||||
|
{ group = "NvimTreeSymlinkIcon", link = "NvimTreeNormal" },
|
||||||
|
|
||||||
|
-- Folder Icons
|
||||||
|
{ group = "NvimTreeFolderIcon", def = "guifg=#8094b4 ctermfg=Blue" },
|
||||||
|
{ group = "NvimTreeOpenedFolderIcon", link = "NvimTreeFolderIcon" },
|
||||||
|
{ group = "NvimTreeClosedFolderIcon", link = "NvimTreeFolderIcon" },
|
||||||
|
{ group = "NvimTreeFolderArrowClosed", link = "NvimTreeIndentMarker" },
|
||||||
|
{ group = "NvimTreeFolderArrowOpen", link = "NvimTreeIndentMarker" },
|
||||||
|
|
||||||
|
-- Indent
|
||||||
|
{ group = "NvimTreeIndentMarker", link = "NvimTreeFolderIcon" },
|
||||||
|
|
||||||
|
-- Picker
|
||||||
|
{ group = "NvimTreeWindowPicker", def = "guifg=#ededed guibg=#4493c8 gui=bold ctermfg=White ctermbg=DarkBlue" },
|
||||||
|
|
||||||
|
-- LiveFilter
|
||||||
|
{ group = "NvimTreeLiveFilterPrefix", link = "PreProc" },
|
||||||
|
{ group = "NvimTreeLiveFilterValue", link = "ModeMsg" },
|
||||||
|
|
||||||
|
-- Clipboard
|
||||||
|
{ group = "NvimTreeCutHL", link = "SpellBad" },
|
||||||
|
{ group = "NvimTreeCopiedHL", link = "SpellRare" },
|
||||||
|
|
||||||
|
-- Bookmark
|
||||||
|
{ group = "NvimTreeBookmarkIcon", link = "NvimTreeFolderIcon" },
|
||||||
|
{ group = "NvimTreeBookmarkHL", link = "SpellLocal" },
|
||||||
|
|
||||||
|
-- Modified
|
||||||
|
{ group = "NvimTreeModifiedIcon", link = "Type" },
|
||||||
|
{ group = "NvimTreeModifiedFileHL", link = "NvimTreeModifiedIcon" },
|
||||||
|
{ group = "NvimTreeModifiedFolderHL", link = "NvimTreeModifiedFileHL" },
|
||||||
|
|
||||||
|
-- Hidden
|
||||||
|
{ group = "NvimTreeHiddenIcon", link = "Conceal" },
|
||||||
|
{ group = "NvimTreeHiddenFileHL", link = "NvimTreeHiddenIcon" },
|
||||||
|
{ group = "NvimTreeHiddenFolderHL", link = "NvimTreeHiddenFileHL" },
|
||||||
|
|
||||||
|
-- Hidden Display
|
||||||
|
{ group = "NvimTreeHiddenDisplay", link = "Conceal" },
|
||||||
|
|
||||||
|
-- Opened
|
||||||
|
{ group = "NvimTreeOpenedHL", link = "Special" },
|
||||||
|
|
||||||
|
-- Git Icon
|
||||||
|
{ group = "NvimTreeGitDeletedIcon", link = "Statement" },
|
||||||
|
{ group = "NvimTreeGitDirtyIcon", link = "Statement" },
|
||||||
|
{ group = "NvimTreeGitIgnoredIcon", link = "Comment" },
|
||||||
|
{ group = "NvimTreeGitMergeIcon", link = "Constant" },
|
||||||
|
{ group = "NvimTreeGitNewIcon", link = "PreProc" },
|
||||||
|
{ group = "NvimTreeGitRenamedIcon", link = "PreProc" },
|
||||||
|
{ group = "NvimTreeGitStagedIcon", link = "Constant" },
|
||||||
|
|
||||||
|
-- Git File Highlight
|
||||||
|
{ group = "NvimTreeGitFileDeletedHL", link = "NvimTreeGitDeletedIcon" },
|
||||||
|
{ group = "NvimTreeGitFileDirtyHL", link = "NvimTreeGitDirtyIcon" },
|
||||||
|
{ group = "NvimTreeGitFileIgnoredHL", link = "NvimTreeGitIgnoredIcon" },
|
||||||
|
{ group = "NvimTreeGitFileMergeHL", link = "NvimTreeGitMergeIcon" },
|
||||||
|
{ group = "NvimTreeGitFileNewHL", link = "NvimTreeGitNewIcon" },
|
||||||
|
{ group = "NvimTreeGitFileRenamedHL", link = "NvimTreeGitRenamedIcon" },
|
||||||
|
{ group = "NvimTreeGitFileStagedHL", link = "NvimTreeGitStagedIcon" },
|
||||||
|
|
||||||
|
-- Git Folder Highlight
|
||||||
|
{ group = "NvimTreeGitFolderDeletedHL", link = "NvimTreeGitFileDeletedHL" },
|
||||||
|
{ group = "NvimTreeGitFolderDirtyHL", link = "NvimTreeGitFileDirtyHL" },
|
||||||
|
{ group = "NvimTreeGitFolderIgnoredHL", link = "NvimTreeGitFileIgnoredHL" },
|
||||||
|
{ group = "NvimTreeGitFolderMergeHL", link = "NvimTreeGitFileMergeHL" },
|
||||||
|
{ group = "NvimTreeGitFolderNewHL", link = "NvimTreeGitFileNewHL" },
|
||||||
|
{ group = "NvimTreeGitFolderRenamedHL", link = "NvimTreeGitFileRenamedHL" },
|
||||||
|
{ group = "NvimTreeGitFolderStagedHL", link = "NvimTreeGitFileStagedHL" },
|
||||||
|
|
||||||
|
-- Diagnostics Icon
|
||||||
|
{ group = "NvimTreeDiagnosticErrorIcon", link = "DiagnosticError" },
|
||||||
|
{ group = "NvimTreeDiagnosticWarnIcon", link = "DiagnosticWarn" },
|
||||||
|
{ group = "NvimTreeDiagnosticInfoIcon", link = "DiagnosticInfo" },
|
||||||
|
{ group = "NvimTreeDiagnosticHintIcon", link = "DiagnosticHint" },
|
||||||
|
|
||||||
|
-- Diagnostics File Highlight
|
||||||
|
{ group = "NvimTreeDiagnosticErrorFileHL", link = "DiagnosticUnderlineError" },
|
||||||
|
{ group = "NvimTreeDiagnosticWarnFileHL", link = "DiagnosticUnderlineWarn" },
|
||||||
|
{ group = "NvimTreeDiagnosticInfoFileHL", link = "DiagnosticUnderlineInfo" },
|
||||||
|
{ group = "NvimTreeDiagnosticHintFileHL", link = "DiagnosticUnderlineHint" },
|
||||||
|
|
||||||
|
-- Diagnostics Folder Highlight
|
||||||
|
{ group = "NvimTreeDiagnosticErrorFolderHL", link = "NvimTreeDiagnosticErrorFileHL" },
|
||||||
|
{ group = "NvimTreeDiagnosticWarnFolderHL", link = "NvimTreeDiagnosticWarnFileHL" },
|
||||||
|
{ group = "NvimTreeDiagnosticInfoFolderHL", link = "NvimTreeDiagnosticInfoFileHL" },
|
||||||
|
{ group = "NvimTreeDiagnosticHintFolderHL", link = "NvimTreeDiagnosticHintFileHL" },
|
||||||
|
}
|
||||||
|
|
||||||
|
-- nvim-tree highlight groups to legacy
|
||||||
|
M.LEGACY_LINKS = {
|
||||||
|
NvimTreeModifiedIcon = "NvimTreeModifiedFile",
|
||||||
|
|
||||||
|
NvimTreeOpenedHL = "NvimTreeOpenedFile",
|
||||||
|
|
||||||
|
NvimTreeBookmarkIcon = "NvimTreeBookmark",
|
||||||
|
|
||||||
|
NvimTreeGitDeletedIcon = "NvimTreeGitDeleted",
|
||||||
|
NvimTreeGitDirtyIcon = "NvimTreeGitDirty",
|
||||||
|
NvimTreeGitIgnoredIcon = "NvimTreeGitIgnored",
|
||||||
|
NvimTreeGitMergeIcon = "NvimTreeGitMerge",
|
||||||
|
NvimTreeGitNewIcon = "NvimTreeGitNew",
|
||||||
|
NvimTreeGitRenamedIcon = "NvimTreeGitRenamed",
|
||||||
|
NvimTreeGitStagedIcon = "NvimTreeGitStaged",
|
||||||
|
|
||||||
|
NvimTreeGitFileDeletedHL = "NvimTreeFileDeleted",
|
||||||
|
NvimTreeGitFileDirtyHL = "NvimTreeFileDirty",
|
||||||
|
NvimTreeGitFileIgnoredHL = "NvimTreeFileIgnored",
|
||||||
|
NvimTreeGitFileMergeHL = "NvimTreeFileMerge",
|
||||||
|
NvimTreeGitFileNewHL = "NvimTreeFileNew",
|
||||||
|
NvimTreeGitFileRenamedHL = "NvimTreeFileRenamed",
|
||||||
|
NvimTreeGitFileStagedHL = "NvimTreeFileStaged",
|
||||||
|
|
||||||
|
NvimTreeGitFolderDeletedHL = "NvimTreeFolderDeleted",
|
||||||
|
NvimTreeGitFolderDirtyHL = "NvimTreeFolderDirty",
|
||||||
|
NvimTreeGitFolderIgnoredHL = "NvimTreeFolderIgnored",
|
||||||
|
NvimTreeGitFolderMergeHL = "NvimTreeFolderMerge",
|
||||||
|
NvimTreeGitFolderNewHL = "NvimTreeFolderNew",
|
||||||
|
NvimTreeGitFolderRenamedHL = "NvimTreeFolderRenamed",
|
||||||
|
NvimTreeGitFolderStagedHL = "NvimTreeFolderStaged",
|
||||||
|
|
||||||
|
NvimTreeDiagnosticErrorIcon = "NvimTreeLspDiagnosticsError",
|
||||||
|
NvimTreeDiagnosticWarnIcon = "NvimTreeLspDiagnosticsWarning",
|
||||||
|
NvimTreeDiagnosticInfoIcon = "NvimTreeLspDiagnosticsInformation",
|
||||||
|
NvimTreeDiagnosticHintIcon = "NvimTreeLspDiagnosticsHint",
|
||||||
|
|
||||||
|
NvimTreeDiagnosticErrorFileHL = "NvimTreeLspDiagnosticsErrorText",
|
||||||
|
NvimTreeDiagnosticWarnFileHL = "NvimTreeLspDiagnosticsWarningText",
|
||||||
|
NvimTreeDiagnosticInfoFileHL = "NvimTreeLspDiagnosticsInformationText",
|
||||||
|
NvimTreeDiagnosticHintFileHL = "NvimTreeLspDiagnosticsHintText",
|
||||||
|
|
||||||
|
NvimTreeDiagnosticErrorFolderHL = "NvimTreeLspDiagnosticsErrorFolderText",
|
||||||
|
NvimTreeDiagnosticWarnFolderHL = "NvimTreeLspDiagnosticsWarningFolderText",
|
||||||
|
NvimTreeDiagnosticInfoFolderHL = "NvimTreeLspDiagnosticsInformationFolderText",
|
||||||
|
NvimTreeDiagnosticHintFolderHL = "NvimTreeLspDiagnosticsHintFolderText",
|
||||||
|
}
|
||||||
|
|
||||||
|
function M.setup()
|
||||||
|
-- non-linked
|
||||||
|
for _, g in ipairs(M.HIGHLIGHT_GROUPS) do
|
||||||
|
if g.def then
|
||||||
|
vim.api.nvim_command("hi def " .. g.group .. " " .. g.def)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- hard link override when legacy only is present
|
||||||
|
for from, to in pairs(M.LEGACY_LINKS) do
|
||||||
|
local hl_from = vim.api.nvim_get_hl(0, { name = from })
|
||||||
|
local hl_to = vim.api.nvim_get_hl(0, { name = to })
|
||||||
|
if vim.tbl_isempty(hl_from) and not vim.tbl_isempty(hl_to) then
|
||||||
|
vim.api.nvim_command("hi link " .. from .. " " .. to)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- default links
|
||||||
|
for _, g in ipairs(M.HIGHLIGHT_GROUPS) do
|
||||||
|
if g.link then
|
||||||
|
vim.api.nvim_command("hi def link " .. g.group .. " " .. g.link)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
65
lua/nvim-tree/buffers.lua
Normal file
65
lua/nvim-tree/buffers.lua
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
local DirectoryNode = require("nvim-tree.node.directory")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@type table<string, boolean> record of which file is modified
|
||||||
|
M._modified = {}
|
||||||
|
|
||||||
|
---refresh M._modified
|
||||||
|
function M.reload_modified()
|
||||||
|
M._modified = {}
|
||||||
|
local bufs = vim.fn.getbufinfo({ bufmodified = 1, buflisted = 1 })
|
||||||
|
for _, buf in pairs(bufs) do
|
||||||
|
local path = buf.name
|
||||||
|
if path ~= "" then -- not a [No Name] buffer
|
||||||
|
-- mark all the parent as modified as well
|
||||||
|
while M._modified[path] ~= true do
|
||||||
|
-- no need to keep going if already recorded
|
||||||
|
-- This also prevents an infinite loop
|
||||||
|
M._modified[path] = true
|
||||||
|
path = vim.fn.fnamemodify(path, ":h")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
---@return boolean
|
||||||
|
function M.is_modified(node)
|
||||||
|
if not M.config.modified.enable then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if not M._modified[node.absolute_path] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local dir = node:as(DirectoryNode)
|
||||||
|
if dir then
|
||||||
|
if not M.config.modified.show_on_dirs then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if dir.open and not M.config.modified.show_on_open_dirs then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
---A buffer exists for the node's absolute path
|
||||||
|
---@param node Node
|
||||||
|
---@return boolean
|
||||||
|
function M.is_opened(node)
|
||||||
|
return node and vim.fn.bufloaded(node.absolute_path) > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param opts table
|
||||||
|
function M.setup(opts)
|
||||||
|
M.config = {
|
||||||
|
modified = opts.modified,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
91
lua/nvim-tree/classic.lua
Normal file
91
lua/nvim-tree/classic.lua
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
--
|
||||||
|
-- classic
|
||||||
|
--
|
||||||
|
-- Copyright (c) 2014, rxi
|
||||||
|
--
|
||||||
|
-- This module is free software; you can redistribute it and/or modify it under
|
||||||
|
-- the terms of the MIT license. See LICENSE for details.
|
||||||
|
--
|
||||||
|
-- https://github.com/rxi/classic
|
||||||
|
--
|
||||||
|
|
||||||
|
---@class (exact) Class
|
||||||
|
---@field super Class
|
||||||
|
---@field private implements table<Class, boolean>
|
||||||
|
local Class = {}
|
||||||
|
Class.__index = Class ---@diagnostic disable-line: inject-field
|
||||||
|
|
||||||
|
---Default constructor
|
||||||
|
---@protected
|
||||||
|
function Class:new(...) --luacheck: ignore 212
|
||||||
|
end
|
||||||
|
|
||||||
|
---Extend a class, setting .super
|
||||||
|
function Class:extend()
|
||||||
|
local cls = {}
|
||||||
|
for k, v in pairs(self) do
|
||||||
|
if k:find("__") == 1 then
|
||||||
|
cls[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
cls.__index = cls
|
||||||
|
cls.super = self
|
||||||
|
setmetatable(cls, self)
|
||||||
|
return cls
|
||||||
|
end
|
||||||
|
|
||||||
|
---Implement the functions of a mixin
|
||||||
|
---Add the mixin to .implements
|
||||||
|
---@param mixin Class
|
||||||
|
function Class:implement(mixin)
|
||||||
|
if not rawget(self, "implements") then
|
||||||
|
-- set on the class itself instead of parents
|
||||||
|
rawset(self, "implements", {})
|
||||||
|
end
|
||||||
|
self.implements[mixin] = true
|
||||||
|
for k, v in pairs(mixin) do
|
||||||
|
if self[k] == nil and type(v) == "function" then
|
||||||
|
self[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Object is an instance of class or implements a mixin
|
||||||
|
---@generic T
|
||||||
|
---@param class T
|
||||||
|
---@return boolean
|
||||||
|
function Class:is(class)
|
||||||
|
local mt = getmetatable(self)
|
||||||
|
while mt do
|
||||||
|
if mt == class then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if mt.implements and mt.implements[class] then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
mt = getmetatable(mt)
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
---Return object if :is otherwise nil
|
||||||
|
---@generic T
|
||||||
|
---@param class T
|
||||||
|
---@return T|nil
|
||||||
|
function Class:as(class)
|
||||||
|
return self:is(class) and self or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
---Constructor to create instance, call :new and return
|
||||||
|
function Class:__call(...)
|
||||||
|
local obj = setmetatable({}, self)
|
||||||
|
obj:new(...)
|
||||||
|
return obj
|
||||||
|
end
|
||||||
|
|
||||||
|
-- avoid unused param warnings in abstract methods
|
||||||
|
---@param ... any
|
||||||
|
function Class:nop(...) --luacheck: ignore 212
|
||||||
|
end
|
||||||
|
|
||||||
|
return Class
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
local api = vim.api
|
|
||||||
local icons = require "nvim-tree.renderer.icon-config"
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
local function get_color_from_hl(hl_name, fallback)
|
|
||||||
local id = vim.api.nvim_get_hl_id_by_name(hl_name)
|
|
||||||
if not id then
|
|
||||||
return fallback
|
|
||||||
end
|
|
||||||
|
|
||||||
local foreground = vim.fn.synIDattr(vim.fn.synIDtrans(id), "fg")
|
|
||||||
if not foreground or foreground == "" then
|
|
||||||
return fallback
|
|
||||||
end
|
|
||||||
|
|
||||||
return foreground
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_colors()
|
|
||||||
return {
|
|
||||||
red = vim.g.terminal_color_1 or get_color_from_hl("Keyword", "Red"),
|
|
||||||
green = vim.g.terminal_color_2 or get_color_from_hl("Character", "Green"),
|
|
||||||
yellow = vim.g.terminal_color_3 or get_color_from_hl("PreProc", "Yellow"),
|
|
||||||
blue = vim.g.terminal_color_4 or get_color_from_hl("Include", "Blue"),
|
|
||||||
purple = vim.g.terminal_color_5 or get_color_from_hl("Define", "Purple"),
|
|
||||||
cyan = vim.g.terminal_color_6 or get_color_from_hl("Conditional", "Cyan"),
|
|
||||||
dark_red = vim.g.terminal_color_9 or get_color_from_hl("Keyword", "DarkRed"),
|
|
||||||
orange = vim.g.terminal_color_11 or get_color_from_hl("Number", "Orange"),
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_hl_groups()
|
|
||||||
local colors = get_colors()
|
|
||||||
|
|
||||||
return {
|
|
||||||
IndentMarker = { fg = "#8094b4" },
|
|
||||||
Symlink = { gui = "bold", fg = colors.cyan },
|
|
||||||
FolderIcon = { fg = "#8094b4" },
|
|
||||||
RootFolder = { fg = colors.purple },
|
|
||||||
|
|
||||||
ExecFile = { gui = "bold", fg = colors.green },
|
|
||||||
SpecialFile = { gui = "bold,underline", fg = colors.yellow },
|
|
||||||
ImageFile = { gui = "bold", fg = colors.purple },
|
|
||||||
OpenedFile = { gui = "bold", fg = colors.green },
|
|
||||||
|
|
||||||
GitDirty = { fg = colors.dark_red },
|
|
||||||
GitDeleted = { fg = colors.dark_red },
|
|
||||||
GitStaged = { fg = colors.green },
|
|
||||||
GitMerge = { fg = colors.orange },
|
|
||||||
GitRenamed = { fg = colors.purple },
|
|
||||||
GitNew = { fg = colors.yellow },
|
|
||||||
|
|
||||||
WindowPicker = { gui = "bold", fg = "#ededed", bg = "#4493c8" },
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_links()
|
|
||||||
return {
|
|
||||||
FolderName = "Directory",
|
|
||||||
EmptyFolderName = "Directory",
|
|
||||||
OpenedFolderName = "Directory",
|
|
||||||
Normal = "Normal",
|
|
||||||
NormalNC = "NvimTreeNormal",
|
|
||||||
EndOfBuffer = "EndOfBuffer",
|
|
||||||
CursorLine = "CursorLine",
|
|
||||||
VertSplit = "VertSplit",
|
|
||||||
WinSeparator = "NvimTreeVertSplit",
|
|
||||||
CursorColumn = "CursorColumn",
|
|
||||||
FileDirty = "NvimTreeGitDirty",
|
|
||||||
FileNew = "NvimTreeGitNew",
|
|
||||||
FileRenamed = "NvimTreeGitRenamed",
|
|
||||||
FileMerge = "NvimTreeGitMerge",
|
|
||||||
FileStaged = "NvimTreeGitStaged",
|
|
||||||
FileDeleted = "NvimTreeGitDeleted",
|
|
||||||
Popup = "Normal",
|
|
||||||
GitIgnored = "Comment",
|
|
||||||
StatusLine = "StatusLine",
|
|
||||||
StatusLineNC = "StatusLineNC",
|
|
||||||
SignColumn = "NvimTreeNormal",
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.setup()
|
|
||||||
if icons.get_config().show_file_icon and icons.get_config().has_devicons then
|
|
||||||
require("nvim-web-devicons").setup()
|
|
||||||
end
|
|
||||||
local higlight_groups = get_hl_groups()
|
|
||||||
for k, d in pairs(higlight_groups) do
|
|
||||||
local gui = d.gui and " gui=" .. d.gui or ""
|
|
||||||
local fg = d.fg and " guifg=" .. d.fg or ""
|
|
||||||
local bg = d.bg and " guibg=" .. d.bg or ""
|
|
||||||
api.nvim_command("hi def NvimTree" .. k .. gui .. fg .. bg)
|
|
||||||
end
|
|
||||||
|
|
||||||
local links = get_links()
|
|
||||||
for k, d in pairs(links) do
|
|
||||||
api.nvim_command("hi def link NvimTree" .. k .. " " .. d)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
157
lua/nvim-tree/commands.lua
Normal file
157
lua/nvim-tree/commands.lua
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
local api = require("nvim-tree.api")
|
||||||
|
local view = require("nvim-tree.view")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local CMDS = {
|
||||||
|
{
|
||||||
|
name = "NvimTreeOpen",
|
||||||
|
opts = {
|
||||||
|
desc = "nvim-tree: open",
|
||||||
|
nargs = "?",
|
||||||
|
complete = "dir",
|
||||||
|
},
|
||||||
|
command = function(c)
|
||||||
|
api.tree.open({ path = c.args })
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "NvimTreeClose",
|
||||||
|
opts = {
|
||||||
|
desc = "nvim-tree: close",
|
||||||
|
bar = true,
|
||||||
|
},
|
||||||
|
command = function()
|
||||||
|
api.tree.close()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "NvimTreeToggle",
|
||||||
|
opts = {
|
||||||
|
desc = "nvim-tree: toggle",
|
||||||
|
nargs = "?",
|
||||||
|
complete = "dir",
|
||||||
|
},
|
||||||
|
command = function(c)
|
||||||
|
api.tree.toggle({
|
||||||
|
find_file = false,
|
||||||
|
focus = true,
|
||||||
|
path = c.args,
|
||||||
|
update_root = false,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "NvimTreeFocus",
|
||||||
|
opts = {
|
||||||
|
desc = "nvim-tree: focus",
|
||||||
|
bar = true,
|
||||||
|
},
|
||||||
|
command = function()
|
||||||
|
api.tree.open()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "NvimTreeRefresh",
|
||||||
|
opts = {
|
||||||
|
desc = "nvim-tree: refresh",
|
||||||
|
bar = true,
|
||||||
|
},
|
||||||
|
command = function()
|
||||||
|
api.tree.reload()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "NvimTreeClipboard",
|
||||||
|
opts = {
|
||||||
|
desc = "nvim-tree: print clipboard",
|
||||||
|
bar = true,
|
||||||
|
},
|
||||||
|
command = function()
|
||||||
|
api.fs.print_clipboard()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "NvimTreeFindFile",
|
||||||
|
opts = {
|
||||||
|
desc = "nvim-tree: find file",
|
||||||
|
bang = true,
|
||||||
|
bar = true,
|
||||||
|
},
|
||||||
|
command = function(c)
|
||||||
|
api.tree.find_file({
|
||||||
|
open = true,
|
||||||
|
focus = true,
|
||||||
|
update_root = c.bang,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "NvimTreeFindFileToggle",
|
||||||
|
opts = {
|
||||||
|
desc = "nvim-tree: find file, toggle",
|
||||||
|
bang = true,
|
||||||
|
nargs = "?",
|
||||||
|
complete = "dir",
|
||||||
|
},
|
||||||
|
command = function(c)
|
||||||
|
api.tree.toggle({
|
||||||
|
find_file = true,
|
||||||
|
focus = true,
|
||||||
|
path = c.args,
|
||||||
|
update_root = c.bang,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "NvimTreeResize",
|
||||||
|
opts = {
|
||||||
|
desc = "nvim-tree: resize",
|
||||||
|
nargs = 1,
|
||||||
|
bar = true,
|
||||||
|
},
|
||||||
|
command = function(c)
|
||||||
|
view.resize(c.args)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "NvimTreeCollapse",
|
||||||
|
opts = {
|
||||||
|
desc = "nvim-tree: collapse",
|
||||||
|
bar = true,
|
||||||
|
},
|
||||||
|
command = function()
|
||||||
|
api.tree.collapse_all(false)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "NvimTreeCollapseKeepBuffers",
|
||||||
|
opts = {
|
||||||
|
desc = "nvim-tree: collapse, keep directories open",
|
||||||
|
bar = true,
|
||||||
|
},
|
||||||
|
command = function()
|
||||||
|
api.tree.collapse_all(true)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "NvimTreeHiTest",
|
||||||
|
opts = {
|
||||||
|
desc = "nvim-tree: highlight test",
|
||||||
|
},
|
||||||
|
command = api.diagnostics.hi_test,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function M.get()
|
||||||
|
return vim.deepcopy(CMDS)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup()
|
||||||
|
for _, cmd in ipairs(CMDS) do
|
||||||
|
local opts = vim.tbl_extend("force", cmd.opts, { force = true })
|
||||||
|
vim.api.nvim_create_user_command(cmd.name, cmd.command, opts)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
-- INFO: DEPRECATED FILE, DO NOT ADD ANYTHING IN THERE
|
|
||||||
-- keeping to avoid breaking user configs. Will remove during a weekend.
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
-- TODO: remove this once the cb property is not supported in mappings
|
|
||||||
function M.nvim_tree_callback(callback_name)
|
|
||||||
return string.format(":lua require'nvim-tree.actions'.on_keypress('%s')<CR>", callback_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -1,33 +1,66 @@
|
|||||||
local events = require "nvim-tree.events"
|
local events = require("nvim-tree.events")
|
||||||
local explorer = require "nvim-tree.explorer"
|
local notify = require("nvim-tree.notify")
|
||||||
local view = require "nvim-tree.view"
|
local view = require("nvim-tree.view")
|
||||||
|
local log = require("nvim-tree.log")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
TreeExplorer = nil
|
---@type Explorer|nil
|
||||||
|
local TreeExplorer = nil
|
||||||
local first_init_done = false
|
local first_init_done = false
|
||||||
|
|
||||||
|
---@param foldername string
|
||||||
function M.init(foldername)
|
function M.init(foldername)
|
||||||
TreeExplorer = explorer.Explorer.new(foldername)
|
local profile = log.profile_start("core init %s", foldername)
|
||||||
|
|
||||||
|
if TreeExplorer then
|
||||||
|
TreeExplorer:destroy()
|
||||||
|
end
|
||||||
|
|
||||||
|
local err, path
|
||||||
|
|
||||||
|
if foldername then
|
||||||
|
path, err = vim.loop.fs_realpath(foldername)
|
||||||
|
else
|
||||||
|
path, err = vim.loop.cwd()
|
||||||
|
end
|
||||||
|
if path then
|
||||||
|
TreeExplorer = require("nvim-tree.explorer")({ path = path })
|
||||||
|
else
|
||||||
|
notify.error(err)
|
||||||
|
TreeExplorer = nil
|
||||||
|
end
|
||||||
|
|
||||||
if not first_init_done then
|
if not first_init_done then
|
||||||
events._dispatch_ready()
|
events._dispatch_ready()
|
||||||
first_init_done = true
|
first_init_done = true
|
||||||
end
|
end
|
||||||
|
log.profile_end(profile)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return Explorer|nil
|
||||||
function M.get_explorer()
|
function M.get_explorer()
|
||||||
return TreeExplorer
|
return TreeExplorer
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.get_cwd()
|
function M.reset_explorer()
|
||||||
return TreeExplorer.cwd
|
TreeExplorer = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return string|nil
|
||||||
|
function M.get_cwd()
|
||||||
|
return TreeExplorer and TreeExplorer.absolute_path
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return integer
|
||||||
function M.get_nodes_starting_line()
|
function M.get_nodes_starting_line()
|
||||||
local offset = 1
|
local offset = 1
|
||||||
if view.is_root_folder_visible(M.get_cwd()) then
|
if view.is_root_folder_visible(M.get_cwd()) then
|
||||||
offset = offset + 1
|
offset = offset + 1
|
||||||
end
|
end
|
||||||
|
if TreeExplorer and TreeExplorer.live_filter.filter then
|
||||||
|
return offset + 1
|
||||||
|
end
|
||||||
return offset
|
return offset
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,177 +1,233 @@
|
|||||||
local a = vim.api
|
local core = require("nvim-tree.core")
|
||||||
local utils = require "nvim-tree.utils"
|
local utils = require("nvim-tree.utils")
|
||||||
local view = require "nvim-tree.view"
|
local view = require("nvim-tree.view")
|
||||||
local core = require "nvim-tree.core"
|
local log = require("nvim-tree.log")
|
||||||
local log = require "nvim-tree.log"
|
|
||||||
|
local DirectoryNode = require("nvim-tree.node.directory")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local GROUP = "NvimTreeDiagnosticSigns"
|
---COC severity level strings to LSP severity levels
|
||||||
|
---@enum COC_SEVERITY_LEVELS
|
||||||
local function get_lowest_severity(diagnostics)
|
local COC_SEVERITY_LEVELS = {
|
||||||
local severity = math.huge
|
Error = 1,
|
||||||
for _, v in ipairs(diagnostics) do
|
Warning = 2,
|
||||||
if v.severity < severity then
|
Information = 3,
|
||||||
severity = v.severity
|
Hint = 4,
|
||||||
end
|
|
||||||
end
|
|
||||||
return severity
|
|
||||||
end
|
|
||||||
|
|
||||||
local severity_levels = { Error = 1, Warning = 2, Information = 3, Hint = 4 }
|
|
||||||
local sign_names = {
|
|
||||||
{ "NvimTreeSignError", "NvimTreeLspDiagnosticsError" },
|
|
||||||
{ "NvimTreeSignWarning", "NvimTreeLspDiagnosticsWarning" },
|
|
||||||
{ "NvimTreeSignInformation", "NvimTreeLspDiagnosticsInformation" },
|
|
||||||
{ "NvimTreeSignHint", "NvimTreeLspDiagnosticsHint" },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
local function add_sign(linenr, severity)
|
---Absolute Node path to LSP severity level
|
||||||
local buf = view.get_bufnr()
|
---@alias NodeSeverities table<string, lsp.DiagnosticSeverity>
|
||||||
if not a.nvim_buf_is_valid(buf) or not a.nvim_buf_is_loaded(buf) then
|
|
||||||
return
|
---@class DiagStatus
|
||||||
end
|
---@field value lsp.DiagnosticSeverity|nil
|
||||||
local sign_name = sign_names[severity][1]
|
---@field cache_version integer
|
||||||
vim.fn.sign_place(1, GROUP, sign_name, buf, { lnum = linenr })
|
|
||||||
|
--- The buffer-severity mappings derived during the last diagnostic list update.
|
||||||
|
---@type NodeSeverities
|
||||||
|
local NODE_SEVERITIES = {}
|
||||||
|
|
||||||
|
---The cache version number of the buffer-severity mappings.
|
||||||
|
---@type integer
|
||||||
|
local NODE_SEVERITIES_VERSION = 0
|
||||||
|
|
||||||
|
---@param path string
|
||||||
|
---@return string
|
||||||
|
local function uniformize_path(path)
|
||||||
|
return utils.canonical_path(path:gsub("\\", "/"))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Marshal severities from LSP. Does nothing when LSP disabled.
|
||||||
|
---@return NodeSeverities
|
||||||
local function from_nvim_lsp()
|
local function from_nvim_lsp()
|
||||||
local buffer_severity = {}
|
local buffer_severity = {}
|
||||||
|
|
||||||
-- vim.lsp.diagnostic.get_all was deprecated in nvim 0.7 and replaced with vim.diagnostic.get
|
-- is_enabled is not present in all 0.10 builds/releases, see #2781
|
||||||
-- This conditional can be removed when the minimum required version of nvim is changed to 0.7.
|
local is_enabled = false
|
||||||
if vim.diagnostic then
|
if vim.fn.has("nvim-0.10") == 1 and type(vim.diagnostic.is_enabled) == "function" then
|
||||||
-- nvim version >= 0.7
|
is_enabled = vim.diagnostic.is_enabled()
|
||||||
for _, diagnostic in ipairs(vim.diagnostic.get()) do
|
elseif type(vim.diagnostic.is_disabled) == "function" then ---@diagnostic disable-line: deprecated
|
||||||
local buf = diagnostic.bufnr
|
is_enabled = not vim.diagnostic.is_disabled() ---@diagnostic disable-line: deprecated
|
||||||
if a.nvim_buf_is_valid(buf) then
|
end
|
||||||
local bufname = a.nvim_buf_get_name(buf)
|
|
||||||
local lowest_severity = buffer_severity[bufname]
|
if is_enabled then
|
||||||
if not lowest_severity or diagnostic.severity < lowest_severity then
|
for _, diagnostic in ipairs(vim.diagnostic.get(nil, { severity = M.severity })) do
|
||||||
|
if diagnostic.severity and diagnostic.bufnr and vim.api.nvim_buf_is_valid(diagnostic.bufnr) then
|
||||||
|
local bufname = uniformize_path(vim.api.nvim_buf_get_name(diagnostic.bufnr))
|
||||||
|
if not buffer_severity[bufname] or diagnostic.severity < buffer_severity[bufname] then
|
||||||
buffer_severity[bufname] = diagnostic.severity
|
buffer_severity[bufname] = diagnostic.severity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
|
||||||
-- nvim version < 0.7
|
|
||||||
for buf, diagnostics in pairs(vim.lsp.diagnostic.get_all()) do
|
|
||||||
if a.nvim_buf_is_valid(buf) then
|
|
||||||
local bufname = a.nvim_buf_get_name(buf)
|
|
||||||
if not buffer_severity[bufname] then
|
|
||||||
local severity = get_lowest_severity(diagnostics)
|
|
||||||
buffer_severity[bufname] = severity
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return buffer_severity
|
return buffer_severity
|
||||||
end
|
end
|
||||||
|
|
||||||
local function from_coc()
|
---Severity is within diagnostics.severity.min, diagnostics.severity.max
|
||||||
if vim.g.coc_service_initialized ~= 1 then
|
---@param severity lsp.DiagnosticSeverity
|
||||||
return {}
|
---@param config table
|
||||||
end
|
---@return boolean
|
||||||
|
local function is_severity_in_range(severity, config)
|
||||||
local diagnostic_list = vim.fn.CocAction "diagnosticList"
|
return config.max <= severity and severity <= config.min
|
||||||
if type(diagnostic_list) ~= "table" or vim.tbl_isempty(diagnostic_list) then
|
|
||||||
return {}
|
|
||||||
end
|
|
||||||
|
|
||||||
local buffer_severity = {}
|
|
||||||
local diagnostics = {}
|
|
||||||
|
|
||||||
for _, diagnostic in ipairs(diagnostic_list) do
|
|
||||||
local bufname = diagnostic.file
|
|
||||||
local severity = severity_levels[diagnostic.severity]
|
|
||||||
|
|
||||||
local severity_list = diagnostics[bufname] or {}
|
|
||||||
table.insert(severity_list, severity)
|
|
||||||
diagnostics[bufname] = severity_list
|
|
||||||
end
|
|
||||||
|
|
||||||
for bufname, severity_list in pairs(diagnostics) do
|
|
||||||
if not buffer_severity[bufname] then
|
|
||||||
local severity = math.min(unpack(severity_list))
|
|
||||||
buffer_severity[bufname] = severity
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return buffer_severity
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Handle any COC exceptions, preventing any propagation
|
||||||
|
---@param err string
|
||||||
|
local function handle_coc_exception(err)
|
||||||
|
log.line("diagnostics", "handle_coc_exception: %s", vim.inspect(err))
|
||||||
|
local notify = true
|
||||||
|
|
||||||
|
-- avoid distractions on interrupts (CTRL-C)
|
||||||
|
if err:find("Vim:Interrupt") or err:find("Keyboard interrupt") then
|
||||||
|
notify = false
|
||||||
|
end
|
||||||
|
|
||||||
|
if notify then
|
||||||
|
require("nvim-tree.notify").error("Diagnostics update from coc.nvim failed. " .. vim.inspect(err))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---COC service initialized
|
||||||
|
---@return boolean
|
||||||
local function is_using_coc()
|
local function is_using_coc()
|
||||||
return vim.g.coc_service_initialized == 1
|
return vim.g.coc_service_initialized == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.clear()
|
---Marshal severities from COC. Does nothing when COC service not started.
|
||||||
if not M.enable or not view.is_buf_valid(view.get_bufnr()) then
|
---@return NodeSeverities
|
||||||
return
|
local function from_coc()
|
||||||
|
if not is_using_coc() then
|
||||||
|
return {}
|
||||||
end
|
end
|
||||||
|
|
||||||
vim.fn.sign_unplace(GROUP)
|
local ok, diagnostic_list = xpcall(function()
|
||||||
|
return vim.fn.CocAction("diagnosticList")
|
||||||
|
end, handle_coc_exception)
|
||||||
|
if not ok or type(diagnostic_list) ~= "table" or vim.tbl_isempty(diagnostic_list) then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local buffer_severity = {}
|
||||||
|
for _, diagnostic in ipairs(diagnostic_list) do
|
||||||
|
local bufname = uniformize_path(diagnostic.file)
|
||||||
|
local coc_severity = COC_SEVERITY_LEVELS[diagnostic.severity]
|
||||||
|
local highest_severity = buffer_severity[bufname] or coc_severity
|
||||||
|
if is_severity_in_range(highest_severity, M.severity) then
|
||||||
|
buffer_severity[bufname] = math.min(highest_severity, coc_severity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return buffer_severity
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.update()
|
---Maybe retrieve severity level from the cache
|
||||||
if not M.enable or not core.get_explorer() or not view.is_buf_valid(view.get_bufnr()) then
|
---@param node Node
|
||||||
return
|
---@return DiagStatus
|
||||||
end
|
local function from_cache(node)
|
||||||
local ps = log.profile_start "diagnostics update"
|
local nodepath = uniformize_path(node.absolute_path)
|
||||||
log.line("diagnostics", "update")
|
local max_severity = nil
|
||||||
|
if not node:is(DirectoryNode) then
|
||||||
local buffer_severity
|
-- direct cache hit for files
|
||||||
if is_using_coc() then
|
max_severity = NODE_SEVERITIES[nodepath]
|
||||||
buffer_severity = from_coc()
|
|
||||||
else
|
else
|
||||||
buffer_severity = from_nvim_lsp()
|
-- dirs should be searched in the list of cached buffer names by prefix
|
||||||
end
|
for bufname, severity in pairs(NODE_SEVERITIES) do
|
||||||
|
local node_contains_buf = vim.startswith(bufname, nodepath .. "/")
|
||||||
M.clear()
|
if node_contains_buf then
|
||||||
for bufname, severity in pairs(buffer_severity) do
|
if severity == M.severity.max then
|
||||||
local bufpath = utils.canonical_path(bufname)
|
max_severity = severity
|
||||||
log.line("diagnostics", " bufpath '%s' severity %d", bufpath, severity)
|
break
|
||||||
if 0 < severity and severity < 5 then
|
else
|
||||||
local nodes_by_line = utils.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line())
|
max_severity = math.min(max_severity or severity, severity)
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
log.profile_end(ps, "diagnostics update")
|
return { value = max_severity, cache_version = NODE_SEVERITIES_VERSION }
|
||||||
end
|
end
|
||||||
|
|
||||||
local links = {
|
---Fired on DiagnosticChanged and CocDiagnosticChanged events:
|
||||||
NvimTreeLspDiagnosticsError = "DiagnosticError",
|
---debounced retrieval, cache update, version increment and draw
|
||||||
NvimTreeLspDiagnosticsWarning = "DiagnosticWarn",
|
function M.update()
|
||||||
NvimTreeLspDiagnosticsInformation = "DiagnosticInfo",
|
if not M.enable then
|
||||||
NvimTreeLspDiagnosticsHint = "DiagnosticHint",
|
return
|
||||||
}
|
end
|
||||||
|
utils.debounce("diagnostics", M.debounce_delay, function()
|
||||||
|
local profile = log.profile_start("diagnostics update")
|
||||||
|
if is_using_coc() then
|
||||||
|
NODE_SEVERITIES = from_coc()
|
||||||
|
else
|
||||||
|
NODE_SEVERITIES = from_nvim_lsp()
|
||||||
|
end
|
||||||
|
NODE_SEVERITIES_VERSION = NODE_SEVERITIES_VERSION + 1
|
||||||
|
if log.enabled("diagnostics") then
|
||||||
|
for bufname, severity in pairs(NODE_SEVERITIES) do
|
||||||
|
log.line("diagnostics", "Indexing bufname '%s' with severity %d", bufname, severity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
log.profile_end(profile)
|
||||||
|
|
||||||
|
local bufnr = view.get_bufnr()
|
||||||
|
local should_draw = bufnr
|
||||||
|
and vim.api.nvim_buf_is_valid(bufnr)
|
||||||
|
and vim.api.nvim_buf_is_loaded(bufnr)
|
||||||
|
and vim.api.nvim_get_option_value("buflisted", { buf = bufnr })
|
||||||
|
if should_draw then
|
||||||
|
local explorer = core.get_explorer()
|
||||||
|
if explorer then
|
||||||
|
explorer.renderer:draw()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Maybe retrieve diagnostic status for a node.
|
||||||
|
---Returns cached value when node's version matches.
|
||||||
|
---@param node Node
|
||||||
|
---@return DiagStatus|nil
|
||||||
|
function M.get_diag_status(node)
|
||||||
|
if not M.enable then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- dir but we shouldn't show on dirs at all
|
||||||
|
if node:is(DirectoryNode) and not M.show_on_dirs then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- here, we do a lazy update of the diagnostic status carried by the node.
|
||||||
|
-- This is by design, as diagnostics and nodes live in completely separate
|
||||||
|
-- worlds, and this module is the link between the two
|
||||||
|
if not node.diag_status or node.diag_status.cache_version < NODE_SEVERITIES_VERSION then
|
||||||
|
node.diag_status = from_cache(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
local dir = node:as(DirectoryNode)
|
||||||
|
|
||||||
|
-- file
|
||||||
|
if not dir then
|
||||||
|
return node.diag_status
|
||||||
|
end
|
||||||
|
|
||||||
|
-- dir is closed or we should show on open_dirs
|
||||||
|
if not dir.open or M.show_on_open_dirs then
|
||||||
|
return node.diag_status
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
function M.setup(opts)
|
function M.setup(opts)
|
||||||
M.enable = opts.diagnostics.enable
|
M.enable = opts.diagnostics.enable
|
||||||
M.show_on_dirs = opts.diagnostics.show_on_dirs
|
M.debounce_delay = opts.diagnostics.debounce_delay
|
||||||
vim.fn.sign_define(sign_names[1][1], { text = opts.diagnostics.icons.error, texthl = sign_names[1][2] })
|
M.severity = opts.diagnostics.severity
|
||||||
vim.fn.sign_define(sign_names[2][1], { text = opts.diagnostics.icons.warning, texthl = sign_names[2][2] })
|
|
||||||
vim.fn.sign_define(sign_names[3][1], { text = opts.diagnostics.icons.info, texthl = sign_names[3][2] })
|
|
||||||
vim.fn.sign_define(sign_names[4][1], { text = opts.diagnostics.icons.hint, texthl = sign_names[4][2] })
|
|
||||||
|
|
||||||
for lhs, rhs in pairs(links) do
|
|
||||||
vim.cmd("hi def link " .. lhs .. " " .. rhs)
|
|
||||||
end
|
|
||||||
|
|
||||||
if M.enable then
|
if M.enable then
|
||||||
log.line("diagnostics", "setup")
|
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
|
||||||
|
|
||||||
|
M.show_on_dirs = opts.diagnostics.show_on_dirs
|
||||||
|
M.show_on_open_dirs = opts.diagnostics.show_on_open_dirs
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
14
lua/nvim-tree/enum.lua
Normal file
14
lua/nvim-tree/enum.lua
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
---Reason for filter in filter.lua
|
||||||
|
---@enum FILTER_REASON
|
||||||
|
M.FILTER_REASON = {
|
||||||
|
none = 0, -- It's not filtered
|
||||||
|
git = 1,
|
||||||
|
buf = 2,
|
||||||
|
dotfile = 4,
|
||||||
|
custom = 8,
|
||||||
|
bookmark = 16,
|
||||||
|
}
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -1,129 +1,119 @@
|
|||||||
|
local notify = require("nvim-tree.notify")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local global_handlers = {}
|
local global_handlers = {}
|
||||||
|
|
||||||
local Event = {
|
M.Event = {
|
||||||
Ready = "Ready",
|
Ready = "Ready",
|
||||||
|
WillRenameNode = "WillRenameNode",
|
||||||
NodeRenamed = "NodeRenamed",
|
NodeRenamed = "NodeRenamed",
|
||||||
TreeOpen = "TreeOpen",
|
TreeOpen = "TreeOpen",
|
||||||
TreeClose = "TreeClose",
|
TreeClose = "TreeClose",
|
||||||
|
WillCreateFile = "WillCreateFile",
|
||||||
FileCreated = "FileCreated",
|
FileCreated = "FileCreated",
|
||||||
|
WillRemoveFile = "WillRemoveFile",
|
||||||
FileRemoved = "FileRemoved",
|
FileRemoved = "FileRemoved",
|
||||||
FolderCreated = "FolderCreated",
|
FolderCreated = "FolderCreated",
|
||||||
FolderRemoved = "FolderRemoved",
|
FolderRemoved = "FolderRemoved",
|
||||||
|
Resize = "Resize",
|
||||||
|
TreeAttachedPost = "TreeAttachedPost",
|
||||||
|
TreeRendered = "TreeRendered",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
---@param event_name string
|
||||||
|
---@return table
|
||||||
local function get_handlers(event_name)
|
local function get_handlers(event_name)
|
||||||
return global_handlers[event_name] or {}
|
return global_handlers[event_name] or {}
|
||||||
end
|
end
|
||||||
|
|
||||||
local function register_handler(event_name, handler)
|
---@param event_name string
|
||||||
|
---@param handler function
|
||||||
|
function M.subscribe(event_name, handler)
|
||||||
local handlers = get_handlers(event_name)
|
local handlers = get_handlers(event_name)
|
||||||
table.insert(handlers, handler)
|
table.insert(handlers, handler)
|
||||||
global_handlers[event_name] = handlers
|
global_handlers[event_name] = handlers
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param event_name string
|
||||||
|
---@param payload table|nil
|
||||||
local function dispatch(event_name, payload)
|
local function dispatch(event_name, payload)
|
||||||
for _, handler in pairs(get_handlers(event_name)) do
|
for _, handler in pairs(get_handlers(event_name)) do
|
||||||
local success, error = pcall(handler, payload)
|
local success, error = pcall(handler, payload)
|
||||||
if not success then
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--@private
|
--@private
|
||||||
function M._dispatch_ready()
|
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
|
end
|
||||||
|
|
||||||
--@private
|
--@private
|
||||||
function M._dispatch_node_renamed(old_name, new_name)
|
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_will_remove_file(fname)
|
||||||
|
dispatch(M.Event.WillRemoveFile, { fname = fname })
|
||||||
end
|
end
|
||||||
|
|
||||||
--@private
|
--@private
|
||||||
function M._dispatch_file_removed(fname)
|
function M._dispatch_file_removed(fname)
|
||||||
dispatch(Event.FileRemoved, { fname = fname })
|
dispatch(M.Event.FileRemoved, { fname = fname })
|
||||||
|
end
|
||||||
|
|
||||||
|
--@private
|
||||||
|
function M._dispatch_will_create_file(fname)
|
||||||
|
dispatch(M.Event.WillCreateFile, { fname = fname })
|
||||||
end
|
end
|
||||||
|
|
||||||
--@private
|
--@private
|
||||||
function M._dispatch_file_created(fname)
|
function M._dispatch_file_created(fname)
|
||||||
dispatch(Event.FileCreated, { fname = fname })
|
dispatch(M.Event.FileCreated, { fname = fname })
|
||||||
end
|
end
|
||||||
|
|
||||||
--@private
|
--@private
|
||||||
function M._dispatch_folder_created(folder_name)
|
function M._dispatch_folder_created(folder_name)
|
||||||
dispatch(Event.FolderCreated, { folder_name = folder_name })
|
dispatch(M.Event.FolderCreated, { folder_name = folder_name })
|
||||||
end
|
end
|
||||||
|
|
||||||
--@private
|
--@private
|
||||||
function M._dispatch_folder_removed(folder_name)
|
function M._dispatch_folder_removed(folder_name)
|
||||||
dispatch(Event.FolderRemoved, { folder_name = folder_name })
|
dispatch(M.Event.FolderRemoved, { folder_name = folder_name })
|
||||||
end
|
end
|
||||||
|
|
||||||
--@private
|
--@private
|
||||||
function M._dispatch_on_tree_open()
|
function M._dispatch_on_tree_open()
|
||||||
dispatch(Event.TreeOpen, nil)
|
dispatch(M.Event.TreeOpen, nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
--@private
|
--@private
|
||||||
function M._dispatch_on_tree_close()
|
function M._dispatch_on_tree_close()
|
||||||
dispatch(Event.TreeClose, nil)
|
dispatch(M.Event.TreeClose, nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
--Registers a handler for the Ready event.
|
--@private
|
||||||
--@param handler (function) Handler with the signature `function()`
|
function M._dispatch_on_tree_resize(size)
|
||||||
function M.on_nvim_tree_ready(handler)
|
dispatch(M.Event.Resize, size)
|
||||||
register_handler(Event.Ready, handler)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--Registers a handler for the NodeRenamed event.
|
--@private
|
||||||
--@param handler (function) Handler with the signature function(payload), where payload is a table containing:
|
function M._dispatch_tree_attached_post(buf)
|
||||||
-- - old_name (string) Absolute path to the old node location.
|
dispatch(M.Event.TreeAttachedPost, buf)
|
||||||
-- - new_name (string) Absolute path to the new node location.
|
|
||||||
function M.on_node_renamed(handler)
|
|
||||||
register_handler(Event.NodeRenamed, handler)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--Registers a handler for the FileCreated event.
|
--@private
|
||||||
--@param handler (function) Handler with the signature function(payload), where payload is a table containing:
|
function M._dispatch_on_tree_rendered(bufnr, winnr)
|
||||||
-- - fname (string) Absolute path to the created file.
|
dispatch(M.Event.TreeRendered, { bufnr = bufnr, winnr = winnr })
|
||||||
function M.on_file_created(handler)
|
|
||||||
register_handler(Event.FileCreated, handler)
|
|
||||||
end
|
|
||||||
|
|
||||||
--Registers a handler for the FileRemoved event.
|
|
||||||
--@param handler (function) Handler with the signature function(payload), where payload is a table containing:
|
|
||||||
-- - fname (string) Absolute path to the removed file.
|
|
||||||
function M.on_file_removed(handler)
|
|
||||||
register_handler(Event.FileRemoved, handler)
|
|
||||||
end
|
|
||||||
|
|
||||||
--Registers a handler for the FolderCreated event.
|
|
||||||
--@param handler (function) Handler with the signature function(payload), where payload is a table containing:
|
|
||||||
-- - folder_name (string) Absolute path to the created folder.
|
|
||||||
function M.on_folder_created(handler)
|
|
||||||
register_handler(Event.FolderCreated, handler)
|
|
||||||
end
|
|
||||||
|
|
||||||
--Registers a handler for the FolderRemoved event.
|
|
||||||
--@param handler (function) Handler with the signature function(payload), where payload is a table containing:
|
|
||||||
-- - folder_name (string) Absolute path to the removed folder.
|
|
||||||
function M.on_folder_removed(handler)
|
|
||||||
register_handler(Event.FolderRemoved, handler)
|
|
||||||
end
|
|
||||||
|
|
||||||
--Registers a handler for the TreeOpen event.
|
|
||||||
--@param handler (function) Handler with the signature function(payload)
|
|
||||||
function M.on_tree_open(handler)
|
|
||||||
register_handler(Event.TreeOpen, handler)
|
|
||||||
end
|
|
||||||
|
|
||||||
--Registers a handler for the TreeClose event.
|
|
||||||
--@param handler (function) Handler with the signature function(payload)
|
|
||||||
function M.on_tree_close(handler)
|
|
||||||
register_handler(Event.TreeClose, handler)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
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
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_git_status(parent_ignored, status, absolute_path)
|
|
||||||
return parent_ignored and "!!" or status.files and status.files[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")
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.update_git_status(node, parent_ignored, status)
|
|
||||||
-- status of the node's absolute path
|
|
||||||
if node.nodes then
|
|
||||||
node.git_status = get_dir_git_status(parent_ignored, status, node.absolute_path)
|
|
||||||
else
|
|
||||||
node.git_status = get_git_status(parent_ignored, status, node.absolute_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- status of the link target, if the link itself is not dirty
|
|
||||||
if node.link_to and not node.git_status then
|
|
||||||
if node.nodes then
|
|
||||||
node.git_status = get_dir_git_status(parent_ignored, status, node.link_to)
|
|
||||||
else
|
|
||||||
node.git_status = get_git_status(parent_ignored, status, node.link_to)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
local api = vim.api
|
|
||||||
local uv = vim.loop
|
|
||||||
|
|
||||||
local utils = require "nvim-tree.utils"
|
|
||||||
local builders = require "nvim-tree.explorer.node-builders"
|
|
||||||
local common = require "nvim-tree.explorer.common"
|
|
||||||
local sorters = require "nvim-tree.explorer.sorters"
|
|
||||||
local filters = require "nvim-tree.explorer.filters"
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
local function get_type_from(type_, cwd)
|
|
||||||
return type_ or (uv.fs_stat(cwd) or {}).type
|
|
||||||
end
|
|
||||||
|
|
||||||
local function populate_children(handle, cwd, node, status)
|
|
||||||
local node_ignored = node.git_status == "!!"
|
|
||||||
while true do
|
|
||||||
local name, t = uv.fs_scandir_next(handle)
|
|
||||||
if not name then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
local nodes_by_path = utils.key_by(node.nodes, "absolute_path")
|
|
||||||
local abs = utils.path_join { cwd, name }
|
|
||||||
t = get_type_from(t, abs)
|
|
||||||
if
|
|
||||||
not filters.should_ignore(abs)
|
|
||||||
and not filters.should_ignore_git(abs, status.files)
|
|
||||||
and not nodes_by_path[abs]
|
|
||||||
then
|
|
||||||
local child = nil
|
|
||||||
if t == "directory" and uv.fs_access(abs, "R") then
|
|
||||||
child = builders.folder(node, abs, name, status, node_ignored)
|
|
||||||
elseif t == "file" then
|
|
||||||
child = builders.file(node, abs, name, status, node_ignored)
|
|
||||||
elseif t == "link" then
|
|
||||||
local link = builders.link(node, abs, name, status, node_ignored)
|
|
||||||
if link.link_to ~= nil then
|
|
||||||
child = link
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if child then
|
|
||||||
table.insert(node.nodes, child)
|
|
||||||
common.update_git_status(child, node_ignored, status)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function get_dir_handle(cwd)
|
|
||||||
local handle = uv.fs_scandir(cwd)
|
|
||||||
if type(handle) == "string" then
|
|
||||||
api.nvim_err_writeln(handle)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
return handle
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.explore(node, status)
|
|
||||||
local cwd = node.cwd or node.link_to or node.absolute_path
|
|
||||||
local handle = get_dir_handle(cwd)
|
|
||||||
if not handle then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
populate_children(handle, cwd, node, status)
|
|
||||||
|
|
||||||
local is_root = node.cwd ~= nil
|
|
||||||
local child_folder_only = common.has_one_child_folder(node) and node.nodes[1]
|
|
||||||
if vim.g.nvim_tree_group_empty == 1 and not is_root and child_folder_only then
|
|
||||||
node.group_next = child_folder_only
|
|
||||||
local ns = M.explore(child_folder_only, status)
|
|
||||||
node.nodes = ns or {}
|
|
||||||
return ns
|
|
||||||
end
|
|
||||||
|
|
||||||
sorters.merge_sort(node.nodes, sorters.node_comparator)
|
|
||||||
return node.nodes
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -1,12 +1,60 @@
|
|||||||
local utils = require "nvim-tree.utils"
|
local utils = require("nvim-tree.utils")
|
||||||
|
local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON
|
||||||
|
|
||||||
local M = {
|
local Class = require("nvim-tree.classic")
|
||||||
ignore_list = {},
|
|
||||||
exclude_list = {},
|
|
||||||
}
|
|
||||||
|
|
||||||
local function is_excluded(path)
|
---@alias FilterType "custom" | "dotfiles" | "git_ignored" | "git_clean" | "no_buffer" | "no_bookmark"
|
||||||
for _, node in ipairs(M.exclude_list) do
|
|
||||||
|
---@class (exact) Filters: Class
|
||||||
|
---@field enabled boolean
|
||||||
|
---@field state table<FilterType, boolean>
|
||||||
|
---@field private explorer Explorer
|
||||||
|
---@field private exclude_list string[] filters.exclude
|
||||||
|
---@field private ignore_list table<string, boolean> filters.custom string table
|
||||||
|
---@field private custom_function (fun(absolute_path: string): boolean)|nil filters.custom function
|
||||||
|
local Filters = Class:extend()
|
||||||
|
|
||||||
|
---@class Filters
|
||||||
|
---@overload fun(args: FiltersArgs): Filters
|
||||||
|
|
||||||
|
---@class (exact) FiltersArgs
|
||||||
|
---@field explorer Explorer
|
||||||
|
|
||||||
|
---@protected
|
||||||
|
---@param args FiltersArgs
|
||||||
|
function Filters:new(args)
|
||||||
|
self.explorer = args.explorer
|
||||||
|
self.ignore_list = {}
|
||||||
|
self.exclude_list = self.explorer.opts.filters.exclude
|
||||||
|
self.custom_function = nil
|
||||||
|
|
||||||
|
self.enabled = self.explorer.opts.filters.enable
|
||||||
|
self.state = {
|
||||||
|
custom = true,
|
||||||
|
dotfiles = self.explorer.opts.filters.dotfiles,
|
||||||
|
git_ignored = self.explorer.opts.filters.git_ignored,
|
||||||
|
git_clean = self.explorer.opts.filters.git_clean,
|
||||||
|
no_buffer = self.explorer.opts.filters.no_buffer,
|
||||||
|
no_bookmark = self.explorer.opts.filters.no_bookmark,
|
||||||
|
}
|
||||||
|
|
||||||
|
local custom_filter = self.explorer.opts.filters.custom
|
||||||
|
if type(custom_filter) == "function" then
|
||||||
|
self.custom_function = custom_filter
|
||||||
|
else
|
||||||
|
if custom_filter and #custom_filter > 0 then
|
||||||
|
for _, filter_name in pairs(custom_filter) do
|
||||||
|
self.ignore_list[filter_name] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@param path string
|
||||||
|
---@return boolean
|
||||||
|
function Filters:is_excluded(path)
|
||||||
|
for _, node in ipairs(self.exclude_list) do
|
||||||
if path:match(node) then
|
if path:match(node) then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
@@ -14,36 +62,126 @@ local function is_excluded(path)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
---Check if the given path should be ignored.
|
---Check if the given path is git clean/ignored
|
||||||
|
---@private
|
||||||
---@param path string Absolute path
|
---@param path string Absolute path
|
||||||
|
---@param project GitProject from prepare
|
||||||
---@return boolean
|
---@return boolean
|
||||||
function M.should_ignore(path)
|
function Filters:git(path, project)
|
||||||
local basename = utils.path_basename(path)
|
if type(project) ~= "table" or type(project.files) ~= "table" or type(project.dirs) ~= "table" then
|
||||||
|
|
||||||
if is_excluded(path) then
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
if M.config.filter_dotfiles then
|
-- default status to clean
|
||||||
if basename:sub(1, 1) == "." then
|
local xy = project.files[path]
|
||||||
return true
|
xy = xy or project.dirs.direct[path] and project.dirs.direct[path][1]
|
||||||
|
xy = xy or project.dirs.indirect[path] and project.dirs.indirect[path][1]
|
||||||
|
|
||||||
|
-- filter ignored; overrides clean as they are effectively dirty
|
||||||
|
if self.state.git_ignored and xy == "!!" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- filter clean
|
||||||
|
if self.state.git_clean and not xy then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
---Check if the given path has no listed buffer
|
||||||
|
---@private
|
||||||
|
---@param path string Absolute path
|
||||||
|
---@param bufinfo table vim.fn.getbufinfo { buflisted = 1 }
|
||||||
|
---@return boolean
|
||||||
|
function Filters:buf(path, bufinfo)
|
||||||
|
if not self.state.no_buffer or type(bufinfo) ~= "table" then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- filter files with no open buffer and directories containing no open buffers
|
||||||
|
for _, b in ipairs(bufinfo) do
|
||||||
|
if b.name == path or b.name:find(path .. "/", 1, true) then
|
||||||
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if not M.config.filter_custom then
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@param path string
|
||||||
|
---@return boolean
|
||||||
|
function Filters:dotfile(path)
|
||||||
|
return self.state.dotfiles and utils.path_basename(path):sub(1, 1) == "."
|
||||||
|
end
|
||||||
|
|
||||||
|
---Bookmark is present
|
||||||
|
---@private
|
||||||
|
---@param path string
|
||||||
|
---@param path_type string|nil filetype of path
|
||||||
|
---@param bookmarks table<string, string|nil> path, filetype table of bookmarked files
|
||||||
|
---@return boolean
|
||||||
|
function Filters:bookmark(path, path_type, bookmarks)
|
||||||
|
if not self.state.no_bookmark then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
-- if bookmark is empty, we should see a empty filetree
|
||||||
|
if next(bookmarks) == nil then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local mark_parent = utils.path_add_trailing(path)
|
||||||
|
for mark, mark_type in pairs(bookmarks) do
|
||||||
|
if path == mark then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if path_type == "directory" then
|
||||||
|
-- check if path is mark's parent
|
||||||
|
if vim.fn.stridx(mark, mark_parent) == 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if mark_type == "directory" then
|
||||||
|
-- check if mark is path's parent
|
||||||
|
local path_parent = utils.path_add_trailing(mark)
|
||||||
|
if vim.fn.stridx(path, path_parent) == 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@param path string
|
||||||
|
---@return boolean
|
||||||
|
function Filters:custom(path)
|
||||||
|
if not self.state.custom then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local basename = utils.path_basename(path)
|
||||||
|
|
||||||
|
-- filter user's custom function
|
||||||
|
if self.custom_function and self.custom_function(path) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- filter custom regexes
|
||||||
local relpath = utils.path_relative(path, vim.loop.cwd())
|
local relpath = utils.path_relative(path, vim.loop.cwd())
|
||||||
for pat, _ in pairs(M.ignore_list) do
|
for pat, _ in pairs(self.ignore_list) do
|
||||||
if vim.fn.match(relpath, pat) ~= -1 or vim.fn.match(basename, pat) ~= -1 then
|
if vim.fn.match(relpath, pat) ~= -1 or vim.fn.match(basename, pat) ~= -1 then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local idx = path:match ".+()%.[^.]+$"
|
local idx = path:match(".+()%.[^.]+$")
|
||||||
if idx then
|
if idx then
|
||||||
if M.ignore_list["*" .. string.sub(path, idx)] == true then
|
if self.ignore_list["*" .. string.sub(path, idx)] == true then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -51,27 +189,99 @@ function M.should_ignore(path)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.should_ignore_git(path, status)
|
---Prepare arguments for should_filter. This is done prior to should_filter for efficiency reasons.
|
||||||
return M.config.filter_git_ignored
|
---@param project GitProject? optional results of git.load_projects(...)
|
||||||
and (M.config.filter_git_ignored and status and status[path] == "!!")
|
---@return table
|
||||||
and not is_excluded(path)
|
--- project: reference
|
||||||
end
|
--- bufinfo: empty unless no_buffer set: vim.fn.getbufinfo { buflisted = 1 }
|
||||||
|
--- bookmarks: absolute paths to boolean
|
||||||
function M.setup(opts)
|
function Filters:prepare(project)
|
||||||
M.config = {
|
local status = {
|
||||||
filter_custom = true,
|
project = project or {},
|
||||||
filter_dotfiles = opts.filters.dotfiles,
|
bufinfo = {},
|
||||||
filter_git_ignored = opts.git.ignore,
|
bookmarks = {},
|
||||||
}
|
}
|
||||||
|
|
||||||
M.exclude_list = opts.filters.exclude
|
if self.state.no_buffer then
|
||||||
|
status.bufinfo = vim.fn.getbufinfo({ buflisted = 1 })
|
||||||
|
end
|
||||||
|
|
||||||
local custom_filter = opts.filters.custom
|
local explorer = require("nvim-tree.core").get_explorer()
|
||||||
if custom_filter and #custom_filter > 0 then
|
if explorer then
|
||||||
for _, filter_name in pairs(custom_filter) do
|
for _, node in pairs(explorer.marks:list()) do
|
||||||
M.ignore_list[filter_name] = true
|
status.bookmarks[node.absolute_path] = node.type
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return status
|
||||||
|
end
|
||||||
|
|
||||||
|
---Check if the given path should be filtered.
|
||||||
|
---@param path string Absolute path
|
||||||
|
---@param fs_stat uv.fs_stat.result|nil fs_stat of file
|
||||||
|
---@param status table from prepare
|
||||||
|
---@return boolean
|
||||||
|
function Filters:should_filter(path, fs_stat, status)
|
||||||
|
if not self.enabled then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- exclusions override all filters
|
||||||
|
if self:is_excluded(path) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return self:git(path, status.project)
|
||||||
|
or self:buf(path, status.bufinfo)
|
||||||
|
or self:dotfile(path)
|
||||||
|
or self:custom(path)
|
||||||
|
or self:bookmark(path, fs_stat and fs_stat.type, status.bookmarks)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check if the given path should be filtered, and provide the reason why it was
|
||||||
|
---@param path string Absolute path
|
||||||
|
---@param fs_stat uv.fs_stat.result|nil fs_stat of file
|
||||||
|
---@param status table from prepare
|
||||||
|
---@return FILTER_REASON
|
||||||
|
function Filters:should_filter_as_reason(path, fs_stat, status)
|
||||||
|
if not self.enabled then
|
||||||
|
return FILTER_REASON.none
|
||||||
|
end
|
||||||
|
|
||||||
|
if self:is_excluded(path) then
|
||||||
|
return FILTER_REASON.none
|
||||||
|
end
|
||||||
|
|
||||||
|
if self:git(path, status.project) then
|
||||||
|
return FILTER_REASON.git
|
||||||
|
elseif self:buf(path, status.bufinfo) then
|
||||||
|
return FILTER_REASON.buf
|
||||||
|
elseif self:dotfile(path) then
|
||||||
|
return FILTER_REASON.dotfile
|
||||||
|
elseif self:custom(path) then
|
||||||
|
return FILTER_REASON.custom
|
||||||
|
elseif self:bookmark(path, fs_stat and fs_stat.type, status.bookmarks) then
|
||||||
|
return FILTER_REASON.bookmark
|
||||||
|
else
|
||||||
|
return FILTER_REASON.none
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
---Toggle a type and refresh
|
||||||
|
---@private
|
||||||
|
---@param type FilterType? nil to disable all
|
||||||
|
function Filters:toggle(type)
|
||||||
|
if not type or self.state[type] == nil then
|
||||||
|
self.enabled = not self.enabled
|
||||||
|
else
|
||||||
|
self.state[type] = not self.state[type]
|
||||||
|
end
|
||||||
|
|
||||||
|
local node = self.explorer:get_node_at_cursor()
|
||||||
|
self.explorer:reload_explorer()
|
||||||
|
if node then
|
||||||
|
utils.focus_node_or_parent(node)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return Filters
|
||||||
|
|||||||
@@ -1,40 +1,542 @@
|
|||||||
local uv = vim.loop
|
local appearance = require("nvim-tree.appearance")
|
||||||
|
local buffers = require("nvim-tree.buffers")
|
||||||
|
local core = require("nvim-tree.core")
|
||||||
|
local git = require("nvim-tree.git")
|
||||||
|
local log = require("nvim-tree.log")
|
||||||
|
local utils = require("nvim-tree.utils")
|
||||||
|
local view = require("nvim-tree.view")
|
||||||
|
local node_factory = require("nvim-tree.node.factory")
|
||||||
|
|
||||||
local git = require "nvim-tree.git"
|
local DirectoryNode = require("nvim-tree.node.directory")
|
||||||
|
local RootNode = require("nvim-tree.node.root")
|
||||||
|
local Watcher = require("nvim-tree.watcher")
|
||||||
|
|
||||||
local M = {}
|
local Iterator = require("nvim-tree.iterators.node-iterator")
|
||||||
|
local NodeIterator = require("nvim-tree.iterators.node-iterator")
|
||||||
|
|
||||||
M.explore = require("nvim-tree.explorer.explore").explore
|
local Filters = require("nvim-tree.explorer.filters")
|
||||||
M.reload = require("nvim-tree.explorer.reload").reload
|
local Marks = require("nvim-tree.marks")
|
||||||
|
local LiveFilter = require("nvim-tree.explorer.live-filter")
|
||||||
|
local Sorter = require("nvim-tree.explorer.sorter")
|
||||||
|
local Clipboard = require("nvim-tree.actions.fs.clipboard")
|
||||||
|
local Renderer = require("nvim-tree.renderer")
|
||||||
|
|
||||||
local Explorer = {}
|
local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON
|
||||||
Explorer.__index = Explorer
|
|
||||||
|
|
||||||
function Explorer.new(cwd)
|
local config
|
||||||
cwd = uv.fs_realpath(cwd or uv.cwd())
|
|
||||||
local explorer = setmetatable({
|
---@class (exact) Explorer: RootNode
|
||||||
cwd = cwd,
|
---@field uid_explorer number vim.loop.hrtime() at construction time
|
||||||
nodes = {},
|
---@field opts table user options
|
||||||
}, Explorer)
|
---@field augroup_id integer
|
||||||
explorer:_load(explorer)
|
---@field renderer Renderer
|
||||||
return explorer
|
---@field filters Filters
|
||||||
|
---@field live_filter LiveFilter
|
||||||
|
---@field sorters Sorter
|
||||||
|
---@field marks Marks
|
||||||
|
---@field clipboard Clipboard
|
||||||
|
local Explorer = RootNode:extend()
|
||||||
|
|
||||||
|
---@class Explorer
|
||||||
|
---@overload fun(args: ExplorerArgs): Explorer
|
||||||
|
|
||||||
|
---@class (exact) ExplorerArgs
|
||||||
|
---@field path string
|
||||||
|
|
||||||
|
---@protected
|
||||||
|
---@param args ExplorerArgs
|
||||||
|
function Explorer:new(args)
|
||||||
|
Explorer.super.new(self, {
|
||||||
|
explorer = self,
|
||||||
|
absolute_path = args.path,
|
||||||
|
name = "..",
|
||||||
|
})
|
||||||
|
|
||||||
|
self.uid_explorer = vim.loop.hrtime()
|
||||||
|
self.augroup_id = vim.api.nvim_create_augroup("NvimTree_Explorer_" .. self.uid_explorer, {})
|
||||||
|
|
||||||
|
self.open = true
|
||||||
|
self.opts = config
|
||||||
|
|
||||||
|
self.sorters = Sorter({ explorer = self })
|
||||||
|
self.renderer = Renderer({ explorer = self })
|
||||||
|
self.filters = Filters({ explorer = self })
|
||||||
|
self.live_filter = LiveFilter({ explorer = self })
|
||||||
|
self.marks = Marks({ explorer = self })
|
||||||
|
self.clipboard = Clipboard({ explorer = self })
|
||||||
|
|
||||||
|
self:create_autocmds()
|
||||||
|
|
||||||
|
self:_load(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Explorer:_load(node)
|
function Explorer:destroy()
|
||||||
local cwd = node.cwd or node.link_to or node.absolute_path
|
log.line("dev", "Explorer:destroy")
|
||||||
local git_statuses = git.load_project_status(cwd)
|
|
||||||
M.explore(node, git_statuses)
|
vim.api.nvim_del_augroup_by_id(self.augroup_id)
|
||||||
|
|
||||||
|
RootNode.destroy(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Explorer:create_autocmds()
|
||||||
|
-- reset and draw (highlights) when colorscheme is changed
|
||||||
|
vim.api.nvim_create_autocmd("ColorScheme", {
|
||||||
|
group = self.augroup_id,
|
||||||
|
callback = function()
|
||||||
|
appearance.setup()
|
||||||
|
view.reset_winhl()
|
||||||
|
self.renderer:draw()
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
vim.api.nvim_create_autocmd("BufWritePost", {
|
||||||
|
group = self.augroup_id,
|
||||||
|
callback = function()
|
||||||
|
if self.opts.auto_reload_on_write and not self.opts.filesystem_watchers.enable then
|
||||||
|
self:reload_explorer()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
vim.api.nvim_create_autocmd("BufReadPost", {
|
||||||
|
group = self.augroup_id,
|
||||||
|
callback = function(data)
|
||||||
|
if (self.filters.state.no_buffer or self.opts.highlight_opened_files ~= "none") and vim.bo[data.buf].buftype == "" then
|
||||||
|
utils.debounce("Buf:filter_buffer_" .. self.uid_explorer, self.opts.view.debounce_delay, function()
|
||||||
|
self:reload_explorer()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- update opened file buffers
|
||||||
|
vim.api.nvim_create_autocmd("BufUnload", {
|
||||||
|
group = self.augroup_id,
|
||||||
|
callback = function(data)
|
||||||
|
if (self.filters.state.no_buffer or self.opts.highlight_opened_files ~= "none") and vim.bo[data.buf].buftype == "" then
|
||||||
|
utils.debounce("Buf:filter_buffer_" .. self.uid_explorer, self.opts.view.debounce_delay, function()
|
||||||
|
self:reload_explorer()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
vim.api.nvim_create_autocmd("BufEnter", {
|
||||||
|
group = self.augroup_id,
|
||||||
|
pattern = "NvimTree_*",
|
||||||
|
callback = function()
|
||||||
|
if utils.is_nvim_tree_buf(0) then
|
||||||
|
if vim.fn.getcwd() ~= core.get_cwd() or (self.opts.reload_on_bufenter and not self.opts.filesystem_watchers.enable) then
|
||||||
|
self:reload_explorer()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
vim.api.nvim_create_autocmd("User", {
|
||||||
|
group = self.augroup_id,
|
||||||
|
pattern = { "FugitiveChanged", "NeogitStatusRefreshed" },
|
||||||
|
callback = function()
|
||||||
|
if not self.opts.filesystem_watchers.enable and self.opts.git.enable then
|
||||||
|
self:reload_git()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
if self.opts.hijack_cursor then
|
||||||
|
vim.api.nvim_create_autocmd("CursorMoved", {
|
||||||
|
group = self.augroup_id,
|
||||||
|
pattern = "NvimTree_*",
|
||||||
|
callback = function()
|
||||||
|
if utils.is_nvim_tree_buf(0) then
|
||||||
|
self:place_cursor_on_node()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.opts.modified.enable then
|
||||||
|
vim.api.nvim_create_autocmd({ "BufModifiedSet", "BufWritePost" }, {
|
||||||
|
group = self.augroup_id,
|
||||||
|
callback = function()
|
||||||
|
utils.debounce("Buf:modified_" .. self.uid_explorer, self.opts.view.debounce_delay, function()
|
||||||
|
buffers.reload_modified()
|
||||||
|
self:reload_explorer()
|
||||||
|
end)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node DirectoryNode
|
||||||
function Explorer:expand(node)
|
function Explorer:expand(node)
|
||||||
self:_load(node)
|
self:_load(node)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.setup(opts)
|
---@param node DirectoryNode
|
||||||
require("nvim-tree.explorer.filters").setup(opts)
|
---@param project GitProject?
|
||||||
require("nvim-tree.explorer.sorters").setup(opts)
|
---@return Node[]?
|
||||||
|
function Explorer:reload(node, project)
|
||||||
|
local cwd = node.link_to or node.absolute_path
|
||||||
|
local handle = vim.loop.fs_scandir(cwd)
|
||||||
|
if not handle then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local profile = log.profile_start("reload %s", node.absolute_path)
|
||||||
|
|
||||||
|
local filter_status = self.filters:prepare(project)
|
||||||
|
|
||||||
|
if node.group_next then
|
||||||
|
node.nodes = { node.group_next }
|
||||||
|
node.group_next = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local remain_childs = {}
|
||||||
|
|
||||||
|
local node_ignored = node:is_git_ignored()
|
||||||
|
---@type table<string, Node>
|
||||||
|
local nodes_by_path = utils.key_by(node.nodes, "absolute_path")
|
||||||
|
|
||||||
|
-- To reset we must 'zero' everything that we use
|
||||||
|
node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, {
|
||||||
|
git = 0,
|
||||||
|
buf = 0,
|
||||||
|
dotfile = 0,
|
||||||
|
custom = 0,
|
||||||
|
bookmark = 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local name, _ = vim.loop.fs_scandir_next(handle)
|
||||||
|
if not name then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local abs = utils.path_join({ cwd, name })
|
||||||
|
---@type uv.fs_stat.result|nil
|
||||||
|
local stat = vim.loop.fs_lstat(abs)
|
||||||
|
|
||||||
|
local filter_reason = self.filters:should_filter_as_reason(abs, stat, filter_status)
|
||||||
|
if filter_reason == FILTER_REASON.none then
|
||||||
|
remain_childs[abs] = true
|
||||||
|
|
||||||
|
-- Recreate node if type changes.
|
||||||
|
if nodes_by_path[abs] then
|
||||||
|
local n = nodes_by_path[abs]
|
||||||
|
|
||||||
|
if not stat or n.type ~= stat.type then
|
||||||
|
utils.array_remove(node.nodes, n)
|
||||||
|
n:destroy()
|
||||||
|
nodes_by_path[abs] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not nodes_by_path[abs] then
|
||||||
|
local new_child = node_factory.create({
|
||||||
|
explorer = self,
|
||||||
|
parent = node,
|
||||||
|
absolute_path = abs,
|
||||||
|
name = name,
|
||||||
|
fs_stat = stat
|
||||||
|
})
|
||||||
|
if new_child then
|
||||||
|
table.insert(node.nodes, new_child)
|
||||||
|
nodes_by_path[abs] = new_child
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local n = nodes_by_path[abs]
|
||||||
|
if n then
|
||||||
|
n.executable = utils.is_executable(abs) or false
|
||||||
|
n.fs_stat = stat
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
for reason, value in pairs(FILTER_REASON) do
|
||||||
|
if filter_reason == value then
|
||||||
|
node.hidden_stats[reason] = node.hidden_stats[reason] + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
node.nodes = vim.tbl_map(
|
||||||
|
self:update_git_statuses(nodes_by_path, node_ignored, project),
|
||||||
|
vim.tbl_filter(function(n)
|
||||||
|
if remain_childs[n.absolute_path] then
|
||||||
|
return remain_childs[n.absolute_path]
|
||||||
|
else
|
||||||
|
n:destroy()
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end, node.nodes)
|
||||||
|
)
|
||||||
|
|
||||||
|
local single_child = node:single_child_directory()
|
||||||
|
if config.renderer.group_empty and node.parent and single_child then
|
||||||
|
node.group_next = single_child
|
||||||
|
local ns = self:reload(single_child, project)
|
||||||
|
node.nodes = ns or {}
|
||||||
|
log.profile_end(profile)
|
||||||
|
return ns
|
||||||
|
end
|
||||||
|
|
||||||
|
self.sorters:sort(node.nodes)
|
||||||
|
self.live_filter:apply_filter(node)
|
||||||
|
log.profile_end(profile)
|
||||||
|
return node.nodes
|
||||||
end
|
end
|
||||||
|
|
||||||
M.Explorer = Explorer
|
---Refresh contents of all nodes to a path: actual directory and links.
|
||||||
|
---Groups will be expanded if needed.
|
||||||
|
---@param path string absolute path
|
||||||
|
function Explorer:refresh_parent_nodes_for_path(path)
|
||||||
|
local profile = log.profile_start("refresh_parent_nodes_for_path %s", path)
|
||||||
|
|
||||||
return M
|
-- collect parent nodes from the top down
|
||||||
|
local parent_nodes = {}
|
||||||
|
NodeIterator.builder({ self })
|
||||||
|
:recursor(function(node)
|
||||||
|
return node.nodes
|
||||||
|
end)
|
||||||
|
:applier(function(node)
|
||||||
|
local abs_contains = node.absolute_path and path:find(node.absolute_path, 1, true) == 1
|
||||||
|
local link_contains = node.link_to and path:find(node.link_to, 1, true) == 1
|
||||||
|
if abs_contains or link_contains then
|
||||||
|
table.insert(parent_nodes, node)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
:iterate()
|
||||||
|
|
||||||
|
-- refresh in order; this will expand groups as needed
|
||||||
|
for _, node in ipairs(parent_nodes) do
|
||||||
|
local toplevel = git.get_toplevel(node.absolute_path)
|
||||||
|
local project = git.get_project(toplevel) or {}
|
||||||
|
|
||||||
|
self:reload(node, project)
|
||||||
|
git.update_parent_projects(node, project, toplevel)
|
||||||
|
end
|
||||||
|
|
||||||
|
log.profile_end(profile)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@param node DirectoryNode
|
||||||
|
function Explorer:_load(node)
|
||||||
|
local cwd = node.link_to or node.absolute_path
|
||||||
|
local project = git.load_project(cwd)
|
||||||
|
self:explore(node, project, self)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@param nodes_by_path Node[]
|
||||||
|
---@param node_ignored boolean
|
||||||
|
---@param project GitProject?
|
||||||
|
---@return fun(node: Node): Node
|
||||||
|
function Explorer:update_git_statuses(nodes_by_path, node_ignored, project)
|
||||||
|
return function(node)
|
||||||
|
if nodes_by_path[node.absolute_path] then
|
||||||
|
node:update_git_status(node_ignored, project)
|
||||||
|
end
|
||||||
|
return node
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@param handle uv.uv_fs_t
|
||||||
|
---@param cwd string
|
||||||
|
---@param node DirectoryNode
|
||||||
|
---@param project GitProject
|
||||||
|
---@param parent Explorer
|
||||||
|
function Explorer:populate_children(handle, cwd, node, project, parent)
|
||||||
|
local node_ignored = node:is_git_ignored()
|
||||||
|
local nodes_by_path = utils.bool_record(node.nodes, "absolute_path")
|
||||||
|
|
||||||
|
local filter_status = parent.filters:prepare(project)
|
||||||
|
|
||||||
|
node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, {
|
||||||
|
git = 0,
|
||||||
|
buf = 0,
|
||||||
|
dotfile = 0,
|
||||||
|
custom = 0,
|
||||||
|
bookmark = 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local name, _ = vim.loop.fs_scandir_next(handle)
|
||||||
|
if not name then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local abs = utils.path_join({ cwd, name })
|
||||||
|
|
||||||
|
if Watcher.is_fs_event_capable(abs) then
|
||||||
|
local profile = log.profile_start("populate_children %s", abs)
|
||||||
|
|
||||||
|
---@type uv.fs_stat.result|nil
|
||||||
|
local stat = vim.loop.fs_lstat(abs)
|
||||||
|
local filter_reason = parent.filters:should_filter_as_reason(abs, stat, filter_status)
|
||||||
|
if filter_reason == FILTER_REASON.none and not nodes_by_path[abs] then
|
||||||
|
local child = node_factory.create({
|
||||||
|
explorer = self,
|
||||||
|
parent = node,
|
||||||
|
absolute_path = abs,
|
||||||
|
name = name,
|
||||||
|
fs_stat = stat
|
||||||
|
})
|
||||||
|
if child then
|
||||||
|
table.insert(node.nodes, child)
|
||||||
|
nodes_by_path[child.absolute_path] = true
|
||||||
|
child:update_git_status(node_ignored, project)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
for reason, value in pairs(FILTER_REASON) do
|
||||||
|
if filter_reason == value then
|
||||||
|
node.hidden_stats[reason] = node.hidden_stats[reason] + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
log.profile_end(profile)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@param node DirectoryNode
|
||||||
|
---@param project GitProject
|
||||||
|
---@param parent Explorer
|
||||||
|
---@return Node[]|nil
|
||||||
|
function Explorer:explore(node, project, parent)
|
||||||
|
local cwd = node.link_to or node.absolute_path
|
||||||
|
local handle = vim.loop.fs_scandir(cwd)
|
||||||
|
if not handle then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local profile = log.profile_start("explore %s", node.absolute_path)
|
||||||
|
|
||||||
|
self:populate_children(handle, cwd, node, project, parent)
|
||||||
|
|
||||||
|
local is_root = not node.parent
|
||||||
|
local single_child = node:single_child_directory()
|
||||||
|
if config.renderer.group_empty and not is_root and single_child then
|
||||||
|
local child_cwd = single_child.link_to or single_child.absolute_path
|
||||||
|
local child_project = git.load_project(child_cwd)
|
||||||
|
node.group_next = single_child
|
||||||
|
local ns = self:explore(single_child, child_project, parent)
|
||||||
|
node.nodes = ns or {}
|
||||||
|
|
||||||
|
log.profile_end(profile)
|
||||||
|
return ns
|
||||||
|
end
|
||||||
|
|
||||||
|
parent.sorters:sort(node.nodes)
|
||||||
|
parent.live_filter:apply_filter(node)
|
||||||
|
|
||||||
|
log.profile_end(profile)
|
||||||
|
return node.nodes
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@param projects GitProject[]
|
||||||
|
function Explorer:refresh_nodes(projects)
|
||||||
|
Iterator.builder({ self })
|
||||||
|
:applier(function(n)
|
||||||
|
local dir = n:as(DirectoryNode)
|
||||||
|
if dir then
|
||||||
|
local toplevel = git.get_toplevel(dir.cwd or dir.link_to or dir.absolute_path)
|
||||||
|
self:reload(dir, projects[toplevel] or {})
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
:recursor(function(n)
|
||||||
|
return n.group_next and { n.group_next } or (n.open and n.nodes)
|
||||||
|
end)
|
||||||
|
:iterate()
|
||||||
|
end
|
||||||
|
|
||||||
|
local event_running = false
|
||||||
|
function Explorer:reload_explorer()
|
||||||
|
if event_running or vim.v.exiting ~= vim.NIL then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
event_running = true
|
||||||
|
|
||||||
|
local projects = git.reload_all_projects()
|
||||||
|
self:refresh_nodes(projects)
|
||||||
|
if view.is_visible() then
|
||||||
|
self.renderer:draw()
|
||||||
|
end
|
||||||
|
event_running = false
|
||||||
|
end
|
||||||
|
|
||||||
|
function Explorer:reload_git()
|
||||||
|
if not git.config.git.enable or event_running then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
event_running = true
|
||||||
|
|
||||||
|
local projects = git.reload_all_projects()
|
||||||
|
git.reload_node_status(self, projects)
|
||||||
|
self.renderer:draw()
|
||||||
|
event_running = false
|
||||||
|
end
|
||||||
|
|
||||||
|
---Cursor position as per vim.api.nvim_win_get_cursor
|
||||||
|
---nil on no explorer or invalid view win
|
||||||
|
---@return integer[]|nil
|
||||||
|
function Explorer:get_cursor_position()
|
||||||
|
local winnr = view.get_winnr()
|
||||||
|
if not winnr or not vim.api.nvim_win_is_valid(winnr) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
return vim.api.nvim_win_get_cursor(winnr)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return Node|nil
|
||||||
|
function Explorer:get_node_at_cursor()
|
||||||
|
local cursor = self:get_cursor_position()
|
||||||
|
if not cursor then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if cursor[1] == 1 and view.is_root_folder_visible(core.get_cwd()) then
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
return utils.get_nodes_by_line(self.nodes, core.get_nodes_starting_line())[cursor[1]]
|
||||||
|
end
|
||||||
|
|
||||||
|
function Explorer:place_cursor_on_node()
|
||||||
|
local ok, search = pcall(vim.fn.searchcount)
|
||||||
|
if ok and search and search.exact_match == 1 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local node = self:get_node_at_cursor()
|
||||||
|
if not node or node.name == ".." then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
node = node:get_parent_of_group() or node
|
||||||
|
|
||||||
|
local line = vim.api.nvim_get_current_line()
|
||||||
|
local cursor = vim.api.nvim_win_get_cursor(0)
|
||||||
|
local idx = vim.fn.stridx(line, node.name)
|
||||||
|
|
||||||
|
if idx >= 0 then
|
||||||
|
vim.api.nvim_win_set_cursor(0, { cursor[1], idx })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Api.tree.get_nodes
|
||||||
|
---@return Node
|
||||||
|
function Explorer:get_nodes()
|
||||||
|
return self:clone()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Explorer:setup(opts)
|
||||||
|
config = opts
|
||||||
|
end
|
||||||
|
|
||||||
|
return Explorer
|
||||||
|
|||||||
229
lua/nvim-tree/explorer/live-filter.lua
Normal file
229
lua/nvim-tree/explorer/live-filter.lua
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
local view = require("nvim-tree.view")
|
||||||
|
local utils = require("nvim-tree.utils")
|
||||||
|
|
||||||
|
local Class = require("nvim-tree.classic")
|
||||||
|
local Iterator = require("nvim-tree.iterators.node-iterator")
|
||||||
|
local DirectoryNode = require("nvim-tree.node.directory")
|
||||||
|
|
||||||
|
---@class (exact) LiveFilter: Class
|
||||||
|
---@field explorer Explorer
|
||||||
|
---@field prefix string
|
||||||
|
---@field always_show_folders boolean
|
||||||
|
---@field filter string
|
||||||
|
local LiveFilter = Class:extend()
|
||||||
|
|
||||||
|
---@class LiveFilter
|
||||||
|
---@overload fun(args: LiveFilterArgs): LiveFilter
|
||||||
|
|
||||||
|
---@class (exact) LiveFilterArgs
|
||||||
|
---@field explorer Explorer
|
||||||
|
|
||||||
|
---@protected
|
||||||
|
---@param args LiveFilterArgs
|
||||||
|
function LiveFilter:new(args)
|
||||||
|
self.explorer = args.explorer
|
||||||
|
self.prefix = self.explorer.opts.live_filter.prefix
|
||||||
|
self.always_show_folders = self.explorer.opts.live_filter.always_show_folders
|
||||||
|
self.filter = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node_ Node?
|
||||||
|
local function reset_filter(self, node_)
|
||||||
|
node_ = node_ or self.explorer
|
||||||
|
|
||||||
|
if node_ == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local dir_ = node_:as(DirectoryNode)
|
||||||
|
if dir_ then
|
||||||
|
dir_.hidden_stats = vim.tbl_deep_extend("force", dir_.hidden_stats or {}, { live_filter = 0, })
|
||||||
|
end
|
||||||
|
|
||||||
|
Iterator.builder(node_.nodes)
|
||||||
|
:hidden()
|
||||||
|
:applier(function(node)
|
||||||
|
node.hidden = false
|
||||||
|
local dir = node:as(DirectoryNode)
|
||||||
|
if dir then
|
||||||
|
dir.hidden_stats = vim.tbl_deep_extend("force", dir.hidden_stats or {}, { live_filter = 0, })
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
:iterate()
|
||||||
|
end
|
||||||
|
|
||||||
|
local overlay_bufnr = 0
|
||||||
|
local overlay_winnr = 0
|
||||||
|
|
||||||
|
local function remove_overlay(self)
|
||||||
|
if view.View.float.enable and view.View.float.quit_on_focus_loss then
|
||||||
|
-- return to normal nvim-tree float behaviour when filter window is closed
|
||||||
|
vim.api.nvim_create_autocmd("WinLeave", {
|
||||||
|
pattern = "NvimTree_*",
|
||||||
|
group = vim.api.nvim_create_augroup("NvimTree", { clear = false }),
|
||||||
|
callback = function()
|
||||||
|
if utils.is_nvim_tree_buf(0) then
|
||||||
|
view.close()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.api.nvim_win_close(overlay_winnr, true)
|
||||||
|
vim.api.nvim_buf_delete(overlay_bufnr, { force = true })
|
||||||
|
overlay_bufnr = 0
|
||||||
|
overlay_winnr = 0
|
||||||
|
|
||||||
|
if self.filter == "" then
|
||||||
|
self:clear_filter()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
---@return boolean
|
||||||
|
local function matches(self, node)
|
||||||
|
if not self.explorer.filters.enabled then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local path = node.absolute_path
|
||||||
|
local name = vim.fn.fnamemodify(path, ":t")
|
||||||
|
return vim.regex(self.filter):match_str(name) ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node_ DirectoryNode?
|
||||||
|
function LiveFilter:apply_filter(node_)
|
||||||
|
if not self.filter or self.filter == "" then
|
||||||
|
reset_filter(self, node_)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- this iterator cannot yet be refactored with the Iterator module
|
||||||
|
-- since the node mapper is based on its children
|
||||||
|
local function iterate(node)
|
||||||
|
local filtered_nodes = 0
|
||||||
|
local nodes = node.group_next and { node.group_next } or node.nodes
|
||||||
|
|
||||||
|
node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, {
|
||||||
|
live_filter = 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
if nodes then
|
||||||
|
for _, n in pairs(nodes) do
|
||||||
|
iterate(n)
|
||||||
|
if n.hidden then
|
||||||
|
filtered_nodes = filtered_nodes + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
node.hidden_stats.live_filter = filtered_nodes
|
||||||
|
|
||||||
|
local has_nodes = nodes and (self.always_show_folders or #nodes > filtered_nodes)
|
||||||
|
local ok, is_match = pcall(matches, self, node)
|
||||||
|
node.hidden = not (has_nodes or (ok and is_match))
|
||||||
|
end
|
||||||
|
|
||||||
|
iterate(node_ or self.explorer)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function record_char(self)
|
||||||
|
vim.schedule(function()
|
||||||
|
self.filter = vim.api.nvim_buf_get_lines(overlay_bufnr, 0, -1, false)[1]
|
||||||
|
self:apply_filter()
|
||||||
|
self.explorer.renderer:draw()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function configure_buffer_overlay(self)
|
||||||
|
overlay_bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
|
|
||||||
|
vim.api.nvim_buf_attach(overlay_bufnr, true, {
|
||||||
|
on_lines = function()
|
||||||
|
return record_char(self)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
vim.api.nvim_create_autocmd("InsertLeave", {
|
||||||
|
callback = function()
|
||||||
|
return remove_overlay(self)
|
||||||
|
end,
|
||||||
|
once = true,
|
||||||
|
})
|
||||||
|
|
||||||
|
vim.api.nvim_buf_set_keymap(overlay_bufnr, "i", "<CR>", "<cmd>stopinsert<CR>", {})
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return integer
|
||||||
|
local function calculate_overlay_win_width(self)
|
||||||
|
local wininfo = vim.fn.getwininfo(view.get_winnr())[1]
|
||||||
|
|
||||||
|
if wininfo then
|
||||||
|
return wininfo.width - wininfo.textoff - #self.prefix
|
||||||
|
end
|
||||||
|
|
||||||
|
return 20
|
||||||
|
end
|
||||||
|
|
||||||
|
local function create_overlay(self)
|
||||||
|
if view.View.float.enable then
|
||||||
|
-- don't close nvim-tree float when focus is changed to filter window
|
||||||
|
vim.api.nvim_clear_autocmds({
|
||||||
|
event = "WinLeave",
|
||||||
|
pattern = "NvimTree_*",
|
||||||
|
group = vim.api.nvim_create_augroup("NvimTree", { clear = false }),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
configure_buffer_overlay(self)
|
||||||
|
overlay_winnr = vim.api.nvim_open_win(overlay_bufnr, true, {
|
||||||
|
col = 1,
|
||||||
|
row = 0,
|
||||||
|
relative = "cursor",
|
||||||
|
width = calculate_overlay_win_width(self),
|
||||||
|
height = 1,
|
||||||
|
border = "none",
|
||||||
|
style = "minimal",
|
||||||
|
})
|
||||||
|
|
||||||
|
if vim.fn.has("nvim-0.10") == 1 then
|
||||||
|
vim.api.nvim_set_option_value("modifiable", true, { buf = overlay_bufnr })
|
||||||
|
else
|
||||||
|
vim.api.nvim_buf_set_option(overlay_bufnr, "modifiable", true) ---@diagnostic disable-line: deprecated
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.api.nvim_buf_set_lines(overlay_bufnr, 0, -1, false, { self.filter })
|
||||||
|
vim.cmd("startinsert")
|
||||||
|
vim.api.nvim_win_set_cursor(overlay_winnr, { 1, #self.filter + 1 })
|
||||||
|
end
|
||||||
|
|
||||||
|
function LiveFilter:start_filtering()
|
||||||
|
view.View.live_filter.prev_focused_node = self.explorer:get_node_at_cursor()
|
||||||
|
self.filter = self.filter or ""
|
||||||
|
|
||||||
|
self.explorer.renderer:draw()
|
||||||
|
local row = require("nvim-tree.core").get_nodes_starting_line() - 1
|
||||||
|
local col = #self.prefix > 0 and #self.prefix - 1 or 1
|
||||||
|
view.set_cursor({ row, col })
|
||||||
|
-- needs scheduling to let the cursor move before initializing the window
|
||||||
|
vim.schedule(function()
|
||||||
|
return create_overlay(self)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function LiveFilter:clear_filter()
|
||||||
|
local node = self.explorer:get_node_at_cursor()
|
||||||
|
local last_node = view.View.live_filter.prev_focused_node
|
||||||
|
|
||||||
|
self.filter = nil
|
||||||
|
reset_filter(self)
|
||||||
|
self.explorer.renderer:draw()
|
||||||
|
|
||||||
|
if node then
|
||||||
|
utils.focus_file(node.absolute_path)
|
||||||
|
elseif last_node then
|
||||||
|
utils.focus_file(last_node.absolute_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return LiveFilter
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
local uv = vim.loop
|
|
||||||
local utils = require "nvim-tree.utils"
|
|
||||||
|
|
||||||
local M = {
|
|
||||||
is_windows = vim.fn.has "win32" == 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
|
|
||||||
|
|
||||||
return {
|
|
||||||
absolute_path = absolute_path,
|
|
||||||
fs_stat = uv.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,
|
|
||||||
nodes = {},
|
|
||||||
open = false,
|
|
||||||
parent = parent,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local function is_executable(absolute_path, ext)
|
|
||||||
if M.is_windows then
|
|
||||||
return utils.is_windows_exe(ext)
|
|
||||||
end
|
|
||||||
return uv.fs_access(absolute_path, "X")
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.file(parent, absolute_path, name)
|
|
||||||
local ext = string.match(name, ".?[^.]+%.(.*)") or ""
|
|
||||||
|
|
||||||
return {
|
|
||||||
absolute_path = absolute_path,
|
|
||||||
executable = is_executable(absolute_path, ext),
|
|
||||||
extension = ext,
|
|
||||||
fs_stat = uv.fs_stat(absolute_path),
|
|
||||||
name = name,
|
|
||||||
parent = parent,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
-- TODO-INFO: sometimes fs_realpath returns nil
|
|
||||||
-- I expect this be a bug in glibc, because it fails to retrieve the path for some
|
|
||||||
-- links (for instance libr2.so in /usr/lib) and thus even with a C program realpath fails
|
|
||||||
-- when it has no real reason to. Maybe there is a reason, but errno is definitely wrong.
|
|
||||||
-- So we need to check for link_to ~= nil when adding new links to the main tree
|
|
||||||
function M.link(parent, absolute_path, name)
|
|
||||||
--- I dont know if this is needed, because in my understanding, there isnt hard links in windows, but just to be sure i changed it.
|
|
||||||
local link_to = uv.fs_realpath(absolute_path)
|
|
||||||
local open, nodes, has_children
|
|
||||||
if (link_to ~= nil) and uv.fs_stat(link_to).type == "directory" then
|
|
||||||
local handle = uv.fs_scandir(link_to)
|
|
||||||
has_children = handle and uv.fs_scandir_next(handle) ~= nil
|
|
||||||
open = false
|
|
||||||
nodes = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
absolute_path = absolute_path,
|
|
||||||
fs_stat = uv.fs_stat(absolute_path),
|
|
||||||
group_next = nil, -- If node is grouped, this points to the next child dir/link node
|
|
||||||
has_children = has_children,
|
|
||||||
link_to = link_to,
|
|
||||||
name = name,
|
|
||||||
nodes = nodes,
|
|
||||||
open = open,
|
|
||||||
parent = parent,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
local api = vim.api
|
|
||||||
local uv = vim.loop
|
|
||||||
|
|
||||||
local utils = require "nvim-tree.utils"
|
|
||||||
local builders = require "nvim-tree.explorer.node-builders"
|
|
||||||
local common = require "nvim-tree.explorer.common"
|
|
||||||
local filters = require "nvim-tree.explorer.filters"
|
|
||||||
local sorters = require "nvim-tree.explorer.sorters"
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
local function update_status(nodes_by_path, node_ignored, status)
|
|
||||||
return function(node)
|
|
||||||
if nodes_by_path[node.absolute_path] then
|
|
||||||
common.update_git_status(node, node_ignored, status)
|
|
||||||
end
|
|
||||||
return node
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.reload(node, status)
|
|
||||||
local cwd = node.cwd or node.link_to or node.absolute_path
|
|
||||||
local handle = uv.fs_scandir(cwd)
|
|
||||||
if type(handle) == "string" then
|
|
||||||
api.nvim_err_writeln(handle)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if node.group_next then
|
|
||||||
node.nodes = { node.group_next }
|
|
||||||
node.group_next = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local child_names = {}
|
|
||||||
|
|
||||||
local node_ignored = node.git_status == "!!"
|
|
||||||
local nodes_by_path = utils.key_by(node.nodes, "absolute_path")
|
|
||||||
while true do
|
|
||||||
local name, t = uv.fs_scandir_next(handle)
|
|
||||||
if not name then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
local abs = utils.path_join { cwd, name }
|
|
||||||
t = t or (uv.fs_stat(abs) or {}).type
|
|
||||||
if not filters.should_ignore(abs) and not filters.should_ignore_git(abs, status.files) then
|
|
||||||
child_names[abs] = true
|
|
||||||
if not nodes_by_path[abs] then
|
|
||||||
if t == "directory" and uv.fs_access(abs, "R") then
|
|
||||||
table.insert(node.nodes, builders.folder(node, abs, name, status, node_ignored))
|
|
||||||
elseif t == "file" then
|
|
||||||
table.insert(node.nodes, builders.file(node, abs, name, status, node_ignored))
|
|
||||||
elseif t == "link" then
|
|
||||||
local link = builders.link(node, abs, name, status, node_ignored)
|
|
||||||
if link.link_to ~= nil then
|
|
||||||
table.insert(node.nodes, link)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
node.nodes = vim.tbl_map(
|
|
||||||
update_status(nodes_by_path, node_ignored, status),
|
|
||||||
vim.tbl_filter(function(n)
|
|
||||||
return child_names[n.absolute_path]
|
|
||||||
end, node.nodes)
|
|
||||||
)
|
|
||||||
|
|
||||||
local is_root = node.cwd ~= nil
|
|
||||||
local child_folder_only = common.has_one_child_folder(node) and node.nodes[1]
|
|
||||||
if vim.g.nvim_tree_group_empty == 1 and not is_root and child_folder_only then
|
|
||||||
node.group_next = child_folder_only
|
|
||||||
local ns = M.reload(child_folder_only, status)
|
|
||||||
node.nodes = ns or {}
|
|
||||||
return ns
|
|
||||||
end
|
|
||||||
|
|
||||||
sorters.merge_sort(node.nodes, sorters.node_comparator)
|
|
||||||
return node.nodes
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
331
lua/nvim-tree/explorer/sorter.lua
Normal file
331
lua/nvim-tree/explorer/sorter.lua
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
local Class = require("nvim-tree.classic")
|
||||||
|
local DirectoryNode = require("nvim-tree.node.directory")
|
||||||
|
|
||||||
|
---@alias SorterType "name" | "case_sensitive" | "modification_time" | "extension" | "suffix" | "filetype"
|
||||||
|
---@alias SorterComparator fun(self: Sorter, a: Node, b: Node): boolean?
|
||||||
|
|
||||||
|
---@alias SorterUser fun(nodes: Node[]): SorterType?
|
||||||
|
|
||||||
|
---@class (exact) Sorter: Class
|
||||||
|
---@field private explorer Explorer
|
||||||
|
local Sorter = Class:extend()
|
||||||
|
|
||||||
|
---@class Sorter
|
||||||
|
---@overload fun(args: SorterArgs): Sorter
|
||||||
|
|
||||||
|
---@class (exact) SorterArgs
|
||||||
|
---@field explorer Explorer
|
||||||
|
|
||||||
|
---@protected
|
||||||
|
---@param args SorterArgs
|
||||||
|
function Sorter:new(args)
|
||||||
|
self.explorer = args.explorer
|
||||||
|
end
|
||||||
|
|
||||||
|
---Create a shallow copy of a portion of a list.
|
||||||
|
---@param t table
|
||||||
|
---@param first integer First index, inclusive
|
||||||
|
---@param last integer Last index, inclusive
|
||||||
|
---@return table
|
||||||
|
local function tbl_slice(t, first, last)
|
||||||
|
local slice = {}
|
||||||
|
for i = first, last or #t, 1 do
|
||||||
|
table.insert(slice, t[i])
|
||||||
|
end
|
||||||
|
|
||||||
|
return slice
|
||||||
|
end
|
||||||
|
|
||||||
|
---Evaluate folders_first and sort.files_first returning nil when no order is necessary
|
||||||
|
---@private
|
||||||
|
---@type SorterComparator
|
||||||
|
function Sorter:folders_or_files_first(a, b)
|
||||||
|
if not (self.explorer.opts.sort.folders_first or self.explorer.opts.sort.files_first) then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if not a:is(DirectoryNode) and b:is(DirectoryNode) then
|
||||||
|
-- file <> folder
|
||||||
|
return self.explorer.opts.sort.files_first
|
||||||
|
elseif a:is(DirectoryNode) and not b:is(DirectoryNode) then
|
||||||
|
-- folder <> file
|
||||||
|
return not self.explorer.opts.sort.files_first
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@param t Node[]
|
||||||
|
---@param first number
|
||||||
|
---@param mid number
|
||||||
|
---@param last number
|
||||||
|
---@param comparator SorterComparator
|
||||||
|
function Sorter:merge(t, first, mid, last, comparator)
|
||||||
|
local n1 = mid - first + 1
|
||||||
|
local n2 = last - mid
|
||||||
|
local ls = tbl_slice(t, first, mid)
|
||||||
|
local rs = tbl_slice(t, mid + 1, last)
|
||||||
|
local i = 1
|
||||||
|
local j = 1
|
||||||
|
local k = first
|
||||||
|
|
||||||
|
while i <= n1 and j <= n2 do
|
||||||
|
if comparator(self, ls[i], rs[j]) then
|
||||||
|
t[k] = ls[i]
|
||||||
|
i = i + 1
|
||||||
|
else
|
||||||
|
t[k] = rs[j]
|
||||||
|
j = j + 1
|
||||||
|
end
|
||||||
|
k = k + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
while i <= n1 do
|
||||||
|
t[k] = ls[i]
|
||||||
|
i = i + 1
|
||||||
|
k = k + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
while j <= n2 do
|
||||||
|
t[k] = rs[j]
|
||||||
|
j = j + 1
|
||||||
|
k = k + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@param t Node[]
|
||||||
|
---@param first number
|
||||||
|
---@param last number
|
||||||
|
---@param comparator SorterComparator
|
||||||
|
function Sorter:split_merge(t, first, last, comparator)
|
||||||
|
if (last - first) < 1 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local mid = math.floor((first + last) / 2)
|
||||||
|
|
||||||
|
self:split_merge(t, first, mid, comparator)
|
||||||
|
self:split_merge(t, mid + 1, last, comparator)
|
||||||
|
self:merge(t, first, mid, last, comparator)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Perform a merge sort using sorter option.
|
||||||
|
---@param t Node[]
|
||||||
|
function Sorter:sort(t)
|
||||||
|
if self[self.explorer.opts.sort.sorter] then
|
||||||
|
self:split_merge(t, 1, #t, self[self.explorer.opts.sort.sorter])
|
||||||
|
elseif type(self.explorer.opts.sort.sorter) == "function" then
|
||||||
|
local t_user = {}
|
||||||
|
local origin_index = {}
|
||||||
|
|
||||||
|
for _, n in ipairs(t) do
|
||||||
|
table.insert(t_user, {
|
||||||
|
absolute_path = n.absolute_path,
|
||||||
|
executable = n.executable,
|
||||||
|
extension = n.extension,
|
||||||
|
filetype = vim.filetype.match({ filename = n.name }),
|
||||||
|
link_to = n.link_to,
|
||||||
|
name = n.name,
|
||||||
|
type = n.type,
|
||||||
|
})
|
||||||
|
table.insert(origin_index, n)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- user may return a SorterType
|
||||||
|
local ret = self.explorer.opts.sort.sorter(t_user)
|
||||||
|
if self[ret] then
|
||||||
|
self:split_merge(t, 1, #t, self[ret])
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- do merge sort for prevent memory exceed
|
||||||
|
local user_index = {}
|
||||||
|
for i, v in ipairs(t_user) do
|
||||||
|
if type(v.absolute_path) == "string" and user_index[v.absolute_path] == nil then
|
||||||
|
user_index[v.absolute_path] = i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if missing value found, then using origin_index
|
||||||
|
local mini_comparator = function(_, a, b)
|
||||||
|
local a_index = user_index[a.absolute_path] or origin_index[a.absolute_path]
|
||||||
|
local b_index = user_index[b.absolute_path] or origin_index[b.absolute_path]
|
||||||
|
|
||||||
|
if type(a_index) == "number" and type(b_index) == "number" then
|
||||||
|
return a_index <= b_index
|
||||||
|
end
|
||||||
|
return (a_index or 0) <= (b_index or 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
self:split_merge(t, 1, #t, mini_comparator) -- sort by user order
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@param a Node
|
||||||
|
---@param b Node
|
||||||
|
---@param ignore_case boolean
|
||||||
|
---@return boolean
|
||||||
|
function Sorter:name_case(a, b, ignore_case)
|
||||||
|
if not (a and b) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local early_return = self:folders_or_files_first(a, b)
|
||||||
|
if early_return ~= nil then
|
||||||
|
return early_return
|
||||||
|
end
|
||||||
|
|
||||||
|
if ignore_case then
|
||||||
|
return a.name:lower() <= b.name:lower()
|
||||||
|
else
|
||||||
|
return a.name <= b.name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@type SorterComparator
|
||||||
|
function Sorter:case_sensitive(a, b)
|
||||||
|
return self:name_case(a, b, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@type SorterComparator
|
||||||
|
function Sorter:name(a, b)
|
||||||
|
return self:name_case(a, b, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@type SorterComparator
|
||||||
|
function Sorter:modification_time(a, b)
|
||||||
|
if not (a and b) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local early_return = self:folders_or_files_first(a, b)
|
||||||
|
if early_return ~= nil then
|
||||||
|
return early_return
|
||||||
|
end
|
||||||
|
|
||||||
|
local last_modified_a = 0
|
||||||
|
local last_modified_b = 0
|
||||||
|
|
||||||
|
if a.fs_stat ~= nil then
|
||||||
|
last_modified_a = a.fs_stat.mtime.sec
|
||||||
|
end
|
||||||
|
|
||||||
|
if b.fs_stat ~= nil then
|
||||||
|
last_modified_b = b.fs_stat.mtime.sec
|
||||||
|
end
|
||||||
|
|
||||||
|
return last_modified_b <= last_modified_a
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@type SorterComparator
|
||||||
|
function Sorter:suffix(a, b)
|
||||||
|
if not (a and b) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- directories go first
|
||||||
|
local early_return = self:folders_or_files_first(a, b)
|
||||||
|
if early_return ~= nil then
|
||||||
|
return early_return
|
||||||
|
elseif a.nodes and b.nodes then
|
||||||
|
return self:name(a, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- dotfiles go second
|
||||||
|
if a.name:sub(1, 1) == "." and b.name:sub(1, 1) ~= "." then
|
||||||
|
return true
|
||||||
|
elseif a.name:sub(1, 1) ~= "." and b.name:sub(1, 1) == "." then
|
||||||
|
return false
|
||||||
|
elseif a.name:sub(1, 1) == "." and b.name:sub(1, 1) == "." then
|
||||||
|
return self:name(a, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- unsuffixed go third
|
||||||
|
local a_suffix_ndx = a.name:find("%.%w+$")
|
||||||
|
local b_suffix_ndx = b.name:find("%.%w+$")
|
||||||
|
|
||||||
|
if not a_suffix_ndx and b_suffix_ndx then
|
||||||
|
return true
|
||||||
|
elseif a_suffix_ndx and not b_suffix_ndx then
|
||||||
|
return false
|
||||||
|
elseif not (a_suffix_ndx and b_suffix_ndx) then
|
||||||
|
return self:name(a, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- finally, compare by suffixes
|
||||||
|
local a_suffix = a.name:sub(a_suffix_ndx)
|
||||||
|
local b_suffix = b.name:sub(b_suffix_ndx)
|
||||||
|
|
||||||
|
if a_suffix and not b_suffix then
|
||||||
|
return true
|
||||||
|
elseif not a_suffix and b_suffix then
|
||||||
|
return false
|
||||||
|
elseif a_suffix:lower() == b_suffix:lower() then
|
||||||
|
return self:name(a, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
return a_suffix:lower() < b_suffix:lower()
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@type SorterComparator
|
||||||
|
function Sorter:extension(a, b)
|
||||||
|
if not (a and b) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local early_return = self:folders_or_files_first(a, b)
|
||||||
|
if early_return ~= nil then
|
||||||
|
return early_return
|
||||||
|
end
|
||||||
|
|
||||||
|
if a.extension and not b.extension then
|
||||||
|
return true
|
||||||
|
elseif not a.extension and b.extension then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local a_ext = (a.extension or ""):lower()
|
||||||
|
local b_ext = (b.extension or ""):lower()
|
||||||
|
if a_ext == b_ext then
|
||||||
|
return self:name(a, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
return a_ext < b_ext
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@type SorterComparator
|
||||||
|
function Sorter:filetype(a, b)
|
||||||
|
local a_ft = vim.filetype.match({ filename = a.name })
|
||||||
|
local b_ft = vim.filetype.match({ filename = b.name })
|
||||||
|
|
||||||
|
-- directories first
|
||||||
|
local early_return = self:folders_or_files_first(a, b)
|
||||||
|
if early_return ~= nil then
|
||||||
|
return early_return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- one is nil, the other wins
|
||||||
|
if a_ft and not b_ft then
|
||||||
|
return true
|
||||||
|
elseif not a_ft and b_ft then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- same filetype or both nil, sort by name
|
||||||
|
if a_ft == b_ft then
|
||||||
|
return self:name(a, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
return a_ft < b_ft
|
||||||
|
end
|
||||||
|
|
||||||
|
return Sorter
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
local M = {
|
|
||||||
sort_by = nil,
|
|
||||||
node_comparator = nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
---Create a shallow copy of a portion of a list.
|
|
||||||
---@param t table
|
|
||||||
---@param first integer First index, inclusive
|
|
||||||
---@param last integer Last index, inclusive
|
|
||||||
---@return table
|
|
||||||
local function tbl_slice(t, first, last)
|
|
||||||
local slice = {}
|
|
||||||
for i = first, last or #t, 1 do
|
|
||||||
table.insert(slice, t[i])
|
|
||||||
end
|
|
||||||
|
|
||||||
return slice
|
|
||||||
end
|
|
||||||
|
|
||||||
local function merge(t, first, mid, last, comparator)
|
|
||||||
local n1 = mid - first + 1
|
|
||||||
local n2 = last - mid
|
|
||||||
local ls = tbl_slice(t, first, mid)
|
|
||||||
local rs = tbl_slice(t, mid + 1, last)
|
|
||||||
local i = 1
|
|
||||||
local j = 1
|
|
||||||
local k = first
|
|
||||||
|
|
||||||
while i <= n1 and j <= n2 do
|
|
||||||
if comparator(ls[i], rs[j]) then
|
|
||||||
t[k] = ls[i]
|
|
||||||
i = i + 1
|
|
||||||
else
|
|
||||||
t[k] = rs[j]
|
|
||||||
j = j + 1
|
|
||||||
end
|
|
||||||
k = k + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
while i <= n1 do
|
|
||||||
t[k] = ls[i]
|
|
||||||
i = i + 1
|
|
||||||
k = k + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
while j <= n2 do
|
|
||||||
t[k] = rs[j]
|
|
||||||
j = j + 1
|
|
||||||
k = k + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function split_merge(t, first, last, comparator)
|
|
||||||
if (last - first) < 1 then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local mid = math.floor((first + last) / 2)
|
|
||||||
|
|
||||||
split_merge(t, first, mid, comparator)
|
|
||||||
split_merge(t, mid + 1, last, comparator)
|
|
||||||
merge(t, first, mid, last, comparator)
|
|
||||||
end
|
|
||||||
|
|
||||||
---Perform a merge sort on a given list.
|
|
||||||
---@param t any[]
|
|
||||||
---@param comparator function|nil
|
|
||||||
function M.merge_sort(t, comparator)
|
|
||||||
if not comparator then
|
|
||||||
comparator = function(left, right)
|
|
||||||
return left < right
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
split_merge(t, 1, #t, comparator)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function node_comparator_name_ignorecase_or_not(a, b, ignorecase)
|
|
||||||
if not (a and b) then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
if a.nodes and not b.nodes then
|
|
||||||
return true
|
|
||||||
elseif not a.nodes and b.nodes then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
if ignorecase then
|
|
||||||
return a.name:lower() <= b.name:lower()
|
|
||||||
else
|
|
||||||
return a.name <= b.name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.node_comparator_name_case_sensisive(a, b)
|
|
||||||
return node_comparator_name_ignorecase_or_not(a, b, false)
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.node_comparator_name_ignorecase(a, b)
|
|
||||||
return node_comparator_name_ignorecase_or_not(a, b, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.node_comparator_modification_time(a, b)
|
|
||||||
if not (a and b) then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
if a.nodes and not b.nodes then
|
|
||||||
return true
|
|
||||||
elseif not a.nodes and b.nodes then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local last_modified_a = 0
|
|
||||||
local last_modified_b = 0
|
|
||||||
|
|
||||||
if a.fs_stat ~= nil then
|
|
||||||
last_modified_a = a.fs_stat.mtime.sec
|
|
||||||
end
|
|
||||||
|
|
||||||
if b.fs_stat ~= nil then
|
|
||||||
last_modified_b = b.fs_stat.mtime.sec
|
|
||||||
end
|
|
||||||
|
|
||||||
return last_modified_b <= last_modified_a
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.setup(opts)
|
|
||||||
M.sort_by = opts.sort_by
|
|
||||||
if M.sort_by == "modification_time" then
|
|
||||||
M.node_comparator = M.node_comparator_modification_time
|
|
||||||
elseif M.sort_by == "case_sensitive" then
|
|
||||||
M.node_comparator = M.node_comparator_name_case_sensisive
|
|
||||||
else
|
|
||||||
M.node_comparator = M.node_comparator_name_ignorecase
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
100
lua/nvim-tree/explorer/watch.lua
Normal file
100
lua/nvim-tree/explorer/watch.lua
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
local log = require("nvim-tree.log")
|
||||||
|
local git = require("nvim-tree.git")
|
||||||
|
local utils = require("nvim-tree.utils")
|
||||||
|
local Watcher = require("nvim-tree.watcher").Watcher
|
||||||
|
|
||||||
|
local M = {
|
||||||
|
config = {},
|
||||||
|
uid = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
---@param path string
|
||||||
|
---@return boolean
|
||||||
|
local function is_git(path)
|
||||||
|
-- If $GIT_DIR is set, consider its value to be equivalent to '.git'.
|
||||||
|
-- Expand $GIT_DIR (and `path`) to a full path (see :help filename-modifiers), since
|
||||||
|
-- it's possible to set it to a relative path. We want to make our best
|
||||||
|
-- effort to expand that to a valid absolute path.
|
||||||
|
if vim.fn.fnamemodify(path, ":p") == vim.fn.fnamemodify(vim.env.GIT_DIR, ":p") then
|
||||||
|
return true
|
||||||
|
elseif vim.fn.fnamemodify(path, ":t") == ".git" then
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local IGNORED_PATHS = {
|
||||||
|
-- disable watchers on kernel filesystems
|
||||||
|
-- which have a lot of unwanted events
|
||||||
|
"/sys",
|
||||||
|
"/proc",
|
||||||
|
"/dev",
|
||||||
|
}
|
||||||
|
|
||||||
|
---@param path string
|
||||||
|
---@return boolean
|
||||||
|
local function is_folder_ignored(path)
|
||||||
|
for _, folder in ipairs(IGNORED_PATHS) do
|
||||||
|
if vim.startswith(path, folder) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(M.config.filesystem_watchers.ignore_dirs) == "table" then
|
||||||
|
for _, ignore_dir in ipairs(M.config.filesystem_watchers.ignore_dirs) do
|
||||||
|
if vim.fn.match(path, ignore_dir) ~= -1 then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif type(M.config.filesystem_watchers.ignore_dirs) == "function" then
|
||||||
|
return M.config.filesystem_watchers.ignore_dirs(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node DirectoryNode
|
||||||
|
---@return Watcher|nil
|
||||||
|
function M.create_watcher(node)
|
||||||
|
if not M.config.filesystem_watchers.enable or type(node) ~= "table" then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local path = node.link_to or node.absolute_path
|
||||||
|
if is_git(path) or is_folder_ignored(path) then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param watcher Watcher
|
||||||
|
local function callback(watcher)
|
||||||
|
log.line("watcher", "node event scheduled refresh %s", watcher.data.context)
|
||||||
|
utils.debounce(watcher.data.context, M.config.filesystem_watchers.debounce_delay, function()
|
||||||
|
if watcher.destroyed then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if node.link_to then
|
||||||
|
log.line("watcher", "node event executing refresh '%s' -> '%s'", node.link_to, node.absolute_path)
|
||||||
|
else
|
||||||
|
log.line("watcher", "node event executing refresh '%s'", node.absolute_path)
|
||||||
|
end
|
||||||
|
git.refresh_dir(node)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.uid = M.uid + 1
|
||||||
|
return Watcher:create({
|
||||||
|
path = path,
|
||||||
|
callback = callback,
|
||||||
|
data = {
|
||||||
|
context = "explorer:watch:" .. path .. ":" .. M.uid
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.config.filesystem_watchers = opts.filesystem_watchers
|
||||||
|
M.uid = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -1,78 +1,415 @@
|
|||||||
local git_utils = require "nvim-tree.git.utils"
|
local log = require("nvim-tree.log")
|
||||||
local Runner = require "nvim-tree.git.runner"
|
local utils = require("nvim-tree.utils")
|
||||||
|
local git_utils = require("nvim-tree.git.utils")
|
||||||
|
|
||||||
|
local GitRunner = require("nvim-tree.git.runner")
|
||||||
|
local Watcher = require("nvim-tree.watcher").Watcher
|
||||||
|
local Iterator = require("nvim-tree.iterators.node-iterator")
|
||||||
|
local DirectoryNode = require("nvim-tree.node.directory")
|
||||||
|
|
||||||
|
---Git short format status xy
|
||||||
|
---@alias GitXY string
|
||||||
|
|
||||||
|
-- Git short-format status
|
||||||
|
---@alias GitPathXY table<string, GitXY>
|
||||||
|
|
||||||
|
-- Git short-format statuses
|
||||||
|
---@alias GitPathXYs table<string, GitXY[]>
|
||||||
|
|
||||||
|
---Git short-format statuses for a single node
|
||||||
|
---@class (exact) GitNodeStatus
|
||||||
|
---@field file GitXY?
|
||||||
|
---@field dir table<"direct" | "indirect", GitXY[]>?
|
||||||
|
|
||||||
|
---Git state for an entire repo
|
||||||
|
---@class (exact) GitProject
|
||||||
|
---@field files GitProjectFiles?
|
||||||
|
---@field dirs GitProjectDirs?
|
||||||
|
---@field watcher Watcher?
|
||||||
|
|
||||||
|
---@alias GitProjectFiles GitPathXY
|
||||||
|
---@alias GitProjectDirs table<"direct" | "indirect", GitPathXYs>
|
||||||
|
|
||||||
local M = {
|
local M = {
|
||||||
config = nil,
|
config = {},
|
||||||
projects = {},
|
|
||||||
cwd_to_project_root = {},
|
---all projects keyed by toplevel
|
||||||
|
---@type table<string, GitProject>
|
||||||
|
_projects_by_toplevel = {},
|
||||||
|
|
||||||
|
---index of paths inside toplevels, false when not inside a project
|
||||||
|
---@type table<string, string|false>
|
||||||
|
_toplevels_by_path = {},
|
||||||
|
|
||||||
|
-- git dirs by toplevel
|
||||||
|
---@type table<string, string>
|
||||||
|
_git_dirs_by_toplevel = {},
|
||||||
}
|
}
|
||||||
|
|
||||||
function M.reload()
|
-- Files under .git that should result in a reload when changed.
|
||||||
if not M.config.enable then
|
-- Utilities (like watchman) can also write to this directory (often) and aren't useful for us.
|
||||||
|
local WATCHED_FILES = {
|
||||||
|
"FETCH_HEAD", -- remote ref
|
||||||
|
"HEAD", -- local ref
|
||||||
|
"HEAD.lock", -- HEAD will not always be updated e.g. revert
|
||||||
|
"config", -- user config
|
||||||
|
"index", -- staging area
|
||||||
|
}
|
||||||
|
|
||||||
|
---@param toplevel string|nil
|
||||||
|
---@param path string|nil
|
||||||
|
---@param project GitProject
|
||||||
|
---@param project_files GitProjectFiles?
|
||||||
|
local function reload_git_project(toplevel, path, project, project_files)
|
||||||
|
if path then
|
||||||
|
for p in pairs(project.files) do
|
||||||
|
if p:find(path, 1, true) == 1 then
|
||||||
|
project.files[p] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
project.files = vim.tbl_deep_extend("force", project.files, project_files)
|
||||||
|
else
|
||||||
|
project.files = project_files or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
project.dirs = git_utils.project_files_to_project_dirs(project.files, toplevel)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Is this path in a known ignored directory?
|
||||||
|
---@param path string
|
||||||
|
---@param project GitProject
|
||||||
|
---@return boolean
|
||||||
|
local function path_ignored_in_project(path, project)
|
||||||
|
if not path or not project then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if project.files then
|
||||||
|
for p, xy in pairs(project.files) do
|
||||||
|
if xy == "!!" and vim.startswith(path, p) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return GitProject[] maybe empty
|
||||||
|
function M.reload_all_projects()
|
||||||
|
if not M.config.git.enable then
|
||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
|
|
||||||
for project_root in pairs(M.projects) do
|
for toplevel in pairs(M._projects_by_toplevel) do
|
||||||
M.projects[project_root] = {}
|
M.reload_project(toplevel)
|
||||||
local git_status = Runner.run {
|
|
||||||
project_root = project_root,
|
|
||||||
list_untracked = git_utils.should_show_untracked(project_root),
|
|
||||||
list_ignored = true,
|
|
||||||
timeout = M.config.timeout,
|
|
||||||
}
|
|
||||||
M.projects[project_root] = {
|
|
||||||
files = git_status,
|
|
||||||
dirs = git_utils.file_status_to_dir_status(git_status, project_root),
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return M.projects
|
return M._projects_by_toplevel
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.get_project_root(cwd)
|
--- Reload one project. Does nothing when no project or path is ignored
|
||||||
if M.cwd_to_project_root[cwd] then
|
---@param toplevel string?
|
||||||
return M.cwd_to_project_root[cwd]
|
---@param path string? optional path to update only
|
||||||
|
---@param callback function?
|
||||||
|
function M.reload_project(toplevel, path, callback)
|
||||||
|
local project = M._projects_by_toplevel[toplevel] --[[@as GitProject]]
|
||||||
|
|
||||||
|
if not toplevel or not project or not M.config.git.enable then
|
||||||
|
if callback then
|
||||||
|
callback()
|
||||||
|
end
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if M.cwd_to_project_root[cwd] == false then
|
if path and (path:find(toplevel, 1, true) ~= 1 or path_ignored_in_project(path, project)) then
|
||||||
|
if callback then
|
||||||
|
callback()
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type GitRunnerArgs
|
||||||
|
local args = {
|
||||||
|
toplevel = toplevel,
|
||||||
|
path = path,
|
||||||
|
list_untracked = git_utils.should_show_untracked(toplevel),
|
||||||
|
list_ignored = true,
|
||||||
|
timeout = M.config.git.timeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
if callback then
|
||||||
|
---@param path_xy GitPathXY
|
||||||
|
args.callback = function(path_xy)
|
||||||
|
reload_git_project(toplevel, path, project, path_xy)
|
||||||
|
callback()
|
||||||
|
end
|
||||||
|
GitRunner:run(args)
|
||||||
|
else
|
||||||
|
-- TODO #1974 use callback once async/await is available
|
||||||
|
reload_git_project(toplevel, path, project, GitRunner:run(args))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Retrieve a known project
|
||||||
|
---@param toplevel string?
|
||||||
|
---@return GitProject? project
|
||||||
|
function M.get_project(toplevel)
|
||||||
|
return M._projects_by_toplevel[toplevel]
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Retrieve the toplevel for a path. nil on:
|
||||||
|
--- git disabled
|
||||||
|
--- not part of a project
|
||||||
|
--- not a directory
|
||||||
|
--- path in git.disable_for_dirs
|
||||||
|
---@param path string absolute
|
||||||
|
---@return string|nil
|
||||||
|
function M.get_toplevel(path)
|
||||||
|
if not path then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local project_root = git_utils.get_toplevel(cwd)
|
if not M.config.git.enable then
|
||||||
return project_root
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local tl = M._toplevels_by_path[path]
|
||||||
|
if tl then
|
||||||
|
return tl
|
||||||
|
elseif tl == false then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local stat, _ = vim.loop.fs_stat(path)
|
||||||
|
if not stat or stat.type ~= "directory" then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- short-circuit any known ignored paths
|
||||||
|
for root, project in pairs(M._projects_by_toplevel) do
|
||||||
|
if project and path_ignored_in_project(path, project) then
|
||||||
|
M._toplevels_by_path[path] = root
|
||||||
|
return root
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- attempt to fetch toplevel
|
||||||
|
local toplevel, git_dir = git_utils.get_toplevel(path)
|
||||||
|
if not toplevel or not git_dir then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local toplevel_norm = vim.fn.fnamemodify(toplevel, ":p")
|
||||||
|
|
||||||
|
-- ignore disabled paths
|
||||||
|
if type(M.config.git.disable_for_dirs) == "table" then
|
||||||
|
for _, disabled_for_dir in ipairs(M.config.git.disable_for_dirs) do
|
||||||
|
local disabled_norm = vim.fn.fnamemodify(disabled_for_dir, ":p")
|
||||||
|
if toplevel_norm == disabled_norm then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif type(M.config.git.disable_for_dirs) == "function" then
|
||||||
|
if M.config.git.disable_for_dirs(toplevel_norm) then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M._toplevels_by_path[path] = toplevel
|
||||||
|
|
||||||
|
M._git_dirs_by_toplevel[toplevel] = git_dir
|
||||||
|
|
||||||
|
toplevel = M._toplevels_by_path[path]
|
||||||
|
if toplevel == false then
|
||||||
|
return nil
|
||||||
|
else
|
||||||
|
return toplevel
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.load_project_status(cwd)
|
local function reload_tree_at(toplevel)
|
||||||
if not M.config.enable then
|
if not M.config.git.enable or not toplevel then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
log.line("watcher", "git event executing '%s'", toplevel)
|
||||||
|
local root_node = utils.get_node_from_path(toplevel)
|
||||||
|
if not root_node then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
M.reload_project(toplevel, nil, function()
|
||||||
|
local project = M.get_project(toplevel)
|
||||||
|
|
||||||
|
Iterator.builder(root_node.nodes)
|
||||||
|
:hidden()
|
||||||
|
:applier(function(node)
|
||||||
|
local parent_ignored = node.parent and node.parent:is_git_ignored() or false
|
||||||
|
node:update_git_status(parent_ignored, project)
|
||||||
|
end)
|
||||||
|
:recursor(function(node)
|
||||||
|
return node.nodes and #node.nodes > 0 and node.nodes
|
||||||
|
end)
|
||||||
|
:iterate()
|
||||||
|
|
||||||
|
root_node.explorer.renderer:draw()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Load the project status for a path. Does nothing when no toplevel for path.
|
||||||
|
--- Only fetches project status when unknown, otherwise returns existing.
|
||||||
|
---@param path string absolute
|
||||||
|
---@return GitProject maybe empty
|
||||||
|
function M.load_project(path)
|
||||||
|
if not M.config.git.enable then
|
||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
|
|
||||||
local project_root = M.get_project_root(cwd)
|
local toplevel = M.get_toplevel(path)
|
||||||
if not project_root then
|
if not toplevel then
|
||||||
M.cwd_to_project_root[cwd] = false
|
M._toplevels_by_path[path] = false
|
||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
|
|
||||||
local status = M.projects[project_root]
|
local project = M._projects_by_toplevel[toplevel]
|
||||||
if status then
|
if project then
|
||||||
return status
|
return project
|
||||||
end
|
end
|
||||||
|
|
||||||
local git_status = Runner.run {
|
local path_xys = GitRunner:run({
|
||||||
project_root = project_root,
|
toplevel = toplevel,
|
||||||
list_untracked = git_utils.should_show_untracked(project_root),
|
list_untracked = git_utils.should_show_untracked(toplevel),
|
||||||
list_ignored = true,
|
list_ignored = true,
|
||||||
timeout = M.config.timeout,
|
timeout = M.config.git.timeout,
|
||||||
}
|
})
|
||||||
M.projects[project_root] = {
|
|
||||||
files = git_status,
|
local watcher = nil
|
||||||
dirs = git_utils.file_status_to_dir_status(git_status, project_root),
|
if M.config.filesystem_watchers.enable then
|
||||||
}
|
log.line("watcher", "git start")
|
||||||
return M.projects[project_root]
|
|
||||||
|
---@param w Watcher
|
||||||
|
local callback = function(w)
|
||||||
|
log.line("watcher", "git event scheduled '%s'", w.data.toplevel)
|
||||||
|
utils.debounce("git:watcher:" .. w.data.toplevel, M.config.filesystem_watchers.debounce_delay, function()
|
||||||
|
if w.destroyed then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
reload_tree_at(w.data.toplevel)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local git_dir = vim.env.GIT_DIR or M._git_dirs_by_toplevel[toplevel] or utils.path_join({ toplevel, ".git" })
|
||||||
|
watcher = Watcher:create({
|
||||||
|
path = git_dir,
|
||||||
|
files = WATCHED_FILES,
|
||||||
|
callback = callback,
|
||||||
|
data = {
|
||||||
|
toplevel = toplevel,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
if path_xys then
|
||||||
|
M._projects_by_toplevel[toplevel] = {
|
||||||
|
files = path_xys,
|
||||||
|
dirs = git_utils.project_files_to_project_dirs(path_xys, toplevel),
|
||||||
|
watcher = watcher,
|
||||||
|
}
|
||||||
|
return M._projects_by_toplevel[toplevel]
|
||||||
|
else
|
||||||
|
M._toplevels_by_path[path] = false
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param dir DirectoryNode
|
||||||
|
---@param project GitProject?
|
||||||
|
---@param root string?
|
||||||
|
function M.update_parent_projects(dir, project, root)
|
||||||
|
while project and dir do
|
||||||
|
-- step up to the containing project
|
||||||
|
if dir.absolute_path == root then
|
||||||
|
-- stop at the top of the tree
|
||||||
|
if not dir.parent then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
root = M.get_toplevel(dir.parent.absolute_path)
|
||||||
|
|
||||||
|
-- stop when no more projects
|
||||||
|
if not root then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
-- update the containing project
|
||||||
|
project = M.get_project(root)
|
||||||
|
M.reload_project(root, dir.absolute_path, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- update status
|
||||||
|
dir:update_git_status(dir.parent and dir.parent:is_git_ignored() or false, project)
|
||||||
|
|
||||||
|
-- maybe parent
|
||||||
|
dir = dir.parent
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Refresh contents and git status for a single directory
|
||||||
|
---@param dir DirectoryNode
|
||||||
|
function M.refresh_dir(dir)
|
||||||
|
local node = dir:get_parent_of_group() or dir
|
||||||
|
local toplevel = M.get_toplevel(dir.absolute_path)
|
||||||
|
|
||||||
|
M.reload_project(toplevel, dir.absolute_path, function()
|
||||||
|
local project = M.get_project(toplevel) or {}
|
||||||
|
|
||||||
|
dir.explorer:reload(node, project)
|
||||||
|
|
||||||
|
M.update_parent_projects(dir, project, toplevel)
|
||||||
|
|
||||||
|
dir.explorer.renderer:draw()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param dir DirectoryNode?
|
||||||
|
---@param projects GitProject[]
|
||||||
|
function M.reload_node_status(dir, projects)
|
||||||
|
dir = dir and dir:as(DirectoryNode)
|
||||||
|
if not dir or #dir.nodes == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local toplevel = M.get_toplevel(dir.absolute_path)
|
||||||
|
local project = projects[toplevel] or {}
|
||||||
|
for _, node in ipairs(dir.nodes) do
|
||||||
|
node:update_git_status(dir:is_git_ignored(), project)
|
||||||
|
M.reload_node_status(node:as(DirectoryNode), projects)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.purge_state()
|
||||||
|
log.line("git", "purge_state")
|
||||||
|
|
||||||
|
for _, project in pairs(M._projects_by_toplevel) do
|
||||||
|
if project.watcher then
|
||||||
|
project.watcher:destroy()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M._projects_by_toplevel = {}
|
||||||
|
M._toplevels_by_path = {}
|
||||||
|
M._git_dirs_by_toplevel = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Disable git integration permanently
|
||||||
|
function M.disable_git_integration()
|
||||||
|
log.line("git", "disabling git integration")
|
||||||
|
M.purge_state()
|
||||||
|
M.config.git.enable = false
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.setup(opts)
|
function M.setup(opts)
|
||||||
M.config = opts.git
|
M.config.git = opts.git
|
||||||
|
M.config.filesystem_watchers = opts.filesystem_watchers
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -1,30 +1,87 @@
|
|||||||
local uv = vim.loop
|
local log = require("nvim-tree.log")
|
||||||
local log = require "nvim-tree.log"
|
local utils = require("nvim-tree.utils")
|
||||||
local utils = require "nvim-tree.utils"
|
local notify = require("nvim-tree.notify")
|
||||||
|
|
||||||
local Runner = {}
|
local Class = require("nvim-tree.classic")
|
||||||
Runner.__index = Runner
|
|
||||||
|
---@class (exact) GitRunner: Class
|
||||||
|
---@field private toplevel string absolute path
|
||||||
|
---@field private path string? absolute path
|
||||||
|
---@field private list_untracked boolean
|
||||||
|
---@field private list_ignored boolean
|
||||||
|
---@field private timeout integer
|
||||||
|
---@field private callback fun(path_xy: GitPathXY)?
|
||||||
|
---@field private path_xy GitPathXY
|
||||||
|
---@field private rc integer? -- -1 indicates timeout
|
||||||
|
local GitRunner = Class:extend()
|
||||||
|
|
||||||
|
---@class GitRunner
|
||||||
|
---@overload fun(args: GitRunnerArgs): GitRunner
|
||||||
|
|
||||||
|
---@class (exact) GitRunnerArgs
|
||||||
|
---@field toplevel string absolute path
|
||||||
|
---@field path string? absolute path
|
||||||
|
---@field list_untracked boolean
|
||||||
|
---@field list_ignored boolean
|
||||||
|
---@field timeout integer
|
||||||
|
---@field callback fun(path_xy: GitPathXY)?
|
||||||
|
|
||||||
|
local timeouts = 0
|
||||||
|
local MAX_TIMEOUTS = 5
|
||||||
|
|
||||||
|
---@protected
|
||||||
|
---@param args GitRunnerArgs
|
||||||
|
function GitRunner:new(args)
|
||||||
|
self.toplevel = args.toplevel
|
||||||
|
self.path = args.path
|
||||||
|
self.list_untracked = args.list_untracked
|
||||||
|
self.list_ignored = args.list_ignored
|
||||||
|
self.timeout = args.timeout
|
||||||
|
self.callback = args.callback
|
||||||
|
|
||||||
|
self.path_xy = {}
|
||||||
|
self.rc = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@param status string
|
||||||
|
---@param path string|nil
|
||||||
|
function GitRunner:parse_status_output(status, path)
|
||||||
|
if not path then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
function Runner:_parse_status_output(line)
|
|
||||||
local status = line:sub(1, 2)
|
|
||||||
-- removing `"` when git is returning special file status containing spaces
|
|
||||||
local path = line:sub(4, -2):gsub('^"', ""):gsub('"$', "")
|
|
||||||
-- replacing slashes if on windows
|
-- replacing slashes if on windows
|
||||||
if vim.fn.has "win32" == 1 then
|
if vim.fn.has("win32") == 1 then
|
||||||
path = path:gsub("/", "\\")
|
path = path:gsub("/", "\\")
|
||||||
end
|
end
|
||||||
if #status > 0 and #path > 0 then
|
if #status > 0 and #path > 0 then
|
||||||
self.output[utils.path_remove_trailing(utils.path_join { self.project_root, path })] = status
|
self.path_xy[utils.path_remove_trailing(utils.path_join({ self.toplevel, path }))] = status
|
||||||
end
|
end
|
||||||
return #line
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Runner:_handle_incoming_data(prev_output, incoming)
|
---@private
|
||||||
|
---@param prev_output string
|
||||||
|
---@param incoming string
|
||||||
|
---@return string
|
||||||
|
function GitRunner:handle_incoming_data(prev_output, incoming)
|
||||||
if incoming and utils.str_find(incoming, "\n") then
|
if incoming and utils.str_find(incoming, "\n") then
|
||||||
local prev = prev_output .. incoming
|
local prev = prev_output .. incoming
|
||||||
local i = 1
|
local i = 1
|
||||||
for line in prev:gmatch "[^\n]*\n" do
|
local skip_next_line = false
|
||||||
i = i + self:_parse_status_output(line)
|
for line in prev:gmatch("[^\n]*\n") do
|
||||||
|
if skip_next_line then
|
||||||
|
skip_next_line = false
|
||||||
|
else
|
||||||
|
local status = line:sub(1, 2)
|
||||||
|
local path = line:sub(4, -2)
|
||||||
|
if utils.str_find(status, "R") then
|
||||||
|
-- skip next line if it is a rename entry
|
||||||
|
skip_next_line = true
|
||||||
|
end
|
||||||
|
self:parse_status_output(status, path)
|
||||||
|
end
|
||||||
|
i = i + #line
|
||||||
end
|
end
|
||||||
|
|
||||||
return prev:sub(i, -1)
|
return prev:sub(i, -1)
|
||||||
@@ -34,38 +91,54 @@ function Runner:_handle_incoming_data(prev_output, incoming)
|
|||||||
return prev_output .. incoming
|
return prev_output .. incoming
|
||||||
end
|
end
|
||||||
|
|
||||||
for line in prev_output:gmatch "[^\n]*\n" do
|
for line in prev_output:gmatch("[^\n]*\n") do
|
||||||
self._parse_status_output(line)
|
self:parse_status_output(line)
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil
|
return ""
|
||||||
end
|
end
|
||||||
|
|
||||||
function Runner:_getopts(stdout_handle, stderr_handle)
|
---@private
|
||||||
|
---@param stdout_handle uv.uv_pipe_t
|
||||||
|
---@param stderr_handle uv.uv_pipe_t
|
||||||
|
---@return uv.spawn.options
|
||||||
|
function GitRunner:get_spawn_options(stdout_handle, stderr_handle)
|
||||||
local untracked = self.list_untracked and "-u" or nil
|
local untracked = self.list_untracked and "-u" or nil
|
||||||
local ignored = (self.list_untracked and self.list_ignored) and "--ignored=matching" or "--ignored=no"
|
local ignored = (self.list_untracked and self.list_ignored) and "--ignored=matching" or "--ignored=no"
|
||||||
return {
|
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,
|
cwd = self.toplevel,
|
||||||
stdio = { nil, stdout_handle, stderr_handle },
|
stdio = { nil, stdout_handle, stderr_handle },
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
function Runner:_log_raw_output(output)
|
---@private
|
||||||
if output and type(output) == "string" then
|
---@param output string
|
||||||
|
function GitRunner:log_raw_output(output)
|
||||||
|
if log.enabled("git") and output and type(output) == "string" then
|
||||||
log.raw("git", "%s", output)
|
log.raw("git", "%s", output)
|
||||||
|
log.line("git", "done")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Runner:_run_git_job()
|
---@private
|
||||||
|
---@param callback function|nil
|
||||||
|
function GitRunner:run_git_job(callback)
|
||||||
local handle, pid
|
local handle, pid
|
||||||
local stdout = uv.new_pipe(false)
|
local stdout = vim.loop.new_pipe(false)
|
||||||
local stderr = uv.new_pipe(false)
|
local stderr = vim.loop.new_pipe(false)
|
||||||
local timer = uv.new_timer()
|
local timer = vim.loop.new_timer()
|
||||||
|
|
||||||
|
if stdout == nil or stderr == nil or timer == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local function on_finish(rc)
|
local function on_finish(rc)
|
||||||
self.rc = rc or 0
|
self.rc = rc or 0
|
||||||
if timer:is_closing() or stdout:is_closing() or stderr:is_closing() or (handle and handle:is_closing()) then
|
if timer:is_closing() or stdout:is_closing() or stderr:is_closing() or (handle and handle:is_closing()) then
|
||||||
|
if callback then
|
||||||
|
callback()
|
||||||
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
timer:stop()
|
timer:stop()
|
||||||
@@ -74,20 +147,26 @@ function Runner:_run_git_job()
|
|||||||
stderr:read_stop()
|
stderr:read_stop()
|
||||||
stdout:close()
|
stdout:close()
|
||||||
stderr:close()
|
stderr:close()
|
||||||
if handle then
|
|
||||||
|
-- don't close the handle when killing as it will leave a zombie
|
||||||
|
if rc == -1 then
|
||||||
|
pcall(vim.loop.kill, pid, "sigkill")
|
||||||
|
elseif handle then
|
||||||
handle:close()
|
handle:close()
|
||||||
end
|
end
|
||||||
|
|
||||||
pcall(uv.kill, pid)
|
if callback then
|
||||||
|
callback()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local opts = self:_getopts(stdout, stderr)
|
local spawn_options = self:get_spawn_options(stdout, stderr)
|
||||||
log.line("git", "running job with timeout %dms", self.timeout)
|
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(spawn_options.args), " "))
|
||||||
|
|
||||||
handle, pid = uv.spawn(
|
handle, pid = vim.loop.spawn(
|
||||||
"git",
|
"git",
|
||||||
opts,
|
spawn_options,
|
||||||
vim.schedule_wrap(function(rc)
|
vim.schedule_wrap(function(rc)
|
||||||
on_finish(rc)
|
on_finish(rc)
|
||||||
end)
|
end)
|
||||||
@@ -106,53 +185,89 @@ function Runner:_run_git_job()
|
|||||||
if err then
|
if err then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
self:_log_raw_output(data)
|
if data then
|
||||||
output_leftover = self:_handle_incoming_data(output_leftover, data)
|
data = data:gsub("%z", "\n")
|
||||||
|
end
|
||||||
|
self:log_raw_output(data)
|
||||||
|
output_leftover = self:handle_incoming_data(output_leftover, data)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function manage_stderr(_, data)
|
local function manage_stderr(_, data)
|
||||||
self:_log_raw_output(data)
|
self:log_raw_output(data)
|
||||||
end
|
end
|
||||||
|
|
||||||
uv.read_start(stdout, vim.schedule_wrap(manage_stdout))
|
vim.loop.read_start(stdout, vim.schedule_wrap(manage_stdout))
|
||||||
uv.read_start(stderr, vim.schedule_wrap(manage_stderr))
|
vim.loop.read_start(stderr, vim.schedule_wrap(manage_stderr))
|
||||||
end
|
end
|
||||||
|
|
||||||
function Runner:_wait()
|
---@private
|
||||||
|
function GitRunner:wait()
|
||||||
local function is_done()
|
local function is_done()
|
||||||
return self.rc ~= nil
|
return self.rc ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
while not vim.wait(30, is_done) do
|
while not vim.wait(30, is_done) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- This module runs a git process, which will be killed if it takes more than timeout which defaults to 400ms
|
---@private
|
||||||
function Runner.run(opts)
|
function GitRunner:finalise()
|
||||||
local ps = log.profile_start("git job %s", opts.project_root)
|
|
||||||
|
|
||||||
local self = setmetatable({
|
|
||||||
project_root = opts.project_root,
|
|
||||||
list_untracked = opts.list_untracked,
|
|
||||||
list_ignored = opts.list_ignored,
|
|
||||||
timeout = opts.timeout or 400,
|
|
||||||
output = {},
|
|
||||||
rc = nil, -- -1 indicates timeout
|
|
||||||
}, Runner)
|
|
||||||
|
|
||||||
self:_run_git_job()
|
|
||||||
self:_wait()
|
|
||||||
|
|
||||||
log.profile_end(ps, "git job %s", opts.project_root)
|
|
||||||
|
|
||||||
if self.rc == -1 then
|
if self.rc == -1 then
|
||||||
log.line("git", "job timed out")
|
log.line("git", "job timed out %s %s", self.toplevel, self.path)
|
||||||
|
timeouts = timeouts + 1
|
||||||
|
if timeouts == MAX_TIMEOUTS then
|
||||||
|
notify.warn(string.format("%d git jobs have timed out after git.timeout %dms, disabling git integration.", timeouts,
|
||||||
|
self.timeout))
|
||||||
|
require("nvim-tree.git").disable_git_integration()
|
||||||
|
end
|
||||||
elseif self.rc ~= 0 then
|
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, self.toplevel, self.path)
|
||||||
else
|
else
|
||||||
log.line("git", "job success")
|
log.line("git", "job success %s %s", self.toplevel, self.path)
|
||||||
end
|
end
|
||||||
|
|
||||||
return self.output
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return Runner
|
---Return nil when callback present
|
||||||
|
---@private
|
||||||
|
---@return GitPathXY?
|
||||||
|
function GitRunner:execute()
|
||||||
|
local async = self.callback ~= nil
|
||||||
|
local profile = log.profile_start("git %s job %s %s", async and "async" or "sync", self.toplevel, self.path)
|
||||||
|
|
||||||
|
if async and self.callback then
|
||||||
|
-- async, always call back
|
||||||
|
self:run_git_job(function()
|
||||||
|
log.profile_end(profile)
|
||||||
|
|
||||||
|
self:finalise()
|
||||||
|
|
||||||
|
self.callback(self.path_xy)
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
-- sync, maybe call back
|
||||||
|
self:run_git_job()
|
||||||
|
self:wait()
|
||||||
|
|
||||||
|
log.profile_end(profile)
|
||||||
|
|
||||||
|
self:finalise()
|
||||||
|
|
||||||
|
if self.callback then
|
||||||
|
self.callback(self.path_xy)
|
||||||
|
else
|
||||||
|
return self.path_xy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Static method to run a git process, which will be killed if it takes more than timeout
|
||||||
|
---Return nil when callback present
|
||||||
|
---@param args GitRunnerArgs
|
||||||
|
---@return GitPathXY?
|
||||||
|
function GitRunner:run(args)
|
||||||
|
local runner = GitRunner(args)
|
||||||
|
|
||||||
|
return runner:execute()
|
||||||
|
end
|
||||||
|
|
||||||
|
return GitRunner
|
||||||
|
|||||||
@@ -1,53 +1,191 @@
|
|||||||
local M = {}
|
local log = require("nvim-tree.log")
|
||||||
|
local utils = require("nvim-tree.utils")
|
||||||
|
|
||||||
|
local M = {
|
||||||
|
use_cygpath = false,
|
||||||
|
}
|
||||||
|
|
||||||
|
--- Retrieve the git toplevel directory
|
||||||
|
---@param cwd string path
|
||||||
|
---@return string|nil toplevel absolute path
|
||||||
|
---@return string|nil git_dir absolute path
|
||||||
function M.get_toplevel(cwd)
|
function M.get_toplevel(cwd)
|
||||||
local cmd = "git -C " .. vim.fn.shellescape(cwd) .. " rev-parse --show-toplevel"
|
local profile = log.profile_start("git toplevel git_dir %s", cwd)
|
||||||
local toplevel = vim.fn.system(cmd)
|
|
||||||
|
|
||||||
if not toplevel or #toplevel == 0 or toplevel:match "fatal" then
|
-- both paths are absolute
|
||||||
return nil
|
local cmd = { "git", "-C", cwd, "rev-parse", "--show-toplevel", "--absolute-git-dir" }
|
||||||
|
log.line("git", "%s", table.concat(cmd, " "))
|
||||||
|
|
||||||
|
local out = vim.fn.system(cmd)
|
||||||
|
|
||||||
|
log.raw("git", out)
|
||||||
|
log.profile_end(profile)
|
||||||
|
|
||||||
|
if vim.v.shell_error ~= 0 or not out or #out == 0 or out:match("fatal") then
|
||||||
|
return nil, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local toplevel, git_dir = out:match("([^\n]+)\n+([^\n]+)")
|
||||||
|
if not toplevel then
|
||||||
|
return nil, nil
|
||||||
|
end
|
||||||
|
if not git_dir then
|
||||||
|
git_dir = utils.path_join({ toplevel, ".git" })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- git always returns path with forward slashes
|
-- git always returns path with forward slashes
|
||||||
if vim.fn.has "win32" == 1 then
|
if vim.fn.has("win32") == 1 then
|
||||||
|
-- msys2 git support
|
||||||
|
-- cygpath calls must in array format to avoid shell compatibility issues
|
||||||
|
if M.use_cygpath then
|
||||||
|
toplevel = vim.fn.system({ "cygpath", "-w", toplevel })
|
||||||
|
if vim.v.shell_error ~= 0 then
|
||||||
|
return nil, nil
|
||||||
|
end
|
||||||
|
-- remove trailing newline(\n) character added by vim.fn.system
|
||||||
|
toplevel = toplevel:gsub("\n", "")
|
||||||
|
git_dir = vim.fn.system({ "cygpath", "-w", git_dir })
|
||||||
|
if vim.v.shell_error ~= 0 then
|
||||||
|
return nil, nil
|
||||||
|
end
|
||||||
|
-- remove trailing newline(\n) character added by vim.fn.system
|
||||||
|
git_dir = git_dir:gsub("\n", "")
|
||||||
|
end
|
||||||
toplevel = toplevel:gsub("/", "\\")
|
toplevel = toplevel:gsub("/", "\\")
|
||||||
|
git_dir = git_dir:gsub("/", "\\")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- remove newline
|
return toplevel, git_dir
|
||||||
return toplevel:sub(0, -2)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@type table<string, boolean>
|
||||||
local untracked = {}
|
local untracked = {}
|
||||||
|
|
||||||
|
---@param cwd string
|
||||||
|
---@return boolean
|
||||||
function M.should_show_untracked(cwd)
|
function M.should_show_untracked(cwd)
|
||||||
if untracked[cwd] ~= nil then
|
if untracked[cwd] ~= nil then
|
||||||
return untracked[cwd]
|
return untracked[cwd]
|
||||||
end
|
end
|
||||||
|
|
||||||
local cmd = "git -C " .. cwd .. " config --type=bool status.showUntrackedFiles"
|
local profile = log.profile_start("git untracked %s", cwd)
|
||||||
|
|
||||||
|
local cmd = { "git", "-C", cwd, "config", "status.showUntrackedFiles" }
|
||||||
|
log.line("git", table.concat(cmd, " "))
|
||||||
|
|
||||||
local has_untracked = vim.fn.system(cmd)
|
local has_untracked = vim.fn.system(cmd)
|
||||||
untracked[cwd] = vim.trim(has_untracked) ~= "false"
|
|
||||||
|
log.raw("git", has_untracked)
|
||||||
|
log.profile_end(profile)
|
||||||
|
|
||||||
|
untracked[cwd] = vim.trim(has_untracked) ~= "no"
|
||||||
return untracked[cwd]
|
return untracked[cwd]
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.file_status_to_dir_status(status, cwd)
|
---@param t table<string|integer, boolean>?
|
||||||
local dirs = {}
|
---@param k string|integer
|
||||||
for p, s in pairs(status) do
|
---@return table
|
||||||
|
local function nil_insert(t, k)
|
||||||
|
t = t or {}
|
||||||
|
t[k] = true
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param project_files GitProjectFiles
|
||||||
|
---@param cwd string|nil
|
||||||
|
---@return GitProjectDirs
|
||||||
|
function M.project_files_to_project_dirs(project_files, cwd)
|
||||||
|
---@type GitProjectDirs
|
||||||
|
local project_dirs = {}
|
||||||
|
|
||||||
|
project_dirs.direct = {}
|
||||||
|
for p, s in pairs(project_files) do
|
||||||
if s ~= "!!" then
|
if s ~= "!!" then
|
||||||
local modified = vim.fn.fnamemodify(p, ":h")
|
local modified = vim.fn.fnamemodify(p, ":h")
|
||||||
dirs[modified] = s
|
project_dirs.direct[modified] = nil_insert(project_dirs.direct[modified], s)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for dirname, s in pairs(dirs) do
|
project_dirs.indirect = {}
|
||||||
local modified = dirname
|
for dirname, statuses in pairs(project_dirs.direct) do
|
||||||
while modified ~= cwd and modified ~= "/" do
|
for s, _ in pairs(statuses) do
|
||||||
modified = vim.fn.fnamemodify(modified, ":h")
|
local modified = dirname
|
||||||
dirs[modified] = s
|
while modified ~= cwd and modified ~= "/" do
|
||||||
|
modified = vim.fn.fnamemodify(modified, ":h")
|
||||||
|
project_dirs.indirect[modified] = nil_insert(project_dirs.indirect[modified], s)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return dirs
|
for _, d in pairs(project_dirs) do
|
||||||
|
for dirname, statuses in pairs(d) do
|
||||||
|
local new_statuses = {}
|
||||||
|
for s, _ in pairs(statuses) do
|
||||||
|
table.insert(new_statuses, s)
|
||||||
|
end
|
||||||
|
d[dirname] = new_statuses
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return project_dirs
|
||||||
|
end
|
||||||
|
|
||||||
|
---Git file status for an absolute path
|
||||||
|
---@param parent_ignored boolean
|
||||||
|
---@param project GitProject?
|
||||||
|
---@param path string
|
||||||
|
---@param path_fallback string? alternative file path when no other file status
|
||||||
|
---@return GitNodeStatus
|
||||||
|
function M.git_status_file(parent_ignored, project, path, path_fallback)
|
||||||
|
---@type GitNodeStatus
|
||||||
|
local ns
|
||||||
|
|
||||||
|
if parent_ignored then
|
||||||
|
ns = {
|
||||||
|
file = "!!"
|
||||||
|
}
|
||||||
|
elseif project and project.files then
|
||||||
|
ns = {
|
||||||
|
file = project.files[path] or project.files[path_fallback]
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ns = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
return ns
|
||||||
|
end
|
||||||
|
|
||||||
|
---Git file and directory status for an absolute path
|
||||||
|
---@param parent_ignored boolean
|
||||||
|
---@param project GitProject?
|
||||||
|
---@param path string
|
||||||
|
---@param path_fallback string? alternative file path when no other file status
|
||||||
|
---@return GitNodeStatus?
|
||||||
|
function M.git_status_dir(parent_ignored, project, path, path_fallback)
|
||||||
|
---@type GitNodeStatus?
|
||||||
|
local ns
|
||||||
|
|
||||||
|
if parent_ignored then
|
||||||
|
ns = {
|
||||||
|
file = "!!"
|
||||||
|
}
|
||||||
|
elseif project then
|
||||||
|
ns = {
|
||||||
|
file = project.files and (project.files[path] or project.files[path_fallback]),
|
||||||
|
dir = project.dirs and {
|
||||||
|
direct = project.dirs.direct and project.dirs.direct[path],
|
||||||
|
indirect = project.dirs.indirect and project.dirs.indirect[path],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
return ns
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
if opts.git.cygwin_support then
|
||||||
|
M.use_cygpath = vim.fn.executable("cygpath") == 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
262
lua/nvim-tree/help.lua
Normal file
262
lua/nvim-tree/help.lua
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
local keymap = require("nvim-tree.keymap")
|
||||||
|
local api = {} -- circular dependency
|
||||||
|
|
||||||
|
local PAT_MOUSE = "^<.*Mouse"
|
||||||
|
local PAT_CTRL = "^<C%-"
|
||||||
|
local PAT_SPECIAL = "^<.+"
|
||||||
|
|
||||||
|
local WIN_HL = table.concat({
|
||||||
|
"NormalFloat:NvimTreeNormalFloat",
|
||||||
|
"WinSeparator:NvimTreeWinSeparator",
|
||||||
|
"CursorLine:NvimTreeCursorLine",
|
||||||
|
}, ",")
|
||||||
|
|
||||||
|
local M = {
|
||||||
|
config = {},
|
||||||
|
|
||||||
|
-- one and only buf/win
|
||||||
|
bufnr = nil,
|
||||||
|
winnr = nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
--- Shorten and normalise a vim command lhs
|
||||||
|
---@param lhs string
|
||||||
|
---@return string
|
||||||
|
local function tidy_lhs(lhs)
|
||||||
|
-- nvim_buf_get_keymap replaces leading "<" with "<lt>" e.g. "<lt>CTRL-v>"
|
||||||
|
lhs = lhs:gsub("^<lt>", "<")
|
||||||
|
|
||||||
|
-- shorten ctrls
|
||||||
|
if lhs:lower():match("^<ctrl%-") then
|
||||||
|
lhs = lhs:lower():gsub("^<ctrl%-", "<C%-")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- uppercase ctrls
|
||||||
|
if lhs:lower():match("^<c%-") then
|
||||||
|
lhs = lhs:upper()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- space is not escaped
|
||||||
|
lhs = lhs:gsub(" ", "<Space>")
|
||||||
|
|
||||||
|
return lhs
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Remove prefix 'nvim-tree: '
|
||||||
|
--- Hardcoded to keep default_on_attach simple
|
||||||
|
---@param desc string
|
||||||
|
---@return string
|
||||||
|
local function tidy_desc(desc)
|
||||||
|
return desc and desc:gsub("^nvim%-tree: ", "") or ""
|
||||||
|
end
|
||||||
|
|
||||||
|
--- sort vim command lhs roughly as per :help index
|
||||||
|
---@param a string
|
||||||
|
---@param b string
|
||||||
|
---@return boolean
|
||||||
|
local function sort_lhs(a, b)
|
||||||
|
-- mouse first
|
||||||
|
if a:match(PAT_MOUSE) and not b:match(PAT_MOUSE) then
|
||||||
|
return true
|
||||||
|
elseif not a:match(PAT_MOUSE) and b:match(PAT_MOUSE) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ctrl next
|
||||||
|
if a:match(PAT_CTRL) and not b:match(PAT_CTRL) then
|
||||||
|
return true
|
||||||
|
elseif not a:match(PAT_CTRL) and b:match(PAT_CTRL) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- special next
|
||||||
|
if a:match(PAT_SPECIAL) and not b:match(PAT_SPECIAL) then
|
||||||
|
return true
|
||||||
|
elseif not a:match(PAT_SPECIAL) and b:match(PAT_SPECIAL) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- remainder alpha
|
||||||
|
return a:gsub("[^a-zA-Z]", "") < b:gsub("[^a-zA-Z]", "")
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Compute all lines for the buffer
|
||||||
|
---@param map table keymap.get_keymap
|
||||||
|
---@return table strings of text
|
||||||
|
---@return table arrays of arguments 3-6 for nvim_buf_add_highlight()
|
||||||
|
---@return number maximum length of text
|
||||||
|
local function compute(map)
|
||||||
|
local head_lhs = "nvim-tree mappings"
|
||||||
|
local head_rhs1 = "exit: q"
|
||||||
|
local head_rhs2 = string.format("sort by %s: s", M.config.sort_by == "key" and "description" or "keymap")
|
||||||
|
|
||||||
|
-- formatted lhs and desc from active keymap
|
||||||
|
local mappings = vim.tbl_map(function(m)
|
||||||
|
return { lhs = tidy_lhs(m.lhs), desc = tidy_desc(m.desc) }
|
||||||
|
end, map)
|
||||||
|
|
||||||
|
-- sorter function for mappings
|
||||||
|
local sort_fn
|
||||||
|
|
||||||
|
if M.config.sort_by == "desc" then
|
||||||
|
sort_fn = function(a, b)
|
||||||
|
return a.desc:lower() < b.desc:lower()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- by default sort roughly by lhs
|
||||||
|
sort_fn = function(a, b)
|
||||||
|
return sort_lhs(a.lhs, b.lhs)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(mappings, sort_fn)
|
||||||
|
|
||||||
|
-- longest lhs and description
|
||||||
|
local max_lhs = 0
|
||||||
|
local max_desc = 0
|
||||||
|
for _, l in pairs(mappings) do
|
||||||
|
max_lhs = math.max(#l.lhs, max_lhs)
|
||||||
|
max_desc = math.max(#l.desc, max_desc)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- increase desc if lines are shorter than the header
|
||||||
|
max_desc = math.max(max_desc, #head_lhs + #head_rhs1 - max_lhs)
|
||||||
|
|
||||||
|
-- header text, not padded
|
||||||
|
local lines = {
|
||||||
|
head_lhs .. string.rep(" ", max_desc + max_lhs - #head_lhs - #head_rhs1 + 2) .. head_rhs1,
|
||||||
|
string.rep(" ", max_desc + max_lhs - #head_rhs2 + 2) .. head_rhs2,
|
||||||
|
}
|
||||||
|
local width = #lines[1]
|
||||||
|
|
||||||
|
-- header highlight, assume one character keys
|
||||||
|
local hl = {
|
||||||
|
{ "NvimTreeFolderName", 0, 0, #head_lhs },
|
||||||
|
{ "NvimTreeFolderName", 0, width - 1, width },
|
||||||
|
{ "NvimTreeFolderName", 1, width - 1, width },
|
||||||
|
}
|
||||||
|
|
||||||
|
-- mappings, left padded 1
|
||||||
|
local fmt = string.format(" %%-%ds %%-%ds", max_lhs, max_desc)
|
||||||
|
for i, l in ipairs(mappings) do
|
||||||
|
-- format in left aligned columns
|
||||||
|
local line = string.format(fmt, l.lhs, l.desc)
|
||||||
|
table.insert(lines, line)
|
||||||
|
width = math.max(#line, width)
|
||||||
|
|
||||||
|
-- highlight lhs
|
||||||
|
table.insert(hl, { "NvimTreeFolderName", i + 1, 1, #l.lhs + 1 })
|
||||||
|
end
|
||||||
|
|
||||||
|
return lines, hl, width
|
||||||
|
end
|
||||||
|
|
||||||
|
--- close the window and delete the buffer, if they exist
|
||||||
|
local function close()
|
||||||
|
if M.winnr then
|
||||||
|
vim.api.nvim_win_close(M.winnr, true)
|
||||||
|
M.winnr = nil
|
||||||
|
end
|
||||||
|
if M.bufnr then
|
||||||
|
vim.api.nvim_buf_delete(M.bufnr, { force = true })
|
||||||
|
M.bufnr = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- open a new window and buffer
|
||||||
|
local function open()
|
||||||
|
-- close existing, shouldn't be necessary
|
||||||
|
close()
|
||||||
|
|
||||||
|
-- fetch all mappings
|
||||||
|
local map = keymap.get_keymap()
|
||||||
|
|
||||||
|
-- text and highlight
|
||||||
|
local lines, hl, width = compute(map)
|
||||||
|
|
||||||
|
-- create the buffer
|
||||||
|
M.bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
|
|
||||||
|
-- populate it
|
||||||
|
vim.api.nvim_buf_set_lines(M.bufnr, 0, -1, false, lines)
|
||||||
|
|
||||||
|
if vim.fn.has("nvim-0.10") == 1 then
|
||||||
|
vim.api.nvim_set_option_value("modifiable", false, { buf = M.bufnr })
|
||||||
|
else
|
||||||
|
vim.api.nvim_buf_set_option(M.bufnr, "modifiable", false) ---@diagnostic disable-line: deprecated
|
||||||
|
end
|
||||||
|
|
||||||
|
-- highlight it
|
||||||
|
for _, h in ipairs(hl) do
|
||||||
|
vim.api.nvim_buf_add_highlight(M.bufnr, -1, h[1], h[2], h[3], h[4])
|
||||||
|
end
|
||||||
|
|
||||||
|
-- open a very restricted window
|
||||||
|
M.winnr = vim.api.nvim_open_win(M.bufnr, true, {
|
||||||
|
relative = "editor",
|
||||||
|
border = "single",
|
||||||
|
width = width,
|
||||||
|
height = #lines,
|
||||||
|
row = 1,
|
||||||
|
col = 0,
|
||||||
|
style = "minimal",
|
||||||
|
noautocmd = true,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- style it a bit like the tree
|
||||||
|
vim.wo[M.winnr].winhl = WIN_HL
|
||||||
|
vim.wo[M.winnr].cursorline = M.config.cursorline
|
||||||
|
|
||||||
|
local function toggle_sort()
|
||||||
|
M.config.sort_by = (M.config.sort_by == "desc") and "key" or "desc"
|
||||||
|
open()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- hardcoded
|
||||||
|
local help_keymaps = {
|
||||||
|
q = { fn = close, desc = "nvim-tree: exit help" },
|
||||||
|
["<Esc>"] = { fn = close, desc = "nvim-tree: exit help" }, -- hidden
|
||||||
|
s = { fn = toggle_sort, desc = "nvim-tree: toggle sorting method" },
|
||||||
|
}
|
||||||
|
|
||||||
|
-- api help binding closes
|
||||||
|
for _, m in ipairs(map) do
|
||||||
|
if m.callback == api.tree.toggle_help then
|
||||||
|
help_keymaps[m.lhs] = { fn = close, desc = "nvim-tree: exit help" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for k, v in pairs(help_keymaps) do
|
||||||
|
vim.keymap.set("n", k, v.fn, {
|
||||||
|
desc = v.desc,
|
||||||
|
buffer = M.bufnr,
|
||||||
|
noremap = true,
|
||||||
|
silent = true,
|
||||||
|
nowait = true,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- close window and delete buffer on leave
|
||||||
|
vim.api.nvim_create_autocmd({ "BufLeave", "WinLeave" }, {
|
||||||
|
buffer = M.bufnr,
|
||||||
|
once = true,
|
||||||
|
callback = close,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.toggle()
|
||||||
|
if M.winnr or M.bufnr then
|
||||||
|
close()
|
||||||
|
else
|
||||||
|
open()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.config.cursorline = opts.view.cursorline
|
||||||
|
M.config.sort_by = opts.help.sort_by
|
||||||
|
|
||||||
|
api = require("nvim-tree.api")
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
79
lua/nvim-tree/iterators/node-iterator.lua
Normal file
79
lua/nvim-tree/iterators/node-iterator.lua
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
---@class NodeIterator
|
||||||
|
local NodeIterator = {}
|
||||||
|
NodeIterator.__index = NodeIterator
|
||||||
|
|
||||||
|
---@param nodes Node[]
|
||||||
|
---@return NodeIterator
|
||||||
|
function NodeIterator.builder(nodes)
|
||||||
|
return setmetatable({
|
||||||
|
nodes = nodes,
|
||||||
|
_filter_hidden = function(node)
|
||||||
|
return not node.hidden
|
||||||
|
end,
|
||||||
|
_apply_fn_on_node = function(_) end,
|
||||||
|
_match = function(_) end,
|
||||||
|
_recurse_with = function(node)
|
||||||
|
return node.nodes
|
||||||
|
end,
|
||||||
|
}, NodeIterator)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return NodeIterator
|
||||||
|
function NodeIterator:hidden()
|
||||||
|
self._filter_hidden = function(_)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param f fun(node: Node): boolean
|
||||||
|
---@return NodeIterator
|
||||||
|
function NodeIterator:matcher(f)
|
||||||
|
self._match = f
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param f fun(node: Node, i: number)
|
||||||
|
---@return NodeIterator
|
||||||
|
function NodeIterator:applier(f)
|
||||||
|
self._apply_fn_on_node = f
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param f fun(node: Node): any
|
||||||
|
---@return NodeIterator
|
||||||
|
function NodeIterator:recursor(f)
|
||||||
|
self._recurse_with = f
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return Node|nil
|
||||||
|
---@return number|nil
|
||||||
|
function NodeIterator:iterate()
|
||||||
|
local iteration_count = 0
|
||||||
|
local function iter(nodes)
|
||||||
|
for _, node in ipairs(nodes) do
|
||||||
|
if self._filter_hidden(node) then
|
||||||
|
if not node.group_next then
|
||||||
|
iteration_count = iteration_count + 1
|
||||||
|
end
|
||||||
|
if self._match(node) then
|
||||||
|
return node, iteration_count
|
||||||
|
end
|
||||||
|
self._apply_fn_on_node(node, iteration_count)
|
||||||
|
local children = self._recurse_with(node)
|
||||||
|
if children then
|
||||||
|
local n = iter(children)
|
||||||
|
if n then
|
||||||
|
return n, iteration_count
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil, 0
|
||||||
|
end
|
||||||
|
|
||||||
|
return iter(self.nodes)
|
||||||
|
end
|
||||||
|
|
||||||
|
return NodeIterator
|
||||||
116
lua/nvim-tree/keymap.lua
Normal file
116
lua/nvim-tree/keymap.lua
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
--- Apply mappings to a scratch buffer and return buffer local mappings
|
||||||
|
---@param fn fun(bufnr: integer) on_attach or default_on_attach
|
||||||
|
---@return table as per vim.api.nvim_buf_get_keymap
|
||||||
|
local function generate_keymap(fn)
|
||||||
|
-- create an unlisted scratch buffer
|
||||||
|
local scratch_bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
|
|
||||||
|
-- apply mappings
|
||||||
|
fn(scratch_bufnr)
|
||||||
|
|
||||||
|
-- retrieve all
|
||||||
|
local keymap = vim.api.nvim_buf_get_keymap(scratch_bufnr, "")
|
||||||
|
|
||||||
|
-- delete the scratch buffer
|
||||||
|
vim.api.nvim_buf_delete(scratch_bufnr, { force = true })
|
||||||
|
|
||||||
|
return keymap
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return table
|
||||||
|
function M.get_keymap()
|
||||||
|
return generate_keymap(M.on_attach)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return table
|
||||||
|
function M.get_keymap_default()
|
||||||
|
return generate_keymap(M.default_on_attach)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param bufnr integer
|
||||||
|
function M.default_on_attach(bufnr)
|
||||||
|
local api = require("nvim-tree.api")
|
||||||
|
|
||||||
|
local function opts(desc)
|
||||||
|
return {
|
||||||
|
desc = "nvim-tree: " .. desc,
|
||||||
|
buffer = bufnr,
|
||||||
|
noremap = true,
|
||||||
|
silent = true,
|
||||||
|
nowait = true,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- BEGIN_DEFAULT_ON_ATTACH
|
||||||
|
vim.keymap.set("n", "<C-]>", api.tree.change_root_to_node, opts("CD"))
|
||||||
|
vim.keymap.set("n", "<C-e>", api.node.open.replace_tree_buffer, opts("Open: In Place"))
|
||||||
|
vim.keymap.set("n", "<C-k>", api.node.show_info_popup, opts("Info"))
|
||||||
|
vim.keymap.set("n", "<C-r>", api.fs.rename_sub, opts("Rename: Omit Filename"))
|
||||||
|
vim.keymap.set("n", "<C-t>", api.node.open.tab, opts("Open: New Tab"))
|
||||||
|
vim.keymap.set("n", "<C-v>", api.node.open.vertical, opts("Open: Vertical Split"))
|
||||||
|
vim.keymap.set("n", "<C-x>", api.node.open.horizontal, opts("Open: Horizontal Split"))
|
||||||
|
vim.keymap.set("n", "<BS>", api.node.navigate.parent_close, opts("Close Directory"))
|
||||||
|
vim.keymap.set("n", "<CR>", api.node.open.edit, opts("Open"))
|
||||||
|
vim.keymap.set("n", "<Tab>", api.node.open.preview, opts("Open Preview"))
|
||||||
|
vim.keymap.set("n", ">", api.node.navigate.sibling.next, opts("Next Sibling"))
|
||||||
|
vim.keymap.set("n", "<", api.node.navigate.sibling.prev, opts("Previous Sibling"))
|
||||||
|
vim.keymap.set("n", ".", api.node.run.cmd, opts("Run Command"))
|
||||||
|
vim.keymap.set("n", "-", api.tree.change_root_to_parent, opts("Up"))
|
||||||
|
vim.keymap.set("n", "a", api.fs.create, opts("Create File Or Directory"))
|
||||||
|
vim.keymap.set("n", "bd", api.marks.bulk.delete, opts("Delete Bookmarked"))
|
||||||
|
vim.keymap.set("n", "bt", api.marks.bulk.trash, opts("Trash Bookmarked"))
|
||||||
|
vim.keymap.set("n", "bmv", api.marks.bulk.move, opts("Move Bookmarked"))
|
||||||
|
vim.keymap.set("n", "B", api.tree.toggle_no_buffer_filter, opts("Toggle Filter: No Buffer"))
|
||||||
|
vim.keymap.set("n", "c", api.fs.copy.node, opts("Copy"))
|
||||||
|
vim.keymap.set("n", "C", api.tree.toggle_git_clean_filter, opts("Toggle Filter: Git Clean"))
|
||||||
|
vim.keymap.set("n", "[c", api.node.navigate.git.prev, opts("Prev Git"))
|
||||||
|
vim.keymap.set("n", "]c", api.node.navigate.git.next, opts("Next Git"))
|
||||||
|
vim.keymap.set("n", "d", api.fs.remove, opts("Delete"))
|
||||||
|
vim.keymap.set("n", "D", api.fs.trash, opts("Trash"))
|
||||||
|
vim.keymap.set("n", "E", api.tree.expand_all, opts("Expand All"))
|
||||||
|
vim.keymap.set("n", "e", api.fs.rename_basename, opts("Rename: Basename"))
|
||||||
|
vim.keymap.set("n", "]e", api.node.navigate.diagnostics.next, opts("Next Diagnostic"))
|
||||||
|
vim.keymap.set("n", "[e", api.node.navigate.diagnostics.prev, opts("Prev Diagnostic"))
|
||||||
|
vim.keymap.set("n", "F", api.live_filter.clear, opts("Live Filter: Clear"))
|
||||||
|
vim.keymap.set("n", "f", api.live_filter.start, opts("Live Filter: Start"))
|
||||||
|
vim.keymap.set("n", "g?", api.tree.toggle_help, opts("Help"))
|
||||||
|
vim.keymap.set("n", "gy", api.fs.copy.absolute_path, opts("Copy Absolute Path"))
|
||||||
|
vim.keymap.set("n", "ge", api.fs.copy.basename, opts("Copy Basename"))
|
||||||
|
vim.keymap.set("n", "H", api.tree.toggle_hidden_filter, opts("Toggle Filter: Dotfiles"))
|
||||||
|
vim.keymap.set("n", "I", api.tree.toggle_gitignore_filter, opts("Toggle Filter: Git Ignore"))
|
||||||
|
vim.keymap.set("n", "J", api.node.navigate.sibling.last, opts("Last Sibling"))
|
||||||
|
vim.keymap.set("n", "K", api.node.navigate.sibling.first, opts("First Sibling"))
|
||||||
|
vim.keymap.set("n", "L", api.node.open.toggle_group_empty, opts("Toggle Group Empty"))
|
||||||
|
vim.keymap.set("n", "M", api.tree.toggle_no_bookmark_filter, opts("Toggle Filter: No Bookmark"))
|
||||||
|
vim.keymap.set("n", "m", api.marks.toggle, opts("Toggle Bookmark"))
|
||||||
|
vim.keymap.set("n", "o", api.node.open.edit, opts("Open"))
|
||||||
|
vim.keymap.set("n", "O", api.node.open.no_window_picker, opts("Open: No Window Picker"))
|
||||||
|
vim.keymap.set("n", "p", api.fs.paste, opts("Paste"))
|
||||||
|
vim.keymap.set("n", "P", api.node.navigate.parent, opts("Parent Directory"))
|
||||||
|
vim.keymap.set("n", "q", api.tree.close, opts("Close"))
|
||||||
|
vim.keymap.set("n", "r", api.fs.rename, opts("Rename"))
|
||||||
|
vim.keymap.set("n", "R", api.tree.reload, opts("Refresh"))
|
||||||
|
vim.keymap.set("n", "s", api.node.run.system, opts("Run System"))
|
||||||
|
vim.keymap.set("n", "S", api.tree.search_node, opts("Search"))
|
||||||
|
vim.keymap.set("n", "u", api.fs.rename_full, opts("Rename: Full Path"))
|
||||||
|
vim.keymap.set("n", "U", api.tree.toggle_custom_filter, opts("Toggle Filter: Hidden"))
|
||||||
|
vim.keymap.set("n", "W", api.tree.collapse_all, opts("Collapse"))
|
||||||
|
vim.keymap.set("n", "x", api.fs.cut, opts("Cut"))
|
||||||
|
vim.keymap.set("n", "y", api.fs.copy.filename, opts("Copy Name"))
|
||||||
|
vim.keymap.set("n", "Y", api.fs.copy.relative_path, opts("Copy Relative Path"))
|
||||||
|
vim.keymap.set("n", "<2-LeftMouse>", api.node.open.edit, opts("Open"))
|
||||||
|
vim.keymap.set("n", "<2-RightMouse>", api.tree.change_root_to_node, opts("CD"))
|
||||||
|
-- END_DEFAULT_ON_ATTACH
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
if type(opts.on_attach) ~= "function" then
|
||||||
|
M.on_attach = M.default_on_attach
|
||||||
|
else
|
||||||
|
M.on_attach = opts.on_attach
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -1,238 +1,98 @@
|
|||||||
local utils = require "nvim-tree.utils"
|
local utils = require("nvim-tree.utils")
|
||||||
|
local notify = require("nvim-tree.notify")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
-- TODO update bit.ly/3vIpEOJ when adding a migration
|
-- silently move, please add to help nvim-tree-legacy-opts
|
||||||
|
|
||||||
-- 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)
|
local function refactored(opts)
|
||||||
-- mapping actions
|
-- 2022/06/20
|
||||||
if opts.view and opts.view.mappings and opts.view.mappings.list then
|
utils.move_missing_val(opts, "update_focused_file", "update_cwd", opts, "update_focused_file", "update_root", true)
|
||||||
for _, m in pairs(opts.view.mappings.list) do
|
utils.move_missing_val(opts, "", "update_cwd", opts, "", "sync_root_with_cwd", true)
|
||||||
if m.action == "toggle_ignored" then
|
|
||||||
m.action = "toggle_git_ignored"
|
-- 2022/11/07
|
||||||
end
|
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", true)
|
||||||
|
utils.move_missing_val(opts, "", "ignore_buf_on_tab_change", opts, "tab.sync", "ignore", true)
|
||||||
|
|
||||||
|
-- 2022/11/22
|
||||||
|
utils.move_missing_val(opts, "renderer", "root_folder_modifier", opts, "renderer", "root_folder_label", true)
|
||||||
|
|
||||||
|
-- 2023/01/01
|
||||||
|
utils.move_missing_val(opts, "update_focused_file", "debounce_delay", opts, "view", "debounce_delay", true)
|
||||||
|
|
||||||
|
-- 2023/01/08
|
||||||
|
utils.move_missing_val(opts, "trash", "require_confirm", opts, "ui.confirm", "trash", true)
|
||||||
|
|
||||||
|
-- 2023/01/15
|
||||||
|
if type(opts.view) == "table" and opts.view.adaptive_size ~= nil then
|
||||||
|
if opts.view.adaptive_size and type(opts.view.width) ~= "table" then
|
||||||
|
local width = opts.view.width
|
||||||
|
opts.view.width = {
|
||||||
|
min = width,
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
opts.view.adaptive_size = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- update_to_buf_dir -> hijack_directories
|
-- 2023/07/15
|
||||||
if opts.update_to_buf_dir ~= nil then
|
utils.move_missing_val(opts, "", "sort_by", opts, "sort", "sorter", true)
|
||||||
utils.table_create_missing(opts, "hijack_directories")
|
|
||||||
if opts.hijack_directories.enable == nil then
|
-- 2023/07/16
|
||||||
opts.hijack_directories.enable = opts.update_to_buf_dir.enable
|
utils.move_missing_val(opts, "git", "ignore", opts, "filters", "git_ignored", true)
|
||||||
end
|
|
||||||
if opts.hijack_directories.auto_open == nil then
|
-- 2023/08/26
|
||||||
opts.hijack_directories.auto_open = opts.update_to_buf_dir.auto_open
|
utils.move_missing_val(opts, "renderer.icons", "webdev_colors", opts, "renderer.icons.web_devicons.file", "color", true)
|
||||||
end
|
|
||||||
opts.update_to_buf_dir = nil
|
-- 2023/10/08
|
||||||
|
if type(opts.renderer) == "table" and type(opts.renderer.highlight_diagnostics) == "boolean" then
|
||||||
|
opts.renderer.highlight_diagnostics = opts.renderer.highlight_diagnostics and "name" or "none"
|
||||||
end
|
end
|
||||||
|
|
||||||
-- view.auto_resize -> actions.open_file.resize_window
|
-- 2023/10/21
|
||||||
if opts.view and opts.view.auto_resize ~= nil then
|
if type(opts.renderer) == "table" and type(opts.renderer.highlight_git) == "boolean" then
|
||||||
utils.table_create_missing(opts, "actions.open_file")
|
opts.renderer.highlight_git = opts.renderer.highlight_git and "name" or "none"
|
||||||
if opts.actions.open_file.resize_window == nil then
|
end
|
||||||
opts.actions.open_file.resize_window = opts.view.auto_resize
|
|
||||||
|
-- 2024/02/15
|
||||||
|
if type(opts.update_focused_file) == "table" then
|
||||||
|
if type(opts.update_focused_file.update_root) ~= "table" then
|
||||||
|
opts.update_focused_file.update_root = { enable = opts.update_focused_file.update_root }
|
||||||
end
|
end
|
||||||
opts.view.auto_resize = nil
|
end
|
||||||
|
utils.move_missing_val(opts, "update_focused_file", "ignore_list", opts, "update_focused_file.update_root", "ignore_list", true)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function deprecated(opts)
|
||||||
|
if type(opts.view) == "table" and opts.view.hide_root_folder then
|
||||||
|
notify.info("view.hide_root_folder is deprecated, please set renderer.root_folder_label = false")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function removed(opts)
|
local function removed(opts)
|
||||||
if opts.auto_close then
|
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: https://github.com/nvim-tree/nvim-tree.lua/wiki/Auto-Close")
|
||||||
opts.auto_close = nil
|
opts.auto_close = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if opts.focus_empty_on_setup then
|
||||||
|
notify.warn("focus_empty_on_setup has been removed: https://github.com/nvim-tree/nvim-tree.lua/wiki/Open-At-Startup")
|
||||||
|
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
|
end
|
||||||
|
|
||||||
function M.migrate_legacy_options(opts)
|
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
|
-- silently move
|
||||||
refactored(opts)
|
refactored(opts)
|
||||||
|
|
||||||
|
-- warn
|
||||||
|
deprecated(opts)
|
||||||
|
|
||||||
-- warn and delete
|
-- warn and delete
|
||||||
removed(opts)
|
removed(opts)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,61 +1,19 @@
|
|||||||
local api = vim.api
|
local view = require("nvim-tree.view")
|
||||||
|
local core = require("nvim-tree.core")
|
||||||
|
local events = require("nvim-tree.events")
|
||||||
|
local notify = require("nvim-tree.notify")
|
||||||
|
|
||||||
local renderer = require "nvim-tree.renderer"
|
---@class LibOpenOpts
|
||||||
local view = require "nvim-tree.view"
|
---@field path string|nil path
|
||||||
local core = require "nvim-tree.core"
|
---@field current_window boolean|nil default false
|
||||||
local utils = require "nvim-tree.utils"
|
---@field winid number|nil
|
||||||
|
|
||||||
local M = {
|
local M = {
|
||||||
target_winid = nil,
|
target_winid = nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
function M.get_node_at_cursor()
|
|
||||||
if not core.get_explorer() then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local winnr = view.get_winnr()
|
|
||||||
if not winnr then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local cursor = api.nvim_win_get_cursor(view.get_winnr())
|
|
||||||
local line = cursor[1]
|
|
||||||
if view.is_help_ui() then
|
|
||||||
local help_lines = require("nvim-tree.renderer.help").compute_lines()
|
|
||||||
local help_text = 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
|
|
||||||
end
|
|
||||||
|
|
||||||
-- If node is grouped, return the last node in the group. Otherwise, return the given node.
|
|
||||||
function M.get_last_group_node(node)
|
|
||||||
local next = node
|
|
||||||
while next.group_next do
|
|
||||||
next = next.group_next
|
|
||||||
end
|
|
||||||
return next
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.expand_or_collapse(node)
|
|
||||||
node.open = not node.open
|
|
||||||
if node.has_children then
|
|
||||||
node.has_children = false
|
|
||||||
end
|
|
||||||
|
|
||||||
if #node.nodes == 0 then
|
|
||||||
core.get_explorer():expand(node)
|
|
||||||
end
|
|
||||||
|
|
||||||
renderer.draw()
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.set_target_win()
|
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()
|
local tree_id = view.get_winnr()
|
||||||
if tree_id and id == tree_id then
|
if tree_id and id == tree_id then
|
||||||
M.target_winid = 0
|
M.target_winid = 0
|
||||||
@@ -65,10 +23,10 @@ function M.set_target_win()
|
|||||||
M.target_winid = id
|
M.target_winid = id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param cwd string
|
||||||
local function handle_buf_cwd(cwd)
|
local function handle_buf_cwd(cwd)
|
||||||
local respect_buf_cwd = vim.g.nvim_tree_respect_buf_cwd or 0
|
if M.respect_buf_cwd and cwd ~= core.get_cwd() then
|
||||||
if respect_buf_cwd == 1 and cwd ~= core.get_explorer().cwd then
|
require("nvim-tree.actions.root.change-dir").fn(cwd)
|
||||||
require("nvim-tree.actions.change-dir").fn(cwd)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -76,14 +34,25 @@ local function open_view_and_draw()
|
|||||||
local cwd = vim.fn.getcwd()
|
local cwd = vim.fn.getcwd()
|
||||||
view.open()
|
view.open()
|
||||||
handle_buf_cwd(cwd)
|
handle_buf_cwd(cwd)
|
||||||
renderer.draw()
|
|
||||||
|
local explorer = core.get_explorer()
|
||||||
|
if explorer then
|
||||||
|
explorer.renderer:draw()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function should_hijack_current_buf()
|
local function should_hijack_current_buf()
|
||||||
local bufnr = api.nvim_get_current_buf()
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
local bufname = api.nvim_buf_get_name(bufnr)
|
local bufname = vim.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 bufmodified, ft
|
||||||
|
if vim.fn.has("nvim-0.10") == 1 then
|
||||||
|
bufmodified = vim.api.nvim_get_option_value("modified", { buf = bufnr })
|
||||||
|
ft = vim.api.nvim_get_option_value("ft", { buf = bufnr })
|
||||||
|
else
|
||||||
|
bufmodified = vim.api.nvim_buf_get_option(bufnr, "modified") ---@diagnostic disable-line: deprecated
|
||||||
|
ft = vim.api.nvim_buf_get_option(bufnr, "ft") ---@diagnostic disable-line: deprecated
|
||||||
|
end
|
||||||
|
|
||||||
local should_hijack_unnamed = M.hijack_unnamed_buffer_when_opening and bufname == "" and not bufmodified and ft == ""
|
local should_hijack_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
|
local should_hijack_dir = bufname ~= "" and vim.fn.isdirectory(bufname) == 1 and M.hijack_directories.enable
|
||||||
@@ -91,36 +60,85 @@ local function should_hijack_current_buf()
|
|||||||
return should_hijack_dir or should_hijack_unnamed
|
return should_hijack_dir or should_hijack_unnamed
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.open(cwd)
|
---@param prompt_input string
|
||||||
M.set_target_win()
|
---@param prompt_select string
|
||||||
if not core.get_explorer() or cwd then
|
---@param items_short string[]
|
||||||
core.init(cwd or vim.loop.cwd())
|
---@param items_long string[]
|
||||||
|
---@param kind string|nil
|
||||||
|
---@param callback fun(item_short: string|nil)
|
||||||
|
function M.prompt(prompt_input, prompt_select, items_short, items_long, kind, callback)
|
||||||
|
local function format_item(short)
|
||||||
|
for i, s in ipairs(items_short) do
|
||||||
|
if short == s then
|
||||||
|
return items_long[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return ""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if M.select_prompts then
|
||||||
|
vim.ui.select(items_short, { prompt = prompt_select, kind = kind, format_item = format_item }, function(item_short)
|
||||||
|
callback(item_short)
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
vim.ui.input({ prompt = prompt_input, default = items_short[1] or "" }, function(item_short)
|
||||||
|
if item_short then
|
||||||
|
callback(string.lower(item_short and item_short:sub(1, 1)) or nil)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Open the tree, initialising as needed. Maybe hijack the current buffer.
|
||||||
|
---@param opts LibOpenOpts|nil
|
||||||
|
function M.open(opts)
|
||||||
|
opts = opts or {}
|
||||||
|
|
||||||
|
M.set_target_win()
|
||||||
|
if not core.get_explorer() or opts.path then
|
||||||
|
if opts.path then
|
||||||
|
core.init(opts.path)
|
||||||
|
else
|
||||||
|
local cwd, err = vim.loop.cwd()
|
||||||
|
if not cwd then
|
||||||
|
notify.error(string.format("current working directory unavailable: %s", err))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
core.init(cwd)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local explorer = core.get_explorer()
|
||||||
|
|
||||||
if should_hijack_current_buf() then
|
if should_hijack_current_buf() then
|
||||||
view.open_in_current_win()
|
view.close_this_tab_only()
|
||||||
renderer.draw()
|
view.open_in_win()
|
||||||
|
if explorer then
|
||||||
|
explorer.renderer:draw()
|
||||||
|
end
|
||||||
|
elseif opts.winid then
|
||||||
|
view.open_in_win({ hijack_current_buf = false, resize = false, winid = opts.winid })
|
||||||
|
if explorer then
|
||||||
|
explorer.renderer:draw()
|
||||||
|
end
|
||||||
|
elseif opts.current_window then
|
||||||
|
view.open_in_win({ hijack_current_buf = false, resize = false })
|
||||||
|
if explorer then
|
||||||
|
explorer.renderer:draw()
|
||||||
|
end
|
||||||
else
|
else
|
||||||
open_view_and_draw()
|
open_view_and_draw()
|
||||||
end
|
end
|
||||||
view.restore_tab_state()
|
view.restore_tab_state()
|
||||||
|
events._dispatch_on_tree_open()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- @deprecated: use nvim-tree.actions.collapse-all.fn
|
|
||||||
M.collapse_all = require("nvim-tree.actions.collapse-all").fn
|
|
||||||
-- @deprecated: use nvim-tree.actions.dir-up.fn
|
|
||||||
M.dir_up = require("nvim-tree.actions.dir-up").fn
|
|
||||||
-- @deprecated: use nvim-tree.actions.change-dir.fn
|
|
||||||
M.change_dir = require("nvim-tree.actions.change-dir").fn
|
|
||||||
-- @deprecated: use nvim-tree.actions.reloaders.reload_explorer
|
|
||||||
M.refresh_tree = require("nvim-tree.actions.reloaders").reload_explorer
|
|
||||||
-- @deprecated: use nvim-tree.actions.reloaders.reload_git
|
|
||||||
M.reload_git = require("nvim-tree.actions.reloaders").reload_git
|
|
||||||
-- @deprecated: use nvim-tree.actions.find-file.fn
|
|
||||||
M.set_index_and_redraw = require("nvim-tree.actions.find-file").fn
|
|
||||||
|
|
||||||
function M.setup(opts)
|
function M.setup(opts)
|
||||||
M.hijack_unnamed_buffer_when_opening = opts.hijack_unnamed_buffer_when_opening
|
M.hijack_unnamed_buffer_when_opening = opts.hijack_unnamed_buffer_when_opening
|
||||||
M.hijack_directories = opts.hijack_directories
|
M.hijack_directories = opts.hijack_directories
|
||||||
|
M.respect_buf_cwd = opts.respect_buf_cwd
|
||||||
|
M.select_prompts = opts.select_prompts
|
||||||
|
M.group_empty = opts.renderer.group_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user