@lokascript/language-server 3.0.0 → 3.1.1

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 CHANGED
@@ -15,7 +15,7 @@ The language server supports four operating modes:
15
15
 
16
16
  ### Mode Selection
17
17
 
18
- - **auto** (default): If `@lokascript/semantic` is available, uses `lokascript` mode; otherwise uses `hyperscript` mode
18
+ - **auto** (default): `lokascript` mode when the semantic package exposes its API; `hyperscript` mode in builds that replace it with a shim (the standalone `hyperscript-vscode` extension does this)
19
19
  - **hyperscript**: Enforces \_hyperscript-compatible syntax, English keywords only
20
20
  - **hyperscript-i18n**: Enforces \_hyperscript-compatible syntax with multilingual keyword support. Use this if you have original \_hyperscript with `@lokascript/hyperscript-adapter` for writing in non-English languages
21
21
  - **lokascript**: Enables all features including LokaScript extensions and multilingual support
@@ -50,9 +50,11 @@ This allows LokaScript users to maintain \_hyperscript compatibility by using `h
50
50
 
51
51
  ### Multilingual Support
52
52
 
53
- Works with hyperscript written in any of 21 supported languages:
53
+ Works with hyperscript written in any of the 24 supported languages:
54
54
 
55
- en (English), es (Spanish), pt (Portuguese), fr (French), de (German), it (Italian), ru (Russian), pl (Polish), uk (Ukrainian), ja (Japanese), ko (Korean), zh (Chinese), ar (Arabic), he (Hebrew), tr (Turkish), id (Indonesian), ms (Malay), th (Thai), vi (Vietnamese), tl (Tagalog), sw (Swahili)
55
+ ar (Arabic), bn (Bengali), de (German), en (English), es (Spanish), fr (French), he (Hebrew), hi (Hindi), id (Indonesian), it (Italian), ja (Japanese), ko (Korean), ms (Malay), pl (Polish), pt (Portuguese), qu (Quechua), ru (Russian), sw (Swahili), th (Thai), tl (Tagalog), tr (Turkish), uk (Ukrainian), vi (Vietnamese), zh (Chinese)
56
+
57
+ In `lokascript` and `hyperscript-i18n` modes the server parses each region with the semantic front-end in the configured `language`, falling back to auto-detection across the other languages when that fails. When the front-end accepts a region in a non-English language, the English core parser's errors for that region are suppressed (the core parser is English-only, and its "unexpected token" on valid Spanish is noise, not a diagnostic).
56
58
 
57
59
  ### HTML Support
58
60
 
@@ -113,7 +115,13 @@ require('lspconfig').lokascript.setup{}
113
115
 
114
116
  ## Configuration
115
117
 
116
- The server accepts configuration via the LSP `workspace/configuration` mechanism:
118
+ The server reads its settings three ways, and every field is optional (a partial object is merged over the defaults):
119
+
120
+ 1. `initializationOptions` on `initialize` (both VS Code extensions pass `{ language }` here);
121
+ 2. a `workspace/didChangeConfiguration` push carrying a `lokascript` or `hyperscript` section (vscode-languageclient only sends one when the client sets `synchronize.configurationSection`; both bundled extensions do);
122
+ 3. a `workspace/configuration` pull for the `lokascript` and `hyperscript` sections, made at startup and whenever a push arrives with no payload, for clients that advertise the capability.
123
+
124
+ When both namespaces are present the `lokascript` one wins, unless the server was launched with `HYPERSCRIPT_LS_DEFAULT_MODE` set (the standalone hyperscript product), in which case `hyperscript` wins.
117
125
 
118
126
  ```json
