@forinda/kickjs-view-language-server 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -3
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/main.js +2 -2
- package/dist/{server-Dak0ereo.js → server-pe6A1Wn-.js} +732 -28
- package/dist/server-pe6A1Wn-.js.map +1 -0
- package/package.json +1 -1
- package/dist/server-Dak0ereo.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs-view-language-server v0.
|
|
2
|
+
* @forinda/kickjs-view-language-server v0.2.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -53,6 +53,48 @@ function closeParen$1(s, i) {
|
|
|
53
53
|
}
|
|
54
54
|
return s.length;
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Where an `@let` value from `from` ends: its `;` outside quotes, as the
|
|
58
|
+
* parser reads it; the line's end while it's unterminated, so typing one
|
|
59
|
+
* doesn't swallow the lines below.
|
|
60
|
+
*/
|
|
61
|
+
function letEnd(s, from) {
|
|
62
|
+
let quote = "";
|
|
63
|
+
for (let j = from; j < s.length; j++) {
|
|
64
|
+
const c = s[j];
|
|
65
|
+
if (c === "\n") return {
|
|
66
|
+
at: j,
|
|
67
|
+
closed: false
|
|
68
|
+
};
|
|
69
|
+
if (quote) {
|
|
70
|
+
if (c === "\\") j++;
|
|
71
|
+
else if (c === quote) quote = "";
|
|
72
|
+
} else if (c === "'" || c === "\"") quote = c;
|
|
73
|
+
else if (c === ";") return {
|
|
74
|
+
at: j,
|
|
75
|
+
closed: true
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
at: s.length,
|
|
80
|
+
closed: false
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** The end of `@verbatim { … }` from `brace` (at `{`): after its `}`, quoted braces skipped as the parser does. */
|
|
84
|
+
function verbatimEnd(s, brace) {
|
|
85
|
+
let depth = 0;
|
|
86
|
+
let quote = "";
|
|
87
|
+
for (let k = brace; k < s.length; k++) {
|
|
88
|
+
const c = s[k];
|
|
89
|
+
if (quote) {
|
|
90
|
+
if (c === "\\") k++;
|
|
91
|
+
else if (c === quote) quote = "";
|
|
92
|
+
} else if (c === "'" || c === "\"" || c === "`") quote = c;
|
|
93
|
+
else if (c === "{") depth++;
|
|
94
|
+
else if (c === "}" && --depth === 0) return k + 1;
|
|
95
|
+
}
|
|
96
|
+
return s.length;
|
|
97
|
+
}
|
|
56
98
|
/** The end of a `{{ … }}` starting at `i`; the text's end when unclosed. */
|
|
57
99
|
function closeInterp(s, i) {
|
|
58
100
|
let quote = "";
|
|
@@ -108,9 +150,9 @@ function scan(s) {
|
|
|
108
150
|
const kw = m[1].replace(/\s+/g, " ");
|
|
109
151
|
let j = i + m[0].length;
|
|
110
152
|
if (kw === "let" || kw === "input") {
|
|
111
|
-
const semi = kw === "let" ? s.indexOf(";", j) : -1;
|
|
112
153
|
const eol = s.indexOf("\n", j);
|
|
113
|
-
const
|
|
154
|
+
const end = kw === "let" ? letEnd(s, j) : void 0;
|
|
155
|
+
const stop = end ? end.at + (end.closed ? 1 : 0) : eol < 0 ? s.length : eol;
|
|
114
156
|
out.blank.push([i, stop]);
|
|
115
157
|
i = stop;
|
|
116
158
|
continue;
|
|
@@ -118,11 +160,7 @@ function scan(s) {
|
|
|
118
160
|
if (kw === "verbatim") {
|
|
119
161
|
const b = skipWs(j);
|
|
120
162
|
if (s[b] === "{") {
|
|
121
|
-
|
|
122
|
-
let k = b;
|
|
123
|
-
for (; k < s.length; k++) if (s[k] === "{") d++;
|
|
124
|
-
else if (s[k] === "}" && --d === 0) break;
|
|
125
|
-
const stop = Math.min(k + 1, s.length);
|
|
163
|
+
const stop = verbatimEnd(s, b);
|
|
126
164
|
out.blank.push([i, stop]);
|
|
127
165
|
i = stop;
|
|
128
166
|
continue;
|
|
@@ -277,12 +315,38 @@ var KickVirtualCode = class {
|
|
|
277
315
|
];
|
|
278
316
|
}
|
|
279
317
|
};
|
|
318
|
+
/** TS / JS: a code only the kick service plugin reads, for `ctx.render('…')` names. */
|
|
319
|
+
const SCRIPT_IDS = /* @__PURE__ */ new Set([
|
|
320
|
+
"typescript",
|
|
321
|
+
"typescriptreact",
|
|
322
|
+
"javascript",
|
|
323
|
+
"javascriptreact"
|
|
324
|
+
]);
|
|
325
|
+
const SCRIPT_FILE = /\.(?:[cm]?[jt]s|[jt]sx)$/;
|
|
326
|
+
var RenderCallsCode = class {
|
|
327
|
+
id = "root";
|
|
328
|
+
languageId = "kick-render";
|
|
329
|
+
snapshot;
|
|
330
|
+
mappings;
|
|
331
|
+
embeddedCodes = [];
|
|
332
|
+
constructor(text) {
|
|
333
|
+
this.snapshot = snapshotOf(text);
|
|
334
|
+
this.mappings = [{
|
|
335
|
+
sourceOffsets: [0],
|
|
336
|
+
generatedOffsets: [0],
|
|
337
|
+
lengths: [text.length],
|
|
338
|
+
data: EMBEDDED
|
|
339
|
+
}];
|
|
340
|
+
}
|
|
341
|
+
};
|
|
280
342
|
const kickLanguagePlugin = {
|
|
281
343
|
getLanguageId(uri) {
|
|
282
344
|
return uri.path.endsWith(".kick") ? "kick" : void 0;
|
|
283
345
|
},
|
|
284
|
-
createVirtualCode(
|
|
285
|
-
|
|
346
|
+
createVirtualCode(uri, languageId, snapshot) {
|
|
347
|
+
const text = snapshot.getText(0, snapshot.getLength());
|
|
348
|
+
if (languageId === "kick" || uri.path.endsWith(".kick")) return new KickVirtualCode(text);
|
|
349
|
+
if (SCRIPT_IDS.has(languageId) || SCRIPT_FILE.test(uri.path)) return new RenderCallsCode(text);
|
|
286
350
|
}
|
|
287
351
|
};
|
|
288
352
|
//#endregion
|
|
@@ -295,7 +359,7 @@ const LOOP_VARS = [
|
|
|
295
359
|
"$odd",
|
|
296
360
|
"$count"
|
|
297
361
|
];
|
|
298
|
-
const IDENT = /[A-Za-z_$][\w$]*/;
|
|
362
|
+
const IDENT$1 = /[A-Za-z_$][\w$]*/;
|
|
299
363
|
/** The end of `(`…`)` from `i` (at `(`), or -1 when it doesn't close before `stop`. */
|
|
300
364
|
function closeParen(s, i, stop) {
|
|
301
365
|
let depth = 0;
|
|
@@ -354,7 +418,7 @@ function scopeAt(s, stop) {
|
|
|
354
418
|
const kw = m[1].replace(/\s+/g, " ");
|
|
355
419
|
let j = i + m[0].length;
|
|
356
420
|
if (kw === "input") {
|
|
357
|
-
const name = IDENT.exec(s.slice(j, j + 64));
|
|
421
|
+
const name = IDENT$1.exec(s.slice(j, j + 64));
|
|
358
422
|
if (name) inputs.push(name[0]);
|
|
359
423
|
i = j;
|
|
360
424
|
continue;
|
|
@@ -462,7 +526,7 @@ const KEYWORD = 14;
|
|
|
462
526
|
const FUNCTION = 3;
|
|
463
527
|
const CLASS = 7;
|
|
464
528
|
const PROPERTY = 10;
|
|
465
|
-
const FILE = 17;
|
|
529
|
+
const FILE$1 = 17;
|
|
466
530
|
const VARIABLE = 6;
|
|
467
531
|
const KEYWORDS = {
|
|
468
532
|
if: "if (${1:condition}) {\n $0\n}",
|
|
@@ -489,11 +553,19 @@ function componentOf(ws, tag) {
|
|
|
489
553
|
function layoutSlots(ws, name) {
|
|
490
554
|
const out = /* @__PURE__ */ new Set();
|
|
491
555
|
const seen = /* @__PURE__ */ new Set();
|
|
492
|
-
|
|
493
|
-
|
|
556
|
+
const chain = [];
|
|
557
|
+
let layout = name === void 0 ? void 0 : ws.layoutOf(name);
|
|
558
|
+
while (layout !== void 0 && !seen.has(layout)) {
|
|
494
559
|
seen.add(layout);
|
|
495
|
-
|
|
496
|
-
|
|
560
|
+
chain.push(layout);
|
|
561
|
+
layout = ws.layoutOf(layout);
|
|
562
|
+
}
|
|
563
|
+
const base = ws.baseLayout();
|
|
564
|
+
const page = name === void 0 ? void 0 : ws.astOf(name);
|
|
565
|
+
if (base !== void 0 && name !== void 0 && page?.layout !== null && !name.startsWith(`${ws.componentsDir()}/`) && name !== base && !seen.has(base)) chain.push(base);
|
|
566
|
+
for (const l of chain) {
|
|
567
|
+
const ast = ws.astOf(l);
|
|
568
|
+
if (!ast) continue;
|
|
497
569
|
const stack = [ast.body];
|
|
498
570
|
while (stack.length) for (const n of stack.pop()) if (n.k === "slot-outlet") {
|
|
499
571
|
if (n.name) out.add(n.name);
|
|
@@ -503,7 +575,6 @@ function layoutSlots(ws, name) {
|
|
|
503
575
|
else if (n.k === "for") stack.push(n.body, n.empty ?? []);
|
|
504
576
|
else if (n.k === "switch") stack.push(...n.cases.map((c) => c.body), n.default ?? []);
|
|
505
577
|
else if (n.k === "slot-fill") stack.push(n.body);
|
|
506
|
-
layout = ast.layout;
|
|
507
578
|
}
|
|
508
579
|
return [...out];
|
|
509
580
|
}
|
|
@@ -562,7 +633,7 @@ function completions(ws, uri, text, offset) {
|
|
|
562
633
|
const dir = ws.componentsDir();
|
|
563
634
|
return ws.names().filter((n) => !n.startsWith(`${dir}/`)).map((label) => ({
|
|
564
635
|
label,
|
|
565
|
-
kind: FILE
|
|
636
|
+
kind: FILE$1
|
|
566
637
|
}));
|
|
567
638
|
}
|
|
568
639
|
case "slot-name": return layoutSlots(ws, ws.nameOf(uri)).map((label) => ({
|
|
@@ -631,7 +702,7 @@ function posOf(text, offset) {
|
|
|
631
702
|
}
|
|
632
703
|
//#endregion
|
|
633
704
|
//#region src/plugin/definition.ts
|
|
634
|
-
const TOP = {
|
|
705
|
+
const TOP$1 = {
|
|
635
706
|
start: {
|
|
636
707
|
line: 0,
|
|
637
708
|
character: 0
|
|
@@ -648,7 +719,7 @@ function definition(ws, _uri, text, offset) {
|
|
|
648
719
|
start: posOf(text, start),
|
|
649
720
|
end: posOf(text, end)
|
|
650
721
|
};
|
|
651
|
-
const link = (targetUri, targetRange = TOP) => ({
|
|
722
|
+
const link = (targetUri, targetRange = TOP$1) => ({
|
|
652
723
|
originSelectionRange: origin,
|
|
653
724
|
targetUri,
|
|
654
725
|
targetRange,
|
|
@@ -678,6 +749,75 @@ function definition(ws, _uri, text, offset) {
|
|
|
678
749
|
return null;
|
|
679
750
|
}
|
|
680
751
|
//#endregion
|
|
752
|
+
//#region src/plugin/render-calls.ts
|
|
753
|
+
/** A call's template name as typed so far, up to the cursor. */
|
|
754
|
+
const OPEN_CALL = /\b(?:ctx|views)\.render\(\s*(['"`])([^'"`\n]*)$/;
|
|
755
|
+
/** Every call with a literal name. */
|
|
756
|
+
const CALLS = /\b(?:ctx|views)\.render\(\s*(['"`])([^'"`\n]*)\1/g;
|
|
757
|
+
const FILE = 17;
|
|
758
|
+
/** Template names a page can render: everything but components. */
|
|
759
|
+
function renderable(ws) {
|
|
760
|
+
const dir = `${ws.componentsDir()}/`;
|
|
761
|
+
return ws.names().filter((n) => !n.startsWith(dir));
|
|
762
|
+
}
|
|
763
|
+
function renderCompletions(ws, text, offset) {
|
|
764
|
+
const m = OPEN_CALL.exec(text.slice(0, offset));
|
|
765
|
+
if (!m) return void 0;
|
|
766
|
+
const range = {
|
|
767
|
+
start: posOf(text, offset - m[2].length),
|
|
768
|
+
end: posOf(text, offset)
|
|
769
|
+
};
|
|
770
|
+
return renderable(ws).map((name) => ({
|
|
771
|
+
label: name,
|
|
772
|
+
kind: FILE,
|
|
773
|
+
textEdit: {
|
|
774
|
+
range,
|
|
775
|
+
newText: name
|
|
776
|
+
}
|
|
777
|
+
}));
|
|
778
|
+
}
|
|
779
|
+
/** Calls whose name is a plain literal (not a template string with `${…}`). */
|
|
780
|
+
function calls(text) {
|
|
781
|
+
return [...text.matchAll(CALLS)].filter((m) => !m[2].includes("${")).map((m) => ({
|
|
782
|
+
name: m[2],
|
|
783
|
+
offset: m.index + m[0].length - m[2].length - 1
|
|
784
|
+
}));
|
|
785
|
+
}
|
|
786
|
+
function renderDefinition(ws, text, offset) {
|
|
787
|
+
const call = calls(text).find((c) => offset >= c.offset && offset <= c.offset + c.name.length);
|
|
788
|
+
if (!call || !ws.astOf(call.name)) return null;
|
|
789
|
+
const top = {
|
|
790
|
+
start: {
|
|
791
|
+
line: 0,
|
|
792
|
+
character: 0
|
|
793
|
+
},
|
|
794
|
+
end: {
|
|
795
|
+
line: 0,
|
|
796
|
+
character: 0
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
return {
|
|
800
|
+
originSelectionRange: {
|
|
801
|
+
start: posOf(text, call.offset),
|
|
802
|
+
end: posOf(text, call.offset + call.name.length)
|
|
803
|
+
},
|
|
804
|
+
targetUri: ws.uriOf(call.name),
|
|
805
|
+
targetRange: top,
|
|
806
|
+
targetSelectionRange: top
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
function renderDiagnostics(ws, text) {
|
|
810
|
+
return calls(text).filter((c) => !ws.astOf(c.name)).map((c) => ({
|
|
811
|
+
range: {
|
|
812
|
+
start: posOf(text, c.offset),
|
|
813
|
+
end: posOf(text, c.offset + c.name.length)
|
|
814
|
+
},
|
|
815
|
+
severity: 2,
|
|
816
|
+
source: "kick",
|
|
817
|
+
message: `template '${c.name}' not found`
|
|
818
|
+
}));
|
|
819
|
+
}
|
|
820
|
+
//#endregion
|
|
681
821
|
//#region src/plugin/diagnostics.ts
|
|
682
822
|
/** The `.kick` file a root virtual-code document belongs to. */
|
|
683
823
|
function sourceUri(context, uri) {
|
|
@@ -692,6 +832,7 @@ function createDiagnosticsPlugin(ws) {
|
|
|
692
832
|
} },
|
|
693
833
|
create(context) {
|
|
694
834
|
return { provideDiagnostics(document) {
|
|
835
|
+
if (document.languageId === "kick-render") return renderDiagnostics(ws, document.getText());
|
|
695
836
|
if (document.languageId !== "kick") return void 0;
|
|
696
837
|
const uri = sourceUri(context, document.uri);
|
|
697
838
|
ws.sync(uri, document.getText());
|
|
@@ -718,6 +859,421 @@ function formatEdits(view, text, file, tabSize) {
|
|
|
718
859
|
}];
|
|
719
860
|
}
|
|
720
861
|
//#endregion
|
|
862
|
+
//#region src/plugin/tokens.ts
|
|
863
|
+
/**
|
|
864
|
+
* Semantic tokens for `.kick` templates, so every LSP editor highlights the
|
|
865
|
+
* template syntax, not only VS Code with its TextMate grammar. Found by a
|
|
866
|
+
* lexical scan, like the language plugin's: the buffer being typed in rarely
|
|
867
|
+
* parses.
|
|
868
|
+
*/
|
|
869
|
+
/** The legend, in order: standard types, so themes colour them. */
|
|
870
|
+
const TOKEN_TYPES = [
|
|
871
|
+
"keyword",
|
|
872
|
+
"comment",
|
|
873
|
+
"operator",
|
|
874
|
+
"function",
|
|
875
|
+
"variable",
|
|
876
|
+
"property",
|
|
877
|
+
"string",
|
|
878
|
+
"number",
|
|
879
|
+
"class",
|
|
880
|
+
"parameter"
|
|
881
|
+
];
|
|
882
|
+
const LITERALS = /* @__PURE__ */ new Set([
|
|
883
|
+
"true",
|
|
884
|
+
"false",
|
|
885
|
+
"null",
|
|
886
|
+
"undefined"
|
|
887
|
+
]);
|
|
888
|
+
/** Words with a meaning in block headers: `@for (x of xs; track x.id; let i = $index)`, `@if (a; as b)`, `@layout(none)`. */
|
|
889
|
+
const HEADER_WORDS = /* @__PURE__ */ new Set([
|
|
890
|
+
"of",
|
|
891
|
+
"track",
|
|
892
|
+
"let",
|
|
893
|
+
"as",
|
|
894
|
+
"none"
|
|
895
|
+
]);
|
|
896
|
+
function kickTokens(s, isComponent) {
|
|
897
|
+
const out = [];
|
|
898
|
+
const push = (offset, length, type) => {
|
|
899
|
+
if (length > 0) out.push({
|
|
900
|
+
offset,
|
|
901
|
+
length,
|
|
902
|
+
type
|
|
903
|
+
});
|
|
904
|
+
};
|
|
905
|
+
/** An expression in `[from, to)`. */
|
|
906
|
+
const expr = (from, to, header) => {
|
|
907
|
+
let prev = "";
|
|
908
|
+
for (let i = from; i < to;) {
|
|
909
|
+
const c = s[i];
|
|
910
|
+
if (/\s/.test(c)) {
|
|
911
|
+
i++;
|
|
912
|
+
continue;
|
|
913
|
+
}
|
|
914
|
+
if (c === "'" || c === "\"" || c === "`") {
|
|
915
|
+
let j = i + 1;
|
|
916
|
+
while (j < to && s[j] !== c) j += s[j] === "\\" ? 2 : 1;
|
|
917
|
+
j = Math.min(j + 1, to);
|
|
918
|
+
push(i, j - i, "string");
|
|
919
|
+
prev = "lit";
|
|
920
|
+
i = j;
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
const num = /^\d+(?:\.\d+)?/.exec(s.slice(i, Math.min(to, i + 32)));
|
|
924
|
+
if (num) {
|
|
925
|
+
push(i, num[0].length, "number");
|
|
926
|
+
prev = "lit";
|
|
927
|
+
i += num[0].length;
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
const word = /^[A-Za-z_$][\w$]*/.exec(s.slice(i, Math.min(to, i + 128)));
|
|
931
|
+
if (word) {
|
|
932
|
+
const w = word[0];
|
|
933
|
+
const type = prev === "." ? "property" : prev === "|" ? "function" : LITERALS.has(w) || header && HEADER_WORDS.has(w) ? "keyword" : "variable";
|
|
934
|
+
push(i, w.length, type);
|
|
935
|
+
prev = "word";
|
|
936
|
+
i += w.length;
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
if (c === "|" && s[i + 1] === "|") {
|
|
940
|
+
prev = "||";
|
|
941
|
+
i += 2;
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
944
|
+
if (c === "|") push(i, 1, "operator");
|
|
945
|
+
prev = c === "?" && s[i + 1] === "." ? "." : c;
|
|
946
|
+
i += c === "?" && s[i + 1] === "." ? 2 : 1;
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
/** `{{ … }}` from `i` (at `{{`); returns its end. */
|
|
950
|
+
const interp = (i, limit) => {
|
|
951
|
+
const end = Math.min(closeInterp(s, i), limit);
|
|
952
|
+
const closed = s.startsWith("}}", end - 2) && end - 2 >= i + 2;
|
|
953
|
+
push(i, 2, "operator");
|
|
954
|
+
expr(i + 2, closed ? end - 2 : end, false);
|
|
955
|
+
if (closed) push(end - 2, 2, "operator");
|
|
956
|
+
return end;
|
|
957
|
+
};
|
|
958
|
+
/** One tag from `i` (at `<`); returns where scanning continues. */
|
|
959
|
+
const tag = (i) => {
|
|
960
|
+
const m = /^<(\/?)([A-Za-z][\w:.-]*)/.exec(s.slice(i, i + 64));
|
|
961
|
+
const name = m[2].toLowerCase();
|
|
962
|
+
if (isComponent(name)) push(i + 1 + m[1].length, m[2].length, "class");
|
|
963
|
+
let j = i + m[0].length;
|
|
964
|
+
while (j < s.length && s[j] !== ">") {
|
|
965
|
+
const binding = /^\[([^\]\s]+)\]\s*=\s*(["'])/.exec(s.slice(j, j + 128));
|
|
966
|
+
if (binding) {
|
|
967
|
+
push(j + 1, binding[1].length, "property");
|
|
968
|
+
const from = j + binding[0].length;
|
|
969
|
+
const close = s.indexOf(binding[2], from);
|
|
970
|
+
const stop = close < 0 ? s.length : close;
|
|
971
|
+
expr(from, stop, false);
|
|
972
|
+
j = stop + 1;
|
|
973
|
+
continue;
|
|
974
|
+
}
|
|
975
|
+
const q = s[j];
|
|
976
|
+
if (q === "\"" || q === "'") {
|
|
977
|
+
const close = s.indexOf(q, j + 1);
|
|
978
|
+
const stop = close < 0 ? s.length : close;
|
|
979
|
+
for (let k = j + 1; k < stop; k++) if (s.startsWith("{{", k)) k = interp(k, stop) - 1;
|
|
980
|
+
j = stop + 1;
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
j++;
|
|
984
|
+
}
|
|
985
|
+
j = Math.min(j + 1, s.length);
|
|
986
|
+
if (m[1] || name !== "script" && name !== "style" || s[j - 2] === "/") return j;
|
|
987
|
+
const close = new RegExp(`</${name}(?=[\\t\\n\\f\\r />])`, "i").exec(s.slice(j));
|
|
988
|
+
const end = close ? j + close.index : s.length;
|
|
989
|
+
for (let k = j; k < end; k++) if (s.startsWith("{{", k)) k = interp(k, end) - 1;
|
|
990
|
+
return end;
|
|
991
|
+
};
|
|
992
|
+
let i = 0;
|
|
993
|
+
while (i < s.length) {
|
|
994
|
+
const c = s[i];
|
|
995
|
+
if (s.startsWith("{{--", i)) {
|
|
996
|
+
const end = s.indexOf("--}}", i + 4);
|
|
997
|
+
const stop = end < 0 ? s.length : end + 4;
|
|
998
|
+
push(i, stop - i, "comment");
|
|
999
|
+
i = stop;
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
if (s.startsWith("{{", i)) {
|
|
1003
|
+
i = interp(i, s.length);
|
|
1004
|
+
continue;
|
|
1005
|
+
}
|
|
1006
|
+
if (s.startsWith("<!--", i)) {
|
|
1007
|
+
const end = s.indexOf("-->", i + 4);
|
|
1008
|
+
i = end < 0 ? s.length : end + 3;
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
if (c === "<" && /^<\/?[A-Za-z]/.test(s.slice(i, i + 3))) {
|
|
1012
|
+
i = tag(i);
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
1015
|
+
if (c === "@" && !/[\w@.-]/.test(s[i - 1] ?? "")) {
|
|
1016
|
+
const m = KEYWORD$1.exec(s.slice(i, i + 16));
|
|
1017
|
+
if (m) {
|
|
1018
|
+
const kw = m[1].replace(/\s+/g, " ");
|
|
1019
|
+
push(i, m[0].length, "keyword");
|
|
1020
|
+
let j = i + m[0].length;
|
|
1021
|
+
if (kw === "input" || kw === "let") {
|
|
1022
|
+
const decl = /^\s+([\w$]+)/.exec(s.slice(j, j + 64));
|
|
1023
|
+
if (decl) push(j + decl[0].length - decl[1].length, decl[1].length, kw === "input" ? "parameter" : "variable");
|
|
1024
|
+
const eol = s.indexOf("\n", j);
|
|
1025
|
+
const stop = kw === "let" ? letEnd(s, j).at : eol < 0 ? s.length : eol;
|
|
1026
|
+
const eq = s.indexOf("=", j);
|
|
1027
|
+
if (eq >= 0 && eq < stop) expr(eq + 1, stop, false);
|
|
1028
|
+
i = stop;
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
while (j < s.length && /\s/.test(s[j])) j++;
|
|
1032
|
+
if (kw === "verbatim" && s[j] === "{") {
|
|
1033
|
+
i = verbatimEnd(s, j);
|
|
1034
|
+
continue;
|
|
1035
|
+
}
|
|
1036
|
+
if (s[j] === "(") {
|
|
1037
|
+
const end = closeParen$1(s, j);
|
|
1038
|
+
expr(j + 1, s[end - 1] === ")" ? end - 1 : end, true);
|
|
1039
|
+
i = end;
|
|
1040
|
+
continue;
|
|
1041
|
+
}
|
|
1042
|
+
i = i + m[0].length;
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
i++;
|
|
1047
|
+
}
|
|
1048
|
+
return out;
|
|
1049
|
+
}
|
|
1050
|
+
//#endregion
|
|
1051
|
+
//#region src/plugin/references.ts
|
|
1052
|
+
const TOP = {
|
|
1053
|
+
start: {
|
|
1054
|
+
line: 0,
|
|
1055
|
+
character: 0
|
|
1056
|
+
},
|
|
1057
|
+
end: {
|
|
1058
|
+
line: 0,
|
|
1059
|
+
character: 0
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
const IDENT = /^[A-Za-z_$][\w$]*$/;
|
|
1063
|
+
const LAYOUT = /@layout\s*\(\s*(['"])([^'"]*)\1/g;
|
|
1064
|
+
const rangeOf = (text, offset, length) => ({
|
|
1065
|
+
start: posOf(text, offset),
|
|
1066
|
+
end: posOf(text, offset + length)
|
|
1067
|
+
});
|
|
1068
|
+
/** The shared name under the cursor, with where it sits in `text`. */
|
|
1069
|
+
function targetAt(ws, text, offset) {
|
|
1070
|
+
const t = kickTokens(text, (tag) => componentOf(ws, tag) !== void 0).find((k) => offset >= k.offset && offset <= k.offset + k.length);
|
|
1071
|
+
if (!t) return void 0;
|
|
1072
|
+
const word = text.slice(t.offset, t.offset + t.length);
|
|
1073
|
+
if (t.type === "class") {
|
|
1074
|
+
const name = componentOf(ws, word.toLowerCase());
|
|
1075
|
+
return name === void 0 ? void 0 : {
|
|
1076
|
+
target: {
|
|
1077
|
+
kind: "component",
|
|
1078
|
+
name,
|
|
1079
|
+
tag: word.toLowerCase()
|
|
1080
|
+
},
|
|
1081
|
+
...t
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
if (t.type === "function") return {
|
|
1085
|
+
target: {
|
|
1086
|
+
kind: "pipe",
|
|
1087
|
+
name: word
|
|
1088
|
+
},
|
|
1089
|
+
...t
|
|
1090
|
+
};
|
|
1091
|
+
if (t.type === "string" && /@layout\s*\(\s*$/.test(text.slice(0, t.offset))) {
|
|
1092
|
+
const name = word.slice(1, -1);
|
|
1093
|
+
return ws.astOf(name) ? {
|
|
1094
|
+
target: {
|
|
1095
|
+
kind: "layout",
|
|
1096
|
+
name
|
|
1097
|
+
},
|
|
1098
|
+
offset: t.offset + 1,
|
|
1099
|
+
length: name.length
|
|
1100
|
+
} : void 0;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
/** Every use of `target` in the templates. */
|
|
1104
|
+
function usesOf(ws, target) {
|
|
1105
|
+
const out = [];
|
|
1106
|
+
for (const name of ws.names()) {
|
|
1107
|
+
const text = ws.textOf(name);
|
|
1108
|
+
if (text === void 0) continue;
|
|
1109
|
+
const uri = ws.uriOf(name);
|
|
1110
|
+
if (target.kind === "layout") {
|
|
1111
|
+
for (const m of text.matchAll(LAYOUT)) if (m[2] === target.name) out.push({
|
|
1112
|
+
uri,
|
|
1113
|
+
text,
|
|
1114
|
+
offset: m.index + m[0].length - m[2].length - 1,
|
|
1115
|
+
length: m[2].length
|
|
1116
|
+
});
|
|
1117
|
+
continue;
|
|
1118
|
+
}
|
|
1119
|
+
const want = target.kind === "component" ? "class" : "function";
|
|
1120
|
+
const match = target.kind === "component" ? target.tag : target.name;
|
|
1121
|
+
for (const t of kickTokens(text, (tag) => target.kind === "component" && tag === target.tag)) if (t.type === want && text.slice(t.offset, t.offset + t.length).toLowerCase() === match.toLowerCase()) out.push({
|
|
1122
|
+
uri,
|
|
1123
|
+
text,
|
|
1124
|
+
...t
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
return out;
|
|
1128
|
+
}
|
|
1129
|
+
/** Where a pipe is declared in `pipes.ts`; only the forms the server can read. */
|
|
1130
|
+
function pipeDeclaration(ws, name) {
|
|
1131
|
+
const decl = ws.pipeDeclaration(name);
|
|
1132
|
+
if (!decl) return void 0;
|
|
1133
|
+
const start = {
|
|
1134
|
+
line: decl.line,
|
|
1135
|
+
character: decl.column
|
|
1136
|
+
};
|
|
1137
|
+
return {
|
|
1138
|
+
uri: URI.file(ws.pipesFile()).toString(),
|
|
1139
|
+
range: {
|
|
1140
|
+
start,
|
|
1141
|
+
end: {
|
|
1142
|
+
line: decl.line,
|
|
1143
|
+
character: decl.column + name.length
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
function references(ws, text, offset, includeDeclaration) {
|
|
1149
|
+
const at = targetAt(ws, text, offset);
|
|
1150
|
+
if (!at) return [];
|
|
1151
|
+
const { target } = at;
|
|
1152
|
+
const locations = usesOf(ws, target).map((u) => ({
|
|
1153
|
+
uri: u.uri,
|
|
1154
|
+
range: rangeOf(u.text, u.offset, u.length)
|
|
1155
|
+
}));
|
|
1156
|
+
if (includeDeclaration) {
|
|
1157
|
+
if (target.kind === "pipe") {
|
|
1158
|
+
const decl = pipeDeclaration(ws, target.name);
|
|
1159
|
+
if (decl) locations.push(decl);
|
|
1160
|
+
} else locations.push({
|
|
1161
|
+
uri: ws.uriOf(target.name),
|
|
1162
|
+
range: TOP
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
return locations;
|
|
1166
|
+
}
|
|
1167
|
+
/** What F2 renames here: a pipe, a component tag or a layout name. */
|
|
1168
|
+
function renameRange(ws, text, offset) {
|
|
1169
|
+
const at = targetAt(ws, text, offset);
|
|
1170
|
+
if (!at) return void 0;
|
|
1171
|
+
if (at.target.kind === "pipe" && !pipeDeclaration(ws, at.target.name)) return { message: `Rename ${at.target.name} in your pipes file; it isn't declared in a form the editor can rename.` };
|
|
1172
|
+
return rangeOf(text, at.offset, at.length);
|
|
1173
|
+
}
|
|
1174
|
+
const TAG = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)+$/;
|
|
1175
|
+
/** The component file for `tag`, kept in the old one's folder when the tag still starts with it. */
|
|
1176
|
+
function componentNameFor(ws, oldName, tag) {
|
|
1177
|
+
if (!TAG.test(tag)) return void 0;
|
|
1178
|
+
const dir = ws.componentsDir();
|
|
1179
|
+
const folders = oldName.slice(dir.length + 1).split("/").slice(0, -1);
|
|
1180
|
+
const prefix = folders.join("-");
|
|
1181
|
+
const name = prefix && tag.startsWith(`${prefix}-`) ? `${dir}/${folders.join("/")}/${tag.slice(prefix.length + 1)}` : `${dir}/${tag}`;
|
|
1182
|
+
return ws.view.componentTag(name, dir) === tag ? name : void 0;
|
|
1183
|
+
}
|
|
1184
|
+
/** Every use of `target` replaced with `newText`, then the template's file renamed. */
|
|
1185
|
+
function renameTemplate(ws, target, newText, newName) {
|
|
1186
|
+
if (target.kind === "pipe") return void 0;
|
|
1187
|
+
if (ws.view.templateName(newName) !== newName || ws.astOf(newName)) return void 0;
|
|
1188
|
+
const byUri = /* @__PURE__ */ new Map();
|
|
1189
|
+
for (const u of usesOf(ws, target)) {
|
|
1190
|
+
const edits = byUri.get(u.uri) ?? [];
|
|
1191
|
+
edits.push({
|
|
1192
|
+
range: rangeOf(u.text, u.offset, u.length),
|
|
1193
|
+
newText
|
|
1194
|
+
});
|
|
1195
|
+
byUri.set(u.uri, edits);
|
|
1196
|
+
}
|
|
1197
|
+
return { documentChanges: [...[...byUri].map(([uri, edits]) => ({
|
|
1198
|
+
textDocument: {
|
|
1199
|
+
uri,
|
|
1200
|
+
version: null
|
|
1201
|
+
},
|
|
1202
|
+
edits
|
|
1203
|
+
})), {
|
|
1204
|
+
kind: "rename",
|
|
1205
|
+
oldUri: ws.uriOf(target.name),
|
|
1206
|
+
newUri: ws.uriOf(newName)
|
|
1207
|
+
}] };
|
|
1208
|
+
}
|
|
1209
|
+
/** A pipe renamed in every template and in `pipes.ts`; a component or layout, file included. */
|
|
1210
|
+
function renameEdits(ws, text, offset, newName) {
|
|
1211
|
+
const at = targetAt(ws, text, offset);
|
|
1212
|
+
if (!at) return void 0;
|
|
1213
|
+
const { target } = at;
|
|
1214
|
+
if (target.kind === "component") {
|
|
1215
|
+
const name = componentNameFor(ws, target.name, newName);
|
|
1216
|
+
return name === void 0 ? void 0 : renameTemplate(ws, target, newName, name);
|
|
1217
|
+
}
|
|
1218
|
+
if (target.kind === "layout") return renameTemplate(ws, target, newName, newName);
|
|
1219
|
+
if (!IDENT.test(newName) || ws.project().pipes.has(newName)) return void 0;
|
|
1220
|
+
const decl = pipeDeclaration(ws, target.name);
|
|
1221
|
+
if (!decl) return void 0;
|
|
1222
|
+
const changes = {};
|
|
1223
|
+
const add = (uri, range) => (changes[uri] ??= []).push({
|
|
1224
|
+
range,
|
|
1225
|
+
newText: newName
|
|
1226
|
+
});
|
|
1227
|
+
for (const u of usesOf(ws, target)) add(u.uri, rangeOf(u.text, u.offset, u.length));
|
|
1228
|
+
add(decl.uri, decl.range);
|
|
1229
|
+
return { changes };
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Edits that keep uses pointing at renamed templates: a component's tags, a
|
|
1233
|
+
* layout's `@layout('…')`, a page's `ctx.render('…')`. `oldUri` / `newUri`
|
|
1234
|
+
* can be a file or a folder.
|
|
1235
|
+
*/
|
|
1236
|
+
async function fileRenameEdits(ws, oldUri, newUri) {
|
|
1237
|
+
const from = ws.relOf(oldUri)?.replace(/\.kick$/, "");
|
|
1238
|
+
const to = ws.relOf(newUri)?.replace(/\.kick$/, "");
|
|
1239
|
+
if (from === void 0 || to === void 0 || from === to) return void 0;
|
|
1240
|
+
const renamed = /* @__PURE__ */ new Map();
|
|
1241
|
+
for (const name of ws.names()) if (name === from) renamed.set(name, to);
|
|
1242
|
+
else if (name.startsWith(`${from}/`)) renamed.set(name, to + name.slice(from.length));
|
|
1243
|
+
if (!renamed.size) return void 0;
|
|
1244
|
+
const changes = {};
|
|
1245
|
+
const dir = ws.componentsDir();
|
|
1246
|
+
for (const [oldName, newName] of renamed) {
|
|
1247
|
+
const comp = oldName.startsWith(`${dir}/`);
|
|
1248
|
+
const newTag = newName.startsWith(`${dir}/`) ? ws.view.componentTag(newName, dir) : void 0;
|
|
1249
|
+
const targets = [[{
|
|
1250
|
+
kind: "layout",
|
|
1251
|
+
name: oldName
|
|
1252
|
+
}, newName]];
|
|
1253
|
+
if (comp && newTag) targets.push([{
|
|
1254
|
+
kind: "component",
|
|
1255
|
+
name: oldName,
|
|
1256
|
+
tag: ws.view.componentTag(oldName, dir)
|
|
1257
|
+
}, newTag]);
|
|
1258
|
+
for (const [target, newText] of targets) for (const u of usesOf(ws, target)) (changes[u.uri] ??= []).push({
|
|
1259
|
+
range: rangeOf(u.text, u.offset, u.length),
|
|
1260
|
+
newText
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
const pages = [...renamed].filter(([n]) => !n.startsWith(`${dir}/`));
|
|
1264
|
+
if (pages.length) {
|
|
1265
|
+
const to = new Map(pages);
|
|
1266
|
+
for (const script of await ws.scripts()) for (const c of calls(script.text)) {
|
|
1267
|
+
const newName = to.get(c.name);
|
|
1268
|
+
if (newName !== void 0) (changes[script.uri] ??= []).push({
|
|
1269
|
+
range: rangeOf(script.text, c.offset, c.name.length),
|
|
1270
|
+
newText: newName
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
return Object.keys(changes).length ? { changes } : void 0;
|
|
1275
|
+
}
|
|
1276
|
+
//#endregion
|
|
721
1277
|
//#region src/plugin/index.ts
|
|
722
1278
|
function createKickServicePlugin(ws) {
|
|
723
1279
|
const diagnostics = createDiagnosticsPlugin(ws);
|
|
@@ -735,7 +1291,14 @@ function createKickServicePlugin(ws) {
|
|
|
735
1291
|
] },
|
|
736
1292
|
hoverProvider: true,
|
|
737
1293
|
definitionProvider: true,
|
|
738
|
-
documentFormattingProvider: true
|
|
1294
|
+
documentFormattingProvider: true,
|
|
1295
|
+
semanticTokensProvider: { legend: {
|
|
1296
|
+
tokenTypes: [...TOKEN_TYPES],
|
|
1297
|
+
tokenModifiers: []
|
|
1298
|
+
} },
|
|
1299
|
+
referencesProvider: true,
|
|
1300
|
+
renameProvider: { prepareProvider: true },
|
|
1301
|
+
fileRenameEditsProvider: true
|
|
739
1302
|
},
|
|
740
1303
|
create(context) {
|
|
741
1304
|
/** The source file's uri and text, with the workspace synced to the buffer. */
|
|
@@ -752,6 +1315,13 @@ function createKickServicePlugin(ws) {
|
|
|
752
1315
|
return {
|
|
753
1316
|
...diagnostics.create(context),
|
|
754
1317
|
provideCompletionItems(document, position) {
|
|
1318
|
+
if (document.languageId === "kick-render") {
|
|
1319
|
+
const items = renderCompletions(ws, document.getText(), document.offsetAt(position));
|
|
1320
|
+
return items && {
|
|
1321
|
+
isIncomplete: false,
|
|
1322
|
+
items
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
755
1325
|
const d = open(document);
|
|
756
1326
|
if (!d) return void 0;
|
|
757
1327
|
const items = completions(ws, d.uri, d.text, document.offsetAt(position));
|
|
@@ -765,10 +1335,51 @@ function createKickServicePlugin(ws) {
|
|
|
765
1335
|
return d ? hover(ws, d.uri, d.text, document.offsetAt(position)) ?? void 0 : void 0;
|
|
766
1336
|
},
|
|
767
1337
|
provideDefinition(document, position) {
|
|
1338
|
+
if (document.languageId === "kick-render") {
|
|
1339
|
+
const link = renderDefinition(ws, document.getText(), document.offsetAt(position));
|
|
1340
|
+
return link ? [link] : void 0;
|
|
1341
|
+
}
|
|
768
1342
|
const d = open(document);
|
|
769
1343
|
const link = d && definition(ws, d.uri, d.text, document.offsetAt(position));
|
|
770
1344
|
return link ? [link] : void 0;
|
|
771
1345
|
},
|
|
1346
|
+
provideReferences(document, position, context) {
|
|
1347
|
+
const d = open(document);
|
|
1348
|
+
return d ? references(ws, d.text, document.offsetAt(position), context.includeDeclaration) : void 0;
|
|
1349
|
+
},
|
|
1350
|
+
provideRenameRange(document, position) {
|
|
1351
|
+
const d = open(document);
|
|
1352
|
+
return d ? renameRange(ws, d.text, document.offsetAt(position)) : void 0;
|
|
1353
|
+
},
|
|
1354
|
+
provideRenameEdits(document, position, newName) {
|
|
1355
|
+
const d = open(document);
|
|
1356
|
+
return d ? renameEdits(ws, d.text, document.offsetAt(position), newName) : void 0;
|
|
1357
|
+
},
|
|
1358
|
+
provideFileRenameEdits(oldUri, newUri) {
|
|
1359
|
+
return fileRenameEdits(ws, oldUri.toString(), newUri.toString());
|
|
1360
|
+
},
|
|
1361
|
+
provideDocumentSemanticTokens(document, range, legend) {
|
|
1362
|
+
const d = open(document);
|
|
1363
|
+
if (!d) return void 0;
|
|
1364
|
+
const tokens = [];
|
|
1365
|
+
for (const t of kickTokens(d.text, (tag) => componentOf(ws, tag) !== void 0)) {
|
|
1366
|
+
const type = legend.tokenTypes.indexOf(t.type);
|
|
1367
|
+
if (type < 0) continue;
|
|
1368
|
+
let at = t.offset;
|
|
1369
|
+
for (const part of d.text.slice(t.offset, t.offset + t.length).split("\n")) {
|
|
1370
|
+
const p = document.positionAt(at);
|
|
1371
|
+
if (part && p.line >= range.start.line && p.line <= range.end.line) tokens.push([
|
|
1372
|
+
p.line,
|
|
1373
|
+
p.character,
|
|
1374
|
+
part.length,
|
|
1375
|
+
type,
|
|
1376
|
+
0
|
|
1377
|
+
]);
|
|
1378
|
+
at += part.length + 1;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
return tokens;
|
|
1382
|
+
},
|
|
772
1383
|
provideDocumentFormattingEdits(document, _range, options) {
|
|
773
1384
|
const d = open(document);
|
|
774
1385
|
if (!d) return void 0;
|
|
@@ -952,6 +1563,11 @@ var Workspace = class {
|
|
|
952
1563
|
if (rel.startsWith("..") || !rel.endsWith(".kick")) return void 0;
|
|
953
1564
|
return this.opts.view.templateName(rel.slice(0, -5));
|
|
954
1565
|
}
|
|
1566
|
+
/** `uri`'s path inside the views folder (`layouts/main.kick`, `layouts`); `undefined` outside it. */
|
|
1567
|
+
relOf(uri) {
|
|
1568
|
+
const rel = relative(this.opts.viewsDir, URI.parse(uri).fsPath).split(sep).join(posix.sep);
|
|
1569
|
+
return rel.startsWith("..") || isAbsolute(rel) ? void 0 : rel;
|
|
1570
|
+
}
|
|
955
1571
|
nameOf(uri) {
|
|
956
1572
|
return this.nameOfPath(URI.parse(uri).fsPath);
|
|
957
1573
|
}
|
|
@@ -1069,6 +1685,7 @@ var Workspace = class {
|
|
|
1069
1685
|
}
|
|
1070
1686
|
/** Templates that use `name` as a component or a layout — a layout even after it's gone. */
|
|
1071
1687
|
dependentsOf(name) {
|
|
1688
|
+
if (this.configuredLayouts().has(name)) return new Set(this.names().filter((n) => n !== name));
|
|
1072
1689
|
const out = /* @__PURE__ */ new Set();
|
|
1073
1690
|
const tag = name.startsWith(`${this.opts.componentsDir}/`) ? this.opts.view.componentTag(name, this.opts.componentsDir) : void 0;
|
|
1074
1691
|
for (const [n, e] of this.entries) {
|
|
@@ -1091,9 +1708,38 @@ var Workspace = class {
|
|
|
1091
1708
|
return {
|
|
1092
1709
|
templates: new Map([...this.entries].map(([n, e]) => [n, e.ast])),
|
|
1093
1710
|
pipes: new Set(this.pipeNames()),
|
|
1094
|
-
componentsDir: this.opts.componentsDir
|
|
1711
|
+
componentsDir: this.opts.componentsDir,
|
|
1712
|
+
layouts: this.opts.layout,
|
|
1713
|
+
baseLayout: this.opts.baseLayout
|
|
1095
1714
|
};
|
|
1096
1715
|
}
|
|
1716
|
+
/** Templates that are layouts by configuration: the mapping's targets and the base layout. */
|
|
1717
|
+
configuredLayouts() {
|
|
1718
|
+
const { layout, baseLayout } = this.opts;
|
|
1719
|
+
const out = new Set(typeof layout === "string" ? [layout] : Object.values(layout ?? {}));
|
|
1720
|
+
if (baseLayout) out.add(baseLayout);
|
|
1721
|
+
return out;
|
|
1722
|
+
}
|
|
1723
|
+
scripts() {
|
|
1724
|
+
return this.opts.scripts?.() ?? Promise.resolve([]);
|
|
1725
|
+
}
|
|
1726
|
+
baseLayout() {
|
|
1727
|
+
return this.opts.baseLayout;
|
|
1728
|
+
}
|
|
1729
|
+
/**
|
|
1730
|
+
* The layout `name` renders in: its own `@layout`, else the configured
|
|
1731
|
+
* mapping's longest matching folder prefix (pages only, as `check` does).
|
|
1732
|
+
*/
|
|
1733
|
+
layoutOf(name) {
|
|
1734
|
+
const ast = this.astOf(name);
|
|
1735
|
+
if (!ast) return void 0;
|
|
1736
|
+
if (ast.layout !== void 0) return ast.layout ?? void 0;
|
|
1737
|
+
const { layout } = this.opts;
|
|
1738
|
+
if (layout === void 0 || name.startsWith(`${this.opts.componentsDir}/`) || this.configuredLayouts().has(name)) return void 0;
|
|
1739
|
+
if (typeof layout === "string") return layout;
|
|
1740
|
+
const hit = Object.keys(layout).toSorted((a, b) => b.length - a.length).find((p) => p === "" || name === p || name.startsWith(`${p}/`));
|
|
1741
|
+
return hit === void 0 ? void 0 : layout[hit];
|
|
1742
|
+
}
|
|
1097
1743
|
/** Parse errors, else everything `check` reports — as LSP diagnostics. */
|
|
1098
1744
|
diagnosticsFor(uri) {
|
|
1099
1745
|
const name = this.nameOf(uri);
|
|
@@ -1126,6 +1772,47 @@ async function listKick(dir) {
|
|
|
1126
1772
|
return [];
|
|
1127
1773
|
}
|
|
1128
1774
|
}
|
|
1775
|
+
const SKIP = /* @__PURE__ */ new Set([
|
|
1776
|
+
"node_modules",
|
|
1777
|
+
"dist",
|
|
1778
|
+
"build",
|
|
1779
|
+
"coverage",
|
|
1780
|
+
"out"
|
|
1781
|
+
]);
|
|
1782
|
+
const SCRIPT = /\.(?:[cm]?[jt]s|[jt]sx)$/;
|
|
1783
|
+
/** TS / JS files under `root`, for `ctx.render('…')` names: no dependencies, build output, dot folders or `.d.ts`. */
|
|
1784
|
+
async function listScripts(root) {
|
|
1785
|
+
const out = [];
|
|
1786
|
+
const walk = async (dir) => {
|
|
1787
|
+
let entries;
|
|
1788
|
+
try {
|
|
1789
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
1790
|
+
} catch {
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
for (const e of entries) {
|
|
1794
|
+
if (e.name.startsWith(".") || SKIP.has(e.name)) continue;
|
|
1795
|
+
const p = join(dir, e.name);
|
|
1796
|
+
if (e.isDirectory()) await walk(p);
|
|
1797
|
+
else if (SCRIPT.test(e.name) && !e.name.endsWith(".d.ts")) out.push(p);
|
|
1798
|
+
}
|
|
1799
|
+
};
|
|
1800
|
+
await walk(root);
|
|
1801
|
+
return out;
|
|
1802
|
+
}
|
|
1803
|
+
/** `fn` over `items` with at most `limit` running at once, results in order. */
|
|
1804
|
+
async function mapLimit(items, limit, fn) {
|
|
1805
|
+
const out = Array.from({ length: items.length });
|
|
1806
|
+
let next = 0;
|
|
1807
|
+
const worker = async () => {
|
|
1808
|
+
while (next < items.length) {
|
|
1809
|
+
const i = next++;
|
|
1810
|
+
out[i] = await fn(items[i]);
|
|
1811
|
+
}
|
|
1812
|
+
};
|
|
1813
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
1814
|
+
return out;
|
|
1815
|
+
}
|
|
1129
1816
|
/** What the server watches: every template, and the pipes file wherever it's configured. */
|
|
1130
1817
|
function watchPatterns(root, pipesFile) {
|
|
1131
1818
|
const patterns = ["**/*.kick", "**/pipes.{ts,js}"];
|
|
@@ -1155,14 +1842,23 @@ function startServer() {
|
|
|
1155
1842
|
pipesFile,
|
|
1156
1843
|
view,
|
|
1157
1844
|
read: (p) => readFile(p, "utf8").catch(() => void 0),
|
|
1158
|
-
list: listKick
|
|
1845
|
+
list: listKick,
|
|
1846
|
+
layout: opts.layout || void 0,
|
|
1847
|
+
baseLayout: opts.baseLayout || void 0,
|
|
1848
|
+
scripts: async () => mapLimit(await listScripts(root), 64, async (p) => {
|
|
1849
|
+
const uri = URI.file(p).toString();
|
|
1850
|
+
return {
|
|
1851
|
+
uri,
|
|
1852
|
+
text: server.documents.get(URI.parse(uri))?.getText() ?? await readFile(p, "utf8").catch(() => "")
|
|
1853
|
+
};
|
|
1854
|
+
})
|
|
1159
1855
|
});
|
|
1160
1856
|
await ws.load();
|
|
1161
1857
|
return server.initialize(params, createSimpleProject([kickLanguagePlugin]), [
|
|
1858
|
+
createKickServicePlugin(ws),
|
|
1162
1859
|
create$2(),
|
|
1163
1860
|
create(),
|
|
1164
|
-
create$1()
|
|
1165
|
-
createKickServicePlugin(ws)
|
|
1861
|
+
create$1()
|
|
1166
1862
|
]);
|
|
1167
1863
|
});
|
|
1168
1864
|
connection.onInitialized(() => {
|
|
@@ -1179,7 +1875,7 @@ function startServer() {
|
|
|
1179
1875
|
await server.languageFeatures.requestRefresh(false);
|
|
1180
1876
|
});
|
|
1181
1877
|
server.documents.onDidChangeContent(({ document }) => {
|
|
1182
|
-
if (document.
|
|
1878
|
+
if (document.uri.endsWith(".kick")) ws?.sync(document.uri, document.getText());
|
|
1183
1879
|
});
|
|
1184
1880
|
server.documents.onDidClose(async ({ document }) => {
|
|
1185
1881
|
if (!ws) return;
|
|
@@ -1191,9 +1887,17 @@ function startServer() {
|
|
|
1191
1887
|
const dir = `${ws.componentsDir()}/`;
|
|
1192
1888
|
return ws.names().filter((n) => !n.startsWith(dir));
|
|
1193
1889
|
});
|
|
1890
|
+
connection.onRequest("kick/renderDefinition", ({ text, offset }) => {
|
|
1891
|
+
const link = ws && renderDefinition(ws, text, offset);
|
|
1892
|
+
return link ? {
|
|
1893
|
+
uri: link.targetUri,
|
|
1894
|
+
range: link.originSelectionRange
|
|
1895
|
+
} : null;
|
|
1896
|
+
});
|
|
1897
|
+
connection.onRequest("kick/renderDiagnostics", ({ text }) => ws ? renderDiagnostics(ws, text) : []);
|
|
1194
1898
|
connection.onShutdown(() => server.shutdown());
|
|
1195
1899
|
}
|
|
1196
1900
|
//#endregion
|
|
1197
1901
|
export { KickVirtualCode as a, createKickServicePlugin as i, Workspace as n, kickLanguagePlugin as o, loadViewModule as r, startServer as t };
|
|
1198
1902
|
|
|
1199
|
-
//# sourceMappingURL=server-
|
|
1903
|
+
//# sourceMappingURL=server-pe6A1Wn-.js.map
|