mirror of
https://github.com/helix-editor/helix.git
synced 2025-10-06 00:13:28 +02:00
Compare commits
798 Commits
snippet_pl
...
foreground
Author | SHA1 | Date | |
---|---|---|---|
|
2304f4203f | ||
|
a932eb8e53 | ||
|
0ae37dc52b | ||
|
dbb472d4c4 | ||
|
46e3617f6b | ||
|
c0921202a0 | ||
|
ffe513d6aa | ||
|
d015eff4aa | ||
|
a4d780483b | ||
|
ea2541f424 | ||
|
69713975c6 | ||
|
2dddace12e | ||
|
66737c6a4f | ||
|
fbf6407ab3 | ||
|
1f020b1d72 | ||
|
9acfa51209 | ||
|
2d7436fc20 | ||
|
1388166570 | ||
|
342f2a982d | ||
|
5bfc7bfc07 | ||
|
5609e3b3c2 | ||
|
c614467be8 | ||
|
c531b7a4fa | ||
|
92b0a2f414 | ||
|
378b27cad9 | ||
|
209558645a | ||
|
987b04bd26 | ||
|
322bb1c189 | ||
|
34e0f7e82f | ||
|
97293c9f36 | ||
|
71038266e8 | ||
|
05a99c2cca | ||
|
14030d0b63 | ||
|
ff376b0d1a | ||
|
d25726f573 | ||
|
8f2af68b30 | ||
|
fe8e21a07f | ||
|
d0218f7e78 | ||
|
e2333f60ae | ||
|
70187c430c | ||
|
8058fefef0 | ||
|
0928e5ea1c | ||
|
b391185716 | ||
|
f59dc9e48f | ||
|
d63c2d2fea | ||
|
0a4207be32 | ||
|
3adc021c06 | ||
|
d1750a7502 | ||
|
c5f0a4bc22 | ||
|
4967229e85 | ||
|
68f11f9324 | ||
|
af74a61ad4 | ||
|
cfb5158cd1 | ||
|
e3fafb6bad | ||
|
6e9939a2d1 | ||
|
b08aba8e8e | ||
|
83abbe56df | ||
|
9cc912a63e | ||
|
fe1393cec8 | ||
|
392e444ff9 | ||
|
0ea5d87985 | ||
|
6b73c3c550 | ||
|
b309d72688 | ||
|
d546a799e5 | ||
|
7c37e8acea | ||
|
d4c91daa5e | ||
|
dc7c2acc08 | ||
|
99cea8c284 | ||
|
077c901be9 | ||
|
a5bf7c0d5e | ||
|
8ab20720da | ||
|
feeaec097a | ||
|
4f5eaa4186 | ||
|
7a5b618fe5 | ||
|
77ff51caa4 | ||
|
7e4e556f84 | ||
|
96c60198ec | ||
|
3dadd82c89 | ||
|
5a8fb732f2 | ||
|
8671882ee2 | ||
|
1d3e65fdbc | ||
|
f81b59fc15 | ||
|
cc8e890906 | ||
|
aa14cd38fc | ||
|
22a3b10dd8 | ||
|
535e6ee77b | ||
|
4b40b45527 | ||
|
95c378a764 | ||
|
74bb02ffe7 | ||
|
b81ee02db4 | ||
|
9ec07cf1f6 | ||
|
9f34f8b5ff | ||
|
3fb1443162 | ||
|
207c0e3899 | ||
|
f9f5fe6b12 | ||
|
e5e7fe43ce | ||
|
da4ede9535 | ||
|
6f26a257d5 | ||
|
b1eb9c09f4 | ||
|
b6ccbddc77 | ||
|
a4a2b50a50 | ||
|
e5d1f6c517 | ||
|
050e1d9b47 | ||
|
2b9cc20d23 | ||
|
9e3b510dc7 | ||
|
6b93c77033 | ||
|
fdaec3f972 | ||
|
8a898c88bc | ||
|
e0544d01f1 | ||
|
001efa801e | ||
|
00dbca93c4 | ||
|
6726c1f41c | ||
|
7747d3b93e | ||
|
4d0466d30c | ||
|
ed2807ae07 | ||
|
327f3852f4 | ||
|
155fde5178 | ||
|
f5a399c7f9 | ||
|
cb7188d5cc | ||
|
a44695e4e8 | ||
|
ef3a49d03c | ||
|
56fa9bf7c1 | ||
|
b5a9c34e14 | ||
|
e8e36a6a8e | ||
|
18572973e6 | ||
|
4a25f63169 | ||
|
0345400c41 | ||
|
43990ed0c8 | ||
|
178c55708a | ||
|
6c0d598183 | ||
|
5b5f6daab3 | ||
|
601c904e50 | ||
|
93cf3b1baf | ||
|
fdfc6df122 | ||
|
d2595930fa | ||
|
758f80a4fc | ||
|
e58b08d22a | ||
|
62f3cd3f5a | ||
|
39cccc23e5 | ||
|
f0be627dcb | ||
|
1bdd8ae784 | ||
|
2d5a19f081 | ||
|
d9fe4798fa | ||
|
39eec87284 | ||
|
285a7440a3 | ||
|
2f7cc9d0ae | ||
|
ca4f638dfd | ||
|
4480da752c | ||
|
1f7b593857 | ||
|
6807e32ec1 | ||
|
54e748b0ce | ||
|
c8224bcf4e | ||
|
1941f0b639 | ||
|
e17b80a5a2 | ||
|
6dc4722665 | ||
|
6ea3677b9f | ||
|
fe2291a59b | ||
|
a789ec7f4b | ||
|
6b511964bb | ||
|
ddbac29d14 | ||
|
f4557d0bff | ||
|
6479f74a57 | ||
|
27c90b7fff | ||
|
9ea190b729 | ||
|
c2782568f1 | ||
|
8dbc664a30 | ||
|
f72b6f758b | ||
|
4fd4588482 | ||
|
94c96cfe0e | ||
|
44b5413716 | ||
|
e15134beac | ||
|
22e60d6a71 | ||
|
8297d60ca0 | ||
|
4281228da3 | ||
|
395a71bf53 | ||
|
1e4bf6704a | ||
|
b01fbb4a22 | ||
|
f75a26cb9b | ||
|
21ae1c98fb | ||
|
7b8a4b7a51 | ||
|
715d4ae2d5 | ||
|
22b184b570 | ||
|
665ee4da22 | ||
|
ecd18e3eb2 | ||
|
e7f95ca6b2 | ||
|
4418e338e8 | ||
|
6c71fc00b2 | ||
|
727758e068 | ||
|
63eb1b870c | ||
|
2d5826d194 | ||
|
9f4ef2fc3d | ||
|
fd8aacc1a4 | ||
|
2ee11a0a9d | ||
|
9512cb9472 | ||
|
3658e97c2b | ||
|
ab668c2dfc | ||
|
ef2ebc5f24 | ||
|
5cda70e866 | ||
|
c67c3faa78 | ||
|
6fd1efd1c2 | ||
|
86f10ae24c | ||
|
d2f37b1559 | ||
|
e844a4365d | ||
|
ca7479ca88 | ||
|
7e1fbb05fd | ||
|
2f560914fb | ||
|
636cbe58e3 | ||
|
43187f2ed3 | ||
|
532f241287 | ||
|
ba04f53830 | ||
|
242353b2ba | ||
|
de898460b8 | ||
|
8e0f326ebb | ||
|
16d06643a4 | ||
|
9447a9cc93 | ||
|
febc3d03b3 | ||
|
06047808eb | ||
|
02fe437622 | ||
|
e88e48f41c | ||
|
fc53af9f4e | ||
|
3e5bb392fa | ||
|
479c3b5584 | ||
|
9789b27461 | ||
|
6c6607ef62 | ||
|
bcb6c20a84 | ||
|
f7ab5ec4a1 | ||
|
6a090471a8 | ||
|
6081a5df81 | ||
|
0043c16506 | ||
|
e5f9937c1d | ||
|
91dff9393d | ||
|
0ca12250bc | ||
|
4d782bbd18 | ||
|
b036fa0b9b | ||
|
0d799235f6 | ||
|
305f8bc165 | ||
|
e03f100187 | ||
|
f75d71844f | ||
|
d654a07d3d | ||
|
930340e646 | ||
|
44293dfd22 | ||
|
a9d51ef258 | ||
|
b9f980f567 | ||
|
c3c4895179 | ||
|
974ac9eaf3 | ||
|
60fce357fb | ||
|
4f985832bf | ||
|
43963473e3 | ||
|
2338b44909 | ||
|
58dfa158c2 | ||
|
171dfc60e5 | ||
|
250af462cd | ||
|
40a3fb9b92 | ||
|
c96642125f | ||
|
4a85171907 | ||
|
472a27e4f2 | ||
|
036729211a | ||
|
d3fb8fc9b8 | ||
|
684e108fd0 | ||
|
3c6c221d45 | ||
|
6b94d70f20 | ||
|
1491cbc8f3 | ||
|
e11794be37 | ||
|
fba644f2b4 | ||
|
24fe989596 | ||
|
fed3edcab7 | ||
|
4099465632 | ||
|
9100bce9aa | ||
|
f5dc8245ea | ||
|
362e97e927 | ||
|
ba54b6afe4 | ||
|
837627dd8a | ||
|
1246549afd | ||
|
ada8004ea5 | ||
|
205e7ece70 | ||
|
1315b7e2b1 | ||
|
52192ae29e | ||
|
fba1a6188a | ||
|
b90d8960a8 | ||
|
6e4ec96101 | ||
|
62f270e5d2 | ||
|
3b7aaddb13 | ||
|
ab97585b69 | ||
|
9dbfb9b4eb | ||
|
091f19f67c | ||
|
ae3eac8aeb | ||
|
fbe9785613 | ||
|
7092e30f8d | ||
|
705d467932 | ||
|
05a4d05646 | ||
|
2b26d27416 | ||
|
e773d6cc92 | ||
|
633c5fbf0f | ||
|
b75e95862c | ||
|
f4b488e380 | ||
|
7410fe35a3 | ||
|
637274c4d4 | ||
|
01341cbbf6 | ||
|
b1f4717356 | ||
|
25b299abc5 | ||
|
4dd4ba798c | ||
|
ca4ae7f287 | ||
|
d375f1e7f4 | ||
|
8d2870b94a | ||
|
f6878f62f7 | ||
|
6c43dc4962 | ||
|
1ea9050a5e | ||
|
2baff46b25 | ||
|
921ca08e1b | ||
|
17fb12bcf3 | ||
|
67f1fe20c3 | ||
|
8961ae1dc6 | ||
|
3366db0afb | ||
|
733ebcdaeb | ||
|
2bd7452fe0 | ||
|
7dcddf98c6 | ||
|
c9e7b0f84f | ||
|
2fbe7fc5b5 | ||
|
12523cd126 | ||
|
8d58f6ce8d | ||
|
702a961517 | ||
|
1023e8f964 | ||
|
223ceec10a | ||
|
cb1ec1b27e | ||
|
4098151591 | ||
|
1edf98262c | ||
|
237d875e7d | ||
|
b70b8df916 | ||
|
ae0dd313bd | ||
|
76029e5840 | ||
|
3a6c9747b8 | ||
|
ebf96bd469 | ||
|
5a1dcc2429 | ||
|
be1bf2f909 | ||
|
05ae617e1c | ||
|
e606652a96 | ||
|
df02ef6a99 | ||
|
3ceae88c3a | ||
|
b4e51ef895 | ||
|
f157a918a3 | ||
|
a7c3a43069 | ||
|
702b1d0a0f | ||
|
b0528bbac4 | ||
|
09bc67ad6d | ||
|
6be38642f4 | ||
|
f46222ced3 | ||
|
9bb80a74e1 | ||
|
be1cf090c3 | ||
|
a3b64b6da2 | ||
|
aea53523dd | ||
|
24e3ccc31b | ||
|
c94fde8d1c | ||
|
84e95d35ee | ||
|
447a6d3299 | ||
|
47547e94ad | ||
|
908b9edf28 | ||
|
63a1a94d92 | ||
|
bfd2c72715 | ||
|
4c8600967c | ||
|
313ef30f64 | ||
|
9bb370c91e | ||
|
cb1ecc9128 | ||
|
7a6bc53528 | ||
|
60a03a35c6 | ||
|
e4ef096945 | ||
|
9e7a6a5dbd | ||
|
1460a086df | ||
|
51d3b15557 | ||
|
e53462c78c | ||
|
fb45017a26 | ||
|
cbac427383 | ||
|
46cb177792 | ||
|
4784650ccf | ||
|
ece12dd74d | ||
|
72932a391b | ||
|
4c630c148a | ||
|
ac3c6ebaff | ||
|
12139a4c30 | ||
|
aa3fad84ef | ||
|
69b9db2fbb | ||
|
1c32fb2d4d | ||
|
b42e1d20d2 | ||
|
949d9e4433 | ||
|
fbc4e5798e | ||
|
47cdd23e50 | ||
|
9f3b193743 | ||
|
99b57181d5 | ||
|
448e3c2ef5 | ||
|
95c8d9c9e9 | ||
|
8580d35de9 | ||
|
c5e9dda269 | ||
|
01f7e636d5 | ||
|
2ec59f8ff6 | ||
|
23e16264b3 | ||
|
0354ceb95f | ||
|
626407395a | ||
|
52d4d775ce | ||
|
0815b52e09 | ||
|
523e8aa781 | ||
|
3bfec18a45 | ||
|
0f1af75f76 | ||
|
37b5d8ba99 | ||
|
795040910b | ||
|
0ca01a9649 | ||
|
8f30f39c6a | ||
|
31cc2110ec | ||
|
bce166290a | ||
|
b1345b302d | ||
|
340934db92 | ||
|
d0275a554a | ||
|
4a7939928e | ||
|
8b952bb1d5 | ||
|
d24e4fcf0f | ||
|
34aa4d41c6 | ||
|
032c7b897d | ||
|
5d16aae58e | ||
|
a799794623 | ||
|
0609b06638 | ||
|
4130b162a7 | ||
|
46f7cdb5a9 | ||
|
9cfb8afa99 | ||
|
29789f2a9f | ||
|
efdcf34b79 | ||
|
e9a3dcd858 | ||
|
effe849cf4 | ||
|
130f725026 | ||
|
42de785779 | ||
|
6c42ed1bd5 | ||
|
5b72b59448 | ||
|
1bc45c8b3a | ||
|
f857a98671 | ||
|
5a671e65fd | ||
|
994b750dd4 | ||
|
1fc19c6d8e | ||
|
d0c5a2044d | ||
|
2bb0d52f3e | ||
|
7ebf650029 | ||
|
db187c4870 | ||
|
e148d8b311 | ||
|
fb815e2c6f | ||
|
e735485277 | ||
|
bb96a535fc | ||
|
01fce51c45 | ||
|
7929c0719d | ||
|
68d7308e25 | ||
|
a2c580c4ae | ||
|
cf7eb5517f | ||
|
388a3b78e3 | ||
|
d43de14807 | ||
|
04d1180a0c | ||
|
5adb4b7413 | ||
|
13b2dc31f5 | ||
|
0ee5850016 | ||
|
8ff544757f | ||
|
f07c1c1b29 | ||
|
8ad6e53b1f | ||
|
6bedca8064 | ||
|
7e7a98560e | ||
|
6da1a79d80 | ||
|
2cc33b5c47 | ||
|
683fac65e7 | ||
|
2d4c2a170c | ||
|
14cab4ba62 | ||
|
3a63e85b6a | ||
|
f6cb90593d | ||
|
1c9a5bd366 | ||
|
1dee64f7ec | ||
|
b7d735ffe6 | ||
|
7ca916ab73 | ||
|
8e65077065 | ||
|
d6cacb2731 | ||
|
ccf9564123 | ||
|
e7c82a34a5 | ||
|
33c17d48ff | ||
|
6f463dbeb3 | ||
|
70a60efcbe | ||
|
9d31e4df11 | ||
|
27ca9d2c33 | ||
|
e56d3abb0a | ||
|
9574e551cf | ||
|
44bddf51b7 | ||
|
b47c9da3a1 | ||
|
fdaf12a35d | ||
|
0d84bd563c | ||
|
1bd7a3901c | ||
|
694b61514f | ||
|
7f416704b1 | ||
|
430ce9c46b | ||
|
d1e0891260 | ||
|
e74956fa4d | ||
|
8d590e8aee | ||
|
63ed85bc62 | ||
|
9bd3cecd49 | ||
|
8df58b2e17 | ||
|
f9360fb27e | ||
|
88a254d8bf | ||
|
c5c9e65cc4 | ||
|
9db6c534a3 | ||
|
ff558f9105 | ||
|
b38eae1f98 | ||
|
67879a1e5b | ||
|
aa20eb8e7f | ||
|
ee9db440ce | ||
|
296eb9be83 | ||
|
dc4761ad3a | ||
|
2d3b75a8c5 | ||
|
8da226f0b4 | ||
|
fab08c0981 | ||
|
b6e58c0fa4 | ||
|
19558839b7 | ||
|
c4d314d7ba | ||
|
b423ed42f1 | ||
|
a3fa65880e | ||
|
b1ee4ab5c6 | ||
|
fbc0f956b3 | ||
|
486f4297b7 | ||
|
28e69f09fc | ||
|
ab56f9e26b | ||
|
1d453785e5 | ||
|
671a6036b3 | ||
|
82f8ac208f | ||
|
9440feae7c | ||
|
1a28999002 | ||
|
0efa8207d8 | ||
|
e1c7a1ed77 | ||
|
7bebe0a70e | ||
|
682967d328 | ||
|
1e8774a030 | ||
|
c36408457a | ||
|
1dd8a19ad6 | ||
|
43eab10a4c | ||
|
83d4ca41cc | ||
|
534d0907d3 | ||
|
bb3af143f1 | ||
|
26cb3c20e7 | ||
|
69f25a85da | ||
|
8cb0d869e6 | ||
|
c98302a543 | ||
|
0ba2e05a6f | ||
|
e1060a2785 | ||
|
fcddd50325 | ||
|
35575b0b0f | ||
|
7e87a36e93 | ||
|
6182bdc860 | ||
|
e1c26ebfc7 | ||
|
3683cd9ea3 | ||
|
0deb8bbce6 | ||
|
3d7e2730e7 | ||
|
6304e7b2a7 | ||
|
d031260180 | ||
|
e0da129727 | ||
|
b8912adbbf | ||
|
1c0b36b1b4 | ||
|
e35d420199 | ||
|
48194825b9 | ||
|
82f07fe6d1 | ||
|
1c47aec30c | ||
|
ef375d690e | ||
|
0445062d27 | ||
|
46728046fd | ||
|
7275b7f850 | ||
|
ed3bc2b294 | ||
|
3ccf8d58de | ||
|
efb44e0b22 | ||
|
144a4f402f | ||
|
df752bbd45 | ||
|
d8c4c7c26f | ||
|
c3e9a0d607 | ||
|
258e3e1136 | ||
|
5a66270c00 | ||
|
6aa82bb3f8 | ||
|
518d054fcb | ||
|
35faa73be1 | ||
|
7a3470c48d | ||
|
199dc05a04 | ||
|
5e2501da30 | ||
|
a03becf021 | ||
|
a6f94e04e6 | ||
|
2197b3cfa0 | ||
|
a19c95a0a7 | ||
|
ff012e844f | ||
|
fcfa70e66c | ||
|
1b89f998e8 | ||
|
c36ed6ad92 | ||
|
18b9eb9e06 | ||
|
b0e1eaf50d | ||
|
a36730cb21 | ||
|
75abc23428 | ||
|
313a6479b1 | ||
|
62625eda46 | ||
|
16ff06370f | ||
|
ee33a84489 | ||
|
1258111394 | ||
|
26db54155e | ||
|
d456377821 | ||
|
d0d16931e3 | ||
|
8995ccaae2 | ||
|
066e938ba0 | ||
|
e882a750ea | ||
|
ebdab86ce6 | ||
|
ab6a92ed49 | ||
|
e22bbf5489 | ||
|
0ab403d428 | ||
|
b8bfc44e42 | ||
|
5952d564d1 | ||
|
382401020c | ||
|
93fa990e56 | ||
|
70d452db3e | ||
|
369f2bb93d | ||
|
0f594c35f2 | ||
|
de11273857 | ||
|
5c1f3f814f | ||
|
4e0fc0efc6 | ||
|
1ab35ade2d | ||
|
018081a5b1 | ||
|
f0fa905622 | ||
|
5532ef35d9 | ||
|
0ea401d2d7 | ||
|
e70f8833e2 | ||
|
30616344d7 | ||
|
17ffa38a5a | ||
|
c3620b7116 | ||
|
e9c16b7fc5 | ||
|
8439ce5683 | ||
|
6edff24c81 | ||
|
47f84d04ea | ||
|
2367b20318 | ||
|
28047fed7f | ||
|
025719c1d8 | ||
|
80dbe030a1 | ||
|
6906164177 | ||
|
d285a8a9e5 | ||
|
1a821ac726 | ||
|
f7394d53fd | ||
|
ba116b47a0 | ||
|
c9dc940428 | ||
|
8328c422b7 | ||
|
6049f2035b | ||
|
8d6efaf350 | ||
|
98ddbf0086 | ||
|
0c8f0c0334 | ||
|
fec5101a41 | ||
|
9bc63c1c59 | ||
|
20151a5594 | ||
|
51832b02c9 | ||
|
39b72329b4 | ||
|
0364521dca | ||
|
f5f9f499cf | ||
|
b00b475dfe | ||
|
9829ac0c02 | ||
|
7c907e66f4 | ||
|
259be07f05 | ||
|
360c6bb061 | ||
|
4919058e90 | ||
|
23b424a46d | ||
|
aac0ce5fd1 | ||
|
899afad4a6 | ||
|
3fdd98979c | ||
|
de738bac6a | ||
|
81708b70e6 | ||
|
8bf9adf7b6 | ||
|
9088f8a599 | ||
|
a63a2ad281 | ||
|
4ded712dbd | ||
|
151caeacc6 | ||
|
d4ade40983 | ||
|
0b9701e899 | ||
|
9d6ea773e9 | ||
|
6b044aeb29 | ||
|
8af33108f6 | ||
|
1afa63d457 | ||
|
5f62c5c24c | ||
|
fa27ae16a7 | ||
|
8986f8b953 | ||
|
650af50c13 | ||
|
c1d382a532 | ||
|
168b11e091 | ||
|
f70923c448 | ||
|
8f1585a097 | ||
|
0d5f6f04c9 | ||
|
122bbea7cf | ||
|
088ba58af5 | ||
|
ce348d84f6 | ||
|
def6139abd | ||
|
cf7b36f0bf | ||
|
c81e0136c5 | ||
|
dca235c5c8 | ||
|
fcf981bbd7 | ||
|
d123193902 | ||
|
430414979d | ||
|
6593969f8d | ||
|
cb0f201d0e | ||
|
032dadaf37 | ||
|
7dea2b0ddd | ||
|
76a8682c4d | ||
|
060255344c | ||
|
09b2f6ab5f | ||
|
64aca8b350 | ||
|
ccdb710431 | ||
|
ba4793fca0 | ||
|
e7ac2fcdec | ||
|
7cc93eb1c5 | ||
|
2c09a35ccf | ||
|
954c97f2b5 | ||
|
076d8bd173 | ||
|
343397391f | ||
|
69068770c8 | ||
|
4c41c5250c | ||
|
ffdfb59033 | ||
|
d4ee22b483 | ||
|
29dda1403f | ||
|
19f7bc9ecb | ||
|
3318953bf6 | ||
|
4bd17e542e | ||
|
99d33c741a | ||
|
ca19496eed | ||
|
f69659c5be | ||
|
27bb2447db | ||
|
3d772afc8b | ||
|
60bff8feee | ||
|
134aebf8cc | ||
|
367ccc1c64 | ||
|
e01775a667 | ||
|
0f2ce303c5 | ||
|
b05971f178 | ||
|
a539199666 | ||
|
e440e54e79 | ||
|
8f5f818c88 | ||
|
b26903cd13 | ||
|
9721144e03 | ||
|
a83c23bb03 | ||
|
931dd9c1dc | ||
|
917174e546 | ||
|
a0bd39d40e | ||
|
e698b20245 | ||
|
5616f1d66d | ||
|
217818681e | ||
|
03f35af9c1 | ||
|
6c9619d094 | ||
|
e856dde738 | ||
|
f80ae997f2 | ||
|
64b38d1a28 | ||
|
377e36908a | ||
|
fa4aa0fb42 | ||
|
2b8f8df1af | ||
|
eed052e86b | ||
|
0654a1f058 | ||
|
353176082e | ||
|
b47b946c47 | ||
|
827deba74c | ||
|
dabfb6ceea | ||
|
9dcc1f06b0 | ||
|
eaa7c8e9f6 | ||
|
4817bfa003 | ||
|
e0bccd2c58 | ||
|
38e8382b01 | ||
|
c9cc14728f | ||
|
b1759f998d | ||
|
4a59f68a0d | ||
|
2b4a77b9bf | ||
|
41763b4851 | ||
|
bbeb99dc40 | ||
|
9cc056e755 | ||
|
6d07ae4f07 | ||
|
073efe48f9 | ||
|
127567df8e | ||
|
4b288e4de7 | ||
|
19c91dfb80 | ||
|
a5a7cff311 | ||
|
7b9b9329b9 | ||
|
c262fe41ab | ||
|
a074129f9c | ||
|
eda96cc285 | ||
|
c63616b421 | ||
|
faf0eee3c9 | ||
|
a2b241eb08 | ||
|
0fd4a4aae2 | ||
|
9e77c44b38 | ||
|
ea17b9edb7 | ||
|
4f63a46e14 | ||
|
ac4c017165 | ||
|
b946b21b01 | ||
|
ba6e6dc3dd | ||
|
a91263d604 | ||
|
06d0f33c94 | ||
|
eaff0c3cd6 | ||
|
1e9412269a | ||
|
355e381626 | ||
|
cbc06d1f15 | ||
|
9e4da4b950 | ||
|
13e5a2ee5a | ||
|
0134bb7063 | ||
|
ec65cc4913 | ||
|
91a5d407da | ||
|
6eb186eb7b | ||
|
1980bd5992 | ||
|
cc3b77b584 | ||
|
fcded6ce1e |
3
.envrc
3
.envrc
@@ -1,7 +1,8 @@
|
||||
watch_file shell.nix
|
||||
watch_file default.nix
|
||||
watch_file flake.lock
|
||||
watch_file rust-toolchain.toml
|
||||
|
||||
# try to use flakes, if it fails use normal nix (ie. shell.nix)
|
||||
use flake || use nix
|
||||
eval "$shellHook"
|
||||
eval "$shellHook"
|
||||
|
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -8,4 +8,5 @@
|
||||
*.md text diff=markdown
|
||||
|
||||
book/theme/highlight.js linguist-vendored
|
||||
runtime/queries/**/*.scm linguist-language=Tree-sitter-Query
|
||||
Cargo.lock text
|
||||
|
4
.github/ISSUE_TEMPLATE/blank_issue.md
vendored
4
.github/ISSUE_TEMPLATE/blank_issue.md
vendored
@@ -1,4 +0,0 @@
|
||||
---
|
||||
name: Blank Issue
|
||||
about: Create a blank issue.
|
||||
---
|
3
.github/dependabot.yml
vendored
3
.github/dependabot.yml
vendored
@@ -8,9 +8,6 @@ updates:
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
groups:
|
||||
tree-sitter:
|
||||
patterns:
|
||||
- "tree-sitter*"
|
||||
rust-dependencies:
|
||||
update-types:
|
||||
- "minor"
|
||||
|
42
.github/workflows/build.yml
vendored
42
.github/workflows/build.yml
vendored
@@ -9,7 +9,9 @@ on:
|
||||
- cron: "00 01 * * *"
|
||||
|
||||
env:
|
||||
MSRV: "1.76"
|
||||
MSRV: "1.82"
|
||||
# This key can be changed to bust the cache of tree-sitter grammars.
|
||||
GRAMMAR_CACHE_VERSION: ""
|
||||
|
||||
jobs:
|
||||
check:
|
||||
@@ -18,7 +20,7 @@ jobs:
|
||||
if: github.repository == 'helix-editor/helix' || github.event_name != 'schedule'
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install MSRV toolchain
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
@@ -29,6 +31,13 @@ jobs:
|
||||
with:
|
||||
shared-key: "build"
|
||||
|
||||
- name: Cache tree-sitter grammars
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: runtime/grammars
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-${{ hashFiles('languages.toml') }}
|
||||
restore-keys: ${{ runner.os }}-${{ runner.arch }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-
|
||||
|
||||
- name: Run cargo check
|
||||
run: cargo check
|
||||
|
||||
@@ -36,12 +45,13 @@ jobs:
|
||||
name: Test Suite
|
||||
runs-on: ${{ matrix.os }}
|
||||
if: github.repository == 'helix-editor/helix' || github.event_name != 'schedule'
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
HELIX_LOG_LEVEL: info
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install MSRV toolchain
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
@@ -52,12 +62,12 @@ jobs:
|
||||
with:
|
||||
shared-key: "build"
|
||||
|
||||
- name: Cache test tree-sitter grammar
|
||||
- name: Cache tree-sitter grammars
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: runtime/grammars
|
||||
key: ${{ runner.os }}-stable-v${{ env.CACHE_VERSION }}-tree-sitter-grammars-${{ hashFiles('languages.toml') }}
|
||||
restore-keys: ${{ runner.os }}-stable-v${{ env.CACHE_VERSION }}-tree-sitter-grammars-
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-${{ hashFiles('languages.toml') }}
|
||||
restore-keys: ${{ runner.os }}-${{ runner.arch }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-
|
||||
|
||||
- name: Run cargo test
|
||||
run: cargo test --workspace
|
||||
@@ -67,7 +77,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest, ubuntu-24.04-arm]
|
||||
|
||||
lints:
|
||||
name: Lints
|
||||
@@ -75,7 +85,7 @@ jobs:
|
||||
if: github.repository == 'helix-editor/helix' || github.event_name != 'schedule'
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install MSRV toolchain
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
@@ -87,6 +97,13 @@ jobs:
|
||||
with:
|
||||
shared-key: "build"
|
||||
|
||||
- name: Cache tree-sitter grammars
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: runtime/grammars
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-${{ hashFiles('languages.toml') }}
|
||||
restore-keys: ${{ runner.os }}-${{ runner.arch }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-
|
||||
|
||||
- name: Run cargo fmt
|
||||
run: cargo fmt --all --check
|
||||
|
||||
@@ -104,7 +121,7 @@ jobs:
|
||||
if: github.repository == 'helix-editor/helix' || github.event_name != 'schedule'
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install MSRV toolchain
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
@@ -115,6 +132,13 @@ jobs:
|
||||
with:
|
||||
shared-key: "build"
|
||||
|
||||
- name: Cache tree-sitter grammars
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: runtime/grammars
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-${{ hashFiles('languages.toml') }}
|
||||
restore-keys: ${{ runner.os }}-${{ runner.arch }}-stable-v${{ env.GRAMMAR_CACHE_VERSION }}-tree-sitter-grammars-
|
||||
|
||||
- name: Validate queries
|
||||
run: cargo xtask query-check
|
||||
|
||||
|
6
.github/workflows/cachix.yml
vendored
6
.github/workflows/cachix.yml
vendored
@@ -11,13 +11,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install nix
|
||||
uses: cachix/install-nix-action@v30
|
||||
uses: cachix/install-nix-action@v31
|
||||
|
||||
- name: Authenticate with Cachix
|
||||
uses: cachix/cachix-action@v15
|
||||
uses: cachix/cachix-action@v16
|
||||
with:
|
||||
name: helix
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
|
4
.github/workflows/gh-pages.yml
vendored
4
.github/workflows/gh-pages.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Github Pages
|
||||
name: GitHub Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup mdBook
|
||||
uses: peaceiris/actions-mdbook@v2
|
||||
|
72
.github/workflows/release.yml
vendored
72
.github/workflows/release.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install stable toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -58,23 +58,21 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false # don't fail other jobs if one fails
|
||||
matrix:
|
||||
build: [x86_64-linux, x86_64-macos, x86_64-windows] #, x86_64-win-gnu, win32-msvc
|
||||
build: [x86_64-linux, aarch64-linux, x86_64-macos, x86_64-windows, aarch64-macos] #, x86_64-win-gnu, win32-msvc
|
||||
include:
|
||||
- build: x86_64-linux
|
||||
os: ubuntu-latest
|
||||
# WARN: When changing this to a newer version, make sure that the GLIBC isnt too new, as this can cause issues
|
||||
# with portablity on older systems that dont follow ubuntus more rapid release cadence.
|
||||
os: ubuntu-22.04
|
||||
rust: stable
|
||||
target: x86_64-unknown-linux-gnu
|
||||
cross: false
|
||||
- build: aarch64-linux
|
||||
os: ubuntu-latest
|
||||
# Version should be kept in lockstep with the x86_64 version
|
||||
os: ubuntu-22.04-arm
|
||||
rust: stable
|
||||
target: aarch64-unknown-linux-gnu
|
||||
cross: true
|
||||
# - build: riscv64-linux
|
||||
# os: ubuntu-latest
|
||||
# rust: stable
|
||||
# target: riscv64gc-unknown-linux-gnu
|
||||
# cross: true
|
||||
cross: false
|
||||
- build: x86_64-macos
|
||||
os: macos-latest
|
||||
rust: stable
|
||||
@@ -85,13 +83,16 @@ jobs:
|
||||
rust: stable
|
||||
target: x86_64-pc-windows-msvc
|
||||
cross: false
|
||||
# 23.03: build issues
|
||||
- build: aarch64-macos
|
||||
os: macos-latest
|
||||
rust: stable
|
||||
target: aarch64-apple-darwin
|
||||
cross: false
|
||||
skip_tests: true # x86_64 host can't run aarch64 code
|
||||
# - build: riscv64-linux
|
||||
# os: ubuntu-22.04
|
||||
# rust: stable
|
||||
# target: riscv64gc-unknown-linux-gnu
|
||||
# cross: true
|
||||
# - build: x86_64-win-gnu
|
||||
# os: windows-2019
|
||||
# rust: stable-x86_64-gnu
|
||||
@@ -103,16 +104,16 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Download grammars
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
|
||||
- name: Move grammars under runtime
|
||||
if: "!startsWith(matrix.os, 'windows')"
|
||||
run: |
|
||||
mkdir -p runtime/grammars/sources
|
||||
tar xJf grammars/grammars.tar.xz -C runtime/grammars/sources
|
||||
tar xJf grammars.tar.xz -C runtime/grammars/sources
|
||||
|
||||
# The rust-toolchain action ignores rust-toolchain.toml files.
|
||||
# Removing this before building with cargo ensures that the rust-toolchain
|
||||
@@ -147,16 +148,8 @@ jobs:
|
||||
if: "!matrix.skip_tests"
|
||||
run: ${{ env.CARGO }} test --release --locked --target ${{ matrix.target }} --workspace
|
||||
|
||||
- name: Set profile.release.strip = true
|
||||
shell: bash
|
||||
run: |
|
||||
cat >> .cargo/config.toml <<EOF
|
||||
[profile.release]
|
||||
strip = true
|
||||
EOF
|
||||
|
||||
- name: Build release binary
|
||||
run: ${{ env.CARGO }} build --release --locked --target ${{ matrix.target }}
|
||||
run: ${{ env.CARGO }} build --profile opt --locked --target ${{ matrix.target }}
|
||||
|
||||
- name: Build AppImage
|
||||
shell: bash
|
||||
@@ -183,7 +176,7 @@ jobs:
|
||||
|
||||
mkdir -p "$APP.AppDir"/usr/{bin,lib/helix}
|
||||
|
||||
cp "target/${{ matrix.target }}/release/hx" "$APP.AppDir/usr/bin/hx"
|
||||
cp "target/${{ matrix.target }}/opt/hx" "$APP.AppDir/usr/bin/hx"
|
||||
rm -rf runtime/grammars/sources
|
||||
cp -r runtime "$APP.AppDir/usr/lib/helix/runtime"
|
||||
|
||||
@@ -206,14 +199,25 @@ jobs:
|
||||
mv "$APP-$VERSION-$ARCH.AppImage" \
|
||||
"$APP-$VERSION-$ARCH.AppImage.zsync" dist
|
||||
|
||||
- name: Build Deb
|
||||
shell: bash
|
||||
if: matrix.build == 'x86_64-linux'
|
||||
run: |
|
||||
cargo install cargo-deb
|
||||
mkdir -p target/release
|
||||
cp target/${{ matrix.target }}/opt/hx target/release/
|
||||
cargo deb --no-build
|
||||
mkdir -p dist
|
||||
mv target/debian/*.deb dist/
|
||||
|
||||
- name: Build archive
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p dist
|
||||
if [ "${{ matrix.os }}" = "windows-2019" ]; then
|
||||
cp "target/${{ matrix.target }}/release/hx.exe" "dist/"
|
||||
if [ "${{ matrix.os }}" = "windows-latest" ]; then
|
||||
cp "target/${{ matrix.target }}/opt/hx.exe" "dist/"
|
||||
else
|
||||
cp "target/${{ matrix.target }}/release/hx" "dist/"
|
||||
cp "target/${{ matrix.target }}/opt/hx" "dist/"
|
||||
fi
|
||||
if [ -d runtime/grammars/sources ]; then
|
||||
rm -rf runtime/grammars/sources
|
||||
@@ -231,9 +235,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v5
|
||||
|
||||
- name: Build archive
|
||||
shell: bash
|
||||
@@ -241,6 +245,7 @@ jobs:
|
||||
set -ex
|
||||
|
||||
source="$(pwd)"
|
||||
tag=${GITHUB_REF_NAME//\//}
|
||||
mkdir -p runtime/grammars/sources
|
||||
tar xJf grammars/grammars.tar.xz -C runtime/grammars/sources
|
||||
rm -rf grammars
|
||||
@@ -254,7 +259,7 @@ jobs:
|
||||
if [[ $platform =~ "windows" ]]; then
|
||||
exe=".exe"
|
||||
fi
|
||||
pkgname=helix-$GITHUB_REF_NAME-$platform
|
||||
pkgname=helix-$tag-$platform
|
||||
mkdir -p $pkgname
|
||||
cp $source/LICENSE $source/README.md $pkgname
|
||||
mkdir $pkgname/contrib
|
||||
@@ -265,6 +270,7 @@ jobs:
|
||||
|
||||
if [[ "$platform" = "x86_64-linux" ]]; then
|
||||
mv bins-$platform/helix-*.AppImage* dist/
|
||||
mv bins-$platform/*.deb dist/
|
||||
fi
|
||||
|
||||
if [ "$exe" = "" ]; then
|
||||
@@ -274,7 +280,7 @@ jobs:
|
||||
fi
|
||||
done
|
||||
|
||||
tar cJf dist/helix-$GITHUB_REF_NAME-source.tar.xz -C $source .
|
||||
tar cJf dist/helix-$tag-source.tar.xz -C $source .
|
||||
mv dist $source/
|
||||
|
||||
- name: Upload binaries to release
|
||||
@@ -286,7 +292,7 @@ jobs:
|
||||
file_glob: true
|
||||
tag: ${{ github.ref_name }}
|
||||
overwrite: true
|
||||
|
||||
|
||||
- name: Upload binaries as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
if: env.preview == 'true'
|
||||
|
609
CHANGELOG.md
609
CHANGELOG.md
@@ -1,3 +1,602 @@
|
||||
<!--
|
||||
# YY.0M (YYYY-0M-0D)
|
||||
|
||||
Breaking changes:
|
||||
|
||||
Features:
|
||||
|
||||
Commands:
|
||||
|
||||
Usability improvements:
|
||||
|
||||
Fixes:
|
||||
|
||||
Themes:
|
||||
|
||||
New languages:
|
||||
|
||||
Updated languages and queries:
|
||||
|
||||
Packaging:
|
||||
-->
|
||||
|
||||
# 25.07.1 (2025-07-18)
|
||||
|
||||
This is a patch release which lowers the GLIBC requirements of the release artifacts published to GitHub ([#13983](https://github.com/helix-editor/helix/pull/13983))
|
||||
|
||||
# 25.07 (2025-07-15)
|
||||
|
||||
As always, a big thank you to all of the contributors! This release saw changes from 195 contributors.
|
||||
|
||||
Breaking changes:
|
||||
|
||||
* The parsing of the command line has been rewritten and now supports flags and expansions ([#12527](https://github.com/helix-editor/helix/pull/12527), [#13018](https://github.com/helix-editor/helix/pull/13018), [9574e55](https://github.com/helix-editor/helix/commit/9574e55), [2d4c2a1](https://github.com/helix-editor/helix/commit/2d4c2a1), [#13192](https://github.com/helix-editor/helix/pull/13192), [67f1fe2](https://github.com/helix-editor/helix/commit/67f1fe2), [#13466](https://github.com/helix-editor/helix/pull/13466), [#13467](https://github.com/helix-editor/helix/pull/13467), [#13840](https://github.com/helix-editor/helix/pull/13840))
|
||||
* Quoting and spaces are now handled differently. This can break existing keymaps which use typable commands, in particular `:sh`, `:set-option` or `:toggle-option`.
|
||||
* The `:rsort` command has been removed. Use the reverse flag instead: `:sort --reverse`
|
||||
|
||||
Features:
|
||||
|
||||
* Add a picker which explores directories ([#11285](https://github.com/helix-editor/helix/pull/11285), [d4aed40](https://github.com/helix-editor/helix/commit/d4aed40))
|
||||
* Allow cycling through multiple LSP Hover responses with `A-n`/`A-p` ([#10122](https://github.com/helix-editor/helix/pull/10122), [2367b20](https://github.com/helix-editor/helix/commit/2367b20))
|
||||
* Add support for incomplete LSP completions ([5c1f3f8](https://github.com/helix-editor/helix/commit/5c1f3f8))
|
||||
* Add support for EditorConfig ([#13056](https://github.com/helix-editor/helix/pull/13056), [#13443](https://github.com/helix-editor/helix/pull/13443))
|
||||
* Add support for LSP document colors ([#12308](https://github.com/helix-editor/helix/pull/12308), [d43de14](https://github.com/helix-editor/helix/commit/d43de14), [47cdd23](https://github.com/helix-editor/helix/commit/47cdd23), [ba54b6a](https://github.com/helix-editor/helix/commit/ba54b6a), [#13188](https://github.com/helix-editor/helix/pull/13188))
|
||||
* Support expansions in external formatter arguments ([#13429](https://github.com/helix-editor/helix/pull/13429))
|
||||
* Switch out the highlighter for the `tree-house` crate ([#12972](https://github.com/helix-editor/helix/pull/12972), [09bc67a](https://github.com/helix-editor/helix/commit/09bc67a), [a7c3a43](https://github.com/helix-editor/helix/commit/a7c3a43), [3ceae88](https://github.com/helix-editor/helix/commit/3ceae88), [05ae617](https://github.com/helix-editor/helix/commit/05ae617), [5a1dcc2](https://github.com/helix-editor/helix/commit/5a1dcc2), [ebf96bd](https://github.com/helix-editor/helix/commit/ebf96bd), [#13644](https://github.com/helix-editor/helix/pull/13644), [b1f4717](https://github.com/helix-editor/helix/commit/b1f4717), [7410fe3](https://github.com/helix-editor/helix/commit/7410fe3), [633c5fb](https://github.com/helix-editor/helix/commit/633c5fb), [362e97e](https://github.com/helix-editor/helix/commit/362e97e), [#13828](https://github.com/helix-editor/helix/pull/13828), [6fd1efd](https://github.com/helix-editor/helix/commit/6fd1efd))
|
||||
* This fixes a number of highlighter bugs.
|
||||
* Locals like parameter highlights are now highlighted even when the definition is not in view.
|
||||
* Markdown is now injected into rust doc comments (`///` and `//!`).
|
||||
* Add support for the DAP `startDebugging` reverse request ([#13403](https://github.com/helix-editor/helix/pull/13403))
|
||||
|
||||
Commands:
|
||||
|
||||
* Add `copy_between_registers` for interactive copying between two registers ([066e938](https://github.com/helix-editor/helix/commit/066e938))
|
||||
* Add `extend_to_file_{start,end}`, select-mode variants of `goto_file_{start,end}` ([#11767](https://github.com/helix-editor/helix/pull/11767))
|
||||
* Add `:!` alias for `:sh` and `:|` for `:pipe` ([#13263](https://github.com/helix-editor/helix/pull/13263))
|
||||
* Add `goto_column` and `extend_to_column` ([#13440](https://github.com/helix-editor/helix/pull/13440))
|
||||
* Add an `--insensitive`/`-i` flag to the `:sort` command ([#13560](https://github.com/helix-editor/helix/pull/13560))
|
||||
* Add `rotate_selections_first` and `rotate_selections_last` ([#13615](https://github.com/helix-editor/helix/pull/13615))
|
||||
* Add a `--no-format` flag for all `:write` commands ([2f56091](https://github.com/helix-editor/helix/commit/2f56091))
|
||||
* Add a `--skip-visible` flag for `:buffer-close-others` and `:buffer-close-others!` ([#5393](https://github.com/helix-editor/helix/pull/5393))
|
||||
|
||||
Usability improvements:
|
||||
|
||||
* Replace current file using `A-ret` in pickers rather than loading it in the background ([#12605](https://github.com/helix-editor/helix/pull/12605))
|
||||
* Set multiple selections when passing a file with multiple locations to `hx` ([#12192](https://github.com/helix-editor/helix/pull/12192))
|
||||
* Add path completion for multiple cursors ([#12550](https://github.com/helix-editor/helix/pull/12550), [c9dc940](https://github.com/helix-editor/helix/commit/c9dc940))
|
||||
* Truncate long prompt lines with "…" ([#12036](https://github.com/helix-editor/helix/pull/12036), [9d6ea77](https://github.com/helix-editor/helix/commit/9d6ea77), [0b9701e](https://github.com/helix-editor/helix/commit/0b9701e), [d3fb8fc](https://github.com/helix-editor/helix/commit/d3fb8fc))
|
||||
* Allow specifying languages in `:lsp-stop` and `:lsp-restart` ([#12578](https://github.com/helix-editor/helix/pull/12578), [3d7e273](https://github.com/helix-editor/helix/commit/3d7e273))
|
||||
* Add `m` (nearest matching pair) to infobox popups for `md` and `mr` ([#12650](https://github.com/helix-editor/helix/pull/12650))
|
||||
* Add a hint message in the statusline when using `:sort` on a single selection ([#12585](https://github.com/helix-editor/helix/pull/12585))
|
||||
* Avoid wrapping around in `goto_{next,prev}_diag` ([#12704](https://github.com/helix-editor/helix/pull/12704))
|
||||
* Support responses from multiple language servers for LSP goto-definition (and declaration, type definition and implementation) and goto-references ([f7394d5](https://github.com/helix-editor/helix/commit/f7394d5), [1a821ac](https://github.com/helix-editor/helix/commit/1a821ac), [d285a8a](https://github.com/helix-editor/helix/commit/d285a8a))
|
||||
* Show formatter errors in `:format` ([47f84d0](https://github.com/helix-editor/helix/commit/47f84d0))
|
||||
* Show typable command docs in keybinding infobox popups when the command takes no arguments ([e9c16b7](https://github.com/helix-editor/helix/commit/e9c16b7))
|
||||
* Add per-command titles to register selection infobox popups for `select_register`, `insert_register` and `copy_between_registers` ([e0da129](https://github.com/helix-editor/helix/commit/e0da129))
|
||||
* Add container name column to the LSP symbol picker ([#12930](https://github.com/helix-editor/helix/pull/12930))
|
||||
* Add a theme key for highlighting directories in completions and picker items ([#12855](https://github.com/helix-editor/helix/pull/12855), [7bebe0a](https://github.com/helix-editor/helix/commit/7bebe0a))
|
||||
* Add `editor.trim-final-newlines` and `editor.trim-trailing-whitespace` config options ([aa20eb8](https://github.com/helix-editor/helix/commit/aa20eb8))
|
||||
* Warn when the configured theme is unusable because true-color is not available ([#13058](https://github.com/helix-editor/helix/pull/13058))
|
||||
* Allow configuring `[workspace-]diagnostic` statusline element severities ([#13288](https://github.com/helix-editor/helix/pull/13288), [b0528bb](https://github.com/helix-editor/helix/commit/b0528bb))
|
||||
* Improve completion for shell commands ([#12883](https://github.com/helix-editor/helix/pull/12883), [532f241](https://github.com/helix-editor/helix/commit/532f241))
|
||||
* Show the primary selection index in the `selections` statusline element when there are multiple selections ([#12326](https://github.com/helix-editor/helix/pull/12326))
|
||||
* Use configured language server names when possible in `--health` output ([#13573](https://github.com/helix-editor/helix/pull/13573))
|
||||
* Add a statusline element for indentation style ([#13632](https://github.com/helix-editor/helix/pull/13632))
|
||||
* Set the working directory of language server commands to the workspace root ([#13691](https://github.com/helix-editor/helix/pull/13691))
|
||||
* Avoid jumpiness in the picker preview for languages with non-default tab widths ([#13761](https://github.com/helix-editor/helix/pull/13761))
|
||||
* Add a config option for limiting LSP inlay hint length ([#13742](https://github.com/helix-editor/helix/pull/13742))
|
||||
* Improve heuristics used in the diff gutter ([#13722](https://github.com/helix-editor/helix/pull/13722))
|
||||
* Allow moving a file with `:move` when its old path does not exist ([#13748](https://github.com/helix-editor/helix/pull/13748))
|
||||
* Allow moving a file into a directory with `:move` ([#13922](https://github.com/helix-editor/helix/pull/13922))
|
||||
* Show human-readable file sizes in the statusline message for file writes ([#13627](https://github.com/helix-editor/helix/pull/13627))
|
||||
* Add diagnostic source to the diagnosics pickers ([#13758](https://github.com/helix-editor/helix/pull/13758))
|
||||
* Show all active scopes under the cursor in `:tree-sitter-highlight-name` ([4a85171](https://github.com/helix-editor/helix/commit/4a85171))
|
||||
* Auto-close the LSP code-actions popup ([#13832](https://github.com/helix-editor/helix/pull/13832))
|
||||
* Add a configuration option for controlling atomic writes to disk ([#13656](https://github.com/helix-editor/helix/pull/13656))
|
||||
|
||||
Fixes:
|
||||
|
||||
* Fix panic from using `search_selection_detect_word_boundaries` (`*`) at the end of the file ([#12611](https://github.com/helix-editor/helix/pull/12611))
|
||||
* Discard placeholder text for zero tabstop `${0:placeholder}` ([#12647](https://github.com/helix-editor/helix/pull/12647))
|
||||
* Fix panic in `goto_file` (`gf`) on file names with non-ASCII characters ([#12673](https://github.com/helix-editor/helix/pull/12673))
|
||||
* Only accept unmodified characters in `goto_word` (`gw`) ([f5f9f49](https://github.com/helix-editor/helix/commit/f5f9f49), [0364521](https://github.com/helix-editor/helix/commit/0364521))
|
||||
* Skip recording keys pressed by macros while recording a macro ([#12733](https://github.com/helix-editor/helix/pull/12733))
|
||||
* Deny unknown fields in `editor.smart-tab` config ([28047fe](https://github.com/helix-editor/helix/commit/28047fe))
|
||||
* Fix soft-wrap word boundary detection for Unicode combining accent characters ([#12483](https://github.com/helix-editor/helix/pull/12483))
|
||||
* Fix clearing of infobox popups in `select_register` and `insert_register` commands ([e882a75](https://github.com/helix-editor/helix/commit/e882a75))
|
||||
* Fix handling of `stderr` of DAP child processes ([d0d1693](https://github.com/helix-editor/helix/commit/d0d1693))
|
||||
* Cancel all pending requests when a DAP session terminates ([26db541](https://github.com/helix-editor/helix/commit/26db541))
|
||||
* Properly discard out-of-date diagnostics ([313a647](https://github.com/helix-editor/helix/commit/313a647))
|
||||
* Fix display of multiple language servers in `hx --health` ([#12841](https://github.com/helix-editor/helix/pull/12841))
|
||||
* Respect `editor.default-yank-register` in `:yank-joined` ([#12890](https://github.com/helix-editor/helix/pull/12890))
|
||||
* Escape percent character when pasting the history register into the picker ([#12886](https://github.com/helix-editor/helix/pull/12886))
|
||||
* Render rulers before the cursor ([2d3b75a](https://github.com/helix-editor/helix/commit/2d3b75a))
|
||||
* Avoid inserting final newlines in empty files ([67879a1](https://github.com/helix-editor/helix/commit/67879a1))
|
||||
* Gracefully handle partial failure in multi-language-server requests ([#13156](https://github.com/helix-editor/helix/pull/13156), [14cab4b](https://github.com/helix-editor/helix/commit/14cab4b))
|
||||
* Improve LSP progress message display in the statusline ([#13180](https://github.com/helix-editor/helix/pull/13180))
|
||||
* Fix behavior of `<esc>` removing added indentation in documents with CRLF line endings ([702a961](https://github.com/helix-editor/helix/commit/702a961))
|
||||
* Append changes to document history before pushing jumplist jumps ([#13619](https://github.com/helix-editor/helix/pull/13619))
|
||||
* Fix overflow in the display of large chunks of text in the signature-help component ([#13566](https://github.com/helix-editor/helix/pull/13566))
|
||||
* Fix panic from clearing whitespace when changing multiple selections on one line ([#13673](https://github.com/helix-editor/helix/pull/13673))
|
||||
* Include formatting options in LSP range formatting request ([#13734](https://github.com/helix-editor/helix/pull/13734))
|
||||
* Consistently set statusline errors when LSP features are not available ([#12577](https://github.com/helix-editor/helix/pull/12577))
|
||||
* Fix `goto_file` on Windows ([#13770](https://github.com/helix-editor/helix/pull/13770))
|
||||
* Fix crash in `goto_word` (`gw`) when `editor.jump-label-alphabet` is configured to be empty ([#13863](https://github.com/helix-editor/helix/pull/13863))
|
||||
* Fix `open_above` / `open_below` (`o` / `O`) when using a count on a document with CRLF line-endings ([#13905](https://github.com/helix-editor/helix/pull/13905))
|
||||
|
||||
Themes:
|
||||
|
||||
* Update `modus` themes ([#12670](https://github.com/helix-editor/helix/pull/12670))
|
||||
* Update `snazzy` ([#11089](https://github.com/helix-editor/helix/pull/11089))
|
||||
* Update `gruber-darker` ([#12797](https://github.com/helix-editor/helix/pull/12797))
|
||||
* Update `cyan_light` ([#12864](https://github.com/helix-editor/helix/pull/12864), [#12891](https://github.com/helix-editor/helix/pull/12891))
|
||||
* Update `onedarker` ([#12833](https://github.com/helix-editor/helix/pull/12833))
|
||||
* Update `github_light` ([#12907](https://github.com/helix-editor/helix/pull/12907))
|
||||
* Update `kanagawa` ([#12895](https://github.com/helix-editor/helix/pull/12895))
|
||||
* Add `beans` ([#12963](https://github.com/helix-editor/helix/pull/12963))
|
||||
* Update `base16_transparent` ([#13080](https://github.com/helix-editor/helix/pull/13080))
|
||||
* Update `sunset` ([#13086](https://github.com/helix-editor/helix/pull/13086))
|
||||
* Add `carbon` ([#13067](https://github.com/helix-editor/helix/pull/13067))
|
||||
* Update `soralized` ([#13121](https://github.com/helix-editor/helix/pull/13121))
|
||||
* Add `focus_nova` ([#13144](https://github.com/helix-editor/helix/pull/13144))
|
||||
* Update `onedark` ([#13166](https://github.com/helix-editor/helix/pull/13166))
|
||||
* Update `adwaita-light` ([#13174](https://github.com/helix-editor/helix/pull/13174))
|
||||
* Add `earl_grey` ([#13203](https://github.com/helix-editor/helix/pull/13203))
|
||||
* Update `spacebones` ([#13213](https://github.com/helix-editor/helix/pull/13213))
|
||||
* Add `peachpuff` ([#13225](https://github.com/helix-editor/helix/pull/13225))
|
||||
* Update catppuccin themes ([#13262](https://github.com/helix-editor/helix/pull/13262))
|
||||
* Update gruvbox themes ([#13315](https://github.com/helix-editor/helix/pull/13315))
|
||||
* Update serika themes ([#13341](https://github.com/helix-editor/helix/pull/13341))
|
||||
* Add `gruvbox-material` ([#13311](https://github.com/helix-editor/helix/pull/13311))
|
||||
* Add `ashen` ([#13366](https://github.com/helix-editor/helix/pull/13366))
|
||||
* Update Zed themes ([#13370](https://github.com/helix-editor/helix/pull/13370))
|
||||
* Update Tokyonight themes ([#13375](https://github.com/helix-editor/helix/pull/13375))
|
||||
* Update `onelight` ([#13413](https://github.com/helix-editor/helix/pull/13413))
|
||||
* Add `ataraxia` ([#13390](https://github.com/helix-editor/helix/pull/13390))
|
||||
* Add `vesper` ([#13394](https://github.com/helix-editor/helix/pull/13394))
|
||||
* Add `kinda_nvim` and `kinda_nvim_light` ([#13406](https://github.com/helix-editor/helix/pull/13406))
|
||||
* Update `sonokai` ([#13410](https://github.com/helix-editor/helix/pull/13410))
|
||||
* Add `nyxvamp` themes ([#12185](https://github.com/helix-editor/helix/pull/12185))
|
||||
* Update nord themes ([#13574](https://github.com/helix-editor/helix/pull/13574))
|
||||
* Add `lapis_aquamarine` ([#13726](https://github.com/helix-editor/helix/pull/13726))
|
||||
* Add `sidra` ([#13575](https://github.com/helix-editor/helix/pull/13575))
|
||||
* Add `dark-synthwave` ([#13857](https://github.com/helix-editor/helix/pull/13857))
|
||||
* Update `rose_pine` ([#13908](https://github.com/helix-editor/helix/pull/13908))
|
||||
* Add `doom-one` ([#13933](https://github.com/helix-editor/helix/pull/13933))
|
||||
* Update `nightfox` ([#13957](https://github.com/helix-editor/helix/pull/13957))
|
||||
|
||||
New languages:
|
||||
|
||||
* Ghostty config ([#12703](https://github.com/helix-editor/helix/pull/12703))
|
||||
* Tera ([#12756](https://github.com/helix-editor/helix/pull/12756))
|
||||
* FGA ([#12763](https://github.com/helix-editor/helix/pull/12763))
|
||||
* CSV ([#11973](https://github.com/helix-editor/helix/pull/11973))
|
||||
* Yara ([#12753](https://github.com/helix-editor/helix/pull/12753))
|
||||
* Djot ([#12562](https://github.com/helix-editor/helix/pull/12562))
|
||||
* Ink ([#12773](https://github.com/helix-editor/helix/pull/12773))
|
||||
* Mail ([#12945](https://github.com/helix-editor/helix/pull/12945))
|
||||
* SourcePawn ([#13028](https://github.com/helix-editor/helix/pull/13028))
|
||||
* TLA+ ([#13081](https://github.com/helix-editor/helix/pull/13081))
|
||||
* Werk ([#13136](https://github.com/helix-editor/helix/pull/13136))
|
||||
* Debian control file ([#13245](https://github.com/helix-editor/helix/pull/13245))
|
||||
* WESL ([#13267](https://github.com/helix-editor/helix/pull/13267))
|
||||
* Fennel ([#13260](https://github.com/helix-editor/helix/pull/13260), [6081a5d](https://github.com/helix-editor/helix/commit/6081a5d))
|
||||
* Quarto ([#13339](https://github.com/helix-editor/helix/pull/13339))
|
||||
* Pug ([#13435](https://github.com/helix-editor/helix/pull/13435))
|
||||
* Slang ([#13449](https://github.com/helix-editor/helix/pull/13449))
|
||||
* Dunst config ([#13458](https://github.com/helix-editor/helix/pull/13458))
|
||||
* Luau ([#13702](https://github.com/helix-editor/helix/pull/13702))
|
||||
* Caddyfile ([#13859](https://github.com/helix-editor/helix/pull/13859))
|
||||
* Java properties ([#13874](https://github.com/helix-editor/helix/pull/13874))
|
||||
* Git notes ([#13885](https://github.com/helix-editor/helix/pull/13885))
|
||||
* systemd (split from INI) ([#13907](https://github.com/helix-editor/helix/pull/13907))
|
||||
* JSON-LD (split from JSON) ([#13925](https://github.com/helix-editor/helix/pull/13925))
|
||||
* Django HTML ([#13935](https://github.com/helix-editor/helix/pull/13935))
|
||||
|
||||
Updated languages and queries:
|
||||
|
||||
* Add `ruby-lsp` for Ruby ([#12511](https://github.com/helix-editor/helix/pull/12511))
|
||||
* Add `wat_server` for Wat ([#12581](https://github.com/helix-editor/helix/pull/12581))
|
||||
* Recognize `bun.lock` as JSONC ([fcf981b](https://github.com/helix-editor/helix/commit/fcf981b))
|
||||
* Update tree-sitter-rust ([#12607](https://github.com/helix-editor/helix/pull/12607), [1afa63d](https://github.com/helix-editor/helix/commit/1afa63d))
|
||||
* Fix configuration of `cs-lsp` ([#12615](https://github.com/helix-editor/helix/pull/12615))
|
||||
* Add `beancount-language-server` for Beancount ([#12610](https://github.com/helix-editor/helix/pull/12610))
|
||||
* Update tree-sitter-fish ([#12456](https://github.com/helix-editor/helix/pull/12456))
|
||||
* Add `fish-lsp` for Fish ([#12456](https://github.com/helix-editor/helix/pull/12456))
|
||||
* Update tree-sitter-ini ([#12456](https://github.com/helix-editor/helix/pull/12456), [#13088](https://github.com/helix-editor/helix/pull/13088))
|
||||
* Recognize `hgrc` as INI ([#12456](https://github.com/helix-editor/helix/pull/12456))
|
||||
* Restrict tagged template injection languages for ECMA languages ([#12217](https://github.com/helix-editor/helix/pull/12217))
|
||||
* Update tree-sitter-zig ([#11980](https://github.com/helix-editor/helix/pull/11980), [#12708](https://github.com/helix-editor/helix/pull/12708))
|
||||
* Update tree-sitter-elixir ([8bf9adf](https://github.com/helix-editor/helix/commit/8bf9adf))
|
||||
* Add `asm-lsp` for Assembly dialects ([#12684](https://github.com/helix-editor/helix/pull/12684))
|
||||
* Update tree-sitter-just ([#12692](https://github.com/helix-editor/helix/pull/12692), #)
|
||||
* Update tree-sitter-cairo ([#12712](https://github.com/helix-editor/helix/pull/12712))
|
||||
* Configure a comment token for Svelte ([#12743](https://github.com/helix-editor/helix/pull/12743))
|
||||
* Recognize `.sublime-*` files ([#12750](https://github.com/helix-editor/helix/pull/12750))
|
||||
* Highlight `$` tagged templates as shell commands in ECMA languages ([#12751](https://github.com/helix-editor/helix/pull/12751))
|
||||
* Add `#'` comment token for R ([#12748](https://github.com/helix-editor/helix/pull/12748))
|
||||
* Fix module/namespace highlight in Unison ([93fa990](https://github.com/helix-editor/helix/commit/93fa990))
|
||||
* Add missing `#not-eq?` and `#not-match?` highlights in TSQ ([3824010](https://github.com/helix-editor/helix/commit/3824010))
|
||||
* Reverse the precedence order of highlight queries ([#9458](https://github.com/helix-editor/helix/pull/9458), [#12777](https://github.com/helix-editor/helix/pull/12777), [#12795](https://github.com/helix-editor/helix/pull/12795), [144a4f4](https://github.com/helix-editor/helix/commit/144a4f4), [e1c26eb](https://github.com/helix-editor/helix/commit/e1c26eb), [e1060a2](https://github.com/helix-editor/helix/commit/e1060a2), [7f41670](https://github.com/helix-editor/helix/commit/7f41670), [#13293](https://github.com/helix-editor/helix/pull/13293))
|
||||
* Update Rust highlights ([b8bfc44](https://github.com/helix-editor/helix/commit/b8bfc44), [#12871](https://github.com/helix-editor/helix/pull/12871), [#13664](https://github.com/helix-editor/helix/pull/13664))
|
||||
* Add block comment configuration for PHP ([0ab403d](https://github.com/helix-editor/helix/commit/0ab403d))
|
||||
* Update Gren highlights ([#12769](https://github.com/helix-editor/helix/pull/12769))
|
||||
* Remove `ERROR` node highlighting from all highlight queries ([16ff063](https://github.com/helix-editor/helix/commit/16ff063))
|
||||
* Update tree-sitter-erlang and highlights ([18b9eb9](https://github.com/helix-editor/helix/commit/18b9eb9), [9f3b193](https://github.com/helix-editor/helix/commit/9f3b193), [12139a4](https://github.com/helix-editor/helix/commit/12139a4))
|
||||
* Update Nix injections ([#12776](https://github.com/helix-editor/helix/pull/12776), [#12774](https://github.com/helix-editor/helix/pull/12774), [#13851](https://github.com/helix-editor/helix/pull/13851))
|
||||
* Add indent queries for Nix ([#12829](https://github.com/helix-editor/helix/pull/12829))
|
||||
* Update Markdown highlights ([#12696](https://github.com/helix-editor/helix/pull/12696))
|
||||
* Recognize `xsl` as XML ([#12834](https://github.com/helix-editor/helix/pull/12834))
|
||||
* Remove deprecated `typst-lsp` config ([5a66270](https://github.com/helix-editor/helix/commit/5a66270))
|
||||
* Replace `pkgbuild-language-server` with `termux-language-server` ([c3c9a0d](https://github.com/helix-editor/helix/commit/c3c9a0d))
|
||||
* Update SQL highlights ([#12837](https://github.com/helix-editor/helix/pull/12837))
|
||||
* Recognize `mpd` and `smil` as XML ([#12916](https://github.com/helix-editor/helix/pull/12916))
|
||||
* Add indents and textojbects for Kotlin ([#12925](https://github.com/helix-editor/helix/pull/12925))
|
||||
* Fix module highlights in Koto ([7e87a36](https://github.com/helix-editor/helix/commit/7e87a36))
|
||||
* Update language servers for Protobuf ([#12936](https://github.com/helix-editor/helix/pull/12936))
|
||||
* Add `astro-ls` for Astro ([#12939](https://github.com/helix-editor/helix/pull/12939))
|
||||
* Fix recognition of "scons*" files as Python ([#12943](https://github.com/helix-editor/helix/pull/12943))
|
||||
* Update C# queries ([#12948](https://github.com/helix-editor/helix/pull/12948))
|
||||
* Add comment textojbect to TOML ([#12952](https://github.com/helix-editor/helix/pull/12952))
|
||||
* Add `starpls` as Starlark language server ([#12958](https://github.com/helix-editor/helix/pull/12958))
|
||||
* Add `pkl-lsp` for PKL ([#12962](https://github.com/helix-editor/helix/pull/12962))
|
||||
* Add `kdlfmt` formatter for KDL ([#12967](https://github.com/helix-editor/helix/pull/12967))
|
||||
* Update CSS highlights ([#12497](https://github.com/helix-editor/helix/pull/12497), [fed3edc](https://github.com/helix-editor/helix/commit/fed3edc))
|
||||
* Add `harper-ls` ([#13029](https://github.com/helix-editor/helix/pull/13029))
|
||||
* Change `wgsl_analyzer` to `wgsl-analyzer` ([#13063](https://github.com/helix-editor/helix/pull/13063))
|
||||
* Update tree-sitter-vhdl ([#13091](https://github.com/helix-editor/helix/pull/13091))
|
||||
* Update tree-sitter-openscad ([#13033](https://github.com/helix-editor/helix/pull/13033))
|
||||
* Update Rust injections ([694b615](https://github.com/helix-editor/helix/commit/694b615), [1bd7a39](https://github.com/helix-editor/helix/commit/1bd7a39))
|
||||
* Update Ruby highlights ([#13055](https://github.com/helix-editor/helix/pull/13055))
|
||||
* Recognize `gitconfig` as an extension ([#13115](https://github.com/helix-editor/helix/pull/13115))
|
||||
* Add `///` comment token for Amber ([#13122](https://github.com/helix-editor/helix/pull/13122))
|
||||
* Add indent queries for Starlark ([#13126](https://github.com/helix-editor/helix/pull/13126))
|
||||
* Recognize more systemd file types as INI ([#13139](https://github.com/helix-editor/helix/pull/13139))
|
||||
* Update scheme queries ([#13143](https://github.com/helix-editor/helix/pull/13143))
|
||||
* Recognize `tmTheme` as XML ([#13202](https://github.com/helix-editor/helix/pull/13202))
|
||||
* Update `golangci-lint` command for v2 ([#13204](https://github.com/helix-editor/helix/pull/13204))
|
||||
* Add `just-lsp` for Just ([#13276](https://github.com/helix-editor/helix/pull/13276))
|
||||
* Add a tree-sitter-prolog grammar ([#11611](https://github.com/helix-editor/helix/pull/11611))
|
||||
* Fix typos in Ada queries ([#13251](https://github.com/helix-editor/helix/pull/13251))
|
||||
* Update mint language server args ([#13248](https://github.com/helix-editor/helix/pull/13248))
|
||||
* Update typescript highlights ([#13250](https://github.com/helix-editor/helix/pull/13250))
|
||||
* Update tree-sitter-jjdescription ([#13329](https://github.com/helix-editor/helix/pull/13329))
|
||||
* Add injection queries for Quint ([#13322](https://github.com/helix-editor/helix/pull/13322))
|
||||
* Update tree-sitter-scss and highlights ([#13414](https://github.com/helix-editor/helix/pull/13414))
|
||||
* Update tree-sitter-go-mod ([#13395](https://github.com/helix-editor/helix/pull/13395))
|
||||
* Update tree-sitter-svelte ([#13423](https://github.com/helix-editor/helix/pull/13423))
|
||||
* Update Lua highlights ([#13401](https://github.com/helix-editor/helix/pull/13401))
|
||||
* Update Go highlights ([#13425](https://github.com/helix-editor/helix/pull/13425), [25b299a](https://github.com/helix-editor/helix/commit/25b299a), [#13825](https://github.com/helix-editor/helix/pull/13825))
|
||||
* Recognize `.git-blame-ignore-revs` as gitignore ([#13460](https://github.com/helix-editor/helix/pull/13460))
|
||||
* Update Verilog highlights ([#13473](https://github.com/helix-editor/helix/pull/13473), [#13493](https://github.com/helix-editor/helix/pull/13493))
|
||||
* Update tree-sitter-v ([#13469](https://github.com/helix-editor/helix/pull/13469))
|
||||
* Update WGSL highlights ([#13479](https://github.com/helix-editor/helix/pull/13479))
|
||||
* Update Bash highlights ([#13477](https://github.com/helix-editor/helix/pull/13477))
|
||||
* Update tree-sitter-cpp ([#13504](https://github.com/helix-editor/helix/pull/13504))
|
||||
* Update rust-analyzer config to use server-side file watching ([#13432](https://github.com/helix-editor/helix/pull/13432))
|
||||
* Update Vue injections ([#13511](https://github.com/helix-editor/helix/pull/13511))
|
||||
* Recognize `sld` as Scheme ([#13528](https://github.com/helix-editor/helix/pull/13528))
|
||||
* Recognize more files as git-attributes ([#13540](https://github.com/helix-editor/helix/pull/13540))
|
||||
* Update tree-sitter-haskell and queries ([#13475](https://github.com/helix-editor/helix/pull/13475))
|
||||
* Align INI highlights with TOML ([#13589](https://github.com/helix-editor/helix/pull/13589))
|
||||
* Add tree-sitter-rust-format-args for `format_args!` injections in Rust ([#13533](https://github.com/helix-editor/helix/pull/13533), [#13657](https://github.com/helix-editor/helix/pull/13657), [4dd4ba7](https://github.com/helix-editor/helix/commit/4dd4ba7), [86f10ae](https://github.com/helix-editor/helix/commit/86f10ae))
|
||||
* Update Ungrammar highlights ([8d58f6c](https://github.com/helix-editor/helix/commit/8d58f6c))
|
||||
* Add `ty` language server for Python ([#13643](https://github.com/helix-editor/helix/pull/13643))
|
||||
* Add `clarinet` language server for Clarity ([#13647](https://github.com/helix-editor/helix/pull/13647))
|
||||
* Update prisma config to avoid a crash in the language server ([f6878f6](https://github.com/helix-editor/helix/commit/f6878f6))
|
||||
* Add `pyrefly` for Python ([#13713](https://github.com/helix-editor/helix/pull/13713))
|
||||
* Update Python highlights ([#13715](https://github.com/helix-editor/helix/pull/13715))
|
||||
* Update Mojo language server and formatter to `pixi` ([#13648](https://github.com/helix-editor/helix/pull/13648))
|
||||
* Add `tombi` for TOML ([#13723](https://github.com/helix-editor/helix/pull/13723))
|
||||
* Add `neocmakelsp` for CMake ([#13740](https://github.com/helix-editor/helix/pull/13740))
|
||||
* Update C and C++ highlights ([#13747](https://github.com/helix-editor/helix/pull/13747), [#13772](https://github.com/helix-editor/helix/pull/13772))
|
||||
* Highlight escape sequences in ECMA languages ([#13762](https://github.com/helix-editor/helix/pull/13762))
|
||||
* Add an external formatter config for Crystal ([#13759](https://github.com/helix-editor/helix/pull/13759))
|
||||
* Add `amber-lsp` for Amber ([#13763](https://github.com/helix-editor/helix/pull/13763))
|
||||
* Update HTML highlights ([#13753](https://github.com/helix-editor/helix/pull/13753))
|
||||
* Update tree-sitter-purescript and highlights ([#13782](https://github.com/helix-editor/helix/pull/13782))
|
||||
* Update tree-sitter-gleam and highlights ([#13793](https://github.com/helix-editor/helix/pull/13793), [#13807](https://github.com/helix-editor/helix/pull/13807), [#13813](https://github.com/helix-editor/helix/pull/13813))
|
||||
* Recognize Buck files as Starlark ([#13810](https://github.com/helix-editor/helix/pull/13810))
|
||||
* Use tree-sitter-crystal instead of tree-sitter-ruby for Crystal and add custom queries ([#13805](https://github.com/helix-editor/helix/pull/13805))
|
||||
* Update tree-sitter-twig ([#13689](https://github.com/helix-editor/helix/pull/13689))
|
||||
* Recognize `jsconfig.json` as JSONC, use as JavaScript and JSX roots ([#13822](https://github.com/helix-editor/helix/pull/13822))
|
||||
* Recognize `.gem/credentials` as YAML ([#13843](https://github.com/helix-editor/helix/pull/13843))
|
||||
* Update Dockerfile injections ([#13845](https://github.com/helix-editor/helix/pull/13845), 13852)
|
||||
* Change tree-sitter parser for Git commit message files ([44293df](https://github.com/helix-editor/helix/commit/44293df))
|
||||
* Recognize `mimeapps.list` as INI ([#13850](https://github.com/helix-editor/helix/pull/13850))
|
||||
* Update tree-sitter-odin, highlights and indents ([#13877](https://github.com/helix-editor/helix/pull/13877), [#13917](https://github.com/helix-editor/helix/pull/13917))
|
||||
* Add locals queries for C, improve parameter highlighting ([#13876](https://github.com/helix-editor/helix/pull/13876))
|
||||
* Add textobjects for QML ([#13855](https://github.com/helix-editor/helix/pull/13855))
|
||||
* Add comment tokens for DTD ([#13904](https://github.com/helix-editor/helix/pull/13904))
|
||||
* Add `dts-lsp` for DeviceTree ([#13907](https://github.com/helix-editor/helix/pull/13907))
|
||||
* Update gomod highlights ([#13913](https://github.com/helix-editor/helix/pull/13913))
|
||||
* Recognize `compose.yaml` and `compose.yml` as Docker Compose ([#13930](https://github.com/helix-editor/helix/pull/13930))
|
||||
|
||||
Packaging:
|
||||
|
||||
* Fix handling of spaces in Bash completion ([#12828](https://github.com/helix-editor/helix/pull/12828))
|
||||
* Refactor Nix flake ([#12831](https://github.com/helix-editor/helix/pull/12831), [#13024](https://github.com/helix-editor/helix/pull/13024), [cb1ecc9](https://github.com/helix-editor/helix/commit/cb1ecc9), [#13305](https://github.com/helix-editor/helix/pull/13305))
|
||||
* Add `ConsoleOnly` to `Helix.desktop` categories ([#13236](https://github.com/helix-editor/helix/pull/13236))
|
||||
* Drop Nix flake dependency on flake-utils ([60a03a3](https://github.com/helix-editor/helix/commit/60a03a3))
|
||||
* Increase the MSRV to 1.82 ([#13275](https://github.com/helix-editor/helix/pull/13275))
|
||||
|
||||
# 25.01.1 (2025-01-19)
|
||||
|
||||
25.01.1 is a patch release focusing on fixing bugs and panics from changes in 25.01.
|
||||
|
||||
Usability improvements:
|
||||
|
||||
* Run external formatters from the document's directory ([#12315](https://github.com/helix-editor/helix/pull/12315))
|
||||
|
||||
Fixes:
|
||||
|
||||
* Fix blank buffer picker preview on doc with no views ([917174e](https://github.com/helix-editor/helix/commit/917174e))
|
||||
* Fix `join_selections` behavior on tabs ([#12452](https://github.com/helix-editor/helix/pull/12452))
|
||||
* Fix recognition for color LSP completion hex codes for some language servers ([#12501](https://github.com/helix-editor/helix/pull/12501))
|
||||
* Fix offsets to selections updated by `open_below`/`open_above` (`o`/`O`) in multi-cursor scenarios ([#12465](https://github.com/helix-editor/helix/pull/12465))
|
||||
* Fix offsets to selections updated by `insert_newline` when trimming whitespace in multi-cursor scenarios ([4bd17e5](https://github.com/helix-editor/helix/commit/4bd17e5))
|
||||
* Fix panic in path completion from resolving variables like `${HOME:-$HOME}` ([#12556](https://github.com/helix-editor/helix/pull/12556))
|
||||
* Prevent line comment continuation when using `change_selection` (`c`) on a line above a comment ([#12575](https://github.com/helix-editor/helix/pull/12575))
|
||||
|
||||
Themes:
|
||||
|
||||
* Update `onelight` ([#12399](https://github.com/helix-editor/helix/pull/12399))
|
||||
* Add cursorline color to iceberg themes ([#12404](https://github.com/helix-editor/helix/pull/12404))
|
||||
* Update `special`, `ui.text.directory` and `ui.virtual.wrap` in `dark_plus` ([#12530](https://github.com/helix-editor/helix/pull/12530))
|
||||
|
||||
New languages:
|
||||
|
||||
* CodeQL ([#12470](https://github.com/helix-editor/helix/pull/12470))
|
||||
* Gren ([#12525](https://github.com/helix-editor/helix/pull/12525))
|
||||
|
||||
Updated languages and queries:
|
||||
|
||||
* Fix Teal LSP name ([#12395](https://github.com/helix-editor/helix/pull/12395))
|
||||
* Highlight `:` in Rust as a delimiter ([#12408](https://github.com/helix-editor/helix/pull/12408))
|
||||
* Update Swift highlights ([#12409](https://github.com/helix-editor/helix/pull/12409))
|
||||
* Highlight JSX attributes as `@attribute` ([#12416](https://github.com/helix-editor/helix/pull/12416))
|
||||
* Improve markdown heading highlights ([#12417](https://github.com/helix-editor/helix/pull/12417))
|
||||
* Add comment tokens configuration for JSONC ([b26903c](https://github.com/helix-editor/helix/commit/b26903c))
|
||||
* Highlight the never type `!` as a type in Rust ([#12485](https://github.com/helix-editor/helix/pull/12485))
|
||||
* Expand builtin function highlights for ECMA languages, Rust and Haskell ([#12488](https://github.com/helix-editor/helix/pull/12488))
|
||||
* Recognize `.clang-tidy` as YAML ([#12498](https://github.com/helix-editor/helix/pull/12498))
|
||||
* Update MATLAB grammar and indent queries ([#12518](https://github.com/helix-editor/helix/pull/12518))
|
||||
* Recognize `rockspec` as Lua ([#12516](https://github.com/helix-editor/helix/pull/12516))
|
||||
* Add `///` to Dart comment tokens configuration ([99d33c7](https://github.com/helix-editor/helix/commit/99d33c7))
|
||||
* Update Solidity grammar and queries ([#12457](https://github.com/helix-editor/helix/pull/12457))
|
||||
* Update Spade grammar and queries ([#12583](https://github.com/helix-editor/helix/pull/12583))
|
||||
* Re-enable Hare fetching and building by default ([#11507](https://github.com/helix-editor/helix/pull/11507))
|
||||
|
||||
Packaging:
|
||||
|
||||
* `--version` now prints a leading zero for single-digit months, for example `25.01` (03f35af)
|
||||
* Pin the Ubuntu GitHub Actions runners used for releases to `ubuntu-22.04` ([#12464](https://github.com/helix-editor/helix/pull/12464))
|
||||
* Produce a Debian package (`.deb` file) in the release GitHub Actions workflow ([#12453](https://github.com/helix-editor/helix/pull/12453))
|
||||
|
||||
# 25.01 (2025-01-03)
|
||||
|
||||
As always, a big thank you to all of the contributors! This release saw changes from 171 contributors.
|
||||
|
||||
Breaking changes:
|
||||
|
||||
* The `editor.lsp.display-messages` key now controls messages sent with the LSP `window/showMessage` notification rather than progress messages. If you want to enable progress messages you should now enable the `editor.lsp.display-progress-messages` key instead. ([#5535](https://github.com/helix-editor/helix/pull/5535))
|
||||
|
||||
Features:
|
||||
|
||||
* Big refactor for `Picker`s ([#9647](https://github.com/helix-editor/helix/pull/9647), [#11209](https://github.com/helix-editor/helix/pull/11209), [#11216](https://github.com/helix-editor/helix/pull/11216), [#11211](https://github.com/helix-editor/helix/pull/11211), [#11343](https://github.com/helix-editor/helix/pull/11343), [#11406](https://github.com/helix-editor/helix/pull/11406))
|
||||
* Use a table layout and allow filtering by column
|
||||
* Reimplement `global_search` to allow changing the query dynamically
|
||||
* Add an alternative "inline" display for LSP diagnostics ([#6417](https://github.com/helix-editor/helix/pull/6417), [#11815](https://github.com/helix-editor/helix/pull/11815))
|
||||
* Support defining keybindings as macros ([#4709](https://github.com/helix-editor/helix/pull/4709))
|
||||
* Continue line comments in `o`/`O` and on `<ret>` in insert mode ([#10996](https://github.com/helix-editor/helix/pull/10996), [#12213](https://github.com/helix-editor/helix/pull/12213), [#12215](https://github.com/helix-editor/helix/pull/12215))
|
||||
* Allow configuring and switching clipboard providers at runtime ([#10839](https://github.com/helix-editor/helix/pull/10839), [b855cd0](https://github.com/helix-editor/helix/commit/b855cd0), [467fad5](https://github.com/helix-editor/helix/commit/467fad5), [191b0f0](https://github.com/helix-editor/helix/commit/191b0f0))
|
||||
* Add support for path completion ([#2608](https://github.com/helix-editor/helix/pull/2608))
|
||||
* Support bindings with the Super (Cmd/Win/Meta) modifier ([#6592](https://github.com/helix-editor/helix/pull/6592))
|
||||
* Support rendering and jumping between tabstops in snippet completions ([#9801](https://github.com/helix-editor/helix/pull/9801))
|
||||
* Allow theming directory completions ([#12205](https://github.com/helix-editor/helix/pull/12205), [#12295](https://github.com/helix-editor/helix/pull/12295))
|
||||
|
||||
Commands:
|
||||
|
||||
* Add commands to move within snake_case or camelCase words ([#8147](https://github.com/helix-editor/helix/pull/8147))
|
||||
* Add `search_selection_detect_word_boundaries` ([#12126](https://github.com/helix-editor/helix/pull/12126))
|
||||
* This command takes the `*` key in normal and select mode, replacing `search_selection` which was moved to `A-*`.
|
||||
|
||||
Usability improvements:
|
||||
|
||||
* Add `:edit` and `:e` aliases for `:open` ([#11186](https://github.com/helix-editor/helix/pull/11186), [#11196](https://github.com/helix-editor/helix/pull/11196))
|
||||
* Trim trailing newline from pipe command outputs when the input doesn't have a trailing newline ([#11183](https://github.com/helix-editor/helix/pull/11183), [4f63a46](https://github.com/helix-editor/helix/commit/4f63a46))
|
||||
* Add `:mv` alias for `:move` ([#11256](https://github.com/helix-editor/helix/pull/11256))
|
||||
* Return document display name instead of absolute path from the `%` special register ([#11275](https://github.com/helix-editor/helix/pull/11275))
|
||||
* Track view position on a per-view instead of per-document basis ([#10559](https://github.com/helix-editor/helix/pull/10559))
|
||||
* Improve scrolloff calculation to leave a gap in the middle ([#11323](https://github.com/helix-editor/helix/pull/11323))
|
||||
* Show a popup for stderr printed by failed `:sh` commands ([#11239](https://github.com/helix-editor/helix/pull/11239))
|
||||
* Add statusline errors when nothing is selected with `s`, `K`, `A-K` ([#11370](https://github.com/helix-editor/helix/pull/11370))
|
||||
* Add `.svn` as a workspace root marker ([#11429](https://github.com/helix-editor/helix/pull/11429))
|
||||
* Trim the end of `:sh` outputs ([#11161](https://github.com/helix-editor/helix/pull/11161))
|
||||
* Show LSP `window/showMessage` messages in the statusline ([#5535](https://github.com/helix-editor/helix/pull/5535))
|
||||
* Support finding workspace directories via `.jj` directories ([#11685](https://github.com/helix-editor/helix/pull/11685))
|
||||
* Join single-line comments with `join_selections` (`J`) ([#11742](https://github.com/helix-editor/helix/pull/11742))
|
||||
* Show anonymous syntax tree nodes in `:tree-sitter-subtree` ([#11663](https://github.com/helix-editor/helix/pull/11663), [38e8382](https://github.com/helix-editor/helix/commit/38e8382))
|
||||
* Save an undo checkpoint before paste in insert mode ([#8121](https://github.com/helix-editor/helix/pull/8121))
|
||||
* Only break on ASCII spaces in `:reflow` ([#12048](https://github.com/helix-editor/helix/pull/12048))
|
||||
* Add a `default-yank-register` config option ([#11430](https://github.com/helix-editor/helix/pull/11430))
|
||||
* Show a statusline error for `:format` when a formatter is not available ([#12183](https://github.com/helix-editor/helix/pull/12183))
|
||||
* Change to the home directory with `:cd` with no arguments ([#12042](https://github.com/helix-editor/helix/pull/12042))
|
||||
* Change default comment token to `#` for unrecognized files ([#12080](https://github.com/helix-editor/helix/pull/12080), [#12266](https://github.com/helix-editor/helix/pull/12266), [bae6a58](https://github.com/helix-editor/helix/commit/bae6a58))
|
||||
* Trim all trailing whitespace on `insert_newline` ([#12177](https://github.com/helix-editor/helix/pull/12177))
|
||||
* Change to the prior directory with `:cd -` ([#12194](https://github.com/helix-editor/helix/pull/12194))
|
||||
* Allow parsing `-` (with no modifiers) as a keybinding ([#12191](https://github.com/helix-editor/helix/pull/12191))
|
||||
* Improve opening statusline and error messages when opening duplicate files or directories ([#12199](https://github.com/helix-editor/helix/pull/12199))
|
||||
* Trim trailing colons in paths passed on the argv ([#9963](https://github.com/helix-editor/helix/pull/9963))
|
||||
* Show tree-sitter parser availability in `hx --health <lang>` ([#12228](https://github.com/helix-editor/helix/pull/12228))
|
||||
* Show a preview block for colors in the LSP completion menu ([#12299](https://github.com/helix-editor/helix/pull/12299))
|
||||
* Add infobox help for `surround_add`, `surround_replace` and `surround_delete` ([#12262](https://github.com/helix-editor/helix/pull/12262))
|
||||
|
||||
Fixes:
|
||||
|
||||
* Respect document indentation settings in `format_selections` (`=`) ([#11169](https://github.com/helix-editor/helix/pull/11169))
|
||||
* Avoid switching the current document to normal mode during an LSP `workspace/applyEdit` operation ([#11176](https://github.com/helix-editor/helix/pull/11176))
|
||||
* Fix off-by-one in LSP `find_completion_range` ([#11266](https://github.com/helix-editor/helix/pull/11266))
|
||||
* Prefer file-system mtime to local system time for detecting external modifications ([#11142](https://github.com/helix-editor/helix/pull/11142), [#11352](https://github.com/helix-editor/helix/pull/11352), [#11358](https://github.com/helix-editor/helix/pull/11358), [#11361](https://github.com/helix-editor/helix/pull/11361))
|
||||
* Fix writing of hardlinks ([#11340](https://github.com/helix-editor/helix/pull/11340))
|
||||
* Prevent language servers from being automatically restarted when stopped with `:lsp-stop` ([#11321](https://github.com/helix-editor/helix/pull/11321))
|
||||
* Stable-sort LSP text edits ([#11357](https://github.com/helix-editor/helix/pull/11357))
|
||||
* Fix determination of current language layer in documents with nested language injections ([#11365](https://github.com/helix-editor/helix/pull/11365))
|
||||
* Fix a panic from `:move`ing a file to a new extension which starts a language server ([#11387](https://github.com/helix-editor/helix/pull/11387))
|
||||
* Fix a panic from duplicating the diff gutter ([#11092](https://github.com/helix-editor/helix/pull/11092))
|
||||
* Keep cursor position when exactly replacing text ([#5930](https://github.com/helix-editor/helix/pull/5930))
|
||||
* Fix a panic from `jump_backward` on a newly opened split ([#11508](https://github.com/helix-editor/helix/pull/11508))
|
||||
* Fix a panic from language servers sending an unknown diagnostic severity ([#11569](https://github.com/helix-editor/helix/pull/11569))
|
||||
* Fix a panic when drawing at the edge of the screen ([#11737](https://github.com/helix-editor/helix/pull/11737))
|
||||
* Fix git repo detection on symlinks ([#11732](https://github.com/helix-editor/helix/pull/11732))
|
||||
* Fix a panic from a language server sending an out-of-range active signature index in `textDocument/signatureHelp` ([#11825](https://github.com/helix-editor/helix/pull/11825))
|
||||
* Fix a panic from using `C-k` in a prompt ending in a multi-byte character ([#12237](https://github.com/helix-editor/helix/pull/12237))
|
||||
* Expand tildes in paths passed to `:read` ([#12271](https://github.com/helix-editor/helix/pull/12271))
|
||||
* Respect per-language `workspace-lsp-roots` configuration when opening new documents ([#12223](https://github.com/helix-editor/helix/pull/12223))
|
||||
* Consistently replace line-endings in paste/replace commands ([c262fe4](https://github.com/helix-editor/helix/commit/c262fe4))
|
||||
* Fix formatting in error statusline messages when inspecting variables in DAP ([#12354](https://github.com/helix-editor/helix/pull/12354))
|
||||
* Fix invisible printing of headers in `--health` output on light terminals ([#12355](https://github.com/helix-editor/helix/pull/12355))
|
||||
* Accept integers serialized as floats in the JSONRPC `id` field ([#12376](https://github.com/helix-editor/helix/pull/12376))
|
||||
|
||||
Themes:
|
||||
|
||||
* Bring `kanagawa` colors better in line with neovim version ([#11187](https://github.com/helix-editor/helix/pull/11187), [#11270](https://github.com/helix-editor/helix/pull/11270))
|
||||
* Add `ao` ([#11063](https://github.com/helix-editor/helix/pull/11063))
|
||||
* Update `dark_plus` ([#11415](https://github.com/helix-editor/helix/pull/11415))
|
||||
* Add `iceberg-light` and `iceberg-dark` ([#10674](https://github.com/helix-editor/helix/pull/10674))
|
||||
* Update everforest themes ([#11459](https://github.com/helix-editor/helix/pull/11459))
|
||||
* Update gruvbox themes ([#11477](https://github.com/helix-editor/helix/pull/11477))
|
||||
* Change primary selection cursor color for `naysayer` ([#11617](https://github.com/helix-editor/helix/pull/11617))
|
||||
* Style picker column names in `horizon-dark` ([#11649](https://github.com/helix-editor/helix/pull/11649))
|
||||
* Style picker column names in Darcula themes ([#11649](https://github.com/helix-editor/helix/pull/11649))
|
||||
* Update diagnostics colors in `snazzy` ([#11731](https://github.com/helix-editor/helix/pull/11731))
|
||||
* Update bogster themes ([#11353](https://github.com/helix-editor/helix/pull/11353))
|
||||
* Highlight `keyword.storage` in `onedark` ([#11802](https://github.com/helix-editor/helix/pull/11802))
|
||||
* Add `ui.virtual.jump-label` to `serika-dark` ([#11911](https://github.com/helix-editor/helix/pull/11911))
|
||||
* Add `adwaita-light` ([#10869](https://github.com/helix-editor/helix/pull/10869))
|
||||
* Add seoul256 themes ([#11466](https://github.com/helix-editor/helix/pull/11466))
|
||||
* Add yo themes ([#11703](https://github.com/helix-editor/helix/pull/11703))
|
||||
* Add `eiffel` ([#11679](https://github.com/helix-editor/helix/pull/11679))
|
||||
* Add `carbonfox` ([#11558](https://github.com/helix-editor/helix/pull/11558))
|
||||
* Set tags color in monokai themes ([#11917](https://github.com/helix-editor/helix/pull/11917))
|
||||
* Improve readability of spacebones picker selection ([#12064](https://github.com/helix-editor/helix/pull/12064))
|
||||
* Update modus themes ([#11949](https://github.com/helix-editor/helix/pull/11949))
|
||||
* Use bold for statusline mode indicator in `onedarker` ([#11958](https://github.com/helix-editor/helix/pull/11958))
|
||||
* Update hex themes, add a new hex theme ([#10849](https://github.com/helix-editor/helix/pull/10849))
|
||||
* Add `sunset` ([#12093](https://github.com/helix-editor/helix/pull/12093))
|
||||
* Add bufferline highlighting for flexoki themes ([#12146](https://github.com/helix-editor/helix/pull/12146))
|
||||
* Add colors for (un)checked list items to catppuccin themes ([#12167](https://github.com/helix-editor/helix/pull/12167))
|
||||
* Update `voxed` ([#9328](https://github.com/helix-editor/helix/pull/9328))
|
||||
* Add `vintage` ([#9361](https://github.com/helix-editor/helix/pull/9361))
|
||||
* Add directory style to everforest themes ([#12287](https://github.com/helix-editor/helix/pull/12287))
|
||||
* Add inactive text and update jump label highlights in `dark_plus` ([#12289](https://github.com/helix-editor/helix/pull/12289))
|
||||
* Sync changes with catppuccin themes ([#12304](https://github.com/helix-editor/helix/pull/12304))
|
||||
* Add `ui.text.directory` to `nightfox` ([#12328](https://github.com/helix-editor/helix/pull/12328))
|
||||
* Add `ui.text.directory` to `sunset` ([#12328](https://github.com/helix-editor/helix/pull/12328))
|
||||
* Add `diagnostic.unnecessary` to Catppuccin themes ([#12391](https://github.com/helix-editor/helix/pull/12391))
|
||||
|
||||
New languages:
|
||||
|
||||
* `jjdescription` ([#11271](https://github.com/helix-editor/helix/pull/11271), [#11857](https://github.com/helix-editor/helix/pull/11857), [#12305](https://github.com/helix-editor/helix/pull/12305))
|
||||
* i3wm and Sway configs ([#11424](https://github.com/helix-editor/helix/pull/11424))
|
||||
* TypeSpec ([#11412](https://github.com/helix-editor/helix/pull/11412))
|
||||
* jq ([#11393](https://github.com/helix-editor/helix/pull/11393))
|
||||
* Thrift ([#11367](https://github.com/helix-editor/helix/pull/11367))
|
||||
* Gherkin ([#11083](https://github.com/helix-editor/helix/pull/11083))
|
||||
* Circom ([#11676](https://github.com/helix-editor/helix/pull/11676))
|
||||
* Dune ([#11829](https://github.com/helix-editor/helix/pull/11829))
|
||||
* Snakemake ([#11858](https://github.com/helix-editor/helix/pull/11858), [#11936](https://github.com/helix-editor/helix/pull/11936))
|
||||
* Cylc ([#11830](https://github.com/helix-editor/helix/pull/11830))
|
||||
* textproto ([#11874](https://github.com/helix-editor/helix/pull/11874))
|
||||
* Spade ([#11448](https://github.com/helix-editor/helix/pull/11448), [#12276](https://github.com/helix-editor/helix/pull/12276))
|
||||
* NestedText ([#11987](https://github.com/helix-editor/helix/pull/11987))
|
||||
* Quint ([#11898](https://github.com/helix-editor/helix/pull/11898))
|
||||
* Amber-lang ([#12021](https://github.com/helix-editor/helix/pull/12021))
|
||||
* Vento ([#12147](https://github.com/helix-editor/helix/pull/12147))
|
||||
* Teal ([#12081](https://github.com/helix-editor/helix/pull/12081))
|
||||
* Koto ([#12307](https://github.com/helix-editor/helix/pull/12307))
|
||||
* NGINX ([#12309](https://github.com/helix-editor/helix/pull/12309))
|
||||
|
||||
Updated languages and queries:
|
||||
|
||||
* Add comment injections for Hare ([#11173](https://github.com/helix-editor/helix/pull/11173))
|
||||
* Improve highlights for `blade.php` files ([#11138](https://github.com/helix-editor/helix/pull/11138))
|
||||
* Update tree-sitter-slint ([#11224](https://github.com/helix-editor/helix/pull/11224), [#11757](https://github.com/helix-editor/helix/pull/11757), [#12297](https://github.com/helix-editor/helix/pull/12297))
|
||||
* Recognize `just` files as Just ([#11286](https://github.com/helix-editor/helix/pull/11286))
|
||||
* Recognize `mdx` as Markdown ([#11122](https://github.com/helix-editor/helix/pull/11122))
|
||||
* Update Just grammar and queries ([#11306](https://github.com/helix-editor/helix/pull/11306))
|
||||
* Recognize `tclsh` as TCL ([#11236](https://github.com/helix-editor/helix/pull/11236))
|
||||
* Update Godot grammar and queries ([#11235](https://github.com/helix-editor/helix/pull/11235))
|
||||
* Update Gleam grammar and queries ([#11427](https://github.com/helix-editor/helix/pull/11427))
|
||||
* Add `mesonlsp` for Meson ([#11416](https://github.com/helix-editor/helix/pull/11416))
|
||||
* Update HTML highlights ([#11400](https://github.com/helix-editor/helix/pull/11400))
|
||||
* Add comment textobjects for Verilog ([#11388](https://github.com/helix-editor/helix/pull/11388))
|
||||
* Switch tree-sitter-just grammar ([#11380](https://github.com/helix-editor/helix/pull/11380), [#11606](https://github.com/helix-editor/helix/pull/11606), [#12141](https://github.com/helix-editor/helix/pull/12141))
|
||||
* Update tree-sitter-fsharp ([#11061](https://github.com/helix-editor/helix/pull/11061))
|
||||
* Add `nixd` for Nix ([#10767](https://github.com/helix-editor/helix/pull/10767))
|
||||
* Highlight types and enum members from the Rust prelude ([#8535](https://github.com/helix-editor/helix/pull/8535))
|
||||
* Improve textobjects for HCL, Nix ([#11513](https://github.com/helix-editor/helix/pull/11513))
|
||||
* Add textobjects queries for docker-compose, dockerfile, env, git-config, hcl, hocon, prisma, SQL and YAML ([#11513](https://github.com/helix-editor/helix/pull/11513))
|
||||
* Recognize cshtml files as HTML ([#11540](https://github.com/helix-editor/helix/pull/11540))
|
||||
* Set a memory limit for the Lean language server ([#11683](https://github.com/helix-editor/helix/pull/11683))
|
||||
* Add configurations for jedi and ruff language servers ([#11630](https://github.com/helix-editor/helix/pull/11630))
|
||||
* Update Vue highlights ([#11706](https://github.com/helix-editor/helix/pull/11706))
|
||||
* Switch tree-sitter-hcl grammar ([#11749](https://github.com/helix-editor/helix/pull/11749))
|
||||
* Fix `odinfmt` formatter configuration ([#11759](https://github.com/helix-editor/helix/pull/11759))
|
||||
* Recognize `rbs` files as Ruby ([#11786](https://github.com/helix-editor/helix/pull/11786))
|
||||
* Update tree-sitter-nickel ([#11771](https://github.com/helix-editor/helix/pull/11771))
|
||||
* Recognize `ldtk` and `ldtkl` files as JSON ([#11793](https://github.com/helix-editor/helix/pull/11793))
|
||||
* Fix highlights for builtin functions in Fish ([#11792](https://github.com/helix-editor/helix/pull/11792))
|
||||
* Add `superhtml` for HTML ([#11609](https://github.com/helix-editor/helix/pull/11609))
|
||||
* Add a configuration for the Vale language server ([#11636](https://github.com/helix-editor/helix/pull/11636))
|
||||
* Add Erlang Language Platform (`elp`) for Erlang ([#11499](https://github.com/helix-editor/helix/pull/11499))
|
||||
* Update Odin highlights ([#11804](https://github.com/helix-editor/helix/pull/11804))
|
||||
* Remove auto-pairs for single quotes in SML ([#11838](https://github.com/helix-editor/helix/pull/11838))
|
||||
* Add `glsl_analyzer` for GLSL ([#11891](https://github.com/helix-editor/helix/pull/11891))
|
||||
* Recognize `.prettierrc` as YAML ([#11997](https://github.com/helix-editor/helix/pull/11997))
|
||||
* Fix `swift-format` formatter configuration ([#12052](https://github.com/helix-editor/helix/pull/12052))
|
||||
* Add `package.json` and `tsconfig.json` as JS/TS workspace roots ([#10652](https://github.com/helix-editor/helix/pull/10652))
|
||||
* Add "INVARIANT" to comment error highlights ([#12094](https://github.com/helix-editor/helix/pull/12094))
|
||||
* Update Rescript grammar and queries ([#11165](https://github.com/helix-editor/helix/pull/11165))
|
||||
* Update tree-sitter-nasm ([#11795](https://github.com/helix-editor/helix/pull/11795))
|
||||
* Update LLVM grammars ([#11851](https://github.com/helix-editor/helix/pull/11851))
|
||||
* Update Perl and Pod grammars ([#11848](https://github.com/helix-editor/helix/pull/11848))
|
||||
* Add Nim injections in Nix ([#11837](https://github.com/helix-editor/helix/pull/11837))
|
||||
* Recognize `livemd` as Markdown ([#12034](https://github.com/helix-editor/helix/pull/12034))
|
||||
* Update Unison grammar and queries ([#12039](https://github.com/helix-editor/helix/pull/12039))
|
||||
* Turn off Swift auto-format by default ([#12071](https://github.com/helix-editor/helix/pull/12071))
|
||||
* Recognize `.swift-format` as JSON ([#12071](https://github.com/helix-editor/helix/pull/12071))
|
||||
* Recognize `.clangd` and `.clang-format` as YAML ([#12032](https://github.com/helix-editor/helix/pull/12032))
|
||||
* Recognize `ssh_config.d/*.conf` as sshclientconfig ([#11947](https://github.com/helix-editor/helix/pull/11947))
|
||||
* Update comment token configs for Zig ([#12049](https://github.com/helix-editor/helix/pull/12049))
|
||||
* Update tree-sitter-bicep ([#11525](https://github.com/helix-editor/helix/pull/11525))
|
||||
* Add `hyperls` for Hyperlang ([#11056](https://github.com/helix-editor/helix/pull/11056))
|
||||
* Add highlight queries for Solidity ([#12102](https://github.com/helix-editor/helix/pull/12102))
|
||||
* Recognize `WORKSPACE.bzlmod` as Starlark ([#12103](https://github.com/helix-editor/helix/pull/12103))
|
||||
* Update Ada grammar and queries ([#12131](https://github.com/helix-editor/helix/pull/12131))
|
||||
* Restrict Hocon file-types glob patterns ([#12156](https://github.com/helix-editor/helix/pull/12156))
|
||||
* Update Mojo language server to Magic ([#12195](https://github.com/helix-editor/helix/pull/12195))
|
||||
* Switch tree-sitter-v grammar ([#12236](https://github.com/helix-editor/helix/pull/12236))
|
||||
* Add "COMPLIANCE" to comment error highlights ([#12094](https://github.com/helix-editor/helix/pull/12094))
|
||||
* Add a language server configuration for `ltex-ls-plus` ([#12251](https://github.com/helix-editor/helix/pull/12251))
|
||||
* Update tree-sitter-dockerfile ([#12230](https://github.com/helix-editor/helix/pull/12230))
|
||||
* Add `]` to PHP outdents ([#12286](https://github.com/helix-editor/helix/pull/12286))
|
||||
* Add textobjects for Odin ([#12302](https://github.com/helix-editor/helix/pull/12302))
|
||||
* Update tree-sitter-heex and queries ([#12334](https://github.com/helix-editor/helix/pull/12334))
|
||||
* Update protobuf highlights ([#12339](https://github.com/helix-editor/helix/pull/12339))
|
||||
* Switch tree-sitter-query (TSQ) grammar ([#12148](https://github.com/helix-editor/helix/pull/12148), [e0bccd2](https://github.com/helix-editor/helix/commit/e0bccd2))
|
||||
* Add block comment configurations for jinja and nunjucks ([#12348](https://github.com/helix-editor/helix/pull/12348))
|
||||
* Add `uv` shebang for python ([#12360](https://github.com/helix-editor/helix/pull/12360))
|
||||
* Update tree-sitter-vento ([#12368](https://github.com/helix-editor/helix/pull/12368))
|
||||
* Switch Protobuf tree-sitter grammar ([#12225](https://github.com/helix-editor/helix/pull/12225))
|
||||
* Recognize `hypr/*.conf` as Hyprland ([#12384](https://github.com/helix-editor/helix/pull/12384))
|
||||
|
||||
Packaging:
|
||||
|
||||
* Add completions for Nushell ([#11262](https://github.com/helix-editor/helix/pull/11262), [#11346](https://github.com/helix-editor/helix/pull/11346))
|
||||
* Fix completion of flags in Bash completions ([#11246](https://github.com/helix-editor/helix/pull/11246))
|
||||
* Include shell completions in Nix outputs ([#11518](https://github.com/helix-editor/helix/pull/11518))
|
||||
|
||||
# 24.07 (2024-07-14)
|
||||
|
||||
Thanks to all of the contributors! This release has changes from 160 contributors.
|
||||
@@ -436,7 +1035,7 @@ Updated languages and queries:
|
||||
- Recognize common Dockerfile file types ([#9772](https://github.com/helix-editor/helix/pull/9772))
|
||||
- Recognize NUON files as Nu ([#9839](https://github.com/helix-editor/helix/pull/9839))
|
||||
- Add textobjects for Java native functions and constructors ([#9806](https://github.com/helix-editor/helix/pull/9806))
|
||||
- Fix "braket" typeo in JSX highlights ([#9910](https://github.com/helix-editor/helix/pull/9910))
|
||||
- Fix "braket" typo in JSX highlights ([#9910](https://github.com/helix-editor/helix/pull/9910))
|
||||
- Update tree-sitter-hurl ([#9775](https://github.com/helix-editor/helix/pull/9775))
|
||||
- Add textobjects queries for Vala ([#8541](https://github.com/helix-editor/helix/pull/8541))
|
||||
- Update tree-sitter-git-config ([9610254](https://github.com/helix-editor/helix/commit/9610254))
|
||||
@@ -643,7 +1242,7 @@ Updated languages and queries:
|
||||
- Add Fortran comment injections ([#7305](https://github.com/helix-editor/helix/pull/7305))
|
||||
- Switch Vue language server to `vue-language-server` ([#7312](https://github.com/helix-editor/helix/pull/7312))
|
||||
- Update tree-sitter-sql ([#7387](https://github.com/helix-editor/helix/pull/7387), [#8464](https://github.com/helix-editor/helix/pull/8464))
|
||||
- Replace the MATLAB tre-sitter grammar ([#7388](https://github.com/helix-editor/helix/pull/7388), [#7442](https://github.com/helix-editor/helix/pull/7442), [#7491](https://github.com/helix-editor/helix/pull/7491), [#7493](https://github.com/helix-editor/helix/pull/7493), [#7511](https://github.com/helix-editor/helix/pull/7511), [#7532](https://github.com/helix-editor/helix/pull/7532), [#8040](https://github.com/helix-editor/helix/pull/8040))
|
||||
- Replace the MATLAB tree-sitter grammar ([#7388](https://github.com/helix-editor/helix/pull/7388), [#7442](https://github.com/helix-editor/helix/pull/7442), [#7491](https://github.com/helix-editor/helix/pull/7491), [#7493](https://github.com/helix-editor/helix/pull/7493), [#7511](https://github.com/helix-editor/helix/pull/7511), [#7532](https://github.com/helix-editor/helix/pull/7532), [#8040](https://github.com/helix-editor/helix/pull/8040))
|
||||
- Highlight TOML table headers ([#7441](https://github.com/helix-editor/helix/pull/7441))
|
||||
- Recognize `cppm` file-type as C++ ([#7492](https://github.com/helix-editor/helix/pull/7492))
|
||||
- Refactor ecma language queries into private and public queries ([#7207](https://github.com/helix-editor/helix/pull/7207))
|
||||
@@ -1130,7 +1729,7 @@ Features:
|
||||
- Support underline styles and colors ([#4061](https://github.com/helix-editor/helix/pull/4061), [98c121c](https://github.com/helix-editor/helix/commit/98c121c))
|
||||
- Inheritance for themes ([#3067](https://github.com/helix-editor/helix/pull/3067), [#4096](https://github.com/helix-editor/helix/pull/4096))
|
||||
- Cursorcolumn ([#4084](https://github.com/helix-editor/helix/pull/4084))
|
||||
- Overhauled system for writing files and quiting ([#2267](https://github.com/helix-editor/helix/pull/2267), [#4397](https://github.com/helix-editor/helix/pull/4397))
|
||||
- Overhauled system for writing files and quitting ([#2267](https://github.com/helix-editor/helix/pull/2267), [#4397](https://github.com/helix-editor/helix/pull/4397))
|
||||
- Autosave when terminal loses focus ([#3178](https://github.com/helix-editor/helix/pull/3178))
|
||||
- Use OSC52 as a fallback for the system clipboard ([#3220](https://github.com/helix-editor/helix/pull/3220))
|
||||
- Show git diffs in the gutter ([#3890](https://github.com/helix-editor/helix/pull/3890), [#5012](https://github.com/helix-editor/helix/pull/5012), [#4995](https://github.com/helix-editor/helix/pull/4995))
|
||||
@@ -1285,7 +1884,7 @@ Themes:
|
||||
- Update `pop-dark` ([#4323](https://github.com/helix-editor/helix/pull/4323))
|
||||
- Update `rose_pine` ([#4221](https://github.com/helix-editor/helix/pull/4221))
|
||||
- Add `kanagawa` ([#4300](https://github.com/helix-editor/helix/pull/4300))
|
||||
- Add `hex_steel`, `hex_toxic` and `hex_lavendar` ([#4367](https://github.com/helix-editor/helix/pull/4367), [#4990](https://github.com/helix-editor/helix/pull/4990))
|
||||
- Add `hex_steel`, `hex_toxic` and `hex_lavender` ([#4367](https://github.com/helix-editor/helix/pull/4367), [#4990](https://github.com/helix-editor/helix/pull/4990))
|
||||
- Update `tokyonight` and `tokyonight_storm` ([#4415](https://github.com/helix-editor/helix/pull/4415))
|
||||
- Update `gruvbox` ([#4626](https://github.com/helix-editor/helix/pull/4626))
|
||||
- Update `dark_plus` ([#4661](https://github.com/helix-editor/helix/pull/4661), [#4678](https://github.com/helix-editor/helix/pull/4678))
|
||||
@@ -1452,7 +2051,7 @@ Usability improvements and fixes:
|
||||
- Introduce `keyword.storage` highlight scope ([#2731](https://github.com/helix-editor/helix/pull/2731))
|
||||
- Handle symlinks more consistently ([#2718](https://github.com/helix-editor/helix/pull/2718))
|
||||
- Improve markdown list rendering ([#2687](https://github.com/helix-editor/helix/pull/2687))
|
||||
- Update auto-pairs and idle-timout settings when the config is reloaded ([#2736](https://github.com/helix-editor/helix/pull/2736))
|
||||
- Update auto-pairs and idle-timeout settings when the config is reloaded ([#2736](https://github.com/helix-editor/helix/pull/2736))
|
||||
- Fix panic on closing last buffer ([#2658](https://github.com/helix-editor/helix/pull/2658))
|
||||
- Prevent modifying jumplist until jumping to a reference ([#2670](https://github.com/helix-editor/helix/pull/2670))
|
||||
- Ensure `:quit` and `:quit!` take no arguments ([#2654](https://github.com/helix-editor/helix/pull/2654))
|
||||
|
1233
Cargo.lock
generated
1233
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
20
Cargo.toml
20
Cargo.toml
@@ -22,13 +22,12 @@ default-members = [
|
||||
|
||||
[profile.release]
|
||||
lto = "thin"
|
||||
# debug = true
|
||||
|
||||
[profile.opt]
|
||||
inherits = "release"
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
# strip = "debuginfo" # TODO: or strip = true
|
||||
strip = true
|
||||
opt-level = 3
|
||||
|
||||
[profile.integration]
|
||||
@@ -38,17 +37,28 @@ package.helix-tui.opt-level = 2
|
||||
package.helix-term.opt-level = 2
|
||||
|
||||
[workspace.dependencies]
|
||||
tree-sitter = { version = "0.22" }
|
||||
tree-house = { version = "0.3.0", default-features = false }
|
||||
nucleo = "0.5.0"
|
||||
slotmap = "1.0.7"
|
||||
thiserror = "2.0"
|
||||
tempfile = "3.22.0"
|
||||
bitflags = "2.9"
|
||||
unicode-segmentation = "1.2"
|
||||
ropey = { version = "1.6.1", default-features = false, features = ["simd"] }
|
||||
foldhash = "0.2"
|
||||
parking_lot = "0.12"
|
||||
futures-executor = "0.3"
|
||||
futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
|
||||
tokio-stream = "0.1.17"
|
||||
toml = "0.9"
|
||||
termina = "0.1"
|
||||
|
||||
[workspace.package]
|
||||
version = "24.7.0"
|
||||
version = "25.7.1"
|
||||
edition = "2021"
|
||||
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
||||
categories = ["editor"]
|
||||
repository = "https://github.com/helix-editor/helix"
|
||||
homepage = "https://helix-editor.com"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.76"
|
||||
rust-version = "1.82"
|
||||
|
@@ -32,7 +32,7 @@
|
||||
"ui.statusline.select" = { fg = "black", bg = "blue" }
|
||||
"ui.virtual" = { fg = "gray", modifiers = ["italic"] }
|
||||
"ui.virtual.jump-label" = { fg = "blue", modifiers = ["bold", "underlined"] }
|
||||
"ui.virtual.ruler" = { bg = "black" }
|
||||
"ui.virtual.ruler" = { fg = "black" }
|
||||
|
||||
"markup.heading" = "blue"
|
||||
"markup.list" = "red"
|
||||
|
@@ -1,7 +1,6 @@
|
||||
[book]
|
||||
authors = ["Blaž Hrastnik"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
|
||||
[output.html]
|
||||
|
@@ -11,10 +11,14 @@
|
||||
- [Textobjects](./textobjects.md)
|
||||
- [Syntax aware motions](./syntax-aware-motions.md)
|
||||
- [Pickers](./pickers.md)
|
||||
- [Jumplist](./jumplist.md)
|
||||
- [Keymap](./keymap.md)
|
||||
- [Command line](./command-line.md)
|
||||
- [Commands](./commands.md)
|
||||
- [Language support](./lang-support.md)
|
||||
- [Migrating from Vim](./from-vim.md)
|
||||
- [Ecosystem](./ecosystem.md)
|
||||
- [Migrating from Vim](./from-vim.md)
|
||||
- [Helix mode in other software](./other-software.md)
|
||||
- [Configuration](./configuration.md)
|
||||
- [Editor](./editor.md)
|
||||
- [Themes](./themes.md)
|
||||
@@ -25,3 +29,5 @@
|
||||
- [Adding textobject queries](./guides/textobject.md)
|
||||
- [Adding indent queries](./guides/indent.md)
|
||||
- [Adding injection queries](./guides/injection.md)
|
||||
- [Adding tags queries](./guides/tags.md)
|
||||
- [Adding rainbow bracket queries](./guides/rainbow_bracket_queries.md)
|
||||
|
@@ -7,6 +7,7 @@
|
||||
- [Note to packagers](#note-to-packagers)
|
||||
- [Validating the installation](#validating-the-installation)
|
||||
- [Configure the desktop shortcut](#configure-the-desktop-shortcut)
|
||||
- [Building the Debian package](#building-the-debian-package)
|
||||
|
||||
Requirements:
|
||||
|
||||
@@ -34,10 +35,19 @@ RUSTFLAGS="-C target-feature=-crt-static"
|
||||
2. Compile from source:
|
||||
|
||||
```sh
|
||||
# Reproducible
|
||||
cargo install --path helix-term --locked
|
||||
```
|
||||
```sh
|
||||
# Optimized
|
||||
cargo install \
|
||||
--profile opt \
|
||||
--config 'build.rustflags=["-C", "target-cpu=native"]' \
|
||||
--path helix-term \
|
||||
--locked
|
||||
```
|
||||
|
||||
This command will create the `hx` executable and construct the tree-sitter
|
||||
Either command will create the `hx` executable and construct the tree-sitter
|
||||
grammars in the local `runtime` folder.
|
||||
|
||||
> 💡 If you do not want to fetch or build grammars, set an environment variable `HELIX_DISABLE_AUTO_GRAMMAR_BUILD`
|
||||
@@ -63,11 +73,9 @@ export HELIX_RUNTIME=~/src/helix/runtime
|
||||
Or, create a symbolic link:
|
||||
|
||||
```sh
|
||||
ln -Ts $PWD/runtime ~/.config/helix/runtime
|
||||
ln -Tsf $PWD/runtime ~/.config/helix/runtime
|
||||
```
|
||||
|
||||
If the above command fails to create a symbolic link because the file exists either move `~/.config/helix/runtime` to a new location or delete it, then run the symlink command above again.
|
||||
|
||||
#### Windows
|
||||
|
||||
Either set the `HELIX_RUNTIME` environment variable to point to the runtime files using the Windows setting (search for
|
||||
@@ -75,7 +83,7 @@ Either set the `HELIX_RUNTIME` environment variable to point to the runtime file
|
||||
Cmd:
|
||||
|
||||
```sh
|
||||
setx HELIX_RUNTIME "%userprofile%\source\repos\helix\runtime"
|
||||
setx HELIX_RUNTIME "%userprofile%\src\helix\runtime"
|
||||
```
|
||||
|
||||
> 💡 `%userprofile%` resolves to your user directory like
|
||||
@@ -162,3 +170,39 @@ file. For example, to use `kitty`:
|
||||
sed -i "s|Exec=hx %F|Exec=kitty hx %F|g" ~/.local/share/applications/Helix.desktop
|
||||
sed -i "s|Terminal=true|Terminal=false|g" ~/.local/share/applications/Helix.desktop
|
||||
```
|
||||
|
||||
### Building the Debian package
|
||||
|
||||
If the `.deb` file provided on the release page uses a `libc` version higher
|
||||
than that used by your Debian, Ubuntu, or Mint system, you can build the package
|
||||
from source to match your system's dependencies.
|
||||
|
||||
Install `cargo-deb`, the tool used for building the `.deb` file:
|
||||
|
||||
```sh
|
||||
cargo install cargo-deb
|
||||
```
|
||||
|
||||
After cloning and entering the Helix repository as previously described,
|
||||
use the following command to build the release binary and package it into a `.deb` file in a single step.
|
||||
|
||||
```sh
|
||||
cargo deb -- --locked
|
||||
```
|
||||
|
||||
> 💡 This locks you into the `--release` profile. But you can also build helix in any way you like.
|
||||
> As long as you leave a `target/release/hx` file, it will get packaged with `cargo deb --no-build`
|
||||
|
||||
> 💡 Don't worry about the following:
|
||||
> ```
|
||||
> warning: Failed to find dependency specification
|
||||
> ```
|
||||
> Cargo deb just reports which packaged files it didn't derive dependencies for. But
|
||||
> so far the dependency deriving seams very good, even if some of the grammar files are skipped.
|
||||
|
||||
You can find the resulted `.deb` in `target/debian/`. It should contain everything it needs, including the
|
||||
|
||||
- completions for bash, fish, zsh
|
||||
- .desktop file
|
||||
- icon (though desktop environments might use their own since the name of the package is correctly `helix`)
|
||||
- launcher to the binary with the runtime
|
||||
|
88
book/src/command-line.md
Normal file
88
book/src/command-line.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Command line
|
||||
|
||||
- [Quoting](#quoting)
|
||||
- [Flags](#flags)
|
||||
- [Expansions](#expansions)
|
||||
- [Exceptions](#exceptions)
|
||||
|
||||
The command line is used for executing [typable commands](./commands.md#typable-commands) like `:write` or `:quit`. Press `:` to activate the command line.
|
||||
|
||||
Typable commands optionally accept arguments. `:write` for example accepts an optional path to write the file contents. The command line also supports a quoting syntax for arguments, flags to modify command behaviors, and _expansions_ - a way to insert values from the editor. Most commands support these features but some have custom parsing rules (see the [exceptions](#exceptions) below).
|
||||
|
||||
## Quoting
|
||||
|
||||
By default, command arguments are split on tabs and space characters. `:open README.md CHANGELOG.md` for example should open two files, `README.md` and `CHANGELOG.md`. Arguments that contain spaces can be surrounded in single quotes (`'`) or backticks (`` ` ``) to prevent the space from separating the argument, like `:open 'a b.txt'`.
|
||||
|
||||
Double quotes may be used the same way, but double quotes _expand_ their inner content. `:echo "%{cursor_line}"` for example may print `1` because of the expansion for the `cursor_line` variable. `:echo '%{cursor_line}'` though prints `%{cursor_line}` literally: content within single quotes or backticks is interpreted as-is.
|
||||
|
||||
On Unix systems the backslash character may be used to escape certain characters depending on where it is used. Within an argument which isn't surround in quotes, the backslash can be used to escape the space or tab characters: `:open a\ b.txt` is equivalent to `:open 'a b.txt'`. The backslash may also be used to escape quote characters (`'`, `` ` ``, `"`) or the percent token (`%`) when used at the beginning of an argument. `:echo \%%sh{foo}` for example prints `%sh{foo}` instead of invoking a `foo` shell command and `:echo \"quote` prints `"quote`. The backslash character is treated literally in any other situation on Unix systems and always on Windows: `:echo \n` always prints `\n`.
|
||||
|
||||
## Flags
|
||||
|
||||
Command flags are optional switches that can be used to alter the behavior of a command. For example the `:sort` command accepts an optional `--reverse` (or `-r` for short) flag which causes the sort command to reverse the sorting direction. Typing the `-` character shows completions for the current command's flags, if any.
|
||||
|
||||
The `--` flag specifies the end of flags. All arguments after `--` are treated as positional arguments: `:open -- -a.txt` opens a file called `-a.txt`.
|
||||
|
||||
## Expansions
|
||||
|
||||
Expansions are patterns that Helix recognizes and replaces within the command line. Helix recognizes anything starting with a percent token (`%`) as an expansion, for example `%sh{echo hi!}`. Expansions are particularly useful when used in commands like `:echo` or `:noop` for executing simple scripts. For example:
|
||||
|
||||
```toml
|
||||
[keys.normal]
|
||||
# Print the current line's git blame information to the statusline.
|
||||
space.B = ":echo %sh{git blame -L %{cursor_line},+1 %{buffer_name}}"
|
||||
```
|
||||
|
||||
Expansions take the form `%[<kind>]<open><contents><close>`. In `%sh{echo hi!}`, for example, the kind is `sh` - the shell expansion - and the contents are "echo hi!", with `{` and `}` acting as opening and closing delimiters. The following open/close characters are recognized as expansion delimiter pairs: `(`/`)`, `[`/`]`, `{`/`}` and `<`/`>`. Plus the single characters `'`, `"` or `|` may be used instead: `%{cursor_line}` is equivalent to `%<cursor_line>`, `%[cursor_line]` or `%|cursor_line|`.
|
||||
|
||||
To escape a percent character instead of treating it as an expansion, use two percent characters consecutively. To execute a shell command like `date -u +'%Y-%m-%d'`, double the percent characters: `:echo %sh{date -u +'%%Y-%%m-%%d'}`.
|
||||
|
||||
When no `<kind>` is provided, Helix will expand a **variable**. For example `%{cursor_line}` can be used as in argument to insert the line number. `:echo %{cursor_line}` for instance may print `1` to the statusline.
|
||||
|
||||
The following variables are supported:
|
||||
|
||||
| Name | Description |
|
||||
|--- |--- |
|
||||
| `cursor_line` | The line number of the primary cursor in the currently focused document, starting at 1. |
|
||||
| `cursor_column` | The column number of the primary cursor in the currently focused document, starting at 1. This is counted as the number of grapheme clusters from the start of the line rather than bytes or codepoints. |
|
||||
| `buffer_name` | The relative path of the currently focused document. `[scratch]` is expanded instead for scratch buffers. |
|
||||
| `line_ending` | A string containing the line ending of the currently focused document. For example on Unix systems this is usually a line-feed character (`\n`) but on Windows systems this may be a carriage-return plus a line-feed (`\r\n`). The line ending kind of the currently focused document can be inspected with the `:line-ending` command. |
|
||||
| `current_working_directory` | Current working directory |
|
||||
| `workspace_directory` | Nearest ancestor directory of the current working directory that contains `.git`, `.svn`, `jj` or `.helix` |
|
||||
| `language` | A string containing the language name of the currently focused document.|
|
||||
| `selection` | A string containing the contents of the primary selection of the currently focused document. |
|
||||
| `selection_line_start` | The line number of the start of the primary selection in the currently focused document, starting at 1. |
|
||||
| `selection_line_end` | The line number of the end of the primary selection in the currently focused document, starting at 1. |
|
||||
|
||||
Aside from editor variables, the following expansions may be used:
|
||||
|
||||
* Unicode `%u{..}`. The contents may contain up to six hexadecimal numbers corresponding to a Unicode codepoint value. For example `:echo %u{25CF}` prints `●` to the statusline.
|
||||
* Shell `%sh{..}`. The contents are passed to the configured shell command. For example `:echo %sh{echo "20 * 5" | bc}` may print `100` on the statusline on when using a shell with `echo` and the `bc` calculator installed. Shell expansions are evaluated recursively. `%sh{echo '%{buffer_name}:%{cursor_line}'}` for example executes a command like `echo 'README.md:1'`: the variables within the `%sh{..}` expansion are evaluated before executing the shell command.
|
||||
|
||||
As mentioned above, double quotes can be used to surround arguments containing spaces but also support expansions within the quoted content unlike singe quotes or backticks. For example `:echo "circle: %u{25CF}"` prints `circle: ●` to the statusline while `:echo 'circle: %u{25CF}'` prints `circle: %u{25CF}`.
|
||||
|
||||
Note that expansions are only evaluated once the Enter key is pressed in command mode.
|
||||
|
||||
## Exceptions
|
||||
|
||||
The following commands support expansions but otherwise pass the given argument directly to the shell program without interpreting quotes:
|
||||
|
||||
* `:insert-output`
|
||||
* `:append-output`
|
||||
* `:pipe`
|
||||
* `:pipe-to`
|
||||
* `:run-shell-command`
|
||||
|
||||
For example executing `:sh echo "%{buffer_name}:%{cursor_column}"` would pass text like `echo "README.md:1"` as an argument to the shell program: the expansions are evaluated but not the quotes. As mentioned above, percent characters can be used in shell commands by doubling the percent character. To insert the output of a command like `date -u +'%Y-%m-%d'` use `:insert-output date -u +'%%Y-%%m-%%d'`.
|
||||
|
||||
The `:set-option` and `:toggle-option` commands use regular parsing for the first argument - the config option name - and parse the rest depending on the config option's type. `:set-option` interprets the second argument as a string for string config options and parses everything else as JSON.
|
||||
|
||||
`:toggle-option`'s behavior depends on the JSON type of the config option supplied as the first argument:
|
||||
|
||||
* Booleans: only the config option name should be provided. For example `:toggle-option auto-format` will flip the `auto-format` option.
|
||||
* Strings: the rest of the command line is parsed with regular quoting rules. For example `:toggle-option indent-heuristic hybrid tree-sitter simple` cycles through "hybrid", "tree-sitter" and "simple" values on each invocation of the command.
|
||||
* Numbers, arrays and objects: the rest of the command line is parsed as a stream of JSON values. For example `:toggle-option rulers [81] [51, 73]` cycles through `[81]` and `[51, 73]`.
|
||||
|
||||
When providing multiple values to `:toggle-option` there should be no duplicates. `:toggle-option indent-heuristic hybrid simple tree-sitter simple` for example would only toggle between "hybrid" and "tree-sitter" values.
|
||||
|
||||
`:lsp-workspace-command` works similarly to `:toggle-option`. The first argument (if present) is parsed according to normal rules. The rest of the line is parsed as JSON values. Unlike `:toggle-option`, string arguments for a command must be quoted. For example `:lsp-workspace-command lsp.Command "foo" "bar"`.
|
3
book/src/ecosystem.md
Normal file
3
book/src/ecosystem.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Ecosystem
|
||||
|
||||
This section has information related to the wider Helix ecosystem.
|
@@ -1,11 +1,13 @@
|
||||
## Editor
|
||||
|
||||
- [`[editor]` Section](#editor-section)
|
||||
- [`[editor.clipboard-provider]` Section](#editorclipboard-provider-section)
|
||||
- [`[editor.statusline]` Section](#editorstatusline-section)
|
||||
- [`[editor.lsp]` Section](#editorlsp-section)
|
||||
- [`[editor.cursor-shape]` Section](#editorcursor-shape-section)
|
||||
- [`[editor.file-picker]` Section](#editorfile-picker-section)
|
||||
- [`[editor.auto-pairs]` Section](#editorauto-pairs-section)
|
||||
- [`[editor.auto-save]` Section](#editorauto-save-section)
|
||||
- [`[editor.search]` Section](#editorsearch-section)
|
||||
- [`[editor.whitespace]` Section](#editorwhitespace-section)
|
||||
- [`[editor.indent-guides]` Section](#editorindent-guides-section)
|
||||
@@ -17,6 +19,7 @@
|
||||
- [`[editor.soft-wrap]` Section](#editorsoft-wrap-section)
|
||||
- [`[editor.smart-tab]` Section](#editorsmart-tab-section)
|
||||
- [`[editor.inline-diagnostics]` Section](#editorinline-diagnostics-section)
|
||||
- [`[editor.word-completion]` Section](#editorword-completion-section)
|
||||
|
||||
### `[editor]` Section
|
||||
|
||||
@@ -45,17 +48,24 @@
|
||||
| `true-color` | Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative | `false` |
|
||||
| `undercurl` | Set to `true` to override automatic detection of terminal undercurl support in the event of a false negative | `false` |
|
||||
| `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file | `[]` |
|
||||
| `ruler-char` | Character used to draw rulers | `┊` |
|
||||
| `bufferline` | Renders a line at the top of the editor displaying open buffers. Can be `always`, `never` or `multiple` (only shown if more than one buffer is in use) | `never` |
|
||||
| `color-modes` | Whether to color the mode indicator with different colors depending on the mode itself | `false` |
|
||||
| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set | `80` |
|
||||
| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` |
|
||||
| `default-line-ending` | The line ending to use for new documents. Can be `native`, `lf`, `crlf`, `ff`, `cr` or `nel`. `native` uses the platform's native line ending (`crlf` on Windows, otherwise `lf`). | `native` |
|
||||
| `insert-final-newline` | Whether to automatically insert a trailing line-ending on write if missing | `true` |
|
||||
| `atomic-save` | Whether to use atomic operations to write documents to disk. This prevents data loss if the editor is interrupted while writing the file, but may confuse some file watching/hot reloading programs. | `true` |
|
||||
| `trim-final-newlines` | Whether to automatically remove line-endings after the final one on write | `false` |
|
||||
| `trim-trailing-whitespace` | Whether to automatically remove whitespace preceding line endings on write | `false` |
|
||||
| `popup-border` | Draw border around `popup`, `menu`, `all`, or `none` | `none` |
|
||||
| `indent-heuristic` | How the indentation for a newly inserted line is computed: `simple` just copies the indentation level from the previous line, `tree-sitter` computes the indentation based on the syntax tree and `hybrid` combines both approaches. If the chosen heuristic is not available, a different one will be used as a fallback (the fallback order being `hybrid` -> `tree-sitter` -> `simple`). | `hybrid`
|
||||
| `jump-label-alphabet` | The characters that are used to generate two character jump labels. Characters at the start of the alphabet are used first. | `"abcdefghijklmnopqrstuvwxyz"`
|
||||
| `end-of-line-diagnostics` | Minimum severity of diagnostics to render at the end of the line. Set to `disable` to disable entirely. Refer to the setting about `inline-diagnostics` for more details | "disable"
|
||||
| `clipboard-provider` | Which API to use for clipboard interaction. One of `pasteboard` (MacOS), `wayland`, `x-clip`, `x-sel`, `win-32-yank`, `termux`, `tmux`, `windows`, `termcode`, `none`, or a custom command set. | Platform and environment specific. |
|
||||
| `end-of-line-diagnostics` | Minimum severity of diagnostics to render at the end of the line. Set to `disable` to disable entirely. Refer to the setting about `inline-diagnostics` for more details | `"hint"`
|
||||
| `clipboard-provider` | Which API to use for clipboard interaction. One of `pasteboard` (MacOS), `wayland`, `x-clip`, `x-sel`, `win32-yank`, `termux`, `tmux`, `windows`, `termcode`, `none`, or a custom command set. | Platform and environment specific. |
|
||||
| `editor-config` | Whether to read settings from [EditorConfig](https://editorconfig.org) files | `true` |
|
||||
| `rainbow-brackets` | Whether to render rainbow colors for matching brackets. Requires tree-sitter `rainbows.scm` queries for the language. | `false` |
|
||||
| `kitty-keyboard-protocol` | Whether to enable Kitty Keyboard Protocol. Can be `enabled`, `disabled` or `auto` | `auto` |
|
||||
|
||||
### `[editor.clipboard-provider]` Section
|
||||
|
||||
@@ -68,7 +78,7 @@ For instance, setting it to use OSC 52 termcodes, the configuration would be:
|
||||
clipboard-provider = "termcode"
|
||||
```
|
||||
|
||||
Alternatively, Helix can be configured to use arbitary commands for clipboard integration:
|
||||
Alternatively, Helix can be configured to use arbitrary commands for clipboard integration:
|
||||
|
||||
```toml
|
||||
[editor.clipboard-provider.custom]
|
||||
@@ -99,6 +109,8 @@ separator = "│"
|
||||
mode.normal = "NORMAL"
|
||||
mode.insert = "INSERT"
|
||||
mode.select = "SELECT"
|
||||
diagnostics = ["warning", "error"]
|
||||
workspace-diagnostics = ["warning", "error"]
|
||||
```
|
||||
The `[editor.statusline]` key takes the following sub-keys:
|
||||
|
||||
@@ -111,6 +123,8 @@ The `[editor.statusline]` key takes the following sub-keys:
|
||||
| `mode.normal` | The text shown in the `mode` element for normal mode | `"NOR"` |
|
||||
| `mode.insert` | The text shown in the `mode` element for insert mode | `"INS"` |
|
||||
| `mode.select` | The text shown in the `mode` element for select mode | `"SEL"` |
|
||||
| `diagnostics` | A list of severities which are displayed for the current buffer | `["warning", "error"]` |
|
||||
| `workspace-diagnostics` | A list of severities which are displayed for the workspace | `["warning", "error"]` |
|
||||
|
||||
The following statusline elements can be configured:
|
||||
|
||||
@@ -121,15 +135,17 @@ The following statusline elements can be configured:
|
||||
| `file-name` | The path/name of the opened file |
|
||||
| `file-absolute-path` | The absolute path/name of the opened file |
|
||||
| `file-base-name` | The basename of the opened file |
|
||||
| `current-working-directory` | The current working directory |
|
||||
| `file-modification-indicator` | The indicator to show whether the file is modified (a `[+]` appears when there are unsaved changes) |
|
||||
| `file-encoding` | The encoding of the opened file if it differs from UTF-8 |
|
||||
| `file-line-ending` | The file line endings (CRLF or LF) |
|
||||
| `file-indent-style` | The file indentation style |
|
||||
| `read-only-indicator` | An indicator that shows `[readonly]` when a file cannot be written |
|
||||
| `total-line-numbers` | The total line numbers of the opened file |
|
||||
| `file-type` | The type of the opened file |
|
||||
| `diagnostics` | The number of warnings and/or errors |
|
||||
| `workspace-diagnostics` | The number of warnings and/or errors on workspace |
|
||||
| `selections` | The number of active selections |
|
||||
| `selections` | The primary selection index out of the number of active selections |
|
||||
| `primary-selection-length` | The number of characters currently in primary selection |
|
||||
| `position` | The cursor position |
|
||||
| `position-percentage` | The cursor position as a percentage of the total number of lines |
|
||||
@@ -143,9 +159,12 @@ The following statusline elements can be configured:
|
||||
| Key | Description | Default |
|
||||
| --- | ----------- | ------- |
|
||||
| `enable` | Enables LSP integration. Setting to false will completely disable language servers regardless of language settings.| `true` |
|
||||
| `display-messages` | Display LSP progress messages below statusline[^1] | `false` |
|
||||
| `display-messages` | Display LSP `window/showMessage` messages below statusline[^1] | `true` |
|
||||
| `display-progress-messages` | Display LSP progress messages below statusline[^1] | `false` |
|
||||
| `auto-signature-help` | Enable automatic popup of signature help (parameter hints) | `true` |
|
||||
| `display-inlay-hints` | Display inlay hints[^2] | `false` |
|
||||
| `inlay-hints-length-limit` | Maximum displayed length (non-zero number) of inlay hints | Unset by default |
|
||||
| `display-color-swatches` | Show color swatches next to colors | `true` |
|
||||
| `display-signature-help-docs` | Display docs under signature help popup | `true` |
|
||||
| `snippets` | Enables snippet completions. Requires a server restart (`:lsp-restart`) to take effect after `:config-reload`/`:set`. | `true` |
|
||||
| `goto-reference-include-declaration` | Include declaration in the goto references popup. | `true` |
|
||||
@@ -436,12 +455,14 @@ fn main() {
|
||||
|
||||
| Key | Description | Default |
|
||||
|------------|-------------|---------|
|
||||
| `cursor-line` | The minimum severity that a diagnostic must have to be shown inline on the line that contains the primary cursor. Set to `disable` to not show any diagnostics inline. This option does not have any effect when in insert-mode and will only take effect 350ms after moving the cursor to a different line. | `"disable"` |
|
||||
| `cursor-line` | The minimum severity that a diagnostic must have to be shown inline on the line that contains the primary cursor. Set to `disable` to not show any diagnostics inline. This option does not have any effect when in insert-mode and will only take effect 350ms after moving the cursor to a different line. | `"warning"` |
|
||||
| `other-lines` | The minimum severity that a diagnostic must have to be shown inline on a line that does not contain the cursor-line. Set to `disable` to not show any diagnostics inline. | `"disable"` |
|
||||
| `prefix-len` | How many horizontal bars `─` are rendered before the diagnostic text. | `1` |
|
||||
| `max-wrap` | Equivalent of the `editor.soft-wrap.max-wrap` option for diagnostics. | `20` |
|
||||
| `max-diagnostics` | Maximum number of diagnostics to render inline for a given line | `10` |
|
||||
|
||||
The allowed values for `cursor-line` and `other-lines` are: `error`, `warning`, `info`, `hint`.
|
||||
|
||||
The (first) diagnostic with the highest severity that is not shown inline is rendered at the end of the line (as long as its severity is higher than the `end-of-line-diagnostics` config option):
|
||||
|
||||
```
|
||||
@@ -452,12 +473,20 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
### `[editor.word-completion]` Section
|
||||
|
||||
The new diagnostic rendering is not yet enabled by default. As soon as end of line or inline diagnostics are enabled the old diagnostics rendering is automatically disabled. The recommended default setting are:
|
||||
Options for controlling completion of words from open buffers.
|
||||
|
||||
| Key | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `enable` | Whether word completion is enabled | `true` |
|
||||
| `trigger-length` | Number of word characters to type before triggering completion | `7` |
|
||||
|
||||
Example:
|
||||
|
||||
```toml
|
||||
[editor]
|
||||
end-of-line-diagnostics = "hint"
|
||||
[editor.inline-diagnostics]
|
||||
cursor-line = "warning" # show warnings and errors on the cursorline inline
|
||||
[editor.word-completion]
|
||||
enable = true
|
||||
# Set the trigger length lower so that words are completed more often
|
||||
trigger-length = 4
|
||||
```
|
||||
|
@@ -1,245 +1,301 @@
|
||||
| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default language servers |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| ada | ✓ | ✓ | | `ada_language_server` |
|
||||
| adl | ✓ | ✓ | ✓ | |
|
||||
| agda | ✓ | | | |
|
||||
| amber | ✓ | | | |
|
||||
| astro | ✓ | | | |
|
||||
| awk | ✓ | ✓ | | `awk-language-server` |
|
||||
| bash | ✓ | ✓ | ✓ | `bash-language-server` |
|
||||
| bass | ✓ | | | `bass` |
|
||||
| beancount | ✓ | | | |
|
||||
| bibtex | ✓ | | | `texlab` |
|
||||
| bicep | ✓ | | | `bicep-langserver` |
|
||||
| bitbake | ✓ | | | `bitbake-language-server` |
|
||||
| blade | ✓ | | | |
|
||||
| blueprint | ✓ | | | `blueprint-compiler` |
|
||||
| c | ✓ | ✓ | ✓ | `clangd` |
|
||||
| c-sharp | ✓ | ✓ | | `OmniSharp` |
|
||||
| cabal | | | | `haskell-language-server-wrapper` |
|
||||
| cairo | ✓ | ✓ | ✓ | `cairo-language-server` |
|
||||
| capnp | ✓ | | ✓ | |
|
||||
| cel | ✓ | | | |
|
||||
| circom | ✓ | | | `circom-lsp` |
|
||||
| clojure | ✓ | | | `clojure-lsp` |
|
||||
| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
|
||||
| comment | ✓ | | | |
|
||||
| common-lisp | ✓ | | ✓ | `cl-lsp` |
|
||||
| cpon | ✓ | | ✓ | |
|
||||
| cpp | ✓ | ✓ | ✓ | `clangd` |
|
||||
| crystal | ✓ | ✓ | | `crystalline` |
|
||||
| css | ✓ | | ✓ | `vscode-css-language-server` |
|
||||
| cue | ✓ | | | `cuelsp` |
|
||||
| cylc | ✓ | ✓ | ✓ | |
|
||||
| d | ✓ | ✓ | ✓ | `serve-d` |
|
||||
| dart | ✓ | ✓ | ✓ | `dart` |
|
||||
| dbml | ✓ | | | |
|
||||
| devicetree | ✓ | | | |
|
||||
| dhall | ✓ | ✓ | | `dhall-lsp-server` |
|
||||
| diff | ✓ | | | |
|
||||
| docker-compose | ✓ | ✓ | ✓ | `docker-compose-langserver`, `yaml-language-server` |
|
||||
| dockerfile | ✓ | ✓ | | `docker-langserver` |
|
||||
| dot | ✓ | | | `dot-language-server` |
|
||||
| dtd | ✓ | | | |
|
||||
| dune | ✓ | | | |
|
||||
| earthfile | ✓ | ✓ | ✓ | `earthlyls` |
|
||||
| edoc | ✓ | | | |
|
||||
| eex | ✓ | | | |
|
||||
| ejs | ✓ | | | |
|
||||
| elisp | ✓ | | | |
|
||||
| elixir | ✓ | ✓ | ✓ | `elixir-ls` |
|
||||
| elm | ✓ | ✓ | | `elm-language-server` |
|
||||
| elvish | ✓ | | | `elvish` |
|
||||
| env | ✓ | ✓ | | |
|
||||
| erb | ✓ | | | |
|
||||
| erlang | ✓ | ✓ | | `erlang_ls`, `elp` |
|
||||
| esdl | ✓ | | | |
|
||||
| fidl | ✓ | | | |
|
||||
| fish | ✓ | ✓ | ✓ | |
|
||||
| forth | ✓ | | | `forth-lsp` |
|
||||
| fortran | ✓ | | ✓ | `fortls` |
|
||||
| fsharp | ✓ | | | `fsautocomplete` |
|
||||
| gas | ✓ | ✓ | | |
|
||||
| gdscript | ✓ | ✓ | ✓ | |
|
||||
| gemini | ✓ | | | |
|
||||
| gherkin | ✓ | | | |
|
||||
| git-attributes | ✓ | | | |
|
||||
| git-commit | ✓ | ✓ | | |
|
||||
| git-config | ✓ | ✓ | | |
|
||||
| git-ignore | ✓ | | | |
|
||||
| git-rebase | ✓ | | | |
|
||||
| gjs | ✓ | ✓ | ✓ | `typescript-language-server`, `vscode-eslint-language-server`, `ember-language-server` |
|
||||
| gleam | ✓ | ✓ | | `gleam` |
|
||||
| glimmer | ✓ | | | `ember-language-server` |
|
||||
| glsl | ✓ | ✓ | ✓ | `glsl_analyzer` |
|
||||
| gn | ✓ | | | |
|
||||
| go | ✓ | ✓ | ✓ | `gopls`, `golangci-lint-langserver` |
|
||||
| godot-resource | ✓ | ✓ | | |
|
||||
| gomod | ✓ | | | `gopls` |
|
||||
| gotmpl | ✓ | | | `gopls` |
|
||||
| gowork | ✓ | | | `gopls` |
|
||||
| gpr | ✓ | | | `ada_language_server` |
|
||||
| graphql | ✓ | ✓ | | `graphql-lsp` |
|
||||
| groovy | ✓ | | | |
|
||||
| gts | ✓ | ✓ | ✓ | `typescript-language-server`, `vscode-eslint-language-server`, `ember-language-server` |
|
||||
| hare | ✓ | | | |
|
||||
| haskell | ✓ | ✓ | | `haskell-language-server-wrapper` |
|
||||
| haskell-persistent | ✓ | | | |
|
||||
| hcl | ✓ | ✓ | ✓ | `terraform-ls` |
|
||||
| heex | ✓ | ✓ | | `elixir-ls` |
|
||||
| helm | ✓ | | | `helm_ls` |
|
||||
| hocon | ✓ | ✓ | ✓ | |
|
||||
| hoon | ✓ | | | |
|
||||
| hosts | ✓ | | | |
|
||||
| html | ✓ | | | `vscode-html-language-server`, `superhtml` |
|
||||
| hurl | ✓ | ✓ | ✓ | |
|
||||
| hyprlang | ✓ | | ✓ | `hyprls` |
|
||||
| idris | | | | `idris2-lsp` |
|
||||
| iex | ✓ | | | |
|
||||
| ini | ✓ | | | |
|
||||
| inko | ✓ | ✓ | ✓ | |
|
||||
| janet | ✓ | | | |
|
||||
| java | ✓ | ✓ | ✓ | `jdtls` |
|
||||
| javascript | ✓ | ✓ | ✓ | `typescript-language-server` |
|
||||
| jinja | ✓ | | | |
|
||||
| jjdescription | ✓ | | | |
|
||||
| jq | ✓ | ✓ | | `jq-lsp` |
|
||||
| jsdoc | ✓ | | | |
|
||||
| json | ✓ | ✓ | ✓ | `vscode-json-language-server` |
|
||||
| json5 | ✓ | | | |
|
||||
| jsonc | ✓ | | ✓ | `vscode-json-language-server` |
|
||||
| jsonnet | ✓ | | | `jsonnet-language-server` |
|
||||
| jsx | ✓ | ✓ | ✓ | `typescript-language-server` |
|
||||
| julia | ✓ | ✓ | ✓ | `julia` |
|
||||
| just | ✓ | ✓ | ✓ | |
|
||||
| kdl | ✓ | ✓ | ✓ | |
|
||||
| koka | ✓ | | ✓ | `koka` |
|
||||
| kotlin | ✓ | | | `kotlin-language-server` |
|
||||
| latex | ✓ | ✓ | | `texlab` |
|
||||
| ld | ✓ | | ✓ | |
|
||||
| ldif | ✓ | | | |
|
||||
| lean | ✓ | | | `lean` |
|
||||
| ledger | ✓ | | | |
|
||||
| llvm | ✓ | ✓ | ✓ | |
|
||||
| llvm-mir | ✓ | ✓ | ✓ | |
|
||||
| llvm-mir-yaml | ✓ | | ✓ | |
|
||||
| log | ✓ | | | |
|
||||
| lpf | ✓ | | | |
|
||||
| lua | ✓ | ✓ | ✓ | `lua-language-server` |
|
||||
| make | ✓ | | ✓ | |
|
||||
| markdoc | ✓ | | | `markdoc-ls` |
|
||||
| markdown | ✓ | | | `marksman`, `markdown-oxide` |
|
||||
| markdown.inline | ✓ | | | |
|
||||
| matlab | ✓ | ✓ | ✓ | |
|
||||
| mermaid | ✓ | | | |
|
||||
| meson | ✓ | | ✓ | `mesonlsp` |
|
||||
| mint | | | | `mint` |
|
||||
| mojo | ✓ | ✓ | ✓ | `magic` |
|
||||
| move | ✓ | | | |
|
||||
| msbuild | ✓ | | ✓ | |
|
||||
| nasm | ✓ | ✓ | | |
|
||||
| nestedtext | ✓ | ✓ | ✓ | |
|
||||
| nickel | ✓ | | ✓ | `nls` |
|
||||
| nim | ✓ | ✓ | ✓ | `nimlangserver` |
|
||||
| nix | ✓ | ✓ | | `nil`, `nixd` |
|
||||
| nu | ✓ | | | `nu` |
|
||||
| nunjucks | ✓ | | | |
|
||||
| ocaml | ✓ | | ✓ | `ocamllsp` |
|
||||
| ocaml-interface | ✓ | | | `ocamllsp` |
|
||||
| odin | ✓ | | ✓ | `ols` |
|
||||
| ohm | ✓ | ✓ | ✓ | |
|
||||
| opencl | ✓ | ✓ | ✓ | `clangd` |
|
||||
| openscad | ✓ | | | `openscad-lsp` |
|
||||
| org | ✓ | | | |
|
||||
| pascal | ✓ | ✓ | | `pasls` |
|
||||
| passwd | ✓ | | | |
|
||||
| pem | ✓ | | | |
|
||||
| perl | ✓ | ✓ | ✓ | `perlnavigator` |
|
||||
| pest | ✓ | ✓ | ✓ | `pest-language-server` |
|
||||
| php | ✓ | ✓ | ✓ | `intelephense` |
|
||||
| php-only | ✓ | | | |
|
||||
| pkgbuild | ✓ | ✓ | ✓ | `pkgbuild-language-server`, `bash-language-server` |
|
||||
| pkl | ✓ | | ✓ | |
|
||||
| po | ✓ | ✓ | | |
|
||||
| pod | ✓ | | | |
|
||||
| ponylang | ✓ | ✓ | ✓ | |
|
||||
| powershell | ✓ | | | |
|
||||
| prisma | ✓ | ✓ | | `prisma-language-server` |
|
||||
| prolog | | | | `swipl` |
|
||||
| protobuf | ✓ | ✓ | ✓ | `bufls`, `pb` |
|
||||
| prql | ✓ | | | |
|
||||
| purescript | ✓ | ✓ | | `purescript-language-server` |
|
||||
| python | ✓ | ✓ | ✓ | `ruff`, `jedi-language-server`, `pylsp` |
|
||||
| qml | ✓ | | ✓ | `qmlls` |
|
||||
| quint | ✓ | | | `quint-language-server` |
|
||||
| r | ✓ | | | `R` |
|
||||
| racket | ✓ | | ✓ | `racket` |
|
||||
| regex | ✓ | | | |
|
||||
| rego | ✓ | | | `regols` |
|
||||
| rescript | ✓ | ✓ | | `rescript-language-server` |
|
||||
| rmarkdown | ✓ | | ✓ | `R` |
|
||||
| robot | ✓ | | | `robotframework_ls` |
|
||||
| ron | ✓ | | ✓ | |
|
||||
| rst | ✓ | | | |
|
||||
| ruby | ✓ | ✓ | ✓ | `solargraph` |
|
||||
| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
|
||||
| sage | ✓ | ✓ | | |
|
||||
| scala | ✓ | ✓ | ✓ | `metals` |
|
||||
| scheme | ✓ | | ✓ | |
|
||||
| scss | ✓ | | | `vscode-css-language-server` |
|
||||
| slint | ✓ | ✓ | ✓ | `slint-lsp` |
|
||||
| smali | ✓ | | ✓ | |
|
||||
| smithy | ✓ | | | `cs` |
|
||||
| sml | ✓ | | | |
|
||||
| snakemake | ✓ | | ✓ | `pylsp` |
|
||||
| solidity | ✓ | ✓ | | `solc` |
|
||||
| spade | ✓ | | ✓ | `spade-language-server` |
|
||||
| spicedb | ✓ | | | |
|
||||
| sql | ✓ | ✓ | | |
|
||||
| sshclientconfig | ✓ | | | |
|
||||
| starlark | ✓ | ✓ | | |
|
||||
| strace | ✓ | | | |
|
||||
| supercollider | ✓ | | | |
|
||||
| svelte | ✓ | | ✓ | `svelteserver` |
|
||||
| sway | ✓ | ✓ | ✓ | `forc` |
|
||||
| swift | ✓ | ✓ | | `sourcekit-lsp` |
|
||||
| t32 | ✓ | | | |
|
||||
| tablegen | ✓ | ✓ | ✓ | |
|
||||
| tact | ✓ | ✓ | ✓ | |
|
||||
| task | ✓ | | | |
|
||||
| tcl | ✓ | | ✓ | |
|
||||
| teal | ✓ | | | |
|
||||
| templ | ✓ | | | `templ` |
|
||||
| textproto | ✓ | ✓ | ✓ | |
|
||||
| tfvars | ✓ | | ✓ | `terraform-ls` |
|
||||
| thrift | ✓ | | | |
|
||||
| todotxt | ✓ | | | |
|
||||
| toml | ✓ | ✓ | | `taplo` |
|
||||
| tsq | ✓ | | | |
|
||||
| tsx | ✓ | ✓ | ✓ | `typescript-language-server` |
|
||||
| twig | ✓ | | | |
|
||||
| typescript | ✓ | ✓ | ✓ | `typescript-language-server` |
|
||||
| typespec | ✓ | ✓ | ✓ | `tsp-server` |
|
||||
| typst | ✓ | | | `tinymist`, `typst-lsp` |
|
||||
| ungrammar | ✓ | | | |
|
||||
| unison | ✓ | ✓ | ✓ | |
|
||||
| uxntal | ✓ | | | |
|
||||
| v | ✓ | ✓ | ✓ | `v-analyzer` |
|
||||
| vala | ✓ | ✓ | | `vala-language-server` |
|
||||
| vento | ✓ | | | |
|
||||
| verilog | ✓ | ✓ | | `svlangserver` |
|
||||
| vhdl | ✓ | | | `vhdl_ls` |
|
||||
| vhs | ✓ | | | |
|
||||
| vue | ✓ | | | `vue-language-server` |
|
||||
| wast | ✓ | | | |
|
||||
| wat | ✓ | | | |
|
||||
| webc | ✓ | | | |
|
||||
| wgsl | ✓ | | | `wgsl_analyzer` |
|
||||
| wit | ✓ | | ✓ | |
|
||||
| wren | ✓ | ✓ | ✓ | |
|
||||
| xit | ✓ | | | |
|
||||
| xml | ✓ | | ✓ | |
|
||||
| xtc | ✓ | | | |
|
||||
| yaml | ✓ | ✓ | ✓ | `yaml-language-server`, `ansible-language-server` |
|
||||
| yuck | ✓ | | | |
|
||||
| zig | ✓ | ✓ | ✓ | `zls` |
|
||||
| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Code Navigation Tags | Rainbow Brackets | Default language servers |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| ada | ✓ | ✓ | | | | `ada_language_server` |
|
||||
| adl | ✓ | ✓ | ✓ | | | |
|
||||
| agda | ✓ | | | | | |
|
||||
| alloy | ✓ | | | | | |
|
||||
| amber | ✓ | | | | | `amber-lsp` |
|
||||
| astro | ✓ | | | | | `astro-ls` |
|
||||
| awk | ✓ | ✓ | | | | `awk-language-server` |
|
||||
| bash | ✓ | ✓ | ✓ | ✓ | ✓ | `bash-language-server` |
|
||||
| bass | ✓ | | | | | `bass` |
|
||||
| beancount | ✓ | | | | | `beancount-language-server` |
|
||||
| bibtex | ✓ | | | | | `texlab` |
|
||||
| bicep | ✓ | | | | | `bicep-langserver` |
|
||||
| bitbake | ✓ | | | | | `bitbake-language-server` |
|
||||
| blade | ✓ | ✓ | | | ✓ | |
|
||||
| blueprint | ✓ | | | | | `blueprint-compiler` |
|
||||
| c | ✓ | ✓ | ✓ | ✓ | ✓ | `clangd` |
|
||||
| c-sharp | ✓ | ✓ | | ✓ | | `OmniSharp` |
|
||||
| cabal | | | | | | `haskell-language-server-wrapper` |
|
||||
| caddyfile | ✓ | ✓ | ✓ | | | |
|
||||
| cairo | ✓ | ✓ | ✓ | | | `cairo-language-server` |
|
||||
| capnp | ✓ | | ✓ | | | |
|
||||
| cel | ✓ | | | | | |
|
||||
| circom | ✓ | | | | | `circom-lsp` |
|
||||
| clarity | ✓ | | | | | `clarinet` |
|
||||
| clojure | ✓ | | | | ✓ | `clojure-lsp` |
|
||||
| cmake | ✓ | ✓ | ✓ | | | `neocmakelsp`, `cmake-language-server` |
|
||||
| codeql | ✓ | ✓ | | | | `codeql` |
|
||||
| comment | ✓ | | | | | |
|
||||
| common-lisp | ✓ | | ✓ | | ✓ | `cl-lsp` |
|
||||
| cpon | ✓ | | ✓ | | | |
|
||||
| cpp | ✓ | ✓ | ✓ | ✓ | ✓ | `clangd` |
|
||||
| cross-config | ✓ | ✓ | | | ✓ | `taplo`, `tombi` |
|
||||
| crystal | ✓ | ✓ | ✓ | ✓ | | `crystalline`, `ameba-ls` |
|
||||
| css | ✓ | | ✓ | | ✓ | `vscode-css-language-server` |
|
||||
| csv | ✓ | | | | | |
|
||||
| cue | ✓ | | | | | `cuelsp` |
|
||||
| cylc | ✓ | ✓ | ✓ | | | |
|
||||
| cython | ✓ | | ✓ | ✓ | | |
|
||||
| d | ✓ | ✓ | ✓ | | | `serve-d` |
|
||||
| dart | ✓ | ✓ | ✓ | | | `dart` |
|
||||
| dbml | ✓ | | | | | |
|
||||
| debian | ✓ | | | | | |
|
||||
| devicetree | ✓ | | | | | `dts-lsp` |
|
||||
| dhall | ✓ | ✓ | | | | `dhall-lsp-server` |
|
||||
| diff | ✓ | | | | | |
|
||||
| djot | ✓ | | | | | |
|
||||
| docker-bake | ✓ | ✓ | ✓ | ✓ | ✓ | `docker-language-server` |
|
||||
| docker-compose | ✓ | ✓ | ✓ | | | `docker-compose-langserver`, `yaml-language-server`, `docker-language-server` |
|
||||
| dockerfile | ✓ | ✓ | | | | `docker-langserver`, `docker-language-server` |
|
||||
| dot | ✓ | | | | | `dot-language-server` |
|
||||
| doxyfile | ✓ | ✓ | ✓ | ✓ | | |
|
||||
| dtd | ✓ | | | | | |
|
||||
| dune | ✓ | | | | | |
|
||||
| dunstrc | ✓ | | | | | |
|
||||
| earthfile | ✓ | ✓ | ✓ | | | `earthlyls` |
|
||||
| edoc | ✓ | | | | | |
|
||||
| eex | ✓ | | | | | |
|
||||
| ejs | ✓ | | | | | |
|
||||
| elisp | ✓ | | | ✓ | | |
|
||||
| elixir | ✓ | ✓ | ✓ | ✓ | ✓ | `elixir-ls`, `expert` |
|
||||
| elm | ✓ | ✓ | | ✓ | | `elm-language-server` |
|
||||
| elvish | ✓ | | | | | `elvish` |
|
||||
| env | ✓ | ✓ | | | | |
|
||||
| erb | ✓ | | | | | |
|
||||
| erlang | ✓ | ✓ | | ✓ | ✓ | `erlang_ls`, `elp` |
|
||||
| esdl | ✓ | | | | | |
|
||||
| fennel | ✓ | | | | | `fennel-ls` |
|
||||
| fga | ✓ | ✓ | ✓ | | | |
|
||||
| fidl | ✓ | | | | | |
|
||||
| fish | ✓ | ✓ | ✓ | | | `fish-lsp` |
|
||||
| flatbuffers | ✓ | | | | | |
|
||||
| forth | ✓ | | | | | `forth-lsp` |
|
||||
| fortran | ✓ | | ✓ | | | `fortls` |
|
||||
| fsharp | ✓ | | | | | `fsautocomplete` |
|
||||
| gas | ✓ | ✓ | | | | `asm-lsp` |
|
||||
| gdscript | ✓ | ✓ | ✓ | ✓ | | |
|
||||
| gemini | ✓ | | | | | |
|
||||
| gherkin | ✓ | | | | | |
|
||||
| ghostty | ✓ | | | | | |
|
||||
| git-attributes | ✓ | | | | | |
|
||||
| git-cliff-config | ✓ | ✓ | | | ✓ | `taplo`, `tombi` |
|
||||
| git-commit | ✓ | ✓ | | | | |
|
||||
| git-config | ✓ | ✓ | | | | |
|
||||
| git-ignore | ✓ | | | | | |
|
||||
| git-notes | ✓ | | | | | |
|
||||
| git-rebase | ✓ | | | | | |
|
||||
| gitlab-ci | ✓ | ✓ | ✓ | ✓ | ✓ | `yaml-language-server`, `gitlab-ci-ls` |
|
||||
| gjs | ✓ | ✓ | ✓ | ✓ | | `typescript-language-server`, `vscode-eslint-language-server`, `ember-language-server` |
|
||||
| gleam | ✓ | ✓ | | | ✓ | `gleam` |
|
||||
| glimmer | ✓ | | | | | `ember-language-server` |
|
||||
| glsl | ✓ | ✓ | ✓ | | | `glsl_analyzer` |
|
||||
| gn | ✓ | | | | | |
|
||||
| go | ✓ | ✓ | ✓ | ✓ | ✓ | `gopls`, `golangci-lint-langserver` |
|
||||
| go-format-string | ✓ | | | | ✓ | |
|
||||
| godot-resource | ✓ | ✓ | | | | |
|
||||
| gomod | ✓ | | | | | `gopls` |
|
||||
| gotmpl | ✓ | | | | | `gopls` |
|
||||
| gowork | ✓ | | | | | `gopls` |
|
||||
| gpr | ✓ | | | | | `ada_language_server` |
|
||||
| graphql | ✓ | ✓ | | | | `graphql-lsp` |
|
||||
| gren | ✓ | ✓ | | | | |
|
||||
| groovy | ✓ | | | | | |
|
||||
| gts | ✓ | ✓ | ✓ | ✓ | | `typescript-language-server`, `vscode-eslint-language-server`, `ember-language-server` |
|
||||
| hare | ✓ | | | | | |
|
||||
| haskell | ✓ | ✓ | | | | `haskell-language-server-wrapper` |
|
||||
| haskell-persistent | ✓ | | | | | |
|
||||
| hcl | ✓ | ✓ | ✓ | | | `terraform-ls` |
|
||||
| hdl | ✓ | | | | | `hdls` |
|
||||
| heex | ✓ | ✓ | | | | `elixir-ls`, `expert` |
|
||||
| helm | ✓ | | | | | `helm_ls` |
|
||||
| hocon | ✓ | ✓ | ✓ | | | |
|
||||
| hoon | ✓ | | | | | |
|
||||
| hosts | ✓ | | | | | |
|
||||
| html | ✓ | ✓ | | | ✓ | `vscode-html-language-server`, `superhtml` |
|
||||
| htmldjango | ✓ | | | | | `djlsp`, `vscode-html-language-server`, `superhtml` |
|
||||
| hurl | ✓ | ✓ | ✓ | | | |
|
||||
| hyprlang | ✓ | | ✓ | | | `hyprls` |
|
||||
| idris | | | | | | `idris2-lsp` |
|
||||
| iex | ✓ | | | | | |
|
||||
| ini | ✓ | | | | | |
|
||||
| ink | ✓ | | | | | |
|
||||
| inko | ✓ | ✓ | ✓ | ✓ | | |
|
||||
| janet | ✓ | | ✓ | | ✓ | |
|
||||
| java | ✓ | ✓ | ✓ | ✓ | ✓ | `jdtls` |
|
||||
| javascript | ✓ | ✓ | ✓ | ✓ | ✓ | `typescript-language-server` |
|
||||
| jinja | ✓ | | | | | |
|
||||
| jjconfig | ✓ | ✓ | ✓ | | | `taplo`, `tombi` |
|
||||
| jjdescription | ✓ | | | | | |
|
||||
| jjrevset | ✓ | | | | | |
|
||||
| jjtemplate | ✓ | | | | | |
|
||||
| jq | ✓ | ✓ | | | | `jq-lsp` |
|
||||
| jsdoc | ✓ | | | | | |
|
||||
| json | ✓ | ✓ | ✓ | | ✓ | `vscode-json-language-server` |
|
||||
| json-ld | ✓ | ✓ | ✓ | | | `vscode-json-language-server` |
|
||||
| json5 | ✓ | | | | | |
|
||||
| jsonc | ✓ | | ✓ | | | `vscode-json-language-server` |
|
||||
| jsonnet | ✓ | | | | | `jsonnet-language-server` |
|
||||
| jsx | ✓ | ✓ | ✓ | ✓ | ✓ | `typescript-language-server` |
|
||||
| julia | ✓ | ✓ | ✓ | | | `julia` |
|
||||
| just | ✓ | ✓ | ✓ | ✓ | | `just-lsp` |
|
||||
| kconfig | ✓ | | ✓ | | | |
|
||||
| kdl | ✓ | ✓ | ✓ | ✓ | | |
|
||||
| koka | ✓ | | ✓ | | | `koka` |
|
||||
| kotlin | ✓ | ✓ | ✓ | ✓ | | `kotlin-language-server` |
|
||||
| koto | ✓ | ✓ | ✓ | | ✓ | `koto-ls` |
|
||||
| latex | ✓ | ✓ | | | | `texlab` |
|
||||
| ld | ✓ | | ✓ | | | |
|
||||
| ldif | ✓ | | | | | |
|
||||
| lean | ✓ | | | | | `lake` |
|
||||
| ledger | ✓ | | | | | |
|
||||
| llvm | ✓ | ✓ | ✓ | | | |
|
||||
| llvm-mir | ✓ | ✓ | ✓ | | | |
|
||||
| llvm-mir-yaml | ✓ | | ✓ | | | |
|
||||
| log | ✓ | | | | | |
|
||||
| lpf | ✓ | | | | | |
|
||||
| lua | ✓ | ✓ | ✓ | | ✓ | `lua-language-server` |
|
||||
| luap | ✓ | | | | | |
|
||||
| luau | ✓ | ✓ | ✓ | | | `luau-lsp` |
|
||||
| mail | ✓ | ✓ | | | | |
|
||||
| make | ✓ | | ✓ | | | |
|
||||
| markdoc | ✓ | | | | | `markdoc-ls` |
|
||||
| markdown | ✓ | | | ✓ | | `marksman`, `markdown-oxide` |
|
||||
| markdown-rustdoc | ✓ | | | | | |
|
||||
| markdown.inline | ✓ | | | | | |
|
||||
| matlab | ✓ | ✓ | ✓ | | | |
|
||||
| mermaid | ✓ | | | | | |
|
||||
| meson | ✓ | | ✓ | | | `mesonlsp` |
|
||||
| mint | | | | | | `mint` |
|
||||
| mojo | ✓ | ✓ | ✓ | | | `pixi` |
|
||||
| move | ✓ | | | | | |
|
||||
| msbuild | ✓ | | ✓ | | | |
|
||||
| nasm | ✓ | ✓ | | | | `asm-lsp` |
|
||||
| nestedtext | ✓ | ✓ | ✓ | | | |
|
||||
| nginx | ✓ | | | | | |
|
||||
| nickel | ✓ | | ✓ | | | `nls` |
|
||||
| nim | ✓ | ✓ | ✓ | | | `nimlangserver` |
|
||||
| nix | ✓ | ✓ | ✓ | | ✓ | `nil`, `nixd` |
|
||||
| nu | ✓ | | | | | `nu` |
|
||||
| nunjucks | ✓ | | | | | |
|
||||
| ocaml | ✓ | | ✓ | | | `ocamllsp` |
|
||||
| ocaml-interface | ✓ | | | | | `ocamllsp` |
|
||||
| odin | ✓ | ✓ | ✓ | | | `ols` |
|
||||
| ohm | ✓ | ✓ | ✓ | | | |
|
||||
| opencl | ✓ | ✓ | ✓ | | | `clangd` |
|
||||
| openscad | ✓ | | | | | `openscad-lsp` |
|
||||
| org | ✓ | | | | | |
|
||||
| pascal | ✓ | ✓ | | | | `pasls` |
|
||||
| passwd | ✓ | | | | | |
|
||||
| pem | ✓ | | | | | |
|
||||
| perl | ✓ | ✓ | ✓ | | | `perlnavigator` |
|
||||
| pest | ✓ | ✓ | ✓ | | | `pest-language-server` |
|
||||
| php | ✓ | ✓ | ✓ | ✓ | ✓ | `intelephense` |
|
||||
| php-only | ✓ | | | ✓ | | |
|
||||
| pip-requirements | ✓ | | | | | |
|
||||
| pkgbuild | ✓ | ✓ | ✓ | | | `termux-language-server`, `bash-language-server` |
|
||||
| pkl | ✓ | | ✓ | | | `pkl-lsp` |
|
||||
| po | ✓ | ✓ | | | | |
|
||||
| pod | ✓ | | | | | |
|
||||
| ponylang | ✓ | ✓ | ✓ | | | |
|
||||
| powershell | ✓ | | | | | |
|
||||
| prisma | ✓ | ✓ | | | | `prisma-language-server` |
|
||||
| prolog | ✓ | | ✓ | | | `swipl` |
|
||||
| properties | ✓ | ✓ | | | | |
|
||||
| protobuf | ✓ | ✓ | ✓ | ✓ | | `buf`, `pb`, `protols` |
|
||||
| prql | ✓ | | | | | |
|
||||
| pug | ✓ | | | | | |
|
||||
| purescript | ✓ | ✓ | | | | `purescript-language-server` |
|
||||
| python | ✓ | ✓ | ✓ | ✓ | ✓ | `ty`, `ruff`, `jedi-language-server`, `pylsp` |
|
||||
| qml | ✓ | ✓ | ✓ | | | `qmlls` |
|
||||
| quarto | ✓ | | ✓ | | | |
|
||||
| quint | ✓ | | | | | `quint-language-server` |
|
||||
| r | ✓ | | | | | `R` |
|
||||
| racket | ✓ | | ✓ | | ✓ | `racket` |
|
||||
| regex | ✓ | | | | ✓ | |
|
||||
| rego | ✓ | | | | | `regols` |
|
||||
| rescript | ✓ | ✓ | | | | `rescript-language-server` |
|
||||
| rmarkdown | ✓ | | ✓ | | | `R` |
|
||||
| robot | ✓ | | | | | `robotframework_ls` |
|
||||
| robots.txt | ✓ | ✓ | | ✓ | | |
|
||||
| ron | ✓ | | ✓ | ✓ | ✓ | |
|
||||
| rst | ✓ | | | | | |
|
||||
| ruby | ✓ | ✓ | ✓ | ✓ | ✓ | `ruby-lsp`, `solargraph` |
|
||||
| rust | ✓ | ✓ | ✓ | ✓ | ✓ | `rust-analyzer` |
|
||||
| rust-format-args | ✓ | | | | | |
|
||||
| rust-format-args-macro | ✓ | ✓ | ✓ | | ✓ | |
|
||||
| sage | ✓ | ✓ | | | | |
|
||||
| scala | ✓ | ✓ | ✓ | | | `metals` |
|
||||
| scheme | ✓ | ✓ | ✓ | | ✓ | |
|
||||
| scss | ✓ | | | | ✓ | `vscode-css-language-server` |
|
||||
| shellcheckrc | ✓ | ✓ | | | | |
|
||||
| slang | ✓ | ✓ | ✓ | | | `slangd` |
|
||||
| slint | ✓ | ✓ | ✓ | | | `slint-lsp` |
|
||||
| slisp | ✓ | | | ✓ | | |
|
||||
| smali | ✓ | | ✓ | | | |
|
||||
| smithy | ✓ | | | | | `cs` |
|
||||
| sml | ✓ | | | | | |
|
||||
| snakemake | ✓ | | ✓ | | | `pylsp` |
|
||||
| solidity | ✓ | ✓ | | | | `solc` |
|
||||
| sourcepawn | ✓ | ✓ | | | | `sourcepawn-studio` |
|
||||
| spade | ✓ | | ✓ | | | `spade-language-server` |
|
||||
| spicedb | ✓ | | | ✓ | | |
|
||||
| sql | ✓ | ✓ | | | | |
|
||||
| sshclientconfig | ✓ | | | | | |
|
||||
| starlark | ✓ | ✓ | ✓ | | ✓ | `starpls` |
|
||||
| strace | ✓ | | | | | |
|
||||
| strictdoc | ✓ | | | ✓ | | |
|
||||
| supercollider | ✓ | | | | | |
|
||||
| svelte | ✓ | | ✓ | | | `svelteserver` |
|
||||
| sway | ✓ | ✓ | ✓ | | | `forc` |
|
||||
| swift | ✓ | ✓ | | | ✓ | `sourcekit-lsp` |
|
||||
| systemd | ✓ | | | | | `systemd-lsp` |
|
||||
| systemverilog | ✓ | | | | | |
|
||||
| t32 | ✓ | | | | | |
|
||||
| tablegen | ✓ | ✓ | ✓ | | | |
|
||||
| tact | ✓ | ✓ | ✓ | | | |
|
||||
| task | ✓ | | | | | |
|
||||
| tcl | ✓ | | ✓ | | | |
|
||||
| teal | ✓ | | | | | `teal-language-server` |
|
||||
| templ | ✓ | | | | | `templ` |
|
||||
| tera | ✓ | | | | | |
|
||||
| textproto | ✓ | ✓ | ✓ | | | |
|
||||
| tfvars | ✓ | | ✓ | | | `terraform-ls` |
|
||||
| thrift | ✓ | | | | | |
|
||||
| tlaplus | ✓ | | | | | |
|
||||
| todotxt | ✓ | | | | | |
|
||||
| toml | ✓ | ✓ | | | ✓ | `taplo`, `tombi` |
|
||||
| tsq | ✓ | | | | ✓ | `ts_query_ls` |
|
||||
| tsx | ✓ | ✓ | ✓ | ✓ | ✓ | `typescript-language-server` |
|
||||
| twig | ✓ | | | | | |
|
||||
| typescript | ✓ | ✓ | ✓ | ✓ | ✓ | `typescript-language-server` |
|
||||
| typespec | ✓ | ✓ | ✓ | | | `tsp-server` |
|
||||
| typst | ✓ | | | ✓ | | `tinymist` |
|
||||
| ungrammar | ✓ | | | | | |
|
||||
| unison | ✓ | ✓ | ✓ | | | |
|
||||
| uxntal | ✓ | | | | | |
|
||||
| v | ✓ | ✓ | ✓ | | | `v-analyzer` |
|
||||
| vala | ✓ | ✓ | | | | `vala-language-server` |
|
||||
| vento | ✓ | | | | | |
|
||||
| verilog | ✓ | ✓ | | | | `svlangserver` |
|
||||
| vhdl | ✓ | | | | | `vhdl_ls` |
|
||||
| vhs | ✓ | | | | | |
|
||||
| vim | ✓ | | | | | |
|
||||
| vue | ✓ | | | | | `vue-language-server` |
|
||||
| wast | ✓ | | | | | |
|
||||
| wat | ✓ | | | | | `wat_server` |
|
||||
| webc | ✓ | | | | | |
|
||||
| werk | ✓ | | | | | |
|
||||
| wesl | ✓ | ✓ | | | | |
|
||||
| wgsl | ✓ | ✓ | ✓ | ✓ | ✓ | `wgsl-analyzer` |
|
||||
| wikitext | ✓ | | | | | `wikitext-lsp` |
|
||||
| wit | ✓ | | ✓ | | | |
|
||||
| wren | ✓ | ✓ | ✓ | | | |
|
||||
| xit | ✓ | | | | | |
|
||||
| xml | ✓ | ✓ | ✓ | | ✓ | |
|
||||
| xtc | ✓ | | | | | |
|
||||
| yaml | ✓ | ✓ | ✓ | | ✓ | `yaml-language-server`, `ansible-language-server` |
|
||||
| yara | ✓ | | | | | `yls` |
|
||||
| yuck | ✓ | | | | | |
|
||||
| zig | ✓ | ✓ | ✓ | | | `zls` |
|
||||
|
@@ -100,13 +100,20 @@
|
||||
| `file_picker` | Open file picker | normal: `` <space>f ``, select: `` <space>f `` |
|
||||
| `file_picker_in_current_buffer_directory` | Open file picker at current buffer's directory | |
|
||||
| `file_picker_in_current_directory` | Open file picker at current working directory | normal: `` <space>F ``, select: `` <space>F `` |
|
||||
| `file_explorer` | Open file explorer in workspace root | normal: `` <space>e ``, select: `` <space>e `` |
|
||||
| `file_explorer_in_current_buffer_directory` | Open file explorer at current buffer's directory | normal: `` <space>E ``, select: `` <space>E `` |
|
||||
| `file_explorer_in_current_directory` | Open file explorer at current working directory | |
|
||||
| `code_action` | Perform code action | normal: `` <space>a ``, select: `` <space>a `` |
|
||||
| `buffer_picker` | Open buffer picker | normal: `` <space>b ``, select: `` <space>b `` |
|
||||
| `jumplist_picker` | Open jumplist picker | normal: `` <space>j ``, select: `` <space>j `` |
|
||||
| `symbol_picker` | Open symbol picker | normal: `` <space>s ``, select: `` <space>s `` |
|
||||
| `symbol_picker` | Open symbol picker | |
|
||||
| `syntax_symbol_picker` | Open symbol picker from syntax information | |
|
||||
| `lsp_or_syntax_symbol_picker` | Open symbol picker from LSP or syntax information | normal: `` <space>s ``, select: `` <space>s `` |
|
||||
| `changed_file_picker` | Open changed file picker | normal: `` <space>g ``, select: `` <space>g `` |
|
||||
| `select_references_to_symbol_under_cursor` | Select symbol references | normal: `` <space>h ``, select: `` <space>h `` |
|
||||
| `workspace_symbol_picker` | Open workspace symbol picker | normal: `` <space>S ``, select: `` <space>S `` |
|
||||
| `workspace_symbol_picker` | Open workspace symbol picker | |
|
||||
| `syntax_workspace_symbol_picker` | Open workspace symbol picker from syntax information | |
|
||||
| `lsp_or_syntax_workspace_symbol_picker` | Open workspace symbol picker from LSP or syntax information | normal: `` <space>S ``, select: `` <space>S `` |
|
||||
| `diagnostics_picker` | Open diagnostic picker | normal: `` <space>d ``, select: `` <space>d `` |
|
||||
| `workspace_diagnostics_picker` | Open workspace diagnostic picker | normal: `` <space>D ``, select: `` <space>D `` |
|
||||
| `last_picker` | Open last picker | normal: `` <space>' ``, select: `` <space>' `` |
|
||||
@@ -123,8 +130,10 @@
|
||||
| `add_newline_below` | Add newline below | normal: `` ]<space> ``, select: `` ]<space> `` |
|
||||
| `goto_type_definition` | Goto type definition | normal: `` gy ``, select: `` gy `` |
|
||||
| `goto_implementation` | Goto implementation | normal: `` gi ``, select: `` gi `` |
|
||||
| `goto_file_start` | Goto line number <n> else file start | normal: `` gg ``, select: `` gg `` |
|
||||
| `goto_file_start` | Goto line number <n> else file start | normal: `` gg `` |
|
||||
| `goto_file_end` | Goto file end | |
|
||||
| `extend_to_file_start` | Extend to line number<n> else file start | select: `` gg `` |
|
||||
| `extend_to_file_end` | Extend to file end | |
|
||||
| `goto_file` | Goto files/URLs in selections | normal: `` gf ``, select: `` gf `` |
|
||||
| `goto_file_hsplit` | Goto files in selections (hsplit) | normal: `` <C-w>f ``, `` <space>wf ``, select: `` <C-w>f ``, `` <space>wf `` |
|
||||
| `goto_file_vsplit` | Goto files in selections (vsplit) | normal: `` <C-w>F ``, `` <space>wF ``, select: `` <C-w>F ``, `` <space>wF `` |
|
||||
@@ -136,7 +145,8 @@
|
||||
| `goto_last_modified_file` | Goto last modified file | normal: `` gm ``, select: `` gm `` |
|
||||
| `goto_last_modification` | Goto last modification | normal: `` g. ``, select: `` g. `` |
|
||||
| `goto_line` | Goto line | normal: `` G ``, select: `` G `` |
|
||||
| `goto_last_line` | Goto last line | normal: `` ge ``, select: `` ge `` |
|
||||
| `goto_last_line` | Goto last line | normal: `` ge `` |
|
||||
| `extend_to_last_line` | Extend to last line | select: `` ge `` |
|
||||
| `goto_first_diag` | Goto first diagnostic | normal: `` [D ``, select: `` [D `` |
|
||||
| `goto_last_diag` | Goto last diagnostic | normal: `` ]D ``, select: `` ]D `` |
|
||||
| `goto_next_diag` | Goto next diagnostic | normal: `` ]d ``, select: `` ]d `` |
|
||||
@@ -147,6 +157,8 @@
|
||||
| `goto_last_change` | Goto last change | normal: `` ]G ``, select: `` ]G `` |
|
||||
| `goto_line_start` | Goto line start | normal: `` gh ``, `` <home> ``, select: `` gh ``, insert: `` <home> `` |
|
||||
| `goto_line_end` | Goto line end | normal: `` gl ``, `` <end> ``, select: `` gl `` |
|
||||
| `goto_column` | Goto column | normal: `` g\| `` |
|
||||
| `extend_to_column` | Extend to column | select: `` g\| `` |
|
||||
| `goto_next_buffer` | Goto next buffer | normal: `` gn ``, select: `` gn `` |
|
||||
| `goto_previous_buffer` | Goto previous buffer | normal: `` gp ``, select: `` gp `` |
|
||||
| `goto_line_end_newline` | Goto newline at line end | insert: `` <end> `` |
|
||||
@@ -160,6 +172,8 @@
|
||||
| `smart_tab` | Insert tab if all cursors have all whitespace to their left; otherwise, run a separate command. | insert: `` <tab> `` |
|
||||
| `insert_tab` | Insert tab char | insert: `` <S-tab> `` |
|
||||
| `insert_newline` | Insert newline char | insert: `` <C-j> ``, `` <ret> `` |
|
||||
| `insert_char_interactive` | Insert an interactively-chosen char | |
|
||||
| `append_char_interactive` | Append an interactively-chosen char | |
|
||||
| `delete_char_backward` | Delete previous char | insert: `` <C-h> ``, `` <backspace> ``, `` <S-backspace> `` |
|
||||
| `delete_char_forward` | Delete next char | insert: `` <C-d> ``, `` <del> `` |
|
||||
| `delete_word_backward` | Delete previous word | insert: `` <C-w> ``, `` <A-backspace> `` |
|
||||
@@ -236,6 +250,7 @@
|
||||
| `wonly` | Close windows except current | normal: `` <C-w>o ``, `` <space>wo ``, `` <C-w><C-o> ``, `` <space>w<C-o> ``, select: `` <C-w>o ``, `` <space>wo ``, `` <C-w><C-o> ``, `` <space>w<C-o> `` |
|
||||
| `select_register` | Select register | normal: `` " ``, select: `` " `` |
|
||||
| `insert_register` | Insert register | insert: `` <C-r> `` |
|
||||
| `copy_between_registers` | Copy between two registers | |
|
||||
| `align_view_middle` | Align view middle | normal: `` Zm ``, `` zm ``, select: `` Zm ``, `` zm `` |
|
||||
| `align_view_top` | Align view top | normal: `` Zt ``, `` zt ``, select: `` Zt ``, `` zt `` |
|
||||
| `align_view_center` | Align view center | normal: `` Zc ``, `` Zz ``, `` zc ``, `` zz ``, select: `` Zc ``, `` Zz ``, `` zc ``, `` zz `` |
|
||||
@@ -258,6 +273,8 @@
|
||||
| `goto_prev_comment` | Goto previous comment | normal: `` [c ``, select: `` [c `` |
|
||||
| `goto_next_test` | Goto next test | normal: `` ]T ``, select: `` ]T `` |
|
||||
| `goto_prev_test` | Goto previous test | normal: `` [T ``, select: `` [T `` |
|
||||
| `goto_next_xml_element` | Goto next (X)HTML element | normal: `` ]x ``, select: `` ]x `` |
|
||||
| `goto_prev_xml_element` | Goto previous (X)HTML element | normal: `` [x ``, select: `` [x `` |
|
||||
| `goto_next_entry` | Goto next pairing | normal: `` ]e ``, select: `` ]e `` |
|
||||
| `goto_prev_entry` | Goto previous pairing | normal: `` [e ``, select: `` [e `` |
|
||||
| `goto_next_paragraph` | Goto next paragraph | normal: `` ]p ``, select: `` ]p `` |
|
||||
@@ -292,5 +309,7 @@
|
||||
| `command_palette` | Open command palette | normal: `` <space>? ``, select: `` <space>? `` |
|
||||
| `goto_word` | Jump to a two-character label | normal: `` gw `` |
|
||||
| `extend_to_word` | Extend to a two-character label | select: `` gw `` |
|
||||
| `goto_next_tabstop` | goto next snippet placeholder | |
|
||||
| `goto_prev_tabstop` | goto next snippet placeholder | |
|
||||
| `goto_next_tabstop` | Goto next snippet placeholder | |
|
||||
| `goto_prev_tabstop` | Goto next snippet placeholder | |
|
||||
| `rotate_selections_first` | Make the first selection your primary one | |
|
||||
| `rotate_selections_last` | Make the last selection your primary one | |
|
||||
|
@@ -52,8 +52,8 @@
|
||||
| `:reload-all`, `:rla` | Discard changes and reload all documents from the source files. |
|
||||
| `:update`, `:u` | Write changes only if the file has been modified. |
|
||||
| `:lsp-workspace-command` | Open workspace command picker |
|
||||
| `:lsp-restart` | Restarts the language servers used by the current doc |
|
||||
| `:lsp-stop` | Stops the language servers that are used by the current doc |
|
||||
| `:lsp-restart` | Restarts the given language servers, or all language servers that are used by the current file if no arguments are supplied |
|
||||
| `:lsp-stop` | Stops the given language servers, or all language servers that are used by the current file if no arguments are supplied |
|
||||
| `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. |
|
||||
| `:tree-sitter-highlight-name` | Display name of tree-sitter highlight scope under the cursor. |
|
||||
| `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. |
|
||||
@@ -67,10 +67,9 @@
|
||||
| `:goto`, `:g` | Goto line number. |
|
||||
| `:set-language`, `:lang` | Set the language of current buffer (show current language if no value specified). |
|
||||
| `:set-option`, `:set` | Set a config option at runtime.<br>For example to disable smart case search, use `:set search.smart-case false`. |
|
||||
| `:toggle-option`, `:toggle` | Toggle a boolean config option at runtime.<br>For example to toggle smart case search, use `:toggle search.smart-case`. |
|
||||
| `:toggle-option`, `:toggle` | Toggle a config option at runtime.<br>For example to toggle smart case search, use `:toggle search.smart-case`. |
|
||||
| `:get-option`, `:get` | Get the current value of a config option. |
|
||||
| `:sort` | Sort ranges in selection. |
|
||||
| `:rsort` | Sort ranges in selection in reverse order. |
|
||||
| `:reflow` | Hard-wrap the current selection of lines to a given width. |
|
||||
| `:tree-sitter-subtree`, `:ts-subtree` | Display the smallest tree-sitter subtree that spans the primary selection, primarily for debugging queries. |
|
||||
| `:config-reload` | Refresh user config. |
|
||||
@@ -79,12 +78,14 @@
|
||||
| `:log-open` | Open the helix log file. |
|
||||
| `:insert-output` | Run shell command, inserting output before each selection. |
|
||||
| `:append-output` | Run shell command, appending output after each selection. |
|
||||
| `:pipe` | Pipe each selection to the shell command. |
|
||||
| `:pipe`, `:\|` | Pipe each selection to the shell command. |
|
||||
| `:pipe-to` | Pipe each selection to the shell command, ignoring output. |
|
||||
| `:run-shell-command`, `:sh` | Run a shell command |
|
||||
| `:run-shell-command`, `:sh`, `:!` | Run a shell command |
|
||||
| `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. |
|
||||
| `:clear-register` | Clear given register. If no argument is provided, clear all registers. |
|
||||
| `:redraw` | Clear and re-render the whole UI |
|
||||
| `:move`, `:mv` | Move the current buffer and its corresponding file to a different path |
|
||||
| `:yank-diagnostic` | Yank diagnostic(s) under primary cursor to register, or clipboard by default |
|
||||
| `:read`, `:r` | Load a file into buffer |
|
||||
| `:echo` | Prints the given arguments to the statusline. |
|
||||
| `:noop` | Does nothing. |
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# Guides
|
||||
|
||||
This section contains guides for adding new language server configurations,
|
||||
tree-sitter grammars, textobject queries, and other similar items.
|
||||
tree-sitter grammars, textobject and rainbow bracket queries, and other similar items.
|
||||
|
@@ -34,16 +34,10 @@ below.
|
||||
2. Create a new directory for the language with the path
|
||||
`runtime/queries/<name>/`.
|
||||
3. Refer to the
|
||||
[tree-sitter website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
|
||||
[tree-sitter website](https://tree-sitter.github.io/tree-sitter/3-syntax-highlighting.html#highlights)
|
||||
for more information on writing queries.
|
||||
4. A list of highlight captures can be found [on the themes page](https://docs.helix-editor.com/themes.html#scopes).
|
||||
|
||||
> 💡 In Helix, the first matching query takes precedence when evaluating
|
||||
> queries, which is different from other editors such as Neovim where the last
|
||||
> matching query supersedes the ones before it. See
|
||||
> [this issue](https://github.com/helix-editor/helix/pull/1170#issuecomment-997294090)
|
||||
> for an example.
|
||||
|
||||
## Common issues
|
||||
|
||||
- If you encounter errors when running Helix after switching branches, you may
|
||||
|
@@ -4,11 +4,16 @@ Writing language injection queries allows one to highlight a specific node as a
|
||||
In addition to the [standard][upstream-docs] language injection options used by tree-sitter, there
|
||||
are a few Helix specific extensions that allow for more control.
|
||||
|
||||
And example of a simple query that would highlight all strings as bash in Nix:
|
||||
An example of a simple query that would highlight all strings as bash in Nix:
|
||||
```scm
|
||||
((string_expression (string_fragment) @injection.content)
|
||||
(#set! injection.language "bash"))
|
||||
```
|
||||
Another example is this query, which highlights links in comments and keywords like "TODO", by reusing the dedicated "comment" language:
|
||||
```
|
||||
((comment) @injection.content
|
||||
(#set! injection.language "comment"))
|
||||
```
|
||||
|
||||
## Capture Types
|
||||
|
||||
@@ -57,4 +62,4 @@ second argument (a string).
|
||||
- `#any-of?` (standard):
|
||||
The first argument (a capture) must be one of the other arguments (strings).
|
||||
|
||||
[upstream-docs]: http://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection
|
||||
[upstream-docs]: https://tree-sitter.github.io/tree-sitter/3-syntax-highlighting.html#language-injection
|
||||
|
132
book/src/guides/rainbow_bracket_queries.md
Normal file
132
book/src/guides/rainbow_bracket_queries.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Adding Rainbow Bracket Queries
|
||||
|
||||
Helix uses `rainbows.scm` tree-sitter query files to provide rainbow bracket
|
||||
functionality.
|
||||
|
||||
Tree-sitter queries are documented in the tree-sitter online documentation.
|
||||
If you're writing queries for the first time, be sure to check out the section
|
||||
on [syntax highlighting queries] and on [query syntax].
|
||||
|
||||
Rainbow queries have two captures: `@rainbow.scope` and `@rainbow.bracket`.
|
||||
`@rainbow.scope` should capture any node that increases the nesting level
|
||||
while `@rainbow.bracket` should capture any bracket nodes. Put another way:
|
||||
`@rainbow.scope` switches to the next rainbow color for all nodes in the tree
|
||||
under it while `@rainbow.bracket` paints captured nodes with the current
|
||||
rainbow color.
|
||||
|
||||
For an example, let's add rainbow queries for the tree-sitter query (TSQ)
|
||||
language itself. These queries will go into a
|
||||
`runtime/queries/tsq/rainbows.scm` file in the repository root.
|
||||
|
||||
First we'll add the `@rainbow.bracket` captures. TSQ only has parentheses and
|
||||
square brackets:
|
||||
|
||||
```tsq
|
||||
["(" ")" "[" "]"] @rainbow.bracket
|
||||
```
|
||||
|
||||
The ordering of the nodes within the alternation (square brackets) is not
|
||||
taken into consideration.
|
||||
|
||||
> Note: Why are these nodes quoted? Most syntax highlights capture text
|
||||
> surrounded by parentheses. These are _named nodes_ and correspond to the
|
||||
> names of rules in the grammar. Brackets are usually written in tree-sitter
|
||||
> grammars as literal strings, for example:
|
||||
>
|
||||
> ```js
|
||||
> {
|
||||
> // ...
|
||||
> arguments: seq("(", repeat($.argument), ")"),
|
||||
> // ...
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> Nodes written as literal strings in tree-sitter grammars may be captured
|
||||
> in queries with those same literal strings.
|
||||
|
||||
Then we'll add `@rainbow.scope` captures. The easiest way to do this is to
|
||||
view the `grammar.js` file in the tree-sitter grammar's repository. For TSQ,
|
||||
that file is [here][tsq grammar.js]. As we scroll down the `grammar.js`, we
|
||||
see that the `(alternation)`, (L36) `(group)` (L57), `(named_node)` (L59),
|
||||
`(predicate)` (L87) and `(wildcard_node)` (L97) nodes all contain literal
|
||||
parentheses or square brackets in their definitions. These nodes are all
|
||||
direct parents of brackets and happen to also be the nodes we want to change
|
||||
to the next rainbow color, so we capture them as `@rainbow.scope`.
|
||||
|
||||
```tsq
|
||||
[
|
||||
(group)
|
||||
(named_node)
|
||||
(wildcard_node)
|
||||
(predicate)
|
||||
(alternation)
|
||||
] @rainbow.scope
|
||||
```
|
||||
|
||||
This strategy works as a rule of thumb for most programming and configuration
|
||||
languages. Markup languages can be trickier and may take additional
|
||||
experimentation to find the correct nodes to use for scopes and brackets.
|
||||
|
||||
The `:tree-sitter-subtree` command shows the syntax tree under the primary
|
||||
selection in S-expression format and can be a useful tool for determining how
|
||||
to write a query.
|
||||
|
||||
### Properties
|
||||
|
||||
The `rainbow.include-children` property may be applied to `@rainbow.scope`
|
||||
captures. By default, all `@rainbow.bracket` captures must be direct descendant
|
||||
of a node captured with `@rainbow.scope` in a syntax tree in order to be
|
||||
highlighted. The `rainbow.include-children` property disables that check and
|
||||
allows `@rainbow.bracket` captures to be highlighted if they are direct or
|
||||
indirect descendants of some node captured with `@rainbow.scope`.
|
||||
|
||||
For example, this property is used in the HTML rainbow queries.
|
||||
|
||||
For a document like `<a>link</a>`, the syntax tree is:
|
||||
|
||||
```tsq
|
||||
(element ; <a>link</a>
|
||||
(start_tag ; <a>
|
||||
(tag_name)) ; a
|
||||
(text) ; link
|
||||
(end_tag ; </a>
|
||||
(tag_name))) ; a
|
||||
```
|
||||
|
||||
If we want to highlight the `<`, `>` and `</` nodes with rainbow colors, we
|
||||
capture them as `@rainbow.bracket`:
|
||||
|
||||
```tsq
|
||||
["<" ">" "</"] @rainbow.bracket
|
||||
```
|
||||
|
||||
And we capture `(element)` as `@rainbow.scope` because `(element)` nodes nest
|
||||
within each other: they increment the nesting level and switch to the next
|
||||
color in the rainbow.
|
||||
|
||||
```tsq
|
||||
(element) @rainbow.scope
|
||||
```
|
||||
|
||||
But this combination of `@rainbow.scope` and `@rainbow.bracket` will not
|
||||
highlight any nodes. `<`, `>` and `</` are children of the `(start_tag)` and
|
||||
`(end_tag)` nodes. We can't capture `(start_tag)` and `(end_tag)` as
|
||||
`@rainbow.scope` because they don't nest other elements. We can fix this case
|
||||
by removing the requirement that `<`, `>` and `</` are direct descendants of
|
||||
`(element)` using the `rainbow.include-children` property.
|
||||
|
||||
```tsq
|
||||
((element) @rainbow.scope
|
||||
(#set! rainbow.include-children))
|
||||
```
|
||||
|
||||
With this property set, `<`, `>`, and `</` will highlight with rainbow colors
|
||||
even though they aren't direct descendents of the `(element)` node.
|
||||
|
||||
`rainbow.include-children` is not necessary for the vast majority of programming
|
||||
languages. It is only necessary when the node that increments the nesting level
|
||||
(changes rainbow color) is not the direct parent of the bracket node.
|
||||
|
||||
[syntax highlighting queries]: https://tree-sitter.github.io/tree-sitter/syntax-highlighting#highlights
|
||||
[query syntax]: https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries
|
||||
[tsq grammar.js]: https://github.com/the-mikedavis/tree-sitter-tsq/blob/48b5e9f82ae0a4727201626f33a17f69f8e0ff86/grammar.js
|
34
book/src/guides/tags.md
Normal file
34
book/src/guides/tags.md
Normal file
@@ -0,0 +1,34 @@
|
||||
## Adding tags queries
|
||||
|
||||
See tree-sitter's documentation on [Code Navigation Systems] for more
|
||||
background on tags queries.
|
||||
|
||||
Helix provides LSP-like features such as document and workspace symbol pickers
|
||||
out-of-the-box for languages with `tags.scm` queries based on syntax trees. To
|
||||
be analyzed a language must have a tree-sitter grammar and a `tags.scm` query
|
||||
file which pattern matches interesting nodes from syntax trees.
|
||||
|
||||
Query files should be placed in `runtime/queries/{language}/tags.scm`
|
||||
when contributing to Helix. You may place these under your local runtime
|
||||
directory (`~/.config/helix/runtime` in Linux for example) for the sake of
|
||||
testing.
|
||||
|
||||
The following [captures][tree-sitter-captures] are recognized:
|
||||
|
||||
| Capture name |
|
||||
|--- |
|
||||
| `definition.class` |
|
||||
| `definition.constant` |
|
||||
| `definition.function` |
|
||||
| `definition.interface` |
|
||||
| `definition.macro` |
|
||||
| `definition.module` |
|
||||
| `definition.struct` |
|
||||
| `definition.type` |
|
||||
|
||||
[Example query files][example-queries] can be found in the Helix GitHub
|
||||
repository.
|
||||
|
||||
[Code Navigation Systems]: https://tree-sitter.github.io/tree-sitter/4-code-navigation.html
|
||||
[tree-sitter-captures]: https://tree-sitter.github.io/tree-sitter/using-parsers/queries/index.html
|
||||
[example-queries]: https://github.com/search?q=repo%3Ahelix-editor%2Fhelix+path%3A%2A%2A/tags.scm&type=Code
|
@@ -23,10 +23,13 @@ The following [captures][tree-sitter-captures] are recognized:
|
||||
| `test.inside` |
|
||||
| `test.around` |
|
||||
| `parameter.inside` |
|
||||
| `parameter.around` |
|
||||
| `comment.inside` |
|
||||
| `comment.around` |
|
||||
| `entry.inside` |
|
||||
| `entry.around` |
|
||||
| `xml-element.inside` |
|
||||
| `xml-element.around` |
|
||||
|
||||
[Example query files][textobject-examples] can be found in the helix GitHub repository.
|
||||
|
||||
@@ -44,6 +47,6 @@ in its `textobjects.scm` file, function navigation should also work automaticall
|
||||
`function.movement` should be defined only if the node captured by `function.around`
|
||||
doesn't make sense in a navigation context.
|
||||
|
||||
[tree-sitter-queries]: https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax
|
||||
[tree-sitter-captures]: https://tree-sitter.github.io/tree-sitter/using-parsers#capturing-nodes
|
||||
[tree-sitter-queries]: https://tree-sitter.github.io/tree-sitter/using-parsers/queries/1-syntax.html
|
||||
[tree-sitter-captures]: https://tree-sitter.github.io/tree-sitter/using-parsers/queries/2-operators.html#capturing-nodes
|
||||
[textobject-examples]: https://github.com/search?q=repo%3Ahelix-editor%2Fhelix+path%3A%2A%2A/textobjects.scm&type=Code&ref=advsearch&l=&l=
|
||||
|
@@ -1,6 +1,7 @@
|
||||
# Installing Helix
|
||||
|
||||
To install Helix, follow the instructions specific to your operating system.
|
||||
The typical way to install Helix is via [your operating system's package manager](./package-managers.md).
|
||||
|
||||
Note that:
|
||||
|
||||
- To get the latest nightly version of Helix, you need to
|
||||
@@ -8,7 +9,7 @@ Note that:
|
||||
|
||||
- To take full advantage of Helix, install the language servers for your
|
||||
preferred programming languages. See the
|
||||
[wiki](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers)
|
||||
[wiki](https://github.com/helix-editor/helix/wiki/Language-Server-Configurations)
|
||||
for instructions.
|
||||
|
||||
## Pre-built binaries
|
||||
|
36
book/src/jumplist.md
Normal file
36
book/src/jumplist.md
Normal file
@@ -0,0 +1,36 @@
|
||||
## Using the jumplist
|
||||
|
||||
To help with quick navigation, Helix maintains a list of "jumps" called the jumplist.
|
||||
Whenever you make a significant movement (see next section), Helix stores your selections from before the move as a jump.
|
||||
A jump serves as a kind of checkpoint, allowing you to jump to a separate location, make edits, and return to where you were with your previous selections.
|
||||
This way, the jumplist tracks both your previous location and your selections.
|
||||
You can manually save a jump by using `Ctrl-s`.
|
||||
To jump backward in the jumplist, use `Ctrl-o`; to go forward, use `Ctrl-i`. To view and select from the full jumplist, use `Space-j` to open the jumplist picker.
|
||||
|
||||
### What makes a jump
|
||||
The following is a non-exhaustive list of which actions add a jump to the jumplist:
|
||||
- Switching buffers
|
||||
- Using the buffer picker, going to the next/previous buffer
|
||||
- Going to the last accessed/modified file
|
||||
- Making a new file (`:new FILE`)
|
||||
- Opening a file (`:open FILE`)
|
||||
- Includes `:log-open`, `:config-open`, `:config-open-workspace`, `:tutor`
|
||||
- Navigating by pickers, global search, or the file explorer
|
||||
- `goto_file` (`gf`)
|
||||
- Big in-file movements
|
||||
- `select_regex` (`s`)
|
||||
- `split_regex` (`S`)
|
||||
- `search` (`/`)
|
||||
- `keep_selections` and `remove_selections` (`K` and `<A-K>`)
|
||||
- `goto_file_start` (`gg`)
|
||||
- `goto_file_end`
|
||||
- `goto_last_line` (`ge`)
|
||||
- `:goto 123` / `:123` / `123G`
|
||||
- `goto_definition` (`gd`)
|
||||
- `goto_declaration` (`gD`)
|
||||
- `goto_type_definition` (`gy`)
|
||||
- `goto_reference` (`gr`)
|
||||
- Other
|
||||
- `Ctrl-s` manually creates a jump
|
||||
- Trying to close a modified buffer can switch you to that buffer and create a jump
|
||||
- The debugger can create jumps as you jump stack frames
|
@@ -35,6 +35,8 @@ Normal mode is the default mode when you launch helix. You can return to it from
|
||||
|
||||
> NOTE: Unlike Vim, `f`, `F`, `t` and `T` are not confined to the current line.
|
||||
|
||||
> Hereafter, `<n>` represents an integer by typing a sequence of digits.
|
||||
|
||||
| Key | Description | Command |
|
||||
| ----- | ----------- | ------- |
|
||||
| `h`, `Left` | Move left | `move_char_left` |
|
||||
@@ -47,11 +49,11 @@ Normal mode is the default mode when you launch helix. You can return to it from
|
||||
| `W` | Move next WORD start | `move_next_long_word_start` |
|
||||
| `B` | Move previous WORD start | `move_prev_long_word_start` |
|
||||
| `E` | Move next WORD end | `move_next_long_word_end` |
|
||||
| `t` | Find 'till next char | `find_till_char` |
|
||||
| `t` | Find till next char | `find_till_char` |
|
||||
| `f` | Find next char | `find_next_char` |
|
||||
| `T` | Find 'till previous char | `till_prev_char` |
|
||||
| `T` | Find till previous char | `till_prev_char` |
|
||||
| `F` | Find previous char | `find_prev_char` |
|
||||
| `G` | Go to line number `<n>` | `goto_line` |
|
||||
| `<n>G`, `<n>gg` | Go to line number `<n>` | `goto_line` |
|
||||
| `Alt-.` | Repeat last motion (`f`, `t`, `m`, `[` or `]`) | `repeat_last_motion` |
|
||||
| `Home` | Move to the start of the line | `goto_line_start` |
|
||||
| `End` | Move to the end of the line | `goto_line_end` |
|
||||
@@ -212,7 +214,10 @@ Jumps to various locations.
|
||||
|
||||
| Key | Description | Command |
|
||||
| ----- | ----------- | ------- |
|
||||
| `g` | Go to line number `<n>` else start of file | `goto_file_start` |
|
||||
| `<n>g`| Go to line number `<n>` | `goto_file_start` |
|
||||
| `g` | Go to the start of the file | `goto_file_start` |
|
||||
| <code><n>|</code> | Go to column number `<n>` | `goto_column` |
|
||||
| <code>|</code> | Go to the start of line | `goto_column` |
|
||||
| `e` | Go to the end of the file | `goto_last_line` |
|
||||
| `f` | Go to files in the selections | `goto_file` |
|
||||
| `h` | Go to the start of the line | `goto_line_start` |
|
||||
@@ -347,30 +352,32 @@ Displays the signature of the selected completion item. Remapping currently not
|
||||
|
||||
These mappings are in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired).
|
||||
|
||||
| Key | Description | Command |
|
||||
| ----- | ----------- | ------- |
|
||||
| `]d` | Go to next diagnostic (**LSP**) | `goto_next_diag` |
|
||||
| `[d` | Go to previous diagnostic (**LSP**) | `goto_prev_diag` |
|
||||
| `]D` | Go to last diagnostic in document (**LSP**) | `goto_last_diag` |
|
||||
| `[D` | Go to first diagnostic in document (**LSP**) | `goto_first_diag` |
|
||||
| `]f` | Go to next function (**TS**) | `goto_next_function` |
|
||||
| `[f` | Go to previous function (**TS**) | `goto_prev_function` |
|
||||
| `]t` | Go to next type definition (**TS**) | `goto_next_class` |
|
||||
| `[t` | Go to previous type definition (**TS**) | `goto_prev_class` |
|
||||
| `]a` | Go to next argument/parameter (**TS**) | `goto_next_parameter` |
|
||||
| `[a` | Go to previous argument/parameter (**TS**) | `goto_prev_parameter` |
|
||||
| `]c` | Go to next comment (**TS**) | `goto_next_comment` |
|
||||
| `[c` | Go to previous comment (**TS**) | `goto_prev_comment` |
|
||||
| `]T` | Go to next test (**TS**) | `goto_next_test` |
|
||||
| `[T` | Go to previous test (**TS**) | `goto_prev_test` |
|
||||
| `]p` | Go to next paragraph | `goto_next_paragraph` |
|
||||
| `[p` | Go to previous paragraph | `goto_prev_paragraph` |
|
||||
| `]g` | Go to next change | `goto_next_change` |
|
||||
| `[g` | Go to previous change | `goto_prev_change` |
|
||||
| `]G` | Go to last change | `goto_last_change` |
|
||||
| `[G` | Go to first change | `goto_first_change` |
|
||||
| `]Space` | Add newline below | `add_newline_below` |
|
||||
| `[Space` | Add newline above | `add_newline_above` |
|
||||
| Key | Description | Command |
|
||||
| ----- | ----------- | ------- |
|
||||
| `]d` | Go to next diagnostic (**LSP**) | `goto_next_diag` |
|
||||
| `[d` | Go to previous diagnostic (**LSP**) | `goto_prev_diag` |
|
||||
| `]D` | Go to last diagnostic in document (**LSP**) | `goto_last_diag` |
|
||||
| `[D` | Go to first diagnostic in document (**LSP**) | `goto_first_diag` |
|
||||
| `]f` | Go to next function (**TS**) | `goto_next_function` |
|
||||
| `[f` | Go to previous function (**TS**) | `goto_prev_function` |
|
||||
| `]t` | Go to next type definition (**TS**) | `goto_next_class` |
|
||||
| `[t` | Go to previous type definition (**TS**) | `goto_prev_class` |
|
||||
| `]a` | Go to next argument/parameter (**TS**) | `goto_next_parameter` |
|
||||
| `[a` | Go to previous argument/parameter (**TS**) | `goto_prev_parameter` |
|
||||
| `]c` | Go to next comment (**TS**) | `goto_next_comment` |
|
||||
| `[c` | Go to previous comment (**TS**) | `goto_prev_comment` |
|
||||
| `]T` | Go to next test (**TS**) | `goto_next_test` |
|
||||
| `[T` | Go to previous test (**TS**) | `goto_prev_test` |
|
||||
| `]p` | Go to next paragraph | `goto_next_paragraph` |
|
||||
| `[p` | Go to previous paragraph | `goto_prev_paragraph` |
|
||||
| `]g` | Go to next change | `goto_next_change` |
|
||||
| `[g` | Go to previous change | `goto_prev_change` |
|
||||
| `]G` | Go to last change | `goto_last_change` |
|
||||
| `[G` | Go to first change | `goto_first_change` |
|
||||
| `[x` | Go to next (X)HTML element | `goto_next_xml_element` |
|
||||
| `]x` | Go to previous (X)HTML element | `goto_prev_xml_element` |
|
||||
| `]Space` | Add newline below | `add_newline_below` |
|
||||
| `[Space` | Add newline above | `add_newline_above` |
|
||||
|
||||
## Insert mode
|
||||
|
||||
|
@@ -60,18 +60,21 @@ These configuration keys are available:
|
||||
| `shebangs` | The interpreters from the shebang line, for example `["sh", "bash"]` |
|
||||
| `roots` | A set of marker files to look for when trying to find the workspace root. For example `Cargo.lock`, `yarn.lock` |
|
||||
| `auto-format` | Whether to autoformat this language when saving |
|
||||
| `diagnostic-severity` | Minimal severity of diagnostic for it to be displayed. (Allowed values: `Error`, `Warning`, `Info`, `Hint`) |
|
||||
| `diagnostic-severity` | Minimal severity of diagnostic for it to be displayed. (Allowed values: `error`, `warning`, `info`, `hint`) |
|
||||
| `comment-tokens` | The tokens to use as a comment token, either a single token `"//"` or an array `["//", "///", "//!"]` (the first token will be used for commenting). Also configurable as `comment-token` for backwards compatibility|
|
||||
| `block-comment-tokens`| The start and end tokens for a multiline comment either an array or single table of `{ start = "/*", end = "*/"}`. The first set of tokens will be used for commenting, any pairs in the array can be uncommented |
|
||||
| `indent` | The indent to use. Has sub keys `unit` (the text inserted into the document when indenting; usually set to N spaces or `"\t"` for tabs) and `tab-width` (the number of spaces rendered for a tab) |
|
||||
| `language-servers` | The Language Servers used for this language. See below for more information in the section [Configuring Language Servers for a language](#configuring-language-servers-for-a-language) |
|
||||
| `grammar` | The tree-sitter grammar to use (defaults to the value of `name`) |
|
||||
| `formatter` | The formatter for the language, it will take precedence over the lsp when defined. The formatter must be able to take the original file as input from stdin and write the formatted file to stdout |
|
||||
| `soft-wrap` | [editor.softwrap](./configuration.md#editorsoft-wrap-section)
|
||||
| `formatter` | The formatter for the language, it will take precedence over the lsp when defined. The formatter must be able to take the original file as input from stdin and write the formatted file to stdout. The filename of the current buffer can be passed as argument by using the `%{buffer_name}` expansion variable. See below for more information in the [Configuring the formatter command](#configuring-the-formatter-command) |
|
||||
| `soft-wrap` | [editor.softwrap](./editor.md#editorsoft-wrap-section)
|
||||
| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set, defaults to `editor.text-width` |
|
||||
| `rulers` | Overrides the `editor.rulers` config key for the language. |
|
||||
| `path-completion` | Overrides the `editor.path-completion` config key for the language. |
|
||||
| `word-completion` | Overrides the [`editor.word-completion`](./editor.md#editorword-completion-section) configuration for the language. |
|
||||
| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml`. Overwrites the setting of the same name in `config.toml` if set. |
|
||||
| `persistent-diagnostic-sources` | An array of LSP diagnostic sources assumed unchanged when the language server resends the same set of diagnostics. Helix can track the position for these diagnostics internally instead. Useful for diagnostics that are recomputed on save.
|
||||
| `rainbow-brackets` | Overrides the `editor.rainbow-brackets` config key for the language |
|
||||
|
||||
### File-type detection and the `file-types` key
|
||||
|
||||
@@ -101,6 +104,16 @@ with the following priorities:
|
||||
the file extension of a given file wins. In the example above, the `"toml"`
|
||||
config matches files like `Cargo.toml` or `languages.toml`.
|
||||
|
||||
### Configuring the formatter command
|
||||
|
||||
[Command line expansions](./command-line.md#expansions) are supported in the arguments
|
||||
of the formatter command. In particular, the `%{buffer_name}` variable can be passed as
|
||||
argument to the formatter:
|
||||
|
||||
```toml
|
||||
formatter = { command = "mylang-formatter" , args = ["--stdin", "--stdin-filename", "%{buffer_name}"] }
|
||||
```
|
||||
|
||||
## Language Server configuration
|
||||
|
||||
Language servers are configured separately in the table `language-server` in the same file as the languages `languages.toml`
|
||||
@@ -241,4 +254,4 @@ use-grammars = { except = [ "yaml", "json" ] }
|
||||
|
||||
When omitted, all grammars are fetched and built.
|
||||
|
||||
[treesitter-language-injection]: https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection
|
||||
[treesitter-language-injection]: https://tree-sitter.github.io/tree-sitter/3-syntax-highlighting.html#language-injection
|
||||
|
35
book/src/other-software.md
Normal file
35
book/src/other-software.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Helix mode in other software
|
||||
|
||||
Helix' keymap and interaction model ([Using Helix](#usage.md)) is easier to adopt if it can be used consistently in many editing contexts. Yet, certain use cases cannot easily be addressed directly in Helix. Similar to vim, this leads to the creation of "Helix mode" in various other software products, allowing Helix-style editing for a greater variety of use cases.
|
||||
|
||||
"Helix mode" is frequently still in early stages or missing entirely. For such cases, we also link to relevant bugs or discussions.
|
||||
|
||||
## Other editors
|
||||
|
||||
| Editor | Plugin or feature providing Helix editing | Comments
|
||||
| --- | --- | --- |
|
||||
| [Vim](https://www.vim.org/) | [helix.vim](https://github.com/chtenb/helix.vim) config |
|
||||
| [IntelliJ IDEA](https://www.jetbrains.com/idea/) / [Android Studio](https://developer.android.com/studio)| [IdeaVim](https://plugins.jetbrains.com/plugin/164-ideavim) plugin + [helix.idea.vim](https://github.com/chtenb/helix.vim) config | Minimum recommended version is IdeaVim 2.19.0.
|
||||
| [Visual Studio](https://visualstudio.microsoft.com/) | [VsVim](https://marketplace.visualstudio.com/items?itemName=JaredParMSFT.VsVim) plugin + [helix.vs.vim](https://github.com/chtenb/helix.vim) config |
|
||||
| [Visual Studio Code](https://code.visualstudio.com/) | [Dance](https://marketplace.visualstudio.com/items?itemName=gregoire.dance) extension, or its [Helix fork](https://marketplace.visualstudio.com/items?itemName=kend.dancehelixkey) | The Helix fork has diverged. You can also use the original Dance and tweak its keybindings directly (try [this config](https://github.com/71/dance/issues/299#issuecomment-1655509531)).
|
||||
| [Visual Studio Code](https://code.visualstudio.com/) | [Helix for VS Code](https://marketplace.visualstudio.com/items?itemName=jasew.vscode-helix-emulation) extension|
|
||||
| [Zed](https://zed.dev/) | native via keybindings ([Bug](https://github.com/zed-industries/zed/issues/4642)) |
|
||||
| [CodeMirror](https://codemirror.net/) | [codemirror-helix](https://gitlab.com/_rvidal/codemirror-helix) |
|
||||
| [Lite XL](https://lite-xl.com/) | [lite-modal-hx](https://codeberg.org/Mandarancio/lite-modal-hx) |
|
||||
| [Lapce](https://lap.dev/lapce/) | | Requested: https://github.com/lapce/lapce/issues/281 |
|
||||
|
||||
|
||||
## Shells
|
||||
|
||||
| Shell | Plugin or feature providing Helix editing
|
||||
| --- | ---
|
||||
| Fish | [Feature Request](https://github.com/fish-shell/fish-shell/issues/7748)
|
||||
| Fish | [fish-helix](https://github.com/sshilovsky/fish-helix/tree/main)
|
||||
| Zsh | [helix-zsh](https://github.com/john-h-k/helix-zsh) or [zsh-helix-mode](https://github.com/Multirious/zsh-helix-mode)
|
||||
| Nushell | [Feature Request](https://github.com/nushell/reedline/issues/639)
|
||||
|
||||
## Other software
|
||||
|
||||
| Software | Plugin or feature providing Helix editing. | Comments
|
||||
| --- | --- | --- |
|
||||
| [Obsidian](https://obsidian.md/) | [Obsidian-Helix](https://github.com/Sinono3/obsidian-helix) | Uses `codemirror-helix` listed above.
|
@@ -1,7 +1,7 @@
|
||||
## Package managers
|
||||
|
||||
- [Linux](#linux)
|
||||
- [Ubuntu](#ubuntu)
|
||||
- [Ubuntu/Debian](#ubuntudebian)
|
||||
- [Fedora/RHEL](#fedorarhel)
|
||||
- [Arch Linux extra](#arch-linux-extra)
|
||||
- [NixOS](#nixos)
|
||||
@@ -23,15 +23,12 @@
|
||||
|
||||
The following third party repositories are available:
|
||||
|
||||
### Ubuntu
|
||||
### Ubuntu/Debian
|
||||
|
||||
Add the `PPA` for Helix:
|
||||
Install the Debian package [from the release page](https://github.com/helix-editor/helix/releases/latest).
|
||||
|
||||
```sh
|
||||
sudo add-apt-repository ppa:maveonair/helix-editor
|
||||
sudo apt update
|
||||
sudo apt install helix
|
||||
```
|
||||
If you are running a system older than Ubuntu 22.04, Mint 21, or Debian 12, you can build the `.deb` file locally
|
||||
[from source](./building-from-source.md#building-the-debian-package).
|
||||
|
||||
### Fedora/RHEL
|
||||
|
||||
@@ -121,7 +118,7 @@ brew install helix
|
||||
### MacPorts
|
||||
|
||||
```sh
|
||||
port install helix
|
||||
sudo port install helix
|
||||
```
|
||||
|
||||
## Windows
|
||||
|
@@ -12,7 +12,7 @@ There are three kinds of commands that can be used in keymaps:
|
||||
in [`helix-term/src/commands.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands.rs)
|
||||
at the invocation of `static_commands!` macro.
|
||||
* Typable commands: commands that can be executed from command mode (`:`), for
|
||||
example `:write!`. See the [Commands](./commands.html) documentation for a
|
||||
example `:write!`. See the [Commands](./commands.md) documentation for a
|
||||
list of available typeable commands or the `TypableCommandList` declaration in
|
||||
the source code at [`helix-term/src/commands/typed.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands/typed.rs).
|
||||
* Macros: sequences of keys that are executed in order. These keybindings
|
||||
@@ -89,24 +89,26 @@ Cmd-s = ":write" # Cmd or Win or Meta and 's' to write
|
||||
|
||||
Special keys are encoded as follows:
|
||||
|
||||
| Key name | Representation |
|
||||
| --- | --- |
|
||||
| Backspace | `"backspace"` |
|
||||
| Space | `"space"` |
|
||||
| Return/Enter | `"ret"` |
|
||||
| Left | `"left"` |
|
||||
| Right | `"right"` |
|
||||
| Up | `"up"` |
|
||||
| Down | `"down"` |
|
||||
| Home | `"home"` |
|
||||
| End | `"end"` |
|
||||
| Page Up | `"pageup"` |
|
||||
| Page Down | `"pagedown"` |
|
||||
| Tab | `"tab"` |
|
||||
| Delete | `"del"` |
|
||||
| Insert | `"ins"` |
|
||||
| Null | `"null"` |
|
||||
| Escape | `"esc"` |
|
||||
| Key name | Representation |
|
||||
| --- | --- |
|
||||
| Backspace | `"backspace"` |
|
||||
| Space | `"space"` |
|
||||
| Return/Enter | `"ret"` |
|
||||
| Left | `"left"` |
|
||||
| Right | `"right"` |
|
||||
| Up | `"up"` |
|
||||
| Down | `"down"` |
|
||||
| Home | `"home"` |
|
||||
| End | `"end"` |
|
||||
| Page Up | `"pageup"` |
|
||||
| Page Down | `"pagedown"` |
|
||||
| Tab | `"tab"` |
|
||||
| Delete | `"del"` |
|
||||
| Insert | `"ins"` |
|
||||
| Null | `"null"` |
|
||||
| Escape | `"esc"` |
|
||||
| Less Than (<) | `"lt"` |
|
||||
| Greater Than (>) | `"gt"` |
|
||||
|
||||
Keys can be disabled by binding them to the `no_op` command.
|
||||
|
||||
|
@@ -24,6 +24,7 @@ function or block of code.
|
||||
| `c` | Comment |
|
||||
| `T` | Test |
|
||||
| `g` | Change |
|
||||
| `x` | (X)HTML element |
|
||||
|
||||
> 💡 `f`, `t`, etc. need a tree-sitter grammar active for the current
|
||||
document and a special tree-sitter query file to work properly. [Only
|
||||
|
@@ -130,13 +130,24 @@ inherits = "boo_berry"
|
||||
berry = "#2A2A4D"
|
||||
```
|
||||
|
||||
### Rainbow
|
||||
|
||||
The `rainbow` key is used for rainbow highlight for matching brackets.
|
||||
The key is a list of styles.
|
||||
|
||||
```toml
|
||||
rainbow = ["#ff0000", "#ffa500", "#fff000", { fg = "#00ff00", modifiers = ["bold"] }]
|
||||
```
|
||||
|
||||
Colors from the palette and modifiers may be used.
|
||||
|
||||
### Scopes
|
||||
|
||||
The following is a list of scopes available to use for styling:
|
||||
|
||||
#### Syntax highlighting
|
||||
|
||||
These keys match [tree-sitter scopes](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#theme).
|
||||
These keys match [tree-sitter scopes](https://tree-sitter.github.io/tree-sitter/3-syntax-highlighting.html#highlights).
|
||||
|
||||
When determining styling for a highlight, the longest matching theme key will be used. For example, if the highlight is `function.builtin.static`, the key `function.builtin` will be used instead of `function`.
|
||||
|
||||
@@ -171,8 +182,10 @@ We use a similar set of scopes as
|
||||
|
||||
- `comment` - Code comments
|
||||
- `line` - Single line comments (`//`)
|
||||
- `documentation` - Line documentation comments (e.g. `///` in Rust)
|
||||
- `block` - Block comments (e.g. (`/* */`)
|
||||
- `documentation` - Documentation comments (e.g. `///` in Rust)
|
||||
- `documentation` - Block documentation comments (e.g. `/** */` in Rust)
|
||||
- `unused` - Unused variables and patterns, e.g. `_` and `_foo`
|
||||
|
||||
- `variable` - Variables
|
||||
- `builtin` - Reserved language variables (`self`, `this`, `super`, etc.)
|
||||
@@ -181,7 +194,7 @@ We use a similar set of scopes as
|
||||
- `member` - Fields of composite data types (e.g. structs, unions)
|
||||
- `private` - Private fields that use a unique syntax (currently just ECMAScript-based languages)
|
||||
|
||||
- `label`
|
||||
- `label` - `.class`, `#id` in CSS, etc.
|
||||
|
||||
- `punctuation`
|
||||
- `delimiter` - Commas, colons
|
||||
@@ -216,7 +229,7 @@ We use a similar set of scopes as
|
||||
|
||||
- `namespace`
|
||||
|
||||
- `special`
|
||||
- `special` - `derive` in Rust, etc.
|
||||
|
||||
- `markup`
|
||||
- `heading`
|
||||
@@ -305,6 +318,7 @@ These scopes are used for theming the editor interface:
|
||||
| `ui.text.focus` | The currently selected line in the picker |
|
||||
| `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) |
|
||||
| `ui.text.info` | The key: command text in `ui.popup.info` boxes |
|
||||
| `ui.text.directory` | Directory names in prompt completion |
|
||||
| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) |
|
||||
| `ui.virtual.whitespace` | Visible whitespace characters |
|
||||
| `ui.virtual.indent-guide` | Vertical indent width guides |
|
||||
@@ -335,5 +349,6 @@ These scopes are used for theming the editor interface:
|
||||
| `diagnostic.error` | Diagnostics error (editing area) |
|
||||
| `diagnostic.unnecessary` | Diagnostics with unnecessary tag (editing area) |
|
||||
| `diagnostic.deprecated` | Diagnostics with deprecated tag (editing area) |
|
||||
| `tabstop` | Snippet placeholder |
|
||||
|
||||
[editor-section]: ./configuration.md#editor-section
|
||||
|
@@ -25,7 +25,7 @@ Inspired by [Kakoune](http://kakoune.org/), Helix follows the `selection → act
|
||||
|
||||
## Multiple selections
|
||||
|
||||
Also inspired by Kakoune, multiple selections are a core mode of interaction in Helix. For example, the standard way of replacing multiple instance of a word is to first select all instances (so there is one selection per instance) and then use the change action (`c`) to edit them all at the same time.
|
||||
Also inspired by Kakoune, multiple selections are a core mode of interaction in Helix. For example, the standard way of replacing multiple instances of a word is to first select all instances (so there is one selection per instance) and then use the change action (`c`) to edit them all at the same time.
|
||||
|
||||
## Motions
|
||||
|
||||
|
@@ -47,6 +47,18 @@
|
||||
<content_rating type="oars-1.1" />
|
||||
|
||||
<releases>
|
||||
<release version="25.07.1" date="2025-07-18">
|
||||
<url>https://github.com/helix-editor/helix/releases/tag/25.07.1</url>
|
||||
</release>
|
||||
<release version="25.07" date="2025-07-15">
|
||||
<url>https://helix-editor.com/news/release-25-07-highlights/</url>
|
||||
</release>
|
||||
<release version="25.01.1" date="2025-01-19">
|
||||
<url>https://github.com/helix-editor/helix/releases/tag/25.01.1</url>
|
||||
</release>
|
||||
<release version="25.01" date="2025-01-03">
|
||||
<url>https://helix-editor.com/news/release-25-01-highlights/</url>
|
||||
</release>
|
||||
<release version="24.07" date="2024-07-14">
|
||||
<url>https://github.com/helix-editor/helix/releases/tag/24.07</url>
|
||||
</release>
|
||||
|
@@ -86,6 +86,6 @@ Keywords[ru]=текст;текстовый редактор;
|
||||
Keywords[sr]=Текст;едитор;
|
||||
Keywords[tr]=Metin;düzenleyici;
|
||||
Icon=helix
|
||||
Categories=Utility;TextEditor;
|
||||
Categories=Utility;TextEditor;ConsoleOnly
|
||||
StartupNotify=false
|
||||
MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;
|
||||
|
@@ -9,23 +9,23 @@ _hx() {
|
||||
|
||||
case "$prev" in
|
||||
-g | --grammar)
|
||||
COMPREPLY=($(compgen -W 'fetch build' -- "$cur"))
|
||||
mapfile -t COMPREPLY < <(compgen -W 'fetch build' -- "$cur")
|
||||
return 0
|
||||
;;
|
||||
--health)
|
||||
languages=$(hx --health | tail -n '+7' | awk '{print $1}' | sed 's/\x1b\[[0-9;]*m//g')
|
||||
COMPREPLY=($(compgen -W """$languages""" -- "$cur"))
|
||||
languages=$(hx --health all-languages | tail -n '+2' | awk '{print $1}' | sed 's/\x1b\[[0-9;]*m//g')
|
||||
mapfile -t COMPREPLY < <(compgen -W """clipboard languages all-languages all $languages""" -- "$cur")
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$2" in
|
||||
-*)
|
||||
COMPREPLY=($(compgen -W "-h --help --tutor -V --version -v -vv -vvv --health -g --grammar --vsplit --hsplit -c --config --log" -- """$2"""))
|
||||
mapfile -t COMPREPLY < <(compgen -W "-h --help --tutor -V --version -v -vv -vvv --health -g --grammar --vsplit --hsplit -c --config --log" -- """$2""")
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
COMPREPLY=($(compgen -fd -- """$2"""))
|
||||
mapfile -t COMPREPLY < <(compgen -fd -- """$2""")
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
@@ -20,7 +20,7 @@ var config = [ "--config" "-c" ]
|
||||
set edit:completion:arg-completer[hx] = {|@args|
|
||||
var n = (count $args)
|
||||
if (>= $n 3) {
|
||||
# Stop completions if passed arg will take presedence
|
||||
# Stop completions if passed arg will take precedence
|
||||
# and invalidate further input
|
||||
if (has-value $skips $args[-2]) {
|
||||
return
|
||||
|
@@ -4,6 +4,10 @@
|
||||
complete -c hx -s h -l help -d "Prints help information"
|
||||
complete -c hx -l tutor -d "Loads the tutorial"
|
||||
complete -c hx -l health -xa "(__hx_langs_ops)" -d "Checks for errors"
|
||||
complete -c hx -l health -xka all -d "Prints all diagnostic informations"
|
||||
complete -c hx -l health -xka all-languages -d "Lists all languages"
|
||||
complete -c hx -l health -xka languages -d "Lists user configured languages"
|
||||
complete -c hx -l health -xka clipboard -d "Prints system clipboard provider"
|
||||
complete -c hx -s g -l grammar -x -a "fetch build" -d "Fetch or build tree-sitter grammars"
|
||||
complete -c hx -s v -o vv -o vvv -d "Increases logging verbosity"
|
||||
complete -c hx -s V -l version -d "Prints version information"
|
||||
@@ -14,5 +18,5 @@ complete -c hx -l log -r -d "Specifies a file to use for logging"
|
||||
complete -c hx -s w -l working-dir -d "Specify initial working directory" -xa "(__fish_complete_directories)"
|
||||
|
||||
function __hx_langs_ops
|
||||
hx --health languages | tail -n '+2' | string replace -fr '^(\S+) .*' '$1'
|
||||
hx --health all-languages | tail -n '+2' | string replace -fr '^(\S+) .*' '$1'
|
||||
end
|
||||
|
@@ -2,11 +2,11 @@
|
||||
#
|
||||
# NOTE: the `+N` syntax is not supported in Nushell (https://github.com/nushell/nushell/issues/13418)
|
||||
# so it has not been specified here and will not be proposed in the autocompletion of Nushell.
|
||||
# The help message won't be overriden though, so it will still be present here
|
||||
# The help message won't be overridden though, so it will still be present here
|
||||
|
||||
def health_categories [] {
|
||||
let languages = ^hx --health languages | detect columns | get Language | filter { $in != null }
|
||||
let completions = [ "all", "clipboard", "languages" ] | append $languages
|
||||
let languages = ^hx --health all-languages | detect columns | get Language | where { $in != null }
|
||||
let completions = [ "all", "clipboard", "languages", "all-languages" ] | append $languages
|
||||
return $completions
|
||||
}
|
||||
|
||||
|
@@ -25,7 +25,7 @@ _hx() {
|
||||
|
||||
case "$state" in
|
||||
health)
|
||||
local languages=($(hx --health | tail -n '+11' | awk '{print $1}' | sed 's/\x1b\[[0-9;]*m//g;s/[✘✓]//g'))
|
||||
local languages=($(hx --health all-languages | tail -n '+2' | awk '{print $1}' | sed 's/\x1b\[[0-9;]*m//g;s/[✘✓]//g'))
|
||||
_values 'language' $languages
|
||||
;;
|
||||
grammar)
|
||||
|
3
contrib/hx_launcher.sh
Executable file
3
contrib/hx_launcher.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
HELIX_RUNTIME=/usr/lib/helix/runtime exec /usr/lib/helix/hx "$@"
|
93
default.nix
93
default.nix
@@ -1,8 +1,87 @@
|
||||
# Flake's default package for non-flake-enabled nix instances
|
||||
let
|
||||
compat = builtins.fetchTarball {
|
||||
url = "https://github.com/edolstra/flake-compat/archive/b4a34015c698c7793d592d66adbab377907a2be8.tar.gz";
|
||||
sha256 = "sha256:1qc703yg0babixi6wshn5wm2kgl5y1drcswgszh4xxzbrwkk9sv7";
|
||||
};
|
||||
{
|
||||
lib,
|
||||
rustPlatform,
|
||||
callPackage,
|
||||
runCommand,
|
||||
installShellFiles,
|
||||
git,
|
||||
gitRev ? null,
|
||||
grammarOverlays ? [],
|
||||
includeGrammarIf ? _: true,
|
||||
}: let
|
||||
fs = lib.fileset;
|
||||
|
||||
src = fs.difference (fs.gitTracked ./.) (fs.unions [
|
||||
./.envrc
|
||||
./rustfmt.toml
|
||||
./screenshot.png
|
||||
./book
|
||||
./docs
|
||||
./runtime
|
||||
./flake.lock
|
||||
(fs.fileFilter (file: lib.strings.hasInfix ".git" file.name) ./.)
|
||||
(fs.fileFilter (file: file.hasExt "svg") ./.)
|
||||
(fs.fileFilter (file: file.hasExt "md") ./.)
|
||||
(fs.fileFilter (file: file.hasExt "nix") ./.)
|
||||
]);
|
||||
|
||||
# Next we actually need to build the grammars and the runtime directory
|
||||
# that they reside in. It is built by calling the derivation in the
|
||||
# grammars.nix file, then taking the runtime directory in the git repo
|
||||
# and hooking symlinks up to it.
|
||||
grammars = callPackage ./grammars.nix {inherit grammarOverlays includeGrammarIf;};
|
||||
runtimeDir = runCommand "helix-runtime" {} ''
|
||||
mkdir -p $out
|
||||
ln -s ${./runtime}/* $out
|
||||
rm -r $out/grammars
|
||||
ln -s ${grammars} $out/grammars
|
||||
'';
|
||||
in
|
||||
(import compat {src = ./.;}).defaultNix
|
||||
rustPlatform.buildRustPackage (self: {
|
||||
cargoLock = {
|
||||
lockFile = ./Cargo.lock;
|
||||
# This is not allowed in nixpkgs but is very convenient here: it allows us to
|
||||
# avoid specifying `outputHashes` here for any git dependencies we might take
|
||||
# on temporarily.
|
||||
allowBuiltinFetchGit = true;
|
||||
};
|
||||
|
||||
propagatedBuildInputs = [ runtimeDir ];
|
||||
|
||||
nativeBuildInputs = [
|
||||
installShellFiles
|
||||
git
|
||||
];
|
||||
|
||||
buildType = "release";
|
||||
|
||||
name = with builtins; (fromTOML (readFile ./helix-term/Cargo.toml)).package.name;
|
||||
src = fs.toSource {
|
||||
root = ./.;
|
||||
fileset = src;
|
||||
};
|
||||
|
||||
# Helix attempts to reach out to the network and get the grammars. Nix doesn't allow this.
|
||||
HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1";
|
||||
|
||||
# So Helix knows what rev it is.
|
||||
HELIX_NIX_BUILD_REV = gitRev;
|
||||
|
||||
doCheck = false;
|
||||
strictDeps = true;
|
||||
|
||||
# Sets the Helix runtime dir to the grammars
|
||||
env.HELIX_DEFAULT_RUNTIME = "${runtimeDir}";
|
||||
|
||||
# Get all the application stuff in the output directory.
|
||||
postInstall = ''
|
||||
mkdir -p $out/lib
|
||||
installShellCompletion ${./contrib/completion}/hx.{bash,fish,zsh}
|
||||
mkdir -p $out/share/{applications,icons/hicolor/{256x256,scalable}/apps}
|
||||
cp ${./contrib/Helix.desktop} $out/share/applications/Helix.desktop
|
||||
cp ${./logo.svg} $out/share/icons/hicolor/scalable/apps/helix.svg
|
||||
cp ${./contrib/helix.png} $out/share/icons/hicolor/256x256/apps/helix.png
|
||||
'';
|
||||
|
||||
meta.mainProgram = "hx";
|
||||
})
|
||||
|
@@ -13,8 +13,10 @@ Some suggestions to get started:
|
||||
- Instead of running a release version of Helix, while developing you may want to run in debug mode with `cargo run` which is way faster to compile
|
||||
- Looking for even faster compile times? Give a try to [mold](https://github.com/rui314/mold)
|
||||
- If your preferred language is missing, integrating a tree-sitter grammar for
|
||||
it and defining syntax highlight queries for it is straight forward and
|
||||
it and defining syntax highlight queries for it is straightforward and
|
||||
doesn't require much knowledge of the internals.
|
||||
- If you don't use the Nix development shell and are getting your rust-analyzer binary from rustup, you may need to run `rustup component add rust-analyzer`.
|
||||
This is because `rust-toolchain.toml` selects our MSRV for the development toolchain but doesn't download the matching rust-analyzer automatically.
|
||||
|
||||
We provide an [architecture.md][architecture.md] that should give you
|
||||
a good overview of the internals.
|
||||
|
@@ -1,10 +1,12 @@
|
||||
|
||||
| Crate | Description |
|
||||
| ----------- | ----------- |
|
||||
| helix-stdx | Extensions to the standard library (similar to [`rust-analyzer`'s](https://github.com/rust-lang/rust-analyzer/blob/ea413f67a8f730b4211c09e103f8207c62e7dbc3/crates/stdx/Cargo.toml#L5)) |
|
||||
| helix-core | Core editing primitives, functional. |
|
||||
| helix-lsp | Language server client |
|
||||
| helix-lsp-types | Language Server Protocol type definitions |
|
||||
| helix-dap | Debug Adapter Protocol (DAP) client |
|
||||
| helix-event | Primitives for defining and handling events within the editor |
|
||||
| helix-loader | Functions for building, fetching, and loading external resources |
|
||||
| helix-view | UI abstractions for use in backends, imperative shell. |
|
||||
| helix-term | Terminal UI |
|
||||
@@ -110,3 +112,17 @@ The `main` function sets up a new `Application` that runs the event loop.
|
||||
## TUI / Term
|
||||
|
||||
TODO: document Component and rendering related stuff
|
||||
|
||||
## Event
|
||||
|
||||
The `helix-event` crate defines primitives for defining and acting on events
|
||||
within the editor. "Events" cover things like opening, changing and closing of
|
||||
documents, starting and stopping of language servers and more.
|
||||
|
||||
`helix-event` has tools for defining events and registering _hooks_ which run
|
||||
any time an event is emitted. `helix-event` also provides `AsyncHook` - a tool
|
||||
for running cancellable tasks which run after events with _debouncing_.
|
||||
|
||||
See the `AsyncHook` type for more information. Events can be created within the
|
||||
`events!` macro. Synchronous hooks can be created with `register_hook!`. And
|
||||
editor-wide events can be sent to hooks with `helix_event::dispatch`.
|
||||
|
@@ -16,7 +16,7 @@ being published.
|
||||
* Add new `<release>` entry in `contrib/Helix.appdata.xml` with release information according to the [AppStream spec](https://www.freedesktop.org/software/appstream/docs/sect-Metadata-Releases.html)
|
||||
* Tag and push
|
||||
* Switch to master and pull
|
||||
* `git tag -s -m "<tag>" -a <tag> && git push` (note the `-s` which signs the tag)
|
||||
* `git tag -s -m "<tag>" -a <tag> && git push origin <tag>` (note the `-s` which signs the tag)
|
||||
* Wait for the Release CI to finish
|
||||
* It will automatically turn the git tag into a GitHub release when it uploads artifacts
|
||||
* Edit the new release
|
||||
|
62
flake.lock
generated
62
flake.lock
generated
@@ -1,45 +1,12 @@
|
||||
{
|
||||
"nodes": {
|
||||
"crane": {
|
||||
"locked": {
|
||||
"lastModified": 1727974419,
|
||||
"narHash": "sha256-WD0//20h+2/yPGkO88d2nYbb23WMWYvnRyDQ9Dx4UHg=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "37e4f9f0976cb9281cd3f0c70081e5e0ecaee93f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1726560853,
|
||||
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1728018373,
|
||||
"narHash": "sha256-NOiTvBbRLIOe5F6RbHaAh6++BNjsb149fGZd1T4+KBg=",
|
||||
"lastModified": 1740560979,
|
||||
"narHash": "sha256-Vr3Qi346M+8CjedtbyUevIGDZW8LcA1fTG0ugPY/Hic=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "bc947f541ae55e999ffdb4013441347d83b00feb",
|
||||
"rev": "5135c59491985879812717f4c9fea69604e7f26f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -51,8 +18,6 @@
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"crane": "crane",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay"
|
||||
}
|
||||
@@ -64,11 +29,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1728268235,
|
||||
"narHash": "sha256-lJMFnMO4maJuNO6PQ5fZesrTmglze3UFTTBuKGwR1Nw=",
|
||||
"lastModified": 1740623427,
|
||||
"narHash": "sha256-3SdPQrZoa4odlScFDUHd4CUPQ/R1gtH4Mq9u8CBiK8M=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "25685cc2c7054efc31351c172ae77b21814f2d42",
|
||||
"rev": "d342e8b5fd88421ff982f383c853f0fc78a847ab",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -76,21 +41,6 @@
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
241
flake.nix
241
flake.nix
@@ -3,189 +3,98 @@
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
rust-overlay = {
|
||||
url = "github:oxalica/rust-overlay";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
crane.url = "github:ipetkov/crane";
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
crane,
|
||||
flake-utils,
|
||||
rust-overlay,
|
||||
...
|
||||
}:
|
||||
flake-utils.lib.eachDefaultSystem (system: let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [(import rust-overlay)];
|
||||
};
|
||||
mkRootPath = rel:
|
||||
builtins.path {
|
||||
path = "${toString ./.}/${rel}";
|
||||
name = rel;
|
||||
}: let
|
||||
inherit (nixpkgs) lib;
|
||||
systems = [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
"x86_64-darwin"
|
||||
"aarch64-darwin"
|
||||
];
|
||||
eachSystem = lib.genAttrs systems;
|
||||
pkgsFor = eachSystem (system:
|
||||
import nixpkgs {
|
||||
localSystem.system = system;
|
||||
overlays = [(import rust-overlay) self.overlays.helix];
|
||||
});
|
||||
gitRev = self.rev or self.dirtyRev or null;
|
||||
in {
|
||||
packages = eachSystem (system: {
|
||||
inherit (pkgsFor.${system}) helix;
|
||||
/*
|
||||
The default Helix build. Uses the latest stable Rust toolchain, and unstable
|
||||
nixpkgs.
|
||||
|
||||
The build inputs can be overridden with the following:
|
||||
|
||||
packages.${system}.default.override { rustPlatform = newPlatform; };
|
||||
|
||||
Overriding a derivation attribute can be done as well:
|
||||
|
||||
packages.${system}.default.overrideAttrs { buildType = "debug"; };
|
||||
*/
|
||||
default = self.packages.${system}.helix;
|
||||
});
|
||||
checks =
|
||||
lib.mapAttrs (system: pkgs: let
|
||||
# Get Helix's MSRV toolchain to build with by default.
|
||||
msrvToolchain = pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
|
||||
msrvPlatform = pkgs.makeRustPlatform {
|
||||
cargo = msrvToolchain;
|
||||
rustc = msrvToolchain;
|
||||
};
|
||||
filteredSource = let
|
||||
pathsToIgnore = [
|
||||
".envrc"
|
||||
".ignore"
|
||||
".github"
|
||||
".gitignore"
|
||||
"logo_dark.svg"
|
||||
"logo_light.svg"
|
||||
"rust-toolchain.toml"
|
||||
"rustfmt.toml"
|
||||
"runtime"
|
||||
"screenshot.png"
|
||||
"book"
|
||||
"docs"
|
||||
"README.md"
|
||||
"CHANGELOG.md"
|
||||
"shell.nix"
|
||||
"default.nix"
|
||||
"grammars.nix"
|
||||
"flake.nix"
|
||||
"flake.lock"
|
||||
];
|
||||
ignorePaths = path: type: let
|
||||
inherit (nixpkgs) lib;
|
||||
# split the nix store path into its components
|
||||
components = lib.splitString "/" path;
|
||||
# drop off the `/nix/hash-source` section from the path
|
||||
relPathComponents = lib.drop 4 components;
|
||||
# reassemble the path components
|
||||
relPath = lib.concatStringsSep "/" relPathComponents;
|
||||
in {
|
||||
helix = self.packages.${system}.helix.override {
|
||||
rustPlatform = msrvPlatform;
|
||||
};
|
||||
})
|
||||
pkgsFor;
|
||||
|
||||
# Devshell behavior is preserved.
|
||||
devShells =
|
||||
lib.mapAttrs (system: pkgs: {
|
||||
default = let
|
||||
commonRustFlagsEnv = "-C link-arg=-fuse-ld=lld -C target-cpu=native --cfg tokio_unstable";
|
||||
platformRustFlagsEnv = lib.optionalString pkgs.stdenv.isLinux "-Clink-arg=-Wl,--no-rosegment";
|
||||
in
|
||||
lib.all (p: ! (lib.hasPrefix p relPath)) pathsToIgnore;
|
||||
in
|
||||
builtins.path {
|
||||
name = "helix-source";
|
||||
path = toString ./.;
|
||||
# filter out unnecessary paths
|
||||
filter = ignorePaths;
|
||||
};
|
||||
makeOverridableHelix = old: config: let
|
||||
grammars = pkgs.callPackage ./grammars.nix config;
|
||||
runtimeDir = pkgs.runCommand "helix-runtime" {} ''
|
||||
mkdir -p $out
|
||||
ln -s ${mkRootPath "runtime"}/* $out
|
||||
rm -r $out/grammars
|
||||
ln -s ${grammars} $out/grammars
|
||||
'';
|
||||
helix-wrapped =
|
||||
pkgs.runCommand
|
||||
old.name
|
||||
{
|
||||
inherit (old) pname version;
|
||||
meta = old.meta or {};
|
||||
passthru =
|
||||
(old.passthru or {})
|
||||
// {
|
||||
unwrapped = old;
|
||||
};
|
||||
nativeBuildInputs = [pkgs.makeWrapper];
|
||||
makeWrapperArgs = config.makeWrapperArgs or [];
|
||||
}
|
||||
''
|
||||
cp -rs --no-preserve=mode,ownership ${old} $out
|
||||
wrapProgram "$out/bin/hx" ''${makeWrapperArgs[@]} --set HELIX_RUNTIME "${runtimeDir}"
|
||||
'';
|
||||
in
|
||||
helix-wrapped
|
||||
// {
|
||||
override = makeOverridableHelix old;
|
||||
passthru =
|
||||
helix-wrapped.passthru
|
||||
// {
|
||||
wrapper = old: makeOverridableHelix old config;
|
||||
};
|
||||
};
|
||||
stdenv =
|
||||
if pkgs.stdenv.isLinux
|
||||
then pkgs.stdenv
|
||||
else pkgs.clangStdenv;
|
||||
rustFlagsEnv = pkgs.lib.optionalString stdenv.isLinux "-C link-arg=-fuse-ld=lld -C target-cpu=native -Clink-arg=-Wl,--no-rosegment --cfg tokio_unstable";
|
||||
rustToolchain = pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
|
||||
craneLibMSRV = (crane.mkLib pkgs).overrideToolchain rustToolchain;
|
||||
craneLibStable = (crane.mkLib pkgs).overrideToolchain pkgs.pkgsBuildHost.rust-bin.stable.latest.default;
|
||||
commonArgs = {
|
||||
inherit stdenv;
|
||||
inherit (craneLibMSRV.crateNameFromCargoToml {cargoToml = ./helix-term/Cargo.toml;}) pname;
|
||||
inherit (craneLibMSRV.crateNameFromCargoToml {cargoToml = ./Cargo.toml;}) version;
|
||||
src = filteredSource;
|
||||
# disable fetching and building of tree-sitter grammars in the helix-term build.rs
|
||||
HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1";
|
||||
buildInputs = [stdenv.cc.cc.lib];
|
||||
nativeBuildInputs = [pkgs.installShellFiles];
|
||||
# disable tests
|
||||
doCheck = false;
|
||||
meta.mainProgram = "hx";
|
||||
};
|
||||
cargoArtifacts = craneLibMSRV.buildDepsOnly commonArgs;
|
||||
in {
|
||||
packages = {
|
||||
helix-unwrapped = craneLibStable.buildPackage (commonArgs
|
||||
// {
|
||||
cargoArtifacts = craneLibStable.buildDepsOnly commonArgs;
|
||||
postInstall = ''
|
||||
mkdir -p $out/share/applications $out/share/icons/hicolor/scalable/apps $out/share/icons/hicolor/256x256/apps
|
||||
cp contrib/Helix.desktop $out/share/applications
|
||||
cp logo.svg $out/share/icons/hicolor/scalable/apps/helix.svg
|
||||
cp contrib/helix.png $out/share/icons/hicolor/256x256/apps
|
||||
installShellCompletion contrib/completion/hx.{bash,fish,zsh}
|
||||
pkgs.mkShell {
|
||||
inputsFrom = [self.checks.${system}.helix];
|
||||
nativeBuildInputs = with pkgs;
|
||||
[
|
||||
lld
|
||||
cargo-flamegraph
|
||||
rust-bin.nightly.latest.rust-analyzer
|
||||
]
|
||||
++ (lib.optional (stdenv.isx86_64 && stdenv.isLinux) cargo-tarpaulin)
|
||||
++ (lib.optional stdenv.isLinux lldb)
|
||||
++ (lib.optional stdenv.isDarwin darwin.apple_sdk.frameworks.CoreFoundation);
|
||||
shellHook = ''
|
||||
export RUST_BACKTRACE="1"
|
||||
export RUSTFLAGS="''${RUSTFLAGS:-""} ${commonRustFlagsEnv} ${platformRustFlagsEnv}"
|
||||
'';
|
||||
});
|
||||
helix = makeOverridableHelix self.packages.${system}.helix-unwrapped {};
|
||||
default = self.packages.${system}.helix;
|
||||
};
|
||||
})
|
||||
pkgsFor;
|
||||
|
||||
overlays = {
|
||||
helix = final: prev: {
|
||||
helix = final.callPackage ./default.nix {inherit gitRev;};
|
||||
};
|
||||
|
||||
checks = {
|
||||
# Build the crate itself
|
||||
inherit (self.packages.${system}) helix;
|
||||
|
||||
clippy = craneLibMSRV.cargoClippy (commonArgs
|
||||
// {
|
||||
inherit cargoArtifacts;
|
||||
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
|
||||
});
|
||||
|
||||
fmt = craneLibMSRV.cargoFmt commonArgs;
|
||||
|
||||
doc = craneLibMSRV.cargoDoc (commonArgs
|
||||
// {
|
||||
inherit cargoArtifacts;
|
||||
});
|
||||
|
||||
test = craneLibMSRV.cargoTest (commonArgs
|
||||
// {
|
||||
inherit cargoArtifacts;
|
||||
});
|
||||
};
|
||||
|
||||
devShells.default = pkgs.mkShell {
|
||||
inputsFrom = builtins.attrValues self.checks.${system};
|
||||
nativeBuildInputs = with pkgs;
|
||||
[lld_13 cargo-flamegraph rust-analyzer]
|
||||
++ (lib.optional (stdenv.isx86_64 && stdenv.isLinux) pkgs.cargo-tarpaulin)
|
||||
++ (lib.optional stdenv.isLinux pkgs.lldb)
|
||||
++ (lib.optional stdenv.isDarwin pkgs.darwin.apple_sdk.frameworks.CoreFoundation);
|
||||
shellHook = ''
|
||||
export HELIX_RUNTIME="$PWD/runtime"
|
||||
export RUST_BACKTRACE="1"
|
||||
export RUSTFLAGS="''${RUSTFLAGS:-""} ${rustFlagsEnv}"
|
||||
'';
|
||||
};
|
||||
})
|
||||
// {
|
||||
overlays.default = final: prev: {
|
||||
inherit (self.packages.${final.system}) helix;
|
||||
};
|
||||
default = self.overlays.helix;
|
||||
};
|
||||
|
||||
};
|
||||
nixConfig = {
|
||||
extra-substituters = ["https://helix.cachix.org"];
|
||||
extra-trusted-public-keys = ["helix.cachix.org-1:ejp9KQpR1FBI2onstMQ34yogDm4OgU2ru6lIwPvuCVs="];
|
||||
|
47
grammars.nix
47
grammars.nix
@@ -1,22 +1,13 @@
|
||||
{
|
||||
stdenv,
|
||||
lib,
|
||||
runCommandLocal,
|
||||
runCommand,
|
||||
yj,
|
||||
includeGrammarIf ? _: true,
|
||||
grammarOverlays ? [],
|
||||
...
|
||||
}: let
|
||||
# HACK: nix < 2.6 has a bug in the toml parser, so we convert to JSON
|
||||
# before parsing
|
||||
languages-json = runCommandLocal "languages-toml-to-json" {} ''
|
||||
${yj}/bin/yj -t < ${./languages.toml} > $out
|
||||
'';
|
||||
languagesConfig =
|
||||
if lib.versionAtLeast builtins.nixVersion "2.6.0"
|
||||
then builtins.fromTOML (builtins.readFile ./languages.toml)
|
||||
else builtins.fromJSON (builtins.readFile (builtins.toPath languages-json));
|
||||
builtins.fromTOML (builtins.readFile ./languages.toml);
|
||||
isGitGrammar = grammar:
|
||||
builtins.hasAttr "source" grammar
|
||||
&& builtins.hasAttr "git" grammar.source
|
||||
@@ -32,10 +23,10 @@
|
||||
# If `use-grammars.except` is set, use all other grammars.
|
||||
# Otherwise use all grammars.
|
||||
useGrammar = grammar:
|
||||
if languagesConfig?use-grammars.only then
|
||||
builtins.elem grammar.name languagesConfig.use-grammars.only
|
||||
else if languagesConfig?use-grammars.except then
|
||||
!(builtins.elem grammar.name languagesConfig.use-grammars.except)
|
||||
if languagesConfig ? use-grammars.only
|
||||
then builtins.elem grammar.name languagesConfig.use-grammars.only
|
||||
else if languagesConfig ? use-grammars.except
|
||||
then !(builtins.elem grammar.name languagesConfig.use-grammars.except)
|
||||
else true;
|
||||
grammarsToUse = builtins.filter useGrammar languagesConfig.grammar;
|
||||
gitGrammars = builtins.filter isGitGrammar grammarsToUse;
|
||||
@@ -66,10 +57,10 @@
|
||||
version = grammar.source.rev;
|
||||
|
||||
src = source;
|
||||
sourceRoot = if builtins.hasAttr "subpath" grammar.source then
|
||||
"source/${grammar.source.subpath}"
|
||||
else
|
||||
"source";
|
||||
sourceRoot =
|
||||
if builtins.hasAttr "subpath" grammar.source
|
||||
then "source/${grammar.source.subpath}"
|
||||
else "source";
|
||||
|
||||
dontConfigure = true;
|
||||
|
||||
@@ -96,8 +87,6 @@
|
||||
$CC -c src/parser.c -o parser.o $FLAGS
|
||||
$CXX -shared -o $NAME.so *.o
|
||||
|
||||
ls -al
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
@@ -116,17 +105,21 @@
|
||||
'';
|
||||
};
|
||||
grammarsToBuild = builtins.filter includeGrammarIf gitGrammars;
|
||||
builtGrammars = builtins.map (grammar: {
|
||||
inherit (grammar) name;
|
||||
value = buildGrammar grammar;
|
||||
}) grammarsToBuild;
|
||||
builtGrammars =
|
||||
builtins.map (grammar: {
|
||||
inherit (grammar) name;
|
||||
value = buildGrammar grammar;
|
||||
})
|
||||
grammarsToBuild;
|
||||
extensibleGrammars =
|
||||
lib.makeExtensible (self: builtins.listToAttrs builtGrammars);
|
||||
overlayedGrammars = lib.pipe extensibleGrammars
|
||||
overlaidGrammars =
|
||||
lib.pipe extensibleGrammars
|
||||
(builtins.map (overlay: grammar: grammar.extend overlay) grammarOverlays);
|
||||
grammarLinks = lib.mapAttrsToList
|
||||
grammarLinks =
|
||||
lib.mapAttrsToList
|
||||
(name: artifact: "ln -s ${artifact}/${name}.so $out/${name}.so")
|
||||
(lib.filterAttrs (n: v: lib.isDerivation v) overlayedGrammars);
|
||||
(lib.filterAttrs (n: v: lib.isDerivation v) overlaidGrammars);
|
||||
in
|
||||
runCommand "consolidated-helix-grammars" {} ''
|
||||
mkdir -p $out
|
||||
|
@@ -20,10 +20,10 @@ helix-stdx = { path = "../helix-stdx" }
|
||||
helix-loader = { path = "../helix-loader" }
|
||||
helix-parsec = { path = "../helix-parsec" }
|
||||
|
||||
ropey = { version = "1.6.1", default-features = false, features = ["simd"] }
|
||||
smallvec = "1.13"
|
||||
ropey.workspace = true
|
||||
smallvec = "1.15"
|
||||
smartstring = "1.0.1"
|
||||
unicode-segmentation = "1.12"
|
||||
unicode-segmentation.workspace = true
|
||||
# unicode-width is changing width definitions
|
||||
# that both break our logic and disagree with common
|
||||
# width definitions in terminals, we need to replace it.
|
||||
@@ -32,36 +32,32 @@ unicode-segmentation = "1.12"
|
||||
unicode-width = "=0.1.12"
|
||||
unicode-general-category = "1.0"
|
||||
slotmap.workspace = true
|
||||
tree-sitter.workspace = true
|
||||
once_cell = "1.20"
|
||||
tree-house.workspace = true
|
||||
once_cell = "1.21"
|
||||
arc-swap = "1"
|
||||
regex = "1"
|
||||
bitflags = "2.6"
|
||||
ahash = "0.8.11"
|
||||
hashbrown = { version = "0.14.5", features = ["raw"] }
|
||||
dunce = "1.0"
|
||||
bitflags.workspace = true
|
||||
foldhash.workspace = true
|
||||
url = "2.5.4"
|
||||
|
||||
log = "0.4"
|
||||
anyhow = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
toml = "0.8"
|
||||
|
||||
imara-diff = "0.1.7"
|
||||
toml.workspace = true
|
||||
|
||||
imara-diff = "0.2.0"
|
||||
encoding_rs = "0.8"
|
||||
|
||||
chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }
|
||||
|
||||
etcetera = "0.8"
|
||||
textwrap = "0.16.1"
|
||||
textwrap = "0.16.2"
|
||||
|
||||
nucleo.workspace = true
|
||||
parking_lot = "0.12"
|
||||
globset = "0.4.15"
|
||||
regex-cursor = "0.1.4"
|
||||
parking_lot.workspace = true
|
||||
globset = "0.4.16"
|
||||
regex-cursor = "0.1.5"
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = { version = "1", default-features = false }
|
||||
indoc = "2.0.5"
|
||||
indoc = "2.0.6"
|
||||
|
1279
helix-core/src/command_line.rs
Normal file
1279
helix-core/src/command_line.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,8 @@
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{
|
||||
syntax::BlockCommentToken, Change, Range, Rope, RopeSlice, Selection, Tendril, Transaction,
|
||||
syntax::config::BlockCommentToken, Change, Range, Rope, RopeSlice, Selection, Tendril,
|
||||
Transaction,
|
||||
};
|
||||
use helix_stdx::rope::RopeSliceExt;
|
||||
use std::borrow::Cow;
|
||||
@@ -204,13 +205,9 @@ pub fn find_block_comments(
|
||||
range: *range,
|
||||
start_pos,
|
||||
end_pos,
|
||||
start_margin: selection_slice
|
||||
.get_char(after_start)
|
||||
.map_or(false, |c| c == ' '),
|
||||
start_margin: selection_slice.get_char(after_start) == Some(' '),
|
||||
end_margin: after_start != before_end
|
||||
&& selection_slice
|
||||
.get_char(before_end)
|
||||
.map_or(false, |c| c == ' '),
|
||||
&& (selection_slice.get_char(before_end) == Some(' ')),
|
||||
start_token: start_token.to_string(),
|
||||
end_token: end_token.to_string(),
|
||||
});
|
||||
|
@@ -1,6 +1,6 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::Transaction;
|
||||
use crate::{diagnostic::LanguageServerId, Transaction};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct CompletionItem {
|
||||
@@ -8,5 +8,19 @@ pub struct CompletionItem {
|
||||
pub label: Cow<'static, str>,
|
||||
pub kind: Cow<'static, str>,
|
||||
/// Containing Markdown
|
||||
pub documentation: String,
|
||||
pub documentation: Option<String>,
|
||||
pub provider: CompletionProvider,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
pub enum CompletionProvider {
|
||||
Lsp(LanguageServerId),
|
||||
Path,
|
||||
Word,
|
||||
}
|
||||
|
||||
impl From<LanguageServerId> for CompletionProvider {
|
||||
fn from(id: LanguageServerId) -> Self {
|
||||
CompletionProvider::Lsp(id)
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
use crate::syntax::{Configuration, Loader, LoaderError};
|
||||
use crate::syntax::{config::Configuration, Loader, LoaderError};
|
||||
|
||||
/// Language configuration based on built-in languages.toml.
|
||||
pub fn default_lang_config() -> Configuration {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
//! LSP diagnostic utility types.
|
||||
use std::fmt;
|
||||
use std::{fmt, sync::Arc};
|
||||
|
||||
pub use helix_stdx::range::Range;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -50,8 +50,35 @@ pub struct Diagnostic {
|
||||
pub data: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
// TODO turn this into an enum + feature flag when lsp becomes optional
|
||||
pub type DiagnosticProvider = LanguageServerId;
|
||||
/// The source of a diagnostic.
|
||||
///
|
||||
/// This type is cheap to clone: all data is either `Copy` or wrapped in an `Arc`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum DiagnosticProvider {
|
||||
Lsp {
|
||||
/// The ID of the language server which sent the diagnostic.
|
||||
server_id: LanguageServerId,
|
||||
/// An optional identifier under which diagnostics are managed by the client.
|
||||
///
|
||||
/// `identifier` is a field from the LSP "Pull Diagnostics" feature meant to provide an
|
||||
/// optional "namespace" for diagnostics: a language server can respond to a diagnostics
|
||||
/// pull request with an identifier and these diagnostics should be treated as separate
|
||||
/// from push diagnostics. Rust-analyzer uses this feature for example to provide Cargo
|
||||
/// diagnostics with push and internal diagnostics with pull. The push diagnostics should
|
||||
/// not clear the pull diagnostics and vice-versa.
|
||||
identifier: Option<Arc<str>>,
|
||||
},
|
||||
// Future internal features can go here...
|
||||
}
|
||||
|
||||
impl DiagnosticProvider {
|
||||
pub fn language_server_id(&self) -> Option<LanguageServerId> {
|
||||
match self {
|
||||
Self::Lsp { server_id, .. } => Some(*server_id),
|
||||
// _ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// while I would prefer having this in helix-lsp that necessitates a bunch of
|
||||
// conversions I would rather not add. I think its fine since this just a very
|
||||
|
@@ -1,51 +1,22 @@
|
||||
use std::ops::Range;
|
||||
use std::time::Instant;
|
||||
|
||||
use imara_diff::intern::InternedInput;
|
||||
use imara_diff::Algorithm;
|
||||
use imara_diff::{Algorithm, Diff, Hunk, IndentHeuristic, IndentLevel, InternedInput};
|
||||
use ropey::RopeSlice;
|
||||
|
||||
use crate::{ChangeSet, Rope, Tendril, Transaction};
|
||||
|
||||
/// A `imara_diff::Sink` that builds a `ChangeSet` for a character diff of a hunk
|
||||
struct CharChangeSetBuilder<'a> {
|
||||
res: &'a mut ChangeSet,
|
||||
hunk: &'a InternedInput<char>,
|
||||
pos: u32,
|
||||
}
|
||||
|
||||
impl imara_diff::Sink for CharChangeSetBuilder<'_> {
|
||||
type Out = ();
|
||||
fn process_change(&mut self, before: Range<u32>, after: Range<u32>) {
|
||||
self.res.retain((before.start - self.pos) as usize);
|
||||
self.res.delete(before.len());
|
||||
self.pos = before.end;
|
||||
|
||||
let res = self.hunk.after[after.start as usize..after.end as usize]
|
||||
.iter()
|
||||
.map(|&token| self.hunk.interner[token])
|
||||
.collect();
|
||||
|
||||
self.res.insert(res);
|
||||
}
|
||||
|
||||
fn finish(self) -> Self::Out {
|
||||
self.res.retain(self.hunk.before.len() - self.pos as usize);
|
||||
}
|
||||
}
|
||||
|
||||
struct LineChangeSetBuilder<'a> {
|
||||
struct ChangeSetBuilder<'a> {
|
||||
res: ChangeSet,
|
||||
after: RopeSlice<'a>,
|
||||
file: &'a InternedInput<RopeSlice<'a>>,
|
||||
current_hunk: InternedInput<char>,
|
||||
char_diff: Diff,
|
||||
pos: u32,
|
||||
}
|
||||
|
||||
impl imara_diff::Sink for LineChangeSetBuilder<'_> {
|
||||
type Out = ChangeSet;
|
||||
|
||||
fn process_change(&mut self, before: Range<u32>, after: Range<u32>) {
|
||||
impl ChangeSetBuilder<'_> {
|
||||
fn process_hunk(&mut self, before: Range<u32>, after: Range<u32>) {
|
||||
let len = self.file.before[self.pos as usize..before.start as usize]
|
||||
.iter()
|
||||
.map(|&it| self.file.interner[it].len_chars())
|
||||
@@ -109,25 +80,36 @@ impl imara_diff::Sink for LineChangeSetBuilder<'_> {
|
||||
.flat_map(|&it| self.file.interner[it].chars());
|
||||
self.current_hunk.update_before(hunk_before);
|
||||
self.current_hunk.update_after(hunk_after);
|
||||
|
||||
// the histogram heuristic does not work as well
|
||||
// for characters because the same characters often reoccur
|
||||
// use myer diff instead
|
||||
imara_diff::diff(
|
||||
self.char_diff.compute_with(
|
||||
Algorithm::Myers,
|
||||
&self.current_hunk,
|
||||
CharChangeSetBuilder {
|
||||
res: &mut self.res,
|
||||
hunk: &self.current_hunk,
|
||||
pos: 0,
|
||||
},
|
||||
&self.current_hunk.before,
|
||||
&self.current_hunk.after,
|
||||
self.current_hunk.interner.num_tokens(),
|
||||
);
|
||||
let mut pos = 0;
|
||||
for Hunk { before, after } in self.char_diff.hunks() {
|
||||
self.res.retain((before.start - pos) as usize);
|
||||
self.res.delete(before.len());
|
||||
pos = before.end;
|
||||
|
||||
let res = self.current_hunk.after[after.start as usize..after.end as usize]
|
||||
.iter()
|
||||
.map(|&token| self.current_hunk.interner[token])
|
||||
.collect();
|
||||
|
||||
self.res.insert(res);
|
||||
}
|
||||
self.res
|
||||
.retain(self.current_hunk.before.len() - pos as usize);
|
||||
// reuse allocations
|
||||
self.current_hunk.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(mut self) -> Self::Out {
|
||||
fn finish(mut self) -> ChangeSet {
|
||||
let len = self.file.before[self.pos as usize..]
|
||||
.iter()
|
||||
.map(|&it| self.file.interner[it].len_chars())
|
||||
@@ -140,7 +122,7 @@ impl imara_diff::Sink for LineChangeSetBuilder<'_> {
|
||||
|
||||
struct RopeLines<'a>(RopeSlice<'a>);
|
||||
|
||||
impl<'a> imara_diff::intern::TokenSource for RopeLines<'a> {
|
||||
impl<'a> imara_diff::TokenSource for RopeLines<'a> {
|
||||
type Token = RopeSlice<'a>;
|
||||
type Tokenizer = ropey::iter::Lines<'a>;
|
||||
|
||||
@@ -161,15 +143,23 @@ pub fn compare_ropes(before: &Rope, after: &Rope) -> Transaction {
|
||||
let res = ChangeSet::with_capacity(32);
|
||||
let after = after.slice(..);
|
||||
let file = InternedInput::new(RopeLines(before.slice(..)), RopeLines(after));
|
||||
let builder = LineChangeSetBuilder {
|
||||
let mut builder = ChangeSetBuilder {
|
||||
res,
|
||||
file: &file,
|
||||
after,
|
||||
pos: 0,
|
||||
current_hunk: InternedInput::default(),
|
||||
char_diff: Diff::default(),
|
||||
};
|
||||
|
||||
let res = imara_diff::diff(Algorithm::Histogram, &file, builder).into();
|
||||
let mut diff = Diff::compute(Algorithm::Histogram, &file);
|
||||
diff.postprocess_with_heuristic(
|
||||
&file,
|
||||
IndentHeuristic::new(|token| IndentLevel::for_ascii_line(file.interner[token].bytes(), 4)),
|
||||
);
|
||||
for hunk in diff.hunks() {
|
||||
builder.process_hunk(hunk.before, hunk.after)
|
||||
}
|
||||
let res = builder.finish().into();
|
||||
|
||||
log::debug!(
|
||||
"rope diff took {}s",
|
||||
|
@@ -19,10 +19,12 @@ mod test;
|
||||
|
||||
use unicode_segmentation::{Graphemes, UnicodeSegmentation};
|
||||
|
||||
use helix_stdx::rope::{RopeGraphemes, RopeSliceExt};
|
||||
|
||||
use crate::graphemes::{Grapheme, GraphemeStr};
|
||||
use crate::syntax::Highlight;
|
||||
use crate::text_annotations::TextAnnotations;
|
||||
use crate::{Position, RopeGraphemes, RopeSlice};
|
||||
use crate::{Position, RopeSlice};
|
||||
|
||||
/// TODO make Highlight a u32 to reduce the size of this enum to a single word.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -219,7 +221,7 @@ impl<'t> DocumentFormatter<'t> {
|
||||
text_fmt,
|
||||
annotations,
|
||||
visual_pos: Position { row: 0, col: 0 },
|
||||
graphemes: RopeGraphemes::new(text.slice(block_char_idx..)),
|
||||
graphemes: text.slice(block_char_idx..).graphemes(),
|
||||
char_pos: block_char_idx,
|
||||
exhausted: false,
|
||||
indent_level: None,
|
||||
@@ -370,8 +372,8 @@ impl<'t> DocumentFormatter<'t> {
|
||||
match col.cmp(&(self.text_fmt.viewport_width as usize)) {
|
||||
// The EOF char and newline chars are always selectable in helix. That means
|
||||
// that wrapping happens "too-early" if a word fits a line perfectly. This
|
||||
// is intentional so that all selectable graphemes are always visisble (and
|
||||
// therefore the cursor never dissapears). However if the user manually set a
|
||||
// is intentional so that all selectable graphemes are always visible (and
|
||||
// therefore the cursor never disappears). However if the user manually set a
|
||||
// lower softwrap width then this is undesirable. Just increasing the viewport-
|
||||
// width by one doesn't work because if a line is wrapped multiple times then
|
||||
// some words may extend past the specified width.
|
||||
@@ -380,9 +382,10 @@ impl<'t> DocumentFormatter<'t> {
|
||||
// by a newline/eof character here.
|
||||
Ordering::Equal
|
||||
if self.text_fmt.soft_wrap_at_text_width
|
||||
&& self.peek_grapheme(col, char_pos).map_or(false, |grapheme| {
|
||||
grapheme.is_newline() || grapheme.is_eof()
|
||||
}) => {}
|
||||
&& self
|
||||
.peek_grapheme(col, char_pos)
|
||||
.is_some_and(|grapheme| grapheme.is_newline() || grapheme.is_eof()) => {
|
||||
}
|
||||
Ordering::Equal if word_width > self.text_fmt.max_wrap as usize => return,
|
||||
Ordering::Greater if word_width > self.text_fmt.max_wrap as usize => {
|
||||
self.peeked_grapheme = self.word_buf.pop();
|
||||
|
@@ -102,6 +102,14 @@ fn long_word_softwrap() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn softwrap_multichar_grapheme() {
|
||||
assert_eq!(
|
||||
softwrap_text("xxxx xxxx xxx a\u{0301}bc\n"),
|
||||
"xxxx xxxx xxx \n.ábc \n "
|
||||
)
|
||||
}
|
||||
|
||||
fn softwrap_text_at_text_width(text: &str) -> String {
|
||||
let mut text_fmt = TextFormat::new_test(true);
|
||||
text_fmt.soft_wrap_at_text_width = true;
|
||||
|
334
helix-core/src/editor_config.rs
Normal file
334
helix-core/src/editor_config.rs
Normal file
@@ -0,0 +1,334 @@
|
||||
//! Support for [EditorConfig](https://EditorConfig.org) configuration loading.
|
||||
//!
|
||||
//! EditorConfig is an editor-agnostic format for specifying configuration in an INI-like, human
|
||||
//! friendly syntax in `.editorconfig` files (which are intended to be checked into VCS). This
|
||||
//! module provides functions to search for all `.editorconfig` files that apply to a given path
|
||||
//! and returns an `EditorConfig` type containing any specified configuration options.
|
||||
//!
|
||||
//! At time of writing, this module follows the [spec](https://spec.editorconfig.org/) at
|
||||
//! version 0.17.2.
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
num::{NonZeroU16, NonZeroU8},
|
||||
path::Path,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use encoding_rs::Encoding;
|
||||
use globset::{GlobBuilder, GlobMatcher};
|
||||
|
||||
use crate::{
|
||||
indent::{IndentStyle, MAX_INDENT},
|
||||
LineEnding,
|
||||
};
|
||||
|
||||
/// Configuration declared for a path in `.editorconfig` files.
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub struct EditorConfig {
|
||||
pub indent_style: Option<IndentStyle>,
|
||||
pub tab_width: Option<NonZeroU8>,
|
||||
pub line_ending: Option<LineEnding>,
|
||||
pub encoding: Option<&'static Encoding>,
|
||||
// pub spelling_language: Option<SpellingLanguage>,
|
||||
pub trim_trailing_whitespace: Option<bool>,
|
||||
pub insert_final_newline: Option<bool>,
|
||||
pub max_line_length: Option<NonZeroU16>,
|
||||
}
|
||||
|
||||
impl EditorConfig {
|
||||
/// Finds any configuration in `.editorconfig` files which applies to the given path.
|
||||
///
|
||||
/// If no configuration applies then `EditorConfig::default()` is returned.
|
||||
pub fn find(path: &Path) -> Self {
|
||||
let mut configs = Vec::new();
|
||||
// <https://spec.editorconfig.org/#file-processing>
|
||||
for ancestor in path.ancestors() {
|
||||
let editor_config_file = ancestor.join(".editorconfig");
|
||||
let Ok(contents) = fs::read_to_string(&editor_config_file) else {
|
||||
continue;
|
||||
};
|
||||
let ini = match contents.parse::<Ini>() {
|
||||
Ok(ini) => ini,
|
||||
Err(err) => {
|
||||
log::warn!("Ignoring EditorConfig file at '{editor_config_file:?}' because a glob failed to compile: {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let is_root = ini.pairs.get("root").map(AsRef::as_ref) == Some("true");
|
||||
configs.push((ini, ancestor));
|
||||
// > The search shall stop if an EditorConfig file is found with the `root` key set to
|
||||
// > `true` in the preamble or when reaching the root filesystem directory.
|
||||
if is_root {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut pairs = Pairs::new();
|
||||
// Reverse the configuration stack so that the `.editorconfig` files closest to `path`
|
||||
// are applied last and overwrite settings in files closer to the search ceiling.
|
||||
//
|
||||
// > If multiple EditorConfig files have matching sections, the pairs from the closer
|
||||
// > EditorConfig file are read last, so pairs in closer files take precedence.
|
||||
for (config, dir) in configs.into_iter().rev() {
|
||||
let relative_path = path.strip_prefix(dir).expect("dir is an ancestor of path");
|
||||
|
||||
for section in config.sections {
|
||||
if section.glob.is_match(relative_path) {
|
||||
log::info!(
|
||||
"applying EditorConfig from section '{}' in file {:?}",
|
||||
section.glob.glob(),
|
||||
dir.join(".editorconfig")
|
||||
);
|
||||
pairs.extend(section.pairs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self::from_pairs(pairs)
|
||||
}
|
||||
|
||||
fn from_pairs(pairs: Pairs) -> Self {
|
||||
enum IndentSize {
|
||||
Tab,
|
||||
Spaces(NonZeroU8),
|
||||
}
|
||||
|
||||
// <https://spec.editorconfig.org/#supported-pairs>
|
||||
let indent_size = pairs.get("indent_size").and_then(|value| {
|
||||
if value.as_ref() == "tab" {
|
||||
Some(IndentSize::Tab)
|
||||
} else if let Ok(spaces) = value.parse::<NonZeroU8>() {
|
||||
Some(IndentSize::Spaces(spaces))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
let tab_width = pairs
|
||||
.get("tab_width")
|
||||
.and_then(|value| value.parse::<NonZeroU8>().ok())
|
||||
.or(match indent_size {
|
||||
Some(IndentSize::Spaces(spaces)) => Some(spaces),
|
||||
_ => None,
|
||||
});
|
||||
let indent_style = pairs
|
||||
.get("indent_style")
|
||||
.and_then(|value| match value.as_ref() {
|
||||
"tab" => Some(IndentStyle::Tabs),
|
||||
"space" => {
|
||||
let spaces = match indent_size {
|
||||
Some(IndentSize::Spaces(spaces)) => spaces.get(),
|
||||
Some(IndentSize::Tab) => tab_width.map(|n| n.get()).unwrap_or(4),
|
||||
None => 4,
|
||||
};
|
||||
Some(IndentStyle::Spaces(spaces.clamp(1, MAX_INDENT)))
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
let line_ending = pairs
|
||||
.get("end_of_line")
|
||||
.and_then(|value| match value.as_ref() {
|
||||
"lf" => Some(LineEnding::LF),
|
||||
"crlf" => Some(LineEnding::Crlf),
|
||||
#[cfg(feature = "unicode-lines")]
|
||||
"cr" => Some(LineEnding::CR),
|
||||
_ => None,
|
||||
});
|
||||
let encoding = pairs.get("charset").and_then(|value| match value.as_ref() {
|
||||
"latin1" => Some(encoding_rs::WINDOWS_1252),
|
||||
"utf-8" => Some(encoding_rs::UTF_8),
|
||||
// `utf-8-bom` is intentionally ignored.
|
||||
// > `utf-8-bom` is discouraged.
|
||||
"utf-16le" => Some(encoding_rs::UTF_16LE),
|
||||
"utf-16be" => Some(encoding_rs::UTF_16BE),
|
||||
_ => None,
|
||||
});
|
||||
let trim_trailing_whitespace =
|
||||
pairs
|
||||
.get("trim_trailing_whitespace")
|
||||
.and_then(|value| match value.as_ref() {
|
||||
"true" => Some(true),
|
||||
"false" => Some(false),
|
||||
_ => None,
|
||||
});
|
||||
let insert_final_newline = pairs
|
||||
.get("insert_final_newline")
|
||||
.and_then(|value| match value.as_ref() {
|
||||
"true" => Some(true),
|
||||
"false" => Some(false),
|
||||
_ => None,
|
||||
});
|
||||
// This option is not in the spec but is supported by some editors.
|
||||
// <https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties#max_line_length>
|
||||
let max_line_length = pairs
|
||||
.get("max_line_length")
|
||||
.and_then(|value| value.parse::<NonZeroU16>().ok());
|
||||
|
||||
Self {
|
||||
indent_style,
|
||||
tab_width,
|
||||
line_ending,
|
||||
encoding,
|
||||
trim_trailing_whitespace,
|
||||
insert_final_newline,
|
||||
max_line_length,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Pairs = HashMap<Box<str>, Box<str>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Section {
|
||||
glob: GlobMatcher,
|
||||
pairs: Pairs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct Ini {
|
||||
pairs: Pairs,
|
||||
sections: Vec<Section>,
|
||||
}
|
||||
|
||||
impl FromStr for Ini {
|
||||
type Err = globset::Error;
|
||||
|
||||
fn from_str(source: &str) -> Result<Self, Self::Err> {
|
||||
// <https://spec.editorconfig.org/#file-format>
|
||||
let mut ini = Ini::default();
|
||||
// > EditorConfig files are in an INI-like file format. To read an EditorConfig file, take
|
||||
// > one line at a time, from beginning to end. For each line:
|
||||
for full_line in source.lines() {
|
||||
// > 1. Remove all leading and trailing whitespace.
|
||||
let line = full_line.trim();
|
||||
// > 2. Process the remaining text as specified for its type below.
|
||||
// > The types of lines are:
|
||||
// > * Blank: contains nothing. Blank lines are ignored.
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
// > * Comment: starts with a ';' or '#'. Comment lines are ignored.
|
||||
if line.starts_with([';', '#']) {
|
||||
continue;
|
||||
}
|
||||
if let Some(section) = line.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
|
||||
// > * Section Header: starts with a `[` and ends with a `]`. These lines define
|
||||
// > globs...
|
||||
|
||||
// <https://spec.editorconfig.org/#glob-expressions>
|
||||
// We need to modify the glob string slightly since EditorConfig's glob flavor
|
||||
// doesn't match `globset`'s exactly. `globset` only allows '**' at the beginning
|
||||
// or end of a glob or between two '/'s. (This replacement is not very fancy but
|
||||
// should cover most practical cases.)
|
||||
let mut glob_str = section.replace("**.", "**/*.");
|
||||
if !is_glob_relative(section) {
|
||||
glob_str.insert_str(0, "**/");
|
||||
}
|
||||
let glob = GlobBuilder::new(&glob_str)
|
||||
.literal_separator(true)
|
||||
.backslash_escape(true)
|
||||
.empty_alternates(true)
|
||||
.build()?;
|
||||
ini.sections.push(Section {
|
||||
glob: glob.compile_matcher(),
|
||||
pairs: Pairs::new(),
|
||||
});
|
||||
} else if let Some((key, value)) = line.split_once('=') {
|
||||
// > * Key-Value Pair (or Pair): contains a key and a value, separated by an `=`.
|
||||
// > * Key: The part before the first `=` on the line.
|
||||
// > * Value: The part, if any, after the first `=` on the line.
|
||||
// > * Keys and values are trimmed of leading and trailing whitespace, but
|
||||
// > include any whitespace that is between non-whitespace characters.
|
||||
// > * If a value is not provided, then the value is an empty string.
|
||||
let key = key.trim().to_lowercase().into_boxed_str();
|
||||
let value = value.trim().to_lowercase().into_boxed_str();
|
||||
if let Some(section) = ini.sections.last_mut() {
|
||||
section.pairs.insert(key, value);
|
||||
} else {
|
||||
ini.pairs.insert(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(ini)
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines whether a glob is relative to the directory of the config file.
|
||||
fn is_glob_relative(source: &str) -> bool {
|
||||
// > If the glob contains a path separator (a `/` not inside square brackets), then the
|
||||
// > glob is relative to the directory level of the particular `.editorconfig` file itself.
|
||||
let mut idx = 0;
|
||||
while let Some(open) = source[idx..].find('[').map(|open| idx + open) {
|
||||
if source[..open].contains('/') {
|
||||
return true;
|
||||
}
|
||||
idx = source[open..]
|
||||
.find(']')
|
||||
.map_or(source.len(), |close| idx + close);
|
||||
}
|
||||
source[idx..].contains('/')
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn is_glob_relative_test() {
|
||||
assert!(is_glob_relative("subdir/*.c"));
|
||||
assert!(!is_glob_relative("*.txt"));
|
||||
assert!(!is_glob_relative("[a/b].c"));
|
||||
}
|
||||
|
||||
fn editor_config(path: impl AsRef<Path>, source: &str) -> EditorConfig {
|
||||
let path = path.as_ref();
|
||||
let ini = source.parse::<Ini>().unwrap();
|
||||
let pairs = ini
|
||||
.sections
|
||||
.into_iter()
|
||||
.filter(|section| section.glob.is_match(path))
|
||||
.fold(Pairs::new(), |mut acc, section| {
|
||||
acc.extend(section.pairs);
|
||||
acc
|
||||
});
|
||||
EditorConfig::from_pairs(pairs)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_test() {
|
||||
let source = r#"
|
||||
[*]
|
||||
indent_style = space
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
[docs/**.txt]
|
||||
insert_final_newline = true
|
||||
"#;
|
||||
|
||||
assert_eq!(
|
||||
editor_config("a.txt", source),
|
||||
EditorConfig {
|
||||
indent_style: Some(IndentStyle::Spaces(4)),
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
editor_config("pkg/Makefile", source),
|
||||
EditorConfig {
|
||||
indent_style: Some(IndentStyle::Tabs),
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
editor_config("docs/config/editor.txt", source),
|
||||
EditorConfig {
|
||||
indent_style: Some(IndentStyle::Spaces(4)),
|
||||
insert_final_newline: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
//! Utility functions to traverse the unicode graphemes of a `Rope`'s text contents.
|
||||
//!
|
||||
//! Based on <https://github.com/cessen/led/blob/c4fa72405f510b7fd16052f90a598c429b3104a6/src/graphemes.rs>
|
||||
use ropey::{iter::Chunks, str_utils::byte_to_char_idx, RopeSlice};
|
||||
use ropey::{str_utils::byte_to_char_idx, RopeSlice};
|
||||
use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
@@ -64,7 +64,7 @@ impl<'a> Grapheme<'a> {
|
||||
}
|
||||
|
||||
pub fn is_whitespace(&self) -> bool {
|
||||
!matches!(&self, Grapheme::Other { g } if !g.chars().all(char_is_whitespace))
|
||||
!matches!(&self, Grapheme::Other { g } if !g.chars().next().is_some_and(char_is_whitespace))
|
||||
}
|
||||
|
||||
// TODO currently word boundaries are used for softwrapping.
|
||||
@@ -72,7 +72,7 @@ impl<'a> Grapheme<'a> {
|
||||
// This could however be improved in the future by considering unicode
|
||||
// character classes but
|
||||
pub fn is_word_boundary(&self) -> bool {
|
||||
!matches!(&self, Grapheme::Other { g,.. } if g.chars().all(char_is_word))
|
||||
!matches!(&self, Grapheme::Other { g,.. } if g.chars().next().is_some_and(char_is_word))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +119,9 @@ pub fn grapheme_width(g: &str) -> usize {
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: for byte indexing versions of these functions see `RopeSliceExt`'s
|
||||
// `floor_grapheme_boundary` and `ceil_grapheme_boundary` and the rope grapheme iterators.
|
||||
|
||||
#[must_use]
|
||||
pub fn nth_prev_grapheme_boundary(slice: RopeSlice, char_idx: usize, n: usize) -> usize {
|
||||
// Bounds check
|
||||
@@ -208,43 +211,6 @@ pub fn nth_next_grapheme_boundary(slice: RopeSlice, char_idx: usize, n: usize) -
|
||||
chunk_char_idx + tmp
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn nth_next_grapheme_boundary_byte(slice: RopeSlice, mut byte_idx: usize, n: usize) -> usize {
|
||||
// Bounds check
|
||||
debug_assert!(byte_idx <= slice.len_bytes());
|
||||
|
||||
// Get the chunk with our byte index in it.
|
||||
let (mut chunk, mut chunk_byte_idx, mut _chunk_char_idx, _) = slice.chunk_at_byte(byte_idx);
|
||||
|
||||
// Set up the grapheme cursor.
|
||||
let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
|
||||
|
||||
// Find the nth next grapheme cluster boundary.
|
||||
for _ in 0..n {
|
||||
loop {
|
||||
match gc.next_boundary(chunk, chunk_byte_idx) {
|
||||
Ok(None) => return slice.len_bytes(),
|
||||
Ok(Some(n)) => {
|
||||
byte_idx = n;
|
||||
break;
|
||||
}
|
||||
Err(GraphemeIncomplete::NextChunk) => {
|
||||
chunk_byte_idx += chunk.len();
|
||||
let (a, _, _c, _) = slice.chunk_at_byte(chunk_byte_idx);
|
||||
chunk = a;
|
||||
// chunk_char_idx = c;
|
||||
}
|
||||
Err(GraphemeIncomplete::PreContext(n)) => {
|
||||
let ctx_chunk = slice.chunk_at_byte(n - 1).0;
|
||||
gc.provide_context(ctx_chunk, n - ctx_chunk.len());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
byte_idx
|
||||
}
|
||||
|
||||
/// Finds the next grapheme boundary after the given char position.
|
||||
#[must_use]
|
||||
#[inline(always)]
|
||||
@@ -252,13 +218,6 @@ pub fn next_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> usize {
|
||||
nth_next_grapheme_boundary(slice, char_idx, 1)
|
||||
}
|
||||
|
||||
/// Finds the next grapheme boundary after the given byte position.
|
||||
#[must_use]
|
||||
#[inline(always)]
|
||||
pub fn next_grapheme_boundary_byte(slice: RopeSlice, byte_idx: usize) -> usize {
|
||||
nth_next_grapheme_boundary_byte(slice, byte_idx, 1)
|
||||
}
|
||||
|
||||
/// Returns the passed char index if it's already a grapheme boundary,
|
||||
/// or the next grapheme boundary char index if not.
|
||||
#[must_use]
|
||||
@@ -283,215 +242,6 @@ pub fn ensure_grapheme_boundary_prev(slice: RopeSlice, char_idx: usize) -> usize
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the given char position is a grapheme boundary.
|
||||
#[must_use]
|
||||
pub fn is_grapheme_boundary(slice: RopeSlice, char_idx: usize) -> bool {
|
||||
// Bounds check
|
||||
debug_assert!(char_idx <= slice.len_chars());
|
||||
|
||||
// We work with bytes for this, so convert.
|
||||
let byte_idx = slice.char_to_byte(char_idx);
|
||||
|
||||
// Get the chunk with our byte index in it.
|
||||
let (chunk, chunk_byte_idx, _, _) = slice.chunk_at_byte(byte_idx);
|
||||
|
||||
// Set up the grapheme cursor.
|
||||
let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
|
||||
|
||||
// Determine if the given position is a grapheme cluster boundary.
|
||||
loop {
|
||||
match gc.is_boundary(chunk, chunk_byte_idx) {
|
||||
Ok(n) => return n,
|
||||
Err(GraphemeIncomplete::PreContext(n)) => {
|
||||
let (ctx_chunk, ctx_byte_start, _, _) = slice.chunk_at_byte(n - 1);
|
||||
gc.provide_context(ctx_chunk, ctx_byte_start);
|
||||
}
|
||||
Err(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the given byte position is a grapheme boundary.
|
||||
#[must_use]
|
||||
pub fn is_grapheme_boundary_byte(slice: RopeSlice, byte_idx: usize) -> bool {
|
||||
// Bounds check
|
||||
debug_assert!(byte_idx <= slice.len_bytes());
|
||||
|
||||
// Get the chunk with our byte index in it.
|
||||
let (chunk, chunk_byte_idx, _, _) = slice.chunk_at_byte(byte_idx);
|
||||
|
||||
// Set up the grapheme cursor.
|
||||
let mut gc = GraphemeCursor::new(byte_idx, slice.len_bytes(), true);
|
||||
|
||||
// Determine if the given position is a grapheme cluster boundary.
|
||||
loop {
|
||||
match gc.is_boundary(chunk, chunk_byte_idx) {
|
||||
Ok(n) => return n,
|
||||
Err(GraphemeIncomplete::PreContext(n)) => {
|
||||
let (ctx_chunk, ctx_byte_start, _, _) = slice.chunk_at_byte(n - 1);
|
||||
gc.provide_context(ctx_chunk, ctx_byte_start);
|
||||
}
|
||||
Err(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over the graphemes of a `RopeSlice`.
|
||||
#[derive(Clone)]
|
||||
pub struct RopeGraphemes<'a> {
|
||||
text: RopeSlice<'a>,
|
||||
chunks: Chunks<'a>,
|
||||
cur_chunk: &'a str,
|
||||
cur_chunk_start: usize,
|
||||
cursor: GraphemeCursor,
|
||||
}
|
||||
|
||||
impl fmt::Debug for RopeGraphemes<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("RopeGraphemes")
|
||||
.field("text", &self.text)
|
||||
.field("chunks", &self.chunks)
|
||||
.field("cur_chunk", &self.cur_chunk)
|
||||
.field("cur_chunk_start", &self.cur_chunk_start)
|
||||
// .field("cursor", &self.cursor)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl RopeGraphemes<'_> {
|
||||
#[must_use]
|
||||
pub fn new(slice: RopeSlice) -> RopeGraphemes {
|
||||
let mut chunks = slice.chunks();
|
||||
let first_chunk = chunks.next().unwrap_or("");
|
||||
RopeGraphemes {
|
||||
text: slice,
|
||||
chunks,
|
||||
cur_chunk: first_chunk,
|
||||
cur_chunk_start: 0,
|
||||
cursor: GraphemeCursor::new(0, slice.len_bytes(), true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for RopeGraphemes<'a> {
|
||||
type Item = RopeSlice<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<RopeSlice<'a>> {
|
||||
let a = self.cursor.cur_cursor();
|
||||
let b;
|
||||
loop {
|
||||
match self
|
||||
.cursor
|
||||
.next_boundary(self.cur_chunk, self.cur_chunk_start)
|
||||
{
|
||||
Ok(None) => {
|
||||
return None;
|
||||
}
|
||||
Ok(Some(n)) => {
|
||||
b = n;
|
||||
break;
|
||||
}
|
||||
Err(GraphemeIncomplete::NextChunk) => {
|
||||
self.cur_chunk_start += self.cur_chunk.len();
|
||||
self.cur_chunk = self.chunks.next().unwrap_or("");
|
||||
}
|
||||
Err(GraphemeIncomplete::PreContext(idx)) => {
|
||||
let (chunk, byte_idx, _, _) = self.text.chunk_at_byte(idx.saturating_sub(1));
|
||||
self.cursor.provide_context(chunk, byte_idx);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
if a < self.cur_chunk_start {
|
||||
Some(self.text.byte_slice(a..b))
|
||||
} else {
|
||||
let a2 = a - self.cur_chunk_start;
|
||||
let b2 = b - self.cur_chunk_start;
|
||||
Some((&self.cur_chunk[a2..b2]).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over the graphemes of a `RopeSlice` in reverse.
|
||||
#[derive(Clone)]
|
||||
pub struct RevRopeGraphemes<'a> {
|
||||
text: RopeSlice<'a>,
|
||||
chunks: Chunks<'a>,
|
||||
cur_chunk: &'a str,
|
||||
cur_chunk_start: usize,
|
||||
cursor: GraphemeCursor,
|
||||
}
|
||||
|
||||
impl fmt::Debug for RevRopeGraphemes<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("RevRopeGraphemes")
|
||||
.field("text", &self.text)
|
||||
.field("chunks", &self.chunks)
|
||||
.field("cur_chunk", &self.cur_chunk)
|
||||
.field("cur_chunk_start", &self.cur_chunk_start)
|
||||
// .field("cursor", &self.cursor)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl RevRopeGraphemes<'_> {
|
||||
#[must_use]
|
||||
pub fn new(slice: RopeSlice) -> RevRopeGraphemes {
|
||||
let (mut chunks, mut cur_chunk_start, _, _) = slice.chunks_at_byte(slice.len_bytes());
|
||||
chunks.reverse();
|
||||
let first_chunk = chunks.next().unwrap_or("");
|
||||
cur_chunk_start -= first_chunk.len();
|
||||
RevRopeGraphemes {
|
||||
text: slice,
|
||||
chunks,
|
||||
cur_chunk: first_chunk,
|
||||
cur_chunk_start,
|
||||
cursor: GraphemeCursor::new(slice.len_bytes(), slice.len_bytes(), true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for RevRopeGraphemes<'a> {
|
||||
type Item = RopeSlice<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<RopeSlice<'a>> {
|
||||
let a = self.cursor.cur_cursor();
|
||||
let b;
|
||||
loop {
|
||||
match self
|
||||
.cursor
|
||||
.prev_boundary(self.cur_chunk, self.cur_chunk_start)
|
||||
{
|
||||
Ok(None) => {
|
||||
return None;
|
||||
}
|
||||
Ok(Some(n)) => {
|
||||
b = n;
|
||||
break;
|
||||
}
|
||||
Err(GraphemeIncomplete::PrevChunk) => {
|
||||
self.cur_chunk = self.chunks.next().unwrap_or("");
|
||||
self.cur_chunk_start -= self.cur_chunk.len();
|
||||
}
|
||||
Err(GraphemeIncomplete::PreContext(idx)) => {
|
||||
let (chunk, byte_idx, _, _) = self.text.chunk_at_byte(idx.saturating_sub(1));
|
||||
self.cursor.provide_context(chunk, byte_idx);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
if a >= self.cur_chunk_start + self.cur_chunk.len() {
|
||||
Some(self.text.byte_slice(b..a))
|
||||
} else {
|
||||
let a2 = a - self.cur_chunk_start;
|
||||
let b2 = b - self.cur_chunk_start;
|
||||
Some((&self.cur_chunk[b2..a2]).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A highly compressed Cow<'a, str> that holds
|
||||
/// atmost u31::MAX bytes and is readonly
|
||||
pub struct GraphemeStr<'a> {
|
||||
|
@@ -1,14 +1,18 @@
|
||||
use std::{borrow::Cow, collections::HashMap, iter};
|
||||
|
||||
use helix_stdx::rope::RopeSliceExt;
|
||||
use tree_sitter::{Query, QueryCursor, QueryPredicateArg};
|
||||
use tree_house::TREE_SITTER_MATCH_LIMIT;
|
||||
|
||||
use crate::{
|
||||
chars::{char_is_line_ending, char_is_whitespace},
|
||||
graphemes::{grapheme_width, tab_width_at},
|
||||
syntax::{IndentationHeuristic, LanguageConfiguration, RopeProvider, Syntax},
|
||||
tree_sitter::Node,
|
||||
Position, Rope, RopeGraphemes, RopeSlice, Tendril,
|
||||
syntax::{self, config::IndentationHeuristic},
|
||||
tree_sitter::{
|
||||
self,
|
||||
query::{InvalidPredicateError, UserPredicate},
|
||||
Capture, Grammar, InactiveQueryCursor, Node, Pattern, Query, QueryMatch, RopeInput,
|
||||
},
|
||||
Position, Rope, RopeSlice, Syntax, Tendril,
|
||||
};
|
||||
|
||||
/// Enum representing indentation style.
|
||||
@@ -149,6 +153,12 @@ pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
|
||||
// Give more weight to tabs, because their presence is a very
|
||||
// strong indicator.
|
||||
histogram[0] *= 2;
|
||||
// Gives less weight to single indent, as single spaces are
|
||||
// often used in certain languages' comment systems and rarely
|
||||
// used as the actual document indentation.
|
||||
if histogram[1] > 1 {
|
||||
histogram[1] /= 2;
|
||||
}
|
||||
|
||||
histogram
|
||||
};
|
||||
@@ -200,7 +210,7 @@ pub fn indent_level_for_line(line: RopeSlice, tab_width: usize, indent_width: us
|
||||
/// Create a string of tabs & spaces that has the same visual width as the given RopeSlice (independent of the tab width).
|
||||
fn whitespace_with_same_width(text: RopeSlice) -> String {
|
||||
let mut s = String::new();
|
||||
for grapheme in RopeGraphemes::new(text) {
|
||||
for grapheme in text.graphemes() {
|
||||
if grapheme == "\t" {
|
||||
s.push('\t');
|
||||
} else {
|
||||
@@ -279,18 +289,164 @@ fn add_indent_level(
|
||||
|
||||
/// Return true if only whitespace comes before the node on its line.
|
||||
/// If given, new_line_byte_pos is treated the same way as any existing newline.
|
||||
fn is_first_in_line(node: Node, text: RopeSlice, new_line_byte_pos: Option<usize>) -> bool {
|
||||
let mut line_start_byte_pos = text.line_to_byte(node.start_position().row);
|
||||
fn is_first_in_line(node: &Node, text: RopeSlice, new_line_byte_pos: Option<u32>) -> bool {
|
||||
let line = text.byte_to_line(node.start_byte() as usize);
|
||||
let mut line_start_byte_pos = text.line_to_byte(line) as u32;
|
||||
if let Some(pos) = new_line_byte_pos {
|
||||
if line_start_byte_pos < pos && pos <= node.start_byte() {
|
||||
line_start_byte_pos = pos;
|
||||
}
|
||||
}
|
||||
text.byte_slice(line_start_byte_pos..node.start_byte())
|
||||
text.byte_slice(line_start_byte_pos as usize..node.start_byte() as usize)
|
||||
.chars()
|
||||
.all(|c| c.is_whitespace())
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct IndentQueryPredicates {
|
||||
not_kind_eq: Vec<(Capture, Box<str>)>,
|
||||
same_line: Option<(Capture, Capture, bool)>,
|
||||
one_line: Option<(Capture, bool)>,
|
||||
}
|
||||
|
||||
impl IndentQueryPredicates {
|
||||
fn are_satisfied(
|
||||
&self,
|
||||
match_: &QueryMatch,
|
||||
text: RopeSlice,
|
||||
new_line_byte_pos: Option<u32>,
|
||||
) -> bool {
|
||||
for (capture, not_expected_kind) in self.not_kind_eq.iter() {
|
||||
let node = match_.nodes_for_capture(*capture).next();
|
||||
if node.is_some_and(|n| n.kind() == not_expected_kind.as_ref()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((capture1, capture2, negated)) = self.same_line {
|
||||
let n1 = match_.nodes_for_capture(capture1).next();
|
||||
let n2 = match_.nodes_for_capture(capture2).next();
|
||||
let satisfied = n1.zip(n2).is_some_and(|(n1, n2)| {
|
||||
let n1_line = get_node_start_line(text, n1, new_line_byte_pos);
|
||||
let n2_line = get_node_start_line(text, n2, new_line_byte_pos);
|
||||
let same_line = n1_line == n2_line;
|
||||
same_line != negated
|
||||
});
|
||||
|
||||
if !satisfied {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((capture, negated)) = self.one_line {
|
||||
let node = match_.nodes_for_capture(capture).next();
|
||||
let satisfied = node.is_some_and(|node| {
|
||||
let start_line = get_node_start_line(text, node, new_line_byte_pos);
|
||||
let end_line = get_node_end_line(text, node, new_line_byte_pos);
|
||||
let one_line = end_line == start_line;
|
||||
one_line != negated
|
||||
});
|
||||
|
||||
if !satisfied {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IndentQuery {
|
||||
query: Query,
|
||||
properties: HashMap<Pattern, IndentScope>,
|
||||
predicates: HashMap<Pattern, IndentQueryPredicates>,
|
||||
indent_capture: Option<Capture>,
|
||||
indent_always_capture: Option<Capture>,
|
||||
outdent_capture: Option<Capture>,
|
||||
outdent_always_capture: Option<Capture>,
|
||||
align_capture: Option<Capture>,
|
||||
anchor_capture: Option<Capture>,
|
||||
extend_capture: Option<Capture>,
|
||||
extend_prevent_once_capture: Option<Capture>,
|
||||
}
|
||||
|
||||
impl IndentQuery {
|
||||
pub fn new(grammar: Grammar, source: &str) -> Result<Self, tree_sitter::query::ParseError> {
|
||||
let mut properties = HashMap::new();
|
||||
let mut predicates: HashMap<Pattern, IndentQueryPredicates> = HashMap::new();
|
||||
let query = Query::new(grammar, source, |pattern, predicate| match predicate {
|
||||
UserPredicate::SetProperty { key: "scope", val } => {
|
||||
let scope = match val {
|
||||
Some("all") => IndentScope::All,
|
||||
Some("tail") => IndentScope::Tail,
|
||||
Some(other) => {
|
||||
return Err(format!("unknown scope (#set! scope \"{other}\")").into())
|
||||
}
|
||||
None => return Err("missing scope value (#set! scope ...)".into()),
|
||||
};
|
||||
|
||||
properties.insert(pattern, scope);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
UserPredicate::Other(predicate) => {
|
||||
let name = predicate.name();
|
||||
match name {
|
||||
"not-kind-eq?" => {
|
||||
predicate.check_arg_count(2)?;
|
||||
let capture = predicate.capture_arg(0)?;
|
||||
let not_expected_kind = predicate.str_arg(1)?;
|
||||
|
||||
predicates
|
||||
.entry(pattern)
|
||||
.or_default()
|
||||
.not_kind_eq
|
||||
.push((capture, not_expected_kind.into()));
|
||||
Ok(())
|
||||
}
|
||||
"same-line?" | "not-same-line?" => {
|
||||
predicate.check_arg_count(2)?;
|
||||
let capture1 = predicate.capture_arg(0)?;
|
||||
let capture2 = predicate.capture_arg(1)?;
|
||||
let negated = name == "not-same-line?";
|
||||
|
||||
predicates.entry(pattern).or_default().same_line =
|
||||
Some((capture1, capture2, negated));
|
||||
Ok(())
|
||||
}
|
||||
"one-line?" | "not-one-line?" => {
|
||||
predicate.check_arg_count(1)?;
|
||||
let capture = predicate.capture_arg(0)?;
|
||||
let negated = name == "not-one-line?";
|
||||
|
||||
predicates.entry(pattern).or_default().one_line = Some((capture, negated));
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(InvalidPredicateError::unknown(UserPredicate::Other(
|
||||
predicate,
|
||||
))),
|
||||
}
|
||||
}
|
||||
_ => Err(InvalidPredicateError::unknown(predicate)),
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
properties,
|
||||
predicates,
|
||||
indent_capture: query.get_capture("indent"),
|
||||
indent_always_capture: query.get_capture("indent.always"),
|
||||
outdent_capture: query.get_capture("outdent"),
|
||||
outdent_always_capture: query.get_capture("outdent.always"),
|
||||
align_capture: query.get_capture("align"),
|
||||
anchor_capture: query.get_capture("anchor"),
|
||||
extend_capture: query.get_capture("extend"),
|
||||
extend_prevent_once_capture: query.get_capture("extend.prevent-once"),
|
||||
query,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The total indent for some line of code.
|
||||
/// This is usually constructed in one of 2 ways:
|
||||
/// - Successively add indent captures to get the (added) indent from a single line
|
||||
@@ -453,193 +609,114 @@ struct IndentQueryResult<'a> {
|
||||
extend_captures: HashMap<usize, Vec<ExtendCapture>>,
|
||||
}
|
||||
|
||||
fn get_node_start_line(node: Node, new_line_byte_pos: Option<usize>) -> usize {
|
||||
let mut node_line = node.start_position().row;
|
||||
fn get_node_start_line(text: RopeSlice, node: &Node, new_line_byte_pos: Option<u32>) -> usize {
|
||||
let mut node_line = text.byte_to_line(node.start_byte() as usize);
|
||||
// Adjust for the new line that will be inserted
|
||||
if new_line_byte_pos.map_or(false, |pos| node.start_byte() >= pos) {
|
||||
if new_line_byte_pos.is_some_and(|pos| node.start_byte() >= pos) {
|
||||
node_line += 1;
|
||||
}
|
||||
node_line
|
||||
}
|
||||
fn get_node_end_line(node: Node, new_line_byte_pos: Option<usize>) -> usize {
|
||||
let mut node_line = node.end_position().row;
|
||||
fn get_node_end_line(text: RopeSlice, node: &Node, new_line_byte_pos: Option<u32>) -> usize {
|
||||
let mut node_line = text.byte_to_line(node.end_byte() as usize);
|
||||
// Adjust for the new line that will be inserted (with a strict inequality since end_byte is exclusive)
|
||||
if new_line_byte_pos.map_or(false, |pos| node.end_byte() > pos) {
|
||||
if new_line_byte_pos.is_some_and(|pos| node.end_byte() > pos) {
|
||||
node_line += 1;
|
||||
}
|
||||
node_line
|
||||
}
|
||||
|
||||
fn query_indents<'a>(
|
||||
query: &Query,
|
||||
query: &IndentQuery,
|
||||
syntax: &Syntax,
|
||||
cursor: &mut QueryCursor,
|
||||
text: RopeSlice<'a>,
|
||||
range: std::ops::Range<usize>,
|
||||
new_line_byte_pos: Option<usize>,
|
||||
range: std::ops::Range<u32>,
|
||||
new_line_byte_pos: Option<u32>,
|
||||
) -> IndentQueryResult<'a> {
|
||||
let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
|
||||
let mut extend_captures: HashMap<usize, Vec<ExtendCapture>> = HashMap::new();
|
||||
cursor.set_byte_range(range);
|
||||
|
||||
let mut cursor = InactiveQueryCursor::new(range, TREE_SITTER_MATCH_LIMIT).execute_query(
|
||||
&query.query,
|
||||
&syntax.tree().root_node(),
|
||||
RopeInput::new(text),
|
||||
);
|
||||
|
||||
// Iterate over all captures from the query
|
||||
for m in cursor.matches(query, syntax.tree().root_node(), RopeProvider(text)) {
|
||||
while let Some(m) = cursor.next_match() {
|
||||
// Skip matches where not all custom predicates are fulfilled
|
||||
if !query.general_predicates(m.pattern_index).iter().all(|pred| {
|
||||
match pred.operator.as_ref() {
|
||||
"not-kind-eq?" => match (pred.args.first(), pred.args.get(1)) {
|
||||
(
|
||||
Some(QueryPredicateArg::Capture(capture_idx)),
|
||||
Some(QueryPredicateArg::String(kind)),
|
||||
) => {
|
||||
let node = m.nodes_for_capture_index(*capture_idx).next();
|
||||
match node {
|
||||
Some(node) => node.kind()!=kind.as_ref(),
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
panic!("Invalid indent query: Arguments to \"not-kind-eq?\" must be a capture and a string");
|
||||
}
|
||||
},
|
||||
"same-line?" | "not-same-line?" => {
|
||||
match (pred.args.first(), pred.args.get(1)) {
|
||||
(
|
||||
Some(QueryPredicateArg::Capture(capt1)),
|
||||
Some(QueryPredicateArg::Capture(capt2))
|
||||
) => {
|
||||
let n1 = m.nodes_for_capture_index(*capt1).next();
|
||||
let n2 = m.nodes_for_capture_index(*capt2).next();
|
||||
match (n1, n2) {
|
||||
(Some(n1), Some(n2)) => {
|
||||
let n1_line = get_node_start_line(n1, new_line_byte_pos);
|
||||
let n2_line = get_node_start_line(n2, new_line_byte_pos);
|
||||
let same_line = n1_line == n2_line;
|
||||
same_line==(pred.operator.as_ref()=="same-line?")
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
panic!("Invalid indent query: Arguments to \"{}\" must be 2 captures", pred.operator);
|
||||
}
|
||||
}
|
||||
}
|
||||
"one-line?" | "not-one-line?" => match pred.args.first() {
|
||||
Some(QueryPredicateArg::Capture(capture_idx)) => {
|
||||
let node = m.nodes_for_capture_index(*capture_idx).next();
|
||||
|
||||
match node {
|
||||
Some(node) => {
|
||||
let (start_line, end_line) = (get_node_start_line(node,new_line_byte_pos), get_node_end_line(node, new_line_byte_pos));
|
||||
let one_line = end_line == start_line;
|
||||
one_line != (pred.operator.as_ref() == "not-one-line?")
|
||||
},
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
panic!("Invalid indent query: Arguments to \"not-kind-eq?\" must be a capture and a string");
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
panic!(
|
||||
"Invalid indent query: Unknown predicate (\"{}\")",
|
||||
pred.operator
|
||||
);
|
||||
}
|
||||
}
|
||||
}) {
|
||||
if query
|
||||
.predicates
|
||||
.get(&m.pattern())
|
||||
.is_some_and(|preds| !preds.are_satisfied(&m, text, new_line_byte_pos))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// A list of pairs (node_id, indent_capture) that are added by this match.
|
||||
// They cannot be added to indent_captures immediately since they may depend on other captures (such as an @anchor).
|
||||
let mut added_indent_captures: Vec<(usize, IndentCapture)> = Vec::new();
|
||||
// The row/column position of the optional anchor in this query
|
||||
let mut anchor: Option<tree_sitter::Node> = None;
|
||||
for capture in m.captures {
|
||||
let capture_name = query.capture_names()[capture.index as usize];
|
||||
let capture_type = match capture_name {
|
||||
"indent" => IndentCaptureType::Indent,
|
||||
"indent.always" => IndentCaptureType::IndentAlways,
|
||||
"outdent" => IndentCaptureType::Outdent,
|
||||
"outdent.always" => IndentCaptureType::OutdentAlways,
|
||||
// The alignment will be updated to the correct value at the end, when the anchor is known.
|
||||
"align" => IndentCaptureType::Align(RopeSlice::from("")),
|
||||
"anchor" => {
|
||||
if anchor.is_some() {
|
||||
log::error!("Invalid indent query: Encountered more than one @anchor in the same match.")
|
||||
} else {
|
||||
anchor = Some(capture.node);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
"extend" => {
|
||||
extend_captures
|
||||
.entry(capture.node.id())
|
||||
.or_insert_with(|| Vec::with_capacity(1))
|
||||
.push(ExtendCapture::Extend);
|
||||
continue;
|
||||
}
|
||||
"extend.prevent-once" => {
|
||||
extend_captures
|
||||
.entry(capture.node.id())
|
||||
.or_insert_with(|| Vec::with_capacity(1))
|
||||
.push(ExtendCapture::PreventOnce);
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
// Ignore any unknown captures (these may be needed for predicates such as #match?)
|
||||
continue;
|
||||
let mut anchor: Option<&Node> = None;
|
||||
for matched_node in m.matched_nodes() {
|
||||
let node_id = matched_node.node.id();
|
||||
let capture = Some(matched_node.capture);
|
||||
let capture_type = if capture == query.indent_capture {
|
||||
IndentCaptureType::Indent
|
||||
} else if capture == query.indent_always_capture {
|
||||
IndentCaptureType::IndentAlways
|
||||
} else if capture == query.outdent_capture {
|
||||
IndentCaptureType::Outdent
|
||||
} else if capture == query.outdent_always_capture {
|
||||
IndentCaptureType::OutdentAlways
|
||||
} else if capture == query.align_capture {
|
||||
IndentCaptureType::Align(RopeSlice::from(""))
|
||||
} else if capture == query.anchor_capture {
|
||||
if anchor.is_some() {
|
||||
log::error!("Invalid indent query: Encountered more than one @anchor in the same match.")
|
||||
} else {
|
||||
anchor = Some(&matched_node.node);
|
||||
}
|
||||
continue;
|
||||
} else if capture == query.extend_capture {
|
||||
extend_captures
|
||||
.entry(node_id)
|
||||
.or_insert_with(|| Vec::with_capacity(1))
|
||||
.push(ExtendCapture::Extend);
|
||||
continue;
|
||||
} else if capture == query.extend_prevent_once_capture {
|
||||
extend_captures
|
||||
.entry(node_id)
|
||||
.or_insert_with(|| Vec::with_capacity(1))
|
||||
.push(ExtendCapture::PreventOnce);
|
||||
continue;
|
||||
} else {
|
||||
// Ignore any unknown captures (these may be needed for predicates such as #match?)
|
||||
continue;
|
||||
};
|
||||
let scope = capture_type.default_scope();
|
||||
let mut indent_capture = IndentCapture {
|
||||
|
||||
// Apply additional settings for this capture
|
||||
let scope = query
|
||||
.properties
|
||||
.get(&m.pattern())
|
||||
.copied()
|
||||
.unwrap_or_else(|| capture_type.default_scope());
|
||||
let indent_capture = IndentCapture {
|
||||
capture_type,
|
||||
scope,
|
||||
};
|
||||
// Apply additional settings for this capture
|
||||
for property in query.property_settings(m.pattern_index) {
|
||||
match property.key.as_ref() {
|
||||
"scope" => {
|
||||
indent_capture.scope = match property.value.as_deref() {
|
||||
Some("all") => IndentScope::All,
|
||||
Some("tail") => IndentScope::Tail,
|
||||
Some(s) => {
|
||||
panic!("Invalid indent query: Unknown value for \"scope\" property (\"{}\")", s);
|
||||
}
|
||||
None => {
|
||||
panic!(
|
||||
"Invalid indent query: Missing value for \"scope\" property"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
panic!(
|
||||
"Invalid indent query: Unknown property \"{}\"",
|
||||
property.key
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
added_indent_captures.push((capture.node.id(), indent_capture))
|
||||
added_indent_captures.push((node_id, indent_capture))
|
||||
}
|
||||
for (node_id, mut capture) in added_indent_captures {
|
||||
// Set the anchor for all align queries.
|
||||
if let IndentCaptureType::Align(_) = capture.capture_type {
|
||||
let anchor = match anchor {
|
||||
None => {
|
||||
log::error!(
|
||||
"Invalid indent query: @align requires an accompanying @anchor."
|
||||
);
|
||||
continue;
|
||||
}
|
||||
Some(anchor) => anchor,
|
||||
let Some(anchor) = anchor else {
|
||||
log::error!("Invalid indent query: @align requires an accompanying @anchor.");
|
||||
continue;
|
||||
};
|
||||
let line = text.byte_to_line(anchor.start_byte() as usize);
|
||||
let line_start = text.line_to_byte(line);
|
||||
capture.capture_type = IndentCaptureType::Align(
|
||||
text.line(anchor.start_position().row)
|
||||
.byte_slice(0..anchor.start_position().column),
|
||||
text.byte_slice(line_start..anchor.start_byte() as usize),
|
||||
);
|
||||
}
|
||||
indent_captures
|
||||
@@ -691,13 +768,15 @@ fn extend_nodes<'a>(
|
||||
// - the cursor is on the same line as the end of the node OR
|
||||
// - the line that the cursor is on is more indented than the
|
||||
// first line of the node
|
||||
if deepest_preceding.end_position().row == line {
|
||||
if text.byte_to_line(deepest_preceding.end_byte() as usize) == line {
|
||||
extend_node = true;
|
||||
} else {
|
||||
let cursor_indent =
|
||||
indent_level_for_line(text.line(line), tab_width, indent_width);
|
||||
let node_indent = indent_level_for_line(
|
||||
text.line(deepest_preceding.start_position().row),
|
||||
text.line(
|
||||
text.byte_to_line(deepest_preceding.start_byte() as usize),
|
||||
),
|
||||
tab_width,
|
||||
indent_width,
|
||||
);
|
||||
@@ -714,7 +793,7 @@ fn extend_nodes<'a>(
|
||||
if node_captured && stop_extend {
|
||||
stop_extend = false;
|
||||
} else if extend_node && !stop_extend {
|
||||
*node = deepest_preceding;
|
||||
*node = deepest_preceding.clone();
|
||||
break;
|
||||
}
|
||||
// If the tree contains a syntax error, `deepest_preceding` may not
|
||||
@@ -731,17 +810,17 @@ fn extend_nodes<'a>(
|
||||
/// - The indent captures for all relevant nodes.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn init_indent_query<'a, 'b>(
|
||||
query: &Query,
|
||||
query: &IndentQuery,
|
||||
syntax: &'a Syntax,
|
||||
text: RopeSlice<'b>,
|
||||
tab_width: usize,
|
||||
indent_width: usize,
|
||||
line: usize,
|
||||
byte_pos: usize,
|
||||
new_line_byte_pos: Option<usize>,
|
||||
byte_pos: u32,
|
||||
new_line_byte_pos: Option<u32>,
|
||||
) -> Option<(Node<'a>, HashMap<usize, Vec<IndentCapture<'b>>>)> {
|
||||
// The innermost tree-sitter node which is considered for the indent
|
||||
// computation. It may change if some predeceding node is extended
|
||||
// computation. It may change if some preceding node is extended
|
||||
let mut node = syntax
|
||||
.tree()
|
||||
.root_node()
|
||||
@@ -751,37 +830,25 @@ fn init_indent_query<'a, 'b>(
|
||||
// The query range should intersect with all nodes directly preceding
|
||||
// the position of the indent query in case one of them is extended.
|
||||
let mut deepest_preceding = None; // The deepest node preceding the indent query position
|
||||
let mut tree_cursor = node.walk();
|
||||
for child in node.children(&mut tree_cursor) {
|
||||
for child in node.children() {
|
||||
if child.byte_range().end <= byte_pos {
|
||||
deepest_preceding = Some(child);
|
||||
deepest_preceding = Some(child.clone());
|
||||
}
|
||||
}
|
||||
deepest_preceding = deepest_preceding.map(|mut prec| {
|
||||
// Get the deepest directly preceding node
|
||||
while prec.child_count() > 0 {
|
||||
prec = prec.child(prec.child_count() - 1).unwrap();
|
||||
prec = prec.child(prec.child_count() - 1).unwrap().clone();
|
||||
}
|
||||
prec
|
||||
});
|
||||
let query_range = deepest_preceding
|
||||
.as_ref()
|
||||
.map(|prec| prec.byte_range().end - 1..byte_pos + 1)
|
||||
.unwrap_or(byte_pos..byte_pos + 1);
|
||||
|
||||
crate::syntax::PARSER.with(|ts_parser| {
|
||||
let mut ts_parser = ts_parser.borrow_mut();
|
||||
let mut cursor = ts_parser.cursors.pop().unwrap_or_default();
|
||||
let query_result = query_indents(
|
||||
query,
|
||||
syntax,
|
||||
&mut cursor,
|
||||
text,
|
||||
query_range,
|
||||
new_line_byte_pos,
|
||||
);
|
||||
ts_parser.cursors.push(cursor);
|
||||
(query_result, deepest_preceding)
|
||||
})
|
||||
let query_result = query_indents(query, syntax, text, query_range, new_line_byte_pos);
|
||||
(query_result, deepest_preceding)
|
||||
};
|
||||
let extend_captures = query_result.extend_captures;
|
||||
|
||||
@@ -839,7 +906,7 @@ fn init_indent_query<'a, 'b>(
|
||||
/// ```
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn treesitter_indent_for_pos<'a>(
|
||||
query: &Query,
|
||||
query: &IndentQuery,
|
||||
syntax: &Syntax,
|
||||
tab_width: usize,
|
||||
indent_width: usize,
|
||||
@@ -848,7 +915,7 @@ pub fn treesitter_indent_for_pos<'a>(
|
||||
pos: usize,
|
||||
new_line: bool,
|
||||
) -> Option<Indentation<'a>> {
|
||||
let byte_pos = text.char_to_byte(pos);
|
||||
let byte_pos = text.char_to_byte(pos) as u32;
|
||||
let new_line_byte_pos = new_line.then_some(byte_pos);
|
||||
let (mut node, mut indent_captures) = init_indent_query(
|
||||
query,
|
||||
@@ -868,7 +935,7 @@ pub fn treesitter_indent_for_pos<'a>(
|
||||
let mut indent_for_line_below = Indentation::default();
|
||||
|
||||
loop {
|
||||
let is_first = is_first_in_line(node, text, new_line_byte_pos);
|
||||
let is_first = is_first_in_line(&node, text, new_line_byte_pos);
|
||||
|
||||
// Apply all indent definitions for this node.
|
||||
// Since we only iterate over each node once, we can remove the
|
||||
@@ -891,8 +958,8 @@ pub fn treesitter_indent_for_pos<'a>(
|
||||
}
|
||||
|
||||
if let Some(parent) = node.parent() {
|
||||
let node_line = get_node_start_line(node, new_line_byte_pos);
|
||||
let parent_line = get_node_start_line(parent, new_line_byte_pos);
|
||||
let node_line = get_node_start_line(text, &node, new_line_byte_pos);
|
||||
let parent_line = get_node_start_line(text, &parent, new_line_byte_pos);
|
||||
|
||||
if node_line != parent_line {
|
||||
// Don't add indent for the line below the line of the query
|
||||
@@ -914,8 +981,9 @@ pub fn treesitter_indent_for_pos<'a>(
|
||||
} else {
|
||||
// Only add the indentation for the line below if that line
|
||||
// is not after the line that the indentation is calculated for.
|
||||
if (node.start_position().row < line)
|
||||
|| (new_line && node.start_position().row == line && node.start_byte() < byte_pos)
|
||||
let node_start_line = text.byte_to_line(node.start_byte() as usize);
|
||||
if node_start_line < line
|
||||
|| (new_line && node_start_line == line && node.start_byte() < byte_pos)
|
||||
{
|
||||
result.add_line(indent_for_line_below);
|
||||
}
|
||||
@@ -930,7 +998,7 @@ pub fn treesitter_indent_for_pos<'a>(
|
||||
/// This is done either using treesitter, or if that's not available by copying the indentation from the current line
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn indent_for_newline(
|
||||
language_config: Option<&LanguageConfiguration>,
|
||||
loader: &syntax::Loader,
|
||||
syntax: Option<&Syntax>,
|
||||
indent_heuristic: &IndentationHeuristic,
|
||||
indent_style: &IndentStyle,
|
||||
@@ -947,7 +1015,7 @@ pub fn indent_for_newline(
|
||||
Some(syntax),
|
||||
) = (
|
||||
indent_heuristic,
|
||||
language_config.and_then(|config| config.indent_query()),
|
||||
syntax.and_then(|syntax| loader.indent_query(syntax.root_language())),
|
||||
syntax,
|
||||
) {
|
||||
if let Some(indent) = treesitter_indent_for_pos(
|
||||
@@ -1015,10 +1083,10 @@ pub fn indent_for_newline(
|
||||
indent_style.as_str().repeat(indent_level)
|
||||
}
|
||||
|
||||
pub fn get_scopes(syntax: Option<&Syntax>, text: RopeSlice, pos: usize) -> Vec<&'static str> {
|
||||
pub fn get_scopes<'a>(syntax: Option<&'a Syntax>, text: RopeSlice, pos: usize) -> Vec<&'a str> {
|
||||
let mut scopes = Vec::new();
|
||||
if let Some(syntax) = syntax {
|
||||
let pos = text.char_to_byte(pos);
|
||||
let pos = text.char_to_byte(pos) as u32;
|
||||
let mut node = match syntax
|
||||
.tree()
|
||||
.root_node()
|
||||
|
@@ -3,12 +3,14 @@ pub use encoding_rs as encoding;
|
||||
pub mod auto_pairs;
|
||||
pub mod case_conversion;
|
||||
pub mod chars;
|
||||
pub mod command_line;
|
||||
pub mod comment;
|
||||
pub mod completion;
|
||||
pub mod config;
|
||||
pub mod diagnostic;
|
||||
pub mod diff;
|
||||
pub mod doc_formatter;
|
||||
pub mod editor_config;
|
||||
pub mod fuzzy;
|
||||
pub mod graphemes;
|
||||
pub mod history;
|
||||
@@ -22,7 +24,6 @@ pub mod object;
|
||||
mod position;
|
||||
pub mod search;
|
||||
pub mod selection;
|
||||
pub mod shellwords;
|
||||
pub mod snippets;
|
||||
pub mod surround;
|
||||
pub mod syntax;
|
||||
@@ -52,9 +53,8 @@ pub use smartstring::SmartString;
|
||||
pub type Tendril = SmartString<smartstring::LazyCompact>;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use {regex, tree_sitter};
|
||||
pub use {regex, tree_house::tree_sitter};
|
||||
|
||||
pub use graphemes::RopeGraphemes;
|
||||
pub use position::{
|
||||
char_idx_at_visual_offset, coords_at_pos, pos_at_coords, softwrapped_dimensions,
|
||||
visual_offset_from_anchor, visual_offset_from_block, Position, VisualOffsetError,
|
||||
@@ -73,3 +73,5 @@ pub use line_ending::{LineEnding, NATIVE_LINE_ENDING};
|
||||
pub use transaction::{Assoc, Change, ChangeSet, Deletion, Operation, Transaction};
|
||||
|
||||
pub use uri::Uri;
|
||||
|
||||
pub use tree_house::Language;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
use std::iter;
|
||||
|
||||
use crate::tree_sitter::Node;
|
||||
use ropey::RopeSlice;
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::movement::Direction::{self, Backward, Forward};
|
||||
use crate::Syntax;
|
||||
@@ -75,7 +75,7 @@ fn find_pair(
|
||||
pos_: usize,
|
||||
traverse_parents: bool,
|
||||
) -> Option<usize> {
|
||||
let pos = doc.char_to_byte(pos_);
|
||||
let pos = doc.char_to_byte(pos_) as u32;
|
||||
|
||||
let root = syntax.tree_for_byte_range(pos, pos).root_node();
|
||||
let mut node = root.descendant_for_byte_range(pos, pos)?;
|
||||
@@ -128,7 +128,7 @@ fn find_pair(
|
||||
if find_pair_end(doc, sibling.prev_sibling(), start_char, end_char, Backward)
|
||||
.is_some()
|
||||
{
|
||||
return doc.try_byte_to_char(sibling.start_byte()).ok();
|
||||
return doc.try_byte_to_char(sibling.start_byte() as usize).ok();
|
||||
}
|
||||
}
|
||||
} else if node.is_named() {
|
||||
@@ -144,9 +144,9 @@ fn find_pair(
|
||||
if node.child_count() != 0 {
|
||||
return None;
|
||||
}
|
||||
let node_start = doc.byte_to_char(node.start_byte());
|
||||
find_matching_bracket_plaintext(doc.byte_slice(node.byte_range()), pos_ - node_start)
|
||||
.map(|pos| pos + node_start)
|
||||
let node_start = doc.byte_to_char(node.start_byte() as usize);
|
||||
let node_text = doc.byte_slice(node.start_byte() as usize..node.end_byte() as usize);
|
||||
find_matching_bracket_plaintext(node_text, pos_ - node_start).map(|pos| pos + node_start)
|
||||
}
|
||||
|
||||
/// Returns the position of the matching bracket under cursor.
|
||||
@@ -304,7 +304,7 @@ fn as_char(doc: RopeSlice, node: &Node) -> Option<(usize, char)> {
|
||||
if node.byte_range().len() != 1 {
|
||||
return None;
|
||||
}
|
||||
let pos = doc.try_byte_to_char(node.start_byte()).ok()?;
|
||||
let pos = doc.try_byte_to_char(node.start_byte() as usize).ok()?;
|
||||
Some((pos, doc.char(pos)))
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,6 @@
|
||||
use std::{cmp::Reverse, iter};
|
||||
use std::{borrow::Cow, cmp::Reverse, iter};
|
||||
|
||||
use ropey::iter::Chars;
|
||||
use tree_sitter::{Node, QueryCursor};
|
||||
|
||||
use crate::{
|
||||
char_idx_at_visual_offset,
|
||||
@@ -13,9 +12,10 @@ use crate::{
|
||||
},
|
||||
line_ending::rope_is_line_ending,
|
||||
position::char_idx_at_visual_block_offset,
|
||||
syntax::LanguageConfiguration,
|
||||
syntax,
|
||||
text_annotations::TextAnnotations,
|
||||
textobject::TextObject,
|
||||
tree_sitter::Node,
|
||||
visual_offset_from_block, Range, RopeSlice, Selection, Syntax,
|
||||
};
|
||||
|
||||
@@ -560,21 +560,23 @@ fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> boo
|
||||
|
||||
/// Finds the range of the next or previous textobject in the syntax sub-tree of `node`.
|
||||
/// Returns the range in the forwards direction.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn goto_treesitter_object(
|
||||
slice: RopeSlice,
|
||||
range: Range,
|
||||
object_name: &str,
|
||||
dir: Direction,
|
||||
slice_tree: Node,
|
||||
lang_config: &LanguageConfiguration,
|
||||
slice_tree: &Node,
|
||||
syntax: &Syntax,
|
||||
loader: &syntax::Loader,
|
||||
count: usize,
|
||||
) -> Range {
|
||||
let textobject_query = loader.textobject_query(syntax.root_language());
|
||||
let get_range = move |range: Range| -> Option<Range> {
|
||||
let byte_pos = slice.char_to_byte(range.cursor(slice));
|
||||
|
||||
let cap_name = |t: TextObject| format!("{}.{}", object_name, t);
|
||||
let mut cursor = QueryCursor::new();
|
||||
let nodes = lang_config.textobject_query()?.capture_nodes_any(
|
||||
let nodes = textobject_query?.capture_nodes_any(
|
||||
&[
|
||||
&cap_name(TextObject::Movement),
|
||||
&cap_name(TextObject::Around),
|
||||
@@ -582,7 +584,6 @@ pub fn goto_treesitter_object(
|
||||
],
|
||||
slice_tree,
|
||||
slice,
|
||||
&mut cursor,
|
||||
)?;
|
||||
|
||||
let node = match dir {
|
||||
@@ -617,14 +618,15 @@ pub fn goto_treesitter_object(
|
||||
last_range
|
||||
}
|
||||
|
||||
fn find_parent_start(mut node: Node) -> Option<Node> {
|
||||
fn find_parent_start<'tree>(node: &Node<'tree>) -> Option<Node<'tree>> {
|
||||
let start = node.start_byte();
|
||||
let mut node = Cow::Borrowed(node);
|
||||
|
||||
while node.start_byte() >= start || !node.is_named() {
|
||||
node = node.parent()?;
|
||||
node = Cow::Owned(node.parent()?);
|
||||
}
|
||||
|
||||
Some(node)
|
||||
Some(node.into_owned())
|
||||
}
|
||||
|
||||
pub fn move_parent_node_end(
|
||||
@@ -635,8 +637,8 @@ pub fn move_parent_node_end(
|
||||
movement: Movement,
|
||||
) -> Selection {
|
||||
selection.transform(|range| {
|
||||
let start_from = text.char_to_byte(range.from());
|
||||
let start_to = text.char_to_byte(range.to());
|
||||
let start_from = text.char_to_byte(range.from()) as u32;
|
||||
let start_to = text.char_to_byte(range.to()) as u32;
|
||||
|
||||
let mut node = match syntax.named_descendant_for_byte_range(start_from, start_to) {
|
||||
Some(node) => node,
|
||||
@@ -654,18 +656,18 @@ pub fn move_parent_node_end(
|
||||
// moving forward, we always want to move one past the end of the
|
||||
// current node, so use the end byte of the current node, which is an exclusive
|
||||
// end of the range
|
||||
Direction::Forward => text.byte_to_char(node.end_byte()),
|
||||
Direction::Forward => text.byte_to_char(node.end_byte() as usize),
|
||||
|
||||
// moving backward, we want the cursor to land on the start char of
|
||||
// the current node, or if it is already at the start of a node, to traverse up to
|
||||
// the parent
|
||||
Direction::Backward => {
|
||||
let end_head = text.byte_to_char(node.start_byte());
|
||||
let end_head = text.byte_to_char(node.start_byte() as usize);
|
||||
|
||||
// if we're already on the beginning, look up to the parent
|
||||
if end_head == range.cursor(text) {
|
||||
node = find_parent_start(node).unwrap_or(node);
|
||||
text.byte_to_char(node.start_byte())
|
||||
node = find_parent_start(&node).unwrap_or(node);
|
||||
text.byte_to_char(node.start_byte() as usize)
|
||||
} else {
|
||||
end_head
|
||||
}
|
||||
|
@@ -4,8 +4,8 @@ pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: Selection)
|
||||
let cursor = &mut syntax.walk();
|
||||
|
||||
selection.transform(|range| {
|
||||
let from = text.char_to_byte(range.from());
|
||||
let to = text.char_to_byte(range.to());
|
||||
let from = text.char_to_byte(range.from()) as u32;
|
||||
let to = text.char_to_byte(range.to()) as u32;
|
||||
|
||||
let byte_range = from..to;
|
||||
cursor.reset_to_byte_range(from, to);
|
||||
@@ -17,8 +17,8 @@ pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: Selection)
|
||||
}
|
||||
|
||||
let node = cursor.node();
|
||||
let from = text.byte_to_char(node.start_byte());
|
||||
let to = text.byte_to_char(node.end_byte());
|
||||
let from = text.byte_to_char(node.start_byte() as usize);
|
||||
let to = text.byte_to_char(node.end_byte() as usize);
|
||||
|
||||
Range::new(to, from).with_direction(range.direction())
|
||||
})
|
||||
@@ -53,10 +53,10 @@ pub fn select_next_sibling(syntax: &Syntax, text: RopeSlice, selection: Selectio
|
||||
}
|
||||
|
||||
pub fn select_all_siblings(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
|
||||
selection.transform_iter(|range| {
|
||||
let mut cursor = syntax.walk();
|
||||
let mut cursor = syntax.walk();
|
||||
selection.transform_iter(move |range| {
|
||||
let (from, to) = range.into_byte_range(text);
|
||||
cursor.reset_to_byte_range(from, to);
|
||||
cursor.reset_to_byte_range(from as u32, to as u32);
|
||||
|
||||
if !cursor.goto_parent_with(|parent| parent.child_count() > 1) {
|
||||
return vec![range].into_iter();
|
||||
@@ -67,21 +67,18 @@ pub fn select_all_siblings(syntax: &Syntax, text: RopeSlice, selection: Selectio
|
||||
}
|
||||
|
||||
pub fn select_all_children(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
|
||||
selection.transform_iter(|range| {
|
||||
let mut cursor = syntax.walk();
|
||||
let mut cursor = syntax.walk();
|
||||
selection.transform_iter(move |range| {
|
||||
let (from, to) = range.into_byte_range(text);
|
||||
cursor.reset_to_byte_range(from, to);
|
||||
cursor.reset_to_byte_range(from as u32, to as u32);
|
||||
select_children(&mut cursor, text, range).into_iter()
|
||||
})
|
||||
}
|
||||
|
||||
fn select_children<'n>(
|
||||
cursor: &'n mut TreeCursor<'n>,
|
||||
text: RopeSlice,
|
||||
range: Range,
|
||||
) -> Vec<Range> {
|
||||
fn select_children(cursor: &mut TreeCursor, text: RopeSlice, range: Range) -> Vec<Range> {
|
||||
let children = cursor
|
||||
.named_children()
|
||||
.children()
|
||||
.filter(|child| child.is_named())
|
||||
.map(|child| Range::from_node(child, text, range.direction()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -98,7 +95,7 @@ pub fn select_prev_sibling(syntax: &Syntax, text: RopeSlice, selection: Selectio
|
||||
text,
|
||||
selection,
|
||||
|cursor| {
|
||||
while !cursor.goto_prev_sibling() {
|
||||
while !cursor.goto_previous_sibling() {
|
||||
if !cursor.goto_parent() {
|
||||
break;
|
||||
}
|
||||
@@ -121,16 +118,16 @@ where
|
||||
let cursor = &mut syntax.walk();
|
||||
|
||||
selection.transform(|range| {
|
||||
let from = text.char_to_byte(range.from());
|
||||
let to = text.char_to_byte(range.to());
|
||||
let from = text.char_to_byte(range.from()) as u32;
|
||||
let to = text.char_to_byte(range.to()) as u32;
|
||||
|
||||
cursor.reset_to_byte_range(from, to);
|
||||
|
||||
motion(cursor);
|
||||
|
||||
let node = cursor.node();
|
||||
let from = text.byte_to_char(node.start_byte());
|
||||
let to = text.byte_to_char(node.end_byte());
|
||||
let from = text.byte_to_char(node.start_byte() as usize);
|
||||
let to = text.byte_to_char(node.end_byte() as usize);
|
||||
|
||||
Range::new(from, to).with_direction(direction.unwrap_or_else(|| range.direction()))
|
||||
})
|
||||
|
@@ -4,10 +4,12 @@ use std::{
|
||||
ops::{Add, AddAssign, Sub, SubAssign},
|
||||
};
|
||||
|
||||
use helix_stdx::rope::RopeSliceExt;
|
||||
|
||||
use crate::{
|
||||
chars::char_is_line_ending,
|
||||
doc_formatter::{DocumentFormatter, TextFormat},
|
||||
graphemes::{ensure_grapheme_boundary_prev, grapheme_width, RopeGraphemes},
|
||||
graphemes::{ensure_grapheme_boundary_prev, grapheme_width},
|
||||
line_ending::line_end_char_index,
|
||||
text_annotations::TextAnnotations,
|
||||
RopeSlice,
|
||||
@@ -87,11 +89,6 @@ impl From<(usize, usize)> for Position {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Position> for tree_sitter::Point {
|
||||
fn from(pos: Position) -> Self {
|
||||
Self::new(pos.row, pos.col)
|
||||
}
|
||||
}
|
||||
/// Convert a character index to (line, column) coordinates.
|
||||
///
|
||||
/// column in `char` count which can be used for row:column display in
|
||||
@@ -101,7 +98,7 @@ pub fn coords_at_pos(text: RopeSlice, pos: usize) -> Position {
|
||||
|
||||
let line_start = text.line_to_char(line);
|
||||
let pos = ensure_grapheme_boundary_prev(text, pos);
|
||||
let col = RopeGraphemes::new(text.slice(line_start..pos)).count();
|
||||
let col = text.slice(line_start..pos).graphemes().count();
|
||||
|
||||
Position::new(line, col)
|
||||
}
|
||||
@@ -126,7 +123,7 @@ pub fn visual_coords_at_pos(text: RopeSlice, pos: usize, tab_width: usize) -> Po
|
||||
|
||||
let mut col = 0;
|
||||
|
||||
for grapheme in RopeGraphemes::new(text.slice(line_start..pos)) {
|
||||
for grapheme in text.slice(line_start..pos).graphemes() {
|
||||
if grapheme == "\t" {
|
||||
col += tab_width - (col % tab_width);
|
||||
} else {
|
||||
@@ -275,7 +272,7 @@ pub fn pos_at_coords(text: RopeSlice, coords: Position, limit_before_line_ending
|
||||
};
|
||||
|
||||
let mut col_char_offset = 0;
|
||||
for (i, g) in RopeGraphemes::new(text.slice(line_start..line_end)).enumerate() {
|
||||
for (i, g) in text.slice(line_start..line_end).graphemes().enumerate() {
|
||||
if i == col {
|
||||
break;
|
||||
}
|
||||
@@ -306,7 +303,7 @@ pub fn pos_at_visual_coords(text: RopeSlice, coords: Position, tab_width: usize)
|
||||
|
||||
let mut col_char_offset = 0;
|
||||
let mut cols_remaining = col;
|
||||
for grapheme in RopeGraphemes::new(text.slice(line_start..line_end)) {
|
||||
for grapheme in text.slice(line_start..line_end).graphemes() {
|
||||
let grapheme_width = if grapheme == "\t" {
|
||||
tab_width - ((col - cols_remaining) % tab_width)
|
||||
} else {
|
||||
|
@@ -9,13 +9,13 @@ use crate::{
|
||||
},
|
||||
line_ending::get_line_ending,
|
||||
movement::Direction,
|
||||
Assoc, ChangeSet, RopeGraphemes, RopeSlice,
|
||||
tree_sitter::Node,
|
||||
Assoc, ChangeSet, RopeSlice,
|
||||
};
|
||||
use helix_stdx::range::is_subset;
|
||||
use helix_stdx::rope::{self, RopeSliceExt};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::{borrow::Cow, iter, slice};
|
||||
use tree_sitter::Node;
|
||||
|
||||
/// A single selection range.
|
||||
///
|
||||
@@ -76,8 +76,8 @@ impl Range {
|
||||
}
|
||||
|
||||
pub fn from_node(node: Node, text: RopeSlice, direction: Direction) -> Self {
|
||||
let from = text.byte_to_char(node.start_byte());
|
||||
let to = text.byte_to_char(node.end_byte());
|
||||
let from = text.byte_to_char(node.start_byte() as usize);
|
||||
let to = text.byte_to_char(node.end_byte() as usize);
|
||||
Range::new(from, to).with_direction(direction)
|
||||
}
|
||||
|
||||
@@ -379,7 +379,7 @@ impl Range {
|
||||
|
||||
/// Returns true if this Range covers a single grapheme in the given text
|
||||
pub fn is_single_grapheme(&self, doc: RopeSlice) -> bool {
|
||||
let mut graphemes = RopeGraphemes::new(doc.slice(self.from()..self.to()));
|
||||
let mut graphemes = doc.slice(self.from()..self.to()).graphemes();
|
||||
let first = graphemes.next();
|
||||
let second = graphemes.next();
|
||||
first.is_some() && second.is_none()
|
||||
@@ -619,7 +619,6 @@ impl Selection {
|
||||
self
|
||||
}
|
||||
|
||||
// TODO: consume an iterator or a vec to reduce allocations?
|
||||
#[must_use]
|
||||
pub fn new(ranges: SmallVec<[Range; 1]>, primary_index: usize) -> Self {
|
||||
assert!(!ranges.is_empty());
|
||||
@@ -721,6 +720,12 @@ impl IntoIterator for Selection {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<Range> for Selection {
|
||||
fn from_iter<T: IntoIterator<Item = Range>>(ranges: T) -> Self {
|
||||
Self::new(ranges.into_iter().collect(), 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Range> for Selection {
|
||||
fn from(range: Range) -> Self {
|
||||
Self {
|
||||
|
@@ -1,350 +0,0 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Auto escape for shellwords usage.
|
||||
pub fn escape(input: Cow<str>) -> Cow<str> {
|
||||
if !input.chars().any(|x| x.is_ascii_whitespace()) {
|
||||
input
|
||||
} else if cfg!(unix) {
|
||||
Cow::Owned(input.chars().fold(String::new(), |mut buf, c| {
|
||||
if c.is_ascii_whitespace() {
|
||||
buf.push('\\');
|
||||
}
|
||||
buf.push(c);
|
||||
buf
|
||||
}))
|
||||
} else {
|
||||
Cow::Owned(format!("\"{}\"", input))
|
||||
}
|
||||
}
|
||||
|
||||
enum State {
|
||||
OnWhitespace,
|
||||
Unquoted,
|
||||
UnquotedEscaped,
|
||||
Quoted,
|
||||
QuoteEscaped,
|
||||
Dquoted,
|
||||
DquoteEscaped,
|
||||
}
|
||||
|
||||
pub struct Shellwords<'a> {
|
||||
state: State,
|
||||
/// Shellwords where whitespace and escapes has been resolved.
|
||||
words: Vec<Cow<'a, str>>,
|
||||
/// The parts of the input that are divided into shellwords. This can be
|
||||
/// used to retrieve the original text for a given word by looking up the
|
||||
/// same index in the Vec as the word in `words`.
|
||||
parts: Vec<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Shellwords<'a> {
|
||||
fn from(input: &'a str) -> Self {
|
||||
use State::*;
|
||||
|
||||
let mut state = Unquoted;
|
||||
let mut words = Vec::new();
|
||||
let mut parts = Vec::new();
|
||||
let mut escaped = String::with_capacity(input.len());
|
||||
|
||||
let mut part_start = 0;
|
||||
let mut unescaped_start = 0;
|
||||
let mut end = 0;
|
||||
|
||||
for (i, c) in input.char_indices() {
|
||||
state = match state {
|
||||
OnWhitespace => match c {
|
||||
'"' => {
|
||||
end = i;
|
||||
Dquoted
|
||||
}
|
||||
'\'' => {
|
||||
end = i;
|
||||
Quoted
|
||||
}
|
||||
'\\' => {
|
||||
if cfg!(unix) {
|
||||
escaped.push_str(&input[unescaped_start..i]);
|
||||
unescaped_start = i + 1;
|
||||
UnquotedEscaped
|
||||
} else {
|
||||
OnWhitespace
|
||||
}
|
||||
}
|
||||
c if c.is_ascii_whitespace() => {
|
||||
end = i;
|
||||
OnWhitespace
|
||||
}
|
||||
_ => Unquoted,
|
||||
},
|
||||
Unquoted => match c {
|
||||
'\\' => {
|
||||
if cfg!(unix) {
|
||||
escaped.push_str(&input[unescaped_start..i]);
|
||||
unescaped_start = i + 1;
|
||||
UnquotedEscaped
|
||||
} else {
|
||||
Unquoted
|
||||
}
|
||||
}
|
||||
c if c.is_ascii_whitespace() => {
|
||||
end = i;
|
||||
OnWhitespace
|
||||
}
|
||||
_ => Unquoted,
|
||||
},
|
||||
UnquotedEscaped => Unquoted,
|
||||
Quoted => match c {
|
||||
'\\' => {
|
||||
if cfg!(unix) {
|
||||
escaped.push_str(&input[unescaped_start..i]);
|
||||
unescaped_start = i + 1;
|
||||
QuoteEscaped
|
||||
} else {
|
||||
Quoted
|
||||
}
|
||||
}
|
||||
'\'' => {
|
||||
end = i;
|
||||
OnWhitespace
|
||||
}
|
||||
_ => Quoted,
|
||||
},
|
||||
QuoteEscaped => Quoted,
|
||||
Dquoted => match c {
|
||||
'\\' => {
|
||||
if cfg!(unix) {
|
||||
escaped.push_str(&input[unescaped_start..i]);
|
||||
unescaped_start = i + 1;
|
||||
DquoteEscaped
|
||||
} else {
|
||||
Dquoted
|
||||
}
|
||||
}
|
||||
'"' => {
|
||||
end = i;
|
||||
OnWhitespace
|
||||
}
|
||||
_ => Dquoted,
|
||||
},
|
||||
DquoteEscaped => Dquoted,
|
||||
};
|
||||
|
||||
let c_len = c.len_utf8();
|
||||
if i == input.len() - c_len && end == 0 {
|
||||
end = i + c_len;
|
||||
}
|
||||
|
||||
if end > 0 {
|
||||
let esc_trim = escaped.trim();
|
||||
let inp = &input[unescaped_start..end];
|
||||
|
||||
if !(esc_trim.is_empty() && inp.trim().is_empty()) {
|
||||
if esc_trim.is_empty() {
|
||||
words.push(inp.into());
|
||||
parts.push(inp);
|
||||
} else {
|
||||
words.push([escaped, inp.into()].concat().into());
|
||||
parts.push(&input[part_start..end]);
|
||||
escaped = "".to_string();
|
||||
}
|
||||
}
|
||||
unescaped_start = i + 1;
|
||||
part_start = i + 1;
|
||||
end = 0;
|
||||
}
|
||||
}
|
||||
|
||||
debug_assert!(words.len() == parts.len());
|
||||
|
||||
Self {
|
||||
state,
|
||||
words,
|
||||
parts,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Shellwords<'a> {
|
||||
/// Checks that the input ends with a whitespace character which is not escaped.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use helix_core::shellwords::Shellwords;
|
||||
/// assert_eq!(Shellwords::from(" ").ends_with_whitespace(), true);
|
||||
/// assert_eq!(Shellwords::from(":open ").ends_with_whitespace(), true);
|
||||
/// assert_eq!(Shellwords::from(":open foo.txt ").ends_with_whitespace(), true);
|
||||
/// assert_eq!(Shellwords::from(":open").ends_with_whitespace(), false);
|
||||
/// #[cfg(unix)]
|
||||
/// assert_eq!(Shellwords::from(":open a\\ ").ends_with_whitespace(), false);
|
||||
/// #[cfg(unix)]
|
||||
/// assert_eq!(Shellwords::from(":open a\\ b.txt").ends_with_whitespace(), false);
|
||||
/// ```
|
||||
pub fn ends_with_whitespace(&self) -> bool {
|
||||
matches!(self.state, State::OnWhitespace)
|
||||
}
|
||||
|
||||
/// Returns the list of shellwords calculated from the input string.
|
||||
pub fn words(&self) -> &[Cow<'a, str>] {
|
||||
&self.words
|
||||
}
|
||||
|
||||
/// Returns a list of strings which correspond to [`Self::words`] but represent the original
|
||||
/// text in the input string - including escape characters - without separating whitespace.
|
||||
pub fn parts(&self) -> &[&'a str] {
|
||||
&self.parts
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn test_normal() {
|
||||
let input = r#":o single_word twó wörds \three\ \"with\ escaping\\"#;
|
||||
let shellwords = Shellwords::from(input);
|
||||
let result = shellwords.words().to_vec();
|
||||
let expected = vec![
|
||||
Cow::from(":o"),
|
||||
Cow::from("single_word"),
|
||||
Cow::from("twó"),
|
||||
Cow::from("wörds"),
|
||||
Cow::from("\\three\\"),
|
||||
Cow::from("\\"),
|
||||
Cow::from("with\\ escaping\\\\"),
|
||||
];
|
||||
// TODO test is_owned and is_borrowed, once they get stabilized.
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_normal() {
|
||||
let input = r#":o single_word twó wörds \three\ \"with\ escaping\\"#;
|
||||
let shellwords = Shellwords::from(input);
|
||||
let result = shellwords.words().to_vec();
|
||||
let expected = vec![
|
||||
Cow::from(":o"),
|
||||
Cow::from("single_word"),
|
||||
Cow::from("twó"),
|
||||
Cow::from("wörds"),
|
||||
Cow::from(r#"three "with escaping\"#),
|
||||
];
|
||||
// TODO test is_owned and is_borrowed, once they get stabilized.
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_quoted() {
|
||||
let quoted =
|
||||
r#":o 'single_word' 'twó wörds' '' ' ''\three\' \"with\ escaping\\' 'quote incomplete"#;
|
||||
let shellwords = Shellwords::from(quoted);
|
||||
let result = shellwords.words().to_vec();
|
||||
let expected = vec![
|
||||
Cow::from(":o"),
|
||||
Cow::from("single_word"),
|
||||
Cow::from("twó wörds"),
|
||||
Cow::from(r#"three' "with escaping\"#),
|
||||
Cow::from("quote incomplete"),
|
||||
];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_dquoted() {
|
||||
let dquoted = r#":o "single_word" "twó wörds" "" " ""\three\' \"with\ escaping\\" "dquote incomplete"#;
|
||||
let shellwords = Shellwords::from(dquoted);
|
||||
let result = shellwords.words().to_vec();
|
||||
let expected = vec![
|
||||
Cow::from(":o"),
|
||||
Cow::from("single_word"),
|
||||
Cow::from("twó wörds"),
|
||||
Cow::from(r#"three' "with escaping\"#),
|
||||
Cow::from("dquote incomplete"),
|
||||
];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_mixed() {
|
||||
let dquoted = r#":o single_word 'twó wörds' "\three\' \"with\ escaping\\""no space before"'and after' $#%^@ "%^&(%^" ')(*&^%''a\\\\\b' '"#;
|
||||
let shellwords = Shellwords::from(dquoted);
|
||||
let result = shellwords.words().to_vec();
|
||||
let expected = vec![
|
||||
Cow::from(":o"),
|
||||
Cow::from("single_word"),
|
||||
Cow::from("twó wörds"),
|
||||
Cow::from("three' \"with escaping\\"),
|
||||
Cow::from("no space before"),
|
||||
Cow::from("and after"),
|
||||
Cow::from("$#%^@"),
|
||||
Cow::from("%^&(%^"),
|
||||
Cow::from(")(*&^%"),
|
||||
Cow::from(r#"a\\b"#),
|
||||
//last ' just changes to quoted but since we dont have anything after it, it should be ignored
|
||||
];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lists() {
|
||||
let input =
|
||||
r#":set statusline.center ["file-type","file-encoding"] '["list", "in", "quotes"]'"#;
|
||||
let shellwords = Shellwords::from(input);
|
||||
let result = shellwords.words().to_vec();
|
||||
let expected = vec![
|
||||
Cow::from(":set"),
|
||||
Cow::from("statusline.center"),
|
||||
Cow::from(r#"["file-type","file-encoding"]"#),
|
||||
Cow::from(r#"["list", "in", "quotes"]"#),
|
||||
];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_escaping_unix() {
|
||||
assert_eq!(escape("foobar".into()), Cow::Borrowed("foobar"));
|
||||
assert_eq!(escape("foo bar".into()), Cow::Borrowed("foo\\ bar"));
|
||||
assert_eq!(escape("foo\tbar".into()), Cow::Borrowed("foo\\\tbar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn test_escaping_windows() {
|
||||
assert_eq!(escape("foobar".into()), Cow::Borrowed("foobar"));
|
||||
assert_eq!(escape("foo bar".into()), Cow::Borrowed("\"foo bar\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_parts() {
|
||||
assert_eq!(Shellwords::from(":o a").parts(), &[":o", "a"]);
|
||||
assert_eq!(Shellwords::from(":o a\\ ").parts(), &[":o", "a\\ "]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn test_parts() {
|
||||
assert_eq!(Shellwords::from(":o a").parts(), &[":o", "a"]);
|
||||
assert_eq!(Shellwords::from(":o a\\ ").parts(), &[":o", "a\\"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multibyte_at_end() {
|
||||
assert_eq!(Shellwords::from("𒀀").parts(), &["𒀀"]);
|
||||
assert_eq!(
|
||||
Shellwords::from(":sh echo 𒀀").parts(),
|
||||
&[":sh", "echo", "𒀀"]
|
||||
);
|
||||
assert_eq!(
|
||||
Shellwords::from(":sh echo 𒀀 hello world𒀀").parts(),
|
||||
&[":sh", "echo", "𒀀", "hello", "world𒀀"]
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
use std::ops::{Index, IndexMut};
|
||||
|
||||
use hashbrown::HashSet;
|
||||
use foldhash::HashSet;
|
||||
use helix_stdx::range::{is_exact_subset, is_subset};
|
||||
use helix_stdx::Range;
|
||||
use ropey::Rope;
|
||||
@@ -35,7 +35,7 @@ impl ActiveSnippet {
|
||||
let snippet = Self {
|
||||
ranges: snippet.ranges,
|
||||
tabstops: snippet.tabstops,
|
||||
active_tabstops: HashSet::new(),
|
||||
active_tabstops: HashSet::default(),
|
||||
current_tabstop: TabstopIdx(0),
|
||||
};
|
||||
(snippet.tabstops.len() != 1).then_some(snippet)
|
||||
@@ -252,4 +252,21 @@ mod tests {
|
||||
snippet.map(edit.changes());
|
||||
assert!(!snippet.is_valid(&Selection::point(4)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tabstop_zero_with_placeholder() {
|
||||
// The `$0` tabstop should not have placeholder text. When we receive a snippet like this
|
||||
// (from older versions of clangd for example) we should discard the placeholder text.
|
||||
let snippet = Snippet::parse("sizeof(${0:expression-or-type})").unwrap();
|
||||
let mut doc = Rope::from("\n");
|
||||
let (transaction, _, snippet) = snippet.render(
|
||||
&doc,
|
||||
&Selection::point(0),
|
||||
|_| (0, 0),
|
||||
&mut SnippetRenderCtx::test_ctx(),
|
||||
);
|
||||
assert!(transaction.apply(&mut doc));
|
||||
assert_eq!(doc, "sizeof()\n");
|
||||
assert!(ActiveSnippet::new(snippet).is_none());
|
||||
}
|
||||
}
|
||||
|
@@ -178,9 +178,16 @@ impl Snippet {
|
||||
&mut self,
|
||||
idx: usize,
|
||||
parent: Option<TabstopIdx>,
|
||||
default: Vec<parser::SnippetElement>,
|
||||
mut default: Vec<parser::SnippetElement>,
|
||||
) -> TabstopIdx {
|
||||
let idx = TabstopIdx::elaborate(idx);
|
||||
if idx == LAST_TABSTOP_IDX && !default.is_empty() {
|
||||
// Older versions of clangd for example may send a snippet like `${0:placeholder}`
|
||||
// which is considered by VSCode to be a misuse of the `$0` tabstop.
|
||||
log::warn!("Discarding placeholder text for the `$0` tabstop ({default:?}). \
|
||||
The `$0` tabstop signifies the final cursor position and should not include placeholder text.");
|
||||
default.clear();
|
||||
}
|
||||
let default = self.elaborate(default, Some(idx));
|
||||
self.tabstops.push(Tabstop {
|
||||
idx,
|
||||
|
@@ -361,7 +361,20 @@ mod test {
|
||||
Text(")".into()),
|
||||
]),
|
||||
parse("match(${1:Arg1})")
|
||||
)
|
||||
);
|
||||
// The `$0` tabstop should not have placeholder text. The parser should handle this case
|
||||
// normally and then the placeholder text should be discarded during elaboration.
|
||||
assert_eq!(
|
||||
Ok(vec![
|
||||
Text("sizeof(".into()),
|
||||
Placeholder {
|
||||
tabstop: 0,
|
||||
value: vec![Text("expression-or-type".into())],
|
||||
},
|
||||
Text(")".into()),
|
||||
]),
|
||||
parse("sizeof(${0:expression-or-type})")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
File diff suppressed because it is too large
Load Diff
630
helix-core/src/syntax/config.rs
Normal file
630
helix-core/src/syntax/config.rs
Normal file
@@ -0,0 +1,630 @@
|
||||
use crate::{auto_pairs::AutoPairs, diagnostic::Severity, Language};
|
||||
|
||||
use globset::GlobSet;
|
||||
use helix_stdx::rope;
|
||||
use serde::{ser::SerializeSeq as _, Deserialize, Serialize};
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fmt::{self, Display},
|
||||
num::NonZeroU8,
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct Configuration {
|
||||
pub language: Vec<LanguageConfiguration>,
|
||||
#[serde(default)]
|
||||
pub language_server: HashMap<String, LanguageServerConfiguration>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub struct LanguageConfiguration {
|
||||
#[serde(skip)]
|
||||
pub(super) language: Option<Language>,
|
||||
|
||||
#[serde(rename = "name")]
|
||||
pub language_id: String, // c-sharp, rust, tsx
|
||||
#[serde(rename = "language-id")]
|
||||
// see the table under https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem
|
||||
pub language_server_language_id: Option<String>, // csharp, rust, typescriptreact, for the language-server
|
||||
pub scope: String, // source.rust
|
||||
pub file_types: Vec<FileType>, // filename extension or ends_with? <Gemfile, rb, etc>
|
||||
#[serde(default)]
|
||||
pub shebangs: Vec<String>, // interpreter(s) associated with language
|
||||
#[serde(default)]
|
||||
pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing,
|
||||
deserialize_with = "from_comment_tokens",
|
||||
alias = "comment-token"
|
||||
)]
|
||||
pub comment_tokens: Option<Vec<String>>,
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing,
|
||||
deserialize_with = "from_block_comment_tokens"
|
||||
)]
|
||||
pub block_comment_tokens: Option<Vec<BlockCommentToken>>,
|
||||
pub text_width: Option<usize>,
|
||||
pub soft_wrap: Option<SoftWrap>,
|
||||
|
||||
#[serde(default)]
|
||||
pub auto_format: bool,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub formatter: Option<FormatterConfiguration>,
|
||||
|
||||
/// If set, overrides `editor.path-completion`.
|
||||
pub path_completion: Option<bool>,
|
||||
/// If set, overrides `editor.word-completion`.
|
||||
pub word_completion: Option<WordCompletion>,
|
||||
|
||||
#[serde(default)]
|
||||
pub diagnostic_severity: Severity,
|
||||
|
||||
pub grammar: Option<String>, // tree-sitter grammar name, defaults to language_id
|
||||
|
||||
// content_regex
|
||||
#[serde(default, skip_serializing, deserialize_with = "deserialize_regex")]
|
||||
pub injection_regex: Option<rope::Regex>,
|
||||
// first_line_regex
|
||||
//
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Vec::is_empty",
|
||||
serialize_with = "serialize_lang_features",
|
||||
deserialize_with = "deserialize_lang_features"
|
||||
)]
|
||||
pub language_servers: Vec<LanguageServerFeatures>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub indent: Option<IndentationConfiguration>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub debugger: Option<DebugAdapterConfig>,
|
||||
|
||||
/// Automatic insertion of pairs to parentheses, brackets,
|
||||
/// etc. Defaults to true. Optionally, this can be a list of 2-tuples
|
||||
/// to specify a list of characters to pair. This overrides the
|
||||
/// global setting.
|
||||
#[serde(default, skip_serializing, deserialize_with = "deserialize_auto_pairs")]
|
||||
pub auto_pairs: Option<AutoPairs>,
|
||||
|
||||
pub rulers: Option<Vec<u16>>, // if set, override editor's rulers
|
||||
|
||||
/// Hardcoded LSP root directories relative to the workspace root, like `examples` or `tools/fuzz`.
|
||||
/// Falling back to the current working directory if none are configured.
|
||||
pub workspace_lsp_roots: Option<Vec<PathBuf>>,
|
||||
#[serde(default)]
|
||||
pub persistent_diagnostic_sources: Vec<String>,
|
||||
/// Overrides the `editor.rainbow-brackets` config key for the language.
|
||||
pub rainbow_brackets: Option<bool>,
|
||||
}
|
||||
|
||||
impl LanguageConfiguration {
|
||||
pub fn language(&self) -> Language {
|
||||
// This value must be set by `super::Loader::new`.
|
||||
self.language.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub enum FileType {
|
||||
/// The extension of the file, either the `Path::extension` or the full
|
||||
/// filename if the file does not have an extension.
|
||||
Extension(String),
|
||||
/// A Unix-style path glob. This is compared to the file's absolute path, so
|
||||
/// it can be used to detect files based on their directories. If the glob
|
||||
/// is not an absolute path and does not already start with a glob pattern,
|
||||
/// a glob pattern will be prepended to it.
|
||||
Glob(globset::Glob),
|
||||
}
|
||||
|
||||
impl Serialize for FileType {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
use serde::ser::SerializeMap;
|
||||
|
||||
match self {
|
||||
FileType::Extension(extension) => serializer.serialize_str(extension),
|
||||
FileType::Glob(glob) => {
|
||||
let mut map = serializer.serialize_map(Some(1))?;
|
||||
map.serialize_entry("glob", glob.glob())?;
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for FileType {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::de::Deserializer<'de>,
|
||||
{
|
||||
struct FileTypeVisitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for FileTypeVisitor {
|
||||
type Value = FileType;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("string or table")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(FileType::Extension(value.to_string()))
|
||||
}
|
||||
|
||||
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
|
||||
where
|
||||
M: serde::de::MapAccess<'de>,
|
||||
{
|
||||
match map.next_entry::<String, String>()? {
|
||||
Some((key, mut glob)) if key == "glob" => {
|
||||
// If the glob isn't an absolute path or already starts
|
||||
// with a glob pattern, add a leading glob so we
|
||||
// properly match relative paths.
|
||||
if !glob.starts_with('/') && !glob.starts_with("*/") {
|
||||
glob.insert_str(0, "*/");
|
||||
}
|
||||
|
||||
globset::Glob::new(glob.as_str())
|
||||
.map(FileType::Glob)
|
||||
.map_err(|err| {
|
||||
serde::de::Error::custom(format!("invalid `glob` pattern: {}", err))
|
||||
})
|
||||
}
|
||||
Some((key, _value)) => Err(serde::de::Error::custom(format!(
|
||||
"unknown key in `file-types` list: {}",
|
||||
key
|
||||
))),
|
||||
None => Err(serde::de::Error::custom(
|
||||
"expected a `suffix` key in the `file-types` entry",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(FileTypeVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
fn from_comment_tokens<'de, D>(deserializer: D) -> Result<Option<Vec<String>>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum CommentTokens {
|
||||
Multiple(Vec<String>),
|
||||
Single(String),
|
||||
}
|
||||
Ok(
|
||||
Option::<CommentTokens>::deserialize(deserializer)?.map(|tokens| match tokens {
|
||||
CommentTokens::Single(val) => vec![val],
|
||||
CommentTokens::Multiple(vals) => vals,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct BlockCommentToken {
|
||||
pub start: String,
|
||||
pub end: String,
|
||||
}
|
||||
|
||||
impl Default for BlockCommentToken {
|
||||
fn default() -> Self {
|
||||
BlockCommentToken {
|
||||
start: "/*".to_string(),
|
||||
end: "*/".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn from_block_comment_tokens<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<Vec<BlockCommentToken>>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum BlockCommentTokens {
|
||||
Multiple(Vec<BlockCommentToken>),
|
||||
Single(BlockCommentToken),
|
||||
}
|
||||
Ok(
|
||||
Option::<BlockCommentTokens>::deserialize(deserializer)?.map(|tokens| match tokens {
|
||||
BlockCommentTokens::Single(val) => vec![val],
|
||||
BlockCommentTokens::Multiple(vals) => vals,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum LanguageServerFeature {
|
||||
Format,
|
||||
GotoDeclaration,
|
||||
GotoDefinition,
|
||||
GotoTypeDefinition,
|
||||
GotoReference,
|
||||
GotoImplementation,
|
||||
// Goto, use bitflags, combining previous Goto members?
|
||||
SignatureHelp,
|
||||
Hover,
|
||||
DocumentHighlight,
|
||||
Completion,
|
||||
CodeAction,
|
||||
WorkspaceCommand,
|
||||
DocumentSymbols,
|
||||
WorkspaceSymbols,
|
||||
// Symbols, use bitflags, see above?
|
||||
Diagnostics,
|
||||
RenameSymbol,
|
||||
InlayHints,
|
||||
DocumentColors,
|
||||
}
|
||||
|
||||
impl Display for LanguageServerFeature {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use LanguageServerFeature::*;
|
||||
let feature = match self {
|
||||
Format => "format",
|
||||
GotoDeclaration => "goto-declaration",
|
||||
GotoDefinition => "goto-definition",
|
||||
GotoTypeDefinition => "goto-type-definition",
|
||||
GotoReference => "goto-reference",
|
||||
GotoImplementation => "goto-implementation",
|
||||
SignatureHelp => "signature-help",
|
||||
Hover => "hover",
|
||||
DocumentHighlight => "document-highlight",
|
||||
Completion => "completion",
|
||||
CodeAction => "code-action",
|
||||
WorkspaceCommand => "workspace-command",
|
||||
DocumentSymbols => "document-symbols",
|
||||
WorkspaceSymbols => "workspace-symbols",
|
||||
Diagnostics => "diagnostics",
|
||||
RenameSymbol => "rename-symbol",
|
||||
InlayHints => "inlay-hints",
|
||||
DocumentColors => "document-colors",
|
||||
};
|
||||
write!(f, "{feature}",)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(untagged, rename_all = "kebab-case", deny_unknown_fields)]
|
||||
enum LanguageServerFeatureConfiguration {
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
Features {
|
||||
#[serde(default, skip_serializing_if = "HashSet::is_empty")]
|
||||
only_features: HashSet<LanguageServerFeature>,
|
||||
#[serde(default, skip_serializing_if = "HashSet::is_empty")]
|
||||
except_features: HashSet<LanguageServerFeature>,
|
||||
name: String,
|
||||
},
|
||||
Simple(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct LanguageServerFeatures {
|
||||
pub name: String,
|
||||
pub only: HashSet<LanguageServerFeature>,
|
||||
pub excluded: HashSet<LanguageServerFeature>,
|
||||
}
|
||||
|
||||
impl LanguageServerFeatures {
|
||||
pub fn has_feature(&self, feature: LanguageServerFeature) -> bool {
|
||||
(self.only.is_empty() || self.only.contains(&feature)) && !self.excluded.contains(&feature)
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_lang_features<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Vec<LanguageServerFeatures>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let raw: Vec<LanguageServerFeatureConfiguration> = Deserialize::deserialize(deserializer)?;
|
||||
let res = raw
|
||||
.into_iter()
|
||||
.map(|config| match config {
|
||||
LanguageServerFeatureConfiguration::Simple(name) => LanguageServerFeatures {
|
||||
name,
|
||||
..Default::default()
|
||||
},
|
||||
LanguageServerFeatureConfiguration::Features {
|
||||
only_features,
|
||||
except_features,
|
||||
name,
|
||||
} => LanguageServerFeatures {
|
||||
name,
|
||||
only: only_features,
|
||||
excluded: except_features,
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
Ok(res)
|
||||
}
|
||||
fn serialize_lang_features<S>(
|
||||
map: &Vec<LanguageServerFeatures>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut serializer = serializer.serialize_seq(Some(map.len()))?;
|
||||
for features in map {
|
||||
let features = if features.only.is_empty() && features.excluded.is_empty() {
|
||||
LanguageServerFeatureConfiguration::Simple(features.name.to_owned())
|
||||
} else {
|
||||
LanguageServerFeatureConfiguration::Features {
|
||||
only_features: features.only.clone(),
|
||||
except_features: features.excluded.clone(),
|
||||
name: features.name.to_owned(),
|
||||
}
|
||||
};
|
||||
serializer.serialize_element(&features)?;
|
||||
}
|
||||
serializer.end()
|
||||
}
|
||||
|
||||
fn deserialize_required_root_patterns<'de, D>(deserializer: D) -> Result<Option<GlobSet>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let patterns = Vec::<String>::deserialize(deserializer)?;
|
||||
if patterns.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
let mut builder = globset::GlobSetBuilder::new();
|
||||
for pattern in patterns {
|
||||
let glob = globset::Glob::new(&pattern).map_err(serde::de::Error::custom)?;
|
||||
builder.add(glob);
|
||||
}
|
||||
builder.build().map(Some).map_err(serde::de::Error::custom)
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct LanguageServerConfiguration {
|
||||
pub command: String,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub args: Vec<String>,
|
||||
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
||||
pub environment: HashMap<String, String>,
|
||||
#[serde(default, skip_serializing, deserialize_with = "deserialize_lsp_config")]
|
||||
pub config: Option<serde_json::Value>,
|
||||
#[serde(default = "default_timeout")]
|
||||
pub timeout: u64,
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing,
|
||||
deserialize_with = "deserialize_required_root_patterns"
|
||||
)]
|
||||
pub required_root_patterns: Option<GlobSet>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct FormatterConfiguration {
|
||||
pub command: String,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub args: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct AdvancedCompletion {
|
||||
pub name: Option<String>,
|
||||
pub completion: Option<String>,
|
||||
pub default: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case", untagged)]
|
||||
pub enum DebugConfigCompletion {
|
||||
Named(String),
|
||||
Advanced(AdvancedCompletion),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum DebugArgumentValue {
|
||||
String(String),
|
||||
Array(Vec<String>),
|
||||
Boolean(bool),
|
||||
Table(HashMap<String, String>),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct DebugTemplate {
|
||||
pub name: String,
|
||||
pub request: String,
|
||||
#[serde(default)]
|
||||
pub completion: Vec<DebugConfigCompletion>,
|
||||
pub args: HashMap<String, DebugArgumentValue>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct DebugAdapterConfig {
|
||||
pub name: String,
|
||||
pub transport: String,
|
||||
#[serde(default)]
|
||||
pub command: String,
|
||||
#[serde(default)]
|
||||
pub args: Vec<String>,
|
||||
pub port_arg: Option<String>,
|
||||
pub templates: Vec<DebugTemplate>,
|
||||
#[serde(default)]
|
||||
pub quirks: DebuggerQuirks,
|
||||
}
|
||||
|
||||
// Different workarounds for adapters' differences
|
||||
#[derive(Debug, Default, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
||||
pub struct DebuggerQuirks {
|
||||
#[serde(default)]
|
||||
pub absolute_paths: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct IndentationConfiguration {
|
||||
#[serde(deserialize_with = "deserialize_tab_width")]
|
||||
pub tab_width: usize,
|
||||
pub unit: String,
|
||||
}
|
||||
|
||||
/// How the indentation for a newly inserted line should be determined.
|
||||
/// If the selected heuristic is not available (e.g. because the current
|
||||
/// language has no tree-sitter indent queries), a simpler one will be used.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum IndentationHeuristic {
|
||||
/// Just copy the indentation of the line that the cursor is currently on.
|
||||
Simple,
|
||||
/// Use tree-sitter indent queries to compute the expected absolute indentation level of the new line.
|
||||
TreeSitter,
|
||||
/// Use tree-sitter indent queries to compute the expected difference in indentation between the new line
|
||||
/// and the line before. Add this to the actual indentation level of the line before.
|
||||
#[default]
|
||||
Hybrid,
|
||||
}
|
||||
|
||||
/// Configuration for auto pairs
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields, untagged)]
|
||||
pub enum AutoPairConfig {
|
||||
/// Enables or disables auto pairing. False means disabled. True means to use the default pairs.
|
||||
Enable(bool),
|
||||
|
||||
/// The mappings of pairs.
|
||||
Pairs(HashMap<char, char>),
|
||||
}
|
||||
|
||||
impl Default for AutoPairConfig {
|
||||
fn default() -> Self {
|
||||
AutoPairConfig::Enable(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&AutoPairConfig> for Option<AutoPairs> {
|
||||
fn from(auto_pair_config: &AutoPairConfig) -> Self {
|
||||
match auto_pair_config {
|
||||
AutoPairConfig::Enable(false) => None,
|
||||
AutoPairConfig::Enable(true) => Some(AutoPairs::default()),
|
||||
AutoPairConfig::Pairs(pairs) => Some(AutoPairs::new(pairs.iter())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AutoPairConfig> for Option<AutoPairs> {
|
||||
fn from(auto_pairs_config: AutoPairConfig) -> Self {
|
||||
(&auto_pairs_config).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for AutoPairConfig {
|
||||
type Err = std::str::ParseBoolError;
|
||||
|
||||
// only do bool parsing for runtime setting
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let enable: bool = s.parse()?;
|
||||
Ok(AutoPairConfig::Enable(enable))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub struct SoftWrap {
|
||||
/// Soft wrap lines that exceed viewport width. Default to off
|
||||
// NOTE: Option on purpose because the struct is shared between language config and global config.
|
||||
// By default the option is None so that the language config falls back to the global config unless explicitly set.
|
||||
pub enable: Option<bool>,
|
||||
/// Maximum space left free at the end of the line.
|
||||
/// This space is used to wrap text at word boundaries. If that is not possible within this limit
|
||||
/// the word is simply split at the end of the line.
|
||||
///
|
||||
/// This is automatically hard-limited to a quarter of the viewport to ensure correct display on small views.
|
||||
///
|
||||
/// Default to 20
|
||||
pub max_wrap: Option<u16>,
|
||||
/// Maximum number of indentation that can be carried over from the previous line when softwrapping.
|
||||
/// If a line is indented further then this limit it is rendered at the start of the viewport instead.
|
||||
///
|
||||
/// This is automatically hard-limited to a quarter of the viewport to ensure correct display on small views.
|
||||
///
|
||||
/// Default to 40
|
||||
pub max_indent_retain: Option<u16>,
|
||||
/// Indicator placed at the beginning of softwrapped lines
|
||||
///
|
||||
/// Defaults to ↪
|
||||
pub wrap_indicator: Option<String>,
|
||||
/// Softwrap at `text_width` instead of viewport width if it is shorter
|
||||
pub wrap_at_text_width: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub struct WordCompletion {
|
||||
pub enable: Option<bool>,
|
||||
pub trigger_length: Option<NonZeroU8>,
|
||||
}
|
||||
|
||||
fn deserialize_regex<'de, D>(deserializer: D) -> Result<Option<rope::Regex>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
Option::<String>::deserialize(deserializer)?
|
||||
.map(|buf| rope::Regex::new(&buf).map_err(serde::de::Error::custom))
|
||||
.transpose()
|
||||
}
|
||||
|
||||
fn deserialize_lsp_config<'de, D>(deserializer: D) -> Result<Option<serde_json::Value>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
Option::<toml::Value>::deserialize(deserializer)?
|
||||
.map(|toml| toml.try_into().map_err(serde::de::Error::custom))
|
||||
.transpose()
|
||||
}
|
||||
|
||||
fn deserialize_tab_width<'de, D>(deserializer: D) -> Result<usize, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
usize::deserialize(deserializer).and_then(|n| {
|
||||
if n > 0 && n <= 16 {
|
||||
Ok(n)
|
||||
} else {
|
||||
Err(serde::de::Error::custom(
|
||||
"tab width must be a value from 1 to 16 inclusive",
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn deserialize_auto_pairs<'de, D>(deserializer: D) -> Result<Option<AutoPairs>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
Ok(Option::<AutoPairConfig>::deserialize(deserializer)?.and_then(AutoPairConfig::into))
|
||||
}
|
||||
|
||||
fn default_timeout() -> u64 {
|
||||
20
|
||||
}
|
@@ -1,264 +0,0 @@
|
||||
use std::{cmp::Reverse, ops::Range};
|
||||
|
||||
use super::{LanguageLayer, LayerId};
|
||||
|
||||
use slotmap::HopSlotMap;
|
||||
use tree_sitter::Node;
|
||||
|
||||
/// The byte range of an injection layer.
|
||||
///
|
||||
/// Injection ranges may overlap, but all overlapping parts are subsets of their parent ranges.
|
||||
/// This allows us to sort the ranges ahead of time in order to efficiently find a range that
|
||||
/// contains a point with maximum depth.
|
||||
#[derive(Debug)]
|
||||
struct InjectionRange {
|
||||
start: usize,
|
||||
end: usize,
|
||||
layer_id: LayerId,
|
||||
depth: u32,
|
||||
}
|
||||
|
||||
pub struct TreeCursor<'a> {
|
||||
layers: &'a HopSlotMap<LayerId, LanguageLayer>,
|
||||
root: LayerId,
|
||||
current: LayerId,
|
||||
injection_ranges: Vec<InjectionRange>,
|
||||
// TODO: Ideally this would be a `tree_sitter::TreeCursor<'a>` but
|
||||
// that returns very surprising results in testing.
|
||||
cursor: Node<'a>,
|
||||
}
|
||||
|
||||
impl<'a> TreeCursor<'a> {
|
||||
pub(super) fn new(layers: &'a HopSlotMap<LayerId, LanguageLayer>, root: LayerId) -> Self {
|
||||
let mut injection_ranges = Vec::new();
|
||||
|
||||
for (layer_id, layer) in layers.iter() {
|
||||
// Skip the root layer
|
||||
if layer.parent.is_none() {
|
||||
continue;
|
||||
}
|
||||
for byte_range in layer.ranges.iter() {
|
||||
let range = InjectionRange {
|
||||
start: byte_range.start_byte,
|
||||
end: byte_range.end_byte,
|
||||
layer_id,
|
||||
depth: layer.depth,
|
||||
};
|
||||
injection_ranges.push(range);
|
||||
}
|
||||
}
|
||||
|
||||
injection_ranges.sort_unstable_by_key(|range| (range.end, Reverse(range.depth)));
|
||||
|
||||
let cursor = layers[root].tree().root_node();
|
||||
|
||||
Self {
|
||||
layers,
|
||||
root,
|
||||
current: root,
|
||||
injection_ranges,
|
||||
cursor,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn node(&self) -> Node<'a> {
|
||||
self.cursor
|
||||
}
|
||||
|
||||
pub fn goto_parent(&mut self) -> bool {
|
||||
if let Some(parent) = self.node().parent() {
|
||||
self.cursor = parent;
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we are already on the root layer, we cannot ascend.
|
||||
if self.current == self.root {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ascend to the parent layer.
|
||||
let range = self.node().byte_range();
|
||||
let parent_id = self.layers[self.current]
|
||||
.parent
|
||||
.expect("non-root layers have a parent");
|
||||
self.current = parent_id;
|
||||
let root = self.layers[self.current].tree().root_node();
|
||||
self.cursor = root
|
||||
.descendant_for_byte_range(range.start, range.end)
|
||||
.unwrap_or(root);
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn goto_parent_with<P>(&mut self, predicate: P) -> bool
|
||||
where
|
||||
P: Fn(&Node) -> bool,
|
||||
{
|
||||
while self.goto_parent() {
|
||||
if predicate(&self.node()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Finds the injection layer that has exactly the same range as the given `range`.
|
||||
fn layer_id_of_byte_range(&self, search_range: Range<usize>) -> Option<LayerId> {
|
||||
let start_idx = self
|
||||
.injection_ranges
|
||||
.partition_point(|range| range.end < search_range.end);
|
||||
|
||||
self.injection_ranges[start_idx..]
|
||||
.iter()
|
||||
.take_while(|range| range.end == search_range.end)
|
||||
.find_map(|range| (range.start == search_range.start).then_some(range.layer_id))
|
||||
}
|
||||
|
||||
fn goto_first_child_impl(&mut self, named: bool) -> bool {
|
||||
// Check if the current node's range is an exact injection layer range.
|
||||
if let Some(layer_id) = self
|
||||
.layer_id_of_byte_range(self.node().byte_range())
|
||||
.filter(|&layer_id| layer_id != self.current)
|
||||
{
|
||||
// Switch to the child layer.
|
||||
self.current = layer_id;
|
||||
self.cursor = self.layers[self.current].tree().root_node();
|
||||
return true;
|
||||
}
|
||||
|
||||
let child = if named {
|
||||
self.cursor.named_child(0)
|
||||
} else {
|
||||
self.cursor.child(0)
|
||||
};
|
||||
|
||||
if let Some(child) = child {
|
||||
// Otherwise descend in the current tree.
|
||||
self.cursor = child;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn goto_first_child(&mut self) -> bool {
|
||||
self.goto_first_child_impl(false)
|
||||
}
|
||||
|
||||
pub fn goto_first_named_child(&mut self) -> bool {
|
||||
self.goto_first_child_impl(true)
|
||||
}
|
||||
|
||||
fn goto_next_sibling_impl(&mut self, named: bool) -> bool {
|
||||
let sibling = if named {
|
||||
self.cursor.next_named_sibling()
|
||||
} else {
|
||||
self.cursor.next_sibling()
|
||||
};
|
||||
|
||||
if let Some(sibling) = sibling {
|
||||
self.cursor = sibling;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn goto_next_sibling(&mut self) -> bool {
|
||||
self.goto_next_sibling_impl(false)
|
||||
}
|
||||
|
||||
pub fn goto_next_named_sibling(&mut self) -> bool {
|
||||
self.goto_next_sibling_impl(true)
|
||||
}
|
||||
|
||||
fn goto_prev_sibling_impl(&mut self, named: bool) -> bool {
|
||||
let sibling = if named {
|
||||
self.cursor.prev_named_sibling()
|
||||
} else {
|
||||
self.cursor.prev_sibling()
|
||||
};
|
||||
|
||||
if let Some(sibling) = sibling {
|
||||
self.cursor = sibling;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn goto_prev_sibling(&mut self) -> bool {
|
||||
self.goto_prev_sibling_impl(false)
|
||||
}
|
||||
|
||||
pub fn goto_prev_named_sibling(&mut self) -> bool {
|
||||
self.goto_prev_sibling_impl(true)
|
||||
}
|
||||
|
||||
/// Finds the injection layer that contains the given start-end range.
|
||||
fn layer_id_containing_byte_range(&self, start: usize, end: usize) -> LayerId {
|
||||
let start_idx = self
|
||||
.injection_ranges
|
||||
.partition_point(|range| range.end < end);
|
||||
|
||||
self.injection_ranges[start_idx..]
|
||||
.iter()
|
||||
.take_while(|range| range.start < end || range.depth > 1)
|
||||
.find_map(|range| (range.start <= start).then_some(range.layer_id))
|
||||
.unwrap_or(self.root)
|
||||
}
|
||||
|
||||
pub fn reset_to_byte_range(&mut self, start: usize, end: usize) {
|
||||
self.current = self.layer_id_containing_byte_range(start, end);
|
||||
let root = self.layers[self.current].tree().root_node();
|
||||
self.cursor = root.descendant_for_byte_range(start, end).unwrap_or(root);
|
||||
}
|
||||
|
||||
/// Returns an iterator over the children of the node the TreeCursor is on
|
||||
/// at the time this is called.
|
||||
pub fn children(&'a mut self) -> ChildIter<'a> {
|
||||
let parent = self.node();
|
||||
|
||||
ChildIter {
|
||||
cursor: self,
|
||||
parent,
|
||||
named: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over the named children of the node the TreeCursor is on
|
||||
/// at the time this is called.
|
||||
pub fn named_children(&'a mut self) -> ChildIter<'a> {
|
||||
let parent = self.node();
|
||||
|
||||
ChildIter {
|
||||
cursor: self,
|
||||
parent,
|
||||
named: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ChildIter<'n> {
|
||||
cursor: &'n mut TreeCursor<'n>,
|
||||
parent: Node<'n>,
|
||||
named: bool,
|
||||
}
|
||||
|
||||
impl<'n> Iterator for ChildIter<'n> {
|
||||
type Item = Node<'n>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// first iteration, just visit the first child
|
||||
if self.cursor.node() == self.parent {
|
||||
self.cursor
|
||||
.goto_first_child_impl(self.named)
|
||||
.then(|| self.cursor.node())
|
||||
} else {
|
||||
self.cursor
|
||||
.goto_next_sibling_impl(self.named)
|
||||
.then(|| self.cursor.node())
|
||||
}
|
||||
}
|
||||
}
|
@@ -65,7 +65,7 @@ pub fn print(s: &str) -> (String, Selection) {
|
||||
let head_at_beg = iter.next_if_eq(&"|").is_some();
|
||||
let last_grapheme = |s: &str| {
|
||||
UnicodeSegmentation::graphemes(s, true)
|
||||
.last()
|
||||
.next_back()
|
||||
.map(String::from)
|
||||
};
|
||||
|
||||
|
@@ -5,7 +5,7 @@ use std::ops::Range;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use crate::doc_formatter::FormattedGrapheme;
|
||||
use crate::syntax::Highlight;
|
||||
use crate::syntax::{Highlight, OverlayHighlights};
|
||||
use crate::{Position, Tendril};
|
||||
|
||||
/// An inline annotation is continuous text shown
|
||||
@@ -300,10 +300,7 @@ impl<'a> TextAnnotations<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collect_overlay_highlights(
|
||||
&self,
|
||||
char_range: Range<usize>,
|
||||
) -> Vec<(usize, Range<usize>)> {
|
||||
pub fn collect_overlay_highlights(&self, char_range: Range<usize>) -> OverlayHighlights {
|
||||
let mut highlights = Vec::new();
|
||||
self.reset_pos(char_range.start);
|
||||
for char_idx in char_range {
|
||||
@@ -311,11 +308,11 @@ impl<'a> TextAnnotations<'a> {
|
||||
// we don't know the number of chars the original grapheme takes
|
||||
// however it doesn't matter as highlight boundaries are automatically
|
||||
// aligned to grapheme boundaries in the rendering code
|
||||
highlights.push((highlight.0, char_idx..char_idx + 1))
|
||||
highlights.push((highlight, char_idx..char_idx + 1));
|
||||
}
|
||||
}
|
||||
|
||||
highlights
|
||||
OverlayHighlights::Heterogenous { highlights }
|
||||
}
|
||||
|
||||
/// Add new inline annotations.
|
||||
|
@@ -1,13 +1,12 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use ropey::RopeSlice;
|
||||
use tree_sitter::{Node, QueryCursor};
|
||||
|
||||
use crate::chars::{categorize_char, char_is_whitespace, CharCategory};
|
||||
use crate::graphemes::{next_grapheme_boundary, prev_grapheme_boundary};
|
||||
use crate::line_ending::rope_is_line_ending;
|
||||
use crate::movement::Direction;
|
||||
use crate::syntax::LanguageConfiguration;
|
||||
use crate::syntax;
|
||||
use crate::Range;
|
||||
use crate::{surround, Syntax};
|
||||
|
||||
@@ -260,18 +259,18 @@ pub fn textobject_treesitter(
|
||||
range: Range,
|
||||
textobject: TextObject,
|
||||
object_name: &str,
|
||||
slice_tree: Node,
|
||||
lang_config: &LanguageConfiguration,
|
||||
syntax: &Syntax,
|
||||
loader: &syntax::Loader,
|
||||
_count: usize,
|
||||
) -> Range {
|
||||
let root = syntax.tree().root_node();
|
||||
let textobject_query = loader.textobject_query(syntax.root_language());
|
||||
let get_range = move || -> Option<Range> {
|
||||
let byte_pos = slice.char_to_byte(range.cursor(slice));
|
||||
|
||||
let capture_name = format!("{}.{}", object_name, textobject); // eg. function.inner
|
||||
let mut cursor = QueryCursor::new();
|
||||
let node = lang_config
|
||||
.textobject_query()?
|
||||
.capture_nodes(&capture_name, slice_tree, slice, &mut cursor)?
|
||||
let node = textobject_query?
|
||||
.capture_nodes(&capture_name, &root, slice)?
|
||||
.filter(|node| node.byte_range().contains(&byte_pos))
|
||||
.min_by_key(|node| node.byte_range().len())?;
|
||||
|
||||
|
@@ -19,6 +19,16 @@ pub enum Operation {
|
||||
Insert(Tendril),
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
/// The number of characters affected by the operation.
|
||||
pub fn len_chars(&self) -> usize {
|
||||
match self {
|
||||
Self::Retain(n) | Self::Delete(n) => *n,
|
||||
Self::Insert(s) => s.chars().count(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Assoc {
|
||||
Before,
|
||||
|
@@ -1,12 +1,11 @@
|
||||
use arc_swap::ArcSwap;
|
||||
use helix_core::{
|
||||
indent::{indent_level_for_line, treesitter_indent_for_pos, IndentStyle},
|
||||
syntax::{Configuration, Loader},
|
||||
syntax::{config::Configuration, Loader},
|
||||
Syntax,
|
||||
};
|
||||
use helix_stdx::rope::RopeSliceExt;
|
||||
use ropey::Rope;
|
||||
use std::{ops::Range, path::PathBuf, process::Command, sync::Arc};
|
||||
use std::{ops::Range, path::PathBuf, process::Command};
|
||||
|
||||
#[test]
|
||||
fn test_treesitter_indent_rust() {
|
||||
@@ -196,17 +195,12 @@ fn test_treesitter_indent(
|
||||
runtime.push("../runtime");
|
||||
std::env::set_var("HELIX_RUNTIME", runtime.to_str().unwrap());
|
||||
|
||||
let language_config = loader.language_config_for_scope(lang_scope).unwrap();
|
||||
let language = loader.language_for_scope(lang_scope).unwrap();
|
||||
let language_config = loader.language(language).config();
|
||||
let indent_style = IndentStyle::from_str(&language_config.indent.as_ref().unwrap().unit);
|
||||
let highlight_config = language_config.highlight_config(&[]).unwrap();
|
||||
let text = doc.slice(..);
|
||||
let syntax = Syntax::new(
|
||||
text,
|
||||
highlight_config,
|
||||
Arc::new(ArcSwap::from_pointee(loader)),
|
||||
)
|
||||
.unwrap();
|
||||
let indent_query = language_config.indent_query().unwrap();
|
||||
let syntax = Syntax::new(text, language, &loader).unwrap();
|
||||
let indent_query = loader.indent_query(language).unwrap();
|
||||
|
||||
for i in 0..doc.len_lines() {
|
||||
let line = text.line(i);
|
||||
|
@@ -22,6 +22,11 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "net", "sync"] }
|
||||
thiserror.workspace = true
|
||||
slotmap.workspace = true
|
||||
futures-executor.workspace = true
|
||||
futures-util.workspace = true
|
||||
tokio-stream.workspace = true
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
fern = "0.7"
|
||||
|
@@ -1,10 +1,11 @@
|
||||
use crate::{
|
||||
requests::DisconnectArguments,
|
||||
registry::DebugAdapterId,
|
||||
requests::{DisconnectArguments, TerminateArguments},
|
||||
transport::{Payload, Request, Response, Transport},
|
||||
types::*,
|
||||
Error, Result,
|
||||
};
|
||||
use helix_core::syntax::DebuggerQuirks;
|
||||
use helix_core::syntax::config::{DebugAdapterConfig, DebuggerQuirks};
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
@@ -27,12 +28,14 @@ use tokio::{
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Client {
|
||||
id: usize,
|
||||
id: DebugAdapterId,
|
||||
_process: Option<Child>,
|
||||
server_tx: UnboundedSender<Payload>,
|
||||
request_counter: AtomicU64,
|
||||
connection_type: Option<ConnectionType>,
|
||||
starting_request_args: Option<Value>,
|
||||
/// The socket address of the debugger, if using TCP transport.
|
||||
pub socket: Option<SocketAddr>,
|
||||
pub caps: Option<DebuggerCapabilities>,
|
||||
// thread_id -> frames
|
||||
pub stack_frames: HashMap<ThreadId, Vec<StackFrame>>,
|
||||
@@ -41,23 +44,20 @@ pub struct Client {
|
||||
/// Currently active frame for the current thread.
|
||||
pub active_frame: Option<usize>,
|
||||
pub quirks: DebuggerQuirks,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ConnectionType {
|
||||
Launch,
|
||||
Attach,
|
||||
/// The config which was used to start this debugger.
|
||||
pub config: Option<DebugAdapterConfig>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
// Spawn a process and communicate with it by either TCP or stdio
|
||||
// The returned stream includes the Client ID so consumers can differentiate between multiple clients
|
||||
pub async fn process(
|
||||
transport: &str,
|
||||
command: &str,
|
||||
args: Vec<&str>,
|
||||
port_arg: Option<&str>,
|
||||
id: usize,
|
||||
) -> Result<(Self, UnboundedReceiver<Payload>)> {
|
||||
id: DebugAdapterId,
|
||||
) -> Result<(Self, UnboundedReceiver<(DebugAdapterId, Payload)>)> {
|
||||
if command.is_empty() {
|
||||
return Result::Err(Error::Other(anyhow!("Command not provided")));
|
||||
}
|
||||
@@ -72,9 +72,9 @@ impl Client {
|
||||
rx: Box<dyn AsyncBufRead + Unpin + Send>,
|
||||
tx: Box<dyn AsyncWrite + Unpin + Send>,
|
||||
err: Option<Box<dyn AsyncBufRead + Unpin + Send>>,
|
||||
id: usize,
|
||||
id: DebugAdapterId,
|
||||
process: Option<Child>,
|
||||
) -> Result<(Self, UnboundedReceiver<Payload>)> {
|
||||
) -> Result<(Self, UnboundedReceiver<(DebugAdapterId, Payload)>)> {
|
||||
let (server_rx, server_tx) = Transport::start(rx, tx, err, id);
|
||||
let (client_tx, client_rx) = unbounded_channel();
|
||||
|
||||
@@ -86,22 +86,24 @@ impl Client {
|
||||
caps: None,
|
||||
connection_type: None,
|
||||
starting_request_args: None,
|
||||
socket: None,
|
||||
stack_frames: HashMap::new(),
|
||||
thread_states: HashMap::new(),
|
||||
thread_id: None,
|
||||
active_frame: None,
|
||||
quirks: DebuggerQuirks::default(),
|
||||
config: None,
|
||||
};
|
||||
|
||||
tokio::spawn(Self::recv(server_rx, client_tx));
|
||||
tokio::spawn(Self::recv(id, server_rx, client_tx));
|
||||
|
||||
Ok((client, client_rx))
|
||||
}
|
||||
|
||||
pub async fn tcp(
|
||||
addr: std::net::SocketAddr,
|
||||
id: usize,
|
||||
) -> Result<(Self, UnboundedReceiver<Payload>)> {
|
||||
id: DebugAdapterId,
|
||||
) -> Result<(Self, UnboundedReceiver<(DebugAdapterId, Payload)>)> {
|
||||
let stream = TcpStream::connect(addr).await?;
|
||||
let (rx, tx) = stream.into_split();
|
||||
Self::streams(Box::new(BufReader::new(rx)), Box::new(tx), None, id, None)
|
||||
@@ -110,8 +112,8 @@ impl Client {
|
||||
pub fn stdio(
|
||||
cmd: &str,
|
||||
args: Vec<&str>,
|
||||
id: usize,
|
||||
) -> Result<(Self, UnboundedReceiver<Payload>)> {
|
||||
id: DebugAdapterId,
|
||||
) -> Result<(Self, UnboundedReceiver<(DebugAdapterId, Payload)>)> {
|
||||
// Resolve path to the binary
|
||||
let cmd = helix_stdx::env::which(cmd)?;
|
||||
|
||||
@@ -119,6 +121,7 @@ impl Client {
|
||||
.args(args)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
// make sure the process is reaped on drop
|
||||
.kill_on_drop(true)
|
||||
.spawn();
|
||||
@@ -128,16 +131,12 @@ impl Client {
|
||||
// TODO: do we need bufreader/writer here? or do we use async wrappers on unblock?
|
||||
let writer = BufWriter::new(process.stdin.take().expect("Failed to open stdin"));
|
||||
let reader = BufReader::new(process.stdout.take().expect("Failed to open stdout"));
|
||||
let errors = process.stderr.take().map(BufReader::new);
|
||||
let stderr = BufReader::new(process.stderr.take().expect("Failed to open stderr"));
|
||||
|
||||
Self::streams(
|
||||
Box::new(BufReader::new(reader)),
|
||||
Box::new(reader),
|
||||
Box::new(writer),
|
||||
// errors.map(|errors| Box::new(BufReader::new(errors))),
|
||||
match errors {
|
||||
Some(errors) => Some(Box::new(BufReader::new(errors))),
|
||||
None => None,
|
||||
},
|
||||
Some(Box::new(stderr)),
|
||||
id,
|
||||
Some(process),
|
||||
)
|
||||
@@ -165,8 +164,8 @@ impl Client {
|
||||
cmd: &str,
|
||||
args: Vec<&str>,
|
||||
port_format: &str,
|
||||
id: usize,
|
||||
) -> Result<(Self, UnboundedReceiver<Payload>)> {
|
||||
id: DebugAdapterId,
|
||||
) -> Result<(Self, UnboundedReceiver<(DebugAdapterId, Payload)>)> {
|
||||
let port = Self::get_port().await.unwrap();
|
||||
|
||||
let process = Command::new(cmd)
|
||||
@@ -181,40 +180,49 @@ impl Client {
|
||||
|
||||
// Wait for adapter to become ready for connection
|
||||
time::sleep(time::Duration::from_millis(500)).await;
|
||||
|
||||
let stream = TcpStream::connect(SocketAddr::new(
|
||||
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
|
||||
port,
|
||||
))
|
||||
.await?;
|
||||
let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port);
|
||||
let stream = TcpStream::connect(socket).await?;
|
||||
|
||||
let (rx, tx) = stream.into_split();
|
||||
Self::streams(
|
||||
let mut result = Self::streams(
|
||||
Box::new(BufReader::new(rx)),
|
||||
Box::new(tx),
|
||||
None,
|
||||
id,
|
||||
Some(process),
|
||||
)
|
||||
);
|
||||
|
||||
// Set the socket address for the client
|
||||
if let Ok((client, _)) = &mut result {
|
||||
client.socket = Some(socket);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
async fn recv(mut server_rx: UnboundedReceiver<Payload>, client_tx: UnboundedSender<Payload>) {
|
||||
async fn recv(
|
||||
id: DebugAdapterId,
|
||||
mut server_rx: UnboundedReceiver<Payload>,
|
||||
client_tx: UnboundedSender<(DebugAdapterId, Payload)>,
|
||||
) {
|
||||
while let Some(msg) = server_rx.recv().await {
|
||||
match msg {
|
||||
Payload::Event(ev) => {
|
||||
client_tx.send(Payload::Event(ev)).expect("Failed to send");
|
||||
client_tx
|
||||
.send((id, Payload::Event(ev)))
|
||||
.expect("Failed to send");
|
||||
}
|
||||
Payload::Response(_) => unreachable!(),
|
||||
Payload::Request(req) => {
|
||||
client_tx
|
||||
.send(Payload::Request(req))
|
||||
.send((id, Payload::Request(req)))
|
||||
.expect("Failed to send");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> usize {
|
||||
pub fn id(&self) -> DebugAdapterId {
|
||||
self.id
|
||||
}
|
||||
|
||||
@@ -357,6 +365,14 @@ impl Client {
|
||||
self.call::<requests::Disconnect>(args)
|
||||
}
|
||||
|
||||
pub fn terminate(
|
||||
&mut self,
|
||||
args: Option<TerminateArguments>,
|
||||
) -> impl Future<Output = Result<Value>> {
|
||||
self.connection_type = None;
|
||||
self.call::<requests::Terminate>(args)
|
||||
}
|
||||
|
||||
pub fn launch(&mut self, args: serde_json::Value) -> impl Future<Output = Result<Value>> {
|
||||
self.connection_type = Some(ConnectionType::Launch);
|
||||
self.starting_request_args = Some(args.clone());
|
||||
|
@@ -1,12 +1,14 @@
|
||||
mod client;
|
||||
pub mod registry;
|
||||
mod transport;
|
||||
mod types;
|
||||
|
||||
pub use client::{Client, ConnectionType};
|
||||
pub use events::Event;
|
||||
pub use client::Client;
|
||||
pub use transport::{Payload, Response, Transport};
|
||||
pub use types::*;
|
||||
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
use thiserror::Error;
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
@@ -18,9 +20,86 @@ pub enum Error {
|
||||
Timeout(u64),
|
||||
#[error("server closed the stream")]
|
||||
StreamClosed,
|
||||
#[error("Unhandled")]
|
||||
Unhandled,
|
||||
#[error(transparent)]
|
||||
ExecutableNotFound(#[from] helix_stdx::env::ExecutableNotFoundError),
|
||||
#[error(transparent)]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
pub type Result<T> = core::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Request {
|
||||
RunInTerminal(<requests::RunInTerminal as types::Request>::Arguments),
|
||||
StartDebugging(<requests::StartDebugging as types::Request>::Arguments),
|
||||
}
|
||||
|
||||
impl Request {
|
||||
pub fn parse(command: &str, arguments: Option<serde_json::Value>) -> Result<Self> {
|
||||
use crate::types::Request as _;
|
||||
|
||||
let arguments = arguments.unwrap_or_default();
|
||||
let request = match command {
|
||||
requests::RunInTerminal::COMMAND => Self::RunInTerminal(parse_value(arguments)?),
|
||||
requests::StartDebugging::COMMAND => Self::StartDebugging(parse_value(arguments)?),
|
||||
_ => return Err(Error::Unhandled),
|
||||
};
|
||||
|
||||
Ok(request)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Event {
|
||||
Initialized(<events::Initialized as events::Event>::Body),
|
||||
Stopped(<events::Stopped as events::Event>::Body),
|
||||
Continued(<events::Continued as events::Event>::Body),
|
||||
Exited(<events::Exited as events::Event>::Body),
|
||||
Terminated(<events::Terminated as events::Event>::Body),
|
||||
Thread(<events::Thread as events::Event>::Body),
|
||||
Output(<events::Output as events::Event>::Body),
|
||||
Breakpoint(<events::Breakpoint as events::Event>::Body),
|
||||
Module(<events::Module as events::Event>::Body),
|
||||
LoadedSource(<events::LoadedSource as events::Event>::Body),
|
||||
Process(<events::Process as events::Event>::Body),
|
||||
Capabilities(<events::Capabilities as events::Event>::Body),
|
||||
// ProgressStart(),
|
||||
// ProgressUpdate(),
|
||||
// ProgressEnd(),
|
||||
// Invalidated(),
|
||||
Memory(<events::Memory as events::Event>::Body),
|
||||
}
|
||||
|
||||
impl Event {
|
||||
pub fn parse(event: &str, body: Option<serde_json::Value>) -> Result<Self> {
|
||||
use crate::events::Event as _;
|
||||
|
||||
let body = body.unwrap_or_default();
|
||||
let event = match event {
|
||||
events::Initialized::EVENT => Self::Initialized(parse_value(body)?),
|
||||
events::Stopped::EVENT => Self::Stopped(parse_value(body)?),
|
||||
events::Continued::EVENT => Self::Continued(parse_value(body)?),
|
||||
events::Exited::EVENT => Self::Exited(parse_value(body)?),
|
||||
events::Terminated::EVENT => Self::Terminated(parse_value(body)?),
|
||||
events::Thread::EVENT => Self::Thread(parse_value(body)?),
|
||||
events::Output::EVENT => Self::Output(parse_value(body)?),
|
||||
events::Breakpoint::EVENT => Self::Breakpoint(parse_value(body)?),
|
||||
events::Module::EVENT => Self::Module(parse_value(body)?),
|
||||
events::LoadedSource::EVENT => Self::LoadedSource(parse_value(body)?),
|
||||
events::Process::EVENT => Self::Process(parse_value(body)?),
|
||||
events::Capabilities::EVENT => Self::Capabilities(parse_value(body)?),
|
||||
events::Memory::EVENT => Self::Memory(parse_value(body)?),
|
||||
_ => return Err(Error::Unhandled),
|
||||
};
|
||||
|
||||
Ok(event)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_value<T>(value: serde_json::Value) -> Result<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
serde_json::from_value(value).map_err(|err| err.into())
|
||||
}
|
||||
|
114
helix-dap/src/registry.rs
Normal file
114
helix-dap/src/registry.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use crate::{Client, Payload, Result, StackFrame};
|
||||
use futures_executor::block_on;
|
||||
use futures_util::stream::SelectAll;
|
||||
use helix_core::syntax::config::DebugAdapterConfig;
|
||||
use slotmap::SlotMap;
|
||||
use std::fmt;
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
|
||||
/// The resgistry is a struct that manages and owns multiple debugger clients
|
||||
/// This holds the responsibility of managing the lifecycle of each client
|
||||
/// plus showing the heirarcihical nature betweeen them
|
||||
pub struct Registry {
|
||||
inner: SlotMap<DebugAdapterId, Client>,
|
||||
/// The active debugger client
|
||||
///
|
||||
/// TODO: You can have multiple active debuggers, so the concept of a single active debugger
|
||||
/// may need to be changed
|
||||
current_client_id: Option<DebugAdapterId>,
|
||||
/// A stream of incoming messages from all debuggers
|
||||
pub incoming: SelectAll<UnboundedReceiverStream<(DebugAdapterId, Payload)>>,
|
||||
}
|
||||
|
||||
impl Registry {
|
||||
/// Creates a new DebuggerService instance
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: SlotMap::with_key(),
|
||||
current_client_id: None,
|
||||
incoming: SelectAll::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_client(
|
||||
&mut self,
|
||||
socket: Option<std::net::SocketAddr>,
|
||||
config: &DebugAdapterConfig,
|
||||
) -> Result<DebugAdapterId> {
|
||||
self.inner.try_insert_with_key(|id| {
|
||||
let result = match socket {
|
||||
Some(socket) => block_on(Client::tcp(socket, id)),
|
||||
None => block_on(Client::process(
|
||||
&config.transport,
|
||||
&config.command,
|
||||
config.args.iter().map(|arg| arg.as_str()).collect(),
|
||||
config.port_arg.as_deref(),
|
||||
id,
|
||||
)),
|
||||
};
|
||||
|
||||
let (mut client, receiver) = result?;
|
||||
self.incoming.push(UnboundedReceiverStream::new(receiver));
|
||||
|
||||
client.config = Some(config.clone());
|
||||
block_on(client.initialize(config.name.clone()))?;
|
||||
client.quirks = config.quirks.clone();
|
||||
|
||||
Ok(client)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn remove_client(&mut self, id: DebugAdapterId) {
|
||||
self.inner.remove(id);
|
||||
}
|
||||
|
||||
pub fn get_client(&self, id: DebugAdapterId) -> Option<&Client> {
|
||||
self.inner.get(id)
|
||||
}
|
||||
|
||||
pub fn get_client_mut(&mut self, id: DebugAdapterId) -> Option<&mut Client> {
|
||||
self.inner.get_mut(id)
|
||||
}
|
||||
|
||||
pub fn get_active_client(&self) -> Option<&Client> {
|
||||
self.current_client_id.and_then(|id| self.get_client(id))
|
||||
}
|
||||
|
||||
pub fn get_active_client_mut(&mut self) -> Option<&mut Client> {
|
||||
self.current_client_id
|
||||
.and_then(|id| self.get_client_mut(id))
|
||||
}
|
||||
|
||||
pub fn set_active_client(&mut self, id: DebugAdapterId) {
|
||||
if self.get_client(id).is_some() {
|
||||
self.current_client_id = Some(id);
|
||||
} else {
|
||||
self.current_client_id = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unset_active_client(&mut self) {
|
||||
self.current_client_id = None;
|
||||
}
|
||||
|
||||
pub fn current_stack_frame(&self) -> Option<&StackFrame> {
|
||||
self.get_active_client()
|
||||
.and_then(|debugger| debugger.current_stack_frame())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Registry {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
slotmap::new_key_type! {
|
||||
pub struct DebugAdapterId;
|
||||
}
|
||||
|
||||
impl fmt::Display for DebugAdapterId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self.0)
|
||||
}
|
||||
}
|
@@ -1,10 +1,10 @@
|
||||
use crate::{Error, Event, Result};
|
||||
use crate::{registry::DebugAdapterId, Error, Result};
|
||||
use anyhow::Context;
|
||||
use log::{error, info, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::{collections::HashMap, fmt::Debug};
|
||||
use tokio::{
|
||||
io::{AsyncBufRead, AsyncBufReadExt, AsyncReadExt, AsyncWrite, AsyncWriteExt},
|
||||
sync::{
|
||||
@@ -32,11 +32,17 @@ pub struct Response {
|
||||
pub body: Option<Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
pub struct Event {
|
||||
pub event: String,
|
||||
pub body: Option<Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum Payload {
|
||||
// type = "event"
|
||||
Event(Box<Event>),
|
||||
Event(Event),
|
||||
// type = "response"
|
||||
Response(Response),
|
||||
// type = "request"
|
||||
@@ -46,7 +52,7 @@ pub enum Payload {
|
||||
#[derive(Debug)]
|
||||
pub struct Transport {
|
||||
#[allow(unused)]
|
||||
id: usize,
|
||||
id: DebugAdapterId,
|
||||
pending_requests: Mutex<HashMap<u64, Sender<Result<Response>>>>,
|
||||
}
|
||||
|
||||
@@ -55,7 +61,7 @@ impl Transport {
|
||||
server_stdout: Box<dyn AsyncBufRead + Unpin + Send>,
|
||||
server_stdin: Box<dyn AsyncWrite + Unpin + Send>,
|
||||
server_stderr: Option<Box<dyn AsyncBufRead + Unpin + Send>>,
|
||||
id: usize,
|
||||
id: DebugAdapterId,
|
||||
) -> (UnboundedReceiver<Payload>, UnboundedSender<Payload>) {
|
||||
let (client_tx, rx) = unbounded_channel();
|
||||
let (tx, client_rx) = unbounded_channel();
|
||||
@@ -67,7 +73,7 @@ impl Transport {
|
||||
|
||||
let transport = Arc::new(transport);
|
||||
|
||||
tokio::spawn(Self::recv(transport.clone(), server_stdout, client_tx));
|
||||
tokio::spawn(Self::recv(id, transport.clone(), server_stdout, client_tx));
|
||||
tokio::spawn(Self::send(transport, server_stdin, client_rx));
|
||||
if let Some(stderr) = server_stderr {
|
||||
tokio::spawn(Self::err(stderr));
|
||||
@@ -77,12 +83,14 @@ impl Transport {
|
||||
}
|
||||
|
||||
async fn recv_server_message(
|
||||
id: DebugAdapterId,
|
||||
reader: &mut Box<dyn AsyncBufRead + Unpin + Send>,
|
||||
buffer: &mut String,
|
||||
content: &mut Vec<u8>,
|
||||
) -> Result<Payload> {
|
||||
let mut content_length = None;
|
||||
loop {
|
||||
buffer.truncate(0);
|
||||
buffer.clear();
|
||||
if reader.read_line(buffer).await? == 0 {
|
||||
return Err(Error::StreamClosed);
|
||||
};
|
||||
@@ -111,17 +119,17 @@ impl Transport {
|
||||
}
|
||||
|
||||
let content_length = content_length.context("missing content length")?;
|
||||
content.resize(content_length, 0);
|
||||
reader.read_exact(content).await?;
|
||||
let msg = std::str::from_utf8(content).context("invalid utf8 from server")?;
|
||||
|
||||
//TODO: reuse vector
|
||||
let mut content = vec![0; content_length];
|
||||
reader.read_exact(&mut content).await?;
|
||||
let msg = std::str::from_utf8(&content).context("invalid utf8 from server")?;
|
||||
|
||||
info!("<- DAP {}", msg);
|
||||
info!("[{}] <- DAP {}", id, msg);
|
||||
|
||||
// try parsing as output (server response) or call (server request)
|
||||
let output: serde_json::Result<Payload> = serde_json::from_str(msg);
|
||||
|
||||
content.clear();
|
||||
|
||||
Ok(output?)
|
||||
}
|
||||
|
||||
@@ -157,7 +165,7 @@ impl Transport {
|
||||
server_stdin: &mut Box<dyn AsyncWrite + Unpin + Send>,
|
||||
request: String,
|
||||
) -> Result<()> {
|
||||
info!("-> DAP {}", request);
|
||||
info!("[{}] -> DAP {}", self.id, request);
|
||||
|
||||
// send the headers
|
||||
server_stdin
|
||||
@@ -172,15 +180,18 @@ impl Transport {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_response(res: Response) -> Result<Response> {
|
||||
fn process_response(&self, res: Response) -> Result<Response> {
|
||||
if res.success {
|
||||
info!("<- DAP success in response to {}", res.request_seq);
|
||||
info!(
|
||||
"[{}] <- DAP success in response to {}",
|
||||
self.id, res.request_seq
|
||||
);
|
||||
|
||||
Ok(res)
|
||||
} else {
|
||||
error!(
|
||||
"<- DAP error {:?} ({:?}) for command #{} {}",
|
||||
res.message, res.body, res.request_seq, res.command
|
||||
"[{}] <- DAP error {:?} ({:?}) for command #{} {}",
|
||||
self.id, res.message, res.body, res.request_seq, res.command
|
||||
);
|
||||
|
||||
Err(Error::Other(anyhow::format_err!("{:?}", res.body)))
|
||||
@@ -198,7 +209,7 @@ impl Transport {
|
||||
let tx = self.pending_requests.lock().await.remove(&request_seq);
|
||||
|
||||
match tx {
|
||||
Some(tx) => match tx.send(Self::process_response(res)).await {
|
||||
Some(tx) => match tx.send(self.process_response(res)).await {
|
||||
Ok(_) => (),
|
||||
Err(_) => error!(
|
||||
"Tried sending response into a closed channel (id={:?}), original request likely timed out",
|
||||
@@ -218,37 +229,58 @@ impl Transport {
|
||||
ref seq,
|
||||
..
|
||||
}) => {
|
||||
info!("<- DAP request {} #{}", command, seq);
|
||||
info!("[{}] <- DAP request {} #{}", self.id, command, seq);
|
||||
client_tx.send(msg).expect("Failed to send");
|
||||
Ok(())
|
||||
}
|
||||
Payload::Event(ref event) => {
|
||||
info!("<- DAP event {:?}", event);
|
||||
info!("[{}] <- DAP event {:?}", self.id, event);
|
||||
client_tx.send(msg).expect("Failed to send");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn recv_inner(
|
||||
async fn recv(
|
||||
id: DebugAdapterId,
|
||||
transport: Arc<Self>,
|
||||
mut server_stdout: Box<dyn AsyncBufRead + Unpin + Send>,
|
||||
client_tx: UnboundedSender<Payload>,
|
||||
) -> Result<()> {
|
||||
let mut recv_buffer = String::new();
|
||||
loop {
|
||||
let msg = Self::recv_server_message(&mut server_stdout, &mut recv_buffer).await?;
|
||||
transport.process_server_message(&client_tx, msg).await?;
|
||||
}
|
||||
}
|
||||
|
||||
async fn recv(
|
||||
transport: Arc<Self>,
|
||||
server_stdout: Box<dyn AsyncBufRead + Unpin + Send>,
|
||||
client_tx: UnboundedSender<Payload>,
|
||||
) {
|
||||
if let Err(err) = Self::recv_inner(transport, server_stdout, client_tx).await {
|
||||
error!("err: <- {:?}", err);
|
||||
let mut recv_buffer = String::new();
|
||||
let mut content_buffer = Vec::new();
|
||||
loop {
|
||||
match Self::recv_server_message(
|
||||
id,
|
||||
&mut server_stdout,
|
||||
&mut recv_buffer,
|
||||
&mut content_buffer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(msg) => match transport.process_server_message(&client_tx, msg).await {
|
||||
Ok(_) => (),
|
||||
Err(err) => {
|
||||
error!(" [{id}] err: <- {err:?}");
|
||||
break;
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
if !matches!(err, Error::StreamClosed) {
|
||||
error!("Exiting after unexpected error: {err:?}");
|
||||
}
|
||||
|
||||
// Close any outstanding requests.
|
||||
for (id, tx) in transport.pending_requests.lock().await.drain() {
|
||||
match tx.send(Err(Error::StreamClosed)).await {
|
||||
Ok(_) => (),
|
||||
Err(_) => {
|
||||
error!("Could not close request on a closed channel (id={id})");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -438,6 +438,21 @@ pub mod requests {
|
||||
const COMMAND: &'static str = "disconnect";
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TerminateArguments {
|
||||
pub restart: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Terminate {}
|
||||
|
||||
impl Request for Terminate {
|
||||
type Arguments = Option<TerminateArguments>;
|
||||
type Result = ();
|
||||
const COMMAND: &'static str = "terminate";
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ConfigurationDone {}
|
||||
|
||||
@@ -752,6 +767,21 @@ pub mod requests {
|
||||
type Result = RunInTerminalResponse;
|
||||
const COMMAND: &'static str = "runInTerminal";
|
||||
}
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StartDebuggingArguments {
|
||||
pub request: ConnectionType,
|
||||
pub configuration: Value,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StartDebugging {}
|
||||
|
||||
impl Request for StartDebugging {
|
||||
type Arguments = StartDebuggingArguments;
|
||||
type Result = ();
|
||||
const COMMAND: &'static str = "startDebugging";
|
||||
}
|
||||
}
|
||||
|
||||
// Events
|
||||
@@ -759,33 +789,30 @@ pub mod requests {
|
||||
pub mod events {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(tag = "event", content = "body")]
|
||||
// seq is omitted as unused and is not sent by some implementations
|
||||
pub enum Event {
|
||||
Initialized(Option<DebuggerCapabilities>),
|
||||
Stopped(Stopped),
|
||||
Continued(Continued),
|
||||
Exited(Exited),
|
||||
Terminated(Option<Terminated>),
|
||||
Thread(Thread),
|
||||
Output(Output),
|
||||
Breakpoint(Breakpoint),
|
||||
Module(Module),
|
||||
LoadedSource(LoadedSource),
|
||||
Process(Process),
|
||||
Capabilities(Capabilities),
|
||||
// ProgressStart(),
|
||||
// ProgressUpdate(),
|
||||
// ProgressEnd(),
|
||||
// Invalidated(),
|
||||
Memory(Memory),
|
||||
pub trait Event {
|
||||
type Body: serde::de::DeserializeOwned + serde::Serialize;
|
||||
const EVENT: &'static str;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Initialized {}
|
||||
|
||||
impl Event for Initialized {
|
||||
type Body = Option<DebuggerCapabilities>;
|
||||
const EVENT: &'static str = "initialized";
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Stopped {}
|
||||
|
||||
impl Event for Stopped {
|
||||
type Body = StoppedBody;
|
||||
const EVENT: &'static str = "stopped";
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Stopped {
|
||||
pub struct StoppedBody {
|
||||
pub reason: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
@@ -801,37 +828,77 @@ pub mod events {
|
||||
pub hit_breakpoint_ids: Option<Vec<usize>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Continued {}
|
||||
|
||||
impl Event for Continued {
|
||||
type Body = ContinuedBody;
|
||||
const EVENT: &'static str = "continued";
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Continued {
|
||||
pub struct ContinuedBody {
|
||||
pub thread_id: ThreadId,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub all_threads_continued: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Exited {
|
||||
pub exit_code: usize,
|
||||
#[derive(Debug)]
|
||||
pub enum Exited {}
|
||||
|
||||
impl Event for Exited {
|
||||
type Body = ExitedBody;
|
||||
const EVENT: &'static str = "exited";
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Terminated {
|
||||
pub struct ExitedBody {
|
||||
pub exit_code: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Terminated {}
|
||||
|
||||
impl Event for Terminated {
|
||||
type Body = Option<TerminatedBody>;
|
||||
const EVENT: &'static str = "terminated";
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TerminatedBody {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub restart: Option<Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Thread {
|
||||
pub reason: String,
|
||||
pub thread_id: ThreadId,
|
||||
#[derive(Debug)]
|
||||
pub enum Thread {}
|
||||
|
||||
impl Event for Thread {
|
||||
type Body = ThreadBody;
|
||||
const EVENT: &'static str = "thread";
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Output {
|
||||
pub struct ThreadBody {
|
||||
pub reason: String,
|
||||
pub thread_id: ThreadId,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Output {}
|
||||
|
||||
impl Event for Output {
|
||||
type Body = OutputBody;
|
||||
const EVENT: &'static str = "output";
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OutputBody {
|
||||
pub output: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub category: Option<String>,
|
||||
@@ -849,30 +916,62 @@ pub mod events {
|
||||
pub data: Option<Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Breakpoint {}
|
||||
|
||||
impl Event for Breakpoint {
|
||||
type Body = BreakpointBody;
|
||||
const EVENT: &'static str = "breakpoint";
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Breakpoint {
|
||||
pub struct BreakpointBody {
|
||||
pub reason: String,
|
||||
pub breakpoint: super::Breakpoint,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Module {}
|
||||
|
||||
impl Event for Module {
|
||||
type Body = ModuleBody;
|
||||
const EVENT: &'static str = "module";
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Module {
|
||||
pub struct ModuleBody {
|
||||
pub reason: String,
|
||||
pub module: super::Module,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LoadedSource {
|
||||
pub reason: String,
|
||||
pub source: super::Source,
|
||||
#[derive(Debug)]
|
||||
pub enum LoadedSource {}
|
||||
|
||||
impl Event for LoadedSource {
|
||||
type Body = LoadedSourceBody;
|
||||
const EVENT: &'static str = "loadedSource";
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Process {
|
||||
pub struct LoadedSourceBody {
|
||||
pub reason: String,
|
||||
pub source: super::Source,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Process {}
|
||||
|
||||
impl Event for Process {
|
||||
type Body = ProcessBody;
|
||||
const EVENT: &'static str = "process";
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProcessBody {
|
||||
pub name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub system_process_id: Option<usize>,
|
||||
@@ -884,39 +983,62 @@ pub mod events {
|
||||
pub pointer_size: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Capabilities {}
|
||||
|
||||
impl Event for Capabilities {
|
||||
type Body = CapabilitiesBody;
|
||||
const EVENT: &'static str = "capabilities";
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Capabilities {
|
||||
pub struct CapabilitiesBody {
|
||||
pub capabilities: super::DebuggerCapabilities,
|
||||
}
|
||||
|
||||
// #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
// #[serde(rename_all = "camelCase")]
|
||||
// pub struct Invalidated {
|
||||
// pub struct InvalidatedBody {
|
||||
// pub areas: Vec<InvalidatedArea>,
|
||||
// pub thread_id: Option<ThreadId>,
|
||||
// pub stack_frame_id: Option<usize>,
|
||||
// }
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Memory {}
|
||||
|
||||
impl Event for Memory {
|
||||
type Body = MemoryBody;
|
||||
const EVENT: &'static str = "memory";
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Memory {
|
||||
pub struct MemoryBody {
|
||||
pub memory_reference: String,
|
||||
pub offset: usize,
|
||||
pub count: usize,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_module_id_from_number() {
|
||||
let raw = r#"{"id": 0, "name": "Name"}"#;
|
||||
let module: super::Module = serde_json::from_str(raw).expect("Error!");
|
||||
assert_eq!(module.id, "0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_module_id_from_string() {
|
||||
let raw = r#"{"id": "0", "name": "Name"}"#;
|
||||
let module: super::Module = serde_json::from_str(raw).expect("Error!");
|
||||
assert_eq!(module.id, "0");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ConnectionType {
|
||||
Launch,
|
||||
Attach,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_module_id_from_number() {
|
||||
let raw = r#"{"id": 0, "name": "Name"}"#;
|
||||
let module: Module = serde_json::from_str(raw).expect("Error!");
|
||||
assert_eq!(module.id, "0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_module_id_from_string() {
|
||||
let raw = r#"{"id": "0", "name": "Name"}"#;
|
||||
let module: Module = serde_json::from_str(raw).expect("Error!");
|
||||
assert_eq!(module.id, "0");
|
||||
}
|
||||
|
@@ -12,14 +12,14 @@ homepage.workspace = true
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
ahash = "0.8.11"
|
||||
hashbrown = "0.14.5"
|
||||
foldhash.workspace = true
|
||||
hashbrown = "0.16"
|
||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "parking_lot", "macros"] }
|
||||
# the event registry is essentially read only but must be an rwlock so we can
|
||||
# setup new events on initialization, hardware-lock-elision hugely benefits this case
|
||||
# as it essentially makes the lock entirely free as long as there is no writes
|
||||
parking_lot = { version = "0.12", features = ["hardware-lock-elision"] }
|
||||
once_cell = "1.20"
|
||||
parking_lot = { workspace = true, features = ["hardware-lock-elision"] }
|
||||
once_cell = "1.21"
|
||||
|
||||
anyhow = "1"
|
||||
log = "0.4"
|
||||
|
@@ -14,8 +14,8 @@ use crate::hook::ErasedHook;
|
||||
use crate::runtime_local;
|
||||
|
||||
pub struct Registry {
|
||||
events: HashMap<&'static str, TypeId, ahash::RandomState>,
|
||||
handlers: HashMap<&'static str, Vec<ErasedHook>, ahash::RandomState>,
|
||||
events: HashMap<&'static str, TypeId, foldhash::fast::FixedState>,
|
||||
handlers: HashMap<&'static str, Vec<ErasedHook>, foldhash::fast::FixedState>,
|
||||
}
|
||||
|
||||
impl Registry {
|
||||
@@ -105,8 +105,8 @@ runtime_local! {
|
||||
static REGISTRY: RwLock<Registry> = RwLock::new(Registry {
|
||||
// hardcoded random number is good enough here we don't care about DOS resistance
|
||||
// and avoids the additional complexity of `Option<Registry>`
|
||||
events: HashMap::with_hasher(ahash::RandomState::with_seeds(423, 9978, 38322, 3280080)),
|
||||
handlers: HashMap::with_hasher(ahash::RandomState::with_seeds(423, 99078, 382322, 3282938)),
|
||||
events: HashMap::with_hasher(foldhash::fast::FixedState::with_seed(72536814787)),
|
||||
handlers: HashMap::with_hasher(foldhash::fast::FixedState::with_seed(72536814787)),
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -41,8 +41,9 @@ macro_rules! runtime_local {
|
||||
|
||||
#[cfg(feature = "integration_test")]
|
||||
pub struct RuntimeLocal<T: 'static> {
|
||||
data:
|
||||
parking_lot::RwLock<hashbrown::HashMap<tokio::runtime::Id, &'static T, ahash::RandomState>>,
|
||||
data: parking_lot::RwLock<
|
||||
hashbrown::HashMap<tokio::runtime::Id, &'static T, foldhash::fast::FixedState>,
|
||||
>,
|
||||
init: fn() -> T,
|
||||
}
|
||||
|
||||
@@ -53,7 +54,7 @@ impl<T> RuntimeLocal<T> {
|
||||
pub const fn __new(init: fn() -> T) -> Self {
|
||||
Self {
|
||||
data: parking_lot::RwLock::new(hashbrown::HashMap::with_hasher(
|
||||
ahash::RandomState::with_seeds(423, 9978, 38322, 3280080),
|
||||
foldhash::fast::FixedState::with_seed(12345678910),
|
||||
)),
|
||||
init,
|
||||
}
|
||||
|
@@ -56,6 +56,7 @@ fn smoke_test() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(dead_code)]
|
||||
fn dynamic() {
|
||||
events! {
|
||||
Event3 {}
|
||||
|
@@ -19,10 +19,9 @@ helix-stdx = { path = "../helix-stdx" }
|
||||
|
||||
anyhow = "1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
toml = "0.8"
|
||||
etcetera = "0.8"
|
||||
tree-sitter.workspace = true
|
||||
once_cell = "1.20"
|
||||
toml.workspace = true
|
||||
etcetera = "0.10"
|
||||
once_cell = "1.21"
|
||||
log = "0.4"
|
||||
|
||||
# TODO: these two should be on !wasm32 only
|
||||
@@ -30,8 +29,6 @@ log = "0.4"
|
||||
# cloning/compiling tree-sitter grammars
|
||||
cc = { version = "1" }
|
||||
threadpool = { version = "1.0" }
|
||||
tempfile = "3.14.0"
|
||||
dunce = "1.0.5"
|
||||
tempfile.workspace = true
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
libloading = "0.8"
|
||||
tree-house.workspace = true
|
||||
|
@@ -6,23 +6,26 @@ const MAJOR: &str = env!("CARGO_PKG_VERSION_MAJOR");
|
||||
const MINOR: &str = env!("CARGO_PKG_VERSION_MINOR");
|
||||
const PATCH: &str = env!("CARGO_PKG_VERSION_PATCH");
|
||||
|
||||
fn get_calver() -> String {
|
||||
if PATCH == "0" {
|
||||
format!("{MAJOR}.{MINOR}")
|
||||
} else {
|
||||
format!("{MAJOR}.{MINOR}.{PATCH}")
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let git_hash = Command::new("git")
|
||||
.args(["rev-parse", "HEAD"])
|
||||
.output()
|
||||
.ok()
|
||||
.filter(|output| output.status.success())
|
||||
.and_then(|x| String::from_utf8(x.stdout).ok());
|
||||
.and_then(|x| String::from_utf8(x.stdout).ok())
|
||||
.or_else(|| option_env!("HELIX_NIX_BUILD_REV").map(|s| s.to_string()));
|
||||
|
||||
let calver = get_calver();
|
||||
let minor = if MINOR.len() == 1 {
|
||||
// Print single-digit months in '0M' format
|
||||
format!("0{MINOR}")
|
||||
} else {
|
||||
MINOR.to_string()
|
||||
};
|
||||
let calver = if PATCH == "0" {
|
||||
format!("{MAJOR}.{minor}")
|
||||
} else {
|
||||
format!("{MAJOR}.{minor}.{PATCH}")
|
||||
};
|
||||
let version: Cow<_> = match &git_hash {
|
||||
Some(git_hash) => format!("{} ({})", calver, &git_hash[..8]).into(),
|
||||
None => calver.into(),
|
||||
|
@@ -23,22 +23,6 @@ pub fn user_lang_config() -> Result<toml::Value, toml::de::Error> {
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.into_iter()
|
||||
.fold(default_lang_config(), |a, b| {
|
||||
// combines for example
|
||||
// b:
|
||||
// [[language]]
|
||||
// name = "toml"
|
||||
// language-server = { command = "taplo", args = ["lsp", "stdio"] }
|
||||
//
|
||||
// a:
|
||||
// [[language]]
|
||||
// language-server = { command = "/usr/bin/taplo" }
|
||||
//
|
||||
// into:
|
||||
// [[language]]
|
||||
// name = "toml"
|
||||
// language-server = { command = "/usr/bin/taplo" }
|
||||
//
|
||||
// thus it overrides the third depth-level of b with values of a if they exist, but otherwise merges their values
|
||||
crate::merge_toml_values(a, b, 3)
|
||||
});
|
||||
|
||||
|
@@ -9,7 +9,7 @@ use std::{
|
||||
sync::mpsc::channel,
|
||||
};
|
||||
use tempfile::TempPath;
|
||||
use tree_sitter::Language;
|
||||
use tree_house::tree_sitter::Grammar;
|
||||
|
||||
#[cfg(unix)]
|
||||
const DYLIB_EXTENSION: &str = "so";
|
||||
@@ -61,28 +61,21 @@ const BUILD_TARGET: &str = env!("BUILD_TARGET");
|
||||
const REMOTE_NAME: &str = "origin";
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn get_language(name: &str) -> Result<Language> {
|
||||
pub fn get_language(name: &str) -> Result<Option<Grammar>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn get_language(name: &str) -> Result<Language> {
|
||||
use libloading::{Library, Symbol};
|
||||
pub fn get_language(name: &str) -> Result<Option<Grammar>> {
|
||||
let mut rel_library_path = PathBuf::new().join("grammars").join(name);
|
||||
rel_library_path.set_extension(DYLIB_EXTENSION);
|
||||
let library_path = crate::runtime_file(&rel_library_path);
|
||||
if !library_path.exists() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let library = unsafe { Library::new(&library_path) }
|
||||
.with_context(|| format!("Error opening dynamic library {:?}", library_path))?;
|
||||
let language_fn_name = format!("tree_sitter_{}", name.replace('-', "_"));
|
||||
let language = unsafe {
|
||||
let language_fn: Symbol<unsafe extern "C" fn() -> Language> = library
|
||||
.get(language_fn_name.as_bytes())
|
||||
.with_context(|| format!("Failed to load symbol {}", language_fn_name))?;
|
||||
language_fn()
|
||||
};
|
||||
std::mem::forget(library);
|
||||
Ok(language)
|
||||
let grammar = unsafe { Grammar::new(name, &library_path) }?;
|
||||
Ok(Some(grammar))
|
||||
}
|
||||
|
||||
fn ensure_git_is_available() -> Result<()> {
|
||||
@@ -220,6 +213,27 @@ fn get_grammar_configs() -> Result<Vec<GrammarConfiguration>> {
|
||||
Ok(grammars)
|
||||
}
|
||||
|
||||
pub fn get_grammar_names() -> Result<Option<HashSet<String>>> {
|
||||
let config: Configuration = crate::config::user_lang_config()
|
||||
.context("Could not parse languages.toml")?
|
||||
.try_into()?;
|
||||
|
||||
let grammars = match config.grammar_selection {
|
||||
Some(GrammarSelection::Only { only: selections }) => Some(selections),
|
||||
Some(GrammarSelection::Except { except: rejections }) => Some(
|
||||
config
|
||||
.grammar
|
||||
.into_iter()
|
||||
.map(|grammar| grammar.grammar_id)
|
||||
.filter(|id| !rejections.contains(id))
|
||||
.collect(),
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(grammars)
|
||||
}
|
||||
|
||||
fn run_parallel<F, Res>(grammars: Vec<GrammarConfiguration>, job: F) -> Vec<(String, Result<Res>)>
|
||||
where
|
||||
F: Fn(GrammarConfiguration) -> Result<Res> + Send + 'static + Clone,
|
||||
@@ -273,12 +287,12 @@ fn fetch_grammar(grammar: GrammarConfiguration) -> Result<FetchStatus> {
|
||||
}
|
||||
|
||||
// ensure the remote matches the configured remote
|
||||
if get_remote_url(&grammar_dir).map_or(true, |s| s != remote) {
|
||||
if get_remote_url(&grammar_dir).as_ref() != Some(&remote) {
|
||||
set_remote(&grammar_dir, &remote)?;
|
||||
}
|
||||
|
||||
// ensure the revision matches the configured revision
|
||||
if get_revision(&grammar_dir).map_or(true, |s| s != revision) {
|
||||
if get_revision(&grammar_dir).as_ref() != Some(&revision) {
|
||||
// Fetch the exact revision from the remote.
|
||||
// Supported by server-side git since v2.5.0 (July 2015),
|
||||
// enabled by default on major git hosts.
|
||||
@@ -451,7 +465,6 @@ fn build_tree_sitter_library(
|
||||
command
|
||||
.args(["/nologo", "/LD", "/I"])
|
||||
.arg(header_path)
|
||||
.arg("/Od")
|
||||
.arg("/utf-8")
|
||||
.arg("/std:c11");
|
||||
if let Some(scanner_path) = scanner_path.as_ref() {
|
||||
@@ -469,7 +482,6 @@ fn build_tree_sitter_library(
|
||||
cpp_command
|
||||
.args(["/nologo", "/LD", "/I"])
|
||||
.arg(header_path)
|
||||
.arg("/Od")
|
||||
.arg("/utf-8")
|
||||
.arg("/std:c++14")
|
||||
.arg(format!("/Fo{}", object_file.display()))
|
||||
@@ -496,9 +508,11 @@ fn build_tree_sitter_library(
|
||||
.arg("/link")
|
||||
.arg(format!("/out:{}", library_path.to_str().unwrap()));
|
||||
} else {
|
||||
#[cfg(not(windows))]
|
||||
command.arg("-fPIC");
|
||||
|
||||
command
|
||||
.arg("-shared")
|
||||
.arg("-fPIC")
|
||||
.arg("-fno-exceptions")
|
||||
.arg("-I")
|
||||
.arg(header_path)
|
||||
@@ -517,8 +531,11 @@ fn build_tree_sitter_library(
|
||||
cpp_command.args(compiler.args());
|
||||
let object_file =
|
||||
library_path.with_file_name(format!("{}_scanner.o", &grammar.grammar_id));
|
||||
|
||||
#[cfg(not(windows))]
|
||||
cpp_command.arg("-fPIC");
|
||||
|
||||
cpp_command
|
||||
.arg("-fPIC")
|
||||
.arg("-fno-exceptions")
|
||||
.arg("-I")
|
||||
.arg(header_path)
|
||||
@@ -592,6 +609,6 @@ fn mtime(path: &Path) -> Result<SystemTime> {
|
||||
/// Gives the contents of a file from a language's `runtime/queries/<lang>`
|
||||
/// directory
|
||||
pub fn load_runtime_file(language: &str, filename: &str) -> Result<String, std::io::Error> {
|
||||
let path = crate::runtime_file(&PathBuf::new().join("queries").join(language).join(filename));
|
||||
let path = crate::runtime_file(PathBuf::new().join("queries").join(language).join(filename));
|
||||
std::fs::read_to_string(path)
|
||||
}
|
||||
|
@@ -107,8 +107,8 @@ fn find_runtime_file(rel_path: &Path) -> Option<PathBuf> {
|
||||
/// The valid runtime directories are searched in priority order and the first
|
||||
/// file found to exist is returned, otherwise the path to the final attempt
|
||||
/// that failed.
|
||||
pub fn runtime_file(rel_path: &Path) -> PathBuf {
|
||||
find_runtime_file(rel_path).unwrap_or_else(|| {
|
||||
pub fn runtime_file(rel_path: impl AsRef<Path>) -> PathBuf {
|
||||
find_runtime_file(rel_path.as_ref()).unwrap_or_else(|| {
|
||||
RUNTIME_DIRS
|
||||
.last()
|
||||
.map(|dir| dir.join(rel_path))
|
||||
@@ -154,17 +154,36 @@ pub fn default_log_file() -> PathBuf {
|
||||
|
||||
/// Merge two TOML documents, merging values from `right` onto `left`
|
||||
///
|
||||
/// When an array exists in both `left` and `right`, `right`'s array is
|
||||
/// used. When a table exists in both `left` and `right`, the merged table
|
||||
/// consists of all keys in `left`'s table unioned with all keys in `right`
|
||||
/// with the values of `right` being merged recursively onto values of
|
||||
/// `left`.
|
||||
/// `merge_depth` sets the nesting depth up to which values are merged instead
|
||||
/// of overridden.
|
||||
///
|
||||
/// `merge_toplevel_arrays` controls whether a top-level array in the TOML
|
||||
/// document is merged instead of overridden. This is useful for TOML
|
||||
/// documents that use a top-level array of values like the `languages.toml`,
|
||||
/// where one usually wants to override or add to the array instead of
|
||||
/// replacing it altogether.
|
||||
/// When a table exists in both `left` and `right`, the merged table consists of
|
||||
/// all keys in `left`'s table unioned with all keys in `right` with the values
|
||||
/// of `right` being merged recursively onto values of `left`.
|
||||
///
|
||||
/// `crate::merge_toml_values(a, b, 3)` combines, for example:
|
||||
///
|
||||
/// b:
|
||||
/// ```toml
|
||||
/// [[language]]
|
||||
/// name = "toml"
|
||||
/// language-server = { command = "taplo", args = ["lsp", "stdio"] }
|
||||
/// ```
|
||||
/// a:
|
||||
/// ```toml
|
||||
/// [[language]]
|
||||
/// language-server = { command = "/usr/bin/taplo" }
|
||||
/// ```
|
||||
///
|
||||
/// into:
|
||||
/// ```toml
|
||||
/// [[language]]
|
||||
/// name = "toml"
|
||||
/// language-server = { command = "/usr/bin/taplo" }
|
||||
/// ```
|
||||
///
|
||||
/// thus it overrides the third depth-level of b with values of a if they exist,
|
||||
/// but otherwise merges their values
|
||||
pub fn merge_toml_values(left: toml::Value, right: toml::Value, merge_depth: usize) -> toml::Value {
|
||||
use toml::Value;
|
||||
|
||||
@@ -174,11 +193,6 @@ pub fn merge_toml_values(left: toml::Value, right: toml::Value, merge_depth: usi
|
||||
|
||||
match (left, right) {
|
||||
(Value::Array(mut left_items), Value::Array(right_items)) => {
|
||||
// The top-level arrays should be merged but nested arrays should
|
||||
// act as overrides. For the `languages.toml` config, this means
|
||||
// that you can specify a sub-set of languages in an overriding
|
||||
// `languages.toml` but that nested arrays like Language Server
|
||||
// arguments are replaced instead of merged.
|
||||
if merge_depth > 0 {
|
||||
left_items.reserve(right_items.len());
|
||||
for rvalue in right_items {
|
||||
@@ -230,7 +244,12 @@ pub fn merge_toml_values(left: toml::Value, right: toml::Value, merge_depth: usi
|
||||
/// Otherwise (workspace, false) is returned
|
||||
pub fn find_workspace() -> (PathBuf, bool) {
|
||||
let current_dir = current_working_dir();
|
||||
for ancestor in current_dir.ancestors() {
|
||||
find_workspace_in(current_dir)
|
||||
}
|
||||
|
||||
pub fn find_workspace_in(dir: impl AsRef<Path>) -> (PathBuf, bool) {
|
||||
let dir = dir.as_ref();
|
||||
for ancestor in dir.ancestors() {
|
||||
if ancestor.join(".git").exists()
|
||||
|| ancestor.join(".svn").exists()
|
||||
|| ancestor.join(".jj").exists()
|
||||
@@ -240,7 +259,7 @@ pub fn find_workspace() -> (PathBuf, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
(current_dir, true)
|
||||
(dir.to_owned(), true)
|
||||
}
|
||||
|
||||
fn default_config_file() -> PathBuf {
|
||||
|
@@ -21,10 +21,9 @@ keywords = ["language", "server", "lsp", "vscode", "lsif"]
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2.6.0"
|
||||
serde = { version = "1.0.216", features = ["derive"] }
|
||||
serde_json = "1.0.133"
|
||||
serde_repr = "0.1"
|
||||
bitflags.workspace = true
|
||||
serde = { version = "1.0.224", features = ["derive"] }
|
||||
serde_json = "1.0.145"
|
||||
url = {version = "2.5.4", features = ["serde"]}
|
||||
|
||||
[features]
|
||||
|
@@ -1,10 +1,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
DynamicRegistrationClientCapabilities, PartialResultParams, Range, SymbolKind, SymbolTag,
|
||||
TextDocumentPositionParams, WorkDoneProgressOptions, WorkDoneProgressParams,
|
||||
TextDocumentPositionParams, Url, WorkDoneProgressOptions, WorkDoneProgressParams,
|
||||
};
|
||||
|
||||
pub type CallHierarchyClientCapabilities = DynamicRegistrationClientCapabilities;
|
||||
|
@@ -1,11 +1,10 @@
|
||||
use std::collections::HashMap;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
Diagnostic, PartialResultParams, StaticRegistrationOptions, TextDocumentIdentifier,
|
||||
TextDocumentRegistrationOptions, WorkDoneProgressOptions, WorkDoneProgressParams,
|
||||
TextDocumentRegistrationOptions, Url, WorkDoneProgressOptions, WorkDoneProgressParams,
|
||||
};
|
||||
|
||||
/// Client capabilities specific to diagnostic pull requests.
|
||||
@@ -34,8 +33,13 @@ pub struct DiagnosticClientCapabilities {
|
||||
pub struct DiagnosticOptions {
|
||||
/// An optional identifier under which the diagnostics are
|
||||
/// managed by the client.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub identifier: Option<String>,
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
serialize_with = "serialize_option_arc_str",
|
||||
deserialize_with = "deserialize_option_arc_str"
|
||||
)]
|
||||
pub identifier: Option<Arc<str>>,
|
||||
|
||||
/// Whether the language has inter file dependencies, meaning that editing code in one file can
|
||||
/// result in a different diagnostic set in another file. Inter file dependencies are common
|
||||
@@ -49,6 +53,19 @@ pub struct DiagnosticOptions {
|
||||
pub work_done_progress_options: WorkDoneProgressOptions,
|
||||
}
|
||||
|
||||
fn serialize_option_arc_str<S: serde::Serializer>(
|
||||
val: &Option<Arc<str>>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_str(val.as_ref().unwrap())
|
||||
}
|
||||
|
||||
fn deserialize_option_arc_str<'de, D: serde::Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<Arc<str>>, D::Error> {
|
||||
Option::<String>::deserialize(deserializer).map(|opt| opt.map(|s| s.into()))
|
||||
}
|
||||
|
||||
/// Diagnostic registration options.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
@@ -82,7 +99,13 @@ pub struct DocumentDiagnosticParams {
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
|
||||
/// The additional identifier provided during registration.
|
||||
pub identifier: Option<String>,
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
serialize_with = "serialize_option_arc_str",
|
||||
deserialize_with = "deserialize_option_arc_str"
|
||||
)]
|
||||
pub identifier: Option<Arc<str>>,
|
||||
|
||||
/// The result ID of a previous response if provided.
|
||||
pub previous_result_id: Option<String>,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user