119
127
  {
@@ -218,13 +226,10 @@ For users who prefer the `hyperscript` namespace:
218
226
 
219
227
  ## Dependencies
220
228
 
221
- The language server works best with these optional peer dependencies installed:
229
+ - `@lokascript/semantic` and `@lokascript/framework` are **required** (regular dependencies): the semantic package registers the 24 languages at startup, and the framework renders the LSE bracket notation shown in hover. They used to be declared as optional peers while being imported statically, so a server installed without them crashed at startup instead of falling back.
230
+ - `@hyperfixi/core` is an **optional** peer. With it the server surfaces real parse errors, complexity diagnostics and schema-inferred roles in hover; without it, diagnostics degrade to the pattern-based quote/bracket checks and hover uses built-in fallback docs.
222
231
 
223
- - `@lokascript/semantic` - Enables 21-language support and semantic analysis
224
- - `@lokascript/ast-toolkit` - Enables AST-based analysis and complexity metrics
225
- - `@lokascript/core` - Enables full hyperscript parsing and AST-based formatting
226
-
227
- Without these dependencies, the server falls back to pattern-based analysis (English only).
232
+ A bundler that wants an English-only, dependency-free server (the standalone `hyperscript-vscode` extension) replaces the semantic, framework and core imports with throwing or empty shims at bundle time; the server's capability probes are written for that case.
228
233
 
229
234
  ## Development
230
235
 
@@ -247,7 +252,7 @@ npm test -- --coverage
247
252
 
248
253
  ## Architecture
249
254
 
250
- The language server is implemented as a single `server.ts` file that:
255
+ The language server entry point is `server.ts` (LSP wiring and the request handlers), with the testable logic in sibling modules — `extraction.ts` (HTML region extraction and position mapping), `simple-diagnostics.ts`, `command-tiers.ts` (the _hyperscript-compatibility allowlists), `symbol-table.ts` (definition/references/rename), `document-symbols.ts`, `completion-context.ts`, `formatting.ts` and `localized-descriptions.ts`. Together they:
251
256
 
252
257
  1. **Extracts** hyperscript regions from HTML documents
253
258
  2. **Analyzes** code using semantic parsing (multilingual) or pattern-based fallback
@@ -299,13 +304,10 @@ npm test -- --run src/server.test.ts
299
304
 
300
305
  Test categories:
301
306
 
302
- - HTML document detection
303
- - Hyperscript region extraction
304
- - Position mapping (offset to line/character)
305
- - Go to Definition
306
- - Find References
307
- - Code Formatting
308
- - LSP integration tests
307
+ - Unit tests against the shipped modules: HTML document detection, region extraction, position mapping, the symbol table (definition, references, rename), document symbols, completion context, formatting, diagnostics, and the compatibility allowlists
308
+ - `lsp-integration.test.ts` spawns the built `dist/server.js` over stdio and speaks JSON-RPC: diagnostics publishing (including the non-English cases), completions, hover, symbols, definition, references, rename, formatting, code actions, configuration changes in both push and pull form, and the `lokascript/translateWithVerification` request
309
+
310
+ `npm test` rebuilds this package first (`pretest`), and refreshes any stale sibling `dist/` the integration suite resolves through the workspace.
309
311
 
310
312
  ## License
311
313
 
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
@@ -161,10 +160,11 @@ var LOKASCRIPT_ONLY_COMMANDS = [
161
160
  var ALL_COMMANDS = [...HYPERSCRIPT_COMMANDS, ...LOKASCRIPT_ONLY_COMMANDS];
162
161
  var LOKASCRIPT_SYNTAX_PATTERNS = {
163
162
  /**
164
- * Possessive dot notation: my.textContent, its.value, your.classList
165
- * 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.
166
166
  */
167
- dotNotation: /\b(my|your|its)\.\w+/,
167
+ dotNotation: /\bits\.\w+/,
168
168
  /**
169
169
  * Optional chaining: my?.value
170
170
  */
@@ -172,17 +172,49 @@ var LOKASCRIPT_SYNTAX_PATTERNS = {
172
172
  /**
173
173
  * Extended 'as' conversions
174
174
  */
175
- extendedAsConversion: /\bas\s+(Int|Integer|Float|JSON|FormData|Date|URLSearchParams|Set|Map)\b/i,
175
+ extendedAsConversion: /\bas\s+(Math|Values)\b/i,
176
176
  /**
177
177
  * Debounce/throttle modifiers with duration: .debounce(300), .throttle(1s)
178
178
  */
179
179
  temporalModifiers: /\.(debounce|throttle)\s*\(\s*\d+/i
180
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
+ }
181
212
  function detectLokascriptFeatures(code) {
182
213
  const detected = [];
214
+ const scannable = blankStringsAndComments(code);
183
215
  for (const cmd of LOKASCRIPT_ONLY_COMMANDS) {
184
- const pattern = new RegExp(`\\b${cmd}\\b`, "i");
185
- 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)) {
186
218
  detected.push({
187
219
  feature: "command",
188
220
  description: `'${cmd}' command is a LokaScript extension`,
@@ -226,6 +258,7 @@ function detectLokascriptFeatures(code) {
226
258
  // src/extraction.ts
227
259
  function isHtmlDocument(uri, content) {
228
260
  if (uri.endsWith(".html") || uri.endsWith(".htm")) return true;
261
+ if (/\.(hs|_hs|hyperscript)$/i.test(uri)) return false;
229
262
  if (content.trim().startsWith("<!DOCTYPE") || content.trim().startsWith("<html")) return true;
230
263
  if (/<\w+[^>]*>/.test(content) && !content.trim().startsWith("on ")) return true;
231
264
  return false;
@@ -376,6 +409,9 @@ function getWordAtPosition(line, character) {
376
409
  if (start === end) return null;
377
410
  return { text: line.slice(start, end), start, end };
378
411
  }
412
+ function escapeRegExp(str) {
413
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
414
+ }
379
415
  function findNextNonEmptyLine(lines, startIndex) {
380
416
  for (let i = startIndex; i < lines.length; i++) {
381
417
  const trimmed = lines[i].trim();
@@ -425,7 +461,7 @@ import { DiagnosticSeverity } from "vscode-languageserver/node.js";
425
461
  function isPossessive(code, i) {
426
462
  if (i === 0) return false;
427
463
  const prev = code[i - 1];
428
- if (!/[\w\-#.]/.test(prev)) return false;
464
+ if (!/[\w\-#.)\]>]/.test(prev)) return false;
429
465
  if (i + 1 >= code.length || code[i + 1] !== "s") return false;
430
466
  if (i + 2 >= code.length) return true;
431
467
  const after = code[i + 2];
@@ -459,6 +495,13 @@ function runSimpleDiagnostics(code, _language = "en") {
459
495
  col++;
460
496
  continue;
461
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
+ }
462
505
  switch (ch) {
463
506
  case "'":
464
507
  if (isPossessive(code, i)) {
@@ -632,14 +675,15 @@ function buildSymbolTable(regions, getKeywordVariants2) {
632
675
  const defaultKws = variants("default");
633
676
  const installKws = variants("install");
634
677
  const callKws = variants("call");
635
- const behaviorDefPat = new RegExp(`\\b(${behaviorKws.join("|")})\\s+(\\w+)`, "gi");
636
- const funcDefPat = new RegExp(`\\b(${defKws.join("|")})\\s+(\\w+)`, "gi");
637
- const varDefPat = new RegExp(`\\b(${[...setKws, ...defaultKws].join("|")})\\s+([:$]\\w+)`, "gi");
638
- const installPat = new RegExp(`\\b(${installKws.join("|")})\\s+(\\w+)`, "gi");
639
- const callPat = new RegExp(`\\b(${callKws.join("|")})\\s+(\\w+)`, "gi");
640
- 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;
641
685
  const funcCallPat = /\b(\w+)\s*\(/g;
642
- 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");
643
687
  const caretRefPat = /\^([A-Za-z_]\w*)/g;
644
688
  const attrsRefPat = /\battrs\.([A-Za-z_]\w*)/g;
645
689
  function getOrCreate(name, kind, scope) {
@@ -662,6 +706,11 @@ function buildSymbolTable(regions, getKeywordVariants2) {
662
706
  character: toDocChar(region, localLine, localChar),
663
707
  length
664
708
  };
709
+ if (locationIndex.some(
710
+ (l) => l.name === entry.name && l.line === loc.line && l.start === loc.character
711
+ )) {
712
+ return;
713
+ }
665
714
  if (kind === "def") {
666
715
  entry.definitions.push(loc);
667
716
  } else {
@@ -881,6 +930,82 @@ function getSymbolTable(uri, version, regions, getKeywordVariants2) {
881
930
  return table;
882
931
  }
883
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
+
884
1009
  // src/localized-descriptions.ts
885
1010
  var DESCRIPTIONS = {
886
1011
  en: {
@@ -1241,7 +1366,7 @@ try {
1241
1366
  ({ schemaRoleInferrer: inferRoles } = await import("@hyperfixi/core/multilingual"));
1242
1367
  } catch {
1243
1368
  console.error(
1244
- "[lokascript-ls] @lokascript/semantic not available \u2014 hover shows roles for `set` and `go` only. Install the semantic package for full role inference."
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."
1245
1370
  );
1246
1371
  }
1247
1372
  fromCoreASTFn = (node) => core.fromCoreAST(
@@ -1262,7 +1387,7 @@ try {
1262
1387
  }
1263
1388
  var frameworkIR = null;
1264
1389
  try {
1265
- const fw = await import("./dist-2GK6K2QJ.js");
1390
+ const fw = await import("@lokascript/framework");
1266
1391
  frameworkIR = { fromInterchangeNode: fw.fromInterchangeNode, renderExplicit: fw.renderExplicit };
1267
1392
  console.error(
1268
1393
  "[lokascript-ls] @lokascript/framework loaded \u2014 LSE bracket notation in hover enabled"
@@ -1564,8 +1689,60 @@ function getSemanticAnalyzer() {
1564
1689
  }
1565
1690
  return cachedAnalyzer;
1566
1691
  }
1567
- var globalSettings = envDefaultMode ? { ...defaultSettings, mode: envDefaultMode } : defaultSettings;
1568
- 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);
1569
1746
  resolvedMode = resolveMode(globalSettings);
1570
1747
  const brand = getBranding(resolvedMode);
1571
1748
  connection.console.log(`${brand} Language Server initializing (mode: ${resolvedMode})...`);
@@ -1592,19 +1769,20 @@ connection.onInitialized(() => {
1592
1769
  connection.client.register(DidChangeConfigurationNotification.type, void 0);
1593
1770
  const brand = getBranding(resolvedMode);
1594
1771
  connection.console.log(`${brand} Language Server initialized`);
1772
+ void pullConfiguration();
1595
1773
  });
1596
1774
  connection.onDidChangeConfiguration((change) => {
1597
- const envDefaults = envDefaultMode ? { ...defaultSettings, mode: envDefaultMode } : defaultSettings;
1598
- globalSettings = change.settings?.lokascript || change.settings?.hyperscript || envDefaults;
1599
- const previousMode = resolvedMode;
1600
- resolvedMode = resolveMode(globalSettings);
1601
- if (previousMode !== resolvedMode) {
1602
- const brand = getBranding(resolvedMode);
1603
- 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;
1604
1780
  }
1605
- translationCache = null;
1606
- reverseKeywordCache = null;
1607
- documents.all().forEach(validateDocument);
1781
+ if (hasConfigurationCapability) {
1782
+ void pullConfiguration();
1783
+ return;
1784
+ }
1785
+ applySettings(baseSettings());
1608
1786
  });
1609
1787
  documents.onDidChangeContent((change) => {
1610
1788
  validateDocument(change.document);
@@ -1660,6 +1838,7 @@ async function getDiagnostics(code, language) {
1660
1838
  }
1661
1839
  }
1662
1840
  const analyzer = isMultilingualEnabled(resolvedMode) ? getSemanticAnalyzer() : null;
1841
+ let semanticParsedForeign = false;
1663
1842
  if (analyzer) {
1664
1843
  try {
1665
1844
  let result = analyzer.analyze(code, language);
@@ -1706,6 +1885,7 @@ async function getDiagnostics(code, language) {
1706
1885
  message: `Code could not be fully parsed (${(result.confidence * 100).toFixed(0)}% confidence)`
1707
1886
  });
1708
1887
  }
1888
+ semanticParsedForeign = result.confidence >= 0.5 && usedLanguage !== "en";
1709
1889
  if (result.confidence >= 0.5 && result.command) {
1710
1890
  const cmd = result.command.name;
1711
1891
  const roles = result.command.roles;
@@ -1735,7 +1915,7 @@ async function getDiagnostics(code, language) {
1735
1915
  if (parseFunction && fromCoreASTFn) {
1736
1916
  try {
1737
1917
  const parseResult = parseFunction(code);
1738
- if (parseResult.errors?.length) {
1918
+ if (parseResult.errors?.length && !semanticParsedForeign) {
1739
1919
  for (const err of parseResult.errors) {
1740
1920
  diagnostics.push({
1741
1921
  range: {
@@ -1766,16 +1946,18 @@ async function getDiagnostics(code, language) {
1766
1946
  }
1767
1947
  }
1768
1948
  } catch (parseError) {
1769
- diagnostics.push({
1770
- range: {
1771
- start: { line: 0, character: 0 },
1772
- end: { line: 0, character: Math.min(code.length, 80) }
1773
- },
1774
- severity: DiagnosticSeverity2.Error,
1775
- code: "parse-error",
1776
- source: brand,
1777
- message: parseError.message || "Failed to parse"
1778
- });
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
+ }
1779
1961
  }
1780
1962
  }
1781
1963
  if (diagnostics.length === 0) {
@@ -1907,18 +2089,6 @@ function getAttrsCompletions() {
1907
2089
  }
1908
2090
  ];
1909
2091
  }
1910
- function inferContext(beforeCursor) {
1911
- if (/\^\w*$/.test(beforeCursor)) return "caret";
1912
- if (/\battrs\.\w*$/.test(beforeCursor)) return "attrs-property";
1913
- const trimmed = beforeCursor.trim();
1914
- if (/\bon\s*$/.test(trimmed)) return "event";
1915
- if (/\bthen\s*$/.test(trimmed)) return "command";
1916
- if (/^(on\s+\w+\s*)$/.test(trimmed)) return "command";
1917
- if (/(to|from|into|on)\s*$/.test(trimmed)) return "selector";
1918
- if (/\bif\s*$/.test(trimmed)) return "expression";
1919
- if (/\bset\s+:\w+\s+to\s*$/.test(trimmed)) return "expression";
1920
- return "default";
1921
- }
1922
2092
  function getContextualCompletions(context, language) {
1923
2093
  const completions = [];
1924
2094
  const effectiveLanguage = isMultilingualEnabled(resolvedMode) ? language : "en";
@@ -2551,27 +2721,27 @@ connection.onDocumentSymbol((params) => {
2551
2721
  const allSymbols = [];
2552
2722
  for (const region of regions) {
2553
2723
  const symbols = extractSymbols(region.code, language);
2554
- for (const symbol of symbols) {
2724
+ for (const symbol2 of symbols) {
2555
2725
  allSymbols.push({
2556
- ...symbol,
2726
+ ...symbol2,
2557
2727
  range: {
2558
2728
  start: {
2559
- line: region.startLine + symbol.range.start.line,
2560
- 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
2561
2731
  },
2562
2732
  end: {
2563
- line: region.startLine + symbol.range.end.line,
2564
- 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
2565
2735
  }
2566
2736
  },
2567
2737
  selectionRange: {
2568
2738
  start: {
2569
- line: region.startLine + symbol.selectionRange.start.line,
2570
- 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
2571
2741
  },
2572
2742
  end: {
2573
- line: region.startLine + symbol.selectionRange.end.line,
2574
- 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
2575
2745
  }
2576
2746
  }
2577
2747
  });
@@ -2583,8 +2753,6 @@ connection.onDocumentSymbol((params) => {
2583
2753
  }
2584
2754
  });
2585
2755
  function extractSymbols(code, language) {
2586
- const symbols = [];
2587
- const lines = code.split("\n");
2588
2756
  const getVariants = (eng) => {
2589
2757
  const variants = [eng];
2590
2758
  if (!semanticPackage || language === "en") return variants;
@@ -2602,82 +2770,7 @@ function extractSymbols(code, language) {
2602
2770
  }
2603
2771
  return variants;
2604
2772
  };
2605
- const onVariants = getVariants("on");
2606
- const behaviorVariants = getVariants("behavior");
2607
- const defVariants = getVariants("def");
2608
- const initVariants = getVariants("init");
2609
- const onPattern = new RegExp(`\\b(${onVariants.join("|")})\\s+(\\w+(?:\\[.*?\\])?)`, "gi");
2610
- const behaviorPattern = new RegExp(`\\b(${behaviorVariants.join("|")})\\s+(\\w+)`, "gi");
2611
- const defPattern = new RegExp(`\\b(${defVariants.join("|")})\\s+(\\w+)`, "gi");
2612
- const initPattern = new RegExp(`\\b(${initVariants.join("|")})\\b`, "gi");
2613
- for (let i = 0; i < lines.length; i++) {
2614
- const line = lines[i];
2615
- for (const match of line.matchAll(onPattern)) {
2616
- const matchIndex = match.index ?? 0;
2617
- symbols.push({
2618
- name: `${match[1]} ${match[2]}`,
2619
- detail: "Event Handler",
2620
- kind: SymbolKind.Event,
2621
- range: {
2622
- start: { line: i, character: matchIndex },
2623
- end: { line: i, character: matchIndex + match[0].length }
2624
- },
2625
- selectionRange: {
2626
- start: { line: i, character: matchIndex },
2627
- end: { line: i, character: matchIndex + match[0].length }
2628
- }
2629
- });
2630
- }
2631
- for (const match of line.matchAll(behaviorPattern)) {
2632
- const matchIndex = match.index ?? 0;
2633
- symbols.push({
2634
- name: `${match[1]} ${match[2]}`,
2635
- detail: "Behavior Definition",
2636
- kind: SymbolKind.Class,
2637
- range: {
2638
- start: { line: i, character: matchIndex },
2639
- end: { line: i, character: matchIndex + match[0].length }
2640
- },
2641
- selectionRange: {
2642
- start: { line: i, character: matchIndex },
2643
- end: { line: i, character: matchIndex + match[0].length }
2644
- }
2645
- });
2646
- }
2647
- for (const match of line.matchAll(defPattern)) {
2648
- const matchIndex = match.index ?? 0;
2649
- symbols.push({
2650
- name: `${match[1]} ${match[2]}`,
2651
- detail: "Function Definition",
2652
- kind: SymbolKind.Function,
2653
- range: {
2654
- start: { line: i, character: matchIndex },
2655
- end: { line: i, character: matchIndex + match[0].length }
2656
- },
2657
- selectionRange: {
2658
- start: { line: i, character: matchIndex },
2659
- end: { line: i, character: matchIndex + match[0].length }
2660
- }
2661
- });
2662
- }
2663
- for (const match of line.matchAll(initPattern)) {
2664
- const matchIndex = match.index ?? 0;
2665
- symbols.push({
2666
- name: match[1],
2667
- detail: "Initialization",
2668
- kind: SymbolKind.Constructor,
2669
- range: {
2670
- start: { line: i, character: matchIndex },
2671
- end: { line: i, character: matchIndex + match[1].length }
2672
- },
2673
- selectionRange: {
2674
- start: { line: i, character: matchIndex },
2675
- end: { line: i, character: matchIndex + match[1].length }
2676
- }
2677
- });
2678
- }
2679
- }
2680
- return symbols;
2773
+ return extractDocumentSymbols(code, getVariants);
2681
2774
  }
2682
2775
  connection.onCodeAction((params) => {
2683
2776
  const diagnostics = params.context.diagnostics;