@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/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: my.textContent, its.value, your.classList
167
- * Original _hyperscript uses space: my textContent
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: /\b(my|your|its)\.\w+/,
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+(Int|Integer|Float|JSON|FormData|Date|URLSearchParams|Set|Map)\b/i,
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(`\\b${cmd}\\b`, "i");
187
- if (pattern.test(code)) {
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 behaviorDefPat = new RegExp(`\\b(${behaviorKws.join("|")})\\s+(\\w+)`, "gi");
638
- const funcDefPat = new RegExp(`\\b(${defKws.join("|")})\\s+(\\w+)`, "gi");
639
- const varDefPat = new RegExp(`\\b(${[...setKws, ...defaultKws].join("|")})\\s+([:$]\\w+)`, "gi");
640
- const installPat = new RegExp(`\\b(${installKws.join("|")})\\s+(\\w+)`, "gi");
641
- const callPat = new RegExp(`\\b(${callKws.join("|")})\\s+(\\w+)`, "gi");
642
- const varRefPat = /[:$]\w+/g;
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(`\\b(${setKws.join("|")})\\s+\\^([A-Za-z_]\\w*)`, "gi");
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
- fromCoreASTFn = core.fromCoreAST;
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("./dist-QIYYJ23V.js");
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 globalSettings = envDefaultMode ? { ...defaultSettings, mode: envDefaultMode } : defaultSettings;
1565
- connection.onInitialize((_params) => {
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 envDefaults = envDefaultMode ? { ...defaultSettings, mode: envDefaultMode } : defaultSettings;
1595
- globalSettings = change.settings?.lokascript || change.settings?.hyperscript || envDefaults;
1596
- const previousMode = resolvedMode;
1597
- resolvedMode = resolveMode(globalSettings);
1598
- if (previousMode !== resolvedMode) {
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
- translationCache = null;
1603
- reverseKeywordCache = null;
1604
- documents.all().forEach(validateDocument);
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
- diagnostics.push({
1767
- range: {
1768
- start: { line: 0, character: 0 },
1769
- end: { line: 0, character: Math.min(code.length, 80) }
1770
- },
1771
- severity: DiagnosticSeverity2.Error,
1772
- code: "parse-error",
1773
- source: brand,
1774
- message: parseError.message || "Failed to parse"
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 symbol of symbols) {
2724
+ for (const symbol2 of symbols) {
2552
2725
  allSymbols.push({
2553
- ...symbol,
2726
+ ...symbol2,
2554
2727
  range: {
2555
2728
  start: {
2556
- line: region.startLine + symbol.range.start.line,
2557
- character: symbol.range.start.line === 0 ? region.startChar + symbol.range.start.character : symbol.range.start.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 + symbol.range.end.line,
2561
- character: symbol.range.end.line === 0 ? region.startChar + symbol.range.end.character : symbol.range.end.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 + symbol.selectionRange.start.line,
2567
- character: symbol.selectionRange.start.line === 0 ? region.startChar + symbol.selectionRange.start.character : symbol.selectionRange.start.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 + symbol.selectionRange.end.line,
2571
- character: symbol.selectionRange.end.line === 0 ? region.startChar + symbol.selectionRange.end.character : symbol.selectionRange.end.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
- const onVariants = getVariants("on");
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": "2.11.1",
4
- "description": "Language Server Protocol implementation for LokaScript/hyperscript with 21 language support",
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 --dts",
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": "^2.11.1",
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": "^2.11.1",
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",