@lokascript/language-server 2.11.1 → 3.1.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/CHANGELOG.md +8 -0
- package/README.md +20 -18
- package/dist/server.js +244 -148
- package/package.json +9 -11
- package/dist/dist-QIYYJ23V.js +0 -7889
- package/dist/server.d.ts +0 -1
package/dist/server.js
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
CompletionItemKind,
|
|
10
10
|
MarkupKind,
|
|
11
11
|
DiagnosticSeverity as DiagnosticSeverity2,
|
|
12
|
-
SymbolKind,
|
|
13
12
|
CodeActionKind,
|
|
14
13
|
DidChangeConfigurationNotification,
|
|
15
14
|
TextEdit
|
|
@@ -138,8 +137,6 @@ var LOKASCRIPT_ONLY_COMMANDS = [
|
|
|
138
137
|
"process",
|
|
139
138
|
// `process "<p>x</p>"` → Unexpected Token : process
|
|
140
139
|
// Added by Arc A step 4.1 — the omissions that produced NO diagnostic.
|
|
141
|
-
"async",
|
|
142
|
-
// `async do log "x" end` → Unexpected Token : async
|
|
143
140
|
"copy",
|
|
144
141
|
// `copy "x"` → Unexpected Token : copy
|
|
145
142
|
"push",
|
|
@@ -163,10 +160,11 @@ var LOKASCRIPT_ONLY_COMMANDS = [
|
|
|
163
160
|
var ALL_COMMANDS = [...HYPERSCRIPT_COMMANDS, ...LOKASCRIPT_ONLY_COMMANDS];
|
|
164
161
|
var LOKASCRIPT_SYNTAX_PATTERNS = {
|
|
165
162
|
/**
|
|
166
|
-
* Possessive dot notation
|
|
167
|
-
*
|
|
163
|
+
* Possessive dot notation on `its`: its.value
|
|
164
|
+
* Upstream resolves `my.x` and `your.x` (they are plain symbols), so only
|
|
165
|
+
* `its.` is non-portable; write `its value` for _hyperscript compatibility.
|
|
168
166
|
*/
|
|
169
|
-
dotNotation: /\
|
|
167
|
+
dotNotation: /\bits\.\w+/,
|
|
170
168
|
/**
|
|
171
169
|
* Optional chaining: my?.value
|
|
172
170
|
*/
|
|
@@ -174,17 +172,49 @@ var LOKASCRIPT_SYNTAX_PATTERNS = {
|
|
|
174
172
|
/**
|
|
175
173
|
* Extended 'as' conversions
|
|
176
174
|
*/
|
|
177
|
-
extendedAsConversion: /\bas\s+(
|
|
175
|
+
extendedAsConversion: /\bas\s+(Math|Values)\b/i,
|
|
178
176
|
/**
|
|
179
177
|
* Debounce/throttle modifiers with duration: .debounce(300), .throttle(1s)
|
|
180
178
|
*/
|
|
181
179
|
temporalModifiers: /\.(debounce|throttle)\s*\(\s*\d+/i
|
|
182
180
|
};
|
|
181
|
+
function blankStringsAndComments(code) {
|
|
182
|
+
let out = "";
|
|
183
|
+
let i = 0;
|
|
184
|
+
while (i < code.length) {
|
|
185
|
+
const ch = code[i];
|
|
186
|
+
if (ch === "-" && code[i + 1] === "-") {
|
|
187
|
+
const eol = code.indexOf("\n", i);
|
|
188
|
+
const end = eol === -1 ? code.length : eol;
|
|
189
|
+
out += " ".repeat(end - i);
|
|
190
|
+
i = end;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const prev = i === 0 ? " " : code[i - 1];
|
|
194
|
+
const opensString = ch === '"' || ch === "`" || ch === "'" && /[\s(,\[=]/.test(prev);
|
|
195
|
+
if (opensString) {
|
|
196
|
+
let j = i + 1;
|
|
197
|
+
while (j < code.length && code[j] !== ch) {
|
|
198
|
+
if (code[j] === "\\") j++;
|
|
199
|
+
if (code[j] === "\n" && ch !== "`") break;
|
|
200
|
+
j++;
|
|
201
|
+
}
|
|
202
|
+
const end = Math.min(j + 1, code.length);
|
|
203
|
+
out += " ".repeat(end - i);
|
|
204
|
+
i = end;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
out += ch;
|
|
208
|
+
i++;
|
|
209
|
+
}
|
|
210
|
+
return out;
|
|
211
|
+
}
|
|
183
212
|
function detectLokascriptFeatures(code) {
|
|
184
213
|
const detected = [];
|
|
214
|
+
const scannable = blankStringsAndComments(code);
|
|
185
215
|
for (const cmd of LOKASCRIPT_ONLY_COMMANDS) {
|
|
186
|
-
const pattern = new RegExp(
|
|
187
|
-
if (pattern.test(
|
|
216
|
+
const pattern = cmd === "unless" ? /(?<=^[ \t]*|\bthen\s+|\bend\s+|\belse\s+)unless(?![\w-])/im : new RegExp(`(?<![\\w.:$@^#-])${cmd}(?![\\w-])(?!\\s*\\()`, "i");
|
|
217
|
+
if (pattern.test(scannable)) {
|
|
188
218
|
detected.push({
|
|
189
219
|
feature: "command",
|
|
190
220
|
description: `'${cmd}' command is a LokaScript extension`,
|
|
@@ -228,6 +258,7 @@ function detectLokascriptFeatures(code) {
|
|
|
228
258
|
// src/extraction.ts
|
|
229
259
|
function isHtmlDocument(uri, content) {
|
|
230
260
|
if (uri.endsWith(".html") || uri.endsWith(".htm")) return true;
|
|
261
|
+
if (/\.(hs|_hs|hyperscript)$/i.test(uri)) return false;
|
|
231
262
|
if (content.trim().startsWith("<!DOCTYPE") || content.trim().startsWith("<html")) return true;
|
|
232
263
|
if (/<\w+[^>]*>/.test(content) && !content.trim().startsWith("on ")) return true;
|
|
233
264
|
return false;
|
|
@@ -378,6 +409,9 @@ function getWordAtPosition(line, character) {
|
|
|
378
409
|
if (start === end) return null;
|
|
379
410
|
return { text: line.slice(start, end), start, end };
|
|
380
411
|
}
|
|
412
|
+
function escapeRegExp(str) {
|
|
413
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
414
|
+
}
|
|
381
415
|
function findNextNonEmptyLine(lines, startIndex) {
|
|
382
416
|
for (let i = startIndex; i < lines.length; i++) {
|
|
383
417
|
const trimmed = lines[i].trim();
|
|
@@ -427,7 +461,7 @@ import { DiagnosticSeverity } from "vscode-languageserver/node.js";
|
|
|
427
461
|
function isPossessive(code, i) {
|
|
428
462
|
if (i === 0) return false;
|
|
429
463
|
const prev = code[i - 1];
|
|
430
|
-
if (!/[\w\-#.]/.test(prev)) return false;
|
|
464
|
+
if (!/[\w\-#.)\]>]/.test(prev)) return false;
|
|
431
465
|
if (i + 1 >= code.length || code[i + 1] !== "s") return false;
|
|
432
466
|
if (i + 2 >= code.length) return true;
|
|
433
467
|
const after = code[i + 2];
|
|
@@ -461,6 +495,13 @@ function runSimpleDiagnostics(code, _language = "en") {
|
|
|
461
495
|
col++;
|
|
462
496
|
continue;
|
|
463
497
|
}
|
|
498
|
+
if (ch === "-" && code[i + 1] === "-") {
|
|
499
|
+
const eol = code.indexOf("\n", i);
|
|
500
|
+
if (eol === -1) break;
|
|
501
|
+
col += eol - i;
|
|
502
|
+
i = eol - 1;
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
464
505
|
switch (ch) {
|
|
465
506
|
case "'":
|
|
466
507
|
if (isPossessive(code, i)) {
|
|
@@ -634,14 +675,15 @@ function buildSymbolTable(regions, getKeywordVariants2) {
|
|
|
634
675
|
const defaultKws = variants("default");
|
|
635
676
|
const installKws = variants("install");
|
|
636
677
|
const callKws = variants("call");
|
|
637
|
-
const
|
|
638
|
-
const
|
|
639
|
-
const
|
|
640
|
-
const
|
|
641
|
-
const
|
|
642
|
-
const
|
|
678
|
+
const kw = (words) => `(?<![\\p{L}\\p{N}_])(${words.map(escapeRegExp).join("|")})(?![\\p{L}\\p{N}_])`;
|
|
679
|
+
const behaviorDefPat = new RegExp(`${kw(behaviorKws)}\\s+(\\w+)`, "giu");
|
|
680
|
+
const funcDefPat = new RegExp(`${kw(defKws)}\\s+(\\w+)`, "giu");
|
|
681
|
+
const varDefPat = new RegExp(`${kw([...setKws, ...defaultKws])}\\s+([:$]\\w+)`, "giu");
|
|
682
|
+
const installPat = new RegExp(`${kw(installKws)}\\s+(\\w+)`, "giu");
|
|
683
|
+
const callPat = new RegExp(`${kw(callKws)}\\s+(\\w+)`, "giu");
|
|
684
|
+
const varRefPat = /(?<![\p{L}\p{N}_)\]\-])[:$]\w+/gu;
|
|
643
685
|
const funcCallPat = /\b(\w+)\s*\(/g;
|
|
644
|
-
const caretWritePat = new RegExp(
|
|
686
|
+
const caretWritePat = new RegExp(`${kw(setKws)}\\s+\\^([A-Za-z_]\\w*)`, "giu");
|
|
645
687
|
const caretRefPat = /\^([A-Za-z_]\w*)/g;
|
|
646
688
|
const attrsRefPat = /\battrs\.([A-Za-z_]\w*)/g;
|
|
647
689
|
function getOrCreate(name, kind, scope) {
|
|
@@ -664,6 +706,11 @@ function buildSymbolTable(regions, getKeywordVariants2) {
|
|
|
664
706
|
character: toDocChar(region, localLine, localChar),
|
|
665
707
|
length
|
|
666
708
|
};
|
|
709
|
+
if (locationIndex.some(
|
|
710
|
+
(l) => l.name === entry.name && l.line === loc.line && l.start === loc.character
|
|
711
|
+
)) {
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
667
714
|
if (kind === "def") {
|
|
668
715
|
entry.definitions.push(loc);
|
|
669
716
|
} else {
|
|
@@ -844,7 +891,6 @@ function buildSymbolTable(regions, getKeywordVariants2) {
|
|
|
844
891
|
"def",
|
|
845
892
|
"install",
|
|
846
893
|
"init",
|
|
847
|
-
"async",
|
|
848
894
|
...behaviorKws,
|
|
849
895
|
...defKws,
|
|
850
896
|
...setKws,
|
|
@@ -884,6 +930,82 @@ function getSymbolTable(uri, version, regions, getKeywordVariants2) {
|
|
|
884
930
|
return table;
|
|
885
931
|
}
|
|
886
932
|
|
|
933
|
+
// src/completion-context.ts
|
|
934
|
+
function inferContext(beforeCursor) {
|
|
935
|
+
if (/\^\w*$/.test(beforeCursor)) return "caret";
|
|
936
|
+
if (/\battrs\.\w*$/.test(beforeCursor)) return "attrs-property";
|
|
937
|
+
const trimmed = beforeCursor.trim();
|
|
938
|
+
if (/\bon\s*$/.test(trimmed)) return "event";
|
|
939
|
+
if (/\bthen\s*$/.test(trimmed)) return "command";
|
|
940
|
+
if (/^(on\s+\w+\s*)$/.test(trimmed)) return "command";
|
|
941
|
+
if (/(to|from|into|on)\s*$/.test(trimmed)) return "selector";
|
|
942
|
+
if (/\bif\s*$/.test(trimmed)) return "expression";
|
|
943
|
+
if (/\bset\s+:\w+\s+to\s*$/.test(trimmed)) return "expression";
|
|
944
|
+
return "default";
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// src/document-symbols.ts
|
|
948
|
+
import { SymbolKind } from "vscode-languageserver/node.js";
|
|
949
|
+
var COMMAND_POSITION = "(?<=^[ \\t]*|(?<![\\p{L}\\p{N}_])end\\s+)";
|
|
950
|
+
var NOT_WORD_AFTER = "(?![\\p{L}\\p{N}_])";
|
|
951
|
+
var NAME = "([\\p{L}\\p{N}_]+)";
|
|
952
|
+
function alternation(words) {
|
|
953
|
+
return `(${words.map(escapeRegExp).join("|")})`;
|
|
954
|
+
}
|
|
955
|
+
function symbol(name, detail, kind, line, start, length) {
|
|
956
|
+
const range = {
|
|
957
|
+
start: { line, character: start },
|
|
958
|
+
end: { line, character: start + length }
|
|
959
|
+
};
|
|
960
|
+
return { name, detail, kind, range, selectionRange: range };
|
|
961
|
+
}
|
|
962
|
+
function extractDocumentSymbols(code, getVariants = (eng) => [eng]) {
|
|
963
|
+
const symbols = [];
|
|
964
|
+
const lines = code.split("\n");
|
|
965
|
+
const onPattern = new RegExp(
|
|
966
|
+
`${COMMAND_POSITION}${alternation(getVariants("on"))}\\s+(${NAME.slice(1, -1)}(?::[\\p{L}\\p{N}_]+)?(?:\\[.*?\\])?)`,
|
|
967
|
+
"giu"
|
|
968
|
+
);
|
|
969
|
+
const behaviorPattern = new RegExp(
|
|
970
|
+
`${COMMAND_POSITION}${alternation(getVariants("behavior"))}\\s+${NAME}`,
|
|
971
|
+
"giu"
|
|
972
|
+
);
|
|
973
|
+
const defPattern = new RegExp(
|
|
974
|
+
`${COMMAND_POSITION}${alternation(getVariants("def"))}\\s+${NAME}`,
|
|
975
|
+
"giu"
|
|
976
|
+
);
|
|
977
|
+
const initPattern = new RegExp(
|
|
978
|
+
`${COMMAND_POSITION}${alternation(getVariants("init"))}${NOT_WORD_AFTER}`,
|
|
979
|
+
"giu"
|
|
980
|
+
);
|
|
981
|
+
for (let i = 0; i < lines.length; i++) {
|
|
982
|
+
const line = lines[i];
|
|
983
|
+
for (const m of line.matchAll(onPattern)) {
|
|
984
|
+
const at = m.index ?? 0;
|
|
985
|
+
symbols.push(
|
|
986
|
+
symbol(`${m[1]} ${m[2]}`, "Event Handler", SymbolKind.Event, i, at, m[0].length)
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
for (const m of line.matchAll(behaviorPattern)) {
|
|
990
|
+
const at = m.index ?? 0;
|
|
991
|
+
symbols.push(
|
|
992
|
+
symbol(`${m[1]} ${m[2]}`, "Behavior Definition", SymbolKind.Class, i, at, m[0].length)
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
for (const m of line.matchAll(defPattern)) {
|
|
996
|
+
const at = m.index ?? 0;
|
|
997
|
+
symbols.push(
|
|
998
|
+
symbol(`${m[1]} ${m[2]}`, "Function Definition", SymbolKind.Function, i, at, m[0].length)
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
for (const m of line.matchAll(initPattern)) {
|
|
1002
|
+
const at = m.index ?? 0;
|
|
1003
|
+
symbols.push(symbol(m[1], "Initialization", SymbolKind.Constructor, i, at, m[1].length));
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return symbols;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
887
1009
|
// src/localized-descriptions.ts
|
|
888
1010
|
var DESCRIPTIONS = {
|
|
889
1011
|
en: {
|
|
@@ -1239,7 +1361,18 @@ var lspMetadata = null;
|
|
|
1239
1361
|
try {
|
|
1240
1362
|
const core = await import("@hyperfixi/core");
|
|
1241
1363
|
parseFunction = core.parse;
|
|
1242
|
-
|
|
1364
|
+
let inferRoles = null;
|
|
1365
|
+
try {
|
|
1366
|
+
({ schemaRoleInferrer: inferRoles } = await import("@hyperfixi/core/multilingual"));
|
|
1367
|
+
} catch {
|
|
1368
|
+
console.error(
|
|
1369
|
+
"[lokascript-ls] @hyperfixi/core/multilingual not available \u2014 hover shows roles for `set` and `go` only. Install a core build that ships the multilingual entry."
|
|
1370
|
+
);
|
|
1371
|
+
}
|
|
1372
|
+
fromCoreASTFn = (node) => core.fromCoreAST(
|
|
1373
|
+
node,
|
|
1374
|
+
inferRoles ? { inferRoles } : void 0
|
|
1375
|
+
);
|
|
1243
1376
|
interchangeLSP = await import("@hyperfixi/core/ast-utils");
|
|
1244
1377
|
console.error("[lokascript-ls] @hyperfixi/core loaded \u2014 AST parsing + interchange LSP enabled");
|
|
1245
1378
|
} catch {
|
|
@@ -1254,7 +1387,7 @@ try {
|
|
|
1254
1387
|
}
|
|
1255
1388
|
var frameworkIR = null;
|
|
1256
1389
|
try {
|
|
1257
|
-
const fw = await import("
|
|
1390
|
+
const fw = await import("@lokascript/framework");
|
|
1258
1391
|
frameworkIR = { fromInterchangeNode: fw.fromInterchangeNode, renderExplicit: fw.renderExplicit };
|
|
1259
1392
|
console.error(
|
|
1260
1393
|
"[lokascript-ls] @lokascript/framework loaded \u2014 LSE bracket notation in hover enabled"
|
|
@@ -1478,11 +1611,6 @@ var FALLBACK_HOVER_DOCS = {
|
|
|
1478
1611
|
description: "Closes a block (event handler, behavior, if, repeat).",
|
|
1479
1612
|
example: "on click\\n toggle .active\\nend"
|
|
1480
1613
|
},
|
|
1481
|
-
async: {
|
|
1482
|
-
title: "async",
|
|
1483
|
-
description: "Runs a command asynchronously without blocking.",
|
|
1484
|
-
example: "async fetch /api/data"
|
|
1485
|
-
},
|
|
1486
1614
|
tell: {
|
|
1487
1615
|
title: "tell",
|
|
1488
1616
|
description: "Sets the default target for subsequent commands.",
|
|
@@ -1561,8 +1689,60 @@ function getSemanticAnalyzer() {
|
|
|
1561
1689
|
}
|
|
1562
1690
|
return cachedAnalyzer;
|
|
1563
1691
|
}
|
|
1564
|
-
var
|
|
1565
|
-
|
|
1692
|
+
var VALID_MODES = /* @__PURE__ */ new Set([
|
|
1693
|
+
"hyperscript",
|
|
1694
|
+
"hyperscript-i18n",
|
|
1695
|
+
"lokascript",
|
|
1696
|
+
"auto"
|
|
1697
|
+
]);
|
|
1698
|
+
function baseSettings() {
|
|
1699
|
+
return envDefaultMode ? { ...defaultSettings, mode: envDefaultMode } : { ...defaultSettings };
|
|
1700
|
+
}
|
|
1701
|
+
function isRecord(value) {
|
|
1702
|
+
return typeof value === "object" && value !== null;
|
|
1703
|
+
}
|
|
1704
|
+
function mergeSettings(base, partial) {
|
|
1705
|
+
const out = { ...base };
|
|
1706
|
+
if (!isRecord(partial)) return out;
|
|
1707
|
+
const p = partial;
|
|
1708
|
+
if (typeof p.mode === "string" && VALID_MODES.has(p.mode)) out.mode = p.mode;
|
|
1709
|
+
if (typeof p.language === "string" && p.language.trim()) out.language = p.language.trim();
|
|
1710
|
+
if (typeof p.maxDiagnostics === "number" && p.maxDiagnostics > 0) {
|
|
1711
|
+
out.maxDiagnostics = Math.floor(p.maxDiagnostics);
|
|
1712
|
+
}
|
|
1713
|
+
return out;
|
|
1714
|
+
}
|
|
1715
|
+
var globalSettings = baseSettings();
|
|
1716
|
+
var hasConfigurationCapability = false;
|
|
1717
|
+
var configSections = envDefaultMode ? ["hyperscript", "lokascript"] : ["lokascript", "hyperscript"];
|
|
1718
|
+
function applySettings(next) {
|
|
1719
|
+
globalSettings = next;
|
|
1720
|
+
const previousMode = resolvedMode;
|
|
1721
|
+
resolvedMode = resolveMode(globalSettings);
|
|
1722
|
+
if (previousMode !== resolvedMode) {
|
|
1723
|
+
connection.console.log(`Mode changed to '${resolvedMode}' (${getBranding(resolvedMode)})`);
|
|
1724
|
+
}
|
|
1725
|
+
translationCache = null;
|
|
1726
|
+
reverseKeywordCache = null;
|
|
1727
|
+
documents.all().forEach(validateDocument);
|
|
1728
|
+
}
|
|
1729
|
+
async function pullConfiguration() {
|
|
1730
|
+
if (!hasConfigurationCapability) return;
|
|
1731
|
+
try {
|
|
1732
|
+
const items = await connection.workspace.getConfiguration(
|
|
1733
|
+
configSections.map((section) => ({ section }))
|
|
1734
|
+
);
|
|
1735
|
+
const [primary, fallback] = Array.isArray(items) ? items : [];
|
|
1736
|
+
let merged = mergeSettings(baseSettings(), fallback);
|
|
1737
|
+
merged = mergeSettings(merged, primary);
|
|
1738
|
+
applySettings(merged);
|
|
1739
|
+
} catch (e) {
|
|
1740
|
+
connection.console.log(`[lokascript-ls] workspace/configuration failed: ${e}`);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
connection.onInitialize((params) => {
|
|
1744
|
+
hasConfigurationCapability = !!params.capabilities.workspace?.configuration;
|
|
1745
|
+
globalSettings = mergeSettings(baseSettings(), params.initializationOptions);
|
|
1566
1746
|
resolvedMode = resolveMode(globalSettings);
|
|
1567
1747
|
const brand = getBranding(resolvedMode);
|
|
1568
1748
|
connection.console.log(`${brand} Language Server initializing (mode: ${resolvedMode})...`);
|
|
@@ -1589,19 +1769,20 @@ connection.onInitialized(() => {
|
|
|
1589
1769
|
connection.client.register(DidChangeConfigurationNotification.type, void 0);
|
|
1590
1770
|
const brand = getBranding(resolvedMode);
|
|
1591
1771
|
connection.console.log(`${brand} Language Server initialized`);
|
|
1772
|
+
void pullConfiguration();
|
|
1592
1773
|
});
|
|
1593
1774
|
connection.onDidChangeConfiguration((change) => {
|
|
1594
|
-
const
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
const brand = getBranding(resolvedMode);
|
|
1600
|
-
connection.console.log(`Mode changed to '${resolvedMode}' (${brand})`);
|
|
1775
|
+
const settings = change.settings;
|
|
1776
|
+
const pushed = isRecord(settings) ? settings[configSections[0]] ?? settings[configSections[1]] : void 0;
|
|
1777
|
+
if (pushed !== void 0) {
|
|
1778
|
+
applySettings(mergeSettings(baseSettings(), pushed));
|
|
1779
|
+
return;
|
|
1601
1780
|
}
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1781
|
+
if (hasConfigurationCapability) {
|
|
1782
|
+
void pullConfiguration();
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
applySettings(baseSettings());
|
|
1605
1786
|
});
|
|
1606
1787
|
documents.onDidChangeContent((change) => {
|
|
1607
1788
|
validateDocument(change.document);
|
|
@@ -1657,6 +1838,7 @@ async function getDiagnostics(code, language) {
|
|
|
1657
1838
|
}
|
|
1658
1839
|
}
|
|
1659
1840
|
const analyzer = isMultilingualEnabled(resolvedMode) ? getSemanticAnalyzer() : null;
|
|
1841
|
+
let semanticParsedForeign = false;
|
|
1660
1842
|
if (analyzer) {
|
|
1661
1843
|
try {
|
|
1662
1844
|
let result = analyzer.analyze(code, language);
|
|
@@ -1703,6 +1885,7 @@ async function getDiagnostics(code, language) {
|
|
|
1703
1885
|
message: `Code could not be fully parsed (${(result.confidence * 100).toFixed(0)}% confidence)`
|
|
1704
1886
|
});
|
|
1705
1887
|
}
|
|
1888
|
+
semanticParsedForeign = result.confidence >= 0.5 && usedLanguage !== "en";
|
|
1706
1889
|
if (result.confidence >= 0.5 && result.command) {
|
|
1707
1890
|
const cmd = result.command.name;
|
|
1708
1891
|
const roles = result.command.roles;
|
|
@@ -1732,7 +1915,7 @@ async function getDiagnostics(code, language) {
|
|
|
1732
1915
|
if (parseFunction && fromCoreASTFn) {
|
|
1733
1916
|
try {
|
|
1734
1917
|
const parseResult = parseFunction(code);
|
|
1735
|
-
if (parseResult.errors?.length) {
|
|
1918
|
+
if (parseResult.errors?.length && !semanticParsedForeign) {
|
|
1736
1919
|
for (const err of parseResult.errors) {
|
|
1737
1920
|
diagnostics.push({
|
|
1738
1921
|
range: {
|
|
@@ -1763,16 +1946,18 @@ async function getDiagnostics(code, language) {
|
|
|
1763
1946
|
}
|
|
1764
1947
|
}
|
|
1765
1948
|
} catch (parseError) {
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1949
|
+
if (!semanticParsedForeign) {
|
|
1950
|
+
diagnostics.push({
|
|
1951
|
+
range: {
|
|
1952
|
+
start: { line: 0, character: 0 },
|
|
1953
|
+
end: { line: 0, character: Math.min(code.length, 80) }
|
|
1954
|
+
},
|
|
1955
|
+
severity: DiagnosticSeverity2.Error,
|
|
1956
|
+
code: "parse-error",
|
|
1957
|
+
source: brand,
|
|
1958
|
+
message: parseError.message || "Failed to parse"
|
|
1959
|
+
});
|
|
1960
|
+
}
|
|
1776
1961
|
}
|
|
1777
1962
|
}
|
|
1778
1963
|
if (diagnostics.length === 0) {
|
|
@@ -1904,18 +2089,6 @@ function getAttrsCompletions() {
|
|
|
1904
2089
|
}
|
|
1905
2090
|
];
|
|
1906
2091
|
}
|
|
1907
|
-
function inferContext(beforeCursor) {
|
|
1908
|
-
if (/\^\w*$/.test(beforeCursor)) return "caret";
|
|
1909
|
-
if (/\battrs\.\w*$/.test(beforeCursor)) return "attrs-property";
|
|
1910
|
-
const trimmed = beforeCursor.trim();
|
|
1911
|
-
if (/\bon\s*$/.test(trimmed)) return "event";
|
|
1912
|
-
if (/\bthen\s*$/.test(trimmed)) return "command";
|
|
1913
|
-
if (/^(on\s+\w+\s*)$/.test(trimmed)) return "command";
|
|
1914
|
-
if (/(to|from|into|on)\s*$/.test(trimmed)) return "selector";
|
|
1915
|
-
if (/\bif\s*$/.test(trimmed)) return "expression";
|
|
1916
|
-
if (/\bset\s+:\w+\s+to\s*$/.test(trimmed)) return "expression";
|
|
1917
|
-
return "default";
|
|
1918
|
-
}
|
|
1919
2092
|
function getContextualCompletions(context, language) {
|
|
1920
2093
|
const completions = [];
|
|
1921
2094
|
const effectiveLanguage = isMultilingualEnabled(resolvedMode) ? language : "en";
|
|
@@ -2548,27 +2721,27 @@ connection.onDocumentSymbol((params) => {
|
|
|
2548
2721
|
const allSymbols = [];
|
|
2549
2722
|
for (const region of regions) {
|
|
2550
2723
|
const symbols = extractSymbols(region.code, language);
|
|
2551
|
-
for (const
|
|
2724
|
+
for (const symbol2 of symbols) {
|
|
2552
2725
|
allSymbols.push({
|
|
2553
|
-
...
|
|
2726
|
+
...symbol2,
|
|
2554
2727
|
range: {
|
|
2555
2728
|
start: {
|
|
2556
|
-
line: region.startLine +
|
|
2557
|
-
character:
|
|
2729
|
+
line: region.startLine + symbol2.range.start.line,
|
|
2730
|
+
character: symbol2.range.start.line === 0 ? region.startChar + symbol2.range.start.character : symbol2.range.start.character
|
|
2558
2731
|
},
|
|
2559
2732
|
end: {
|
|
2560
|
-
line: region.startLine +
|
|
2561
|
-
character:
|
|
2733
|
+
line: region.startLine + symbol2.range.end.line,
|
|
2734
|
+
character: symbol2.range.end.line === 0 ? region.startChar + symbol2.range.end.character : symbol2.range.end.character
|
|
2562
2735
|
}
|
|
2563
2736
|
},
|
|
2564
2737
|
selectionRange: {
|
|
2565
2738
|
start: {
|
|
2566
|
-
line: region.startLine +
|
|
2567
|
-
character:
|
|
2739
|
+
line: region.startLine + symbol2.selectionRange.start.line,
|
|
2740
|
+
character: symbol2.selectionRange.start.line === 0 ? region.startChar + symbol2.selectionRange.start.character : symbol2.selectionRange.start.character
|
|
2568
2741
|
},
|
|
2569
2742
|
end: {
|
|
2570
|
-
line: region.startLine +
|
|
2571
|
-
character:
|
|
2743
|
+
line: region.startLine + symbol2.selectionRange.end.line,
|
|
2744
|
+
character: symbol2.selectionRange.end.line === 0 ? region.startChar + symbol2.selectionRange.end.character : symbol2.selectionRange.end.character
|
|
2572
2745
|
}
|
|
2573
2746
|
}
|
|
2574
2747
|
});
|
|
@@ -2580,8 +2753,6 @@ connection.onDocumentSymbol((params) => {
|
|
|
2580
2753
|
}
|
|
2581
2754
|
});
|
|
2582
2755
|
function extractSymbols(code, language) {
|
|
2583
|
-
const symbols = [];
|
|
2584
|
-
const lines = code.split("\n");
|
|
2585
2756
|
const getVariants = (eng) => {
|
|
2586
2757
|
const variants = [eng];
|
|
2587
2758
|
if (!semanticPackage || language === "en") return variants;
|
|
@@ -2599,82 +2770,7 @@ function extractSymbols(code, language) {
|
|
|
2599
2770
|
}
|
|
2600
2771
|
return variants;
|
|
2601
2772
|
};
|
|
2602
|
-
|
|
2603
|
-
const behaviorVariants = getVariants("behavior");
|
|
2604
|
-
const defVariants = getVariants("def");
|
|
2605
|
-
const initVariants = getVariants("init");
|
|
2606
|
-
const onPattern = new RegExp(`\\b(${onVariants.join("|")})\\s+(\\w+(?:\\[.*?\\])?)`, "gi");
|
|
2607
|
-
const behaviorPattern = new RegExp(`\\b(${behaviorVariants.join("|")})\\s+(\\w+)`, "gi");
|
|
2608
|
-
const defPattern = new RegExp(`\\b(${defVariants.join("|")})\\s+(\\w+)`, "gi");
|
|
2609
|
-
const initPattern = new RegExp(`\\b(${initVariants.join("|")})\\b`, "gi");
|
|
2610
|
-
for (let i = 0; i < lines.length; i++) {
|
|
2611
|
-
const line = lines[i];
|
|
2612
|
-
for (const match of line.matchAll(onPattern)) {
|
|
2613
|
-
const matchIndex = match.index ?? 0;
|
|
2614
|
-
symbols.push({
|
|
2615
|
-
name: `${match[1]} ${match[2]}`,
|
|
2616
|
-
detail: "Event Handler",
|
|
2617
|
-
kind: SymbolKind.Event,
|
|
2618
|
-
range: {
|
|
2619
|
-
start: { line: i, character: matchIndex },
|
|
2620
|
-
end: { line: i, character: matchIndex + match[0].length }
|
|
2621
|
-
},
|
|
2622
|
-
selectionRange: {
|
|
2623
|
-
start: { line: i, character: matchIndex },
|
|
2624
|
-
end: { line: i, character: matchIndex + match[0].length }
|
|
2625
|
-
}
|
|
2626
|
-
});
|
|
2627
|
-
}
|
|
2628
|
-
for (const match of line.matchAll(behaviorPattern)) {
|
|
2629
|
-
const matchIndex = match.index ?? 0;
|
|
2630
|
-
symbols.push({
|
|
2631
|
-
name: `${match[1]} ${match[2]}`,
|
|
2632
|
-
detail: "Behavior Definition",
|
|
2633
|
-
kind: SymbolKind.Class,
|
|
2634
|
-
range: {
|
|
2635
|
-
start: { line: i, character: matchIndex },
|
|
2636
|
-
end: { line: i, character: matchIndex + match[0].length }
|
|
2637
|
-
},
|
|
2638
|
-
selectionRange: {
|
|
2639
|
-
start: { line: i, character: matchIndex },
|
|
2640
|
-
end: { line: i, character: matchIndex + match[0].length }
|
|
2641
|
-
}
|
|
2642
|
-
});
|
|
2643
|
-
}
|
|
2644
|
-
for (const match of line.matchAll(defPattern)) {
|
|
2645
|
-
const matchIndex = match.index ?? 0;
|
|
2646
|
-
symbols.push({
|
|
2647
|
-
name: `${match[1]} ${match[2]}`,
|
|
2648
|
-
detail: "Function Definition",
|
|
2649
|
-
kind: SymbolKind.Function,
|
|
2650
|
-
range: {
|
|
2651
|
-
start: { line: i, character: matchIndex },
|
|
2652
|
-
end: { line: i, character: matchIndex + match[0].length }
|
|
2653
|
-
},
|
|
2654
|
-
selectionRange: {
|
|
2655
|
-
start: { line: i, character: matchIndex },
|
|
2656
|
-
end: { line: i, character: matchIndex + match[0].length }
|
|
2657
|
-
}
|
|
2658
|
-
});
|
|
2659
|
-
}
|
|
2660
|
-
for (const match of line.matchAll(initPattern)) {
|
|
2661
|
-
const matchIndex = match.index ?? 0;
|
|
2662
|
-
symbols.push({
|
|
2663
|
-
name: match[1],
|
|
2664
|
-
detail: "Initialization",
|
|
2665
|
-
kind: SymbolKind.Constructor,
|
|
2666
|
-
range: {
|
|
2667
|
-
start: { line: i, character: matchIndex },
|
|
2668
|
-
end: { line: i, character: matchIndex + match[1].length }
|
|
2669
|
-
},
|
|
2670
|
-
selectionRange: {
|
|
2671
|
-
start: { line: i, character: matchIndex },
|
|
2672
|
-
end: { line: i, character: matchIndex + match[1].length }
|
|
2673
|
-
}
|
|
2674
|
-
});
|
|
2675
|
-
}
|
|
2676
|
-
}
|
|
2677
|
-
return symbols;
|
|
2773
|
+
return extractDocumentSymbols(code, getVariants);
|
|
2678
2774
|
}
|
|
2679
2775
|
connection.onCodeAction((params) => {
|
|
2680
2776
|
const diagnostics = params.context.diagnostics;
|
package/package.json
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lokascript/language-server",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Language Server Protocol implementation for LokaScript/hyperscript with
|
|
3
|
+
"version": "3.1.0",
|
|
4
|
+
"description": "Language Server Protocol implementation for LokaScript/hyperscript with 24 language support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/server.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"lokascript-language-server": "./dist/server.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"build": "tsup src/server.ts --format esm --
|
|
11
|
+
"build": "tsup src/server.ts --format esm --clean",
|
|
12
12
|
"start": "node dist/server.js --stdio",
|
|
13
13
|
"dev": "tsx src/server.ts --stdio",
|
|
14
14
|
"test": "vitest",
|
|
15
15
|
"typecheck": "tsc --noEmit",
|
|
16
|
-
"test:check": "VITEST_QUIET=1 bash ../../scripts/vitest-run.sh --reporter=dot"
|
|
16
|
+
"test:check": "npm run pretest --silent && VITEST_QUIET=1 bash ../../scripts/vitest-run.sh --reporter=dot",
|
|
17
|
+
"pretest": "bash ../../scripts/ensure-fresh.sh ../intent ../framework ../semantic ../core && npm run build --silent"
|
|
17
18
|
},
|
|
18
19
|
"keywords": [
|
|
19
20
|
"lsp",
|
|
@@ -27,24 +28,21 @@
|
|
|
27
28
|
"author": "LokaScript Contributors",
|
|
28
29
|
"license": "MIT",
|
|
29
30
|
"dependencies": {
|
|
31
|
+
"@lokascript/framework": "^3.1.0",
|
|
32
|
+
"@lokascript/semantic": "^3.1.0",
|
|
30
33
|
"vscode-languageserver": "^9.0.1",
|
|
31
34
|
"vscode-languageserver-textdocument": "^1.0.11"
|
|
32
35
|
},
|
|
33
36
|
"peerDependencies": {
|
|
34
|
-
"@hyperfixi/core": "^
|
|
35
|
-
"@lokascript/semantic": "^2.11.1"
|
|
37
|
+
"@hyperfixi/core": "^3.1.0"
|
|
36
38
|
},
|
|
37
39
|
"peerDependenciesMeta": {
|
|
38
|
-
"@lokascript/semantic": {
|
|
39
|
-
"optional": true
|
|
40
|
-
},
|
|
41
40
|
"@hyperfixi/core": {
|
|
42
41
|
"optional": true
|
|
43
42
|
}
|
|
44
43
|
},
|
|
45
44
|
"devDependencies": {
|
|
46
|
-
"@hyperfixi/core": "^
|
|
47
|
-
"@lokascript/semantic": "^2.11.1",
|
|
45
|
+
"@hyperfixi/core": "^3.1.0",
|
|
48
46
|
"@types/node": "^26.1.2",
|
|
49
47
|
"@vitest/coverage-v8": "^4.0.0",
|
|
50
48
|
"tsup": "^8.0.0",
|