Compare commits
624 Commits
compat-nvi
...
v1.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -7,3 +7,6 @@ end_of_line = lf
|
|||||||
[*.lua]
|
[*.lua]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
|
[nvim-tree-lua.txt]
|
||||||
|
max_line_length = 78
|
||||||
|
|||||||
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 [...]
|
||||||
|
|||||||
17
.github/ISSUE_TEMPLATE/nvt-min.lua
vendored
17
.github/ISSUE_TEMPLATE/nvt-min.lua
vendored
@@ -1,3 +1,6 @@
|
|||||||
|
vim.g.loaded_netrw = 1
|
||||||
|
vim.g.loaded_netrwPlugin = 1
|
||||||
|
|
||||||
vim.cmd [[set runtimepath=$VIMRUNTIME]]
|
vim.cmd [[set runtimepath=$VIMRUNTIME]]
|
||||||
vim.cmd [[set packpath=/tmp/nvt-min/site]]
|
vim.cmd [[set packpath=/tmp/nvt-min/site]]
|
||||||
local package_root = "/tmp/nvt-min/site/pack"
|
local package_root = "/tmp/nvt-min/site/pack"
|
||||||
@@ -6,8 +9,8 @@ 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 = {
|
||||||
@@ -32,3 +35,13 @@ _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 { cmd = { "lua-language-server" } }
|
||||||
|
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 |
102
.github/workflows/ci.yml
vendored
102
.github/workflows/ci.yml
vendored
@@ -2,33 +2,91 @@ 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:
|
||||||
|
luaVersion: ${{ matrix.lua_version }}
|
||||||
|
|
||||||
|
- 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.stylua_version }}-${{ github.head_ref || github.ref_name }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
stylua_version: [ 0.19.1 ]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: stylua
|
||||||
|
uses: JohnnyMorganz/stylua-action@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
args: --color always --check lua/
|
version: ${{ matrix.stylua_version }}
|
||||||
|
args: --check lua
|
||||||
|
|
||||||
|
- 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.9.1 ]
|
||||||
|
|
||||||
|
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@v5
|
||||||
|
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.2
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/luals-out/
|
||||||
|
/luals/
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env bash
|
#!/bin/sh
|
||||||
|
|
||||||
stylua . --check || exit 1
|
make
|
||||||
luacheck . || exit 1
|
|
||||||
|
|||||||
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": "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": "None",
|
||||||
|
"inject-field": "Any",
|
||||||
|
"invisible": "Any",
|
||||||
|
"lowercase-global": "Any",
|
||||||
|
"missing-fields": "Any",
|
||||||
|
"missing-global-doc": "Any",
|
||||||
|
"missing-local-export-doc": "None",
|
||||||
|
"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.4.0"
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
column_width = 120
|
column_width = 140
|
||||||
line_endings = "Unix"
|
line_endings = "Unix"
|
||||||
indent_type = "Spaces"
|
indent_type = "Spaces"
|
||||||
indent_width = 2
|
indent_width = 2
|
||||||
|
|||||||
137
CHANGELOG.md
Normal file
137
CHANGELOG.md
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [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))
|
||||||
116
CONTRIBUTING.md
116
CONTRIBUTING.md
@@ -2,23 +2,119 @@
|
|||||||
|
|
||||||
Thank you for contributing.
|
Thank you for contributing.
|
||||||
|
|
||||||
## Styling and formatting
|
See [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
|
Lint: [luacheck](https://github.com/lunarmodules/luacheck/)
|
||||||
|
|
||||||
|
Style: [StyLua](https://github.com/JohnnyMorganz/StyLua)
|
||||||
|
|
||||||
|
Language server: [luals](https://luals.github.io)
|
||||||
|
|
||||||
|
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 stylua using `.stylua.toml` settings
|
||||||
|
1. Runs `scripts/doc-comments.sh` to validate annotated documentation
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make style
|
||||||
|
```
|
||||||
|
|
||||||
|
You can automatically fix stylua 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
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|||||||
47
Makefile
Normal file
47
Makefile
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
all: lint style check
|
||||||
|
|
||||||
|
#
|
||||||
|
# mandatory checks
|
||||||
|
#
|
||||||
|
lint: luacheck
|
||||||
|
|
||||||
|
style: stylua style-doc
|
||||||
|
|
||||||
|
check: luals
|
||||||
|
|
||||||
|
#
|
||||||
|
# subtasks
|
||||||
|
#
|
||||||
|
luacheck:
|
||||||
|
luacheck -q lua
|
||||||
|
|
||||||
|
stylua:
|
||||||
|
stylua lua --check
|
||||||
|
|
||||||
|
style-doc:
|
||||||
|
scripts/doc-comments.sh
|
||||||
|
|
||||||
|
luals:
|
||||||
|
@scripts/luals-check.sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# fixes
|
||||||
|
#
|
||||||
|
style-fix:
|
||||||
|
stylua 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 stylua style-doc luals style-fix help-update help-check
|
||||||
|
|
||||||
484
README.md
484
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
|
|
||||||
|
-- optionally enable 24-bit colour
|
||||||
|
vim.opt.termguicolors = true
|
||||||
|
|
||||||
|
-- empty setup using defaults
|
||||||
|
require("nvim-tree").setup()
|
||||||
|
|
||||||
|
-- OR setup with some options
|
||||||
|
require("nvim-tree").setup({
|
||||||
|
sort = {
|
||||||
|
sorter = "case_sensitive",
|
||||||
},
|
},
|
||||||
tag = 'nightly' -- optional, updated every week. (see issue #1193)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
Options are currently being migrated into the setup function, you need to run `require'nvim-tree'.setup()` in your personal configurations.
|
|
||||||
Setup should be run in a lua file or in a lua heredoc (`:help lua-heredoc`) if using in a vim file.
|
|
||||||
Note that options under the `g:` command should be set **BEFORE** running the setup function.
|
|
||||||
These are being migrated to the setup function incrementally, check [this issue](https://github.com/kyazdani42/nvim-tree.lua/issues/674) if you encounter any problems related to configs not working after update.
|
|
||||||
```vim
|
|
||||||
" vimrc
|
|
||||||
let g:nvim_tree_git_hl = 1 "0 by default, will enable file highlight for git attributes (can be used without the icons).
|
|
||||||
let g:nvim_tree_highlight_opened_files = 1 "0 by default, will enable folder and file icon highlight for opened files/directories.
|
|
||||||
let g:nvim_tree_root_folder_modifier = ':~' "This is the default. See :help filename-modifiers for more options
|
|
||||||
let g:nvim_tree_add_trailing = 1 "0 by default, append a trailing slash to folder names
|
|
||||||
let g:nvim_tree_group_empty = 1 " 0 by default, compact folders that only contain a single folder into one node in the file tree
|
|
||||||
let g:nvim_tree_icon_padding = ' ' "one space by default, used for rendering the space between the icon and the filename. Use with caution, it could break rendering if you set an empty string depending on your font.
|
|
||||||
let g:nvim_tree_symlink_arrow = ' >> ' " defaults to ' ➛ '. used as a separator between symlinks' source and target.
|
|
||||||
let g:nvim_tree_respect_buf_cwd = 1 "0 by default, will change cwd of nvim-tree to that of new buffer's when opening nvim-tree.
|
|
||||||
let g:nvim_tree_create_in_closed_folder = 1 "0 by default, When creating files, sets the path of a file when cursor is on a closed folder to the parent folder when 0, and inside the folder when 1.
|
|
||||||
let g:nvim_tree_special_files = { 'README.md': 1, 'Makefile': 1, 'MAKEFILE': 1 } " List of filenames that gets highlighted with NvimTreeSpecialFile
|
|
||||||
let g:nvim_tree_show_icons = {
|
|
||||||
\ 'git': 1,
|
|
||||||
\ 'folders': 0,
|
|
||||||
\ 'files': 0,
|
|
||||||
\ 'folder_arrows': 0,
|
|
||||||
\ }
|
|
||||||
"If 0, do not show the icons for one of 'git' 'folder' and 'files'
|
|
||||||
"1 by default, notice that if 'files' is 1, it will only display
|
|
||||||
"if nvim-web-devicons is installed and on your runtimepath.
|
|
||||||
"if folder is 1, you can also tell folder_arrows 1 to show small arrows next to the folder icons.
|
|
||||||
"but this will not work when you set renderer.indent_markers.enable (because of UI conflict)
|
|
||||||
|
|
||||||
" default will show icon by default if no icon is provided
|
|
||||||
" default shows no icon by default
|
|
||||||
let g:nvim_tree_icons = {
|
|
||||||
\ 'default': "",
|
|
||||||
\ 'symlink': "",
|
|
||||||
\ 'git': {
|
|
||||||
\ 'unstaged': "✗",
|
|
||||||
\ 'staged': "✓",
|
|
||||||
\ 'unmerged': "",
|
|
||||||
\ 'renamed': "➜",
|
|
||||||
\ 'untracked': "★",
|
|
||||||
\ 'deleted': "",
|
|
||||||
\ 'ignored': "◌"
|
|
||||||
\ },
|
|
||||||
\ 'folder': {
|
|
||||||
\ 'arrow_open': "",
|
|
||||||
\ 'arrow_closed': "",
|
|
||||||
\ 'default': "",
|
|
||||||
\ 'open': "",
|
|
||||||
\ 'empty': "",
|
|
||||||
\ 'empty_open': "",
|
|
||||||
\ 'symlink': "",
|
|
||||||
\ 'symlink_open': "",
|
|
||||||
\ }
|
|
||||||
\ }
|
|
||||||
|
|
||||||
nnoremap <C-n> :NvimTreeToggle<CR>
|
|
||||||
nnoremap <leader>r :NvimTreeRefresh<CR>
|
|
||||||
nnoremap <leader>n :NvimTreeFindFile<CR>
|
|
||||||
" More available functions:
|
|
||||||
" NvimTreeOpen
|
|
||||||
" NvimTreeClose
|
|
||||||
" NvimTreeFocus
|
|
||||||
" NvimTreeFindFileToggle
|
|
||||||
" NvimTreeResize
|
|
||||||
" NvimTreeCollapse
|
|
||||||
" NvimTreeCollapseKeepBuffers
|
|
||||||
|
|
||||||
set termguicolors " this variable must be enabled for colors to be applied properly
|
|
||||||
|
|
||||||
" a list of groups can be found at `:help nvim_tree_highlight`
|
|
||||||
highlight NvimTreeFolderIcon guibg=blue
|
|
||||||
```
|
|
||||||
|
|
||||||
```lua
|
|
||||||
-- init.lua
|
|
||||||
|
|
||||||
-- empty setup using defaults: add your own options
|
|
||||||
require'nvim-tree'.setup {
|
|
||||||
}
|
|
||||||
|
|
||||||
-- OR
|
|
||||||
|
|
||||||
-- setup with all defaults
|
|
||||||
-- each of these are documented in `:help nvim-tree.OPTION_NAME`
|
|
||||||
-- 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 }
|
||||||
local function print_node_path(node) {
|
end
|
||||||
print(node.absolute_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
local list = {
|
|
||||||
{ key = {"<CR>", "o" }, action = "edit", mode = "n"},
|
|
||||||
{ key = "p", action = "print_path", action_cb = print_node_path },
|
|
||||||
{ key = "s", cb = tree_cb("vsplit") }, --tree_cb and the cb property are deprecated
|
|
||||||
{ key = "<2-RightMouse>", action = "" }, -- will remove default cd action
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
These are the default bindings:
|
|
||||||
```lua
|
|
||||||
|
|
||||||
-- default mappings
|
-- default mappings
|
||||||
local list = {
|
api.config.mappings.default_on_attach(bufnr)
|
||||||
{ key = {"<CR>", "o", "<2-LeftMouse>"}, action = "edit" },
|
|
||||||
{ key = "<C-e>", action = "edit_in_place" },
|
-- custom mappings
|
||||||
{ key = {"O"}, action = "edit_no_picker" },
|
vim.keymap.set('n', '<C-t>', api.tree.change_root_to_parent, opts('Up'))
|
||||||
{ key = {"<2-RightMouse>", "<C-]>"}, action = "cd" },
|
vim.keymap.set('n', '?', api.tree.toggle_help, opts('Help'))
|
||||||
{ key = "<C-v>", action = "vsplit" },
|
end
|
||||||
{ key = "<C-x>", action = "split" },
|
|
||||||
{ key = "<C-t>", action = "tabnew" },
|
-- pass to setup along with your other options
|
||||||
{ key = "<", action = "prev_sibling" },
|
require("nvim-tree").setup {
|
||||||
{ key = ">", action = "next_sibling" },
|
---
|
||||||
{ key = "P", action = "parent_node" },
|
on_attach = my_on_attach,
|
||||||
{ 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?`.
|
### Highlight
|
||||||
|
|
||||||
## Tips & reminders
|
Run `:NvimTreeHiTest` to show all the highlights that nvim-tree uses.
|
||||||
|
|
||||||
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.
|
They can be customised before or after setup is called and will be immediately
|
||||||
2. You can update window options for the tree by setting `require"nvim-tree.view".View.winopts.MY_OPTION = MY_OPTION_VALUE`
|
applied at runtime. e.g.
|
||||||
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,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
|
|
||||||
88
lua/nvim-tree/actions/finders/find-file.lua
Normal file
88
lua/nvim-tree/actions/finders/find-file.lua
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
local log = require "nvim-tree.log"
|
||||||
|
local view = require "nvim-tree.view"
|
||||||
|
local utils = require "nvim-tree.utils"
|
||||||
|
local renderer = require "nvim-tree.renderer"
|
||||||
|
local reload = require "nvim-tree.explorer.reload"
|
||||||
|
local core = require "nvim-tree.core"
|
||||||
|
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)
|
||||||
|
if not core.get_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
|
||||||
|
reload.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
|
||||||
|
if not node.group_next then
|
||||||
|
node.open = true
|
||||||
|
end
|
||||||
|
if #node.nodes == 0 then
|
||||||
|
core.get_explorer():expand(node)
|
||||||
|
if node.group_next and incremented_line then
|
||||||
|
line = line - 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
:recursor(function(node)
|
||||||
|
return node.group_next and { node.group_next } or (node.open and #node.nodes > 0 and node.nodes)
|
||||||
|
end)
|
||||||
|
:iterate()
|
||||||
|
|
||||||
|
if found and view.is_visible() then
|
||||||
|
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
|
||||||
112
lua/nvim-tree/actions/finders/search-node.lua
Normal file
112
lua/nvim-tree/actions/finders/search-node.lua
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
local core = require "nvim-tree.core"
|
||||||
|
local filters = require "nvim-tree.explorer.filters"
|
||||||
|
local find_file = require("nvim-tree.actions.finders.find-file").fn
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@param search_dir string|nil
|
||||||
|
---@param input_path string
|
||||||
|
---@return string|nil
|
||||||
|
local function search(search_dir, input_path)
|
||||||
|
local realpaths_searched = {}
|
||||||
|
|
||||||
|
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 = 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 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
|
||||||
350
lua/nvim-tree/actions/fs/copy-paste.lua
Normal file
350
lua/nvim-tree/actions/fs/copy-paste.lua
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
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 renderer = require "nvim-tree.renderer"
|
||||||
|
local reloaders = require "nvim-tree.actions.reloaders"
|
||||||
|
|
||||||
|
local find_file = require("nvim-tree.actions.finders.find-file").fn
|
||||||
|
|
||||||
|
local M = {
|
||||||
|
config = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
local clipboard = {
|
||||||
|
cut = {},
|
||||||
|
copy = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
---@param source string
|
||||||
|
---@param destination string
|
||||||
|
---@return boolean
|
||||||
|
---@return string|nil
|
||||||
|
local function do_copy(source, destination)
|
||||||
|
local source_stats, handle
|
||||||
|
local success, errmsg
|
||||||
|
|
||||||
|
source_stats, errmsg = vim.loop.fs_stat(source)
|
||||||
|
if not source_stats then
|
||||||
|
log.line("copy_paste", "do_copy fs_stat '%s' failed '%s'", source, errmsg)
|
||||||
|
return false, errmsg
|
||||||
|
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 = vim.loop.fs_copyfile(source, destination)
|
||||||
|
if not success then
|
||||||
|
log.line("copy_paste", "do_copy fs_copyfile failed '%s'", errmsg)
|
||||||
|
return false, errmsg
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
elseif source_stats.type == "directory" then
|
||||||
|
handle, errmsg = 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, errmsg)
|
||||||
|
return false, errmsg
|
||||||
|
end
|
||||||
|
|
||||||
|
success, errmsg = vim.loop.fs_mkdir(destination, source_stats.mode)
|
||||||
|
if not success then
|
||||||
|
log.line("copy_paste", "do_copy fs_mkdir '%s' failed '%s'", destination, errmsg)
|
||||||
|
return false, errmsg
|
||||||
|
end
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local name, _ = 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, 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
|
||||||
|
|
||||||
|
---@param source string
|
||||||
|
---@param dest string
|
||||||
|
---@param action_type string
|
||||||
|
---@param action_fn fun(source: string, dest: string)
|
||||||
|
---@return boolean|nil -- success
|
||||||
|
---@return string|nil -- error message
|
||||||
|
local function do_single_paste(source, dest, action_type, action_fn)
|
||||||
|
local dest_stats
|
||||||
|
local success, errmsg, errcode
|
||||||
|
local notify_source = notify.render_path(source)
|
||||||
|
|
||||||
|
log.line("copy_paste", "do_single_paste '%s' -> '%s'", source, dest)
|
||||||
|
|
||||||
|
dest_stats, errmsg, errcode = vim.loop.fs_stat(dest)
|
||||||
|
if not dest_stats and errcode ~= "ENOENT" then
|
||||||
|
notify.error("Could not " .. action_type .. " " .. notify_source .. " - " .. (errmsg or "???"))
|
||||||
|
return false, errmsg
|
||||||
|
end
|
||||||
|
|
||||||
|
local function on_process()
|
||||||
|
success, errmsg = action_fn(source, dest)
|
||||||
|
if not success then
|
||||||
|
notify.error("Could not " .. action_type .. " " .. notify_source .. " - " .. (errmsg or "???"))
|
||||||
|
return false, errmsg
|
||||||
|
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_type, 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_type, action_fn)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
on_process()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
---@param clip table
|
||||||
|
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
|
||||||
|
|
||||||
|
function M.clear_clipboard()
|
||||||
|
clipboard.cut = {}
|
||||||
|
clipboard.copy = {}
|
||||||
|
notify.info "Clipboard has been emptied."
|
||||||
|
renderer.draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
function M.copy(node)
|
||||||
|
utils.array_remove(clipboard.cut, node)
|
||||||
|
toggle(node, clipboard.copy)
|
||||||
|
renderer.draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
function M.cut(node)
|
||||||
|
utils.array_remove(clipboard.copy, node)
|
||||||
|
toggle(node, clipboard.cut)
|
||||||
|
renderer.draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
---@param action_type string
|
||||||
|
---@param action_fn fun(source: string, dest: string)
|
||||||
|
local function do_paste(node, action_type, action_fn)
|
||||||
|
node = lib.get_last_group_node(node)
|
||||||
|
local explorer = core.get_explorer()
|
||||||
|
if node.name == ".." and explorer then
|
||||||
|
node = explorer
|
||||||
|
end
|
||||||
|
local clip = clipboard[action_type]
|
||||||
|
if #clip == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local destination = node.absolute_path
|
||||||
|
local stats, errmsg, errcode = vim.loop.fs_stat(destination)
|
||||||
|
if not stats and errcode ~= "ENOENT" then
|
||||||
|
log.line("copy_paste", "do_paste fs_stat '%s' failed '%s'", destination, errmsg)
|
||||||
|
notify.error("Could not " .. action_type .. " " .. notify.render_path(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")
|
||||||
|
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] = {}
|
||||||
|
if not M.config.filesystem_watchers.enable then
|
||||||
|
reloaders.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
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
function M.paste(node)
|
||||||
|
if clipboard.cut[1] ~= nil then
|
||||||
|
do_paste(node, "cut", do_cut)
|
||||||
|
else
|
||||||
|
do_paste(node, "copy", do_copy)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.print_clipboard()
|
||||||
|
local content = {}
|
||||||
|
if #clipboard.cut > 0 then
|
||||||
|
table.insert(content, "Cut")
|
||||||
|
for _, node in pairs(clipboard.cut) do
|
||||||
|
table.insert(content, " * " .. (notify.render_path(node.absolute_path)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #clipboard.copy > 0 then
|
||||||
|
table.insert(content, "Copy")
|
||||||
|
for _, node in pairs(clipboard.copy) do
|
||||||
|
table.insert(content, " * " .. (notify.render_path(node.absolute_path)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
notify.info(table.concat(content, "\n") .. "\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param content string
|
||||||
|
local function copy_to_clipboard(content)
|
||||||
|
local clipboard_name
|
||||||
|
local reg
|
||||||
|
if M.config.actions.use_system_clipboard == true then
|
||||||
|
clipboard_name = "system"
|
||||||
|
reg = "+"
|
||||||
|
else
|
||||||
|
clipboard_name = "neovim"
|
||||||
|
reg = "1"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 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$', reg))
|
||||||
|
end)
|
||||||
|
vim.api.nvim_buf_delete(temp_buf, {})
|
||||||
|
|
||||||
|
notify.info(string.format("Copied %s to %s clipboard!", content, clipboard_name))
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
function M.copy_filename(node)
|
||||||
|
copy_to_clipboard(node.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
function M.copy_basename(node)
|
||||||
|
local basename = vim.fn.fnamemodify(node.name, ":r")
|
||||||
|
copy_to_clipboard(basename)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
function M.copy_path(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)
|
||||||
|
local content = node.nodes ~= nil and utils.path_add_trailing(relative_path) or relative_path
|
||||||
|
copy_to_clipboard(content)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
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
|
||||||
|
copy_to_clipboard(content)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Node is cut. Will not be copied.
|
||||||
|
---@param node Node
|
||||||
|
---@return boolean
|
||||||
|
function M.is_cut(node)
|
||||||
|
return vim.tbl_contains(clipboard.cut, node)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Node is copied. Will not be cut.
|
||||||
|
---@param node Node
|
||||||
|
---@return boolean
|
||||||
|
function M.is_copied(node)
|
||||||
|
return vim.tbl_contains(clipboard.copy, node)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.config.filesystem_watchers = opts.filesystem_watchers
|
||||||
|
M.config.actions = opts.actions
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -1,38 +1,27 @@
|
|||||||
local a = vim.api
|
|
||||||
local uv = vim.loop
|
|
||||||
|
|
||||||
local utils = require "nvim-tree.utils"
|
local utils = require "nvim-tree.utils"
|
||||||
local events = require "nvim-tree.events"
|
local events = require "nvim-tree.events"
|
||||||
local lib = require "nvim-tree.lib"
|
local lib = require "nvim-tree.lib"
|
||||||
local core = require "nvim-tree.core"
|
local core = require "nvim-tree.core"
|
||||||
|
local notify = require "nvim-tree.notify"
|
||||||
|
|
||||||
|
local find_file = require("nvim-tree.actions.finders.find-file").fn
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local function focus_file(file)
|
---@param file string
|
||||||
local _, i = utils.find_node(core.get_explorer().nodes, function(node)
|
local function create_and_notify(file)
|
||||||
return node.absolute_path == file
|
events._dispatch_will_create_file(file)
|
||||||
end)
|
local ok, fd = pcall(vim.loop.fs_open, file, "w", 420)
|
||||||
require("nvim-tree.view").set_cursor { i + 1, 1 }
|
if not ok or type(fd) ~= "number" then
|
||||||
end
|
notify.error("Couldn't create file " .. notify.render_path(file))
|
||||||
|
|
||||||
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
|
return
|
||||||
end
|
end
|
||||||
end
|
vim.loop.fs_close(fd)
|
||||||
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)
|
events._dispatch_file_created(file)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param iter function iterable
|
||||||
|
---@return integer
|
||||||
local function get_num_nodes(iter)
|
local function get_num_nodes(iter)
|
||||||
local i = 0
|
local i = 0
|
||||||
for _ in iter do
|
for _ in iter do
|
||||||
@@ -41,20 +30,28 @@ local function get_num_nodes(iter)
|
|||||||
return i
|
return i
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
---@return string
|
||||||
local function get_containing_folder(node)
|
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 then
|
||||||
if node.nodes ~= nil and is_open then
|
|
||||||
return utils.path_add_trailing(node.absolute_path)
|
return utils.path_add_trailing(node.absolute_path)
|
||||||
end
|
end
|
||||||
local node_name_size = #(node.name or "")
|
local node_name_size = #(node.name or "")
|
||||||
return node.absolute_path:sub(0, -node_name_size - 1)
|
return node.absolute_path:sub(0, -node_name_size - 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param node Node|nil
|
||||||
function M.fn(node)
|
function M.fn(node)
|
||||||
node = lib.get_last_group_node(node)
|
local cwd = core.get_cwd()
|
||||||
if node.name == ".." then
|
if cwd == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
node = node and lib.get_last_group_node(node)
|
||||||
|
if not node or node.name == ".." then
|
||||||
node = {
|
node = {
|
||||||
absolute_path = core.get_cwd(),
|
absolute_path = cwd,
|
||||||
|
name = "",
|
||||||
nodes = core.get_explorer().nodes,
|
nodes = core.get_explorer().nodes,
|
||||||
open = true,
|
open = true,
|
||||||
}
|
}
|
||||||
@@ -62,17 +59,20 @@ function M.fn(node)
|
|||||||
|
|
||||||
local containing_folder = get_containing_folder(node)
|
local containing_folder = get_containing_folder(node)
|
||||||
|
|
||||||
local input_opts = { prompt = "Create file ", default = containing_folder, completion = "file" }
|
local input_opts = {
|
||||||
|
prompt = "Create file ",
|
||||||
|
default = containing_folder,
|
||||||
|
completion = "file",
|
||||||
|
}
|
||||||
|
|
||||||
vim.ui.input(input_opts, function(new_file_path)
|
vim.ui.input(input_opts, function(new_file_path)
|
||||||
|
utils.clear_prompt()
|
||||||
if not new_file_path or new_file_path == containing_folder then
|
if not new_file_path or new_file_path == containing_folder then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
utils.clear_prompt()
|
|
||||||
|
|
||||||
if utils.file_exists(new_file_path) then
|
if utils.file_exists(new_file_path) then
|
||||||
utils.warn "Cannot create: file already exists"
|
notify.warn "Cannot create: file already exists"
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -93,22 +93,23 @@ function M.fn(node)
|
|||||||
path_to_create = utils.path_join { path_to_create, p }
|
path_to_create = utils.path_join { path_to_create, p }
|
||||||
end
|
end
|
||||||
if is_last_path_file and idx == num_nodes then
|
if is_last_path_file and idx == num_nodes then
|
||||||
create_file(path_to_create)
|
create_and_notify(path_to_create)
|
||||||
elseif not utils.file_exists(path_to_create) then
|
elseif not utils.file_exists(path_to_create) then
|
||||||
local success = uv.fs_mkdir(path_to_create, 493)
|
local success = vim.loop.fs_mkdir(path_to_create, 493)
|
||||||
if not success then
|
if not success then
|
||||||
a.nvim_err_writeln("Could not create folder " .. path_to_create)
|
notify.error("Could not create folder " .. notify.render_path(path_to_create))
|
||||||
is_error = true
|
is_error = true
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
events._dispatch_folder_created(new_file_path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if not is_error then
|
if not is_error then
|
||||||
a.nvim_out_write(new_file_path .. " was properly created\n")
|
notify.info(notify.render_path(new_file_path) .. " was properly created")
|
||||||
end
|
end
|
||||||
events._dispatch_folder_created(new_file_path)
|
|
||||||
require("nvim-tree.actions.reloaders").reload_explorer()
|
-- synchronously refreshes as we can't wait for the watchers
|
||||||
focus_file(new_file_path)
|
find_file(utils.path_remove_trailing(new_file_path))
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
16
lua/nvim-tree/actions/fs/init.lua
Normal file
16
lua/nvim-tree/actions/fs/init.lua
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
M.copy_paste = require "nvim-tree.actions.fs.copy-paste"
|
||||||
|
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.copy_paste.setup(opts)
|
||||||
|
M.remove_file.setup(opts)
|
||||||
|
M.rename_file.setup(opts)
|
||||||
|
M.trash.setup(opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
150
lua/nvim-tree/actions/fs/remove-file.lua
Normal file
150
lua/nvim-tree/actions/fs/remove-file.lua
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
local utils = require "nvim-tree.utils"
|
||||||
|
local events = require "nvim-tree.events"
|
||||||
|
local view = require "nvim-tree.view"
|
||||||
|
local lib = require "nvim-tree.lib"
|
||||||
|
local notify = require "nvim-tree.notify"
|
||||||
|
|
||||||
|
local M = {
|
||||||
|
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, t = vim.loop.fs_scandir_next(handle)
|
||||||
|
if not name then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local new_cwd = utils.path_join { cwd, name }
|
||||||
|
if t == "directory" then
|
||||||
|
local success = remove_dir(new_cwd)
|
||||||
|
if not success then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local success = vim.loop.fs_unlink(new_cwd)
|
||||||
|
if not success then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
clear_buffer(new_cwd)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return vim.loop.fs_rmdir(cwd)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- 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.nodes ~= nil and not node.link_to 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)
|
||||||
|
if not M.config.filesystem_watchers.enable then
|
||||||
|
require("nvim-tree.actions.reloaders").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
|
||||||
158
lua/nvim-tree/actions/fs/rename-file.lua
Normal file
158
lua/nvim-tree/actions/fs/rename-file.lua
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
local lib = require "nvim-tree.lib"
|
||||||
|
local utils = require "nvim-tree.utils"
|
||||||
|
local events = require "nvim-tree.events"
|
||||||
|
local notify = require "nvim-tree.notify"
|
||||||
|
|
||||||
|
local find_file = require("nvim-tree.actions.finders.find-file").fn
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
---@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 utils.file_exists(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 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
|
||||||
|
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)
|
||||||
|
if type(node) ~= "table" then
|
||||||
|
node = lib.get_node_at_cursor()
|
||||||
|
end
|
||||||
|
|
||||||
|
if node == nil 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
|
||||||
|
|
||||||
|
node = lib.get_last_group_node(node)
|
||||||
|
if node.name == ".." then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local namelen = node.name:len()
|
||||||
|
local directory = node.absolute_path:sub(0, namelen * -1 - 1)
|
||||||
|
local default_path
|
||||||
|
local prepend = ""
|
||||||
|
local append = ""
|
||||||
|
default_path = vim.fn.fnamemodify(node.absolute_path, modifier)
|
||||||
|
if modifier:sub(0, 2) == ":t" then
|
||||||
|
prepend = directory
|
||||||
|
end
|
||||||
|
if modifier == ":t:r" then
|
||||||
|
local extension = vim.fn.fnamemodify(node.name, ":e")
|
||||||
|
append = extension:len() == 0 and "" or "." .. extension
|
||||||
|
end
|
||||||
|
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
|
||||||
|
|
||||||
|
M.rename(node, prepend .. new_file_path .. append)
|
||||||
|
if not M.config.filesystem_watchers.enable then
|
||||||
|
require("nvim-tree.actions.reloaders").reload_explorer()
|
||||||
|
end
|
||||||
|
|
||||||
|
find_file(utils.path_remove_trailing(new_file_path))
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.config.filesystem_watchers = opts.filesystem_watchers
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
123
lua/nvim-tree/actions/fs/trash.lua
Normal file
123
lua/nvim-tree/actions/fs/trash.lua
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
local lib = require "nvim-tree.lib"
|
||||||
|
local notify = require "nvim-tree.notify"
|
||||||
|
local reloaders = require "nvim-tree.actions.reloaders"
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if node.nodes ~= nil and not node.link_to then
|
||||||
|
trash_path(function(_, rc)
|
||||||
|
if rc ~= 0 then
|
||||||
|
notify.warn("trash failed: " .. err_msg .. "; please see :help nvim-tree.trash")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
events._dispatch_folder_removed(node.absolute_path)
|
||||||
|
if not M.config.filesystem_watchers.enable then
|
||||||
|
reloaders.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 then
|
||||||
|
reloaders.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,18 @@
|
|||||||
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.reloaders = require "nvim-tree.actions.reloaders"
|
||||||
|
M.root = require "nvim-tree.actions.root"
|
||||||
local M = {
|
M.tree = require "nvim-tree.actions.tree"
|
||||||
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
|
||||||
238
lua/nvim-tree/actions/moves/item.lua
Normal file
238
lua/nvim-tree/actions/moves/item.lua
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
local utils = require "nvim-tree.utils"
|
||||||
|
local view = require "nvim-tree.view"
|
||||||
|
local core = require "nvim-tree.core"
|
||||||
|
local lib = require "nvim-tree.lib"
|
||||||
|
local explorer_node = require "nvim-tree.explorer.node"
|
||||||
|
local diagnostics = require "nvim-tree.diagnostics"
|
||||||
|
|
||||||
|
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 table 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_status = explorer_node.get_git_status(node)
|
||||||
|
return git_status ~= nil and (not skip_gitignored or git_status[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 where string where to move (forwards or backwards)
|
||||||
|
---@param what string type of status
|
||||||
|
---@param skip_gitignored boolean default false
|
||||||
|
local function move(where, what, skip_gitignored)
|
||||||
|
local node_cur = lib.get_node_at_cursor()
|
||||||
|
local first_node_line = core.get_nodes_starting_line()
|
||||||
|
local nodes_by_line = utils.get_nodes_by_line(core.get_explorer().nodes, first_node_line)
|
||||||
|
local iter_start, iter_end, iter_step, cur, first, nex
|
||||||
|
|
||||||
|
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 node == node_cur 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
|
||||||
|
|
||||||
|
local function expand_node(node)
|
||||||
|
if not node.open then
|
||||||
|
-- Expand the node.
|
||||||
|
-- Should never collapse since we checked open.
|
||||||
|
lib.expand_or_collapse(node)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Move to the next node recursively.
|
||||||
|
---@param what string type of status
|
||||||
|
---@param skip_gitignored boolean default false
|
||||||
|
local function move_next_recursive(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 = lib.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
|
||||||
|
if node_init.nodes ~= nil and valid and not node_init.open then
|
||||||
|
lib.expand_or_collapse(node_init)
|
||||||
|
end
|
||||||
|
|
||||||
|
move("next", what, skip_gitignored)
|
||||||
|
|
||||||
|
local node_cur = lib.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 is_dir = node_cur.nodes ~= nil
|
||||||
|
while is_dir and i < MAX_DEPTH do
|
||||||
|
expand_node(node_cur)
|
||||||
|
|
||||||
|
move("next", what, skip_gitignored)
|
||||||
|
|
||||||
|
-- Save current node.
|
||||||
|
node_cur = lib.get_node_at_cursor()
|
||||||
|
-- Update is_dir.
|
||||||
|
if node_cur then
|
||||||
|
is_dir = node_cur.nodes ~= nil
|
||||||
|
else
|
||||||
|
is_dir = false
|
||||||
|
end
|
||||||
|
|
||||||
|
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 what string type of status
|
||||||
|
---@param skip_gitignored boolean default false
|
||||||
|
local function move_prev_recursive(what, skip_gitignored)
|
||||||
|
local node_init, node_cur
|
||||||
|
|
||||||
|
-- 1)
|
||||||
|
node_init = lib.get_node_at_cursor()
|
||||||
|
if node_init == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 2)
|
||||||
|
move("prev", what, skip_gitignored)
|
||||||
|
|
||||||
|
node_cur = lib.get_node_at_cursor()
|
||||||
|
if node_cur == node_init.parent then
|
||||||
|
-- 3)
|
||||||
|
move_prev_recursive(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 not node_cur.nodes -- node is a file
|
||||||
|
then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 4.2)
|
||||||
|
local node_dir = node_cur
|
||||||
|
expand_node(node_dir)
|
||||||
|
|
||||||
|
-- 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("prev", what, skip_gitignored)
|
||||||
|
|
||||||
|
-- 4.5)
|
||||||
|
node_cur = lib.get_node_at_cursor()
|
||||||
|
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@class NavigationItemOpts
|
||||||
|
---@field where string
|
||||||
|
---@field what string
|
||||||
|
|
||||||
|
---@param opts NavigationItemOpts
|
||||||
|
---@return fun()
|
||||||
|
function M.fn(opts)
|
||||||
|
return function()
|
||||||
|
local recurse = false
|
||||||
|
local skip_gitignored = 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 opts.skip_gitignored ~= nil then
|
||||||
|
skip_gitignored = opts.skip_gitignored
|
||||||
|
end
|
||||||
|
|
||||||
|
if not recurse then
|
||||||
|
move(opts.where, opts.what, skip_gitignored)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.where == "next" then
|
||||||
|
move_next_recursive(opts.what, skip_gitignored)
|
||||||
|
elseif opts.where == "prev" then
|
||||||
|
move_prev_recursive(opts.what, skip_gitignored)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
39
lua/nvim-tree/actions/moves/parent.lua
Normal file
39
lua/nvim-tree/actions/moves/parent.lua
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
local renderer = require "nvim-tree.renderer"
|
||||||
|
local view = require "nvim-tree.view"
|
||||||
|
local utils = require "nvim-tree.utils"
|
||||||
|
local core = require "nvim-tree.core"
|
||||||
|
local lib = require "nvim-tree.lib"
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@param should_close boolean|nil
|
||||||
|
---@return fun(node: Node): boolean|nil
|
||||||
|
function M.fn(should_close)
|
||||||
|
should_close = should_close or false
|
||||||
|
|
||||||
|
return function(node)
|
||||||
|
node = lib.get_last_group_node(node)
|
||||||
|
if should_close and node.open then
|
||||||
|
node.open = false
|
||||||
|
return renderer.draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
local parent = utils.get_parent_of_group(node).parent
|
||||||
|
|
||||||
|
if not parent or not parent.parent then
|
||||||
|
return view.set_cursor { 1, 0 }
|
||||||
|
end
|
||||||
|
|
||||||
|
local _, line = utils.find_node(core.get_explorer().nodes, function(n)
|
||||||
|
return n.absolute_path == parent.absolute_path
|
||||||
|
end)
|
||||||
|
|
||||||
|
view.set_cursor { line + 1, 0 }
|
||||||
|
if should_close then
|
||||||
|
parent.open = false
|
||||||
|
renderer.draw()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
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.nodes)
|
||||||
|
:recursor(function()
|
||||||
|
return nil
|
||||||
|
end)
|
||||||
|
:applier(function(n)
|
||||||
|
first = first or n
|
||||||
|
last = n
|
||||||
|
if n.absolute_path == node.absolute_path then
|
||||||
|
found = true
|
||||||
|
return
|
||||||
|
end
|
||||||
|
prev = not found and n or prev
|
||||||
|
if found and not next then
|
||||||
|
next = n
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
:iterate()
|
||||||
|
|
||||||
|
local target_node
|
||||||
|
if direction == "first" then
|
||||||
|
target_node = first
|
||||||
|
elseif direction == "last" then
|
||||||
|
target_node = last
|
||||||
|
elseif direction == "next" then
|
||||||
|
target_node = next or first
|
||||||
|
else
|
||||||
|
target_node = prev or last
|
||||||
|
end
|
||||||
|
|
||||||
|
if target_node then
|
||||||
|
utils.focus_file(target_node.absolute_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -1,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
|
||||||
411
lua/nvim-tree/actions/node/open-file.lua
Normal file
411
lua/nvim-tree/actions/node/open-file.lua
Normal file
@@ -0,0 +1,411 @@
|
|||||||
|
-- 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 = {}
|
||||||
|
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, ok_hl, winhl
|
||||||
|
|
||||||
|
if vim.fn.has "nvim-0.10" == 1 then
|
||||||
|
ok_status, statusline = pcall(vim.api.nvim_get_option_value, "statusline", { win = win_id })
|
||||||
|
ok_hl, winhl = pcall(vim.api.nvim_get_option_value, "winhl", { win = win_id })
|
||||||
|
else
|
||||||
|
ok_status, statusline = pcall(vim.api.nvim_win_get_option, win_id, "statusline") ---@diagnostic disable-line: deprecated
|
||||||
|
ok_hl, winhl = pcall(vim.api.nvim_win_get_option, win_id, "winhl") ---@diagnostic disable-line: deprecated
|
||||||
|
end
|
||||||
|
|
||||||
|
win_opts[win_id] = {
|
||||||
|
statusline = ok_status and statusline or "",
|
||||||
|
winhl = ok_hl and winhl 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[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[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
|
||||||
|
for opt, value in pairs(win_opts[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
|
||||||
|
|
||||||
|
vim.o.laststatus = laststatus
|
||||||
|
|
||||||
|
if not vim.tbl_contains(vim.split(M.window_picker.chars, ""), resp) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
return win_map[resp]
|
||||||
|
end
|
||||||
|
|
||||||
|
local function open_file_in_tab(filename)
|
||||||
|
if M.quit_on_open then
|
||||||
|
view.close()
|
||||||
|
end
|
||||||
|
vim.cmd("tabe " .. vim.fn.fnameescape(filename))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function drop(filename)
|
||||||
|
if M.quit_on_open then
|
||||||
|
view.close()
|
||||||
|
end
|
||||||
|
vim.cmd("drop " .. vim.fn.fnameescape(filename))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function tab_drop(filename)
|
||||||
|
if M.quit_on_open then
|
||||||
|
view.close()
|
||||||
|
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
|
||||||
|
|
||||||
|
local fname = utils.escape_special_chars(vim.fn.fnameescape(filename))
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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()
|
||||||
|
vim.cmd("keepalt keepjumps edit " .. vim.fn.fnameescape(filename))
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param mode string
|
||||||
|
---@param filename string
|
||||||
|
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
|
||||||
|
if opts.actions.open_file.window_picker.chars then
|
||||||
|
opts.actions.open_file.window_picker.chars = tostring(opts.actions.open_file.window_picker.chars):upper()
|
||||||
|
end
|
||||||
|
M.window_picker = opts.actions.open_file.window_picker
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -6,14 +6,18 @@ 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>")
|
||||||
70
lua/nvim-tree/actions/node/system-open.lua
Normal file
70
lua/nvim-tree/actions/node/system-open.lua
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
local notify = require "nvim-tree.notify"
|
||||||
|
local utils = require "nvim-tree.utils"
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
function M.fn(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
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.config = {}
|
||||||
|
M.config.system_open = opts.system_open or {}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
|
||||||
@@ -3,29 +3,38 @@ local view = require "nvim-tree.view"
|
|||||||
local renderer = require "nvim-tree.renderer"
|
local renderer = require "nvim-tree.renderer"
|
||||||
local explorer_module = require "nvim-tree.explorer"
|
local explorer_module = require "nvim-tree.explorer"
|
||||||
local core = require "nvim-tree.core"
|
local core = require "nvim-tree.core"
|
||||||
|
local explorer_node = require "nvim-tree.explorer.node"
|
||||||
|
local Iterator = require "nvim-tree.iterators.node-iterator"
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
---@param node Explorer|nil
|
||||||
|
---@param projects table
|
||||||
local function refresh_nodes(node, projects)
|
local function refresh_nodes(node, projects)
|
||||||
local cwd = node.cwd or node.link_to or node.absolute_path
|
Iterator.builder({ node })
|
||||||
local project_root = git.get_project_root(cwd)
|
:applier(function(n)
|
||||||
explorer_module.reload(node, projects[project_root] or {})
|
if n.nodes then
|
||||||
for _, _node in ipairs(node.nodes) do
|
local toplevel = git.get_toplevel(n.cwd or n.link_to or n.absolute_path)
|
||||||
if _node.nodes and _node.open then
|
explorer_module.reload(n, projects[toplevel] or {})
|
||||||
refresh_nodes(_node, projects)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
end)
|
||||||
|
:recursor(function(n)
|
||||||
|
return n.group_next and { n.group_next } or (n.open and n.nodes)
|
||||||
|
end)
|
||||||
|
:iterate()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param parent_node Node|nil
|
||||||
|
---@param projects table
|
||||||
function M.reload_node_status(parent_node, projects)
|
function M.reload_node_status(parent_node, projects)
|
||||||
local project_root = git.get_project_root(parent_node.absolute_path or parent_node.cwd)
|
if parent_node == nil then
|
||||||
local status = projects[project_root] or {}
|
return
|
||||||
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
|
end
|
||||||
|
|
||||||
|
local toplevel = git.get_toplevel(parent_node.absolute_path)
|
||||||
|
local status = projects[toplevel] or {}
|
||||||
|
for _, node in ipairs(parent_node.nodes) do
|
||||||
|
explorer_node.update_git_status(node, explorer_node.is_git_ignored(parent_node), status)
|
||||||
if node.nodes and #node.nodes > 0 then
|
if node.nodes and #node.nodes > 0 then
|
||||||
M.reload_node_status(node, projects)
|
M.reload_node_status(node, projects)
|
||||||
end
|
end
|
||||||
@@ -48,7 +57,7 @@ function M.reload_explorer()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function M.reload_git()
|
function M.reload_git()
|
||||||
if not core.get_explorer() or not git.config.enable or event_running then
|
if not core.get_explorer() or not git.config.git.enable or event_running then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
event_running = true
|
event_running = true
|
||||||
|
|||||||
@@ -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
|
|
||||||
102
lua/nvim-tree/actions/root/change-dir.lua
Normal file
102
lua/nvim-tree/actions/root/change-dir.lua
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
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
|
||||||
|
require("nvim-tree.renderer").draw()
|
||||||
|
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
|
||||||
15
lua/nvim-tree/actions/tree/init.lua
Normal file
15
lua/nvim-tree/actions/tree/init.lua
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
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"
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.find_file.setup(opts)
|
||||||
|
M.modifiers.setup(opts)
|
||||||
|
M.open.setup(opts)
|
||||||
|
M.toggle.setup(opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
53
lua/nvim-tree/actions/tree/modifiers/collapse-all.lua
Normal file
53
lua/nvim-tree/actions/tree/modifiers/collapse-all.lua
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
local renderer = require "nvim-tree.renderer"
|
||||||
|
local utils = require "nvim-tree.utils"
|
||||||
|
local core = require "nvim-tree.core"
|
||||||
|
local lib = require "nvim-tree.lib"
|
||||||
|
local Iterator = require "nvim-tree.iterators.node-iterator"
|
||||||
|
|
||||||
|
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 node = lib.get_node_at_cursor()
|
||||||
|
local explorer = core.get_explorer()
|
||||||
|
|
||||||
|
if explorer == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local matches = buf_match()
|
||||||
|
|
||||||
|
Iterator.builder(explorer.nodes)
|
||||||
|
:hidden()
|
||||||
|
:applier(function(n)
|
||||||
|
if n.nodes ~= nil then
|
||||||
|
n.open = keep_buffers == true and matches(n.absolute_path)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
:recursor(function(n)
|
||||||
|
return n.group_next and { n.group_next } or n.nodes
|
||||||
|
end)
|
||||||
|
:iterate()
|
||||||
|
|
||||||
|
renderer.draw()
|
||||||
|
utils.focus_node_or_parent(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
80
lua/nvim-tree/actions/tree/modifiers/expand-all.lua
Normal file
80
lua/nvim-tree/actions/tree/modifiers/expand-all.lua
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
local core = require "nvim-tree.core"
|
||||||
|
local renderer = require "nvim-tree.renderer"
|
||||||
|
local Iterator = require "nvim-tree.iterators.node-iterator"
|
||||||
|
local notify = require "nvim-tree.notify"
|
||||||
|
local lib = require "nvim-tree.lib"
|
||||||
|
|
||||||
|
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 Node
|
||||||
|
local function expand(node)
|
||||||
|
node = lib.get_last_group_node(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 should_halt = expansion_count >= M.MAX_FOLDER_DISCOVERY
|
||||||
|
local should_exclude = M.EXCLUDE[node.name]
|
||||||
|
return not should_halt and node.nodes and not node.open and not should_exclude
|
||||||
|
end
|
||||||
|
|
||||||
|
local function gen_iterator()
|
||||||
|
local expansion_count = 0
|
||||||
|
|
||||||
|
return function(parent)
|
||||||
|
if parent.parent and parent.nodes and not parent.open then
|
||||||
|
expansion_count = expansion_count + 1
|
||||||
|
expand(parent)
|
||||||
|
end
|
||||||
|
|
||||||
|
Iterator.builder(parent.nodes)
|
||||||
|
:hidden()
|
||||||
|
:applier(function(node)
|
||||||
|
if should_expand(expansion_count, node) then
|
||||||
|
expansion_count = expansion_count + 1
|
||||||
|
expand(node)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
:recursor(function(node)
|
||||||
|
return expansion_count < M.MAX_FOLDER_DISCOVERY and (node.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
|
||||||
|
|
||||||
|
---@param base_node table
|
||||||
|
function M.fn(base_node)
|
||||||
|
local node = base_node.nodes and base_node or core.get_explorer()
|
||||||
|
if gen_iterator()(node) then
|
||||||
|
notify.warn("expansion iteration was halted after " .. M.MAX_FOLDER_DISCOVERY .. " discovered folders")
|
||||||
|
end
|
||||||
|
renderer.draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.MAX_FOLDER_DISCOVERY = opts.actions.expand_all.max_folder_discovery
|
||||||
|
M.EXCLUDE = to_lookup_table(opts.actions.expand_all.exclude)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
11
lua/nvim-tree/actions/tree/modifiers/init.lua
Normal file
11
lua/nvim-tree/actions/tree/modifiers/init.lua
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
M.collapse_all = require "nvim-tree.actions.tree.modifiers.collapse-all"
|
||||||
|
M.expand_all = require "nvim-tree.actions.tree.modifiers.expand-all"
|
||||||
|
M.toggles = require "nvim-tree.actions.tree.modifiers.toggles"
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.expand_all.setup(opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
49
lua/nvim-tree/actions/tree/modifiers/toggles.lua
Normal file
49
lua/nvim-tree/actions/tree/modifiers/toggles.lua
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
local lib = require "nvim-tree.lib"
|
||||||
|
local utils = require "nvim-tree.utils"
|
||||||
|
local filters = require "nvim-tree.explorer.filters"
|
||||||
|
local reloaders = require "nvim-tree.actions.reloaders"
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
local function reload()
|
||||||
|
local node = lib.get_node_at_cursor()
|
||||||
|
reloaders.reload_explorer()
|
||||||
|
utils.focus_node_or_parent(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.custom()
|
||||||
|
filters.config.filter_custom = not filters.config.filter_custom
|
||||||
|
reload()
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.git_ignored()
|
||||||
|
filters.config.filter_git_ignored = not filters.config.filter_git_ignored
|
||||||
|
reload()
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.git_clean()
|
||||||
|
filters.config.filter_git_clean = not filters.config.filter_git_clean
|
||||||
|
reload()
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.no_buffer()
|
||||||
|
filters.config.filter_no_buffer = not filters.config.filter_no_buffer
|
||||||
|
reload()
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.no_bookmark()
|
||||||
|
filters.config.filter_no_bookmark = not filters.config.filter_no_bookmark
|
||||||
|
reload()
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.dotfiles()
|
||||||
|
filters.config.filter_dotfiles = not filters.config.filter_dotfiles
|
||||||
|
reload()
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.enable()
|
||||||
|
filters.config.enable = not filters.config.enable
|
||||||
|
reload()
|
||||||
|
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
|
||||||
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
|
||||||
258
lua/nvim-tree/api.lua
Normal file
258
lua/nvim-tree/api.lua
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
local lib = require "nvim-tree.lib"
|
||||||
|
local view = require "nvim-tree.view"
|
||||||
|
local utils = require "nvim-tree.utils"
|
||||||
|
local actions = require "nvim-tree.actions"
|
||||||
|
local appearance_diagnostics = require "nvim-tree.appearance.diagnostics"
|
||||||
|
local events = require "nvim-tree.events"
|
||||||
|
local help = require "nvim-tree.help"
|
||||||
|
local live_filter = require "nvim-tree.live-filter"
|
||||||
|
local marks = require "nvim-tree.marks"
|
||||||
|
local marks_navigation = require "nvim-tree.marks.navigation"
|
||||||
|
local marks_bulk_delete = require "nvim-tree.marks.bulk-delete"
|
||||||
|
local marks_bulk_trash = require "nvim-tree.marks.bulk-trash"
|
||||||
|
local marks_bulk_move = require "nvim-tree.marks.bulk-move"
|
||||||
|
local keymap = require "nvim-tree.keymap"
|
||||||
|
local notify = require "nvim-tree.notify"
|
||||||
|
|
||||||
|
local Api = {
|
||||||
|
tree = {},
|
||||||
|
node = {
|
||||||
|
navigate = {
|
||||||
|
sibling = {},
|
||||||
|
git = {},
|
||||||
|
diagnostics = {},
|
||||||
|
opened = {},
|
||||||
|
},
|
||||||
|
run = {},
|
||||||
|
open = {},
|
||||||
|
},
|
||||||
|
events = {},
|
||||||
|
marks = {
|
||||||
|
bulk = {},
|
||||||
|
navigate = {},
|
||||||
|
},
|
||||||
|
fs = {
|
||||||
|
copy = {},
|
||||||
|
},
|
||||||
|
git = {},
|
||||||
|
live_filter = {},
|
||||||
|
config = {
|
||||||
|
mappings = {},
|
||||||
|
},
|
||||||
|
commands = {},
|
||||||
|
diagnostics = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
--- Do nothing when setup not called.
|
||||||
|
--- f function to invoke
|
||||||
|
---@param f function
|
||||||
|
local function wrap(f)
|
||||||
|
return function(...)
|
||||||
|
if vim.g.NvimTreeSetup == 1 then
|
||||||
|
return f(...)
|
||||||
|
else
|
||||||
|
notify.error "nvim-tree setup not called"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Inject the node as the first argument if absent.
|
||||||
|
---@param fn function function to invoke
|
||||||
|
local function wrap_node(fn)
|
||||||
|
return function(node, ...)
|
||||||
|
node = node or lib.get_node_at_cursor()
|
||||||
|
if node then
|
||||||
|
fn(node, ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Inject the node or nil as the first argument if absent.
|
||||||
|
---@param fn function function to invoke
|
||||||
|
local function wrap_node_or_nil(fn)
|
||||||
|
return function(node, ...)
|
||||||
|
node = node or lib.get_node_at_cursor()
|
||||||
|
fn(node, ...)
|
||||||
|
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(actions.reloaders.reload_explorer)
|
||||||
|
|
||||||
|
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 == ".." then
|
||||||
|
actions.root.change_dir.fn ".."
|
||||||
|
elseif node.nodes ~= nil then
|
||||||
|
actions.root.change_dir.fn(lib.get_last_group_node(node).absolute_path)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
Api.tree.change_root_to_parent = wrap_node(actions.root.dir_up.fn)
|
||||||
|
Api.tree.get_node_under_cursor = wrap(lib.get_node_at_cursor)
|
||||||
|
Api.tree.get_nodes = wrap(lib.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(actions.tree.modifiers.toggles.enable)
|
||||||
|
Api.tree.toggle_gitignore_filter = wrap(actions.tree.modifiers.toggles.git_ignored)
|
||||||
|
Api.tree.toggle_git_clean_filter = wrap(actions.tree.modifiers.toggles.git_clean)
|
||||||
|
Api.tree.toggle_no_buffer_filter = wrap(actions.tree.modifiers.toggles.no_buffer)
|
||||||
|
Api.tree.toggle_custom_filter = wrap(actions.tree.modifiers.toggles.custom)
|
||||||
|
Api.tree.toggle_hidden_filter = wrap(actions.tree.modifiers.toggles.dotfiles)
|
||||||
|
Api.tree.toggle_no_bookmark_filter = wrap(actions.tree.modifiers.toggles.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(actions.fs.copy_paste.cut)
|
||||||
|
Api.fs.paste = wrap_node(actions.fs.copy_paste.paste)
|
||||||
|
Api.fs.clear_clipboard = wrap(actions.fs.copy_paste.clear_clipboard)
|
||||||
|
Api.fs.print_clipboard = wrap(actions.fs.copy_paste.print_clipboard)
|
||||||
|
Api.fs.copy.node = wrap_node(actions.fs.copy_paste.copy)
|
||||||
|
Api.fs.copy.absolute_path = wrap_node(actions.fs.copy_paste.copy_absolute_path)
|
||||||
|
Api.fs.copy.filename = wrap_node(actions.fs.copy_paste.copy_filename)
|
||||||
|
Api.fs.copy.basename = wrap_node(actions.fs.copy_paste.copy_basename)
|
||||||
|
Api.fs.copy.relative_path = wrap_node(actions.fs.copy_paste.copy_path)
|
||||||
|
|
||||||
|
---@param mode string
|
||||||
|
---@param node table
|
||||||
|
local function edit(mode, node)
|
||||||
|
local path = node.absolute_path
|
||||||
|
if node.link_to and not node.nodes then
|
||||||
|
path = node.link_to
|
||||||
|
end
|
||||||
|
actions.node.open_file.fn(mode, path)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param mode string
|
||||||
|
---@return fun(node: table)
|
||||||
|
local function open_or_expand_or_dir_up(mode, toggle_group)
|
||||||
|
return function(node)
|
||||||
|
if node.name == ".." then
|
||||||
|
actions.root.change_dir.fn ".."
|
||||||
|
elseif node.nodes then
|
||||||
|
lib.expand_or_collapse(node, 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(actions.reloaders.reload_git)
|
||||||
|
|
||||||
|
Api.events.subscribe = events.subscribe
|
||||||
|
Api.events.Event = events.Event
|
||||||
|
|
||||||
|
Api.live_filter.start = wrap(live_filter.start_filtering)
|
||||||
|
Api.live_filter.clear = wrap(live_filter.clear_filter)
|
||||||
|
|
||||||
|
Api.marks.get = wrap_node(marks.get_mark)
|
||||||
|
Api.marks.list = wrap(marks.get_marks)
|
||||||
|
Api.marks.toggle = wrap_node(marks.toggle_mark)
|
||||||
|
Api.marks.clear = wrap(marks.clear_marks)
|
||||||
|
Api.marks.bulk.delete = wrap(marks_bulk_delete.bulk_delete)
|
||||||
|
Api.marks.bulk.trash = wrap(marks_bulk_trash.bulk_trash)
|
||||||
|
Api.marks.bulk.move = wrap(marks_bulk_move.bulk_move)
|
||||||
|
Api.marks.navigate.next = wrap(marks_navigation.next)
|
||||||
|
Api.marks.navigate.prev = wrap(marks_navigation.prev)
|
||||||
|
Api.marks.navigate.select = wrap(marks_navigation.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_diagnostics.hi_test)
|
||||||
|
|
||||||
|
Api.commands.get = wrap(function()
|
||||||
|
return require("nvim-tree.commands").get()
|
||||||
|
end)
|
||||||
|
|
||||||
|
return Api
|
||||||
141
lua/nvim-tree/appearance/diagnostics.lua
Normal file
141
lua/nvim-tree/appearance/diagnostics.lua
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
local appearance = require "nvim-tree.appearance"
|
||||||
|
|
||||||
|
-- others with name and links less than this arbitrary value are short
|
||||||
|
local SHORT_LEN = 50
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@class HighlightDisplay 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 = {}
|
||||||
|
|
||||||
|
---@param group string nvim-tree highlight group name
|
||||||
|
---@return HighlightDisplay
|
||||||
|
function HighlightDisplay:new(group)
|
||||||
|
local o = {}
|
||||||
|
setmetatable(o, self)
|
||||||
|
self.__index = self
|
||||||
|
|
||||||
|
o.group = group
|
||||||
|
local concrete = o.group
|
||||||
|
|
||||||
|
-- maybe follow links
|
||||||
|
local links = {}
|
||||||
|
local link = vim.api.nvim_get_hl(0, { name = o.group }).link
|
||||||
|
while link do
|
||||||
|
table.insert(links, link)
|
||||||
|
concrete = link
|
||||||
|
link = vim.api.nvim_get_hl(0, { name = link }).link
|
||||||
|
end
|
||||||
|
o.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
|
||||||
|
o.def = res:gsub(".*xxx *", "")
|
||||||
|
else
|
||||||
|
o.def = ""
|
||||||
|
end
|
||||||
|
|
||||||
|
return o
|
||||||
|
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
|
||||||
|
function M.hi_test()
|
||||||
|
-- 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:new(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:new(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
|
||||||
|
|
||||||
|
return M
|
||||||
201
lua/nvim-tree/appearance/init.lua
Normal file
201
lua/nvim-tree/appearance/init.lua
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
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 = "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" },
|
||||||
|
|
||||||
|
-- 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
|
||||||
50
lua/nvim-tree/buffers.lua
Normal file
50
lua/nvim-tree/buffers.lua
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
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
|
||||||
|
-- no need to keep going if already recorded
|
||||||
|
-- This also prevents an infinite loop
|
||||||
|
do
|
||||||
|
M._modified[path] = true
|
||||||
|
path = vim.fn.fnamemodify(path, ":h")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node table
|
||||||
|
---@return boolean
|
||||||
|
function M.is_modified(node)
|
||||||
|
return node
|
||||||
|
and M.config.modified.enable
|
||||||
|
and M._modified[node.absolute_path]
|
||||||
|
and (not node.nodes or M.config.modified.show_on_dirs)
|
||||||
|
and (not node.open or M.config.modified.show_on_open_dirs)
|
||||||
|
end
|
||||||
|
|
||||||
|
---A buffer exists for the node's absolute path
|
||||||
|
---@param node table
|
||||||
|
---@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
|
||||||
@@ -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,53 @@
|
|||||||
local events = require "nvim-tree.events"
|
local events = require "nvim-tree.events"
|
||||||
local explorer = require "nvim-tree.explorer"
|
local explorer = require "nvim-tree.explorer"
|
||||||
|
local live_filter = require "nvim-tree.live-filter"
|
||||||
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)
|
||||||
|
local profile = log.profile_start("core init %s", foldername)
|
||||||
|
|
||||||
|
if TreeExplorer then
|
||||||
|
TreeExplorer:destroy()
|
||||||
|
end
|
||||||
TreeExplorer = explorer.Explorer.new(foldername)
|
TreeExplorer = explorer.Explorer.new(foldername)
|
||||||
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 live_filter.filter then
|
||||||
|
return offset + 1
|
||||||
|
end
|
||||||
return offset
|
return offset
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,177 +1,219 @@
|
|||||||
local a = vim.api
|
|
||||||
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 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
|
||||||
|
---@return boolean
|
||||||
|
local function is_severity_in_range(severity, config)
|
||||||
|
return config.max <= severity and severity <= config.min
|
||||||
end
|
end
|
||||||
|
|
||||||
local diagnostic_list = vim.fn.CocAction "diagnosticList"
|
---Handle any COC exceptions, preventing any propagation
|
||||||
if type(diagnostic_list) ~= "table" or vim.tbl_isempty(diagnostic_list) then
|
---@param err string
|
||||||
return {}
|
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
|
end
|
||||||
|
|
||||||
local buffer_severity = {}
|
if notify then
|
||||||
local diagnostics = {}
|
require("nvim-tree.notify").error("Diagnostics update from coc.nvim failed. " .. vim.inspect(err))
|
||||||
|
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
||||||
return buffer_severity
|
---COC service initialized
|
||||||
end
|
---@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
|
end
|
||||||
|
|
||||||
function M.update()
|
local buffer_severity = {}
|
||||||
if not M.enable or not core.get_explorer() or not view.is_buf_valid(view.get_bufnr()) then
|
for _, diagnostic in ipairs(diagnostic_list) do
|
||||||
return
|
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
|
end
|
||||||
local ps = log.profile_start "diagnostics update"
|
|
||||||
log.line("diagnostics", "update")
|
|
||||||
|
|
||||||
local buffer_severity
|
return buffer_severity
|
||||||
if is_using_coc() then
|
end
|
||||||
buffer_severity = from_coc()
|
|
||||||
|
---Maybe retrieve severity level from the cache
|
||||||
|
---@param node Node
|
||||||
|
---@return DiagStatus
|
||||||
|
local function from_cache(node)
|
||||||
|
local nodepath = uniformize_path(node.absolute_path)
|
||||||
|
local max_severity = nil
|
||||||
|
if not node.nodes then
|
||||||
|
-- direct cache hit for files
|
||||||
|
max_severity = NODE_SEVERITIES[nodepath]
|
||||||
else
|
else
|
||||||
buffer_severity = from_nvim_lsp()
|
-- dirs should be searched in the list of cached buffer names by prefix
|
||||||
|
for bufname, severity in pairs(NODE_SEVERITIES) do
|
||||||
|
local node_contains_buf = vim.startswith(bufname, nodepath .. "/")
|
||||||
|
if node_contains_buf then
|
||||||
|
if severity == M.severity.max then
|
||||||
|
max_severity = severity
|
||||||
|
break
|
||||||
|
else
|
||||||
|
max_severity = math.min(max_severity or severity, severity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return { value = max_severity, cache_version = NODE_SEVERITIES_VERSION }
|
||||||
end
|
end
|
||||||
|
|
||||||
M.clear()
|
---Fired on DiagnosticChanged and CocDiagnosticChanged events:
|
||||||
for bufname, severity in pairs(buffer_severity) do
|
---debounced retrieval, cache update, version increment and draw
|
||||||
local bufpath = utils.canonical_path(bufname)
|
function M.update()
|
||||||
log.line("diagnostics", " bufpath '%s' severity %d", bufpath, severity)
|
if not M.enable then
|
||||||
if 0 < severity and severity < 5 then
|
return
|
||||||
local nodes_by_line = utils.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line())
|
end
|
||||||
for line, node in pairs(nodes_by_line) do
|
utils.debounce("diagnostics", M.debounce_delay, function()
|
||||||
local nodepath = utils.canonical_path(node.absolute_path)
|
local profile = log.profile_start "diagnostics update"
|
||||||
log.line("diagnostics", " %d checking nodepath '%s'", line, nodepath)
|
if is_using_coc() then
|
||||||
if M.show_on_dirs and vim.startswith(bufpath, nodepath) then
|
NODE_SEVERITIES = from_coc()
|
||||||
log.line("diagnostics", " matched fold node '%s'", node.absolute_path)
|
else
|
||||||
add_sign(line, severity)
|
NODE_SEVERITIES = from_nvim_lsp()
|
||||||
elseif nodepath == bufpath then
|
end
|
||||||
log.line("diagnostics", " matched file node '%s'", node.absolute_path)
|
NODE_SEVERITIES_VERSION = NODE_SEVERITIES_VERSION + 1
|
||||||
add_sign(line, severity)
|
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
|
||||||
end
|
end
|
||||||
|
log.profile_end(profile)
|
||||||
|
if view.is_buf_valid(view.get_bufnr()) then
|
||||||
|
require("nvim-tree.renderer").draw()
|
||||||
end
|
end
|
||||||
end
|
end)
|
||||||
log.profile_end(ps, "diagnostics update")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local links = {
|
---Maybe retrieve diagnostic status for a node.
|
||||||
NvimTreeLspDiagnosticsError = "DiagnosticError",
|
---Returns cached value when node's version matches.
|
||||||
NvimTreeLspDiagnosticsWarning = "DiagnosticWarn",
|
---@param node Node
|
||||||
NvimTreeLspDiagnosticsInformation = "DiagnosticInfo",
|
---@return DiagStatus|nil
|
||||||
NvimTreeLspDiagnosticsHint = "DiagnosticHint",
|
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.nodes ~= nil 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
|
||||||
|
|
||||||
|
-- file
|
||||||
|
if not node.nodes then
|
||||||
|
return node.diag_status
|
||||||
|
end
|
||||||
|
|
||||||
|
-- dir is closed or we should show on open_dirs
|
||||||
|
if not node.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
|
||||||
|
|||||||
21
lua/nvim-tree/enum.lua
Normal file
21
lua/nvim-tree/enum.lua
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
---Setup options for "highlight_*"
|
||||||
|
---@enum HL_POSITION
|
||||||
|
M.HL_POSITION = {
|
||||||
|
none = 0,
|
||||||
|
icon = 1,
|
||||||
|
name = 2,
|
||||||
|
all = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
---Setup options for "*_placement"
|
||||||
|
---@enum ICON_PLACEMENT
|
||||||
|
M.ICON_PLACEMENT = {
|
||||||
|
none = 0,
|
||||||
|
signcolumn = 1,
|
||||||
|
before = 2,
|
||||||
|
after = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 +1,95 @@
|
|||||||
local api = vim.api
|
|
||||||
local uv = vim.loop
|
|
||||||
|
|
||||||
local utils = require "nvim-tree.utils"
|
local utils = require "nvim-tree.utils"
|
||||||
local builders = require "nvim-tree.explorer.node-builders"
|
local builders = require "nvim-tree.explorer.node-builders"
|
||||||
local common = require "nvim-tree.explorer.common"
|
local explorer_node = require "nvim-tree.explorer.node"
|
||||||
|
local git = require "nvim-tree.git"
|
||||||
local sorters = require "nvim-tree.explorer.sorters"
|
local sorters = require "nvim-tree.explorer.sorters"
|
||||||
local filters = require "nvim-tree.explorer.filters"
|
local filters = require "nvim-tree.explorer.filters"
|
||||||
|
local live_filter = require "nvim-tree.live-filter"
|
||||||
|
local log = require "nvim-tree.log"
|
||||||
|
|
||||||
|
local Watcher = require "nvim-tree.watcher"
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local function get_type_from(type_, cwd)
|
---@param handle uv.uv_fs_t
|
||||||
return type_ or (uv.fs_stat(cwd) or {}).type
|
---@param cwd string
|
||||||
end
|
---@param node Node
|
||||||
|
---@param git_status table
|
||||||
local function populate_children(handle, cwd, node, status)
|
local function populate_children(handle, cwd, node, git_status)
|
||||||
local node_ignored = node.git_status == "!!"
|
local node_ignored = explorer_node.is_git_ignored(node)
|
||||||
|
local nodes_by_path = utils.bool_record(node.nodes, "absolute_path")
|
||||||
|
local filter_status = filters.prepare(git_status)
|
||||||
while true do
|
while true do
|
||||||
local name, t = uv.fs_scandir_next(handle)
|
local name, t = vim.loop.fs_scandir_next(handle)
|
||||||
if not name then
|
if not name then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
local nodes_by_path = utils.key_by(node.nodes, "absolute_path")
|
|
||||||
local abs = utils.path_join { cwd, name }
|
local abs = utils.path_join { cwd, name }
|
||||||
t = get_type_from(t, abs)
|
local profile = log.profile_start("explore populate_children %s", abs)
|
||||||
if
|
|
||||||
not filters.should_ignore(abs)
|
---@type uv.fs_stat.result|nil
|
||||||
and not filters.should_ignore_git(abs, status.files)
|
local stat = vim.loop.fs_stat(abs)
|
||||||
and not nodes_by_path[abs]
|
|
||||||
then
|
if not filters.should_filter(abs, stat, filter_status) and not nodes_by_path[abs] and Watcher.is_fs_event_capable(abs) then
|
||||||
local child = nil
|
local child = nil
|
||||||
if t == "directory" and uv.fs_access(abs, "R") then
|
if t == "directory" and vim.loop.fs_access(abs, "R") then
|
||||||
child = builders.folder(node, abs, name, status, node_ignored)
|
child = builders.folder(node, abs, name, stat)
|
||||||
elseif t == "file" then
|
elseif t == "file" then
|
||||||
child = builders.file(node, abs, name, status, node_ignored)
|
child = builders.file(node, abs, name, stat)
|
||||||
elseif t == "link" then
|
elseif t == "link" then
|
||||||
local link = builders.link(node, abs, name, status, node_ignored)
|
local link = builders.link(node, abs, name, stat)
|
||||||
if link.link_to ~= nil then
|
if link.link_to ~= nil then
|
||||||
child = link
|
child = link
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if child then
|
if child then
|
||||||
table.insert(node.nodes, child)
|
table.insert(node.nodes, child)
|
||||||
common.update_git_status(child, node_ignored, status)
|
nodes_by_path[child.absolute_path] = true
|
||||||
end
|
explorer_node.update_git_status(child, node_ignored, git_status)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function get_dir_handle(cwd)
|
log.profile_end(profile)
|
||||||
local handle = uv.fs_scandir(cwd)
|
|
||||||
if type(handle) == "string" then
|
|
||||||
api.nvim_err_writeln(handle)
|
|
||||||
return
|
|
||||||
end
|
end
|
||||||
return handle
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
---@param status table
|
||||||
|
---@return Node[]|nil
|
||||||
function M.explore(node, status)
|
function M.explore(node, status)
|
||||||
local cwd = node.cwd or node.link_to or node.absolute_path
|
local cwd = node.link_to or node.absolute_path
|
||||||
local handle = get_dir_handle(cwd)
|
local handle = vim.loop.fs_scandir(cwd)
|
||||||
if not handle then
|
if not handle then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local profile = log.profile_start("explore init %s", node.absolute_path)
|
||||||
|
|
||||||
populate_children(handle, cwd, node, status)
|
populate_children(handle, cwd, node, status)
|
||||||
|
|
||||||
local is_root = node.cwd ~= nil
|
local is_root = not node.parent
|
||||||
local child_folder_only = common.has_one_child_folder(node) and node.nodes[1]
|
local child_folder_only = explorer_node.has_one_child_folder(node) and node.nodes[1]
|
||||||
if vim.g.nvim_tree_group_empty == 1 and not is_root and child_folder_only then
|
if M.config.group_empty and not is_root and child_folder_only then
|
||||||
|
local child_cwd = child_folder_only.link_to or child_folder_only.absolute_path
|
||||||
|
local child_status = git.load_project_status(child_cwd)
|
||||||
node.group_next = child_folder_only
|
node.group_next = child_folder_only
|
||||||
local ns = M.explore(child_folder_only, status)
|
local ns = M.explore(child_folder_only, child_status)
|
||||||
node.nodes = ns or {}
|
node.nodes = ns or {}
|
||||||
|
|
||||||
|
log.profile_end(profile)
|
||||||
return ns
|
return ns
|
||||||
end
|
end
|
||||||
|
|
||||||
sorters.merge_sort(node.nodes, sorters.node_comparator)
|
sorters.sort(node.nodes)
|
||||||
|
live_filter.apply_filter(node)
|
||||||
|
|
||||||
|
log.profile_end(profile)
|
||||||
return node.nodes
|
return node.nodes
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.config = opts.renderer
|
||||||
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
local utils = require "nvim-tree.utils"
|
local utils = require "nvim-tree.utils"
|
||||||
|
local marks = require "nvim-tree.marks"
|
||||||
|
|
||||||
local M = {
|
local M = {
|
||||||
ignore_list = {},
|
ignore_list = {},
|
||||||
exclude_list = {},
|
exclude_list = {},
|
||||||
|
custom_function = nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
---@param path string
|
||||||
|
---@return boolean
|
||||||
local function is_excluded(path)
|
local function is_excluded(path)
|
||||||
for _, node in ipairs(M.exclude_list) do
|
for _, node in ipairs(M.exclude_list) do
|
||||||
if path:match(node) then
|
if path:match(node) then
|
||||||
@@ -14,26 +18,109 @@ 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
|
||||||
---@param path string Absolute path
|
---@param path string Absolute path
|
||||||
|
---@param git_status table from prepare
|
||||||
---@return boolean
|
---@return boolean
|
||||||
function M.should_ignore(path)
|
local function git(path, git_status)
|
||||||
local basename = utils.path_basename(path)
|
if type(git_status) ~= "table" or type(git_status.files) ~= "table" or type(git_status.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 status = git_status.files[path]
|
||||||
|
status = status or git_status.dirs.direct[path] and git_status.dirs.direct[path][1]
|
||||||
|
status = status or git_status.dirs.indirect[path] and git_status.dirs.indirect[path][1]
|
||||||
|
|
||||||
|
-- filter ignored; overrides clean as they are effectively dirty
|
||||||
|
if M.config.filter_git_ignored and status == "!!" then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- filter clean
|
||||||
|
if M.config.filter_git_clean and not status then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
---Check if the given path has no listed buffer
|
||||||
|
---@param path string Absolute path
|
||||||
|
---@param bufinfo table vim.fn.getbufinfo { buflisted = 1 }
|
||||||
|
---@return boolean
|
||||||
|
local function buf(path, bufinfo)
|
||||||
|
if not M.config.filter_no_buffer or type(bufinfo) ~= "table" then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- filter files with no open buffer and directories containing no open buffers
|
||||||
|
for _, b in ipairs(bufinfo) do
|
||||||
|
if b.name == path or b.name:find(path .. "/", 1, true) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param path string
|
||||||
|
---@return boolean
|
||||||
|
local function dotfile(path)
|
||||||
|
return M.config.filter_dotfiles and utils.path_basename(path):sub(1, 1) == "."
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param path string
|
||||||
|
---@param path_type string|nil filetype of path
|
||||||
|
---@param bookmarks table<string, string|nil> path, filetype table of bookmarked files
|
||||||
|
local function bookmark(path, path_type, bookmarks)
|
||||||
|
if not M.config.filter_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
|
||||||
|
|
||||||
|
---@param path string
|
||||||
|
---@return boolean
|
||||||
|
local function custom(path)
|
||||||
if not M.config.filter_custom then
|
if not M.config.filter_custom then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local basename = utils.path_basename(path)
|
||||||
|
|
||||||
|
-- filter user's custom function
|
||||||
|
if M.custom_function and M.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(M.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
|
||||||
@@ -51,27 +138,76 @@ 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 git_status table|nil optional results of git.load_project_status(...)
|
||||||
and (M.config.filter_git_ignored and status and status[path] == "!!")
|
---@return table
|
||||||
and not is_excluded(path)
|
--- git_status: reference
|
||||||
|
--- bufinfo: empty unless no_buffer set: vim.fn.getbufinfo { buflisted = 1 }
|
||||||
|
--- bookmarks: absolute paths to boolean
|
||||||
|
function M.prepare(git_status)
|
||||||
|
local status = {
|
||||||
|
git_status = git_status or {},
|
||||||
|
bufinfo = {},
|
||||||
|
bookmarks = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
if M.config.filter_no_buffer then
|
||||||
|
status.bufinfo = vim.fn.getbufinfo { buflisted = 1 }
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, node in pairs(marks.get_marks()) do
|
||||||
|
status.bookmarks[node.absolute_path] = node.type
|
||||||
|
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 M.should_filter(path, fs_stat, status)
|
||||||
|
if not M.config.enable then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- exclusions override all filters
|
||||||
|
if is_excluded(path) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return git(path, status.git_status)
|
||||||
|
or buf(path, status.bufinfo)
|
||||||
|
or dotfile(path)
|
||||||
|
or custom(path)
|
||||||
|
or bookmark(path, fs_stat and fs_stat.type, status.bookmarks)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.setup(opts)
|
function M.setup(opts)
|
||||||
M.config = {
|
M.config = {
|
||||||
|
enable = opts.filters.enable,
|
||||||
filter_custom = true,
|
filter_custom = true,
|
||||||
filter_dotfiles = opts.filters.dotfiles,
|
filter_dotfiles = opts.filters.dotfiles,
|
||||||
filter_git_ignored = opts.git.ignore,
|
filter_git_ignored = opts.filters.git_ignored,
|
||||||
|
filter_git_clean = opts.filters.git_clean,
|
||||||
|
filter_no_buffer = opts.filters.no_buffer,
|
||||||
|
filter_no_bookmark = opts.filters.no_bookmark,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
M.ignore_list = {}
|
||||||
M.exclude_list = opts.filters.exclude
|
M.exclude_list = opts.filters.exclude
|
||||||
|
|
||||||
local custom_filter = opts.filters.custom
|
local custom_filter = opts.filters.custom
|
||||||
|
if type(custom_filter) == "function" then
|
||||||
|
M.custom_function = custom_filter
|
||||||
|
else
|
||||||
if custom_filter and #custom_filter > 0 then
|
if custom_filter and #custom_filter > 0 then
|
||||||
for _, filter_name in pairs(custom_filter) do
|
for _, filter_name in pairs(custom_filter) do
|
||||||
M.ignore_list[filter_name] = true
|
M.ignore_list[filter_name] = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -1,38 +1,79 @@
|
|||||||
local uv = vim.loop
|
|
||||||
|
|
||||||
local git = require "nvim-tree.git"
|
local git = require "nvim-tree.git"
|
||||||
|
local notify = require "nvim-tree.notify"
|
||||||
|
local watch = require "nvim-tree.explorer.watch"
|
||||||
|
local explorer_node = require "nvim-tree.explorer.node"
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
M.explore = require("nvim-tree.explorer.explore").explore
|
M.explore = require("nvim-tree.explorer.explore").explore
|
||||||
M.reload = require("nvim-tree.explorer.reload").reload
|
M.reload = require("nvim-tree.explorer.reload").reload
|
||||||
|
|
||||||
|
---@class Explorer
|
||||||
|
---@field absolute_path string
|
||||||
|
---@field nodes Node[]
|
||||||
|
---@field open boolean
|
||||||
|
|
||||||
local Explorer = {}
|
local Explorer = {}
|
||||||
Explorer.__index = Explorer
|
Explorer.__index = Explorer
|
||||||
|
|
||||||
function Explorer.new(cwd)
|
---@param path string|nil
|
||||||
cwd = uv.fs_realpath(cwd or uv.cwd())
|
---@return Explorer|nil
|
||||||
|
function Explorer.new(path)
|
||||||
|
local err
|
||||||
|
|
||||||
|
if path then
|
||||||
|
path, err = vim.loop.fs_realpath(path)
|
||||||
|
else
|
||||||
|
path, err = vim.loop.cwd()
|
||||||
|
end
|
||||||
|
if not path then
|
||||||
|
notify.error(err)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
---@class Explorer
|
||||||
local explorer = setmetatable({
|
local explorer = setmetatable({
|
||||||
cwd = cwd,
|
absolute_path = path,
|
||||||
nodes = {},
|
nodes = {},
|
||||||
|
open = true,
|
||||||
}, Explorer)
|
}, Explorer)
|
||||||
|
explorer.watcher = watch.create_watcher(explorer)
|
||||||
explorer:_load(explorer)
|
explorer:_load(explorer)
|
||||||
return explorer
|
return explorer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@param node Node
|
||||||
function Explorer:_load(node)
|
function Explorer:_load(node)
|
||||||
local cwd = node.cwd or node.link_to or node.absolute_path
|
local cwd = node.link_to or node.absolute_path
|
||||||
local git_statuses = git.load_project_status(cwd)
|
local git_status = git.load_project_status(cwd)
|
||||||
M.explore(node, git_statuses)
|
M.explore(node, git_status)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
function Explorer:expand(node)
|
function Explorer:expand(node)
|
||||||
self:_load(node)
|
self:_load(node)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Explorer:destroy()
|
||||||
|
local function iterate(node)
|
||||||
|
explorer_node.node_destroy(node)
|
||||||
|
if node.nodes then
|
||||||
|
for _, child in pairs(node.nodes) do
|
||||||
|
iterate(child)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
iterate(self)
|
||||||
|
end
|
||||||
|
|
||||||
function M.setup(opts)
|
function M.setup(opts)
|
||||||
|
require("nvim-tree.explorer.node").setup(opts)
|
||||||
|
require("nvim-tree.explorer.explore").setup(opts)
|
||||||
require("nvim-tree.explorer.filters").setup(opts)
|
require("nvim-tree.explorer.filters").setup(opts)
|
||||||
require("nvim-tree.explorer.sorters").setup(opts)
|
require("nvim-tree.explorer.sorters").setup(opts)
|
||||||
|
require("nvim-tree.explorer.reload").setup(opts)
|
||||||
|
require("nvim-tree.explorer.watch").setup(opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
M.Explorer = Explorer
|
M.Explorer = Explorer
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
local uv = vim.loop
|
|
||||||
local utils = require "nvim-tree.utils"
|
local utils = require "nvim-tree.utils"
|
||||||
|
local watch = require "nvim-tree.explorer.watch"
|
||||||
|
|
||||||
local M = {
|
local M = {}
|
||||||
is_windows = vim.fn.has "win32" == 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
function M.folder(parent, absolute_path, name)
|
---@param parent Node
|
||||||
local handle = uv.fs_scandir(absolute_path)
|
---@param absolute_path string
|
||||||
local has_children = handle and uv.fs_scandir_next(handle) ~= nil
|
---@param name string
|
||||||
|
---@param fs_stat uv.fs_stat.result|nil
|
||||||
|
---@return Node
|
||||||
|
function M.folder(parent, absolute_path, name, fs_stat)
|
||||||
|
local handle = vim.loop.fs_scandir(absolute_path)
|
||||||
|
local has_children = handle and vim.loop.fs_scandir_next(handle) ~= nil
|
||||||
|
|
||||||
return {
|
local node = {
|
||||||
|
type = "directory",
|
||||||
absolute_path = absolute_path,
|
absolute_path = absolute_path,
|
||||||
fs_stat = uv.fs_stat(absolute_path),
|
fs_stat = fs_stat,
|
||||||
group_next = nil, -- If node is grouped, this points to the next child dir/link node
|
group_next = nil, -- If node is grouped, this points to the next child dir/link node
|
||||||
has_children = has_children,
|
has_children = has_children,
|
||||||
name = name,
|
name = name,
|
||||||
@@ -19,23 +23,38 @@ function M.folder(parent, absolute_path, name)
|
|||||||
open = false,
|
open = false,
|
||||||
parent = parent,
|
parent = parent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
node.watcher = watch.create_watcher(node)
|
||||||
|
|
||||||
|
return node
|
||||||
end
|
end
|
||||||
|
|
||||||
local function is_executable(absolute_path, ext)
|
--- path is an executable file or directory
|
||||||
if M.is_windows then
|
---@param absolute_path string
|
||||||
return utils.is_windows_exe(ext)
|
---@return boolean|nil
|
||||||
|
function M.is_executable(absolute_path)
|
||||||
|
if utils.is_windows or utils.is_wsl then
|
||||||
|
--- executable detection on windows is buggy and not performant hence it is disabled
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
return vim.loop.fs_access(absolute_path, "X")
|
||||||
end
|
end
|
||||||
return uv.fs_access(absolute_path, "X")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.file(parent, absolute_path, name)
|
---@param parent Node
|
||||||
|
---@param absolute_path string
|
||||||
|
---@param name string
|
||||||
|
---@param fs_stat uv.fs_stat.result|nil
|
||||||
|
---@return Node
|
||||||
|
function M.file(parent, absolute_path, name, fs_stat)
|
||||||
local ext = string.match(name, ".?[^.]+%.(.*)") or ""
|
local ext = string.match(name, ".?[^.]+%.(.*)") or ""
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
type = "file",
|
||||||
absolute_path = absolute_path,
|
absolute_path = absolute_path,
|
||||||
executable = is_executable(absolute_path, ext),
|
executable = M.is_executable(absolute_path),
|
||||||
extension = ext,
|
extension = ext,
|
||||||
fs_stat = uv.fs_stat(absolute_path),
|
fs_stat = fs_stat,
|
||||||
name = name,
|
name = name,
|
||||||
parent = parent,
|
parent = parent,
|
||||||
}
|
}
|
||||||
@@ -46,20 +65,29 @@ end
|
|||||||
-- links (for instance libr2.so in /usr/lib) and thus even with a C program realpath fails
|
-- 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.
|
-- 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
|
-- So we need to check for link_to ~= nil when adding new links to the main tree
|
||||||
function M.link(parent, absolute_path, name)
|
---@param parent Node
|
||||||
--- 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.
|
---@param absolute_path string
|
||||||
local link_to = uv.fs_realpath(absolute_path)
|
---@param name string
|
||||||
|
---@param fs_stat uv.fs_stat.result|nil
|
||||||
|
---@return Node
|
||||||
|
function M.link(parent, absolute_path, name, fs_stat)
|
||||||
|
--- I dont know if this is needed, because in my understanding, there isn't hard links in windows, but just to be sure i changed it.
|
||||||
|
local link_to = vim.loop.fs_realpath(absolute_path)
|
||||||
local open, nodes, has_children
|
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)
|
local is_dir_link = (link_to ~= nil) and vim.loop.fs_stat(link_to).type == "directory"
|
||||||
has_children = handle and uv.fs_scandir_next(handle) ~= nil
|
|
||||||
|
if is_dir_link and link_to then
|
||||||
|
local handle = vim.loop.fs_scandir(link_to)
|
||||||
|
has_children = handle and vim.loop.fs_scandir_next(handle) ~= nil
|
||||||
open = false
|
open = false
|
||||||
nodes = {}
|
nodes = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
local node = {
|
||||||
|
type = "link",
|
||||||
absolute_path = absolute_path,
|
absolute_path = absolute_path,
|
||||||
fs_stat = uv.fs_stat(absolute_path),
|
fs_stat = fs_stat,
|
||||||
group_next = nil, -- If node is grouped, this points to the next child dir/link node
|
group_next = nil, -- If node is grouped, this points to the next child dir/link node
|
||||||
has_children = has_children,
|
has_children = has_children,
|
||||||
link_to = link_to,
|
link_to = link_to,
|
||||||
@@ -68,6 +96,12 @@ function M.link(parent, absolute_path, name)
|
|||||||
open = open,
|
open = open,
|
||||||
parent = parent,
|
parent = parent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if is_dir_link then
|
||||||
|
node.watcher = watch.create_watcher(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
return node
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
149
lua/nvim-tree/explorer/node.lua
Normal file
149
lua/nvim-tree/explorer/node.lua
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@class GitStatus
|
||||||
|
---@field file string|nil
|
||||||
|
---@field dir table|nil
|
||||||
|
|
||||||
|
---@param parent_ignored boolean
|
||||||
|
---@param status table|nil
|
||||||
|
---@param absolute_path string
|
||||||
|
---@return GitStatus|nil
|
||||||
|
local function get_dir_git_status(parent_ignored, status, absolute_path)
|
||||||
|
if parent_ignored then
|
||||||
|
return { file = "!!" }
|
||||||
|
end
|
||||||
|
|
||||||
|
if status then
|
||||||
|
return {
|
||||||
|
file = status.files and status.files[absolute_path],
|
||||||
|
dir = status.dirs and {
|
||||||
|
direct = status.dirs.direct[absolute_path],
|
||||||
|
indirect = status.dirs.indirect[absolute_path],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param parent_ignored boolean
|
||||||
|
---@param status table
|
||||||
|
---@param absolute_path string
|
||||||
|
---@return GitStatus
|
||||||
|
local function get_git_status(parent_ignored, status, absolute_path)
|
||||||
|
local file_status = parent_ignored and "!!" or (status and status.files and status.files[absolute_path])
|
||||||
|
return { file = file_status }
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
---@return boolean
|
||||||
|
function M.has_one_child_folder(node)
|
||||||
|
return #node.nodes == 1 and node.nodes[1].nodes and vim.loop.fs_access(node.nodes[1].absolute_path, "R") or false
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
---@param parent_ignored boolean
|
||||||
|
---@param status table|nil
|
||||||
|
function M.update_git_status(node, parent_ignored, status)
|
||||||
|
local get_status
|
||||||
|
if node.nodes then
|
||||||
|
get_status = get_dir_git_status
|
||||||
|
else
|
||||||
|
get_status = get_git_status
|
||||||
|
end
|
||||||
|
|
||||||
|
-- status of the node's absolute path
|
||||||
|
node.git_status = get_status(parent_ignored, status, node.absolute_path)
|
||||||
|
|
||||||
|
-- status of the link target, if the link itself is not dirty
|
||||||
|
if node.link_to and not node.git_status then
|
||||||
|
node.git_status = get_status(parent_ignored, status, node.link_to)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
---@return GitStatus|nil
|
||||||
|
function M.get_git_status(node)
|
||||||
|
local git_status = node and node.git_status
|
||||||
|
if not git_status then
|
||||||
|
-- status doesn't exist
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if not node.nodes then
|
||||||
|
-- file
|
||||||
|
return git_status.file and { git_status.file }
|
||||||
|
end
|
||||||
|
|
||||||
|
-- dir
|
||||||
|
if not M.config.git.show_on_dirs then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local status = {}
|
||||||
|
if not require("nvim-tree.lib").get_last_group_node(node).open or M.config.git.show_on_open_dirs then
|
||||||
|
-- dir is closed or we should show on open_dirs
|
||||||
|
if git_status.file ~= nil then
|
||||||
|
table.insert(status, git_status.file)
|
||||||
|
end
|
||||||
|
if git_status.dir ~= nil then
|
||||||
|
if git_status.dir.direct ~= nil then
|
||||||
|
for _, s in pairs(node.git_status.dir.direct) do
|
||||||
|
table.insert(status, s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if git_status.dir.indirect ~= nil then
|
||||||
|
for _, s in pairs(node.git_status.dir.indirect) do
|
||||||
|
table.insert(status, s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- dir is open and we shouldn't show on open_dirs
|
||||||
|
if git_status.file ~= nil then
|
||||||
|
table.insert(status, git_status.file)
|
||||||
|
end
|
||||||
|
if git_status.dir ~= nil and git_status.dir.direct ~= nil then
|
||||||
|
local deleted = {
|
||||||
|
[" D"] = true,
|
||||||
|
["D "] = true,
|
||||||
|
["RD"] = true,
|
||||||
|
["DD"] = true,
|
||||||
|
}
|
||||||
|
for _, s in pairs(node.git_status.dir.direct) do
|
||||||
|
if deleted[s] then
|
||||||
|
table.insert(status, s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #status == 0 then
|
||||||
|
return nil
|
||||||
|
else
|
||||||
|
return status
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
---@return boolean
|
||||||
|
function M.is_git_ignored(node)
|
||||||
|
return node and node.git_status ~= nil and node.git_status.file == "!!"
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
function M.node_destroy(node)
|
||||||
|
if not node then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if node.watcher then
|
||||||
|
node.watcher:destroy()
|
||||||
|
node.watcher = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.config = {
|
||||||
|
git = opts.git,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -1,83 +1,232 @@
|
|||||||
local api = vim.api
|
|
||||||
local uv = vim.loop
|
|
||||||
|
|
||||||
local utils = require "nvim-tree.utils"
|
local utils = require "nvim-tree.utils"
|
||||||
local builders = require "nvim-tree.explorer.node-builders"
|
local builders = require "nvim-tree.explorer.node-builders"
|
||||||
local common = require "nvim-tree.explorer.common"
|
local explorer_node = require "nvim-tree.explorer.node"
|
||||||
local filters = require "nvim-tree.explorer.filters"
|
local filters = require "nvim-tree.explorer.filters"
|
||||||
local sorters = require "nvim-tree.explorer.sorters"
|
local sorters = require "nvim-tree.explorer.sorters"
|
||||||
|
local live_filter = require "nvim-tree.live-filter"
|
||||||
|
local git = require "nvim-tree.git"
|
||||||
|
local log = require "nvim-tree.log"
|
||||||
|
|
||||||
|
local NodeIterator = require "nvim-tree.iterators.node-iterator"
|
||||||
|
local Watcher = require "nvim-tree.watcher"
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
---@param nodes_by_path table
|
||||||
|
---@param node_ignored boolean
|
||||||
|
---@param status table
|
||||||
|
---@return fun(node: Node): table
|
||||||
local function update_status(nodes_by_path, node_ignored, status)
|
local function update_status(nodes_by_path, node_ignored, status)
|
||||||
return function(node)
|
return function(node)
|
||||||
if nodes_by_path[node.absolute_path] then
|
if nodes_by_path[node.absolute_path] then
|
||||||
common.update_git_status(node, node_ignored, status)
|
explorer_node.update_git_status(node, node_ignored, status)
|
||||||
end
|
end
|
||||||
return node
|
return node
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.reload(node, status)
|
---@param path string
|
||||||
local cwd = node.cwd or node.link_to or node.absolute_path
|
---@param callback fun(toplevel: string|nil, project: table|nil)
|
||||||
local handle = uv.fs_scandir(cwd)
|
local function reload_and_get_git_project(path, callback)
|
||||||
if type(handle) == "string" then
|
local toplevel = git.get_toplevel(path)
|
||||||
api.nvim_err_writeln(handle)
|
|
||||||
|
git.reload_project(toplevel, path, function()
|
||||||
|
callback(toplevel, git.get_project(toplevel) or {})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
---@param project table|nil
|
||||||
|
---@param root string|nil
|
||||||
|
local function update_parent_statuses(node, project, root)
|
||||||
|
while project and node do
|
||||||
|
-- step up to the containing project
|
||||||
|
if node.absolute_path == root then
|
||||||
|
-- stop at the top of the tree
|
||||||
|
if not node.parent then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
root = git.get_toplevel(node.parent.absolute_path)
|
||||||
|
|
||||||
|
-- stop when no more projects
|
||||||
|
if not root then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
-- update the containing project
|
||||||
|
project = git.get_project(root)
|
||||||
|
git.reload_project(root, node.absolute_path, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- update status
|
||||||
|
explorer_node.update_git_status(node, explorer_node.is_git_ignored(node.parent), project)
|
||||||
|
|
||||||
|
-- maybe parent
|
||||||
|
node = node.parent
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
---@param git_status table
|
||||||
|
function M.reload(node, git_status)
|
||||||
|
local cwd = node.link_to or node.absolute_path
|
||||||
|
local handle = vim.loop.fs_scandir(cwd)
|
||||||
|
if not handle then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local profile = log.profile_start("reload %s", node.absolute_path)
|
||||||
|
|
||||||
|
local filter_status = filters.prepare(git_status)
|
||||||
|
|
||||||
if node.group_next then
|
if node.group_next then
|
||||||
node.nodes = { node.group_next }
|
node.nodes = { node.group_next }
|
||||||
node.group_next = nil
|
node.group_next = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local child_names = {}
|
local remain_childs = {}
|
||||||
|
|
||||||
local node_ignored = node.git_status == "!!"
|
local node_ignored = explorer_node.is_git_ignored(node)
|
||||||
|
---@type table<string, Node>
|
||||||
local nodes_by_path = utils.key_by(node.nodes, "absolute_path")
|
local nodes_by_path = utils.key_by(node.nodes, "absolute_path")
|
||||||
while true do
|
while true do
|
||||||
local name, t = uv.fs_scandir_next(handle)
|
local name, t = vim.loop.fs_scandir_next(handle)
|
||||||
if not name then
|
if not name then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
local abs = utils.path_join { cwd, name }
|
local abs = utils.path_join { cwd, name }
|
||||||
t = t or (uv.fs_stat(abs) or {}).type
|
---@type uv.fs_stat.result|nil
|
||||||
if not filters.should_ignore(abs) and not filters.should_ignore_git(abs, status.files) then
|
local stat = vim.loop.fs_stat(abs)
|
||||||
child_names[abs] = true
|
|
||||||
if not nodes_by_path[abs] then
|
if not filters.should_filter(abs, stat, filter_status) then
|
||||||
if t == "directory" and uv.fs_access(abs, "R") then
|
remain_childs[abs] = true
|
||||||
table.insert(node.nodes, builders.folder(node, abs, name, status, node_ignored))
|
|
||||||
elseif t == "file" then
|
-- Recreate node if type changes.
|
||||||
table.insert(node.nodes, builders.file(node, abs, name, status, node_ignored))
|
if nodes_by_path[abs] then
|
||||||
elseif t == "link" then
|
local n = nodes_by_path[abs]
|
||||||
local link = builders.link(node, abs, name, status, node_ignored)
|
|
||||||
if link.link_to ~= nil then
|
if n.type ~= t then
|
||||||
table.insert(node.nodes, link)
|
utils.array_remove(node.nodes, n)
|
||||||
|
explorer_node.node_destroy(n)
|
||||||
|
nodes_by_path[abs] = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if not nodes_by_path[abs] then
|
||||||
|
local new_child = nil
|
||||||
|
if t == "directory" and vim.loop.fs_access(abs, "R") and Watcher.is_fs_event_capable(abs) then
|
||||||
|
new_child = builders.folder(node, abs, name, stat)
|
||||||
|
elseif t == "file" then
|
||||||
|
new_child = builders.file(node, abs, name, stat)
|
||||||
|
elseif t == "link" then
|
||||||
|
local link = builders.link(node, abs, name, stat)
|
||||||
|
if link.link_to ~= nil then
|
||||||
|
new_child = link
|
||||||
|
end
|
||||||
|
end
|
||||||
|
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 = builders.is_executable(abs) or false
|
||||||
|
n.fs_stat = stat
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
node.nodes = vim.tbl_map(
|
node.nodes = vim.tbl_map(
|
||||||
update_status(nodes_by_path, node_ignored, status),
|
update_status(nodes_by_path, node_ignored, git_status),
|
||||||
vim.tbl_filter(function(n)
|
vim.tbl_filter(function(n)
|
||||||
return child_names[n.absolute_path]
|
if remain_childs[n.absolute_path] then
|
||||||
|
return remain_childs[n.absolute_path]
|
||||||
|
else
|
||||||
|
explorer_node.node_destroy(n)
|
||||||
|
return false
|
||||||
|
end
|
||||||
end, node.nodes)
|
end, node.nodes)
|
||||||
)
|
)
|
||||||
|
|
||||||
local is_root = node.cwd ~= nil
|
local is_root = not node.parent
|
||||||
local child_folder_only = common.has_one_child_folder(node) and node.nodes[1]
|
local child_folder_only = explorer_node.has_one_child_folder(node) and node.nodes[1]
|
||||||
if vim.g.nvim_tree_group_empty == 1 and not is_root and child_folder_only then
|
if M.config.group_empty and not is_root and child_folder_only then
|
||||||
node.group_next = child_folder_only
|
node.group_next = child_folder_only
|
||||||
local ns = M.reload(child_folder_only, status)
|
local ns = M.reload(child_folder_only, git_status)
|
||||||
node.nodes = ns or {}
|
node.nodes = ns or {}
|
||||||
|
log.profile_end(profile)
|
||||||
return ns
|
return ns
|
||||||
end
|
end
|
||||||
|
|
||||||
sorters.merge_sort(node.nodes, sorters.node_comparator)
|
sorters.sort(node.nodes)
|
||||||
|
live_filter.apply_filter(node)
|
||||||
|
log.profile_end(profile)
|
||||||
return node.nodes
|
return node.nodes
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Refresh contents and git status for a single node
|
||||||
|
---@param node Node
|
||||||
|
---@param callback function
|
||||||
|
function M.refresh_node(node, callback)
|
||||||
|
if type(node) ~= "table" then
|
||||||
|
callback()
|
||||||
|
end
|
||||||
|
|
||||||
|
local parent_node = utils.get_parent_of_group(node)
|
||||||
|
|
||||||
|
reload_and_get_git_project(node.absolute_path, function(toplevel, project)
|
||||||
|
require("nvim-tree.explorer.reload").reload(parent_node, project)
|
||||||
|
|
||||||
|
update_parent_statuses(parent_node, project, toplevel)
|
||||||
|
|
||||||
|
callback()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Refresh contents of all nodes to a path: actual directory and links.
|
||||||
|
---Groups will be expanded if needed.
|
||||||
|
---@param path string absolute path
|
||||||
|
function M.refresh_parent_nodes_for_path(path)
|
||||||
|
local explorer = require("nvim-tree.core").get_explorer()
|
||||||
|
if not explorer then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local profile = log.profile_start("refresh_parent_nodes_for_path %s", path)
|
||||||
|
|
||||||
|
-- collect parent nodes from the top down
|
||||||
|
local parent_nodes = {}
|
||||||
|
NodeIterator.builder({ explorer })
|
||||||
|
: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 {}
|
||||||
|
|
||||||
|
M.reload(node, project)
|
||||||
|
update_parent_statuses(node, project, toplevel)
|
||||||
|
end
|
||||||
|
|
||||||
|
log.profile_end(profile)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.config = opts.renderer
|
||||||
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
local M = {
|
local M = {}
|
||||||
sort_by = nil,
|
|
||||||
node_comparator = nil,
|
local C = {}
|
||||||
}
|
|
||||||
|
--- Predefined comparator, defaulting to name
|
||||||
|
---@param sorter string as per options
|
||||||
|
---@return function
|
||||||
|
local function get_comparator(sorter)
|
||||||
|
return C[sorter] or C.name
|
||||||
|
end
|
||||||
|
|
||||||
---Create a shallow copy of a portion of a list.
|
---Create a shallow copy of a portion of a list.
|
||||||
---@param t table
|
---@param t table
|
||||||
@@ -17,6 +23,29 @@ local function tbl_slice(t, first, last)
|
|||||||
return slice
|
return slice
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Evaluate `sort.folders_first` and `sort.files_first`
|
||||||
|
---@param a Node
|
||||||
|
---@param b Node
|
||||||
|
---@return boolean|nil
|
||||||
|
local function folders_or_files_first(a, b)
|
||||||
|
if not (M.config.sort.folders_first or M.config.sort.files_first) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not a.nodes and b.nodes then
|
||||||
|
-- file <> folder
|
||||||
|
return M.config.sort.files_first
|
||||||
|
elseif a.nodes and not b.nodes then
|
||||||
|
-- folder <> file
|
||||||
|
return not M.config.sort.files_first
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param t table
|
||||||
|
---@param first number
|
||||||
|
---@param mid number
|
||||||
|
---@param last number
|
||||||
|
---@param comparator fun(a: Node, b: Node): boolean
|
||||||
local function merge(t, first, mid, last, comparator)
|
local function merge(t, first, mid, last, comparator)
|
||||||
local n1 = mid - first + 1
|
local n1 = mid - first + 1
|
||||||
local n2 = last - mid
|
local n2 = last - mid
|
||||||
@@ -50,6 +79,10 @@ local function merge(t, first, mid, last, comparator)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param t table
|
||||||
|
---@param first number
|
||||||
|
---@param last number
|
||||||
|
---@param comparator fun(a: Node, b: Node): boolean
|
||||||
local function split_merge(t, first, last, comparator)
|
local function split_merge(t, first, last, comparator)
|
||||||
if (last - first) < 1 then
|
if (last - first) < 1 then
|
||||||
return
|
return
|
||||||
@@ -62,27 +95,69 @@ local function split_merge(t, first, last, comparator)
|
|||||||
merge(t, first, mid, last, comparator)
|
merge(t, first, mid, last, comparator)
|
||||||
end
|
end
|
||||||
|
|
||||||
---Perform a merge sort on a given list.
|
---Perform a merge sort using sorter option.
|
||||||
---@param t any[]
|
---@param t table nodes
|
||||||
---@param comparator function|nil
|
function M.sort(t)
|
||||||
function M.merge_sort(t, comparator)
|
if C.user then
|
||||||
if not comparator then
|
local t_user = {}
|
||||||
comparator = function(left, right)
|
local origin_index = {}
|
||||||
return left < right
|
|
||||||
|
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
|
||||||
|
|
||||||
|
local predefined = C.user(t_user)
|
||||||
|
if predefined then
|
||||||
|
split_merge(t, 1, #t, get_comparator(predefined))
|
||||||
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
split_merge(t, 1, #t, comparator)
|
-- 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
|
end
|
||||||
|
|
||||||
|
split_merge(t, 1, #t, mini_comparator) -- sort by user order
|
||||||
|
else
|
||||||
|
split_merge(t, 1, #t, get_comparator(M.config.sort.sorter))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param a Node
|
||||||
|
---@param b Node
|
||||||
|
---@param ignorecase boolean|nil
|
||||||
|
---@return boolean
|
||||||
local function node_comparator_name_ignorecase_or_not(a, b, ignorecase)
|
local function node_comparator_name_ignorecase_or_not(a, b, ignorecase)
|
||||||
if not (a and b) then
|
if not (a and b) then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
if a.nodes and not b.nodes then
|
|
||||||
return true
|
local early_return = folders_or_files_first(a, b)
|
||||||
elseif not a.nodes and b.nodes then
|
if early_return ~= nil then
|
||||||
return false
|
return early_return
|
||||||
end
|
end
|
||||||
|
|
||||||
if ignorecase then
|
if ignorecase then
|
||||||
@@ -92,22 +167,22 @@ local function node_comparator_name_ignorecase_or_not(a, b, ignorecase)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.node_comparator_name_case_sensisive(a, b)
|
function C.case_sensitive(a, b)
|
||||||
return node_comparator_name_ignorecase_or_not(a, b, false)
|
return node_comparator_name_ignorecase_or_not(a, b, false)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.node_comparator_name_ignorecase(a, b)
|
function C.name(a, b)
|
||||||
return node_comparator_name_ignorecase_or_not(a, b, true)
|
return node_comparator_name_ignorecase_or_not(a, b, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.node_comparator_modification_time(a, b)
|
function C.modification_time(a, b)
|
||||||
if not (a and b) then
|
if not (a and b) then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
if a.nodes and not b.nodes then
|
|
||||||
return true
|
local early_return = folders_or_files_first(a, b)
|
||||||
elseif not a.nodes and b.nodes then
|
if early_return ~= nil then
|
||||||
return false
|
return early_return
|
||||||
end
|
end
|
||||||
|
|
||||||
local last_modified_a = 0
|
local last_modified_a = 0
|
||||||
@@ -124,14 +199,111 @@ function M.node_comparator_modification_time(a, b)
|
|||||||
return last_modified_b <= last_modified_a
|
return last_modified_b <= last_modified_a
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function C.suffix(a, b)
|
||||||
|
if not (a and b) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- directories go first
|
||||||
|
local early_return = folders_or_files_first(a, b)
|
||||||
|
if early_return ~= nil then
|
||||||
|
return early_return
|
||||||
|
elseif a.nodes and b.nodes then
|
||||||
|
return C.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 C.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 C.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 C.name(a, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
return a_suffix:lower() < b_suffix:lower()
|
||||||
|
end
|
||||||
|
|
||||||
|
function C.extension(a, b)
|
||||||
|
if not (a and b) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local early_return = 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 C.name(a, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
return a_ext < b_ext
|
||||||
|
end
|
||||||
|
|
||||||
|
function C.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 = 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 C.name(a, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
return a_ft < b_ft
|
||||||
|
end
|
||||||
|
|
||||||
function M.setup(opts)
|
function M.setup(opts)
|
||||||
M.sort_by = opts.sort_by
|
M.config = {}
|
||||||
if M.sort_by == "modification_time" then
|
M.config.sort = opts.sort
|
||||||
M.node_comparator = M.node_comparator_modification_time
|
|
||||||
elseif M.sort_by == "case_sensitive" then
|
if type(M.config.sort.sorter) == "function" then
|
||||||
M.node_comparator = M.node_comparator_name_case_sensisive
|
C.user = M.config.sort.sorter
|
||||||
else
|
|
||||||
M.node_comparator = M.node_comparator_name_ignorecase
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
92
lua/nvim-tree/explorer/watch.lua
Normal file
92
lua/nvim-tree/explorer/watch.lua
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
local log = require "nvim-tree.log"
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
---@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
|
||||||
|
|
||||||
|
local function callback(watcher)
|
||||||
|
log.line("watcher", "node event scheduled refresh %s", watcher.context)
|
||||||
|
utils.debounce(watcher.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
|
||||||
|
require("nvim-tree.explorer.reload").refresh_node(node, function()
|
||||||
|
require("nvim-tree.renderer").draw()
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.uid = M.uid + 1
|
||||||
|
return Watcher:new(path, nil, callback, {
|
||||||
|
context = "explorer:watch:" .. path .. ":" .. M.uid,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.config.filesystem_watchers = opts.filesystem_watchers
|
||||||
|
M.uid = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -1,78 +1,303 @@
|
|||||||
|
local log = require "nvim-tree.log"
|
||||||
|
local utils = require "nvim-tree.utils"
|
||||||
local git_utils = require "nvim-tree.git.utils"
|
local git_utils = require "nvim-tree.git.utils"
|
||||||
local Runner = require "nvim-tree.git.runner"
|
local Runner = require "nvim-tree.git.runner"
|
||||||
|
local Watcher = require("nvim-tree.watcher").Watcher
|
||||||
|
local Iterator = require "nvim-tree.iterators.node-iterator"
|
||||||
|
local explorer_node = require "nvim-tree.explorer.node"
|
||||||
|
|
||||||
local M = {
|
local M = {
|
||||||
config = nil,
|
config = {},
|
||||||
projects = {},
|
|
||||||
cwd_to_project_root = {},
|
-- all projects keyed by toplevel
|
||||||
|
_projects_by_toplevel = {},
|
||||||
|
|
||||||
|
-- index of paths inside toplevels, false when not inside a project
|
||||||
|
_toplevels_by_path = {},
|
||||||
|
|
||||||
|
-- git dirs by toplevel
|
||||||
|
_git_dirs_by_toplevel = {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- Files under .git that should result in a reload when changed.
|
||||||
|
-- Utilities (like watchman) can also write to this directory (often) and aren't useful for us.
|
||||||
|
local WATCHED_FILES = {
|
||||||
|
"FETCH_HEAD", -- remote ref
|
||||||
|
"HEAD", -- local ref
|
||||||
|
"HEAD.lock", -- HEAD will not always be updated e.g. revert
|
||||||
|
"config", -- user config
|
||||||
|
"index", -- staging area
|
||||||
|
}
|
||||||
|
|
||||||
|
---@param toplevel string|nil
|
||||||
|
---@param path string|nil
|
||||||
|
---@param project table
|
||||||
|
---@param git_status table|nil
|
||||||
|
local function reload_git_status(toplevel, path, project, git_status)
|
||||||
|
if path then
|
||||||
|
for p in pairs(project.files) do
|
||||||
|
if p:find(path, 1, true) == 1 then
|
||||||
|
project.files[p] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
project.files = vim.tbl_deep_extend("force", project.files, git_status)
|
||||||
|
else
|
||||||
|
project.files = git_status
|
||||||
|
end
|
||||||
|
|
||||||
|
project.dirs = git_utils.file_status_to_dir_status(project.files, toplevel)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Is this path in a known ignored directory?
|
||||||
|
---@param path string
|
||||||
|
---@param project table git status
|
||||||
|
---@return boolean
|
||||||
|
local function path_ignored_in_project(path, project)
|
||||||
|
if not path or not project then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if project and project.files then
|
||||||
|
for file, status in pairs(project.files) do
|
||||||
|
if status == "!!" and vim.startswith(path, file) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Reload all projects
|
||||||
|
---@return table projects maybe empty
|
||||||
function M.reload()
|
function M.reload()
|
||||||
if not M.config.enable then
|
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 {
|
end
|
||||||
project_root = project_root,
|
|
||||||
list_untracked = git_utils.should_show_untracked(project_root),
|
return M._projects_by_toplevel
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Reload one project. Does nothing when no project or path is ignored
|
||||||
|
---@param toplevel string|nil
|
||||||
|
---@param path string|nil optional path to update only
|
||||||
|
---@param callback function|nil
|
||||||
|
function M.reload_project(toplevel, path, callback)
|
||||||
|
local project = M._projects_by_toplevel[toplevel]
|
||||||
|
if not toplevel or not project or not M.config.git.enable then
|
||||||
|
if callback then
|
||||||
|
callback()
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if path and (path:find(toplevel, 1, true) ~= 1 or path_ignored_in_project(path, project)) then
|
||||||
|
if callback then
|
||||||
|
callback()
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local opts = {
|
||||||
|
toplevel = toplevel,
|
||||||
|
path = path,
|
||||||
|
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,
|
|
||||||
dirs = git_utils.file_status_to_dir_status(git_status, project_root),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if callback then
|
||||||
|
Runner.run(opts, function(git_status)
|
||||||
|
reload_git_status(toplevel, path, project, git_status)
|
||||||
|
callback()
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
-- TODO use callback once async/await is available
|
||||||
|
local git_status = Runner.run(opts)
|
||||||
|
reload_git_status(toplevel, path, project, git_status)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return M.projects
|
--- Retrieve a known project
|
||||||
|
---@param toplevel string|nil
|
||||||
|
---@return table|nil project
|
||||||
|
function M.get_project(toplevel)
|
||||||
|
return M._projects_by_toplevel[toplevel]
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.get_project_root(cwd)
|
--- Retrieve the toplevel for a path. nil on:
|
||||||
if M.cwd_to_project_root[cwd] then
|
--- git disabled
|
||||||
return M.cwd_to_project_root[cwd]
|
--- not part of a project
|
||||||
end
|
--- not a directory
|
||||||
|
--- path in git.disable_for_dirs
|
||||||
if M.cwd_to_project_root[cwd] == false then
|
---@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
|
end
|
||||||
|
|
||||||
function M.load_project_status(cwd)
|
if M._toplevels_by_path[path] then
|
||||||
if not M.config.enable then
|
return M._toplevels_by_path[path]
|
||||||
|
end
|
||||||
|
|
||||||
|
if M._toplevels_by_path[path] == 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
|
||||||
|
|
||||||
|
-- ignore disabled paths
|
||||||
|
for _, disabled_for_dir in ipairs(M.config.git.disable_for_dirs) do
|
||||||
|
local toplevel_norm = vim.fn.fnamemodify(toplevel, ":p")
|
||||||
|
local disabled_norm = vim.fn.fnamemodify(disabled_for_dir, ":p")
|
||||||
|
if toplevel_norm == disabled_norm then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M._toplevels_by_path[path] = toplevel
|
||||||
|
M._git_dirs_by_toplevel[toplevel] = git_dir
|
||||||
|
return M._toplevels_by_path[path]
|
||||||
|
end
|
||||||
|
|
||||||
|
local function reload_tree_at(toplevel)
|
||||||
|
if not M.config.git.enable or not toplevel then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
log.line("watcher", "git event executing '%s'", toplevel)
|
||||||
|
local root_node = utils.get_node_from_path(toplevel)
|
||||||
|
if not root_node then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
M.reload_project(toplevel, nil, function()
|
||||||
|
local git_status = M.get_project(toplevel)
|
||||||
|
|
||||||
|
Iterator.builder(root_node.nodes)
|
||||||
|
:hidden()
|
||||||
|
:applier(function(node)
|
||||||
|
local parent_ignored = explorer_node.is_git_ignored(node.parent)
|
||||||
|
explorer_node.update_git_status(node, parent_ignored, git_status)
|
||||||
|
end)
|
||||||
|
:recursor(function(node)
|
||||||
|
return node.nodes and #node.nodes > 0 and node.nodes
|
||||||
|
end)
|
||||||
|
:iterate()
|
||||||
|
|
||||||
|
require("nvim-tree.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 table project maybe empty
|
||||||
|
function M.load_project_status(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 status = M._projects_by_toplevel[toplevel]
|
||||||
if status then
|
if status then
|
||||||
return status
|
return status
|
||||||
end
|
end
|
||||||
|
|
||||||
local git_status = Runner.run {
|
local git_status = Runner.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] = {
|
|
||||||
|
local watcher = nil
|
||||||
|
if M.config.filesystem_watchers.enable then
|
||||||
|
log.line("watcher", "git start")
|
||||||
|
|
||||||
|
local callback = function(w)
|
||||||
|
log.line("watcher", "git event scheduled '%s'", w.toplevel)
|
||||||
|
utils.debounce("git:watcher:" .. w.toplevel, M.config.filesystem_watchers.debounce_delay, function()
|
||||||
|
if w.destroyed then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
reload_tree_at(w.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:new(git_dir, WATCHED_FILES, callback, {
|
||||||
|
toplevel = toplevel,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
if git_status then
|
||||||
|
M._projects_by_toplevel[toplevel] = {
|
||||||
files = git_status,
|
files = git_status,
|
||||||
dirs = git_utils.file_status_to_dir_status(git_status, project_root),
|
dirs = git_utils.file_status_to_dir_status(git_status, toplevel),
|
||||||
|
watcher = watcher,
|
||||||
}
|
}
|
||||||
return M.projects[project_root]
|
return M._projects_by_toplevel[toplevel]
|
||||||
|
else
|
||||||
|
M._toplevels_by_path[path] = false
|
||||||
|
return {}
|
||||||
|
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,53 @@
|
|||||||
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"
|
||||||
|
|
||||||
|
---@class Runner
|
||||||
local Runner = {}
|
local Runner = {}
|
||||||
Runner.__index = Runner
|
Runner.__index = Runner
|
||||||
|
|
||||||
function Runner:_parse_status_output(line)
|
local timeouts = 0
|
||||||
local status = line:sub(1, 2)
|
local MAX_TIMEOUTS = 5
|
||||||
-- removing `"` when git is returning special file status containing spaces
|
|
||||||
local path = line:sub(4, -2):gsub('^"', ""):gsub('"$', "")
|
---@private
|
||||||
|
---@param status string
|
||||||
|
---@param path string|nil
|
||||||
|
function Runner:_parse_status_output(status, path)
|
||||||
|
if not path then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
-- 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.output[utils.path_remove_trailing(utils.path_join { self.toplevel, path })] = status
|
||||||
end
|
end
|
||||||
return #line
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@param prev_output string
|
||||||
|
---@param incoming string
|
||||||
|
---@return string
|
||||||
function Runner:_handle_incoming_data(prev_output, incoming)
|
function Runner:_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
|
||||||
|
local skip_next_line = false
|
||||||
for line in prev:gmatch "[^\n]*\n" do
|
for line in prev:gmatch "[^\n]*\n" do
|
||||||
i = i + self:_parse_status_output(line)
|
if skip_next_line then
|
||||||
|
skip_next_line = false
|
||||||
|
else
|
||||||
|
local status = line:sub(1, 2)
|
||||||
|
local path = line:sub(4, -2)
|
||||||
|
if utils.str_find(status, "R") then
|
||||||
|
-- skip next line if it is a rename entry
|
||||||
|
skip_next_line = true
|
||||||
|
end
|
||||||
|
self:_parse_status_output(status, path)
|
||||||
|
end
|
||||||
|
i = i + #line
|
||||||
end
|
end
|
||||||
|
|
||||||
return prev:sub(i, -1)
|
return prev:sub(i, -1)
|
||||||
@@ -35,37 +58,50 @@ function Runner:_handle_incoming_data(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
|
||||||
|
|
||||||
|
---@param stdout_handle uv.uv_pipe_t
|
||||||
|
---@param stderr_handle uv.uv_pipe_t
|
||||||
|
---@return table
|
||||||
function Runner:_getopts(stdout_handle, stderr_handle)
|
function Runner:_getopts(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
|
||||||
|
|
||||||
|
---@param output string
|
||||||
function Runner:_log_raw_output(output)
|
function Runner:_log_raw_output(output)
|
||||||
if output and type(output) == "string" then
|
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()
|
---@param callback function|nil
|
||||||
|
function Runner:_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,18 +110,24 @@ 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 opts = self:_getopts(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(opts.args), " "))
|
||||||
|
|
||||||
handle, pid = uv.spawn(
|
handle, pid = vim.loop.spawn(
|
||||||
"git",
|
"git",
|
||||||
opts,
|
opts,
|
||||||
vim.schedule_wrap(function(rc)
|
vim.schedule_wrap(function(rc)
|
||||||
@@ -106,6 +148,9 @@ function Runner:_run_git_job()
|
|||||||
if err then
|
if err then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
if data then
|
||||||
|
data = data:gsub("%z", "\n")
|
||||||
|
end
|
||||||
self:_log_raw_output(data)
|
self:_log_raw_output(data)
|
||||||
output_leftover = self:_handle_incoming_data(output_leftover, data)
|
output_leftover = self:_handle_incoming_data(output_leftover, data)
|
||||||
end
|
end
|
||||||
@@ -114,24 +159,43 @@ function Runner:_run_git_job()
|
|||||||
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()
|
function Runner:_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
|
---@param opts table
|
||||||
function Runner.run(opts)
|
function Runner:_finalise(opts)
|
||||||
local ps = log.profile_start("git job %s", opts.project_root)
|
if self.rc == -1 then
|
||||||
|
log.line("git", "job timed out %s %s", opts.toplevel, opts.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, opts.timeout))
|
||||||
|
require("nvim-tree.git").disable_git_integration()
|
||||||
|
end
|
||||||
|
elseif self.rc ~= 0 then
|
||||||
|
log.line("git", "job fail rc %d %s %s", self.rc, opts.toplevel, opts.path)
|
||||||
|
else
|
||||||
|
log.line("git", "job success %s %s", opts.toplevel, opts.path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Runs a git process, which will be killed if it takes more than timeout which defaults to 400ms
|
||||||
|
---@param opts table
|
||||||
|
---@param callback function|nil executed passing return when complete
|
||||||
|
---@return table|nil status by absolute path, nil if callback present
|
||||||
|
function Runner.run(opts, callback)
|
||||||
local self = setmetatable({
|
local self = setmetatable({
|
||||||
project_root = opts.project_root,
|
toplevel = opts.toplevel,
|
||||||
|
path = opts.path,
|
||||||
list_untracked = opts.list_untracked,
|
list_untracked = opts.list_untracked,
|
||||||
list_ignored = opts.list_ignored,
|
list_ignored = opts.list_ignored,
|
||||||
timeout = opts.timeout or 400,
|
timeout = opts.timeout or 400,
|
||||||
@@ -139,20 +203,33 @@ function Runner.run(opts)
|
|||||||
rc = nil, -- -1 indicates timeout
|
rc = nil, -- -1 indicates timeout
|
||||||
}, Runner)
|
}, Runner)
|
||||||
|
|
||||||
|
local async = callback ~= nil
|
||||||
|
local profile = log.profile_start("git %s job %s %s", async and "async" or "sync", opts.toplevel, opts.path)
|
||||||
|
|
||||||
|
if async and callback then
|
||||||
|
-- async, always call back
|
||||||
|
self:_run_git_job(function()
|
||||||
|
log.profile_end(profile)
|
||||||
|
|
||||||
|
self:_finalise(opts)
|
||||||
|
|
||||||
|
callback(self.output)
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
-- sync, maybe call back
|
||||||
self:_run_git_job()
|
self:_run_git_job()
|
||||||
self:_wait()
|
self:_wait()
|
||||||
|
|
||||||
log.profile_end(ps, "git job %s", opts.project_root)
|
log.profile_end(profile)
|
||||||
|
|
||||||
if self.rc == -1 then
|
self:_finalise(opts)
|
||||||
log.line("git", "job timed out")
|
|
||||||
elseif self.rc ~= 0 then
|
if callback then
|
||||||
log.line("git", "job failed with return code %d", self.rc)
|
callback(self.output)
|
||||||
else
|
else
|
||||||
log.line("git", "job success")
|
|
||||||
end
|
|
||||||
|
|
||||||
return self.output
|
return self.output
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return Runner
|
return Runner
|
||||||
|
|||||||
@@ -1,53 +1,135 @@
|
|||||||
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
|
||||||
|
|
||||||
local untracked = {}
|
local untracked = {}
|
||||||
|
|
||||||
|
---@param cwd string
|
||||||
|
---@return string|nil
|
||||||
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
|
||||||
|
|
||||||
|
---@param t table|nil
|
||||||
|
---@param k string
|
||||||
|
---@return table
|
||||||
|
local function nil_insert(t, k)
|
||||||
|
t = t or {}
|
||||||
|
t[k] = true
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param status table
|
||||||
|
---@param cwd string|nil
|
||||||
|
---@return table
|
||||||
function M.file_status_to_dir_status(status, cwd)
|
function M.file_status_to_dir_status(status, cwd)
|
||||||
local dirs = {}
|
local direct = {}
|
||||||
for p, s in pairs(status) do
|
for p, s in pairs(status) do
|
||||||
if s ~= "!!" then
|
if s ~= "!!" then
|
||||||
local modified = vim.fn.fnamemodify(p, ":h")
|
local modified = vim.fn.fnamemodify(p, ":h")
|
||||||
dirs[modified] = s
|
direct[modified] = nil_insert(direct[modified], s)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for dirname, s in pairs(dirs) do
|
local indirect = {}
|
||||||
|
for dirname, statuses in pairs(direct) do
|
||||||
|
for s, _ in pairs(statuses) do
|
||||||
local modified = dirname
|
local modified = dirname
|
||||||
while modified ~= cwd and modified ~= "/" do
|
while modified ~= cwd and modified ~= "/" do
|
||||||
modified = vim.fn.fnamemodify(modified, ":h")
|
modified = vim.fn.fnamemodify(modified, ":h")
|
||||||
dirs[modified] = s
|
indirect[modified] = nil_insert(indirect[modified], s)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return dirs
|
local r = { indirect = indirect, direct = direct }
|
||||||
|
for _, d in pairs(r) 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 r
|
||||||
|
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
|
||||||
|
|||||||
245
lua/nvim-tree/help.lua
Normal file
245
lua/nvim-tree/help.lua
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
local keymap = require "nvim-tree.keymap"
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
---@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()
|
||||||
|
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(map)
|
||||||
|
return { lhs = tidy_lhs(map.lhs), desc = tidy_desc(map.desc) }
|
||||||
|
end, keymap.get_keymap())
|
||||||
|
|
||||||
|
-- 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()
|
||||||
|
|
||||||
|
-- text and highlight
|
||||||
|
local lines, hl, width = compute()
|
||||||
|
|
||||||
|
-- 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
|
||||||
|
|
||||||
|
local keymaps = {
|
||||||
|
q = { fn = close, desc = "nvim-tree: exit help" },
|
||||||
|
s = { fn = toggle_sort, desc = "nvim-tree: toggle sorting method" },
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v in pairs(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
|
||||||
|
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
|
||||||
118
lua/nvim-tree/keymap.lua
Normal file
118
lua/nvim-tree/keymap.lua
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
--- Apply mappings to a scratch buffer and return buffer local mappings
|
||||||
|
---@param fn function(bufnr) 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
|
||||||
|
|
||||||
|
-- stylua: ignore start
|
||||||
|
---@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
|
||||||
|
-- stylua: ignore 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
|
||||||
|
|
||||||
|
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,97 @@
|
|||||||
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
|
|
||||||
opts.actions.open_file.resize_window = opts.view.auto_resize
|
|
||||||
end
|
end
|
||||||
opts.view.auto_resize = nil
|
|
||||||
|
-- 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
|
||||||
|
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,48 +1,148 @@
|
|||||||
local api = vim.api
|
|
||||||
|
|
||||||
local renderer = require "nvim-tree.renderer"
|
local renderer = require "nvim-tree.renderer"
|
||||||
local view = require "nvim-tree.view"
|
local view = require "nvim-tree.view"
|
||||||
local core = require "nvim-tree.core"
|
local core = require "nvim-tree.core"
|
||||||
local utils = require "nvim-tree.utils"
|
local utils = require "nvim-tree.utils"
|
||||||
|
local events = require "nvim-tree.events"
|
||||||
|
local notify = require "nvim-tree.notify"
|
||||||
|
local explorer_node = require "nvim-tree.explorer.node"
|
||||||
|
|
||||||
|
---@class LibOpenOpts
|
||||||
|
---@field path string|nil path
|
||||||
|
---@field current_window boolean|nil default false
|
||||||
|
---@field winid number|nil
|
||||||
|
|
||||||
local M = {
|
local M = {
|
||||||
target_winid = nil,
|
target_winid = nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
---@return Node|nil
|
||||||
function M.get_node_at_cursor()
|
function M.get_node_at_cursor()
|
||||||
if not core.get_explorer() then
|
if not core.get_explorer() then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local winnr = view.get_winnr()
|
local winnr = view.get_winnr()
|
||||||
if not winnr then
|
if not winnr then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local cursor = api.nvim_win_get_cursor(view.get_winnr())
|
|
||||||
|
local cursor = vim.api.nvim_win_get_cursor(winnr)
|
||||||
local line = cursor[1]
|
local line = cursor[1]
|
||||||
if view.is_help_ui() then
|
|
||||||
local help_lines = require("nvim-tree.renderer.help").compute_lines()
|
if line == 1 and view.is_root_folder_visible(core.get_cwd()) then
|
||||||
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 = ".." }
|
return { name = ".." }
|
||||||
end
|
end
|
||||||
|
|
||||||
return utils.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line())[line]
|
return utils.get_nodes_by_line(core.get_explorer().nodes, core.get_nodes_starting_line())[line]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Create a sanitized partial copy of a node, populating children recursively.
|
||||||
|
---@param node Node|nil
|
||||||
|
---@return Node|nil cloned node
|
||||||
|
local function clone_node(node)
|
||||||
|
if not node then
|
||||||
|
node = core.get_explorer()
|
||||||
|
if not node then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local n = {
|
||||||
|
absolute_path = node.absolute_path,
|
||||||
|
executable = node.executable,
|
||||||
|
extension = node.extension,
|
||||||
|
git_status = node.git_status,
|
||||||
|
has_children = node.has_children,
|
||||||
|
hidden = node.hidden,
|
||||||
|
link_to = node.link_to,
|
||||||
|
name = node.name,
|
||||||
|
open = node.open,
|
||||||
|
type = node.type,
|
||||||
|
fs_stat = node.fs_stat,
|
||||||
|
}
|
||||||
|
|
||||||
|
if type(node.nodes) == "table" then
|
||||||
|
n.nodes = {}
|
||||||
|
for _, child in ipairs(node.nodes) do
|
||||||
|
table.insert(n.nodes, clone_node(child))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return n
|
||||||
|
end
|
||||||
|
|
||||||
|
---Api.tree.get_nodes
|
||||||
|
---@return Node[]|nil
|
||||||
|
function M.get_nodes()
|
||||||
|
return clone_node(core.get_explorer())
|
||||||
end
|
end
|
||||||
|
|
||||||
-- If node is grouped, return the last node in the group. Otherwise, return the given node.
|
-- If node is grouped, return the last node in the group. Otherwise, return the given node.
|
||||||
|
---@param node Node
|
||||||
|
---@return Node
|
||||||
function M.get_last_group_node(node)
|
function M.get_last_group_node(node)
|
||||||
local next = node
|
while node and node.group_next do
|
||||||
while next.group_next do
|
node = node.group_next
|
||||||
next = next.group_next
|
|
||||||
end
|
|
||||||
return next
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.expand_or_collapse(node)
|
return node ---@diagnostic disable-line: return-type-mismatch -- it can't be nil
|
||||||
node.open = not node.open
|
end
|
||||||
|
|
||||||
|
---Group empty folders
|
||||||
|
-- Recursively group nodes
|
||||||
|
---@param node Node
|
||||||
|
---@return Node[]
|
||||||
|
function M.group_empty_folders(node)
|
||||||
|
local is_root = not node.parent
|
||||||
|
local child_folder_only = explorer_node.has_one_child_folder(node) and node.nodes[1]
|
||||||
|
if M.group_empty and not is_root and child_folder_only then
|
||||||
|
node.group_next = child_folder_only
|
||||||
|
local ns = M.group_empty_folders(child_folder_only)
|
||||||
|
node.nodes = ns or {}
|
||||||
|
return ns
|
||||||
|
end
|
||||||
|
return node.nodes
|
||||||
|
end
|
||||||
|
|
||||||
|
---Ungroup empty folders
|
||||||
|
-- If a node is grouped, ungroup it: put node.group_next to the node.nodes and set node.group_next to nil
|
||||||
|
---@param node Node
|
||||||
|
function M.ungroup_empty_folders(node)
|
||||||
|
local cur = node
|
||||||
|
while cur and cur.group_next do
|
||||||
|
cur.nodes = { cur.group_next }
|
||||||
|
cur.group_next = nil
|
||||||
|
cur = cur.nodes[1]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
---@return Node[]
|
||||||
|
function M.get_all_nodes_in_group(node)
|
||||||
|
local next_node = utils.get_parent_of_group(node)
|
||||||
|
local nodes = {}
|
||||||
|
while next_node do
|
||||||
|
table.insert(nodes, next_node)
|
||||||
|
next_node = next_node.group_next
|
||||||
|
end
|
||||||
|
return nodes
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Toggle group empty folders
|
||||||
|
---@param head_node Node
|
||||||
|
local function toggle_group_folders(head_node)
|
||||||
|
local is_grouped = head_node.group_next ~= nil
|
||||||
|
|
||||||
|
if is_grouped then
|
||||||
|
M.ungroup_empty_folders(head_node)
|
||||||
|
else
|
||||||
|
M.group_empty_folders(head_node)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
function M.expand_or_collapse(node, toggle_group)
|
||||||
|
toggle_group = toggle_group or false
|
||||||
if node.has_children then
|
if node.has_children then
|
||||||
node.has_children = false
|
node.has_children = false
|
||||||
end
|
end
|
||||||
@@ -51,11 +151,27 @@ function M.expand_or_collapse(node)
|
|||||||
core.get_explorer():expand(node)
|
core.get_explorer():expand(node)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local head_node = utils.get_parent_of_group(node)
|
||||||
|
if toggle_group then
|
||||||
|
toggle_group_folders(head_node)
|
||||||
|
end
|
||||||
|
|
||||||
|
local open = M.get_last_group_node(node).open
|
||||||
|
local next_open
|
||||||
|
if toggle_group then
|
||||||
|
next_open = open
|
||||||
|
else
|
||||||
|
next_open = not open
|
||||||
|
end
|
||||||
|
for _, n in ipairs(M.get_all_nodes_in_group(head_node)) do
|
||||||
|
n.open = next_open
|
||||||
|
end
|
||||||
|
|
||||||
renderer.draw()
|
renderer.draw()
|
||||||
end
|
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 +181,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
|
||||||
|
|
||||||
@@ -80,10 +196,17 @@ local function open_view_and_draw()
|
|||||||
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 +214,76 @@ 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
|
||||||
|
---@param prompt_select string
|
||||||
|
---@param items_short string[]
|
||||||
|
---@param items_long string[]
|
||||||
|
---@param kind string|nil
|
||||||
|
---@param callback fun(item_short: string)
|
||||||
|
function M.prompt(prompt_input, prompt_select, items_short, items_long, kind, callback)
|
||||||
|
local function format_item(short)
|
||||||
|
for i, s in ipairs(items_short) do
|
||||||
|
if short == s then
|
||||||
|
return items_long[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
|
||||||
|
if M.select_prompts then
|
||||||
|
vim.ui.select(items_short, { prompt = prompt_select, kind = kind, format_item = format_item }, function(item_short)
|
||||||
|
callback(item_short)
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
vim.ui.input({ prompt = prompt_input, default = items_short[1] or "" }, function(item_short)
|
||||||
|
if item_short then
|
||||||
|
callback(string.lower(item_short and item_short:sub(1, 1)) or nil)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Open the tree, initialising as needed. Maybe hijack the current buffer.
|
||||||
|
---@param opts LibOpenOpts|nil
|
||||||
|
function M.open(opts)
|
||||||
|
opts = opts or {}
|
||||||
|
|
||||||
M.set_target_win()
|
M.set_target_win()
|
||||||
if not core.get_explorer() or cwd then
|
if not core.get_explorer() or opts.path then
|
||||||
core.init(cwd or vim.loop.cwd())
|
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
|
end
|
||||||
if should_hijack_current_buf() then
|
if should_hijack_current_buf() then
|
||||||
view.open_in_current_win()
|
view.close_this_tab_only()
|
||||||
|
view.open_in_win()
|
||||||
|
renderer.draw()
|
||||||
|
elseif opts.winid then
|
||||||
|
view.open_in_win { hijack_current_buf = false, resize = false, winid = opts.winid }
|
||||||
|
renderer.draw()
|
||||||
|
elseif opts.current_window then
|
||||||
|
view.open_in_win { hijack_current_buf = false, resize = false }
|
||||||
renderer.draw()
|
renderer.draw()
|
||||||
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
|
||||||
|
|||||||
197
lua/nvim-tree/live-filter.lua
Normal file
197
lua/nvim-tree/live-filter.lua
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
local view = require "nvim-tree.view"
|
||||||
|
local utils = require "nvim-tree.utils"
|
||||||
|
local Iterator = require "nvim-tree.iterators.node-iterator"
|
||||||
|
local filters = require "nvim-tree.explorer.filters"
|
||||||
|
|
||||||
|
local M = {
|
||||||
|
filter = nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function redraw()
|
||||||
|
require("nvim-tree.renderer").draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node_ Node|nil
|
||||||
|
local function reset_filter(node_)
|
||||||
|
node_ = node_ or require("nvim-tree.core").get_explorer()
|
||||||
|
|
||||||
|
if node_ == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
Iterator.builder(node_.nodes)
|
||||||
|
:hidden()
|
||||||
|
:applier(function(node)
|
||||||
|
node.hidden = false
|
||||||
|
end)
|
||||||
|
:iterate()
|
||||||
|
end
|
||||||
|
|
||||||
|
local overlay_bufnr = 0
|
||||||
|
local overlay_winnr = 0
|
||||||
|
|
||||||
|
local function remove_overlay()
|
||||||
|
if view.View.float.enable and view.View.float.quit_on_focus_loss then
|
||||||
|
-- return to normal nvim-tree float behaviour when filter window is closed
|
||||||
|
vim.api.nvim_create_autocmd("WinLeave", {
|
||||||
|
pattern = "NvimTree_*",
|
||||||
|
group = vim.api.nvim_create_augroup("NvimTree", { clear = false }),
|
||||||
|
callback = function()
|
||||||
|
if utils.is_nvim_tree_buf(0) then
|
||||||
|
view.close()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.api.nvim_win_close(overlay_winnr, true)
|
||||||
|
vim.api.nvim_buf_delete(overlay_bufnr, { force = true })
|
||||||
|
overlay_bufnr = 0
|
||||||
|
overlay_winnr = 0
|
||||||
|
|
||||||
|
if M.filter == "" then
|
||||||
|
M.clear_filter()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node Node
|
||||||
|
---@return boolean
|
||||||
|
local function matches(node)
|
||||||
|
if not filters.config.enable then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local path = node.absolute_path
|
||||||
|
local name = vim.fn.fnamemodify(path, ":t")
|
||||||
|
return vim.regex(M.filter):match_str(name) ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param node_ Node|nil
|
||||||
|
function M.apply_filter(node_)
|
||||||
|
if not M.filter or M.filter == "" then
|
||||||
|
reset_filter(node_)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO(kiyan): this iterator cannot yet be refactored with the Iterator module
|
||||||
|
-- since the node mapper is based on its children
|
||||||
|
local function iterate(node)
|
||||||
|
local filtered_nodes = 0
|
||||||
|
local nodes = node.group_next and { node.group_next } or node.nodes
|
||||||
|
|
||||||
|
if nodes then
|
||||||
|
for _, n in pairs(nodes) do
|
||||||
|
iterate(n)
|
||||||
|
if n.hidden then
|
||||||
|
filtered_nodes = filtered_nodes + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local has_nodes = nodes and (M.always_show_folders or #nodes > filtered_nodes)
|
||||||
|
local ok, is_match = pcall(matches, node)
|
||||||
|
node.hidden = not (has_nodes or (ok and is_match))
|
||||||
|
end
|
||||||
|
|
||||||
|
iterate(node_ or require("nvim-tree.core").get_explorer())
|
||||||
|
end
|
||||||
|
|
||||||
|
local function record_char()
|
||||||
|
vim.schedule(function()
|
||||||
|
M.filter = vim.api.nvim_buf_get_lines(overlay_bufnr, 0, -1, false)[1]
|
||||||
|
M.apply_filter()
|
||||||
|
redraw()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function configure_buffer_overlay()
|
||||||
|
overlay_bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
|
|
||||||
|
vim.api.nvim_buf_attach(overlay_bufnr, true, {
|
||||||
|
on_lines = record_char,
|
||||||
|
})
|
||||||
|
|
||||||
|
vim.api.nvim_create_autocmd("InsertLeave", {
|
||||||
|
callback = remove_overlay,
|
||||||
|
once = true,
|
||||||
|
})
|
||||||
|
|
||||||
|
vim.api.nvim_buf_set_keymap(overlay_bufnr, "i", "<CR>", "<cmd>stopinsert<CR>", {})
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return integer
|
||||||
|
local function calculate_overlay_win_width()
|
||||||
|
local wininfo = vim.fn.getwininfo(view.get_winnr())[1]
|
||||||
|
|
||||||
|
if wininfo then
|
||||||
|
return wininfo.width - wininfo.textoff - #M.prefix
|
||||||
|
end
|
||||||
|
|
||||||
|
return 20
|
||||||
|
end
|
||||||
|
|
||||||
|
local function create_overlay()
|
||||||
|
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()
|
||||||
|
overlay_winnr = vim.api.nvim_open_win(overlay_bufnr, true, {
|
||||||
|
col = 1,
|
||||||
|
row = 0,
|
||||||
|
relative = "cursor",
|
||||||
|
width = calculate_overlay_win_width(),
|
||||||
|
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, { M.filter })
|
||||||
|
vim.cmd "startinsert"
|
||||||
|
vim.api.nvim_win_set_cursor(overlay_winnr, { 1, #M.filter + 1 })
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.start_filtering()
|
||||||
|
view.View.live_filter.prev_focused_node = require("nvim-tree.lib").get_node_at_cursor()
|
||||||
|
M.filter = M.filter or ""
|
||||||
|
|
||||||
|
redraw()
|
||||||
|
local row = require("nvim-tree.core").get_nodes_starting_line() - 1
|
||||||
|
local col = #M.prefix > 0 and #M.prefix - 1 or 1
|
||||||
|
view.set_cursor { row, col }
|
||||||
|
-- needs scheduling to let the cursor move before initializing the window
|
||||||
|
vim.schedule(create_overlay)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.clear_filter()
|
||||||
|
local node = require("nvim-tree.lib").get_node_at_cursor()
|
||||||
|
local last_node = view.View.live_filter.prev_focused_node
|
||||||
|
|
||||||
|
M.filter = nil
|
||||||
|
reset_filter()
|
||||||
|
redraw()
|
||||||
|
|
||||||
|
if node then
|
||||||
|
utils.focus_file(node.absolute_path)
|
||||||
|
elseif last_node then
|
||||||
|
utils.focus_file(last_node.absolute_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.prefix = opts.live_filter.prefix
|
||||||
|
M.always_show_folders = opts.live_filter.always_show_folders
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
local uv = vim.loop
|
|
||||||
|
|
||||||
local M = {
|
local M = {
|
||||||
config = nil,
|
config = nil,
|
||||||
path = nil,
|
path = nil,
|
||||||
@@ -8,55 +6,96 @@ local M = {
|
|||||||
--- Write to log file
|
--- Write to log file
|
||||||
---@param typ string as per log.types config
|
---@param typ string as per log.types config
|
||||||
---@param fmt string for string.format
|
---@param fmt string for string.format
|
||||||
--- @vararg any arguments for string.format
|
---@param ... any arguments for string.format
|
||||||
function M.raw(typ, fmt, ...)
|
function M.raw(typ, fmt, ...)
|
||||||
if not M.path or not M.config.types[typ] and not M.config.types.all then
|
if not M.enabled(typ) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local line = string.format(fmt, ...)
|
local line = string.format(fmt, ...)
|
||||||
local file = io.open(M.path, "a")
|
local file = io.open(M.path, "a")
|
||||||
|
if file then
|
||||||
io.output(file)
|
io.output(file)
|
||||||
io.write(line)
|
io.write(line)
|
||||||
io.close(file)
|
io.close(file)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--- Write to log file via M.line
|
---@class Profile
|
||||||
|
---@field start number nanos
|
||||||
|
---@field tag string
|
||||||
|
|
||||||
|
--- Write profile start to log file
|
||||||
--- START is prefixed
|
--- START is prefixed
|
||||||
--- @return number nanos to pass to profile_end
|
---@param fmt string for string.format
|
||||||
|
---@param ... any arguments for string.format
|
||||||
|
---@return Profile to pass to profile_end
|
||||||
function M.profile_start(fmt, ...)
|
function M.profile_start(fmt, ...)
|
||||||
if not M.path or not M.config.types.profile and not M.config.types.all then
|
local profile = {}
|
||||||
return
|
if M.enabled "profile" then
|
||||||
|
profile.start = vim.loop.hrtime()
|
||||||
|
profile.tag = string.format((fmt or "???"), ...)
|
||||||
|
M.line("profile", "START %s", profile.tag)
|
||||||
end
|
end
|
||||||
M.line("profile", "START " .. (fmt or "???"), ...)
|
return profile
|
||||||
return uv.hrtime()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Write to log file via M.line
|
--- Write profile end to log file
|
||||||
--- END is prefixed and duration in seconds is suffixed
|
--- END is prefixed and duration in seconds is suffixed
|
||||||
--- @param start number nanos returned from profile_start
|
---@param profile Profile returned from profile_start
|
||||||
function M.profile_end(start, fmt, ...)
|
function M.profile_end(profile)
|
||||||
if not M.path or not M.config.types.profile and not M.config.types.all then
|
if M.enabled "profile" and type(profile) == "table" then
|
||||||
return
|
local millis = profile.start and math.modf((vim.loop.hrtime() - profile.start) / 1000000) or -1
|
||||||
|
M.line("profile", "END %s %dms", profile.tag or "", millis)
|
||||||
end
|
end
|
||||||
local millis = start and math.modf((uv.hrtime() - start) / 1000000) or -1
|
|
||||||
M.line("profile", "END " .. (fmt or "???") .. " " .. millis .. "ms", ...)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Write to log file via M.raw
|
--- Write to log file
|
||||||
-- time and typ are prefixed and a trailing newline is added
|
--- time and typ are prefixed and a trailing newline is added
|
||||||
|
---@param typ string as per log.types config
|
||||||
|
---@param fmt string for string.format
|
||||||
|
---@param ... any arguments for string.format
|
||||||
function M.line(typ, fmt, ...)
|
function M.line(typ, fmt, ...)
|
||||||
M.raw(typ, string.format("[%s] [%s] %s\n", os.date "%Y-%m-%d %H:%M:%S", typ, fmt), ...)
|
if M.enabled(typ) then
|
||||||
|
M.raw(typ, string.format("[%s] [%s] %s\n", os.date "%Y-%m-%d %H:%M:%S", typ, (fmt or "???")), ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local inspect_opts = {}
|
||||||
|
|
||||||
|
---@param opts table
|
||||||
|
function M.set_inspect_opts(opts)
|
||||||
|
inspect_opts = opts
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Write to log file the inspection of a node
|
||||||
|
--- defaults to the node under cursor if none is provided
|
||||||
|
---@param typ string as per log.types config
|
||||||
|
---@param node table|nil node to be inspected
|
||||||
|
---@param fmt string for string.format
|
||||||
|
---@vararg any arguments for string.format
|
||||||
|
function M.node(typ, node, fmt, ...)
|
||||||
|
if M.enabled(typ) then
|
||||||
|
node = node or require("nvim-tree.lib").get_node_at_cursor()
|
||||||
|
M.raw(typ, string.format("[%s] [%s] %s\n%s\n", os.date "%Y-%m-%d %H:%M:%S", typ, (fmt or "???"), vim.inspect(node, inspect_opts)), ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Logging is enabled for typ or all
|
||||||
|
---@param typ string as per log.types config
|
||||||
|
---@return boolean
|
||||||
|
function M.enabled(typ)
|
||||||
|
return M.path ~= nil and (M.config.types[typ] or M.config.types.all)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.setup(opts)
|
function M.setup(opts)
|
||||||
M.config = opts.log
|
M.config = opts.log
|
||||||
if M.config and M.config.enable and M.config.types then
|
if M.config and M.config.enable and M.config.types then
|
||||||
M.path = string.format("%s/nvim-tree.log", vim.fn.stdpath "cache", os.date "%H:%M:%S", vim.env.USER)
|
M.path = string.format("%s/nvim-tree.log", vim.fn.stdpath "log", os.date "%H:%M:%S", vim.env.USER)
|
||||||
if M.config.truncate then
|
if M.config.truncate then
|
||||||
os.remove(M.path)
|
os.remove(M.path)
|
||||||
end
|
end
|
||||||
print("nvim-tree.lua logging to " .. M.path)
|
require("nvim-tree.notify").debug("nvim-tree.lua logging to " .. M.path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
52
lua/nvim-tree/marks/bulk-delete.lua
Normal file
52
lua/nvim-tree/marks/bulk-delete.lua
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
local marks = require "nvim-tree.marks"
|
||||||
|
local utils = require "nvim-tree.utils"
|
||||||
|
local remove_file = require "nvim-tree.actions.fs.remove-file"
|
||||||
|
local notify = require "nvim-tree.notify"
|
||||||
|
local lib = require "nvim-tree.lib"
|
||||||
|
|
||||||
|
local M = {
|
||||||
|
config = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
--- Delete nodes; each removal will be optionally notified
|
||||||
|
---@param nodes Node[]
|
||||||
|
local function do_delete(nodes)
|
||||||
|
for _, node in pairs(nodes) do
|
||||||
|
remove_file.remove(node)
|
||||||
|
end
|
||||||
|
|
||||||
|
marks.clear_marks()
|
||||||
|
|
||||||
|
if not M.config.filesystem_watchers.enable then
|
||||||
|
require("nvim-tree.actions.reloaders").reload_explorer()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Delete marked nodes, optionally prompting
|
||||||
|
function M.bulk_delete()
|
||||||
|
local nodes = marks.get_marks()
|
||||||
|
if not nodes or #nodes == 0 then
|
||||||
|
notify.warn "No bookmarksed to delete."
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if M.config.ui.confirm.remove then
|
||||||
|
local prompt_select = "Remove bookmarked ?"
|
||||||
|
local prompt_input = prompt_select .. " y/N: "
|
||||||
|
lib.prompt(prompt_input, prompt_select, { "", "y" }, { "No", "Yes" }, "nvimtree_bulk_delete", function(item_short)
|
||||||
|
utils.clear_prompt()
|
||||||
|
if item_short == "y" then
|
||||||
|
do_delete(nodes)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
do_delete(nodes)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.config.ui = opts.ui
|
||||||
|
M.config.filesystem_watchers = opts.filesystem_watchers
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
62
lua/nvim-tree/marks/bulk-move.lua
Normal file
62
lua/nvim-tree/marks/bulk-move.lua
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
local marks = require "nvim-tree.marks"
|
||||||
|
local core = require "nvim-tree.core"
|
||||||
|
local utils = require "nvim-tree.utils"
|
||||||
|
local rename_file = require "nvim-tree.actions.fs.rename-file"
|
||||||
|
local notify = require "nvim-tree.notify"
|
||||||
|
local lib = require "nvim-tree.lib"
|
||||||
|
|
||||||
|
local M = {
|
||||||
|
config = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
function M.bulk_move()
|
||||||
|
if #marks.get_marks() == 0 then
|
||||||
|
notify.warn "No bookmarks to move."
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local node_at_cursor = lib.get_node_at_cursor()
|
||||||
|
local default_path = core.get_cwd()
|
||||||
|
|
||||||
|
if node_at_cursor and node_at_cursor.type == "directory" then
|
||||||
|
default_path = node_at_cursor.absolute_path
|
||||||
|
elseif node_at_cursor and node_at_cursor.parent then
|
||||||
|
default_path = node_at_cursor.parent.absolute_path
|
||||||
|
end
|
||||||
|
|
||||||
|
local input_opts = {
|
||||||
|
prompt = "Move to: ",
|
||||||
|
default = default_path,
|
||||||
|
completion = "dir",
|
||||||
|
}
|
||||||
|
|
||||||
|
vim.ui.input(input_opts, function(location)
|
||||||
|
utils.clear_prompt()
|
||||||
|
if not location or location == "" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if vim.fn.filewritable(location) ~= 2 then
|
||||||
|
notify.warn(location .. " is not writable, cannot move.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local nodes = marks.get_marks()
|
||||||
|
for _, node in pairs(nodes) do
|
||||||
|
local head = vim.fn.fnamemodify(node.absolute_path, ":t")
|
||||||
|
local to = utils.path_join { location, head }
|
||||||
|
rename_file.rename(node, to)
|
||||||
|
end
|
||||||
|
|
||||||
|
marks.clear_marks()
|
||||||
|
|
||||||
|
if not M.config.filesystem_watchers.enable then
|
||||||
|
require("nvim-tree.actions.reloaders").reload_explorer()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M.config.filesystem_watchers = opts.filesystem_watchers
|
||||||
|
end
|
||||||
|
|
||||||
|
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