on master help sign in
Merge pull request #5 from helix-editor/lsp

LSP: mk1

commitb7a3e525ed7fed5ed79e8580df2e3496bd994419 authorBlaž Hrastnik <blaz@mxxn.io> dateThu 3 Dec 2020, 13:12 (6 years ago) parent2e12fc939bf1ca
25 files changed+2322−648
Cargo.lock+600−78 Cargo.toml+1 helix-core/src/diagnostic.rs+7 helix-core/src/history.rs+25−21 helix-core/src/indent.rs+17−12 helix-core/src/lib.rs+3−1 helix-core/src/selection.rs+5 helix-core/src/state.rs+2−76 helix-core/src/syntax.rs+5−1 helix-core/src/transaction.rs+14−31 helix-lsp/Cargo.toml+26 helix-lsp/src/client.rs+355 helix-lsp/src/lib.rs+117 helix-lsp/src/transport.rs+212 helix-term/Cargo.toml+9−1 helix-term/src/application.rs+299−155 helix-term/src/main.rs+48−3 helix-view/Cargo.toml+3 helix-view/src/commands.rs+290−243 helix-view/src/document.rs+209 helix-view/src/editor.rs+28−4 helix-view/src/keymap.rs+10−6 helix-view/src/lib.rs+3 helix-view/src/theme.rs+2 helix-view/src/view.rs+32−16
Cargo.lock+600−78
@@ -2,24 +2,30 @@
22# It is not intended for manual editing.
33[[package]]
44name = "aho-corasick"
5−version = "0.7.14"
5+version = "0.7.15"
66source = "registry+https://github.com/rust-lang/crates.io-index"
7−checksum = "b476ce7103678b0c6d3d395dbbae31d48ff910bd28be979ba5d48c6351131d0d"
7+checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
88dependencies = [
99 "memchr",
1010]
1111
1212[[package]]
1313name = "anyhow"
14−version = "1.0.33"
14+version = "1.0.34"
1515source = "registry+https://github.com/rust-lang/crates.io-index"
16−checksum = "a1fd36ffbb1fb7c834eac128ea8d0e310c5aeb635548f9d58861e1308d46e71c"
16+checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7"
1717
1818[[package]]
19−name = "arc-swap"
20−version = "0.4.7"
19+name = "arrayref"
20+version = "0.3.6"
2121source = "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"
2329
2430[[package]]
2531name = "async-channel"
@@ -34,9 +40,9 @@ dependencies = [
3440
3541[[package]]
3642name = "async-executor"
37−version = "1.3.0"
43+version = "1.4.0"
3844source = "registry+https://github.com/rust-lang/crates.io-index"
39−checksum = "d373d78ded7d0b3fa8039375718cde0aace493f2e34fb60f51cbf567562ca801"
45+checksum = "eb877970c7b440ead138f6321a3b5395d6061183af779340b65e20c0fede9146"
4046dependencies = [
4147 "async-task",
4248 "concurrent-queue",
@@ -59,9 +65,9 @@ dependencies = [
5965
6066[[package]]
6167name = "async-io"
62−version = "1.1.10"
68+version = "1.3.1"
6369source = "registry+https://github.com/rust-lang/crates.io-index"
64−checksum = "d54bc4c1c7292475efb2253227dbcfad8fe1ca4c02bc62c510cc2f3da5c4704e"
70+checksum = "9315f8f07556761c3e48fec2e6b276004acf426e6dc068b2c2251854d65ee0fd"
6571dependencies = [
6672 "concurrent-queue",
6773 "fastrand",
@@ -88,9 +94,9 @@ dependencies = [
8894
8995[[package]]
9096name = "async-net"
91−version = "1.4.7"
97+version = "1.5.0"
9298source = "registry+https://github.com/rust-lang/crates.io-index"
93−checksum = "ee4c3668eb091d781e97f0026b5289b457c77d407a85749a9bb4c057456c428f"
99+checksum = "06de475c85affe184648202401d7622afb32f0f74e02192857d0201a16defbe5"
94100dependencies = [
95101 "async-io",
96102 "blocking",
@@ -106,7 +112,7 @@ checksum = "4c8cea09c1fb10a317d1b5af8024eeba256d6554763e85ecd90ff8df31c7bbda"
106112dependencies = [
107113 "async-io",
108114 "blocking",
109− "cfg-if",
115+ "cfg-if 0.1.10",
110116 "event-listener",
111117 "futures-lite",
112118 "once_cell",
@@ -116,9 +122,9 @@ dependencies = [
116122
117123[[package]]
118124name = "async-task"
119−version = "4.0.2"
125+version = "4.0.3"
120126source = "registry+https://github.com/rust-lang/crates.io-index"
121−checksum = "8ab27c1aa62945039e44edaeee1dc23c74cc0c303dd5fe0fb462a184f1c3a518"
127+checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0"
122128
123129[[package]]
124130name = "atomic-waker"
@@ -132,12 +138,35 @@ version = "1.0.1"
132138source = "registry+https://github.com/rust-lang/crates.io-index"
133139checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
134140
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+
135153[[package]]
136154name = "bitflags"
137155version = "1.2.1"
138156source = "registry+https://github.com/rust-lang/crates.io-index"
139157checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
140158
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+
141170[[package]]
142171name = "blocking"
143172version = "1.0.2"
@@ -166,9 +195,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
166195
167196[[package]]
168197name = "cc"
169−version = "1.0.61"
198+version = "1.0.65"
170199source = "registry+https://github.com/rust-lang/crates.io-index"
171−checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d"
200+checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15"
172201dependencies = [
173202 "jobserver",
174203]
@@ -179,6 +208,25 @@ version = "0.1.10"
179208source = "registry+https://github.com/rust-lang/crates.io-index"
180209checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
181210
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+
182230[[package]]
183231name = "clap"
184232version = "3.0.0-beta.2"
@@ -212,11 +260,28 @@ dependencies = [
212260 "cache-padded",
213261]
214262
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+
215280[[package]]
216281name = "crossterm"
217−version = "0.18.0"
282+version = "0.18.2"
218283source = "registry+https://github.com/rust-lang/crates.io-index"
219−checksum = "c2fcdc3c9cf8ee446222e8ee8691a6d21b563b8fe1a64b1873080db7b5b23cf0"
284+checksum = "4e86d73f2a0b407b5768d10a8c720cf5d2df49a9efc10ca09176d201ead4b7fb"
220285dependencies = [
221286 "bitflags",
222287 "crossterm_winapi",
@@ -231,13 +296,34 @@ dependencies = [
231296
232297[[package]]
233298name = "crossterm_winapi"
234−version = "0.6.1"
299+version = "0.6.2"
235300source = "registry+https://github.com/rust-lang/crates.io-index"
236−checksum = "057b7146d02fb50175fd7dbe5158f6097f33d02831f43b4ee8ae4ddf67b68f5c"
301+checksum = "c2265c3f8e080075d9b6417aa72293fc71662f34b4af2612d8d1b074d29510db"
237302dependencies = [
238303 "winapi",
239304]
240305
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+
241327[[package]]
242328name = "event-listener"
243329version = "2.5.1"
@@ -253,6 +339,25 @@ dependencies = [
253339 "instant",
254340]
255341
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+
256361[[package]]
257362name = "futf"
258363version = "0.1.4"
@@ -263,23 +368,29 @@ dependencies = [
263368 "new_debug_unreachable",
264369]
265370
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+
266377[[package]]
267378name = "futures-core"
268−version = "0.3.6"
379+version = "0.3.8"
269380source = "registry+https://github.com/rust-lang/crates.io-index"
270−checksum = "d674eaa0056896d5ada519900dbf97ead2e46a7b6621e8160d79e2f2e1e2784b"
381+checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748"
271382
272383[[package]]
273384name = "futures-io"
274−version = "0.3.6"
385+version = "0.3.8"
275386source = "registry+https://github.com/rust-lang/crates.io-index"
276−checksum = "5fc94b64bb39543b4e432f1790b6bf18e3ee3b74653c5449f63310e9a74b123c"
387+checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb"
277388
278389[[package]]
279390name = "futures-lite"
280−version = "1.11.1"
391+version = "1.11.2"
281392source = "registry+https://github.com/rust-lang/crates.io-index"
282−checksum = "381a7ad57b1bad34693f63f6f377e1abded7a9c85c9d3eb6771e11c60aaadab9"
393+checksum = "5e6c079abfac3ab269e2927ec048dabc89d009ebfdda6b8ee86624f30c689658"
283394dependencies = [
284395 "fastrand",
285396 "futures-core",
@@ -290,6 +401,60 @@ dependencies = [
290401 "waker-fn",
291402]
292403
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+
293458[[package]]
294459name = "hashbrown"
295460version = "0.9.1"
@@ -312,6 +477,28 @@ dependencies = [
312477 "unicode-width",
313478]
314479
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+
315502[[package]]
316503name = "helix-syntax"
317504version = "0.1.0"
@@ -325,10 +512,15 @@ name = "helix-term"
325512version = "0.1.0"
326513dependencies = [
327514 "anyhow",
515+ "chrono",
328516 "clap",
329517 "crossterm",
518+ "fern",
519+ "futures-util",
330520 "helix-core",
521+ "helix-lsp",
331522 "helix-view",
523+ "log",
332524 "num_cpus",
333525 "smol",
334526 "tui",
@@ -342,7 +534,9 @@ dependencies = [
342534 "crossterm",
343535 "helix-core",
344536 "once_cell",
537+ "smol",
345538 "tui",
539+ "url",
346540]
347541
348542[[package]]
@@ -354,6 +548,17 @@ dependencies = [
354548 "libc",
355549]
356550
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+
357562[[package]]
358563name = "indexmap"
359564version = "1.6.0"
@@ -366,13 +571,19 @@ dependencies = [
366571
367572[[package]]
368573name = "instant"
369−version = "0.1.7"
574+version = "0.1.9"
370575source = "registry+https://github.com/rust-lang/crates.io-index"
371−checksum = "63312a18f7ea8760cdd0a7c5aac1a619752a246b833545e3e36d1f81f7cd9e66"
576+checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
372577dependencies = [
373− "cfg-if",
578+ "cfg-if 1.0.0",
374579]
375580
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+
376587[[package]]
377588name = "jobserver"
378589version = "0.1.21"
@@ -382,6 +593,19 @@ dependencies = [
382593 "libc",
383594]
384595
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+
385609[[package]]
386610name = "lazy_static"
387611version = "1.4.0"
@@ -390,15 +614,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
390614
391615[[package]]
392616name = "libc"
393−version = "0.2.79"
617+version = "0.2.80"
394618source = "registry+https://github.com/rust-lang/crates.io-index"
395−checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"
619+checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
396620
397621[[package]]
398622name = "lock_api"
399−version = "0.4.1"
623+version = "0.4.2"
400624source = "registry+https://github.com/rust-lang/crates.io-index"
401−checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c"
625+checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
402626dependencies = [
403627 "scopeguard",
404628]
@@ -409,7 +633,21 @@ version = "0.4.11"
409633source = "registry+https://github.com/rust-lang/crates.io-index"
410634checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
411635dependencies = [
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",
413651]
414652
415653[[package]]
@@ -418,17 +656,23 @@ version = "0.1.1"
418656source = "registry+https://github.com/rust-lang/crates.io-index"
419657checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
420658
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+
421665[[package]]
422666name = "memchr"
423−version = "2.3.3"
667+version = "2.3.4"
424668source = "registry+https://github.com/rust-lang/crates.io-index"
425−checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
669+checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
426670
427671[[package]]
428672name = "mio"
429−version = "0.7.3"
673+version = "0.7.6"
430674source = "registry+https://github.com/rust-lang/crates.io-index"
431−checksum = "e53a6ea5f38c0a48ca42159868c6d8e1bd56c0451238856cc08d58563643bdc3"
675+checksum = "f33bc887064ef1fd66020c9adfc45bb9f33d75a42096c81e7c56c65b75dd1a8b"
432676dependencies = [
433677 "libc",
434678 "log",
@@ -439,9 +683,9 @@ dependencies = [
439683
440684[[package]]
441685name = "miow"
442−version = "0.3.5"
686+version = "0.3.6"
443687source = "registry+https://github.com/rust-lang/crates.io-index"
444−checksum = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e"
688+checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897"
445689dependencies = [
446690 "socket2",
447691 "winapi",
@@ -465,13 +709,32 @@ checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
465709
466710[[package]]
467711name = "ntapi"
468−version = "0.3.4"
712+version = "0.3.6"
469713source = "registry+https://github.com/rust-lang/crates.io-index"
470−checksum = "7a31937dea023539c72ddae0e3571deadc1414b300483fa7aaec176168cfa9d2"
714+checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
471715dependencies = [
472716 "winapi",
473717]
474718
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+
475738[[package]]
476739name = "num_cpus"
477740version = "1.13.0"
@@ -484,15 +747,15 @@ dependencies = [
484747
485748[[package]]
486749name = "once_cell"
487−version = "1.4.1"
750+version = "1.5.2"
488751source = "registry+https://github.com/rust-lang/crates.io-index"
489−checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad"
752+checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
490753
491754[[package]]
492755name = "os_str_bytes"
493−version = "2.3.2"
756+version = "2.4.0"
494757source = "registry+https://github.com/rust-lang/crates.io-index"
495−checksum = "2ac6fe3538f701e339953a3ebbe4f39941aababa8a3f6964635b24ab526daeac"
758+checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85"
496759
497760[[package]]
498761name = "parking"
@@ -502,9 +765,9 @@ checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
502765
503766[[package]]
504767name = "parking_lot"
505−version = "0.11.0"
768+version = "0.11.1"
506769source = "registry+https://github.com/rust-lang/crates.io-index"
507−checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733"
770+checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
508771dependencies = [
509772 "instant",
510773 "lock_api",
@@ -517,7 +780,7 @@ version = "0.8.0"
517780source = "registry+https://github.com/rust-lang/crates.io-index"
518781checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
519782dependencies = [
520− "cfg-if",
783+ "cfg-if 0.1.10",
521784 "cloudabi",
522785 "instant",
523786 "libc",
@@ -526,36 +789,115 @@ dependencies = [
526789 "winapi",
527790]
528791
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+
529824[[package]]
530825name = "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"
532833source = "registry+https://github.com/rust-lang/crates.io-index"
533−checksum = "e555d9e657502182ac97b539fb3dae8b79cda19e3e4f8ffb5e8de4f18df93c95"
834+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
534835
535836[[package]]
536837name = "polling"
537−version = "2.0.1"
838+version = "2.0.2"
538839source = "registry+https://github.com/rust-lang/crates.io-index"
539−checksum = "ab773feb154f12c49ffcfd66ab8bdcf9a1843f950db48b0d8be9d4393783b058"
840+checksum = "a2a7bc6b2a29e632e45451c941832803a18cce6781db04de8a04696cdca8bde4"
540841dependencies = [
541− "cfg-if",
842+ "cfg-if 0.1.10",
542843 "libc",
543844 "log",
544845 "wepoll-sys",
545846 "winapi",
546847]
547848
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+
548879[[package]]
549880name = "redox_syscall"
550881version = "0.1.57"
551882source = "registry+https://github.com/rust-lang/crates.io-index"
552883checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
553884
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+
554896[[package]]
555897name = "regex"
556−version = "1.4.1"
898+version = "1.4.2"
557899source = "registry+https://github.com/rust-lang/crates.io-index"
558−checksum = "8963b85b8ce3074fecffde43b4b0dded83ce2f367dc8d363afc56679f3ee820b"
900+checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c"
559901dependencies = [
560902 "aho-corasick",
561903 "memchr",
@@ -565,9 +907,9 @@ dependencies = [
565907
566908[[package]]
567909name = "regex-syntax"
568−version = "0.6.20"
910+version = "0.6.21"
569911source = "registry+https://github.com/rust-lang/crates.io-index"
570−checksum = "8cab7a364d15cde1e505267766a2d3c4e22a843e1a601f0fa7564c0f82ced11c"
912+checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
571913
572914[[package]]
573915name = "ropey"
@@ -578,12 +920,81 @@ dependencies = [
578920 "smallvec",
579921]
580922
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+
581941[[package]]
582942name = "scopeguard"
583943version = "1.1.0"
584944source = "registry+https://github.com/rust-lang/crates.io-index"
585945checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
586946
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+
587998[[package]]
588999name = "signal-hook"
5891000version = "0.1.16"
@@ -597,25 +1008,30 @@ dependencies = [
5971008
5981009[[package]]
5991010name = "signal-hook-registry"
600−version = "1.2.1"
1011+version = "1.2.2"
6011012source = "registry+https://github.com/rust-lang/crates.io-index"
602−checksum = "a3e12110bc539e657a646068aaf5eb5b63af9d0c1f7b29c97113fad80e15f035"
1013+checksum = "ce32ea0c6c56d5eacaeb814fbed9960547021d3edd010ded1425f180536b20ab"
6031014dependencies = [
604− "arc-swap",
6051015 "libc",
6061016]
6071017
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+
6081024[[package]]
6091025name = "smallvec"
610−version = "1.4.2"
1026+version = "1.5.0"
6111027source = "registry+https://github.com/rust-lang/crates.io-index"
612−checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252"
1028+checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85"
6131029
6141030[[package]]
6151031name = "smol"
616−version = "1.2.4"
1032+version = "1.2.5"
6171033source = "registry+https://github.com/rust-lang/crates.io-index"
618−checksum = "aaf8ded16994c0ae59596c6e4733c76faeb0533c26fd5ca1b1bc89271a049a66"
1034+checksum = "85cf3b5351f3e783c1d79ab5fc604eeed8b8ae9abd36b166e8b87a089efd85e4"
6191035dependencies = [
6201036 "async-channel",
6211037 "async-executor",
@@ -631,16 +1047,27 @@ dependencies = [
6311047
6321048[[package]]
6331049name = "socket2"
634−version = "0.3.15"
1050+version = "0.3.17"
6351051source = "registry+https://github.com/rust-lang/crates.io-index"
636−checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44"
1052+checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902"
6371053dependencies = [
638− "cfg-if",
1054+ "cfg-if 1.0.0",
6391055 "libc",
6401056 "redox_syscall",
6411057 "winapi",
6421058]
6431059
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+
6441071[[package]]
6451072name = "tendril"
6461073version = "0.4.1"
@@ -660,6 +1087,26 @@ dependencies = [
6601087 "unicode-width",
6611088]
6621089
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+
6631110[[package]]
6641111name = "thread_local"
6651112version = "1.0.1"
@@ -669,11 +1116,37 @@ dependencies = [
6691116 "lazy_static",
6701117]
6711118
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+
6721145[[package]]
6731146name = "tree-sitter"
674−version = "0.17.0"
1147+version = "0.17.1"
6751148source = "registry+https://github.com/rust-lang/crates.io-index"
676−checksum = "70ee7370fec3aecde3862a7d64c571048f70a7298daef1815e8fc68b9de54b5c"
1149+checksum = "d18dcb776d3affaba6db04d11d645946d34a69b3172e588af96ce9fecd20faac"
6771150dependencies = [
6781151 "cc",
6791152 "regex",
@@ -681,8 +1154,8 @@ dependencies = [
6811154
6821155[[package]]
6831156name = "tui"
684−version = "0.12.0"
685−source = "git+https://github.com/fdehau/tui-rs#25ff2e5e61f8902101e485743992db2412f77aad"
1157+version = "0.13.0"
1158+source = "git+https://github.com/fdehau/tui-rs#efdd6bfb193dafcb5e3bdc75e7d2d314065da1d7"
6861159dependencies = [
6871160 "bitflags",
6881161 "cassowary",
@@ -691,11 +1164,29 @@ dependencies = [
6911164 "unicode-width",
6921165]
6931166
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+
6941185[[package]]
6951186name = "unicode-segmentation"
696−version = "1.6.0"
1187+version = "1.7.1"
6971188source = "registry+https://github.com/rust-lang/crates.io-index"
698−checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
1189+checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
6991190
7001191[[package]]
7011192name = "unicode-width"
@@ -703,6 +1194,25 @@ version = "0.1.8"
7031194source = "registry+https://github.com/rust-lang/crates.io-index"
7041195checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
7051196
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+
7061216[[package]]
7071217name = "utf-8"
7081218version = "0.7.5"
@@ -727,11 +1237,23 @@ version = "1.1.0"
7271237source = "registry+https://github.com/rust-lang/crates.io-index"
7281238checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
7291239
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+
7301252[[package]]
7311253name = "wepoll-sys"
732−version = "3.0.0"
1254+version = "3.0.1"
7331255source = "registry+https://github.com/rust-lang/crates.io-index"
734−checksum = "142bc2cba3fe88be1a8fcb55c727fa4cd5b0cf2d7438722792e22f26f04bc1e0"
1256+checksum = "0fcb14dea929042224824779fbc82d9fab8d2e6d3cbc0ac404de8edf489e77ff"
7351257dependencies = [
7361258 "cc",
7371259]
Cargo.toml+1
@@ -4,6 +4,7 @@ members = [
44 "helix-view",
55 "helix-term",
66 "helix-syntax",
7+ "helix-lsp",
78]
89
910# 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 {
5757 self.cursor == 0
5858 }
5959
60− pub fn undo(&mut self, state: &mut State) {
60+ // TODO: I'd like to pass Transaction by reference but it fights with the borrowck
61+
62+ pub fn undo(&mut self) -> Option<Transaction> {
6163 if self.at_root() {
6264 // We're at the root of undo, nothing to do.
63− return;
65+ return None;
6466 }
6567
6668 let current_revision = &self.revisions[self.cursor];
6769
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−
7570 self.cursor = current_revision.parent;
71+
72+ Some(current_revision.revert.clone())
7673 }
7774
78− pub fn redo(&mut self, state: &mut State) {
75+ pub fn redo(&mut self) -> Option<Transaction> {
7976 let current_revision = &self.revisions[self.cursor];
8077
8178 // for now, simply pick the latest child (linear undo / redo)
8279 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−
8980 self.cursor = *index;
81+
82+ return Some(transaction.clone());
9083 }
84+ None
9185 }
9286}
9387
@@ -120,17 +114,27 @@ mod test {
120114 assert_eq!("hello 世界!", state.doc());
121115
122116 // ---
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+ }
123127
124− history.undo(&mut state);
128+ undo(&mut history, &mut state);
125129 assert_eq!("hello world!", state.doc());
126− history.redo(&mut state);
130+ redo(&mut history, &mut state);
127131 assert_eq!("hello 世界!", state.doc());
128− history.undo(&mut state);
129− history.undo(&mut state);
132+ undo(&mut history, &mut state);
133+ undo(&mut history, &mut state);
130134 assert_eq!("hello", state.doc());
131135
132136 // undo at root is a no-op
133− history.undo(&mut state);
137+ undo(&mut history, &mut state);
134138 assert_eq!("hello", state.doc());
135139 }
136140}
helix-core/src/indent.rs+17−12
@@ -111,17 +111,17 @@ fn find_first_non_whitespace_char(state: &State, line_num: usize) -> usize {
111111 start
112112}
113113
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 {
115115 let line = state.doc.line(line_num);
116116 let current = indent_level_for_line(line);
117117
118118 let start = find_first_non_whitespace_char(state, line_num);
119119
120− suggested_indent_for_pos(state, start)
120+ suggested_indent_for_pos(syntax, state, start)
121121}
122122
123−pub fn suggested_indent_for_pos(state: &State, pos: usize) -> usize {
124− if let Some(syntax) = &state.syntax {
123+pub fn suggested_indent_for_pos(syntax: Option<&Syntax>, state: &State, pos: usize) -> usize {
124+ if let Some(syntax) = syntax {
125125 let byte_start = state.doc.char_to_byte(pos);
126126 let node = get_highest_syntax_node_at_bytepos(syntax, byte_start);
127127
@@ -163,13 +163,18 @@ mod test {
163163",
164164 );
165165
166− let mut state = State::new(doc);
167− state.set_language("source.rust", &[]);
168−
169− assert_eq!(suggested_indent_for_line(&state, 0), 0); // mod
170− assert_eq!(suggested_indent_for_line(&state, 1), 1); // fn
171− assert_eq!(suggested_indent_for_line(&state, 2), 2); // 1 + 1
172− assert_eq!(suggested_indent_for_line(&state, 4), 1); // }
173− assert_eq!(suggested_indent_for_line(&state, 5), 0); // }
166+ let state = State::new(doc);
167+ // TODO: set_language
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+
174+ assert_eq!(suggested_indent_for_line(Some(&syntax), &state, 0), 0); // mod
175+ assert_eq!(suggested_indent_for_line(Some(&syntax), &state, 1), 1); // fn
176+ assert_eq!(suggested_indent_for_line(Some(&syntax), &state, 2), 2); // 1 + 1
177+ assert_eq!(suggested_indent_for_line(Some(&syntax), &state, 4), 1); // }
178+ assert_eq!(suggested_indent_for_line(Some(&syntax), &state, 5), 0); // }
174179 }
175180}
helix-core/src/lib.rs+3−1
@@ -1,4 +1,5 @@
11#![allow(unused)]
2+mod diagnostic;
23pub mod graphemes;
34mod history;
45pub mod indent;
@@ -22,7 +23,8 @@ pub use selection::Range;
2223pub use selection::Selection;
2324pub use syntax::Syntax;
2425
26+pub use diagnostic::Diagnostic;
2527pub use history::History;
2628pub use state::State;
2729
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 {
179179 }
180180 }
181181
182+ /// Constructs a selection holding a single cursor.
183+ pub fn point(pos: usize) -> Self {
184+ Self::single(pos, pos)
185+ }
186+
182187 #[must_use]
183188 pub fn new(ranges: SmallVec<[Range; 1]>, primary_index: usize) -> Self {
184189 fn normalize(mut ranges: SmallVec<[Range; 1]>, mut primary_index: usize) -> Selection {
helix-core/src/state.rs+2−76
@@ -1,33 +1,14 @@
11use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes};
22use crate::syntax::LOADER;
3−use crate::{ChangeSet, Position, Range, Rope, RopeSlice, Selection, Syntax};
3+use crate::{ChangeSet, Diagnostic, Position, Range, Rope, RopeSlice, Selection, Syntax};
44use anyhow::Error;
55
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−
156/// A state represents the current editor state of a single buffer.
7+#[derive(Clone)]
168pub struct State {
179 // TODO: fields should be private but we need to refactor commands.rs first
18− /// Path to file on disk.
19− pub path: Option<PathBuf>,
2010 pub doc: Rope,
2111 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)>,
3112}
3213
3314#[derive(Copy, Clone, PartialEq, Eq)]
@@ -46,57 +27,12 @@ pub enum Granularity {
4627impl State {
4728 #[must_use]
4829 pub fn new(doc: Rope) -> Self {
49− let changes = ChangeSet::new(&doc);
50− let old_state = Some((doc.clone(), Selection::single(0, 0)));
51−
5230 Self {
53− path: None,
5431 doc,
5532 selection: Selection::single(0, 0),
56− mode: Mode::Normal,
57− restore_cursor: false,
58− syntax: None,
59− changes,
60− old_state,
6133 }
6234 }
6335
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−
10036 // TODO: doc/selection accessors
10137
10238 // TODO: be able to take either Rope or RopeSlice
@@ -110,16 +46,6 @@ impl State {
11046 &self.selection
11147 }
11248
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−
12349 // pub fn doc<R>(&self, range: R) -> RopeSlice
12450 // where
12551 // R: std::ops::RangeBounds<usize>,
helix-core/src/syntax.rs+5−1
@@ -62,11 +62,15 @@ impl LanguageConfiguration {
6262 })
6363 .map(Option::as_ref)
6464 }
65+
66+ pub fn scope(&self) -> &str {
67+ &self.scope
68+ }
6569}
6670
6771use once_cell::sync::Lazy;
6872
69−pub(crate) static LOADER: Lazy<Loader> = Lazy::new(Loader::init);
73+pub static LOADER: Lazy<Loader> = Lazy::new(Loader::init);
7074
7175pub struct Loader {
7276 // highlight_names ?
helix-core/src/transaction.rs+14−31
@@ -5,8 +5,9 @@ use std::convert::TryFrom;
55/// (from, to, replacement)
66pub type Change = (usize, usize, Option<Tendril>);
77
8+// TODO: pub(crate)
89#[derive(Debug, Clone, PartialEq, Eq)]
9−pub(crate) enum Operation {
10+pub enum Operation {
1011 /// Move cursor by n characters.
1112 Retain(usize),
1213 /// Delete n characters.
@@ -40,6 +41,12 @@ impl ChangeSet {
4041 }
4142
4243 // 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+ }
4350
4451 #[must_use]
4552 fn len_after(&self) -> usize {
@@ -351,22 +358,6 @@ pub struct Transaction {
351358 // scroll_into_view
352359}
353360
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−
370361impl Transaction {
371362 /// Create a new, empty transaction.
372363 pub fn new(state: &mut State) -> Self {
@@ -376,29 +367,21 @@ impl Transaction {
376367 }
377368 }
378369
370+ pub fn changes(&self) -> &ChangeSet {
371+ &self.changes
372+ }
373+
379374 /// Returns true if applied successfully.
380375 pub fn apply(&self, state: &mut State) -> bool {
381376 if !self.changes.is_empty() {
382− // TODO: also avoid mapping the selection if not necessary
383−
384− let old_doc = state.doc().clone();
385−
386377 // apply changes to the document
387378 if !self.changes.apply(&mut state.doc) {
388379 return false;
389380 }
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− }
400381 }
401382
383+ // TODO: also avoid mapping the selection if not necessary
384+
402385 // update the selection: either take the selection specified in the transaction, or map the
403386 // current selection through changes.
404387 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"
1414[dependencies]
1515helix-core = { path = "../helix-core" }
1616helix-view = { path = "../helix-view", features = ["term"]}
17+helix-lsp = { path = "../helix-lsp"}
1718
1819anyhow = "1"
1920
2021smol = "1"
21−num_cpus = "1.13"
22+num_cpus = "1"
2223# tui = { version = "0.12", default-features = false, features = ["crossterm"] }
2324tui = { git = "https://github.com/fdehau/tui-rs", default-features = false, features = ["crossterm"] }
2425crossterm = { version = "0.18", features = ["event-stream"] }
2526clap = { 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 @@
11use clap::ArgMatches as Args;
2−use helix_core::{indent::TAB_WIDTH, state::Mode, syntax::HighlightEvent, Position, Range, State};
3−use helix_view::{commands, keymap, prompt::Prompt, Editor, View};
2+use helix_core::{indent::TAB_WIDTH, syntax::HighlightEvent, Position, Range, State};
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};
412
513use std::{
614 borrow::Cow,
@@ -15,8 +23,7 @@ use anyhow::Error;
1523
1624use crossterm::{
1725 cursor,
18− cursor::position,
19− event::{self, read, Event, EventStream, KeyCode, KeyEvent},
26+ event::{read, Event, EventStream, KeyCode, KeyEvent},
2027 execute, queue,
2128 terminal::{self, disable_raw_mode, enable_raw_mode},
2229};
@@ -25,21 +32,23 @@ use tui::{
2532 backend::CrosstermBackend,
2633 buffer::Buffer as Surface,
2734 layout::Rect,
28− style::{Color, Style},
35+ style::{Color, Modifier, Style},
2936};
3037
31−const OFFSET: u16 = 6; // 5 linenr + 1 gutter
38+const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
3239
3340type Terminal = tui::Terminal<CrosstermBackend<std::io::Stdout>>;
3441
35−static EX: smol::Executor = smol::Executor::new();
36−
3742const BASE_WIDTH: u16 = 30;
3843
39−pub struct Application {
44+pub struct Application<'a> {
4045 editor: Editor,
4146 prompt: Option<Prompt>,
4247 terminal: Renderer,
48+
49+ keymap: Keymaps,
50+ executor: &'a smol::Executor<'a>,
51+ language_server: helix_lsp::Client,
4352}
4453
4554struct Renderer {
@@ -75,30 +84,29 @@ impl Renderer {
7584 self.cache = Surface::empty(area);
7685 }
7786
78− pub fn render_view(&mut self, view: &mut View, viewport: Rect) {
79− self.render_buffer(view, viewport);
80− self.render_statusline(view);
87+ pub fn render_view(&mut self, view: &mut View, viewport: Rect, theme: &Theme) {
88+ self.render_buffer(view, viewport, theme);
89+ self.render_statusline(view, theme);
8190 }
8291
8392 // 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) {
8594 let area = Rect::new(0, 0, self.size.0, self.size.1);
8695 self.surface.reset(); // reset is faster than allocating new empty surface
8796
8897 // 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"));
9199
92100 // 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();
94102
95103 let last_line = view.last_line();
96104
97105 let range = {
98106 // calculate viewport byte ranges
99− let start = view.state.doc().line_to_byte(view.first_line);
100− let end = view.state.doc().line_to_byte(last_line)
101− + view.state.doc().line(last_line).len_bytes();
107+ let start = view.doc.text().line_to_byte(view.first_line);
108+ let end = view.doc.text().line_to_byte(last_line)
109+ + view.doc.text().line(last_line).len_bytes();
102110
103111 start..end
104112 };
@@ -106,7 +114,7 @@ impl Renderer {
106114 // TODO: range doesn't actually restrict source, just highlight range
107115 // TODO: cache highlight results
108116 // 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() {
110118 Some(syntax) => {
111119 syntax
112120 .highlight_iter(source_code.as_bytes(), Some(range), None, |_| None)
@@ -122,6 +130,7 @@ impl Renderer {
122130 let mut visual_x = 0;
123131 let mut line = 0u16;
124132 let visible_selections: Vec<Range> = view
133+ .doc
125134 .state
126135 .selection()
127136 .ranges()
@@ -142,15 +151,15 @@ impl Renderer {
142151 HighlightEvent::Source { start, end } => {
143152 // TODO: filter out spans out of viewport for now..
144153
145− let start = view.state.doc().byte_to_char(start);
146− let end = view.state.doc().byte_to_char(end); // <-- index 744, len 743
154+ let start = view.doc.text().byte_to_char(start);
155+ let end = view.doc.text().byte_to_char(end); // <-- index 744, len 743
147156
148− let text = view.state.doc().slice(start..end);
157+ let text = view.doc.text().slice(start..end);
149158
150159 use helix_core::graphemes::{grapheme_width, RopeGraphemes};
151160
152161 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()),
154163 None => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
155164 };
156165
@@ -200,6 +209,16 @@ impl Renderer {
200209 style
201210 };
202211
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+
203222 // TODO: paint cursor heads except primary
204223
205224 self.surface
@@ -207,23 +226,28 @@ impl Renderer {
207226
208227 visual_x += width;
209228 }
210− // if grapheme == "\t"
211229
212230 char_index += 1;
213231 }
214232 }
215233 }
216234 }
217− let style: Style = view.theme.get("ui.linenr");
235+
236+ let style: Style = theme.get("ui.linenr");
237+ let warning: Style = theme.get("warning");
218238 let last_line = view.last_line();
219239 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+
220244 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);
222246 }
223247 }
224248
225− pub fn render_statusline(&mut self, view: &View) {
226− let mode = match view.state.mode() {
249+ pub fn render_statusline(&mut self, view: &View, theme: &Theme) {
250+ let mode = match view.doc.mode() {
227251 Mode::Insert => "INS",
228252 Mode::Normal => "NOR",
229253 Mode::Goto => "GOTO",
@@ -231,13 +255,20 @@ impl Renderer {
231255 // statusline
232256 self.surface.set_style(
233257 Rect::new(0, self.size.1 - 2, self.size.0, 1),
234− view.theme.get("ui.statusline"),
258+ theme.get("ui.statusline"),
235259 );
236260 self.surface
237261 .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+ );
238269 }
239270
240− pub fn render_prompt(&mut self, view: &View, prompt: &Prompt) {
271+ pub fn render_prompt(&mut self, view: &View, prompt: &Prompt, theme: &Theme) {
241272 // completion
242273 if !prompt.completion.is_empty() {
243274 // TODO: find out better way of clearing individual lines of the screen
@@ -256,7 +287,7 @@ impl Renderer {
256287 }
257288 self.surface.set_style(
258289 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"),
260291 );
261292 for (i, command) in prompt.completion.iter().enumerate() {
262293 let color = if prompt.completion_selection_index.is_some()
@@ -302,14 +333,14 @@ impl Renderer {
302333
303334 pub fn render_cursor(&mut self, view: &View, prompt: Option<&Prompt>, viewport: Rect) {
304335 let mut stdout = stdout();
305− match view.state.mode() {
336+ match view.doc.mode() {
306337 Mode::Insert => write!(stdout, "\x1B[6 q"),
307338 mode => write!(stdout, "\x1B[2 q"),
308339 };
309340 let pos = if let Some(prompt) = prompt {
310341 Position::new(self.size.0 as usize, 2 + prompt.cursor)
311342 } else {
312− if let Some(path) = view.state.path() {
343+ if let Some(path) = view.doc.path() {
313344 self.surface.set_string(
314345 6,
315346 self.size.1 - 1,
@@ -318,10 +349,10 @@ impl Renderer {
318349 );
319350 }
320351
321− let cursor = view.state.selection().cursor();
352+ let cursor = view.doc.state.selection().cursor();
322353
323354 let mut pos = view
324− .screen_coords_at_pos(&view.state.doc().slice(..), cursor)
355+ .screen_coords_at_pos(&view.doc.text().slice(..), cursor)
325356 .expect("Cursor is out of bounds.");
326357 pos.col += viewport.x as usize;
327358 pos.row += viewport.y as usize;
@@ -332,8 +363,8 @@ impl Renderer {
332363 }
333364}
334365
335−impl Application {
336− pub fn new(mut args: Args) -> Result<Self, Error> {
366+impl<'a> Application<'a> {
367+ pub fn new(mut args: Args, executor: &'a smol::Executor<'a>) -> Result<Self, Error> {
337368 let terminal = Renderer::new()?;
338369 let mut editor = Editor::new();
339370
@@ -341,11 +372,18 @@ impl Application {
341372 editor.open(file, terminal.size)?;
342373 }
343374
375+ let language_server = helix_lsp::Client::start(&executor, "rust-analyzer", &[]);
376+
344377 let mut app = Self {
345378 editor,
346379 terminal,
347380 // TODO; move to state
348381 prompt: None,
382+
383+ //
384+ keymap: keymap::default(),
385+ executor,
386+ language_server,
349387 };
350388
351389 Ok(app)
@@ -354,13 +392,16 @@ impl Application {
354392 fn render(&mut self) {
355393 let viewport = Rect::new(OFFSET, 0, self.terminal.size.0, self.terminal.size.1 - 2); // - 2 for statusline and prompt
356394
357− if let Some(view) = &mut self.editor.view {
358− self.terminal.render_view(view, viewport);
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) };
398+ if let Some(view) = self.editor.view_mut() {
399+ self.terminal.render_view(view, viewport, theme_ref);
359400 if let Some(prompt) = &self.prompt {
360401 if prompt.should_close {
361402 self.prompt = None;
362403 } else {
363− self.terminal.render_prompt(view, prompt);
404+ self.terminal.render_prompt(view, prompt, theme_ref);
364405 }
365406 }
366407 }
@@ -368,16 +409,19 @@ impl Application {
368409 self.terminal.draw();
369410
370411 // TODO: drop unwrap
371− self.terminal.render_cursor(
372− self.editor.view.as_ref().unwrap(),
373− self.prompt.as_ref(),
374− viewport,
375− );
412+ self.terminal
413+ .render_cursor(self.editor.view().unwrap(), self.prompt.as_ref(), viewport);
376414 }
377415
378416 pub async fn event_loop(&mut self) {
379417 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();
381425
382426 self.render();
383427
@@ -386,125 +430,225 @@ impl Application {
386430 break;
387431 }
388432
389− // Handle key events
390− match reader.next().await {
391− Some(Ok(Event::Resize(width, height))) => {
392− self.terminal.resize(width, height);
433+ use futures_util::{select, FutureExt};
434+ select! {
435+ event = reader.next().fuse() => {
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+ }
393444
394− // TODO: simplistic ensure cursor in view for now
395− if let Some(view) = &mut self.editor.view {
396− view.size = self.terminal.size;
397− view.ensure_cursor_in_view()
398− };
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+
454+ // TODO: simplistic ensure cursor in view for now
455+ // TODO: loop over views
456+ if let Some(view) = self.editor.view_mut() {
457+ view.size = self.terminal.size;
458+ view.ensure_cursor_in_view()
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);
399470
400471 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 {
412− let keys = vec![event];
413− // TODO: sequences (`gg`)
414− // TODO: handle count other than 1
415− match view.state.mode() {
416− Mode::Insert => {
417− if let Some(command) = keymap[&Mode::Insert].get(&keys) {
418− command(view, 1);
419− } else if let KeyEvent {
420− code: KeyCode::Char(c),
421− ..
422− } = event
423− {
424− commands::insert::insert_char(view, c);
425− }
472+ } else if let Some(view) = self.editor.view_mut() {
473+ let keys = vec![event];
474+ // TODO: sequences (`gg`)
475+ // TODO: handle count other than 1
476+ match view.doc.mode() {
477+ Mode::Insert => {
478+ if let Some(command) = self.keymap[&Mode::Insert].get(&keys) {
479+ let mut cx = helix_view::commands::Context {
480+ view,
481+ executor: self.executor,
482+ count: 1,
483+ };
484+
485+ command(&mut cx);
486+ } else if let KeyEvent {
487+ code: KeyCode::Char(c),
488+ ..
489+ } = event
490+ {
491+ let mut cx = helix_view::commands::Context {
492+ view,
493+ executor: self.executor,
494+ count: 1,
495+ };
496+ commands::insert::insert_char(&mut cx, c);
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
426566 view.ensure_cursor_in_view();
427567 }
428− Mode::Normal => {
429− if let &[KeyEvent {
430− code: KeyCode::Char(':'),
431− ..
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);
487−
488− // TODO: simplistic ensure cursor in view for now
489− view.ensure_cursor_in_view();
490− }
568+ }
569+ mode => {
570+ if let Some(command) = self.keymap[&mode].get(&keys) {
571+ let mut cx = helix_view::commands::Context {
572+ view,
573+ executor: self.executor,
574+ count: 1,
575+ };
576+ command(&mut cx);
577+
578+ // TODO: simplistic ensure cursor in view for now
579+ view.ensure_cursor_in_view();
491580 }
492− mode => {
493− if let Some(command) = keymap[&mode].get(&keys) {
494− command(view, 1);
581+ }
582+ }
583+ self.render();
584+ }
585+ }
586+ Some(Ok(Event::Mouse(_))) => (), // unhandled
587+ Some(Err(x)) => panic!(x),
588+ None => panic!(),
589+ };
590+ }
495591
496− // TODO: simplistic ensure cursor in view for now
497− view.ensure_cursor_in_view();
498− }
499− }
592+ pub async fn handle_language_server_message(&mut self, call: Option<helix_lsp::Call>) {
593+ use helix_lsp::{Call, Notification};
594+ match call {
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();
500633 }
501− self.render();
502634 }
635+ _ => unreachable!(),
503636 }
504− Some(Ok(Event::Mouse(_))) => (), // unhandled
505− Some(Err(x)) => panic!(x),
506− None => break,
507637 }
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!(),
508652 }
509653 }
510654
helix-term/src/main.rs+48−3
@@ -11,6 +11,39 @@ use anyhow::Error;
1111
1212static EX: smol::Executor = smol::Executor::new();
1313
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+
1447fn main() -> Result<(), Error> {
1548 let args = clap::app_from_crate!()
1649 .arg(
@@ -20,15 +53,27 @@ fn main() -> Result<(), Error> {
2053 .multiple(true)
2154 .index(1),
2255 )
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+ )
2363 .get_matches();
2464
65+ let verbosity: u64 = args.occurrences_of("verbose");
66+
67+ setup_logging(verbosity).expect("failed to initialize logging.");
68+
2569 for _ in 0..num_cpus::get() {
2670 std::thread::spawn(move || smol::block_on(EX.run(smol::future::pending::<()>())));
2771 }
2872
29− smol::block_on(EX.run(async {
30− Application::new(args).unwrap().run().await;
31− }));
73+ let mut app = Application::new(args, &EX).unwrap();
74+
75+ // we use the thread local executor to spawn the application task separately from the work pool
76+ smol::block_on(app.run());
3277
3378 Ok(())
3479}
helix-view/Cargo.toml+3
@@ -19,3 +19,6 @@ helix-core = { path = "../helix-core" }
1919tui = { git = "https://github.com/fdehau/tui-rs", default-features = false, features = ["crossterm"], optional = true}
2020crossterm = { version = "0.18", features = ["event-stream"], optional = true}
2121once_cell = "1.4"
22+url = "2"
23+
24+smol = "1"
helix-view/src/commands.rs+290−243
@@ -3,52 +3,65 @@ use helix_core::{
33 indent::TAB_WIDTH,
44 regex::Regex,
55 register, selection,
6− state::{Direction, Granularity, Mode, State},
6+ state::{Direction, Granularity, State},
77 ChangeSet, Range, Selection, Tendril, Transaction,
88};
99use once_cell::sync::Lazy;
1010
1111use crate::{
12+ document::Mode,
1213 prompt::Prompt,
1314 view::{View, PADDING},
1415};
1516
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+
1623/// A command is a function that takes the current state and a count, and does a side-effect on the
1724/// 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);
1926
20−pub fn move_char_left(view: &mut View, count: usize) {
21− // TODO: use a transaction
22− let selection = view
23− .state
24− .move_selection(Direction::Backward, Granularity::Character, count);
25− view.state.selection = selection;
27+pub fn move_char_left(cx: &mut Context) {
28+ let selection =
29+ cx.view
30+ .doc
31+ .state
32+ .move_selection(Direction::Backward, Granularity::Character, cx.count);
33+ cx.view.doc.set_selection(selection);
2634}
2735
28−pub fn move_char_right(view: &mut View, count: usize) {
29− // TODO: use a transaction
30− view.state.selection =
31− view.state
32− .move_selection(Direction::Forward, Granularity::Character, count);
36+pub fn move_char_right(cx: &mut Context) {
37+ let selection =
38+ cx.view
39+ .doc
40+ .state
41+ .move_selection(Direction::Forward, Granularity::Character, cx.count);
42+ cx.view.doc.set_selection(selection);
3343}
3444
35−pub fn move_line_up(view: &mut View, count: usize) {
36− // TODO: use a transaction
37− view.state.selection = view
38− .state
39− .move_selection(Direction::Backward, Granularity::Line, count);
45+pub fn move_line_up(cx: &mut Context) {
46+ let selection =
47+ cx.view
48+ .doc
49+ .state
50+ .move_selection(Direction::Backward, Granularity::Line, cx.count);
51+ cx.view.doc.set_selection(selection);
4052}
4153
42−pub fn move_line_down(view: &mut View, count: usize) {
43− // TODO: use a transaction
44− view.state.selection = view
45− .state
46− .move_selection(Direction::Forward, Granularity::Line, count);
54+pub fn move_line_down(cx: &mut Context) {
55+ let selection =
56+ cx.view
57+ .doc
58+ .state
59+ .move_selection(Direction::Forward, Granularity::Line, cx.count);
60+ cx.view.doc.set_selection(selection);
4761}
4862
49−pub fn move_line_end(view: &mut View, _count: usize) {
50− // TODO: use a transaction
51− let lines = selection_lines(&view.state);
63+pub fn move_line_end(cx: &mut Context) {
64+ let lines = selection_lines(&cx.view.doc.state);
5265
5366 let positions = lines
5467 .into_iter()
@@ -57,89 +70,80 @@ pub fn move_line_end(view: &mut View, _count: usize) {
5770
5871 // Line end is pos at the start of next line - 1
5972 // 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)
6174 })
6275 .map(|pos| Range::new(pos, pos));
6376
6477 let selection = Selection::new(positions.collect(), 0);
6578
66− let transaction = Transaction::new(&mut view.state).with_selection(selection);
67−
68− transaction.apply(&mut view.state);
79+ cx.view.doc.set_selection(selection);
6980}
7081
71−pub fn move_line_start(view: &mut View, _count: usize) {
72− let lines = selection_lines(&view.state);
82+pub fn move_line_start(cx: &mut Context) {
83+ let lines = selection_lines(&cx.view.doc.state);
7384
7485 let positions = lines
7586 .into_iter()
7687 .map(|index| {
7788 // 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)
7990 })
8091 .map(|pos| Range::new(pos, pos));
8192
8293 let selection = Selection::new(positions.collect(), 0);
8394
84− let transaction = Transaction::new(&mut view.state).with_selection(selection);
85−
86− transaction.apply(&mut view.state);
95+ cx.view.doc.set_selection(selection);
8796}
8897
89−pub fn move_next_word_start(view: &mut View, count: usize) {
90− let pos = view.state.move_pos(
91− view.state.selection.cursor(),
98+pub fn move_next_word_start(cx: &mut Context) {
99+ let pos = cx.view.doc.state.move_pos(
100+ cx.view.doc.selection().cursor(),
92101 Direction::Forward,
93102 Granularity::Word,
94− count,
103+ cx.count,
95104 );
96105
97− // TODO: use a transaction
98− view.state.selection = Selection::single(pos, pos);
106+ cx.view.doc.set_selection(Selection::point(pos));
99107}
100108
101−pub fn move_prev_word_start(view: &mut View, count: usize) {
102− let pos = view.state.move_pos(
103− view.state.selection.cursor(),
109+pub fn move_prev_word_start(cx: &mut Context) {
110+ let pos = cx.view.doc.state.move_pos(
111+ cx.view.doc.selection().cursor(),
104112 Direction::Backward,
105113 Granularity::Word,
106− count,
114+ cx.count,
107115 );
108116
109− // TODO: use a transaction
110− view.state.selection = Selection::single(pos, pos);
117+ cx.view.doc.set_selection(Selection::point(pos));
111118}
112119
113−pub fn move_next_word_end(view: &mut View, count: usize) {
120+pub fn move_next_word_end(cx: &mut Context) {
114121 let pos = State::move_next_word_end(
115− &view.state.doc().slice(..),
116− view.state.selection.cursor(),
117− count,
122+ &cx.view.doc.text().slice(..),
123+ cx.view.doc.selection().cursor(),
124+ cx.count,
118125 );
119126
120− // TODO: use a transaction
121− view.state.selection = Selection::single(pos, pos);
127+ cx.view.doc.set_selection(Selection::point(pos));
122128}
123129
124−pub fn move_file_start(view: &mut View, _count: usize) {
125− // TODO: use a transaction
126− view.state.selection = Selection::single(0, 0);
130+pub fn move_file_start(cx: &mut Context) {
131+ cx.view.doc.set_selection(Selection::point(0));
127132
128− view.state.mode = Mode::Normal;
133+ cx.view.doc.mode = Mode::Normal;
129134}
130135
131−pub fn move_file_end(view: &mut View, _count: usize) {
132− // TODO: use a transaction
133− let text = &view.state.doc;
136+pub fn move_file_end(cx: &mut Context) {
137+ let text = &cx.view.doc.text();
134138 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));
136140
137− view.state.mode = Mode::Normal;
141+ cx.view.doc.mode = Mode::Normal;
138142}
139143
140−pub fn check_cursor_in_view(view: &mut View) -> bool {
141− let cursor = view.state.selection().cursor();
142− let line = view.state.doc().char_to_line(cursor);
144+pub fn check_cursor_in_view(view: &View) -> bool {
145+ let cursor = view.doc.selection().cursor();
146+ let line = view.doc.text().char_to_line(cursor);
143147 let document_end = view.first_line + view.size.1.saturating_sub(1) as usize;
144148
145149 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 {
148152 true
149153}
150154
151−pub fn page_up(view: &mut View, _count: usize) {
152− if view.first_line < PADDING {
155+pub fn page_up(cx: &mut Context) {
156+ if cx.view.first_line < PADDING {
153157 return;
154158 }
155159
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);
157161
158− if !check_cursor_in_view(view) {
159− let text = view.state.doc();
160− let pos = text.line_to_char(view.last_line().saturating_sub(PADDING));
161− view.state.selection = Selection::single(pos, pos);
162+ if !check_cursor_in_view(cx.view) {
163+ let text = cx.view.doc.text();
164+ let pos = text.line_to_char(cx.view.last_line().saturating_sub(PADDING));
165+ cx.view.doc.set_selection(Selection::point(pos));
162166 }
163167}
164168
165−pub fn page_down(view: &mut View, _count: usize) {
166− view.first_line += view.size.1 as usize + PADDING;
169+pub fn page_down(cx: &mut Context) {
170+ cx.view.first_line += cx.view.size.1 as usize + PADDING;
167171
168− if view.first_line < view.state.doc().len_lines() {
169− let text = view.state.doc();
170− let pos = text.line_to_char(view.first_line as usize);
171− view.state.selection = Selection::single(pos, pos);
172+ if cx.view.first_line < cx.view.doc.text().len_lines() {
173+ let text = cx.view.doc.text();
174+ let pos = text.line_to_char(cx.view.first_line as usize);
175+ cx.view.doc.set_selection(Selection::point(pos));
172176 }
173177}
174178
175−pub fn half_page_up(view: &mut View, _count: usize) {
176− if view.first_line < PADDING {
179+pub fn half_page_up(cx: &mut Context) {
180+ if cx.view.first_line < PADDING {
177181 return;
178182 }
179183
180− view.first_line = view.first_line.saturating_sub(view.size.1 as usize / 2);
184+ cx.view.first_line = cx
185+ .view
186+ .first_line
187+ .saturating_sub(cx.view.size.1 as usize / 2);
181188
182− if !check_cursor_in_view(view) {
183− let text = &view.state.doc;
184− let pos = text.line_to_char(view.last_line() - PADDING);
185− view.state.selection = Selection::single(pos, pos);
189+ if !check_cursor_in_view(cx.view) {
190+ let text = &cx.view.doc.text();
191+ let pos = text.line_to_char(cx.view.last_line() - PADDING);
192+ cx.view.doc.set_selection(Selection::point(pos));
186193 }
187194}
188195
189−pub fn half_page_down(view: &mut View, _count: usize) {
190− let lines = view.state.doc().len_lines();
191− if view.first_line < lines.saturating_sub(view.size.1 as usize) {
192− view.first_line += view.size.1 as usize / 2;
196+pub fn half_page_down(cx: &mut Context) {
197+ let lines = cx.view.doc.text().len_lines();
198+ if cx.view.first_line < lines.saturating_sub(cx.view.size.1 as usize) {
199+ cx.view.first_line += cx.view.size.1 as usize / 2;
193200 }
194− if !check_cursor_in_view(view) {
195− let text = view.state.doc();
196− let pos = text.line_to_char(view.first_line as usize);
197− view.state.selection = Selection::single(pos, pos);
201+ if !check_cursor_in_view(cx.view) {
202+ let text = cx.view.doc.text();
203+ let pos = text.line_to_char(cx.view.first_line as usize);
204+ cx.view.doc.set_selection(Selection::point(pos));
198205 }
199206}
200207// avoid select by default by having a visual mode switch that makes movements into selects
201208
202−pub fn extend_char_left(view: &mut View, count: usize) {
203− // TODO: use a transaction
204− let selection = view
205− .state
206− .extend_selection(Direction::Backward, Granularity::Character, count);
207− view.state.selection = selection;
209+pub fn extend_char_left(cx: &mut Context) {
210+ let selection =
211+ cx.view
212+ .doc
213+ .state
214+ .extend_selection(Direction::Backward, Granularity::Character, cx.count);
215+ cx.view.doc.set_selection(selection);
208216}
209217
210−pub fn extend_char_right(view: &mut View, count: usize) {
211− // TODO: use a transaction
212− view.state.selection =
213− view.state
214− .extend_selection(Direction::Forward, Granularity::Character, count);
218+pub fn extend_char_right(cx: &mut Context) {
219+ let selection =
220+ cx.view
221+ .doc
222+ .state
223+ .extend_selection(Direction::Forward, Granularity::Character, cx.count);
224+ cx.view.doc.set_selection(selection);
215225}
216226
217−pub fn extend_line_up(view: &mut View, count: usize) {
218− // TODO: use a transaction
219− view.state.selection =
220− view.state
221− .extend_selection(Direction::Backward, Granularity::Line, count);
227+pub fn extend_line_up(cx: &mut Context) {
228+ let selection =
229+ cx.view
230+ .doc
231+ .state
232+ .extend_selection(Direction::Backward, Granularity::Line, cx.count);
233+ cx.view.doc.set_selection(selection);
222234}
223235
224−pub fn extend_line_down(view: &mut View, count: usize) {
225− // TODO: use a transaction
226− view.state.selection =
227− view.state
228− .extend_selection(Direction::Forward, Granularity::Line, count);
236+pub fn extend_line_down(cx: &mut Context) {
237+ let selection =
238+ cx.view
239+ .doc
240+ .state
241+ .extend_selection(Direction::Forward, Granularity::Line, cx.count);
242+ cx.view.doc.set_selection(selection);
229243}
230244
231−pub fn split_selection_on_newline(view: &mut View, _count: usize) {
232− let text = &view.state.doc.slice(..);
245+pub fn split_selection_on_newline(cx: &mut Context) {
246+ let text = &cx.view.doc.text().slice(..);
233247 // only compile the regex once
234248 #[allow(clippy::trivial_regex)]
235249 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);
238252}
239253
240−pub fn select_line(view: &mut View, _count: usize) {
254+pub fn select_line(cx: &mut Context) {
241255 // TODO: count
242− let pos = view.state.selection().primary();
243− let text = view.state.doc();
256+ let pos = cx.view.doc.selection().primary();
257+ let text = cx.view.doc.text();
244258 let line = text.char_to_line(pos.head);
245259 let start = text.line_to_char(line);
246260 let end = text.line_to_char(line + 1).saturating_sub(1);
247261
248− // TODO: use a transaction
249− view.state.selection = Selection::single(start, end);
262+ cx.view.doc.set_selection(Selection::single(start, end));
250263}
251264
252−pub fn delete_selection(view: &mut View, _count: usize) {
253− let transaction =
254− Transaction::change_by_selection(&view.state, |range| (range.from(), range.to() + 1, None));
255− transaction.apply(&mut view.state);
265+pub fn delete_selection(cx: &mut Context) {
266+ let transaction = Transaction::change_by_selection(&cx.view.doc.state, |range| {
267+ (range.from(), range.to() + 1, None)
268+ });
269+ cx.view.doc.apply(&transaction);
256270
257− append_changes_to_history(view);
271+ append_changes_to_history(cx);
258272}
259273
260−pub fn change_selection(view: &mut View, count: usize) {
261− delete_selection(view, count);
262− insert_mode(view, count);
274+pub fn change_selection(cx: &mut Context) {
275+ delete_selection(cx);
276+ insert_mode(cx);
263277}
264278
265−pub fn collapse_selection(view: &mut View, _count: usize) {
266− view.state.selection = view
267− .state
268− .selection
269− .transform(|range| Range::new(range.head, range.head))
279+pub fn collapse_selection(cx: &mut Context) {
280+ let selection = cx
281+ .view
282+ .doc
283+ .selection()
284+ .transform(|range| Range::new(range.head, range.head));
285+
286+ cx.view.doc.set_selection(selection);
270287}
271288
272−pub fn flip_selections(view: &mut View, _count: usize) {
273− view.state.selection = view
274− .state
275− .selection
276− .transform(|range| Range::new(range.head, range.anchor))
289+pub fn flip_selections(cx: &mut Context) {
290+ let selection = cx
291+ .view
292+ .doc
293+ .selection()
294+ .transform(|range| Range::new(range.head, range.anchor));
295+
296+ cx.view.doc.set_selection(selection);
277297}
278298
279−fn enter_insert_mode(view: &mut View) {
280− view.state.mode = Mode::Insert;
299+fn enter_insert_mode(cx: &mut Context) {
300+ cx.view.doc.mode = Mode::Insert;
281301
282− append_changes_to_history(view);
302+ append_changes_to_history(cx);
283303}
284304// inserts at the start of each selection
285−pub fn insert_mode(view: &mut View, _count: usize) {
286− enter_insert_mode(view);
305+pub fn insert_mode(cx: &mut Context) {
306+ enter_insert_mode(cx);
287307
288− view.state.selection = view
289− .state
290− .selection
291− .transform(|range| Range::new(range.to(), range.from()))
308+ let selection = cx
309+ .view
310+ .doc
311+ .selection()
312+ .transform(|range| Range::new(range.to(), range.from()));
313+ cx.view.doc.set_selection(selection);
292314}
293315
294316// inserts at the end of each selection
295−pub fn append_mode(view: &mut View, _count: usize) {
296− enter_insert_mode(view);
297− view.state.restore_cursor = true;
317+pub fn append_mode(cx: &mut Context) {
318+ enter_insert_mode(cx);
319+ cx.view.doc.restore_cursor = true;
298320
299321 // TODO: as transaction
300− let text = &view.state.doc.slice(..);
301− view.state.selection = view.state.selection.transform(|range| {
322+ let text = &cx.view.doc.text().slice(..);
323+ let selection = cx.view.doc.selection().transform(|range| {
302324 // TODO: to() + next char
303325 Range::new(
304326 range.from(),
305327 graphemes::next_grapheme_boundary(text, range.to()),
306328 )
307− })
329+ });
330+ cx.view.doc.set_selection(selection);
308331}
309332
310333// 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) {
313335 unimplemented!()
314336}
315337
@@ -329,30 +351,30 @@ fn selection_lines(state: &State) -> Vec<usize> {
329351}
330352
331353// I inserts at the start of each line with a selection
332−pub fn prepend_to_line(view: &mut View, count: usize) {
333− enter_insert_mode(view);
354+pub fn prepend_to_line(cx: &mut Context) {
355+ enter_insert_mode(cx);
334356
335− move_line_start(view, count);
357+ move_line_start(cx);
336358}
337359
338360// A inserts at the end of each line with a selection
339−pub fn append_to_line(view: &mut View, count: usize) {
340− enter_insert_mode(view);
361+pub fn append_to_line(cx: &mut Context) {
362+ enter_insert_mode(cx);
341363
342− move_line_end(view, count);
364+ move_line_end(cx);
343365}
344366
345367// o inserts a new line after each line with a selection
346−pub fn open_below(view: &mut View, _count: usize) {
347− enter_insert_mode(view);
368+pub fn open_below(cx: &mut Context) {
369+ enter_insert_mode(cx);
348370
349− let lines = selection_lines(&view.state);
371+ let lines = selection_lines(&cx.view.doc.state);
350372
351373 let positions: Vec<_> = lines
352374 .into_iter()
353375 .map(|index| {
354376 // 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)
356378 })
357379 .collect();
358380
@@ -373,111 +395,119 @@ pub fn open_below(view: &mut View, _count: usize) {
373395 0,
374396 );
375397
376− let transaction = Transaction::change(&view.state, changes).with_selection(selection);
398+ let transaction = Transaction::change(&cx.view.doc.state, changes).with_selection(selection);
377399
378− transaction.apply(&mut view.state);
400+ cx.view.doc.apply(&transaction);
379401}
380402
381403// O inserts a new line before each line with a selection
382404
383−fn append_changes_to_history(view: &mut View) {
384− if view.state.changes.is_empty() {
405+fn append_changes_to_history(cx: &mut Context) {
406+ if cx.view.doc.changes.is_empty() {
385407 return;
386408 }
387409
388− let new_changeset = ChangeSet::new(view.state.doc());
389− let changes = std::mem::replace(&mut view.state.changes, new_changeset);
410+ let new_changeset = ChangeSet::new(cx.view.doc.text());
411+ let changes = std::mem::replace(&mut cx.view.doc.changes, new_changeset);
390412 // Instead of doing this messy merge we could always commit, and based on transaction
391413 // 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());
393415
394− // HAXX: we need to reconstruct the state as it was before the changes..
395− let (doc, selection) = view.state.old_state.take().unwrap();
396− let mut old_state = State::new(doc);
397− old_state.selection = selection;
416+ // increment document version
417+ // TODO: needs to happen on undo/redo too
418+ cx.view.doc.version += 1;
398419
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());
399424 // TODO: take transaction by value?
400− view.history.commit_revision(&transaction, &old_state);
425+ cx.view
426+ .doc
427+ .history
428+ .commit_revision(&transaction, &old_state);
401429
402− // TODO: need to start the state with these vals
403− // HAXX
404− view.state.old_state = Some((view.state.doc().clone(), view.state.selection.clone()));
430+ // TODO: notify LSP of changes
405431}
406432
407−pub fn normal_mode(view: &mut View, _count: usize) {
408− view.state.mode = Mode::Normal;
433+pub fn normal_mode(cx: &mut Context) {
434+ cx.view.doc.mode = Mode::Normal;
409435
410− append_changes_to_history(view);
436+ append_changes_to_history(cx);
411437
412438 // if leaving append mode, move cursor back by 1
413− if view.state.restore_cursor {
414− let text = &view.state.doc.slice(..);
415− view.state.selection = view.state.selection.transform(|range| {
439+ if cx.view.doc.restore_cursor {
440+ let text = &cx.view.doc.text().slice(..);
441+ let selection = cx.view.doc.selection().transform(|range| {
416442 Range::new(
417443 range.from(),
418444 graphemes::prev_grapheme_boundary(text, range.to()),
419445 )
420446 });
447+ cx.view.doc.set_selection(selection);
421448
422− view.state.restore_cursor = false;
449+ cx.view.doc.restore_cursor = false;
423450 }
424451}
425452
426−pub fn goto_mode(view: &mut View, _count: usize) {
427− view.state.mode = Mode::Goto;
453+pub fn goto_mode(cx: &mut Context) {
454+ cx.view.doc.mode = Mode::Goto;
428455}
429456
430457// NOTE: Transactions in this module get appended to history when we switch back to normal mode.
431458pub mod insert {
432459 use super::*;
433460 // 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) {
435462 let c = Tendril::from_char(c);
436− let transaction = Transaction::insert(&view.state, c);
463+ let transaction = Transaction::insert(&cx.view.doc.state, c);
437464
438− transaction.apply(&mut view.state);
465+ cx.view.doc.apply(&transaction);
439466 }
440467
441− pub fn insert_tab(view: &mut View, _count: usize) {
442− insert_char(view, '\t');
468+ pub fn insert_tab(cx: &mut Context) {
469+ insert_char(cx, '\t');
443470 }
444471
445− pub fn insert_newline(view: &mut View, _count: usize) {
446− let transaction = Transaction::change_by_selection(&view.state, |range| {
447− let indent_level =
448− helix_core::indent::suggested_indent_for_pos(&view.state, range.head);
472+ pub fn insert_newline(cx: &mut Context) {
473+ let transaction = Transaction::change_by_selection(&cx.view.doc.state, |range| {
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+ );
449479 let indent = " ".repeat(TAB_WIDTH).repeat(indent_level);
450480 let mut text = String::with_capacity(1 + indent.len());
451481 text.push('\n');
452482 text.push_str(&indent);
453483 (range.head, range.head, Some(text.into()))
454484 });
455− transaction.apply(&mut view.state);
485+ cx.view.doc.apply(&transaction);
456486 }
457487
458488 // TODO: handle indent-aware delete
459− pub fn delete_char_backward(view: &mut View, count: usize) {
460− let text = &view.state.doc.slice(..);
461− let transaction = Transaction::change_by_selection(&view.state, |range| {
489+ pub fn delete_char_backward(cx: &mut Context) {
490+ let text = &cx.view.doc.text().slice(..);
491+ let transaction = Transaction::change_by_selection(&cx.view.doc.state, |range| {
462492 (
463− graphemes::nth_prev_grapheme_boundary(text, range.head, count),
493+ graphemes::nth_prev_grapheme_boundary(text, range.head, cx.count),
464494 range.head,
465495 None,
466496 )
467497 });
468− transaction.apply(&mut view.state);
498+ cx.view.doc.apply(&transaction);
469499 }
470500
471− pub fn delete_char_forward(view: &mut View, count: usize) {
472− let text = &view.state.doc.slice(..);
473− let transaction = Transaction::change_by_selection(&view.state, |range| {
501+ pub fn delete_char_forward(cx: &mut Context) {
502+ let text = &cx.view.doc.text().slice(..);
503+ let transaction = Transaction::change_by_selection(&cx.view.doc.state, |range| {
474504 (
475505 range.head,
476− graphemes::nth_next_grapheme_boundary(text, range.head, count),
506+ graphemes::nth_next_grapheme_boundary(text, range.head, cx.count),
477507 None,
478508 )
479509 });
480− transaction.apply(&mut view.state);
510+ cx.view.doc.apply(&transaction);
481511 }
482512}
483513
@@ -487,24 +517,32 @@ pub fn insert_char_prompt(prompt: &mut Prompt, c: char) {
487517
488518// Undo / Redo
489519
490−pub fn undo(view: &mut View, _count: usize) {
491− view.history.undo(&mut view.state);
520+pub fn undo(cx: &mut Context) {
521+ if let Some(revert) = cx.view.doc.history.undo() {
522+ cx.view.doc.version += 1;
523+ cx.view.doc.apply(&revert);
524+ }
492525
493526 // TODO: each command could simply return a Option<transaction>, then the higher level handles storing it?
494527}
495528
496−pub fn redo(view: &mut View, _count: usize) {
497− view.history.redo(&mut view.state);
529+pub fn redo(cx: &mut Context) {
530+ if let Some(transaction) = cx.view.doc.history.redo() {
531+ cx.view.doc.version += 1;
532+ cx.view.doc.apply(&transaction);
533+ }
498534}
499535
500536// Yank / Paste
501537
502−pub fn yank(view: &mut View, _count: usize) {
538+pub fn yank(cx: &mut Context) {
503539 // TODO: should selections be made end inclusive?
504− let values = view
540+ let values = cx
541+ .view
542+ .doc
505543 .state
506544 .selection()
507− .fragments(&view.state.doc().slice(..))
545+ .fragments(&cx.view.doc.text().slice(..))
508546 .map(|cow| cow.into_owned())
509547 .collect();
510548
@@ -513,7 +551,7 @@ pub fn yank(view: &mut View, _count: usize) {
513551 register::set(reg, values);
514552}
515553
516−pub fn paste(view: &mut View, _count: usize) {
554+pub fn paste(cx: &mut Context) {
517555 // TODO: allow specifying reg
518556 let reg = '"';
519557 if let Some(values) = register::get(reg) {
@@ -545,19 +583,19 @@ pub fn paste(view: &mut View, _count: usize) {
545583 let transaction = if linewise {
546584 // paste on the next line
547585 // TODO: can simply take a range + modifier and compute the right pos without ifs
548− let text = view.state.doc();
549− Transaction::change_by_selection(&view.state, |range| {
586+ let text = cx.view.doc.text();
587+ Transaction::change_by_selection(&cx.view.doc.state, |range| {
550588 let line_end = text.line_to_char(text.char_to_line(range.head) + 1);
551589 (line_end, line_end, Some(values.next().unwrap()))
552590 })
553591 } else {
554− Transaction::change_by_selection(&view.state, |range| {
592+ Transaction::change_by_selection(&cx.view.doc.state, |range| {
555593 (range.head + 1, range.head + 1, Some(values.next().unwrap()))
556594 })
557595 };
558596
559− transaction.apply(&mut view.state);
560− append_changes_to_history(view);
597+ cx.view.doc.apply(&transaction);
598+ append_changes_to_history(cx);
561599 }
562600}
563601
@@ -565,9 +603,9 @@ fn get_lines(view: &View) -> Vec<usize> {
565603 let mut lines = Vec::new();
566604
567605 // Get all line numbers
568− for range in view.state.selection.ranges() {
569− let start = view.state.doc.char_to_line(range.from());
570− let end = view.state.doc.char_to_line(range.to());
606+ for range in view.doc.selection().ranges() {
607+ let start = view.doc.text().char_to_line(range.from());
608+ let end = view.doc.text().char_to_line(range.to());
571609
572610 for line in start..=end {
573611 lines.push(line)
@@ -578,29 +616,29 @@ fn get_lines(view: &View) -> Vec<usize> {
578616 lines
579617}
580618
581−pub fn indent(view: &mut View, _count: usize) {
582− let lines = get_lines(view);
619+pub fn indent(cx: &mut Context) {
620+ let lines = get_lines(cx.view);
583621
584622 // Indent by one level
585623 let indent = Tendril::from(" ".repeat(TAB_WIDTH));
586624
587625 let transaction = Transaction::change(
588− &view.state,
626+ &cx.view.doc.state,
589627 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);
591629 (pos, pos, Some(indent.clone()))
592630 }),
593631 );
594− transaction.apply(&mut view.state);
595− append_changes_to_history(view);
632+ cx.view.doc.apply(&transaction);
633+ append_changes_to_history(cx);
596634}
597635
598−pub fn unindent(view: &mut View, _count: usize) {
599− let lines = get_lines(view);
636+pub fn unindent(cx: &mut Context) {
637+ let lines = get_lines(cx.view);
600638 let mut changes = Vec::with_capacity(lines.len());
601639
602640 for line_idx in lines {
603− let line = view.state.doc.line(line_idx);
641+ let line = cx.view.doc.text().line(line_idx);
604642 let mut width = 0;
605643
606644 for ch in line.chars() {
@@ -616,18 +654,27 @@ pub fn unindent(view: &mut View, _count: usize) {
616654 }
617655
618656 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);
620658 changes.push((start, start + width, None))
621659 }
622660 }
623661
624− let transaction = Transaction::change(&view.state, changes.into_iter());
662+ let transaction = Transaction::change(&cx.view.doc.state, changes.into_iter());
625663
626− transaction.apply(&mut view.state);
627− append_changes_to_history(view);
664+ cx.view.doc.apply(&transaction);
665+ append_changes_to_history(cx);
628666}
629667
630−pub fn indent_selection(_view: &mut View, _count: usize) {
668+pub fn indent_selection(_cx: &mut Context) {
631669 // loop over each line and recompute proper indentation
632670 unimplemented!()
633671}
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};
23
34use std::path::PathBuf;
45
56use anyhow::Error;
67
78pub struct Editor {
8− pub view: Option<View>,
9+ pub views: Vec<View>,
10+ pub focus: usize,
911 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+ }
1019}
1120
1221impl Editor {
1322 pub fn new() -> Self {
23+ let theme = Theme::default();
24+
1425 Self {
15− view: None,
26+ views: Vec::new(),
27+ focus: 0,
1628 should_close: false,
29+ theme,
1730 }
1831 }
1932
2033 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;
2238 Ok(())
2339 }
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+ }
2448}
helix-view/src/keymap.rs+10−6
@@ -1,5 +1,6 @@
11use crate::commands::{self, Command};
2−use helix_core::{hashmap, state};
2+use crate::document::Mode;
3+use helix_core::hashmap;
34use std::collections::HashMap;
45
56// Kakoune-inspired:
@@ -81,14 +82,17 @@ use std::collections::HashMap;
8182// = = align?
8283// + =
8384// }
85+//
86+// gd = goto definition
87+// gr = goto reference
8488// }
8589
8690#[cfg(feature = "term")]
8791pub use crossterm::event::{KeyCode, KeyEvent as Key, KeyModifiers as Modifiers};
8892
8993// TODO: could be trie based
90−type Keymap = HashMap<Vec<Key>, Command>;
91−type Keymaps = HashMap<state::Mode, Keymap>;
94+pub type Keymap = HashMap<Vec<Key>, Command>;
95+pub type Keymaps = HashMap<Mode, Keymap>;
9296
9397macro_rules! key {
9498 ($ch:expr) => {
@@ -128,7 +132,7 @@ macro_rules! ctrl {
128132
129133pub fn default() -> Keymaps {
130134 hashmap!(
131− state::Mode::Normal =>
135+ Mode::Normal =>
132136 // as long as you cast the first item, rust is able to infer the other cases
133137 hashmap!(
134138 vec![key!('h')] => commands::move_char_left as Command,
@@ -179,7 +183,7 @@ pub fn default() -> Keymaps {
179183 vec![ctrl!('u')] => commands::half_page_up,
180184 vec![ctrl!('d')] => commands::half_page_down,
181185 ),
182− state::Mode::Insert => hashmap!(
186+ Mode::Insert => hashmap!(
183187 vec![Key {
184188 code: KeyCode::Esc,
185189 modifiers: Modifiers::NONE
@@ -201,7 +205,7 @@ pub fn default() -> Keymaps {
201205 modifiers: Modifiers::NONE
202206 }] => commands::insert::insert_tab,
203207 ),
204− state::Mode::Goto => hashmap!(
208+ Mode::Goto => hashmap!(
205209 vec![Key {
206210 code: KeyCode::Esc,
207211 modifiers: Modifiers::NONE
helix-view/src/lib.rs+3
@@ -1,9 +1,12 @@
11pub mod commands;
2+pub mod document;
23pub mod editor;
34pub mod keymap;
45pub mod prompt;
56pub mod theme;
67pub mod view;
78
9+pub use document::Document;
810pub use editor::Editor;
11+pub use theme::Theme;
912pub use view::View;
helix-view/src/theme.rs+2
@@ -157,6 +157,8 @@ impl Default for Theme {
157157 "ui.background" => Style::default().bg(Color::Rgb(59, 34, 76)), // midnight
158158 "ui.linenr" => Style::default().fg(Color::Rgb(90, 89, 119)), // comet
159159 "ui.statusline" => Style::default().bg(Color::Rgb(40, 23, 51)), // revolver
160+
161+ "warning" => Style::default().fg(Color::Rgb(255, 205, 28)),
160162 };
161163
162164 let scopes = mapping.keys().map(ToString::to_string).collect();
helix-view/src/view.rs+32−16
@@ -1,44 +1,39 @@
11use anyhow::Error;
22
3−use std::{borrow::Cow, path::PathBuf};
3+use std::borrow::Cow;
44
5−use crate::theme::Theme;
5+use crate::Document;
66use helix_core::{
77 graphemes::{grapheme_width, RopeGraphemes},
88 indent::TAB_WIDTH,
9− History, Position, RopeSlice, State,
9+ Position, RopeSlice,
1010};
1111use tui::layout::Rect;
1212
1313pub const PADDING: usize = 5;
1414
15+// TODO: view should be View { doc: Document(state, history,..) }
16+// since we can have multiple views into the same file
1517pub struct View {
16− pub state: State,
17− pub history: History,
18+ pub doc: Document,
1819 pub first_line: usize,
1920 pub size: (u16, u16),
20− pub theme: Theme, // TODO: share one instance
2121}
2222
2323impl View {
24− pub fn open(path: PathBuf, size: (u16, u16)) -> Result<Self, Error> {
25− let theme = Theme::default();
26− let state = State::load(path, theme.scopes())?;
27−
24+ pub fn new(doc: Document, size: (u16, u16)) -> Result<Self, Error> {
2825 let view = Self {
29− state,
26+ doc,
3027 first_line: 0,
3128 size,
32− theme,
33− history: History::default(),
3429 };
3530
3631 Ok(view)
3732 }
3833
3934 pub fn ensure_cursor_in_view(&mut self) {
40− let cursor = self.state.selection().cursor();
41− let line = self.state.doc().char_to_line(cursor);
35+ let cursor = self.doc.state.selection().cursor();
36+ let line = self.doc.text().char_to_line(cursor);
4237 let document_end = self.first_line + (self.size.1 as usize).saturating_sub(2);
4338
4439 // TODO: side scroll
@@ -58,7 +53,7 @@ impl View {
5853 let viewport = Rect::new(6, 0, self.size.0, self.size.1 - 2); // - 2 for statusline and prompt
5954 std::cmp::min(
6055 self.first_line + (viewport.height as usize),
61− self.state.doc().len_lines() - 1,
56+ self.doc.text().len_lines() - 1,
6257 )
6358 }
6459
@@ -90,4 +85,25 @@ impl View {
9085
9186 Some(Position::new(row, col))
9287 }
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+ // }
93109}