mirror of
https://github.com/folke/lazy.nvim.git
synced 2025-06-27 19:24:13 +00:00
Compare commits
1618 commits
Author | SHA1 | Date | |
---|---|---|---|
|
6c3bda4aca |
||
|
d51cf69783 |
||
|
1c9ba37045 |
||
|
96a205c8ce |
||
|
a8c6db5da7 |
||
|
e5e9bf4821 |
||
|
f81a3fb7fe |
||
|
ac21a639c7 | ||
|
c6a57a3534 |
||
|
f15a93907d |
||
|
7527af40dd | ||
|
5586fda88d |
||
|
4f30c61b64 |
||
|
d8f26efd45 | ||
|
4df5c4d65a |
||
|
72aa3a2624 | ||
|
a9c660d6ef |
||
|
7e6c863bc7 |
||
|
805b85c2ea |
||
|
7c493713bc |
||
|
b97ee167f5 | ||
|
703be1dda3 |
||
|
014d1d6d78 |
||
|
656cf43093 |
||
|
b08dba8107 |
||
|
ee64abc76b |
||
|
3388a26417 | ||
|
a44e9cd165 |
||
|
9570a5ae7b |
||
|
56ead98e05 | ||
|
7d0fe7615a |
||
|
8e11d208d6 | ||
|
25749704e4 |
||
|
e41dffcbaf |
||
|
7967abe557 |
||
|
60cf258a9a |
||
|
b1134ab82e | ||
|
408449a59a |
||
|
cf8ecc2c5e | ||
|
40dab7450e |
||
|
1159bdccd8 | ||
|
e9fd76e239 |
||
|
460e1cd8f2 | ||
|
aca30f6361 |
||
|
48b52b5cfc | ||
|
591ef40f2d |
||
|
80da254e64 |
||
|
014a72b7a8 |
||
|
077102c5bf |
||
|
7108809ab1 |
||
|
4496b4cad6 |
||
|
b4a5a1209e |
||
|
c02268ac6e |
||
|
d5686efbd0 |
||
|
839f9e78e7 |
||
|
7d29719ade |
||
|
cc028e77eb |
||
|
16a5c46aa3 |
||
|
12f2c74244 |
||
|
34b0126e5b |
||
|
a09c876f6e |
||
|
a692bf8688 |
||
|
8bef0742a2 |
||
|
18d1c1b47e |
||
|
9a374a0fb4 |
||
|
5bdb12a038 |
||
|
c92c6b5fd2 | ||
|
124b864233 |
||
|
8f62257511 |
||
|
6ca90a2120 |
||
|
5473e3d77c |
||
|
d731a6b005 |
||
|
9d445ebbd8 |
||
|
315191aa9e |
||
|
b02c9eae6a |
||
|
6e66f8e655 |
||
|
93499c5deb |
||
|
788feaf10e |
||
|
58c6bc4ab2 |
||
|
7ed9f7173c |
||
|
54f70c757c |
||
|
070418dca1 |
||
|
1d451b4c2c |
||
|
17473db1d7 |
||
|
54b003c650 |
||
|
d1de92dffa | ||
|
2cb8af1eb1 |
||
|
159036c576 | ||
|
fadebdc76b |
||
|
1870238cf9 | ||
|
f918318d21 |
||
|
a4c473cc2d | ||
|
f0324defdd |
||
|
0002bfbd9f | ||
|
2dfccd7b94 |
||
|
44cd12fa27 | ||
|
a6daaf68a2 |
||
|
436d09af7d | ||
|
93c9a3f872 | ||
|
72c0dc9462 |
||
|
c771cf4928 | ||
|
25026d2382 | ||
|
083f3dfb5e | ||
|
89b264ac1d |
||
|
23aeb224ed | ||
|
81d2bfffdc |
||
|
933f0b596c | ||
|
55b46b3993 |
||
|
23ea80b6a3 |
||
|
e6035dc59b |
||
|
0ff7e83c17 | ||
|
1e7745a4a0 | ||
|
49a35d3c8c |
||
|
16ccd54360 | ||
|
c060de160a |
||
|
e3154ff0b7 | ||
|
c3a9cec06b |
||
|
d901d2166f | ||
|
94b6b67031 | ||
|
538f060e42 |
||
|
894cd193e9 | ||
|
11e802dbaa | ||
|
6ca23c15f6 |
||
|
40e08f2b8a | ||
|
d0c00e697a |
||
|
61c7156b57 | ||
|
6186b3de3e |
||
|
53661bb38c |
||
|
a1d23e80ba | ||
|
baac551777 |
||
|
407e65c792 |
||
|
851b12034d |
||
|
1225f1dc60 |
||
|
a17ad27435 |
||
|
923e1aa7a4 |
||
|
6fdd904ee4 |
||
|
0f2786bcc9 |
||
|
cea5920abb |
||
|
36c85945ee | ||
|
d0921f5b9b |
||
|
a9d7ade203 |
||
|
1fad61712b |
||
|
c882227f1f |
||
|
2f4ac035bc |
||
|
c7ed87f9ca |
||
|
0507e19289 |
||
|
5e3c112cb3 |
||
|
3772914075 |
||
|
5bddef2415 |
||
|
5d334b9f57 |
||
|
a75d950b8f |
||
|
332a7ff9b3 |
||
|
440999fc5a |
||
|
07ccb49ace |
||
|
09f69bae4b |
||
|
9ab3061690 |
||
|
8dd947fccd |
||
|
3513227a9a |
||
|
00c23e72a3 |
||
|
695a05872a |
||
|
7af8a317e2 | ||
|
cece2a9b4a |
||
|
88f4d13e5f |
||
|
3078688263 |
||
|
cdfea60506 |
||
|
0e106c085c |
||
|
b1821ca2fa |
||
|
f47ab692f1 |
||
|
cfdfb786b1 |
||
|
c93eb359a3 |
||
|
ba1a9c5392 |
||
|
a1fffe18f9 | ||
|
9cf745939d |
||
|
ec95702ae6 |
||
|
2a6a2dce1b |
||
|
a617d9f47b |
||
|
f85575ab23 |
||
|
ab46edbd47 |
||
|
a36ebd2a75 |
||
|
4319846b8c |
||
|
60fe75c88d |
||
|
461552474c |
||
|
37c7163f8d |
||
|
68cee30cdb |
||
|
79bcc02d17 |
||
|
e3e431480d |
||
|
c0fd59b020 |
||
|
82276321f5 |
||
|
e02c5b1b57 |
||
|
a0391c3e21 |
||
|
53f314d9e6 |
||
|
2e1167df4a |
||
|
9c8e7a4840 |
||
|
6a423278a1 |
||
|
24a86d5ca4 |
||
|
591ded8309 |
||
|
249902ab31 |
||
|
a0a51c06c2 |
||
|
93b3a77286 |
||
|
66a4170f0e |
||
|
97f4df0824 |
||
|
804cae0a65 |
||
|
20af3fcc4e |
||
|
56075b57c4 |
||
|
206d208018 |
||
|
6c7ef7e27a |
||
|
765773a176 |
||
|
768de1ebf6 |
||
|
0eb46e7816 |
||
|
9c1dd5a090 |
||
|
bbe136bda6 |
||
|
0614ca6ca6 |
||
|
36952153ec |
||
|
64fd346728 |
||
|
707e2e923b |
||
|
90e14d1585 |
||
|
2aa8e061f2 |
||
|
aa1c9572aa |
||
|
473361139c |
||
|
d63e80bae9 |
||
|
e79805d706 |
||
|
28e435b7f3 |
||
|
6d60dc3c05 | ||
|
67e1e8e6a3 |
||
|
69041bccb7 |
||
|
3f7368c3ac |
||
|
0d9fd636be |
||
|
9ac375653b |
||
|
6b8bf58ebf |
||
|
be74a8a535 |
||
|
25981e1f39 |
||
|
aff7ee8e89 | ||
|
aff65371fc |
||
|
33be7ac317 | ||
|
7cda552c1c |
||
|
49c0b86a6f |
||
|
786a3febc0 | ||
|
88911547e7 |
||
|
8abfed457c |
||
|
bc7bfb5d36 | ||
|
b6eba0d026 |
||
|
2ca68f9979 |
||
|
24c832213c |
||
|
4ca3e9aa51 |
||
|
7d3f69104f |
||
|
9005e8ede7 | ||
|
45cd8d3f0f |
||
|
0a5839ceea | ||
|
d87da76679 |
||
|
dea1f687fe |
||
|
378bfb4656 |
||
|
62a47b921f |
||
|
9bcbbc17a7 |
||
|
a089d43dba |
||
|
1446f6cfbb |
||
|
972baa615b | ||
|
0081c95aee |
||
|
b6b0c4c15c |
||
|
ae4881d36e |
||
|
77edda11bf |
||
|
bd397ff1e3 | ||
|
e3ee51b668 |
||
|
07c067a1a8 |
||
|
105d4805ad |
||
|
656d3d1f5b |
||
|
0f45c0d062 |
||
|
79c2efc8d8 | ||
|
79afa96b90 |
||
|
cd3581efd1 | ||
|
7f52977c1d |
||
|
146de4e801 |
||
|
c33b9fbf8d |
||
|
eb26e95deb | ||
|
9a6c219826 | ||
|
dbffad6f44 | ||
|
fd04bc62f9 | ||
|
b73c57ed9e | ||
|
368747bc9a | ||
|
fcfd54835d | ||
|
b3ee5b96f2 | ||
|
d4c07d062d | ||
|
d498f81b8c | ||
|
23c2851265 | ||
|
502600d3e6 | ||
|
7b6ddbfc13 | ||
|
3515cb518f | ||
|
fd8229d6e3 | ||
|
183f59e2e8 | ||
|
4326d4b487 | ||
|
ee2ca39f67 | ||
|
05e31ad059 | ||
|
d2bea9eefd | ||
|
c1912e2348 | ||
|
75ffe56f70 | ||
|
8d35e60eeb | ||
|
3be55a4615 | ||
|
f1ba2e3d05 | ||
|
8eba74c3fc | ||
|
4ea9fe0600 |
||
|
849890c2c4 | ||
|
6994ee3751 |
||
|
bc62078366 |
||
|
acc0449d83 |
||
|
07269d494e | ||
|
1325119e85 |
||
|
cd6a829343 | ||
|
f4d57485b0 |
||
|
025520d083 |
||
|
c501b429cf |
||
|
b4316da731 |
||
|
38d6b74b63 |
||
|
5da0245f05 | ||
|
be5dfba542 |
||
|
4c6479e98a |
||
|
e2e10d9cbe |
||
|
6944b105cb | ||
|
067fd41933 |
||
|
eb4957442e |
||
|
db5c465509 |
||
|
0aac19ccc7 |
||
|
938e195108 |
||
|
034b03c803 |
||
|
fafe1f7c64 |
||
|
e0bc9a07e4 | ||
|
1f7b720cff |
||
|
89ddc59d19 |
||
|
74fd3611f2 |
||
|
70f2c090d3 |
||
|
ff90417808 |
||
|
0c1ec520af | ||
|
3e4c795cec |
||
|
0fc34a0cf5 | ||
|
784a726f2e |
||
|
ebbf84eb23 | ||
|
f39c79fcb1 |
||
|
b0ba3f9399 |
||
|
9242edb739 |
||
|
1418f30806 | ||
|
58536d4c81 |
||
|
ad30030b6a |
||
|
a3edeae34a | ||
|
6a141a6dbb |
||
|
eab487c252 |
||
|
b77aaa08cb |
||
|
4a8f813d81 |
||
|
52244a0c1d | ||
|
917dfbe2a9 |
||
|
24fa2a9708 |
||
|
a55d275eca | ||
|
9dde1f1bce |
||
|
ea7b9c3c3f |
||
|
98210e2f82 | ||
|
7667a73dee |
||
|
8f19915175 |
||
|
8411fe9467 | ||
|
82cf974e09 |
||
|
9895337d1f | ||
|
39de11a2fa |
||
|
0de782a6b0 | ||
|
56a34a825b |
||
|
5d29ffeaa0 |
||
|
c717ab88ff | ||
|
2e04a0c02c |
||
|
05240b4154 | ||
|
d039aecddb |
||
|
e44636a433 | ||
|
d2a4ce22dc |
||
|
758bb5de98 |
||
|
1651030404 | ||
|
bb0179139a |
||
|
2fcbcaf07a |
||
|
76d321008f | ||
|
40845063a2 |
||
|
d3974346b6 | ||
|
24234f47a2 |
||
|
3f13f08043 | ||
|
481aed70cc |
||
|
31ddbea7c1 |
||
|
7a2617575a | ||
|
bef521ac89 |
||
|
0ccf031227 |
||
|
ba58b87ed9 | ||
|
f61ca6ec70 |
||
|
e753eb6025 |
||
|
e888d5b64c | ||
|
a836600573 |
||
|
65887ea871 |
||
|
b38b2257b6 | ||
|
eefb8974d6 |
||
|
d37a76b871 |
||
|
68941b7b13 |
||
|
79e2e85934 |
||
|
107719d31e |
||
|
6749a259c1 | ||
|
a6b74f30d5 |
||
|
08954f723b | ||
|
eade87fb83 |
||
|
af6afefbb4 |
||
|
66466a2594 |
||
|
8134f2ac04 |
||
|
9131ea4c4a |
||
|
3132d7d27d |
||
|
b6f7ef856b |
||
|
c76cc60030 |
||
|
61dddaec58 |
||
|
b2174810cd |
||
|
6705a0f11f | ||
|
9e157df077 |
||
|
83493db50a |
||
|
a5ac16955e | ||
|
298bed190e |
||
|
d5c58bb193 |
||
|
e1e8d2f0f6 | ||
|
0694651fd3 |
||
|
c96fc24555 |
||
|
5be95fe3b4 | ||
|
5aea4e7021 |
||
|
aedcd79811 | ||
|
670a6fec7f |
||
|
28126922c9 |
||
|
d09084c4b1 |
||
|
a9b9a4b3a2 |
||
|
ababf0dab5 | ||
|
7ded44ce2a |
||
|
17d9c93943 | ||
|
0b507680ee |
||
|
89e6840d8b |
||
|
d0d410bc22 |
||
|
1b3df6c007 |
||
|
3e0c0a05bd | ||
|
a6f782adc1 |
||
|
42694c4fda | ||
|
5757b551fc |
||
|
747bb955c5 | ||
|
47d4baafcc |
||
|
96584866b9 |
||
|
3674036a59 |
||
|
32dead0376 | ||
|
314193af1d |
||
|
16603c6917 |
||
|
f82bac799c | ||
|
312e424a08 |
||
|
e42fccc3cd | ||
|
4446fdb9af |
||
|
42fb1e89ad | ||
|
6b7b4c5b96 |
||
|
9788a19ec0 | ||
|
71007c715f |
||
|
4c75c8eeb9 |
||
|
daab5fe280 |
||
|
c059eece0c |
||
|
03419f3e5f |
||
|
7613ab2abb |
||
|
c7298a10db |
||
|
c373663b49 | ||
|
cdfa788881 |
||
|
f73986546c |
||
|
fb9795e49f |
||
|
3049575bd8 |
||
|
c1b9887373 |
||
|
2f169e74d4 |
||
|
b9c604e839 |
||
|
2270bbbc48 |
||
|
239f0fa9c1 |
||
|
24a93426c4 | ||
|
1ea2eaefa6 |
||
|
1cfd6d1f36 |
||
|
ed6c9ffe21 |
||
|
c5598617da | ||
|
c8e2091e6d |
||
|
3dc413d6fd |
||
|
0c53d4673f |
||
|
1c16e4236f |
||
|
3b31897275 |
||
|
a993bfd6de |
||
|
a27935e0d4 |
||
|
2b8b8b020b | ||
|
ad5da0ae20 |
||
|
276e572f64 |
||
|
8d712c8e5d | ||
|
9f5637f1d7 |
||
|
8a6379eddd | ||
|
a026f73953 |
||
|
e15dfab3c3 | ||
|
70f764bf73 |
||
|
3769461194 |
||
|
33c447b96e |
||
|
7f70dd1749 |
||
|
117556d9e7 |
||
|
84ae36f30d | ||
|
43c284a578 |
||
|
906ff8e569 |
||
|
9ca9a63be5 | ||
|
99ee284739 |
||
|
b65d308662 |
||
|
303a3ed6a8 |
||
|
73fbf5ccab |
||
|
2947f104e1 | ||
|
7b84609a06 |
||
|
cb3a0555b9 |
||
|
1a47d3b2aa | ||
|
a617db7e79 |
||
|
84d13c1d7e |
||
|
58e5726592 |
||
|
92869d0928 |
||
|
736529d097 |
||
|
43e9165994 |
||
|
067544c74a | ||
|
89581ce37e |
||
|
0d9989d46c |
||
|
2782f8125e |
||
|
5579d72576 |
||
|
41d3b2a9db |
||
|
336bbbebcb | ||
|
423a152e94 |
||
|
ce3e8523de | ||
|
22bf6ae04b |
||
|
f0cfbf9952 |
||
|
0a07fa6cd7 |
||
|
9d92e65fd1 |
||
|
9102d051a8 |
||
|
26762c97e6 |
||
|
4b5b03f603 |
||
|
cd7493da78 | ||
|
b79099cc9d |
||
|
62745a7320 |
||
|
09e30f88cd |
||
|
5aaafcb301 |
||
|
8871602e54 |
||
|
f2132946c7 | ||
|
e4ea874e33 |
||
|
6687afae42 |
||
|
6b37927be9 |
||
|
25f6009087 |
||
|
2b2adb9d4d |
||
|
58e954a735 |
||
|
acda8d29ca | ||
|
ef2a5d0bd1 |
||
|
6b6f0a4512 |
||
|
d9509b56ce | ||
|
c42e63c198 |
||
|
19d1b3aa72 |
||
|
64cb9b16f6 |
||
|
82da5a0048 |
||
|
d8453bc137 | ||
|
638c8e6382 |
||
|
28a80136b5 |
||
|
f4ed421453 | ||
|
8eb8de29af |
||
|
8b73492249 |
||
|
59335c5b9d |
||
|
1605df75ad | ||
|
09e5010741 |
||
|
6a2c47e642 |
||
|
753c391b44 |
||
|
7bf6a27458 | ||
|
f3c7169dd6 |
||
|
6b55e4695a | ||
|
54ecfc7c24 |
||
|
4f27fc33c4 |
||
|
7ca3bdb566 |
||
|
0e1d264ab6 |
||
|
cc24fc0e85 |
||
|
fe9f93692e | ||
|
24f6b6f1c7 |
||
|
2a9354c7d2 | ||
|
d179a17fa1 |
||
|
0e9c3934ab | ||
|
4eb3e932e5 |
||
|
dac844ed61 | ||
|
a977a7ab21 |
||
|
3ad55ae678 |
||
|
e7334d8db5 |
||
|
af4d24b8d0 |
||
|
54a2aa4db4 | ||
|
e428c5ee4b |
||
|
ed15f6b394 | ||
|
b7303a6830 |
||
|
50c365bfe9 | ||
|
61c43455b0 |
||
|
84266b9f0f |
||
|
25beed5e2e | ||
|
2b241f74ed |
||
|
14d76aac4b |
||
|
f6aedf1269 | ||
|
ea5b2e00bf |
||
|
da8b00581a |
||
|
8ae6cb4b44 | ||
|
fd94e69ceb |
||
|
5af331ea65 |
||
|
867528c0fe |
||
|
5082cd56e4 |
||
|
29f8e3ecb7 |
||
|
c27cd36e6c | ||
|
045f23eb35 |
||
|
fbb0bea2db |
||
|
f8611632d0 | ||
|
0d61488b89 |
||
|
d65d5441d9 | ||
|
189371c8d8 |
||
|
de0a911ad9 |
||
|
d54ae0b96d | ||
|
2ea3c54b5f |
||
|
4c26421785 |
||
|
2772cc587e | ||
|
d5c31f1ed7 |
||
|
4c8b625bc8 | ||
|
24803fcbe3 |
||
|
b7043f2983 |
||
|
d7d5842d1c |
||
|
3867a81bb2 |
||
|
ccd96bfa2e | ||
|
6c42a305b7 |
||
|
c1aad95243 |
||
|
bf11e4907b | ||
|
410a7360c1 |
||
|
56b1f7715e |
||
|
3b0632977e | ||
|
616341372d |
||
|
67af46a7f5 |
||
|
15cdefee46 | ||
|
0bca18de5d |
||
|
10d4371745 | ||
|
7f4da7d511 |
||
|
678179543e | ||
|
6b2311a46a |
||
|
d1b02c2dda | ||
|
f131606190 |
||
|
ae25448d39 |
||
|
27c12ff0a9 | ||
|
bc89502357 |
||
|
f145e6f42a |
||
|
dbb2b609f6 |
||
|
6ad76ecc26 |
||
|
d65a3d6755 |
||
|
9223c1aa20 |
||
|
33448b968b | ||
|
94472b8303 |
||
|
e6bf3a0d9c | ||
|
beb5ff7f3f |
||
|
ebdd049955 |
||
|
36a91320f9 |
||
|
b4e520579b | ||
|
fe9fcdb0b9 |
||
|
a93d8983c4 |
||
|
efa02ff8d3 |
||
|
a6c8f22362 |
||
|
d8a5829fda |
||
|
7f34cb892b |
||
|
ae7de6b56b | ||
|
9dce0816f1 |
||
|
97c2f8858c |
||
|
41b64eaca2 | ||
|
42ff6009f6 |
||
|
199e100464 |
||
|
67ae8bbbe3 |
||
|
c325c50ba4 |
||
|
721e37b4dd | ||
|
32170a8891 |
||
|
57062f3a09 |
||
|
49a7f21ee3 |
||
|
761b8388af |
||
|
72c3592e04 | ||
|
b0aa5348d8 |
||
|
b1ad64a73e | ||
|
f125a7d333 |
||
|
6610b15dfd |
||
|
9177778891 |
||
|
8cd4a59674 |
||
|
af39d61d3f |
||
|
cd3802a54b | ||
|
8564f6d22b |
||
|
b382495d51 | ||
|
451f217e9b |
||
|
5f316cea4f | ||
|
cc7a764aec |
||
|
91564cb0a6 | ||
|
c711b4f8a6 |
||
|
6d4135d83d | ||
|
11131eafa1 |
||
|
53e1c49bae |
||
|
a4e49029b9 | ||
|
aecdaab6a6 |
||
|
80c4decc32 |
||
|
aba872ec78 |
||
|
dab6cd5080 |
||
|
e9b3b83914 |
||
|
3fef769786 | ||
|
96dd2058fb |
||
|
652b6febf8 | ||
|
d2d67b5a0b |
||
|
9ac19880b6 | ||
|
51a3873ccd |
||
|
83a625f5c8 | ||
|
dbe0e29d85 |
||
|
ceb413678d |
||
|
98ba47efed |
||
|
5b7b8eecea |
||
|
5a470ae558 | ||
|
b00d6f7102 |
||
|
bb5cc9ef3b | ||
|
d6a782c700 |
||
|
5d3d5fb31d | ||
|
0c7b41872e |
||
|
903f0fe542 |
||
|
24e8fb0f8d | ||
|
a758588008 |
||
|
a55ab60625 |
||
|
f66e68c00e | ||
|
0cbf466913 |
||
|
fe28f4b73e | ||
|
dff6f07290 |
||
|
5c89dc52f4 |
||
|
349af24861 | ||
|
78b981b1f3 |
||
|
5f13f69851 |
||
|
a7f18f0192 | ||
|
b582fc5545 |
||
|
eddee830ed |
||
|
c8cad54895 |
||
|
86c1a767f0 | ||
|
3c14a0a279 |
||
|
b7a1a0fbaf |
||
|
57cce98dfd |
||
|
37c0aef4e3 | ||
|
9f9d733df9 |
||
|
32d5f4af2f |
||
|
983aad3e79 | ||
|
db5b67e75c |
||
|
a80422f217 | ||
|
be77c59bf7 |
||
|
0e230caab9 |
||
|
4cdc8aa8db | ||
|
261c2d6f95 |
||
|
690f9e88e2 |
||
|
887eb75591 |
||
|
e7622b78f6 |
||
|
c9aeaae699 | ||
|
236f8517ba |
||
|
b306eb3d0f | ||
|
29b3bf9535 |
||
|
8456a867f7 | ||
|
67cc8dc07c |
||
|
f4d53dc18a | ||
|
37541e57e4 |
||
|
7d81994529 | ||
|
959f8c36bc |
||
|
fdb41229ca |
||
|
357893a288 | ||
|
e5759d202a |
||
|
b3eca0c3fb | ||
|
e2102c3a3a |
||
|
aacaa600cf | ||
|
efe36bdfda |
||
|
b4b11f48aa |
||
|
2e3c16e526 |
||
|
6b55862d2d |
||
|
e27c9df5fe |
||
|
6014f8de31 | ||
|
21a219a825 |
||
|
942c805b84 |
||
|
5b7b8c5149 |
||
|
ea1a044e3c |
||
|
4446d69c28 |
||
|
2a88a73ace |
||
|
810acc1e86 |
||
|
da295017e4 |
||
|
0b7596f609 |
||
|
d0db9f83ef |
||
|
49dda8751e |
||
|
1a34636094 |
||
|
5550f99271 |
||
|
51c07f734f | ||
|
9afba388fa |
||
|
10f5844abf |
||
|
623e00cabb |
||
|
8279309934 | ||
|
8d73b9bccd |
||
|
5b4444f0d7 |
||
|
3efcb149cf | ||
|
355312eb51 |
||
|
e89acede13 | ||
|
47fc27e3f1 |
||
|
f9ddb2d41b | ||
|
53be2c0ee1 |
||
|
1407565713 |
||
|
b5667ec94d | ||
|
c60f7ea985 |
||
|
8d18ef44e7 |
||
|
2691c8f7a5 | ||
|
1f7ffec177 |
||
|
26d121ea13 |
||
|
79f85e5fed |
||
|
77223786aa |
||
|
5af93806aa |
||
|
0f713b2958 |
||
|
25132fc08f | ||
|
c7a8e702f7 |
||
|
9bd1c946d6 |
||
|
b814d87089 |
||
|
5694483e87 |
||
|
8cbd95bd05 | ||
|
1657ae9b8c |
||
|
8077428e63 |
||
|
8d5553d11b | ||
|
43496fa82c |
||
|
7339145a22 |
||
|
f2cc9ef6ac |
||
|
ff76e58961 |
||
|
e5ba443416 | ||
|
5cfe1560c5 |
||
|
8186cc5db3 |
||
|
258ee6b30d | ||
|
6771c7e23c |
||
|
e916f41df2 |
||
|
4a0857cc23 |
||
|
8f752bb288 | ||
|
9ca3222061 |
||
|
78264fb935 |
||
|
c249ea376b |
||
|
6a18404b7d |
||
|
8aa90c3423 | ||
|
17a3c3acea |
||
|
6351a2e8f3 |
||
|
b1f7ae68a7 |
||
|
c778b7aa04 |
||
|
27fdb9b468 | ||
|
462633bae1 |
||
|
06f835d0b4 | ||
|
0d3f2c4042 |
||
|
4917222c7e |
||
|
b5eb07e728 | ||
|
bc978ca9be |
||
|
bab744565e | ||
|
c83d2aeb27 |
||
|
e28555950f |
||
|
a147110390 |
||
|
2730814434 | ||
|
d13fe93535 |
||
|
c734d941b4 |
||
|
ddaffa0715 |
||
|
382c8fac56 | ||
|
2451ea4e65 |
||
|
4272d2100a |
||
|
57a3960faf |
||
|
49b43def14 |
||
|
48c9b37294 |
||
|
3c29f196f4 |
||
|
0dcc9071df | ||
|
0e4017152d |
||
|
326556008a | ||
|
812ffcf21c |
||
|
2e8f6e6e8f | ||
|
452d4eb719 |
||
|
3d2dcb2d5e |
||
|
527f83cae5 |
||
|
c83563d34a | ||
|
0c980312fd |
||
|
15fe46a728 | ||
|
07fda7bb98 |
||
|
b64ebb71d4 | ||
|
9b208696e1 |
||
|
a24331762c | ||
|
31dd419aaa |
||
|
9b5cc1bf53 |
||
|
39c4770d81 | ||
|
772d8888cc |
||
|
809d67fcf0 |
||
|
9e0b8c399f | ||
|
5d9d35404f |
||
|
26a67e3c48 |
||
|
c32a6185ac |
||
|
ed210702f5 |
||
|
cab4682d22 | ||
|
908d71872b |
||
|
21871f2269 |
||
|
7674ee6254 |
||
|
fc3104c44b | ||
|
4008b57d88 |
||
|
96d759d1cb | ||
|
9858001c3c |
||
|
75dcd5741d | ||
|
3f8cc2c0df |
||
|
4739c2d95a | ||
|
d6fc848067 |
||
|
c389ad552b |
||
|
813fc944d7 |
||
|
835731f102 | ||
|
c3a0d444f6 |
||
|
27ca918bc3 |
||
|
4f60facf18 |
||
|
c791c0ed7d |
||
|
1b2a6f631c |
||
|
da4e8cc245 |
||
|
baaf8ddfff |
||
|
f95d97a91c | ||
|
bd37afc96e |
||
|
c05d61d208 |
||
|
870af80c68 |
||
|
208f91b52f |
||
|
0cbd91d2cd |
||
|
efe72d98e6 |
||
|
e8cb863703 |
||
|
1e67dc0d56 |
||
|
02482b1ec9 | ||
|
fdf0332fe1 |
||
|
984008f7ae |
||
|
7160ca419e |
||
|
9375f68dcd | ||
|
1b219c1704 |
||
|
6ac67d46dc | ||
|
891cdfacde |
||
|
666ed7bf73 |
||
|
0d0d5870a3 |
||
|
7b78ce3332 |
||
|
e2556e38c7 | ||
|
7b0d1a7866 |
||
|
b73312aa32 |
||
|
81017b99e7 |
||
|
70e5e08dc1 | ||
|
f3695bc5be |
||
|
3f3d9df324 |
||
|
784bb3c100 |
||
|
2128ca90fb |
||
|
bcd87a0215 | ||
|
3fbe4fe27a |
||
|
0741d62319 |
||
|
07fd7adb34 |
||
|
e1cd9cd0ad |
||
|
9d494e0594 |
||
|
a64623899d |
||
|
41c7a6a6c0 | ||
|
74bc61ab97 |
||
|
3b44c3c14a |
||
|
5aca9280df |
||
|
8756c0950c |
||
|
d2110278be |
||
|
5faadf6398 |
||
|
18eb724dff |
||
|
70b07ebad5 |
||
|
ad7aafb257 |
||
|
9fa62ea8ea |
||
|
d3a963d51c |
||
|
1f17bb79b7 | ||
|
b28c6b9000 |
||
|
e6ee0fa610 |
||
|
4304035ef4 |
||
|
d813c518d5 |
||
|
1ca3f101c8 | ||
|
d5345910a7 |
||
|
55e86f855a | ||
|
3a216d008d |
||
|
a32e307981 |
||
|
e77be3cf3b |
||
|
ef87c24e8e |
||
|
2b1fccb817 | ||
|
7260a2b28b |
||
|
6a31b97e37 |
||
|
b178daf9dc | ||
|
615781aebf |
||
|
a39fa0f0ce |
||
|
8a37547171 |
||
|
ad00eb1be2 |
||
|
d34c85d580 |
||
|
8798ccc950 | ||
|
735f2a280e |
||
|
a1c926f87b | ||
|
ae12dd9686 |
||
|
c313249271 |
||
|
5ed89b5a0d |
||
|
457e65eec8 | ||
|
05b55deb16 |
||
|
7eadaacc48 |
||
|
102bc2722e |
||
|
4f76b431f7 |
||
|
317df42fcf | ||
|
eed1ef3c2d |
||
|
2ef44e2dee |
||
|
e89e938991 |
||
|
13af39b83e |
||
|
d3b0d3e851 |
||
|
caf3897841 | ||
|
aadc912694 |
||
|
b2dec14824 |
||
|
af351f69ba | ||
|
e4f79a42d6 |
||
|
847ef091fa |
||
|
c59c05c7a8 |
||
|
457f0bb7ce |
||
|
886a91c688 |
||
|
dea43afc4a |
||
|
3bde7b5ba8 |
||
|
d5e6bdf90c |
||
|
a7ac2ad020 |
||
|
09fd8fabd2 |
||
|
81cb352fe6 |
||
|
bce0c6e327 |
||
|
6d46a3028d |
||
|
b4d4e6b41b |
||
|
2e87520826 |
||
|
e61b334cee |
||
|
62214df989 | ||
|
507b695753 |
||
|
a11a317a92 |
||
|
588caef4e9 |
||
|
b2673f1057 |
||
|
1c854d7a6d |
||
|
c85f929bd9 |
||
|
1fd80159d0 |
||
|
953c2791d8 |
||
|
f36c7cb0dc |
||
|
7b9fa284f8 |
||
|
e93f50fd1b |
||
|
2e3e65b0f7 |
||
|
cdb998c6fe |
||
|
299ffdfd53 |
||
|
05aec48968 |
||
|
dc9c92a9b3 |
||
|
3ad6d95a30 | ||
|
ff1e322b4f |
||
|
e749e68b68 |
||
|
b5f4106892 |
||
|
06db1ec3c6 |
||
|
277a2ab10b |
||
|
85173dcc4d |
||
|
2ea0751a23 | ||
|
e039ff6e28 |
||
|
ddcdc5e447 |
||
|
c1a50a7fc5 |
||
|
ce3e1fc560 |
||
|
9893430187 |
||
|
60e96b478a |
||
|
9e90852a47 |
||
|
1ec8f08480 |
||
|
8b6a98ad26 | ||
|
69121c7721 |
||
|
8063523471 |
||
|
0bc73db503 |
||
|
f6b0172e92 |
||
|
bc4133cb3e |
||
|
d992387912 |
||
|
46997de1c9 |
||
|
50a456c189 |
||
|
2d06faa941 |
||
|
919b7f5de3 |
||
|
313015fdb4 |
||
|
7d755987ba |
||
|
77ff7beaa4 |
||
|
4d77cf2efe |
||
|
6ff480bdee |
||
|
39b66027a5 |
||
|
3974a6cbe3 |
||
|
51c23b661e |
||
|
963e6f72a7 | ||
|
d521a25cfc |
||
|
11eee43c7e |
||
|
b23a5dc8d5 |
||
|
1edd1b8945 |
||
|
0a0f1b95e9 | ||
|
23c0587791 |
||
|
205ce42cdc |
||
|
0fadb5e1ce |
||
|
34e2c78e06 |
||
|
a9de5910f2 |
||
|
3711176164 |
||
|
d6b5d6e756 |
||
|
ed0583e82b |
||
|
3da6c74ed3 | ||
|
b4edbd09f5 |
||
|
cba99de3eb |
||
|
cb29427926 |
||
|
865ff414c7 |
||
|
f656706de0 | ||
|
5575d2b2a9 |
||
|
d02a10d832 | ||
|
d1ed91d8c7 |
||
|
1de5c3d059 | ||
|
2c632e849f |
||
|
fb46cb5862 |
||
|
45d669f61c |
||
|
a834b30c70 |
||
|
a59cd089dc | ||
|
679d85c9ff |
||
|
730bb84364 |
||
|
887c602957 | ||
|
51fb95e4a8 |
||
|
8d452e37bb | ||
|
de82a99197 |
||
|
a2f5c515de |
||
|
32511a1214 |
||
|
def5cc5816 |
||
|
d50eab2164 | ||
|
86dff1b59a |
||
|
716b6cc2b5 | ||
|
2a617a7024 |
||
|
d205cd45d0 |
||
|
8baedf2cd2 | ||
|
81ee02b8f6 |
||
|
1283c2b288 |
||
|
4b29f7e3d5 |
||
|
60ccfe3e27 | ||
|
291dd6a74b |
||
|
0063e439a3 | ||
|
c30c89bc09 |
||
|
5a78451c30 |
||
|
929198bc4f |
||
|
1682edc505 |
||
|
5d9c528771 |
||
|
67cb4e74c8 |
||
|
563519eee7 |
||
|
e6c24914e8 |
||
|
a4bd4dc4a7 |
||
|
68ee0cbe2d |
||
|
0c386bbea2 |
||
|
9f3fb38402 |
||
|
717cd2f3d6 |
||
|
853d4d5838 |
||
|
8544c389ab |
||
|
c7c1295c3e |
||
|
a2eac68575 |
||
|
e2925748df |
||
|
7de662d037 |
||
|
db043da829 |
||
|
3a1a10cd75 |
||
|
c7122d64cd |
||
|
044e28bf8b |
||
|
b8c5ab5dae |
||
|
4e3a973f85 |
||
|
72ab3e2933 | ||
|
15b5b94422 |
||
|
e3ffcff7cc |
||
|
95b9cf743c |
||
|
55335138ce | ||
|
4b75d06c07 |
||
|
6c0b803999 |
||
|
c8553ca44f |
||
|
9997523841 |
||
|
370b1b982e |
||
|
c2f7e2d098 |
||
|
34977c2b80 |
||
|
956164d27d |
||
|
5f423b29c6 |
||
|
021e54655f |
||
|
b813fae61c |
||
|
acd6697d88 |
||
|
0b1e083937 | ||
|
ff8f3783fa |
||
|
dc03fa1ae5 |
||
|
9ab61b3479 |
||
|
0d0d11acb2 |
||
|
3b46160c01 |
||
|
7d20364dba | ||
|
edf8310288 |
||
|
eec0485d45 | ||
|
81943f32d9 |
||
|
5a5487b015 | ||
|
a2fdf369f2 |
||
|
d1739cb7e1 |
||
|
e183601763 | ||
|
5618076a45 |
||
|
b1e1b337a6 |
||
|
0f3782c066 | ||
|
38a9541939 |
||
|
8a3152de93 |
||
|
b5b2ab6b6c | ||
|
96e82986ee |
||
|
b6ebed5888 |
||
|
de383740a2 | ||
|
3a67d2ad25 |
||
|
3c24c506ce |
||
|
c4d924acee |
||
|
511524ebff | ||
|
7ffbead674 |
||
|
4e4493b21d |
||
|
bb53b8473c |
||
|
5697098e97 |
||
|
46280a191b |
||
|
9a2ecc8750 |
||
|
1d3da277da |
||
|
e6de63bc9c | ||
|
0ea771bd70 |
||
|
3d22c496da | ||
|
5f017bf655 |
||
|
aed842ae1e |
||
|
2f5c1be525 |
||
|
6c5af82589 | ||
|
4aa362e8dc |
||
|
9837d5be7e |
||
|
7421e70c08 | ||
|
1dca6edd89 |
||
|
bdd7f0585a | ||
|
3efb849d73 |
||
|
90952239d2 |
||
|
e632eb4ae0 |
||
|
037f242430 |
||
|
ed8259b7c1 | ||
|
db14d4b2a1 |
||
|
8ea9d8b024 |
||
|
434b7d3edd | ||
|
2fabc0958d |
||
|
f22dfd4a44 |
||
|
9f2fb24b10 | ||
|
a36d506393 |
||
|
3352fc6265 |
||
|
b40ec94523 | ||
|
be3909c544 |
||
|
7d02da2ff0 |
||
|
8ad05feef1 |
||
|
7c2eb15444 |
||
|
b34e25873a |
||
|
fc182f7c5d |
||
|
74d8b8e4e1 |
||
|
86f2c67aa8 |
||
|
9110371120 |
||
|
593d6e400b |
||
|
a973c2edc2 | ||
|
83270cc5e5 |
||
|
c1f39f997d | ||
|
011125ccc2 |
||
|
f822a2c12b | ||
|
3991cf56f8 |
||
|
aec3baa555 | ||
|
74b076e030 |
||
|
5a1812a633 |
||
|
a062641c85 |
||
|
2bd563ce99 |
||
|
60fdea7aed |
||
|
68db6337bf |
||
|
451de7e19e |
||
|
5ef7ce80a3 |
||
|
a5d402abed |
||
|
6a6d6f73ee |
||
|
f360e336a5 |
||
|
4cf1f7b26e | ||
|
d6304f0b42 |
||
|
7f10386e23 |
||
|
50e3b91767 |
||
|
e973323e95 |
||
|
3594bfd47d |
||
|
7dfb9c1f5c |
||
|
fde5feea6d |
||
|
cd023dc709 |
||
|
e2bbf3deef |
||
|
65e903652b |
||
|
040aeb68f1 | ||
|
c8da1c19e7 |
||
|
8bd8e6c98a | ||
|
cc6276e9b0 |
||
|
004b11d5ad |
||
|
8251c23c90 |
||
|
7b9b476a62 |
||
|
e5dcc87149 | ||
|
81126403a8 |
||
|
6f9845e2f8 | ||
|
2a7b0047dd |
||
|
9e983898b1 |
||
|
3f60f2dc13 |
||
|
b5d6afc4fa |
||
|
037973c6cb | ||
|
b440b3ac2d |
||
|
9e869a409c |
||
|
72b38999bc |
||
|
08d458c5ba |
||
|
455a1f485b | ||
|
2756a6f756 |
||
|
fb8287c73d | ||
|
4b4ccdb16a |
||
|
56890ce5f4 |
||
|
42f5aa76e2 |
||
|
4bf771a6b2 |
||
|
eb01b6dc0b | ||
|
07b467738d |
||
|
210d1703ec |
||
|
3415a61789 |
||
|
c0c2e1bd68 |
||
|
a8fe63e4d5 | ||
|
1c07ea15a3 |
||
|
28f1511e0a |
||
|
2200284165 |
||
|
63042310f4 |
||
|
dd9648f8ec |
||
|
63418e8932 | ||
|
488b48779c |
||
|
dc150df456 |
||
|
4cf176bdab |
||
|
7f6f31d66f |
||
|
a39d37b51a | ||
|
2e14a2f324 |
||
|
bc617474a0 |
||
|
fd1fbefc3d |
||
|
58f0876e81 |
||
|
94d012511d |
||
|
6dbcdcec83 | ||
|
1fb10b987b |
||
|
472a091b77 |
||
|
f0e1b853a0 |
||
|
2ab651864f |
||
|
d34a02d7b2 |
||
|
2fd78fbed8 |
||
|
eab449b9e5 |
||
|
b7c489b08f |
||
|
7a57ea28d3 | ||
|
c228908ffc |
||
|
b68f94b95a |
||
|
bbebb67934 |
||
|
95fc814818 | ||
|
876f7bd471 |
||
|
a345649510 |
||
|
bbace14dc9 |
||
|
a939243639 |
||
|
57bea32e4f | ||
|
ff24f493ee |
||
|
b802729bf6 | ||
|
9dfeface3f |
||
|
71b2e2ff7d |
||
|
232232da5a |
||
|
4ca30390ec |
||
|
540847b7cb |
||
|
86eaa118c6 |
||
|
e95da35d09 |
||
|
3c3a711dda | ||
|
44f80a7f5d |
||
|
f5734f512f | ||
|
3814883aaa |
||
|
3a7b8c8132 |
||
|
3606d62918 |
||
|
b193f96f7b |
||
|
7be46bceef |
||
|
897d6df5ac |
||
|
14300b3d04 | ||
|
78e9d6c01d |
||
|
06ac8bda66 |
||
|
ffcd0ab7bb |
||
|
9d12cdcc06 |
||
|
06ffcf5874 | ||
|
7fb0652dba |
||
|
1f86cb37d1 | ||
|
3cffb2acef |
||
|
6c767a604d |
||
|
cd162f342a | ||
|
941df31a41 |
||
|
52984419ff |
||
|
2927b0597e |
||
|
4d782030c8 |
||
|
1371a14167 |
||
|
ffabe91b2d |
||
|
316503f124 |
||
|
992c6791ef |
||
|
df6c9863dc |
||
|
49b69b7f9f | ||
|
e9d3a73bbc |
||
|
423432254d | ||
|
6e32759c5d |
||
|
ec0f8d0947 |
||
|
6404d42155 |
||
|
ddf36d7748 |
||
|
50ba619d9e |
||
|
f78d8bf376 |
||
|
b8a005510c | ||
|
1754056475 |
||
|
5ecc988610 |
||
|
ae644a604d |
||
|
7225b055f5 | ||
|
17fd57a5f3 |
||
|
48a596e1d4 | ||
|
dfe8a65d7d |
||
|
1fa2d87ae2 |
||
|
9916318522 | ||
|
abe026a5ae |
||
|
82aea47c35 | ||
|
72d66cdd73 |
||
|
ca430184b3 | ||
|
36cb7ea597 |
||
|
2f59ead574 |
||
|
dbcf675f87 | ||
|
b906ad959c |
||
|
cb87aa3893 |
||
|
75a36f3c94 | ||
|
af87108605 |
||
|
62c1542141 |
||
|
bb1c2f4c3e |
||
|
198963fdab |
||
|
a18988372f |
||
|
833b387213 | ||
|
ff893190f2 |
||
|
b7bf18abd3 |
||
|
66dad89f58 | ||
|
0c0b8b7231 |
||
|
92fd0d43a0 |
||
|
1baa92f8ca |
||
|
d827d8a402 |
||
|
1e163632e4 | ||
|
980cfa95f3 |
||
|
713dcb6901 |
||
|
706fe6f9c5 | ||
|
5ed9855d1c |
||
|
b46278751f |
||
|
f29f3d2157 | ||
|
1efa710210 |
||
|
55d194cf9c | ||
|
bac34cc6b6 |
||
|
c065ca2b32 | ||
|
2dd6230018 |
||
|
8a0da3b27e |
||
|
7eb60345d6 | ||
|
65675808d1 |
||
|
6f00cdedee |
||
|
faac2dd11c |
||
|
32f2b71ff8 |
||
|
1fe43f3e29 |
||
|
d4aee2715f |
||
|
b89e6bffd2 |
||
|
48309ddf09 |
||
|
c87673c4b9 |
||
|
b88b7d75eb | ||
|
e42a18099a |
||
|
2526a011e5 | ||
|
968fa3fe20 |
||
|
5fc87f924a |
||
|
cd3d87cc82 | ||
|
d0651e4782 |
||
|
39f629eedf | ||
|
628d421c27 |
||
|
c88ad91e31 |
||
|
78b284c065 |
||
|
c0d3617e0b |
||
|
0f62ec0ad6 | ||
|
b70bb1955b |
||
|
db469ed275 | ||
|
671b163dd7 |
||
|
1730661ec2 |
||
|
f25f942eb7 |
||
|
0b4a04de7d |
||
|
a46c0c04f1 |
||
|
9026a0e25d |
||
|
85e375223f |
||
|
5c0c381b56 |
||
|
eeb06a5a50 |
||
|
53affcaaf4 | ||
|
f18efa1da1 |
||
|
6f728e698d |
||
|
97366711be |
||
|
23984dd1f3 |
||
|
2d6559302e |
||
|
fc55edc22e |
||
|
dcae07d01e | ||
|
c5d092ed9e |
||
|
0df47d91d1 |
||
|
060cf23aca |
||
|
e897524b1f |
||
|
f23a6eef8c |
||
|
ecf03a6892 |
||
|
17d1653b4a |
||
|
b8fa6f960f |
||
|
7134417e89 |
||
|
6ca03dcd1a | ||
|
57dfb05e2f |
||
|
9f0a02fb0f | ||
|
fd600be2e5 |
||
|
4dfab59c87 |
||
|
70ca110ca1 |
||
|
27178b5e67 |
||
|
a2f0637c77 | ||
|
aa2f982d22 |
||
|
1f70834e1c | ||
|
c812daf9ee |
||
|
70c0ad1b95 | ||
|
d975decf92 |
||
|
f4720ee9f7 |
||
|
46d0cdba46 | ||
|
f98a663e25 |
||
|
ed6929c696 | ||
|
dc1d71d5cf |
||
|
80a7839eec |
||
|
ccdf65b5b8 |
||
|
869e6dab4b |
||
|
48edfecb69 |
||
|
72914b9166 | ||
|
650e192c87 |
||
|
7ca0746fbf | ||
|
1adebbbcc7 |
||
|
0a34571e49 | ||
|
df80b8845a |
||
|
136776744b | ||
|
4392a58c22 |
||
|
eccc3883eb | ||
|
a27be6eb31 |
||
|
1ee4e8b719 |
||
|
5128d896c7 |
||
|
72f64ce1f7 |
||
|
675dece832 |
||
|
4d19babf7e | ||
|
0f56097612 |
||
|
51cbff1c8b | ||
|
c6b76bdc70 |
||
|
1daf321580 | ||
|
e651e0110d |
||
|
861d180dc8 | ||
|
832ba79b77 |
||
|
3a14258e79 | ||
|
002210710f |
||
|
fee15244aa | ||
|
861f2d8a56 |
||
|
ec2f432a84 |
||
|
ad0b4caa64 |
||
|
8de617c01b |
||
|
5c7d29e09d |
||
|
b7713856e0 | ||
|
1375409642 |
||
|
12ded3f422 |
||
|
c1fe69afd5 |
||
|
0625493aad |
||
|
2a7466abad |
||
|
f24c055fe9 |
||
|
4cfe0b5315 |
||
|
43b303bd8f |
||
|
f52cf32f96 | ||
|
0393e524e5 |
||
|
be509c01f9 |
||
|
08d081f21d |
||
|
836cdb2bea | ||
|
01c6ee41ba |
||
|
4a2b954d2c | ||
|
dc2dcd2d5a |
||
|
8531995ec7 |
||
|
315be83afc |
||
|
65cd28e613 |
||
|
71e4b92fd6 |
||
|
dda5c6c0ed |
||
|
f2bfbba134 | ||
|
82f5f617f5 |
||
|
6d68cc6ea2 |
||
|
d36ad410ee |
||
|
b8d8648d28 |
||
|
1ae4e0ce9a | ||
|
7b945eec58 |
||
|
3f517abfa4 |
||
|
c98e722fa4 |
||
|
13b5688487 |
||
|
6e44be0f2d |
||
|
b2727d98a3 |
||
|
c1e44cbc3f |
||
|
df95e60bdc | ||
|
24424f59a0 |
||
|
3ed24baeb0 |
||
|
93d30722a0 |
||
|
ccc506d5f7 |
||
|
6affae6454 | ||
|
dbe2d0942a |
||
|
37c7366ab0 |
||
|
7b272b6ed6 |
||
|
fbfa790d46 |
||
|
ec858db225 |
||
|
7ec65e4cd9 |
||
|
0ba218a065 |
||
|
afcba52b1a |
||
|
756b4849d9 |
||
|
330dbe7203 |
||
|
ebf15fc198 |
||
|
e4cf8b1416 |
||
|
fe6b0b03ea |
||
|
6fe425c91a |
||
|
ae379a62dc |
||
|
3e143c6017 |
||
|
59fb050767 |
||
|
60e8a01b94 | ||
|
d04962b231 | ||
|
b0091d40e3 |
||
|
45e18c977d |
||
|
98ccf556d8 |
||
|
575421b3fb |
||
|
0b6dec46e0 |
||
|
723274efee |
||
|
e22e8e4506 |
||
|
ac9e5401dc |
||
|
d46bc7795c |
||
|
714bc0a136 |
||
|
5eb2622a4e |
||
|
041a716f4e |
||
|
64af691be3 | ||
|
af8b8e128e |
||
|
f0894be69d | ||
|
0dbf72f67e |
||
|
4653119625 |
||
|
5134e797f3 |
||
|
5e0662727d |
||
|
44b6e54dcb |
||
|
2d676b35d2 | ||
|
042aaa4f87 |
||
|
ec4199bada | ||
|
c1a05a5f9b |
||
|
bd2d64230f |
||
|
f2072f0158 | ||
|
2eb11b1f69 |
||
|
b2339ade84 |
||
|
a87b6e1005 |
||
|
a197f751f9 |
||
|
0cff878b2e |
||
|
fb96183753 |
||
|
334f32e595 |
||
|
5d81c5062b |
||
|
47f5c124aa |
||
|
0369278159 |
||
|
1e2f5273bb |
||
|
39c7e21c5f |
||
|
fc0a10150f | ||
|
e1c08d64b3 |
||
|
4438faf9a9 |
||
|
9be3d3d840 |
||
|
3ec5a2ce4c |
||
|
e6bbf92c77 | ||
|
ca78dd77ac |
||
|
812bb3c8b7 |
||
|
ba8b4723a7 | ||
|
69cdb718f8 |
||
|
b796abcc33 |
||
|
54526e062a | ||
|
908b9adf9c |
102 changed files with 132812 additions and 1989 deletions
11
.busted
Normal file
11
.busted
Normal file
|
@ -0,0 +1,11 @@
|
|||
return {
|
||||
_all = {
|
||||
coverage = false,
|
||||
},
|
||||
default = {
|
||||
verbose = true,
|
||||
},
|
||||
tests = {
|
||||
verbose = true,
|
||||
},
|
||||
}
|
7
.editorconfig
Normal file
7
.editorconfig
Normal file
|
@ -0,0 +1,7 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
3
.github/.release-please-manifest.json
vendored
Normal file
3
.github/.release-please-manifest.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
".": "11.17.1"
|
||||
}
|
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -1,36 +0,0 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: "Create a report to help us improve"
|
||||
title: ""
|
||||
labels: "bug"
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Which version of Neovim are you using?**
|
||||
Gui(specify which GUI client you are using)? Nightly? Version?
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
**Expected Behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Log**
|
||||
Please include any related errors from the Noice log file. (open with `:Lazy log`)
|
||||
|
||||
<details>
|
||||
<summary>Lazy log</summary>
|
||||
<pre>
|
||||
paste log here
|
||||
</pre>
|
||||
</details>
|
75
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
75
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
|
@ -0,0 +1,75 @@
|
|||
name: Bug Report
|
||||
description: File a bug/issue
|
||||
title: "bug: "
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**Before** reporting an issue, make sure to read the [documentation](https://github.com/folke/lazy.nvim)
|
||||
and search [existing issues](https://github.com/folke/lazy.nvim/issues).
|
||||
|
||||
Usage questions such as ***"How do I...?"*** belong in [Discussions](https://github.com/folke/lazy.nvim/discussions) and will be closed.
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Did you check docs and existing issues?
|
||||
description: Make sure you checked all of the below before submitting an issue
|
||||
options:
|
||||
- label: I have read all the lazy.nvim docs
|
||||
required: true
|
||||
- label: I have updated the plugin to the latest version before submitting this issue
|
||||
required: true
|
||||
- label: I have searched the existing issues of lazy.nvim
|
||||
required: true
|
||||
- label: I have searched the existing issues of plugins related to this issue
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: "Neovim version (nvim -v)"
|
||||
placeholder: "0.8.0 commit db1b0ee3b30f"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: "Operating system/version"
|
||||
placeholder: "MacOS 11.5"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is. Please include any related errors you see in Neovim.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: Steps to reproduce the behavior.
|
||||
placeholder: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A concise description of what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Repro
|
||||
description: Minimal `init.lua` to reproduce this issue. Save as `repro.lua` and run with `nvim -u repro.lua`
|
||||
value: |
|
||||
vim.env.LAZY_STDPATH = ".repro"
|
||||
load(vim.fn.system("curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua"))()
|
||||
|
||||
require("lazy.minit").repro({
|
||||
spec = {
|
||||
-- add any other plugins here
|
||||
},
|
||||
})
|
||||
render: lua
|
||||
validations:
|
||||
required: false
|
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Ask a question
|
||||
url: https://github.com/folke/lazy.nvim/discussions
|
||||
about: Use Github discussions instead
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -1,20 +0,0 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: 'enhancement'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
36
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
36
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
name: Feature Request
|
||||
description: Suggest a new feature
|
||||
title: "feature: "
|
||||
labels: [enhancement]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Did you check the docs?
|
||||
description: Make sure you read all the docs before submitting a feature request
|
||||
options:
|
||||
- label: I have read all the lazy.nvim docs
|
||||
required: true
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Is your feature request related to a problem? Please describe.
|
||||
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Describe the solution you'd like
|
||||
description: A clear and concise description of what you want to happen.
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Describe alternatives you've considered
|
||||
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||
- type: textarea
|
||||
validations:
|
||||
required: false
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context or screenshots about the feature request here.
|
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
## Description
|
||||
|
||||
<!-- Describe the big picture of your changes to communicate to the maintainers
|
||||
why we should accept this pull request. -->
|
||||
|
||||
## Related Issue(s)
|
||||
|
||||
<!--
|
||||
If this PR fixes any issues, please link to the issue here.
|
||||
- Fixes #<issue_number>
|
||||
-->
|
||||
|
||||
## Screenshots
|
||||
|
||||
<!-- Add screenshots of the changes if applicable. -->
|
||||
|
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
9
.github/release-please-config.json
vendored
Normal file
9
.github/release-please-config.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
|
||||
"packages": {
|
||||
".": {
|
||||
"release-type": "simple",
|
||||
"extra-files": ["lua/lazy/core/config.lua"]
|
||||
}
|
||||
}
|
||||
}
|
51
.github/workflows/ci.yml
vendored
51
.github/workflows/ci.yml
vendored
|
@ -1,48 +1,15 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Neovim
|
||||
run: |
|
||||
wget -q https://github.com/neovim/neovim/releases/download/nightly/nvim-linux64.deb -O /tmp/nvim.deb
|
||||
sudo dpkg -i /tmp/nvim.deb
|
||||
- name: Run Tests
|
||||
run: |
|
||||
nvim --version
|
||||
./tests/run
|
||||
docs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: tests
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: panvimdoc
|
||||
uses: kdheepak/panvimdoc@main
|
||||
with:
|
||||
vimdoc: lazy.nvim
|
||||
demojify: true
|
||||
- name: Push changes
|
||||
uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
commit_message: "chore(build): auto-generate vimdoc"
|
||||
commit_user_name: "github-actions[bot]"
|
||||
commit_user_email: "github-actions[bot]@users.noreply.github.com"
|
||||
commit_author: "github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
|
||||
release:
|
||||
name: release
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
needs:
|
||||
- docs
|
||||
- tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: google-github-actions/release-please-action@v3
|
||||
with:
|
||||
release-type: simple
|
||||
package-name: lazy.nvim
|
||||
ci:
|
||||
uses: folke/github/.github/workflows/ci.yml@main
|
||||
secrets: inherit
|
||||
with:
|
||||
plugin: lazy.nvim
|
||||
repo: folke/lazy.nvim
|
||||
docs: false
|
||||
|
|
30
.github/workflows/community.yml
vendored
Normal file
30
.github/workflows/community.yml
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
name: Community
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
community:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.ref == 'refs/heads/main' && github.repository_owner == 'folke' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: folke/github/neovim@main
|
||||
- name: Rockspec Build
|
||||
id: rockspec-build
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: build
|
||||
key: rockspec-build
|
||||
- name: Generate Rockspec
|
||||
if: steps.rockspec-build.cache-hit != 'true'
|
||||
run: |
|
||||
nvim -l lua/lazy/build.lua
|
||||
- name: Push changes
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
with:
|
||||
commit_message: "chore(build): auto-generate rockspec mappings"
|
||||
commit_user_name: "github-actions[bot]"
|
||||
commit_user_email: "github-actions[bot]@users.noreply.github.com"
|
||||
commit_author: "github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
|
19
.github/workflows/docs.yml
vendored
Normal file
19
.github/workflows/docs.yml
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
name: Docs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.ref == 'refs/heads/main' && github.repository_owner == 'folke' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: docs
|
||||
- name: Generate Docs
|
||||
shell: bash
|
||||
run: gh workflow run "Deploy to Github Pages" --ref docs
|
8
.github/workflows/labeler.yml
vendored
Normal file
8
.github/workflows/labeler.yml
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
name: "PR Labeler"
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
uses: folke/github/.github/workflows/labeler.yml@main
|
||||
secrets: inherit
|
18
.github/workflows/pr.yml
vendored
Normal file
18
.github/workflows/pr.yml
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
name: PR Title
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- synchronize
|
||||
- reopened
|
||||
- ready_for_review
|
||||
|
||||
permissions:
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
pr-title:
|
||||
uses: folke/github/.github/workflows/pr.yml@main
|
||||
secrets: inherit
|
11
.github/workflows/stale.yml
vendored
Normal file
11
.github/workflows/stale.yml
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
name: Stale Issues & PRs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 1 * * *"
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
if: contains(fromJSON('["folke", "LazyVim"]'), github.repository_owner)
|
||||
uses: folke/github/.github/workflows/stale.yml@main
|
||||
secrets: inherit
|
13
.github/workflows/update.yml
vendored
Normal file
13
.github/workflows/update.yml
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
name: Update Repo
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# Run every hour
|
||||
- cron: "0 * * * *"
|
||||
|
||||
jobs:
|
||||
update:
|
||||
if: contains(fromJSON('["folke", "LazyVim"]'), github.repository_owner)
|
||||
uses: folke/github/.github/workflows/update.yml@main
|
||||
secrets: inherit
|
12
.gitignore
vendored
12
.gitignore
vendored
|
@ -1,3 +1,9 @@
|
|||
tt.lua
|
||||
.tests
|
||||
doc/tags
|
||||
*.log
|
||||
/.repro
|
||||
/.tests
|
||||
/build
|
||||
/debug
|
||||
/doc/tags
|
||||
foo.*
|
||||
node_modules
|
||||
tt.*
|
||||
|
|
12
.markdownlint.yaml
Normal file
12
.markdownlint.yaml
Normal file
|
@ -0,0 +1,12 @@
|
|||
MD013:
|
||||
line_length: 120
|
||||
tables: false
|
||||
MD033:
|
||||
allowed_elements:
|
||||
- "details"
|
||||
- "summary"
|
||||
- "b"
|
||||
- "table"
|
||||
- "tr"
|
||||
- "td"
|
||||
- "a"
|
|
@ -7,7 +7,7 @@
|
|||
}
|
||||
},
|
||||
"lspconfig": {
|
||||
"sumneko_lua": {
|
||||
"lua_ls": {
|
||||
"Lua.runtime.version": "LuaJIT",
|
||||
"Lua.workspace.checkThirdParty": false
|
||||
}
|
||||
|
|
1
.styluaignore
Normal file
1
.styluaignore
Normal file
|
@ -0,0 +1 @@
|
|||
lua/lazy/community/_generated.lua
|
2339
CHANGELOG.md
2339
CHANGELOG.md
File diff suppressed because it is too large
Load diff
201
LICENSE
Normal file
201
LICENSE
Normal file
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
109
README.md
109
README.md
|
@ -1,54 +1,69 @@
|
|||
# lazy.nvim
|
||||
<h4 align="center">
|
||||
<a href="https://lazy.folke.io/installation">Install</a>
|
||||
·
|
||||
<a href="https://lazy.folke.io/configuration">Configure</a>
|
||||
·
|
||||
<a href="https://lazy.folke.io">Docs</a>
|
||||
</h4>
|
||||
|
||||
<div align="center"><p>
|
||||
<a href="https://github.com/folke/lazy.nvim/releases/latest">
|
||||
<img alt="Latest release" src="https://img.shields.io/github/v/release/folke/lazy.nvim?style=for-the-badge&logo=starship&color=C9CBFF&logoColor=D9E0EE&labelColor=302D41&include_prerelease&sort=semver" />
|
||||
</a>
|
||||
<a href="https://github.com/folke/lazy.nvim/pulse">
|
||||
<img alt="Last commit" src="https://img.shields.io/github/last-commit/folke/lazy.nvim?style=for-the-badge&logo=starship&color=8bd5ca&logoColor=D9E0EE&labelColor=302D41"/>
|
||||
</a>
|
||||
<a href="https://github.com/folke/lazy.nvim/blob/main/LICENSE">
|
||||
<img alt="License" src="https://img.shields.io/github/license/folke/lazy.nvim?style=for-the-badge&logo=starship&color=ee999f&logoColor=D9E0EE&labelColor=302D41" />
|
||||
</a>
|
||||
<a href="https://github.com/folke/lazy.nvim/stargazers">
|
||||
<img alt="Stars" src="https://img.shields.io/github/stars/folke/lazy.nvim?style=for-the-badge&logo=starship&color=c69ff5&logoColor=D9E0EE&labelColor=302D41" />
|
||||
</a>
|
||||
<a href="https://github.com/folke/lazy.nvim/issues">
|
||||
<img alt="Issues" src="https://img.shields.io/github/issues/folke/lazy.nvim?style=for-the-badge&logo=bilibili&color=F5E0DC&logoColor=D9E0EE&labelColor=302D41" />
|
||||
</a>
|
||||
<a href="https://github.com/folke/lazy.nvim">
|
||||
<img alt="Repo Size" src="https://img.shields.io/github/repo-size/folke/lazy.nvim?color=%23DDB6F2&label=SIZE&logo=codesandbox&style=for-the-badge&logoColor=D9E0EE&labelColor=302D41" />
|
||||
</a>
|
||||
<a href="https://twitter.com/intent/follow?screen_name=folke">
|
||||
<img alt="follow on Twitter" src="https://img.shields.io/twitter/follow/folke?style=for-the-badge&logo=twitter&color=8aadf3&logoColor=D9E0EE&labelColor=302D41" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
**lazy.nvim** is a modern plugin manager for Neovim.
|
||||
|
||||

|
||||
|
||||
## ✨ Features
|
||||
|
||||
- [x] Partial clones instead of shallow clones
|
||||
- [x] waits till missing deps are installed (bootstrap Neovim and start using it right away)
|
||||
- [x] Async
|
||||
- [x] No need for compile
|
||||
- [x] Fast
|
||||
- [x] Correct sequencing of dependencies (deps should always be opt. Maybe make everything opt?)
|
||||
- [ ] Import specs from Packer
|
||||
- [x] Config in multiple files
|
||||
- [x] Patterns for local packages
|
||||
- [x] Profiling
|
||||
- [x] lockfile
|
||||
- [x] upvalues in `config` & `init`
|
||||
- [x] check for updates
|
||||
- [ ] package.lua
|
||||
- [ ] package-lock.lua
|
||||
- [x] tag/version support `git tag --sort version:refname`
|
||||
- [x] auto-loading on completion for lazy-loaded commands
|
||||
- [x] bootstrap code
|
||||
- [x] semver https://devhints.io/semver
|
||||
https://semver.npmjs.com/
|
||||
- 📦 Manage all your Neovim plugins with a powerful UI
|
||||
- 🚀 Fast startup times thanks to automatic caching and bytecode compilation of Lua modules
|
||||
- 💾 Partial clones instead of shallow clones
|
||||
- 🔌 Automatic lazy-loading of Lua modules and lazy-loading on events, commands, filetypes, and key mappings
|
||||
- ⏳ Automatically install missing plugins before starting up Neovim, allowing you to start using it right away
|
||||
- 💪 Async execution for improved performance
|
||||
- 🛠️ No need to manually compile plugins
|
||||
- 🧪 Correct sequencing of dependencies
|
||||
- 📁 Configurable in multiple files
|
||||
- 📚 Generates helptags of the headings in `README.md` files for plugins that don't have vimdocs
|
||||
- 💻 Dev options and patterns for using local plugins
|
||||
- 📊 Profiling tools to optimize performance
|
||||
- 🔒 Lockfile `lazy-lock.json` to keep track of installed plugins
|
||||
- 🔎 Automatically check for updates
|
||||
- 📋 Commit, branch, tag, version, and full [Semver](https://devhints.io/semver) support
|
||||
- 📈 Statusline component to see the number of pending updates
|
||||
- 🎨 Automatically lazy-loads colorschemes
|
||||
|
||||
## ✅ TODO
|
||||
## ⚡️ Requirements
|
||||
|
||||
- [x] support for Plugin.lock
|
||||
- [ ] health checks: check merge conflicts async
|
||||
- [x] defaults for git log
|
||||
- [x] view keybindings for update/clean/...
|
||||
- [x] add profiler to view
|
||||
- [x] add buttons for actions
|
||||
- [x] show time taken for op in view
|
||||
- [ ] package meta index (package.lua cache for all packages)
|
||||
- [ ] migrate from Packer
|
||||
- [ ] auto lazy-loading of lua modules
|
||||
- [ ] use uv file watcher to check for config changes
|
||||
- [x] clear errors
|
||||
- [x] add support for versions `git tag --sort v:refname`
|
||||
- [x] rename requires to dependencies
|
||||
- [x] move tasks etc to Plugin.state
|
||||
- [ ] allow setting up plugins through config
|
||||
- [ ] handlers imply opt
|
||||
- [ ] dependencies imply opt for deps
|
||||
- [x] fix local plugin spec
|
||||
- Neovim >= **0.8.0** (needs to be built with **LuaJIT**)
|
||||
- Git >= **2.19.0** (for partial clones support)
|
||||
- a [Nerd Font](https://www.nerdfonts.com/) **_(optional)_**
|
||||
- [luarocks](https://luarocks.org/) to install rockspecs.
|
||||
You can remove `rockspec` from `opts.pkg.sources` to disable this feature.
|
||||
|
||||
## 📦 Differences with Packer
|
||||
## 🚀 Getting Started
|
||||
|
||||
- **Plugin Spec**:
|
||||
|
||||
- `setup` => `init`
|
||||
- `requires` => `dependencies`
|
||||
- `as` => `name`
|
||||
Check the [documentation website](https://lazy.folke.io/) for more information.
|
79
TODO.md
Normal file
79
TODO.md
Normal file
|
@ -0,0 +1,79 @@
|
|||
# ✅ TODO
|
||||
|
||||
- [x] progress bar?
|
||||
- [x] options when opening file
|
||||
- [x] lazy notify? not ideal when installing missing stuff
|
||||
- [x] topmods?
|
||||
|
||||
- [ ] better merging options?
|
||||
- [ ] especially what to do with merging of handlers?
|
||||
- [ ] overwriting keymaps probably doesn't work
|
||||
- [ ] disabled deps?
|
||||
|
||||
- [x] fancy UI to manage all your Neovim plugins
|
||||
- [x] auto lazy-loading of lua modules
|
||||
- [x] lazy-loading on events, commands, filetypes and key mappings
|
||||
- [x] Partial clones instead of shallow clones
|
||||
- [x] waits till missing deps are installed (bootstrap Neovim and start using it right away)
|
||||
- [x] Async
|
||||
- [x] No need to manually compile
|
||||
- [x] Fast. Automatically caches and compiles byte code of all lua modules needed during startup
|
||||
- [x] Correct sequencing of dependencies (deps should always be opt. Maybe make everything opt?)
|
||||
- [x] Config in multiple files
|
||||
- [x] dev option and patterns for local packages
|
||||
- [x] Profiling
|
||||
- [x] lockfile `lazy-lock.json`
|
||||
- [x] upvalues in `config` & `init`
|
||||
- [x] automatically check for updates
|
||||
- [x] commit, branch, tag, version and full semver support
|
||||
- [x] statusline component to see number of pending updates
|
||||
|
||||
- [x] semver https://devhints.io/semver
|
||||
- [x] auto-loading on completion for lazy-loaded commands
|
||||
- [x] bootstrap code
|
||||
- [x] Background update checker
|
||||
- [x] health checks: check merge conflicts async
|
||||
- [x] unsupported props or props from other managers
|
||||
- [x] other packages still in site?
|
||||
- [x] other package manager artifacts still present? compiled etc
|
||||
- [x] status page showing running handlers and cache stats
|
||||
- [x] temp colorscheme used during startup when installing missing plugins
|
||||
- [x] automatically reloads when config changes are detected
|
||||
- [x] handlers imply opt
|
||||
- [x] dependencies imply opt for deps
|
||||
- [x] show spec errors in health
|
||||
- [x] fix plugin details
|
||||
- [ ] show disabled plugins (strikethrough?)
|
||||
- [ ] log file
|
||||
- [x] git tests
|
||||
- [x] Import specs from other plugin managers
|
||||
- [ ] [packspec](https://github.com/nvim-lua/nvim-package-specification)
|
||||
|
||||
- [ ] add support to specify `engines`, `os` and `cpu` like in `package.json`
|
||||
- [ ] semver merging. Should check if two or more semver ranges are compatible and calculate the union range
|
||||
- default semver merging strategy: if no version matches all, then use the highest version?
|
||||
- [ ] package meta index (package.lua cache for all packages)
|
||||
|
||||
- [x] document highlight groups
|
||||
- [x] document user events
|
||||
- [x] document API, like lazy.plugins()
|
||||
- [x] icons
|
||||
|
||||
- [x] check in cache if rtp files match
|
||||
- [x] I think the installation section, specifically the loading part, could use an
|
||||
extra sentence or two. I was confused on what `config.plugins` was initially.
|
||||
Maybe a quick, "for example, if you have a lua file
|
||||
`~/.config/nvim/lua/config/plugins.lua` that returns a table" or something it'd
|
||||
remove most question marks I think.
|
||||
- [x] When auto-installing the plugins the cursor isn't focused on the floating
|
||||
window, but on the non-floating window in the background.
|
||||
- [x] Doing `:Lazy clean` doesn't show which plugins were removed.
|
||||
- [x] Shouldn't the "Versioning" section be in the "Lockfile" chapter?
|
||||
- [x] Why are personal dotfiles used as examples? Dotfiles change all the time,
|
||||
there's no guarantee this will be relevant or even exist in two years.
|
||||
- [x] What's the difference between lazy-loading and verylazy-loading?
|
||||
- [x] Most emojis in "Configuration" aren't shown for me.
|
||||
- [x] add section on how to uninstall
|
||||
- [x] add `:Packadd` command or something similar
|
||||
- [x] headless install
|
||||
- [x] better keys handling
|
51
bootstrap.lua
Normal file
51
bootstrap.lua
Normal file
|
@ -0,0 +1,51 @@
|
|||
-- Lazy Bootstrapper
|
||||
-- Usage:
|
||||
-- ```lua
|
||||
-- load(vim.fn.system("curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua"))()
|
||||
-- ```
|
||||
local M = {}
|
||||
|
||||
function M.setup()
|
||||
local uv = vim.uv or vim.loop
|
||||
if vim.env.LAZY_STDPATH then
|
||||
local root = vim.fn.fnamemodify(vim.env.LAZY_STDPATH, ":p"):gsub("[\\/]$", "")
|
||||
for _, name in ipairs({ "config", "data", "state", "cache" }) do
|
||||
vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. "/" .. name
|
||||
end
|
||||
end
|
||||
|
||||
if vim.env.LAZY_PATH and not uv.fs_stat(vim.env.LAZY_PATH) then
|
||||
vim.env.LAZY_PATH = nil
|
||||
end
|
||||
|
||||
local lazypath = vim.env.LAZY_PATH or vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
|
||||
if not vim.env.LAZY_PATH and not uv.fs_stat(lazypath) then
|
||||
vim.api.nvim_echo({
|
||||
{
|
||||
"Cloning lazy.nvim\n\n",
|
||||
"DiagnosticInfo",
|
||||
},
|
||||
}, true, {})
|
||||
local lazyrepo = "https://github.com/folke/lazy.nvim.git"
|
||||
local ok, out = pcall(vim.fn.system, {
|
||||
"git",
|
||||
"clone",
|
||||
"--filter=blob:none",
|
||||
lazyrepo,
|
||||
lazypath,
|
||||
})
|
||||
if not ok or vim.v.shell_error ~= 0 then
|
||||
vim.api.nvim_echo({
|
||||
{ "Failed to clone lazy.nvim\n", "ErrorMsg" },
|
||||
{ vim.trim(out or ""), "WarningMsg" },
|
||||
{ "\nPress any key to exit...", "MoreMsg" },
|
||||
}, true, {})
|
||||
vim.fn.getchar()
|
||||
os.exit(1)
|
||||
end
|
||||
end
|
||||
vim.opt.rtp:prepend(lazypath)
|
||||
end
|
||||
M.setup()
|
||||
|
||||
return M
|
0
doc/.keep
Normal file
0
doc/.keep
Normal file
1462
doc/lazy.nvim.txt
1462
doc/lazy.nvim.txt
File diff suppressed because it is too large
Load diff
222
lua/lazy/async.lua
Normal file
222
lua/lazy/async.lua
Normal file
|
@ -0,0 +1,222 @@
|
|||
local Util = require("lazy.core.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
---@type Async[]
|
||||
M._active = {}
|
||||
---@type Async[]
|
||||
M._suspended = {}
|
||||
M._executor = assert(vim.loop.new_check())
|
||||
|
||||
M.BUDGET = 10
|
||||
|
||||
---@type table<thread, Async>
|
||||
M._threads = setmetatable({}, { __mode = "k" })
|
||||
|
||||
---@alias AsyncEvent "done" | "error" | "yield" | "ok"
|
||||
|
||||
---@class Async
|
||||
---@field _co thread
|
||||
---@field _fn fun()
|
||||
---@field _suspended? boolean
|
||||
---@field _on table<AsyncEvent, fun(res:any, async:Async)[]>
|
||||
local Async = {}
|
||||
|
||||
---@param fn async fun()
|
||||
---@return Async
|
||||
function Async.new(fn)
|
||||
local self = setmetatable({}, { __index = Async })
|
||||
return self:init(fn)
|
||||
end
|
||||
|
||||
---@param fn async fun()
|
||||
---@return Async
|
||||
function Async:init(fn)
|
||||
self._fn = fn
|
||||
self._on = {}
|
||||
self._co = coroutine.create(function()
|
||||
local ok, err = pcall(self._fn)
|
||||
if not ok then
|
||||
self:_emit("error", err)
|
||||
end
|
||||
self:_emit("done")
|
||||
end)
|
||||
M._threads[self._co] = self
|
||||
return M.add(self)
|
||||
end
|
||||
|
||||
---@param event AsyncEvent
|
||||
---@param cb async fun(res:any, async:Async)
|
||||
function Async:on(event, cb)
|
||||
self._on[event] = self._on[event] or {}
|
||||
table.insert(self._on[event], cb)
|
||||
return self
|
||||
end
|
||||
|
||||
---@private
|
||||
---@param event AsyncEvent
|
||||
---@param res any
|
||||
function Async:_emit(event, res)
|
||||
for _, cb in ipairs(self._on[event] or {}) do
|
||||
cb(res, self)
|
||||
end
|
||||
end
|
||||
|
||||
function Async:running()
|
||||
return coroutine.status(self._co) ~= "dead"
|
||||
end
|
||||
|
||||
---@async
|
||||
function Async:sleep(ms)
|
||||
vim.defer_fn(function()
|
||||
self:resume()
|
||||
end, ms)
|
||||
self:suspend()
|
||||
end
|
||||
|
||||
---@async
|
||||
---@param yield? boolean
|
||||
function Async:suspend(yield)
|
||||
self._suspended = true
|
||||
if coroutine.running() == self._co and yield ~= false then
|
||||
M.yield()
|
||||
end
|
||||
end
|
||||
|
||||
function Async:resume()
|
||||
self._suspended = false
|
||||
M._run()
|
||||
end
|
||||
|
||||
---@async
|
||||
---@param yield? boolean
|
||||
function Async:wake(yield)
|
||||
local async = M.running()
|
||||
assert(async, "Not in an async context")
|
||||
self:on("done", function()
|
||||
async:resume()
|
||||
end)
|
||||
async:suspend(yield)
|
||||
end
|
||||
|
||||
---@async
|
||||
function Async:wait()
|
||||
if coroutine.running() == self._co then
|
||||
error("Cannot wait on self")
|
||||
end
|
||||
|
||||
local async = M.running()
|
||||
if async then
|
||||
self:wake()
|
||||
else
|
||||
while self:running() do
|
||||
vim.wait(10)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function Async:step()
|
||||
if self._suspended then
|
||||
return true
|
||||
end
|
||||
local status = coroutine.status(self._co)
|
||||
if status == "suspended" then
|
||||
local ok, res = coroutine.resume(self._co)
|
||||
if not ok then
|
||||
error(res)
|
||||
elseif res then
|
||||
self:_emit("yield", res)
|
||||
end
|
||||
end
|
||||
return self:running()
|
||||
end
|
||||
|
||||
function M.abort()
|
||||
for _, async in ipairs(M._active) do
|
||||
coroutine.resume(async._co, "abort")
|
||||
end
|
||||
end
|
||||
|
||||
function M.yield()
|
||||
if coroutine.yield() == "abort" then
|
||||
error("aborted", 2)
|
||||
end
|
||||
end
|
||||
|
||||
function M.step()
|
||||
local start = vim.uv.hrtime()
|
||||
for _ = 1, #M._active do
|
||||
if Util.exiting() or vim.uv.hrtime() - start > M.BUDGET * 1e6 then
|
||||
break
|
||||
end
|
||||
|
||||
local state = table.remove(M._active, 1)
|
||||
if state:step() then
|
||||
if state._suspended then
|
||||
table.insert(M._suspended, state)
|
||||
else
|
||||
table.insert(M._active, state)
|
||||
end
|
||||
end
|
||||
end
|
||||
for _ = 1, #M._suspended do
|
||||
local state = table.remove(M._suspended, 1)
|
||||
table.insert(state._suspended and M._suspended or M._active, state)
|
||||
end
|
||||
|
||||
-- M.debug()
|
||||
if #M._active == 0 or Util.exiting() then
|
||||
return M._executor:stop()
|
||||
end
|
||||
end
|
||||
|
||||
function M.debug()
|
||||
local lines = {
|
||||
"- active: " .. #M._active,
|
||||
"- suspended: " .. #M._suspended,
|
||||
}
|
||||
for _, async in ipairs(M._active) do
|
||||
local info = debug.getinfo(async._fn)
|
||||
local file = vim.fn.fnamemodify(info.short_src:sub(1), ":~:.")
|
||||
table.insert(lines, ("%s:%d"):format(file, info.linedefined))
|
||||
if #lines > 10 then
|
||||
break
|
||||
end
|
||||
end
|
||||
local msg = table.concat(lines, "\n")
|
||||
M._notif = vim.notify(msg, nil, { replace = M._notif })
|
||||
end
|
||||
|
||||
---@param async Async
|
||||
function M.add(async)
|
||||
table.insert(M._active, async)
|
||||
M._run()
|
||||
return async
|
||||
end
|
||||
|
||||
function M._run()
|
||||
if not Util.exiting() and not M._executor:is_active() then
|
||||
M._executor:start(vim.schedule_wrap(M.step))
|
||||
end
|
||||
end
|
||||
|
||||
function M.running()
|
||||
local co = coroutine.running()
|
||||
if co then
|
||||
return M._threads[co]
|
||||
end
|
||||
end
|
||||
|
||||
---@async
|
||||
---@param ms number
|
||||
function M.sleep(ms)
|
||||
local async = M.running()
|
||||
assert(async, "Not in an async context")
|
||||
async:sleep(ms)
|
||||
end
|
||||
|
||||
M.Async = Async
|
||||
M.new = Async.new
|
||||
|
||||
return M
|
100
lua/lazy/build.lua
Normal file
100
lua/lazy/build.lua
Normal file
|
@ -0,0 +1,100 @@
|
|||
vim.opt.rtp:append(".")
|
||||
local Rocks = require("lazy.pkg.rockspec")
|
||||
local Semver = require("lazy.manage.semver")
|
||||
local Util = require("lazy.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
M.patterns = { "nvim", "treesitter", "tree-sitter", "cmp", "neo" }
|
||||
|
||||
function M.fetch(url, file, prefix)
|
||||
if not vim.uv.fs_stat(file) then
|
||||
print((prefix or "") .. "Fetching " .. url .. " to " .. file .. "\n")
|
||||
vim.cmd.redraw()
|
||||
local out = vim.fn.system("wget " .. url .. " -O " .. file)
|
||||
if vim.v.shell_error ~= 0 then
|
||||
pcall(vim.uv.fs_unlink, file)
|
||||
error("Failed to fetch " .. url .. ":\n" .. out)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@return RockManifest?
|
||||
function M.fetch_manifest()
|
||||
local manifest_file = "build/manifest.lua"
|
||||
M.fetch("https://luarocks.org/manifest-5.1", manifest_file)
|
||||
return Rocks.parse(manifest_file)
|
||||
end
|
||||
|
||||
function M.fetch_rockspec(name, version, prefix)
|
||||
version = version or "scm-1"
|
||||
local url = "https://luarocks.org/" .. name .. "-" .. version .. ".rockspec"
|
||||
M.fetch(url, "build/" .. name .. ".rockspec", prefix)
|
||||
end
|
||||
|
||||
function M.build()
|
||||
vim.fn.mkdir("build", "p")
|
||||
local manifest = M.fetch_manifest() or {}
|
||||
---@type {name:string, version:string, url:string}[]
|
||||
local nvim_rocks = {}
|
||||
for rock, vv in pairs(manifest.repository or {}) do
|
||||
local matches = false
|
||||
for _, pattern in ipairs(M.patterns) do
|
||||
if rock:find(pattern, 1, true) then
|
||||
matches = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if matches then
|
||||
local versions = vim.tbl_map(Semver.version, vim.tbl_keys(vv))
|
||||
versions = vim.tbl_filter(function(v)
|
||||
return not not v
|
||||
end, versions)
|
||||
local last = Semver.last(versions) or next(vv)
|
||||
last = type(last) == "table" and last.input or last
|
||||
table.insert(nvim_rocks, { name = rock, version = last })
|
||||
end
|
||||
end
|
||||
table.sort(nvim_rocks, function(a, b)
|
||||
return a.name < b.name
|
||||
end)
|
||||
|
||||
for r, rock in ipairs(nvim_rocks) do
|
||||
local progress = string.format("[%d/%d] ", r, #nvim_rocks)
|
||||
local ok, err = pcall(M.fetch_rockspec, rock.name, rock.version, progress)
|
||||
if not ok then
|
||||
err = vim.trim("Error: " .. err)
|
||||
local lines = vim.split(err, "\n")
|
||||
lines = vim.tbl_map(function(line)
|
||||
return " " .. line
|
||||
end, lines)
|
||||
print(table.concat(lines, "\n") .. "\n")
|
||||
end
|
||||
end
|
||||
|
||||
for _, rock in ipairs(nvim_rocks) do
|
||||
local rockspec = Rocks.rockspec("build/" .. rock.name .. ".rockspec")
|
||||
if rockspec then
|
||||
local url = rockspec.source and rockspec.source.url
|
||||
-- parse github short url
|
||||
if url and url:find("://github.com/") then
|
||||
url = url:gsub("^.*://github.com/", "")
|
||||
local parts = vim.split(url, "/")
|
||||
url = parts[1] .. "/" .. parts[2]
|
||||
url = url:gsub("%.git$", "")
|
||||
end
|
||||
if url then
|
||||
rock.url = url
|
||||
print(rock.name .. " " .. url)
|
||||
else
|
||||
print("Error: " .. rock.name .. " missing source url\n\n")
|
||||
print(vim.inspect(rockspec) .. "\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
Util.write_file("lua/lazy/community/_generated.lua", "return \n" .. vim.inspect(nvim_rocks))
|
||||
end
|
||||
|
||||
M.build()
|
||||
|
||||
return M
|
2410
lua/lazy/community/_generated.lua
Normal file
2410
lua/lazy/community/_generated.lua
Normal file
File diff suppressed because it is too large
Load diff
28
lua/lazy/community/init.lua
Normal file
28
lua/lazy/community/init.lua
Normal file
|
@ -0,0 +1,28 @@
|
|||
local M = {}
|
||||
|
||||
---@type table<string, string>
|
||||
local mapping = nil
|
||||
|
||||
local function load()
|
||||
if not mapping then
|
||||
mapping = {}
|
||||
---@type {name:string, url:string, version:string}[]
|
||||
local gen = require("lazy.community._generated")
|
||||
for _, rock in ipairs(gen) do
|
||||
mapping[rock.name] = rock.url
|
||||
end
|
||||
end
|
||||
return mapping
|
||||
end
|
||||
|
||||
---@param rock string
|
||||
---@return string?
|
||||
function M.get_url(rock)
|
||||
return load()[rock]
|
||||
end
|
||||
|
||||
function M.get_spec(name)
|
||||
return require("lazy.community.specs")[name]
|
||||
end
|
||||
|
||||
return M
|
7
lua/lazy/community/specs.lua
Normal file
7
lua/lazy/community/specs.lua
Normal file
|
@ -0,0 +1,7 @@
|
|||
---@type table<string, LazySpec>
|
||||
return {
|
||||
["plenary.nvim"] = {
|
||||
"nvim-lua/plenary.nvim",
|
||||
lazy = true,
|
||||
},
|
||||
}
|
|
@ -1,104 +1,528 @@
|
|||
-- Simple string cache with fast saving and loading from file
|
||||
local uv = vim.uv or vim.loop
|
||||
|
||||
local M = {}
|
||||
|
||||
M.dirty = false
|
||||
---@alias CacheHash {mtime: {sec:number, nsec:number}, size:number}
|
||||
---@alias CacheEntry {hash:CacheHash, chunk:string}
|
||||
|
||||
local cache_path = vim.fn.stdpath("state") .. "/lazy.state"
|
||||
---@type string
|
||||
local cache_hash = ""
|
||||
---@type table<string,boolean>
|
||||
local used = {}
|
||||
---@type table<string,string>
|
||||
local cache = {}
|
||||
---@class ModuleFindOpts
|
||||
---@field all? boolean Search for all matches (defaults to `false`)
|
||||
---@field rtp? boolean Search for modname in the runtime path (defaults to `true`)
|
||||
---@field patterns? string[] Patterns to use (defaults to `{"/init.lua", ".lua"}`)
|
||||
---@field paths? string[] Extra paths to search for modname
|
||||
|
||||
---@return string?
|
||||
function M.get(key)
|
||||
if cache[key] then
|
||||
used[key] = true
|
||||
return cache[key]
|
||||
---@class ModuleInfo
|
||||
---@field modpath string Path of the module
|
||||
---@field modname string Name of the module
|
||||
---@field stat? uv_fs_t File stat of the module path
|
||||
|
||||
---@alias LoaderStats table<string, {total:number, time:number, [string]:number?}?>
|
||||
|
||||
M.path = vim.fn.stdpath("cache") .. "/luac"
|
||||
M.enabled = false
|
||||
|
||||
---@class Loader
|
||||
---@field _rtp string[]
|
||||
---@field _rtp_pure string[]
|
||||
---@field _rtp_key string
|
||||
local Loader = {
|
||||
VERSION = 3,
|
||||
---@type table<string, table<string,ModuleInfo>>
|
||||
_indexed = {},
|
||||
---@type table<string, string[]>
|
||||
_topmods = {},
|
||||
_loadfile = loadfile,
|
||||
---@type LoaderStats
|
||||
_stats = {
|
||||
find = { total = 0, time = 0, not_found = 0 },
|
||||
},
|
||||
}
|
||||
|
||||
--- Tracks the time spent in a function
|
||||
---@private
|
||||
function Loader.track(stat, start)
|
||||
Loader._stats[stat] = Loader._stats[stat] or { total = 0, time = 0 }
|
||||
Loader._stats[stat].total = Loader._stats[stat].total + 1
|
||||
Loader._stats[stat].time = Loader._stats[stat].time + uv.hrtime() - start
|
||||
end
|
||||
|
||||
--- slightly faster/different version than vim.fs.normalize
|
||||
--- we also need to have it here, since the loader will load vim.fs
|
||||
---@private
|
||||
function Loader.normalize(path)
|
||||
if path:sub(1, 1) == "~" then
|
||||
local home = uv.os_homedir() or "~"
|
||||
if home:sub(-1) == "\\" or home:sub(-1) == "/" then
|
||||
home = home:sub(1, -2)
|
||||
end
|
||||
path = home .. path:sub(2)
|
||||
end
|
||||
path = path:gsub("\\", "/"):gsub("/+", "/")
|
||||
return path:sub(-1) == "/" and path:sub(1, -2) or path
|
||||
end
|
||||
|
||||
function M.debug()
|
||||
local ret = {}
|
||||
for key, value in pairs(cache) do
|
||||
ret[key] = #value
|
||||
--- Gets the rtp excluding after directories.
|
||||
--- The result is cached, and will be updated if the runtime path changes.
|
||||
--- When called from a fast event, the cached value will be returned.
|
||||
--- @return string[] rtp, boolean updated
|
||||
---@private
|
||||
function Loader.get_rtp()
|
||||
local start = uv.hrtime()
|
||||
if vim.in_fast_event() then
|
||||
Loader.track("get_rtp", start)
|
||||
return (Loader._rtp or {}), false
|
||||
end
|
||||
return ret
|
||||
local updated = false
|
||||
local key = vim.go.rtp
|
||||
if key ~= Loader._rtp_key then
|
||||
Loader._rtp = {}
|
||||
for _, path in ipairs(vim.api.nvim_get_runtime_file("", true)) do
|
||||
path = Loader.normalize(path)
|
||||
-- skip after directories
|
||||
if path:sub(-6, -1) ~= "/after" and not (Loader._indexed[path] and vim.tbl_isempty(Loader._indexed[path])) then
|
||||
Loader._rtp[#Loader._rtp + 1] = path
|
||||
end
|
||||
end
|
||||
updated = true
|
||||
Loader._rtp_key = key
|
||||
end
|
||||
Loader.track("get_rtp", start)
|
||||
return Loader._rtp, updated
|
||||
end
|
||||
|
||||
function M.set(key, value)
|
||||
cache[key] = value
|
||||
used[key] = true
|
||||
M.dirty = true
|
||||
--- Returns the cache file name
|
||||
---@param name string can be a module name, or a file name
|
||||
---@return string file_name
|
||||
---@private
|
||||
function Loader.cache_file(name)
|
||||
local ret = M.path .. "/" .. name:gsub("[/\\:]", "%%")
|
||||
return ret:sub(-4) == ".lua" and (ret .. "c") or (ret .. ".luac")
|
||||
end
|
||||
|
||||
function M.del(key)
|
||||
cache[key] = nil
|
||||
M.dirty = true
|
||||
--- Saves the cache entry for a given module or file
|
||||
---@param name string module name or filename
|
||||
---@param entry CacheEntry
|
||||
---@private
|
||||
function Loader.write(name, entry)
|
||||
local cname = Loader.cache_file(name)
|
||||
local f = assert(uv.fs_open(cname, "w", 438))
|
||||
local header = {
|
||||
Loader.VERSION,
|
||||
entry.hash.size,
|
||||
entry.hash.mtime.sec,
|
||||
entry.hash.mtime.nsec,
|
||||
}
|
||||
uv.fs_write(f, table.concat(header, ",") .. "\0")
|
||||
uv.fs_write(f, entry.chunk)
|
||||
uv.fs_close(f)
|
||||
end
|
||||
|
||||
function M.hash(file)
|
||||
local stat = vim.loop.fs_stat(file)
|
||||
return stat and (stat.mtime.sec .. stat.mtime.nsec .. stat.size)
|
||||
--- Loads the cache entry for a given module or file
|
||||
---@param name string module name or filename
|
||||
---@return CacheEntry?
|
||||
---@private
|
||||
function Loader.read(name)
|
||||
local start = uv.hrtime()
|
||||
local cname = Loader.cache_file(name)
|
||||
local f = uv.fs_open(cname, "r", 438)
|
||||
if f then
|
||||
local hash = uv.fs_fstat(f) --[[@as CacheHash]]
|
||||
local data = uv.fs_read(f, hash.size, 0) --[[@as string]]
|
||||
uv.fs_close(f)
|
||||
|
||||
local zero = data:find("\0", 1, true)
|
||||
if not zero then
|
||||
return
|
||||
end
|
||||
|
||||
---@type integer[]|{[0]:integer}
|
||||
local header = vim.split(data:sub(1, zero - 1), ",")
|
||||
if tonumber(header[1]) ~= Loader.VERSION then
|
||||
return
|
||||
end
|
||||
Loader.track("read", start)
|
||||
return {
|
||||
hash = { size = tonumber(header[2]), mtime = { sec = tonumber(header[3]), nsec = tonumber(header[4]) } },
|
||||
chunk = data:sub(zero + 1),
|
||||
}
|
||||
end
|
||||
Loader.track("read", start)
|
||||
end
|
||||
|
||||
function M.setup()
|
||||
M.load()
|
||||
vim.api.nvim_create_autocmd("User", {
|
||||
pattern = "LazyDone",
|
||||
once = true,
|
||||
callback = function()
|
||||
vim.api.nvim_create_autocmd("VimLeavePre", {
|
||||
callback = function()
|
||||
if M.dirty then
|
||||
local hash = M.hash(cache_path)
|
||||
-- abort when the file was changed in the meantime
|
||||
if hash == nil or cache_hash == hash then
|
||||
M.save()
|
||||
--- The `package.loaders` loader for lua files using the cache.
|
||||
---@param modname string module name
|
||||
---@return string|function
|
||||
---@private
|
||||
function Loader.loader(modname)
|
||||
local start = uv.hrtime()
|
||||
local ret = M.find(modname)[1]
|
||||
if ret then
|
||||
local chunk, err = Loader.load(ret.modpath, { hash = ret.stat })
|
||||
Loader.track("loader", start)
|
||||
return chunk or error(err)
|
||||
end
|
||||
Loader.track("loader", start)
|
||||
return "\ncache_loader: module " .. modname .. " not found"
|
||||
end
|
||||
|
||||
--- The `package.loaders` loader for libs
|
||||
---@param modname string module name
|
||||
---@return string|function
|
||||
---@private
|
||||
function Loader.loader_lib(modname)
|
||||
local start = uv.hrtime()
|
||||
local sysname = uv.os_uname().sysname:lower() or ""
|
||||
local is_win = sysname:find("win", 1, true) and not sysname:find("darwin", 1, true)
|
||||
local ret = M.find(modname, { patterns = is_win and { ".dll" } or { ".so" } })[1]
|
||||
---@type function?, string?
|
||||
if ret then
|
||||
-- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is
|
||||
-- a) strip prefix up to and including the first dash, if any
|
||||
-- b) replace all dots by underscores
|
||||
-- c) prepend "luaopen_"
|
||||
-- So "foo-bar.baz" should result in "luaopen_bar_baz"
|
||||
local dash = modname:find("-", 1, true)
|
||||
local funcname = dash and modname:sub(dash + 1) or modname
|
||||
local chunk, err = package.loadlib(ret.modpath, "luaopen_" .. funcname:gsub("%.", "_"))
|
||||
Loader.track("loader_lib", start)
|
||||
return chunk or error(err)
|
||||
end
|
||||
Loader.track("loader_lib", start)
|
||||
return "\ncache_loader_lib: module " .. modname .. " not found"
|
||||
end
|
||||
|
||||
--- `loadfile` using the cache
|
||||
---@param filename? string
|
||||
---@param mode? "b"|"t"|"bt"
|
||||
---@param env? table
|
||||
---@param hash? CacheHash
|
||||
---@return function?, string? error_message
|
||||
---@private
|
||||
-- luacheck: ignore 312
|
||||
function Loader.loadfile(filename, mode, env, hash)
|
||||
local start = uv.hrtime()
|
||||
filename = Loader.normalize(filename)
|
||||
mode = nil -- ignore mode, since we byte-compile the lua source files
|
||||
local chunk, err = Loader.load(filename, { mode = mode, env = env, hash = hash })
|
||||
Loader.track("loadfile", start)
|
||||
return chunk, err
|
||||
end
|
||||
|
||||
--- Checks whether two cache hashes are the same based on:
|
||||
--- * file size
|
||||
--- * mtime in seconds
|
||||
--- * mtime in nanoseconds
|
||||
---@param h1 CacheHash
|
||||
---@param h2 CacheHash
|
||||
---@private
|
||||
function Loader.eq(h1, h2)
|
||||
return h1 and h2 and h1.size == h2.size and h1.mtime.sec == h2.mtime.sec and h1.mtime.nsec == h2.mtime.nsec
|
||||
end
|
||||
|
||||
--- Loads the given module path using the cache
|
||||
---@param modpath string
|
||||
---@param opts? {hash?: CacheHash, mode?: "b"|"t"|"bt", env?:table} (table|nil) Options for loading the module:
|
||||
--- - hash: (table) the hash of the file to load if it is already known. (defaults to `vim.uv.fs_stat({modpath})`)
|
||||
--- - mode: (string) the mode to load the module with. "b"|"t"|"bt" (defaults to `nil`)
|
||||
--- - env: (table) the environment to load the module in. (defaults to `nil`)
|
||||
---@see |luaL_loadfile()|
|
||||
---@return function?, string? error_message
|
||||
---@private
|
||||
function Loader.load(modpath, opts)
|
||||
local start = uv.hrtime()
|
||||
|
||||
opts = opts or {}
|
||||
local hash = opts.hash or uv.fs_stat(modpath)
|
||||
---@type function?, string?
|
||||
local chunk, err
|
||||
|
||||
if not hash then
|
||||
-- trigger correct error
|
||||
chunk, err = Loader._loadfile(modpath, opts.mode, opts.env)
|
||||
Loader.track("load", start)
|
||||
return chunk, err
|
||||
end
|
||||
|
||||
local entry = Loader.read(modpath)
|
||||
if entry and Loader.eq(entry.hash, hash) then
|
||||
-- found in cache and up to date
|
||||
-- selene: allow(incorrect_standard_library_use)
|
||||
chunk, err = load(entry.chunk --[[@as string]], "@" .. modpath, opts.mode, opts.env)
|
||||
if not (err and err:find("cannot load incompatible bytecode", 1, true)) then
|
||||
Loader.track("load", start)
|
||||
return chunk, err
|
||||
end
|
||||
end
|
||||
entry = { hash = hash, modpath = modpath }
|
||||
|
||||
chunk, err = Loader._loadfile(modpath, opts.mode, opts.env)
|
||||
if chunk then
|
||||
entry.chunk = string.dump(chunk)
|
||||
Loader.write(modpath, entry)
|
||||
end
|
||||
Loader.track("load", start)
|
||||
return chunk, err
|
||||
end
|
||||
|
||||
--- Finds lua modules for the given module name.
|
||||
---@param modname string Module name, or `"*"` to find the top-level modules instead
|
||||
---@param opts? ModuleFindOpts (table|nil) Options for finding a module:
|
||||
--- - rtp: (boolean) Search for modname in the runtime path (defaults to `true`)
|
||||
--- - paths: (string[]) Extra paths to search for modname (defaults to `{}`)
|
||||
--- - patterns: (string[]) List of patterns to use when searching for modules.
|
||||
--- A pattern is a string added to the basename of the Lua module being searched.
|
||||
--- (defaults to `{"/init.lua", ".lua"}`)
|
||||
--- - all: (boolean) Return all matches instead of just the first one (defaults to `false`)
|
||||
---@return ModuleInfo[] (list) A list of results with the following properties:
|
||||
--- - modpath: (string) the path to the module
|
||||
--- - modname: (string) the name of the module
|
||||
--- - stat: (table|nil) the fs_stat of the module path. Won't be returned for `modname="*"`
|
||||
function M.find(modname, opts)
|
||||
local start = uv.hrtime()
|
||||
opts = opts or {}
|
||||
|
||||
modname = modname:gsub("/", ".")
|
||||
local basename = modname:gsub("%.", "/")
|
||||
local idx = modname:find(".", 1, true)
|
||||
|
||||
-- HACK: fix incorrect require statements. Really not a fan of keeping this,
|
||||
-- but apparently the regular lua loader also allows this
|
||||
if idx == 1 then
|
||||
modname = modname:gsub("^%.+", "")
|
||||
basename = modname:gsub("%.", "/")
|
||||
idx = modname:find(".", 1, true)
|
||||
end
|
||||
|
||||
-- get the top-level module name
|
||||
local topmod = idx and modname:sub(1, idx - 1) or modname
|
||||
|
||||
-- OPTIM: search for a directory first when topmod == modname
|
||||
local patterns = opts.patterns or (topmod == modname and { "/init.lua", ".lua" } or { ".lua", "/init.lua" })
|
||||
for p, pattern in ipairs(patterns) do
|
||||
patterns[p] = "/lua/" .. basename .. pattern
|
||||
end
|
||||
|
||||
---@type ModuleInfo[]
|
||||
local results = {}
|
||||
|
||||
-- Only continue if we haven't found anything yet or we want to find all
|
||||
---@private
|
||||
local function continue()
|
||||
return #results == 0 or opts.all
|
||||
end
|
||||
|
||||
-- Checks if the given paths contain the top-level module.
|
||||
-- If so, it tries to find the module path for the given module name.
|
||||
---@param paths string[]
|
||||
---@private
|
||||
local function _find(paths)
|
||||
for _, path in ipairs(paths) do
|
||||
if topmod == "*" then
|
||||
for _, r in pairs(Loader.lsmod(path)) do
|
||||
results[#results + 1] = r
|
||||
if not continue() then
|
||||
return
|
||||
end
|
||||
end
|
||||
elseif Loader.lsmod(path)[topmod] then
|
||||
for _, pattern in ipairs(patterns) do
|
||||
local modpath = path .. pattern
|
||||
Loader._stats.find.stat = (Loader._stats.find.stat or 0) + 1
|
||||
local hash = uv.fs_stat(modpath)
|
||||
if hash then
|
||||
results[#results + 1] = { modpath = modpath, stat = hash, modname = modname }
|
||||
if not continue() then
|
||||
return
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- always check the rtp first
|
||||
if opts.rtp ~= false then
|
||||
_find(Loader._rtp or {})
|
||||
if continue() then
|
||||
local rtp, updated = Loader.get_rtp()
|
||||
if updated then
|
||||
_find(rtp)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- check any additional paths
|
||||
if continue() and opts.paths then
|
||||
_find(opts.paths)
|
||||
end
|
||||
|
||||
Loader.track("find", start)
|
||||
if #results == 0 then
|
||||
-- module not found
|
||||
Loader._stats.find.not_found = Loader._stats.find.not_found + 1
|
||||
end
|
||||
|
||||
return results
|
||||
end
|
||||
|
||||
--- Resets the topmods cache for the path, or all the paths
|
||||
--- if path is nil.
|
||||
---@param path string? path to reset
|
||||
function M.reset(path)
|
||||
if path then
|
||||
Loader._indexed[Loader.normalize(path)] = nil
|
||||
else
|
||||
Loader._indexed = {}
|
||||
end
|
||||
end
|
||||
|
||||
--- Enables the experimental Lua module loader:
|
||||
--- * overrides loadfile
|
||||
--- * adds the lua loader using the byte-compilation cache
|
||||
--- * adds the libs loader
|
||||
--- * removes the default Neovim loader
|
||||
function M.enable()
|
||||
if M.enabled then
|
||||
return
|
||||
end
|
||||
M.enabled = true
|
||||
vim.fn.mkdir(vim.fn.fnamemodify(M.path, ":p"), "p")
|
||||
-- selene: allow(global_usage)
|
||||
_G.loadfile = Loader.loadfile
|
||||
-- add lua loader
|
||||
table.insert(package.loaders, 2, Loader.loader)
|
||||
-- add libs loader
|
||||
table.insert(package.loaders, 3, Loader.loader_lib)
|
||||
-- remove Neovim loader
|
||||
for l, loader in ipairs(package.loaders) do
|
||||
if loader == vim._load_package then
|
||||
table.remove(package.loaders, l)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- this will reset the top-mods in case someone adds a new
|
||||
-- top-level lua module to a path already on the rtp
|
||||
vim.api.nvim_create_autocmd("BufWritePost", {
|
||||
group = vim.api.nvim_create_augroup("cache_topmods_reset", { clear = true }),
|
||||
callback = function(event)
|
||||
local bufname = event.match ---@type string
|
||||
local idx = bufname:find("/lua/", 1, true)
|
||||
if idx then
|
||||
M.reset(bufname:sub(1, idx - 1))
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
function M.save()
|
||||
require("lazy.core.plugin").save()
|
||||
require("lazy.core.module").save()
|
||||
|
||||
local f = assert(io.open(cache_path, "wb"))
|
||||
for key, value in pairs(cache) do
|
||||
if used[key] then
|
||||
f:write(key, "\0", tostring(#value), "\0", value)
|
||||
--- Disables the experimental Lua module loader:
|
||||
--- * removes the loaders
|
||||
--- * adds the default Neovim loader
|
||||
function M.disable()
|
||||
if not M.enabled then
|
||||
return
|
||||
end
|
||||
M.enabled = false
|
||||
-- selene: allow(global_usage)
|
||||
_G.loadfile = Loader._loadfile
|
||||
---@diagnostic disable-next-line: no-unknown
|
||||
for l, loader in ipairs(package.loaders) do
|
||||
if loader == Loader.loader or loader == Loader.loader_lib then
|
||||
table.remove(package.loaders, l)
|
||||
end
|
||||
end
|
||||
f:close()
|
||||
table.insert(package.loaders, 2, vim._load_package)
|
||||
vim.api.nvim_del_augroup_by_name("cache_topmods_reset")
|
||||
end
|
||||
|
||||
function M.load()
|
||||
cache = {}
|
||||
local f = io.open(cache_path, "rb")
|
||||
if f then
|
||||
cache_hash = M.hash(cache_path)
|
||||
---@type string
|
||||
local data = f:read("*a")
|
||||
f:close()
|
||||
--- Return the top-level `/lua/*` modules for this path
|
||||
---@param path string path to check for top-level lua modules
|
||||
---@private
|
||||
function Loader.lsmod(path)
|
||||
if not Loader._indexed[path] then
|
||||
local start = uv.hrtime()
|
||||
Loader._indexed[path] = {}
|
||||
local handle = uv.fs_scandir(path .. "/lua")
|
||||
while handle do
|
||||
local name, t = uv.fs_scandir_next(handle)
|
||||
if not name then
|
||||
break
|
||||
end
|
||||
local modpath = path .. "/lua/" .. name
|
||||
-- HACK: type is not always returned due to a bug in luv
|
||||
t = t or uv.fs_stat(modpath).type
|
||||
---@type string
|
||||
local topname
|
||||
local ext = name:sub(-4)
|
||||
if ext == ".lua" or ext == ".dll" then
|
||||
topname = name:sub(1, -5)
|
||||
elseif name:sub(-3) == ".so" then
|
||||
topname = name:sub(1, -4)
|
||||
elseif t == "link" or t == "directory" then
|
||||
topname = name
|
||||
end
|
||||
if topname then
|
||||
Loader._indexed[path][topname] = { modpath = modpath, modname = topname }
|
||||
Loader._topmods[topname] = Loader._topmods[topname] or {}
|
||||
if not vim.tbl_contains(Loader._topmods[topname], path) then
|
||||
table.insert(Loader._topmods[topname], path)
|
||||
end
|
||||
end
|
||||
end
|
||||
Loader.track("lsmod", start)
|
||||
end
|
||||
return Loader._indexed[path]
|
||||
end
|
||||
|
||||
local from = 1
|
||||
local to = data:find("\0", from, true)
|
||||
while to do
|
||||
local key = data:sub(from, to - 1)
|
||||
from = to + 1
|
||||
to = data:find("\0", from, true)
|
||||
local len = tonumber(data:sub(from, to - 1))
|
||||
from = to + 1
|
||||
cache[key] = data:sub(from, from + len - 1)
|
||||
from = from + len
|
||||
to = data:find("\0", from, true)
|
||||
--- Debug function that wraps all loaders and tracks stats
|
||||
---@private
|
||||
function M._profile_loaders()
|
||||
for l, loader in pairs(package.loaders) do
|
||||
local loc = debug.getinfo(loader, "Sn").source:sub(2)
|
||||
package.loaders[l] = function(modname)
|
||||
local start = uv.hrtime()
|
||||
local ret = loader(modname)
|
||||
Loader.track("loader " .. l .. ": " .. loc, start)
|
||||
Loader.track("loader_all", start)
|
||||
return ret
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Prints all cache stats
|
||||
---@param opts? {print?:boolean}
|
||||
---@return LoaderStats
|
||||
---@private
|
||||
function M._inspect(opts)
|
||||
if opts and opts.print then
|
||||
---@private
|
||||
local function ms(nsec)
|
||||
return math.floor(nsec / 1e6 * 1000 + 0.5) / 1000 .. "ms"
|
||||
end
|
||||
local chunks = {} ---@type string[][]
|
||||
---@type string[]
|
||||
local stats = vim.tbl_keys(Loader._stats)
|
||||
table.sort(stats)
|
||||
for _, stat in ipairs(stats) do
|
||||
vim.list_extend(chunks, {
|
||||
{ "\n" .. stat .. "\n", "Title" },
|
||||
{ "* total: " },
|
||||
{ tostring(Loader._stats[stat].total) .. "\n", "Number" },
|
||||
{ "* time: " },
|
||||
{ ms(Loader._stats[stat].time) .. "\n", "Bold" },
|
||||
{ "* avg time: " },
|
||||
{ ms(Loader._stats[stat].time / Loader._stats[stat].total) .. "\n", "Bold" },
|
||||
})
|
||||
for k, v in pairs(Loader._stats[stat]) do
|
||||
if not vim.tbl_contains({ "time", "total" }, k) then
|
||||
chunks[#chunks + 1] = { "* " .. k .. ":" .. string.rep(" ", 9 - #k) }
|
||||
chunks[#chunks + 1] = { tostring(v) .. "\n", "Number" }
|
||||
end
|
||||
end
|
||||
end
|
||||
vim.api.nvim_echo(chunks, true, {})
|
||||
end
|
||||
return Loader._stats
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
@ -1,47 +1,253 @@
|
|||
local Util = require("lazy.core.util")
|
||||
|
||||
---@class LazyCoreConfig
|
||||
local M = {}
|
||||
|
||||
---@class LazyConfig
|
||||
M.defaults = {
|
||||
opt = true,
|
||||
plugins = "config.plugins",
|
||||
plugins_local = {
|
||||
path = vim.fn.expand("~/projects"),
|
||||
---@type string[]
|
||||
patterns = {},
|
||||
root = vim.fn.stdpath("data") .. "/lazy", -- directory where plugins will be installed
|
||||
defaults = {
|
||||
-- Set this to `true` to have all your plugins lazy-loaded by default.
|
||||
-- Only do this if you know what you are doing, as it can lead to unexpected behavior.
|
||||
lazy = false, -- should plugins be lazy-loaded?
|
||||
-- It's recommended to leave version=false for now, since a lot the plugin that support versioning,
|
||||
-- have outdated releases, which may break your Neovim install.
|
||||
version = nil, -- always use the latest git commit
|
||||
-- version = "*", -- try installing the latest stable version for plugins that support semver
|
||||
-- default `cond` you can use to globally disable a lot of plugins
|
||||
-- when running inside vscode for example
|
||||
cond = nil, ---@type boolean|fun(self:LazyPlugin):boolean|nil
|
||||
},
|
||||
interactive = true,
|
||||
packpath = vim.fn.stdpath("data") .. "/site/pack/lazy",
|
||||
lockfile = vim.fn.stdpath("config") .. "/lazy-lock.json",
|
||||
view = {
|
||||
icons = {
|
||||
start = "",
|
||||
plugin = " ",
|
||||
source = " ",
|
||||
config = "",
|
||||
event = "",
|
||||
keys = " ",
|
||||
cmd = " ",
|
||||
ft = "",
|
||||
task = "✔ ",
|
||||
-- leave nil when passing the spec as the first argument to setup()
|
||||
spec = nil, ---@type LazySpec
|
||||
local_spec = true, -- load project specific .lazy.lua spec files. They will be added at the end of the spec.
|
||||
lockfile = vim.fn.stdpath("config") .. "/lazy-lock.json", -- lockfile generated after running update.
|
||||
---@type number? limit the maximum amount of concurrent tasks
|
||||
concurrency = jit.os:find("Windows") and (vim.uv.available_parallelism() * 2) or nil,
|
||||
git = {
|
||||
-- defaults for the `Lazy log` command
|
||||
-- log = { "--since=3 days ago" }, -- show commits from the last 3 days
|
||||
log = { "-8" }, -- show the last 8 commits
|
||||
timeout = 120, -- kill processes that take more than 2 minutes
|
||||
url_format = "https://github.com/%s.git",
|
||||
-- lazy.nvim requires git >=2.19.0. If you really want to use lazy with an older version,
|
||||
-- then set the below to false. This should work, but is NOT supported and will
|
||||
-- increase downloads a lot.
|
||||
filter = true,
|
||||
-- rate of network related git operations (clone, fetch, checkout)
|
||||
throttle = {
|
||||
enabled = false, -- not enabled by default
|
||||
-- max 2 ops every 5 seconds
|
||||
rate = 2,
|
||||
duration = 5 * 1000, -- in ms
|
||||
},
|
||||
-- Time in seconds to wait before running fetch again for a plugin.
|
||||
-- Repeated update/check operations will not run again until this
|
||||
-- cooldown period has passed.
|
||||
cooldown = 0,
|
||||
},
|
||||
pkg = {
|
||||
enabled = true,
|
||||
cache = vim.fn.stdpath("state") .. "/lazy/pkg-cache.lua",
|
||||
-- the first package source that is found for a plugin will be used.
|
||||
sources = {
|
||||
"lazy",
|
||||
"rockspec", -- will only be used when rocks.enabled is true
|
||||
"packspec",
|
||||
},
|
||||
},
|
||||
git = {
|
||||
-- defaults for `Lazy log`
|
||||
log = { "-10" }, -- last 10 commits
|
||||
-- log = { "--since=3 days ago" }, -- commits from the last 3 days
|
||||
rocks = {
|
||||
enabled = true,
|
||||
root = vim.fn.stdpath("data") .. "/lazy-rocks",
|
||||
server = "https://nvim-neorocks.github.io/rocks-binaries/",
|
||||
-- use hererocks to install luarocks?
|
||||
-- set to `nil` to use hererocks when luarocks is not found
|
||||
-- set to `true` to always use hererocks
|
||||
-- set to `false` to always use luarocks
|
||||
hererocks = nil,
|
||||
},
|
||||
dev = {
|
||||
-- Directory where you store your local plugin projects. If a function is used,
|
||||
-- the plugin directory (e.g. `~/projects/plugin-name`) must be returned.
|
||||
---@type string | fun(plugin: LazyPlugin): string
|
||||
path = "~/projects",
|
||||
---@type string[] plugins that match these patterns will use your local versions instead of being fetched from GitHub
|
||||
patterns = {}, -- For example {"folke"}
|
||||
fallback = false, -- Fallback to git when local plugin doesn't exist
|
||||
},
|
||||
install = {
|
||||
-- install missing plugins on startup. This doesn't increase startup time.
|
||||
missing = true,
|
||||
-- try to load one of these colorschemes when starting an installation during startup
|
||||
colorscheme = { "habamax" },
|
||||
},
|
||||
ui = {
|
||||
-- a number <1 is a percentage., >1 is a fixed size
|
||||
size = { width = 0.8, height = 0.8 },
|
||||
wrap = true, -- wrap the lines in the ui
|
||||
-- The border to use for the UI window. Accepts same border values as |nvim_open_win()|.
|
||||
border = "none",
|
||||
-- The backdrop opacity. 0 is fully opaque, 100 is fully transparent.
|
||||
backdrop = 60,
|
||||
title = nil, ---@type string only works when border is not "none"
|
||||
title_pos = "center", ---@type "center" | "left" | "right"
|
||||
-- Show pills on top of the Lazy window
|
||||
pills = true, ---@type boolean
|
||||
icons = {
|
||||
cmd = " ",
|
||||
config = "",
|
||||
debug = "● ",
|
||||
event = " ",
|
||||
favorite = " ",
|
||||
ft = " ",
|
||||
init = " ",
|
||||
import = " ",
|
||||
keys = " ",
|
||||
lazy = " ",
|
||||
loaded = "●",
|
||||
not_loaded = "○",
|
||||
plugin = " ",
|
||||
runtime = " ",
|
||||
require = " ",
|
||||
source = " ",
|
||||
start = " ",
|
||||
task = "✔ ",
|
||||
list = {
|
||||
"●",
|
||||
"➜",
|
||||
"★",
|
||||
"‒",
|
||||
},
|
||||
},
|
||||
-- leave nil, to automatically select a browser depending on your OS.
|
||||
-- If you want to use a specific browser, you can define it here
|
||||
browser = nil, ---@type string?
|
||||
throttle = 1000 / 30, -- how frequently should the ui process render events
|
||||
custom_keys = {
|
||||
-- You can define custom key maps here. If present, the description will
|
||||
-- be shown in the help menu.
|
||||
-- To disable one of the defaults, set it to false.
|
||||
|
||||
["<localleader>l"] = {
|
||||
function(plugin)
|
||||
require("lazy.util").float_term({ "lazygit", "log" }, {
|
||||
cwd = plugin.dir,
|
||||
})
|
||||
end,
|
||||
desc = "Open lazygit log",
|
||||
},
|
||||
|
||||
["<localleader>i"] = {
|
||||
function(plugin)
|
||||
Util.notify(vim.inspect(plugin), {
|
||||
title = "Inspect " .. plugin.name,
|
||||
lang = "lua",
|
||||
})
|
||||
end,
|
||||
desc = "Inspect Plugin",
|
||||
},
|
||||
|
||||
["<localleader>t"] = {
|
||||
function(plugin)
|
||||
require("lazy.util").float_term(nil, {
|
||||
cwd = plugin.dir,
|
||||
})
|
||||
end,
|
||||
desc = "Open terminal in plugin dir",
|
||||
},
|
||||
},
|
||||
},
|
||||
-- Output options for headless mode
|
||||
headless = {
|
||||
-- show the output from process commands like git
|
||||
process = true,
|
||||
-- show log messages
|
||||
log = true,
|
||||
-- show task start/end
|
||||
task = true,
|
||||
-- use ansi colors
|
||||
colors = true,
|
||||
},
|
||||
diff = {
|
||||
-- diff command <d> can be one of:
|
||||
-- * browser: opens the github compare view. Note that this is always mapped to <K> as well,
|
||||
-- so you can have a different command for diff <d>
|
||||
-- * git: will run git diff and open a buffer with filetype git
|
||||
-- * terminal_git: will open a pseudo terminal with git diff
|
||||
-- * diffview.nvim: will open Diffview to show the diff
|
||||
cmd = "git",
|
||||
},
|
||||
checker = {
|
||||
-- automatically check for plugin updates
|
||||
enabled = false,
|
||||
concurrency = nil, ---@type number? set to 1 to check for updates very slowly
|
||||
notify = true, -- get a notification when new updates are found
|
||||
frequency = 3600, -- check for updates every hour
|
||||
check_pinned = false, -- check for pinned packages that can't be updated
|
||||
},
|
||||
change_detection = {
|
||||
-- automatically check for config file changes and reload the ui
|
||||
enabled = true,
|
||||
notify = true, -- get a notification when changes are found
|
||||
},
|
||||
performance = {
|
||||
cache = {
|
||||
enabled = true,
|
||||
},
|
||||
reset_packpath = true, -- reset the package path to improve startup time
|
||||
rtp = {
|
||||
reset = true, -- reset the runtime path to $VIMRUNTIME and your config directory
|
||||
---@type string[]
|
||||
paths = {}, -- add any custom paths here that you want to includes in the rtp
|
||||
---@type string[] list any plugins you want to disable here
|
||||
disabled_plugins = {
|
||||
-- "gzip",
|
||||
-- "matchit",
|
||||
-- "matchparen",
|
||||
-- "netrwPlugin",
|
||||
-- "tarPlugin",
|
||||
-- "tohtml",
|
||||
-- "tutor",
|
||||
-- "zipPlugin",
|
||||
},
|
||||
},
|
||||
},
|
||||
-- lazy can generate helptags from the headings in markdown readme files,
|
||||
-- so :help works even for plugins that don't have vim docs.
|
||||
-- when the readme opens with :help it will be correctly displayed as markdown
|
||||
readme = {
|
||||
enabled = true,
|
||||
root = vim.fn.stdpath("state") .. "/lazy/readme",
|
||||
files = { "README.md", "lua/**/README.md" },
|
||||
-- only generate markdown helptags for plugins that don't have docs
|
||||
skip_if_doc_exists = true,
|
||||
},
|
||||
state = vim.fn.stdpath("state") .. "/lazy/state.json", -- state info for checker and other things
|
||||
-- Enable profiling of lazy.nvim. This will add some overhead,
|
||||
-- so only enable this when you are debugging lazy.nvim
|
||||
profiling = {
|
||||
-- Enables extra stats on the debug tab related to the loader cache.
|
||||
-- Additionally gathers stats about all package.loaders
|
||||
loader = false,
|
||||
-- Track each new require in the Lazy profiling tab
|
||||
require = false,
|
||||
},
|
||||
debug = false,
|
||||
}
|
||||
|
||||
function M.hererocks()
|
||||
if M.options.rocks.hererocks == nil then
|
||||
M.options.rocks.hererocks = vim.fn.executable("luarocks") == 0
|
||||
end
|
||||
return M.options.rocks.hererocks
|
||||
end
|
||||
|
||||
M.version = "11.17.1" -- x-release-please-version
|
||||
|
||||
M.ns = vim.api.nvim_create_namespace("lazy")
|
||||
|
||||
M.paths = {
|
||||
---@type string
|
||||
main = nil,
|
||||
---@type string
|
||||
plugins = nil,
|
||||
}
|
||||
---@type LazySpecLoader
|
||||
M.spec = nil
|
||||
|
||||
---@type table<string, LazyPlugin>
|
||||
M.plugins = {}
|
||||
|
@ -52,25 +258,114 @@ M.to_clean = {}
|
|||
---@type LazyConfig
|
||||
M.options = {}
|
||||
|
||||
---@type string
|
||||
M.me = nil
|
||||
|
||||
---@type string
|
||||
M.mapleader = nil
|
||||
|
||||
---@type string
|
||||
M.maplocalleader = nil
|
||||
|
||||
M.suspended = false
|
||||
|
||||
function M.headless()
|
||||
return not M.suspended and #vim.api.nvim_list_uis() == 0
|
||||
end
|
||||
|
||||
---@param opts? LazyConfig
|
||||
function M.setup(opts)
|
||||
M.options = vim.tbl_deep_extend("force", M.defaults, opts or {})
|
||||
M.paths.plugins = vim.fn.stdpath("config") .. "/lua/" .. M.options.plugins:gsub("%.", "/")
|
||||
M.paths.main = M.paths.plugins .. (vim.loop.fs_stat(M.paths.plugins .. ".lua") and ".lua" or "/init.lua")
|
||||
|
||||
-- TODO: check what this does inside a GUI. Probably still ok
|
||||
if #vim.api.nvim_list_uis() == 0 then
|
||||
M.options.interactive = false
|
||||
if type(M.options.spec) == "string" then
|
||||
M.options.spec = { import = M.options.spec }
|
||||
end
|
||||
table.insert(M.options.install.colorscheme, "habamax")
|
||||
|
||||
-- root
|
||||
M.options.root = Util.norm(M.options.root)
|
||||
if type(M.options.dev.path) == "string" then
|
||||
M.options.dev.path = Util.norm(M.options.dev.path)
|
||||
end
|
||||
M.options.lockfile = Util.norm(M.options.lockfile)
|
||||
M.options.readme.root = Util.norm(M.options.readme.root)
|
||||
|
||||
vim.fn.mkdir(M.options.root, "p")
|
||||
|
||||
if M.options.performance.reset_packpath then
|
||||
vim.go.packpath = vim.env.VIMRUNTIME
|
||||
end
|
||||
|
||||
vim.api.nvim_create_autocmd("User", {
|
||||
pattern = "VeryLazy",
|
||||
M.me = debug.getinfo(1, "S").source:sub(2)
|
||||
M.me = Util.norm(vim.fn.fnamemodify(M.me, ":p:h:h:h:h"))
|
||||
local lib = vim.fn.fnamemodify(vim.v.progpath, ":p:h:h") .. "/lib"
|
||||
lib = vim.uv.fs_stat(lib .. "64") and (lib .. "64") or lib
|
||||
lib = lib .. "/nvim"
|
||||
if M.options.performance.rtp.reset then
|
||||
---@type vim.Option
|
||||
vim.opt.rtp = {
|
||||
vim.fn.stdpath("config"),
|
||||
vim.fn.stdpath("data") .. "/site",
|
||||
M.me,
|
||||
vim.env.VIMRUNTIME,
|
||||
lib,
|
||||
vim.fn.stdpath("config") .. "/after",
|
||||
}
|
||||
end
|
||||
for _, path in ipairs(M.options.performance.rtp.paths) do
|
||||
vim.opt.rtp:append(path)
|
||||
end
|
||||
vim.opt.rtp:append(M.options.readme.root)
|
||||
|
||||
-- disable plugin loading since we do all of that ourselves
|
||||
vim.go.loadplugins = false
|
||||
M.mapleader = vim.g.mapleader
|
||||
M.maplocalleader = vim.g.maplocalleader
|
||||
|
||||
vim.api.nvim_create_autocmd("UIEnter", {
|
||||
once = true,
|
||||
callback = function()
|
||||
require("lazy.view").setup()
|
||||
require("lazy.stats").on_ui_enter()
|
||||
end,
|
||||
})
|
||||
|
||||
if M.headless() then
|
||||
require("lazy.view.commands").setup()
|
||||
else
|
||||
vim.api.nvim_create_autocmd("User", {
|
||||
pattern = "VeryLazy",
|
||||
once = true,
|
||||
callback = function()
|
||||
require("lazy.view.commands").setup()
|
||||
if M.options.change_detection.enabled then
|
||||
require("lazy.manage.reloader").enable()
|
||||
end
|
||||
if M.options.checker.enabled then
|
||||
vim.defer_fn(function()
|
||||
require("lazy.manage.checker").start()
|
||||
end, 10)
|
||||
end
|
||||
|
||||
-- useful for plugin developers when making changes to a packspec file
|
||||
vim.api.nvim_create_autocmd("BufWritePost", {
|
||||
pattern = { "lazy.lua", "pkg.json", "*.rockspec" },
|
||||
callback = function()
|
||||
local plugin = require("lazy.core.plugin").find(vim.uv.cwd() .. "/lua/")
|
||||
if plugin then
|
||||
require("lazy").pkg({ plugins = { plugin } })
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd({ "VimSuspend", "VimResume" }, {
|
||||
callback = function(ev)
|
||||
M.suspended = ev.event == "VimSuspend"
|
||||
end,
|
||||
})
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
Util.very_lazy()
|
||||
end
|
||||
|
||||
|
|
174
lua/lazy/core/fragments.lua
Normal file
174
lua/lazy/core/fragments.lua
Normal file
|
@ -0,0 +1,174 @@
|
|||
local Config = require("lazy.core.config")
|
||||
local Util = require("lazy.core.util")
|
||||
|
||||
--- This class is used to manage the fragments of a plugin spec.
|
||||
--- It keeps track of the fragments and their relations to other fragments.
|
||||
--- A fragment can be a dependency (dependencies) or a child (specs) of another fragment.
|
||||
---@class LazyFragments
|
||||
---@field fragments table<number, LazyFragment>
|
||||
---@field frag_stack number[]
|
||||
---@field dep_stack number[]
|
||||
---@field dirty table<number, boolean>
|
||||
---@field plugins table<LazyPlugin, number>
|
||||
---@field spec LazySpecLoader
|
||||
local M = {}
|
||||
|
||||
M._fid = 0
|
||||
|
||||
local function next_id()
|
||||
M._fid = M._fid + 1
|
||||
return M._fid
|
||||
end
|
||||
|
||||
---@param spec LazySpecLoader
|
||||
---@return LazyFragments
|
||||
function M.new(spec)
|
||||
local self = setmetatable({}, { __index = M })
|
||||
self.fragments = {}
|
||||
self.frag_stack = {}
|
||||
self.dep_stack = {}
|
||||
self.spec = spec
|
||||
self.dirty = {}
|
||||
self.plugins = {}
|
||||
return self
|
||||
end
|
||||
|
||||
---@param id number
|
||||
function M:get(id)
|
||||
return self.fragments[id]
|
||||
end
|
||||
|
||||
--- Remove a fragment and all its children.
|
||||
--- This will also remove the fragment from its parent's children list.
|
||||
---@param id number
|
||||
function M:del(id)
|
||||
-- del fragment
|
||||
local fragment = self.fragments[id]
|
||||
if not fragment then
|
||||
return
|
||||
end
|
||||
|
||||
self.dirty[id] = true
|
||||
|
||||
-- remove from parent
|
||||
local pid = fragment.pid
|
||||
if pid then
|
||||
local parent = self.fragments[pid]
|
||||
if parent.frags then
|
||||
---@param fid number
|
||||
parent.frags = Util.filter(function(fid)
|
||||
return fid ~= id
|
||||
end, parent.frags)
|
||||
end
|
||||
if parent.deps then
|
||||
---@param fid number
|
||||
parent.deps = Util.filter(function(fid)
|
||||
return fid ~= id
|
||||
end, parent.deps)
|
||||
end
|
||||
self.dirty[pid] = true
|
||||
end
|
||||
|
||||
-- remove children
|
||||
if fragment.frags then
|
||||
for _, fid in ipairs(fragment.frags) do
|
||||
self:del(fid)
|
||||
end
|
||||
end
|
||||
|
||||
self.fragments[id] = nil
|
||||
end
|
||||
|
||||
--- Add a fragment to the fragments list.
|
||||
--- This also resolves its name, url, dir, dependencies and child specs.
|
||||
---@param plugin LazyPluginSpec
|
||||
function M:add(plugin)
|
||||
if self.plugins[plugin] then
|
||||
return self.fragments[self.plugins[plugin]]
|
||||
end
|
||||
|
||||
local id = next_id()
|
||||
setmetatable(plugin, nil)
|
||||
|
||||
self.plugins[plugin] = id
|
||||
|
||||
local pid = self.frag_stack[#self.frag_stack]
|
||||
|
||||
---@type LazyFragment
|
||||
local fragment = {
|
||||
id = id,
|
||||
pid = pid,
|
||||
name = plugin.name,
|
||||
url = plugin.url,
|
||||
dir = plugin.dir,
|
||||
spec = plugin --[[@as LazyPlugin]],
|
||||
}
|
||||
|
||||
-- short url / ref
|
||||
if plugin[1] then
|
||||
local slash = plugin[1]:find("/", 1, true)
|
||||
if slash then
|
||||
local prefix = plugin[1]:sub(1, 4)
|
||||
if prefix == "http" or prefix == "git@" then
|
||||
fragment.url = fragment.url or plugin[1]
|
||||
else
|
||||
fragment.name = fragment.name or plugin[1]:sub(slash + 1)
|
||||
fragment.url = fragment.url or Config.options.git.url_format:format(plugin[1])
|
||||
end
|
||||
else
|
||||
fragment.name = fragment.name or plugin[1]
|
||||
end
|
||||
end
|
||||
|
||||
-- name
|
||||
fragment.name = fragment.name
|
||||
or fragment.url and self.spec.get_name(fragment.url)
|
||||
or fragment.dir and self.spec.get_name(fragment.dir)
|
||||
if not fragment.name or fragment.name == "" then
|
||||
return self.spec:error("Invalid plugin spec " .. vim.inspect(plugin))
|
||||
end
|
||||
|
||||
if type(plugin.config) == "table" then
|
||||
self.spec:warn(
|
||||
"{" .. fragment.name .. "}: setting a table to `Plugin.config` is deprecated. Please use `Plugin.opts` instead"
|
||||
)
|
||||
---@diagnostic disable-next-line: assign-type-mismatch
|
||||
plugin.opts = plugin.config
|
||||
plugin.config = nil
|
||||
end
|
||||
|
||||
self.fragments[id] = fragment
|
||||
|
||||
-- add to parent
|
||||
if pid then
|
||||
local parent = self.fragments[pid]
|
||||
parent.frags = parent.frags or {}
|
||||
table.insert(parent.frags, id)
|
||||
end
|
||||
|
||||
-- add to parent's deps
|
||||
local did = self.dep_stack[#self.dep_stack]
|
||||
if did and did == pid then
|
||||
fragment.dep = true
|
||||
local parent = self.fragments[did]
|
||||
parent.deps = parent.deps or {}
|
||||
table.insert(parent.deps, id)
|
||||
end
|
||||
|
||||
table.insert(self.frag_stack, id)
|
||||
-- dependencies
|
||||
if plugin.dependencies then
|
||||
table.insert(self.dep_stack, id)
|
||||
self.spec:normalize(plugin.dependencies)
|
||||
table.remove(self.dep_stack)
|
||||
end
|
||||
-- child specs
|
||||
if plugin.specs then
|
||||
self.spec:normalize(plugin.specs)
|
||||
end
|
||||
table.remove(self.frag_stack)
|
||||
|
||||
return fragment
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,158 +0,0 @@
|
|||
local Util = require("lazy.core.util")
|
||||
local Loader = require("lazy.core.loader")
|
||||
|
||||
---@class LazyPluginHandlers
|
||||
---@field event? string|string[]
|
||||
---@field cmd? string|string[]
|
||||
---@field ft? string|string[]
|
||||
---@field module? string|string[]
|
||||
---@field keys? string|string[]
|
||||
|
||||
local M = {}
|
||||
|
||||
---@alias LazyHandler fun(grouped:table<string, string[]>)
|
||||
|
||||
---@type table<string, table<string, string[]>>
|
||||
M._groups = nil
|
||||
|
||||
---@param plugins LazyPlugin[]
|
||||
---@param rebuild? boolean
|
||||
function M.group(plugins, rebuild)
|
||||
if M._groups == nil or rebuild then
|
||||
M._groups = {}
|
||||
local types = vim.tbl_keys(M.handlers) --[[@as string[] ]]
|
||||
for _, key in ipairs(types) do
|
||||
M._groups[key] = {}
|
||||
for _, plugin in pairs(plugins) do
|
||||
if plugin[key] then
|
||||
---@diagnostic disable-next-line: no-unknown
|
||||
for _, value in pairs(type(plugin[key]) == "table" and plugin[key] or { plugin[key] }) do
|
||||
M._groups[key][value] = M._groups[key][value] or {}
|
||||
table.insert(M._groups[key][value], plugin.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return M._groups
|
||||
end
|
||||
|
||||
---@type table<string, LazyHandler>
|
||||
M.handlers = {}
|
||||
|
||||
function M.handlers.event(grouped)
|
||||
local group = vim.api.nvim_create_augroup("lazy_handler_event", { clear = true })
|
||||
for event, plugins in pairs(grouped) do
|
||||
---@cast event string
|
||||
if event == "VimEnter" and vim.v.vim_did_enter == 1 then
|
||||
Loader.load(plugins, { event = event })
|
||||
else
|
||||
local _event, pattern = event:match("^(%w+)%s+(.*)$")
|
||||
vim.api.nvim_create_autocmd(_event or event, {
|
||||
group = group,
|
||||
once = true,
|
||||
pattern = pattern,
|
||||
callback = function()
|
||||
Util.track({ event = event })
|
||||
Loader.load(plugins, { event = event })
|
||||
Util.track()
|
||||
end,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M.handlers.keys(grouped)
|
||||
for keys, plugins in pairs(grouped) do
|
||||
---@cast keys string
|
||||
vim.keymap.set("n", keys, function()
|
||||
vim.keymap.del("n", keys)
|
||||
Util.track({ keys = keys })
|
||||
Loader.load(plugins, { keys = keys })
|
||||
vim.api.nvim_input(keys)
|
||||
Util.track()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function M.handlers.ft(grouped)
|
||||
local group = vim.api.nvim_create_augroup("lazy_handler_ft", { clear = true })
|
||||
for ft, plugins in pairs(grouped) do
|
||||
---@cast ft string
|
||||
vim.api.nvim_create_autocmd("FileType", {
|
||||
once = true,
|
||||
pattern = ft,
|
||||
group = group,
|
||||
callback = function()
|
||||
Util.track({ ft = ft })
|
||||
Loader.load(plugins, { ft = ft })
|
||||
Util.track()
|
||||
end,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function M.handlers.cmd(grouped)
|
||||
for cmd, plugins in pairs(grouped) do
|
||||
---@cast cmd string
|
||||
local function _load()
|
||||
vim.api.nvim_del_user_command(cmd)
|
||||
Util.track({ cmd = cmd })
|
||||
Loader.load(plugins, { cmd = cmd })
|
||||
Util.track()
|
||||
end
|
||||
vim.api.nvim_create_user_command(cmd, function(event)
|
||||
_load()
|
||||
vim.cmd(
|
||||
("%s %s%s%s %s"):format(
|
||||
event.mods or "",
|
||||
event.line1 == event.line2 and "" or event.line1 .. "," .. event.line2,
|
||||
cmd,
|
||||
event.bang and "!" or "",
|
||||
event.args or ""
|
||||
)
|
||||
)
|
||||
end, {
|
||||
bang = true,
|
||||
nargs = "*",
|
||||
complete = function()
|
||||
_load()
|
||||
-- HACK: trick Neovim to show the newly loaded command completion
|
||||
vim.api.nvim_input("<space><bs><tab>")
|
||||
end,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function M.handlers.module(grouped)
|
||||
---@param modname string
|
||||
table.insert(package.loaders, 2, function(modname)
|
||||
local idx = modname:find(".", 1, true) or #modname + 1
|
||||
while idx do
|
||||
local name = modname:sub(1, idx - 1)
|
||||
---@diagnostic disable-next-line: redefined-local
|
||||
local plugins = grouped[name]
|
||||
if plugins then
|
||||
grouped[name] = nil
|
||||
local reason = { require = modname }
|
||||
if #Loader.loading == 0 then
|
||||
local f = 3
|
||||
while not reason.source do
|
||||
local info = debug.getinfo(f, "S")
|
||||
if not info then
|
||||
break
|
||||
end
|
||||
if info.what ~= "C" then
|
||||
reason.source = info.source:sub(2)
|
||||
end
|
||||
f = f + 1
|
||||
end
|
||||
end
|
||||
Loader.load(plugins, reason)
|
||||
end
|
||||
idx = modname:find(".", idx + 1, true)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
66
lua/lazy/core/handler/cmd.lua
Normal file
66
lua/lazy/core/handler/cmd.lua
Normal file
|
@ -0,0 +1,66 @@
|
|||
local Loader = require("lazy.core.loader")
|
||||
local Util = require("lazy.core.util")
|
||||
|
||||
---@class LazyCmdHandler:LazyHandler
|
||||
local M = {}
|
||||
|
||||
function M:_load(cmd)
|
||||
vim.api.nvim_del_user_command(cmd)
|
||||
Util.track({ cmd = cmd })
|
||||
Loader.load(self.active[cmd], { cmd = cmd })
|
||||
Util.track()
|
||||
end
|
||||
|
||||
---@param cmd string
|
||||
function M:_add(cmd)
|
||||
vim.api.nvim_create_user_command(cmd, function(event)
|
||||
local command = {
|
||||
cmd = cmd,
|
||||
bang = event.bang or nil,
|
||||
mods = event.smods,
|
||||
args = event.fargs,
|
||||
count = event.count >= 0 and event.range == 0 and event.count or nil,
|
||||
}
|
||||
|
||||
if event.range == 1 then
|
||||
command.range = { event.line1 }
|
||||
elseif event.range == 2 then
|
||||
command.range = { event.line1, event.line2 }
|
||||
end
|
||||
|
||||
---@type string
|
||||
local plugins = "`" .. table.concat(vim.tbl_values(self.active[cmd]), ", ") .. "`"
|
||||
|
||||
self:_load(cmd)
|
||||
|
||||
local info = vim.api.nvim_get_commands({})[cmd] or vim.api.nvim_buf_get_commands(0, {})[cmd]
|
||||
if not info then
|
||||
vim.schedule(function()
|
||||
Util.error("Command `" .. cmd .. "` not found after loading " .. plugins)
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
command.nargs = info.nargs
|
||||
if event.args and event.args ~= "" and info.nargs and info.nargs:find("[1?]") then
|
||||
command.args = { event.args }
|
||||
end
|
||||
vim.cmd(command)
|
||||
end, {
|
||||
bang = true,
|
||||
range = true,
|
||||
nargs = "*",
|
||||
complete = function(_, line)
|
||||
self:_load(cmd)
|
||||
-- NOTE: return the newly loaded command completion
|
||||
return vim.fn.getcompletion(line, "cmdline")
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
---@param value string
|
||||
function M:_del(value)
|
||||
pcall(vim.api.nvim_del_user_command, value)
|
||||
end
|
||||
|
||||
return M
|
171
lua/lazy/core/handler/event.lua
Normal file
171
lua/lazy/core/handler/event.lua
Normal file
|
@ -0,0 +1,171 @@
|
|||
local Config = require("lazy.core.config")
|
||||
local Loader = require("lazy.core.loader")
|
||||
local Util = require("lazy.core.util")
|
||||
|
||||
---@class LazyEventOpts
|
||||
---@field event string
|
||||
---@field group? string
|
||||
---@field exclude? string[]
|
||||
---@field data? any
|
||||
---@field buffer? number
|
||||
|
||||
---@alias LazyEvent {id:string, event:string[]|string, pattern?:string[]|string}
|
||||
---@alias LazyEventSpec string|{event?:string|string[], pattern?:string|string[]}|string[]
|
||||
|
||||
---@class LazyEventHandler:LazyHandler
|
||||
---@field events table<string,true>
|
||||
---@field group number
|
||||
local M = {}
|
||||
|
||||
-- Event dependencies
|
||||
M.triggers = {
|
||||
FileType = "BufReadPost",
|
||||
BufReadPost = "BufReadPre",
|
||||
}
|
||||
|
||||
-- A table of mappings for custom events
|
||||
-- Can be used by distros to add custom events (see usage in LazyVim)
|
||||
---@type table<string, LazyEvent>
|
||||
M.mappings = {
|
||||
VeryLazy = { id = "VeryLazy", event = "User", pattern = "VeryLazy" },
|
||||
-- Example:
|
||||
-- LazyFile = { id = "LazyFile", event = { "BufReadPost", "BufNewFile", "BufWritePre" } },
|
||||
}
|
||||
M.mappings["User VeryLazy"] = M.mappings.VeryLazy
|
||||
|
||||
M.group = vim.api.nvim_create_augroup("lazy_handler_event", { clear = true })
|
||||
|
||||
---@param spec LazyEventSpec
|
||||
---@return LazyEvent
|
||||
function M:_parse(spec)
|
||||
local ret = M.mappings[spec] --[[@as LazyEvent?]]
|
||||
if ret then
|
||||
return ret
|
||||
end
|
||||
if type(spec) == "string" then
|
||||
local event, pattern = spec:match("^(%w+)%s+(.*)$")
|
||||
event = event or spec
|
||||
return { id = spec, event = event, pattern = pattern }
|
||||
elseif Util.is_list(spec) then
|
||||
ret = { id = table.concat(spec, "|"), event = spec }
|
||||
else
|
||||
ret = spec --[[@as LazyEvent]]
|
||||
if not ret.id then
|
||||
---@diagnostic disable-next-line: assign-type-mismatch, param-type-mismatch
|
||||
ret.id = type(ret.event) == "string" and ret.event or table.concat(ret.event, "|")
|
||||
if ret.pattern then
|
||||
---@diagnostic disable-next-line: assign-type-mismatch, param-type-mismatch
|
||||
ret.id = ret.id .. " " .. (type(ret.pattern) == "string" and ret.pattern or table.concat(ret.pattern, ", "))
|
||||
end
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
---@param event LazyEvent
|
||||
function M:_add(event)
|
||||
local done = false
|
||||
vim.api.nvim_create_autocmd(event.event, {
|
||||
group = self.group,
|
||||
once = true,
|
||||
pattern = event.pattern,
|
||||
callback = function(ev)
|
||||
if done or not self.active[event.id] then
|
||||
return
|
||||
end
|
||||
-- HACK: work-around for https://github.com/neovim/neovim/issues/25526
|
||||
done = true
|
||||
if event.id ~= "VeryLazy" then
|
||||
Util.track({ [self.type] = event.id })
|
||||
end
|
||||
|
||||
local state = M.get_state(ev.event, ev.buf, ev.data)
|
||||
|
||||
-- load the plugins
|
||||
Loader.load(self.active[event.id], { [self.type] = event.id })
|
||||
|
||||
-- check if any plugin created an event handler for this event and fire the group
|
||||
for _, s in ipairs(state) do
|
||||
M.trigger(s)
|
||||
end
|
||||
if event.id ~= "VeryLazy" then
|
||||
Util.track()
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
-- Get the current state of the event and all the events that will be fired
|
||||
---@param event string
|
||||
---@param buf number
|
||||
---@param data any
|
||||
function M.get_state(event, buf, data)
|
||||
local state = {} ---@type LazyEventOpts[]
|
||||
while event do
|
||||
table.insert(state, 1, {
|
||||
event = event,
|
||||
exclude = event ~= "FileType" and M.get_augroups(event) or nil,
|
||||
buffer = buf,
|
||||
data = data,
|
||||
})
|
||||
data = nil -- only pass the data to the first event
|
||||
event = M.triggers[event]
|
||||
end
|
||||
return state
|
||||
end
|
||||
|
||||
-- Get all augroups for the events
|
||||
---@param event string
|
||||
function M.get_augroups(event)
|
||||
local groups = {} ---@type string[]
|
||||
for _, autocmd in ipairs(vim.api.nvim_get_autocmds({ event = event })) do
|
||||
if autocmd.group_name then
|
||||
table.insert(groups, autocmd.group_name)
|
||||
end
|
||||
end
|
||||
return groups
|
||||
end
|
||||
|
||||
-- Trigger an event. When a group is given, only the events in that group will be triggered.
|
||||
-- When exclude is set, the events in those groups will be skipped.
|
||||
---@param opts LazyEventOpts
|
||||
function M.trigger(opts)
|
||||
if opts.group or opts.exclude == nil then
|
||||
return M._trigger(opts)
|
||||
end
|
||||
local done = {} ---@type table<string,true>
|
||||
for _, autocmd in ipairs(vim.api.nvim_get_autocmds({ event = opts.event })) do
|
||||
local id = autocmd.event .. ":" .. (autocmd.group or "") ---@type string
|
||||
local skip = done[id] or (opts.exclude and vim.tbl_contains(opts.exclude, autocmd.group_name))
|
||||
done[id] = true
|
||||
if autocmd.group and not skip then
|
||||
opts.group = autocmd.group_name
|
||||
M._trigger(opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Trigger an event
|
||||
---@param opts LazyEventOpts
|
||||
function M._trigger(opts)
|
||||
if Config.options.debug then
|
||||
Util.info({
|
||||
"# Firing Events",
|
||||
" - **event:** " .. opts.event,
|
||||
opts.group and (" - **group:** " .. opts.group),
|
||||
opts.buffer and (" - **buffer:** " .. opts.buffer),
|
||||
})
|
||||
end
|
||||
Util.track({ event = opts.group or opts.event })
|
||||
Util.try(function()
|
||||
vim.api.nvim_exec_autocmds(opts.event, {
|
||||
buffer = opts.buffer,
|
||||
group = opts.group,
|
||||
modeline = false,
|
||||
data = opts.data,
|
||||
})
|
||||
Util.track()
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
25
lua/lazy/core/handler/ft.lua
Normal file
25
lua/lazy/core/handler/ft.lua
Normal file
|
@ -0,0 +1,25 @@
|
|||
local Event = require("lazy.core.handler.event")
|
||||
local Loader = require("lazy.core.loader")
|
||||
|
||||
---@class LazyFiletypeHandler:LazyEventHandler
|
||||
local M = {}
|
||||
M.extends = Event
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
function M:add(plugin)
|
||||
self.super.add(self, plugin)
|
||||
if plugin.ft then
|
||||
Loader.ftdetect(plugin.dir)
|
||||
end
|
||||
end
|
||||
|
||||
---@return LazyEvent
|
||||
function M:_parse(value)
|
||||
return {
|
||||
id = value,
|
||||
event = "FileType",
|
||||
pattern = value,
|
||||
}
|
||||
end
|
||||
|
||||
return M
|
139
lua/lazy/core/handler/init.lua
Normal file
139
lua/lazy/core/handler/init.lua
Normal file
|
@ -0,0 +1,139 @@
|
|||
local Config = require("lazy.core.config")
|
||||
local Util = require("lazy.core.util")
|
||||
|
||||
---@class LazyHandler
|
||||
---@field type LazyHandlerTypes
|
||||
---@field extends? LazyHandler
|
||||
---@field active table<string,table<string,string>>
|
||||
---@field managed table<string,string> mapping handler keys to plugin names
|
||||
---@field super LazyHandler
|
||||
local M = {}
|
||||
|
||||
---@enum LazyHandlerTypes
|
||||
M.types = {
|
||||
keys = "keys",
|
||||
event = "event",
|
||||
cmd = "cmd",
|
||||
ft = "ft",
|
||||
}
|
||||
|
||||
---@type table<string,LazyHandler>
|
||||
M.handlers = {}
|
||||
|
||||
M.did_setup = false
|
||||
|
||||
function M.init()
|
||||
for _, type in pairs(M.types) do
|
||||
M.handlers[type] = M.new(type)
|
||||
end
|
||||
end
|
||||
|
||||
function M.setup()
|
||||
M.did_setup = true
|
||||
for _, plugin in pairs(Config.plugins) do
|
||||
Util.try(function()
|
||||
M.enable(plugin)
|
||||
end, "Failed to setup handlers for " .. plugin.name)
|
||||
end
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
function M.disable(plugin)
|
||||
for type in pairs(plugin._.handlers or {}) do
|
||||
M.handlers[type]:del(plugin)
|
||||
end
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
function M.enable(plugin)
|
||||
if not plugin._.loaded then
|
||||
if not plugin._.handlers then
|
||||
M.resolve(plugin)
|
||||
end
|
||||
for type in pairs(plugin._.handlers or {}) do
|
||||
M.handlers[type]:add(plugin)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param type LazyHandlerTypes
|
||||
function M.new(type)
|
||||
---@type LazyHandler
|
||||
local handler = require("lazy.core.handler." .. type)
|
||||
local super = handler.extends or M
|
||||
local self = setmetatable({}, { __index = setmetatable(handler, { __index = super }) })
|
||||
self.super = super
|
||||
self.active = {}
|
||||
self.managed = {}
|
||||
self.type = type
|
||||
return self
|
||||
end
|
||||
|
||||
---@param _value string
|
||||
---@protected
|
||||
function M:_add(_value) end
|
||||
|
||||
---@param _value string
|
||||
---@protected
|
||||
function M:_del(_value) end
|
||||
|
||||
---@param value any
|
||||
---@param _plugin LazyPlugin
|
||||
---@return string|{id:string}
|
||||
function M:_parse(value, _plugin)
|
||||
assert(type(value) == "string", "Expected string, got " .. vim.inspect(value))
|
||||
return value
|
||||
end
|
||||
|
||||
---@param values any[]
|
||||
---@param plugin LazyPlugin
|
||||
function M:_values(values, plugin)
|
||||
---@type table<string,any>
|
||||
local ret = {}
|
||||
for _, value in ipairs(values) do
|
||||
local parsed = self:_parse(value, plugin)
|
||||
ret[type(parsed) == "string" and parsed or parsed.id] = parsed
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
function M.resolve(plugin)
|
||||
local Plugin = require("lazy.core.plugin")
|
||||
plugin._.handlers = {}
|
||||
for type, handler in pairs(M.handlers) do
|
||||
if plugin[type] then
|
||||
plugin._.handlers[type] = handler:_values(Plugin.values(plugin, type, true), plugin)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
function M:add(plugin)
|
||||
for key, value in pairs(plugin._.handlers[self.type] or {}) do
|
||||
if not self.active[key] then
|
||||
self.active[key] = {}
|
||||
self:_add(value)
|
||||
self.managed[key] = plugin.name
|
||||
end
|
||||
self.active[key][plugin.name] = plugin.name
|
||||
end
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
function M:del(plugin)
|
||||
if not plugin._.handlers then
|
||||
return
|
||||
end
|
||||
for key, value in pairs(plugin._.handlers[self.type] or {}) do
|
||||
if self.active[key] and self.active[key][plugin.name] then
|
||||
self.active[key][plugin.name] = nil
|
||||
if vim.tbl_isempty(self.active[key]) then
|
||||
self:_del(value)
|
||||
self.active[key] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
202
lua/lazy/core/handler/keys.lua
Normal file
202
lua/lazy/core/handler/keys.lua
Normal file
|
@ -0,0 +1,202 @@
|
|||
local Loader = require("lazy.core.loader")
|
||||
local Util = require("lazy.core.util")
|
||||
|
||||
---@class LazyKeysBase
|
||||
---@field desc? string
|
||||
---@field noremap? boolean
|
||||
---@field remap? boolean
|
||||
---@field expr? boolean
|
||||
---@field nowait? boolean
|
||||
---@field ft? string|string[]
|
||||
|
||||
---@class LazyKeysSpec: LazyKeysBase
|
||||
---@field [1] string lhs
|
||||
---@field [2]? string|fun():string?|false rhs
|
||||
---@field mode? string|string[]
|
||||
|
||||
---@class LazyKeys: LazyKeysBase
|
||||
---@field lhs string lhs
|
||||
---@field rhs? string|fun() rhs
|
||||
---@field mode? string
|
||||
---@field id string
|
||||
---@field name string
|
||||
|
||||
---@class LazyKeysHandler:LazyHandler
|
||||
local M = {}
|
||||
|
||||
local skip = { mode = true, id = true, ft = true, rhs = true, lhs = true }
|
||||
|
||||
---@param value string|LazyKeysSpec
|
||||
---@param mode? string
|
||||
---@return LazyKeys
|
||||
function M.parse(value, mode)
|
||||
value = type(value) == "string" and { value } or value --[[@as LazyKeysSpec]]
|
||||
local ret = vim.deepcopy(value) --[[@as LazyKeys]]
|
||||
ret.lhs = ret[1] or ""
|
||||
ret.rhs = ret[2]
|
||||
---@diagnostic disable-next-line: no-unknown
|
||||
ret[1] = nil
|
||||
---@diagnostic disable-next-line: no-unknown
|
||||
ret[2] = nil
|
||||
ret.mode = mode or "n"
|
||||
ret.id = vim.api.nvim_replace_termcodes(ret.lhs, true, true, true)
|
||||
|
||||
if ret.ft then
|
||||
local ft = type(ret.ft) == "string" and { ret.ft } or ret.ft --[[@as string[] ]]
|
||||
ret.id = ret.id .. " (" .. table.concat(ft, ", ") .. ")"
|
||||
end
|
||||
|
||||
if ret.mode ~= "n" then
|
||||
ret.id = ret.id .. " (" .. ret.mode .. ")"
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
---@param keys LazyKeys
|
||||
function M.to_string(keys)
|
||||
return keys.lhs .. (keys.mode == "n" and "" or " (" .. keys.mode .. ")")
|
||||
end
|
||||
|
||||
---@param lhs string
|
||||
---@param mode? string
|
||||
function M:have(lhs, mode)
|
||||
local keys = M.parse(lhs, mode)
|
||||
return self.managed[keys.id] ~= nil
|
||||
end
|
||||
|
||||
function M:_values(values)
|
||||
return M.resolve(values)
|
||||
end
|
||||
|
||||
---@param spec? (string|LazyKeysSpec)[]
|
||||
function M.resolve(spec)
|
||||
---@type LazyKeys[]
|
||||
local values = {}
|
||||
---@diagnostic disable-next-line: no-unknown
|
||||
for _, value in ipairs(spec or {}) do
|
||||
value = type(value) == "string" and { value } or value --[[@as LazyKeysSpec]]
|
||||
value.mode = value.mode or "n"
|
||||
local modes = (type(value.mode) == "table" and value.mode or { value.mode }) --[=[@as string[]]=]
|
||||
for _, mode in ipairs(modes) do
|
||||
local keys = M.parse(value, mode)
|
||||
if keys.rhs == vim.NIL or keys.rhs == false then
|
||||
values[keys.id] = nil
|
||||
else
|
||||
values[keys.id] = keys
|
||||
end
|
||||
end
|
||||
end
|
||||
return values
|
||||
end
|
||||
|
||||
---@param keys LazyKeys
|
||||
function M.opts(keys)
|
||||
local opts = {} ---@type LazyKeysBase
|
||||
---@diagnostic disable-next-line: no-unknown
|
||||
for k, v in pairs(keys) do
|
||||
if type(k) ~= "number" and not skip[k] then
|
||||
---@diagnostic disable-next-line: no-unknown
|
||||
opts[k] = v
|
||||
end
|
||||
end
|
||||
return opts
|
||||
end
|
||||
|
||||
---@param keys LazyKeys
|
||||
function M.is_nop(keys)
|
||||
return type(keys.rhs) == "string" and (keys.rhs == "" or keys.rhs:lower() == "<nop>")
|
||||
end
|
||||
|
||||
---@param keys LazyKeys
|
||||
function M:_add(keys)
|
||||
local lhs = keys.lhs
|
||||
local opts = M.opts(keys)
|
||||
|
||||
---@param buf? number
|
||||
local function add(buf)
|
||||
if M.is_nop(keys) then
|
||||
return self:_set(keys, buf)
|
||||
end
|
||||
|
||||
vim.keymap.set(keys.mode, lhs, function()
|
||||
local plugins = self.active[keys.id]
|
||||
|
||||
-- always delete the mapping immediately to prevent recursive mappings
|
||||
self:_del(keys)
|
||||
self.active[keys.id] = nil
|
||||
|
||||
if plugins then
|
||||
local name = M.to_string(keys)
|
||||
Util.track({ keys = name })
|
||||
Loader.load(plugins, { keys = name })
|
||||
Util.track()
|
||||
end
|
||||
|
||||
if keys.mode:sub(-1) == "a" then
|
||||
lhs = lhs .. "<C-]>"
|
||||
end
|
||||
local feed = vim.api.nvim_replace_termcodes("<Ignore>" .. lhs, true, true, true)
|
||||
-- insert instead of append the lhs
|
||||
vim.api.nvim_feedkeys(feed, "i", false)
|
||||
end, {
|
||||
desc = opts.desc,
|
||||
nowait = opts.nowait,
|
||||
-- we do not return anything, but this is still needed to make operator pending mappings work
|
||||
expr = true,
|
||||
buffer = buf,
|
||||
})
|
||||
end
|
||||
|
||||
-- buffer-local mappings
|
||||
if keys.ft then
|
||||
vim.api.nvim_create_autocmd("FileType", {
|
||||
pattern = keys.ft,
|
||||
callback = function(event)
|
||||
if self.active[keys.id] and not M.is_nop(keys) then
|
||||
add(event.buf)
|
||||
else
|
||||
-- Only create the mapping if its managed by lazy
|
||||
-- otherwise the plugin is supposed to manage it
|
||||
self:_set(keys, event.buf)
|
||||
end
|
||||
end,
|
||||
})
|
||||
else
|
||||
add()
|
||||
end
|
||||
end
|
||||
|
||||
-- Delete a mapping and create the real global/buffer-local
|
||||
-- mapping when needed
|
||||
---@param keys LazyKeys
|
||||
function M:_del(keys)
|
||||
-- bufs will be all buffers of the filetype for a buffer-local mapping
|
||||
-- OR `false` for a global mapping
|
||||
local bufs = { false }
|
||||
|
||||
if keys.ft then
|
||||
local ft = type(keys.ft) == "string" and { keys.ft } or keys.ft --[[@as string[] ]]
|
||||
bufs = vim.tbl_filter(function(buf)
|
||||
return vim.tbl_contains(ft, vim.bo[buf].filetype)
|
||||
end, vim.api.nvim_list_bufs())
|
||||
end
|
||||
|
||||
for _, buf in ipairs(bufs) do
|
||||
pcall(vim.keymap.del, keys.mode, keys.lhs, { buffer = buf or nil })
|
||||
self:_set(keys, buf or nil)
|
||||
end
|
||||
end
|
||||
|
||||
-- Create a mapping if it is managed by lazy
|
||||
---@param keys LazyKeys
|
||||
---@param buf number?
|
||||
function M:_set(keys, buf)
|
||||
if keys.rhs then
|
||||
local opts = M.opts(keys)
|
||||
---@diagnostic disable-next-line: inject-field
|
||||
opts.buffer = buf
|
||||
vim.keymap.set(keys.mode, keys.lhs, keys.rhs, opts)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,104 +1,576 @@
|
|||
local Util = require("lazy.core.util")
|
||||
local Cache = require("lazy.core.cache")
|
||||
local Config = require("lazy.core.config")
|
||||
local Handler = require("lazy.core.handler")
|
||||
local Plugin = require("lazy.core.plugin")
|
||||
local Util = require("lazy.core.util")
|
||||
|
||||
---@class LazyCoreLoader
|
||||
local M = {}
|
||||
|
||||
local DEFAULT_PRIORITY = 50
|
||||
|
||||
---@type LazyPlugin[]
|
||||
M.loading = {}
|
||||
M.init_done = false
|
||||
---@type table<string,true>
|
||||
M.disabled_rtp_plugins = { packer_compiled = true }
|
||||
---@type table<string,string>
|
||||
M.did_ftdetect = {}
|
||||
M.did_handlers = false
|
||||
|
||||
function M.disable_rtp_plugin(plugin)
|
||||
M.disabled_rtp_plugins[plugin] = true
|
||||
end
|
||||
|
||||
function M.setup()
|
||||
local Handler = require("lazy.core.handler")
|
||||
local groups = Handler.group(Config.plugins)
|
||||
for t, handler in pairs(Handler.handlers) do
|
||||
if groups[t] then
|
||||
Util.track(t)
|
||||
handler(groups[t])
|
||||
Util.track()
|
||||
for _, file in ipairs(Config.options.performance.rtp.disabled_plugins) do
|
||||
M.disable_rtp_plugin(file)
|
||||
end
|
||||
|
||||
vim.api.nvim_create_autocmd("ColorSchemePre", {
|
||||
callback = function(event)
|
||||
M.colorscheme(event.match)
|
||||
end,
|
||||
})
|
||||
|
||||
-- load the plugins
|
||||
Plugin.load()
|
||||
Handler.init()
|
||||
|
||||
-- install missing plugins
|
||||
if Config.options.install.missing then
|
||||
Util.track("install")
|
||||
local count = 0
|
||||
while M.install_missing() do
|
||||
count = count + 1
|
||||
if count > 5 then
|
||||
Util.error("Too many rounds of missing plugins")
|
||||
break
|
||||
end
|
||||
end
|
||||
Util.track()
|
||||
end
|
||||
Config.mapleader = vim.g.mapleader
|
||||
Config.maplocalleader = vim.g.maplocalleader
|
||||
|
||||
-- report any warnings & errors
|
||||
Config.spec:report()
|
||||
|
||||
-- setup handlers
|
||||
Util.track("handlers")
|
||||
Handler.setup()
|
||||
M.did_handlers = true
|
||||
Util.track()
|
||||
end
|
||||
|
||||
-- this will incrementally install missing plugins
|
||||
-- multiple rounds can happen when importing a spec from a missing plugin
|
||||
function M.install_missing()
|
||||
for _, plugin in pairs(Config.plugins) do
|
||||
local installed = plugin._.installed
|
||||
local has_errors = Plugin.has_errors(plugin)
|
||||
|
||||
if not has_errors and not (installed and not plugin._.build) then
|
||||
for _, colorscheme in ipairs(Config.options.install.colorscheme) do
|
||||
if colorscheme == "default" then
|
||||
break
|
||||
end
|
||||
M.colorscheme(colorscheme)
|
||||
if vim.g.colors_name or pcall(vim.cmd.colorscheme, colorscheme) then
|
||||
break
|
||||
end
|
||||
end
|
||||
Cache.reset()
|
||||
require("lazy.manage").install({ wait = true, lockfile = true, clear = false })
|
||||
-- remove any installed plugins from indexed, so cache will index again
|
||||
for _, p in pairs(Config.plugins) do
|
||||
if p._.installed then
|
||||
Cache.reset(p.dir)
|
||||
end
|
||||
end
|
||||
-- reload plugins
|
||||
Plugin.load()
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M.init_plugins()
|
||||
Util.track("plugin_init")
|
||||
-- Startup sequence
|
||||
-- 1. load any start plugins and do init
|
||||
function M.startup()
|
||||
Util.track({ start = "startup" })
|
||||
|
||||
-- load filetype.lua first since plugins might depend on that
|
||||
M.source(vim.env.VIMRUNTIME .. "/filetype.lua")
|
||||
|
||||
-- backup original rtp
|
||||
local rtp = vim.opt.rtp:get() --[[@as string[] ]]
|
||||
|
||||
-- 1. run plugin init
|
||||
Util.track({ start = "init" })
|
||||
for _, plugin in pairs(Config.plugins) do
|
||||
if plugin.init then
|
||||
Util.track({ plugin = plugin.name, start = "init" })
|
||||
Util.try(plugin.init, "Failed to run `init` for **" .. plugin.name .. "**")
|
||||
Util.track({ plugin = plugin.name, init = "init" })
|
||||
Util.try(function()
|
||||
plugin.init(plugin)
|
||||
end, "Failed to run `init` for **" .. plugin.name .. "**")
|
||||
Util.track()
|
||||
end
|
||||
if plugin.opt == false then
|
||||
end
|
||||
Util.track()
|
||||
|
||||
-- 2. load start plugin
|
||||
Util.track({ start = "start" })
|
||||
for _, plugin in ipairs(M.get_start_plugins()) do
|
||||
-- plugin may be loaded by another plugin in the meantime
|
||||
if not plugin._.loaded then
|
||||
M.load(plugin, { start = "start" })
|
||||
end
|
||||
end
|
||||
Util.track()
|
||||
|
||||
-- 3. load plugins from the original rtp, excluding after
|
||||
Util.track({ start = "rtp plugins" })
|
||||
for _, path in ipairs(rtp) do
|
||||
if not path:find("after/?$") then
|
||||
-- these paths don't will already have their ftdetect ran,
|
||||
-- by sourcing filetype.lua above, so skip them
|
||||
M.did_ftdetect[path] = path
|
||||
M.packadd(path)
|
||||
end
|
||||
end
|
||||
Util.track()
|
||||
|
||||
-- 4. load after plugins
|
||||
Util.track({ start = "after" })
|
||||
for _, path in
|
||||
ipairs(vim.opt.rtp:get() --[[@as string[] ]])
|
||||
do
|
||||
if path:find("after/?$") then
|
||||
M.source_runtime(path, "plugin")
|
||||
end
|
||||
end
|
||||
Util.track()
|
||||
|
||||
M.init_done = true
|
||||
|
||||
Util.track()
|
||||
end
|
||||
|
||||
function M.get_start_plugins()
|
||||
---@type LazyPlugin[]
|
||||
local start = {}
|
||||
for _, plugin in pairs(Config.plugins) do
|
||||
if not plugin._.loaded and (plugin._.rtp_loaded or plugin.lazy == false) then
|
||||
start[#start + 1] = plugin
|
||||
end
|
||||
end
|
||||
table.sort(start, function(a, b)
|
||||
local ap = a.priority or DEFAULT_PRIORITY
|
||||
local bp = b.priority or DEFAULT_PRIORITY
|
||||
return ap > bp
|
||||
end)
|
||||
return start
|
||||
end
|
||||
|
||||
---@class Loader
|
||||
---@param plugins string|LazyPlugin|string[]|LazyPlugin[]
|
||||
---@param reason {[string]:string}
|
||||
---@param opts? {load_start: boolean}
|
||||
---@param opts? {force:boolean} when force is true, we skip the cond check
|
||||
function M.load(plugins, reason, opts)
|
||||
---@diagnostic disable-next-line: cast-local-type
|
||||
plugins = type(plugins) == "string" or plugins.name and { plugins } or plugins
|
||||
plugins = (type(plugins) == "string" or plugins.name) and { plugins } or plugins
|
||||
---@cast plugins (string|LazyPlugin)[]
|
||||
|
||||
for _, plugin in ipairs(plugins) do
|
||||
plugin = type(plugin) == "string" and Config.plugins[plugin] or plugin
|
||||
---@cast plugin LazyPlugin
|
||||
|
||||
if not plugin._.loaded then
|
||||
---@diagnostic disable-next-line: assign-type-mismatch
|
||||
plugin._.loaded = {}
|
||||
for k, v in pairs(reason) do
|
||||
plugin._.loaded[k] = v
|
||||
for _, plugin in pairs(plugins) do
|
||||
if type(plugin) == "string" then
|
||||
if Config.plugins[plugin] then
|
||||
plugin = Config.plugins[plugin]
|
||||
elseif Config.spec.disabled[plugin] then
|
||||
plugin = nil
|
||||
else
|
||||
Util.error("Plugin " .. plugin .. " not found")
|
||||
plugin = nil
|
||||
end
|
||||
if #M.loading > 0 then
|
||||
plugin._.loaded.plugin = M.loading[#M.loading].name
|
||||
end
|
||||
|
||||
table.insert(M.loading, plugin)
|
||||
|
||||
Util.track({ plugin = plugin.name, start = reason.start })
|
||||
M.packadd(plugin, opts and opts.load_start)
|
||||
|
||||
if plugin.dependencies then
|
||||
M.load(plugin.dependencies, {})
|
||||
end
|
||||
|
||||
if plugin.config then
|
||||
Util.try(plugin.config, "Failed to run `config` for " .. plugin.name)
|
||||
end
|
||||
|
||||
plugin._.loaded.time = Util.track().time
|
||||
table.remove(M.loading)
|
||||
vim.schedule(function()
|
||||
vim.cmd("do User LazyRender")
|
||||
end)
|
||||
end
|
||||
if plugin and not plugin._.loaded then
|
||||
M._load(plugin, reason, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
function M.packadd(plugin, load_start)
|
||||
if plugin.opt then
|
||||
vim.cmd.packadd(plugin.name)
|
||||
M.source_plugin_files(plugin, true)
|
||||
elseif load_start then
|
||||
vim.opt.runtimepath:append(plugin.dir)
|
||||
M.source_plugin_files(plugin)
|
||||
M.source_plugin_files(plugin, true)
|
||||
function M.deactivate(plugin)
|
||||
if not plugin._.loaded then
|
||||
return
|
||||
end
|
||||
|
||||
local main = M.get_main(plugin)
|
||||
|
||||
if main then
|
||||
Util.try(function()
|
||||
local mod = require(main)
|
||||
if mod.deactivate then
|
||||
mod.deactivate(plugin)
|
||||
end
|
||||
end, "Failed to deactivate plugin " .. plugin.name)
|
||||
end
|
||||
|
||||
-- execute deactivate when needed
|
||||
if plugin.deactivate then
|
||||
Util.try(function()
|
||||
plugin.deactivate(plugin)
|
||||
end, "Failed to deactivate plugin " .. plugin.name)
|
||||
end
|
||||
|
||||
-- disable handlers
|
||||
Handler.disable(plugin)
|
||||
|
||||
-- clear plugin properties cache
|
||||
plugin._.cache = nil
|
||||
|
||||
-- remove loaded lua modules
|
||||
Util.walkmods(plugin.dir .. "/lua", function(modname)
|
||||
package.loaded[modname] = nil
|
||||
package.preload[modname] = nil
|
||||
end)
|
||||
|
||||
-- clear vim.g.loaded_ for plugins
|
||||
Util.ls(plugin.dir .. "/plugin", function(_, name, type)
|
||||
if type == "file" then
|
||||
vim.g["loaded_" .. name:gsub("%..*", "")] = nil
|
||||
end
|
||||
end)
|
||||
-- set as not loaded
|
||||
plugin._.loaded = nil
|
||||
end
|
||||
|
||||
--- reload a plugin
|
||||
---@param plugin LazyPlugin|string
|
||||
function M.reload(plugin)
|
||||
if type(plugin) == "string" then
|
||||
plugin = Config.plugins[plugin]
|
||||
end
|
||||
|
||||
if not plugin then
|
||||
error("Plugin not found")
|
||||
end
|
||||
|
||||
local load = plugin._.loaded ~= nil
|
||||
M.deactivate(plugin)
|
||||
|
||||
-- enable handlers
|
||||
Handler.enable(plugin)
|
||||
|
||||
-- run init
|
||||
if plugin.init then
|
||||
Util.try(function()
|
||||
plugin.init(plugin)
|
||||
end, "Failed to run `init` for **" .. plugin.name .. "**")
|
||||
end
|
||||
|
||||
-- if this is a start plugin, load it now
|
||||
if plugin.lazy == false then
|
||||
load = true
|
||||
end
|
||||
|
||||
local events = plugin._.handlers and plugin._.handlers.event and plugin._.handlers.event or {}
|
||||
|
||||
for _, event in pairs(events) do
|
||||
if event.id:find("VimEnter") or event.id:find("UIEnter") or event.id:find("VeryLazy") then
|
||||
load = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- reload any vimscript files for this plugin
|
||||
local scripts = vim.fn.getscriptinfo()
|
||||
local loaded_scripts = {}
|
||||
for _, s in ipairs(scripts) do
|
||||
---@type string
|
||||
local path = s.name
|
||||
if
|
||||
path:sub(-4) == ".vim"
|
||||
and path:find(plugin.dir, 1, true) == 1
|
||||
and not path:find("/plugin/", 1, true)
|
||||
and not path:find("/ftplugin/", 1, true)
|
||||
then
|
||||
loaded_scripts[#loaded_scripts + 1] = path
|
||||
end
|
||||
end
|
||||
|
||||
if load then
|
||||
M.load(plugin, { start = "reload" })
|
||||
for _, s in ipairs(loaded_scripts) do
|
||||
M.source(s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
---@param after? boolean
|
||||
function M.source_plugin_files(plugin, after)
|
||||
Util.walk(plugin.dir .. (after and "/after" or "") .. "/plugin", function(path, _, t)
|
||||
local ext = path:sub(-3)
|
||||
if t == "file" and (ext == "lua" or ext == "vim") then
|
||||
vim.cmd("silent source " .. path)
|
||||
end
|
||||
---@param reason {[string]:string}
|
||||
---@param opts? {force:boolean} when force is true, we skip the cond check
|
||||
function M._load(plugin, reason, opts)
|
||||
if not plugin._.installed then
|
||||
return Util.error("Plugin " .. plugin.name .. " is not installed")
|
||||
end
|
||||
|
||||
if plugin._.cond == false and not (opts and opts.force) then
|
||||
return
|
||||
end
|
||||
|
||||
if not Handler.did_setup then
|
||||
Util.try(function()
|
||||
Handler.enable(plugin)
|
||||
end, "Failed to setup handlers for " .. plugin.name)
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line: assign-type-mismatch
|
||||
plugin._.loaded = {}
|
||||
for k, v in pairs(reason) do
|
||||
plugin._.loaded[k] = v
|
||||
end
|
||||
if #M.loading > 0 then
|
||||
plugin._.loaded.plugin = M.loading[#M.loading].name
|
||||
elseif reason.require then
|
||||
plugin._.loaded.source = Util.get_source()
|
||||
end
|
||||
|
||||
table.insert(M.loading, plugin)
|
||||
|
||||
Util.track({ plugin = plugin.name, start = reason.start })
|
||||
Handler.disable(plugin)
|
||||
|
||||
if not plugin.virtual then
|
||||
M.add_to_rtp(plugin)
|
||||
end
|
||||
|
||||
if plugin._.pkg and plugin._.pkg.source == "rockspec" then
|
||||
M.add_to_luapath(plugin)
|
||||
end
|
||||
|
||||
if plugin.dependencies then
|
||||
Util.try(function()
|
||||
M.load(plugin.dependencies, {})
|
||||
end, "Failed to load deps for " .. plugin.name)
|
||||
end
|
||||
|
||||
if not plugin.virtual then
|
||||
M.packadd(plugin.dir)
|
||||
end
|
||||
if plugin.config or plugin.opts then
|
||||
M.config(plugin)
|
||||
end
|
||||
|
||||
plugin._.loaded.time = Util.track().time
|
||||
table.remove(M.loading)
|
||||
vim.schedule(function()
|
||||
vim.api.nvim_exec_autocmds("User", { pattern = "LazyLoad", modeline = false, data = plugin.name })
|
||||
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
|
||||
end)
|
||||
end
|
||||
|
||||
--- runs plugin config
|
||||
---@param plugin LazyPlugin
|
||||
function M.config(plugin)
|
||||
local fn
|
||||
if type(plugin.config) == "function" then
|
||||
fn = function()
|
||||
local opts = Plugin.values(plugin, "opts", false)
|
||||
plugin.config(plugin, opts)
|
||||
end
|
||||
else
|
||||
local main = M.get_main(plugin)
|
||||
if main then
|
||||
fn = function()
|
||||
local opts = Plugin.values(plugin, "opts", false)
|
||||
require(main).setup(opts)
|
||||
end
|
||||
else
|
||||
return Util.error(
|
||||
"Lua module not found for config of " .. plugin.name .. ". Please use a `config()` function instead"
|
||||
)
|
||||
end
|
||||
end
|
||||
Util.try(fn, "Failed to run `config` for " .. plugin.name)
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
function M.get_main(plugin)
|
||||
if plugin.main then
|
||||
return plugin.main
|
||||
end
|
||||
if plugin.name ~= "mini.nvim" and plugin.name:match("^mini%..*$") then
|
||||
return plugin.name
|
||||
end
|
||||
local normname = Util.normname(plugin.name)
|
||||
---@type string[]
|
||||
local mods = {}
|
||||
for _, mod in ipairs(Cache.find("*", { all = true, rtp = false, paths = { plugin.dir } })) do
|
||||
local modname = mod.modname
|
||||
mods[#mods + 1] = modname
|
||||
local modnorm = Util.normname(modname)
|
||||
-- if we found an exact match, then use that
|
||||
if modnorm == normname then
|
||||
mods = { modname }
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return #mods == 1 and mods[1] or nil
|
||||
end
|
||||
|
||||
---@param path string
|
||||
function M.packadd(path)
|
||||
M.source_runtime(path, "plugin")
|
||||
M.ftdetect(path)
|
||||
if M.init_done then
|
||||
M.source_runtime(path, "after/plugin")
|
||||
end
|
||||
end
|
||||
|
||||
---@param path string
|
||||
function M.ftdetect(path)
|
||||
if not M.did_ftdetect[path] then
|
||||
M.did_ftdetect[path] = path
|
||||
vim.cmd("augroup filetypedetect")
|
||||
M.source_runtime(path, "ftdetect")
|
||||
vim.cmd("augroup END")
|
||||
end
|
||||
end
|
||||
|
||||
---@param ... string
|
||||
function M.source_runtime(...)
|
||||
local dir = table.concat({ ... }, "/")
|
||||
---@type string[]
|
||||
local files = {}
|
||||
Util.walk(dir, function(path, name, t)
|
||||
local ext = name:sub(-3)
|
||||
name = name:sub(1, -5)
|
||||
if (t == "file" or t == "link") and (ext == "lua" or ext == "vim") and not M.disabled_rtp_plugins[name] then
|
||||
files[#files + 1] = path
|
||||
end
|
||||
end)
|
||||
-- plugin files are sourced alphabetically per directory
|
||||
table.sort(files)
|
||||
for _, path in ipairs(files) do
|
||||
M.source(path)
|
||||
end
|
||||
end
|
||||
|
||||
-- This does the same as runtime.c:add_pack_dir_to_rtp
|
||||
-- * find first after
|
||||
-- * find lazy pack path
|
||||
-- * insert right after lazy pack path or right before first after or at the end
|
||||
-- * insert after dir right before first after or append to the end
|
||||
---@param plugin LazyPlugin
|
||||
function M.add_to_rtp(plugin)
|
||||
local rtp = vim.api.nvim_get_runtime_file("", true)
|
||||
local idx_dir, idx_after
|
||||
|
||||
for i, path in ipairs(rtp) do
|
||||
if Util.is_win then
|
||||
path = Util.norm(path)
|
||||
end
|
||||
if path == Config.me then
|
||||
idx_dir = i + 1
|
||||
elseif not idx_after and path:sub(-6, -1) == "/after" then
|
||||
idx_after = i + 1 -- +1 to offset the insert of the plugin dir
|
||||
idx_dir = idx_dir or i
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(rtp, idx_dir or (#rtp + 1), plugin.dir)
|
||||
|
||||
local after = plugin.dir .. "/after"
|
||||
if vim.uv.fs_stat(after) then
|
||||
table.insert(rtp, idx_after or (#rtp + 1), after)
|
||||
end
|
||||
|
||||
---@type vim.Option
|
||||
vim.opt.rtp = rtp
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
function M.add_to_luapath(plugin)
|
||||
local root = Config.options.rocks.root .. "/" .. plugin.name
|
||||
local path = root .. "/share/lua/5.1"
|
||||
local cpath = root .. "/lib/lua/5.1"
|
||||
local cpath2 = root .. "/lib64/lua/5.1"
|
||||
|
||||
package.path = package.path .. ";" .. path .. "/?.lua;" .. path .. "/?/init.lua;"
|
||||
package.cpath = package.cpath .. ";" .. cpath .. "/?." .. (jit.os:find("Windows") and "dll" or "so") .. ";"
|
||||
package.cpath = package.cpath .. ";" .. cpath2 .. "/?." .. (jit.os:find("Windows") and "dll" or "so") .. ";"
|
||||
end
|
||||
|
||||
function M.source(path)
|
||||
Util.track({ runtime = path })
|
||||
Util.try(function()
|
||||
vim.cmd("source " .. path)
|
||||
end, "Failed to source `" .. path .. "`")
|
||||
Util.track()
|
||||
end
|
||||
|
||||
function M.colorscheme(name)
|
||||
if vim.tbl_contains(vim.fn.getcompletion("", "color"), name) then
|
||||
return
|
||||
end
|
||||
for _, plugin in pairs(Config.plugins) do
|
||||
if not plugin._.loaded then
|
||||
for _, ext in ipairs({ "lua", "vim" }) do
|
||||
local path = plugin.dir .. "/colors/" .. name .. "." .. ext
|
||||
if vim.uv.fs_stat(path) then
|
||||
return M.load(plugin, { colorscheme = name })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M.auto_load(modname, modpath)
|
||||
local plugin = Plugin.find(modpath, { fast = not M.did_handlers })
|
||||
if plugin then
|
||||
plugin._.rtp_loaded = true
|
||||
-- don't load if:
|
||||
-- * handlers haven't been setup yet
|
||||
-- * we're loading specs
|
||||
-- * the plugin is already loaded
|
||||
if M.did_handlers and not (Plugin.loading or plugin._.loaded) then
|
||||
if plugin.module == false then
|
||||
error("Plugin " .. plugin.name .. " is not loaded and is configured with module=false")
|
||||
end
|
||||
M.load(plugin, { require = modname })
|
||||
if plugin._.cond == false then
|
||||
error("You're trying to load `" .. plugin.name .. "` for which `cond==false`")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param modname string
|
||||
function M.loader(modname)
|
||||
local paths, cached = Util.get_unloaded_rtp(modname, { cache = true })
|
||||
local ret = Cache.find(modname, { rtp = false, paths = paths })[1]
|
||||
|
||||
if not ret and cached then
|
||||
paths = Util.get_unloaded_rtp(modname)
|
||||
ret = Cache.find(modname, { rtp = false, paths = paths })[1]
|
||||
end
|
||||
|
||||
if ret then
|
||||
-- explicitly set to nil to prevent loading errors
|
||||
package.loaded[modname] = nil
|
||||
M.auto_load(modname, ret.modpath)
|
||||
local mod = package.loaded[modname]
|
||||
if type(mod) == "table" then
|
||||
return function()
|
||||
return mod
|
||||
end
|
||||
end
|
||||
-- selene: allow(incorrect_standard_library_use)
|
||||
return loadfile(ret.modpath, nil, nil, ret.stat)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
360
lua/lazy/core/meta.lua
Normal file
360
lua/lazy/core/meta.lua
Normal file
|
@ -0,0 +1,360 @@
|
|||
local Config = require("lazy.core.config")
|
||||
local Pkg = require("lazy.pkg")
|
||||
local Util = require("lazy.core.util")
|
||||
|
||||
--- This class is used to manage the plugins.
|
||||
--- A plugin is a collection of fragments that are related to each other.
|
||||
---@class LazyMeta
|
||||
---@field plugins table<string, LazyPlugin>
|
||||
---@field str_to_meta table<string, LazyPlugin>
|
||||
---@field frag_to_meta table<number, LazyPlugin>
|
||||
---@field dirty table<string, boolean>
|
||||
---@field spec LazySpecLoader
|
||||
---@field fragments LazyFragments
|
||||
---@field pkgs table<string, number>
|
||||
local M = {}
|
||||
|
||||
---@param spec LazySpecLoader
|
||||
---@return LazyMeta
|
||||
function M.new(spec)
|
||||
local self = setmetatable({}, { __index = M })
|
||||
self.spec = spec
|
||||
self.fragments = require("lazy.core.fragments").new(spec)
|
||||
self.plugins = {}
|
||||
self.frag_to_meta = {}
|
||||
self.str_to_meta = {}
|
||||
self.dirty = {}
|
||||
self.pkgs = {}
|
||||
return self
|
||||
end
|
||||
|
||||
-- import package specs
|
||||
function M:load_pkgs()
|
||||
if not Config.options.pkg.enabled then
|
||||
return
|
||||
end
|
||||
for _, pkg in ipairs(Pkg.get()) do
|
||||
local last_id = self.fragments._fid
|
||||
local meta, fragment = self:add(pkg.spec)
|
||||
if meta and fragment then
|
||||
meta._.pkg = pkg
|
||||
-- tag all top-level package fragments that were added as optional
|
||||
for _, fid in ipairs(meta._.frags) do
|
||||
if fid > last_id then
|
||||
local frag = self.fragments:get(fid)
|
||||
frag.spec.optional = true
|
||||
end
|
||||
end
|
||||
-- keep track of the top-level package fragment
|
||||
self.pkgs[pkg.dir] = fragment.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Remove a plugin and all its fragments.
|
||||
---@param name string
|
||||
function M:del(name)
|
||||
local meta = self.plugins[name]
|
||||
if not meta then
|
||||
return
|
||||
end
|
||||
for _, fid in ipairs(meta._.frags or {}) do
|
||||
self.fragments:del(fid)
|
||||
end
|
||||
self.plugins[name] = nil
|
||||
end
|
||||
|
||||
--- Add a fragment to a plugin.
|
||||
--- This will create a new plugin if it does not exist.
|
||||
--- It also keeps track of renames.
|
||||
---@param plugin LazyPluginSpec
|
||||
function M:add(plugin)
|
||||
local fragment = self.fragments:add(plugin)
|
||||
if not fragment then
|
||||
return
|
||||
end
|
||||
|
||||
local meta = self.plugins[fragment.name]
|
||||
or fragment.url and self.str_to_meta[fragment.url]
|
||||
or fragment.dir and self.str_to_meta[fragment.dir]
|
||||
|
||||
if not meta then
|
||||
meta = { name = fragment.name, _ = { frags = {} } }
|
||||
local url, dir = fragment.url, fragment.dir
|
||||
-- add to index
|
||||
if url then
|
||||
self.str_to_meta[url] = meta
|
||||
end
|
||||
if dir then
|
||||
self.str_to_meta[dir] = meta
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(meta._.frags, fragment.id)
|
||||
|
||||
if meta._ and meta._.rtp_loaded and meta.dir then
|
||||
local old_dir = meta.dir
|
||||
self:_rebuild(meta.name)
|
||||
local new_dir = meta.dir
|
||||
if old_dir ~= new_dir then
|
||||
local msg = "Plugin `" .. meta.name .. "` changed `dir`:\n- from: `" .. old_dir .. "`\n- to: `" .. new_dir .. "`"
|
||||
msg = msg .. "\n\nThis plugin was already partially loaded, so things may break.\nPlease fix your config."
|
||||
self.spec:error(msg)
|
||||
end
|
||||
end
|
||||
|
||||
if plugin.name then
|
||||
-- handle renames
|
||||
if meta.name ~= plugin.name then
|
||||
self.plugins[meta.name] = nil
|
||||
meta.name = plugin.name
|
||||
end
|
||||
end
|
||||
|
||||
self.plugins[meta.name] = meta
|
||||
self.frag_to_meta[fragment.id] = meta
|
||||
self.dirty[meta.name] = true
|
||||
return meta, fragment
|
||||
end
|
||||
|
||||
--- Rebuild all plugins based on dirty fragments,
|
||||
--- or dirty plugins. Will remove plugins that no longer have fragments.
|
||||
function M:rebuild()
|
||||
local frag_count = vim.tbl_count(self.fragments.dirty)
|
||||
local plugin_count = vim.tbl_count(self.dirty)
|
||||
if frag_count == 0 and plugin_count == 0 then
|
||||
return
|
||||
end
|
||||
if Config.options.debug then
|
||||
Util.track("rebuild plugins frags=" .. frag_count .. " plugins=" .. plugin_count)
|
||||
end
|
||||
for fid in pairs(self.fragments.dirty) do
|
||||
local meta = self.frag_to_meta[fid]
|
||||
if meta then
|
||||
if self.fragments:get(fid) then
|
||||
-- fragment still exists, so mark plugin as dirty
|
||||
self.dirty[meta.name] = true
|
||||
else
|
||||
-- fragment was deleted, so remove it from plugin
|
||||
self.frag_to_meta[fid] = nil
|
||||
---@param f number
|
||||
meta._.frags = Util.filter(function(f)
|
||||
return f ~= fid
|
||||
end, meta._.frags)
|
||||
-- if no fragments left, delete plugin
|
||||
if #meta._.frags == 0 then
|
||||
self:del(meta.name)
|
||||
else
|
||||
self.dirty[meta.name] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self.fragments.dirty = {}
|
||||
for n, _ in pairs(self.dirty) do
|
||||
self:_rebuild(n)
|
||||
end
|
||||
if Config.options.debug then
|
||||
Util.track()
|
||||
end
|
||||
end
|
||||
|
||||
--- Rebuild a single plugin.
|
||||
--- This will resolve the plugin based on its fragments using metatables.
|
||||
--- This also resolves dependencies, dep, optional, dir, dev, and url.
|
||||
---@param name string
|
||||
function M:_rebuild(name)
|
||||
if not self.dirty[name] then
|
||||
return
|
||||
end
|
||||
self.dirty[name] = nil
|
||||
local plugin = self.plugins[name]
|
||||
if not plugin or #plugin._.frags == 0 then
|
||||
self.plugins[name] = nil
|
||||
return
|
||||
end
|
||||
setmetatable(plugin, nil)
|
||||
plugin.dependencies = {}
|
||||
|
||||
local super = nil
|
||||
plugin.url = nil
|
||||
plugin._.dep = true
|
||||
plugin._.top = true
|
||||
plugin.optional = true
|
||||
|
||||
assert(#plugin._.frags > 0, "no fragments found for plugin " .. name)
|
||||
|
||||
---@type table<number, boolean>
|
||||
local added = {}
|
||||
for _, fid in ipairs(plugin._.frags) do
|
||||
if not added[fid] then
|
||||
added[fid] = true
|
||||
local fragment = self.fragments:get(fid)
|
||||
assert(fragment, "fragment " .. fid .. " not found, for plugin " .. name)
|
||||
---@diagnostic disable-next-line: no-unknown
|
||||
super = setmetatable(fragment.spec, super and { __index = super } or nil)
|
||||
plugin._.dep = plugin._.dep and fragment.dep
|
||||
plugin.optional = plugin.optional and (rawget(fragment.spec, "optional") == true)
|
||||
plugin.url = fragment.url or plugin.url
|
||||
plugin._.top = plugin._.top and fragment.pid == nil
|
||||
|
||||
-- dependencies
|
||||
for _, dep in ipairs(fragment.deps or {}) do
|
||||
local dep_meta = self.frag_to_meta[dep]
|
||||
if dep_meta then
|
||||
table.insert(plugin.dependencies, dep_meta.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
super = super or {}
|
||||
|
||||
-- dir / dev
|
||||
plugin.dev = super.dev
|
||||
plugin.dir = super.dir
|
||||
if plugin.dir then
|
||||
plugin.dir = Util.norm(plugin.dir)
|
||||
elseif super.virtual then
|
||||
plugin.dir = Util.norm("/dev/null/" .. plugin.name)
|
||||
else
|
||||
if plugin.dev == nil and plugin.url then
|
||||
for _, pattern in ipairs(Config.options.dev.patterns) do
|
||||
if plugin.url:find(pattern, 1, true) then
|
||||
plugin.dev = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if plugin.dev == true then
|
||||
local dev_dir = type(Config.options.dev.path) == "string" and Config.options.dev.path .. "/" .. plugin.name
|
||||
or Util.norm(Config.options.dev.path(plugin))
|
||||
if not Config.options.dev.fallback or vim.fn.isdirectory(dev_dir) == 1 then
|
||||
plugin.dir = dev_dir
|
||||
else
|
||||
plugin.dev = false
|
||||
end
|
||||
end
|
||||
plugin.dir = plugin.dir or Config.options.root .. "/" .. plugin.name
|
||||
end
|
||||
|
||||
-- dependencies
|
||||
if #plugin.dependencies == 0 and not super.dependencies then
|
||||
plugin.dependencies = nil
|
||||
end
|
||||
|
||||
-- optional
|
||||
if not plugin.optional and not super.optional then
|
||||
plugin.optional = nil
|
||||
end
|
||||
|
||||
setmetatable(plugin, { __index = super })
|
||||
|
||||
return plugin
|
||||
end
|
||||
|
||||
--- Disable a plugin.
|
||||
---@param plugin LazyPlugin
|
||||
function M:disable(plugin)
|
||||
plugin._.kind = "disabled"
|
||||
self:del(plugin.name)
|
||||
self.spec.disabled[plugin.name] = plugin
|
||||
end
|
||||
|
||||
--- Check if a plugin should be disabled, but ignore uninstalling it.
|
||||
function M:fix_cond()
|
||||
for _, plugin in pairs(self.plugins) do
|
||||
local cond = plugin.cond
|
||||
if cond == nil then
|
||||
cond = Config.options.defaults.cond
|
||||
end
|
||||
if cond == false or (type(cond) == "function" and not cond(plugin)) then
|
||||
plugin._.cond = false
|
||||
local stack = { plugin }
|
||||
while #stack > 0 do
|
||||
local p = table.remove(stack) --[[@as LazyPlugin]]
|
||||
if not self.spec.ignore_installed[p.name] then
|
||||
for _, dep in ipairs(p.dependencies or {}) do
|
||||
table.insert(stack, self.plugins[dep])
|
||||
end
|
||||
self.spec.ignore_installed[p.name] = true
|
||||
end
|
||||
end
|
||||
plugin.enabled = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Removes plugins for which all its fragments are optional.
|
||||
function M:fix_optional()
|
||||
if self.spec.optional then
|
||||
return 0
|
||||
end
|
||||
local changes = 0
|
||||
for _, plugin in pairs(self.plugins) do
|
||||
if plugin.optional then
|
||||
changes = changes + 1
|
||||
self:del(plugin.name)
|
||||
end
|
||||
end
|
||||
self:rebuild()
|
||||
return changes
|
||||
end
|
||||
|
||||
--- Removes plugins that are disabled.
|
||||
function M:fix_disabled()
|
||||
local changes = 0
|
||||
local function check(top)
|
||||
for _, plugin in pairs(self.plugins) do
|
||||
if (plugin._.top or false) == top then
|
||||
if plugin.enabled == false or (type(plugin.enabled) == "function" and not plugin.enabled()) then
|
||||
changes = changes + 1
|
||||
if plugin.optional then
|
||||
self:del(plugin.name)
|
||||
else
|
||||
self:disable(plugin)
|
||||
end
|
||||
self:rebuild()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- disable top-level plugins first, since they may have non-top-level frags
|
||||
-- that disable other plugins
|
||||
check(true)
|
||||
-- then disable non-top-level plugins
|
||||
check(false)
|
||||
return changes
|
||||
end
|
||||
|
||||
--- Removes package fragments for plugins that no longer use the same directory.
|
||||
function M:fix_pkgs()
|
||||
for dir, fid in pairs(self.pkgs) do
|
||||
local plugin = self.frag_to_meta[fid]
|
||||
plugin = plugin and self.plugins[plugin.name]
|
||||
if plugin then
|
||||
-- check if plugin is still in the same directory
|
||||
if plugin.dir ~= dir then
|
||||
self.fragments:del(fid)
|
||||
end
|
||||
end
|
||||
end
|
||||
self:rebuild()
|
||||
end
|
||||
|
||||
--- Resolve all plugins, based on cond, enabled and optional.
|
||||
function M:resolve()
|
||||
Util.track("resolve plugins")
|
||||
self:rebuild()
|
||||
|
||||
self:fix_pkgs()
|
||||
|
||||
self:fix_cond()
|
||||
|
||||
-- selene: allow(empty_loop)
|
||||
while self:fix_disabled() + self:fix_optional() > 0 do
|
||||
end
|
||||
Util.track()
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,71 +0,0 @@
|
|||
local Cache = require("lazy.core.cache")
|
||||
|
||||
local M = {}
|
||||
|
||||
---@type table<string, string>
|
||||
M.hashes = {}
|
||||
|
||||
function M.is_dirty(modname, modpath)
|
||||
return not (Cache.get(modname) and M.hashes[modname] and M.hashes[modname] == Cache.hash(modpath))
|
||||
end
|
||||
|
||||
---@param modname string
|
||||
---@param modpath string
|
||||
---@return any, boolean
|
||||
function M.load(modname, modpath)
|
||||
local err
|
||||
---@type (string|fun())?
|
||||
local chunk = Cache.get(modname)
|
||||
|
||||
local hash = Cache.hash(modpath)
|
||||
if hash ~= M.hashes[modname] then
|
||||
M.hashes[modname] = hash
|
||||
Cache.del(modname)
|
||||
chunk = nil
|
||||
end
|
||||
|
||||
local cached = false
|
||||
if chunk then
|
||||
cached = true
|
||||
chunk, err = load(chunk --[[@as string]], "@" .. modpath, "b")
|
||||
else
|
||||
vim.schedule(function()
|
||||
vim.notify("loadfile(" .. modname .. ")")
|
||||
end)
|
||||
chunk, err = loadfile(modpath)
|
||||
if chunk and not err then
|
||||
Cache.set(modname, string.dump(chunk))
|
||||
end
|
||||
end
|
||||
|
||||
if chunk then
|
||||
return chunk(), cached
|
||||
else
|
||||
error(err)
|
||||
end
|
||||
end
|
||||
|
||||
function M.setup()
|
||||
-- load cache
|
||||
local value = Cache.get("cache.modules")
|
||||
if value then
|
||||
M.hashes = vim.json.decode(value)
|
||||
end
|
||||
|
||||
-- preload core modules
|
||||
local root = vim.fn.fnamemodify(debug.getinfo(1, "S").source:sub(2), ":p:h:h")
|
||||
for _, name in ipairs({ "util", "config", "loader", "plugin", "handler" }) do
|
||||
local modname = "lazy.core." .. name
|
||||
---@diagnostic disable-next-line: no-unknown
|
||||
package.preload[modname] = function()
|
||||
return M.load(modname, root .. "/core/" .. name:gsub("%.", "/") .. ".lua")
|
||||
end
|
||||
end
|
||||
return M
|
||||
end
|
||||
|
||||
function M.save()
|
||||
Cache.set("cache.modules", vim.json.encode(M.hashes))
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,299 +1,484 @@
|
|||
local Config = require("lazy.core.config")
|
||||
local Meta = require("lazy.core.meta")
|
||||
local Pkg = require("lazy.pkg")
|
||||
local Util = require("lazy.core.util")
|
||||
local Module = require("lazy.core.module")
|
||||
local Cache = require("lazy.core.cache")
|
||||
|
||||
---@class LazyCorePlugin
|
||||
local M = {}
|
||||
|
||||
---@alias CachedPlugin LazyPlugin | {_funs: string[]}
|
||||
local skip = { _ = true, dir = true }
|
||||
local funs = { config = true, init = true, run = true }
|
||||
|
||||
M.dirty = false
|
||||
|
||||
---@class LazyPluginHooks
|
||||
---@field init? fun(LazyPlugin) Will always be run
|
||||
---@field config? fun(LazyPlugin) Will be executed when loading the plugin
|
||||
---@field run? string|fun()
|
||||
|
||||
---@class LazyPluginState
|
||||
---@field loaded? {[string]:string, time:number}
|
||||
---@field installed boolean
|
||||
---@field tasks? LazyTask[]
|
||||
---@field dirty? boolean
|
||||
---@field updated? {from:string, to:string}
|
||||
---@field is_local boolean
|
||||
---@field is_symlink? boolean
|
||||
---@field cloned? boolean
|
||||
|
||||
---@class LazyPluginRef
|
||||
---@field branch? string
|
||||
---@field tag? string
|
||||
---@field commit? string
|
||||
---@field version? string
|
||||
---@field lock? boolean
|
||||
|
||||
---@class LazyPlugin: LazyPluginHandlers,LazyPluginHooks,LazyPluginRef
|
||||
---@field [1] string
|
||||
---@field name string display name and name used for plugin config files
|
||||
---@field uri string
|
||||
---@field dir string
|
||||
---@field enabled? boolean|(fun():boolean)
|
||||
---@field opt? boolean
|
||||
---@field dependencies? string[]
|
||||
---@field _ LazyPluginState
|
||||
|
||||
---@alias LazySpec string|LazyPlugin|LazySpec[]|{dependencies:LazySpec}
|
||||
M.loading = false
|
||||
|
||||
---@class LazySpecLoader
|
||||
---@field modname string
|
||||
---@field modpath string
|
||||
---@field meta LazyMeta
|
||||
---@field plugins table<string, LazyPlugin>
|
||||
---@field funs? table<string, string[]>
|
||||
---@field disabled table<string, LazyPlugin>
|
||||
---@field ignore_installed table<string, true>
|
||||
---@field modules string[]
|
||||
---@field notifs {msg:string, level:number, file?:string}[]
|
||||
---@field importing? string
|
||||
---@field optional? boolean
|
||||
local Spec = {}
|
||||
M.Spec = Spec
|
||||
M.LOCAL_SPEC = ".lazy.lua"
|
||||
|
||||
---@param spec? LazySpec
|
||||
function Spec.new(spec)
|
||||
local self = setmetatable({}, { __index = Spec })
|
||||
self.plugins = {}
|
||||
self.modname = nil
|
||||
self.modpath = nil
|
||||
---@param opts? {optional?:boolean, pkg?:boolean}
|
||||
function Spec.new(spec, opts)
|
||||
local self = setmetatable({}, Spec)
|
||||
self.meta = Meta.new(self)
|
||||
self.disabled = {}
|
||||
self.modules = {}
|
||||
self.notifs = {}
|
||||
self.ignore_installed = {}
|
||||
self.optional = opts and opts.optional
|
||||
if not (opts and opts.pkg == false) then
|
||||
self.meta:load_pkgs()
|
||||
end
|
||||
if spec then
|
||||
self:normalize(spec)
|
||||
self:parse(spec)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---@param modname string
|
||||
---@param modpath string
|
||||
function Spec.load(modname, modpath)
|
||||
local self = setmetatable({}, { __index = Spec })
|
||||
self.plugins = {}
|
||||
self.modname = modname
|
||||
self.modpath = modpath
|
||||
local mod, cached = Module.load(modname, modpath)
|
||||
M.dirty = M.dirty or not cached
|
||||
self:normalize(assert(mod))
|
||||
if modname == Config.options.plugins and not self.plugins["lazy.nvim"] then
|
||||
self:add({ "folke/lazy.nvim", opt = false })
|
||||
function Spec:__index(key)
|
||||
if Spec[key] then
|
||||
return Spec[key]
|
||||
end
|
||||
if key == "plugins" then
|
||||
self.meta:rebuild()
|
||||
return self.meta.plugins
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
function Spec:add(plugin)
|
||||
local pkg = plugin[1]
|
||||
if type(pkg) ~= "string" then
|
||||
Util.error("Invalid plugin spec " .. vim.inspect(plugin))
|
||||
end
|
||||
function Spec:parse(spec)
|
||||
self:normalize(spec)
|
||||
self.meta:resolve()
|
||||
end
|
||||
|
||||
if not plugin.uri then
|
||||
local c = pkg:sub(1, 1)
|
||||
if c == "~" then
|
||||
plugin.uri = vim.loop.os_getenv("HOME") .. pkg:sub(2)
|
||||
elseif c == "/" then
|
||||
plugin.uri = pkg
|
||||
elseif pkg:sub(1, 4) == "http" or pkg:sub(1, 3) == "ssh" then
|
||||
plugin.uri = pkg
|
||||
else
|
||||
plugin.uri = ("https://github.com/" .. pkg .. ".git")
|
||||
-- PERF: optimized code to get package name without using lua patterns
|
||||
---@return string
|
||||
function Spec.get_name(pkg)
|
||||
local name = pkg:sub(-4) == ".git" and pkg:sub(1, -5) or pkg
|
||||
name = name:sub(-1) == "/" and name:sub(1, -2) or name
|
||||
local slash = name:reverse():find("/", 1, true) --[[@as number?]]
|
||||
return slash and name:sub(#name - slash + 2) or pkg:gsub("%W+", "_")
|
||||
end
|
||||
|
||||
function Spec:error(msg)
|
||||
self:log(msg, vim.log.levels.ERROR)
|
||||
end
|
||||
|
||||
function Spec:warn(msg)
|
||||
self:log(msg, vim.log.levels.WARN)
|
||||
end
|
||||
|
||||
---@param msg string
|
||||
---@param level number
|
||||
function Spec:log(msg, level)
|
||||
self.notifs[#self.notifs + 1] = { msg = msg, level = level, file = self.importing }
|
||||
end
|
||||
|
||||
function Spec:report(level)
|
||||
level = level or vim.log.levels.ERROR
|
||||
local count = 0
|
||||
for _, notif in ipairs(self.notifs) do
|
||||
if notif.level >= level then
|
||||
Util.notify(notif.msg, { level = notif.level })
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- PERF: optimized code to get package name without using lua patterns
|
||||
if not plugin.name then
|
||||
local name = pkg:sub(-4) == ".git" and pkg:sub(1, -5) or pkg
|
||||
local slash = name:reverse():find("/", 1, true) --[[@as number?]]
|
||||
plugin.name = slash and name:sub(#name - slash + 2) or pkg:gsub("%W+", "_")
|
||||
end
|
||||
|
||||
M.process_local(plugin)
|
||||
local other = self.plugins[plugin.name]
|
||||
self.plugins[plugin.name] = other and vim.tbl_extend("force", self.plugins[plugin.name], plugin) or plugin
|
||||
return self.plugins[plugin.name]
|
||||
return count
|
||||
end
|
||||
|
||||
---@param spec LazySpec
|
||||
---@param results? string[]
|
||||
function Spec:normalize(spec, results)
|
||||
results = results or {}
|
||||
---@param spec LazySpec|LazySpecImport
|
||||
function Spec:normalize(spec)
|
||||
if type(spec) == "string" then
|
||||
table.insert(results, self:add({ spec }).name)
|
||||
self.meta:add({ spec })
|
||||
elseif #spec > 1 or Util.is_list(spec) then
|
||||
---@cast spec LazySpec[]
|
||||
for _, s in ipairs(spec) do
|
||||
self:normalize(s, results)
|
||||
self:normalize(s)
|
||||
end
|
||||
elseif spec.enabled == nil or spec.enabled == true or (type(spec.enabled) == "function" and spec.enabled()) then
|
||||
---@cast spec LazyPlugin
|
||||
local plugin = self:add(spec)
|
||||
plugin.dependencies = plugin.dependencies and self:normalize(plugin.dependencies, {}) or nil
|
||||
table.insert(results, plugin.name)
|
||||
elseif spec[1] or spec.dir or spec.url then
|
||||
---@cast spec LazyPluginSpec
|
||||
self.meta:add(spec)
|
||||
---@diagnostic disable-next-line: cast-type-mismatch
|
||||
---@cast spec LazySpecImport
|
||||
if spec and spec.import then
|
||||
self:import(spec)
|
||||
end
|
||||
elseif spec.import then
|
||||
---@cast spec LazySpecImport
|
||||
self:import(spec)
|
||||
else
|
||||
self:error("Invalid plugin spec " .. vim.inspect(spec))
|
||||
end
|
||||
return results
|
||||
end
|
||||
|
||||
---@param spec LazySpecLoader
|
||||
function Spec.revive(spec)
|
||||
if spec.funs then
|
||||
---@type LazySpecLoader
|
||||
local loaded = nil
|
||||
for fun, plugins in pairs(spec.funs) do
|
||||
for _, name in pairs(plugins) do
|
||||
---@diagnostic disable-next-line: no-unknown
|
||||
spec.plugins[name][fun] = function(...)
|
||||
loaded = loaded or Spec.load(spec.modname, spec.modpath)
|
||||
return loaded.plugins[name][fun](...)
|
||||
end
|
||||
end
|
||||
end
|
||||
---@param spec LazySpecImport
|
||||
function Spec:import(spec)
|
||||
if spec.import == "lazy" then
|
||||
return self:error("You can't name your plugins module `lazy`.")
|
||||
end
|
||||
if type(spec.import) == "function" then
|
||||
if not spec.name then
|
||||
return self:error("Invalid import spec. Missing name: " .. vim.inspect(spec))
|
||||
end
|
||||
elseif type(spec.import) ~= "string" then
|
||||
return self:error("Invalid import spec. `import` should be a string: " .. vim.inspect(spec))
|
||||
end
|
||||
return spec
|
||||
end
|
||||
|
||||
function M.update_state(check_clean)
|
||||
---@type table<"opt"|"start", table<string,FileType>>
|
||||
local installed = { opt = {}, start = {} }
|
||||
for opt, packs in pairs(installed) do
|
||||
Util.ls(Config.options.packpath .. "/" .. opt, function(_, name, type)
|
||||
if type == "directory" or type == "link" then
|
||||
packs[name] = type
|
||||
end
|
||||
local import_name = spec.name or spec.import
|
||||
---@cast import_name string
|
||||
|
||||
if vim.tbl_contains(self.modules, import_name) then
|
||||
return
|
||||
end
|
||||
if spec.cond == false or (type(spec.cond) == "function" and not spec.cond()) then
|
||||
return
|
||||
end
|
||||
if spec.enabled == false or (type(spec.enabled) == "function" and not spec.enabled()) then
|
||||
return
|
||||
end
|
||||
|
||||
self.modules[#self.modules + 1] = import_name
|
||||
|
||||
local import = spec.import
|
||||
|
||||
local imported = 0
|
||||
|
||||
---@type {modname: string, load: fun():(LazyPluginSpec?, string?)}[]
|
||||
local modspecs = {}
|
||||
|
||||
if type(import) == "string" then
|
||||
Util.lsmod(import, function(modname, modpath)
|
||||
modspecs[#modspecs + 1] = {
|
||||
modname = modname,
|
||||
load = function()
|
||||
local mod, err = loadfile(modpath)
|
||||
if mod then
|
||||
return mod()
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end,
|
||||
}
|
||||
end)
|
||||
table.sort(modspecs, function(a, b)
|
||||
return a.modname < b.modname
|
||||
end)
|
||||
else
|
||||
modspecs = { { modname = import_name, load = spec.import } }
|
||||
end
|
||||
|
||||
for _, modspec in ipairs(modspecs) do
|
||||
imported = imported + 1
|
||||
local modname = modspec.modname
|
||||
Util.track({ import = modname })
|
||||
self.importing = modname
|
||||
-- unload the module so we get a clean slate
|
||||
---@diagnostic disable-next-line: no-unknown
|
||||
package.loaded[modname] = nil
|
||||
Util.try(function()
|
||||
local mod, err = modspec.load()
|
||||
if err then
|
||||
self:error("Failed to load `" .. modname .. "`:\n" .. err)
|
||||
elseif type(mod) ~= "table" then
|
||||
return self:error(
|
||||
"Invalid spec module: `"
|
||||
.. modname
|
||||
.. "`\nExpected a `table` of specs, but a `"
|
||||
.. type(mod)
|
||||
.. "` was returned instead"
|
||||
)
|
||||
else
|
||||
self:normalize(mod)
|
||||
end
|
||||
end, {
|
||||
msg = "Failed to load `" .. modname .. "`",
|
||||
on_error = function(msg)
|
||||
self:error(msg)
|
||||
end,
|
||||
})
|
||||
self.importing = nil
|
||||
Util.track()
|
||||
end
|
||||
if imported == 0 then
|
||||
self:error("No specs found for module " .. vim.inspect(spec.import))
|
||||
end
|
||||
end
|
||||
|
||||
function M.update_state()
|
||||
---@type string[]
|
||||
local cloning = {}
|
||||
|
||||
---@type table<string,FileType>
|
||||
local installed = {}
|
||||
Util.ls(Config.options.root, function(_, name, type)
|
||||
if type == "directory" and name ~= "readme" then
|
||||
installed[name] = type
|
||||
elseif type == "file" and name:sub(-8) == ".cloning" then
|
||||
name = name:sub(1, -9)
|
||||
cloning[#cloning + 1] = name
|
||||
end
|
||||
end)
|
||||
|
||||
for _, failed in ipairs(cloning) do
|
||||
installed[failed] = nil
|
||||
end
|
||||
|
||||
for _, plugin in pairs(Config.plugins) do
|
||||
plugin._ = plugin._ or {}
|
||||
plugin[1] = plugin["1"] or plugin[1]
|
||||
if plugin.opt == nil then
|
||||
plugin.opt = Config.options.opt
|
||||
if plugin.lazy == nil then
|
||||
local lazy = plugin._.dep
|
||||
or Config.options.defaults.lazy
|
||||
or plugin.event
|
||||
or plugin.keys
|
||||
or plugin.ft
|
||||
or plugin.cmd
|
||||
plugin.lazy = lazy and true or false
|
||||
end
|
||||
local opt = plugin.opt and "opt" or "start"
|
||||
plugin.dir = Config.options.packpath .. "/" .. opt .. "/" .. plugin.name
|
||||
plugin._.is_local = plugin.uri:sub(1, 4) ~= "http" and plugin.uri:sub(1, 3) ~= "git"
|
||||
plugin._.is_symlink = installed[opt][plugin.name] == "link"
|
||||
plugin._.installed = installed[opt][plugin.name] ~= nil
|
||||
if plugin._.is_local == plugin._.is_symlink then
|
||||
installed[opt][plugin.name] = nil
|
||||
if plugin.virtual then
|
||||
plugin._.is_local = true
|
||||
plugin._.installed = true -- local plugins are managed by the user
|
||||
elseif plugin.dir:find(Config.options.root, 1, true) == 1 then
|
||||
plugin._.installed = installed[plugin.name] ~= nil
|
||||
installed[plugin.name] = nil
|
||||
else
|
||||
plugin._.is_local = true
|
||||
plugin._.installed = vim.fn.isdirectory(plugin.dir) == 1
|
||||
end
|
||||
end
|
||||
|
||||
if check_clean then
|
||||
Config.to_clean = {}
|
||||
for opt, packs in pairs(installed) do
|
||||
for pack in pairs(packs) do
|
||||
table.insert(Config.to_clean, {
|
||||
name = pack,
|
||||
pack = pack,
|
||||
dir = Config.options.packpath .. "/" .. opt .. "/" .. pack,
|
||||
opt = opt == "opt",
|
||||
_ = {
|
||||
installed = true,
|
||||
},
|
||||
})
|
||||
end
|
||||
for name in pairs(Config.spec.ignore_installed) do
|
||||
installed[name] = nil
|
||||
end
|
||||
|
||||
M.update_rocks_state()
|
||||
|
||||
Config.to_clean = {}
|
||||
for pack, dir_type in pairs(installed) do
|
||||
table.insert(Config.to_clean, {
|
||||
name = pack,
|
||||
dir = Config.options.root .. "/" .. pack,
|
||||
_ = {
|
||||
kind = "clean",
|
||||
installed = true,
|
||||
is_symlink = dir_type == "link",
|
||||
is_local = dir_type == "link",
|
||||
},
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function M.update_rocks_state()
|
||||
local root = Config.options.rocks.root
|
||||
---@type table<string,string>
|
||||
local installed = {}
|
||||
Util.ls(root, function(_, name, type)
|
||||
if type == "directory" then
|
||||
installed[name] = name
|
||||
end
|
||||
end)
|
||||
|
||||
for _, plugin in pairs(Config.plugins) do
|
||||
if plugin.build == "rockspec" or plugin.name == "hererocks" then
|
||||
plugin._.build = not installed[plugin.name]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
function M.process_local(plugin)
|
||||
for _, pattern in ipairs(Config.options.plugins_local.patterns) do
|
||||
if plugin[1]:find(pattern, 1, true) then
|
||||
plugin.uri = Config.options.plugins_local.path .. "/" .. plugin.name
|
||||
return
|
||||
---@return LazySpecImport?
|
||||
function M.find_local_spec()
|
||||
if not Config.options.local_spec then
|
||||
return
|
||||
end
|
||||
local path = vim.uv.cwd()
|
||||
while path and path ~= "" do
|
||||
local file = path .. "/" .. M.LOCAL_SPEC
|
||||
if vim.fn.filereadable(file) == 1 then
|
||||
return {
|
||||
name = vim.fn.fnamemodify(file, ":~:."),
|
||||
import = function()
|
||||
local data = vim.secure.read(file)
|
||||
if data then
|
||||
return loadstring(data, M.LOCAL_SPEC)()
|
||||
end
|
||||
return {}
|
||||
end,
|
||||
}
|
||||
end
|
||||
local p = vim.fn.fnamemodify(path, ":h")
|
||||
if p == path then
|
||||
break
|
||||
end
|
||||
path = p
|
||||
end
|
||||
end
|
||||
|
||||
---@param cache? table<string,LazySpecLoader>
|
||||
function M.specs(cache)
|
||||
---@type LazySpecLoader[]
|
||||
local specs = {}
|
||||
|
||||
local function _load(name, modpath)
|
||||
local modname = Config.options.plugins .. (name and ("." .. name) or "")
|
||||
Util.try(function()
|
||||
local spec = cache and cache[modname]
|
||||
spec = spec and not Module.is_dirty(modname, modpath) and Spec.revive(spec) or Spec.load(modname, modpath)
|
||||
table.insert(specs, spec)
|
||||
end, "Failed to load **" .. modname .. "**")
|
||||
end
|
||||
|
||||
_load(nil, Config.paths.main)
|
||||
Util.lsmod(Config.paths.plugins, _load)
|
||||
return specs
|
||||
end
|
||||
|
||||
function M.load()
|
||||
---@type boolean, LazyState?
|
||||
local ok, state = pcall(vim.json.decode, Cache.get("cache.state"))
|
||||
if not (ok and state and vim.deep_equal(Config.options, state.config)) then
|
||||
M.dirty = true
|
||||
state = nil
|
||||
M.loading = true
|
||||
-- load specs
|
||||
Util.track("spec")
|
||||
Config.spec = Spec.new()
|
||||
|
||||
local specs = {
|
||||
---@diagnostic disable-next-line: param-type-mismatch
|
||||
vim.deepcopy(Config.options.spec),
|
||||
}
|
||||
specs[#specs + 1] = M.find_local_spec()
|
||||
specs[#specs + 1] = { "folke/lazy.nvim" }
|
||||
|
||||
Config.spec:parse(specs)
|
||||
|
||||
-- override some lazy props
|
||||
local lazy = Config.spec.plugins["lazy.nvim"]
|
||||
if lazy then
|
||||
lazy.lazy = true
|
||||
lazy.dir = Config.me
|
||||
lazy.config = function()
|
||||
error("lazy config should not be called")
|
||||
end
|
||||
lazy._.loaded = {}
|
||||
end
|
||||
|
||||
-- load specs
|
||||
Util.track("specs")
|
||||
local specs = M.specs(state and state.specs)
|
||||
Util.track()
|
||||
|
||||
-- merge
|
||||
Config.plugins = {}
|
||||
for _, spec in ipairs(specs) do
|
||||
for _, plugin in pairs(spec.plugins) do
|
||||
local other = Config.plugins[plugin.name]
|
||||
Config.plugins[plugin.name] = other and vim.tbl_extend("force", other, plugin) or plugin
|
||||
-- add hererocks when enabled and needed
|
||||
for _, plugin in pairs(Config.spec.plugins) do
|
||||
if plugin.build == "rockspec" then
|
||||
if Config.hererocks() then
|
||||
Config.spec.meta:add({
|
||||
"luarocks/hererocks",
|
||||
build = "rockspec",
|
||||
lazy = true,
|
||||
})
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local existing = Config.plugins
|
||||
Config.plugins = Config.spec.plugins
|
||||
-- copy state. This wont do anything during startup
|
||||
for name, plugin in pairs(existing) do
|
||||
if Config.plugins[name] then
|
||||
local new_state = Config.plugins[name]._
|
||||
Config.plugins[name]._ = plugin._
|
||||
Config.plugins[name]._.dep = new_state.dep
|
||||
Config.plugins[name]._.frags = new_state.frags
|
||||
Config.plugins[name]._.pkg = new_state.pkg
|
||||
end
|
||||
end
|
||||
Util.track()
|
||||
|
||||
Util.track("state")
|
||||
M.update_state()
|
||||
Util.track()
|
||||
|
||||
if M.dirty then
|
||||
Cache.dirty = true
|
||||
elseif state then
|
||||
require("lazy.core.handler")._groups = state.handlers
|
||||
if Config.options.pkg.enabled and Pkg.dirty then
|
||||
Pkg.update()
|
||||
return M.load()
|
||||
end
|
||||
|
||||
M.loading = false
|
||||
vim.api.nvim_exec_autocmds("User", { pattern = "LazyPlugins", modeline = false })
|
||||
end
|
||||
|
||||
function M.save()
|
||||
---@class LazyState
|
||||
local state = {
|
||||
---@type table<string, LazySpecLoader>
|
||||
specs = {},
|
||||
handlers = require("lazy.core.handler").group(Config.plugins, true),
|
||||
config = Config.options,
|
||||
}
|
||||
|
||||
for _, spec in ipairs(M.specs()) do
|
||||
spec.funs = {}
|
||||
state.specs[spec.modname] = spec
|
||||
for _, plugin in pairs(spec.plugins) do
|
||||
if plugin.init or (plugin.opt == false and plugin.config) then
|
||||
-- no use in caching specs that need init,
|
||||
-- or specs that are in start and have a config,
|
||||
-- since we'll load the real spec during startup anyway
|
||||
state.specs[spec.modname] = nil
|
||||
break
|
||||
end
|
||||
---@cast plugin CachedPlugin
|
||||
for k, v in pairs(plugin) do
|
||||
if type(v) == "function" then
|
||||
if funs[k] then
|
||||
spec.funs[k] = spec.funs[k] or {}
|
||||
table.insert(spec.funs[k], plugin.name)
|
||||
end
|
||||
plugin[k] = nil
|
||||
elseif skip[k] then
|
||||
plugin[k] = nil
|
||||
-- Finds the plugin that has this path
|
||||
---@param path string
|
||||
---@param opts? {fast?:boolean}
|
||||
function M.find(path, opts)
|
||||
if not Config.spec then
|
||||
return
|
||||
end
|
||||
opts = opts or {}
|
||||
local lua = path:find("/lua/", 1, true)
|
||||
if lua then
|
||||
local name = path:sub(1, lua - 1)
|
||||
local slash = name:reverse():find("/", 1, true)
|
||||
if slash then
|
||||
name = name:sub(#name - slash + 2)
|
||||
if name then
|
||||
if opts.fast then
|
||||
return Config.spec.meta.plugins[name]
|
||||
end
|
||||
return Config.spec.plugins[name]
|
||||
end
|
||||
end
|
||||
end
|
||||
Cache.set("cache.state", vim.json.encode(state))
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
function M.has_errors(plugin)
|
||||
for _, task in ipairs(plugin._.tasks or {}) do
|
||||
if task:has_errors() then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Merges super values or runs the values function to override values or return new ones.
|
||||
-- Values are cached for performance.
|
||||
-- Used for opts, cmd, event, ft and keys
|
||||
---@param plugin LazyPlugin
|
||||
---@param prop string
|
||||
---@param is_list? boolean
|
||||
function M.values(plugin, prop, is_list)
|
||||
if not plugin[prop] then
|
||||
return {}
|
||||
end
|
||||
plugin._.cache = plugin._.cache or {}
|
||||
local key = prop .. (is_list and "_list" or "")
|
||||
if plugin._.cache[key] == nil then
|
||||
plugin._.cache[key] = M._values(plugin, plugin, prop, is_list)
|
||||
end
|
||||
return plugin._.cache[key]
|
||||
end
|
||||
|
||||
-- Merges super values or runs the values function to override values or return new ones
|
||||
-- Used for opts, cmd, event, ft and keys
|
||||
---@param root LazyPlugin
|
||||
---@param plugin LazyPlugin
|
||||
---@param prop string
|
||||
---@param is_list? boolean
|
||||
function M._values(root, plugin, prop, is_list)
|
||||
if not plugin[prop] then
|
||||
return {}
|
||||
end
|
||||
local super = getmetatable(plugin)
|
||||
---@type table
|
||||
local ret = super and M._values(root, super.__index, prop, is_list) or {}
|
||||
local values = rawget(plugin, prop)
|
||||
|
||||
if not values then
|
||||
return ret
|
||||
elseif type(values) == "function" then
|
||||
ret = values(root, ret) or ret
|
||||
return type(ret) == "table" and ret or { ret }
|
||||
end
|
||||
|
||||
values = type(values) == "table" and values or { values }
|
||||
if is_list then
|
||||
return Util.extend(ret, values)
|
||||
else
|
||||
---@type {path:string[], list:any[]}[]
|
||||
local lists = {}
|
||||
---@diagnostic disable-next-line: no-unknown
|
||||
for _, key in ipairs(plugin[prop .. "_extend"] or {}) do
|
||||
local path = vim.split(key, ".", { plain = true })
|
||||
local r = Util.key_get(ret, path)
|
||||
local v = Util.key_get(values, path)
|
||||
if type(r) == "table" and type(v) == "table" then
|
||||
lists[key] = { path = path, list = {} }
|
||||
vim.list_extend(lists[key].list, r)
|
||||
vim.list_extend(lists[key].list, v)
|
||||
end
|
||||
end
|
||||
local t = Util.merge(ret, values)
|
||||
for _, list in pairs(lists) do
|
||||
Util.key_set(t, list.path, list.list)
|
||||
end
|
||||
return t
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
---@class LazyUtilCore
|
||||
local M = {}
|
||||
|
||||
---@alias LazyProfile {data: string|{[string]:string}, time: number, [number]:LazyProfile}
|
||||
|
||||
---@type LazyProfile[]
|
||||
M._profiles = { { name = "lazy" } }
|
||||
M.is_win = jit.os:find("Windows")
|
||||
|
||||
---@param data (string|{[string]:string})?
|
||||
---@param time number?
|
||||
|
@ -11,7 +13,7 @@ function M.track(data, time)
|
|||
if data then
|
||||
local entry = {
|
||||
data = data,
|
||||
time = time or vim.loop.hrtime(),
|
||||
time = time or vim.uv.hrtime(),
|
||||
}
|
||||
table.insert(M._profiles[#M._profiles], entry)
|
||||
|
||||
|
@ -22,43 +24,110 @@ function M.track(data, time)
|
|||
else
|
||||
---@type LazyProfile
|
||||
local entry = table.remove(M._profiles)
|
||||
entry.time = vim.loop.hrtime() - entry.time
|
||||
entry.time = vim.uv.hrtime() - entry.time
|
||||
return entry
|
||||
end
|
||||
end
|
||||
|
||||
function M.try(fn, msg)
|
||||
function M.exiting()
|
||||
return vim.v.exiting ~= vim.NIL
|
||||
end
|
||||
|
||||
---@generic T
|
||||
---@param list T[]
|
||||
---@param fn fun(v: T):boolean?
|
||||
---@return T[]
|
||||
function M.filter(fn, list)
|
||||
local ret = {}
|
||||
for _, v in ipairs(list) do
|
||||
if fn(v) then
|
||||
table.insert(ret, v)
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
---@generic F: fun()
|
||||
---@param data (string|{[string]:string})?
|
||||
---@param fn F
|
||||
---@return F
|
||||
function M.trackfn(data, fn)
|
||||
return function(...)
|
||||
M.track(data)
|
||||
local ok, ret = pcall(fn, ...)
|
||||
M.track()
|
||||
if not ok then
|
||||
error(ret)
|
||||
end
|
||||
return ret
|
||||
end
|
||||
end
|
||||
|
||||
---@param name string
|
||||
---@return string
|
||||
function M.normname(name)
|
||||
local ret = name:lower():gsub("^n?vim%-", ""):gsub("%.n?vim$", ""):gsub("[%.%-]lua", ""):gsub("[^a-z]+", "")
|
||||
return ret
|
||||
end
|
||||
|
||||
---@return string
|
||||
function M.norm(path)
|
||||
if path:sub(1, 1) == "~" then
|
||||
local home = vim.uv.os_homedir()
|
||||
if home:sub(-1) == "\\" or home:sub(-1) == "/" then
|
||||
home = home:sub(1, -2)
|
||||
end
|
||||
path = home .. path:sub(2)
|
||||
end
|
||||
path = path:gsub("\\", "/"):gsub("/+", "/")
|
||||
return path:sub(-1) == "/" and path:sub(1, -2) or path
|
||||
end
|
||||
|
||||
---@param opts? {level?: number}
|
||||
function M.pretty_trace(opts)
|
||||
opts = opts or {}
|
||||
local Config = require("lazy.core.config")
|
||||
local trace = {}
|
||||
local level = opts.level or 2
|
||||
while true do
|
||||
local info = debug.getinfo(level, "Sln")
|
||||
if not info then
|
||||
break
|
||||
end
|
||||
if info.what ~= "C" and (Config.options.debug or not info.source:find("lazy.nvim")) then
|
||||
local source = info.source:sub(2)
|
||||
if source:find(Config.options.root, 1, true) == 1 then
|
||||
source = source:sub(#Config.options.root + 1)
|
||||
end
|
||||
source = vim.fn.fnamemodify(source, ":p:~:.") --[[@as string]]
|
||||
local line = " - " .. source .. ":" .. info.currentline
|
||||
if info.name then
|
||||
line = line .. " _in_ **" .. info.name .. "**"
|
||||
end
|
||||
table.insert(trace, line)
|
||||
end
|
||||
level = level + 1
|
||||
end
|
||||
return #trace > 0 and ("\n\n# stacktrace:\n" .. table.concat(trace, "\n")) or ""
|
||||
end
|
||||
|
||||
---@generic R
|
||||
---@param fn fun():R?
|
||||
---@param opts? string|{msg:string, on_error:fun(msg)}
|
||||
---@return R
|
||||
function M.try(fn, opts)
|
||||
opts = type(opts) == "string" and { msg = opts } or opts or {}
|
||||
local msg = opts.msg
|
||||
-- error handler
|
||||
local error_handler = function(err)
|
||||
local Config = require("lazy.core.config")
|
||||
local trace = {}
|
||||
local level = 1
|
||||
while true do
|
||||
local info = debug.getinfo(level, "Sln")
|
||||
if not info then
|
||||
break
|
||||
end
|
||||
if info.what == "Lua" and not info.source:find("lazy.nvim") then
|
||||
local source = info.source:sub(2)
|
||||
if source:find(Config.options.packpath, 1, true) == 1 then
|
||||
source = source:sub(#Config.options.packpath + 1):gsub("^/opt/", ""):gsub("^/start/", "")
|
||||
end
|
||||
source = vim.fn.fnamemodify(source, ":p:~:.")
|
||||
local line = " - " .. source .. ":" .. info.currentline
|
||||
if info.name then
|
||||
line = line .. " _in_ **" .. info.name .. "**"
|
||||
end
|
||||
table.insert(trace, line)
|
||||
end
|
||||
level = level + 1
|
||||
msg = (msg and (msg .. "\n\n") or "") .. err .. M.pretty_trace()
|
||||
if opts.on_error then
|
||||
opts.on_error(msg)
|
||||
else
|
||||
vim.schedule(function()
|
||||
M.error(msg)
|
||||
end)
|
||||
end
|
||||
msg = msg .. "\n\n" .. err
|
||||
if #trace > 0 then
|
||||
msg = msg .. "\n\n# stacktrace:\n" .. table.concat(trace, "\n")
|
||||
end
|
||||
vim.schedule(function()
|
||||
M.error(msg)
|
||||
end)
|
||||
return err
|
||||
end
|
||||
|
||||
|
@ -67,6 +136,20 @@ function M.try(fn, msg)
|
|||
return ok and result or nil
|
||||
end
|
||||
|
||||
function M.get_source()
|
||||
local f = 2
|
||||
while true do
|
||||
local info = debug.getinfo(f, "S")
|
||||
if not info then
|
||||
break
|
||||
end
|
||||
if info.what ~= "C" and not info.source:find("lazy.nvim", 1, true) and info.source ~= "@vim/loader.lua" then
|
||||
return info.source:sub(2)
|
||||
end
|
||||
f = f + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- Fast implementation to check if a table is a list
|
||||
---@param t table
|
||||
function M.is_list(t)
|
||||
|
@ -83,9 +166,15 @@ end
|
|||
|
||||
function M.very_lazy()
|
||||
local function _load()
|
||||
vim.defer_fn(function()
|
||||
vim.cmd("do User VeryLazy")
|
||||
end, 100)
|
||||
vim.schedule(function()
|
||||
if vim.v.exiting ~= vim.NIL then
|
||||
return
|
||||
end
|
||||
vim.g.did_very_lazy = true
|
||||
M.track({ event = "VeryLazy" })
|
||||
vim.api.nvim_exec_autocmds("User", { pattern = "VeryLazy", modeline = false })
|
||||
M.track()
|
||||
end)
|
||||
end
|
||||
|
||||
vim.api.nvim_create_autocmd("User", {
|
||||
|
@ -95,7 +184,7 @@ function M.very_lazy()
|
|||
if vim.v.vim_did_enter == 1 then
|
||||
_load()
|
||||
else
|
||||
vim.api.nvim_create_autocmd("VimEnter", {
|
||||
vim.api.nvim_create_autocmd("UIEnter", {
|
||||
once = true,
|
||||
callback = function()
|
||||
_load()
|
||||
|
@ -110,13 +199,19 @@ end
|
|||
---@param path string
|
||||
---@param fn fun(path: string, name:string, type:FileType):boolean?
|
||||
function M.ls(path, fn)
|
||||
local handle = vim.loop.fs_scandir(path)
|
||||
local handle = vim.uv.fs_scandir(path)
|
||||
while handle do
|
||||
local name, t = vim.loop.fs_scandir_next(handle)
|
||||
local name, t = vim.uv.fs_scandir_next(handle)
|
||||
if not name then
|
||||
break
|
||||
end
|
||||
if fn(path .. "/" .. name, name, t) == false then
|
||||
|
||||
local fname = path .. "/" .. name
|
||||
|
||||
-- HACK: type is not always returned due to a bug in luv,
|
||||
-- so fecth it with fs_stat instead when needed.
|
||||
-- see https://github.com/folke/lazy.nvim/issues/306
|
||||
if fn(fname, name, t or vim.uv.fs_stat(fname).type) == false then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
@ -135,35 +230,287 @@ end
|
|||
|
||||
---@param root string
|
||||
---@param fn fun(modname:string, modpath:string)
|
||||
function M.lsmod(root, fn)
|
||||
---@param modname? string
|
||||
function M.walkmods(root, fn, modname)
|
||||
modname = modname and (modname:gsub("%.$", "") .. ".") or ""
|
||||
M.ls(root, function(path, name, type)
|
||||
if type == "file" and name:sub(-4) == ".lua" and name ~= "init.lua" then
|
||||
fn(name:sub(1, -5), path)
|
||||
elseif type == "directory" and vim.loop.fs_stat(path .. "/init.lua") then
|
||||
fn(name, path .. "/init.lua")
|
||||
if name == "init.lua" then
|
||||
fn(modname:gsub("%.$", ""), path)
|
||||
elseif (type == "file" or type == "link") and name:sub(-4) == ".lua" then
|
||||
fn(modname .. name:sub(1, -5), path)
|
||||
elseif type == "directory" then
|
||||
M.walkmods(path, fn, modname .. name .. ".")
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function M.notify(msg, level)
|
||||
vim.notify(msg, level, {
|
||||
---@param modname string
|
||||
---@return string
|
||||
function M.topmod(modname)
|
||||
return modname:match("^[^./]+") or modname
|
||||
end
|
||||
|
||||
---@type table<string, string[]>
|
||||
M.unloaded_cache = {}
|
||||
|
||||
---@param modname string
|
||||
---@param opts? {cache?:boolean}
|
||||
function M.get_unloaded_rtp(modname, opts)
|
||||
opts = opts or {}
|
||||
|
||||
local topmod = M.topmod(modname)
|
||||
if opts.cache and M.unloaded_cache[topmod] then
|
||||
return M.unloaded_cache[topmod], true
|
||||
end
|
||||
|
||||
local norm = M.normname(topmod)
|
||||
|
||||
---@type string[]
|
||||
local rtp = {}
|
||||
local Config = require("lazy.core.config")
|
||||
if Config.spec then
|
||||
for _, plugin in pairs(Config.spec.plugins) do
|
||||
if not (plugin._.loaded or plugin.module == false or plugin.virtual) then
|
||||
if norm == M.normname(plugin.name) then
|
||||
table.insert(rtp, 1, plugin.dir)
|
||||
else
|
||||
table.insert(rtp, plugin.dir)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
M.unloaded_cache[topmod] = rtp
|
||||
return rtp, false
|
||||
end
|
||||
|
||||
function M.find_root(modname)
|
||||
local paths, cached = M.get_unloaded_rtp(modname, { cache = true })
|
||||
|
||||
local ret = require("lazy.core.cache").find(modname, {
|
||||
rtp = true,
|
||||
paths = paths,
|
||||
patterns = { ".lua", "" },
|
||||
})[1]
|
||||
|
||||
if not ret and cached then
|
||||
paths = M.get_unloaded_rtp(modname)
|
||||
ret = require("lazy.core.cache").find(modname, {
|
||||
rtp = false,
|
||||
paths = paths,
|
||||
patterns = { ".lua", "" },
|
||||
})[1]
|
||||
end
|
||||
if ret then
|
||||
return ret.modpath:gsub("%.lua$", ""), ret.modpath
|
||||
end
|
||||
end
|
||||
|
||||
---@param modname string
|
||||
---@param fn fun(modname:string, modpath:string)
|
||||
function M.lsmod(modname, fn)
|
||||
local root, match = M.find_root(modname)
|
||||
if not root then
|
||||
return
|
||||
end
|
||||
|
||||
if match:sub(-4) == ".lua" then
|
||||
fn(modname, match)
|
||||
if not vim.uv.fs_stat(root) then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
M.ls(root, function(path, name, type)
|
||||
if name == "init.lua" then
|
||||
fn(modname, path)
|
||||
elseif (type == "file" or type == "link") and name:sub(-4) == ".lua" then
|
||||
fn(modname .. "." .. name:sub(1, -5), path)
|
||||
elseif type == "directory" and vim.uv.fs_stat(path .. "/init.lua") then
|
||||
fn(modname .. "." .. name, path .. "/init.lua")
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
---@generic T
|
||||
---@param list T[]
|
||||
---@param add T[]
|
||||
---@return T[]
|
||||
function M.extend(list, add)
|
||||
local idx = {}
|
||||
for _, v in ipairs(list) do
|
||||
idx[v] = v
|
||||
end
|
||||
for _, a in ipairs(add) do
|
||||
if not idx[a] then
|
||||
table.insert(list, a)
|
||||
end
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
---@alias LazyNotifyOpts {lang?:string, title?:string, level?:number, once?:boolean, stacktrace?:boolean, stacklevel?:number}
|
||||
|
||||
---@param msg string|string[]
|
||||
---@param opts? LazyNotifyOpts
|
||||
function M.notify(msg, opts)
|
||||
if vim.in_fast_event() then
|
||||
return vim.schedule(function()
|
||||
M.notify(msg, opts)
|
||||
end)
|
||||
end
|
||||
|
||||
opts = opts or {}
|
||||
if type(msg) == "table" then
|
||||
msg = table.concat(
|
||||
vim.tbl_filter(function(line)
|
||||
return line or false
|
||||
end, msg),
|
||||
"\n"
|
||||
)
|
||||
end
|
||||
if opts.stacktrace then
|
||||
msg = msg .. M.pretty_trace({ level = opts.stacklevel or 2 })
|
||||
end
|
||||
local lang = opts.lang or "markdown"
|
||||
local n = opts.once and vim.notify_once or vim.notify
|
||||
n(msg, opts.level or vim.log.levels.INFO, {
|
||||
ft = lang,
|
||||
on_open = function(win)
|
||||
local ok = pcall(function()
|
||||
vim.treesitter.language.add("markdown")
|
||||
end)
|
||||
if not ok then
|
||||
pcall(require, "nvim-treesitter")
|
||||
end
|
||||
vim.wo[win].conceallevel = 3
|
||||
vim.wo[win].concealcursor = ""
|
||||
vim.wo[win].spell = false
|
||||
local buf = vim.api.nvim_win_get_buf(win)
|
||||
vim.bo[buf].filetype = "markdown"
|
||||
if not pcall(vim.treesitter.start, buf, lang) then
|
||||
vim.bo[buf].filetype = lang
|
||||
vim.bo[buf].syntax = lang
|
||||
end
|
||||
end,
|
||||
title = "lazy.nvim",
|
||||
title = opts.title or "lazy.nvim",
|
||||
})
|
||||
end
|
||||
|
||||
function M.error(msg)
|
||||
M.notify(msg, vim.log.levels.ERROR)
|
||||
---@param msg string|string[]
|
||||
---@param opts? LazyNotifyOpts
|
||||
function M.error(msg, opts)
|
||||
opts = opts or {}
|
||||
opts.level = vim.log.levels.ERROR
|
||||
M.notify(msg, opts)
|
||||
end
|
||||
|
||||
function M.info(msg)
|
||||
M.notify(msg, vim.log.levels.INFO)
|
||||
---@param msg string|string[]
|
||||
---@param opts? LazyNotifyOpts
|
||||
function M.info(msg, opts)
|
||||
opts = opts or {}
|
||||
opts.level = vim.log.levels.INFO
|
||||
M.notify(msg, opts)
|
||||
end
|
||||
|
||||
---@param msg string|string[]
|
||||
---@param opts? LazyNotifyOpts
|
||||
function M.warn(msg, opts)
|
||||
opts = opts or {}
|
||||
opts.level = vim.log.levels.WARN
|
||||
M.notify(msg, opts)
|
||||
end
|
||||
|
||||
---@param msg string|table
|
||||
---@param opts? LazyNotifyOpts
|
||||
function M.debug(msg, opts)
|
||||
if not require("lazy.core.config").options.debug then
|
||||
return
|
||||
end
|
||||
opts = opts or {}
|
||||
if opts.title then
|
||||
opts.title = "lazy.nvim: " .. opts.title
|
||||
end
|
||||
if type(msg) == "string" then
|
||||
M.notify(msg, opts)
|
||||
else
|
||||
opts.lang = "lua"
|
||||
M.notify(vim.inspect(msg), opts)
|
||||
end
|
||||
end
|
||||
|
||||
local function can_merge(v)
|
||||
return type(v) == "table" and (vim.tbl_isempty(v) or not M.is_list(v))
|
||||
end
|
||||
|
||||
--- Merges the values similar to vim.tbl_deep_extend with the **force** behavior,
|
||||
--- but the values can be any type, in which case they override the values on the left.
|
||||
--- Values will me merged in-place in the first left-most table. If you want the result to be in
|
||||
--- a new table, then simply pass an empty table as the first argument `vim.merge({}, ...)`
|
||||
--- Supports clearing values by setting a key to `vim.NIL`
|
||||
---@generic T
|
||||
---@param ... T
|
||||
---@return T
|
||||
function M.merge(...)
|
||||
local ret = select(1, ...)
|
||||
if ret == vim.NIL then
|
||||
ret = nil
|
||||
end
|
||||
for i = 2, select("#", ...) do
|
||||
local value = select(i, ...)
|
||||
if can_merge(ret) and can_merge(value) then
|
||||
for k, v in pairs(value) do
|
||||
ret[k] = M.merge(ret[k], v)
|
||||
end
|
||||
elseif value == vim.NIL then
|
||||
ret = nil
|
||||
elseif value ~= nil then
|
||||
ret = value
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function M.lazy_require(module)
|
||||
local mod = nil
|
||||
-- if already loaded, return the module
|
||||
-- otherwise return a lazy module
|
||||
return type(package.loaded[module]) == "table" and package.loaded[module]
|
||||
or setmetatable({}, {
|
||||
__index = function(_, key)
|
||||
mod = mod or require(module)
|
||||
return mod[key]
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param key string|string[]
|
||||
---@return any
|
||||
function M.key_get(t, key)
|
||||
local path = type(key) == "table" and key or vim.split(key, ".", true)
|
||||
local value = t
|
||||
for _, k in ipairs(path) do
|
||||
if type(value) ~= "table" then
|
||||
return value
|
||||
end
|
||||
value = value[k]
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@param key string|string[]
|
||||
---@param value any
|
||||
function M.key_set(t, key, value)
|
||||
local path = type(key) == "table" and key or vim.split(key, ".", true)
|
||||
local last = t
|
||||
for i = 1, #path - 1 do
|
||||
local k = path[i]
|
||||
if type(last[k]) ~= "table" then
|
||||
last[k] = {}
|
||||
end
|
||||
last = last[k]
|
||||
end
|
||||
last[path[#path]] = value
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
167
lua/lazy/docs.lua
Normal file
167
lua/lazy/docs.lua
Normal file
|
@ -0,0 +1,167 @@
|
|||
local Util = require("lazy.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
function M.indent(str, indent)
|
||||
local lines = vim.split(str, "\n")
|
||||
for l, line in ipairs(lines) do
|
||||
lines[l] = (" "):rep(indent) .. line
|
||||
end
|
||||
return table.concat(lines, "\n")
|
||||
end
|
||||
|
||||
---@param str string
|
||||
function M.fix_indent(str)
|
||||
local lines = vim.split(str, "\n")
|
||||
|
||||
local first = table.remove(lines, 1)
|
||||
|
||||
local width = 120
|
||||
for _, line in ipairs(lines) do
|
||||
if not line:find("^%s*$") then
|
||||
width = math.min(width, #line:match("^%s*"))
|
||||
end
|
||||
end
|
||||
|
||||
for l, line in ipairs(lines) do
|
||||
lines[l] = line:sub(width + 1)
|
||||
end
|
||||
table.insert(lines, 1, first)
|
||||
return table.concat(lines, "\n")
|
||||
end
|
||||
|
||||
---@alias ReadmeBlock {content:string, lang?:string}
|
||||
---@param contents table<string, ReadmeBlock|string>
|
||||
---@param readme_file? string
|
||||
function M.save(contents, readme_file)
|
||||
local readme = Util.read_file(readme_file or "README.md")
|
||||
for tag, block in pairs(contents) do
|
||||
if type(block) == "string" then
|
||||
block = { content = block, lang = "lua" }
|
||||
end
|
||||
---@cast block ReadmeBlock
|
||||
local content = M.fix_indent(block.content)
|
||||
content = content:gsub("%%", "%%%%")
|
||||
content = vim.trim(content)
|
||||
local pattern = "(<%!%-%- " .. tag .. ":start %-%->).*(<%!%-%- " .. tag .. ":end %-%->)"
|
||||
if not readme:find(pattern) then
|
||||
error("tag " .. tag .. " not found")
|
||||
end
|
||||
if block.lang then
|
||||
readme = readme:gsub(pattern, "%1\n\n```" .. block.lang .. "\n" .. content .. "\n```\n\n%2")
|
||||
else
|
||||
readme = readme:gsub(pattern, "%1\n\n" .. content .. "\n\n%2")
|
||||
end
|
||||
end
|
||||
|
||||
Util.write_file(readme_file or "README.md", readme)
|
||||
vim.cmd.checktime()
|
||||
end
|
||||
|
||||
---@return string
|
||||
function M.extract(file, pattern)
|
||||
local init = Util.read_file(file)
|
||||
return assert(init:match(pattern))
|
||||
end
|
||||
|
||||
---@return ReadmeBlock
|
||||
function M.commands()
|
||||
local commands = require("lazy.view.commands").commands
|
||||
local modes = require("lazy.view.config").commands
|
||||
modes.load.opts = true
|
||||
local lines = {
|
||||
{ "Command", "Lua", "Description" },
|
||||
{ "---", "---", "---" },
|
||||
}
|
||||
Util.foreach(modes, function(name, mode)
|
||||
if commands[name] then
|
||||
if mode.plugins_required then
|
||||
lines[#lines + 1] = {
|
||||
("`:Lazy %s {plugins}`"):format(name),
|
||||
([[`require("lazy").%s(opts)`]]):format(name),
|
||||
mode.desc,
|
||||
}
|
||||
elseif mode.plugins then
|
||||
lines[#lines + 1] = {
|
||||
("`:Lazy %s [plugins]`"):format(name),
|
||||
([[`require("lazy").%s(opts?)`]]):format(name),
|
||||
mode.desc,
|
||||
}
|
||||
else
|
||||
lines[#lines + 1] = {
|
||||
("`:Lazy %s`"):format(name),
|
||||
([[`require("lazy").%s()`]]):format(name),
|
||||
mode.desc,
|
||||
}
|
||||
end
|
||||
end
|
||||
end)
|
||||
return { content = M.table(lines) }
|
||||
end
|
||||
|
||||
---@param lines string[][]
|
||||
function M.table(lines)
|
||||
---@type string[]
|
||||
local ret = {}
|
||||
for _, line in ipairs(lines) do
|
||||
ret[#ret + 1] = "| " .. table.concat(line, " | ") .. " |"
|
||||
end
|
||||
return table.concat(ret, "\n")
|
||||
end
|
||||
|
||||
---@param opts? {name?:string, path?:string, modname?:string}
|
||||
---@return ReadmeBlock
|
||||
function M.colors(opts)
|
||||
opts = vim.tbl_extend("force", {
|
||||
name = "Lazy",
|
||||
path = "lua/lazy/view/colors.lua",
|
||||
modname = "lazy.view.colors",
|
||||
}, opts or {})
|
||||
local str = M.extract(opts.path, "\nM%.colors = ({.-\n})")
|
||||
---@type table<string,string>
|
||||
local comments = {}
|
||||
for _, line in ipairs(vim.split(str, "\n")) do
|
||||
local group, desc = line:match("^ (%w+) = .* -- (.*)")
|
||||
if group then
|
||||
comments[group] = desc
|
||||
end
|
||||
end
|
||||
local lines = {
|
||||
{ "Highlight Group", "Default Group", "Description" },
|
||||
{ "---", "---", "---" },
|
||||
}
|
||||
Util.foreach(require(opts.modname).colors, function(group, link)
|
||||
link = type(link) == "table" and "`" .. vim.inspect(link):gsub("%s+", " ") .. "`" or "***" .. link .. "***"
|
||||
lines[#lines + 1] = { "**" .. opts.name .. group .. "**", link, comments[group] or "" }
|
||||
end)
|
||||
return { content = M.table(lines) }
|
||||
end
|
||||
|
||||
function M.update()
|
||||
local config = M.extract("lua/lazy/core/config.lua", "\nM%.defaults = ({.-\n})")
|
||||
config = config:gsub("%s*debug = false.\n", "\n")
|
||||
M.save({
|
||||
bootstrap = M.extract("lua/lazy/init.lua", "function M%.bootstrap%(%)\n(.-)\nend"),
|
||||
stats = M.extract("lua/lazy/stats.lua", "\nM%._stats = ({.-\n})"),
|
||||
config = config,
|
||||
spec = Util.read_file("lua/lazy/example.lua"),
|
||||
commands = M.commands(),
|
||||
colors = M.colors(),
|
||||
})
|
||||
end
|
||||
|
||||
---@param plugins? LazyPlugin[]
|
||||
---@return ReadmeBlock
|
||||
function M.plugins(plugins)
|
||||
plugins = plugins or require("lazy.core.config").plugins
|
||||
---@type string[]
|
||||
local lines = {}
|
||||
Util.foreach(plugins, function(name, plugin)
|
||||
if plugin.url then
|
||||
lines[#lines + 1] = "- [" .. name .. "](" .. plugin.url:gsub("%.git$", "") .. ")"
|
||||
end
|
||||
end)
|
||||
return { content = table.concat(lines, "\n") }
|
||||
end
|
||||
|
||||
return M
|
87
lua/lazy/example.lua
Normal file
87
lua/lazy/example.lua
Normal file
|
@ -0,0 +1,87 @@
|
|||
return {
|
||||
-- the colorscheme should be available when starting Neovim
|
||||
{
|
||||
"folke/tokyonight.nvim",
|
||||
lazy = false, -- make sure we load this during startup if it is your main colorscheme
|
||||
priority = 1000, -- make sure to load this before all the other start plugins
|
||||
config = function()
|
||||
-- load the colorscheme here
|
||||
vim.cmd([[colorscheme tokyonight]])
|
||||
end,
|
||||
},
|
||||
|
||||
-- I have a separate config.mappings file where I require which-key.
|
||||
-- With lazy the plugin will be automatically loaded when it is required somewhere
|
||||
{ "folke/which-key.nvim", lazy = true },
|
||||
|
||||
{
|
||||
"nvim-neorg/neorg",
|
||||
-- lazy-load on filetype
|
||||
ft = "norg",
|
||||
-- options for neorg. This will automatically call `require("neorg").setup(opts)`
|
||||
opts = {
|
||||
load = {
|
||||
["core.defaults"] = {},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
"dstein64/vim-startuptime",
|
||||
-- lazy-load on a command
|
||||
cmd = "StartupTime",
|
||||
-- init is called during startup. Configuration for vim plugins typically should be set in an init function
|
||||
init = function()
|
||||
vim.g.startuptime_tries = 10
|
||||
end,
|
||||
},
|
||||
|
||||
{
|
||||
"hrsh7th/nvim-cmp",
|
||||
-- load cmp on InsertEnter
|
||||
event = "InsertEnter",
|
||||
-- these dependencies will only be loaded when cmp loads
|
||||
-- dependencies are always lazy-loaded unless specified otherwise
|
||||
dependencies = {
|
||||
"hrsh7th/cmp-nvim-lsp",
|
||||
"hrsh7th/cmp-buffer",
|
||||
},
|
||||
config = function()
|
||||
-- ...
|
||||
end,
|
||||
},
|
||||
|
||||
-- if some code requires a module from an unloaded plugin, it will be automatically loaded.
|
||||
-- So for api plugins like devicons, we can always set lazy=true
|
||||
{ "nvim-tree/nvim-web-devicons", lazy = true },
|
||||
|
||||
-- you can use the VeryLazy event for things that can
|
||||
-- load later and are not important for the initial UI
|
||||
{ "stevearc/dressing.nvim", event = "VeryLazy" },
|
||||
|
||||
{
|
||||
"Wansmer/treesj",
|
||||
keys = {
|
||||
{ "J", "<cmd>TSJToggle<cr>", desc = "Join Toggle" },
|
||||
},
|
||||
opts = { use_default_keymaps = false, max_join_length = 150 },
|
||||
},
|
||||
|
||||
{
|
||||
"monaqa/dial.nvim",
|
||||
-- lazy-load on keys
|
||||
-- mode is `n` by default. For more advanced options, check the section on key mappings
|
||||
keys = { "<C-a>", { "<C-x>", mode = "n" } },
|
||||
},
|
||||
|
||||
-- local plugins need to be explicitly configured with dir
|
||||
{ dir = "~/projects/secret.nvim" },
|
||||
|
||||
-- you can use a custom url to fetch a plugin
|
||||
{ url = "git@github.com:folke/noice.nvim.git" },
|
||||
|
||||
-- local plugins can also be configured with the dev option.
|
||||
-- This will use {config.dev.path}/noice.nvim/ instead of fetching it from GitHub
|
||||
-- With the dev option, you can easily switch between the local and installed version of a plugin
|
||||
{ "folke/noice.nvim", dev = true },
|
||||
}
|
217
lua/lazy/health.lua
Normal file
217
lua/lazy/health.lua
Normal file
|
@ -0,0 +1,217 @@
|
|||
local Config = require("lazy.core.config")
|
||||
local Process = require("lazy.manage.process")
|
||||
local uv = vim.uv or vim.loop
|
||||
|
||||
local M = {}
|
||||
|
||||
-- "report_" prefix has been deprecated, use the recommended replacements if they exist.
|
||||
local start = vim.health.start or vim.health.report_start
|
||||
local ok = vim.health.ok or vim.health.report_ok
|
||||
local warn = vim.health.warn or vim.health.report_warn
|
||||
local error = vim.health.error or vim.health.report_error
|
||||
local info = vim.health.info or vim.health.report_info
|
||||
|
||||
---@class LazyHealth
|
||||
---@field error? fun(msg:string)
|
||||
---@field warn? fun(msg:string)
|
||||
---@field ok? fun(msg:string)
|
||||
|
||||
---@class LazyHealthHave : LazyHealth
|
||||
---@field version? string
|
||||
---@field version_pattern? string
|
||||
---@field optional? boolean
|
||||
|
||||
---@param cmd string|string[]
|
||||
---@param opts? LazyHealthHave
|
||||
function M.have(cmd, opts)
|
||||
opts = vim.tbl_extend("force", {
|
||||
error = error,
|
||||
warn = warn,
|
||||
ok = ok,
|
||||
version = "--version",
|
||||
}, opts or {})
|
||||
|
||||
cmd = type(cmd) == "table" and cmd or { cmd }
|
||||
---@cast cmd string[]
|
||||
---@type string?
|
||||
local found
|
||||
for _, c in ipairs(cmd) do
|
||||
if vim.fn.executable(c) == 1 then
|
||||
local out, exit_code = Process.exec({ c, opts.version })
|
||||
if exit_code ~= 0 then
|
||||
opts.error(("failed to get version of {%s}\n%s"):format(c, table.concat(out, "\n")))
|
||||
else
|
||||
local version = vim.trim(out[1] or "")
|
||||
version = version:gsub("^%s*" .. vim.pesc(c) .. "%s*", "")
|
||||
if opts.version_pattern and not version:find(opts.version_pattern, 1, true) then
|
||||
opts.warn(("`%s` version `%s` needed, but found `%s`"):format(c, opts.version_pattern, version))
|
||||
else
|
||||
found = ("{%s} `%s`"):format(c, version)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if found then
|
||||
opts.ok(found)
|
||||
return true
|
||||
else
|
||||
(opts.optional and opts.warn or opts.error)(
|
||||
("{%s} %snot installed"):format(
|
||||
table.concat(cmd, "} or {"),
|
||||
opts.version_pattern and "version `" .. opts.version_pattern .. "` " or ""
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function M.check()
|
||||
start("lazy.nvim")
|
||||
info("{lazy.nvim} version `" .. Config.version .. "`")
|
||||
|
||||
M.have("git")
|
||||
|
||||
local sites = vim.opt.packpath:get()
|
||||
local default_site = vim.fn.stdpath("data") .. "/site"
|
||||
if not vim.tbl_contains(sites, default_site) then
|
||||
sites[#sites + 1] = default_site
|
||||
end
|
||||
|
||||
local existing = false
|
||||
for _, site in pairs(sites) do
|
||||
for _, packs in ipairs(vim.fn.expand(site .. "/pack/*", false, true)) do
|
||||
if not packs:find("[/\\]dist$") and uv.fs_stat(packs) then
|
||||
existing = true
|
||||
warn("found existing packages at `" .. packs .. "`")
|
||||
end
|
||||
end
|
||||
end
|
||||
if not existing then
|
||||
ok("no existing packages found by other package managers")
|
||||
end
|
||||
|
||||
for _, name in ipairs({ "packer", "plugged", "paq", "pckr", "mini.deps" }) do
|
||||
for _, path in ipairs(vim.opt.rtp:get()) do
|
||||
if path:find(name, 1, true) then
|
||||
error("Found paths on the rtp from another plugin manager `" .. name .. "`")
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local packer_compiled = vim.fn.stdpath("config") .. "/plugin/packer_compiled.lua"
|
||||
if uv.fs_stat(packer_compiled) then
|
||||
error("please remove the file `" .. packer_compiled .. "`")
|
||||
else
|
||||
ok("packer_compiled.lua not found")
|
||||
end
|
||||
|
||||
local spec = Config.spec
|
||||
if spec == nil then
|
||||
error('No plugins loaded. Did you forget to run `require("lazy").setup()`?')
|
||||
else
|
||||
for _, plugin in pairs(spec.plugins) do
|
||||
M.check_valid(plugin)
|
||||
end
|
||||
if #spec.notifs > 0 then
|
||||
error("Issues were reported when loading your specs:")
|
||||
for _, notif in ipairs(spec.notifs) do
|
||||
local lines = vim.split(notif.msg, "\n")
|
||||
for _, line in ipairs(lines) do
|
||||
if notif.level == vim.log.levels.ERROR then
|
||||
error(line)
|
||||
else
|
||||
warn(line)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
start("luarocks")
|
||||
if Config.options.rocks.enabled then
|
||||
if Config.hererocks() then
|
||||
info("checking `hererocks` installation")
|
||||
else
|
||||
info("checking `luarocks` installation")
|
||||
end
|
||||
local need_luarocks = {}
|
||||
for _, plugin in pairs(spec.plugins) do
|
||||
if plugin.build == "rockspec" then
|
||||
table.insert(need_luarocks, plugin.name)
|
||||
end
|
||||
end
|
||||
if #need_luarocks == 0 then
|
||||
ok("no plugins require `luarocks`, so you can ignore any warnings below")
|
||||
else
|
||||
local lines = vim.tbl_map(function(name)
|
||||
return " * `" .. name .. "`"
|
||||
end, need_luarocks)
|
||||
|
||||
info("you have some plugins that require `luarocks`:\n" .. table.concat(lines, "\n"))
|
||||
end
|
||||
local ok = require("lazy.pkg.rockspec").check({
|
||||
error = #need_luarocks > 0 and error or warn,
|
||||
warn = warn,
|
||||
ok = ok,
|
||||
})
|
||||
if not ok then
|
||||
warn(table.concat({
|
||||
"Lazy won't be able to install plugins that require `luarocks`.",
|
||||
"Here's what you can do:",
|
||||
" - fix your `luarocks` installation",
|
||||
Config.hererocks() and " - disable *hererocks* with `opts.rocks.hererocks = false`"
|
||||
or " - enable `hererocks` with `opts.rocks.hererocks = true`",
|
||||
" - disable `luarocks` support completely with `opts.rocks.enabled = false`",
|
||||
}, "\n"))
|
||||
end
|
||||
else
|
||||
ok("luarocks disabled")
|
||||
end
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
function M.check_valid(plugin)
|
||||
for key in pairs(plugin) do
|
||||
if not vim.tbl_contains(M.valid, key) then
|
||||
if key ~= "module" or type(plugin.module) ~= "boolean" then
|
||||
warn("{" .. plugin.name .. "}: unknown key <" .. key .. ">")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.valid = {
|
||||
1,
|
||||
"_",
|
||||
"branch",
|
||||
"build",
|
||||
"cmd",
|
||||
"commit",
|
||||
"cond",
|
||||
"config",
|
||||
"deactivate",
|
||||
"dependencies",
|
||||
"dev",
|
||||
"dir",
|
||||
"enabled",
|
||||
"event",
|
||||
"ft",
|
||||
"import",
|
||||
"init",
|
||||
"keys",
|
||||
"lazy",
|
||||
"main",
|
||||
"module",
|
||||
"name",
|
||||
"optional",
|
||||
"opts",
|
||||
"pin",
|
||||
"priority",
|
||||
"submodules",
|
||||
"tag",
|
||||
"url",
|
||||
"version",
|
||||
}
|
||||
|
||||
return M
|
71
lua/lazy/help.lua
Normal file
71
lua/lazy/help.lua
Normal file
|
@ -0,0 +1,71 @@
|
|||
local Config = require("lazy.core.config")
|
||||
local Util = require("lazy.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
function M.index(plugin)
|
||||
if Config.options.readme.skip_if_doc_exists and vim.uv.fs_stat(plugin.dir .. "/doc") then
|
||||
return {}
|
||||
end
|
||||
|
||||
local files = {}
|
||||
|
||||
for _, file in ipairs(Config.options.readme.files) do
|
||||
vim.list_extend(files, vim.fn.expand(plugin.dir .. "/" .. file, false, true))
|
||||
end
|
||||
|
||||
---@type table<string,{file:string, tag:string, line:string}>
|
||||
local tags = {}
|
||||
for _, file in ipairs(files) do
|
||||
file = Util.norm(file)
|
||||
if vim.uv.fs_stat(file) then
|
||||
local rel_file = file:sub(#plugin.dir + 1)
|
||||
local tag_filename = plugin.name .. vim.fn.fnamemodify(rel_file, ":h"):gsub("%W+", "-"):gsub("^%-$", "")
|
||||
local lines = vim.split(Util.read_file(file), "\n")
|
||||
for _, line in ipairs(lines) do
|
||||
local title = line:match("^#+%s*(.*)")
|
||||
if title then
|
||||
local tag = tag_filename .. "-" .. title:lower():gsub("%W+", "-")
|
||||
tag = tag:gsub("%-+", "-"):gsub("%-$", "")
|
||||
line = line:gsub("([%[%]/])", "\\%1")
|
||||
tags[tag] = { tag = tag, line = line, file = tag_filename .. ".md" }
|
||||
end
|
||||
end
|
||||
table.insert(lines, [[<!-- vim: set ft=markdown: -->]])
|
||||
Util.write_file(Config.options.readme.root .. "/doc/" .. tag_filename .. ".md", table.concat(lines, "\n"))
|
||||
end
|
||||
end
|
||||
return tags
|
||||
end
|
||||
|
||||
function M.update()
|
||||
if Config.plugins["lazy.nvim"] then
|
||||
vim.cmd.helptags(Config.plugins["lazy.nvim"].dir .. "/doc")
|
||||
end
|
||||
if Config.options.readme.enabled == false then
|
||||
return
|
||||
end
|
||||
|
||||
local docs = Config.options.readme.root .. "/doc"
|
||||
vim.fn.mkdir(docs, "p")
|
||||
|
||||
Util.ls(docs, function(path, name, type)
|
||||
if type == "file" and name:sub(-2) == "md" then
|
||||
vim.uv.fs_unlink(path)
|
||||
end
|
||||
end)
|
||||
---@type {file:string, tag:string, line:string}[]
|
||||
local tags = {}
|
||||
for _, plugin in pairs(Config.plugins) do
|
||||
for key, tag in pairs(M.index(plugin)) do
|
||||
tags[key] = tag
|
||||
end
|
||||
end
|
||||
local lines = { [[!_TAG_FILE_ENCODING utf-8 //]] }
|
||||
Util.foreach(tags, function(_, tag)
|
||||
table.insert(lines, ("%s\t%s\t/%s"):format(tag.tag, tag.file, tag.line))
|
||||
end, { case_sensitive = true })
|
||||
Util.write_file(docs .. "/tags", table.concat(lines, "\n"))
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,90 +1,151 @@
|
|||
---@class Lazy: LazyCommands
|
||||
local M = {}
|
||||
M._start = 0
|
||||
|
||||
---@param opts? LazyConfig
|
||||
function M.setup(opts)
|
||||
local cache_start = vim.loop.hrtime()
|
||||
require("lazy.core.cache").setup()
|
||||
vim.uv = vim.uv or vim.loop
|
||||
|
||||
local function profile_require()
|
||||
local done = {} ---@type table<string, true>
|
||||
local r = require
|
||||
_G.require = function(modname)
|
||||
local Util = package.loaded["lazy.core.util"]
|
||||
if Util and not done[modname] then
|
||||
done[modname] = true
|
||||
Util.track({ require = modname })
|
||||
local ok, ret = pcall(function()
|
||||
return vim.F.pack_len(r(modname))
|
||||
end)
|
||||
Util.track()
|
||||
if not ok then
|
||||
error(ret, 2)
|
||||
end
|
||||
return vim.F.unpack_len(ret)
|
||||
else
|
||||
return r(modname)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@overload fun(opts: LazyConfig)
|
||||
---@overload fun(spec:LazySpec, opts: LazyConfig)
|
||||
function M.setup(spec, opts)
|
||||
if type(spec) == "table" and spec.spec then
|
||||
---@cast spec LazyConfig
|
||||
opts = spec
|
||||
else
|
||||
opts = opts or {}
|
||||
opts.spec = spec
|
||||
end
|
||||
|
||||
M._start = M._start == 0 and vim.uv.hrtime() or M._start
|
||||
if vim.g.lazy_did_setup then
|
||||
return vim.notify(
|
||||
"Re-sourcing your config is not supported with lazy.nvim",
|
||||
vim.log.levels.WARN,
|
||||
{ title = "lazy.nvim" }
|
||||
)
|
||||
end
|
||||
vim.g.lazy_did_setup = true
|
||||
if not vim.go.loadplugins then
|
||||
return
|
||||
end
|
||||
if vim.fn.has("nvim-0.8.0") ~= 1 then
|
||||
return vim.notify("lazy.nvim requires Neovim >= 0.8.0", vim.log.levels.ERROR, { title = "lazy.nvim" })
|
||||
end
|
||||
if not (pcall(require, "ffi") and jit and jit.version) then
|
||||
return vim.notify("lazy.nvim requires Neovim built with LuaJIT", vim.log.levels.ERROR, { title = "lazy.nvim" })
|
||||
end
|
||||
local start = vim.uv.hrtime()
|
||||
|
||||
-- use the Neovim cache if available
|
||||
if vim.loader and vim.fn.has("nvim-0.9.1") == 1 then
|
||||
package.loaded["lazy.core.cache"] = vim.loader
|
||||
end
|
||||
|
||||
local Cache = require("lazy.core.cache")
|
||||
|
||||
local enable_cache = vim.tbl_get(opts, "performance", "cache", "enabled") ~= false
|
||||
-- load module cache before anything else
|
||||
if enable_cache then
|
||||
Cache.enable()
|
||||
end
|
||||
|
||||
if vim.tbl_get(opts, "profiling", "require") then
|
||||
profile_require()
|
||||
end
|
||||
|
||||
require("lazy.stats").track("LazyStart")
|
||||
|
||||
local module_start = vim.loop.hrtime()
|
||||
require("lazy.core.module").setup()
|
||||
local Util = require("lazy.core.util")
|
||||
local Config = require("lazy.core.config")
|
||||
local Loader = require("lazy.core.loader")
|
||||
local Plugin = require("lazy.core.plugin")
|
||||
|
||||
Util.track("cache", module_start - cache_start)
|
||||
Util.track("module", vim.loop.hrtime() - module_start)
|
||||
table.insert(package.loaders, 3, Loader.loader)
|
||||
|
||||
Util.track("setup")
|
||||
if vim.tbl_get(opts, "profiling", "loader") then
|
||||
if vim.loader then
|
||||
vim.loader._profile({ loaders = true })
|
||||
else
|
||||
Cache._profile_loaders()
|
||||
end
|
||||
end
|
||||
|
||||
Util.track({ plugin = "lazy.nvim" }) -- setup start
|
||||
Util.track("module", vim.uv.hrtime() - start)
|
||||
|
||||
-- load config
|
||||
Util.track("config")
|
||||
Config.setup(opts)
|
||||
Util.track()
|
||||
|
||||
Util.track("state")
|
||||
Plugin.load()
|
||||
Util.track()
|
||||
|
||||
Util.track("install")
|
||||
for _, plugin in pairs(Config.plugins) do
|
||||
if not plugin._.installed then
|
||||
vim.cmd("do User LazyInstallPre")
|
||||
require("lazy.manage").install({
|
||||
wait = true,
|
||||
show = Config.options.interactive,
|
||||
})
|
||||
break
|
||||
end
|
||||
end
|
||||
Util.track()
|
||||
|
||||
Util.track("loader")
|
||||
-- setup loader and handlers
|
||||
Loader.setup()
|
||||
Util.track()
|
||||
|
||||
Util.track() -- end setup
|
||||
|
||||
local lazy_delta = vim.loop.hrtime() - cache_start
|
||||
|
||||
Loader.init_plugins()
|
||||
|
||||
-- correct time delta and loaded
|
||||
local delta = vim.uv.hrtime() - start
|
||||
Util.track().time = delta -- end setup
|
||||
if Config.plugins["lazy.nvim"] then
|
||||
Config.plugins["lazy.nvim"]._.loaded.time = lazy_delta
|
||||
Config.plugins["lazy.nvim"]._.loaded = { time = delta, source = "init.lua" }
|
||||
end
|
||||
|
||||
vim.cmd("do User LazyDone")
|
||||
-- load plugins with lazy=false or Plugin.init
|
||||
Loader.startup()
|
||||
|
||||
-- all done!
|
||||
vim.api.nvim_exec_autocmds("User", { pattern = "LazyDone", modeline = false })
|
||||
require("lazy.stats").track("LazyDone")
|
||||
end
|
||||
|
||||
function M.stats()
|
||||
local ret = {
|
||||
count = 0,
|
||||
loaded = 0,
|
||||
}
|
||||
|
||||
for _, plugin in pairs(require("lazy.core.config").plugins) do
|
||||
ret.count = ret.count + 1
|
||||
|
||||
if plugin._.loaded then
|
||||
ret.loaded = ret.loaded + 1
|
||||
end
|
||||
end
|
||||
|
||||
return ret
|
||||
return require("lazy.stats").stats()
|
||||
end
|
||||
|
||||
function M.bootstrap()
|
||||
local lazypath = vim.fn.stdpath("data") .. "/site/pack/lazy/start/lazy.nvim"
|
||||
if not vim.loop.fs_stat(lazypath) then
|
||||
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
|
||||
if not (vim.uv or vim.loop).fs_stat(lazypath) then
|
||||
vim.fn.system({
|
||||
"git",
|
||||
"clone",
|
||||
"--filter=blob:none",
|
||||
"--single-branch",
|
||||
"https://github.com/folke/lazy.nvim.git",
|
||||
"--branch=stable", -- latest stable release
|
||||
lazypath,
|
||||
})
|
||||
vim.opt.runtimepath:append(lazypath)
|
||||
end
|
||||
vim.opt.rtp:prepend(lazypath)
|
||||
end
|
||||
|
||||
---@return LazyPlugin[]
|
||||
function M.plugins()
|
||||
return vim.tbl_values(require("lazy.core.config").plugins)
|
||||
end
|
||||
|
||||
setmetatable(M, {
|
||||
__index = function(_, key)
|
||||
return function(...)
|
||||
return require("lazy.view.commands").commands[key](...)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
return M
|
||||
|
|
97
lua/lazy/manage/checker.lua
Normal file
97
lua/lazy/manage/checker.lua
Normal file
|
@ -0,0 +1,97 @@
|
|||
local Config = require("lazy.core.config")
|
||||
local Git = require("lazy.manage.git")
|
||||
local Manage = require("lazy.manage")
|
||||
local Plugin = require("lazy.core.plugin")
|
||||
local State = require("lazy.state")
|
||||
local Util = require("lazy.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
M.running = false
|
||||
M.updated = {}
|
||||
M.reported = {}
|
||||
|
||||
function M.start()
|
||||
M.fast_check()
|
||||
if M.schedule() > 0 and not M.has_errors() then
|
||||
Manage.log({
|
||||
clear = false,
|
||||
show = false,
|
||||
check = true,
|
||||
concurrency = Config.options.checker.concurrency,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function M.schedule()
|
||||
State.read() -- update state
|
||||
local next_check = State.checker.last_check + Config.options.checker.frequency - os.time()
|
||||
next_check = math.max(next_check, 0)
|
||||
vim.defer_fn(M.check, next_check * 1000)
|
||||
return next_check
|
||||
end
|
||||
|
||||
---@param opts? {report:boolean} report defaults to true
|
||||
function M.fast_check(opts)
|
||||
opts = opts or {}
|
||||
for _, plugin in pairs(Config.plugins) do
|
||||
-- don't check local plugins here, since we mark them as needing updates
|
||||
-- only if local is behind upstream (if the git log task gives no output)
|
||||
if plugin._.installed and not (plugin.pin or plugin._.is_local) then
|
||||
plugin._.updates = nil
|
||||
local info = Git.info(plugin.dir)
|
||||
local ok, target = pcall(Git.get_target, plugin)
|
||||
if ok and info and target and not Git.eq(info, target) then
|
||||
plugin._.updates = { from = info, to = target }
|
||||
end
|
||||
end
|
||||
end
|
||||
M.report(opts.report ~= false)
|
||||
end
|
||||
|
||||
function M.has_errors()
|
||||
for _, plugin in pairs(Config.plugins) do
|
||||
if Plugin.has_errors(plugin) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function M.check()
|
||||
State.checker.last_check = os.time()
|
||||
State.write() -- update state
|
||||
if M.has_errors() then
|
||||
M.schedule()
|
||||
else
|
||||
Manage.check({
|
||||
clear = false,
|
||||
show = false,
|
||||
concurrency = Config.options.checker.concurrency,
|
||||
}):wait(function()
|
||||
M.report()
|
||||
M.schedule()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
---@param notify? boolean
|
||||
function M.report(notify)
|
||||
local lines = {}
|
||||
M.updated = {}
|
||||
for _, plugin in pairs(Config.plugins) do
|
||||
if plugin._.updates then
|
||||
table.insert(M.updated, plugin.name)
|
||||
if not vim.tbl_contains(M.reported, plugin.name) then
|
||||
table.insert(lines, "- **" .. plugin.name .. "**")
|
||||
table.insert(M.reported, plugin.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
if #lines > 0 and notify and Config.options.checker.notify and not Config.headless() then
|
||||
table.insert(lines, 1, "# Plugin Updates")
|
||||
Util.info(lines)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,35 +1,49 @@
|
|||
local Util = require("lazy.util")
|
||||
local Config = require("lazy.core.config")
|
||||
local Process = require("lazy.manage.process")
|
||||
local Semver = require("lazy.manage.semver")
|
||||
local Util = require("lazy.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
---@alias GitInfo {branch?:string, commit?:string, tag?:string, version?:Semver}
|
||||
|
||||
---@param details? boolean
|
||||
---@param repo string
|
||||
---@param details? boolean Fetching details is slow! Don't loop over a plugin to fetch all details!
|
||||
---@return GitInfo?
|
||||
function M.info(repo, details)
|
||||
local line = Util.head(repo .. "/.git/HEAD")
|
||||
local line = M.head(repo)
|
||||
if line then
|
||||
---@type string, string
|
||||
local ref, branch = line:match("ref: (refs/heads/(.*))")
|
||||
local ref, branch = line:match("ref: refs/(heads/(.*))")
|
||||
local ret = ref and {
|
||||
branch = branch,
|
||||
commit = Util.head(repo .. "/.git/" .. ref),
|
||||
commit = M.ref(repo, ref),
|
||||
} or { commit = line }
|
||||
|
||||
if details then
|
||||
Util.ls(repo .. "/.git/refs/tags", function(_, name)
|
||||
if M.ref(repo, "tags/" .. name) == ret.commit then
|
||||
ret.tag = name
|
||||
ret.version = Semver.version(name)
|
||||
return false
|
||||
for tag, tag_ref in pairs(M.get_tag_refs(repo)) do
|
||||
if tag_ref == ret.commit then
|
||||
ret.tag = tag
|
||||
ret.version = ret.version or Semver.version(tag)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
end
|
||||
|
||||
---@param a GitInfo
|
||||
---@param b GitInfo
|
||||
function M.eq(a, b)
|
||||
local ra = a.commit and a.commit:sub(1, 7)
|
||||
local rb = b.commit and b.commit:sub(1, 7)
|
||||
return ra == rb
|
||||
end
|
||||
|
||||
function M.head(repo)
|
||||
return Util.head(repo .. "/.git/HEAD")
|
||||
end
|
||||
|
||||
---@class TaggedSemver: Semver
|
||||
---@field tag string
|
||||
|
||||
|
@ -38,74 +52,198 @@ function M.get_versions(repo, spec)
|
|||
local range = Semver.range(spec or "*")
|
||||
---@type TaggedSemver[]
|
||||
local versions = {}
|
||||
Util.ls(repo .. "/.git/refs/tags", function(_, name)
|
||||
local v = Semver.version(name)
|
||||
for _, tag in ipairs(M.get_tags(repo)) do
|
||||
local v = Semver.version(tag)
|
||||
---@cast v TaggedSemver
|
||||
if v and range:matches(v) then
|
||||
v.tag = name
|
||||
v.tag = tag
|
||||
table.insert(versions, v)
|
||||
end
|
||||
end)
|
||||
end
|
||||
return versions
|
||||
end
|
||||
|
||||
function M.get_tags(repo)
|
||||
---@type string[]
|
||||
local ret = {}
|
||||
Util.ls(repo .. "/.git/refs/tags", function(_, name)
|
||||
ret[#ret + 1] = name
|
||||
end)
|
||||
for name in pairs(M.packed_refs(repo)) do
|
||||
local tag = name:match("^tags/(.*)")
|
||||
if tag then
|
||||
ret[#ret + 1] = tag
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
---@return {branch:string, commit?:string}?
|
||||
---@return string?
|
||||
function M.get_branch(plugin)
|
||||
if plugin.branch then
|
||||
return {
|
||||
branch = plugin.branch,
|
||||
commit = M.ref(plugin.dir, "heads/" .. plugin.branch),
|
||||
}
|
||||
return plugin.branch
|
||||
else
|
||||
-- we need to return the default branch
|
||||
-- Try origin first
|
||||
local main = M.ref(plugin.dir, "remotes/origin/HEAD")
|
||||
if main then
|
||||
local branch = main:match("ref: refs/remotes/origin/(.*)")
|
||||
if branch then
|
||||
return {
|
||||
branch = branch,
|
||||
commit = M.ref(plugin.dir, "heads/" .. branch),
|
||||
}
|
||||
return branch
|
||||
end
|
||||
end
|
||||
|
||||
-- fallback to local HEAD
|
||||
main = assert(M.head(plugin.dir))
|
||||
return main and main:match("ref: refs/heads/(.*)")
|
||||
end
|
||||
end
|
||||
|
||||
-- Return the last commit for the given branch
|
||||
---@param repo string
|
||||
---@param branch string
|
||||
---@param origin? boolean
|
||||
function M.get_commit(repo, branch, origin)
|
||||
if origin then
|
||||
-- origin ref might not exist if it is the same as local
|
||||
return M.ref(repo, "remotes/origin", branch) or M.ref(repo, "heads", branch)
|
||||
else
|
||||
return M.ref(repo, "heads", branch)
|
||||
end
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
---@return GitInfo?
|
||||
function M.get_target(plugin)
|
||||
local branch = M.get_branch(plugin) or M.info(plugin.dir)
|
||||
if plugin._.is_local then
|
||||
local info = M.info(plugin.dir)
|
||||
local branch = assert(info and info.branch or M.get_branch(plugin))
|
||||
return { branch = branch, commit = M.get_commit(plugin.dir, branch, true) }
|
||||
end
|
||||
|
||||
local branch = assert(M.get_branch(plugin))
|
||||
|
||||
if plugin.commit then
|
||||
return {
|
||||
branch = branch and branch.branch,
|
||||
branch = branch,
|
||||
commit = plugin.commit,
|
||||
}
|
||||
end
|
||||
if plugin.tag then
|
||||
return {
|
||||
branch = branch and branch.branch,
|
||||
branch = branch,
|
||||
tag = plugin.tag,
|
||||
commit = M.ref(plugin.dir, "tags/" .. plugin.tag),
|
||||
}
|
||||
end
|
||||
if plugin.version then
|
||||
local last = Semver.last(M.get_versions(plugin.dir, plugin.version))
|
||||
|
||||
local version = (plugin.version == nil and plugin.branch == nil) and Config.options.defaults.version or plugin.version
|
||||
if version then
|
||||
local last = Semver.last(M.get_versions(plugin.dir, version))
|
||||
if last then
|
||||
return {
|
||||
branch = branch and branch.branch,
|
||||
branch = branch,
|
||||
version = last,
|
||||
tag = last.tag,
|
||||
commit = M.ref(plugin.dir, "tags/" .. last.tag),
|
||||
}
|
||||
end
|
||||
end
|
||||
---@diagnostic disable-next-line: return-type-mismatch
|
||||
return branch
|
||||
return { branch = branch, commit = M.get_commit(plugin.dir, branch, true) }
|
||||
end
|
||||
|
||||
function M.ref(repo, ref)
|
||||
return Util.head(repo .. "/.git/refs/" .. ref)
|
||||
function M.ref(repo, ...)
|
||||
local ref = table.concat({ ... }, "/")
|
||||
|
||||
-- if this is a tag ref, then dereference it instead
|
||||
if ref:find("tags/", 1, true) == 1 then
|
||||
local tags = M.get_tag_refs(repo, ref)
|
||||
for _, tag_ref in pairs(tags) do
|
||||
return tag_ref
|
||||
end
|
||||
end
|
||||
|
||||
-- otherwise just get the ref
|
||||
return Util.head(repo .. "/.git/refs/" .. ref) or M.packed_refs(repo)[ref]
|
||||
end
|
||||
|
||||
function M.packed_refs(repo)
|
||||
local ok, refs = pcall(Util.read_file, repo .. "/.git/packed-refs")
|
||||
---@type table<string,string>
|
||||
local ret = {}
|
||||
if ok then
|
||||
for _, line in ipairs(vim.split(refs, "\n")) do
|
||||
local ref, name = line:match("^(.*) refs/(.*)$")
|
||||
if ref then
|
||||
ret[name] = ref
|
||||
end
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
-- this is slow, so don't use on a loop over all plugins!
|
||||
---@param tagref string?
|
||||
function M.get_tag_refs(repo, tagref)
|
||||
tagref = tagref or "--tags"
|
||||
---@type table<string,string>
|
||||
local tags = {}
|
||||
local ok, lines = pcall(function()
|
||||
return Process.exec({ "git", "show-ref", "-d", tagref }, { cwd = repo })
|
||||
end)
|
||||
if not ok then
|
||||
return {}
|
||||
end
|
||||
for _, line in ipairs(lines) do
|
||||
local ref, tag = line:match("^(%w+) refs/tags/([^%^]+)%^?{?}?$")
|
||||
if ref then
|
||||
tags[tag] = ref
|
||||
end
|
||||
end
|
||||
return tags
|
||||
end
|
||||
|
||||
---@param repo string
|
||||
function M.get_origin(repo)
|
||||
return M.get_config(repo)["remote.origin.url"]
|
||||
end
|
||||
|
||||
---@param repo string
|
||||
function M.get_config(repo)
|
||||
local ok, config = pcall(Util.read_file, repo .. "/.git/config")
|
||||
if not ok then
|
||||
return {}
|
||||
end
|
||||
---@type table<string, string>
|
||||
local ret = {}
|
||||
---@type string
|
||||
local current_section = nil
|
||||
for line in config:gmatch("[^\n]+") do
|
||||
-- Check if the line is a section header
|
||||
local section = line:match("^%s*%[(.+)%]%s*$")
|
||||
if section then
|
||||
---@type string
|
||||
current_section = section:gsub('%s+"', "."):gsub('"+%s*$', "")
|
||||
else
|
||||
-- Ignore comments and blank lines
|
||||
if not line:match("^%s*[#;]") and line:match("%S") then
|
||||
local key, value = line:match("^%s*(%S+)%s*=%s*(.+)%s*$")
|
||||
ret[current_section .. "." .. key] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function M.count(repo, commit1, commit2)
|
||||
local lines = Process.exec({ "git", "rev-list", "--count", commit1 .. ".." .. commit2 }, { cwd = repo })
|
||||
return tonumber(lines[1] or "0") or 0
|
||||
end
|
||||
|
||||
function M.age(repo, commit)
|
||||
local lines = Process.exec({ "git", "show", "-s", "--format=%cr", "--date=short", commit }, { cwd = repo })
|
||||
return lines[1] or ""
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
@ -1,33 +1,45 @@
|
|||
local Config = require("lazy.core.config")
|
||||
local Runner = require("lazy.manage.runner")
|
||||
local Plugin = require("lazy.core.plugin")
|
||||
local Runner = require("lazy.manage.runner")
|
||||
|
||||
local M = {}
|
||||
|
||||
---@class ManagerOpts
|
||||
---@field wait? boolean
|
||||
---@field clear? boolean
|
||||
---@field interactive? boolean
|
||||
---@field show? boolean
|
||||
---@field mode? string
|
||||
---@field plugins? LazyPlugin[]
|
||||
---@field plugins? (LazyPlugin|string)[]
|
||||
---@field concurrency? number
|
||||
---@field lockfile? boolean
|
||||
|
||||
---@param ropts RunnerOpts
|
||||
---@param opts? ManagerOpts
|
||||
function M.run(ropts, opts)
|
||||
opts = opts or {}
|
||||
if opts.interactive == nil then
|
||||
opts.interactive = Config.options.interactive
|
||||
|
||||
local mode = opts.mode
|
||||
local event = mode and ("Lazy" .. mode:sub(1, 1):upper() .. mode:sub(2))
|
||||
|
||||
if event then
|
||||
vim.api.nvim_exec_autocmds("User", { pattern = event .. "Pre", modeline = false })
|
||||
end
|
||||
|
||||
if opts.plugins then
|
||||
---@param plugin string|LazyPlugin
|
||||
opts.plugins = vim.tbl_map(function(plugin)
|
||||
return type(plugin) == "string" and Config.plugins[plugin] or plugin
|
||||
end, vim.tbl_values(opts.plugins))
|
||||
ropts.plugins = opts.plugins
|
||||
end
|
||||
|
||||
ropts.concurrency = ropts.concurrency or opts.concurrency or Config.options.concurrency
|
||||
|
||||
if opts.clear then
|
||||
M.clear()
|
||||
M.clear(opts.plugins)
|
||||
end
|
||||
|
||||
if opts.interactive then
|
||||
if opts.show ~= false then
|
||||
vim.schedule(function()
|
||||
require("lazy.view").show(opts.mode)
|
||||
end)
|
||||
|
@ -37,11 +49,16 @@ function M.run(ropts, opts)
|
|||
local runner = Runner.new(ropts)
|
||||
runner:start()
|
||||
|
||||
vim.cmd([[do User LazyRender]])
|
||||
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
|
||||
|
||||
-- wait for post-install to finish
|
||||
runner:wait(function()
|
||||
vim.cmd([[do User LazyRender]])
|
||||
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
|
||||
Plugin.update_state()
|
||||
require("lazy.manage.checker").fast_check({ report = false })
|
||||
if event then
|
||||
vim.api.nvim_exec_autocmds("User", { pattern = event, modeline = false })
|
||||
end
|
||||
end)
|
||||
|
||||
if opts.wait then
|
||||
|
@ -50,81 +67,171 @@ function M.run(ropts, opts)
|
|||
return runner
|
||||
end
|
||||
|
||||
---@generic O: ManagerOpts
|
||||
---@param opts? O
|
||||
---@param defaults? ManagerOpts
|
||||
---@return O
|
||||
function M.opts(opts, defaults)
|
||||
return vim.tbl_deep_extend("force", { clear = true }, defaults or {}, opts or {})
|
||||
end
|
||||
|
||||
---@param opts? ManagerOpts
|
||||
function M.install(opts)
|
||||
M.run({
|
||||
opts = M.opts(opts, { mode = "install" })
|
||||
return M.run({
|
||||
pipeline = {
|
||||
"fs.symlink",
|
||||
"plugin.exists",
|
||||
"git.clone",
|
||||
"git.checkout",
|
||||
{ "git.checkout", lockfile = opts.lockfile },
|
||||
"plugin.docs",
|
||||
"wait",
|
||||
"plugin.run",
|
||||
{
|
||||
"wait",
|
||||
---@param runner Runner
|
||||
sync = function(runner)
|
||||
require("lazy.pkg").update()
|
||||
Plugin.load()
|
||||
runner:update()
|
||||
end,
|
||||
},
|
||||
"plugin.build",
|
||||
},
|
||||
plugins = function(plugin)
|
||||
return plugin.uri and not plugin._.installed
|
||||
return not (plugin._.installed and not plugin._.build)
|
||||
end,
|
||||
}, opts):wait(function()
|
||||
require("lazy.manage.lock").update()
|
||||
require("lazy.help").update()
|
||||
end)
|
||||
end
|
||||
|
||||
---@param opts? ManagerOpts
|
||||
function M.update(opts)
|
||||
opts = M.opts(opts, { mode = "update" })
|
||||
return M.run({
|
||||
pipeline = {
|
||||
"plugin.exists",
|
||||
"git.origin",
|
||||
"git.branch",
|
||||
"git.fetch",
|
||||
"git.status",
|
||||
{ "git.checkout", lockfile = opts.lockfile },
|
||||
"plugin.docs",
|
||||
{
|
||||
"wait",
|
||||
---@param runner Runner
|
||||
sync = function(runner)
|
||||
require("lazy.pkg").update()
|
||||
Plugin.load()
|
||||
runner:update()
|
||||
end,
|
||||
},
|
||||
"plugin.build",
|
||||
{ "git.log", updated = true },
|
||||
},
|
||||
plugins = function(plugin)
|
||||
return plugin.url and plugin._.installed
|
||||
end,
|
||||
}, opts):wait(function()
|
||||
require("lazy.manage.lock").update()
|
||||
require("lazy.help").update()
|
||||
end)
|
||||
end
|
||||
--
|
||||
---@param opts? ManagerOpts
|
||||
function M.restore(opts)
|
||||
opts = M.opts(opts, { mode = "restore", lockfile = true })
|
||||
return M.update(opts)
|
||||
end
|
||||
|
||||
---@param opts? ManagerOpts
|
||||
function M.check(opts)
|
||||
opts = M.opts(opts, { mode = "check" })
|
||||
opts = opts or {}
|
||||
return M.run({
|
||||
pipeline = {
|
||||
"plugin.exists",
|
||||
{ "git.origin", check = true },
|
||||
"git.fetch",
|
||||
"git.status",
|
||||
"wait",
|
||||
{ "git.log", check = true },
|
||||
},
|
||||
plugins = function(plugin)
|
||||
return plugin.url and plugin._.installed
|
||||
end,
|
||||
}, opts)
|
||||
end
|
||||
|
||||
---@param opts? ManagerOpts|{lockfile?:boolean}
|
||||
function M.update(opts)
|
||||
opts = opts or {}
|
||||
M.run({
|
||||
---@param opts? ManagerOpts | {check?:boolean}
|
||||
function M.log(opts)
|
||||
opts = M.opts(opts, { mode = "log" })
|
||||
return M.run({
|
||||
pipeline = {
|
||||
"fs.symlink",
|
||||
"git.branch",
|
||||
"git.fetch",
|
||||
{ "git.checkout", lockfile = opts.lockfile },
|
||||
"plugin.docs",
|
||||
"wait",
|
||||
"plugin.run",
|
||||
{ "git.log", updated = true },
|
||||
{ "git.origin", check = true },
|
||||
{ "git.log", check = opts.check },
|
||||
},
|
||||
plugins = function(plugin)
|
||||
return plugin.uri and plugin._.installed
|
||||
return plugin.url and plugin._.installed
|
||||
end,
|
||||
}, opts)
|
||||
end
|
||||
|
||||
---@param opts? ManagerOpts
|
||||
function M.build(opts)
|
||||
opts = M.opts(opts, { mode = "build" })
|
||||
return M.run({
|
||||
pipeline = { { "plugin.build", force = true } },
|
||||
plugins = function()
|
||||
return false
|
||||
end,
|
||||
}, opts)
|
||||
end
|
||||
|
||||
---@param opts? ManagerOpts
|
||||
function M.sync(opts)
|
||||
opts = M.opts(opts)
|
||||
if opts.clear then
|
||||
M.clear()
|
||||
opts.clear = false
|
||||
end
|
||||
if opts.show ~= false then
|
||||
vim.schedule(function()
|
||||
require("lazy.view").show("sync")
|
||||
end)
|
||||
opts.show = false
|
||||
end
|
||||
|
||||
vim.api.nvim_exec_autocmds("User", { pattern = "LazySyncPre", modeline = false })
|
||||
|
||||
local clean_opts = vim.deepcopy(opts)
|
||||
clean_opts.plugins = nil
|
||||
local clean = M.clean(clean_opts)
|
||||
local install = M.install(opts)
|
||||
local update = M.update(opts)
|
||||
clean:wait(function()
|
||||
install:wait(function()
|
||||
update:wait(function()
|
||||
vim.api.nvim_exec_autocmds("User", { pattern = "LazySync", modeline = false })
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
---@param opts? ManagerOpts
|
||||
function M.clean(opts)
|
||||
opts = M.opts(opts, { mode = "clean" })
|
||||
return M.run({
|
||||
pipeline = { "fs.clean" },
|
||||
plugins = Config.to_clean,
|
||||
}, opts):wait(function()
|
||||
require("lazy.manage.lock").update()
|
||||
end)
|
||||
end
|
||||
|
||||
function M.check(opts)
|
||||
opts = opts or {}
|
||||
M.run({
|
||||
pipeline = {
|
||||
"git.fetch",
|
||||
"wait",
|
||||
{ "git.log", check = true },
|
||||
},
|
||||
plugins = function(plugin)
|
||||
return plugin.uri and plugin._.installed
|
||||
end,
|
||||
}, opts)
|
||||
end
|
||||
|
||||
---@param opts? ManagerOpts
|
||||
function M.log(opts)
|
||||
M.run({
|
||||
pipeline = { "git.log" },
|
||||
plugins = function(plugin)
|
||||
return plugin.uri and plugin._.installed
|
||||
end,
|
||||
}, opts)
|
||||
end
|
||||
|
||||
---@param opts? ManagerOpts
|
||||
function M.clean(opts)
|
||||
Plugin.update_state(true)
|
||||
M.run({
|
||||
pipeline = { "fs.clean" },
|
||||
plugins = Config.to_clean,
|
||||
}, opts)
|
||||
end
|
||||
|
||||
function M.clear()
|
||||
Plugin.update_state(true)
|
||||
for _, plugin in pairs(Config.plugins) do
|
||||
---@param plugins? LazyPlugin[]
|
||||
function M.clear(plugins)
|
||||
for _, plugin in pairs(plugins or Config.plugins) do
|
||||
plugin._.updates = nil
|
||||
plugin._.updated = nil
|
||||
plugin._.cloned = nil
|
||||
plugin._.dirty = nil
|
||||
|
@ -132,11 +239,11 @@ function M.clear()
|
|||
if plugin._.tasks then
|
||||
---@param task LazyTask
|
||||
plugin._.tasks = vim.tbl_filter(function(task)
|
||||
return task:is_running()
|
||||
return task:running() or task:has_errors()
|
||||
end, plugin._.tasks)
|
||||
end
|
||||
end
|
||||
vim.cmd([[do User LazyRender]])
|
||||
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
@ -3,50 +3,53 @@ local Git = require("lazy.manage.git")
|
|||
|
||||
local M = {}
|
||||
|
||||
---@type table<string, {commit:string, branch:string}>
|
||||
---@alias LazyLockfile table<string, {commit:string, branch:string}>
|
||||
---@type LazyLockfile
|
||||
M.lock = {}
|
||||
M._loaded = false
|
||||
|
||||
function M.update()
|
||||
local f = assert(io.open(Config.options.lockfile, "w"))
|
||||
M.load()
|
||||
vim.fn.mkdir(vim.fn.fnamemodify(Config.options.lockfile, ":p:h"), "p")
|
||||
local f = assert(io.open(Config.options.lockfile, "wb"))
|
||||
f:write("{\n")
|
||||
M.lock = {}
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
local plugins = vim.tbl_filter(function(plugin)
|
||||
return not plugin._.is_local and plugin._.installed
|
||||
end, Config.plugins)
|
||||
-- keep disabled and cond plugins
|
||||
for name in pairs(M.lock) do
|
||||
if not (Config.spec.disabled[name] or Config.spec.ignore_installed[name]) then
|
||||
M.lock[name] = nil
|
||||
end
|
||||
end
|
||||
|
||||
for _, plugin in pairs(Config.plugins) do
|
||||
if not plugin._.is_local and plugin._.installed then
|
||||
local info = assert(Git.info(plugin.dir))
|
||||
M.lock[plugin.name] = {
|
||||
branch = info.branch or assert(Git.get_branch(plugin)),
|
||||
commit = assert(info.commit, "commit is nil"),
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
---@type string[]
|
||||
local names = vim.tbl_map(function(plugin)
|
||||
return plugin.name
|
||||
end, plugins)
|
||||
local names = vim.tbl_keys(M.lock)
|
||||
table.sort(names)
|
||||
|
||||
for n, name in ipairs(names) do
|
||||
local plugin = Config.plugins[name]
|
||||
if not plugin._.is_local and plugin._.installed then
|
||||
local info = assert(Git.info(plugin.dir))
|
||||
if not info.branch then
|
||||
local branch = assert(Git.get_branch(plugin))
|
||||
info.branch = branch.branch
|
||||
end
|
||||
info.commit = info.commit
|
||||
-- f:write(([[ [%q] = { branch = %q, commit = %q },]]):format(name, info.branch, info.commit) .. "\n")
|
||||
f:write(([[ %q: { "branch": %q, "commit": %q }]]):format(name, info.branch, info.commit))
|
||||
if n ~= #names then
|
||||
f:write(",\n")
|
||||
end
|
||||
---@diagnostic disable-next-line: assign-type-mismatch
|
||||
M.lock[plugin.name] = info
|
||||
local info = M.lock[name]
|
||||
f:write(([[ %q: { "branch": %q, "commit": %q }]]):format(name, info.branch, info.commit))
|
||||
if n ~= #names then
|
||||
f:write(",\n")
|
||||
end
|
||||
end
|
||||
f:write("\n}")
|
||||
f:write("\n}\n")
|
||||
f:close()
|
||||
end
|
||||
|
||||
function M.load()
|
||||
if M._loaded then
|
||||
return
|
||||
end
|
||||
M.lock = {}
|
||||
M._loaded = true
|
||||
local f = io.open(Config.options.lockfile, "r")
|
||||
|
@ -64,9 +67,7 @@ end
|
|||
---@param plugin LazyPlugin
|
||||
---@return {commit:string, branch:string}
|
||||
function M.get(plugin)
|
||||
if not M._loaded then
|
||||
M.load()
|
||||
end
|
||||
M.load()
|
||||
return M.lock[plugin.name]
|
||||
end
|
||||
|
||||
|
|
|
@ -1,80 +1,239 @@
|
|||
local Async = require("lazy.async")
|
||||
local Config = require("lazy.core.config")
|
||||
|
||||
---@diagnostic disable-next-line: no-unknown
|
||||
local uv = vim.uv
|
||||
|
||||
---@class ProcessOpts
|
||||
---@field args string[]
|
||||
---@field cwd? string
|
||||
---@field on_line? fun(line:string)
|
||||
---@field on_exit? fun(ok:boolean, output:string)
|
||||
---@field on_data? fun(data:string, is_stderr?:boolean)
|
||||
---@field timeout? number
|
||||
---@field env? table<string,string>
|
||||
|
||||
local M = {}
|
||||
|
||||
---@alias ProcessOpts {args: string[], cwd?: string, on_line?:fun(string), on_exit?: fun(ok:boolean, output:string)}
|
||||
---@type table<uv_process_t, LazyProcess>
|
||||
M.running = setmetatable({}, { __mode = "k" })
|
||||
|
||||
function M.spawn(cmd, opts)
|
||||
---@class LazyProcess: Async
|
||||
---@field handle? uv_process_t
|
||||
---@field pid? number
|
||||
---@field cmd string
|
||||
---@field opts ProcessOpts
|
||||
---@field timeout? uv_timer_t
|
||||
---@field timedout? boolean
|
||||
---@field data string
|
||||
---@field check? uv_check_t
|
||||
---@field code? number
|
||||
---@field signal? number
|
||||
local Process = setmetatable({}, { __index = Async.Async })
|
||||
|
||||
---@param cmd string|string[]
|
||||
---@param opts? ProcessOpts
|
||||
function Process.new(cmd, opts)
|
||||
local self = setmetatable({}, { __index = Process })
|
||||
---@async
|
||||
Process.init(self, function()
|
||||
self:_run()
|
||||
end)
|
||||
opts = opts or {}
|
||||
local env = {
|
||||
"GIT_TERMINAL_PROMPT=0",
|
||||
"GIT_SSH_COMMAND=ssh -oBatchMode=yes",
|
||||
}
|
||||
|
||||
for key, value in
|
||||
pairs(vim.loop.os_environ() --[[@as string[] ]])
|
||||
do
|
||||
table.insert(env, key .. "=" .. value)
|
||||
opts.args = opts.args or {}
|
||||
if type(cmd) == "table" then
|
||||
self.cmd = cmd[1]
|
||||
vim.list_extend(opts.args, vim.list_slice(cmd, 2))
|
||||
else
|
||||
self.cmd = cmd
|
||||
end
|
||||
opts.timeout = opts.timeout or (Config.options.git and Config.options.git.timeout * 1000)
|
||||
-- make sure the cwd is valid
|
||||
if not opts.cwd and type(uv.cwd()) ~= "string" then
|
||||
opts.cwd = uv.os_homedir()
|
||||
end
|
||||
opts.on_line = opts.on_line and vim.schedule_wrap(opts.on_line) or nil
|
||||
opts.on_data = opts.on_data and vim.schedule_wrap(opts.on_data) or nil
|
||||
self.data = ""
|
||||
self.opts = opts
|
||||
self.code = 1
|
||||
self.signal = 0
|
||||
return self
|
||||
end
|
||||
|
||||
local stdout = vim.loop.new_pipe()
|
||||
local stderr = vim.loop.new_pipe()
|
||||
|
||||
local output = ""
|
||||
---@type vim.loop.Process
|
||||
local handle = nil
|
||||
|
||||
handle = vim.loop.spawn(cmd, {
|
||||
---@async
|
||||
function Process:_run()
|
||||
self:guard()
|
||||
local stdout = assert(uv.new_pipe())
|
||||
local stderr = assert(uv.new_pipe())
|
||||
self.handle = uv.spawn(self.cmd, {
|
||||
stdio = { nil, stdout, stderr },
|
||||
args = opts.args,
|
||||
cwd = opts.cwd,
|
||||
env = env,
|
||||
}, function(exit_code)
|
||||
handle:close()
|
||||
args = self.opts.args,
|
||||
cwd = self.opts.cwd,
|
||||
env = self:env(),
|
||||
}, function(code, signal)
|
||||
self.code = code
|
||||
self.signal = signal
|
||||
if self.timeout then
|
||||
self.timeout:stop()
|
||||
end
|
||||
self.handle:close()
|
||||
stdout:close()
|
||||
stderr:close()
|
||||
local check = vim.loop.new_check()
|
||||
check:start(function()
|
||||
if not stdout:is_closing() or not stderr:is_closing() then
|
||||
return
|
||||
end
|
||||
check:stop()
|
||||
if opts.on_exit then
|
||||
output = output:gsub("[^\r\n]+\r", "")
|
||||
|
||||
vim.schedule(function()
|
||||
opts.on_exit(exit_code == 0, output)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
self:resume()
|
||||
end)
|
||||
|
||||
if not handle then
|
||||
if opts.on_exit then
|
||||
opts.on_exit(false, "Failed to spawn process " .. cmd .. " " .. vim.inspect(opts))
|
||||
if self.handle then
|
||||
M.running[self.handle] = self
|
||||
stdout:read_start(function(err, data)
|
||||
self:on_data(err, data)
|
||||
end)
|
||||
stderr:read_start(function(err, data)
|
||||
self:on_data(err, data, true)
|
||||
end)
|
||||
self:suspend()
|
||||
while not (self.handle:is_closing() and stdout:is_closing() and stderr:is_closing()) do
|
||||
Async.yield()
|
||||
end
|
||||
else
|
||||
self.data = "Failed to spawn process " .. self.cmd .. " " .. vim.inspect(self.opts)
|
||||
end
|
||||
self:on_exit()
|
||||
end
|
||||
|
||||
function Process:on_exit()
|
||||
self.data = self.data:gsub("[^\r\n]+\r", "")
|
||||
if self.timedout then
|
||||
self.data = self.data .. "\n" .. "Process was killed because it reached the timeout"
|
||||
elseif self.signal ~= 0 then
|
||||
self.data = self.data .. "\n" .. "Process was killed with SIG" .. M.signals[self.signal]:upper()
|
||||
end
|
||||
if self.opts.on_exit then
|
||||
self.opts.on_exit(self.code == 0 and self.signal == 0, self.data)
|
||||
end
|
||||
end
|
||||
|
||||
function Process:guard()
|
||||
if self.opts.timeout then
|
||||
self.timeout = assert(uv.new_timer())
|
||||
self.timeout:start(self.opts.timeout, 0, function()
|
||||
self.timedout = true
|
||||
self:kill()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function Process:env()
|
||||
---@type table<string, string>
|
||||
local env = vim.tbl_extend("force", {
|
||||
GIT_SSH_COMMAND = "ssh -oBatchMode=yes",
|
||||
}, uv.os_environ(), self.opts.env or {})
|
||||
env.GIT_DIR = nil
|
||||
env.GIT_WORK_TREE = nil
|
||||
env.GIT_TERMINAL_PROMPT = "0"
|
||||
env.GIT_INDEX_FILE = nil
|
||||
|
||||
---@type string[]
|
||||
local env_flat = {}
|
||||
for k, v in pairs(env) do
|
||||
env_flat[#env_flat + 1] = k .. "=" .. v
|
||||
end
|
||||
return env_flat
|
||||
end
|
||||
|
||||
---@param signals uv.aliases.signals|uv.aliases.signals[]|nil
|
||||
function Process:kill(signals)
|
||||
if not self.handle or self.handle:is_closing() then
|
||||
return
|
||||
end
|
||||
signals = signals or { "sigterm", "sigkill" }
|
||||
signals = type(signals) == "table" and signals or { signals }
|
||||
---@cast signals uv.aliases.signals[]
|
||||
local timer = assert(uv.new_timer())
|
||||
timer:start(0, 1000, function()
|
||||
if self.handle and not self.handle:is_closing() and #signals > 0 then
|
||||
self.handle:kill(table.remove(signals, 1))
|
||||
else
|
||||
timer:stop()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
---@param err? string
|
||||
---@param data? string
|
||||
---@param is_stderr? boolean
|
||||
function Process:on_data(err, data, is_stderr)
|
||||
assert(not err, err)
|
||||
if not data then
|
||||
return
|
||||
end
|
||||
|
||||
---@param data? string
|
||||
local function on_output(err, data)
|
||||
assert(not err, err)
|
||||
|
||||
if data then
|
||||
output = output .. data:gsub("\r\n", "\n")
|
||||
local lines = vim.split(vim.trim(output:gsub("\r$", "")):gsub("[^\n\r]+\r", ""), "\n")
|
||||
|
||||
if opts.on_line then
|
||||
vim.schedule(function()
|
||||
opts.on_line(lines[#lines])
|
||||
end)
|
||||
end
|
||||
end
|
||||
if self.opts.on_data then
|
||||
self.opts.on_data(data, is_stderr)
|
||||
end
|
||||
self.data = self.data .. data:gsub("\r\n", "\n")
|
||||
local lines = vim.split(vim.trim(self.data:gsub("\r$", "")):gsub("[^\n\r]+\r", ""), "\n")
|
||||
|
||||
vim.loop.read_start(stdout, on_output)
|
||||
vim.loop.read_start(stderr, on_output)
|
||||
if self.opts.on_line then
|
||||
self.opts.on_line(lines[#lines])
|
||||
end
|
||||
end
|
||||
|
||||
return handle
|
||||
M.signals = {
|
||||
"hup",
|
||||
"int",
|
||||
"quit",
|
||||
"ill",
|
||||
"trap",
|
||||
"abrt",
|
||||
"bus",
|
||||
"fpe",
|
||||
"kill",
|
||||
"usr1",
|
||||
"segv",
|
||||
"usr2",
|
||||
"pipe",
|
||||
"alrm",
|
||||
"term",
|
||||
"chld",
|
||||
"cont",
|
||||
"stop",
|
||||
"tstp",
|
||||
"ttin",
|
||||
"ttou",
|
||||
"urg",
|
||||
"xcpu",
|
||||
"xfsz",
|
||||
"vtalrm",
|
||||
"prof",
|
||||
"winch",
|
||||
"io",
|
||||
"pwr",
|
||||
"emt",
|
||||
"sys",
|
||||
"info",
|
||||
}
|
||||
|
||||
---@param cmd string|string[]
|
||||
---@param opts? ProcessOpts
|
||||
function M.spawn(cmd, opts)
|
||||
return Process.new(cmd, opts)
|
||||
end
|
||||
|
||||
function M.abort()
|
||||
for _, proc in pairs(M.running) do
|
||||
proc:kill()
|
||||
end
|
||||
end
|
||||
|
||||
---@async
|
||||
---@param cmd string|string[]
|
||||
---@param opts? ProcessOpts
|
||||
function M.exec(cmd, opts)
|
||||
opts = opts or {}
|
||||
local proc = M.spawn(cmd, opts)
|
||||
proc:wait()
|
||||
return vim.split(proc.data, "\n"), proc.code
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
107
lua/lazy/manage/reloader.lua
Normal file
107
lua/lazy/manage/reloader.lua
Normal file
|
@ -0,0 +1,107 @@
|
|||
local Config = require("lazy.core.config")
|
||||
local Loader = require("lazy.core.loader")
|
||||
local Plugin = require("lazy.core.plugin")
|
||||
local Util = require("lazy.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
---@type table<string, uv.aliases.fs_stat_table>
|
||||
M.files = {}
|
||||
|
||||
---@type uv_timer_t
|
||||
M.timer = nil
|
||||
|
||||
function M.enable()
|
||||
if M.timer then
|
||||
M.timer:stop()
|
||||
end
|
||||
if #Config.spec.modules > 0 then
|
||||
M.timer = assert(vim.uv.new_timer())
|
||||
M.check(true)
|
||||
M.timer:start(2000, 2000, M.check)
|
||||
end
|
||||
end
|
||||
|
||||
function M.disable()
|
||||
if M.timer then
|
||||
M.timer:stop()
|
||||
M.timer = nil
|
||||
end
|
||||
end
|
||||
|
||||
---@param h1 uv.aliases.fs_stat_table
|
||||
---@param h2 uv.aliases.fs_stat_table
|
||||
function M.eq(h1, h2)
|
||||
return h1 and h2 and h1.size == h2.size and h1.mtime.sec == h2.mtime.sec and h1.mtime.nsec == h2.mtime.nsec
|
||||
end
|
||||
|
||||
function M.check(start)
|
||||
---@type table<string,true>
|
||||
local checked = {}
|
||||
---@type {file:string, what:string}[]
|
||||
local changes = {}
|
||||
|
||||
-- spec is a module
|
||||
local function check(_, modpath)
|
||||
checked[modpath] = true
|
||||
local hash = vim.uv.fs_stat(modpath)
|
||||
if hash then
|
||||
if M.files[modpath] then
|
||||
if not M.eq(M.files[modpath], hash) then
|
||||
M.files[modpath] = hash
|
||||
table.insert(changes, { file = modpath, what = "changed" })
|
||||
end
|
||||
else
|
||||
M.files[modpath] = hash
|
||||
table.insert(changes, { file = modpath, what = "added" })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, modname in ipairs(Config.spec.modules) do
|
||||
Util.lsmod(modname, check)
|
||||
end
|
||||
|
||||
for file in pairs(M.files) do
|
||||
if not checked[file] then
|
||||
table.insert(changes, { file = file, what = "deleted" })
|
||||
M.files[file] = nil
|
||||
end
|
||||
end
|
||||
|
||||
if Loader.init_done and Config.mapleader ~= vim.g.mapleader then
|
||||
vim.schedule(function()
|
||||
require("lazy.core.util").warn("You need to set `vim.g.mapleader` **BEFORE** loading lazy")
|
||||
end)
|
||||
Config.mapleader = vim.g.mapleader
|
||||
end
|
||||
|
||||
if Loader.init_done and Config.maplocalleader ~= vim.g.maplocalleader then
|
||||
vim.schedule(function()
|
||||
require("lazy.core.util").warn("You need to set `vim.g.maplocalleader` **BEFORE** loading lazy")
|
||||
end)
|
||||
Config.maplocalleader = vim.g.maplocalleader
|
||||
end
|
||||
|
||||
if not (start or #changes == 0) then
|
||||
M.reload(changes)
|
||||
end
|
||||
end
|
||||
|
||||
---@param {file:string, what:string}[]
|
||||
function M.reload(changes)
|
||||
vim.schedule(function()
|
||||
if Config.options.change_detection.notify and not Config.headless() then
|
||||
local lines = { "# Config Change Detected. Reloading...", "" }
|
||||
for _, change in ipairs(changes) do
|
||||
table.insert(lines, "- **" .. change.what .. "**: `" .. vim.fn.fnamemodify(change.file, ":p:~:.") .. "`")
|
||||
end
|
||||
Util.warn(lines)
|
||||
end
|
||||
Plugin.load()
|
||||
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
|
||||
vim.api.nvim_exec_autocmds("User", { pattern = "LazyReload", modeline = false })
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,20 +1,23 @@
|
|||
local Task = require("lazy.manage.task")
|
||||
local Async = require("lazy.async")
|
||||
local Config = require("lazy.core.config")
|
||||
local Util = require("lazy.util")
|
||||
local Task = require("lazy.manage.task")
|
||||
|
||||
---@class RunnerOpts
|
||||
---@field pipeline (string|{[1]:string, [string]:any})[]
|
||||
---@field plugins? LazyPlugin[]|fun(plugin:LazyPlugin):any?
|
||||
---@field concurrency? number
|
||||
|
||||
---@alias PipelineStep {task:string, opts?:TaskOptions}
|
||||
---@alias LazyRunnerTask {co:thread, status: {task?:LazyTask, waiting?:boolean}}
|
||||
---@class RunnerTask
|
||||
---@field task? LazyTask
|
||||
---@field step number
|
||||
|
||||
---@alias PipelineStep {task:string, opts?:TaskOptions }
|
||||
|
||||
---@class Runner
|
||||
---@field _plugins LazyPlugin[]
|
||||
---@field _running LazyRunnerTask[]
|
||||
---@field _plugins table<string,LazyPlugin>
|
||||
---@field _pipeline PipelineStep[]
|
||||
---@field _on_done fun()[]
|
||||
---@field _opts RunnerOpts
|
||||
---@field _running? Async
|
||||
local Runner = {}
|
||||
|
||||
---@param opts RunnerOpts
|
||||
|
@ -23,13 +26,17 @@ function Runner.new(opts)
|
|||
self._opts = opts or {}
|
||||
|
||||
local plugins = self._opts.plugins
|
||||
---@type LazyPlugin[]
|
||||
local pp = {}
|
||||
if type(plugins) == "function" then
|
||||
self._plugins = vim.tbl_filter(plugins, Config.plugins)
|
||||
pp = vim.tbl_filter(plugins, Config.plugins)
|
||||
else
|
||||
self._plugins = plugins or Config.plugins
|
||||
pp = plugins or Config.plugins
|
||||
end
|
||||
self._plugins = {}
|
||||
for _, plugin in ipairs(pp) do
|
||||
self._plugins[plugin.name] = plugin
|
||||
end
|
||||
self._running = {}
|
||||
self._on_done = {}
|
||||
|
||||
---@param step string|(TaskOptions|{[1]:string})
|
||||
self._pipeline = vim.tbl_map(function(step)
|
||||
|
@ -39,112 +46,154 @@ function Runner.new(opts)
|
|||
return self
|
||||
end
|
||||
|
||||
---@param entry LazyRunnerTask
|
||||
function Runner:_resume(entry)
|
||||
if entry.status.task and not entry.status.task:is_done() then
|
||||
return true
|
||||
end
|
||||
local ok, status = coroutine.resume(entry.co)
|
||||
if not ok then
|
||||
Util.error("Could not resume a task\n" .. status)
|
||||
end
|
||||
entry.status = ok and status
|
||||
return entry.status ~= nil
|
||||
function Runner:plugin(name)
|
||||
return self._plugins[name]
|
||||
end
|
||||
|
||||
function Runner:resume(waiting)
|
||||
local running = false
|
||||
for _, entry in ipairs(self._running) do
|
||||
if entry.status then
|
||||
if waiting and entry.status.waiting then
|
||||
entry.status.waiting = false
|
||||
end
|
||||
if not entry.status.waiting and self:_resume(entry) then
|
||||
running = true
|
||||
end
|
||||
end
|
||||
--- Update plugins
|
||||
function Runner:update()
|
||||
for name in pairs(self._plugins) do
|
||||
self._plugins[name] = Config.plugins[name] or self._plugins[name]
|
||||
end
|
||||
return running or (not waiting and self:resume(true))
|
||||
end
|
||||
|
||||
function Runner:start()
|
||||
for _, plugin in pairs(self._plugins) do
|
||||
local co = coroutine.create(self.run_pipeline)
|
||||
local ok, status = coroutine.resume(co, self, plugin)
|
||||
if ok then
|
||||
table.insert(self._running, { co = co, status = status })
|
||||
else
|
||||
Util.error("Could not start tasks for " .. plugin.name .. "\n" .. status)
|
||||
end
|
||||
end
|
||||
|
||||
local check = vim.loop.new_check()
|
||||
check:start(function()
|
||||
if self:resume() then
|
||||
return
|
||||
end
|
||||
check:stop()
|
||||
self._running = {}
|
||||
for _, cb in ipairs(self._on_done) do
|
||||
vim.schedule(cb)
|
||||
end
|
||||
self._on_done = {}
|
||||
---@async
|
||||
self._running = Async.new(function()
|
||||
self:_start()
|
||||
end)
|
||||
end
|
||||
|
||||
---@async
|
||||
---@param plugin LazyPlugin
|
||||
function Runner:run_pipeline(plugin)
|
||||
for _, step in ipairs(self._pipeline) do
|
||||
if step.task == "wait" then
|
||||
coroutine.yield({ waiting = true })
|
||||
else
|
||||
local task = self:queue(plugin, step.task, step.opts)
|
||||
if task then
|
||||
coroutine.yield({ task = task })
|
||||
assert(task:is_done())
|
||||
if task.error then
|
||||
return
|
||||
function Runner:_start()
|
||||
---@type string[]
|
||||
local names = vim.tbl_keys(self._plugins)
|
||||
table.sort(names)
|
||||
|
||||
---@type table<string,RunnerTask>
|
||||
local state = {}
|
||||
|
||||
local active = 1
|
||||
local waiting = 0
|
||||
---@type number?
|
||||
local wait_step = nil
|
||||
|
||||
---@async
|
||||
---@param resume? boolean
|
||||
local function continue(resume)
|
||||
active = 0
|
||||
waiting = 0
|
||||
wait_step = nil
|
||||
local next = {} ---@type string[]
|
||||
|
||||
-- check running tasks
|
||||
for _, name in ipairs(names) do
|
||||
state[name] = state[name] or { step = 0 }
|
||||
local s = state[name]
|
||||
local is_running = s.task and s.task:running()
|
||||
local step = self._pipeline[s.step]
|
||||
|
||||
if is_running then
|
||||
-- still running
|
||||
active = active + 1
|
||||
-- selene:allow(empty_if)
|
||||
elseif s.task and s.task:has_errors() then
|
||||
-- don't continue tasks if there are errors
|
||||
elseif step and step.task == "wait" and not resume then
|
||||
-- waiting for sync
|
||||
waiting = waiting + 1
|
||||
wait_step = s.step
|
||||
else
|
||||
next[#next + 1] = name
|
||||
end
|
||||
end
|
||||
|
||||
-- schedule next tasks
|
||||
for _, name in ipairs(next) do
|
||||
if self._opts.concurrency and active >= self._opts.concurrency then
|
||||
break
|
||||
end
|
||||
local s = state[name]
|
||||
local plugin = self:plugin(name)
|
||||
while s.step <= #self._pipeline do
|
||||
if s.step == #self._pipeline then
|
||||
-- done
|
||||
s.task = nil
|
||||
plugin._.working = false
|
||||
break
|
||||
elseif s.step < #self._pipeline then
|
||||
-- next
|
||||
s.step = s.step + 1
|
||||
local step = self._pipeline[s.step]
|
||||
if step.task == "wait" then
|
||||
plugin._.working = false
|
||||
waiting = waiting + 1
|
||||
wait_step = s.step
|
||||
break
|
||||
else
|
||||
s.task = self:queue(plugin, step)
|
||||
plugin._.working = true
|
||||
if s.task then
|
||||
active = active + 1
|
||||
s.task:wake(false)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
while active > 0 do
|
||||
continue()
|
||||
if active == 0 and waiting > 0 then
|
||||
local sync = self._pipeline[wait_step]
|
||||
if sync and sync.opts and type(sync.opts.sync) == "function" then
|
||||
sync.opts.sync(self)
|
||||
end
|
||||
continue(true)
|
||||
end
|
||||
if active > 0 then
|
||||
self._running:suspend()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
---@param task_name string
|
||||
---@param opts? TaskOptions
|
||||
---@param step PipelineStep
|
||||
---@return LazyTask?
|
||||
function Runner:queue(plugin, task_name, opts)
|
||||
assert(self._running)
|
||||
local def = vim.split(task_name, ".", { plain = true })
|
||||
function Runner:queue(plugin, step)
|
||||
assert(self._running and self._running:running(), "Runner is not running")
|
||||
local def = vim.split(step.task, ".", { plain = true })
|
||||
---@type LazyTaskDef
|
||||
local task_def = require("lazy.manage.task." .. def[1])[def[2]]
|
||||
assert(task_def)
|
||||
opts = opts or {}
|
||||
assert(task_def, "Task not found: " .. step.task)
|
||||
local opts = step.opts or {}
|
||||
if not (task_def.skip and task_def.skip(plugin, opts)) then
|
||||
local task = Task.new(plugin, def[2], task_def.run, opts)
|
||||
task:start()
|
||||
return task
|
||||
return Task.new(plugin, def[2], task_def.run, opts)
|
||||
end
|
||||
end
|
||||
|
||||
function Runner:is_running()
|
||||
return self._running and self._running:running()
|
||||
end
|
||||
|
||||
-- Execute the callback async when done.
|
||||
-- When no callback is specified, this will wait sync
|
||||
---@param cb? fun()
|
||||
function Runner:wait(cb)
|
||||
if #self._running == 0 then
|
||||
return cb and cb()
|
||||
end
|
||||
|
||||
if cb then
|
||||
table.insert(self._on_done, cb)
|
||||
else
|
||||
-- sync wait
|
||||
while #self._running > 0 do
|
||||
vim.wait(10)
|
||||
if not self:is_running() then
|
||||
if cb then
|
||||
cb()
|
||||
end
|
||||
return self
|
||||
end
|
||||
if cb then
|
||||
self._running:on("done", cb)
|
||||
else
|
||||
self._running:wait()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
return Runner
|
||||
|
|
|
@ -9,6 +9,7 @@ local M = {}
|
|||
---@field patch number
|
||||
---@field prerelease? string
|
||||
---@field build? string
|
||||
---@field input? string
|
||||
local Semver = {}
|
||||
Semver.__index = Semver
|
||||
|
||||
|
@ -90,6 +91,7 @@ function M.version(version)
|
|||
patch = patch == "" and 0 or tonumber(patch),
|
||||
prerelease = prerelease ~= "" and prerelease or nil,
|
||||
build = build ~= "" and build or nil,
|
||||
input = version,
|
||||
}, Semver)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,50 +1,45 @@
|
|||
local Config = require("lazy.core.config")
|
||||
local Util = require("lazy.util")
|
||||
|
||||
---@type table<string, LazyTaskDef>
|
||||
local M = {}
|
||||
|
||||
M.clean = {
|
||||
run = function(self)
|
||||
local dir = self.plugin.dir:gsub("/+$", "")
|
||||
local stat = vim.loop.fs_lstat(dir)
|
||||
|
||||
if stat.type == "directory" then
|
||||
Util.walk(dir, function(path, _, type)
|
||||
if type == "directory" then
|
||||
vim.loop.fs_rmdir(path)
|
||||
else
|
||||
vim.loop.fs_unlink(path)
|
||||
end
|
||||
end)
|
||||
vim.loop.fs_rmdir(dir)
|
||||
local function rm(dir)
|
||||
local stat = vim.uv.fs_lstat(dir)
|
||||
assert(stat and stat.type == "directory", dir .. " should be a directory!")
|
||||
Util.walk(dir, function(path, _, type)
|
||||
if type == "directory" then
|
||||
vim.uv.fs_rmdir(path)
|
||||
else
|
||||
vim.loop.fs_unlink(dir)
|
||||
vim.uv.fs_unlink(path)
|
||||
end
|
||||
end)
|
||||
vim.uv.fs_rmdir(dir)
|
||||
end
|
||||
|
||||
M.clean = {
|
||||
skip = function(plugin)
|
||||
return plugin._.is_local
|
||||
end,
|
||||
---@param opts? {rocks_only?:boolean}
|
||||
run = function(self, opts)
|
||||
opts = opts or {}
|
||||
local dir = self.plugin.dir:gsub("/+$", "")
|
||||
assert(dir:find(Config.options.root, 1, true) == 1, self.plugin.dir .. " should be under packpath!")
|
||||
|
||||
local rock_root = Config.options.rocks.root .. "/" .. self.plugin.name
|
||||
if vim.uv.fs_stat(rock_root) then
|
||||
rm(rock_root)
|
||||
end
|
||||
|
||||
if opts.rocks_only then
|
||||
return
|
||||
end
|
||||
|
||||
rm(dir)
|
||||
|
||||
self.plugin._.installed = false
|
||||
end,
|
||||
}
|
||||
|
||||
M.symlink = {
|
||||
skip = function(plugin)
|
||||
if not plugin._.is_local then
|
||||
return true
|
||||
end
|
||||
return not plugin._.is_symlink and plugin._.installed
|
||||
end,
|
||||
run = function(self)
|
||||
local stat = vim.loop.fs_lstat(self.plugin.dir)
|
||||
if stat then
|
||||
assert(stat.type == "link")
|
||||
if vim.loop.fs_realpath(self.plugin.uri) == vim.loop.fs_realpath(self.plugin.dir) then
|
||||
return
|
||||
else
|
||||
vim.loop.fs_unlink(self.plugin.dir)
|
||||
end
|
||||
end
|
||||
vim.loop.fs_symlink(self.plugin.uri, self.plugin.dir, { dir = true })
|
||||
vim.opt.runtimepath:append(self.plugin.uri)
|
||||
end,
|
||||
}
|
||||
|
||||
return M
|
||||
|
|
|
@ -1,7 +1,53 @@
|
|||
local Util = require("lazy.util")
|
||||
local Async = require("lazy.async")
|
||||
local Config = require("lazy.core.config")
|
||||
local Git = require("lazy.manage.git")
|
||||
local Lock = require("lazy.manage.lock")
|
||||
local Config = require("lazy.core.config")
|
||||
local Util = require("lazy.util")
|
||||
|
||||
local throttle = {}
|
||||
throttle.running = 0
|
||||
throttle.waiting = {} ---@type Async[]
|
||||
throttle.timer = vim.uv.new_timer()
|
||||
|
||||
function throttle.next()
|
||||
throttle.running = 0
|
||||
while #throttle.waiting > 0 and throttle.running < Config.options.git.throttle.rate do
|
||||
---@type Async
|
||||
local task = table.remove(throttle.waiting, 1)
|
||||
task:resume()
|
||||
throttle.running = throttle.running + 1
|
||||
end
|
||||
if throttle.running == 0 then
|
||||
throttle.timer:stop()
|
||||
end
|
||||
end
|
||||
|
||||
function throttle.wait()
|
||||
if not Config.options.git.throttle.enabled then
|
||||
return
|
||||
end
|
||||
if not throttle.timer:is_active() then
|
||||
throttle.timer:start(0, Config.options.git.throttle.duration, vim.schedule_wrap(throttle.next))
|
||||
end
|
||||
local running = Async.running()
|
||||
if throttle.running < Config.options.git.throttle.rate then
|
||||
throttle.running = throttle.running + 1
|
||||
else
|
||||
table.insert(throttle.waiting, running)
|
||||
coroutine.yield("waiting")
|
||||
running:suspend()
|
||||
coroutine.yield("")
|
||||
end
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
local function cooldown(plugin)
|
||||
if not plugin._.last_check then
|
||||
return false
|
||||
end
|
||||
local delta = (vim.uv.now() - plugin._.last_check) / 1000
|
||||
return delta < Config.options.git.cooldown
|
||||
end
|
||||
|
||||
---@type table<string, LazyTaskDef>
|
||||
local M = {}
|
||||
|
@ -9,13 +55,19 @@ local M = {}
|
|||
M.log = {
|
||||
---@param opts {updated?:boolean, check?: boolean}
|
||||
skip = function(plugin, opts)
|
||||
if opts.check and plugin.pin then
|
||||
return true
|
||||
end
|
||||
if opts.updated and not (plugin._.updated and plugin._.updated.from ~= plugin._.updated.to) then
|
||||
return true
|
||||
end
|
||||
return not Util.file_exists(plugin.dir .. "/.git")
|
||||
local stat = vim.uv.fs_stat(plugin.dir .. "/.git")
|
||||
return not (stat and stat.type == "directory")
|
||||
end,
|
||||
---@async
|
||||
---@param opts {args?: string[], updated?:boolean, check?:boolean}
|
||||
run = function(self, opts)
|
||||
-- self:spawn({ "sleep", "5" })
|
||||
local args = {
|
||||
"log",
|
||||
"--pretty=format:%h %s (%cr)",
|
||||
|
@ -23,13 +75,35 @@ M.log = {
|
|||
"--decorate",
|
||||
"--date=short",
|
||||
"--color=never",
|
||||
"--no-show-signature",
|
||||
}
|
||||
|
||||
local info, target
|
||||
|
||||
if opts.updated then
|
||||
table.insert(args, self.plugin._.updated.from .. ".." .. (self.plugin._.updated.to or "HEAD"))
|
||||
elseif opts.check then
|
||||
local info = assert(Git.info(self.plugin.dir))
|
||||
local target = assert(Git.get_target(self.plugin))
|
||||
info = assert(Git.info(self.plugin.dir))
|
||||
target = assert(Git.get_target(self.plugin))
|
||||
if not target.commit then
|
||||
for k, v in pairs(target) do
|
||||
error(k .. " '" .. v .. "' not found")
|
||||
end
|
||||
error("no target commit found")
|
||||
end
|
||||
assert(target.commit, self.plugin.name .. " " .. target.branch)
|
||||
if not self.plugin._.is_local then
|
||||
if Git.eq(info, target) then
|
||||
if Config.options.checker.check_pinned then
|
||||
local last_commit = Git.get_commit(self.plugin.dir, target.branch, true)
|
||||
if not Git.eq(info, { commit = last_commit }) then
|
||||
self.plugin._.outdated = true
|
||||
end
|
||||
end
|
||||
else
|
||||
self.plugin._.updates = { from = info, to = target }
|
||||
end
|
||||
end
|
||||
table.insert(args, info.commit .. ".." .. target.commit)
|
||||
else
|
||||
vim.list_extend(args, opts.args or Config.options.git.log)
|
||||
|
@ -39,6 +113,14 @@ M.log = {
|
|||
args = args,
|
||||
cwd = self.plugin.dir,
|
||||
})
|
||||
|
||||
-- for local plugins, mark as needing updates only if local is
|
||||
-- behind upstream, i.e. if git log gave no output
|
||||
if opts.check and self.plugin._.is_local then
|
||||
if not vim.tbl_isempty(self:get_log()) then
|
||||
self.plugin._.updates = { from = info, to = target }
|
||||
end
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
|
@ -46,23 +128,45 @@ M.clone = {
|
|||
skip = function(plugin)
|
||||
return plugin._.installed or plugin._.is_local
|
||||
end,
|
||||
---@async
|
||||
run = function(self)
|
||||
throttle.wait()
|
||||
local args = {
|
||||
"clone",
|
||||
self.plugin.uri,
|
||||
"--filter=blob:none",
|
||||
"--recurse-submodules",
|
||||
"--single-branch",
|
||||
"--shallow-submodules",
|
||||
"--no-checkout",
|
||||
"--progress",
|
||||
self.plugin.url,
|
||||
}
|
||||
|
||||
if Config.options.git.filter then
|
||||
args[#args + 1] = "--filter=blob:none"
|
||||
end
|
||||
|
||||
if self.plugin.submodules ~= false then
|
||||
args[#args + 1] = "--recurse-submodules"
|
||||
end
|
||||
|
||||
args[#args + 1] = "--origin=origin"
|
||||
|
||||
-- If git config --global core.autocrlf is true on a Unix/Linux system, then the git clone
|
||||
-- process will lead to files with CRLF endings. Vi / vim / neovim cannot handle this.
|
||||
-- Force git to clone with core.autocrlf=false.
|
||||
args[#args + 1] = "-c"
|
||||
args[#args + 1] = "core.autocrlf=false"
|
||||
|
||||
args[#args + 1] = "--progress"
|
||||
|
||||
if self.plugin.branch then
|
||||
vim.list_extend(args, { "-b", self.plugin.branch })
|
||||
end
|
||||
|
||||
table.insert(args, self.plugin.dir)
|
||||
|
||||
if vim.fn.isdirectory(self.plugin.dir) == 1 then
|
||||
require("lazy.manage.task.fs").clean.run(self, {})
|
||||
end
|
||||
|
||||
local marker = self.plugin.dir .. ".cloning"
|
||||
Util.write_file(marker, "")
|
||||
|
||||
self:spawn("git", {
|
||||
args = args,
|
||||
on_exit = function(ok)
|
||||
|
@ -70,28 +174,31 @@ M.clone = {
|
|||
self.plugin._.cloned = true
|
||||
self.plugin._.installed = true
|
||||
self.plugin._.dirty = true
|
||||
vim.uv.fs_unlink(marker)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end,
|
||||
}
|
||||
|
||||
-- setup origin branches if needed
|
||||
-- fetch will retrieve the data
|
||||
M.branch = {
|
||||
skip = function(plugin)
|
||||
if not plugin._.installed or plugin._.is_local then
|
||||
return true
|
||||
end
|
||||
local branch = assert(Git.get_branch(plugin))
|
||||
return branch and branch.commit
|
||||
return Git.get_commit(plugin.dir, branch, true)
|
||||
end,
|
||||
---@async
|
||||
run = function(self)
|
||||
local branch = assert(Git.get_branch(self.plugin))
|
||||
local args = {
|
||||
"remote",
|
||||
"set-branches",
|
||||
"--add",
|
||||
"origin",
|
||||
branch.branch,
|
||||
assert(Git.get_branch(self.plugin)),
|
||||
}
|
||||
|
||||
self:spawn("git", {
|
||||
|
@ -101,42 +208,126 @@ M.branch = {
|
|||
end,
|
||||
}
|
||||
|
||||
M.fetch = {
|
||||
-- check and switch origin
|
||||
M.origin = {
|
||||
skip = function(plugin)
|
||||
if not plugin._.installed or plugin._.is_local then
|
||||
return true
|
||||
end
|
||||
local origin = Git.get_origin(plugin.dir)
|
||||
return origin == plugin.url
|
||||
end,
|
||||
---@async
|
||||
---@param opts {check?:boolean}
|
||||
run = function(self, opts)
|
||||
if opts.check then
|
||||
local origin = Git.get_origin(self.plugin.dir)
|
||||
self:error({
|
||||
"Origin has changed:",
|
||||
" * old: " .. origin,
|
||||
" * new: " .. self.plugin.url,
|
||||
"Please run update to fix",
|
||||
})
|
||||
return
|
||||
end
|
||||
require("lazy.manage.task.fs").clean.run(self, opts)
|
||||
M.clone.run(self, opts)
|
||||
end,
|
||||
}
|
||||
|
||||
M.status = {
|
||||
skip = function(plugin)
|
||||
return not plugin._.installed or plugin._.is_local
|
||||
end,
|
||||
---@async
|
||||
run = function(self)
|
||||
local args = {
|
||||
"fetch",
|
||||
"--recurse-submodules",
|
||||
"--update-shallow",
|
||||
"--progress",
|
||||
}
|
||||
|
||||
self:spawn("git", {
|
||||
args = args,
|
||||
args = { "ls-files", "-d", "-m" },
|
||||
cwd = self.plugin.dir,
|
||||
on_exit = function(ok, output)
|
||||
if ok then
|
||||
local lines = vim.split(output, "\n")
|
||||
---@type string[]
|
||||
lines = vim.tbl_filter(function(line)
|
||||
-- Fix doc/tags being marked as modified
|
||||
if line:gsub("[\\/]", "/") == "doc/tags" then
|
||||
local Process = require("lazy.manage.process")
|
||||
Process.exec({ "git", "checkout", "--", "doc/tags" }, { cwd = self.plugin.dir })
|
||||
return false
|
||||
end
|
||||
return line ~= ""
|
||||
end, lines)
|
||||
if #lines > 0 then
|
||||
local msg = { "You have local changes in `" .. self.plugin.dir .. "`:" }
|
||||
for _, line in ipairs(lines) do
|
||||
msg[#msg + 1] = " * " .. line
|
||||
end
|
||||
msg[#msg + 1] = "Please remove them to update."
|
||||
msg[#msg + 1] = "You can also press `x` to remove the plugin and then `I` to install it again."
|
||||
self:error(msg)
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
||||
end,
|
||||
}
|
||||
|
||||
-- fetches all needed origin branches
|
||||
M.fetch = {
|
||||
skip = function(plugin)
|
||||
return not plugin._.installed or plugin._.is_local or cooldown(plugin)
|
||||
end,
|
||||
|
||||
---@async
|
||||
run = function(self)
|
||||
throttle.wait()
|
||||
local args = {
|
||||
"fetch",
|
||||
"--recurse-submodules",
|
||||
"--tags", -- also fetch remote tags
|
||||
"--force", -- overwrite existing tags if needed
|
||||
"--progress",
|
||||
}
|
||||
|
||||
if self.plugin.submodules == false then
|
||||
table.remove(args, 2)
|
||||
end
|
||||
|
||||
self:spawn("git", {
|
||||
args = args,
|
||||
cwd = self.plugin.dir,
|
||||
on_exit = function(ok)
|
||||
if ok then
|
||||
self.plugin._.last_check = vim.uv.now()
|
||||
end
|
||||
end,
|
||||
})
|
||||
end,
|
||||
}
|
||||
|
||||
-- checkout to the target commit
|
||||
-- branches will exists at this point, so so will the commit
|
||||
M.checkout = {
|
||||
skip = function(plugin)
|
||||
return not plugin._.installed or plugin._.is_local
|
||||
end,
|
||||
|
||||
---@async
|
||||
---@param opts {lockfile?:boolean}
|
||||
run = function(self, opts)
|
||||
throttle.wait()
|
||||
local info = assert(Git.info(self.plugin.dir))
|
||||
local target = assert(Git.get_target(self.plugin))
|
||||
|
||||
-- if the plugin is locked and we did not just clone it,
|
||||
-- if the plugin is pinned and we did not just clone it,
|
||||
-- then don't update
|
||||
if self.plugin.lock and not self.plugin._.cloned then
|
||||
if self.plugin.pin and not self.plugin._.cloned then
|
||||
target = info
|
||||
end
|
||||
|
||||
local lock
|
||||
if opts.lockfile then
|
||||
-- restore to the lock if it exists
|
||||
lock = Lock.get(self.plugin)
|
||||
if lock then
|
||||
---@diagnostic disable-next-line: cast-local-type
|
||||
|
@ -144,7 +335,9 @@ M.checkout = {
|
|||
end
|
||||
end
|
||||
|
||||
if not self.plugin._.cloned and info.commit == target.commit and info.branch == target.branch then
|
||||
-- don't run checkout if target is already reached.
|
||||
-- unless we just cloned, since then we won't have any data yet
|
||||
if Git.eq(info, target) and info.branch == target.branch then
|
||||
self.plugin._.updated = {
|
||||
from = info.commit,
|
||||
to = info.commit,
|
||||
|
@ -155,16 +348,21 @@ M.checkout = {
|
|||
local args = {
|
||||
"checkout",
|
||||
"--progress",
|
||||
"--recurse-submodules",
|
||||
}
|
||||
|
||||
if self.plugin.submodules == false then
|
||||
table.remove(args, 3)
|
||||
end
|
||||
|
||||
if lock then
|
||||
table.insert(args, lock.commit)
|
||||
elseif target.tag then
|
||||
table.insert(args, "tags/" .. target.tag)
|
||||
elseif self.plugin.commit then
|
||||
table.insert(args, self.plugin.commit)
|
||||
elseif target.branch then
|
||||
table.insert(args, target.branch)
|
||||
else
|
||||
table.insert(args, target.commit)
|
||||
end
|
||||
|
||||
self:spawn("git", {
|
||||
|
@ -178,8 +376,10 @@ M.checkout = {
|
|||
from = info.commit,
|
||||
to = new_info.commit,
|
||||
}
|
||||
if self.plugin._.updated.from ~= self.plugin._.updated.to then
|
||||
self.plugin._.dirty = true
|
||||
end
|
||||
end
|
||||
self.plugin._.dirty = true
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
|
|
@ -1,24 +1,29 @@
|
|||
local Async = require("lazy.async")
|
||||
local Config = require("lazy.core.config")
|
||||
local Process = require("lazy.manage.process")
|
||||
local Terminal = require("lazy.terminal")
|
||||
|
||||
local colors = Config.options.headless.colors
|
||||
|
||||
---@class LazyTaskDef
|
||||
---@field skip? fun(plugin:LazyPlugin, opts?:TaskOptions):any?
|
||||
---@field run fun(task:LazyTask, opts:TaskOptions)
|
||||
---@field run async fun(task:LazyTask, opts:TaskOptions)
|
||||
|
||||
---@alias LazyTaskState fun():boolean?
|
||||
---@alias LazyTaskFn async fun(task:LazyTask, opts:TaskOptions)
|
||||
|
||||
---@class LazyTask
|
||||
---@class LazyMsg
|
||||
---@field msg string
|
||||
---@field level? number
|
||||
|
||||
---@class LazyTask: Async
|
||||
---@field plugin LazyPlugin
|
||||
---@field name string
|
||||
---@field type string
|
||||
---@field output string
|
||||
---@field status string
|
||||
---@field error? string
|
||||
---@field private _task fun(task:LazyTask)
|
||||
---@field private _running LazyPluginState[]
|
||||
---@field private _started? number
|
||||
---@field private _log LazyMsg[]
|
||||
---@field private _started number
|
||||
---@field private _ended? number
|
||||
---@field private _opts TaskOptions
|
||||
local Task = {}
|
||||
---@field private _level number
|
||||
local Task = setmetatable({}, { __index = Async.Async })
|
||||
|
||||
---@class TaskOptions: {[string]:any}
|
||||
---@field on_done? fun(task:LazyTask)
|
||||
|
@ -26,142 +31,210 @@ local Task = {}
|
|||
---@param plugin LazyPlugin
|
||||
---@param name string
|
||||
---@param opts? TaskOptions
|
||||
---@param task fun(task:LazyTask)
|
||||
---@param task LazyTaskFn
|
||||
function Task.new(plugin, name, task, opts)
|
||||
local self = setmetatable({}, {
|
||||
__index = Task,
|
||||
})
|
||||
local self = setmetatable({}, { __index = Task })
|
||||
---@async
|
||||
Task.init(self, function()
|
||||
self:_run(task)
|
||||
end)
|
||||
self:set_level()
|
||||
self._opts = opts or {}
|
||||
self._running = {}
|
||||
self._task = task
|
||||
self._started = nil
|
||||
self._log = {}
|
||||
self.plugin = plugin
|
||||
self.name = name
|
||||
self.output = ""
|
||||
self.status = ""
|
||||
plugin._.tasks = plugin._.tasks or {}
|
||||
self._started = vim.uv.hrtime()
|
||||
---@param other LazyTask
|
||||
plugin._.tasks = vim.tbl_filter(function(other)
|
||||
return other.name ~= name or other:running()
|
||||
end, plugin._.tasks or {})
|
||||
table.insert(plugin._.tasks, self)
|
||||
self:render()
|
||||
return self
|
||||
end
|
||||
|
||||
function Task:has_started()
|
||||
return self._started ~= nil
|
||||
---@param level? number
|
||||
---@return LazyMsg[]
|
||||
function Task:get_log(level)
|
||||
level = level or vim.log.levels.DEBUG
|
||||
return vim.tbl_filter(function(msg)
|
||||
return msg.level >= level
|
||||
end, self._log)
|
||||
end
|
||||
|
||||
function Task:is_done()
|
||||
return self:has_started() and not self:is_running()
|
||||
---@param level? number
|
||||
function Task:output(level)
|
||||
return table.concat(
|
||||
---@param m LazyMsg
|
||||
vim.tbl_map(function(m)
|
||||
return m.msg
|
||||
end, self:get_log(level)),
|
||||
"\n"
|
||||
)
|
||||
end
|
||||
|
||||
function Task:is_running()
|
||||
return self:has_started() and self._ended == nil
|
||||
function Task:status()
|
||||
local ret = self._log[#self._log]
|
||||
local msg = ret and vim.trim(ret.msg) or ""
|
||||
return msg ~= "" and msg or nil
|
||||
end
|
||||
|
||||
function Task:start()
|
||||
if vim.in_fast_event() then
|
||||
return vim.schedule(function()
|
||||
self:start()
|
||||
function Task:has_errors()
|
||||
return self._level >= vim.log.levels.ERROR
|
||||
end
|
||||
|
||||
function Task:has_warnings()
|
||||
return self._level >= vim.log.levels.WARN
|
||||
end
|
||||
|
||||
---@param level? number
|
||||
function Task:set_level(level)
|
||||
self._level = level or vim.log.levels.TRACE
|
||||
end
|
||||
|
||||
---@async
|
||||
---@param task LazyTaskFn
|
||||
function Task:_run(task)
|
||||
if Config.headless() and Config.options.headless.task then
|
||||
self:log("Running task " .. self.name, vim.log.levels.INFO)
|
||||
end
|
||||
|
||||
self
|
||||
:on("done", function()
|
||||
self:_done()
|
||||
end)
|
||||
:on("error", function(err)
|
||||
self:error(err)
|
||||
end)
|
||||
:on("yield", function(msg)
|
||||
self:log(msg)
|
||||
end)
|
||||
task(self, self._opts)
|
||||
end
|
||||
|
||||
---@param msg string|string[]|LazyMsg
|
||||
---@param level? number
|
||||
function Task:log(msg, level)
|
||||
if type(msg) == "table" and msg.msg then
|
||||
level = msg.level or level
|
||||
msg = msg.msg
|
||||
end
|
||||
self._started = vim.loop.hrtime()
|
||||
---@type boolean, string|any
|
||||
local ok, err = pcall(self._task, self, self._opts)
|
||||
if not ok then
|
||||
self.error = err or "failed"
|
||||
level = level or vim.log.levels.DEBUG
|
||||
self._level = math.max(self._level or 0, level or 0)
|
||||
msg = type(msg) == "table" and table.concat(msg, "\n") or msg
|
||||
---@cast msg string
|
||||
table.insert(self._log, { msg = msg, level = level })
|
||||
self:render()
|
||||
if Config.headless() then
|
||||
self:headless()
|
||||
end
|
||||
self:_check()
|
||||
end
|
||||
|
||||
function Task:render()
|
||||
vim.schedule(function()
|
||||
vim.api.nvim_exec_autocmds("User", { pattern = "LazyRender", modeline = false })
|
||||
end)
|
||||
end
|
||||
|
||||
function Task:headless()
|
||||
if not Config.options.headless.log then
|
||||
return
|
||||
end
|
||||
local msg = self._log[#self._log]
|
||||
if not msg or msg.level == vim.log.levels.TRACE then
|
||||
return
|
||||
end
|
||||
local map = {
|
||||
[vim.log.levels.ERROR] = Terminal.red,
|
||||
[vim.log.levels.WARN] = Terminal.yellow,
|
||||
[vim.log.levels.INFO] = Terminal.blue,
|
||||
}
|
||||
local color = Config.options.headless.colors and map[msg.level]
|
||||
io.write(Terminal.prefix(color and color(msg.msg) or msg.msg, self:prefix()))
|
||||
io.write("\n")
|
||||
end
|
||||
|
||||
---@param msg string|string[]
|
||||
function Task:error(msg)
|
||||
self:log(msg, vim.log.levels.ERROR)
|
||||
end
|
||||
|
||||
---@param msg string|string[]
|
||||
function Task:warn(msg)
|
||||
self:log(msg, vim.log.levels.WARN)
|
||||
end
|
||||
|
||||
---@private
|
||||
function Task:_check()
|
||||
for _, state in ipairs(self._running) do
|
||||
if state() then
|
||||
return
|
||||
end
|
||||
function Task:_done()
|
||||
if Config.headless() and Config.options.headless.task then
|
||||
local ms = math.floor(self:time() + 0.5)
|
||||
self:log("Finished task " .. self.name .. " in " .. ms .. "ms", vim.log.levels.INFO)
|
||||
end
|
||||
self._ended = vim.loop.hrtime()
|
||||
self._ended = vim.uv.hrtime()
|
||||
if self._opts.on_done then
|
||||
self._opts.on_done(self)
|
||||
end
|
||||
vim.cmd("do User LazyRender")
|
||||
vim.api.nvim_exec_autocmds("User", {
|
||||
pattern = "LazyPlugin" .. self.name:sub(1, 1):upper() .. self.name:sub(2),
|
||||
data = { plugin = self.plugin.name },
|
||||
})
|
||||
self:render()
|
||||
vim.schedule(function()
|
||||
vim.api.nvim_exec_autocmds("User", {
|
||||
pattern = "LazyPlugin" .. self.name:sub(1, 1):upper() .. self.name:sub(2),
|
||||
data = { plugin = self.plugin.name },
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
function Task:time()
|
||||
if not self:has_started() then
|
||||
return 0
|
||||
end
|
||||
if not self:is_done() then
|
||||
return (vim.loop.hrtime() - self._started) / 1e6
|
||||
end
|
||||
return (self._ended - self._started) / 1e6
|
||||
end
|
||||
|
||||
---@param fn fun()
|
||||
function Task:schedule(fn)
|
||||
local done = false
|
||||
table.insert(self._running, function()
|
||||
return not done
|
||||
end)
|
||||
vim.schedule(function()
|
||||
---@type boolean, string|any
|
||||
local ok, err = pcall(fn)
|
||||
if not ok then
|
||||
self.error = err or "failed"
|
||||
end
|
||||
done = true
|
||||
self:_check()
|
||||
end)
|
||||
return ((self._ended or vim.uv.hrtime()) - self._started) / 1e6
|
||||
end
|
||||
|
||||
---@async
|
||||
---@param cmd string
|
||||
---@param opts? ProcessOpts
|
||||
function Task:spawn(cmd, opts)
|
||||
opts = opts or {}
|
||||
local on_line = opts.on_line
|
||||
local on_exit = opts.on_exit
|
||||
|
||||
local headless = Config.headless() and Config.options.headless.process
|
||||
|
||||
function opts.on_line(line)
|
||||
self.status = line
|
||||
if not headless then
|
||||
return self:log(line, vim.log.levels.TRACE)
|
||||
end
|
||||
if on_line then
|
||||
pcall(on_line, line)
|
||||
end
|
||||
vim.cmd("do User LazyRender")
|
||||
end
|
||||
|
||||
---@param output string
|
||||
function opts.on_exit(ok, output)
|
||||
self.output = self.output .. output
|
||||
if not ok then
|
||||
self.error = self.error and (self.error .. "\n" .. output) or output
|
||||
if headless then
|
||||
opts.on_data = function(data)
|
||||
-- prefix with plugin name
|
||||
io.write(Terminal.prefix(data, self:prefix()))
|
||||
end
|
||||
if on_exit then
|
||||
pcall(on_exit, ok, output)
|
||||
end
|
||||
self:_check()
|
||||
end
|
||||
|
||||
local proc = Process.spawn(cmd, opts)
|
||||
table.insert(self._running, function()
|
||||
return proc and not proc:is_closing()
|
||||
end)
|
||||
end
|
||||
proc:wait()
|
||||
|
||||
---@param tasks (LazyTask?)[]
|
||||
function Task.all_done(tasks)
|
||||
for _, task in ipairs(tasks) do
|
||||
if task and not task:is_done() then
|
||||
return false
|
||||
local ok = proc.code == 0 and proc.signal == 0
|
||||
if not headless then
|
||||
local msg = vim.trim(proc.data)
|
||||
if #msg > 0 then
|
||||
self:log(vim.trim(proc.data), ok and vim.log.levels.DEBUG or vim.log.levels.ERROR)
|
||||
end
|
||||
end
|
||||
return true
|
||||
|
||||
if opts.on_exit then
|
||||
pcall(opts.on_exit, ok, proc.data)
|
||||
end
|
||||
return ok
|
||||
end
|
||||
|
||||
function Task:wait()
|
||||
while self:is_running() do
|
||||
vim.wait(10)
|
||||
end
|
||||
function Task:prefix()
|
||||
local plugin = "[" .. self.plugin.name .. "] "
|
||||
local task = string.rep(" ", 20 - #(self.name .. self.plugin.name)) .. self.name
|
||||
|
||||
return colors and Terminal.magenta(plugin) .. Terminal.cyan(task) .. Terminal.bright_black(" | ")
|
||||
or plugin .. " " .. task .. " | "
|
||||
end
|
||||
|
||||
return Task
|
||||
|
|
|
@ -1,29 +1,85 @@
|
|||
local Util = require("lazy.util")
|
||||
local Loader = require("lazy.core.loader")
|
||||
local Rocks = require("lazy.pkg.rockspec")
|
||||
local Util = require("lazy.util")
|
||||
|
||||
---@type table<string, LazyTaskDef>
|
||||
local M = {}
|
||||
|
||||
M.run = {
|
||||
skip = function(plugin)
|
||||
return not (plugin._.dirty and (plugin.opt == false or plugin.run))
|
||||
end,
|
||||
run = function(self)
|
||||
Loader.load(self.plugin, { task = "run" }, { load_start = true })
|
||||
---@param plugin LazyPlugin
|
||||
local function get_build_file(plugin)
|
||||
for _, path in ipairs({ "build.lua", "build/init.lua" }) do
|
||||
if Util.file_exists(plugin.dir .. "/" .. path) then
|
||||
return path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local run = self.plugin.run
|
||||
if run then
|
||||
if type(run) == "string" and run:sub(1, 1) == ":" then
|
||||
local cmd = vim.api.nvim_parse_cmd(run:sub(2), {})
|
||||
self.output = vim.api.nvim_cmd(cmd, { output = true })
|
||||
elseif type(run) == "function" then
|
||||
run()
|
||||
else
|
||||
local args = vim.split(run, "%s+")
|
||||
return self:spawn(table.remove(args, 1), {
|
||||
args = args,
|
||||
cwd = self.plugin.dir,
|
||||
})
|
||||
local B = {}
|
||||
|
||||
---@param task LazyTask
|
||||
---@param build string
|
||||
function B.cmd(task, build)
|
||||
if task.plugin.build ~= "rockspec" then
|
||||
Loader.load(task.plugin, { task = "build" })
|
||||
end
|
||||
local cmd = vim.api.nvim_parse_cmd(build:sub(2), {}) --[[@as vim.api.keyset.cmd]]
|
||||
task:log(vim.api.nvim_cmd(cmd, { output = true }))
|
||||
end
|
||||
|
||||
---@async
|
||||
---@param task LazyTask
|
||||
---@param build string
|
||||
function B.shell(task, build)
|
||||
local shell = vim.env.SHELL or vim.o.shell
|
||||
local shell_args = shell:find("cmd.exe", 1, true) and "/c" or "-c"
|
||||
|
||||
task:spawn(shell, {
|
||||
args = { shell_args, build },
|
||||
cwd = task.plugin.dir,
|
||||
})
|
||||
end
|
||||
|
||||
M.build = {
|
||||
---@param opts? {force:boolean}
|
||||
skip = function(plugin, opts)
|
||||
if opts and opts.force then
|
||||
return false
|
||||
end
|
||||
return not ((plugin._.dirty or plugin._.build) and (plugin.build or get_build_file(plugin)))
|
||||
end,
|
||||
---@async
|
||||
run = function(self)
|
||||
vim.cmd([[silent! runtime plugin/rplugin.vim]])
|
||||
|
||||
local builders = self.plugin.build
|
||||
|
||||
-- Skip if `build` is set to `false`
|
||||
if builders == false then
|
||||
return
|
||||
end
|
||||
|
||||
builders = builders or get_build_file(self.plugin)
|
||||
|
||||
if builders then
|
||||
builders = type(builders) == "table" and builders or { builders }
|
||||
---@cast builders (string|fun(LazyPlugin))[]
|
||||
for _, build in ipairs(builders) do
|
||||
if type(build) == "function" then
|
||||
build(self.plugin)
|
||||
elseif build == "rockspec" then
|
||||
Rocks.build(self)
|
||||
elseif build:sub(1, 1) == ":" then
|
||||
B.cmd(self, build)
|
||||
elseif build:match("%.lua$") then
|
||||
local file = self.plugin.dir .. "/" .. build
|
||||
local chunk, err = loadfile(file)
|
||||
if not chunk or err then
|
||||
error(err)
|
||||
end
|
||||
chunk()
|
||||
else
|
||||
B.shell(self, build)
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
@ -31,12 +87,23 @@ M.run = {
|
|||
|
||||
M.docs = {
|
||||
skip = function(plugin)
|
||||
return not plugin._.dirty
|
||||
return not plugin._.is_local and not plugin._.dirty
|
||||
end,
|
||||
run = function(self)
|
||||
local docs = self.plugin.dir .. "/doc/"
|
||||
local docs = self.plugin.dir .. "/doc"
|
||||
if Util.file_exists(docs) then
|
||||
self.output = vim.api.nvim_cmd({ cmd = "helptags", args = { docs } }, { output = true })
|
||||
self:log(vim.api.nvim_cmd({ cmd = "helptags", args = { docs } }, { output = true }))
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
M.exists = {
|
||||
skip = function(plugin)
|
||||
return not plugin._.is_local or plugin.virtual
|
||||
end,
|
||||
run = function(self)
|
||||
if not Util.file_exists(self.plugin.dir) then
|
||||
self:error("Local plugin does not exist at `" .. self.plugin.dir .. "`")
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
|
214
lua/lazy/minit.lua
Normal file
214
lua/lazy/minit.lua
Normal file
|
@ -0,0 +1,214 @@
|
|||
---@diagnostic disable: inject-field
|
||||
|
||||
local islist = vim.islist or vim.tbl_islist
|
||||
|
||||
local M = {}
|
||||
|
||||
---@param opts LazyConfig
|
||||
---@return LazySpec[]
|
||||
local function get_spec(opts)
|
||||
local ret = opts.spec or {}
|
||||
return ret and type(ret) == "table" and islist(ret) and ret or { ret }
|
||||
end
|
||||
|
||||
---@param defaults LazyConfig
|
||||
---@param opts LazyConfig
|
||||
function M.extend(defaults, opts)
|
||||
local spec = {}
|
||||
vim.list_extend(spec, get_spec(defaults))
|
||||
vim.list_extend(spec, get_spec(opts))
|
||||
return vim.tbl_deep_extend("force", defaults, opts, { spec = spec })
|
||||
end
|
||||
|
||||
---@param opts LazyConfig
|
||||
function M.setup(opts)
|
||||
opts = M.extend({
|
||||
local_spec = false,
|
||||
change_detection = { enabled = false },
|
||||
dev = {
|
||||
patterns = vim.env.LAZY_DEV and vim.split(vim.env.LAZY_DEV, ",") or nil,
|
||||
},
|
||||
}, opts)
|
||||
|
||||
local args = {}
|
||||
local is_busted = false
|
||||
local is_minitest = false
|
||||
for _, a in ipairs(_G.arg) do
|
||||
if a == "--busted" then
|
||||
is_busted = true
|
||||
elseif a == "--minitest" then
|
||||
is_minitest = true
|
||||
else
|
||||
table.insert(args, a)
|
||||
end
|
||||
end
|
||||
_G.arg = args
|
||||
|
||||
if is_busted then
|
||||
opts = M.busted.setup(opts)
|
||||
elseif is_minitest then
|
||||
opts = M.minitest.setup(opts)
|
||||
end
|
||||
|
||||
-- set stdpaths to use .tests
|
||||
if vim.env.LAZY_STDPATH then
|
||||
local root = vim.fn.fnamemodify(vim.env.LAZY_STDPATH, ":p")
|
||||
for _, name in ipairs({ "config", "data", "state", "cache" }) do
|
||||
vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. "/" .. name
|
||||
end
|
||||
end
|
||||
vim.o.loadplugins = true
|
||||
require("lazy").setup(opts)
|
||||
if vim.g.colors_name == nil then
|
||||
vim.cmd("colorscheme habamax")
|
||||
end
|
||||
require("lazy").update():wait()
|
||||
if vim.bo.filetype == "lazy" then
|
||||
local errors = false
|
||||
for _, plugin in pairs(require("lazy.core.config").spec.plugins) do
|
||||
errors = errors or require("lazy.core.plugin").has_errors(plugin)
|
||||
end
|
||||
if not errors then
|
||||
vim.cmd.close()
|
||||
end
|
||||
end
|
||||
|
||||
if is_busted then
|
||||
M.busted.run()
|
||||
elseif is_minitest then
|
||||
M.minitest.run()
|
||||
end
|
||||
end
|
||||
|
||||
function M.repro(opts)
|
||||
opts = M.extend({
|
||||
spec = {
|
||||
{
|
||||
"folke/tokyonight.nvim",
|
||||
priority = 1000,
|
||||
lazy = false,
|
||||
config = function()
|
||||
require("tokyonight").setup({ style = "moon" })
|
||||
require("tokyonight").load()
|
||||
end,
|
||||
},
|
||||
},
|
||||
install = { colorscheme = { "tokyonight" } },
|
||||
}, opts)
|
||||
M.setup(opts)
|
||||
end
|
||||
|
||||
M.minitest = {}
|
||||
|
||||
function M.minitest.run()
|
||||
local Config = require("lazy.core.config")
|
||||
-- disable termnial output for the tests
|
||||
Config.options.headless = {}
|
||||
|
||||
if not require("lazy.core.config").headless() then
|
||||
return vim.notify("busted can only run in headless mode. Please run with `nvim -l`", vim.log.levels.WARN)
|
||||
end
|
||||
package.path = package.path .. ";" .. vim.uv.cwd() .. "/tests/?.lua"
|
||||
local Test = require("mini.test")
|
||||
local expect = Test.expect
|
||||
local _assert = assert
|
||||
local Assert = {
|
||||
__call = function(_, ...)
|
||||
return _assert(...)
|
||||
end,
|
||||
same = expect.equality,
|
||||
equal = expect.equality,
|
||||
are = {
|
||||
equal = expect.equality,
|
||||
},
|
||||
is_not = {
|
||||
same = expect.no_equality,
|
||||
},
|
||||
is_not_nil = function(a)
|
||||
return expect.no_equality(nil, a)
|
||||
end,
|
||||
is_true = function(a)
|
||||
return expect.equality(true, a)
|
||||
end,
|
||||
is_false = function(a)
|
||||
return expect.equality(false, a)
|
||||
end,
|
||||
}
|
||||
Assert.__index = Assert
|
||||
assert = setmetatable({}, Assert)
|
||||
assert = require("luassert")
|
||||
require("mini.test").run()
|
||||
end
|
||||
|
||||
---@param opts LazyConfig
|
||||
function M.minitest.setup(opts)
|
||||
return M.extend({
|
||||
spec = {
|
||||
"lunarmodules/luassert",
|
||||
{
|
||||
"echasnovski/mini.test",
|
||||
opts = {
|
||||
collect = {
|
||||
find_files = function()
|
||||
return vim.fn.globpath("tests", "**/*_spec.lua", true, true)
|
||||
end,
|
||||
},
|
||||
-- script_path = "tests/minit.lua",
|
||||
},
|
||||
},
|
||||
{ dir = vim.uv.cwd() },
|
||||
},
|
||||
rocks = { hererocks = true },
|
||||
}, opts)
|
||||
end
|
||||
|
||||
M.busted = {}
|
||||
|
||||
function M.busted.run()
|
||||
local Config = require("lazy.core.config")
|
||||
-- disable termnial output for the tests
|
||||
Config.options.headless = {}
|
||||
|
||||
if not require("lazy.core.config").headless() then
|
||||
return vim.notify("busted can only run in headless mode. Please run with `nvim -l`", vim.log.levels.WARN)
|
||||
end
|
||||
package.path = package.path .. ";" .. vim.uv.cwd() .. "/tests/?.lua"
|
||||
-- run busted
|
||||
return pcall(require("busted.runner"), {
|
||||
standalone = false,
|
||||
}) or os.exit(1)
|
||||
end
|
||||
|
||||
---@param opts LazyConfig
|
||||
function M.busted.setup(opts)
|
||||
local args = table.concat(_G.arg, " ")
|
||||
local json = args:find("--output[ =]json")
|
||||
|
||||
return M.extend({
|
||||
spec = {
|
||||
"lunarmodules/busted",
|
||||
{ dir = vim.uv.cwd() },
|
||||
},
|
||||
headless = {
|
||||
process = not json,
|
||||
log = not json,
|
||||
task = not json,
|
||||
},
|
||||
rocks = { hererocks = true },
|
||||
}, opts)
|
||||
end
|
||||
|
||||
---@param opts LazyConfig
|
||||
function M.busted.init(opts)
|
||||
opts = M.busted.setup(opts)
|
||||
M.setup(opts)
|
||||
M.busted.run()
|
||||
end
|
||||
|
||||
setmetatable(M.busted, {
|
||||
__call = function(_, opts)
|
||||
M.busted.init(opts)
|
||||
end,
|
||||
})
|
||||
|
||||
return M
|
137
lua/lazy/pkg/init.lua
Normal file
137
lua/lazy/pkg/init.lua
Normal file
|
@ -0,0 +1,137 @@
|
|||
local Config = require("lazy.core.config")
|
||||
local Util = require("lazy.core.util")
|
||||
|
||||
local M = {}
|
||||
M.VERSION = 12
|
||||
M.dirty = false
|
||||
|
||||
---@class LazyPkg
|
||||
---@field name string
|
||||
---@field dir string
|
||||
---@field source "lazy" | "packspec" | "rockspec"
|
||||
---@field file string
|
||||
---@field spec LazyPluginSpec
|
||||
|
||||
---@class LazyPkgSpec
|
||||
---@field file string
|
||||
---@field source? string
|
||||
---@field spec? LazySpec
|
||||
---@field code? string
|
||||
|
||||
---@class LazyPkgSource
|
||||
---@field name string
|
||||
---@field get fun(plugin:LazyPlugin):LazyPkgSpec?
|
||||
|
||||
---@class LazyPkgCache
|
||||
---@field pkgs LazyPkg[]
|
||||
---@field version number
|
||||
|
||||
---@type LazyPkg[]?
|
||||
M.cache = nil
|
||||
|
||||
function M.update()
|
||||
---@type LazyPkgSource[]
|
||||
local sources = {}
|
||||
for _, s in ipairs(Config.options.pkg.sources) do
|
||||
if s ~= "rockspec" or Config.options.rocks.enabled then
|
||||
sources[#sources + 1] = {
|
||||
name = s,
|
||||
get = require("lazy.pkg." .. s).get,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
---@type LazyPkgCache
|
||||
local ret = {
|
||||
version = M.VERSION,
|
||||
pkgs = {},
|
||||
}
|
||||
for _, plugin in pairs(Config.plugins) do
|
||||
if plugin._.installed then
|
||||
for _, source in ipairs(sources) do
|
||||
local spec = source.get(plugin)
|
||||
if spec then
|
||||
---@type LazyPkg
|
||||
local pkg = {
|
||||
name = plugin.name,
|
||||
dir = plugin.dir,
|
||||
source = spec.source or source.name,
|
||||
file = spec.file,
|
||||
spec = spec.spec or {},
|
||||
}
|
||||
if type(spec.code) == "string" then
|
||||
pkg.spec = { _raw = spec.code }
|
||||
end
|
||||
table.insert(ret.pkgs, pkg)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
table.sort(ret.pkgs, function(a, b)
|
||||
return a.name < b.name
|
||||
end)
|
||||
local U = require("lazy.util")
|
||||
local code = "return " .. U.dump(ret)
|
||||
vim.fn.mkdir(vim.fn.fnamemodify(Config.options.pkg.cache, ":h"), "p")
|
||||
U.write_file(Config.options.pkg.cache, code)
|
||||
M.dirty = false
|
||||
M.cache = nil
|
||||
end
|
||||
|
||||
local function _load()
|
||||
Util.track("pkg")
|
||||
M.cache = nil
|
||||
if vim.uv.fs_stat(Config.options.pkg.cache) then
|
||||
Util.try(function()
|
||||
local chunk, err = loadfile(Config.options.pkg.cache)
|
||||
if not chunk then
|
||||
error(err)
|
||||
end
|
||||
---@type LazyPkgCache?
|
||||
local ret = chunk()
|
||||
if ret and ret.version == M.VERSION then
|
||||
M.cache = {}
|
||||
for _, pkg in ipairs(ret.pkgs) do
|
||||
if type(pkg.spec) == "function" then
|
||||
pkg.spec = pkg.spec()
|
||||
end
|
||||
-- wrap in the scope of the plugin
|
||||
pkg.spec = { pkg.name, specs = pkg.spec }
|
||||
end
|
||||
M.cache = ret.pkgs
|
||||
end
|
||||
end, "Error loading pkg:")
|
||||
end
|
||||
if rawget(M, "cache") then
|
||||
M.dirty = false
|
||||
else
|
||||
M.cache = {}
|
||||
M.dirty = true
|
||||
end
|
||||
Util.track()
|
||||
end
|
||||
|
||||
---@param dir string
|
||||
---@return LazyPkg?
|
||||
---@overload fun():LazyPkg[]
|
||||
function M.get(dir)
|
||||
if dir then
|
||||
for _, pkg in ipairs(M.cache) do
|
||||
if pkg.dir == dir then
|
||||
return pkg
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
return M.cache
|
||||
end
|
||||
|
||||
return setmetatable(M, {
|
||||
__index = function(_, key)
|
||||
if key == "cache" then
|
||||
_load()
|
||||
return M.cache
|
||||
end
|
||||
end,
|
||||
})
|
29
lua/lazy/pkg/lazy.lua
Normal file
29
lua/lazy/pkg/lazy.lua
Normal file
|
@ -0,0 +1,29 @@
|
|||
local Util = require("lazy.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
M.lazy_file = "lazy.lua"
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
---@return LazyPkg?
|
||||
function M.get(plugin)
|
||||
local file = Util.norm(plugin.dir .. "/" .. M.lazy_file)
|
||||
if Util.file_exists(file) then
|
||||
---@type fun(): LazySpec
|
||||
local chunk = Util.try(function()
|
||||
local ret, err = loadfile(file)
|
||||
return err and error(err) or ret
|
||||
end, "`" .. M.lazy_file .. "` for **" .. plugin.name .. "** has errors:")
|
||||
if not chunk then
|
||||
Util.error("Invalid `" .. M.lazy_file .. "` for **" .. plugin.name .. "**")
|
||||
return
|
||||
end
|
||||
return {
|
||||
source = "lazy",
|
||||
file = M.lazy_file,
|
||||
code = "function()\n" .. Util.read_file(file) .. "\nend",
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
54
lua/lazy/pkg/packspec.lua
Normal file
54
lua/lazy/pkg/packspec.lua
Normal file
|
@ -0,0 +1,54 @@
|
|||
local Util = require("lazy.util")
|
||||
|
||||
---@class PackSpec
|
||||
---@field dependencies? table<string, string>
|
||||
---@field lazy? LazyPluginSpec
|
||||
---
|
||||
local M = {}
|
||||
|
||||
M.pkg_file = "pkg.json"
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
---@return LazyPkg?
|
||||
function M.get(plugin)
|
||||
local file = Util.norm(plugin.dir .. "/" .. M.pkg_file)
|
||||
if not Util.file_exists(file) then
|
||||
return
|
||||
end
|
||||
---@type PackSpec
|
||||
local pkg = Util.try(function()
|
||||
return vim.json.decode(Util.read_file(file))
|
||||
end, "`" .. M.pkg_file .. "` for **" .. plugin.name .. "** has errors:")
|
||||
|
||||
if not pkg then
|
||||
return
|
||||
end
|
||||
|
||||
---@type LazySpec
|
||||
local ret = {}
|
||||
if pkg.dependencies then
|
||||
for url, version in pairs(pkg.dependencies) do
|
||||
-- HACK: Add `.git` to github urls
|
||||
if url:find("github") and not url:match("%.git$") then
|
||||
url = url .. ".git"
|
||||
end
|
||||
ret[#ret + 1] = { url = url, version = version }
|
||||
end
|
||||
end
|
||||
local p = pkg.lazy
|
||||
if p then
|
||||
p.url = p.url or plugin.url
|
||||
p.dir = p.dir or plugin.dir
|
||||
ret[#ret + 1] = p
|
||||
end
|
||||
if pkg.lazy then
|
||||
ret[#ret + 1] = pkg.lazy
|
||||
end
|
||||
return {
|
||||
source = "lazy",
|
||||
file = M.pkg_file,
|
||||
spec = ret,
|
||||
}
|
||||
end
|
||||
|
||||
return M
|
343
lua/lazy/pkg/rockspec.lua
Normal file
343
lua/lazy/pkg/rockspec.lua
Normal file
|
@ -0,0 +1,343 @@
|
|||
--# selene:allow(incorrect_standard_library_use)
|
||||
local Community = require("lazy.community")
|
||||
|
||||
local Config = require("lazy.core.config")
|
||||
local Health = require("lazy.health")
|
||||
local Util = require("lazy.util")
|
||||
|
||||
---@class RockSpec
|
||||
---@field rockspec_format string
|
||||
---@field package string
|
||||
---@field version string
|
||||
---@field dependencies string[]
|
||||
---@field build? {type?: string, modules?: any[]}
|
||||
---@field source? {url?: string}
|
||||
|
||||
---@class RockManifest
|
||||
---@field repository table<string, table<string,any>>
|
||||
|
||||
local M = {}
|
||||
|
||||
M.skip = { "lua" }
|
||||
M.rewrites = {
|
||||
["plenary.nvim"] = { "nvim-lua/plenary.nvim", lazy = true },
|
||||
}
|
||||
|
||||
M.python = { "python3", "python" }
|
||||
|
||||
---@class HereRocks
|
||||
M.hererocks = {}
|
||||
|
||||
---@param task LazyTask
|
||||
function M.hererocks.build(task)
|
||||
local root = Config.options.rocks.root .. "/hererocks"
|
||||
|
||||
---@param p string
|
||||
local python = vim.tbl_filter(function(p)
|
||||
return vim.fn.executable(p) == 1
|
||||
end, M.python)[1]
|
||||
|
||||
task:spawn(python, {
|
||||
args = {
|
||||
"hererocks.py",
|
||||
"--verbose",
|
||||
"-l",
|
||||
"5.1",
|
||||
"-r",
|
||||
"latest",
|
||||
root,
|
||||
},
|
||||
cwd = task.plugin.dir,
|
||||
})
|
||||
end
|
||||
|
||||
---@param bin string
|
||||
function M.hererocks.bin(bin)
|
||||
local hererocks = Config.options.rocks.root .. "/hererocks/bin"
|
||||
return Util.norm(hererocks .. "/" .. bin)
|
||||
end
|
||||
|
||||
-- check if hererocks is building
|
||||
---@return boolean?
|
||||
function M.hererocks.building()
|
||||
return vim.tbl_get(Config.plugins.hererocks or {}, "_", "build")
|
||||
end
|
||||
|
||||
---@param opts? LazyHealth
|
||||
function M.check(opts)
|
||||
opts = vim.tbl_extend("force", {
|
||||
error = Util.error,
|
||||
warn = Util.warn,
|
||||
ok = function() end,
|
||||
}, opts or {})
|
||||
|
||||
local ok = false
|
||||
if Config.hererocks() then
|
||||
if M.hererocks.building() then
|
||||
ok = true
|
||||
else
|
||||
ok = Health.have(M.python, opts)
|
||||
ok = Health.have(M.hererocks.bin("luarocks")) and ok
|
||||
Health.have(
|
||||
M.hererocks.bin("lua"),
|
||||
vim.tbl_extend("force", opts, {
|
||||
version = "-v",
|
||||
version_pattern = "5.1",
|
||||
})
|
||||
)
|
||||
end
|
||||
else
|
||||
ok = Health.have("luarocks", opts)
|
||||
Health.have(
|
||||
{ "lua5.1", "lua", "lua-5.1" },
|
||||
vim.tbl_extend("force", opts, {
|
||||
version = "-v",
|
||||
version_pattern = "5.1",
|
||||
})
|
||||
)
|
||||
end
|
||||
return ok
|
||||
end
|
||||
|
||||
---@async
|
||||
---@param task LazyTask
|
||||
function M.build(task)
|
||||
M.check({
|
||||
error = function(msg)
|
||||
task:error(msg:gsub("[{}]", "`"))
|
||||
end,
|
||||
warn = function(msg)
|
||||
task:warn(msg)
|
||||
end,
|
||||
ok = function(msg) end,
|
||||
})
|
||||
|
||||
if task:has_warnings() then
|
||||
task:log({
|
||||
"",
|
||||
"This plugin requires `luarocks`. Try one of the following:",
|
||||
" - fix your `luarocks` installation",
|
||||
Config.hererocks() and " - disable *hererocks* with `opts.rocks.hererocks = false`"
|
||||
or " - enable `hererocks` with `opts.rocks.hererocks = true`",
|
||||
" - disable `luarocks` support completely with `opts.rocks.enabled = false`",
|
||||
})
|
||||
task:warn("\nWill try building anyway, but will likely fail...")
|
||||
|
||||
task:warn("\n" .. string.rep("-", 80) .. "\n")
|
||||
|
||||
task:set_level(vim.log.levels.WARN)
|
||||
end
|
||||
|
||||
if task.plugin.name == "hererocks" then
|
||||
return M.hererocks.build(task)
|
||||
end
|
||||
|
||||
local env = {}
|
||||
local luarocks = "luarocks"
|
||||
if Config.hererocks() then
|
||||
-- hererocks is still building, so skip for now
|
||||
-- a new build will happen in the next round
|
||||
if M.hererocks.building() then
|
||||
return
|
||||
end
|
||||
|
||||
local sep = Util.is_win and ";" or ":"
|
||||
local hererocks = Config.options.rocks.root .. "/hererocks/bin"
|
||||
if Util.is_win then
|
||||
hererocks = hererocks:gsub("/", "\\")
|
||||
end
|
||||
local path = vim.split(vim.env.PATH, sep)
|
||||
table.insert(path, 1, hererocks)
|
||||
env = {
|
||||
PATH = table.concat(path, sep),
|
||||
}
|
||||
if Util.is_win then
|
||||
luarocks = luarocks .. ".bat"
|
||||
end
|
||||
end
|
||||
|
||||
local pkg = task.plugin._.pkg
|
||||
assert(pkg, "missing rockspec pkg for " .. task.plugin.name .. "\nThis shouldn't happen, please report.")
|
||||
|
||||
local rockspec = M.rockspec(task.plugin.dir .. "/" .. pkg.file) or {}
|
||||
assert(
|
||||
rockspec.package,
|
||||
"missing rockspec package name for " .. task.plugin.name .. "\nThis shouldn't happen, please report."
|
||||
)
|
||||
|
||||
local root = Config.options.rocks.root .. "/" .. task.plugin.name
|
||||
local ok = task:spawn(luarocks, {
|
||||
args = {
|
||||
"--tree",
|
||||
root,
|
||||
"--server",
|
||||
Config.options.rocks.server,
|
||||
"--lua-version",
|
||||
"5.1",
|
||||
"install", -- use install so that we can make use of pre-built rocks
|
||||
"--force-fast",
|
||||
"--deps-mode",
|
||||
"one",
|
||||
rockspec.package,
|
||||
},
|
||||
cwd = task.plugin.dir,
|
||||
env = env,
|
||||
})
|
||||
|
||||
if ok then
|
||||
return
|
||||
end
|
||||
|
||||
task:warn("Failed installing " .. rockspec.package .. " with `luarocks`.")
|
||||
task:warn("\n" .. string.rep("-", 80) .. "\n")
|
||||
task:warn("Trying to build from source.")
|
||||
|
||||
-- install failed, so try building from source
|
||||
task:set_level() -- reset level
|
||||
ok = task:spawn(luarocks, {
|
||||
args = {
|
||||
"--tree",
|
||||
root,
|
||||
"--dev",
|
||||
"--lua-version",
|
||||
"5.1",
|
||||
"make",
|
||||
"--force-fast",
|
||||
"--deps-mode",
|
||||
"one",
|
||||
},
|
||||
cwd = task.plugin.dir,
|
||||
env = env,
|
||||
})
|
||||
if not ok then
|
||||
require("lazy.manage.task.fs").clean.run(task, { rocks_only = true })
|
||||
end
|
||||
end
|
||||
|
||||
---@param rockspec RockSpec
|
||||
function M.is_simple_build(rockspec)
|
||||
local type = vim.tbl_get(rockspec, "build", "type")
|
||||
return type == nil or type == "none" or (type == "builtin" and not rockspec.build.modules)
|
||||
end
|
||||
|
||||
---@param file string
|
||||
---@return table?
|
||||
function M.parse(file)
|
||||
local ret = {}
|
||||
local ok = pcall(function()
|
||||
loadfile(file, nil, ret)()
|
||||
end) and ret or nil
|
||||
return ok and ret or nil
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
function M.deps(plugin)
|
||||
local root = Config.options.rocks.root .. "/" .. plugin.name
|
||||
---@type RockManifest?
|
||||
local manifest = M.parse(root .. "/lib/luarocks/rocks-5.1/manifest")
|
||||
return manifest and vim.tbl_keys(manifest.repository or {})
|
||||
end
|
||||
|
||||
---@param file string
|
||||
---@return RockSpec?
|
||||
function M.rockspec(file)
|
||||
return M.parse(file)
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
function M.find_rockspec(plugin)
|
||||
local rockspec_file ---@type string?
|
||||
Util.ls(plugin.dir, function(path, name, t)
|
||||
if t == "file" then
|
||||
for _, suffix in ipairs({ "scm", "git", "dev" }) do
|
||||
suffix = suffix .. "-1.rockspec"
|
||||
if name:sub(-#suffix) == suffix then
|
||||
rockspec_file = path
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
return rockspec_file
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
---@return LazyPkgSpec?
|
||||
function M.get(plugin)
|
||||
if Community.get_spec(plugin.name) then
|
||||
return {
|
||||
file = "community",
|
||||
source = "lazy",
|
||||
spec = Community.get_spec(plugin.name),
|
||||
}
|
||||
end
|
||||
|
||||
local rockspec_file = M.find_rockspec(plugin)
|
||||
local rockspec = rockspec_file and M.rockspec(rockspec_file)
|
||||
if not rockspec then
|
||||
return
|
||||
end
|
||||
|
||||
local has_lua = not not vim.uv.fs_stat(plugin.dir .. "/lua")
|
||||
|
||||
---@type LazyPluginSpec
|
||||
local specs = {}
|
||||
|
||||
---@param dep string
|
||||
local rocks = vim.tbl_filter(function(dep)
|
||||
local name = dep:gsub("%s.*", "")
|
||||
local url = Community.get_url(name)
|
||||
local spec = Community.get_spec(name)
|
||||
|
||||
if spec then
|
||||
-- community spec
|
||||
table.insert(specs, spec)
|
||||
return false
|
||||
elseif url then
|
||||
-- Neovim plugin rock
|
||||
table.insert(specs, { url })
|
||||
return false
|
||||
end
|
||||
return not vim.tbl_contains(M.skip, name)
|
||||
end, rockspec.dependencies or {})
|
||||
|
||||
local use =
|
||||
-- package without a /lua directory
|
||||
not has_lua
|
||||
-- has dependencies that are not skipped,
|
||||
-- not in community specs,
|
||||
-- and don't have a rockspec mapping
|
||||
or #rocks > 0
|
||||
-- has a complex build process
|
||||
or not M.is_simple_build(rockspec)
|
||||
|
||||
if not use then
|
||||
-- community specs only
|
||||
return #specs > 0
|
||||
and {
|
||||
file = vim.fn.fnamemodify(rockspec_file, ":t"),
|
||||
spec = {
|
||||
plugin.name,
|
||||
specs = specs,
|
||||
build = false,
|
||||
},
|
||||
}
|
||||
or nil
|
||||
end
|
||||
|
||||
local lazy = nil
|
||||
if not has_lua then
|
||||
lazy = false
|
||||
end
|
||||
|
||||
return {
|
||||
file = vim.fn.fnamemodify(rockspec_file, ":t"),
|
||||
spec = {
|
||||
plugin.name,
|
||||
build = "rockspec",
|
||||
lazy = lazy,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
return M
|
45
lua/lazy/state.lua
Normal file
45
lua/lazy/state.lua
Normal file
|
@ -0,0 +1,45 @@
|
|||
local Config = require("lazy.core.config")
|
||||
local Util = require("lazy.util")
|
||||
|
||||
---@type LazyState
|
||||
local M = {}
|
||||
|
||||
---@class LazyState
|
||||
local defaults = {
|
||||
checker = {
|
||||
last_check = 0,
|
||||
},
|
||||
}
|
||||
|
||||
---@type LazyState
|
||||
local data = nil
|
||||
|
||||
function M.read()
|
||||
pcall(function()
|
||||
---@diagnostic disable-next-line: cast-local-type
|
||||
data = vim.json.decode(Util.read_file(Config.options.state))
|
||||
end)
|
||||
data = vim.tbl_deep_extend("force", {}, defaults, data or {})
|
||||
end
|
||||
|
||||
function M.write()
|
||||
vim.fn.mkdir(vim.fn.fnamemodify(Config.options.state, ":p:h"), "p")
|
||||
Util.write_file(Config.options.state, vim.json.encode(data))
|
||||
end
|
||||
|
||||
function M.__index(_, key)
|
||||
if not data then
|
||||
M.read()
|
||||
end
|
||||
return data[key]
|
||||
end
|
||||
|
||||
function M.__setindex(_, key, value)
|
||||
if not data then
|
||||
M.read()
|
||||
end
|
||||
---@diagnostic disable-next-line: no-unknown
|
||||
data[key] = value
|
||||
end
|
||||
|
||||
return setmetatable(M, M)
|
84
lua/lazy/stats.lua
Normal file
84
lua/lazy/stats.lua
Normal file
|
@ -0,0 +1,84 @@
|
|||
local ffi = require("ffi")
|
||||
|
||||
local M = {}
|
||||
|
||||
---@class LazyStats
|
||||
M._stats = {
|
||||
-- startuptime in milliseconds till UIEnter
|
||||
startuptime = 0,
|
||||
-- when true, startuptime is the accurate cputime for the Neovim process. (Linux & macOS)
|
||||
-- this is more accurate than `nvim --startuptime`, and as such will be slightly higher
|
||||
-- when false, startuptime is calculated based on a delta with a timestamp when lazy started.
|
||||
real_cputime = false,
|
||||
count = 0, -- total number of plugins
|
||||
loaded = 0, -- number of loaded plugins
|
||||
---@type table<string, number>
|
||||
times = {},
|
||||
}
|
||||
|
||||
---@type ffi.namespace*
|
||||
M.C = nil
|
||||
|
||||
function M.on_ui_enter()
|
||||
M._stats.startuptime = M.track("UIEnter")
|
||||
require("lazy.core.util").track({ start = "startuptime" }, M._stats.startuptime * 1e6)
|
||||
vim.api.nvim_exec_autocmds("User", { pattern = "LazyVimStarted", modeline = false })
|
||||
end
|
||||
|
||||
function M.track(event)
|
||||
local time = M.cputime()
|
||||
M._stats.times[event] = time
|
||||
return time
|
||||
end
|
||||
|
||||
function M.cputime()
|
||||
if M.C == nil then
|
||||
pcall(function()
|
||||
ffi.cdef([[
|
||||
typedef long time_t;
|
||||
typedef int clockid_t;
|
||||
typedef struct timespec {
|
||||
time_t tv_sec; /* seconds */
|
||||
long tv_nsec; /* nanoseconds */
|
||||
} nanotime;
|
||||
int clock_gettime(clockid_t clk_id, struct timespec *tp);
|
||||
]])
|
||||
M.C = ffi.C
|
||||
end)
|
||||
end
|
||||
|
||||
local function real()
|
||||
local pnano = assert(ffi.new("nanotime[?]", 1))
|
||||
local CLOCK_PROCESS_CPUTIME_ID = jit.os == "OSX" and 12 or 2
|
||||
ffi.C.clock_gettime(CLOCK_PROCESS_CPUTIME_ID, pnano)
|
||||
return tonumber(pnano[0].tv_sec) * 1e3 + tonumber(pnano[0].tv_nsec) / 1e6
|
||||
end
|
||||
|
||||
local function fallback()
|
||||
return (vim.uv.hrtime() - require("lazy")._start) / 1e6
|
||||
end
|
||||
|
||||
local ok, ret = pcall(real)
|
||||
if ok then
|
||||
M.cputime = real
|
||||
M._stats.real_cputime = true
|
||||
return ret
|
||||
else
|
||||
M.cputime = fallback
|
||||
return fallback()
|
||||
end
|
||||
end
|
||||
|
||||
function M.stats()
|
||||
M._stats.count = 0
|
||||
M._stats.loaded = 0
|
||||
for _, plugin in pairs(require("lazy.core.config").plugins) do
|
||||
M._stats.count = M._stats.count + 1
|
||||
if plugin._.loaded then
|
||||
M._stats.loaded = M._stats.loaded + 1
|
||||
end
|
||||
end
|
||||
return M._stats
|
||||
end
|
||||
|
||||
return M
|
16
lua/lazy/status.lua
Normal file
16
lua/lazy/status.lua
Normal file
|
@ -0,0 +1,16 @@
|
|||
local Config = require("lazy.core.config")
|
||||
|
||||
local M = {}
|
||||
|
||||
function M.updates()
|
||||
local Checker = require("lazy.manage.checker")
|
||||
local updates = #Checker.updated
|
||||
return updates > 0 and (Config.options.ui.icons.plugin .. "" .. updates)
|
||||
end
|
||||
|
||||
function M.has_updates()
|
||||
local Checker = require("lazy.manage.checker")
|
||||
return #Checker.updated > 0
|
||||
end
|
||||
|
||||
return M
|
71
lua/lazy/terminal.lua
Normal file
71
lua/lazy/terminal.lua
Normal file
|
@ -0,0 +1,71 @@
|
|||
---@class Ansi: table<string, fun(string):string>
|
||||
local M = {}
|
||||
|
||||
M.colors = {
|
||||
reset = "\27[0m",
|
||||
black = "\27[30m",
|
||||
red = "\27[31m",
|
||||
green = "\27[32m",
|
||||
yellow = "\27[33m",
|
||||
blue = "\27[34m",
|
||||
magenta = "\27[35m",
|
||||
cyan = "\27[36m",
|
||||
white = "\27[37m",
|
||||
bright_black = "\27[90m",
|
||||
bright_red = "\27[91m",
|
||||
bright_green = "\27[92m",
|
||||
bright_yellow = "\27[93m",
|
||||
bright_blue = "\27[94m",
|
||||
bright_magenta = "\27[95m",
|
||||
bright_cyan = "\27[96m",
|
||||
bright_white = "\27[97m",
|
||||
}
|
||||
|
||||
function M.color(text, color)
|
||||
return M.colors[color] .. text .. M.colors.reset
|
||||
end
|
||||
|
||||
-- stylua: ignore start
|
||||
function M.black(text) return M.color(text, "black") end
|
||||
function M.red(text) return M.color(text, "red") end
|
||||
function M.green(text) return M.color(text, "green") end
|
||||
function M.yellow(text) return M.color(text, "yellow") end
|
||||
function M.blue(text) return M.color(text, "blue") end
|
||||
function M.magenta(text) return M.color(text, "magenta") end
|
||||
function M.cyan(text) return M.color(text, "cyan") end
|
||||
function M.white(text) return M.color(text, "white") end
|
||||
function M.bright_black(text) return M.color(text, "bright_black") end
|
||||
function M.bright_red(text) return M.color(text, "bright_red") end
|
||||
function M.bright_green(text) return M.color(text, "bright_green") end
|
||||
function M.bright_yellow(text) return M.color(text, "bright_yellow") end
|
||||
function M.bright_blue(text) return M.color(text, "bright_blue") end
|
||||
function M.bright_magenta(text) return M.color(text, "bright_magenta") end
|
||||
function M.bright_cyan(text) return M.color(text, "bright_cyan") end
|
||||
function M.bright_white(text) return M.color(text, "bright_white") end
|
||||
-- stylua: ignore end
|
||||
|
||||
---@param data string
|
||||
---@param prefix string
|
||||
function M.prefix(data, prefix)
|
||||
-- Normalize Windows-style newlines to simple newlines
|
||||
data = data:gsub("\r\n", "\n")
|
||||
|
||||
-- Handle prefix for the first line, if data starts immediately
|
||||
data = prefix .. data
|
||||
|
||||
-- Prefix new lines ensuring not to double prefix if a line starts with \r
|
||||
data = data:gsub("(\n)([^\r])", "%1" .. prefix .. "%2")
|
||||
|
||||
-- Handle carriage returns properly to avoid double prefixing
|
||||
-- Replace any \r not followed by \n with \r, then add a prefix only if the following character isn't the start of our prefix
|
||||
data = data:gsub("\r([^\n])", function(nextChar)
|
||||
if nextChar:sub(1, #prefix) == prefix then
|
||||
return "\r" .. nextChar
|
||||
else
|
||||
return "\r" .. prefix .. nextChar
|
||||
end
|
||||
end)
|
||||
return data
|
||||
end
|
||||
|
||||
return M
|
102
lua/lazy/types.lua
Normal file
102
lua/lazy/types.lua
Normal file
|
@ -0,0 +1,102 @@
|
|||
|
||||
---@alias LazyPluginKind "normal"|"clean"|"disabled"
|
||||
|
||||
---@class LazyPluginState
|
||||
---@field cache? table<string,any>
|
||||
---@field cloned? boolean
|
||||
---@field cond? boolean
|
||||
---@field dep? boolean True if this plugin is only in the spec as a dependency
|
||||
---@field dir? string Explicit dir or dev set for this plugin
|
||||
---@field dirty? boolean
|
||||
---@field build? boolean
|
||||
---@field frags? number[]
|
||||
---@field top? boolean
|
||||
---@field handlers? LazyPluginHandlers
|
||||
---@field installed? boolean
|
||||
---@field is_local? boolean
|
||||
---@field kind? LazyPluginKind
|
||||
---@field loaded? {[string]:string}|{time:number}
|
||||
---@field outdated? boolean
|
||||
---@field rtp_loaded? boolean
|
||||
---@field tasks? LazyTask[]
|
||||
---@field updated? {from:string, to:string}
|
||||
---@field updates? {from:GitInfo, to:GitInfo}
|
||||
---@field last_check? number
|
||||
---@field working? boolean
|
||||
---@field pkg? LazyPkg
|
||||
|
||||
---@alias PluginOpts table|fun(self:LazyPlugin, opts:table):table?
|
||||
|
||||
---@class LazyPluginHooks
|
||||
---@field init? fun(self:LazyPlugin) Will always be run
|
||||
---@field deactivate? fun(self:LazyPlugin) Unload/Stop a plugin
|
||||
---@field config? fun(self:LazyPlugin, opts:table)|true Will be executed when loading the plugin
|
||||
---@field build? false|string|async fun(self:LazyPlugin)|(string|async fun(self:LazyPlugin))[]
|
||||
---@field opts? PluginOpts
|
||||
|
||||
---@class LazyPluginHandlers
|
||||
---@field event? table<string,LazyEvent>
|
||||
---@field ft? table<string,LazyEvent>
|
||||
---@field keys? table<string,LazyKeys>
|
||||
---@field cmd? table<string,string>
|
||||
|
||||
---@class LazyPluginRef
|
||||
---@field branch? string
|
||||
---@field tag? string
|
||||
---@field commit? string
|
||||
---@field version? string|boolean
|
||||
---@field pin? boolean
|
||||
---@field submodules? boolean Defaults to true
|
||||
|
||||
---@class LazyPluginBase
|
||||
---@field [1] string?
|
||||
---@field name string display name and name used for plugin config files
|
||||
---@field main? string Entry module that has setup & deactivate
|
||||
---@field url string?
|
||||
---@field dir string
|
||||
---@field enabled? boolean|(fun():boolean)
|
||||
---@field cond? boolean|(fun():boolean)
|
||||
---@field optional? boolean If set, then this plugin will not be added unless it is added somewhere else
|
||||
---@field lazy? boolean
|
||||
---@field priority? number Only useful for lazy=false plugins to force loading certain plugins first. Default priority is 50
|
||||
---@field dev? boolean If set, then link to the respective folder under your ~/projects
|
||||
---@field rocks? string[]
|
||||
---@field virtual? boolean virtual plugins won't be installed or added to the rtp.
|
||||
|
||||
---@class LazyPlugin: LazyPluginBase,LazyPluginHandlers,LazyPluginHooks,LazyPluginRef
|
||||
---@field dependencies? string[]
|
||||
---@field specs? string|string[]|LazyPluginSpec[]
|
||||
---@field _ LazyPluginState
|
||||
|
||||
---@class LazyPluginSpecHandlers
|
||||
---@field event? string[]|string|LazyEventSpec[]|fun(self:LazyPlugin, event:string[]):string[]
|
||||
---@field cmd? string[]|string|fun(self:LazyPlugin, cmd:string[]):string[]
|
||||
---@field ft? string[]|string|fun(self:LazyPlugin, ft:string[]):string[]
|
||||
---@field keys? string|string[]|LazyKeysSpec[]|fun(self:LazyPlugin, keys:string[]):((string|LazyKeys)[])
|
||||
---@field module? false
|
||||
|
||||
---@class LazyPluginSpec: LazyPluginBase,LazyPluginSpecHandlers,LazyPluginHooks,LazyPluginRef
|
||||
---@field name? string display name and name used for plugin config files
|
||||
---@field dir? string
|
||||
---@field dependencies? string|string[]|LazyPluginSpec[]
|
||||
---@field specs? string|string[]|LazyPluginSpec[]
|
||||
|
||||
---@alias LazySpec string|LazyPluginSpec|LazySpecImport|LazySpec[]
|
||||
|
||||
---@class LazySpecImport
|
||||
---@field import string|(fun():LazyPluginSpec) spec module to import
|
||||
---@field name? string
|
||||
---@field enabled? boolean|(fun():boolean)
|
||||
---@field cond? boolean|(fun():boolean)
|
||||
|
||||
---@class LazyFragment
|
||||
---@field id number
|
||||
---@field pkg? boolean
|
||||
---@field pid? number
|
||||
---@field deps? number[]
|
||||
---@field frags? number[]
|
||||
---@field dep? boolean
|
||||
---@field name string
|
||||
---@field url? string
|
||||
---@field dir? string
|
||||
---@field spec LazyPlugin
|
|
@ -1,24 +1,50 @@
|
|||
---@class LazyUtil: LazyUtilCore
|
||||
local M = setmetatable({}, { __index = require("lazy.core.util") })
|
||||
|
||||
function M.file_exists(file)
|
||||
return vim.loop.fs_stat(file) ~= nil
|
||||
return vim.uv.fs_stat(file) ~= nil
|
||||
end
|
||||
|
||||
function M.open(uri)
|
||||
if M.file_exists(uri) then
|
||||
return vim.cmd.view(uri)
|
||||
---@param opts? LazyFloatOptions
|
||||
---@return LazyFloat
|
||||
function M.float(opts)
|
||||
return require("lazy.view.float")(opts)
|
||||
end
|
||||
|
||||
function M.wo(win, k, v)
|
||||
if vim.api.nvim_set_option_value then
|
||||
vim.api.nvim_set_option_value(k, v, { scope = "local", win = win })
|
||||
else
|
||||
vim.wo[win][k] = v
|
||||
end
|
||||
end
|
||||
|
||||
---@param opts? {system?:boolean}
|
||||
function M.open(uri, opts)
|
||||
opts = opts or {}
|
||||
if not opts.system and M.file_exists(uri) then
|
||||
return M.float({ style = "", file = uri })
|
||||
end
|
||||
local Config = require("lazy.core.config")
|
||||
local cmd
|
||||
if vim.fn.has("win32") == 1 then
|
||||
cmd = { "cmd.exe", "/c", "start", '""', vim.fn.shellescape(uri) }
|
||||
if not opts.system and Config.options.ui.browser then
|
||||
cmd = { Config.options.ui.browser, uri }
|
||||
elseif vim.fn.has("win32") == 1 then
|
||||
cmd = { "explorer", uri }
|
||||
elseif vim.fn.has("macunix") == 1 then
|
||||
cmd = { "open", uri }
|
||||
else
|
||||
cmd = { "xdg-open", uri }
|
||||
if vim.fn.executable("xdg-open") == 1 then
|
||||
cmd = { "xdg-open", uri }
|
||||
elseif vim.fn.executable("wslview") == 1 then
|
||||
cmd = { "wslview", uri }
|
||||
else
|
||||
cmd = { "open", uri }
|
||||
end
|
||||
end
|
||||
|
||||
local ret = vim.fn.system(cmd)
|
||||
if vim.v.shell_error ~= 0 then
|
||||
local ret = vim.fn.jobstart(cmd, { detach = true })
|
||||
if ret <= 0 then
|
||||
local msg = {
|
||||
"Failed to open uri",
|
||||
ret,
|
||||
|
@ -28,30 +54,139 @@ function M.open(uri)
|
|||
end
|
||||
end
|
||||
|
||||
function M.read_file(file)
|
||||
local fd = assert(io.open(file, "r"))
|
||||
---@type string
|
||||
local data = fd:read("*a")
|
||||
fd:close()
|
||||
return data
|
||||
end
|
||||
|
||||
function M.write_file(file, contents)
|
||||
local fd = assert(io.open(file, "w+"))
|
||||
fd:write(contents)
|
||||
fd:close()
|
||||
end
|
||||
|
||||
---@generic F: fun()
|
||||
---@param ms number
|
||||
---@param fn fun()
|
||||
---@param fn F
|
||||
---@return F
|
||||
function M.throttle(ms, fn)
|
||||
local timer = vim.loop.new_timer()
|
||||
local running = false
|
||||
local first = true
|
||||
---@type Async
|
||||
local async
|
||||
local pending = false
|
||||
|
||||
return function()
|
||||
if not running then
|
||||
if first then
|
||||
fn()
|
||||
first = false
|
||||
end
|
||||
|
||||
timer:start(ms, 0, function()
|
||||
running = false
|
||||
vim.schedule(fn)
|
||||
end)
|
||||
|
||||
running = true
|
||||
if async and async:running() then
|
||||
pending = true
|
||||
return
|
||||
end
|
||||
---@async
|
||||
async = require("lazy.async").new(function()
|
||||
repeat
|
||||
pending = false
|
||||
fn()
|
||||
async:sleep(ms)
|
||||
|
||||
until not pending
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
--- Creates a weak reference to an object.
|
||||
--- Calling the returned function will return the object if it has not been garbage collected.
|
||||
---@generic T: table
|
||||
---@param obj T
|
||||
---@return T|fun():T?
|
||||
function M.weak(obj)
|
||||
local weak = { _obj = obj }
|
||||
---@return table<any, any>
|
||||
local function get()
|
||||
local ret = rawget(weak, "_obj")
|
||||
return ret == nil and error("Object has been garbage collected", 2) or ret
|
||||
end
|
||||
local mt = {
|
||||
__mode = "v",
|
||||
__call = function(t)
|
||||
return rawget(t, "_obj")
|
||||
end,
|
||||
__index = function(_, k)
|
||||
return get()[k]
|
||||
end,
|
||||
__newindex = function(_, k, v)
|
||||
get()[k] = v
|
||||
end,
|
||||
__pairs = function()
|
||||
return pairs(get())
|
||||
end,
|
||||
}
|
||||
return setmetatable(weak, mt)
|
||||
end
|
||||
|
||||
---@class LazyCmdOptions: LazyFloatOptions
|
||||
---@field cwd? string
|
||||
---@field env? table<string,string>
|
||||
---@field float? LazyFloatOptions
|
||||
|
||||
-- Opens a floating terminal (interactive by default)
|
||||
---@param cmd? string[]|string
|
||||
---@param opts? LazyCmdOptions|{interactive?:boolean}
|
||||
function M.float_term(cmd, opts)
|
||||
cmd = cmd or {}
|
||||
if type(cmd) == "string" then
|
||||
cmd = { cmd }
|
||||
end
|
||||
if #cmd == 0 then
|
||||
cmd = { vim.o.shell }
|
||||
end
|
||||
opts = opts or {}
|
||||
local float = M.float(opts)
|
||||
vim.fn.termopen(cmd, vim.tbl_isempty(opts) and vim.empty_dict() or opts)
|
||||
if opts.interactive ~= false then
|
||||
vim.cmd.startinsert()
|
||||
vim.api.nvim_create_autocmd("TermClose", {
|
||||
once = true,
|
||||
buffer = float.buf,
|
||||
callback = function()
|
||||
float:close({ wipe = true })
|
||||
vim.cmd.checktime()
|
||||
end,
|
||||
})
|
||||
end
|
||||
return float
|
||||
end
|
||||
|
||||
--- Runs the command and shows it in a floating window
|
||||
---@param cmd string[]
|
||||
---@param opts? LazyCmdOptions|{filetype?:string}
|
||||
function M.float_cmd(cmd, opts)
|
||||
opts = opts or {}
|
||||
local Process = require("lazy.manage.process")
|
||||
local lines, code = Process.exec(cmd, { cwd = opts.cwd })
|
||||
if code ~= 0 then
|
||||
M.error({
|
||||
"`" .. table.concat(cmd, " ") .. "`",
|
||||
"",
|
||||
"## Error",
|
||||
table.concat(lines, "\n"),
|
||||
}, { title = "Command Failed (" .. code .. ")" })
|
||||
return
|
||||
end
|
||||
local float = M.float(opts)
|
||||
if opts.filetype then
|
||||
vim.bo[float.buf].filetype = opts.filetype
|
||||
end
|
||||
vim.api.nvim_buf_set_lines(float.buf, 0, -1, false, lines)
|
||||
vim.bo[float.buf].modifiable = false
|
||||
return float
|
||||
end
|
||||
|
||||
---@deprecated use float_term or float_cmd instead
|
||||
function M.open_cmd()
|
||||
M.warn([[`require("lazy.util").open_cmd()` is deprecated. Please use `float_term` instead. Check the docs]])
|
||||
end
|
||||
|
||||
---@return string?
|
||||
function M.head(file)
|
||||
local f = io.open(file)
|
||||
|
@ -91,9 +226,9 @@ function M.markdown(msg, opts)
|
|||
vim.tbl_deep_extend("force", {
|
||||
title = "lazy.nvim",
|
||||
on_open = function(win)
|
||||
vim.wo[win].conceallevel = 3
|
||||
vim.wo[win].concealcursor = "n"
|
||||
vim.wo[win].spell = false
|
||||
M.wo(win, "conceallevel", 3)
|
||||
M.wo(win, "concealcursor", "n")
|
||||
M.wo(win, "spell", false)
|
||||
|
||||
vim.treesitter.start(vim.api.nvim_win_get_buf(win), "markdown")
|
||||
end,
|
||||
|
@ -107,20 +242,25 @@ function M._dump(value, result)
|
|||
table.insert(result, tostring(value))
|
||||
elseif t == "string" then
|
||||
table.insert(result, ("%q"):format(value))
|
||||
elseif t == "table" and value._raw then
|
||||
table.insert(result, value._raw)
|
||||
elseif t == "table" then
|
||||
table.insert(result, "{")
|
||||
local i = 1
|
||||
---@diagnostic disable-next-line: no-unknown
|
||||
for k, v in pairs(value) do
|
||||
if k == i then
|
||||
elseif type(k) == "string" then
|
||||
table.insert(result, ("[%q]="):format(k))
|
||||
else
|
||||
table.insert(result, k .. "=")
|
||||
end
|
||||
for _, v in ipairs(value) do
|
||||
M._dump(v, result)
|
||||
table.insert(result, ",")
|
||||
i = i + 1
|
||||
end
|
||||
---@diagnostic disable-next-line: no-unknown
|
||||
for k, v in pairs(value) do
|
||||
if type(k) == "string" then
|
||||
if k:match("^[a-zA-Z]+$") then
|
||||
table.insert(result, ("%s="):format(k))
|
||||
else
|
||||
table.insert(result, ("[%q]="):format(k))
|
||||
end
|
||||
M._dump(v, result)
|
||||
table.insert(result, ",")
|
||||
end
|
||||
end
|
||||
table.insert(result, "}")
|
||||
else
|
||||
|
@ -134,4 +274,22 @@ function M.dump(value)
|
|||
return table.concat(result, "")
|
||||
end
|
||||
|
||||
---@generic V
|
||||
---@param t table<string, V>
|
||||
---@param fn fun(key:string, value:V)
|
||||
---@param opts? {case_sensitive?:boolean}
|
||||
function M.foreach(t, fn, opts)
|
||||
---@type string[]
|
||||
local keys = vim.tbl_keys(t)
|
||||
pcall(table.sort, keys, function(a, b)
|
||||
if opts and opts.case_sensitive then
|
||||
return a < b
|
||||
end
|
||||
return a:lower() < b:lower()
|
||||
end)
|
||||
for _, key in ipairs(keys) do
|
||||
fn(key, t[key])
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
@ -1,40 +1,51 @@
|
|||
local M = {}
|
||||
|
||||
M.colors = {
|
||||
Error = "ErrorMsg",
|
||||
H1 = "IncSearch",
|
||||
H2 = "Bold",
|
||||
Muted = "Comment",
|
||||
H1 = "IncSearch", -- home button
|
||||
H2 = "Bold", -- titles
|
||||
Comment = "Comment",
|
||||
Normal = "NormalFloat",
|
||||
Commit = "@variable.builtin",
|
||||
Key = "Conceal",
|
||||
Value = "@string",
|
||||
ProgressDone = {
|
||||
bold = true,
|
||||
default = true,
|
||||
fg = "#ff007c",
|
||||
},
|
||||
ProgressTodo = "LineNr",
|
||||
Commit = "@variable.builtin", -- commit ref
|
||||
CommitIssue = "Number",
|
||||
CommitType = "Title", -- conventional commit type
|
||||
CommitScope = "Italic", -- conventional commit scope
|
||||
Dimmed = "Conceal", -- property
|
||||
Prop = "Conceal", -- property
|
||||
Value = "@string", -- value of a property
|
||||
NoCond = "DiagnosticWarn", -- unloaded icon for a plugin where `cond()` was false
|
||||
Local = "Constant",
|
||||
ProgressDone = "Constant", -- progress bar done
|
||||
ProgressTodo = "LineNr", -- progress bar todo
|
||||
Special = "@punctuation.special",
|
||||
LoaderPlugin = "Special",
|
||||
LoaderEvent = "Constant",
|
||||
LoaderKeys = "Statement",
|
||||
LoaderStart = "@field",
|
||||
LoaderSource = "Character",
|
||||
LoaderCmd = "Operator",
|
||||
ReasonRuntime = "@macro",
|
||||
ReasonPlugin = "Special",
|
||||
ReasonEvent = "Constant",
|
||||
ReasonKeys = "Statement",
|
||||
ReasonStart = "@variable.member",
|
||||
ReasonSource = "Character",
|
||||
ReasonFt = "Character",
|
||||
ReasonCmd = "Operator",
|
||||
ReasonImport = "Identifier",
|
||||
ReasonRequire = "@variable.parameter",
|
||||
Button = "CursorLine",
|
||||
ButtonActive = "Visual",
|
||||
TaskOutput = "MsgArea", -- task output
|
||||
Error = "DiagnosticError", -- task errors
|
||||
Warning = "DiagnosticWarn", -- task errors
|
||||
Info = "DiagnosticInfo", -- task errors
|
||||
Dir = "@markup.link", -- directory
|
||||
Url = "@markup.link", -- url
|
||||
Bold = { bold = true },
|
||||
Italic = { italic = true },
|
||||
}
|
||||
|
||||
M.did_setup = false
|
||||
|
||||
function M.set_hl()
|
||||
for hl_group, opts in pairs(M.colors) do
|
||||
if type(opts) == "string" then
|
||||
opts = { link = opts }
|
||||
end
|
||||
opts.default = true
|
||||
vim.api.nvim_set_hl(0, "Lazy" .. hl_group, opts)
|
||||
for hl_group, link in pairs(M.colors) do
|
||||
local hl = type(link) == "table" and link or { link = link }
|
||||
hl.default = true
|
||||
vim.api.nvim_set_hl(0, "Lazy" .. hl_group, hl)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -46,13 +57,12 @@ function M.setup()
|
|||
M.did_setup = true
|
||||
|
||||
M.set_hl()
|
||||
vim.api.nvim_create_autocmd("ColorScheme", {
|
||||
vim.api.nvim_create_autocmd("VimEnter", {
|
||||
callback = function()
|
||||
M.set_hl()
|
||||
end,
|
||||
})
|
||||
vim.api.nvim_create_autocmd("User", {
|
||||
pattern = "VeryLazy",
|
||||
vim.api.nvim_create_autocmd("ColorScheme", {
|
||||
callback = function()
|
||||
M.set_hl()
|
||||
end,
|
||||
|
|
|
@ -1,89 +1,158 @@
|
|||
local View = require("lazy.view")
|
||||
local require = require("lazy.core.util").lazy_require
|
||||
local Config = require("lazy.core.config")
|
||||
local Manage = require("lazy.manage")
|
||||
local Util = require("lazy.util")
|
||||
local View = require("lazy.view")
|
||||
local ViewConfig = require("lazy.view.config")
|
||||
|
||||
local M = {}
|
||||
|
||||
---@param cmd string
|
||||
---@param plugins? LazyPlugin[]
|
||||
function M.cmd(cmd, plugins)
|
||||
cmd = cmd == "" and "show" or cmd
|
||||
local command = M.commands[cmd]
|
||||
---@param opts? ManagerOpts
|
||||
function M.cmd(cmd, opts)
|
||||
cmd = cmd == "" and "home" or cmd
|
||||
local command = M.commands[cmd] --[[@as fun(opts)]]
|
||||
if command == nil then
|
||||
Util.error("Invalid lazy command '" .. cmd .. "'")
|
||||
elseif
|
||||
ViewConfig.commands[cmd]
|
||||
and ViewConfig.commands[cmd].plugins_required
|
||||
and not (opts and vim.tbl_count(opts.plugins or {}) > 0)
|
||||
then
|
||||
return Util.error("`Lazy " .. cmd .. "` requires at least one plugin")
|
||||
else
|
||||
command(plugins)
|
||||
command(opts)
|
||||
end
|
||||
end
|
||||
|
||||
---@class LazyCommands
|
||||
M.commands = {
|
||||
clean = function(plugins)
|
||||
Manage.clean({ clear = true, interactive = true, mode = "clean", plugins = plugins })
|
||||
end,
|
||||
clear = function()
|
||||
Manage.clear()
|
||||
View.show()
|
||||
end,
|
||||
install = function()
|
||||
Manage.install({ clear = true, interactive = true, mode = "install" })
|
||||
health = function()
|
||||
vim.cmd.checkhealth("lazy")
|
||||
end,
|
||||
log = function(plugins)
|
||||
Manage.log({ clear = true, interactive = true, mode = "log", plugins = plugins })
|
||||
---@param opts ManagerOpts
|
||||
pkg = function(opts)
|
||||
local Pkg = require("lazy.pkg")
|
||||
Pkg.update()
|
||||
require("lazy.manage.reloader").reload({
|
||||
{
|
||||
file = "pkg",
|
||||
what = "changed",
|
||||
},
|
||||
})
|
||||
for _, plugin in pairs(opts and opts.plugins or {}) do
|
||||
local spec = Pkg.get(plugin.dir)
|
||||
Util.info(vim.inspect(spec), { lang = "lua", title = plugin.name })
|
||||
end
|
||||
end,
|
||||
home = function()
|
||||
View.show("home")
|
||||
end,
|
||||
show = function()
|
||||
View.show()
|
||||
View.show("home")
|
||||
end,
|
||||
help = function()
|
||||
View.show("help")
|
||||
end,
|
||||
debug = function()
|
||||
View.show("debug")
|
||||
end,
|
||||
profile = function()
|
||||
View.show("profile")
|
||||
end,
|
||||
sync = function()
|
||||
Manage.clean({ interactive = true, clear = true, wait = true, mode = "sync" })
|
||||
Manage.update({ interactive = true })
|
||||
Manage.install({ interactive = true })
|
||||
---@param opts ManagerOpts
|
||||
load = function(opts)
|
||||
-- when a command is executed with a bang, wait will be set
|
||||
require("lazy.core.loader").load(opts.plugins, { cmd = "Lazy load" }, { force = opts.wait })
|
||||
end,
|
||||
update = function(plugins)
|
||||
Manage.update({ clear = true, interactive = true, mode = "update", plugins = plugins })
|
||||
end,
|
||||
check = function(plugins)
|
||||
Manage.check({ clear = true, interactive = true, mode = "check", plugins = plugins })
|
||||
end,
|
||||
restore = function(plugins)
|
||||
Manage.update({ clear = true, interactive = true, lockfile = true, mode = "restore", plugins = plugins })
|
||||
reload = function(opts)
|
||||
for _, plugin in pairs(opts.plugins) do
|
||||
if type(plugin) == "string" then
|
||||
plugin = Config.plugins[plugin]
|
||||
end
|
||||
Util.warn("Reloading **" .. plugin.name .. "**")
|
||||
require("lazy.core.loader").reload(plugin)
|
||||
end
|
||||
end,
|
||||
log = Manage.log,
|
||||
build = Manage.build,
|
||||
clean = Manage.clean,
|
||||
install = Manage.install,
|
||||
sync = Manage.sync,
|
||||
update = Manage.update,
|
||||
check = Manage.check,
|
||||
restore = Manage.restore,
|
||||
}
|
||||
|
||||
function M.complete(cmd, prefix)
|
||||
if not (ViewConfig.commands[cmd] or {}).plugins and cmd ~= "pkg" then
|
||||
return
|
||||
end
|
||||
---@type string[]
|
||||
local plugins = {}
|
||||
if cmd == "load" then
|
||||
plugins[#plugins + 1] = "all"
|
||||
end
|
||||
for name, plugin in pairs(Config.plugins) do
|
||||
if cmd ~= "load" or not plugin._.loaded then
|
||||
plugins[#plugins + 1] = name
|
||||
end
|
||||
end
|
||||
table.sort(plugins)
|
||||
---@param key string
|
||||
return vim.tbl_filter(function(key)
|
||||
return key:find(prefix, 1, true) == 1
|
||||
end, plugins)
|
||||
end
|
||||
|
||||
function M.setup()
|
||||
vim.api.nvim_create_user_command("Lazy", function(args)
|
||||
M.cmd(vim.trim(args.args or ""))
|
||||
vim.api.nvim_create_user_command("Lazy", function(cmd)
|
||||
---@type ManagerOpts
|
||||
local opts = { wait = cmd.bang == true }
|
||||
local prefix, args = M.parse(cmd.args)
|
||||
if #args == 1 and args[1] == "all" then
|
||||
args = vim.tbl_keys(Config.plugins)
|
||||
end
|
||||
if #args > 0 then
|
||||
---@param plugin string
|
||||
opts.plugins = vim.tbl_map(function(plugin)
|
||||
return Config.plugins[plugin]
|
||||
end, args)
|
||||
end
|
||||
M.cmd(prefix, opts)
|
||||
end, {
|
||||
bar = true,
|
||||
bang = true,
|
||||
nargs = "?",
|
||||
desc = "Lazy",
|
||||
complete = function(_, line)
|
||||
if line:match("^%s*Lazy %w+ ") then
|
||||
return {}
|
||||
local prefix, args = M.parse(line)
|
||||
if #args > 0 then
|
||||
return M.complete(prefix, args[#args])
|
||||
end
|
||||
|
||||
local prefix = line:match("^%s*Lazy (%w*)") or ""
|
||||
|
||||
---@param key string
|
||||
return vim.tbl_filter(function(key)
|
||||
return key:find(prefix) == 1
|
||||
return key:find(prefix, 1, true) == 1
|
||||
end, vim.tbl_keys(M.commands))
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
for name in pairs(M.commands) do
|
||||
local cmd = "Lazy" .. name:sub(1, 1):upper() .. name:sub(2)
|
||||
|
||||
vim.api.nvim_create_user_command(cmd, function()
|
||||
M.cmd(name)
|
||||
end, {
|
||||
desc = "Lazy " .. name,
|
||||
})
|
||||
---@return string, string[]
|
||||
function M.parse(args)
|
||||
local parts = vim.split(vim.trim(args), "%s+")
|
||||
if vim.startswith("Lazy", parts[1]) then
|
||||
table.remove(parts, 1)
|
||||
end
|
||||
if args:sub(-1) == " " then
|
||||
parts[#parts + 1] = ""
|
||||
end
|
||||
return table.remove(parts, 1) or "", parts
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
161
lua/lazy/view/config.lua
Normal file
161
lua/lazy/view/config.lua
Normal file
|
@ -0,0 +1,161 @@
|
|||
local M = {}
|
||||
|
||||
---@class LazyViewCommand
|
||||
---@field id number
|
||||
---@field plugins? boolean
|
||||
---@field plugins_required? boolean
|
||||
---@field button? boolean
|
||||
---@field desc? string
|
||||
---@field desc_plugin? string
|
||||
---@field key? string
|
||||
---@field key_plugin? string
|
||||
---@field toggle? boolean
|
||||
|
||||
function M.get_commands()
|
||||
---@type (LazyViewCommand|{name:string})[]
|
||||
local ret = {}
|
||||
for k, v in pairs(M.commands) do
|
||||
v.name = k
|
||||
ret[#ret + 1] = v
|
||||
end
|
||||
table.sort(ret, function(a, b)
|
||||
return a.id < b.id
|
||||
end)
|
||||
return ret
|
||||
end
|
||||
|
||||
M.dimmed_commits = { "bot", "build", "ci", "chore", "doc", "style", "test" }
|
||||
|
||||
M.keys = {
|
||||
hover = "K",
|
||||
diff = "d",
|
||||
close = "q",
|
||||
details = "<cr>",
|
||||
profile_sort = "<C-s>",
|
||||
profile_filter = "<C-f>",
|
||||
abort = "<C-c>",
|
||||
next = "]]",
|
||||
prev = "[[",
|
||||
}
|
||||
|
||||
---@type table<string,LazyViewCommand>
|
||||
M.commands = {
|
||||
home = {
|
||||
button = true,
|
||||
desc = "Go back to plugin list",
|
||||
id = 1,
|
||||
key = "H",
|
||||
},
|
||||
install = {
|
||||
button = true,
|
||||
desc = "Install missing plugins",
|
||||
desc_plugin = "Install a plugin",
|
||||
id = 2,
|
||||
key = "I",
|
||||
key_plugin = "i",
|
||||
plugins = true,
|
||||
},
|
||||
update = {
|
||||
button = true,
|
||||
desc = "Update plugins. This will also update the lockfile",
|
||||
desc_plugin = "Update a plugin. This will also update the lockfile",
|
||||
id = 3,
|
||||
key = "U",
|
||||
key_plugin = "u",
|
||||
plugins = true,
|
||||
},
|
||||
sync = {
|
||||
button = true,
|
||||
desc = "Run install, clean and update",
|
||||
desc_plugin = "Run install, clean and update",
|
||||
id = 4,
|
||||
key = "S",
|
||||
plugins = true,
|
||||
},
|
||||
clean = {
|
||||
button = true,
|
||||
desc = "Clean plugins that are no longer needed",
|
||||
desc_plugin = "Delete a plugin. WARNING: this will delete the plugin even if it should be installed!",
|
||||
id = 5,
|
||||
key = "X",
|
||||
key_plugin = "x",
|
||||
plugins = true,
|
||||
},
|
||||
check = {
|
||||
button = true,
|
||||
desc = "Check for updates and show the log (git fetch)",
|
||||
desc_plugin = "Check for updates and show the log (git fetch)",
|
||||
id = 6,
|
||||
key = "C",
|
||||
key_plugin = "c",
|
||||
plugins = true,
|
||||
},
|
||||
log = {
|
||||
button = true,
|
||||
desc = "Show recent updates",
|
||||
desc_plugin = "Show recent updates",
|
||||
id = 7,
|
||||
key = "L",
|
||||
key_plugin = "gl",
|
||||
plugins = true,
|
||||
},
|
||||
restore = {
|
||||
button = true,
|
||||
desc = "Updates all plugins to the state in the lockfile. For a single plugin: restore it to the state in the lockfile or to a given commit under the cursor",
|
||||
desc_plugin = "Restore a plugin to the state in the lockfile or to a given commit under the cursor",
|
||||
id = 8,
|
||||
key = "R",
|
||||
key_plugin = "r",
|
||||
plugins = true,
|
||||
},
|
||||
profile = {
|
||||
button = true,
|
||||
desc = "Show detailed profiling",
|
||||
id = 9,
|
||||
key = "P",
|
||||
toggle = true,
|
||||
},
|
||||
debug = {
|
||||
button = true,
|
||||
desc = "Show debug information",
|
||||
id = 10,
|
||||
key = "D",
|
||||
toggle = true,
|
||||
},
|
||||
help = {
|
||||
button = true,
|
||||
desc = "Toggle this help page",
|
||||
id = 11,
|
||||
key = "?",
|
||||
toggle = true,
|
||||
},
|
||||
clear = {
|
||||
desc = "Clear finished tasks",
|
||||
id = 12,
|
||||
},
|
||||
load = {
|
||||
desc = "Load a plugin that has not been loaded yet. Similar to `:packadd`. Like `:Lazy load foo.nvim`. Use `:Lazy! load` to skip `cond` checks.",
|
||||
id = 13,
|
||||
plugins = true,
|
||||
plugins_required = true,
|
||||
},
|
||||
health = {
|
||||
desc = "Run `:checkhealth lazy`",
|
||||
id = 14,
|
||||
},
|
||||
build = {
|
||||
desc = "Rebuild a plugin",
|
||||
id = 15,
|
||||
plugins = true,
|
||||
plugins_required = true,
|
||||
key_plugin = "gb",
|
||||
},
|
||||
reload = {
|
||||
desc = "Reload a plugin (experimental!!)",
|
||||
plugins = true,
|
||||
plugins_required = true,
|
||||
id = 16,
|
||||
},
|
||||
}
|
||||
|
||||
return M
|
64
lua/lazy/view/diff.lua
Normal file
64
lua/lazy/view/diff.lua
Normal file
|
@ -0,0 +1,64 @@
|
|||
local Util = require("lazy.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
---@alias LazyDiff {commit:string} | {from:string, to:string}
|
||||
---@alias LazyDiffFun fun(plugin:LazyPlugin, diff:LazyDiff)
|
||||
|
||||
M.handlers = {
|
||||
|
||||
---@type LazyDiffFun
|
||||
browser = function(plugin, diff)
|
||||
if plugin.url then
|
||||
local url = plugin.url:gsub("%.git$", "")
|
||||
if diff.commit then
|
||||
Util.open(url .. "/commit/" .. diff.commit)
|
||||
else
|
||||
Util.open(url .. "/compare/" .. diff.from .. ".." .. diff.to)
|
||||
end
|
||||
else
|
||||
Util.error("No url for " .. plugin.name)
|
||||
end
|
||||
end,
|
||||
|
||||
---@type LazyDiffFun
|
||||
["diffview.nvim"] = function(plugin, diff)
|
||||
local args
|
||||
if diff.commit then
|
||||
args = ("-C=%s"):format(plugin.dir) .. " " .. diff.commit .. "^!"
|
||||
else
|
||||
args = ("-C=%s"):format(plugin.dir) .. " " .. diff.from .. ".." .. diff.to
|
||||
end
|
||||
vim.cmd("DiffviewOpen " .. args)
|
||||
end,
|
||||
|
||||
---@type LazyDiffFun
|
||||
git = function(plugin, diff)
|
||||
local cmd = { "git" }
|
||||
if diff.commit then
|
||||
cmd[#cmd + 1] = "show"
|
||||
cmd[#cmd + 1] = diff.commit
|
||||
else
|
||||
cmd[#cmd + 1] = "diff"
|
||||
cmd[#cmd + 1] = diff.from
|
||||
cmd[#cmd + 1] = diff.to
|
||||
end
|
||||
Util.float_cmd(cmd, { cwd = plugin.dir, filetype = "git" })
|
||||
end,
|
||||
|
||||
---@type LazyDiffFun
|
||||
terminal_git = function(plugin, diff)
|
||||
local cmd = { "git" }
|
||||
if diff.commit then
|
||||
cmd[#cmd + 1] = "show"
|
||||
cmd[#cmd + 1] = diff.commit
|
||||
else
|
||||
cmd[#cmd + 1] = "diff"
|
||||
cmd[#cmd + 1] = diff.from
|
||||
cmd[#cmd + 1] = diff.to
|
||||
end
|
||||
Util.float_term(cmd, { cwd = plugin.dir, interactive = false, env = { PAGER = "cat" } })
|
||||
end,
|
||||
}
|
||||
|
||||
return M
|
354
lua/lazy/view/float.lua
Normal file
354
lua/lazy/view/float.lua
Normal file
|
@ -0,0 +1,354 @@
|
|||
local Config = require("lazy.core.config")
|
||||
local Util = require("lazy.util")
|
||||
local ViewConfig = require("lazy.view.config")
|
||||
|
||||
---@class LazyFloatOptions
|
||||
---@field buf? number
|
||||
---@field file? string
|
||||
---@field margin? {top?:number, right?:number, bottom?:number, left?:number}
|
||||
---@field size? {width:number, height:number}
|
||||
---@field zindex? number
|
||||
---@field style? "" | "minimal"
|
||||
---@field border? "none" | "single" | "double" | "rounded" | "solid" | "shadow"
|
||||
---@field title? string
|
||||
---@field title_pos? "center" | "left" | "right"
|
||||
---@field persistent? boolean
|
||||
---@field ft? string
|
||||
---@field noautocmd? boolean
|
||||
---@field backdrop? float
|
||||
|
||||
---@class LazyFloat
|
||||
---@field buf number
|
||||
---@field win number
|
||||
---@field opts LazyFloatOptions
|
||||
---@field win_opts LazyWinOpts
|
||||
---@field backdrop_buf number
|
||||
---@field backdrop_win number
|
||||
---@field id number
|
||||
---@overload fun(opts?:LazyFloatOptions):LazyFloat
|
||||
local M = {}
|
||||
|
||||
setmetatable(M, {
|
||||
__call = function(_, ...)
|
||||
return M.new(...)
|
||||
end,
|
||||
})
|
||||
|
||||
local _id = 0
|
||||
local function next_id()
|
||||
_id = _id + 1
|
||||
return _id
|
||||
end
|
||||
|
||||
---@param opts? LazyFloatOptions
|
||||
function M.new(opts)
|
||||
local self = setmetatable({}, { __index = M })
|
||||
return self:init(opts)
|
||||
end
|
||||
|
||||
---@param opts? LazyFloatOptions
|
||||
function M:init(opts)
|
||||
require("lazy.view.colors").setup()
|
||||
self.id = next_id()
|
||||
self.opts = vim.tbl_deep_extend("force", {
|
||||
size = Config.options.ui.size,
|
||||
style = "minimal",
|
||||
border = Config.options.ui.border or "none",
|
||||
backdrop = Config.options.ui.backdrop or 60,
|
||||
zindex = 50,
|
||||
}, opts or {})
|
||||
|
||||
---@class LazyWinOpts
|
||||
---@field width number
|
||||
---@field height number
|
||||
---@field row number
|
||||
---@field col number
|
||||
self.win_opts = {
|
||||
relative = "editor",
|
||||
style = self.opts.style ~= "" and self.opts.style or nil,
|
||||
border = self.opts.border,
|
||||
zindex = self.opts.zindex,
|
||||
noautocmd = self.opts.noautocmd,
|
||||
title = self.opts.title,
|
||||
title_pos = self.opts.title and self.opts.title_pos or nil,
|
||||
}
|
||||
self:mount()
|
||||
self:on("VimEnter", function()
|
||||
vim.schedule(function()
|
||||
if not self:win_valid() then
|
||||
self:close()
|
||||
end
|
||||
end)
|
||||
end, { buffer = false })
|
||||
return self
|
||||
end
|
||||
|
||||
function M:layout()
|
||||
local function size(max, value)
|
||||
return value > 1 and math.min(value, max) or math.floor(max * value)
|
||||
end
|
||||
self.win_opts.width = size(vim.o.columns, self.opts.size.width)
|
||||
self.win_opts.height = size(vim.o.lines, self.opts.size.height)
|
||||
self.win_opts.row = math.floor((vim.o.lines - self.win_opts.height) / 2)
|
||||
self.win_opts.col = math.floor((vim.o.columns - self.win_opts.width) / 2)
|
||||
|
||||
if self.opts.border ~= "none" then
|
||||
self.win_opts.row = self.win_opts.row - 1
|
||||
self.win_opts.col = self.win_opts.col - 1
|
||||
end
|
||||
|
||||
if self.opts.margin then
|
||||
if self.opts.margin.top then
|
||||
self.win_opts.height = self.win_opts.height - self.opts.margin.top
|
||||
self.win_opts.row = self.win_opts.row + self.opts.margin.top
|
||||
end
|
||||
if self.opts.margin.right then
|
||||
self.win_opts.width = self.win_opts.width - self.opts.margin.right
|
||||
end
|
||||
if self.opts.margin.bottom then
|
||||
self.win_opts.height = self.win_opts.height - self.opts.margin.bottom
|
||||
end
|
||||
if self.opts.margin.left then
|
||||
self.win_opts.width = self.win_opts.width - self.opts.margin.left
|
||||
self.win_opts.col = self.win_opts.col + self.opts.margin.left
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M:mount()
|
||||
if self:buf_valid() then
|
||||
-- keep existing buffer
|
||||
self.buf = self.buf
|
||||
elseif self.opts.file then
|
||||
self.buf = vim.fn.bufadd(self.opts.file)
|
||||
vim.bo[self.buf].readonly = true
|
||||
vim.bo[self.buf].swapfile = false
|
||||
vim.fn.bufload(self.buf)
|
||||
vim.bo[self.buf].modifiable = false
|
||||
elseif self.opts.buf then
|
||||
self.buf = self.opts.buf
|
||||
else
|
||||
self.buf = vim.api.nvim_create_buf(false, true)
|
||||
end
|
||||
|
||||
local normal, has_bg
|
||||
if vim.fn.has("nvim-0.9.0") == 0 then
|
||||
normal = vim.api.nvim_get_hl_by_name("Normal", true)
|
||||
has_bg = normal and normal.background ~= nil
|
||||
else
|
||||
normal = vim.api.nvim_get_hl(0, { name = "Normal" })
|
||||
has_bg = normal and normal.bg ~= nil
|
||||
end
|
||||
|
||||
if has_bg and self.opts.backdrop and self.opts.backdrop < 100 and vim.o.termguicolors then
|
||||
self.backdrop_buf = vim.api.nvim_create_buf(false, true)
|
||||
self.backdrop_win = vim.api.nvim_open_win(self.backdrop_buf, false, {
|
||||
relative = "editor",
|
||||
width = vim.o.columns,
|
||||
height = vim.o.lines,
|
||||
row = 0,
|
||||
col = 0,
|
||||
style = "minimal",
|
||||
focusable = false,
|
||||
zindex = self.opts.zindex - 1,
|
||||
})
|
||||
vim.api.nvim_set_hl(0, "LazyBackdrop", { bg = "#000000", default = true })
|
||||
Util.wo(self.backdrop_win, "winhighlight", "Normal:LazyBackdrop")
|
||||
Util.wo(self.backdrop_win, "winblend", self.opts.backdrop)
|
||||
vim.bo[self.backdrop_buf].buftype = "nofile"
|
||||
vim.bo[self.backdrop_buf].filetype = "lazy_backdrop"
|
||||
end
|
||||
|
||||
self:layout()
|
||||
self.win = vim.api.nvim_open_win(self.buf, true, self.win_opts)
|
||||
self:on("WinClosed", function()
|
||||
self:close()
|
||||
self:augroup(true)
|
||||
end, { win = true })
|
||||
self:focus()
|
||||
self:on_key(ViewConfig.keys.close, self.close, "Close")
|
||||
self:on({ "BufDelete", "BufHidden" }, self.close)
|
||||
|
||||
if vim.bo[self.buf].buftype == "" then
|
||||
vim.bo[self.buf].buftype = "nofile"
|
||||
end
|
||||
if vim.bo[self.buf].filetype == "" then
|
||||
vim.bo[self.buf].filetype = self.opts.ft or "lazy"
|
||||
end
|
||||
|
||||
local function opts()
|
||||
vim.bo[self.buf].bufhidden = self.opts.persistent and "hide" or "wipe"
|
||||
Util.wo(self.win, "conceallevel", 3)
|
||||
Util.wo(self.win, "foldenable", false)
|
||||
Util.wo(self.win, "spell", false)
|
||||
Util.wo(self.win, "wrap", true)
|
||||
Util.wo(self.win, "winhighlight", "Normal:LazyNormal")
|
||||
Util.wo(self.win, "colorcolumn", "")
|
||||
end
|
||||
opts()
|
||||
|
||||
vim.api.nvim_create_autocmd("VimResized", {
|
||||
callback = function()
|
||||
if not (self.win and vim.api.nvim_win_is_valid(self.win)) then
|
||||
return true
|
||||
end
|
||||
self:layout()
|
||||
local config = {}
|
||||
for _, key in ipairs({ "relative", "width", "height", "col", "row" }) do
|
||||
---@diagnostic disable-next-line: no-unknown
|
||||
config[key] = self.win_opts[key]
|
||||
end
|
||||
config.style = self.opts.style ~= "" and self.opts.style or nil
|
||||
vim.api.nvim_win_set_config(self.win, config)
|
||||
|
||||
if self.backdrop_win and vim.api.nvim_win_is_valid(self.backdrop_win) then
|
||||
vim.api.nvim_win_set_config(self.backdrop_win, {
|
||||
width = vim.o.columns,
|
||||
height = vim.o.lines,
|
||||
})
|
||||
end
|
||||
|
||||
opts()
|
||||
vim.api.nvim_exec_autocmds("User", { pattern = "LazyFloatResized", modeline = false })
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
---@param clear? boolean
|
||||
function M:augroup(clear)
|
||||
return vim.api.nvim_create_augroup("trouble.window." .. self.id, { clear = clear == true })
|
||||
end
|
||||
|
||||
---@param events string|string[]
|
||||
---@param fn fun(self:LazyFloat, event:{buf:number}):boolean?
|
||||
---@param opts? vim.api.keyset.create_autocmd | {buffer: false, win?:boolean}
|
||||
function M:on(events, fn, opts)
|
||||
opts = opts or {}
|
||||
if opts.win then
|
||||
opts.pattern = self.win .. ""
|
||||
opts.win = nil
|
||||
elseif opts.buffer == nil then
|
||||
opts.buffer = self.buf
|
||||
elseif opts.buffer == false then
|
||||
opts.buffer = nil
|
||||
end
|
||||
if opts.pattern then
|
||||
opts.buffer = nil
|
||||
end
|
||||
local _self = Util.weak(self)
|
||||
opts.callback = function(e)
|
||||
local this = _self()
|
||||
if not this then
|
||||
-- delete the autocmd
|
||||
return true
|
||||
end
|
||||
return fn(this, e)
|
||||
end
|
||||
opts.group = self:augroup()
|
||||
vim.api.nvim_create_autocmd(events, opts)
|
||||
end
|
||||
|
||||
---@param key string
|
||||
---@param fn fun(self?)
|
||||
---@param desc? string
|
||||
---@param mode? string[]
|
||||
function M:on_key(key, fn, desc, mode)
|
||||
vim.keymap.set(mode or "n", key, function()
|
||||
fn(self)
|
||||
end, {
|
||||
nowait = true,
|
||||
buffer = self.buf,
|
||||
desc = desc,
|
||||
})
|
||||
end
|
||||
|
||||
---@param opts? {wipe:boolean}
|
||||
function M:close(opts)
|
||||
self:augroup(true)
|
||||
local buf = self.buf
|
||||
local win = self.win
|
||||
local wipe = opts and opts.wipe
|
||||
if wipe == nil then
|
||||
wipe = not self.opts.persistent
|
||||
end
|
||||
|
||||
self.win = nil
|
||||
if wipe then
|
||||
self.buf = nil
|
||||
end
|
||||
local backdrop_buf = self.backdrop_buf
|
||||
local backdrop_win = self.backdrop_win
|
||||
self.backdrop_buf = nil
|
||||
self.backdrop_win = nil
|
||||
|
||||
vim.schedule(function()
|
||||
if backdrop_win and vim.api.nvim_win_is_valid(backdrop_win) then
|
||||
vim.api.nvim_win_close(backdrop_win, true)
|
||||
end
|
||||
if backdrop_buf and vim.api.nvim_buf_is_valid(backdrop_buf) then
|
||||
vim.api.nvim_buf_delete(backdrop_buf, { force = true })
|
||||
end
|
||||
if win and vim.api.nvim_win_is_valid(win) then
|
||||
vim.api.nvim_win_close(win, true)
|
||||
end
|
||||
if wipe and buf and vim.api.nvim_buf_is_valid(buf) then
|
||||
vim.diagnostic.reset(Config.ns, buf)
|
||||
vim.api.nvim_buf_delete(buf, { force = true })
|
||||
end
|
||||
vim.cmd.redraw()
|
||||
end)
|
||||
end
|
||||
|
||||
function M:win_valid()
|
||||
return self.win and vim.api.nvim_win_is_valid(self.win)
|
||||
end
|
||||
|
||||
function M:buf_valid()
|
||||
return self.buf and vim.api.nvim_buf_is_valid(self.buf)
|
||||
end
|
||||
|
||||
function M:hide()
|
||||
if self:win_valid() then
|
||||
self:close({ wipe = false })
|
||||
end
|
||||
end
|
||||
|
||||
function M:toggle()
|
||||
if self:win_valid() then
|
||||
self:hide()
|
||||
return false
|
||||
else
|
||||
self:show()
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function M:show()
|
||||
if self:win_valid() then
|
||||
self:focus()
|
||||
elseif self:buf_valid() then
|
||||
self:mount()
|
||||
else
|
||||
error("LazyFloat: buffer closed")
|
||||
end
|
||||
end
|
||||
|
||||
function M:focus()
|
||||
vim.api.nvim_set_current_win(self.win)
|
||||
|
||||
-- it seems that setting the current win doesn't work before VimEnter,
|
||||
-- so do that then
|
||||
if vim.v.vim_did_enter ~= 1 then
|
||||
vim.api.nvim_create_autocmd("VimEnter", {
|
||||
once = true,
|
||||
callback = function()
|
||||
if self.win and vim.api.nvim_win_is_valid(self.win) then
|
||||
pcall(vim.api.nvim_set_current_win, self.win)
|
||||
end
|
||||
return true
|
||||
end,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,219 +1,364 @@
|
|||
local Util = require("lazy.util")
|
||||
local Render = require("lazy.view.render")
|
||||
local Config = require("lazy.core.config")
|
||||
local Diff = require("lazy.view.diff")
|
||||
local Float = require("lazy.view.float")
|
||||
local Git = require("lazy.manage.git")
|
||||
local Render = require("lazy.view.render")
|
||||
local Util = require("lazy.util")
|
||||
local ViewConfig = require("lazy.view.config")
|
||||
|
||||
local M = {}
|
||||
|
||||
M.modes = {
|
||||
{ name = "install", key = "I", desc = "Install missing plugins" },
|
||||
{ name = "update", key = "U", desc = "Update all plugins. This will also update the lockfile" },
|
||||
{ name = "sync", key = "S", desc = "Run install, clean and update" },
|
||||
{ name = "clean", key = "X", desc = "Clean plugins that are no longer needed" },
|
||||
{ name = "check", key = "C", desc = "Check for updates and show the log (git fetch)" },
|
||||
{ name = "log", key = "L", desc = "Show recent updates for all plugins" },
|
||||
{ name = "restore", key = "R", desc = "Updates all plugins to the state in the lockfile" },
|
||||
{ name = "profile", key = "P", desc = "Show detailed profiling", toggle = true },
|
||||
{ name = "help", key = "g?", hide = true, desc = "Toggle this help page", toggle = true },
|
||||
|
||||
{ plugin = true, name = "update", key = "u", desc = "Update this plugin. This will also update the lockfile" },
|
||||
{
|
||||
plugin = true,
|
||||
name = "clean",
|
||||
key = "x",
|
||||
desc = "Delete this plugin. WARNING: this will delete the plugin even if it should be installed!",
|
||||
---@class LazyViewState
|
||||
---@field mode string
|
||||
---@field plugin? {name:string, kind?: LazyPluginKind}
|
||||
local default_state = {
|
||||
mode = "home",
|
||||
profile = {
|
||||
threshold = 0,
|
||||
sort_time_taken = false,
|
||||
},
|
||||
{ plugin = true, name = "check", key = "c", desc = "Check for updates for this plugin and show the log (git fetch)" },
|
||||
{ plugin = true, name = "install", key = "i", desc = "Install this plugin" },
|
||||
{ plugin = true, name = "log", key = "gl", desc = "Show recent updates for this plugin" },
|
||||
{ plugin = true, name = "restore", key = "r", desc = "Restore this plugin to the state in the lockfile" },
|
||||
}
|
||||
|
||||
---@type string?
|
||||
M.mode = nil
|
||||
---@class LazyView: LazyFloat
|
||||
---@field render LazyRender
|
||||
---@field state LazyViewState
|
||||
local M = {}
|
||||
|
||||
function M.setup()
|
||||
require("lazy.view.commands").setup()
|
||||
require("lazy.view.colors").setup()
|
||||
---@type LazyView
|
||||
M.view = nil
|
||||
|
||||
function M.visible()
|
||||
return M.view and M.view.win and vim.api.nvim_win_is_valid(M.view.win)
|
||||
end
|
||||
|
||||
---@param mode? string
|
||||
function M.show(mode)
|
||||
M.mode = mode or M.mode
|
||||
require("lazy.view.colors").setup()
|
||||
|
||||
if M._buf and vim.api.nvim_buf_is_valid(M._buf) then
|
||||
vim.api.nvim_win_set_cursor(M._win, { 1, 0 })
|
||||
vim.cmd([[do User LazyRender]])
|
||||
if Config.headless() then
|
||||
return
|
||||
end
|
||||
|
||||
local buf = vim.api.nvim_create_buf(false, false)
|
||||
M._buf = buf
|
||||
local vpad = 6
|
||||
local hpad = 20
|
||||
local opts = {
|
||||
relative = "editor",
|
||||
style = "minimal",
|
||||
width = math.min(vim.o.columns - hpad * 2, 200),
|
||||
height = math.min(vim.o.lines - vpad * 2, 70),
|
||||
}
|
||||
opts.row = (vim.o.lines - opts.height) / 2
|
||||
opts.col = (vim.o.columns - opts.width) / 2
|
||||
local win = vim.api.nvim_open_win(buf, true, opts)
|
||||
M._win = win
|
||||
M.view = M.visible() and M.view or M.create()
|
||||
if mode then
|
||||
M.view.state.mode = mode
|
||||
end
|
||||
M.view:update()
|
||||
end
|
||||
|
||||
vim.api.nvim_set_current_win(win)
|
||||
---@param plugin LazyPlugin
|
||||
function M:is_selected(plugin)
|
||||
return vim.deep_equal(self.state.plugin, { name = plugin.name, kind = plugin._.kind })
|
||||
end
|
||||
|
||||
vim.bo[buf].buftype = "nofile"
|
||||
vim.bo[buf].bufhidden = "wipe"
|
||||
vim.wo[win].conceallevel = 3
|
||||
vim.wo[win].spell = false
|
||||
vim.wo[win].wrap = true
|
||||
vim.wo[win].winhighlight = "Normal:LazyNormal"
|
||||
function M.create()
|
||||
local self = setmetatable({}, { __index = setmetatable(M, { __index = Float }) })
|
||||
---@cast self LazyView
|
||||
Float.init(self, {
|
||||
title = Config.options.ui.title,
|
||||
title_pos = Config.options.ui.title_pos,
|
||||
noautocmd = false,
|
||||
})
|
||||
|
||||
local function close()
|
||||
M._buf = nil
|
||||
vim.diagnostic.reset(Config.ns, buf)
|
||||
|
||||
if vim.api.nvim_buf_is_valid(buf) then
|
||||
vim.api.nvim_buf_delete(buf, {
|
||||
force = true,
|
||||
})
|
||||
end
|
||||
|
||||
if vim.api.nvim_win_is_valid(win) then
|
||||
vim.api.nvim_win_close(win, true)
|
||||
end
|
||||
if Config.options.ui.wrap then
|
||||
Util.wo(self.win, "wrap", true)
|
||||
Util.wo(self.win, "linebreak", true)
|
||||
Util.wo(self.win, "breakindent", true)
|
||||
else
|
||||
Util.wo(self.win, "wrap", false)
|
||||
end
|
||||
|
||||
vim.keymap.set("n", "q", close, {
|
||||
nowait = true,
|
||||
buffer = buf,
|
||||
})
|
||||
self.state = vim.deepcopy(default_state)
|
||||
|
||||
vim.api.nvim_create_autocmd({ "BufDelete", "BufLeave", "BufHidden" }, {
|
||||
once = true,
|
||||
buffer = buf,
|
||||
callback = close,
|
||||
})
|
||||
|
||||
local render = Render.new(buf, win, 2)
|
||||
local update = Util.throttle(30, function()
|
||||
if buf and vim.api.nvim_buf_is_valid(buf) then
|
||||
vim.bo[buf].modifiable = true
|
||||
render:update()
|
||||
vim.bo[buf].modifiable = false
|
||||
vim.cmd.redraw()
|
||||
end
|
||||
self.render = Render.new(self)
|
||||
local update = self.update
|
||||
self.update = Util.throttle(Config.options.ui.throttle, function()
|
||||
update(self)
|
||||
end)
|
||||
|
||||
local function get_plugin()
|
||||
local pos = vim.api.nvim_win_get_cursor(win)
|
||||
return render:get_plugin(pos[1])
|
||||
for _, pattern in ipairs({ "LazyRender", "LazyFloatResized" }) do
|
||||
self:on({ "User" }, function()
|
||||
if not (self.buf and vim.api.nvim_buf_is_valid(self.buf)) then
|
||||
return true
|
||||
end
|
||||
self:update()
|
||||
end, { pattern = pattern })
|
||||
end
|
||||
|
||||
vim.keymap.set("n", "<cr>", function()
|
||||
local plugin = get_plugin()
|
||||
if plugin then
|
||||
if render._details == plugin.name then
|
||||
render._details = nil
|
||||
else
|
||||
render._details = plugin.name
|
||||
end
|
||||
update()
|
||||
end
|
||||
end, {
|
||||
nowait = true,
|
||||
buffer = buf,
|
||||
})
|
||||
vim.keymap.set("n", ViewConfig.keys.abort, function()
|
||||
require("lazy.manage.process").abort()
|
||||
require("lazy.async").abort()
|
||||
return ViewConfig.keys.abort
|
||||
end, { silent = true, buffer = self.buf, expr = true, desc = "Abort" })
|
||||
|
||||
local function open(path)
|
||||
local plugin = get_plugin()
|
||||
vim.keymap.set("n", "gx", "K", { buffer = self.buf, remap = true })
|
||||
|
||||
-- plugin details
|
||||
self:on_key(ViewConfig.keys.details, function()
|
||||
local plugin = self.render:get_plugin()
|
||||
if plugin then
|
||||
local url = plugin.uri:gsub("%.git$", "")
|
||||
if Util.file_exists(url) then
|
||||
url = "https://github.com/" .. plugin[1]
|
||||
local selected = {
|
||||
name = plugin.name,
|
||||
kind = plugin._.kind,
|
||||
}
|
||||
|
||||
local open = not vim.deep_equal(self.state.plugin, selected)
|
||||
|
||||
if not open then
|
||||
local row = self.render:get_row(selected)
|
||||
if row then
|
||||
vim.api.nvim_win_set_cursor(self.view.win, { row, 8 })
|
||||
end
|
||||
end
|
||||
|
||||
self.state.plugin = open and selected or nil
|
||||
self:update()
|
||||
end
|
||||
end, "Details")
|
||||
|
||||
self:on_key(ViewConfig.keys.next, function()
|
||||
local cursor = vim.api.nvim_win_get_cursor(self.view.win)
|
||||
for l = 1, #self.render.locations, 1 do
|
||||
local loc = self.render.locations[l]
|
||||
if loc.from > cursor[1] then
|
||||
vim.api.nvim_win_set_cursor(self.view.win, { loc.from, 8 })
|
||||
return
|
||||
end
|
||||
end
|
||||
end, "Next Plugin")
|
||||
|
||||
self:on_key(ViewConfig.keys.prev, function()
|
||||
local cursor = vim.api.nvim_win_get_cursor(self.view.win)
|
||||
for l = #self.render.locations, 1, -1 do
|
||||
local loc = self.render.locations[l]
|
||||
if loc.from < cursor[1] then
|
||||
vim.api.nvim_win_set_cursor(self.view.win, { loc.from, 8 })
|
||||
return
|
||||
end
|
||||
end
|
||||
end, "Prev Plugin")
|
||||
|
||||
self:on_key(ViewConfig.keys.profile_sort, function()
|
||||
if self.state.mode == "profile" then
|
||||
self.state.profile.sort_time_taken = not self.state.profile.sort_time_taken
|
||||
self:update()
|
||||
end
|
||||
end, "Sort Profile")
|
||||
|
||||
self:on_key(ViewConfig.keys.profile_filter, function()
|
||||
if self.state.mode == "profile" then
|
||||
vim.ui.input({
|
||||
prompt = "Enter time threshold in ms: ",
|
||||
default = tostring(self.state.profile.threshold),
|
||||
}, function(input)
|
||||
if not input then
|
||||
return
|
||||
end
|
||||
local num = input == "" and 0 or tonumber(input)
|
||||
if not num then
|
||||
Util.error("Please input a number")
|
||||
else
|
||||
self.state.profile.threshold = num
|
||||
self:update()
|
||||
end
|
||||
end)
|
||||
end
|
||||
end, "Filter Profile")
|
||||
|
||||
for lhs, rhs in pairs(Config.options.ui.custom_keys) do
|
||||
if rhs then
|
||||
local handler = type(rhs) == "table" and rhs[1] or rhs
|
||||
local desc = type(rhs) == "table" and rhs.desc or nil
|
||||
self:on_key(lhs, function()
|
||||
local plugin = self.render:get_plugin()
|
||||
if plugin then
|
||||
handler(plugin)
|
||||
end
|
||||
end, desc)
|
||||
end
|
||||
end
|
||||
|
||||
self:setup_patterns()
|
||||
self:setup_modes()
|
||||
return self
|
||||
end
|
||||
|
||||
function M:update()
|
||||
if self.buf and vim.api.nvim_buf_is_valid(self.buf) then
|
||||
self.render:update()
|
||||
vim.cmd.redraw()
|
||||
end
|
||||
end
|
||||
|
||||
function M:open_url(path)
|
||||
local plugin = self.render:get_plugin()
|
||||
if plugin then
|
||||
if plugin.url then
|
||||
local url = plugin.url:gsub("%.git$", "")
|
||||
Util.open(url .. path)
|
||||
else
|
||||
Util.error("No url for " .. plugin.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.keys(buf, {
|
||||
["%s(" .. string.rep("[a-z0-9]", 7) .. ")%s"] = function(hash)
|
||||
open("/commit/" .. hash)
|
||||
end,
|
||||
["%s(" .. string.rep("[a-z0-9]", 7) .. ")$"] = function(hash)
|
||||
open("/commit/" .. hash)
|
||||
end,
|
||||
["^(" .. string.rep("[a-z0-9]", 7) .. ")%s"] = function(hash)
|
||||
open("/commit/" .. hash)
|
||||
function M:setup_patterns()
|
||||
local commit_pattern = "%f[%w](" .. string.rep("[a-f0-9]", 7) .. ")%f[%W]"
|
||||
self:on_pattern(ViewConfig.keys.hover, {
|
||||
[commit_pattern] = function(hash)
|
||||
self:diff({ commit = hash, browser = true })
|
||||
end,
|
||||
["#(%d+)"] = function(issue)
|
||||
open("/issues/" .. issue)
|
||||
self:open_url("/issues/" .. issue)
|
||||
end,
|
||||
["README.md"] = function()
|
||||
local plugin = get_plugin()
|
||||
Util.open(plugin.dir .. "/README.md")
|
||||
local plugin = self.render:get_plugin()
|
||||
if plugin then
|
||||
Util.open(plugin.dir .. "/README.md")
|
||||
end
|
||||
end,
|
||||
["|(%S-)|"] = function(h)
|
||||
vim.cmd.help(h)
|
||||
self:close()
|
||||
end,
|
||||
["(https?://%S+)"] = function(url)
|
||||
Util.open(url)
|
||||
end,
|
||||
})
|
||||
|
||||
for _, m in ipairs(M.modes) do
|
||||
vim.keymap.set("n", m.key, function()
|
||||
local Commands = require("lazy.view.commands")
|
||||
if m.plugin then
|
||||
local plugin = get_plugin()
|
||||
if plugin then
|
||||
Commands.cmd(m.name, { plugin })
|
||||
end
|
||||
else
|
||||
if M.mode == m.name and m.toggle then
|
||||
M.mode = nil
|
||||
return update()
|
||||
end
|
||||
Commands.cmd(m.name)
|
||||
end
|
||||
end, { buffer = buf })
|
||||
end
|
||||
|
||||
vim.api.nvim_create_autocmd("User", {
|
||||
pattern = "LazyRender",
|
||||
callback = function()
|
||||
if not vim.api.nvim_buf_is_valid(buf) then
|
||||
return true
|
||||
end
|
||||
update()
|
||||
}, self.hover, "Hover")
|
||||
self:on_pattern(ViewConfig.keys.diff, {
|
||||
[commit_pattern] = function(hash)
|
||||
self:diff({ commit = hash })
|
||||
end,
|
||||
})
|
||||
update()
|
||||
}, self.diff, "Diff")
|
||||
self:on_pattern(ViewConfig.commands.restore.key_plugin, {
|
||||
[commit_pattern] = function(hash)
|
||||
self:restore({ commit = hash })
|
||||
end,
|
||||
}, self.restore, "Restore")
|
||||
end
|
||||
|
||||
---@param handlers table<string, fun(str:string)>
|
||||
function M.keys(buf, handlers)
|
||||
local function map(lhs)
|
||||
vim.keymap.set("n", lhs, function()
|
||||
local line = vim.api.nvim_get_current_line()
|
||||
local pos = vim.api.nvim_win_get_cursor(0)
|
||||
local col = pos[2] + 1
|
||||
---@param opts? {commit:string}
|
||||
function M:restore(opts)
|
||||
opts = opts or {}
|
||||
local Lockfile = require("lazy.manage.lock")
|
||||
local Commands = require("lazy.view.commands")
|
||||
local plugin = self.render:get_plugin()
|
||||
if plugin then
|
||||
if opts.commit then
|
||||
Lockfile.get(plugin).commit = opts.commit
|
||||
end
|
||||
Commands.cmd("restore", { plugins = { plugin } })
|
||||
end
|
||||
end
|
||||
|
||||
for pattern, handler in pairs(handlers) do
|
||||
local from = 1
|
||||
local to, url
|
||||
while from do
|
||||
from, to, url = line:find(pattern, from)
|
||||
if from and col >= from and col <= to then
|
||||
return handler(url)
|
||||
end
|
||||
if from then
|
||||
from = to + 1
|
||||
end
|
||||
function M:hover()
|
||||
if self:diff({ browser = true, hover = true }) then
|
||||
return
|
||||
end
|
||||
self:open_url("")
|
||||
end
|
||||
|
||||
---@param opts? {commit?:string, browser:boolean, hover:boolean}
|
||||
function M:diff(opts)
|
||||
opts = opts or {}
|
||||
local plugin = self.render:get_plugin()
|
||||
if plugin then
|
||||
local diff
|
||||
if opts.commit then
|
||||
diff = { commit = opts.commit }
|
||||
elseif plugin._.updated then
|
||||
diff = vim.deepcopy(plugin._.updated)
|
||||
else
|
||||
local info = assert(Git.info(plugin.dir))
|
||||
local target = assert(Git.get_target(plugin))
|
||||
diff = { from = info.commit, to = target.commit }
|
||||
if opts.hover and diff.from == diff.to then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if not diff then
|
||||
return
|
||||
end
|
||||
|
||||
for k, v in pairs(diff) do
|
||||
diff[k] = v:sub(1, 7)
|
||||
end
|
||||
|
||||
if opts.browser then
|
||||
Diff.handlers.browser(plugin, diff)
|
||||
else
|
||||
Diff.handlers[Config.options.diff.cmd](plugin, diff)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
--- will create a key mapping that can be used on certain patterns
|
||||
---@param key string
|
||||
---@param patterns table<string, fun(str:string)>
|
||||
---@param fallback? fun(self)
|
||||
---@param desc? string
|
||||
function M:on_pattern(key, patterns, fallback, desc)
|
||||
self:on_key(key, function()
|
||||
local line = vim.api.nvim_get_current_line()
|
||||
local pos = vim.api.nvim_win_get_cursor(0)
|
||||
local col = pos[2] + 1
|
||||
|
||||
for pattern, handler in pairs(patterns) do
|
||||
local from = 1
|
||||
local to, url
|
||||
while from do
|
||||
from, to, url = line:find(pattern, from)
|
||||
if from and col >= from and col <= to then
|
||||
return handler(url)
|
||||
end
|
||||
if from then
|
||||
from = to + 1
|
||||
end
|
||||
end
|
||||
end, { buffer = buf, silent = true })
|
||||
end
|
||||
end
|
||||
if fallback then
|
||||
fallback(self)
|
||||
end
|
||||
end, desc)
|
||||
end
|
||||
|
||||
map("K")
|
||||
function M:setup_modes()
|
||||
local Commands = require("lazy.view.commands")
|
||||
for name, m in pairs(ViewConfig.commands) do
|
||||
if m.key then
|
||||
self:on_key(m.key, function()
|
||||
if self.state.mode == name and m.toggle then
|
||||
self.state.mode = "home"
|
||||
return self:update()
|
||||
end
|
||||
Commands.cmd(name)
|
||||
end, m.desc)
|
||||
end
|
||||
if m.key_plugin and name ~= "restore" then
|
||||
self:on_key(m.key_plugin, function()
|
||||
local esc = vim.api.nvim_replace_termcodes("<esc>", true, true, true)
|
||||
vim.api.nvim_feedkeys(esc, "n", false)
|
||||
local plugins = {}
|
||||
if vim.api.nvim_get_mode().mode:lower() == "v" then
|
||||
local f, t = vim.fn.line("."), vim.fn.line("v")
|
||||
if f > t then
|
||||
f, t = t, f
|
||||
end
|
||||
for i = f, t do
|
||||
local plugin = self.render:get_plugin(i)
|
||||
if plugin then
|
||||
plugins[plugin.name] = plugin
|
||||
end
|
||||
end
|
||||
plugins = vim.tbl_values(plugins)
|
||||
else
|
||||
plugins[1] = self.render:get_plugin()
|
||||
end
|
||||
if #plugins > 0 then
|
||||
Commands.cmd(name, { plugins = plugins })
|
||||
end
|
||||
end, m.desc_plugin, { "n", "x" })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
@ -1,41 +1,43 @@
|
|||
local Config = require("lazy.core.config")
|
||||
local Util = require("lazy.util")
|
||||
local Sections = require("lazy.view.sections")
|
||||
local Handler = require("lazy.core.handler")
|
||||
local Plugin = require("lazy.core.plugin")
|
||||
local Git = require("lazy.manage.git")
|
||||
local Handler = require("lazy.core.handler")
|
||||
local Keys = require("lazy.core.handler.keys")
|
||||
local Plugin = require("lazy.core.plugin")
|
||||
local Sections = require("lazy.view.sections")
|
||||
local Util = require("lazy.util")
|
||||
local ViewConfig = require("lazy.view.config")
|
||||
|
||||
local Text = require("lazy.view.text")
|
||||
|
||||
---@alias LazyDiagnostic {row: number, severity: number, message:string}
|
||||
|
||||
---@class Render:Text
|
||||
---@field buf buffer
|
||||
---@field win window
|
||||
---@class LazyRender:Text
|
||||
---@field view LazyView
|
||||
---@field plugins LazyPlugin[]
|
||||
---@field progress {total:number, done:number}
|
||||
---@field _diagnostics LazyDiagnostic[]
|
||||
---@field plugin_range table<string, {from: number, to: number}>
|
||||
---@field _details? string
|
||||
local M = setmetatable({}, {
|
||||
__index = Text,
|
||||
})
|
||||
---@field locations {name:string, from: number, to: number, kind?: LazyPluginKind}[]
|
||||
local M = {}
|
||||
|
||||
function M.new(buf, win, padding)
|
||||
local self = setmetatable({}, { __index = M })
|
||||
self.buf = buf
|
||||
self.win = win
|
||||
self.padding = padding or 0
|
||||
---@return LazyRender
|
||||
---@param view LazyView
|
||||
function M.new(view)
|
||||
---@type LazyRender
|
||||
local self = setmetatable({}, { __index = setmetatable(M, { __index = Text }) })
|
||||
self.view = view
|
||||
self.padding = 2
|
||||
self.wrap = view.win_opts.width
|
||||
return self
|
||||
end
|
||||
|
||||
function M:update()
|
||||
self._lines = {}
|
||||
self._diagnostics = {}
|
||||
self.plugin_range = {}
|
||||
self.locations = {}
|
||||
|
||||
self.plugins = vim.tbl_values(Config.plugins)
|
||||
vim.list_extend(self.plugins, vim.tbl_values(Config.to_clean))
|
||||
vim.list_extend(self.plugins, vim.tbl_values(Config.spec.disabled))
|
||||
table.sort(self.plugins, function(a, b)
|
||||
return a.name < b.name
|
||||
end)
|
||||
|
@ -49,19 +51,22 @@ function M:update()
|
|||
if plugin._.tasks then
|
||||
for _, task in ipairs(plugin._.tasks) do
|
||||
self.progress.total = self.progress.total + 1
|
||||
if not task:is_running() then
|
||||
if not task:running() then
|
||||
self.progress.done = self.progress.done + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local mode = self:title()
|
||||
self:title()
|
||||
|
||||
local mode = self.view.state.mode
|
||||
if mode == "help" then
|
||||
self:help()
|
||||
elseif mode == "profile" then
|
||||
self:profile()
|
||||
elseif mode == "debug" then
|
||||
self:debug()
|
||||
else
|
||||
for _, section in ipairs(Sections) do
|
||||
self:section(section)
|
||||
|
@ -69,76 +74,179 @@ function M:update()
|
|||
end
|
||||
|
||||
self:trim()
|
||||
self:render(self.buf)
|
||||
|
||||
vim.bo[self.view.buf].modifiable = true
|
||||
local view = vim.api.nvim_win_call(self.view.win, vim.fn.winsaveview)
|
||||
|
||||
self:render(self.view.buf)
|
||||
|
||||
vim.api.nvim_win_call(self.view.win, function()
|
||||
vim.fn.winrestview(view)
|
||||
end)
|
||||
vim.bo[self.view.buf].modifiable = false
|
||||
|
||||
vim.diagnostic.set(
|
||||
Config.ns,
|
||||
self.buf,
|
||||
self.view.buf,
|
||||
---@param diag LazyDiagnostic
|
||||
vim.tbl_map(function(diag)
|
||||
diag.col = 0
|
||||
diag.lnum = diag.row - 1
|
||||
return diag
|
||||
end, self._diagnostics),
|
||||
{ signs = false }
|
||||
{ signs = false, virtual_text = true, underline = false, virtual_lines = false }
|
||||
)
|
||||
end
|
||||
|
||||
---@param row number
|
||||
---@param row? number
|
||||
---@return LazyPlugin?
|
||||
function M:get_plugin(row)
|
||||
for name, range in pairs(self.plugin_range) do
|
||||
if row >= range.from and row <= range.to then
|
||||
return Config.plugins[name]
|
||||
if not (self.view.win and vim.api.nvim_win_is_valid(self.view.win)) then
|
||||
return
|
||||
end
|
||||
row = row or vim.api.nvim_win_get_cursor(self.view.win)[1]
|
||||
for _, loc in ipairs(self.locations) do
|
||||
if row >= loc.from and row <= loc.to then
|
||||
if loc.kind == "clean" then
|
||||
for _, plugin in ipairs(Config.to_clean) do
|
||||
if plugin.name == loc.name then
|
||||
return plugin
|
||||
end
|
||||
end
|
||||
elseif loc.kind == "disabled" then
|
||||
return Config.spec.disabled[loc.name]
|
||||
else
|
||||
return Config.plugins[loc.name]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param selected {name:string, kind?: LazyPluginKind}
|
||||
function M:get_row(selected)
|
||||
for _, loc in ipairs(self.locations) do
|
||||
if loc.kind == selected.kind and loc.name == selected.name then
|
||||
return loc.from
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M:title()
|
||||
self:append(" lazy.nvim ", "LazyH1"):center():nl()
|
||||
self:append("press "):append("g?", "LazySpecial"):append(" for help"):center():nl()
|
||||
self:append("https://github.com/folke/lazy.nvim", "LazyMuted"):center():nl()
|
||||
|
||||
local View = require("lazy.view")
|
||||
for _, mode in ipairs(View.modes) do
|
||||
if not mode.hide and not mode.plugin then
|
||||
local title = " " .. mode.name:sub(1, 1):upper() .. mode.name:sub(2) .. " (" .. mode.key .. ") "
|
||||
self:append(title, View.mode == mode.name and "LazyButtonActive" or "LazyButton"):append(" ")
|
||||
end
|
||||
end
|
||||
self:nl()
|
||||
local modes = vim.tbl_filter(function(c)
|
||||
return c.button
|
||||
end, ViewConfig.get_commands())
|
||||
|
||||
if Config.options.ui.pills then
|
||||
self:nl()
|
||||
for c, mode in ipairs(modes) do
|
||||
local title = " " .. mode.name:sub(1, 1):upper() .. mode.name:sub(2) .. " (" .. mode.key .. ") "
|
||||
if mode.name == "home" then
|
||||
if self.view.state.mode == "home" then
|
||||
title = " lazy.nvim " .. Config.options.ui.icons.lazy
|
||||
end
|
||||
end
|
||||
|
||||
if self.view.state.mode == mode.name then
|
||||
if mode.name == "home" then
|
||||
self:append(title, "LazyH1", { wrap = true })
|
||||
else
|
||||
self:append(title, "LazyButtonActive", { wrap = true })
|
||||
self:highlight({ ["%(.%)"] = "LazySpecial" })
|
||||
end
|
||||
else
|
||||
self:append(title, "LazyButton", { wrap = true })
|
||||
self:highlight({ ["%(.%)"] = "LazySpecial" })
|
||||
end
|
||||
if c == #modes then
|
||||
break
|
||||
end
|
||||
self:append(" ")
|
||||
end
|
||||
self:nl()
|
||||
end
|
||||
if self.progress.done < self.progress.total then
|
||||
self:progressbar()
|
||||
end
|
||||
self:nl()
|
||||
|
||||
if View.mode ~= "help" and View.mode ~= "profile" then
|
||||
if self.view.state.mode ~= "help" and self.view.state.mode ~= "profile" and self.view.state.mode ~= "debug" then
|
||||
if self.progress.done < self.progress.total then
|
||||
self:append("Tasks: ", "LazyH2")
|
||||
self:append(self.progress.done .. "/" .. self.progress.total, "LazyMuted")
|
||||
self:append(self.progress.done .. "/" .. self.progress.total, "LazyComment")
|
||||
else
|
||||
self:append("Total: ", "LazyH2")
|
||||
self:append(#self.plugins .. " plugins", "LazyMuted")
|
||||
self:append(#self.plugins .. " plugins", "LazyComment")
|
||||
end
|
||||
self:nl():nl()
|
||||
end
|
||||
return View.mode
|
||||
end
|
||||
|
||||
function M:help()
|
||||
local View = require("lazy.view")
|
||||
self:append("Help", "LazyH2"):nl():nl()
|
||||
|
||||
self:append("Use "):append(ViewConfig.keys.abort, "LazySpecial"):append(" to abort all running tasks."):nl():nl()
|
||||
|
||||
self:append("You can press "):append("<CR>", "LazySpecial"):append(" on a plugin to show its details."):nl():nl()
|
||||
|
||||
self:append("Most properties can be hovered with ")
|
||||
self:append("<K>", "LazySpecial")
|
||||
self:append(" to open links, help files, readmes and git commits."):nl()
|
||||
self
|
||||
:append("When hovering with ")
|
||||
:append("<K>", "LazySpecial")
|
||||
:append(" on a plugin anywhere else, a diff will be opened if there are updates")
|
||||
:nl()
|
||||
self:append("or the plugin was just updated. Otherwise the plugin webpage will open."):nl():nl()
|
||||
|
||||
self:append("Use "):append("<d>", "LazySpecial"):append(" on a commit or plugin to open the diff view"):nl():nl()
|
||||
self
|
||||
:append("Use ")
|
||||
:append("<]]>", "LazySpecial")
|
||||
:append(" and ")
|
||||
:append("<[[>", "LazySpecial")
|
||||
:append(" to navigate between plugins")
|
||||
:nl()
|
||||
:nl()
|
||||
self:nl()
|
||||
|
||||
self:append("Keyboard Shortcuts", "LazyH2"):nl()
|
||||
for _, mode in ipairs(View.modes) do
|
||||
local title = mode.name:sub(1, 1):upper() .. mode.name:sub(2)
|
||||
self:append("- ", "LazySpecial", { indent = 2 })
|
||||
self:append(title, "Title"):append(" <" .. mode.key .. "> ", "LazyKey")
|
||||
self:append(mode.desc or ""):nl()
|
||||
for _, mode in ipairs(ViewConfig.get_commands()) do
|
||||
if mode.key then
|
||||
local title = mode.name:sub(1, 1):upper() .. mode.name:sub(2)
|
||||
self:append("- ", "LazySpecial", { indent = 2 })
|
||||
self:append(title, "Title")
|
||||
if mode.key then
|
||||
self:append(" <" .. mode.key .. ">", "LazyProp")
|
||||
end
|
||||
self:append(" " .. (mode.desc or "")):nl()
|
||||
end
|
||||
end
|
||||
|
||||
self:nl():append("Keyboard Shortcuts for Plugins", "LazyH2"):nl()
|
||||
for _, mode in ipairs(ViewConfig.get_commands()) do
|
||||
if mode.key_plugin then
|
||||
local title = mode.name:sub(1, 1):upper() .. mode.name:sub(2)
|
||||
self:append("- ", "LazySpecial", { indent = 2 })
|
||||
self:append(title, "Title")
|
||||
if mode.key_plugin then
|
||||
self:append(" <" .. mode.key_plugin .. ">", "LazyProp")
|
||||
end
|
||||
self:append(" " .. (mode.desc_plugin or mode.desc)):nl()
|
||||
end
|
||||
end
|
||||
for lhs, rhs in pairs(Config.options.ui.custom_keys) do
|
||||
if type(rhs) == "table" and rhs.desc then
|
||||
self:append("- ", "LazySpecial", { indent = 2 })
|
||||
self:append("Custom key ", "Title")
|
||||
self:append(lhs, "LazyProp")
|
||||
self:append(" " .. rhs.desc):nl()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M:progressbar()
|
||||
local width = vim.api.nvim_win_get_width(self.win) - 2 * self.padding
|
||||
local width = vim.api.nvim_win_get_width(self.view.win) - 2 * self.padding
|
||||
local done = math.floor((self.progress.done / self.progress.total) * width + 0.5)
|
||||
if self.progress.done == self.progress.total then
|
||||
done = 0
|
||||
|
@ -167,8 +275,11 @@ function M:section(section)
|
|||
end, self.plugins)
|
||||
|
||||
local count = #section_plugins
|
||||
table.sort(section_plugins, function(a, b)
|
||||
return a.name:lower() < b.name:lower()
|
||||
end)
|
||||
if count > 0 then
|
||||
self:append(section.title, "LazyH2"):append(" (" .. count .. ")", "LazyMuted"):nl()
|
||||
self:append(section.title, "LazyH2"):append(" (" .. count .. ")", "LazyComment"):nl()
|
||||
for _, plugin in ipairs(section_plugins) do
|
||||
self:plugin(plugin)
|
||||
end
|
||||
|
@ -183,6 +294,13 @@ function M:diagnostic(diag)
|
|||
table.insert(self._diagnostics, diag)
|
||||
end
|
||||
|
||||
---@param precision? number
|
||||
function M:ms(nsec, precision)
|
||||
precision = precision or 2
|
||||
local e = math.pow(10, precision)
|
||||
return math.floor(nsec / 1e6 * e + 0.5) / e .. "ms"
|
||||
end
|
||||
|
||||
---@param reason? {[string]:string, time:number}
|
||||
---@param opts? {time_right?:boolean}
|
||||
function M:reason(reason, opts)
|
||||
|
@ -194,89 +312,129 @@ function M:reason(reason, opts)
|
|||
---@type string?
|
||||
local source = reason.source
|
||||
if source then
|
||||
---@type string?
|
||||
local modname = source:match("/lua/(.*)%.lua$")
|
||||
if modname then
|
||||
modname = modname:gsub("/", ".")
|
||||
end
|
||||
local name = source:match("/([^/]-)/lua")
|
||||
for _, other in pairs(Config.plugins) do
|
||||
if (modname and other.modname == modname) or (name and other.name == name) then
|
||||
reason.plugin = other.name
|
||||
reason.source = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
if reason.source then
|
||||
reason.source = modname or reason.source
|
||||
if reason.source == "lua" then
|
||||
reason.source = Config.options.plugins
|
||||
source = Util.norm(source)
|
||||
local plugin = Plugin.find(source)
|
||||
if plugin then
|
||||
reason.plugin = plugin.name
|
||||
reason.source = nil
|
||||
else
|
||||
local config = Util.norm(vim.fn.stdpath("config"))
|
||||
if source == config .. "/init.lua" then
|
||||
reason.source = "init.lua"
|
||||
else
|
||||
config = config .. "/lua"
|
||||
if source:find(config, 1, true) == 1 then
|
||||
reason.source = source:sub(#config + 2):gsub("/", "."):gsub("%.lua$", "")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local time = " " .. math.floor((reason.time or 0) / 1e6 * 100) / 100 .. "ms"
|
||||
if not opts.time_right then
|
||||
if reason.runtime then
|
||||
reason.runtime = Util.norm(reason.runtime)
|
||||
reason.runtime = reason.runtime:gsub(".*/([^/]+/plugin/.*)", "%1")
|
||||
reason.runtime = reason.runtime:gsub(".*/([^/]+/after/.*)", "%1")
|
||||
reason.runtime = reason.runtime:gsub(".*/([^/]+/ftdetect/.*)", "%1")
|
||||
reason.runtime = reason.runtime:gsub(".*/(runtime/.*)", "%1")
|
||||
end
|
||||
local time = reason.time and (" " .. self:ms(reason.time))
|
||||
if time and not opts.time_right then
|
||||
self:append(time, "Bold")
|
||||
self:append(" ")
|
||||
end
|
||||
self:append(" ")
|
||||
-- self:append(" (", "Conceal")
|
||||
local first = true
|
||||
for key, value in pairs(reason) do
|
||||
if type(key) == "number" then
|
||||
elseif key == "require" then
|
||||
-- self:append("require", "@function.builtin")
|
||||
-- self:append("(", "@punctuation.bracket")
|
||||
-- self:append('"' .. value .. '"', "@string")
|
||||
-- self:append(")", "@punctuation.bracket")
|
||||
elseif key ~= "time" then
|
||||
local keys = vim.tbl_keys(reason)
|
||||
table.sort(keys)
|
||||
if vim.tbl_contains(keys, "plugin") then
|
||||
keys = vim.tbl_filter(function(key)
|
||||
return key ~= "plugin"
|
||||
end, keys)
|
||||
table.insert(keys, "plugin")
|
||||
end
|
||||
for _, key in ipairs(keys) do
|
||||
local value = reason[key]
|
||||
local skip = type(key) == "number" or key == "time"
|
||||
if not skip then
|
||||
if first then
|
||||
first = false
|
||||
else
|
||||
self:append(" ")
|
||||
end
|
||||
if key == "event" then
|
||||
value = value:match("User (.*)") or value
|
||||
end
|
||||
local hl = "LazyLoader" .. key:sub(1, 1):upper() .. key:sub(2)
|
||||
local icon = Config.options.view.icons[key]
|
||||
local hl = "LazyReason" .. key:sub(1, 1):upper() .. key:sub(2)
|
||||
local icon = Config.options.ui.icons[key]
|
||||
if icon then
|
||||
icon = icon:gsub("%s*$", "")
|
||||
self:append(icon .. " ", hl)
|
||||
self:append(value, hl)
|
||||
else
|
||||
self:append(key .. " ", "@field")
|
||||
self:append(key .. " ", hl)
|
||||
self:append(value, hl)
|
||||
end
|
||||
end
|
||||
end
|
||||
if opts.time_right then
|
||||
if time and opts.time_right then
|
||||
self:append(time, "Bold")
|
||||
end
|
||||
-- self:append(")", "Conceal")
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
function M:diagnostics(plugin)
|
||||
if plugin._.updated then
|
||||
local skip = false
|
||||
for _, task in ipairs(plugin._.tasks or {}) do
|
||||
if task:running() then
|
||||
self:diagnostic({
|
||||
severity = vim.diagnostic.severity.WARN,
|
||||
message = task.name .. (task:status() and (": " .. task:status()) or ""),
|
||||
})
|
||||
skip = true
|
||||
elseif task:has_errors() then
|
||||
self:diagnostic({
|
||||
message = task.name .. " failed",
|
||||
severity = vim.diagnostic.severity.ERROR,
|
||||
})
|
||||
skip = true
|
||||
elseif task:has_warnings() then
|
||||
self:diagnostic({
|
||||
message = task.name .. " warning",
|
||||
severity = vim.diagnostic.severity.WARN,
|
||||
})
|
||||
skip = true
|
||||
end
|
||||
end
|
||||
if skip then
|
||||
return
|
||||
end
|
||||
if plugin._.build then
|
||||
self:diagnostic({
|
||||
message = "needs build",
|
||||
severity = vim.diagnostic.severity.WARN,
|
||||
})
|
||||
elseif plugin._.updated then
|
||||
if plugin._.updated.from == plugin._.updated.to then
|
||||
self:diagnostic({
|
||||
message = "already up to date",
|
||||
})
|
||||
else
|
||||
self:diagnostic({
|
||||
message = "updated from " .. plugin._.updated.from:sub(1, 7) .. " to " .. plugin._.updated.to:sub(1, 7),
|
||||
})
|
||||
local version = Git.info(plugin.dir, true).version
|
||||
if version then
|
||||
self:diagnostic({
|
||||
message = "updated to " .. tostring(version),
|
||||
})
|
||||
else
|
||||
self:diagnostic({
|
||||
message = "updated from " .. plugin._.updated.from:sub(1, 7) .. " to " .. plugin._.updated.to:sub(1, 7),
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, task in ipairs(plugin._.tasks or {}) do
|
||||
if task:is_running() then
|
||||
elseif plugin._.updates then
|
||||
local version = plugin._.updates.to.version
|
||||
if version then
|
||||
self:diagnostic({
|
||||
severity = vim.diagnostic.severity.WARN,
|
||||
message = task.name .. (task.status == "" and "" or (": " .. task.status)),
|
||||
message = "version " .. tostring(version) .. " is available",
|
||||
})
|
||||
elseif task.error then
|
||||
else
|
||||
self:diagnostic({
|
||||
message = task.name .. " failed",
|
||||
severity = vim.diagnostic.severity.ERROR,
|
||||
message = "updates available",
|
||||
})
|
||||
end
|
||||
end
|
||||
|
@ -284,43 +442,77 @@ end
|
|||
|
||||
---@param plugin LazyPlugin
|
||||
function M:plugin(plugin)
|
||||
local hl = plugin._.is_local and "LazyLocal" or "LazySpecial"
|
||||
if plugin._.loaded then
|
||||
self:append(" ● ", "LazySpecial"):append(plugin.name)
|
||||
self:append(" " .. Config.options.ui.icons.loaded .. " ", hl):append(plugin.name)
|
||||
elseif plugin._.cond == false then
|
||||
self:append(" " .. Config.options.ui.icons.not_loaded .. " ", "LazyNoCond"):append(plugin.name)
|
||||
else
|
||||
self:append(" ○ ", "LazySpecial"):append(plugin.name)
|
||||
self:append(" " .. Config.options.ui.icons.not_loaded .. " ", hl):append(plugin.name)
|
||||
end
|
||||
local plugin_start = self:row()
|
||||
if plugin._.loaded then
|
||||
-- When the plugin is loaded, only show the loading reason
|
||||
self:reason(plugin._.loaded)
|
||||
else
|
||||
-- otherwise show all lazy handlers
|
||||
self:append(" ")
|
||||
self:handlers(plugin)
|
||||
for _, other in pairs(Config.plugins) do
|
||||
if vim.tbl_contains(other.dependencies or {}, plugin.name) then
|
||||
self:reason({ plugin = other.name })
|
||||
self:append(" ")
|
||||
end
|
||||
end
|
||||
end
|
||||
self:diagnostics(plugin)
|
||||
self:nl()
|
||||
|
||||
if self._details == plugin.name then
|
||||
if self.view:is_selected(plugin) then
|
||||
self:details(plugin)
|
||||
end
|
||||
self:tasks(plugin)
|
||||
self.plugin_range[plugin.name] = { from = plugin_start, to = self:row() - 1 }
|
||||
self.locations[#self.locations + 1] =
|
||||
{ name = plugin.name, from = plugin_start, to = self:row() - 1, kind = plugin._.kind }
|
||||
end
|
||||
|
||||
---@param str string
|
||||
---@param hl? string|Extmark
|
||||
---@param opts? {indent?: number, prefix?: string, wrap?: boolean}
|
||||
function M:markdown(str, hl, opts)
|
||||
local lines = vim.split(str, "\n")
|
||||
for _, line in ipairs(lines) do
|
||||
self:append(line, hl, opts):highlight({
|
||||
["`.-`"] = "@markup.raw.markdown_inline",
|
||||
["%*.-%*"] = "LazyItalic",
|
||||
["%*%*.-%*%*"] = "LazyBold",
|
||||
["^%s*-"] = "Special",
|
||||
})
|
||||
self:nl()
|
||||
end
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
function M:tasks(plugin)
|
||||
for _, task in ipairs(plugin._.tasks or {}) do
|
||||
if self._details == plugin.name then
|
||||
self:append("✔ [task] ", "Title", { indent = 4 }):append(task.name)
|
||||
if self.view:is_selected(plugin) then
|
||||
self:append(Config.options.ui.icons.task .. "[task] ", "Title", { indent = 4 }):append(task.name)
|
||||
self:append(" " .. math.floor((task:time()) * 100) / 100 .. "ms", "Bold")
|
||||
self:nl()
|
||||
end
|
||||
if task.name == "log" and not task.error then
|
||||
|
||||
if not task:has_warnings() and task.name == "log" then
|
||||
self:log(task)
|
||||
elseif task.error or self._details == plugin.name then
|
||||
if task.error then
|
||||
self:append(vim.trim(task.error), "LazyError", { indent = 4, prefix = "│ " })
|
||||
self:nl()
|
||||
end
|
||||
if task.output ~= "" and task.output ~= task.error then
|
||||
self:append(vim.trim(task.output), "MsgArea", { indent = 4, prefix = "│ " })
|
||||
self:nl()
|
||||
else
|
||||
local hls = {
|
||||
[vim.log.levels.ERROR] = "LazyError",
|
||||
[vim.log.levels.WARN] = "LazyWarning",
|
||||
[vim.log.levels.INFO] = "LazyInfo",
|
||||
}
|
||||
for _, msg in ipairs(task:get_log()) do
|
||||
if task:has_warnings() or self.view:is_selected(plugin) then
|
||||
self:markdown(msg.msg, hls[msg.level] or "LazyTaskOutput", { indent = 6 })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -328,24 +520,36 @@ end
|
|||
|
||||
---@param task LazyTask
|
||||
function M:log(task)
|
||||
local log = vim.trim(task.output)
|
||||
local log = vim.trim(task:output())
|
||||
if log ~= "" then
|
||||
local lines = vim.split(log, "\n")
|
||||
for _, line in ipairs(lines) do
|
||||
local ref, msg, time = line:match("^(%w+) (.*) (%(.*%))$")
|
||||
if msg:find("^%S+!:") then
|
||||
self:diagnostic({ message = "Breaking Changes", severity = vim.diagnostic.severity.WARN })
|
||||
if msg then
|
||||
if msg:find("^%S+!:") then
|
||||
self:diagnostic({ message = "Breaking Changes", severity = vim.diagnostic.severity.WARN })
|
||||
end
|
||||
self:append(ref:sub(1, 7) .. " ", "LazyCommit", { indent = 6 })
|
||||
|
||||
local dimmed = false
|
||||
for _, dim in ipairs(ViewConfig.dimmed_commits) do
|
||||
if msg:find("^" .. dim) then
|
||||
dimmed = true
|
||||
end
|
||||
end
|
||||
self:append(vim.trim(msg), dimmed and "LazyDimmed" or nil):highlight({
|
||||
["#%d+"] = "LazyCommitIssue",
|
||||
["^%S+:"] = dimmed and "Bold" or "LazyCommitType",
|
||||
["^%S+(%(.*%))!?:"] = "LazyCommitScope",
|
||||
["`.-`"] = "@markup.raw.markdown_inline",
|
||||
["%*.-%*"] = "Italic",
|
||||
["%*%*.-%*%*"] = "Bold",
|
||||
})
|
||||
self:append(" " .. time, "LazyComment")
|
||||
self:nl()
|
||||
-- else
|
||||
-- self:append(line, "LazyTaskOutput", { indent = 6 }):nl()
|
||||
end
|
||||
self:append(ref .. " ", "LazyCommit", { indent = 6 })
|
||||
self:append(vim.trim(msg)):highlight({
|
||||
["#%d+"] = "Number",
|
||||
["^%S+:"] = "Title",
|
||||
["^%S+(%(.*%)):"] = "Italic",
|
||||
["`.-`"] = "@text.literal.markdown_inline",
|
||||
})
|
||||
-- string.gsub
|
||||
self:append(" " .. time, "Comment")
|
||||
self:nl()
|
||||
end
|
||||
self:nl()
|
||||
end
|
||||
|
@ -355,9 +559,13 @@ end
|
|||
function M:details(plugin)
|
||||
---@type string[][]
|
||||
local props = {}
|
||||
table.insert(props, { "uri", (plugin.uri:gsub("%.git$", "")), "@text.reference" })
|
||||
table.insert(props, { "dir", plugin.dir, "LazyDir" })
|
||||
if plugin.url then
|
||||
table.insert(props, { "url", (plugin.url:gsub("%.git$", "")), "LazyUrl" })
|
||||
end
|
||||
local git = Git.info(plugin.dir, true)
|
||||
if git then
|
||||
git.branch = git.branch or Git.get_branch(plugin)
|
||||
if git.version then
|
||||
table.insert(props, { "version", tostring(git.version) })
|
||||
end
|
||||
|
@ -367,60 +575,225 @@ function M:details(plugin)
|
|||
if git.branch then
|
||||
table.insert(props, { "branch", git.branch })
|
||||
end
|
||||
table.insert(props, { "commit", git.commit:sub(1, 7), "LazyCommit" })
|
||||
if git.commit then
|
||||
table.insert(props, { "commit", git.commit:sub(1, 7), "LazyCommit" })
|
||||
end
|
||||
end
|
||||
local rocks = require("lazy.pkg.rockspec").deps(plugin)
|
||||
if rocks then
|
||||
table.insert(props, { "rocks", vim.inspect(rocks) })
|
||||
end
|
||||
|
||||
if Util.file_exists(plugin.dir .. "/README.md") then
|
||||
table.insert(props, { "readme", "README.md" })
|
||||
end
|
||||
Util.ls(plugin.dir .. "/doc", function(path, name)
|
||||
if name:sub(-3) == "txt" then
|
||||
local data = Util.read_file(path)
|
||||
local tag = data:match("%*(%S-)%*")
|
||||
if tag then
|
||||
table.insert(props, { "help", "|" .. tag .. "|" })
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
for handler in pairs(Handler.handlers) do
|
||||
if plugin[handler] then
|
||||
table.insert(props, {
|
||||
handler,
|
||||
type(plugin[handler]) == "string" and plugin[handler] or table.concat(plugin[handler], ", "),
|
||||
"@string",
|
||||
})
|
||||
for handler in pairs(plugin._.handlers or {}) do
|
||||
table.insert(props, {
|
||||
handler,
|
||||
function()
|
||||
self:handlers(plugin, handler)
|
||||
end,
|
||||
})
|
||||
end
|
||||
self:props(props, { indent = 6 })
|
||||
|
||||
self:nl()
|
||||
end
|
||||
|
||||
---@param plugin LazyPlugin
|
||||
---@param types? LazyHandlerTypes[]|LazyHandlerTypes
|
||||
function M:handlers(plugin, types)
|
||||
if not plugin._.handlers then
|
||||
return
|
||||
end
|
||||
types = type(types) == "string" and { types } or types
|
||||
types = types and types or vim.tbl_keys(Handler.types)
|
||||
for _, t in ipairs(types) do
|
||||
for id, value in pairs(plugin._.handlers[t] or {}) do
|
||||
value = t == "keys" and Keys.to_string(value) or id
|
||||
self:reason({ [t] = value })
|
||||
self:append(" ")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@alias LazyProps {[1]:string, [2]:string|fun(), [3]?:string}[]
|
||||
---@param props LazyProps
|
||||
---@param opts? {indent: number}
|
||||
function M:props(props, opts)
|
||||
opts = opts or {}
|
||||
local width = 0
|
||||
for _, prop in ipairs(props) do
|
||||
width = math.max(width, #prop[1])
|
||||
end
|
||||
for _, prop in ipairs(props) do
|
||||
self:append(prop[1] .. string.rep(" ", width - #prop[1] + 1), "LazyKey", { indent = 6 })
|
||||
self:append(prop[2], prop[3] or "LazyValue")
|
||||
self:append(prop[1] .. string.rep(" ", width - #prop[1] + 1), "LazyProp", { indent = opts.indent or 0 })
|
||||
if type(prop[2]) == "function" then
|
||||
prop[2]()
|
||||
else
|
||||
self:append(tostring(prop[2]), prop[3] or "LazyValue")
|
||||
end
|
||||
self:nl()
|
||||
end
|
||||
self:nl()
|
||||
end
|
||||
|
||||
function M:profile()
|
||||
local stats = require("lazy.stats").stats()
|
||||
local ms = (math.floor(stats.startuptime * 100 + 0.5) / 100)
|
||||
self:append("Startuptime: ", "LazyH2"):append(ms .. "ms", "Number"):nl():nl()
|
||||
if stats.real_cputime then
|
||||
self:append("Based on the actual CPU time of the Neovim process till "):append("UIEnter", "LazySpecial")
|
||||
self:append("."):nl()
|
||||
self:append("This is more accurate than ")
|
||||
self:append("`nvim --startuptime`", "@markup.raw.markdown_inline")
|
||||
self:append(".")
|
||||
else
|
||||
self:append("An accurate startuptime based on the actual CPU time of the Neovim process is not available."):nl()
|
||||
self
|
||||
:append("Startuptime is instead based on a delta with a timestamp when lazy started till ")
|
||||
:append("UIEnter", "LazySpecial")
|
||||
self:append(".")
|
||||
end
|
||||
self:nl()
|
||||
|
||||
local times = {}
|
||||
for event, time in pairs(require("lazy.stats").stats().times) do
|
||||
times[#times + 1] = { event, self:ms(time * 1e6), "Bold", time = time }
|
||||
end
|
||||
table.sort(times, function(a, b)
|
||||
return a.time < b.time
|
||||
end)
|
||||
for p, prop in ipairs(times) do
|
||||
if p > 1 then
|
||||
prop[2] = prop[2] .. " (+" .. self:ms((prop.time - times[p - 1].time) * 1e6) .. ")"
|
||||
end
|
||||
end
|
||||
self:props(times, { indent = 2 })
|
||||
|
||||
self:nl()
|
||||
|
||||
self:append("Profile", "LazyH2"):nl():nl()
|
||||
local symbols = {
|
||||
"●",
|
||||
"➜",
|
||||
"★",
|
||||
"‒",
|
||||
}
|
||||
self
|
||||
:append("You can press ")
|
||||
:append(ViewConfig.keys.profile_sort, "LazySpecial")
|
||||
:append(" to change sorting between chronological order & time taken.")
|
||||
:nl()
|
||||
self
|
||||
:append("Press ")
|
||||
:append(ViewConfig.keys.profile_filter, "LazySpecial")
|
||||
:append(" to filter profiling entries that took more time than a given threshold")
|
||||
:nl()
|
||||
|
||||
self:nl()
|
||||
|
||||
---@param a LazyProfile
|
||||
---@param b LazyProfile
|
||||
local function sort(a, b)
|
||||
return a.time > b.time
|
||||
end
|
||||
|
||||
---@param entry LazyProfile
|
||||
local function get_children(entry)
|
||||
---@type LazyProfile[]
|
||||
local children = entry
|
||||
|
||||
if self.view.state.profile.sort_time_taken then
|
||||
children = {}
|
||||
for _, child in ipairs(entry) do
|
||||
children[#children + 1] = child
|
||||
end
|
||||
table.sort(children, sort)
|
||||
end
|
||||
return children
|
||||
end
|
||||
|
||||
---@param entry LazyProfile
|
||||
local function _profile(entry, depth)
|
||||
if entry.time / 1e6 < self.view.state.profile.threshold then
|
||||
return
|
||||
end
|
||||
local data = type(entry.data) == "string" and { source = entry.data } or entry.data
|
||||
data.time = entry.time
|
||||
local symbol = symbols[depth] or symbols[#symbols]
|
||||
self:append((" "):rep(depth)):append(" " .. symbol, "LazySpecial")
|
||||
local symbol = M.list_icon(depth)
|
||||
self:append((" "):rep(depth)):append(symbol, "LazySpecial"):append(" ")
|
||||
self:reason(data, { time_right = true })
|
||||
self:nl()
|
||||
|
||||
for _, child in ipairs(entry) do
|
||||
for _, child in ipairs(get_children(entry)) do
|
||||
_profile(child, depth + 1)
|
||||
end
|
||||
end
|
||||
|
||||
for _, entry in ipairs(Util._profiles[1]) do
|
||||
for _, entry in ipairs(get_children(Util._profiles[1])) do
|
||||
_profile(entry, 1)
|
||||
end
|
||||
end
|
||||
|
||||
function M.list_icon(depth)
|
||||
local symbols = Config.options.ui.icons.list
|
||||
return symbols[(depth - 1) % #symbols + 1]
|
||||
end
|
||||
|
||||
function M:debug()
|
||||
self:append("Active Handlers", "LazyH2"):nl()
|
||||
self
|
||||
:append(
|
||||
"This shows only the lazy handlers that are still active. When a plugin loads, its handlers are removed",
|
||||
"LazyComment",
|
||||
{ indent = 2 }
|
||||
)
|
||||
:nl()
|
||||
|
||||
Util.foreach(require("lazy.core.handler").handlers, function(handler_type, handler)
|
||||
Util.foreach(handler.active, function(value, plugins)
|
||||
if not vim.tbl_isempty(plugins) then
|
||||
---@type string[]
|
||||
plugins = vim.tbl_values(plugins)
|
||||
table.sort(plugins)
|
||||
self:append(Config.options.ui.icons.debug, "LazySpecial", { indent = 2 })
|
||||
if handler_type == "keys" then
|
||||
for k, v in pairs(Config.plugins[plugins[1]]._.handlers.keys) do
|
||||
if k == value then
|
||||
value = Keys.to_string(v)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
self:reason({ [handler_type] = value })
|
||||
for _, plugin in pairs(plugins) do
|
||||
self:append(" ")
|
||||
self:reason({ plugin = plugin })
|
||||
end
|
||||
self:nl()
|
||||
end
|
||||
end)
|
||||
end)
|
||||
self:nl()
|
||||
|
||||
Util.foreach(require("lazy.core.cache")._inspect(), function(name, stats)
|
||||
self:append(name, "LazyH2"):nl()
|
||||
local props = {
|
||||
{ "total", stats.total or 0, "Number" },
|
||||
{ "time", self:ms(stats.time or 0, 3), "Bold" },
|
||||
{ "avg time", self:ms((stats.time or 0) / (stats.total or 0), 3), "Bold" },
|
||||
}
|
||||
for k, v in pairs(stats) do
|
||||
if k ~= "total" and k ~= "time" then
|
||||
props[#props + 1] = { k, v, "Number" }
|
||||
end
|
||||
end
|
||||
self:props(props, { indent = 2 })
|
||||
self:nl()
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
@ -17,27 +17,35 @@ return {
|
|||
{
|
||||
filter = function(plugin)
|
||||
return has_task(plugin, function(task)
|
||||
return task.error ~= nil
|
||||
return task:has_errors()
|
||||
end)
|
||||
end,
|
||||
title = "Failed",
|
||||
},
|
||||
{
|
||||
filter = function(plugin)
|
||||
if plugin._.working then
|
||||
return true
|
||||
end
|
||||
return has_task(plugin, function(task)
|
||||
return task:is_running()
|
||||
return task:running()
|
||||
end)
|
||||
end,
|
||||
title = "Working",
|
||||
},
|
||||
{
|
||||
filter = function(plugin)
|
||||
return plugin._.build
|
||||
end,
|
||||
title = "Build",
|
||||
},
|
||||
{
|
||||
filter = function(plugin)
|
||||
return has_task(plugin, function(task)
|
||||
if task.name ~= "log" then
|
||||
return
|
||||
end
|
||||
local lines = vim.split(task.output, "\n")
|
||||
for _, line in ipairs(lines) do
|
||||
for _, line in ipairs(vim.split(task:output(), "\n")) do
|
||||
if line:find("^%w+ %S+!:") then
|
||||
return true
|
||||
end
|
||||
|
@ -47,42 +55,53 @@ return {
|
|||
title = "Breaking Changes",
|
||||
},
|
||||
{
|
||||
---@param plugin LazyPlugin
|
||||
filter = function(plugin)
|
||||
return plugin._.updated and plugin._.updated.from ~= plugin._.updated.to
|
||||
end,
|
||||
title = "Updated",
|
||||
},
|
||||
{
|
||||
---@param plugin LazyPlugin
|
||||
filter = function(plugin)
|
||||
return plugin._.cloned
|
||||
end,
|
||||
title = "Installed",
|
||||
},
|
||||
{
|
||||
---@param plugin LazyPlugin
|
||||
filter = function(plugin)
|
||||
return plugin._.updates ~= nil
|
||||
end,
|
||||
title = "Updates",
|
||||
},
|
||||
{
|
||||
filter = function(plugin)
|
||||
return has_task(plugin, function(task)
|
||||
return task.name == "log" and vim.trim(task.output) ~= ""
|
||||
return task.name == "log" and vim.trim(task:output()) ~= ""
|
||||
end)
|
||||
end,
|
||||
title = "Log",
|
||||
},
|
||||
{
|
||||
filter = function(plugin)
|
||||
return plugin._.installed and not plugin.uri
|
||||
return plugin._.kind == "clean" and plugin._.installed
|
||||
end,
|
||||
title = "Clean",
|
||||
},
|
||||
{
|
||||
filter = function(plugin)
|
||||
return not plugin._.installed
|
||||
return not plugin._.installed and plugin._.kind ~= "disabled"
|
||||
end,
|
||||
title = "Not Installed",
|
||||
},
|
||||
{
|
||||
filter = function(plugin)
|
||||
return plugin._.loaded
|
||||
return plugin._.outdated
|
||||
end,
|
||||
title = "Outdated",
|
||||
},
|
||||
{
|
||||
filter = function(plugin)
|
||||
return plugin._.loaded ~= nil
|
||||
end,
|
||||
title = "Loaded",
|
||||
},
|
||||
|
@ -90,6 +109,12 @@ return {
|
|||
filter = function(plugin)
|
||||
return plugin._.installed
|
||||
end,
|
||||
title = "Installed",
|
||||
title = "Not Loaded",
|
||||
},
|
||||
{
|
||||
filter = function(plugin)
|
||||
return plugin._.kind == "disabled"
|
||||
end,
|
||||
title = "Disabled",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
local Config = require("lazy.core.config")
|
||||
local Util = require("lazy.util")
|
||||
|
||||
---@alias TextSegment {str: string, hl?:string|Extmark}
|
||||
---@alias Extmark {hl_group?:string, col?:number, end_col?:number}
|
||||
|
@ -6,12 +7,11 @@ local Config = require("lazy.core.config")
|
|||
---@class Text
|
||||
---@field _lines TextSegment[][]
|
||||
---@field padding number
|
||||
---@field wrap number
|
||||
local Text = {}
|
||||
|
||||
function Text.new()
|
||||
local self = setmetatable({}, {
|
||||
__index = Text,
|
||||
})
|
||||
local self = setmetatable({}, { __index = Text })
|
||||
self._lines = {}
|
||||
|
||||
return self
|
||||
|
@ -19,7 +19,7 @@ end
|
|||
|
||||
---@param str string
|
||||
---@param hl? string|Extmark
|
||||
---@param opts? {indent?: number, prefix?: string}
|
||||
---@param opts? {indent?: number, prefix?: string, wrap?: boolean}
|
||||
function Text:append(str, hl, opts)
|
||||
opts = opts or {}
|
||||
if #self._lines == 0 then
|
||||
|
@ -37,6 +37,15 @@ function Text:append(str, hl, opts)
|
|||
if l > 1 then
|
||||
self:nl()
|
||||
end
|
||||
if
|
||||
Config.options.ui.wrap
|
||||
and opts.wrap
|
||||
and str ~= ""
|
||||
and self:col() > 0
|
||||
and self:col() + vim.fn.strwidth(line) + self.padding > self.wrap
|
||||
then
|
||||
self:nl()
|
||||
end
|
||||
table.insert(self._lines[#self._lines], {
|
||||
str = line,
|
||||
hl = hl,
|
||||
|
@ -56,35 +65,51 @@ function Text:render(buf)
|
|||
|
||||
for _, line in ipairs(self._lines) do
|
||||
local str = (" "):rep(self.padding)
|
||||
local has_extmark = false
|
||||
|
||||
for _, segment in ipairs(line) do
|
||||
str = str .. segment.str
|
||||
if type(segment.hl) == "table" then
|
||||
has_extmark = true
|
||||
end
|
||||
end
|
||||
|
||||
if str:match("^%s*$") and not has_extmark then
|
||||
str = ""
|
||||
end
|
||||
table.insert(lines, str)
|
||||
end
|
||||
|
||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
||||
vim.api.nvim_buf_clear_namespace(buf, Config.ns, 0, -1)
|
||||
|
||||
for l, line in ipairs(self._lines) do
|
||||
local col = self.padding
|
||||
if lines[l] ~= "" then
|
||||
local col = self.padding
|
||||
|
||||
for _, segment in ipairs(line) do
|
||||
local width = vim.fn.strlen(segment.str)
|
||||
for _, segment in ipairs(line) do
|
||||
local width = vim.fn.strlen(segment.str)
|
||||
|
||||
local extmark = segment.hl
|
||||
if extmark then
|
||||
if type(extmark) == "string" then
|
||||
extmark = { hl_group = extmark, end_col = col + width }
|
||||
local extmark = segment.hl
|
||||
if extmark then
|
||||
if type(extmark) == "string" then
|
||||
extmark = { hl_group = extmark, end_col = col + width }
|
||||
end
|
||||
---@cast extmark Extmark
|
||||
|
||||
local extmark_col = extmark.col or col
|
||||
extmark.col = nil
|
||||
local ok, err = pcall(vim.api.nvim_buf_set_extmark, buf, Config.ns, l - 1, extmark_col, extmark)
|
||||
if not ok then
|
||||
Util.error(
|
||||
"Failed to set extmark. Please report a bug with this info:\n"
|
||||
.. vim.inspect({ segment = segment, line = line, error = err })
|
||||
)
|
||||
end
|
||||
end
|
||||
---@cast extmark Extmark
|
||||
|
||||
local extmark_col = extmark.col or col
|
||||
extmark.col = nil
|
||||
vim.api.nvim_buf_set_extmark(buf, Config.ns, l - 1, extmark_col, extmark)
|
||||
col = col + width
|
||||
end
|
||||
|
||||
col = col + width
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -120,27 +145,7 @@ function Text:highlight(patterns)
|
|||
end
|
||||
end
|
||||
|
||||
function Text:center()
|
||||
local last = self._lines[#self._lines]
|
||||
if not last then
|
||||
return
|
||||
end
|
||||
local width = 0
|
||||
for _, segment in ipairs(last) do
|
||||
width = width + vim.fn.strwidth(segment.str)
|
||||
end
|
||||
width = vim.api.nvim_win_get_width(self.win) - 2 * self.padding - width
|
||||
table.insert(last, 1, {
|
||||
str = string.rep(" ", math.floor(width / 2 + 0.5)),
|
||||
})
|
||||
return self
|
||||
end
|
||||
|
||||
function Text:trim()
|
||||
while #self._lines > 0 and #self._lines[1] == 0 do
|
||||
table.remove(self._lines, 1)
|
||||
end
|
||||
|
||||
while #self._lines > 0 and #self._lines[#self._lines] == 0 do
|
||||
table.remove(self._lines)
|
||||
end
|
||||
|
|
3
scripts/test
Executable file
3
scripts/test
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/env bash
|
||||
|
||||
nvim -l tests/minit.lua --minitest
|
4
selene.toml
Normal file
4
selene.toml
Normal file
|
@ -0,0 +1,4 @@
|
|||
std="vim"
|
||||
|
||||
[lints]
|
||||
mixed_table="allow"
|
|
@ -1,3 +1,6 @@
|
|||
indent_type = "Spaces"
|
||||
indent_width = 2
|
||||
column_width = 120
|
||||
column_width = 120
|
||||
[sort_requires]
|
||||
enabled = true
|
||||
|
||||
|
|
10
tests/core/init_spec.lua
Normal file
10
tests/core/init_spec.lua
Normal file
|
@ -0,0 +1,10 @@
|
|||
local Util = require("lazy.core.util")
|
||||
|
||||
describe("init", function()
|
||||
it("has correct environment for tests", function()
|
||||
for _, name in ipairs({ "config", "data", "cache", "state" }) do
|
||||
local path = Util.norm(vim.fn.stdpath(name) --[[@as string]])
|
||||
assert(path:find(".tests/" .. name, 1, true), path .. " not in .tests")
|
||||
end
|
||||
end)
|
||||
end)
|
|
@ -1,29 +1,526 @@
|
|||
local Config = require("lazy.core.config")
|
||||
local Handler = require("lazy.core.handler")
|
||||
local Plugin = require("lazy.core.plugin")
|
||||
|
||||
local assert = require("luassert")
|
||||
local function inspect(obj)
|
||||
return vim.inspect(obj):gsub("%s+", " ")
|
||||
end
|
||||
|
||||
Config.setup()
|
||||
---@param plugin LazyPlugin
|
||||
local function resolve(plugin)
|
||||
local meta = getmetatable(plugin)
|
||||
local ret = meta and type(meta.__index) == "table" and resolve(meta.__index) or {}
|
||||
for k, v in pairs(plugin) do
|
||||
ret[k] = v
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
describe("plugin spec", function()
|
||||
---@param plugins LazyPlugin[]
|
||||
local function clean(plugins)
|
||||
return vim.tbl_map(function(plugin)
|
||||
plugin = resolve(plugin)
|
||||
plugin[1] = nil
|
||||
plugin._.frags = nil
|
||||
if plugin._.dep == false then
|
||||
plugin._.dep = nil
|
||||
end
|
||||
plugin._.top = nil
|
||||
return plugin
|
||||
end, plugins)
|
||||
end
|
||||
|
||||
describe("plugin spec url/name", function()
|
||||
local tests = {
|
||||
{ { "~/foo" }, { [1] = "~/foo", name = "foo", uri = vim.fn.fnamemodify("~/foo", ":p") } },
|
||||
{ { "/tmp/foo" }, { [1] = "/tmp/foo", name = "foo", uri = "/tmp/foo" } },
|
||||
{ { "foo/bar" }, { [1] = "foo/bar", name = "bar", uri = "https://github.com/foo/bar.git" } },
|
||||
{ { "foo/bar", name = "foobar" }, { [1] = "foo/bar", name = "foobar", uri = "https://github.com/foo/bar.git" } },
|
||||
{ { "foo/bar", uri = "123" }, { [1] = "foo/bar", name = "bar", uri = "123" } },
|
||||
{ { "https://foobar" }, { [1] = "https://foobar", name = "foobar", uri = "https://foobar" } },
|
||||
{ { "ssh://foobar" }, { [1] = "ssh://foobar", name = "foobar", uri = "ssh://foobar" } },
|
||||
{ "foo/bar", { [1] = "foo/bar", name = "bar", uri = "https://github.com/foo/bar.git" } },
|
||||
{ { { { "foo/bar" } } }, { [1] = "foo/bar", name = "bar", uri = "https://github.com/foo/bar.git" } },
|
||||
{ { dir = "~/foo" }, { name = "foo", dir = vim.fn.fnamemodify("~/foo", ":p") } },
|
||||
{ { dir = "/tmp/foo" }, { dir = "/tmp/foo", name = "foo" } },
|
||||
{ { "foo/bar" }, { [1] = "foo/bar", name = "bar", url = "https://github.com/foo/bar.git" } },
|
||||
{ { "https://foo.bar" }, { [1] = "https://foo.bar", name = "foo.bar", url = "https://foo.bar" } },
|
||||
{ { "foo/bar", name = "foobar" }, { [1] = "foo/bar", name = "foobar", url = "https://github.com/foo/bar.git" } },
|
||||
{ { "foo/bar", url = "123" }, { [1] = "foo/bar", name = "bar", url = "123" } },
|
||||
{ { url = "https://foobar" }, { name = "foobar", url = "https://foobar" } },
|
||||
{
|
||||
{ { url = "https://foo", name = "foobar" }, { url = "https://foo" } },
|
||||
{ name = "foobar", url = "https://foo" },
|
||||
},
|
||||
{
|
||||
{ { url = "https://foo" }, { url = "https://foo", name = "foobar" } },
|
||||
{ name = "foobar", url = "https://foo" },
|
||||
},
|
||||
{ { url = "ssh://foobar" }, { name = "foobar", url = "ssh://foobar" } },
|
||||
{ "foo/bar", { [1] = "foo/bar", name = "bar", url = "https://github.com/foo/bar.git" } },
|
||||
{ { { { "foo/bar" } } }, { [1] = "foo/bar", name = "bar", url = "https://github.com/foo/bar.git" } },
|
||||
}
|
||||
|
||||
for _, test in ipairs(tests) do
|
||||
it("parses uri " .. vim.inspect(test[1]):gsub("%s+", " "), function()
|
||||
test[2]._ = {}
|
||||
it("parses " .. inspect(test[1]), function()
|
||||
if not test[2].dir then
|
||||
test[2].dir = Config.options.root .. "/" .. test[2].name
|
||||
end
|
||||
local spec = Plugin.Spec.new(test[1])
|
||||
local plugins = vim.tbl_values(spec.plugins)
|
||||
assert.equal(1, #plugins)
|
||||
local all = vim.deepcopy(spec.plugins)
|
||||
local plugins = vim.tbl_values(all)
|
||||
plugins = vim.tbl_map(function(plugin)
|
||||
plugin._ = {}
|
||||
return plugin
|
||||
end, plugins)
|
||||
local notifs = vim.tbl_filter(function(notif)
|
||||
return notif.level > 3
|
||||
end, spec.notifs)
|
||||
assert(#notifs == 0, vim.inspect(spec.notifs))
|
||||
assert.equal(1, #plugins, vim.inspect(all))
|
||||
plugins[1]._.super = nil
|
||||
assert.same(test[2], plugins[1])
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
describe("plugin spec dir", function()
|
||||
local tests = {
|
||||
{
|
||||
"~/projects/gitsigns.nvim",
|
||||
{ "lewis6991/gitsigns.nvim", opts = {}, dev = true },
|
||||
{ "lewis6991/gitsigns.nvim" },
|
||||
},
|
||||
{
|
||||
"~/projects/gitsigns.nvim",
|
||||
{ "lewis6991/gitsigns.nvim", opts = {}, dev = true },
|
||||
{ "gitsigns.nvim" },
|
||||
},
|
||||
{
|
||||
"~/projects/gitsigns.nvim",
|
||||
{ "lewis6991/gitsigns.nvim", opts = {} },
|
||||
{ "lewis6991/gitsigns.nvim", dev = true },
|
||||
},
|
||||
{
|
||||
"~/projects/gitsigns.nvim",
|
||||
{ "lewis6991/gitsigns.nvim", opts = {} },
|
||||
{ "gitsigns.nvim", dev = true },
|
||||
},
|
||||
}
|
||||
|
||||
for _, test in ipairs(tests) do
|
||||
local dir = vim.fn.expand(test[1])
|
||||
local input = vim.list_slice(test, 2)
|
||||
it("parses dir " .. inspect(input), function()
|
||||
local spec = Plugin.Spec.new(input)
|
||||
local plugins = vim.tbl_values(spec.plugins)
|
||||
assert(spec:report() == 0)
|
||||
assert.equal(1, #plugins)
|
||||
assert.same(dir, plugins[1].dir)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
describe("plugin dev", function()
|
||||
local tests = {
|
||||
{
|
||||
{ "lewis6991/gitsigns.nvim", opts = {}, dev = true },
|
||||
{ "lewis6991/gitsigns.nvim" },
|
||||
},
|
||||
{
|
||||
{ "lewis6991/gitsigns.nvim", opts = {}, dev = true },
|
||||
{ "gitsigns.nvim" },
|
||||
},
|
||||
{
|
||||
{ "lewis6991/gitsigns.nvim", opts = {} },
|
||||
{ "lewis6991/gitsigns.nvim", dev = true },
|
||||
},
|
||||
{
|
||||
{ "lewis6991/gitsigns.nvim", opts = {} },
|
||||
{ "gitsigns.nvim", dev = true },
|
||||
},
|
||||
}
|
||||
|
||||
for _, test in ipairs(tests) do
|
||||
local dir = vim.fn.expand("~/projects/gitsigns.nvim")
|
||||
local input = test
|
||||
it("parses dir " .. inspect(input), function()
|
||||
local spec = Plugin.Spec.new(input)
|
||||
local plugins = vim.tbl_values(spec.plugins)
|
||||
assert(spec:report() == 0)
|
||||
assert.equal(1, #plugins)
|
||||
assert.same(dir, plugins[1].dir)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
describe("plugin spec opt", function()
|
||||
it("handles dependencies", function()
|
||||
Config.options.defaults.lazy = false
|
||||
local tests = {
|
||||
{ "foo/bar", dependencies = { "foo/dep1", "foo/dep2" } },
|
||||
{ "foo/bar", dependencies = { { "foo/dep1" }, "foo/dep2" } },
|
||||
{ { { "foo/bar", dependencies = { { "foo/dep1" }, "foo/dep2" } } } },
|
||||
}
|
||||
for _, test in ipairs(tests) do
|
||||
local spec = Plugin.Spec.new(vim.deepcopy(test))
|
||||
assert(#spec.notifs == 0)
|
||||
Config.plugins = spec.plugins
|
||||
Config.spec = spec
|
||||
Plugin.update_state()
|
||||
assert(vim.tbl_count(spec.plugins) == 3)
|
||||
assert(#spec.plugins.bar.dependencies == 2)
|
||||
assert(spec.plugins.bar._.dep ~= true)
|
||||
assert(spec.plugins.bar.lazy == false)
|
||||
assert(spec.plugins.dep1._.dep == true)
|
||||
assert(spec.plugins.dep1.lazy == true)
|
||||
assert(spec.plugins.dep2._.dep == true)
|
||||
assert(spec.plugins.dep2.lazy == true)
|
||||
spec = Plugin.Spec.new(test)
|
||||
for _, plugin in pairs(spec.plugins) do
|
||||
plugin.dir = nil
|
||||
end
|
||||
assert.same({
|
||||
bar = {
|
||||
_ = {},
|
||||
dependencies = { "dep1", "dep2" },
|
||||
name = "bar",
|
||||
url = "https://github.com/foo/bar.git",
|
||||
},
|
||||
dep1 = {
|
||||
_ = {
|
||||
dep = true,
|
||||
},
|
||||
name = "dep1",
|
||||
url = "https://github.com/foo/dep1.git",
|
||||
},
|
||||
dep2 = {
|
||||
_ = {
|
||||
dep = true,
|
||||
},
|
||||
name = "dep2",
|
||||
url = "https://github.com/foo/dep2.git",
|
||||
},
|
||||
}, clean(spec.plugins))
|
||||
end
|
||||
end)
|
||||
|
||||
describe("deps", function()
|
||||
before_each(function()
|
||||
Handler.init()
|
||||
end)
|
||||
Config.options.defaults.lazy = false
|
||||
local tests = {
|
||||
{ { "foo/bar", dependencies = { { "dep1" }, "foo/dep2" } }, "foo/dep1" },
|
||||
{ "foo/dep1", { "foo/bar", dependencies = { { "dep1" }, "foo/dep2" } } },
|
||||
}
|
||||
for _, test in ipairs(tests) do
|
||||
it("handles dep names " .. inspect(test), function()
|
||||
local spec = Plugin.Spec.new(vim.deepcopy(test))
|
||||
assert(#spec.notifs == 0)
|
||||
Config.plugins = spec.plugins
|
||||
Plugin.update_state()
|
||||
spec = Plugin.Spec.new(test)
|
||||
for _, plugin in pairs(spec.plugins) do
|
||||
plugin.dir = nil
|
||||
end
|
||||
assert.same({
|
||||
bar = {
|
||||
_ = {},
|
||||
dependencies = { "dep1", "dep2" },
|
||||
name = "bar",
|
||||
url = "https://github.com/foo/bar.git",
|
||||
},
|
||||
dep1 = {
|
||||
_ = {},
|
||||
name = "dep1",
|
||||
url = "https://github.com/foo/dep1.git",
|
||||
},
|
||||
dep2 = {
|
||||
_ = {
|
||||
dep = true,
|
||||
},
|
||||
name = "dep2",
|
||||
url = "https://github.com/foo/dep2.git",
|
||||
},
|
||||
}, clean(spec.plugins))
|
||||
end)
|
||||
end
|
||||
|
||||
Config.options.defaults.lazy = false
|
||||
local tests = {
|
||||
{
|
||||
{ "foo/baz", name = "bar" },
|
||||
{ "foo/fee", dependencies = { "foo/baz" } },
|
||||
},
|
||||
{
|
||||
{ "foo/fee", dependencies = { "foo/baz" } },
|
||||
{ "foo/baz", name = "bar" },
|
||||
},
|
||||
-- {
|
||||
-- { "foo/baz", name = "bar" },
|
||||
-- { "foo/fee", dependencies = { "baz" } },
|
||||
-- },
|
||||
{
|
||||
{ "foo/baz", name = "bar" },
|
||||
{ "foo/fee", dependencies = { "bar" } },
|
||||
},
|
||||
}
|
||||
for _, test in ipairs(tests) do
|
||||
it("handles dep names " .. inspect(test), function()
|
||||
local spec = Plugin.Spec.new(vim.deepcopy(test))
|
||||
assert(#spec.notifs == 0)
|
||||
Config.plugins = spec.plugins
|
||||
Plugin.update_state()
|
||||
spec = Plugin.Spec.new(test)
|
||||
spec.meta:rebuild()
|
||||
for _, plugin in pairs(spec.plugins) do
|
||||
plugin.dir = nil
|
||||
end
|
||||
assert.same({
|
||||
bar = {
|
||||
_ = {},
|
||||
name = "bar",
|
||||
url = "https://github.com/foo/baz.git",
|
||||
},
|
||||
fee = {
|
||||
_ = {},
|
||||
name = "fee",
|
||||
url = "https://github.com/foo/fee.git",
|
||||
dependencies = { "bar" },
|
||||
},
|
||||
}, clean(spec.plugins))
|
||||
end)
|
||||
end
|
||||
|
||||
it("handles opt from dep", function()
|
||||
Config.options.defaults.lazy = false
|
||||
local spec = Plugin.Spec.new({ "foo/dep1", { "foo/bar", dependencies = { "foo/dep1", "foo/dep2" } } })
|
||||
assert(#spec.notifs == 0)
|
||||
Config.plugins = spec.plugins
|
||||
Plugin.update_state()
|
||||
assert.same(3, vim.tbl_count(spec.plugins))
|
||||
assert(spec.plugins.bar._.dep ~= true)
|
||||
assert(spec.plugins.bar.lazy == false)
|
||||
assert(spec.plugins.dep2._.dep == true)
|
||||
assert(spec.plugins.dep2.lazy == true)
|
||||
assert(spec.plugins.dep1._.dep ~= true)
|
||||
assert(spec.plugins.dep1.lazy == false)
|
||||
end)
|
||||
|
||||
it("handles defaults opt", function()
|
||||
do
|
||||
Config.options.defaults.lazy = true
|
||||
local spec = Plugin.Spec.new({ "foo/bar" })
|
||||
assert(#spec.notifs == 0)
|
||||
Config.plugins = spec.plugins
|
||||
Plugin.update_state()
|
||||
assert(spec.plugins.bar.lazy == true)
|
||||
end
|
||||
do
|
||||
Config.options.defaults.lazy = false
|
||||
local spec = Plugin.Spec.new({ "foo/bar" })
|
||||
Config.plugins = spec.plugins
|
||||
Plugin.update_state()
|
||||
assert(spec.plugins.bar.lazy == false)
|
||||
end
|
||||
end)
|
||||
|
||||
it("handles opt from dep", function()
|
||||
Config.options.defaults.lazy = false
|
||||
local spec = Plugin.Spec.new({ "foo/bar", event = "foo" })
|
||||
assert(#spec.notifs == 0)
|
||||
Config.plugins = spec.plugins
|
||||
Plugin.update_state()
|
||||
assert.same(1, vim.tbl_count(spec.plugins))
|
||||
assert(spec.plugins.bar._.dep ~= true)
|
||||
assert(spec.plugins.bar.lazy == true)
|
||||
end)
|
||||
|
||||
it("merges lazy loaders", function()
|
||||
local tests = {
|
||||
{ { "foo/bar", event = "mod1" }, { "foo/bar", event = "mod2" } },
|
||||
{ { "foo/bar", event = { "mod1" } }, { "foo/bar", event = { "mod2" } } },
|
||||
{ { "foo/bar", event = "mod1" }, { "foo/bar", event = { "mod2" } } },
|
||||
}
|
||||
for _, test in ipairs(tests) do
|
||||
local spec = Plugin.Spec.new(test)
|
||||
assert(#spec.notifs == 0)
|
||||
assert(vim.tbl_count(spec.plugins) == 1)
|
||||
Handler.resolve(spec.plugins.bar)
|
||||
-- vim.print(spec.plugins.bar._.handlers)
|
||||
local events = vim.tbl_keys(spec.plugins.bar._.handlers.event or {})
|
||||
assert(type(events) == "table")
|
||||
assert(#events == 2)
|
||||
assert(vim.tbl_contains(events, "mod1"))
|
||||
assert(vim.tbl_contains(events, "mod2"))
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
it("handles opt from dep", function()
|
||||
Config.options.defaults.lazy = false
|
||||
local spec = Plugin.Spec.new({ "foo/dep1", { "foo/bar", dependencies = { "foo/dep1", "foo/dep2" } } })
|
||||
assert(#spec.notifs == 0)
|
||||
Config.plugins = spec.plugins
|
||||
Plugin.update_state()
|
||||
assert.same(3, vim.tbl_count(spec.plugins))
|
||||
assert(spec.plugins.bar._.dep ~= true)
|
||||
assert(spec.plugins.bar.lazy == false)
|
||||
assert(spec.plugins.dep2._.dep == true)
|
||||
assert(spec.plugins.dep2.lazy == true)
|
||||
assert(spec.plugins.dep1._.dep ~= true)
|
||||
assert(spec.plugins.dep1.lazy == false)
|
||||
end)
|
||||
|
||||
it("handles defaults opt", function()
|
||||
do
|
||||
Config.options.defaults.lazy = true
|
||||
local spec = Plugin.Spec.new({ "foo/bar" })
|
||||
assert(#spec.notifs == 0)
|
||||
Config.plugins = spec.plugins
|
||||
Plugin.update_state()
|
||||
assert(spec.plugins.bar.lazy == true)
|
||||
end
|
||||
do
|
||||
Config.options.defaults.lazy = false
|
||||
local spec = Plugin.Spec.new({ "foo/bar" })
|
||||
Config.plugins = spec.plugins
|
||||
Plugin.update_state()
|
||||
assert(spec.plugins.bar.lazy == false)
|
||||
end
|
||||
end)
|
||||
|
||||
it("handles opt from dep", function()
|
||||
Config.options.defaults.lazy = false
|
||||
local spec = Plugin.Spec.new({ "foo/bar", event = "foo" })
|
||||
assert(#spec.notifs == 0)
|
||||
Config.plugins = spec.plugins
|
||||
Plugin.update_state()
|
||||
assert.same(1, vim.tbl_count(spec.plugins))
|
||||
assert(spec.plugins.bar._.dep ~= true)
|
||||
assert(spec.plugins.bar.lazy == true)
|
||||
end)
|
||||
|
||||
it("merges lazy loaders", function()
|
||||
local tests = {
|
||||
{ { "foo/bar", event = "mod1" }, { "foo/bar", event = "mod2" } },
|
||||
{ { "foo/bar", event = { "mod1" } }, { "foo/bar", event = { "mod2" } } },
|
||||
{ { "foo/bar", event = "mod1" }, { "foo/bar", event = { "mod2" } } },
|
||||
}
|
||||
for _, test in ipairs(tests) do
|
||||
Handler.init()
|
||||
local spec = Plugin.Spec.new(test)
|
||||
assert(#spec.notifs == 0)
|
||||
assert(vim.tbl_count(spec.plugins) == 1)
|
||||
Handler.resolve(spec.plugins.bar)
|
||||
local events = spec.plugins.bar._.handlers.event
|
||||
assert(type(events) == "table")
|
||||
assert(vim.tbl_count(events) == 2)
|
||||
assert(events["mod1"])
|
||||
assert(events["mod2"])
|
||||
end
|
||||
end)
|
||||
|
||||
it("handles disabled", function()
|
||||
local tests = {
|
||||
[{ { "foo/bar" }, { "foo/bar", enabled = false } }] = false,
|
||||
[{ { "foo/bar", enabled = false }, { "foo/bar" } }] = false,
|
||||
[{ { "foo/bar", enabled = false }, { "foo/bar", enabled = true } }] = true,
|
||||
[{ { "foo/bar" }, { "foo/bar", enabled = true } }] = true,
|
||||
}
|
||||
for test, ret in pairs(tests) do
|
||||
local spec = Plugin.Spec.new(test)
|
||||
assert(#spec.notifs == 0)
|
||||
if ret then
|
||||
assert(spec.plugins.bar)
|
||||
assert(not spec.disabled.bar)
|
||||
else
|
||||
assert(not spec.plugins.bar)
|
||||
assert(spec.disabled.bar)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
it("handles the optional keyword", function()
|
||||
local tests = {
|
||||
[{ { "foo/bax" }, { "foo/bar", optional = true, dependencies = "foo/dep1" } }] = false,
|
||||
[{ { "foo/bax", dependencies = "foo/dep1" }, { "foo/bar", optional = true, dependencies = "foo/dep1" } }] = true,
|
||||
}
|
||||
for test, ret in pairs(tests) do
|
||||
local spec = Plugin.Spec.new(test)
|
||||
assert(#spec.notifs == 0)
|
||||
assert(spec.plugins.bax)
|
||||
assert(not spec.plugins.bar)
|
||||
assert(#spec.disabled == 0)
|
||||
if ret then
|
||||
assert(spec.plugins.dep1)
|
||||
else
|
||||
assert(not spec.plugins.opt1)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("plugin opts", function()
|
||||
---@type {spec:LazySpec, opts:table}[]
|
||||
local tests = {
|
||||
{
|
||||
spec = { { "foo/foo", opts = { a = 1, b = 1 } }, { "foo/foo", opts = { a = 2 } } },
|
||||
opts = { a = 2, b = 1 },
|
||||
},
|
||||
{
|
||||
spec = { { "foo/foo", config = { a = 1, b = 1 } }, { "foo/foo", opts = { a = 2 } } },
|
||||
opts = { a = 2, b = 1 },
|
||||
},
|
||||
{
|
||||
spec = { { "foo/foo", opts = { a = 1, b = 1 } }, { "foo/foo", config = { a = 2 } } },
|
||||
opts = { a = 2, b = 1 },
|
||||
},
|
||||
{
|
||||
spec = { { "foo/foo", config = { a = 1, b = 1 } }, { "foo/foo", config = { a = 2 } } },
|
||||
opts = { a = 2, b = 1 },
|
||||
},
|
||||
{
|
||||
spec = { { "foo/foo", config = { a = 1, b = 1 } }, { "foo/foo", config = { a = vim.NIL } } },
|
||||
opts = { b = 1 },
|
||||
},
|
||||
{
|
||||
spec = { { "foo/foo", config = { a = 1, b = 1 } }, { "foo/foo" } },
|
||||
opts = { a = 1, b = 1 },
|
||||
},
|
||||
{
|
||||
spec = { { "foo/foo" }, { "foo/foo" } },
|
||||
opts = {},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test in ipairs(tests) do
|
||||
it("correctly parses opts for " .. inspect(test.spec), function()
|
||||
local spec = Plugin.Spec.new(test.spec)
|
||||
assert(spec.plugins.foo)
|
||||
assert.same(test.opts, Plugin.values(spec.plugins.foo, "opts"))
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
describe("plugin spec", function()
|
||||
it("only includes fragments from enabled plugins", function()
|
||||
local tests = {
|
||||
{
|
||||
spec = {
|
||||
{ "foo/disabled", enabled = false, dependencies = { "foo/bar", opts = { key_disabled = true } } },
|
||||
{ "foo/disabled", dependencies = { "foo/bar", opts = { key_disabled_two = true } } },
|
||||
{ "foo/conditional", cond = false, dependencies = { "foo/bar", opts = { key_cond = true } } },
|
||||
{ "foo/optional", optional = true, dependencies = { "foo/bar", opts = { key_optional = true } } },
|
||||
{ "foo/active", dependencies = { "foo/bar", opts = { key_active = true } } },
|
||||
{
|
||||
"foo/bar",
|
||||
opts = { key = true },
|
||||
},
|
||||
},
|
||||
expected_opts = { key = true, key_active = true },
|
||||
}, -- for now, one test...
|
||||
}
|
||||
for _, test in ipairs(tests) do
|
||||
local spec = Plugin.Spec.new(test.spec)
|
||||
assert(#spec.notifs == 0)
|
||||
assert(vim.tbl_count(spec.plugins) == 2)
|
||||
assert(spec.plugins.active)
|
||||
assert(spec.plugins.bar)
|
||||
assert.same(test.expected_opts, Plugin.values(spec.plugins.bar, "opts"))
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
|
147
tests/core/util_spec.lua
Normal file
147
tests/core/util_spec.lua
Normal file
|
@ -0,0 +1,147 @@
|
|||
local Cache = require("lazy.core.cache")
|
||||
local Helpers = require("tests.helpers")
|
||||
local Util = require("lazy.util")
|
||||
|
||||
describe("util", function()
|
||||
local rtp = vim.opt.rtp:get()
|
||||
before_each(function()
|
||||
---@type vim.Option
|
||||
vim.opt.rtp = rtp
|
||||
for k, v in pairs(package.loaded) do
|
||||
if k:find("^foobar") then
|
||||
package.loaded[k] = nil
|
||||
end
|
||||
end
|
||||
Helpers.fs_rm("")
|
||||
assert(not vim.uv.fs_stat(Helpers.path("")), "fs root should be deleted")
|
||||
end)
|
||||
|
||||
it("lsmod lists all mods in dir", function()
|
||||
vim.opt.rtp:append(Helpers.path(""))
|
||||
local tests = {
|
||||
{
|
||||
root = "lua/foo",
|
||||
mod = "foo",
|
||||
files = { "lua/foo/one.lua", "lua/foo/two.lua", "lua/foo/init.lua" },
|
||||
mods = { "foo.one", "foo.two", "foo" },
|
||||
},
|
||||
{
|
||||
root = "lua/foo",
|
||||
mod = "foo",
|
||||
files = { "lua/foo/one.lua", "lua/foo/two.lua", "lua/foo.lua" },
|
||||
mods = { "foo.one", "foo.two", "foo" },
|
||||
},
|
||||
{
|
||||
root = "lua/foo",
|
||||
mod = "foo",
|
||||
files = { "lua/foo/one.lua", "lua/foo/two.lua" },
|
||||
mods = { "foo.one", "foo.two" },
|
||||
},
|
||||
{
|
||||
root = "lua/load-plugins",
|
||||
mod = "load-plugins",
|
||||
files = { "lua/load-plugins.lua" },
|
||||
mods = { "load-plugins" },
|
||||
},
|
||||
}
|
||||
|
||||
for t, test in ipairs(tests) do
|
||||
local expected = vim.deepcopy(test.mods)
|
||||
table.sort(expected)
|
||||
Helpers.fs_rm("")
|
||||
local files = Helpers.fs_create(test.files)
|
||||
|
||||
-- test with empty cache
|
||||
Cache.reset()
|
||||
local root = Util.find_root(test.mod)
|
||||
assert(root, "no root found for " .. test.mod .. " (test " .. t .. ")")
|
||||
assert.same(Helpers.path(test.root), root)
|
||||
local mods = {}
|
||||
Util.lsmod(test.mod, function(modname, modpath)
|
||||
mods[#mods + 1] = modname
|
||||
end)
|
||||
table.sort(mods)
|
||||
assert.same(expected, mods)
|
||||
|
||||
-- fill the cache
|
||||
Cache.reset()
|
||||
root = Util.find_root(test.mod)
|
||||
assert(root, "no root found for " .. test.mod .. " (test " .. t .. ")")
|
||||
assert.same(Helpers.path(test.root), root)
|
||||
mods = {}
|
||||
Util.lsmod(test.mod, function(modname, modpath)
|
||||
mods[#mods + 1] = modname
|
||||
end)
|
||||
table.sort(mods)
|
||||
assert.same(expected, mods)
|
||||
end
|
||||
end)
|
||||
|
||||
it("find the correct root with dels", function()
|
||||
Cache.reset()
|
||||
vim.opt.rtp:append(Helpers.path("old"))
|
||||
Helpers.fs_create({ "old/lua/foobar/init.lua" })
|
||||
local root = Util.find_root("foobar")
|
||||
assert(root, "foobar root not found")
|
||||
assert.same(Helpers.path("old/lua/foobar"), root)
|
||||
|
||||
Helpers.fs_rm("old")
|
||||
assert(not vim.uv.fs_stat(Helpers.path("old/lua/foobar")), "old/lua/foobar should not exist")
|
||||
|
||||
-- vim.opt.rtp = rtp
|
||||
vim.opt.rtp:append(Helpers.path("new"))
|
||||
Helpers.fs_create({ "new/lua/foobar/init.lua" })
|
||||
root = Util.find_root("foobar")
|
||||
assert(root, "foobar root not found")
|
||||
assert.same(Helpers.path("new/lua/foobar"), root)
|
||||
end)
|
||||
|
||||
it("merges correctly", function()
|
||||
local tests = {
|
||||
{
|
||||
input = { { a = 1 }, { b = 2 } },
|
||||
output = { a = 1, b = 2 },
|
||||
},
|
||||
{
|
||||
input = { nil, { a = 1 }, { b = 2 } },
|
||||
output = { a = 1, b = 2 },
|
||||
},
|
||||
{
|
||||
input = { { a = 1 }, { b = 2 }, nil },
|
||||
output = { a = 1, b = 2 },
|
||||
},
|
||||
{
|
||||
input = { { a = 1 }, nil, { b = 2 } },
|
||||
output = { a = 1, b = 2 },
|
||||
},
|
||||
{
|
||||
input = { nil, { a = 1 }, nil, { b = 2 }, nil },
|
||||
output = { a = 1, b = 2 },
|
||||
},
|
||||
{
|
||||
input = { { a = 1 }, { a = 2 } },
|
||||
output = { a = 2 },
|
||||
},
|
||||
{
|
||||
input = { { a = { 1, 2 } }, { a = { 3 } } },
|
||||
output = { a = { 3 } },
|
||||
},
|
||||
{
|
||||
input = { { b = { 1, 2 } }, { a = { 3 }, b = vim.NIL } },
|
||||
output = { a = { 3 } },
|
||||
},
|
||||
{
|
||||
input = { { a = 1 }, { b = 2, a = vim.NIL } },
|
||||
output = { b = 2 },
|
||||
},
|
||||
}
|
||||
|
||||
for _, test in ipairs(tests) do
|
||||
local n = 0
|
||||
for i in pairs(test.input) do
|
||||
n = math.max(n, i)
|
||||
end
|
||||
assert.same(test.output, Util.merge(unpack(test.input, 1, n)))
|
||||
end
|
||||
end)
|
||||
end)
|
18
tests/handlers/keys_spec.lua
Normal file
18
tests/handlers/keys_spec.lua
Normal file
|
@ -0,0 +1,18 @@
|
|||
local Keys = require("lazy.core.handler.keys")
|
||||
|
||||
describe("keys", function()
|
||||
it("parses ids correctly", function()
|
||||
local tests = {
|
||||
{ "<C-/>", "<c-/>", true },
|
||||
{ "<C-h>", "<c-H>", true },
|
||||
{ "<C-h>k", "<c-H>K", false },
|
||||
}
|
||||
for _, test in ipairs(tests) do
|
||||
if test[3] then
|
||||
assert.same(Keys.parse(test[1]).id, Keys.parse(test[2]).id)
|
||||
else
|
||||
assert.is_not.same(Keys.parse(test[1]).id, Keys.parse(test[2]).id)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end)
|
37
tests/helpers.lua
Normal file
37
tests/helpers.lua
Normal file
|
@ -0,0 +1,37 @@
|
|||
local Util = require("lazy.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
M.fs_root = vim.fn.fnamemodify("./.tests/fs", ":p")
|
||||
|
||||
function M.path(path)
|
||||
return Util.norm(M.fs_root .. "/" .. path)
|
||||
end
|
||||
|
||||
---@param files string[]
|
||||
function M.fs_create(files)
|
||||
---@type string[]
|
||||
local ret = {}
|
||||
|
||||
for _, file in ipairs(files) do
|
||||
ret[#ret + 1] = Util.norm(M.fs_root .. "/" .. file)
|
||||
local parent = vim.fn.fnamemodify(ret[#ret], ":h:p")
|
||||
vim.fn.mkdir(parent, "p")
|
||||
Util.write_file(ret[#ret], "")
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function M.fs_rm(dir)
|
||||
dir = Util.norm(M.fs_root .. "/" .. dir)
|
||||
Util.walk(dir, function(path, _, type)
|
||||
if type == "directory" then
|
||||
vim.uv.fs_rmdir(path)
|
||||
else
|
||||
vim.uv.fs_unlink(path)
|
||||
end
|
||||
end)
|
||||
vim.uv.fs_rmdir(dir)
|
||||
end
|
||||
|
||||
return M
|
|
@ -1,32 +0,0 @@
|
|||
local M = {}
|
||||
|
||||
function M.root(root)
|
||||
local f = debug.getinfo(1, "S").source:sub(2)
|
||||
return vim.fn.fnamemodify(f, ":p:h:h") .. "/" .. (root or "")
|
||||
end
|
||||
|
||||
---@param plugin string
|
||||
function M.load(plugin)
|
||||
local name = plugin:match(".*/(.*)")
|
||||
local package_root = M.root(".tests/site/pack/deps/start/")
|
||||
if not vim.loop.fs_stat(package_root .. name) then
|
||||
print("Installing " .. plugin)
|
||||
vim.fn.mkdir(package_root, "p")
|
||||
vim.fn.system({
|
||||
"git",
|
||||
"clone",
|
||||
"--depth=1",
|
||||
"https://github.com/" .. plugin .. ".git",
|
||||
package_root .. "/" .. name,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function M.setup()
|
||||
vim.cmd([[set runtimepath=$VIMRUNTIME]])
|
||||
vim.opt.runtimepath:append(M.root())
|
||||
vim.opt.packpath = { M.root(".tests/site") }
|
||||
M.load("nvim-lua/plenary.nvim")
|
||||
end
|
||||
|
||||
M.setup()
|
19
tests/manage/process_spec.lua
Normal file
19
tests/manage/process_spec.lua
Normal file
|
@ -0,0 +1,19 @@
|
|||
local Async = require("lazy.async")
|
||||
local Process = require("lazy.manage.process")
|
||||
|
||||
describe("process", function()
|
||||
it("runs sync", function()
|
||||
local lines = Process.exec({ "echo", "-n", "hello" })
|
||||
assert.are.same({ "hello" }, lines)
|
||||
end)
|
||||
|
||||
it("runs sync from async context", function()
|
||||
local lines ---@type string[]
|
||||
local async = Async.new(function()
|
||||
lines = Process.exec({ "echo", "-n", "hello" })
|
||||
end)
|
||||
async:wait()
|
||||
|
||||
assert.are.same({ "hello" }, lines)
|
||||
end)
|
||||
end)
|
|
@ -1,3 +1,4 @@
|
|||
local Async = require("lazy.async")
|
||||
local Runner = require("lazy.manage.runner")
|
||||
|
||||
describe("runner", function()
|
||||
|
@ -30,11 +31,11 @@ describe("runner", function()
|
|||
end,
|
||||
}
|
||||
package.loaded["lazy.manage.task.test"]["async" .. i] = {
|
||||
---@async
|
||||
---@param task LazyTask
|
||||
run = function(task)
|
||||
task:schedule(function()
|
||||
table.insert(runs, { plugin = task.plugin.name, task = task.name })
|
||||
end)
|
||||
Async.yield()
|
||||
table.insert(runs, { plugin = task.plugin.name, task = task.name })
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
@ -64,7 +65,7 @@ describe("runner", function()
|
|||
local runner = Runner.new({ plugins = plugins, pipeline = { "test.test1", "test.skip", "test.test2" } })
|
||||
runner:start()
|
||||
runner:wait()
|
||||
assert.equal(4, #runs)
|
||||
assert.equal(4, #runs, runs)
|
||||
end)
|
||||
|
||||
it("handles opts", function()
|
||||
|
|
|
@ -14,6 +14,7 @@ describe("semver version", function()
|
|||
["1.2.3+build"] = { major = 1, minor = 2, patch = 3, build = "build" },
|
||||
}
|
||||
for input, output in pairs(tests) do
|
||||
output.input = input
|
||||
it("correctly parses " .. input, function()
|
||||
assert.same(output, v(input))
|
||||
end)
|
||||
|
|
|
@ -1,94 +1,82 @@
|
|||
--# selene:allow(incorrect_standard_library_use)
|
||||
local Async = require("lazy.async")
|
||||
local Task = require("lazy.manage.task")
|
||||
|
||||
describe("task", function()
|
||||
local plugin = { name = "test", _ = {} }
|
||||
|
||||
local done = false
|
||||
local error = nil
|
||||
---@type {done?:boolean, error:string?}
|
||||
local task_result = {}
|
||||
|
||||
local opts = {
|
||||
---@param task LazyTask
|
||||
on_done = function(task)
|
||||
done = true
|
||||
error = task.error
|
||||
task_result = { done = true, error = task.error }
|
||||
end,
|
||||
}
|
||||
|
||||
before_each(function()
|
||||
done = false
|
||||
error = nil
|
||||
task_result = {}
|
||||
end)
|
||||
|
||||
it("simple function", function()
|
||||
local task = Task.new(plugin, "test", function() end, opts)
|
||||
assert(not task:has_started())
|
||||
assert(not task:is_running())
|
||||
task:start()
|
||||
assert(not task:is_running())
|
||||
assert(task:is_done())
|
||||
assert(done)
|
||||
assert(task:running())
|
||||
task:wait()
|
||||
assert(not task:running())
|
||||
assert(task_result.done)
|
||||
end)
|
||||
|
||||
it("detects errors", function()
|
||||
local task = Task.new(plugin, "test", function()
|
||||
error("test")
|
||||
end, opts)
|
||||
assert(not task:has_started())
|
||||
assert(not task:is_running())
|
||||
task:start()
|
||||
assert(task:is_done())
|
||||
assert(not task:is_running())
|
||||
assert(done)
|
||||
assert(error)
|
||||
assert(task.error and task.error:find("test"))
|
||||
assert(task:running())
|
||||
task:wait()
|
||||
assert(not task:running())
|
||||
assert(task_result.done)
|
||||
assert(task_result.error)
|
||||
assert(task:has_errors() and task:output(vim.log.levels.ERROR):find("test"))
|
||||
end)
|
||||
|
||||
it("schedule", function()
|
||||
local running = false
|
||||
local task = Task.new(plugin, "test", function(task)
|
||||
running = true
|
||||
task:schedule(function()
|
||||
running = false
|
||||
end)
|
||||
it("async", function()
|
||||
local running = true
|
||||
---@async
|
||||
local task = Task.new(plugin, "test", function()
|
||||
Async.yield()
|
||||
running = false
|
||||
end, opts)
|
||||
assert(not task:is_running())
|
||||
assert(not task:has_started())
|
||||
task:start()
|
||||
assert(task:running())
|
||||
assert(running)
|
||||
assert(#task._running == 1)
|
||||
assert(task:is_running())
|
||||
assert(not task:is_done())
|
||||
assert(task:running())
|
||||
task:wait()
|
||||
assert(task:is_done())
|
||||
assert(not task:is_running())
|
||||
assert(done)
|
||||
assert(not task.error)
|
||||
assert(not running)
|
||||
assert(not task:running())
|
||||
assert(task_result.done)
|
||||
assert(not task:has_errors())
|
||||
end)
|
||||
|
||||
it("spawn errors", function()
|
||||
local task = Task.new(plugin, "test", function(task)
|
||||
local task = Task.new(plugin, "spawn_errors", function(task)
|
||||
task:spawn("foobar")
|
||||
end, opts)
|
||||
assert(not task:is_running())
|
||||
task:start()
|
||||
assert(not task:is_running())
|
||||
assert(done)
|
||||
assert(task.error and task.error:find("Failed to spawn"))
|
||||
assert(task:running())
|
||||
task:wait()
|
||||
assert(not task:running())
|
||||
assert(task_result.done)
|
||||
assert(task:has_errors() and task:output(vim.log.levels.ERROR):find("Failed to spawn"), task:output())
|
||||
end)
|
||||
|
||||
it("spawn", function()
|
||||
local task = Task.new(plugin, "test", function(task)
|
||||
task:spawn("echo", { args = { "foo" } })
|
||||
end, opts)
|
||||
assert(not task:is_running())
|
||||
assert(not task:has_started())
|
||||
task:start()
|
||||
assert(task:has_started())
|
||||
assert(task:is_running())
|
||||
assert(task:running())
|
||||
assert(task:running())
|
||||
task:wait()
|
||||
assert(task:is_done())
|
||||
assert.same(task.output, "foo\n")
|
||||
assert(done)
|
||||
assert(not task.error)
|
||||
assert.same(task:output(), "foo")
|
||||
assert(task_result.done)
|
||||
assert(not task:has_errors())
|
||||
end)
|
||||
|
||||
it("spawn 2x", function()
|
||||
|
@ -96,12 +84,11 @@ describe("task", function()
|
|||
task:spawn("echo", { args = { "foo" } })
|
||||
task:spawn("echo", { args = { "bar" } })
|
||||
end, opts)
|
||||
assert(not task:is_running())
|
||||
task:start()
|
||||
assert(task:is_running())
|
||||
assert(task:running())
|
||||
assert(task:running())
|
||||
task:wait()
|
||||
assert.same(task.output, "foo\nbar\n")
|
||||
assert(done)
|
||||
assert(not task.error)
|
||||
assert(task:output() == "foo\nbar" or task:output() == "bar\nfoo", task:output())
|
||||
assert(task_result.done)
|
||||
assert(not task:has_errors())
|
||||
end)
|
||||
end)
|
||||
|
|
12
tests/minit.lua
Executable file
12
tests/minit.lua
Executable file
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env -S nvim -l
|
||||
|
||||
vim.env.LAZY_STDPATH = ".tests"
|
||||
|
||||
vim.opt.rtp:prepend(".")
|
||||
|
||||
-- Setup lazy.nvim
|
||||
require("lazy.minit").setup({
|
||||
spec = {
|
||||
{ dir = vim.uv.cwd() },
|
||||
},
|
||||
})
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue