1use std::collections::HashMap;
28
29use lsp_server::{Connection, ErrorCode, ExtractError, Message, Request, Response};
30use lsp_types::notification::{
31 DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument, Notification as _,
32 PublishDiagnostics,
33};
34use lsp_types::request::{Completion, HoverRequest, Request as _};
35use lsp_types::{
36 CompletionItem, CompletionItemKind, CompletionOptions, CompletionParams, CompletionResponse,
37 Diagnostic, DiagnosticSeverity, DidChangeTextDocumentParams, DidCloseTextDocumentParams,
38 DidOpenTextDocumentParams, Hover, HoverContents, HoverParams, HoverProviderCapability,
39 MarkupContent, MarkupKind, Position, PublishDiagnosticsParams, Range, ServerCapabilities,
40 TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, Uri,
41};
42
43const CORPUS: &[(&str, &str, &str, &str)] = &[
45 (
47 "var",
48 "Keyword",
49 "declare a function-scoped (hoisted) variable",
50 "var x = 1; x // => 1",
51 ),
52 (
53 "let",
54 "Keyword",
55 "declare a block-scoped variable",
56 "let x = 2; x // => 2",
57 ),
58 (
59 "const",
60 "Keyword",
61 "declare a block-scoped binding that cannot be reassigned",
62 "const x = 3; x // => 3",
63 ),
64 (
65 "function",
66 "Keyword",
67 "define a function (declaration or expression)",
68 "function f() { return 9; } f() // => 9",
69 ),
70 (
71 "return",
72 "Keyword",
73 "return a value from the current function (undefined if omitted)",
74 "(function () { return 7; })() // => 7",
75 ),
76 (
77 "if",
78 "Keyword",
79 "conditional branch on a truthy test",
80 "let x; if (true) x = 1; x // => 1",
81 ),
82 (
83 "else",
84 "Keyword",
85 "fallback branch of an if",
86 "let x; if (false) x = 1; else x = 2; x // => 2",
87 ),
88 (
89 "while",
90 "Keyword",
91 "loop while the condition is truthy",
92 "let i = 0; while (i < 3) i++; i // => 3",
93 ),
94 (
95 "do",
96 "Keyword",
97 "do/while loop: run the body once, then repeat while truthy",
98 "let i = 0; do { i++; } while (i < 3); i // => 3",
99 ),
100 (
101 "for",
102 "Keyword",
103 "C-style loop, or for/of and for/in iteration",
104 "let s = 0; for (let i = 0; i < 3; i++) s += i; s // => 3",
105 ),
106 (
107 "of",
108 "Keyword",
109 "iterate values of an iterable: `for (x of iterable)`",
110 "let s = 0; for (const n of [1, 2, 3]) s += n; s // => 6",
111 ),
112 (
113 "in",
114 "Keyword",
115 "for/in key iteration, and the property-membership operator",
116 "\"a\" in { a: 1 } // => true",
117 ),
118 (
119 "switch",
120 "Keyword",
121 "multi-way branch on a discriminant with case labels",
122 "let x; switch (2) { case 2: x = \"b\"; break; } x // => 'b'",
123 ),
124 (
125 "case",
126 "Keyword",
127 "a labeled branch inside a switch",
128 "let x; switch (1) { case 1: x = \"a\"; } x // => 'a'",
129 ),
130 (
131 "default",
132 "Keyword",
133 "the fallback branch of a switch",
134 "let x; switch (9) { default: x = \"d\"; } x // => 'd'",
135 ),
136 (
137 "break",
138 "Keyword",
139 "exit the nearest enclosing loop or switch",
140 "let x; for (const n of [1, 2, 3]) { if (n === 2) break; x = n; } x // => 1",
141 ),
142 (
143 "continue",
144 "Keyword",
145 "skip to the next iteration of the nearest loop",
146 "let s = 0; for (const n of [1, 2, 3]) { if (n === 2) continue; s += n; } s // => 4",
147 ),
148 (
149 "new",
150 "Keyword",
151 "construct an instance: `new Ctor(args)`",
152 "new Error(\"boom\").message // => 'boom'",
153 ),
154 (
155 "this",
156 "Keyword",
157 "the receiver of the current call",
158 "({ n: 5, get() { return this.n; } }).get() // => 5",
159 ),
160 (
161 "typeof",
162 "Keyword",
163 "the type tag of a value as a string",
164 "typeof 3 // => 'number'",
165 ),
166 (
167 "void",
168 "Keyword",
169 "evaluate an expression and yield undefined",
170 "void 0 // => undefined",
171 ),
172 (
173 "delete",
174 "Keyword",
175 "remove a property from an object",
176 "const o = { a: 1 }; delete o.a; o.a // => undefined",
177 ),
178 (
179 "instanceof",
180 "Keyword",
181 "test whether an object was built by a constructor",
182 "new Error(\"e\") instanceof Error // => true",
183 ),
184 (
185 "throw",
186 "Keyword",
187 "raise an exception value",
188 "try { throw \"x\"; } catch (e) { e } // => 'x'",
189 ),
190 (
191 "try",
192 "Keyword",
193 "run a block, routing exceptions to catch/finally",
194 "let x; try { x = 1; } finally { x = 2; } x // => 2",
195 ),
196 (
197 "catch",
198 "Keyword",
199 "handle an exception thrown in the try block",
200 "try { null.x; } catch (e) { \"caught\" } // => 'caught'",
201 ),
202 (
203 "finally",
204 "Keyword",
205 "block that always runs after try/catch",
206 "let x; try { x = 1; } finally { x = 3; } x // => 3",
207 ),
208 (
209 "true",
210 "Keyword",
211 "the boolean true literal",
212 "true && 1 // => 1",
213 ),
214 (
215 "false",
216 "Keyword",
217 "the boolean false literal",
218 "false || 2 // => 2",
219 ),
220 (
221 "null",
222 "Keyword",
223 "the intentional-absence value",
224 "null ?? 5 // => 5",
225 ),
226 (
228 "undefined",
229 "Global",
230 "the value of an unassigned binding or missing property",
231 "typeof undefined // => 'undefined'",
232 ),
233 (
234 "NaN",
235 "Global",
236 "the not-a-number float; unequal to itself",
237 "NaN === NaN // => false",
238 ),
239 (
240 "Infinity",
241 "Global",
242 "the positive-infinity float",
243 "1 / 0 === Infinity // => true",
244 ),
245 (
246 "globalThis",
247 "Global",
248 "the global object",
249 "typeof globalThis // => 'object'",
250 ),
251 (
252 "parseInt",
253 "Global",
254 "parse the leading integer of a string (optional radix)",
255 "parseInt(\"42px\") // => 42",
256 ),
257 (
258 "parseFloat",
259 "Global",
260 "parse the leading floating-point number of a string",
261 "parseFloat(\"3.14abc\") // => 3.14",
262 ),
263 (
264 "isNaN",
265 "Global",
266 "true if the coerced number is NaN",
267 "isNaN(\"x\") // => true",
268 ),
269 (
270 "isFinite",
271 "Global",
272 "true if the coerced number is finite",
273 "isFinite(10) // => true",
274 ),
275 (
276 "String",
277 "Global",
278 "convert a value to its string form",
279 "String(123) // => '123'",
280 ),
281 (
282 "Number",
283 "Global",
284 "convert a value to a number",
285 "Number(\"3.5\") // => 3.5",
286 ),
287 (
288 "Boolean",
289 "Global",
290 "convert a value to its truthiness",
291 "Boolean(\"\") // => false",
292 ),
293 (
294 "Array",
295 "Global",
296 "build an array from the given elements",
297 "Array(1, 2, 3) // => [ 1, 2, 3 ]",
298 ),
299 (
300 "Error",
301 "Global",
302 "construct a generic error with a message",
303 "new Error(\"boom\").message // => 'boom'",
304 ),
305 (
306 "TypeError",
307 "Global",
308 "construct a type error (name is 'TypeError')",
309 "new TypeError(\"bad\").name // => 'TypeError'",
310 ),
311 (
312 "RangeError",
313 "Global",
314 "construct a range error (name is 'RangeError')",
315 "new RangeError(\"oob\").name // => 'RangeError'",
316 ),
317 (
319 "console.log",
320 "console",
321 "write args to stdout, space-separated, ending in a newline",
322 "console.log(\"hi\", 1) // prints: hi 1",
323 ),
324 (
325 "console.error",
326 "console",
327 "write args to stderr",
328 "console.error(\"oops\") // prints oops to stderr",
329 ),
330 (
331 "console.warn",
332 "console",
333 "write args to stderr (warning channel)",
334 "console.warn(\"careful\") // prints careful to stderr",
335 ),
336 (
337 "console.info",
338 "console",
339 "write args to stdout (info channel)",
340 "console.info(\"note\") // prints note",
341 ),
342 (
343 "console.debug",
344 "console",
345 "write args to stdout (debug channel)",
346 "console.debug(\"dbg\") // prints dbg",
347 ),
348 (
350 "Math.PI",
351 "Math",
352 "the ratio of a circle's circumference to its diameter",
353 "Math.PI // => 3.141592653589793",
354 ),
355 (
356 "Math.abs",
357 "Math",
358 "absolute value of a number",
359 "Math.abs(-5) // => 5",
360 ),
361 (
362 "Math.floor",
363 "Math",
364 "largest integer <= x",
365 "Math.floor(3.7) // => 3",
366 ),
367 (
368 "Math.ceil",
369 "Math",
370 "smallest integer >= x",
371 "Math.ceil(3.2) // => 4",
372 ),
373 (
374 "Math.round",
375 "Math",
376 "round to the nearest integer (ties toward +Infinity)",
377 "Math.round(2.5) // => 3",
378 ),
379 (
380 "Math.trunc",
381 "Math",
382 "the integer part of x, dropping any fraction",
383 "Math.trunc(-4.7) // => -4",
384 ),
385 (
386 "Math.sign",
387 "Math",
388 "the sign of x as -1, 0, or 1",
389 "Math.sign(-8) // => -1",
390 ),
391 (
392 "Math.sqrt",
393 "Math",
394 "square root of x",
395 "Math.sqrt(16) // => 4",
396 ),
397 (
398 "Math.cbrt",
399 "Math",
400 "cube root of x",
401 "Math.cbrt(27) // => 3",
402 ),
403 (
404 "Math.pow",
405 "Math",
406 "x raised to the power y",
407 "Math.pow(2, 10) // => 1024",
408 ),
409 (
410 "Math.exp",
411 "Math",
412 "e raised to the power x",
413 "Math.exp(0) // => 1",
414 ),
415 (
416 "Math.log",
417 "Math",
418 "natural logarithm of x",
419 "Math.log(1) // => 0",
420 ),
421 (
422 "Math.max",
423 "Math",
424 "largest of the arguments",
425 "Math.max(3, 1, 2) // => 3",
426 ),
427 (
428 "Math.min",
429 "Math",
430 "smallest of the arguments",
431 "Math.min(3, 1, 2) // => 1",
432 ),
433 (
434 "Math.hypot",
435 "Math",
436 "the square root of the sum of squares of the arguments",
437 "Math.hypot(3, 4) // => 5",
438 ),
439 (
440 "Math.sin",
441 "Math",
442 "sine of x (radians)",
443 "Math.sin(0) // => 0",
444 ),
445 (
446 "Math.cos",
447 "Math",
448 "cosine of x (radians)",
449 "Math.cos(0) // => 1",
450 ),
451 (
452 "Math.tan",
453 "Math",
454 "tangent of x (radians)",
455 "Math.tan(0) // => 0",
456 ),
457 (
458 "Math.random",
459 "Math",
460 "a pseudo-random float in [0, 1)",
461 "Math.random() < 1 // => true",
462 ),
463 (
465 "JSON.stringify",
466 "JSON",
467 "serialize a value to a JSON string",
468 "JSON.stringify({ a: 1 }) // => '{\"a\":1}'",
469 ),
470 (
471 "JSON.parse",
472 "JSON",
473 "parse a JSON string into a value",
474 "JSON.parse(\"[1,2]\") // => [ 1, 2 ]",
475 ),
476 (
478 "Object.keys",
479 "Object",
480 "an array of an object's own enumerable keys",
481 "Object.keys({ a: 1, b: 2 }) // => [ 'a', 'b' ]",
482 ),
483 (
484 "Object.values",
485 "Object",
486 "an array of an object's own enumerable values",
487 "Object.values({ a: 1, b: 2 }) // => [ 1, 2 ]",
488 ),
489 (
490 "Object.entries",
491 "Object",
492 "an array of [key, value] pairs",
493 "Object.entries({ a: 1 }) // => [ [ 'a', 1 ] ]",
494 ),
495 (
496 "Object.assign",
497 "Object",
498 "copy source properties onto a target object (in place)",
499 "Object.assign({ a: 1 }, { b: 2 }) // => { a: 1, b: 2 }",
500 ),
501 (
502 "Object.fromEntries",
503 "Object",
504 "build an object from [key, value] pairs",
505 "Object.fromEntries([[\"a\", 1]]) // => { a: 1 }",
506 ),
507 (
508 "Object.freeze",
509 "Object",
510 "make an object immutable and return it",
511 "Object.freeze({ a: 1 }).a // => 1",
512 ),
513 (
515 "Number.isInteger",
516 "Number",
517 "true if the value is an integer number",
518 "Number.isInteger(3) // => true",
519 ),
520 (
521 "Number.isNaN",
522 "Number",
523 "true only if the value is exactly NaN (no coercion)",
524 "Number.isNaN(NaN) // => true",
525 ),
526 (
527 "Number.isFinite",
528 "Number",
529 "true if the value is a finite number (no coercion)",
530 "Number.isFinite(10) // => true",
531 ),
532 (
533 "Number.isSafeInteger",
534 "Number",
535 "true if the value is an integer within +/-2^53-1",
536 "Number.isSafeInteger(2 ** 53) // => false",
537 ),
538 (
539 "Number.parseInt",
540 "Number",
541 "same as the global parseInt",
542 "Number.parseInt(\"20\", 10) // => 20",
543 ),
544 (
545 "Number.parseFloat",
546 "Number",
547 "same as the global parseFloat",
548 "Number.parseFloat(\"1.5\") // => 1.5",
549 ),
550 (
552 "String.fromCharCode",
553 "String static",
554 "a string built from the given UTF-16 code units",
555 "String.fromCharCode(65, 66) // => 'AB'",
556 ),
557 (
559 "Array.isArray",
560 "Array static",
561 "true if the value is an array",
562 "Array.isArray([1, 2]) // => true",
563 ),
564 (
565 "Array.from",
566 "Array static",
567 "build an array from an iterable or array-like",
568 "Array.from(\"ab\") // => [ 'a', 'b' ]",
569 ),
570 (
571 "Array.of",
572 "Array static",
573 "build an array from the given arguments",
574 "Array.of(1, 2, 3) // => [ 1, 2, 3 ]",
575 ),
576 (
578 "push",
579 "Array method",
580 "append items to the end; returns the new length",
581 "const a = [1]; a.push(2); a // => [ 1, 2 ]",
582 ),
583 (
584 "pop",
585 "Array method",
586 "remove and return the last item",
587 "[1, 2, 3].pop() // => 3",
588 ),
589 (
590 "shift",
591 "Array method",
592 "remove and return the first item",
593 "[1, 2, 3].shift() // => 1",
594 ),
595 (
596 "unshift",
597 "Array method",
598 "prepend items; returns the new length",
599 "const a = [2]; a.unshift(1); a // => [ 1, 2 ]",
600 ),
601 (
602 "map",
603 "Array method",
604 "a new array of the results of calling fn on each item",
605 "[1, 2, 3].map(x => x * 2) // => [ 2, 4, 6 ]",
606 ),
607 (
608 "filter",
609 "Array method",
610 "a new array of the items for which fn is truthy",
611 "[1, 2, 3, 4].filter(x => x % 2 === 0) // => [ 2, 4 ]",
612 ),
613 (
614 "forEach",
615 "Array method",
616 "call fn on each item for effect; returns undefined",
617 "let s = 0; [1, 2, 3].forEach(x => s += x); s // => 6",
618 ),
619 (
620 "reduce",
621 "Array method",
622 "fold the array to a single value with an accumulator",
623 "[1, 2, 3].reduce((a, b) => a + b, 0) // => 6",
624 ),
625 (
626 "join",
627 "Array method",
628 "concatenate items into a string with a separator",
629 "[1, 2, 3].join(\"-\") // => '1-2-3'",
630 ),
631 (
632 "slice",
633 "Array method",
634 "a shallow copy of a [start, end) sub-range",
635 "[1, 2, 3, 4].slice(1, 3) // => [ 2, 3 ]",
636 ),
637 (
638 "splice",
639 "Array method",
640 "remove/insert items in place; returns the removed items",
641 "const a = [1, 2, 3]; a.splice(1, 1); a // => [ 1, 3 ]",
642 ),
643 (
644 "concat",
645 "Array method",
646 "a new array joining this array with more arrays/values",
647 "[1].concat([2, 3]) // => [ 1, 2, 3 ]",
648 ),
649 (
650 "indexOf",
651 "Array method",
652 "index of the first matching item, or -1",
653 "[1, 2, 3].indexOf(2) // => 1",
654 ),
655 (
656 "lastIndexOf",
657 "Array method",
658 "index of the last matching item, or -1",
659 "[1, 2, 1].lastIndexOf(1) // => 2",
660 ),
661 (
662 "includes",
663 "Array method",
664 "true if the array contains the value",
665 "[1, 2, 3].includes(2) // => true",
666 ),
667 (
668 "find",
669 "Array method",
670 "the first item for which fn is truthy, else undefined",
671 "[1, 2, 3].find(x => x > 1) // => 2",
672 ),
673 (
674 "findIndex",
675 "Array method",
676 "the index of the first item for which fn is truthy, else -1",
677 "[1, 2, 3].findIndex(x => x > 1) // => 1",
678 ),
679 (
680 "some",
681 "Array method",
682 "true if fn is truthy for any item",
683 "[1, 2, 3].some(x => x > 2) // => true",
684 ),
685 (
686 "every",
687 "Array method",
688 "true if fn is truthy for every item",
689 "[1, 2, 3].every(x => x > 0) // => true",
690 ),
691 (
692 "reverse",
693 "Array method",
694 "reverse the array in place",
695 "[1, 2, 3].reverse() // => [ 3, 2, 1 ]",
696 ),
697 (
698 "sort",
699 "Array method",
700 "sort in place (default: by string order)",
701 "[3, 1, 2].sort() // => [ 1, 2, 3 ]",
702 ),
703 (
704 "flat",
705 "Array method",
706 "a new array with sub-arrays flattened one level (or by depth)",
707 "[1, [2, [3]]].flat() // => [ 1, 2, [ 3 ] ]",
708 ),
709 (
710 "flatMap",
711 "Array method",
712 "map each item then flatten the result one level",
713 "[1, 2].flatMap(x => [x, x]) // => [ 1, 1, 2, 2 ]",
714 ),
715 (
716 "fill",
717 "Array method",
718 "overwrite a range with a value in place",
719 "[1, 2, 3].fill(0) // => [ 0, 0, 0 ]",
720 ),
721 (
722 "at",
723 "Array method",
724 "the item at an index, allowing negative indexing",
725 "[1, 2, 3].at(-1) // => 3",
726 ),
727 (
729 "toUpperCase",
730 "String method",
731 "a copy with all cased characters uppercased",
732 "\"abc\".toUpperCase() // => 'ABC'",
733 ),
734 (
735 "toLowerCase",
736 "String method",
737 "a copy with all cased characters lowercased",
738 "\"ABC\".toLowerCase() // => 'abc'",
739 ),
740 (
741 "charAt",
742 "String method",
743 "the character at an index",
744 "\"hi\".charAt(1) // => 'i'",
745 ),
746 (
747 "charCodeAt",
748 "String method",
749 "the UTF-16 code unit at an index",
750 "\"A\".charCodeAt(0) // => 65",
751 ),
752 (
753 "codePointAt",
754 "String method",
755 "the Unicode code point at an index",
756 "\"A\".codePointAt(0) // => 65",
757 ),
758 (
759 "slice",
760 "String method",
761 "a substring over a [start, end) range (negatives allowed)",
762 "\"hello\".slice(1, 3) // => 'el'",
763 ),
764 (
765 "substring",
766 "String method",
767 "a substring over a [start, end) range (no negatives)",
768 "\"hello\".substring(0, 2) // => 'he'",
769 ),
770 (
771 "split",
772 "String method",
773 "an array of substrings split on a separator",
774 "\"a,b,c\".split(\",\") // => [ 'a', 'b', 'c' ]",
775 ),
776 (
777 "trim",
778 "String method",
779 "a copy with leading and trailing whitespace removed",
780 "\" hi \".trim() // => 'hi'",
781 ),
782 (
783 "trimStart",
784 "String method",
785 "a copy with leading whitespace removed",
786 "\" hi\".trimStart() // => 'hi'",
787 ),
788 (
789 "trimEnd",
790 "String method",
791 "a copy with trailing whitespace removed",
792 "\"hi \".trimEnd() // => 'hi'",
793 ),
794 (
795 "replace",
796 "String method",
797 "a copy with the first match of a substring replaced",
798 "\"aaa\".replace(\"a\", \"b\") // => 'baa'",
799 ),
800 (
801 "replaceAll",
802 "String method",
803 "a copy with every match of a substring replaced",
804 "\"aaa\".replaceAll(\"a\", \"b\") // => 'bbb'",
805 ),
806 (
807 "repeat",
808 "String method",
809 "the string repeated n times",
810 "\"ab\".repeat(3) // => 'ababab'",
811 ),
812 (
813 "startsWith",
814 "String method",
815 "true if the string starts with the prefix",
816 "\"hello\".startsWith(\"he\") // => true",
817 ),
818 (
819 "endsWith",
820 "String method",
821 "true if the string ends with the suffix",
822 "\"hello\".endsWith(\"lo\") // => true",
823 ),
824 (
825 "padStart",
826 "String method",
827 "pad on the left to a target length",
828 "\"5\".padStart(3, \"0\") // => '005'",
829 ),
830 (
831 "padEnd",
832 "String method",
833 "pad on the right to a target length",
834 "\"5\".padEnd(3, \"0\") // => '500'",
835 ),
836 (
838 "toFixed",
839 "Number method",
840 "a fixed-point string with n digits after the decimal point",
841 "(3.14159).toFixed(2) // => '3.14'",
842 ),
843 (
844 "toPrecision",
845 "Number method",
846 "a string with n significant digits",
847 "(123.456).toPrecision(4) // => '123.5'",
848 ),
849 (
850 "toString",
851 "Number method",
852 "the string form of a number in an optional radix",
853 "(255).toString(16) // => 'ff'",
854 ),
855];
856
857pub fn corpus() -> &'static [(&'static str, &'static str, &'static str, &'static str)] {
860 CORPUS
861}
862
863type Docs = HashMap<String, String>;
866
867pub fn run() -> Result<(), String> {
869 spawn_orphan_guard();
870 let (conn, io_threads) = Connection::stdio();
871 let (init_id, _params) = conn
872 .initialize_start()
873 .map_err(|e| format!("lsp initialize: {e}"))?;
874 let init_result = serde_json::json!({
875 "capabilities": server_capabilities(),
876 "serverInfo": { "name": "nodejs", "version": env!("CARGO_PKG_VERSION") },
877 });
878 conn.sender
879 .send(Response::new_ok(init_id, init_result).into())
880 .map_err(|e| format!("lsp send: {e}"))?;
881
882 let mut docs: Docs = HashMap::new();
883 for msg in &conn.receiver {
884 match msg {
885 Message::Request(req) => {
886 if conn
887 .handle_shutdown(&req)
888 .map_err(|e| format!("lsp shutdown: {e}"))?
889 {
890 break;
891 }
892 dispatch_request(&conn, &docs, req);
893 }
894 Message::Notification(not) => dispatch_notification(&conn, &mut docs, not),
895 Message::Response(_) => {}
896 }
897 }
898 drop(conn);
899 io_threads.join().map_err(|_| "lsp io join".to_string())?;
900 Ok(())
901}
902
903fn server_capabilities() -> ServerCapabilities {
904 ServerCapabilities {
905 text_document_sync: Some(TextDocumentSyncCapability::Options(
906 TextDocumentSyncOptions {
907 open_close: Some(true),
908 change: Some(TextDocumentSyncKind::FULL),
909 ..Default::default()
910 },
911 )),
912 completion_provider: Some(CompletionOptions {
913 resolve_provider: Some(false),
914 ..Default::default()
915 }),
916 hover_provider: Some(HoverProviderCapability::Simple(true)),
917 ..Default::default()
918 }
919}
920
921fn handle<P, R>(conn: &Connection, req: Request, f: impl FnOnce(P) -> R)
922where
923 P: serde::de::DeserializeOwned,
924 R: serde::Serialize,
925{
926 let method = req.method.clone();
927 let id = req.id.clone();
928 match req.extract::<P>(&method) {
929 Ok((id, params)) => {
930 let value = serde_json::to_value(f(params)).unwrap_or(serde_json::Value::Null);
931 let _ = conn.sender.send(Response::new_ok(id, value).into());
932 }
933 Err(ExtractError::JsonError { error, .. }) => {
934 let _ = conn.sender.send(
935 Response::new_err(id, ErrorCode::InvalidParams as i32, error.to_string()).into(),
936 );
937 }
938 Err(ExtractError::MethodMismatch(_)) => unreachable!("method matched before extract"),
939 }
940}
941
942fn dispatch_request(conn: &Connection, docs: &Docs, req: Request) {
943 match req.method.as_str() {
944 Completion::METHOD => handle(conn, req, |_p: CompletionParams| completions()),
945 HoverRequest::METHOD => handle(conn, req, |p: HoverParams| hover(docs, &p)),
946 _ => {
947 let _ = conn.sender.send(
948 Response::new_err(req.id, ErrorCode::MethodNotFound as i32, "unhandled".into())
949 .into(),
950 );
951 }
952 }
953}
954
955fn dispatch_notification(conn: &Connection, docs: &mut Docs, not: lsp_server::Notification) {
956 match not.method.as_str() {
957 DidOpenTextDocument::METHOD => {
958 if let Ok(p) = serde_json::from_value::<DidOpenTextDocumentParams>(not.params) {
959 let uri = p.text_document.uri;
960 docs.insert(uri.as_str().to_string(), p.text_document.text.clone());
961 publish_diagnostics(conn, &uri, &p.text_document.text);
962 }
963 }
964 DidChangeTextDocument::METHOD => {
965 if let Ok(p) = serde_json::from_value::<DidChangeTextDocumentParams>(not.params) {
966 if let Some(change) = p.content_changes.into_iter().last() {
967 let uri = p.text_document.uri;
968 docs.insert(uri.as_str().to_string(), change.text.clone());
969 publish_diagnostics(conn, &uri, &change.text);
970 }
971 }
972 }
973 DidCloseTextDocument::METHOD => {
974 if let Ok(p) = serde_json::from_value::<DidCloseTextDocumentParams>(not.params) {
975 let uri = p.text_document.uri;
976 docs.remove(uri.as_str());
977 publish_diagnostics(conn, &uri, "");
978 }
979 }
980 _ => {}
981 }
982}
983
984fn completions() -> CompletionResponse {
985 let items = CORPUS
986 .iter()
987 .map(|(name, chapter, doc, _example)| CompletionItem {
988 label: name.to_string(),
989 kind: Some(if *chapter == "Keyword" {
990 CompletionItemKind::KEYWORD
991 } else if chapter.contains("method") {
992 CompletionItemKind::METHOD
993 } else {
994 CompletionItemKind::FUNCTION
995 }),
996 detail: Some((*doc).to_string()),
997 ..Default::default()
998 })
999 .collect();
1000 CompletionResponse::Array(items)
1001}
1002
1003fn hover(docs: &Docs, params: &HoverParams) -> Hover {
1007 let pos = params.text_document_position_params.position;
1008 let uri = params
1009 .text_document_position_params
1010 .text_document
1011 .uri
1012 .as_str();
1013 let word = docs
1014 .get(uri)
1015 .and_then(|text| word_at(text, pos))
1016 .unwrap_or_default();
1017
1018 let matches: Vec<&(&str, &str, &str, &str)> =
1019 CORPUS.iter().filter(|(name, ..)| *name == word).collect();
1020
1021 let body = if matches.is_empty() {
1022 "**node-js** — JavaScript on the fusevm bytecode VM + Cranelift JIT.".to_string()
1023 } else {
1024 let mut out = String::new();
1025 for (name, chapter, doc, example) in matches {
1026 out.push_str(&format!(
1027 "**`{name}`** — _{chapter}_\n\n{doc}\n\n```javascript\n{example}\n```\n\n"
1028 ));
1029 }
1030 out.trim_end().to_string()
1031 };
1032
1033 Hover {
1034 contents: HoverContents::Markup(MarkupContent {
1035 kind: MarkupKind::Markdown,
1036 value: body,
1037 }),
1038 range: None,
1039 }
1040}
1041
1042fn word_at(text: &str, pos: Position) -> Option<String> {
1044 let line = text.lines().nth(pos.line as usize)?;
1045 let chars: Vec<char> = line.chars().collect();
1046 let col = (pos.character as usize).min(chars.len());
1047 let is_word = |c: char| c.is_ascii_alphanumeric() || c == '_' || c == '$';
1048
1049 let mut start = col;
1050 while start > 0 && is_word(chars[start - 1]) {
1051 start -= 1;
1052 }
1053 let mut end = col;
1054 while end < chars.len() && is_word(chars[end]) {
1055 end += 1;
1056 }
1057 if start == end {
1058 return None;
1059 }
1060 Some(chars[start..end].iter().collect())
1061}
1062
1063fn publish_diagnostics(conn: &Connection, uri: &Uri, text: &str) {
1064 let params = PublishDiagnosticsParams {
1065 uri: uri.clone(),
1066 diagnostics: compute_diagnostics(text),
1067 version: None,
1068 };
1069 let not = lsp_server::Notification::new(PublishDiagnostics::METHOD.to_string(), params);
1070 let _ = conn.sender.send(not.into());
1071}
1072
1073fn compute_diagnostics(text: &str) -> Vec<Diagnostic> {
1076 if text.trim().is_empty() {
1077 return Vec::new();
1078 }
1079 match crate::parser::parse(text) {
1080 Ok(_) => Vec::new(),
1081 Err(e) => {
1082 let line = parse_error_line(&e).saturating_sub(1);
1083 vec![Diagnostic {
1084 range: Range {
1085 start: Position { line, character: 0 },
1086 end: Position {
1087 line,
1088 character: 200,
1089 },
1090 },
1091 severity: Some(DiagnosticSeverity::ERROR),
1092 message: e,
1093 ..Default::default()
1094 }]
1095 }
1096 }
1097}
1098
1099fn parse_error_line(e: &str) -> u32 {
1102 e.rsplit_once("(line ")
1103 .and_then(|(_, rest)| rest.split(|c: char| !c.is_ascii_digit()).next())
1104 .and_then(|n| n.parse().ok())
1105 .unwrap_or(1)
1106}
1107
1108fn spawn_orphan_guard() {
1110 std::thread::spawn(|| {
1111 #[cfg(target_os = "linux")]
1112 unsafe {
1114 libc::prctl(
1115 libc::PR_SET_PDEATHSIG,
1116 libc::SIGKILL as libc::c_ulong,
1117 0,
1118 0,
1119 0,
1120 );
1121 }
1122 loop {
1123 std::thread::sleep(std::time::Duration::from_secs(2));
1124 if unsafe { libc::getppid() } == 1 {
1126 std::process::exit(0);
1127 }
1128 }
1129 });
1130}