on master help sign in
Merge pull request #5 from helix-editor/lsp
b7a3e52Blaž Hrastnik
Cargo.lock+600−78
@@ -2,24 +2,30 @@
2# It is not intended for manual editing.
2# It is not intended for manual editing.
3[[package]]
3[[package]]
4name = "aho-corasick"
4name = "aho-corasick"
5−version = "0.7.14"
5+version = "0.7.15"
6source = "registry+https://github.com/rust-lang/crates.io-index"
6source = "registry+https://github.com/rust-lang/crates.io-index"
7−checksum = "b476ce7103678b0c6d3d395dbbae31d48ff910bd28be979ba5d48c6351131d0d"
7+checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
8dependencies = [
8dependencies = [
9 "memchr",
9 "memchr",
10]
10]
11
11
12[[package]]
12[[package]]
13name = "anyhow"
13name = "anyhow"
14−version = "1.0.33"
14+version = "1.0.34"
15source = "registry+https://github.com/rust-lang/crates.io-index"
15source = "registry+https://github.com/rust-lang/crates.io-index"
16−checksum = "a1fd36ffbb1fb7c834eac128ea8d0e310c5aeb635548f9d58861e1308d46e71c"
16+checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7"
17
17
18[[package]]
18[[package]]
19−name = "arc-swap"
19+name = "arrayref"
20−version = "0.4.7"
20+version = "0.3.6"
21source = "registry+https://github.com/rust-lang/crates.io-index"
21source = "registry+https://github.com/rust-lang/crates.io-index"
22−checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034"
22+checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
23+
24+[[package]]
25+name = "arrayvec"
26+version = "0.5.2"
27+source = "registry+https://github.com/rust-lang/crates.io-index"
28+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
23
29
24[[package]]
30[[package]]
25name = "async-channel"
31name = "async-channel"
@@ -34,9 +40,9 @@ dependencies = [
34
40
35[[package]]
41[[package]]
36name = "async-executor"
42name = "async-executor"
37−version = "1.3.0"
43+version = "1.4.0"
38source = "registry+https://github.com/rust-lang/crates.io-index"
44source = "registry+https://github.com/rust-lang/crates.io-index"
39−checksum = "d373d78ded7d0b3fa8039375718cde0aace493f2e34fb60f51cbf567562ca801"
45+checksum = "eb877970c7b440ead138f6321a3b5395d6061183af779340b65e20c0fede9146"
40dependencies = [
46dependencies = [
41 "async-task",
47 "async-task",
42 "concurrent-queue",
48 "concurrent-queue",
@@ -59,9 +65,9 @@ dependencies = [
59
65
60[[package]]
66[[package]]
61name = "async-io"
67name = "async-io"
62−version = "1.1.10"
68+version = "1.3.1"
63source = "registry+https://github.com/rust-lang/crates.io-index"
69source = "registry+https://github.com/rust-lang/crates.io-index"
64−checksum = "d54bc4c1c7292475efb2253227dbcfad8fe1ca4c02bc62c510cc2f3da5c4704e"
70+checksum = "9315f8f07556761c3e48fec2e6b276004acf426e6dc068b2c2251854d65ee0fd"
65dependencies = [
71dependencies = [
66 "concurrent-queue",
72 "concurrent-queue",
67 "fastrand",
73 "fastrand",
@@ -88,9 +94,9 @@ dependencies = [
88
94
89[[package]]
95[[package]]
90name = "async-net"
96name = "async-net"
91−version = "1.4.7"
97+version = "1.5.0"
92source = "registry+https://github.com/rust-lang/crates.io-index"
98source = "registry+https://github.com/rust-lang/crates.io-index"
93−checksum = "ee4c3668eb091d781e97f0026b5289b457c77d407a85749a9bb4c057456c428f"
99+checksum = "06de475c85affe184648202401d7622afb32f0f74e02192857d0201a16defbe5"
94dependencies = [
100dependencies = [
95 "async-io",
101 "async-io",
96 "blocking",
102 "blocking",
@@ -106,7 +112,7 @@ checksum = "4c8cea09c1fb10a317d1b5af8024eeba256d6554763e85ecd90ff8df31c7bbda"
106dependencies = [
112dependencies = [
107 "async-io",
113 "async-io",
108 "blocking",
114 "blocking",
109− "cfg-if",
115+ "cfg-if 0.1.10",
110 "event-listener",
116 "event-listener",
111 "futures-lite",
117 "futures-lite",
112 "once_cell",
118 "once_cell",
@@ -116,9 +122,9 @@ dependencies = [
116
122
117[[package]]
123[[package]]
118name = "async-task"
124name = "async-task"
119−version = "4.0.2"
125+version = "4.0.3"
120source = "registry+https://github.com/rust-lang/crates.io-index"
126source = "registry+https://github.com/rust-lang/crates.io-index"
121−checksum = "8ab27c1aa62945039e44edaeee1dc23c74cc0c303dd5fe0fb462a184f1c3a518"
127+checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0"
122
128
123[[package]]
129[[package]]
124name = "atomic-waker"
130name = "atomic-waker"
@@ -132,12 +138,35 @@ version = "1.0.1"
132source = "registry+https://github.com/rust-lang/crates.io-index"
138source = "registry+https://github.com/rust-lang/crates.io-index"
133checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
139checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
134
140
141+[[package]]
142+name = "base64"
143+version = "0.12.3"
144+source = "registry+https://github.com/rust-lang/crates.io-index"
145+checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
146+
147+[[package]]
148+name = "base64"
149+version = "0.13.0"
150+source = "registry+https://github.com/rust-lang/crates.io-index"
151+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
152+
135[[package]]
153[[package]]
136name = "bitflags"
154name = "bitflags"
137version = "1.2.1"
155version = "1.2.1"
138source = "registry+https://github.com/rust-lang/crates.io-index"
156source = "registry+https://github.com/rust-lang/crates.io-index"
139checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
157checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
140
158
159+[[package]]
160+name = "blake2b_simd"
161+version = "0.5.11"
162+source = "registry+https://github.com/rust-lang/crates.io-index"
163+checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
164+dependencies = [
165+ "arrayref",
166+ "arrayvec",
167+ "constant_time_eq",
168+]
169+
141[[package]]
170[[package]]
142name = "blocking"
171name = "blocking"
143version = "1.0.2"
172version = "1.0.2"
@@ -166,9 +195,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
166
195
167[[package]]
196[[package]]
168name = "cc"
197name = "cc"
169−version = "1.0.61"
198+version = "1.0.65"
170source = "registry+https://github.com/rust-lang/crates.io-index"
199source = "registry+https://github.com/rust-lang/crates.io-index"
171−checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d"
200+checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15"
172dependencies = [
201dependencies = [
173 "jobserver",
202 "jobserver",
174]
203]
@@ -179,6 +208,25 @@ version = "0.1.10"
179source = "registry+https://github.com/rust-lang/crates.io-index"
208source = "registry+https://github.com/rust-lang/crates.io-index"
180checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
209checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
181
210
211+[[package]]
212+name = "cfg-if"
213+version = "1.0.0"
214+source = "registry+https://github.com/rust-lang/crates.io-index"
215+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
216+
217+[[package]]
218+name = "chrono"
219+version = "0.4.19"
220+source = "registry+https://github.com/rust-lang/crates.io-index"
221+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
222+dependencies = [
223+ "libc",
224+ "num-integer",
225+ "num-traits",
226+ "time",
227+ "winapi",
228+]
229+
182[[package]]
230[[package]]
183name = "clap"
231name = "clap"
184version = "3.0.0-beta.2"
232version = "3.0.0-beta.2"
@@ -212,11 +260,28 @@ dependencies = [
212 "cache-padded",
260 "cache-padded",
213]
261]
214
262
263+[[package]]
264+name = "constant_time_eq"
265+version = "0.1.5"
266+source = "registry+https://github.com/rust-lang/crates.io-index"
267+checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
268+
269+[[package]]
270+name = "crossbeam-utils"
271+version = "0.8.1"
272+source = "registry+https://github.com/rust-lang/crates.io-index"
273+checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
274+dependencies = [
275+ "autocfg",
276+ "cfg-if 1.0.0",
277+ "lazy_static",
278+]
279+
215[[package]]
280[[package]]
216name = "crossterm"
281name = "crossterm"
217−version = "0.18.0"
282+version = "0.18.2"
218source = "registry+https://github.com/rust-lang/crates.io-index"
283source = "registry+https://github.com/rust-lang/crates.io-index"
219−checksum = "c2fcdc3c9cf8ee446222e8ee8691a6d21b563b8fe1a64b1873080db7b5b23cf0"
284+checksum = "4e86d73f2a0b407b5768d10a8c720cf5d2df49a9efc10ca09176d201ead4b7fb"
220dependencies = [
285dependencies = [
221 "bitflags",
286 "bitflags",
222 "crossterm_winapi",
287 "crossterm_winapi",
@@ -231,13 +296,34 @@ dependencies = [
231
296
232[[package]]
297[[package]]
233name = "crossterm_winapi"
298name = "crossterm_winapi"
234−version = "0.6.1"
299+version = "0.6.2"
235source = "registry+https://github.com/rust-lang/crates.io-index"
300source = "registry+https://github.com/rust-lang/crates.io-index"
236−checksum = "057b7146d02fb50175fd7dbe5158f6097f33d02831f43b4ee8ae4ddf67b68f5c"
301+checksum = "c2265c3f8e080075d9b6417aa72293fc71662f34b4af2612d8d1b074d29510db"
237dependencies = [
302dependencies = [
238 "winapi",
303 "winapi",
239]
304]
240
305
306+[[package]]
307+name = "dirs"
308+version = "2.0.2"
309+source = "registry+https://github.com/rust-lang/crates.io-index"
310+checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
311+dependencies = [
312+ "cfg-if 0.1.10",
313+ "dirs-sys",
314+]
315+
316+[[package]]
317+name = "dirs-sys"
318+version = "0.3.5"
319+source = "registry+https://github.com/rust-lang/crates.io-index"
320+checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a"
321+dependencies = [
322+ "libc",
323+ "redox_users",
324+ "winapi",
325+]
326+
241[[package]]
327[[package]]
242name = "event-listener"
328name = "event-listener"
243version = "2.5.1"
329version = "2.5.1"
@@ -253,6 +339,25 @@ dependencies = [
253 "instant",
339 "instant",
254]
340]
255
341
342+[[package]]
343+name = "fern"
344+version = "0.6.0"
345+source = "registry+https://github.com/rust-lang/crates.io-index"
346+checksum = "8c9a4820f0ccc8a7afd67c39a0f1a0f4b07ca1725164271a64939d7aeb9af065"
347+dependencies = [
348+ "log",
349+]
350+
351+[[package]]
352+name = "form_urlencoded"
353+version = "1.0.0"
354+source = "registry+https://github.com/rust-lang/crates.io-index"
355+checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00"
356+dependencies = [
357+ "matches",
358+ "percent-encoding",
359+]
360+
256[[package]]
361[[package]]
257name = "futf"
362name = "futf"
258version = "0.1.4"
363version = "0.1.4"
@@ -263,23 +368,29 @@ dependencies = [
263 "new_debug_unreachable",
368 "new_debug_unreachable",
264]
369]
265
370
371+[[package]]
372+name = "futures"
373+version = "0.1.30"
374+source = "registry+https://github.com/rust-lang/crates.io-index"
375+checksum = "4c7e4c2612746b0df8fed4ce0c69156021b704c9aefa360311c04e6e9e002eed"
376+
266[[package]]
377[[package]]
267name = "futures-core"
378name = "futures-core"
268−version = "0.3.6"
379+version = "0.3.8"
269source = "registry+https://github.com/rust-lang/crates.io-index"
380source = "registry+https://github.com/rust-lang/crates.io-index"
270−checksum = "d674eaa0056896d5ada519900dbf97ead2e46a7b6621e8160d79e2f2e1e2784b"
381+checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748"
271
382
272[[package]]
383[[package]]
273name = "futures-io"
384name = "futures-io"
274−version = "0.3.6"
385+version = "0.3.8"
275source = "registry+https://github.com/rust-lang/crates.io-index"
386source = "registry+https://github.com/rust-lang/crates.io-index"
276−checksum = "5fc94b64bb39543b4e432f1790b6bf18e3ee3b74653c5449f63310e9a74b123c"
387+checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb"
277
388
278[[package]]
389[[package]]
279name = "futures-lite"
390name = "futures-lite"
280−version = "1.11.1"
391+version = "1.11.2"
281source = "registry+https://github.com/rust-lang/crates.io-index"
392source = "registry+https://github.com/rust-lang/crates.io-index"
282−checksum = "381a7ad57b1bad34693f63f6f377e1abded7a9c85c9d3eb6771e11c60aaadab9"
393+checksum = "5e6c079abfac3ab269e2927ec048dabc89d009ebfdda6b8ee86624f30c689658"
283dependencies = [
394dependencies = [
284 "fastrand",
395 "fastrand",
285 "futures-core",
396 "futures-core",
@@ -290,6 +401,60 @@ dependencies = [
290 "waker-fn",
401 "waker-fn",
291]
402]
292
403
404+[[package]]
405+name = "futures-macro"
406+version = "0.3.8"
407+source = "registry+https://github.com/rust-lang/crates.io-index"
408+checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556"
409+dependencies = [
410+ "proc-macro-hack",
411+ "proc-macro2",
412+ "quote",
413+ "syn",
414+]
415+
416+[[package]]
417+name = "futures-task"
418+version = "0.3.8"
419+source = "registry+https://github.com/rust-lang/crates.io-index"
420+checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d"
421+dependencies = [
422+ "once_cell",
423+]
424+
425+[[package]]
426+name = "futures-util"
427+version = "0.3.8"
428+source = "registry+https://github.com/rust-lang/crates.io-index"
429+checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2"
430+dependencies = [
431+ "futures-core",
432+ "futures-macro",
433+ "futures-task",
434+ "pin-project",
435+ "pin-utils",
436+ "proc-macro-hack",
437+ "proc-macro-nested",
438+ "slab",
439+]
440+
441+[[package]]
442+name = "getrandom"
443+version = "0.1.15"
444+source = "registry+https://github.com/rust-lang/crates.io-index"
445+checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
446+dependencies = [
447+ "cfg-if 0.1.10",
448+ "libc",
449+ "wasi 0.9.0+wasi-snapshot-preview1",
450+]
451+
452+[[package]]
453+name = "glob"
454+version = "0.3.0"
455+source = "registry+https://github.com/rust-lang/crates.io-index"
456+checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
457+
293[[package]]
458[[package]]
294name = "hashbrown"
459name = "hashbrown"
295version = "0.9.1"
460version = "0.9.1"
@@ -312,6 +477,28 @@ dependencies = [
312 "unicode-width",
477 "unicode-width",
313]
478]
314
479
480+[[package]]
481+name = "helix-lsp"
482+version = "0.1.0"
483+dependencies = [
484+ "anyhow",
485+ "futures-util",
486+ "glob",
487+ "helix-core",
488+ "helix-view",
489+ "jsonrpc-core",
490+ "log",
491+ "lsp-types",
492+ "once_cell",
493+ "pathdiff",
494+ "serde",
495+ "serde_json",
496+ "shellexpand",
497+ "smol",
498+ "thiserror",
499+ "url",
500+]
501+
315[[package]]
502[[package]]
316name = "helix-syntax"
503name = "helix-syntax"
317version = "0.1.0"
504version = "0.1.0"
@@ -325,10 +512,15 @@ name = "helix-term"
325version = "0.1.0"
512version = "0.1.0"
326dependencies = [
513dependencies = [
327 "anyhow",
514 "anyhow",
515+ "chrono",
328 "clap",
516 "clap",
329 "crossterm",
517 "crossterm",
518+ "fern",
519+ "futures-util",
330 "helix-core",
520 "helix-core",
521+ "helix-lsp",
331 "helix-view",
522 "helix-view",
523+ "log",
332 "num_cpus",
524 "num_cpus",
333 "smol",
525 "smol",
334 "tui",
526 "tui",
@@ -342,7 +534,9 @@ dependencies = [
342 "crossterm",
534 "crossterm",
343 "helix-core",
535 "helix-core",
344 "once_cell",
536 "once_cell",
537+ "smol",
345 "tui",
538 "tui",
539+ "url",
346]
540]
347
541
348[[package]]
542[[package]]
@@ -354,6 +548,17 @@ dependencies = [
354 "libc",
548 "libc",
355]
549]
356
550
551+[[package]]
552+name = "idna"
553+version = "0.2.0"
554+source = "registry+https://github.com/rust-lang/crates.io-index"
555+checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
556+dependencies = [
557+ "matches",
558+ "unicode-bidi",
559+ "unicode-normalization",
560+]
561+
357[[package]]
562[[package]]
358name = "indexmap"
563name = "indexmap"
359version = "1.6.0"
564version = "1.6.0"
@@ -366,13 +571,19 @@ dependencies = [
366
571
367[[package]]
572[[package]]
368name = "instant"
573name = "instant"
369−version = "0.1.7"
574+version = "0.1.9"
370source = "registry+https://github.com/rust-lang/crates.io-index"
575source = "registry+https://github.com/rust-lang/crates.io-index"
371−checksum = "63312a18f7ea8760cdd0a7c5aac1a619752a246b833545e3e36d1f81f7cd9e66"
576+checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
372dependencies = [
577dependencies = [
373− "cfg-if",
578+ "cfg-if 1.0.0",
374]
579]
375
580
581+[[package]]
582+name = "itoa"
583+version = "0.4.6"
584+source = "registry+https://github.com/rust-lang/crates.io-index"
585+checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
586+
376[[package]]
587[[package]]
377name = "jobserver"
588name = "jobserver"
378version = "0.1.21"
589version = "0.1.21"
@@ -382,6 +593,19 @@ dependencies = [
382 "libc",
593 "libc",
383]
594]
384
595
596+[[package]]
597+name = "jsonrpc-core"
598+version = "15.1.0"
599+source = "registry+https://github.com/rust-lang/crates.io-index"
600+checksum = "0745a6379e3edc893c84ec203589790774e4247420033e71a76d3ab4687991fa"
601+dependencies = [
602+ "futures",
603+ "log",
604+ "serde",
605+ "serde_derive",
606+ "serde_json",
607+]
608+
385[[package]]
609[[package]]
386name = "lazy_static"
610name = "lazy_static"
387version = "1.4.0"
611version = "1.4.0"
@@ -390,15 +614,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
390
614
391[[package]]
615[[package]]
392name = "libc"
616name = "libc"
393−version = "0.2.79"
617+version = "0.2.80"
394source = "registry+https://github.com/rust-lang/crates.io-index"
618source = "registry+https://github.com/rust-lang/crates.io-index"
395−checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"
619+checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
396
620
397[[package]]
621[[package]]
398name = "lock_api"
622name = "lock_api"
399−version = "0.4.1"
623+version = "0.4.2"
400source = "registry+https://github.com/rust-lang/crates.io-index"
624source = "registry+https://github.com/rust-lang/crates.io-index"
401−checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c"
625+checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
402dependencies = [
626dependencies = [
403 "scopeguard",
627 "scopeguard",
404]
628]
@@ -409,7 +633,21 @@ version = "0.4.11"
409source = "registry+https://github.com/rust-lang/crates.io-index"
633source = "registry+https://github.com/rust-lang/crates.io-index"
410checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
634checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
411dependencies = [
635dependencies = [
412− "cfg-if",
636+ "cfg-if 0.1.10",
637+]
638+
639+[[package]]
640+name = "lsp-types"
641+version = "0.84.0"
642+source = "registry+https://github.com/rust-lang/crates.io-index"
643+checksum = "3b95be71fe205e44de754185bcf86447b65813ce1ceb298f8d3793ade5fff08d"
644+dependencies = [
645+ "base64 0.12.3",
646+ "bitflags",
647+ "serde",
648+ "serde_json",
649+ "serde_repr",
650+ "url",
413]
651]
414
652
415[[package]]
653[[package]]
@@ -418,17 +656,23 @@ version = "0.1.1"
418source = "registry+https://github.com/rust-lang/crates.io-index"
656source = "registry+https://github.com/rust-lang/crates.io-index"
419checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
657checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
420
658
659+[[package]]
660+name = "matches"
661+version = "0.1.8"
662+source = "registry+https://github.com/rust-lang/crates.io-index"
663+checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
664+
421[[package]]
665[[package]]
422name = "memchr"
666name = "memchr"
423−version = "2.3.3"
667+version = "2.3.4"
424source = "registry+https://github.com/rust-lang/crates.io-index"
668source = "registry+https://github.com/rust-lang/crates.io-index"
425−checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
669+checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
426
670
427[[package]]
671[[package]]
428name = "mio"
672name = "mio"
429−version = "0.7.3"
673+version = "0.7.6"
430source = "registry+https://github.com/rust-lang/crates.io-index"
674source = "registry+https://github.com/rust-lang/crates.io-index"
431−checksum = "e53a6ea5f38c0a48ca42159868c6d8e1bd56c0451238856cc08d58563643bdc3"
675+checksum = "f33bc887064ef1fd66020c9adfc45bb9f33d75a42096c81e7c56c65b75dd1a8b"
432dependencies = [
676dependencies = [
433 "libc",
677 "libc",
434 "log",
678 "log",
@@ -439,9 +683,9 @@ dependencies = [
439
683
440[[package]]
684[[package]]
441name = "miow"
685name = "miow"
442−version = "0.3.5"
686+version = "0.3.6"
443source = "registry+https://github.com/rust-lang/crates.io-index"
687source = "registry+https://github.com/rust-lang/crates.io-index"
444−checksum = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e"
688+checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897"
445dependencies = [
689dependencies = [
446 "socket2",
690 "socket2",
447 "winapi",
691 "winapi",
@@ -465,13 +709,32 @@ checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
465
709
466[[package]]
710[[package]]
467name = "ntapi"
711name = "ntapi"
468−version = "0.3.4"
712+version = "0.3.6"
469source = "registry+https://github.com/rust-lang/crates.io-index"
713source = "registry+https://github.com/rust-lang/crates.io-index"
470−checksum = "7a31937dea023539c72ddae0e3571deadc1414b300483fa7aaec176168cfa9d2"
714+checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
471dependencies = [
715dependencies = [
472 "winapi",
716 "winapi",
473]
717]
474
718
719+[[package]]
720+name = "num-integer"
721+version = "0.1.44"
722+source = "registry+https://github.com/rust-lang/crates.io-index"
723+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
724+dependencies = [
725+ "autocfg",
726+ "num-traits",
727+]
728+
729+[[package]]
730+name = "num-traits"
731+version = "0.2.14"
732+source = "registry+https://github.com/rust-lang/crates.io-index"
733+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
734+dependencies = [
735+ "autocfg",
736+]
737+
475[[package]]
738[[package]]
476name = "num_cpus"
739name = "num_cpus"
477version = "1.13.0"
740version = "1.13.0"
@@ -484,15 +747,15 @@ dependencies = [
484
747
485[[package]]
748[[package]]
486name = "once_cell"
749name = "once_cell"
487−version = "1.4.1"
750+version = "1.5.2"
488source = "registry+https://github.com/rust-lang/crates.io-index"
751source = "registry+https://github.com/rust-lang/crates.io-index"
489−checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad"
752+checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
490
753
491[[package]]
754[[package]]
492name = "os_str_bytes"
755name = "os_str_bytes"
493−version = "2.3.2"
756+version = "2.4.0"
494source = "registry+https://github.com/rust-lang/crates.io-index"
757source = "registry+https://github.com/rust-lang/crates.io-index"
495−checksum = "2ac6fe3538f701e339953a3ebbe4f39941aababa8a3f6964635b24ab526daeac"
758+checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85"
496
759
497[[package]]
760[[package]]
498name = "parking"
761name = "parking"
@@ -502,9 +765,9 @@ checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
502
765
503[[package]]
766[[package]]
504name = "parking_lot"
767name = "parking_lot"
505−version = "0.11.0"
768+version = "0.11.1"
506source = "registry+https://github.com/rust-lang/crates.io-index"
769source = "registry+https://github.com/rust-lang/crates.io-index"
507−checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733"
770+checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
508dependencies = [
771dependencies = [
509 "instant",
772 "instant",
510 "lock_api",
773 "lock_api",
@@ -517,7 +780,7 @@ version = "0.8.0"
517source = "registry+https://github.com/rust-lang/crates.io-index"
780source = "registry+https://github.com/rust-lang/crates.io-index"
518checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
781checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
519dependencies = [
782dependencies = [
520− "cfg-if",
783+ "cfg-if 0.1.10",
521 "cloudabi",
784 "cloudabi",
522 "instant",
785 "instant",
523 "libc",
786 "libc",
@@ -526,36 +789,115 @@ dependencies = [
526 "winapi",
789 "winapi",
527]
790]
528
791
792+[[package]]
793+name = "pathdiff"
794+version = "0.2.0"
795+source = "registry+https://github.com/rust-lang/crates.io-index"
796+checksum = "877630b3de15c0b64cc52f659345724fbf6bdad9bd9566699fc53688f3c34a34"
797+
798+[[package]]
799+name = "percent-encoding"
800+version = "2.1.0"
801+source = "registry+https://github.com/rust-lang/crates.io-index"
802+checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
803+
804+[[package]]
805+name = "pin-project"
806+version = "1.0.2"
807+source = "registry+https://github.com/rust-lang/crates.io-index"
808+checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7"
809+dependencies = [
810+ "pin-project-internal",
811+]
812+
813+[[package]]
814+name = "pin-project-internal"
815+version = "1.0.2"
816+source = "registry+https://github.com/rust-lang/crates.io-index"
817+checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f"
818+dependencies = [
819+ "proc-macro2",
820+ "quote",
821+ "syn",
822+]
823+
529[[package]]
824[[package]]
530name = "pin-project-lite"
825name = "pin-project-lite"
531−version = "0.1.10"
826+version = "0.1.11"
827+source = "registry+https://github.com/rust-lang/crates.io-index"
828+checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b"
829+
830+[[package]]
831+name = "pin-utils"
832+version = "0.1.0"
532source = "registry+https://github.com/rust-lang/crates.io-index"
833source = "registry+https://github.com/rust-lang/crates.io-index"
533−checksum = "e555d9e657502182ac97b539fb3dae8b79cda19e3e4f8ffb5e8de4f18df93c95"
834+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
534
835
535[[package]]
836[[package]]
536name = "polling"
837name = "polling"
537−version = "2.0.1"
838+version = "2.0.2"
538source = "registry+https://github.com/rust-lang/crates.io-index"
839source = "registry+https://github.com/rust-lang/crates.io-index"
539−checksum = "ab773feb154f12c49ffcfd66ab8bdcf9a1843f950db48b0d8be9d4393783b058"
840+checksum = "a2a7bc6b2a29e632e45451c941832803a18cce6781db04de8a04696cdca8bde4"
540dependencies = [
841dependencies = [
541− "cfg-if",
842+ "cfg-if 0.1.10",
542 "libc",
843 "libc",
543 "log",
844 "log",
544 "wepoll-sys",
845 "wepoll-sys",
545 "winapi",
846 "winapi",
546]
847]
547
848
849+[[package]]
850+name = "proc-macro-hack"
851+version = "0.5.19"
852+source = "registry+https://github.com/rust-lang/crates.io-index"
853+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
854+
855+[[package]]
856+name = "proc-macro-nested"
857+version = "0.1.6"
858+source = "registry+https://github.com/rust-lang/crates.io-index"
859+checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a"
860+
861+[[package]]
862+name = "proc-macro2"
863+version = "1.0.24"
864+source = "registry+https://github.com/rust-lang/crates.io-index"
865+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
866+dependencies = [
867+ "unicode-xid",
868+]
869+
870+[[package]]
871+name = "quote"
872+version = "1.0.7"
873+source = "registry+https://github.com/rust-lang/crates.io-index"
874+checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
875+dependencies = [
876+ "proc-macro2",
877+]
878+
548[[package]]
879[[package]]
549name = "redox_syscall"
880name = "redox_syscall"
550version = "0.1.57"
881version = "0.1.57"
551source = "registry+https://github.com/rust-lang/crates.io-index"
882source = "registry+https://github.com/rust-lang/crates.io-index"
552checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
883checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
553
884
885+[[package]]
886+name = "redox_users"
887+version = "0.3.5"
888+source = "registry+https://github.com/rust-lang/crates.io-index"
889+checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
890+dependencies = [
891+ "getrandom",
892+ "redox_syscall",
893+ "rust-argon2",
894+]
895+
554[[package]]
896[[package]]
555name = "regex"
897name = "regex"
556−version = "1.4.1"
898+version = "1.4.2"
557source = "registry+https://github.com/rust-lang/crates.io-index"
899source = "registry+https://github.com/rust-lang/crates.io-index"
558−checksum = "8963b85b8ce3074fecffde43b4b0dded83ce2f367dc8d363afc56679f3ee820b"
900+checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c"
559dependencies = [
901dependencies = [
560 "aho-corasick",
902 "aho-corasick",
561 "memchr",
903 "memchr",
@@ -565,9 +907,9 @@ dependencies = [
565
907
566[[package]]
908[[package]]
567name = "regex-syntax"
909name = "regex-syntax"
568−version = "0.6.20"
910+version = "0.6.21"
569source = "registry+https://github.com/rust-lang/crates.io-index"
911source = "registry+https://github.com/rust-lang/crates.io-index"
570−checksum = "8cab7a364d15cde1e505267766a2d3c4e22a843e1a601f0fa7564c0f82ced11c"
912+checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
571
913
572[[package]]
914[[package]]
573name = "ropey"
915name = "ropey"
@@ -578,12 +920,81 @@ dependencies = [
578 "smallvec",
920 "smallvec",
579]
921]
580
922
923+[[package]]
924+name = "rust-argon2"
925+version = "0.8.3"
926+source = "registry+https://github.com/rust-lang/crates.io-index"
927+checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
928+dependencies = [
929+ "base64 0.13.0",
930+ "blake2b_simd",
931+ "constant_time_eq",
932+ "crossbeam-utils",
933+]
934+
935+[[package]]
936+name = "ryu"
937+version = "1.0.5"
938+source = "registry+https://github.com/rust-lang/crates.io-index"
939+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
940+
581[[package]]
941[[package]]
582name = "scopeguard"
942name = "scopeguard"
583version = "1.1.0"
943version = "1.1.0"
584source = "registry+https://github.com/rust-lang/crates.io-index"
944source = "registry+https://github.com/rust-lang/crates.io-index"
585checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
945checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
586
946
947+[[package]]
948+name = "serde"
949+version = "1.0.117"
950+source = "registry+https://github.com/rust-lang/crates.io-index"
951+checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
952+dependencies = [
953+ "serde_derive",
954+]
955+
956+[[package]]
957+name = "serde_derive"
958+version = "1.0.117"
959+source = "registry+https://github.com/rust-lang/crates.io-index"
960+checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
961+dependencies = [
962+ "proc-macro2",
963+ "quote",
964+ "syn",
965+]
966+
967+[[package]]
968+name = "serde_json"
969+version = "1.0.59"
970+source = "registry+https://github.com/rust-lang/crates.io-index"
971+checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95"
972+dependencies = [
973+ "itoa",
974+ "ryu",
975+ "serde",
976+]
977+
978+[[package]]
979+name = "serde_repr"
980+version = "0.1.6"
981+source = "registry+https://github.com/rust-lang/crates.io-index"
982+checksum = "2dc6b7951b17b051f3210b063f12cc17320e2fe30ae05b0fe2a3abb068551c76"
983+dependencies = [
984+ "proc-macro2",
985+ "quote",
986+ "syn",
987+]
988+
989+[[package]]
990+name = "shellexpand"
991+version = "2.0.0"
992+source = "registry+https://github.com/rust-lang/crates.io-index"
993+checksum = "9a2b22262a9aaf9464d356f656fea420634f78c881c5eebd5ef5e66d8b9bc603"
994+dependencies = [
995+ "dirs",
996+]
997+
587[[package]]
998[[package]]
588name = "signal-hook"
999name = "signal-hook"
589version = "0.1.16"
1000version = "0.1.16"
@@ -597,25 +1008,30 @@ dependencies = [
597
1008
598[[package]]
1009[[package]]
599name = "signal-hook-registry"
1010name = "signal-hook-registry"
600−version = "1.2.1"
1011+version = "1.2.2"
601source = "registry+https://github.com/rust-lang/crates.io-index"
1012source = "registry+https://github.com/rust-lang/crates.io-index"
602−checksum = "a3e12110bc539e657a646068aaf5eb5b63af9d0c1f7b29c97113fad80e15f035"
1013+checksum = "ce32ea0c6c56d5eacaeb814fbed9960547021d3edd010ded1425f180536b20ab"
603dependencies = [
1014dependencies = [
604− "arc-swap",
605 "libc",
1015 "libc",
606]
1016]
607
1017
1018+[[package]]
1019+name = "slab"
1020+version = "0.4.2"
1021+source = "registry+https://github.com/rust-lang/crates.io-index"
1022+checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
1023+
608[[package]]
1024[[package]]
609name = "smallvec"
1025name = "smallvec"
610−version = "1.4.2"
1026+version = "1.5.0"
611source = "registry+https://github.com/rust-lang/crates.io-index"
1027source = "registry+https://github.com/rust-lang/crates.io-index"
612−checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252"
1028+checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85"
613
1029
614[[package]]
1030[[package]]
615name = "smol"
1031name = "smol"
616−version = "1.2.4"
1032+version = "1.2.5"
617source = "registry+https://github.com/rust-lang/crates.io-index"
1033source = "registry+https://github.com/rust-lang/crates.io-index"
618−checksum = "aaf8ded16994c0ae59596c6e4733c76faeb0533c26fd5ca1b1bc89271a049a66"
1034+checksum = "85cf3b5351f3e783c1d79ab5fc604eeed8b8ae9abd36b166e8b87a089efd85e4"
619dependencies = [
1035dependencies = [
620 "async-channel",
1036 "async-channel",
621 "async-executor",
1037 "async-executor",
@@ -631,16 +1047,27 @@ dependencies = [
631
1047
632[[package]]
1048[[package]]
633name = "socket2"
1049name = "socket2"
634−version = "0.3.15"
1050+version = "0.3.17"
635source = "registry+https://github.com/rust-lang/crates.io-index"
1051source = "registry+https://github.com/rust-lang/crates.io-index"
636−checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44"
1052+checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902"
637dependencies = [
1053dependencies = [
638− "cfg-if",
1054+ "cfg-if 1.0.0",
639 "libc",
1055 "libc",
640 "redox_syscall",
1056 "redox_syscall",
641 "winapi",
1057 "winapi",
642]
1058]
643
1059
1060+[[package]]
1061+name = "syn"
1062+version = "1.0.53"
1063+source = "registry+https://github.com/rust-lang/crates.io-index"
1064+checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68"
1065+dependencies = [
1066+ "proc-macro2",
1067+ "quote",
1068+ "unicode-xid",
1069+]
1070+
644[[package]]
1071[[package]]
645name = "tendril"
1072name = "tendril"
646version = "0.4.1"
1073version = "0.4.1"
@@ -660,6 +1087,26 @@ dependencies = [
660 "unicode-width",
1087 "unicode-width",
661]
1088]
662
1089
1090+[[package]]
1091+name = "thiserror"
1092+version = "1.0.22"
1093+source = "registry+https://github.com/rust-lang/crates.io-index"
1094+checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e"
1095+dependencies = [
1096+ "thiserror-impl",
1097+]
1098+
1099+[[package]]
1100+name = "thiserror-impl"
1101+version = "1.0.22"
1102+source = "registry+https://github.com/rust-lang/crates.io-index"
1103+checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56"
1104+dependencies = [
1105+ "proc-macro2",
1106+ "quote",
1107+ "syn",
1108+]
1109+
663[[package]]
1110[[package]]
664name = "thread_local"
1111name = "thread_local"
665version = "1.0.1"
1112version = "1.0.1"
@@ -669,11 +1116,37 @@ dependencies = [
669 "lazy_static",
1116 "lazy_static",
670]
1117]
671
1118
1119+[[package]]
1120+name = "time"
1121+version = "0.1.44"
1122+source = "registry+https://github.com/rust-lang/crates.io-index"
1123+checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
1124+dependencies = [
1125+ "libc",
1126+ "wasi 0.10.0+wasi-snapshot-preview1",
1127+ "winapi",
1128+]
1129+
1130+[[package]]
1131+name = "tinyvec"
1132+version = "1.1.0"
1133+source = "registry+https://github.com/rust-lang/crates.io-index"
1134+checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f"
1135+dependencies = [
1136+ "tinyvec_macros",
1137+]
1138+
1139+[[package]]
1140+name = "tinyvec_macros"
1141+version = "0.1.0"
1142+source = "registry+https://github.com/rust-lang/crates.io-index"
1143+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
1144+
672[[package]]
1145[[package]]
673name = "tree-sitter"
1146name = "tree-sitter"
674−version = "0.17.0"
1147+version = "0.17.1"
675source = "registry+https://github.com/rust-lang/crates.io-index"
1148source = "registry+https://github.com/rust-lang/crates.io-index"
676−checksum = "70ee7370fec3aecde3862a7d64c571048f70a7298daef1815e8fc68b9de54b5c"
1149+checksum = "d18dcb776d3affaba6db04d11d645946d34a69b3172e588af96ce9fecd20faac"
677dependencies = [
1150dependencies = [
678 "cc",
1151 "cc",
679 "regex",
1152 "regex",
@@ -681,8 +1154,8 @@ dependencies = [
681
1154
682[[package]]
1155[[package]]
683name = "tui"
1156name = "tui"
684−version = "0.12.0"
1157+version = "0.13.0"
685−source = "git+https://github.com/fdehau/tui-rs#25ff2e5e61f8902101e485743992db2412f77aad"
1158+source = "git+https://github.com/fdehau/tui-rs#efdd6bfb193dafcb5e3bdc75e7d2d314065da1d7"
686dependencies = [
1159dependencies = [
687 "bitflags",
1160 "bitflags",
688 "cassowary",
1161 "cassowary",
@@ -691,11 +1164,29 @@ dependencies = [
691 "unicode-width",
1164 "unicode-width",
692]
1165]
693
1166
1167+[[package]]
1168+name = "unicode-bidi"
1169+version = "0.3.4"
1170+source = "registry+https://github.com/rust-lang/crates.io-index"
1171+checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
1172+dependencies = [
1173+ "matches",
1174+]
1175+
1176+[[package]]
1177+name = "unicode-normalization"
1178+version = "0.1.16"
1179+source = "registry+https://github.com/rust-lang/crates.io-index"
1180+checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606"
1181+dependencies = [
1182+ "tinyvec",
1183+]
1184+
694[[package]]
1185[[package]]
695name = "unicode-segmentation"
1186name = "unicode-segmentation"
696−version = "1.6.0"
1187+version = "1.7.1"
697source = "registry+https://github.com/rust-lang/crates.io-index"
1188source = "registry+https://github.com/rust-lang/crates.io-index"
698−checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
1189+checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
699
1190
700[[package]]
1191[[package]]
701name = "unicode-width"
1192name = "unicode-width"
@@ -703,6 +1194,25 @@ version = "0.1.8"
703source = "registry+https://github.com/rust-lang/crates.io-index"
1194source = "registry+https://github.com/rust-lang/crates.io-index"
704checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
1195checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
705
1196
1197+[[package]]
1198+name = "unicode-xid"
1199+version = "0.2.1"
1200+source = "registry+https://github.com/rust-lang/crates.io-index"
1201+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
1202+
1203+[[package]]
1204+name = "url"
1205+version = "2.2.0"
1206+source = "registry+https://github.com/rust-lang/crates.io-index"
1207+checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e"
1208+dependencies = [
1209+ "form_urlencoded",
1210+ "idna",
1211+ "matches",
1212+ "percent-encoding",
1213+ "serde",
1214+]
1215+
706[[package]]
1216[[package]]
707name = "utf-8"
1217name = "utf-8"
708version = "0.7.5"
1218version = "0.7.5"
@@ -727,11 +1237,23 @@ version = "1.1.0"
727source = "registry+https://github.com/rust-lang/crates.io-index"
1237source = "registry+https://github.com/rust-lang/crates.io-index"
728checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
1238checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
729
1239
1240+[[package]]
1241+name = "wasi"
1242+version = "0.9.0+wasi-snapshot-preview1"
1243+source = "registry+https://github.com/rust-lang/crates.io-index"
1244+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
1245+
1246+[[package]]
1247+name = "wasi"
1248+version = "0.10.0+wasi-snapshot-preview1"
1249+source = "registry+https://github.com/rust-lang/crates.io-index"
1250+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
1251+
730[[package]]
1252[[package]]
731name = "wepoll-sys"
1253name = "wepoll-sys"
732−version = "3.0.0"
1254+version = "3.0.1"
733source = "registry+https://github.com/rust-lang/crates.io-index"
1255source = "registry+https://github.com/rust-lang/crates.io-index"
734−checksum = "142bc2cba3fe88be1a8fcb55c727fa4cd5b0cf2d7438722792e22f26f04bc1e0"
1256+checksum = "0fcb14dea929042224824779fbc82d9fab8d2e6d3cbc0ac404de8edf489e77ff"
735dependencies = [
1257dependencies = [
736 "cc",
1258 "cc",
737]
1259]
Cargo.toml+1
@@ -4,6 +4,7 @@ members = [
4 "helix-view",
4 "helix-view",
5 "helix-term",
5 "helix-term",
6 "helix-syntax",
6 "helix-syntax",
7+ "helix-lsp",
7]
8]
8
9
9# Build helix-syntax in release mode to make the code path faster in development.
10# Build helix-syntax in release mode to make the code path faster in development.
helix-core/src/diagnostic.rsnew file+7
@@ -0,0 +1,7 @@
1+use crate::Range;
2+
3+pub struct Diagnostic {
4+ pub range: (usize, usize),
5+ pub line: usize,
6+ pub message: String,
7+}
helix-core/src/history.rs+25−21
@@ -57,37 +57,31 @@ impl History {
57 self.cursor == 0
57 self.cursor == 0
58 }
58 }
59
59
60+ // TODO: I'd like to pass Transaction by reference but it fights with the borrowck
61+
60− pub fn undo(&mut self, state: &mut State) {
62+ pub fn undo(&mut self) -> Option<Transaction> {
61 if self.at_root() {
63 if self.at_root() {
62 // We're at the root of undo, nothing to do.
64 // We're at the root of undo, nothing to do.
63− return;
65+ return None;
64 }
66 }
65
67
66 let current_revision = &self.revisions[self.cursor];
68 let current_revision = &self.revisions[self.cursor];
67
69
68− // TODO: pass the return value through? It should always succeed
69− let success = current_revision.revert.apply(state);
70−
71− if !success {
72− panic!("Failed to apply undo!");
73− }
74−
75 self.cursor = current_revision.parent;
70 self.cursor = current_revision.parent;
71+
72+ Some(current_revision.revert.clone())
76 }
73 }
77
74
78− pub fn redo(&mut self, state: &mut State) {
75+ pub fn redo(&mut self) -> Option<Transaction> {
79 let current_revision = &self.revisions[self.cursor];
76 let current_revision = &self.revisions[self.cursor];
80
77
81 // for now, simply pick the latest child (linear undo / redo)
78 // for now, simply pick the latest child (linear undo / redo)
82 if let Some((index, transaction)) = current_revision.children.last() {
79 if let Some((index, transaction)) = current_revision.children.last() {
83− let success = transaction.apply(state);
84−
85− if !success {
86− panic!("Failed to apply redo!");
87− }
88−
89 self.cursor = *index;
80 self.cursor = *index;
81+
82+ return Some(transaction.clone());
90 }
83 }
84+ None
91 }
85 }
92}
86}
93
87
@@ -120,17 +114,27 @@ mod test {
120 assert_eq!("hello 世界!", state.doc());
114 assert_eq!("hello 世界!", state.doc());
121
115
122 // ---
116 // ---
117+ fn undo(history: &mut History, state: &mut State) {
118+ if let Some(transaction) = history.undo() {
119+ transaction.apply(state);
120+ }
121+ }
122+ fn redo(history: &mut History, state: &mut State) {
123+ if let Some(transaction) = history.redo() {
124+ transaction.apply(state);
125+ }
126+ }
123
127
124− history.undo(&mut state);
128+ undo(&mut history, &mut state);
125 assert_eq!("hello world!", state.doc());
129 assert_eq!("hello world!", state.doc());
126− history.redo(&mut state);
130+ redo(&mut history, &mut state);
127 assert_eq!("hello 世界!", state.doc());
131 assert_eq!("hello 世界!", state.doc());
128− history.undo(&mut state);
132+ undo(&mut history, &mut state);
129− history.undo(&mut state);
133+ undo(&mut history, &mut state);
130 assert_eq!("hello", state.doc());
134 assert_eq!("hello", state.doc());
131
135
132 // undo at root is a no-op
136 // undo at root is a no-op
133− history.undo(&mut state);
137+ undo(&mut history, &mut state);
134 assert_eq!("hello", state.doc());
138 assert_eq!("hello", state.doc());
135 }
139 }
136}
140}
helix-core/src/indent.rs+17−12
@@ -111,17 +111,17 @@ fn find_first_non_whitespace_char(state: &State, line_num: usize) -> usize {
111 start
111 start
112}
112}
113
113
114−fn suggested_indent_for_line(state: &State, line_num: usize) -> usize {
114+fn suggested_indent_for_line(syntax: Option<&Syntax>, state: &State, line_num: usize) -> usize {
115 let line = state.doc.line(line_num);
115 let line = state.doc.line(line_num);
116 let current = indent_level_for_line(line);
116 let current = indent_level_for_line(line);
117
117
118 let start = find_first_non_whitespace_char(state, line_num);
118 let start = find_first_non_whitespace_char(state, line_num);
119
119
120− suggested_indent_for_pos(state, start)
120+ suggested_indent_for_pos(syntax, state, start)
121}
121}
122
122
123−pub fn suggested_indent_for_pos(state: &State, pos: usize) -> usize {
123+pub fn suggested_indent_for_pos(syntax: Option<&Syntax>, state: &State, pos: usize) -> usize {
124− if let Some(syntax) = &state.syntax {
124+ if let Some(syntax) = syntax {
125 let byte_start = state.doc.char_to_byte(pos);
125 let byte_start = state.doc.char_to_byte(pos);
126 let node = get_highest_syntax_node_at_bytepos(syntax, byte_start);
126 let node = get_highest_syntax_node_at_bytepos(syntax, byte_start);
127
127
@@ -163,13 +163,18 @@ mod test {
163",
163",
164 );
164 );
165
165
166− let mut state = State::new(doc);
166+ let state = State::new(doc);
167− state.set_language("source.rust", &[]);
167+ // TODO: set_language
168−
168+ let language_config = crate::syntax::LOADER
169+ .language_config_for_scope("source.rust")
170+ .unwrap();
171+ let highlight_config = language_config.highlight_config(&[]).unwrap().unwrap();
172+ let syntax = Syntax::new(&state.doc, highlight_config.clone());
173+
169− assert_eq!(suggested_indent_for_line(&state, 0), 0); // mod
174+ assert_eq!(suggested_indent_for_line(Some(&syntax), &state, 0), 0); // mod
170− assert_eq!(suggested_indent_for_line(&state, 1), 1); // fn
175+ assert_eq!(suggested_indent_for_line(Some(&syntax), &state, 1), 1); // fn
171− assert_eq!(suggested_indent_for_line(&state, 2), 2); // 1 + 1
176+ assert_eq!(suggested_indent_for_line(Some(&syntax), &state, 2), 2); // 1 + 1
172− assert_eq!(suggested_indent_for_line(&state, 4), 1); // }
177+ assert_eq!(suggested_indent_for_line(Some(&syntax), &state, 4), 1); // }
173− assert_eq!(suggested_indent_for_line(&state, 5), 0); // }
178+ assert_eq!(suggested_indent_for_line(Some(&syntax), &state, 5), 0); // }
174 }
179 }
175}
180}
helix-core/src/lib.rs+3−1
@@ -1,4 +1,5 @@
1#![allow(unused)]
1#![allow(unused)]
2+mod diagnostic;
2pub mod graphemes;
3pub mod graphemes;
3mod history;
4mod history;
4pub mod indent;
5pub mod indent;
@@ -22,7 +23,8 @@ pub use selection::Range;
22pub use selection::Selection;
23pub use selection::Selection;
23pub use syntax::Syntax;
24pub use syntax::Syntax;
24
25
26+pub use diagnostic::Diagnostic;
25pub use history::History;
27pub use history::History;
26pub use state::State;
28pub use state::State;
27
29
28−pub use transaction::{Assoc, Change, ChangeSet, Transaction};
30+pub use transaction::{Assoc, Change, ChangeSet, Operation, Transaction};
helix-core/src/selection.rs+5
@@ -179,6 +179,11 @@ impl Selection {
179 }
179 }
180 }
180 }
181
181
182+ /// Constructs a selection holding a single cursor.
183+ pub fn point(pos: usize) -> Self {
184+ Self::single(pos, pos)
185+ }
186+
182 #[must_use]
187 #[must_use]
183 pub fn new(ranges: SmallVec<[Range; 1]>, primary_index: usize) -> Self {
188 pub fn new(ranges: SmallVec<[Range; 1]>, primary_index: usize) -> Self {
184 fn normalize(mut ranges: SmallVec<[Range; 1]>, mut primary_index: usize) -> Selection {
189 fn normalize(mut ranges: SmallVec<[Range; 1]>, mut primary_index: usize) -> Selection {
helix-core/src/state.rs+2−76
@@ -1,33 +1,14 @@
1use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes};
1use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes};
2use crate::syntax::LOADER;
2use crate::syntax::LOADER;
3−use crate::{ChangeSet, Position, Range, Rope, RopeSlice, Selection, Syntax};
3+use crate::{ChangeSet, Diagnostic, Position, Range, Rope, RopeSlice, Selection, Syntax};
4use anyhow::Error;
4use anyhow::Error;
5
5
6−use std::path::PathBuf;
7−
8−#[derive(Copy, Clone, PartialEq, Eq, Hash)]
9−pub enum Mode {
10− Normal,
11− Insert,
12− Goto,
13−}
14−
15/// A state represents the current editor state of a single buffer.
6/// A state represents the current editor state of a single buffer.
7+#[derive(Clone)]
16pub struct State {
8pub struct State {
17 // TODO: fields should be private but we need to refactor commands.rs first
9 // TODO: fields should be private but we need to refactor commands.rs first
18− /// Path to file on disk.
19− pub path: Option<PathBuf>,
20 pub doc: Rope,
10 pub doc: Rope,
21 pub selection: Selection,
11 pub selection: Selection,
22− pub mode: Mode,
23−
24− pub restore_cursor: bool,
25−
26− //
27− pub syntax: Option<Syntax>,
28− /// Pending changes since last history commit.
29− pub changes: ChangeSet,
30− pub old_state: Option<(Rope, Selection)>,
31}
12}
32
13
33#[derive(Copy, Clone, PartialEq, Eq)]
14#[derive(Copy, Clone, PartialEq, Eq)]
@@ -46,57 +27,12 @@ pub enum Granularity {
46impl State {
27impl State {
47 #[must_use]
28 #[must_use]
48 pub fn new(doc: Rope) -> Self {
29 pub fn new(doc: Rope) -> Self {
49− let changes = ChangeSet::new(&doc);
50− let old_state = Some((doc.clone(), Selection::single(0, 0)));
51−
52 Self {
30 Self {
53− path: None,
54 doc,
31 doc,
55 selection: Selection::single(0, 0),
32 selection: Selection::single(0, 0),
56− mode: Mode::Normal,
57− restore_cursor: false,
58− syntax: None,
59− changes,
60− old_state,
61 }
33 }
62 }
34 }
63
35
64− // TODO: passing scopes here is awkward
65− pub fn load(path: PathBuf, scopes: &[String]) -> Result<Self, Error> {
66− use std::{env, fs::File, io::BufReader, path::PathBuf};
67− let _current_dir = env::current_dir()?;
68−
69− let doc = Rope::from_reader(BufReader::new(File::open(path.clone())?))?;
70−
71− // TODO: create if not found
72−
73− let mut state = Self::new(doc);
74−
75− if let Some(language_config) = LOADER.language_config_for_file_name(path.as_path()) {
76− let highlight_config = language_config.highlight_config(scopes).unwrap().unwrap();
77− // TODO: config.configure(scopes) is now delayed, is that ok?
78−
79− let syntax = Syntax::new(&state.doc, highlight_config.clone());
80−
81− state.syntax = Some(syntax);
82− };
83−
84− state.path = Some(path);
85−
86− Ok(state)
87− }
88−
89− pub fn set_language(&mut self, scope: &str, scopes: &[String]) {
90− if let Some(language_config) = LOADER.language_config_for_scope(scope) {
91− let highlight_config = language_config.highlight_config(scopes).unwrap().unwrap();
92− // TODO: config.configure(scopes) is now delayed, is that ok?
93−
94− let syntax = Syntax::new(&self.doc, highlight_config.clone());
95−
96− self.syntax = Some(syntax);
97− };
98− }
99−
100 // TODO: doc/selection accessors
36 // TODO: doc/selection accessors
101
37
102 // TODO: be able to take either Rope or RopeSlice
38 // TODO: be able to take either Rope or RopeSlice
@@ -110,16 +46,6 @@ impl State {
110 &self.selection
46 &self.selection
111 }
47 }
112
48
113− #[inline]
114− pub fn mode(&self) -> Mode {
115− self.mode
116− }
117−
118− #[inline]
119− pub fn path(&self) -> Option<&PathBuf> {
120− self.path.as_ref()
121− }
122−
123 // pub fn doc<R>(&self, range: R) -> RopeSlice
49 // pub fn doc<R>(&self, range: R) -> RopeSlice
124 // where
50 // where
125 // R: std::ops::RangeBounds<usize>,
51 // R: std::ops::RangeBounds<usize>,
helix-core/src/syntax.rs+5−1
@@ -62,11 +62,15 @@ impl LanguageConfiguration {
62 })
62 })
63 .map(Option::as_ref)
63 .map(Option::as_ref)
64 }
64 }
65+
66+ pub fn scope(&self) -> &str {
67+ &self.scope
68+ }
65}
69}
66
70
67use once_cell::sync::Lazy;
71use once_cell::sync::Lazy;
68
72
69−pub(crate) static LOADER: Lazy<Loader> = Lazy::new(Loader::init);
73+pub static LOADER: Lazy<Loader> = Lazy::new(Loader::init);
70
74
71pub struct Loader {
75pub struct Loader {
72 // highlight_names ?
76 // highlight_names ?
helix-core/src/transaction.rs+14−31
@@ -5,8 +5,9 @@ use std::convert::TryFrom;
5/// (from, to, replacement)
5/// (from, to, replacement)
6pub type Change = (usize, usize, Option<Tendril>);
6pub type Change = (usize, usize, Option<Tendril>);
7
7
8+// TODO: pub(crate)
8#[derive(Debug, Clone, PartialEq, Eq)]
9#[derive(Debug, Clone, PartialEq, Eq)]
9−pub(crate) enum Operation {
10+pub enum Operation {
10 /// Move cursor by n characters.
11 /// Move cursor by n characters.
11 Retain(usize),
12 Retain(usize),
12 /// Delete n characters.
13 /// Delete n characters.
@@ -40,6 +41,12 @@ impl ChangeSet {
40 }
41 }
41
42
42 // TODO: from iter
43 // TODO: from iter
44+ //
45+
46+ #[doc(hidden)] // used by lsp to convert to LSP changes
47+ pub fn changes(&self) -> &[Operation] {
48+ &self.changes
49+ }
43
50
44 #[must_use]
51 #[must_use]
45 fn len_after(&self) -> usize {
52 fn len_after(&self) -> usize {
@@ -351,22 +358,6 @@ pub struct Transaction {
351 // scroll_into_view
358 // scroll_into_view
352}
359}
353
360
354−/// Like std::mem::replace() except it allows the replacement value to be mapped from the
355−/// original value.
356−pub fn take_with<T, F>(mut_ref: &mut T, closure: F)
357−where
358− F: FnOnce(T) -> T,
359−{
360− use std::{panic, ptr};
361−
362− unsafe {
363− let old_t = ptr::read(mut_ref);
364− let new_t = panic::catch_unwind(panic::AssertUnwindSafe(|| closure(old_t)))
365− .unwrap_or_else(|_| ::std::process::abort());
366− ptr::write(mut_ref, new_t);
367− }
368−}
369−
370impl Transaction {
361impl Transaction {
371 /// Create a new, empty transaction.
362 /// Create a new, empty transaction.
372 pub fn new(state: &mut State) -> Self {
363 pub fn new(state: &mut State) -> Self {
@@ -376,29 +367,21 @@ impl Transaction {
376 }
367 }
377 }
368 }
378
369
370+ pub fn changes(&self) -> &ChangeSet {
371+ &self.changes
372+ }
373+
379 /// Returns true if applied successfully.
374 /// Returns true if applied successfully.
380 pub fn apply(&self, state: &mut State) -> bool {
375 pub fn apply(&self, state: &mut State) -> bool {
381 if !self.changes.is_empty() {
376 if !self.changes.is_empty() {
382− // TODO: also avoid mapping the selection if not necessary
383−
384− let old_doc = state.doc().clone();
385−
386 // apply changes to the document
377 // apply changes to the document
387 if !self.changes.apply(&mut state.doc) {
378 if !self.changes.apply(&mut state.doc) {
388 return false;
379 return false;
389 }
380 }
390−
391− // Compose this transaction with the previous one
392− take_with(&mut state.changes, |changes| {
393− changes.compose(self.changes.clone()).unwrap()
394− });
395−
396− if let Some(syntax) = &mut state.syntax {
397− // TODO: no unwrap
398− syntax.update(&old_doc, &state.doc, &self.changes).unwrap();
399− }
400 }
381 }
401
382
383+ // TODO: also avoid mapping the selection if not necessary
384+
402 // update the selection: either take the selection specified in the transaction, or map the
385 // update the selection: either take the selection specified in the transaction, or map the
403 // current selection through changes.
386 // current selection through changes.
404 state.selection = self
387 state.selection = self
helix-lsp/Cargo.tomlnew file+26
@@ -0,0 +1,26 @@
1+[package]
2+name = "helix-lsp"
3+version = "0.1.0"
4+authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5+edition = "2018"
6+
7+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+[dependencies]
10+helix-core = { path = "../helix-core" }
11+helix-view = { path = "../helix-view" }
12+once_cell = "1.4"
13+
14+lsp-types = { version = "0.84", features = ["proposed"] }
15+smol = "1.2"
16+url = "2"
17+pathdiff = "0.2"
18+shellexpand = "2.0"
19+glob = "0.3"
20+anyhow = "1"
21+serde_json = "1.0"
22+serde = { version = "1.0", features = ["derive"] }
23+jsonrpc-core = "15.1"
24+futures-util = "0.3"
25+thiserror = "1"
26+log = "0.4"
helix-lsp/src/client.rsnew file+355
@@ -0,0 +1,355 @@
1+use crate::{
2+ transport::{Payload, Transport},
3+ Call, Error,
4+};
5+
6+type Result<T> = core::result::Result<T, Error>;
7+
8+use helix_core::{ChangeSet, Transaction};
9+use helix_view::Document;
10+
11+// use std::collections::HashMap;
12+use std::sync::atomic::{AtomicU64, Ordering};
13+
14+use jsonrpc_core as jsonrpc;
15+use lsp_types as lsp;
16+use serde_json::Value;
17+
18+use smol::{
19+ channel::{Receiver, Sender},
20+ io::{BufReader, BufWriter},
21+ // prelude::*,
22+ process::{Child, ChildStderr, Command, Stdio},
23+ Executor,
24+};
25+
26+pub struct Client {
27+ _process: Child,
28+ stderr: BufReader<ChildStderr>,
29+
30+ outgoing: Sender<Payload>,
31+ pub incoming: Receiver<Call>,
32+
33+ pub request_counter: AtomicU64,
34+
35+ capabilities: Option<lsp::ServerCapabilities>,
36+ // TODO: handle PublishDiagnostics Version
37+ // diagnostics: HashMap<lsp::Url, Vec<lsp::Diagnostic>>,
38+}
39+
40+impl Client {
41+ pub fn start(ex: &Executor, cmd: &str, args: &[String]) -> Self {
42+ let mut process = Command::new(cmd)
43+ .args(args)
44+ .stdin(Stdio::piped())
45+ .stdout(Stdio::piped())
46+ .stderr(Stdio::piped())
47+ .spawn()
48+ .expect("Failed to start language server");
49+ // smol makes sure the process is reaped on drop, but using kill_on_drop(true) maybe?
50+
51+ // TODO: do we need bufreader/writer here? or do we use async wrappers on unblock?
52+ let writer = BufWriter::new(process.stdin.take().expect("Failed to open stdin"));
53+ let reader = BufReader::new(process.stdout.take().expect("Failed to open stdout"));
54+ let stderr = BufReader::new(process.stderr.take().expect("Failed to open stderr"));
55+
56+ let (incoming, outgoing) = Transport::start(ex, reader, writer);
57+
58+ Client {
59+ _process: process,
60+ stderr,
61+
62+ outgoing,
63+ incoming,
64+
65+ request_counter: AtomicU64::new(0),
66+
67+ capabilities: None,
68+ // diagnostics: HashMap::new(),
69+ }
70+ }
71+
72+ fn next_request_id(&self) -> jsonrpc::Id {
73+ let id = self.request_counter.fetch_add(1, Ordering::Relaxed);
74+ jsonrpc::Id::Num(id)
75+ }
76+
77+ fn to_params(value: Value) -> Result<jsonrpc::Params> {
78+ use jsonrpc::Params;
79+
80+ let params = match value {
81+ Value::Null => Params::None,
82+ Value::Bool(_) | Value::Number(_) | Value::String(_) => Params::Array(vec![value]),
83+ Value::Array(vec) => Params::Array(vec),
84+ Value::Object(map) => Params::Map(map),
85+ };
86+
87+ Ok(params)
88+ }
89+
90+ /// Execute a RPC request on the language server.
91+ pub async fn request<R: lsp::request::Request>(&self, params: R::Params) -> Result<R::Result>
92+ where
93+ R::Params: serde::Serialize,
94+ R::Result: core::fmt::Debug, // TODO: temporary
95+ {
96+ let params = serde_json::to_value(params)?;
97+
98+ let request = jsonrpc::MethodCall {
99+ jsonrpc: Some(jsonrpc::Version::V2),
100+ id: self.next_request_id(),
101+ method: R::METHOD.to_string(),
102+ params: Self::to_params(params)?,
103+ };
104+
105+ let (tx, rx) = smol::channel::bounded::<Result<Value>>(1);
106+
107+ self.outgoing
108+ .send(Payload::Request {
109+ chan: tx,
110+ value: request,
111+ })
112+ .await
113+ .map_err(|e| Error::Other(e.into()))?;
114+
115+ let response = rx.recv().await.map_err(|e| Error::Other(e.into()))??;
116+
117+ let response = serde_json::from_value(response)?;
118+
119+ // TODO: we should pass request to a sender thread via a channel
120+ // so it can't be interleaved
121+
122+ // TODO: responses can be out of order, we need to register a single shot response channel
123+
124+ Ok(response)
125+ }
126+
127+ /// Send a RPC notification to the language server.
128+ pub async fn notify<R: lsp::notification::Notification>(&self, params: R::Params) -> Result<()>
129+ where
130+ R::Params: serde::Serialize,
131+ {
132+ let params = serde_json::to_value(params)?;
133+
134+ let notification = jsonrpc::Notification {
135+ jsonrpc: Some(jsonrpc::Version::V2),
136+ method: R::METHOD.to_string(),
137+ params: Self::to_params(params)?,
138+ };
139+
140+ self.outgoing
141+ .send(Payload::Notification(notification))
142+ .await
143+ .map_err(|e| Error::Other(e.into()))?;
144+
145+ Ok(())
146+ }
147+
148+ /// Reply to a language server RPC call.
149+ pub async fn reply(
150+ &self,
151+ id: jsonrpc::Id,
152+ result: core::result::Result<Value, jsonrpc::Error>,
153+ ) -> Result<()> {
154+ use jsonrpc::{Failure, Output, Success, Version};
155+
156+ let output = match result {
157+ Ok(result) => Output::Success(Success {
158+ jsonrpc: Some(Version::V2),
159+ id,
160+ result,
161+ }),
162+ Err(error) => Output::Failure(Failure {
163+ jsonrpc: Some(Version::V2),
164+ id,
165+ error,
166+ }),
167+ };
168+
169+ self.outgoing
170+ .send(Payload::Response(output))
171+ .await
172+ .map_err(|e| Error::Other(e.into()))?;
173+
174+ Ok(())
175+ }
176+
177+ // -------------------------------------------------------------------------------------------
178+ // General messages
179+ // -------------------------------------------------------------------------------------------
180+
181+ pub async fn initialize(&mut self) -> Result<()> {
182+ // TODO: delay any requests that are triggered prior to initialize
183+
184+ #[allow(deprecated)]
185+ let params = lsp::InitializeParams {
186+ process_id: Some(std::process::id()),
187+ root_path: None,
188+ // root_uri: Some(lsp_types::Url::parse("file://localhost/")?),
189+ root_uri: None, // set to project root in the future
190+ initialization_options: None,
191+ capabilities: lsp::ClientCapabilities {
192+ ..Default::default()
193+ },
194+ trace: None,
195+ workspace_folders: None,
196+ client_info: None,
197+ locale: None, // TODO
198+ };
199+
200+ let response = self.request::<lsp::request::Initialize>(params).await?;
201+ self.capabilities = Some(response.capabilities);
202+
203+ // next up, notify<initialized>
204+ self.notify::<lsp::notification::Initialized>(lsp::InitializedParams {})
205+ .await?;
206+
207+ Ok(())
208+ }
209+
210+ pub async fn shutdown(&self) -> Result<()> {
211+ self.request::<lsp::request::Shutdown>(()).await
212+ }
213+
214+ pub async fn exit(&self) -> Result<()> {
215+ self.notify::<lsp::notification::Exit>(()).await
216+ }
217+
218+ // -------------------------------------------------------------------------------------------
219+ // Text document
220+ // -------------------------------------------------------------------------------------------
221+
222+ pub async fn text_document_did_open(&mut self, doc: &Document) -> Result<()> {
223+ self.notify::<lsp::notification::DidOpenTextDocument>(lsp::DidOpenTextDocumentParams {
224+ text_document: lsp::TextDocumentItem {
225+ uri: lsp::Url::from_file_path(doc.path().unwrap()).unwrap(),
226+ language_id: "rust".to_string(), // TODO: hardcoded for now
227+ version: doc.version,
228+ text: String::from(doc.text()),
229+ },
230+ })
231+ .await
232+ }
233+
234+ fn to_changes(changeset: &ChangeSet) -> Vec<lsp::TextDocumentContentChangeEvent> {
235+ let mut iter = changeset.changes().iter().peekable();
236+ let mut old_pos = 0;
237+
238+ let mut changes = Vec::new();
239+
240+ use crate::util::pos_to_lsp_pos;
241+ use helix_core::Operation::*;
242+
243+ // TEMP
244+ let rope = helix_core::Rope::from("");
245+ let old_text = rope.slice(..);
246+
247+ while let Some(change) = iter.next() {
248+ let len = match change {
249+ Delete(i) | Retain(i) => *i,
250+ Insert(_) => 0,
251+ };
252+ let old_end = old_pos + len;
253+
254+ match change {
255+ Retain(_) => {}
256+ Delete(_) => {
257+ let start = pos_to_lsp_pos(&old_text, old_pos);
258+ let end = pos_to_lsp_pos(&old_text, old_end);
259+
260+ // a subsequent ins means a replace, consume it
261+ if let Some(Insert(s)) = iter.peek() {
262+ iter.next();
263+
264+ // replacement
265+ changes.push(lsp::TextDocumentContentChangeEvent {
266+ range: Some(lsp::Range::new(start, end)),
267+ text: s.into(),
268+ range_length: None,
269+ });
270+ } else {
271+ // deletion
272+ changes.push(lsp::TextDocumentContentChangeEvent {
273+ range: Some(lsp::Range::new(start, end)),
274+ text: "".to_string(),
275+ range_length: None,
276+ });
277+ };
278+ }
279+ Insert(s) => {
280+ let start = pos_to_lsp_pos(&old_text, old_pos);
281+
282+ // insert
283+ changes.push(lsp::TextDocumentContentChangeEvent {
284+ range: Some(lsp::Range::new(start, start)),
285+ text: s.into(),
286+ range_length: None,
287+ });
288+ }
289+ }
290+ old_pos = old_end;
291+ }
292+
293+ changes
294+ }
295+
296+ // TODO: trigger any time history.commit_revision happens
297+ pub async fn text_document_did_change(
298+ &mut self,
299+ doc: &Document,
300+ transaction: &Transaction,
301+ ) -> Result<()> {
302+ // figure out what kind of sync the server supports
303+
304+ let capabilities = self.capabilities.as_ref().unwrap(); // TODO: needs post init
305+
306+ let sync_capabilities = match capabilities.text_document_sync {
307+ Some(lsp::TextDocumentSyncCapability::Kind(kind)) => kind,
308+ Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
309+ change: Some(kind),
310+ ..
311+ })) => kind,
312+ // None | SyncOptions { changes: None }
313+ _ => return Ok(()),
314+ };
315+
316+ let changes = match sync_capabilities {
317+ lsp::TextDocumentSyncKind::Full => {
318+ vec![lsp::TextDocumentContentChangeEvent {
319+ // range = None -> whole document
320+ range: None, //Some(Range)
321+ range_length: None, // u64 apparently deprecated
322+ text: "".to_string(),
323+ }] // TODO: probably need old_state here too?
324+ }
325+ lsp::TextDocumentSyncKind::Incremental => Self::to_changes(transaction.changes()),
326+ lsp::TextDocumentSyncKind::None => return Ok(()),
327+ };
328+
329+ self.notify::<lsp::notification::DidChangeTextDocument>(lsp::DidChangeTextDocumentParams {
330+ text_document: lsp::VersionedTextDocumentIdentifier::new(
331+ lsp::Url::from_file_path(doc.path().unwrap()).unwrap(),
332+ doc.version,
333+ ),
334+ content_changes: changes,
335+ })
336+ .await
337+ }
338+
339+ // TODO: impl into() TextDocumentIdentifier / VersionedTextDocumentIdentifier for Document.
340+
341+ pub async fn text_document_did_close(&mut self, doc: &Document) -> Result<()> {
342+ self.notify::<lsp::notification::DidCloseTextDocument>(lsp::DidCloseTextDocumentParams {
343+ text_document: lsp::TextDocumentIdentifier::new(
344+ lsp::Url::from_file_path(doc.path().unwrap()).unwrap(),
345+ ),
346+ })
347+ .await
348+ }
349+
350+ // will_save / will_save_wait_until
351+
352+ pub async fn text_document_did_save(&mut self) -> anyhow::Result<()> {
353+ unimplemented!()
354+ }
355+}
helix-lsp/src/lib.rsnew file+117
@@ -0,0 +1,117 @@
1+mod client;
2+mod transport;
3+
4+pub use jsonrpc_core as jsonrpc;
5+pub use lsp_types as lsp;
6+
7+pub use once_cell::sync::{Lazy, OnceCell};
8+
9+pub use client::Client;
10+pub use lsp::{Position, Url};
11+
12+use thiserror::Error;
13+
14+use std::{collections::HashMap, sync::Arc};
15+
16+#[derive(Error, Debug)]
17+pub enum Error {
18+ #[error("protocol error: {0}")]
19+ Rpc(#[from] jsonrpc::Error),
20+ #[error("failed to parse: {0}")]
21+ Parse(#[from] serde_json::Error),
22+ #[error("request timed out")]
23+ Timeout,
24+ #[error(transparent)]
25+ Other(#[from] anyhow::Error),
26+}
27+
28+pub mod util {
29+ use super::*;
30+
31+ pub fn lsp_pos_to_pos(doc: &helix_core::RopeSlice, pos: lsp::Position) -> usize {
32+ let line = doc.line_to_char(pos.line as usize);
33+ let line_start = doc.char_to_utf16_cu(line);
34+ doc.utf16_cu_to_char(pos.character as usize + line_start)
35+ }
36+ pub fn pos_to_lsp_pos(doc: &helix_core::RopeSlice, pos: usize) -> lsp::Position {
37+ let line = doc.char_to_line(pos);
38+ let line_start = doc.char_to_utf16_cu(line);
39+ let col = doc.char_to_utf16_cu(pos) - line_start;
40+
41+ lsp::Position::new(line as u32, col as u32)
42+ }
43+}
44+
45+#[derive(Debug, PartialEq, Clone)]
46+pub enum Notification {
47+ PublishDiagnostics(lsp::PublishDiagnosticsParams),
48+}
49+
50+impl Notification {
51+ pub fn parse(method: &str, params: jsonrpc::Params) -> Notification {
52+ use lsp::notification::Notification as _;
53+
54+ match method {
55+ lsp::notification::PublishDiagnostics::METHOD => {
56+ let params: lsp::PublishDiagnosticsParams = params
57+ .parse()
58+ .expect("Failed to parse PublishDiagnostics params");
59+
60+ // TODO: need to loop over diagnostics and distinguish them by URI
61+ Notification::PublishDiagnostics(params)
62+ }
63+ _ => unimplemented!("unhandled notification: {}", method),
64+ }
65+ }
66+}
67+
68+pub use jsonrpc::Call;
69+
70+type LanguageId = String;
71+
72+pub static REGISTRY: Lazy<Registry> = Lazy::new(Registry::init);
73+
74+pub struct Registry {
75+ inner: HashMap<LanguageId, OnceCell<Arc<Client>>>,
76+}
77+
78+impl Registry {
79+ pub fn init() -> Self {
80+ Self {
81+ inner: HashMap::new(),
82+ }
83+ }
84+
85+ pub fn get(&self, id: &str, ex: &smol::Executor) -> Option<Arc<Client>> {
86+ // TODO: use get_or_try_init and propagate the error
87+ self.inner
88+ .get(id)
89+ .map(|cell| {
90+ cell.get_or_init(|| {
91+ // TODO: lookup defaults for id (name, args)
92+
93+ // initialize a new client
94+ let client = Client::start(&ex, "rust-analyzer", &[]);
95+ // TODO: also call initialize().await()
96+ Arc::new(client)
97+ })
98+ })
99+ .cloned()
100+ }
101+}
102+
103+// REGISTRY = HashMap<LanguageId, Lazy/OnceCell<Arc<RwLock<Client>>>
104+// spawn one server per language type, need to spawn one per workspace if server doesn't support
105+// workspaces
106+//
107+// could also be a client per root dir
108+//
109+// storing a copy of Option<Arc<RwLock<Client>>> on Document would make the LSP client easily
110+// accessible during edit/save callbacks
111+//
112+// the event loop needs to process all incoming streams, maybe we can just have that be a separate
113+// task that's continually running and store the state on the client, then use read lock to
114+// retrieve data during render
115+// -> PROBLEM: how do you trigger an update on the editor side when data updates?
116+//
117+// -> The data updates should pull all events until we run out so we don't frequently re-render
helix-lsp/src/transport.rsnew file+212
@@ -0,0 +1,212 @@
1+use std::collections::HashMap;
2+
3+use log::debug;
4+
5+use crate::{Error, Notification};
6+
7+type Result<T> = core::result::Result<T, Error>;
8+
9+use jsonrpc_core as jsonrpc;
10+use serde_json::Value;
11+
12+use smol::prelude::*;
13+
14+use smol::{
15+ channel::{Receiver, Sender},
16+ io::{BufReader, BufWriter},
17+ process::{ChildStderr, ChildStdin, ChildStdout},
18+ Executor,
19+};
20+
21+pub(crate) enum Payload {
22+ Request {
23+ chan: Sender<Result<Value>>,
24+ value: jsonrpc::MethodCall,
25+ },
26+ Notification(jsonrpc::Notification),
27+ Response(jsonrpc::Output),
28+}
29+
30+use serde::{Deserialize, Serialize};
31+/// A type representing all possible values sent from the server to the client.
32+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
33+#[serde(deny_unknown_fields)]
34+#[serde(untagged)]
35+enum Message {
36+ /// A regular JSON-RPC request output (single response).
37+ Output(jsonrpc::Output),
38+ /// A JSON-RPC request or notification.
39+ Call(jsonrpc::Call),
40+}
41+
42+pub(crate) struct Transport {
43+ incoming: Sender<jsonrpc::Call>,
44+ outgoing: Receiver<Payload>,
45+
46+ pending_requests: HashMap<jsonrpc::Id, Sender<Result<Value>>>,
47+ headers: HashMap<String, String>,
48+
49+ writer: BufWriter<ChildStdin>,
50+ reader: BufReader<ChildStdout>,
51+}
52+
53+impl Transport {
54+ pub fn start(
55+ ex: &Executor,
56+ reader: BufReader<ChildStdout>,
57+ writer: BufWriter<ChildStdin>,
58+ ) -> (Receiver<jsonrpc::Call>, Sender<Payload>) {
59+ let (incoming, rx) = smol::channel::unbounded();
60+ let (tx, outgoing) = smol::channel::unbounded();
61+
62+ let transport = Self {
63+ reader,
64+ writer,
65+ incoming,
66+ outgoing,
67+ pending_requests: Default::default(),
68+ headers: Default::default(),
69+ };
70+
71+ ex.spawn(transport.duplex()).detach();
72+
73+ (rx, tx)
74+ }
75+
76+ async fn recv(
77+ reader: &mut (impl AsyncBufRead + Unpin),
78+ headers: &mut HashMap<String, String>,
79+ ) -> core::result::Result<Message, std::io::Error> {
80+ // read headers
81+ loop {
82+ let mut header = String::new();
83+ // detect pipe closed if 0
84+ reader.read_line(&mut header).await?;
85+ let header = header.trim();
86+
87+ if header.is_empty() {
88+ break;
89+ }
90+
91+ let parts: Vec<&str> = header.split(": ").collect();
92+ if parts.len() != 2 {
93+ return Err(std::io::Error::new(
94+ std::io::ErrorKind::Other,
95+ "Failed to parse header",
96+ ));
97+ }
98+ headers.insert(parts[0].to_string(), parts[1].to_string());
99+ }
100+
101+ // find content-length
102+ let content_length = headers.get("Content-Length").unwrap().parse().unwrap();
103+
104+ let mut content = vec![0; content_length];
105+ reader.read_exact(&mut content).await?;
106+ let msg = String::from_utf8(content).unwrap();
107+
108+ // read data
109+
110+ // try parsing as output (server response) or call (server request)
111+ let output: serde_json::Result<Message> = serde_json::from_str(&msg);
112+
113+ Ok(output?)
114+ }
115+
116+ pub async fn send_payload(&mut self, payload: Payload) -> anyhow::Result<()> {
117+ match payload {
118+ Payload::Request { chan, value } => {
119+ self.pending_requests.insert(value.id.clone(), chan);
120+
121+ let json = serde_json::to_string(&value)?;
122+ self.send(json).await
123+ }
124+ Payload::Notification(value) => {
125+ let json = serde_json::to_string(&value)?;
126+ self.send(json).await
127+ }
128+ Payload::Response(error) => {
129+ let json = serde_json::to_string(&error)?;
130+ self.send(json).await
131+ }
132+ }
133+ }
134+
135+ pub async fn send(&mut self, request: String) -> anyhow::Result<()> {
136+ debug!("-> {}", request);
137+
138+ // send the headers
139+ self.writer
140+ .write_all(format!("Content-Length: {}\r\n\r\n", request.len()).as_bytes())
141+ .await?;
142+
143+ // send the body
144+ self.writer.write_all(request.as_bytes()).await?;
145+
146+ self.writer.flush().await?;
147+
148+ Ok(())
149+ }
150+
151+ async fn recv_msg(&mut self, msg: Message) -> anyhow::Result<()> {
152+ match msg {
153+ Message::Output(output) => self.recv_response(output).await?,
154+ Message::Call(call) => {
155+ self.incoming.send(call).await?;
156+ // let notification = Notification::parse(&method, params);
157+ }
158+ };
159+ Ok(())
160+ }
161+
162+ async fn recv_response(&mut self, output: jsonrpc::Output) -> anyhow::Result<()> {
163+ match output {
164+ jsonrpc::Output::Success(jsonrpc::Success { id, result, .. }) => {
165+ debug!("<- {}", result);
166+
167+ let tx = self
168+ .pending_requests
169+ .remove(&id)
170+ .expect("pending_request with id not found!");
171+ tx.send(Ok(result)).await?;
172+ }
173+ jsonrpc::Output::Failure(jsonrpc::Failure { id, error, .. }) => {
174+ let tx = self
175+ .pending_requests
176+ .remove(&id)
177+ .expect("pending_request with id not found!");
178+ tx.send(Err(error.into())).await?;
179+ }
180+ msg => unimplemented!("{:?}", msg),
181+ }
182+ Ok(())
183+ }
184+
185+ pub async fn duplex(mut self) {
186+ use futures_util::{select, FutureExt};
187+ loop {
188+ select! {
189+ // client -> server
190+ msg = self.outgoing.next().fuse() => {
191+ if msg.is_none() {
192+ break;
193+ }
194+ let msg = msg.unwrap();
195+
196+ self.send_payload(msg).await.unwrap();
197+ }
198+ // server <- client
199+ msg = Self::recv(&mut self.reader, &mut self.headers).fuse() => {
200+ if msg.is_err() {
201+ break;
202+ }
203+ let msg = msg.unwrap();
204+
205+ debug!("<- {:?}", msg);
206+
207+ self.recv_msg(msg).await.unwrap();
208+ }
209+ }
210+ }
211+ }
212+}
helix-term/Cargo.toml+9−1
@@ -14,12 +14,20 @@ path = "src/main.rs"
14[dependencies]
14[dependencies]
15helix-core = { path = "../helix-core" }
15helix-core = { path = "../helix-core" }
16helix-view = { path = "../helix-view", features = ["term"]}
16helix-view = { path = "../helix-view", features = ["term"]}
17+helix-lsp = { path = "../helix-lsp"}
17
18
18anyhow = "1"
19anyhow = "1"
19
20
20smol = "1"
21smol = "1"
21−num_cpus = "1.13"
22+num_cpus = "1"
22# tui = { version = "0.12", default-features = false, features = ["crossterm"] }
23# tui = { version = "0.12", default-features = false, features = ["crossterm"] }
23tui = { git = "https://github.com/fdehau/tui-rs", default-features = false, features = ["crossterm"] }
24tui = { git = "https://github.com/fdehau/tui-rs", default-features = false, features = ["crossterm"] }
24crossterm = { version = "0.18", features = ["event-stream"] }
25crossterm = { version = "0.18", features = ["event-stream"] }
25clap = { version = "3.0.0-beta.2 ", default-features = false, features = ["std", "cargo"] }
26clap = { version = "3.0.0-beta.2 ", default-features = false, features = ["std", "cargo"] }
27+
28+futures-util = "0.3"
29+
30+# Logging
31+fern = "0.6"
32+chrono = "0.4"
33+log = "0.4"
helix-term/src/application.rs+299−155
@@ -1,6 +1,14 @@
1use clap::ArgMatches as Args;
1use clap::ArgMatches as Args;
2−use helix_core::{indent::TAB_WIDTH, state::Mode, syntax::HighlightEvent, Position, Range, State};
2+use helix_core::{indent::TAB_WIDTH, syntax::HighlightEvent, Position, Range, State};
3−use helix_view::{commands, keymap, prompt::Prompt, Editor, View};
3+use helix_view::{
4+ commands,
5+ document::Mode,
6+ keymap::{self, Keymaps},
7+ prompt::Prompt,
8+ Document, Editor, Theme, View,
9+};
10+
11+use log::{debug, info};
4
12
5use std::{
13use std::{
6 borrow::Cow,
14 borrow::Cow,
@@ -15,8 +23,7 @@ use anyhow::Error;
15
23
16use crossterm::{
24use crossterm::{
17 cursor,
25 cursor,
18− cursor::position,
19− event::{self, read, Event, EventStream, KeyCode, KeyEvent},
26+ event::{read, Event, EventStream, KeyCode, KeyEvent},
20 execute, queue,
27 execute, queue,
21 terminal::{self, disable_raw_mode, enable_raw_mode},
28 terminal::{self, disable_raw_mode, enable_raw_mode},
22};
29};
@@ -25,21 +32,23 @@ use tui::{
25 backend::CrosstermBackend,
32 backend::CrosstermBackend,
26 buffer::Buffer as Surface,
33 buffer::Buffer as Surface,
27 layout::Rect,
34 layout::Rect,
28− style::{Color, Style},
35+ style::{Color, Modifier, Style},
29};
36};
30
37
31−const OFFSET: u16 = 6; // 5 linenr + 1 gutter
38+const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
32
39
33type Terminal = tui::Terminal<CrosstermBackend<std::io::Stdout>>;
40type Terminal = tui::Terminal<CrosstermBackend<std::io::Stdout>>;
34
41
35−static EX: smol::Executor = smol::Executor::new();
36−
37const BASE_WIDTH: u16 = 30;
42const BASE_WIDTH: u16 = 30;
38
43
39−pub struct Application {
44+pub struct Application<'a> {
40 editor: Editor,
45 editor: Editor,
41 prompt: Option<Prompt>,
46 prompt: Option<Prompt>,
42 terminal: Renderer,
47 terminal: Renderer,
48+
49+ keymap: Keymaps,
50+ executor: &'a smol::Executor<'a>,
51+ language_server: helix_lsp::Client,
43}
52}
44
53
45struct Renderer {
54struct Renderer {
@@ -75,30 +84,29 @@ impl Renderer {
75 self.cache = Surface::empty(area);
84 self.cache = Surface::empty(area);
76 }
85 }
77
86
78− pub fn render_view(&mut self, view: &mut View, viewport: Rect) {
87+ pub fn render_view(&mut self, view: &mut View, viewport: Rect, theme: &Theme) {
79− self.render_buffer(view, viewport);
88+ self.render_buffer(view, viewport, theme);
80− self.render_statusline(view);
89+ self.render_statusline(view, theme);
81 }
90 }
82
91
83 // TODO: ideally not &mut View but highlights require it because of cursor cache
92 // TODO: ideally not &mut View but highlights require it because of cursor cache
84− pub fn render_buffer(&mut self, view: &mut View, viewport: Rect) {
93+ pub fn render_buffer(&mut self, view: &mut View, viewport: Rect, theme: &Theme) {
85 let area = Rect::new(0, 0, self.size.0, self.size.1);
94 let area = Rect::new(0, 0, self.size.0, self.size.1);
86 self.surface.reset(); // reset is faster than allocating new empty surface
95 self.surface.reset(); // reset is faster than allocating new empty surface
87
96
88 // clear with background color
97 // clear with background color
89− self.surface
90− .set_style(area, view.theme.get("ui.background"));
98+ self.surface.set_style(area, theme.get("ui.background"));
91
99
92 // TODO: inefficient, should feed chunks.iter() to tree_sitter.parse_with(|offset, pos|)
100 // TODO: inefficient, should feed chunks.iter() to tree_sitter.parse_with(|offset, pos|)
93− let source_code = view.state.doc().to_string();
101+ let source_code = view.doc.text().to_string();
94
102
95 let last_line = view.last_line();
103 let last_line = view.last_line();
96
104
97 let range = {
105 let range = {
98 // calculate viewport byte ranges
106 // calculate viewport byte ranges
99− let start = view.state.doc().line_to_byte(view.first_line);
107+ let start = view.doc.text().line_to_byte(view.first_line);
100− let end = view.state.doc().line_to_byte(last_line)
108+ let end = view.doc.text().line_to_byte(last_line)
101− + view.state.doc().line(last_line).len_bytes();
109+ + view.doc.text().line(last_line).len_bytes();
102
110
103 start..end
111 start..end
104 };
112 };
@@ -106,7 +114,7 @@ impl Renderer {
106 // TODO: range doesn't actually restrict source, just highlight range
114 // TODO: range doesn't actually restrict source, just highlight range
107 // TODO: cache highlight results
115 // TODO: cache highlight results
108 // TODO: only recalculate when state.doc is actually modified
116 // TODO: only recalculate when state.doc is actually modified
109− let highlights: Vec<_> = match view.state.syntax.as_mut() {
117+ let highlights: Vec<_> = match view.doc.syntax.as_mut() {
110 Some(syntax) => {
118 Some(syntax) => {
111 syntax
119 syntax
112 .highlight_iter(source_code.as_bytes(), Some(range), None, |_| None)
120 .highlight_iter(source_code.as_bytes(), Some(range), None, |_| None)
@@ -122,6 +130,7 @@ impl Renderer {
122 let mut visual_x = 0;
130 let mut visual_x = 0;
123 let mut line = 0u16;
131 let mut line = 0u16;
124 let visible_selections: Vec<Range> = view
132 let visible_selections: Vec<Range> = view
133+ .doc
125 .state
134 .state
126 .selection()
135 .selection()
127 .ranges()
136 .ranges()
@@ -142,15 +151,15 @@ impl Renderer {
142 HighlightEvent::Source { start, end } => {
151 HighlightEvent::Source { start, end } => {
143 // TODO: filter out spans out of viewport for now..
152 // TODO: filter out spans out of viewport for now..
144
153
145− let start = view.state.doc().byte_to_char(start);
154+ let start = view.doc.text().byte_to_char(start);
146− let end = view.state.doc().byte_to_char(end); // <-- index 744, len 743
155+ let end = view.doc.text().byte_to_char(end); // <-- index 744, len 743
147
156
148− let text = view.state.doc().slice(start..end);
157+ let text = view.doc.text().slice(start..end);
149
158
150 use helix_core::graphemes::{grapheme_width, RopeGraphemes};
159 use helix_core::graphemes::{grapheme_width, RopeGraphemes};
151
160
152 let style = match spans.first() {
161 let style = match spans.first() {
153− Some(span) => view.theme.get(view.theme.scopes()[span.0].as_str()),
162+ Some(span) => theme.get(theme.scopes()[span.0].as_str()),
154 None => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
163 None => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
155 };
164 };
156
165
@@ -200,6 +209,16 @@ impl Renderer {
200 style
209 style
201 };
210 };
202
211
212+ // ugh, improve with a traverse method
213+ // or interleave highlight spans with selection and diagnostic spans
214+ let style = if view.doc.diagnostics.iter().any(|diagnostic| {
215+ diagnostic.range.0 <= char_index && diagnostic.range.1 > char_index
216+ }) {
217+ style.clone().add_modifier(Modifier::UNDERLINED)
218+ } else {
219+ style
220+ };
221+
203 // TODO: paint cursor heads except primary
222 // TODO: paint cursor heads except primary
204
223
205 self.surface
224 self.surface
@@ -207,23 +226,28 @@ impl Renderer {
207
226
208 visual_x += width;
227 visual_x += width;
209 }
228 }
210− // if grapheme == "\t"
211
229
212 char_index += 1;
230 char_index += 1;
213 }
231 }
214 }
232 }
215 }
233 }
216 }
234 }
235+
217− let style: Style = view.theme.get("ui.linenr");
236+ let style: Style = theme.get("ui.linenr");
237+ let warning: Style = theme.get("warning");
218 let last_line = view.last_line();
238 let last_line = view.last_line();
219 for (i, line) in (view.first_line..last_line).enumerate() {
239 for (i, line) in (view.first_line..last_line).enumerate() {
240+ if view.doc.diagnostics.iter().any(|d| d.line == line) {
241+ self.surface.set_stringn(0, i as u16, "●", 1, warning);
242+ }
243+
220 self.surface
244 self.surface
221− .set_stringn(0, i as u16, format!("{:>5}", line + 1), 5, style);
245+ .set_stringn(1, i as u16, format!("{:>5}", line + 1), 5, style);
222 }
246 }
223 }
247 }
224
248
225− pub fn render_statusline(&mut self, view: &View) {
249+ pub fn render_statusline(&mut self, view: &View, theme: &Theme) {
226− let mode = match view.state.mode() {
250+ let mode = match view.doc.mode() {
227 Mode::Insert => "INS",
251 Mode::Insert => "INS",
228 Mode::Normal => "NOR",
252 Mode::Normal => "NOR",
229 Mode::Goto => "GOTO",
253 Mode::Goto => "GOTO",
@@ -231,13 +255,20 @@ impl Renderer {
231 // statusline
255 // statusline
232 self.surface.set_style(
256 self.surface.set_style(
233 Rect::new(0, self.size.1 - 2, self.size.0, 1),
257 Rect::new(0, self.size.1 - 2, self.size.0, 1),
234− view.theme.get("ui.statusline"),
258+ theme.get("ui.statusline"),
235 );
259 );
236 self.surface
260 self.surface
237 .set_string(1, self.size.1 - 2, mode, self.text_color);
261 .set_string(1, self.size.1 - 2, mode, self.text_color);
262+
263+ self.surface.set_string(
264+ self.size.0 - 10,
265+ self.size.1 - 2,
266+ format!("{}", view.doc.diagnostics.len()),
267+ self.text_color,
268+ );
238 }
269 }
239
270
240− pub fn render_prompt(&mut self, view: &View, prompt: &Prompt) {
271+ pub fn render_prompt(&mut self, view: &View, prompt: &Prompt, theme: &Theme) {
241 // completion
272 // completion
242 if !prompt.completion.is_empty() {
273 if !prompt.completion.is_empty() {
243 // TODO: find out better way of clearing individual lines of the screen
274 // TODO: find out better way of clearing individual lines of the screen
@@ -256,7 +287,7 @@ impl Renderer {
256 }
287 }
257 self.surface.set_style(
288 self.surface.set_style(
258 Rect::new(0, self.size.1 - col_height - 2, self.size.0, col_height),
289 Rect::new(0, self.size.1 - col_height - 2, self.size.0, col_height),
259− view.theme.get("ui.statusline"),
290+ theme.get("ui.statusline"),
260 );
291 );
261 for (i, command) in prompt.completion.iter().enumerate() {
292 for (i, command) in prompt.completion.iter().enumerate() {
262 let color = if prompt.completion_selection_index.is_some()
293 let color = if prompt.completion_selection_index.is_some()
@@ -302,14 +333,14 @@ impl Renderer {
302
333
303 pub fn render_cursor(&mut self, view: &View, prompt: Option<&Prompt>, viewport: Rect) {
334 pub fn render_cursor(&mut self, view: &View, prompt: Option<&Prompt>, viewport: Rect) {
304 let mut stdout = stdout();
335 let mut stdout = stdout();
305− match view.state.mode() {
336+ match view.doc.mode() {
306 Mode::Insert => write!(stdout, "\x1B[6 q"),
337 Mode::Insert => write!(stdout, "\x1B[6 q"),
307 mode => write!(stdout, "\x1B[2 q"),
338 mode => write!(stdout, "\x1B[2 q"),
308 };
339 };
309 let pos = if let Some(prompt) = prompt {
340 let pos = if let Some(prompt) = prompt {
310 Position::new(self.size.0 as usize, 2 + prompt.cursor)
341 Position::new(self.size.0 as usize, 2 + prompt.cursor)
311 } else {
342 } else {
312− if let Some(path) = view.state.path() {
343+ if let Some(path) = view.doc.path() {
313 self.surface.set_string(
344 self.surface.set_string(
314 6,
345 6,
315 self.size.1 - 1,
346 self.size.1 - 1,
@@ -318,10 +349,10 @@ impl Renderer {
318 );
349 );
319 }
350 }
320
351
321− let cursor = view.state.selection().cursor();
352+ let cursor = view.doc.state.selection().cursor();
322
353
323 let mut pos = view
354 let mut pos = view
324− .screen_coords_at_pos(&view.state.doc().slice(..), cursor)
355+ .screen_coords_at_pos(&view.doc.text().slice(..), cursor)
325 .expect("Cursor is out of bounds.");
356 .expect("Cursor is out of bounds.");
326 pos.col += viewport.x as usize;
357 pos.col += viewport.x as usize;
327 pos.row += viewport.y as usize;
358 pos.row += viewport.y as usize;
@@ -332,8 +363,8 @@ impl Renderer {
332 }
363 }
333}
364}
334
365
335−impl Application {
366+impl<'a> Application<'a> {
336− pub fn new(mut args: Args) -> Result<Self, Error> {
367+ pub fn new(mut args: Args, executor: &'a smol::Executor<'a>) -> Result<Self, Error> {
337 let terminal = Renderer::new()?;
368 let terminal = Renderer::new()?;
338 let mut editor = Editor::new();
369 let mut editor = Editor::new();
339
370
@@ -341,11 +372,18 @@ impl Application {
341 editor.open(file, terminal.size)?;
372 editor.open(file, terminal.size)?;
342 }
373 }
343
374
375+ let language_server = helix_lsp::Client::start(&executor, "rust-analyzer", &[]);
376+
344 let mut app = Self {
377 let mut app = Self {
345 editor,
378 editor,
346 terminal,
379 terminal,
347 // TODO; move to state
380 // TODO; move to state
348 prompt: None,
381 prompt: None,
382+
383+ //
384+ keymap: keymap::default(),
385+ executor,
386+ language_server,
349 };
387 };
350
388
351 Ok(app)
389 Ok(app)
@@ -354,13 +392,16 @@ impl Application {
354 fn render(&mut self) {
392 fn render(&mut self) {
355 let viewport = Rect::new(OFFSET, 0, self.terminal.size.0, self.terminal.size.1 - 2); // - 2 for statusline and prompt
393 let viewport = Rect::new(OFFSET, 0, self.terminal.size.0, self.terminal.size.1 - 2); // - 2 for statusline and prompt
356
394
395+ // SAFETY: we cheat around the view_mut() borrow because it doesn't allow us to also borrow
396+ // theme. Theme is immutable mutating view won't disrupt theme_ref.
397+ let theme_ref = unsafe { &*(&self.editor.theme as *const Theme) };
357− if let Some(view) = &mut self.editor.view {
398+ if let Some(view) = self.editor.view_mut() {
358− self.terminal.render_view(view, viewport);
399+ self.terminal.render_view(view, viewport, theme_ref);
359 if let Some(prompt) = &self.prompt {
400 if let Some(prompt) = &self.prompt {
360 if prompt.should_close {
401 if prompt.should_close {
361 self.prompt = None;
402 self.prompt = None;
362 } else {
403 } else {
363− self.terminal.render_prompt(view, prompt);
404+ self.terminal.render_prompt(view, prompt, theme_ref);
364 }
405 }
365 }
406 }
366 }
407 }
@@ -368,16 +409,19 @@ impl Application {
368 self.terminal.draw();
409 self.terminal.draw();
369
410
370 // TODO: drop unwrap
411 // TODO: drop unwrap
371− self.terminal.render_cursor(
412+ self.terminal
372− self.editor.view.as_ref().unwrap(),
413+ .render_cursor(self.editor.view().unwrap(), self.prompt.as_ref(), viewport);
373− self.prompt.as_ref(),
374− viewport,
375− );
376 }
414 }
377
415
378 pub async fn event_loop(&mut self) {
416 pub async fn event_loop(&mut self) {
379 let mut reader = EventStream::new();
417 let mut reader = EventStream::new();
380− let keymap = keymap::default();
418+
419+ // initialize lsp
420+ self.language_server.initialize().await.unwrap();
421+ self.language_server
422+ .text_document_did_open(&self.editor.view().unwrap().doc)
423+ .await
424+ .unwrap();
381
425
382 self.render();
426 self.render();
383
427
@@ -386,125 +430,225 @@ impl Application {
386 break;
430 break;
387 }
431 }
388
432
389− // Handle key events
433+ use futures_util::{select, FutureExt};
390− match reader.next().await {
434+ select! {
391− Some(Ok(Event::Resize(width, height))) => {
435+ event = reader.next().fuse() => {
392− self.terminal.resize(width, height);
436+ self.handle_terminal_events(event).await
437+ }
438+ call = self.language_server.incoming.next().fuse() => {
439+ self.handle_language_server_message(call).await
440+ }
441+ }
442+ }
443+ }
393
444
445+ pub async fn handle_terminal_events(
446+ &mut self,
447+ event: Option<Result<Event, crossterm::ErrorKind>>,
448+ ) {
449+ // Handle key events
450+ match event {
451+ Some(Ok(Event::Resize(width, height))) => {
452+ self.terminal.resize(width, height);
453+
394− // TODO: simplistic ensure cursor in view for now
454+ // TODO: simplistic ensure cursor in view for now
455+ // TODO: loop over views
395− if let Some(view) = &mut self.editor.view {
456+ if let Some(view) = self.editor.view_mut() {
396− view.size = self.terminal.size;
457+ view.size = self.terminal.size;
397− view.ensure_cursor_in_view()
458+ view.ensure_cursor_in_view()
398− };
459+ };
460+
461+ self.render();
462+ }
463+ Some(Ok(Event::Key(event))) => {
464+ // if there's a prompt, it takes priority
465+ if let Some(prompt) = &mut self.prompt {
466+ self.prompt
467+ .as_mut()
468+ .unwrap()
469+ .handle_input(event, &mut self.editor);
399
470
400 self.render();
471 self.render();
401− }
402− Some(Ok(Event::Key(event))) => {
403− // if there's a prompt, it takes priority
404− if let Some(prompt) = &mut self.prompt {
405− self.prompt
406− .as_mut()
407− .unwrap()
408− .handle_input(event, &mut self.editor);
409−
410− self.render();
411− } else if let Some(view) = &mut self.editor.view {
472+ } else if let Some(view) = self.editor.view_mut() {
412− let keys = vec![event];
473+ let keys = vec![event];
413− // TODO: sequences (`gg`)
474+ // TODO: sequences (`gg`)
414− // TODO: handle count other than 1
475+ // TODO: handle count other than 1
415− match view.state.mode() {
476+ match view.doc.mode() {
416− Mode::Insert => {
477+ Mode::Insert => {
417− if let Some(command) = keymap[&Mode::Insert].get(&keys) {
478+ if let Some(command) = self.keymap[&Mode::Insert].get(&keys) {
479+ let mut cx = helix_view::commands::Context {
418− command(view, 1);
480+ view,
481+ executor: self.executor,
482+ count: 1,
483+ };
484+
485+ command(&mut cx);
419− } else if let KeyEvent {
486+ } else if let KeyEvent {
420− code: KeyCode::Char(c),
487+ code: KeyCode::Char(c),
421− ..
488+ ..
422− } = event
489+ } = event
423− {
490+ {
491+ let mut cx = helix_view::commands::Context {
492+ view,
493+ executor: self.executor,
494+ count: 1,
495+ };
424− commands::insert::insert_char(view, c);
496+ commands::insert::insert_char(&mut cx, c);
425− }
497+ }
498+ view.ensure_cursor_in_view();
499+ }
500+ Mode::Normal => {
501+ if let &[KeyEvent {
502+ code: KeyCode::Char(':'),
503+ ..
504+ }] = keys.as_slice()
505+ {
506+ let prompt = Prompt::new(
507+ ":".to_owned(),
508+ |_input: &str| {
509+ // TODO: i need this duplicate list right now to avoid borrow checker issues
510+ let command_list = vec![
511+ String::from("q"),
512+ String::from("aaa"),
513+ String::from("bbb"),
514+ String::from("ccc"),
515+ String::from("ddd"),
516+ String::from("eee"),
517+ String::from("averylongcommandaverylongcommandaverylongcommandaverylongcommandaverylongcommand"),
518+ String::from("q"),
519+ String::from("aaa"),
520+ String::from("bbb"),
521+ String::from("ccc"),
522+ String::from("ddd"),
523+ String::from("eee"),
524+ String::from("q"),
525+ String::from("aaa"),
526+ String::from("bbb"),
527+ String::from("ccc"),
528+ String::from("ddd"),
529+ String::from("eee"),
530+ String::from("q"),
531+ String::from("aaa"),
532+ String::from("bbb"),
533+ String::from("ccc"),
534+ String::from("ddd"),
535+ String::from("eee"),
536+ String::from("q"),
537+ String::from("aaa"),
538+ String::from("bbb"),
539+ String::from("ccc"),
540+ String::from("ddd"),
541+ String::from("eee"),
542+ ];
543+ command_list
544+ .into_iter()
545+ .filter(|command| command.contains(_input))
546+ .collect()
547+ }, // completion
548+ |editor: &mut Editor, input: &str| match input {
549+ "q" => editor.should_close = true,
550+ _ => (),
551+ },
552+ );
553+
554+ self.prompt = Some(prompt);
555+
556+ // HAXX: special casing for command mode
557+ } else if let Some(command) = self.keymap[&Mode::Normal].get(&keys) {
558+ let mut cx = helix_view::commands::Context {
559+ view,
560+ executor: self.executor,
561+ count: 1,
562+ };
563+ command(&mut cx);
564+
565+ // TODO: simplistic ensure cursor in view for now
426 view.ensure_cursor_in_view();
566 view.ensure_cursor_in_view();
427 }
567 }
428− Mode::Normal => {
568+ }
429− if let &[KeyEvent {
569+ mode => {
430− code: KeyCode::Char(':'),
570+ if let Some(command) = self.keymap[&mode].get(&keys) {
431− ..
571+ let mut cx = helix_view::commands::Context {
432− }] = keys.as_slice()
433− {
434− let prompt = Prompt::new(
435− ":".to_owned(),
436− |_input: &str| {
437− // TODO: i need this duplicate list right now to avoid borrow checker issues
438− let command_list = vec![
439− String::from("q"),
440− String::from("aaa"),
441− String::from("bbb"),
442− String::from("ccc"),
443− String::from("ddd"),
444− String::from("eee"),
445− String::from("averylongcommandaverylongcommandaverylongcommandaverylongcommandaverylongcommand"),
446− String::from("q"),
447− String::from("aaa"),
448− String::from("bbb"),
449− String::from("ccc"),
450− String::from("ddd"),
451− String::from("eee"),
452− String::from("q"),
453− String::from("aaa"),
454− String::from("bbb"),
455− String::from("ccc"),
456− String::from("ddd"),
457− String::from("eee"),
458− String::from("q"),
459− String::from("aaa"),
460− String::from("bbb"),
461− String::from("ccc"),
462− String::from("ddd"),
463− String::from("eee"),
464− String::from("q"),
465− String::from("aaa"),
466− String::from("bbb"),
467− String::from("ccc"),
468− String::from("ddd"),
469− String::from("eee"),
470− ];
471− command_list
472− .into_iter()
473− .filter(|command| command.contains(_input))
474− .collect()
475− }, // completion
476− |editor: &mut Editor, input: &str| match input {
477− "q" => editor.should_close = true,
478− _ => (),
479− },
480− );
481−
482− self.prompt = Some(prompt);
483−
484− // HAXX: special casing for command mode
485− } else if let Some(command) = keymap[&Mode::Normal].get(&keys) {
486− command(view, 1);
572+ view,
487−
573+ executor: self.executor,
574+ count: 1,
575+ };
576+ command(&mut cx);
577+
488− // TODO: simplistic ensure cursor in view for now
578+ // TODO: simplistic ensure cursor in view for now
489− view.ensure_cursor_in_view();
579+ view.ensure_cursor_in_view();
490− }
491 }
580 }
492− mode => {
581+ }
493− if let Some(command) = keymap[&mode].get(&keys) {
582+ }
494− command(view, 1);
583+ self.render();
584+ }
585+ }
586+ Some(Ok(Event::Mouse(_))) => (), // unhandled
587+ Some(Err(x)) => panic!(x),
588+ None => panic!(),
589+ };
590+ }
495
591
496− // TODO: simplistic ensure cursor in view for now
592+ pub async fn handle_language_server_message(&mut self, call: Option<helix_lsp::Call>) {
497− view.ensure_cursor_in_view();
593+ use helix_lsp::{Call, Notification};
498− }
594+ match call {
499− }
595+ Some(Call::Notification(helix_lsp::jsonrpc::Notification {
596+ method, params, ..
597+ })) => {
598+ let notification = Notification::parse(&method, params);
599+ match notification {
600+ Notification::PublishDiagnostics(params) => {
601+ let path = Some(params.uri.to_file_path().unwrap());
602+ let view = self
603+ .editor
604+ .views
605+ .iter_mut()
606+ .find(|view| view.doc.path == path);
607+
608+ if let Some(view) = view {
609+ let doc = view.doc.text().slice(..);
610+ let diagnostics = params
611+ .diagnostics
612+ .into_iter()
613+ .map(|diagnostic| {
614+ use helix_lsp::util::lsp_pos_to_pos;
615+ let start = lsp_pos_to_pos(&doc, diagnostic.range.start);
616+ let end = lsp_pos_to_pos(&doc, diagnostic.range.end);
617+
618+ helix_core::Diagnostic {
619+ range: (start, end),
620+ line: diagnostic.range.start.line as usize,
621+ message: diagnostic.message,
622+ // severity
623+ // code
624+ // source
625+ }
626+ })
627+ .collect();
628+
629+ view.doc.diagnostics = diagnostics;
630+
631+ // TODO: we want to process all the events in queue, then render. publishDiagnostic tends to send a whole bunch of events
632+ self.render();
500 }
633 }
501− self.render();
502 }
634 }
635+ _ => unreachable!(),
503 }
636 }
504− Some(Ok(Event::Mouse(_))) => (), // unhandled
505− Some(Err(x)) => panic!(x),
506− None => break,
507 }
637 }
638+ Some(Call::MethodCall(call)) => {
639+ debug!("Method not found {}", call.method);
640+
641+ self.language_server.reply(
642+ call.id,
643+ // TODO: make a Into trait that can cast to Err(jsonrpc::Error)
644+ Err(helix_lsp::jsonrpc::Error {
645+ code: helix_lsp::jsonrpc::ErrorCode::MethodNotFound,
646+ message: "Method not found".to_string(),
647+ data: None,
648+ }),
649+ );
650+ }
651+ _ => unreachable!(),
508 }
652 }
509 }
653 }
510
654
helix-term/src/main.rs+48−3
@@ -11,6 +11,39 @@ use anyhow::Error;
11
11
12static EX: smol::Executor = smol::Executor::new();
12static EX: smol::Executor = smol::Executor::new();
13
13
14+fn setup_logging(verbosity: u64) -> Result<(), fern::InitError> {
15+ let mut base_config = fern::Dispatch::new();
16+
17+ // Let's say we depend on something which whose "info" level messages are too
18+ // verbose to include in end-user output. If we don't need them,
19+ // let's not include them.
20+ // .level_for("overly-verbose-target", log::LevelFilter::Warn)
21+
22+ base_config = match verbosity {
23+ 0 => base_config.level(log::LevelFilter::Warn),
24+ 1 => base_config.level(log::LevelFilter::Info),
25+ 2 => base_config.level(log::LevelFilter::Debug),
26+ _3_or_more => base_config.level(log::LevelFilter::Trace),
27+ };
28+
29+ // Separate file config so we can include year, month and day in file logs
30+ let file_config = fern::Dispatch::new()
31+ .format(|out, message, record| {
32+ out.finish(format_args!(
33+ "{} {} [{}] {}",
34+ chrono::Local::now().format("%Y-%m-%dT%H:%M:%S%.3f"),
35+ record.target(),
36+ record.level(),
37+ message
38+ ))
39+ })
40+ .chain(fern::log_file("helix.log")?);
41+
42+ base_config.chain(file_config).apply()?;
43+
44+ Ok(())
45+}
46+
14fn main() -> Result<(), Error> {
47fn main() -> Result<(), Error> {
15 let args = clap::app_from_crate!()
48 let args = clap::app_from_crate!()
16 .arg(
49 .arg(
@@ -20,15 +53,27 @@ fn main() -> Result<(), Error> {
20 .multiple(true)
53 .multiple(true)
21 .index(1),
54 .index(1),
22 )
55 )
56+ .arg(
57+ Arg::new("verbose")
58+ .about("Increases logging verbosity each use for up to 3 times")
59+ .short('v')
60+ .takes_value(false)
61+ .multiple_occurrences(true),
62+ )
23 .get_matches();
63 .get_matches();
24
64
65+ let verbosity: u64 = args.occurrences_of("verbose");
66+
67+ setup_logging(verbosity).expect("failed to initialize logging.");
68+
25 for _ in 0..num_cpus::get() {
69 for _ in 0..num_cpus::get() {
26 std::thread::spawn(move || smol::block_on(EX.run(smol::future::pending::<()>())));
70 std::thread::spawn(move || smol::block_on(EX.run(smol::future::pending::<()>())));
27 }
71 }
28
72
29− smol::block_on(EX.run(async {
30− Application::new(args).unwrap().run().await;
73+ let mut app = Application::new(args, &EX).unwrap();
31− }));
74+
75+ // we use the thread local executor to spawn the application task separately from the work pool
76+ smol::block_on(app.run());
32
77
33 Ok(())
78 Ok(())
34}
79}
helix-view/Cargo.toml+3
@@ -19,3 +19,6 @@ helix-core = { path = "../helix-core" }
19tui = { git = "https://github.com/fdehau/tui-rs", default-features = false, features = ["crossterm"], optional = true}
19tui = { git = "https://github.com/fdehau/tui-rs", default-features = false, features = ["crossterm"], optional = true}
20crossterm = { version = "0.18", features = ["event-stream"], optional = true}
20crossterm = { version = "0.18", features = ["event-stream"], optional = true}
21once_cell = "1.4"
21once_cell = "1.4"
22+url = "2"
23+
24+smol = "1"
helix-view/src/commands.rs+290−243
@@ -3,52 +3,65 @@ use helix_core::{
3 indent::TAB_WIDTH,
3 indent::TAB_WIDTH,
4 regex::Regex,
4 regex::Regex,
5 register, selection,
5 register, selection,
6− state::{Direction, Granularity, Mode, State},
6+ state::{Direction, Granularity, State},
7 ChangeSet, Range, Selection, Tendril, Transaction,
7 ChangeSet, Range, Selection, Tendril, Transaction,
8};
8};
9use once_cell::sync::Lazy;
9use once_cell::sync::Lazy;
10
10
11use crate::{
11use crate::{
12+ document::Mode,
12 prompt::Prompt,
13 prompt::Prompt,
13 view::{View, PADDING},
14 view::{View, PADDING},
14};
15};
15
16
17+pub struct Context<'a, 'b> {
18+ pub count: usize,
19+ pub view: &'a mut View,
20+ pub executor: &'a smol::Executor<'b>,
21+}
22+
16/// A command is a function that takes the current state and a count, and does a side-effect on the
23/// A command is a function that takes the current state and a count, and does a side-effect on the
17/// state (usually by creating and applying a transaction).
24/// state (usually by creating and applying a transaction).
18−pub type Command = fn(view: &mut View, count: usize);
25+pub type Command = fn(cx: &mut Context);
19
26
20−pub fn move_char_left(view: &mut View, count: usize) {
27+pub fn move_char_left(cx: &mut Context) {
21− // TODO: use a transaction
22− let selection = view
28+ let selection =
29+ cx.view
30+ .doc
23− .state
31+ .state
24− .move_selection(Direction::Backward, Granularity::Character, count);
32+ .move_selection(Direction::Backward, Granularity::Character, cx.count);
25− view.state.selection = selection;
33+ cx.view.doc.set_selection(selection);
26}
34}
27
35
28−pub fn move_char_right(view: &mut View, count: usize) {
36+pub fn move_char_right(cx: &mut Context) {
29− // TODO: use a transaction
30− view.state.selection =
37+ let selection =
38+ cx.view
39+ .doc
31− view.state
40+ .state
32− .move_selection(Direction::Forward, Granularity::Character, count);
41+ .move_selection(Direction::Forward, Granularity::Character, cx.count);
42+ cx.view.doc.set_selection(selection);
33}
43}
34
44
35−pub fn move_line_up(view: &mut View, count: usize) {
45+pub fn move_line_up(cx: &mut Context) {
36− // TODO: use a transaction
37− view.state.selection = view
46+ let selection =
47+ cx.view
48+ .doc
38− .state
49+ .state
39− .move_selection(Direction::Backward, Granularity::Line, count);
50+ .move_selection(Direction::Backward, Granularity::Line, cx.count);
51+ cx.view.doc.set_selection(selection);
40}
52}
41
53
42−pub fn move_line_down(view: &mut View, count: usize) {
54+pub fn move_line_down(cx: &mut Context) {
43− // TODO: use a transaction
44− view.state.selection = view
55+ let selection =
56+ cx.view
57+ .doc
45− .state
58+ .state
46− .move_selection(Direction::Forward, Granularity::Line, count);
59+ .move_selection(Direction::Forward, Granularity::Line, cx.count);
60+ cx.view.doc.set_selection(selection);
47}
61}
48
62
49−pub fn move_line_end(view: &mut View, _count: usize) {
63+pub fn move_line_end(cx: &mut Context) {
50− // TODO: use a transaction
51− let lines = selection_lines(&view.state);
64+ let lines = selection_lines(&cx.view.doc.state);
52
65
53 let positions = lines
66 let positions = lines
54 .into_iter()
67 .into_iter()
@@ -57,89 +70,80 @@ pub fn move_line_end(view: &mut View, _count: usize) {
57
70
58 // Line end is pos at the start of next line - 1
71 // Line end is pos at the start of next line - 1
59 // subtract another 1 because the line ends with \n
72 // subtract another 1 because the line ends with \n
60− view.state.doc.line_to_char(index + 1).saturating_sub(2)
73+ cx.view.doc.text().line_to_char(index + 1).saturating_sub(2)
61 })
74 })
62 .map(|pos| Range::new(pos, pos));
75 .map(|pos| Range::new(pos, pos));
63
76
64 let selection = Selection::new(positions.collect(), 0);
77 let selection = Selection::new(positions.collect(), 0);
65
78
66− let transaction = Transaction::new(&mut view.state).with_selection(selection);
79+ cx.view.doc.set_selection(selection);
67−
68− transaction.apply(&mut view.state);
69}
80}
70
81
71−pub fn move_line_start(view: &mut View, _count: usize) {
82+pub fn move_line_start(cx: &mut Context) {
72− let lines = selection_lines(&view.state);
83+ let lines = selection_lines(&cx.view.doc.state);
73
84
74 let positions = lines
85 let positions = lines
75 .into_iter()
86 .into_iter()
76 .map(|index| {
87 .map(|index| {
77 // adjust all positions to the start of the line.
88 // adjust all positions to the start of the line.
78− view.state.doc.line_to_char(index)
89+ cx.view.doc.text().line_to_char(index)
79 })
90 })
80 .map(|pos| Range::new(pos, pos));
91 .map(|pos| Range::new(pos, pos));
81
92
82 let selection = Selection::new(positions.collect(), 0);
93 let selection = Selection::new(positions.collect(), 0);
83
94
84− let transaction = Transaction::new(&mut view.state).with_selection(selection);
95+ cx.view.doc.set_selection(selection);
85−
86− transaction.apply(&mut view.state);
87}
96}
88
97
89−pub fn move_next_word_start(view: &mut View, count: usize) {
98+pub fn move_next_word_start(cx: &mut Context) {
90− let pos = view.state.move_pos(
99+ let pos = cx.view.doc.state.move_pos(
91− view.state.selection.cursor(),
100+ cx.view.doc.selection().cursor(),
92 Direction::Forward,
101 Direction::Forward,
93 Granularity::Word,
102 Granularity::Word,
94− count,
103+ cx.count,
95 );
104 );
96
105
97− // TODO: use a transaction
98− view.state.selection = Selection::single(pos, pos);
106+ cx.view.doc.set_selection(Selection::point(pos));
99}
107}
100
108
101−pub fn move_prev_word_start(view: &mut View, count: usize) {
109+pub fn move_prev_word_start(cx: &mut Context) {
102− let pos = view.state.move_pos(
110+ let pos = cx.view.doc.state.move_pos(
103− view.state.selection.cursor(),
111+ cx.view.doc.selection().cursor(),
104 Direction::Backward,
112 Direction::Backward,
105 Granularity::Word,
113 Granularity::Word,
106− count,
114+ cx.count,
107 );
115 );
108
116
109− // TODO: use a transaction
110− view.state.selection = Selection::single(pos, pos);
117+ cx.view.doc.set_selection(Selection::point(pos));
111}
118}
112
119
113−pub fn move_next_word_end(view: &mut View, count: usize) {
120+pub fn move_next_word_end(cx: &mut Context) {
114 let pos = State::move_next_word_end(
121 let pos = State::move_next_word_end(
115− &view.state.doc().slice(..),
122+ &cx.view.doc.text().slice(..),
116− view.state.selection.cursor(),
123+ cx.view.doc.selection().cursor(),
117− count,
124+ cx.count,
118 );
125 );
119
126
120− // TODO: use a transaction
121− view.state.selection = Selection::single(pos, pos);
127+ cx.view.doc.set_selection(Selection::point(pos));
122}
128}
123
129
124−pub fn move_file_start(view: &mut View, _count: usize) {
130+pub fn move_file_start(cx: &mut Context) {
125− // TODO: use a transaction
131+ cx.view.doc.set_selection(Selection::point(0));
126− view.state.selection = Selection::single(0, 0);
127
132
128− view.state.mode = Mode::Normal;
133+ cx.view.doc.mode = Mode::Normal;
129}
134}
130
135
131−pub fn move_file_end(view: &mut View, _count: usize) {
136+pub fn move_file_end(cx: &mut Context) {
132− // TODO: use a transaction
133− let text = &view.state.doc;
137+ let text = &cx.view.doc.text();
134 let last_line = text.line_to_char(text.len_lines().saturating_sub(2));
138 let last_line = text.line_to_char(text.len_lines().saturating_sub(2));
135− view.state.selection = Selection::single(last_line, last_line);
139+ cx.view.doc.set_selection(Selection::point(last_line));
136
140
137− view.state.mode = Mode::Normal;
141+ cx.view.doc.mode = Mode::Normal;
138}
142}
139
143
140−pub fn check_cursor_in_view(view: &mut View) -> bool {
144+pub fn check_cursor_in_view(view: &View) -> bool {
141− let cursor = view.state.selection().cursor();
145+ let cursor = view.doc.selection().cursor();
142− let line = view.state.doc().char_to_line(cursor);
146+ let line = view.doc.text().char_to_line(cursor);
143 let document_end = view.first_line + view.size.1.saturating_sub(1) as usize;
147 let document_end = view.first_line + view.size.1.saturating_sub(1) as usize;
144
148
145 if (line > document_end.saturating_sub(PADDING)) | (line < view.first_line + PADDING) {
149 if (line > document_end.saturating_sub(PADDING)) | (line < view.first_line + PADDING) {
@@ -148,168 +152,186 @@ pub fn check_cursor_in_view(view: &mut View) -> bool {
148 true
152 true
149}
153}
150
154
151−pub fn page_up(view: &mut View, _count: usize) {
155+pub fn page_up(cx: &mut Context) {
152− if view.first_line < PADDING {
156+ if cx.view.first_line < PADDING {
153 return;
157 return;
154 }
158 }
155
159
156− view.first_line = view.first_line.saturating_sub(view.size.1 as usize);
160+ cx.view.first_line = cx.view.first_line.saturating_sub(cx.view.size.1 as usize);
157
161
158− if !check_cursor_in_view(view) {
162+ if !check_cursor_in_view(cx.view) {
159− let text = view.state.doc();
163+ let text = cx.view.doc.text();
160− let pos = text.line_to_char(view.last_line().saturating_sub(PADDING));
164+ let pos = text.line_to_char(cx.view.last_line().saturating_sub(PADDING));
161− view.state.selection = Selection::single(pos, pos);
165+ cx.view.doc.set_selection(Selection::point(pos));
162 }
166 }
163}
167}
164
168
165−pub fn page_down(view: &mut View, _count: usize) {
169+pub fn page_down(cx: &mut Context) {
166− view.first_line += view.size.1 as usize + PADDING;
170+ cx.view.first_line += cx.view.size.1 as usize + PADDING;
167
171
168− if view.first_line < view.state.doc().len_lines() {
172+ if cx.view.first_line < cx.view.doc.text().len_lines() {
169− let text = view.state.doc();
173+ let text = cx.view.doc.text();
170− let pos = text.line_to_char(view.first_line as usize);
174+ let pos = text.line_to_char(cx.view.first_line as usize);
171− view.state.selection = Selection::single(pos, pos);
175+ cx.view.doc.set_selection(Selection::point(pos));
172 }
176 }
173}
177}
174
178
175−pub fn half_page_up(view: &mut View, _count: usize) {
179+pub fn half_page_up(cx: &mut Context) {
176− if view.first_line < PADDING {
180+ if cx.view.first_line < PADDING {
177 return;
181 return;
178 }
182 }
179
183
184+ cx.view.first_line = cx
185+ .view
186+ .first_line
180− view.first_line = view.first_line.saturating_sub(view.size.1 as usize / 2);
187+ .saturating_sub(cx.view.size.1 as usize / 2);
181
188
182− if !check_cursor_in_view(view) {
189+ if !check_cursor_in_view(cx.view) {
183− let text = &view.state.doc;
190+ let text = &cx.view.doc.text();
184− let pos = text.line_to_char(view.last_line() - PADDING);
191+ let pos = text.line_to_char(cx.view.last_line() - PADDING);
185− view.state.selection = Selection::single(pos, pos);
192+ cx.view.doc.set_selection(Selection::point(pos));
186 }
193 }
187}
194}
188
195
189−pub fn half_page_down(view: &mut View, _count: usize) {
196+pub fn half_page_down(cx: &mut Context) {
190− let lines = view.state.doc().len_lines();
197+ let lines = cx.view.doc.text().len_lines();
191− if view.first_line < lines.saturating_sub(view.size.1 as usize) {
198+ if cx.view.first_line < lines.saturating_sub(cx.view.size.1 as usize) {
192− view.first_line += view.size.1 as usize / 2;
199+ cx.view.first_line += cx.view.size.1 as usize / 2;
193 }
200 }
194− if !check_cursor_in_view(view) {
201+ if !check_cursor_in_view(cx.view) {
195− let text = view.state.doc();
202+ let text = cx.view.doc.text();
196− let pos = text.line_to_char(view.first_line as usize);
203+ let pos = text.line_to_char(cx.view.first_line as usize);
197− view.state.selection = Selection::single(pos, pos);
204+ cx.view.doc.set_selection(Selection::point(pos));
198 }
205 }
199}
206}
200// avoid select by default by having a visual mode switch that makes movements into selects
207// avoid select by default by having a visual mode switch that makes movements into selects
201
208
202−pub fn extend_char_left(view: &mut View, count: usize) {
209+pub fn extend_char_left(cx: &mut Context) {
203− // TODO: use a transaction
204− let selection = view
210+ let selection =
211+ cx.view
212+ .doc
205− .state
213+ .state
206− .extend_selection(Direction::Backward, Granularity::Character, count);
214+ .extend_selection(Direction::Backward, Granularity::Character, cx.count);
207− view.state.selection = selection;
215+ cx.view.doc.set_selection(selection);
208}
216}
209
217
210−pub fn extend_char_right(view: &mut View, count: usize) {
218+pub fn extend_char_right(cx: &mut Context) {
211− // TODO: use a transaction
212− view.state.selection =
219+ let selection =
220+ cx.view
221+ .doc
213− view.state
222+ .state
214− .extend_selection(Direction::Forward, Granularity::Character, count);
223+ .extend_selection(Direction::Forward, Granularity::Character, cx.count);
224+ cx.view.doc.set_selection(selection);
215}
225}
216
226
217−pub fn extend_line_up(view: &mut View, count: usize) {
227+pub fn extend_line_up(cx: &mut Context) {
218− // TODO: use a transaction
219− view.state.selection =
228+ let selection =
229+ cx.view
230+ .doc
220− view.state
231+ .state
221− .extend_selection(Direction::Backward, Granularity::Line, count);
232+ .extend_selection(Direction::Backward, Granularity::Line, cx.count);
233+ cx.view.doc.set_selection(selection);
222}
234}
223
235
224−pub fn extend_line_down(view: &mut View, count: usize) {
236+pub fn extend_line_down(cx: &mut Context) {
225− // TODO: use a transaction
226− view.state.selection =
237+ let selection =
238+ cx.view
239+ .doc
227− view.state
240+ .state
228− .extend_selection(Direction::Forward, Granularity::Line, count);
241+ .extend_selection(Direction::Forward, Granularity::Line, cx.count);
242+ cx.view.doc.set_selection(selection);
229}
243}
230
244
231−pub fn split_selection_on_newline(view: &mut View, _count: usize) {
245+pub fn split_selection_on_newline(cx: &mut Context) {
232− let text = &view.state.doc.slice(..);
246+ let text = &cx.view.doc.text().slice(..);
233 // only compile the regex once
247 // only compile the regex once
234 #[allow(clippy::trivial_regex)]
248 #[allow(clippy::trivial_regex)]
235 static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\n").unwrap());
249 static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\n").unwrap());
236− // TODO: use a transaction
237− view.state.selection = selection::split_on_matches(text, view.state.selection(), &REGEX)
250+ let selection = selection::split_on_matches(text, cx.view.doc.selection(), &REGEX);
251+ cx.view.doc.set_selection(selection);
238}
252}
239
253
240−pub fn select_line(view: &mut View, _count: usize) {
254+pub fn select_line(cx: &mut Context) {
241 // TODO: count
255 // TODO: count
242− let pos = view.state.selection().primary();
256+ let pos = cx.view.doc.selection().primary();
243− let text = view.state.doc();
257+ let text = cx.view.doc.text();
244 let line = text.char_to_line(pos.head);
258 let line = text.char_to_line(pos.head);
245 let start = text.line_to_char(line);
259 let start = text.line_to_char(line);
246 let end = text.line_to_char(line + 1).saturating_sub(1);
260 let end = text.line_to_char(line + 1).saturating_sub(1);
247
261
248− // TODO: use a transaction
249− view.state.selection = Selection::single(start, end);
262+ cx.view.doc.set_selection(Selection::single(start, end));
250}
263}
251
264
252−pub fn delete_selection(view: &mut View, _count: usize) {
265+pub fn delete_selection(cx: &mut Context) {
253− let transaction =
254− Transaction::change_by_selection(&view.state, |range| (range.from(), range.to() + 1, None));
266+ let transaction = Transaction::change_by_selection(&cx.view.doc.state, |range| {
255− transaction.apply(&mut view.state);
267+ (range.from(), range.to() + 1, None)
268+ });
269+ cx.view.doc.apply(&transaction);
256
270
257− append_changes_to_history(view);
271+ append_changes_to_history(cx);
258}
272}
259
273
260−pub fn change_selection(view: &mut View, count: usize) {
274+pub fn change_selection(cx: &mut Context) {
261− delete_selection(view, count);
275+ delete_selection(cx);
262− insert_mode(view, count);
276+ insert_mode(cx);
263}
277}
264
278
265−pub fn collapse_selection(view: &mut View, _count: usize) {
279+pub fn collapse_selection(cx: &mut Context) {
266− view.state.selection = view
280+ let selection = cx
267− .state
281+ .view
282+ .doc
268− .selection
283+ .selection()
269− .transform(|range| Range::new(range.head, range.head))
284+ .transform(|range| Range::new(range.head, range.head));
285+
286+ cx.view.doc.set_selection(selection);
270}
287}
271
288
272−pub fn flip_selections(view: &mut View, _count: usize) {
289+pub fn flip_selections(cx: &mut Context) {
273− view.state.selection = view
290+ let selection = cx
274− .state
291+ .view
292+ .doc
275− .selection
293+ .selection()
276− .transform(|range| Range::new(range.head, range.anchor))
294+ .transform(|range| Range::new(range.head, range.anchor));
295+
296+ cx.view.doc.set_selection(selection);
277}
297}
278
298
279−fn enter_insert_mode(view: &mut View) {
299+fn enter_insert_mode(cx: &mut Context) {
280− view.state.mode = Mode::Insert;
300+ cx.view.doc.mode = Mode::Insert;
281
301
282− append_changes_to_history(view);
302+ append_changes_to_history(cx);
283}
303}
284// inserts at the start of each selection
304// inserts at the start of each selection
285−pub fn insert_mode(view: &mut View, _count: usize) {
305+pub fn insert_mode(cx: &mut Context) {
286− enter_insert_mode(view);
306+ enter_insert_mode(cx);
287
307
288− view.state.selection = view
308+ let selection = cx
289− .state
309+ .view
310+ .doc
290− .selection
311+ .selection()
291− .transform(|range| Range::new(range.to(), range.from()))
312+ .transform(|range| Range::new(range.to(), range.from()));
313+ cx.view.doc.set_selection(selection);
292}
314}
293
315
294// inserts at the end of each selection
316// inserts at the end of each selection
295−pub fn append_mode(view: &mut View, _count: usize) {
317+pub fn append_mode(cx: &mut Context) {
296− enter_insert_mode(view);
318+ enter_insert_mode(cx);
297− view.state.restore_cursor = true;
319+ cx.view.doc.restore_cursor = true;
298
320
299 // TODO: as transaction
321 // TODO: as transaction
300− let text = &view.state.doc.slice(..);
322+ let text = &cx.view.doc.text().slice(..);
301− view.state.selection = view.state.selection.transform(|range| {
323+ let selection = cx.view.doc.selection().transform(|range| {
302 // TODO: to() + next char
324 // TODO: to() + next char
303 Range::new(
325 Range::new(
304 range.from(),
326 range.from(),
305 graphemes::next_grapheme_boundary(text, range.to()),
327 graphemes::next_grapheme_boundary(text, range.to()),
306 )
328 )
307− })
329+ });
330+ cx.view.doc.set_selection(selection);
308}
331}
309
332
310// TODO: I, A, o and O can share a lot of the primitives.
333// TODO: I, A, o and O can share a lot of the primitives.
311−
312−pub fn command_mode(_view: &mut View, _count: usize) {
334+pub fn command_mode(_cx: &mut Context) {
313 unimplemented!()
335 unimplemented!()
314}
336}
315
337
@@ -329,30 +351,30 @@ fn selection_lines(state: &State) -> Vec<usize> {
329}
351}
330
352
331// I inserts at the start of each line with a selection
353// I inserts at the start of each line with a selection
332−pub fn prepend_to_line(view: &mut View, count: usize) {
354+pub fn prepend_to_line(cx: &mut Context) {
333− enter_insert_mode(view);
355+ enter_insert_mode(cx);
334
356
335− move_line_start(view, count);
357+ move_line_start(cx);
336}
358}
337
359
338// A inserts at the end of each line with a selection
360// A inserts at the end of each line with a selection
339−pub fn append_to_line(view: &mut View, count: usize) {
361+pub fn append_to_line(cx: &mut Context) {
340− enter_insert_mode(view);
362+ enter_insert_mode(cx);
341
363
342− move_line_end(view, count);
364+ move_line_end(cx);
343}
365}
344
366
345// o inserts a new line after each line with a selection
367// o inserts a new line after each line with a selection
346−pub fn open_below(view: &mut View, _count: usize) {
368+pub fn open_below(cx: &mut Context) {
347− enter_insert_mode(view);
369+ enter_insert_mode(cx);
348
370
349− let lines = selection_lines(&view.state);
371+ let lines = selection_lines(&cx.view.doc.state);
350
372
351 let positions: Vec<_> = lines
373 let positions: Vec<_> = lines
352 .into_iter()
374 .into_iter()
353 .map(|index| {
375 .map(|index| {
354 // adjust all positions to the end of the line/start of the next one.
376 // adjust all positions to the end of the line/start of the next one.
355− view.state.doc.line_to_char(index + 1)
377+ cx.view.doc.text().line_to_char(index + 1)
356 })
378 })
357 .collect();
379 .collect();
358
380
@@ -373,111 +395,119 @@ pub fn open_below(view: &mut View, _count: usize) {
373 0,
395 0,
374 );
396 );
375
397
376− let transaction = Transaction::change(&view.state, changes).with_selection(selection);
398+ let transaction = Transaction::change(&cx.view.doc.state, changes).with_selection(selection);
377
399
378− transaction.apply(&mut view.state);
400+ cx.view.doc.apply(&transaction);
379}
401}
380
402
381// O inserts a new line before each line with a selection
403// O inserts a new line before each line with a selection
382
404
383−fn append_changes_to_history(view: &mut View) {
405+fn append_changes_to_history(cx: &mut Context) {
384− if view.state.changes.is_empty() {
406+ if cx.view.doc.changes.is_empty() {
385 return;
407 return;
386 }
408 }
387
409
388− let new_changeset = ChangeSet::new(view.state.doc());
410+ let new_changeset = ChangeSet::new(cx.view.doc.text());
389− let changes = std::mem::replace(&mut view.state.changes, new_changeset);
411+ let changes = std::mem::replace(&mut cx.view.doc.changes, new_changeset);
390 // Instead of doing this messy merge we could always commit, and based on transaction
412 // Instead of doing this messy merge we could always commit, and based on transaction
391 // annotations either add a new layer or compose into the previous one.
413 // annotations either add a new layer or compose into the previous one.
392− let transaction = Transaction::from(changes).with_selection(view.state.selection().clone());
414+ let transaction = Transaction::from(changes).with_selection(cx.view.doc.selection().clone());
393
415
394− // HAXX: we need to reconstruct the state as it was before the changes..
416+ // increment document version
395− let (doc, selection) = view.state.old_state.take().unwrap();
417+ // TODO: needs to happen on undo/redo too
396− let mut old_state = State::new(doc);
418+ cx.view.doc.version += 1;
397− old_state.selection = selection;
398
419
420+ // TODO: trigger lsp/documentDidChange with changes
421+
422+ // HAXX: we need to reconstruct the state as it was before the changes..
423+ let old_state = std::mem::replace(&mut cx.view.doc.old_state, cx.view.doc.state.clone());
399 // TODO: take transaction by value?
424 // TODO: take transaction by value?
425+ cx.view
426+ .doc
427+ .history
400− view.history.commit_revision(&transaction, &old_state);
428+ .commit_revision(&transaction, &old_state);
401
429
402− // TODO: need to start the state with these vals
430+ // TODO: notify LSP of changes
403− // HAXX
404− view.state.old_state = Some((view.state.doc().clone(), view.state.selection.clone()));
405}
431}
406
432
407−pub fn normal_mode(view: &mut View, _count: usize) {
433+pub fn normal_mode(cx: &mut Context) {
408− view.state.mode = Mode::Normal;
434+ cx.view.doc.mode = Mode::Normal;
409
435
410− append_changes_to_history(view);
436+ append_changes_to_history(cx);
411
437
412 // if leaving append mode, move cursor back by 1
438 // if leaving append mode, move cursor back by 1
413− if view.state.restore_cursor {
439+ if cx.view.doc.restore_cursor {
414− let text = &view.state.doc.slice(..);
440+ let text = &cx.view.doc.text().slice(..);
415− view.state.selection = view.state.selection.transform(|range| {
441+ let selection = cx.view.doc.selection().transform(|range| {
416 Range::new(
442 Range::new(
417 range.from(),
443 range.from(),
418 graphemes::prev_grapheme_boundary(text, range.to()),
444 graphemes::prev_grapheme_boundary(text, range.to()),
419 )
445 )
420 });
446 });
447+ cx.view.doc.set_selection(selection);
421
448
422− view.state.restore_cursor = false;
449+ cx.view.doc.restore_cursor = false;
423 }
450 }
424}
451}
425
452
426−pub fn goto_mode(view: &mut View, _count: usize) {
453+pub fn goto_mode(cx: &mut Context) {
427− view.state.mode = Mode::Goto;
454+ cx.view.doc.mode = Mode::Goto;
428}
455}
429
456
430// NOTE: Transactions in this module get appended to history when we switch back to normal mode.
457// NOTE: Transactions in this module get appended to history when we switch back to normal mode.
431pub mod insert {
458pub mod insert {
432 use super::*;
459 use super::*;
433 // TODO: insert means add text just before cursor, on exit we should be on the last letter.
460 // TODO: insert means add text just before cursor, on exit we should be on the last letter.
434− pub fn insert_char(view: &mut View, c: char) {
461+ pub fn insert_char(cx: &mut Context, c: char) {
435 let c = Tendril::from_char(c);
462 let c = Tendril::from_char(c);
436− let transaction = Transaction::insert(&view.state, c);
463+ let transaction = Transaction::insert(&cx.view.doc.state, c);
437
464
438− transaction.apply(&mut view.state);
465+ cx.view.doc.apply(&transaction);
439 }
466 }
440
467
441− pub fn insert_tab(view: &mut View, _count: usize) {
468+ pub fn insert_tab(cx: &mut Context) {
442− insert_char(view, '\t');
469+ insert_char(cx, '\t');
443 }
470 }
444
471
445− pub fn insert_newline(view: &mut View, _count: usize) {
472+ pub fn insert_newline(cx: &mut Context) {
446− let transaction = Transaction::change_by_selection(&view.state, |range| {
473+ let transaction = Transaction::change_by_selection(&cx.view.doc.state, |range| {
447− let indent_level =
448− helix_core::indent::suggested_indent_for_pos(&view.state, range.head);
474+ let indent_level = helix_core::indent::suggested_indent_for_pos(
475+ cx.view.doc.syntax.as_ref(),
476+ &cx.view.doc.state,
477+ range.head,
478+ );
449 let indent = " ".repeat(TAB_WIDTH).repeat(indent_level);
479 let indent = " ".repeat(TAB_WIDTH).repeat(indent_level);
450 let mut text = String::with_capacity(1 + indent.len());
480 let mut text = String::with_capacity(1 + indent.len());
451 text.push('\n');
481 text.push('\n');
452 text.push_str(&indent);
482 text.push_str(&indent);
453 (range.head, range.head, Some(text.into()))
483 (range.head, range.head, Some(text.into()))
454 });
484 });
455− transaction.apply(&mut view.state);
485+ cx.view.doc.apply(&transaction);
456 }
486 }
457
487
458 // TODO: handle indent-aware delete
488 // TODO: handle indent-aware delete
459− pub fn delete_char_backward(view: &mut View, count: usize) {
489+ pub fn delete_char_backward(cx: &mut Context) {
460− let text = &view.state.doc.slice(..);
490+ let text = &cx.view.doc.text().slice(..);
461− let transaction = Transaction::change_by_selection(&view.state, |range| {
491+ let transaction = Transaction::change_by_selection(&cx.view.doc.state, |range| {
462 (
492 (
463− graphemes::nth_prev_grapheme_boundary(text, range.head, count),
493+ graphemes::nth_prev_grapheme_boundary(text, range.head, cx.count),
464 range.head,
494 range.head,
465 None,
495 None,
466 )
496 )
467 });
497 });
468− transaction.apply(&mut view.state);
498+ cx.view.doc.apply(&transaction);
469 }
499 }
470
500
471− pub fn delete_char_forward(view: &mut View, count: usize) {
501+ pub fn delete_char_forward(cx: &mut Context) {
472− let text = &view.state.doc.slice(..);
502+ let text = &cx.view.doc.text().slice(..);
473− let transaction = Transaction::change_by_selection(&view.state, |range| {
503+ let transaction = Transaction::change_by_selection(&cx.view.doc.state, |range| {
474 (
504 (
475 range.head,
505 range.head,
476− graphemes::nth_next_grapheme_boundary(text, range.head, count),
506+ graphemes::nth_next_grapheme_boundary(text, range.head, cx.count),
477 None,
507 None,
478 )
508 )
479 });
509 });
480− transaction.apply(&mut view.state);
510+ cx.view.doc.apply(&transaction);
481 }
511 }
482}
512}
483
513
@@ -487,24 +517,32 @@ pub fn insert_char_prompt(prompt: &mut Prompt, c: char) {
487
517
488// Undo / Redo
518// Undo / Redo
489
519
490−pub fn undo(view: &mut View, _count: usize) {
520+pub fn undo(cx: &mut Context) {
491− view.history.undo(&mut view.state);
521+ if let Some(revert) = cx.view.doc.history.undo() {
522+ cx.view.doc.version += 1;
523+ cx.view.doc.apply(&revert);
524+ }
492
525
493 // TODO: each command could simply return a Option<transaction>, then the higher level handles storing it?
526 // TODO: each command could simply return a Option<transaction>, then the higher level handles storing it?
494}
527}
495
528
496−pub fn redo(view: &mut View, _count: usize) {
529+pub fn redo(cx: &mut Context) {
497− view.history.redo(&mut view.state);
530+ if let Some(transaction) = cx.view.doc.history.redo() {
531+ cx.view.doc.version += 1;
532+ cx.view.doc.apply(&transaction);
533+ }
498}
534}
499
535
500// Yank / Paste
536// Yank / Paste
501
537
502−pub fn yank(view: &mut View, _count: usize) {
538+pub fn yank(cx: &mut Context) {
503 // TODO: should selections be made end inclusive?
539 // TODO: should selections be made end inclusive?
504− let values = view
540+ let values = cx
541+ .view
542+ .doc
505 .state
543 .state
506 .selection()
544 .selection()
507− .fragments(&view.state.doc().slice(..))
545+ .fragments(&cx.view.doc.text().slice(..))
508 .map(|cow| cow.into_owned())
546 .map(|cow| cow.into_owned())
509 .collect();
547 .collect();
510
548
@@ -513,7 +551,7 @@ pub fn yank(view: &mut View, _count: usize) {
513 register::set(reg, values);
551 register::set(reg, values);
514}
552}
515
553
516−pub fn paste(view: &mut View, _count: usize) {
554+pub fn paste(cx: &mut Context) {
517 // TODO: allow specifying reg
555 // TODO: allow specifying reg
518 let reg = '"';
556 let reg = '"';
519 if let Some(values) = register::get(reg) {
557 if let Some(values) = register::get(reg) {
@@ -545,19 +583,19 @@ pub fn paste(view: &mut View, _count: usize) {
545 let transaction = if linewise {
583 let transaction = if linewise {
546 // paste on the next line
584 // paste on the next line
547 // TODO: can simply take a range + modifier and compute the right pos without ifs
585 // TODO: can simply take a range + modifier and compute the right pos without ifs
548− let text = view.state.doc();
586+ let text = cx.view.doc.text();
549− Transaction::change_by_selection(&view.state, |range| {
587+ Transaction::change_by_selection(&cx.view.doc.state, |range| {
550 let line_end = text.line_to_char(text.char_to_line(range.head) + 1);
588 let line_end = text.line_to_char(text.char_to_line(range.head) + 1);
551 (line_end, line_end, Some(values.next().unwrap()))
589 (line_end, line_end, Some(values.next().unwrap()))
552 })
590 })
553 } else {
591 } else {
554− Transaction::change_by_selection(&view.state, |range| {
592+ Transaction::change_by_selection(&cx.view.doc.state, |range| {
555 (range.head + 1, range.head + 1, Some(values.next().unwrap()))
593 (range.head + 1, range.head + 1, Some(values.next().unwrap()))
556 })
594 })
557 };
595 };
558
596
559− transaction.apply(&mut view.state);
597+ cx.view.doc.apply(&transaction);
560− append_changes_to_history(view);
598+ append_changes_to_history(cx);
561 }
599 }
562}
600}
563
601
@@ -565,9 +603,9 @@ fn get_lines(view: &View) -> Vec<usize> {
565 let mut lines = Vec::new();
603 let mut lines = Vec::new();
566
604
567 // Get all line numbers
605 // Get all line numbers
568− for range in view.state.selection.ranges() {
606+ for range in view.doc.selection().ranges() {
569− let start = view.state.doc.char_to_line(range.from());
607+ let start = view.doc.text().char_to_line(range.from());
570− let end = view.state.doc.char_to_line(range.to());
608+ let end = view.doc.text().char_to_line(range.to());
571
609
572 for line in start..=end {
610 for line in start..=end {
573 lines.push(line)
611 lines.push(line)
@@ -578,29 +616,29 @@ fn get_lines(view: &View) -> Vec<usize> {
578 lines
616 lines
579}
617}
580
618
581−pub fn indent(view: &mut View, _count: usize) {
619+pub fn indent(cx: &mut Context) {
582− let lines = get_lines(view);
620+ let lines = get_lines(cx.view);
583
621
584 // Indent by one level
622 // Indent by one level
585 let indent = Tendril::from(" ".repeat(TAB_WIDTH));
623 let indent = Tendril::from(" ".repeat(TAB_WIDTH));
586
624
587 let transaction = Transaction::change(
625 let transaction = Transaction::change(
588− &view.state,
626+ &cx.view.doc.state,
589 lines.into_iter().map(|line| {
627 lines.into_iter().map(|line| {
590− let pos = view.state.doc.line_to_char(line);
628+ let pos = cx.view.doc.text().line_to_char(line);
591 (pos, pos, Some(indent.clone()))
629 (pos, pos, Some(indent.clone()))
592 }),
630 }),
593 );
631 );
594− transaction.apply(&mut view.state);
632+ cx.view.doc.apply(&transaction);
595− append_changes_to_history(view);
633+ append_changes_to_history(cx);
596}
634}
597
635
598−pub fn unindent(view: &mut View, _count: usize) {
636+pub fn unindent(cx: &mut Context) {
599− let lines = get_lines(view);
637+ let lines = get_lines(cx.view);
600 let mut changes = Vec::with_capacity(lines.len());
638 let mut changes = Vec::with_capacity(lines.len());
601
639
602 for line_idx in lines {
640 for line_idx in lines {
603− let line = view.state.doc.line(line_idx);
641+ let line = cx.view.doc.text().line(line_idx);
604 let mut width = 0;
642 let mut width = 0;
605
643
606 for ch in line.chars() {
644 for ch in line.chars() {
@@ -616,18 +654,27 @@ pub fn unindent(view: &mut View, _count: usize) {
616 }
654 }
617
655
618 if width > 0 {
656 if width > 0 {
619− let start = view.state.doc.line_to_char(line_idx);
657+ let start = cx.view.doc.text().line_to_char(line_idx);
620 changes.push((start, start + width, None))
658 changes.push((start, start + width, None))
621 }
659 }
622 }
660 }
623
661
624− let transaction = Transaction::change(&view.state, changes.into_iter());
662+ let transaction = Transaction::change(&cx.view.doc.state, changes.into_iter());
625
663
626− transaction.apply(&mut view.state);
664+ cx.view.doc.apply(&transaction);
627− append_changes_to_history(view);
665+ append_changes_to_history(cx);
628}
666}
629
667
630−pub fn indent_selection(_view: &mut View, _count: usize) {
668+pub fn indent_selection(_cx: &mut Context) {
631 // loop over each line and recompute proper indentation
669 // loop over each line and recompute proper indentation
632 unimplemented!()
670 unimplemented!()
633}
671}
672+
673+//
674+
675+pub fn save(cx: &mut Context) {
676+ // Spawns an async task to actually do the saving. This way we prevent blocking.
677+
678+ // TODO: handle save errors somehow?
679+ cx.executor.spawn(cx.view.doc.save()).detach();
680+}
helix-view/src/document.rsnew file+209
@@ -0,0 +1,209 @@
1+use anyhow::Error;
2+use std::future::Future;
3+use std::path::PathBuf;
4+
5+use helix_core::{
6+ syntax::LOADER, ChangeSet, Diagnostic, History, Position, Range, Rope, RopeSlice, Selection,
7+ State, Syntax, Transaction,
8+};
9+
10+#[derive(Copy, Clone, PartialEq, Eq, Hash)]
11+pub enum Mode {
12+ Normal,
13+ Insert,
14+ Goto,
15+}
16+
17+pub struct Document {
18+ pub state: State, // rope + selection
19+ /// File path on disk.
20+ pub path: Option<PathBuf>,
21+
22+ /// Current editing mode.
23+ pub mode: Mode,
24+ pub restore_cursor: bool,
25+
26+ /// Tree-sitter AST tree
27+ pub syntax: Option<Syntax>,
28+ /// Corresponding language scope name. Usually `source.<lang>`.
29+ pub language: Option<String>,
30+
31+ /// Pending changes since last history commit.
32+ pub changes: ChangeSet,
33+ pub old_state: State,
34+ pub history: History,
35+ pub version: i32, // should be usize?
36+
37+ pub diagnostics: Vec<Diagnostic>,
38+}
39+
40+/// Like std::mem::replace() except it allows the replacement value to be mapped from the
41+/// original value.
42+fn take_with<T, F>(mut_ref: &mut T, closure: F)
43+where
44+ F: FnOnce(T) -> T,
45+{
46+ use std::{panic, ptr};
47+
48+ unsafe {
49+ let old_t = ptr::read(mut_ref);
50+ let new_t = panic::catch_unwind(panic::AssertUnwindSafe(|| closure(old_t)))
51+ .unwrap_or_else(|_| ::std::process::abort());
52+ ptr::write(mut_ref, new_t);
53+ }
54+}
55+
56+use url::Url;
57+
58+impl Document {
59+ fn new(state: State) -> Self {
60+ let changes = ChangeSet::new(&state.doc);
61+ let old_state = state.clone();
62+
63+ Self {
64+ path: None,
65+ state,
66+ mode: Mode::Normal,
67+ restore_cursor: false,
68+ syntax: None,
69+ language: None,
70+ changes,
71+ old_state,
72+ diagnostics: Vec::new(),
73+ version: 0,
74+ history: History::default(),
75+ }
76+ }
77+
78+ // TODO: passing scopes here is awkward
79+ // TODO: async fn?
80+ pub fn load(path: PathBuf, scopes: &[String]) -> Result<Self, Error> {
81+ use std::{env, fs::File, io::BufReader};
82+ let _current_dir = env::current_dir()?;
83+
84+ let doc = Rope::from_reader(BufReader::new(File::open(path.clone())?))?;
85+
86+ // TODO: create if not found
87+
88+ let mut doc = Self::new(State::new(doc));
89+
90+ if let Some(language_config) = LOADER.language_config_for_file_name(path.as_path()) {
91+ let highlight_config = language_config.highlight_config(scopes).unwrap().unwrap();
92+ // TODO: config.configure(scopes) is now delayed, is that ok?
93+
94+ let syntax = Syntax::new(&doc.state.doc, highlight_config.clone());
95+
96+ doc.syntax = Some(syntax);
97+ // TODO: maybe just keep an Arc<> pointer to the language_config?
98+ doc.language = Some(language_config.scope().to_string());
99+
100+ // TODO: this ties lsp support to tree-sitter enabled languages for now. Language
101+ // config should use Option<HighlightConfig> to let us have non-tree-sitter configs.
102+
103+ // TODO: circular dep: view <-> lsp
104+ // helix_lsp::REGISTRY;
105+ // view should probably depend on lsp
106+ };
107+
108+ // canonicalize path to absolute value
109+ doc.path = Some(std::fs::canonicalize(path)?);
110+
111+ Ok(doc)
112+ }
113+
114+ // TODO: do we need some way of ensuring two save operations on the same doc can't run at once?
115+ // or is that handled by the OS/async layer
116+ pub fn save(&self) -> impl Future<Output = Result<(), anyhow::Error>> {
117+ // we clone and move text + path into the future so that we asynchronously save the current
118+ // state without blocking any further edits.
119+
120+ let text = self.text().clone();
121+ let path = self.path.clone().expect("Can't save with no path set!"); // TODO: handle no path
122+
123+ // TODO: mark changes up to now as saved
124+ // TODO: mark dirty false
125+
126+ async move {
127+ use smol::{fs::File, prelude::*};
128+ let mut file = File::create(path).await?;
129+
130+ // write all the rope chunks to file
131+ for chunk in text.chunks() {
132+ file.write_all(chunk.as_bytes()).await?;
133+ }
134+ // TODO: flush?
135+
136+ Ok(())
137+ } // and_then(// lsp.send_text_saved_notification())
138+ }
139+
140+ pub fn set_language(&mut self, scope: &str, scopes: &[String]) {
141+ if let Some(language_config) = LOADER.language_config_for_scope(scope) {
142+ let highlight_config = language_config.highlight_config(scopes).unwrap().unwrap();
143+ // TODO: config.configure(scopes) is now delayed, is that ok?
144+
145+ let syntax = Syntax::new(&self.state.doc, highlight_config.clone());
146+
147+ self.syntax = Some(syntax);
148+ };
149+ }
150+
151+ pub fn set_selection(&mut self, selection: Selection) {
152+ // TODO: use a transaction?
153+ self.state.selection = selection;
154+ }
155+
156+ pub fn apply(&mut self, transaction: &Transaction) -> bool {
157+ let old_doc = self.text().clone();
158+
159+ let success = transaction.apply(&mut self.state);
160+
161+ if !transaction.changes().is_empty() {
162+ // Compose this transaction with the previous one
163+ take_with(&mut self.changes, |changes| {
164+ changes.compose(transaction.changes().clone()).unwrap()
165+ });
166+
167+ // TODO: when composing, replace transaction.selection too
168+
169+ // update tree-sitter syntax tree
170+ if let Some(syntax) = &mut self.syntax {
171+ // TODO: no unwrap
172+ syntax
173+ .update(&old_doc, &self.state.doc, transaction.changes())
174+ .unwrap();
175+ }
176+
177+ // TODO: map state.diagnostics over changes::map_pos too
178+ }
179+ success
180+ }
181+
182+ #[inline]
183+ pub fn mode(&self) -> Mode {
184+ self.mode
185+ }
186+
187+ #[inline]
188+ pub fn path(&self) -> Option<&PathBuf> {
189+ self.path.as_ref()
190+ }
191+
192+ pub fn url(&self) -> Option<Url> {
193+ self.path().map(|path| Url::from_file_path(path).unwrap())
194+ }
195+
196+ pub fn text(&self) -> &Rope {
197+ &self.state.doc
198+ }
199+
200+ pub fn selection(&self) -> &Selection {
201+ &self.state.selection
202+ }
203+
204+ // pub fn slice<R>(&self, range: R) -> RopeSlice where R: RangeBounds {
205+ // self.state.doc.slice
206+ // }
207+
208+ // TODO: transact(Fn) ?
209+}
helix-view/src/editor.rs+28−4
@@ -1,24 +1,48 @@
1−use crate::View;
1+use crate::theme::Theme;
2+use crate::{Document, View};
2
3
3use std::path::PathBuf;
4use std::path::PathBuf;
4
5
5use anyhow::Error;
6use anyhow::Error;
6
7
7pub struct Editor {
8pub struct Editor {
8− pub view: Option<View>,
9+ pub views: Vec<View>,
10+ pub focus: usize,
9 pub should_close: bool,
11 pub should_close: bool,
12+ pub theme: Theme, // TODO: share one instance
13+}
14+
15+impl Default for Editor {
16+ fn default() -> Self {
17+ Self::new()
18+ }
10}
19}
11
20
12impl Editor {
21impl Editor {
13 pub fn new() -> Self {
22 pub fn new() -> Self {
23+ let theme = Theme::default();
24+
14 Self {
25 Self {
15− view: None,
26+ views: Vec::new(),
27+ focus: 0,
16 should_close: false,
28 should_close: false,
29+ theme,
17 }
30 }
18 }
31 }
19
32
20 pub fn open(&mut self, path: PathBuf, size: (u16, u16)) -> Result<(), Error> {
33 pub fn open(&mut self, path: PathBuf, size: (u16, u16)) -> Result<(), Error> {
21− self.view = Some(View::open(path, size)?);
34+ let pos = self.views.len();
35+ let doc = Document::load(path, self.theme.scopes())?;
36+ self.views.push(View::new(doc, size)?);
37+ self.focus = pos;
22 Ok(())
38 Ok(())
23 }
39 }
40+
41+ pub fn view(&self) -> Option<&View> {
42+ self.views.get(self.focus)
43+ }
44+
45+ pub fn view_mut(&mut self) -> Option<&mut View> {
46+ self.views.get_mut(self.focus)
47+ }
24}
48}
helix-view/src/keymap.rs+10−6
@@ -1,5 +1,6 @@
1use crate::commands::{self, Command};
1use crate::commands::{self, Command};
2+use crate::document::Mode;
2−use helix_core::{hashmap, state};
3+use helix_core::hashmap;
3use std::collections::HashMap;
4use std::collections::HashMap;
4
5
5// Kakoune-inspired:
6// Kakoune-inspired:
@@ -81,14 +82,17 @@ use std::collections::HashMap;
81// = = align?
82// = = align?
82// + =
83// + =
83// }
84// }
85+//
86+// gd = goto definition
87+// gr = goto reference
84// }
88// }
85
89
86#[cfg(feature = "term")]
90#[cfg(feature = "term")]
87pub use crossterm::event::{KeyCode, KeyEvent as Key, KeyModifiers as Modifiers};
91pub use crossterm::event::{KeyCode, KeyEvent as Key, KeyModifiers as Modifiers};
88
92
89// TODO: could be trie based
93// TODO: could be trie based
90−type Keymap = HashMap<Vec<Key>, Command>;
94+pub type Keymap = HashMap<Vec<Key>, Command>;
91−type Keymaps = HashMap<state::Mode, Keymap>;
95+pub type Keymaps = HashMap<Mode, Keymap>;
92
96
93macro_rules! key {
97macro_rules! key {
94 ($ch:expr) => {
98 ($ch:expr) => {
@@ -128,7 +132,7 @@ macro_rules! ctrl {
128
132
129pub fn default() -> Keymaps {
133pub fn default() -> Keymaps {
130 hashmap!(
134 hashmap!(
131− state::Mode::Normal =>
135+ Mode::Normal =>
132 // as long as you cast the first item, rust is able to infer the other cases
136 // as long as you cast the first item, rust is able to infer the other cases
133 hashmap!(
137 hashmap!(
134 vec![key!('h')] => commands::move_char_left as Command,
138 vec![key!('h')] => commands::move_char_left as Command,
@@ -179,7 +183,7 @@ pub fn default() -> Keymaps {
179 vec![ctrl!('u')] => commands::half_page_up,
183 vec![ctrl!('u')] => commands::half_page_up,
180 vec![ctrl!('d')] => commands::half_page_down,
184 vec![ctrl!('d')] => commands::half_page_down,
181 ),
185 ),
182− state::Mode::Insert => hashmap!(
186+ Mode::Insert => hashmap!(
183 vec![Key {
187 vec![Key {
184 code: KeyCode::Esc,
188 code: KeyCode::Esc,
185 modifiers: Modifiers::NONE
189 modifiers: Modifiers::NONE
@@ -201,7 +205,7 @@ pub fn default() -> Keymaps {
201 modifiers: Modifiers::NONE
205 modifiers: Modifiers::NONE
202 }] => commands::insert::insert_tab,
206 }] => commands::insert::insert_tab,
203 ),
207 ),
204− state::Mode::Goto => hashmap!(
208+ Mode::Goto => hashmap!(
205 vec![Key {
209 vec![Key {
206 code: KeyCode::Esc,
210 code: KeyCode::Esc,
207 modifiers: Modifiers::NONE
211 modifiers: Modifiers::NONE
helix-view/src/lib.rs+3
@@ -1,9 +1,12 @@
1pub mod commands;
1pub mod commands;
2+pub mod document;
2pub mod editor;
3pub mod editor;
3pub mod keymap;
4pub mod keymap;
4pub mod prompt;
5pub mod prompt;
5pub mod theme;
6pub mod theme;
6pub mod view;
7pub mod view;
7
8
9+pub use document::Document;
8pub use editor::Editor;
10pub use editor::Editor;
11+pub use theme::Theme;
9pub use view::View;
12pub use view::View;
helix-view/src/theme.rs+2
@@ -157,6 +157,8 @@ impl Default for Theme {
157 "ui.background" => Style::default().bg(Color::Rgb(59, 34, 76)), // midnight
157 "ui.background" => Style::default().bg(Color::Rgb(59, 34, 76)), // midnight
158 "ui.linenr" => Style::default().fg(Color::Rgb(90, 89, 119)), // comet
158 "ui.linenr" => Style::default().fg(Color::Rgb(90, 89, 119)), // comet
159 "ui.statusline" => Style::default().bg(Color::Rgb(40, 23, 51)), // revolver
159 "ui.statusline" => Style::default().bg(Color::Rgb(40, 23, 51)), // revolver
160+
161+ "warning" => Style::default().fg(Color::Rgb(255, 205, 28)),
160 };
162 };
161
163
162 let scopes = mapping.keys().map(ToString::to_string).collect();
164 let scopes = mapping.keys().map(ToString::to_string).collect();
helix-view/src/view.rs+32−16
@@ -1,44 +1,39 @@
1use anyhow::Error;
1use anyhow::Error;
2
2
3−use std::{borrow::Cow, path::PathBuf};
3+use std::borrow::Cow;
4
4
5−use crate::theme::Theme;
5+use crate::Document;
6use helix_core::{
6use helix_core::{
7 graphemes::{grapheme_width, RopeGraphemes},
7 graphemes::{grapheme_width, RopeGraphemes},
8 indent::TAB_WIDTH,
8 indent::TAB_WIDTH,
9− History, Position, RopeSlice, State,
9+ Position, RopeSlice,
10};
10};
11use tui::layout::Rect;
11use tui::layout::Rect;
12
12
13pub const PADDING: usize = 5;
13pub const PADDING: usize = 5;
14
14
15+// TODO: view should be View { doc: Document(state, history,..) }
16+// since we can have multiple views into the same file
15pub struct View {
17pub struct View {
16− pub state: State,
18+ pub doc: Document,
17− pub history: History,
18 pub first_line: usize,
19 pub first_line: usize,
19 pub size: (u16, u16),
20 pub size: (u16, u16),
20− pub theme: Theme, // TODO: share one instance
21}
21}
22
22
23impl View {
23impl View {
24− pub fn open(path: PathBuf, size: (u16, u16)) -> Result<Self, Error> {
24+ pub fn new(doc: Document, size: (u16, u16)) -> Result<Self, Error> {
25− let theme = Theme::default();
26− let state = State::load(path, theme.scopes())?;
27−
28 let view = Self {
25 let view = Self {
29− state,
26+ doc,
30 first_line: 0,
27 first_line: 0,
31 size,
28 size,
32− theme,
33− history: History::default(),
34 };
29 };
35
30
36 Ok(view)
31 Ok(view)
37 }
32 }
38
33
39 pub fn ensure_cursor_in_view(&mut self) {
34 pub fn ensure_cursor_in_view(&mut self) {
40− let cursor = self.state.selection().cursor();
35+ let cursor = self.doc.state.selection().cursor();
41− let line = self.state.doc().char_to_line(cursor);
36+ let line = self.doc.text().char_to_line(cursor);
42 let document_end = self.first_line + (self.size.1 as usize).saturating_sub(2);
37 let document_end = self.first_line + (self.size.1 as usize).saturating_sub(2);
43
38
44 // TODO: side scroll
39 // TODO: side scroll
@@ -58,7 +53,7 @@ impl View {
58 let viewport = Rect::new(6, 0, self.size.0, self.size.1 - 2); // - 2 for statusline and prompt
53 let viewport = Rect::new(6, 0, self.size.0, self.size.1 - 2); // - 2 for statusline and prompt
59 std::cmp::min(
54 std::cmp::min(
60 self.first_line + (viewport.height as usize),
55 self.first_line + (viewport.height as usize),
61− self.state.doc().len_lines() - 1,
56+ self.doc.text().len_lines() - 1,
62 )
57 )
63 }
58 }
64
59
@@ -90,4 +85,25 @@ impl View {
90
85
91 Some(Position::new(row, col))
86 Some(Position::new(row, col))
92 }
87 }
88+
89+ // pub fn traverse<F>(&self, text: &RopeSlice, start: usize, end: usize, fun: F)
90+ // where
91+ // F: Fn(usize, usize),
92+ // {
93+ // let start = self.screen_coords_at_pos(text, start);
94+ // let end = self.screen_coords_at_pos(text, end);
95+
96+ // match (start, end) {
97+ // // fully on screen
98+ // (Some(start), Some(end)) => {
99+ // // we want to calculate ends of lines for each char..
100+ // }
101+ // // from start to end of screen
102+ // (Some(start), None) => {}
103+ // // from start of screen to end
104+ // (None, Some(end)) => {}
105+ // // not on screen
106+ // (None, None) => return,
107+ // }
108+ // }
93}
109}