@lokascript/language-server 2.3.0 → 2.4.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
@@ -8,7 +8,7 @@ import {
8
8
  TextDocumentSyncKind,
9
9
  CompletionItemKind,
10
10
  MarkupKind,
11
- DiagnosticSeverity,
11
+ DiagnosticSeverity as DiagnosticSeverity2,
12
12
  SymbolKind,
13
13
  CodeActionKind,
14
14
  DidChangeConfigurationNotification,
@@ -203,6 +203,42 @@ function extractHyperscriptRegions(content) {
203
203
  type: "script"
204
204
  });
205
205
  }
206
+ const templateRegex = /<template\s+[^>]*\bcomponent=["']([^"']+)["'][^>]*>([\s\S]*?)<\/template>/gi;
207
+ while ((match = templateRegex.exec(fullContent)) !== null) {
208
+ const tag = match[1];
209
+ const body = match[2];
210
+ const bodyStart = match.index + match[0].indexOf(">") + 1;
211
+ const bodyEnd = match.index + match[0].lastIndexOf("</template>");
212
+ const startPos = offsetToPosition(content, bodyStart);
213
+ const endPos = offsetToPosition(content, bodyEnd);
214
+ regions.push({
215
+ code: body,
216
+ startLine: startPos.line,
217
+ startChar: startPos.character,
218
+ endLine: endPos.line,
219
+ endChar: endPos.character,
220
+ type: "template",
221
+ componentTag: tag
222
+ });
223
+ }
224
+ const templateScriptRegex = /<script\s+[^>]*\btype=["']text\/hyperscript-template["'][^>]*\bcomponent=["']([^"']+)["'][^>]*>([\s\S]*?)<\/script>/gi;
225
+ while ((match = templateScriptRegex.exec(fullContent)) !== null) {
226
+ const tag = match[1];
227
+ const body = match[2];
228
+ const bodyStart = match.index + match[0].indexOf(">") + 1;
229
+ const bodyEnd = match.index + match[0].lastIndexOf("</script>");
230
+ const startPos = offsetToPosition(content, bodyStart);
231
+ const endPos = offsetToPosition(content, bodyEnd);
232
+ regions.push({
233
+ code: body,
234
+ startLine: startPos.line,
235
+ startChar: startPos.character,
236
+ endLine: endPos.line,
237
+ endChar: endPos.character,
238
+ type: "template",
239
+ componentTag: tag
240
+ });
241
+ }
206
242
  return regions;
207
243
  }
208
244
  function offsetToPosition(content, offset) {
@@ -243,20 +279,6 @@ function findRegionAtPosition(regions, line, character) {
243
279
  }
244
280
  return null;
245
281
  }
246
- function findLineInRegion(code, offset) {
247
- let line = 0;
248
- for (let i = 0; i < offset && i < code.length; i++) {
249
- if (code[i] === "\n") line++;
250
- }
251
- return line;
252
- }
253
- function findCharInLine(code, offset) {
254
- let lastNewline = -1;
255
- for (let i = 0; i < offset && i < code.length; i++) {
256
- if (code[i] === "\n") lastNewline = i;
257
- }
258
- return offset - lastNewline - 1;
259
- }
260
282
 
261
283
  // src/utils.ts
262
284
  function getWordAtPosition(line, character) {
@@ -275,9 +297,6 @@ function getWordAtPosition(line, character) {
275
297
  if (start === end) return null;
276
298
  return { text: line.slice(start, end), start, end };
277
299
  }
278
- function escapeRegExp(str) {
279
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
280
- }
281
300
  function findNextNonEmptyLine(lines, startIndex) {
282
301
  for (let i = startIndex; i < lines.length; i++) {
283
302
  const trimmed = lines[i].trim();
@@ -322,953 +341,466 @@ function formatHyperscript(code, indentStr = " ") {
322
341
  return formattedLines.join("\n");
323
342
  }
324
343
 
325
- // src/chevrotain-parser.ts
326
- import { CstParser } from "chevrotain";
327
-
328
- // src/generated/chevrotain-vocabulary.ts
329
- import { createToken, Lexer } from "chevrotain";
330
- var Identifier = createToken({ name: "Identifier", pattern: /[a-zA-Z\u00C0-\u024F\u0400-\u04FF\u0600-\u06FF\u0900-\u09FF\u0E00-\u0E7F\u3040-\u9FFF\uAC00-\uD7AF][\w\u00C0-\u024F\u0400-\u04FF\u0600-\u06FF\u0900-\u09FF\u0E00-\u0E7F\u3040-\u9FFF\uAC00-\uD7AF'-]*/ });
331
- var WhiteSpace = createToken({ name: "WhiteSpace", pattern: /\s+/ });
332
- var StringLiteral = createToken({ name: "StringLiteral", pattern: /'[^']*'|"[^"]*"/ });
333
- var NumberLiteral = createToken({ name: "NumberLiteral", pattern: /\d+(\.\d+)?/ });
334
- var URLLiteral = createToken({ name: "URLLiteral", pattern: /\/[a-zA-Z0-9_\-.\/]+/ });
335
- var CSSSelector = createToken({ name: "CSSSelector", pattern: /[#.][a-zA-Z_][\w-]*/ });
336
- var AttributeSelector = createToken({ name: "AttributeSelector", pattern: /@[a-zA-Z_][\w-]*/ });
337
- var HTMLSelector = createToken({ name: "HTMLSelector", pattern: /<[a-zA-Z][\w.-]*\/>/ });
338
- var LocalVariable = createToken({ name: "LocalVariable", pattern: /:[a-zA-Z_][\w]*/ });
339
- var GlobalVariable = createToken({ name: "GlobalVariable", pattern: /\$[a-zA-Z_][\w]*/ });
340
- var LParen = createToken({ name: "LParen", pattern: /\(/ });
341
- var RParen = createToken({ name: "RParen", pattern: /\)/ });
342
- var Comma = createToken({ name: "Comma", pattern: /,/ });
343
- var Dot = createToken({ name: "Dot", pattern: /\./ });
344
- var Kw_previous = createToken({ name: "Kw_previous", pattern: /previous/, longer_alt: Identifier });
345
- var Kw_closest = createToken({ name: "Kw_closest", pattern: /closest/, longer_alt: Identifier });
346
- var Kw_while = createToken({ name: "Kw_while", pattern: /while/, longer_alt: Identifier });
347
- var Kw_until = createToken({ name: "Kw_until", pattern: /until/, longer_alt: Identifier });
348
- var Kw_times = createToken({ name: "Kw_times", pattern: /times/, longer_alt: Identifier });
349
- var Kw_first = createToken({ name: "Kw_first", pattern: /first/, longer_alt: Identifier });
350
- var Kw_false = createToken({ name: "Kw_false", pattern: /false/, longer_alt: Identifier });
351
- var Kw_then = createToken({ name: "Kw_then", pattern: /then/, longer_alt: Identifier });
352
- var Kw_else = createToken({ name: "Kw_else", pattern: /else/, longer_alt: Identifier });
353
- var Kw_from = createToken({ name: "Kw_from", pattern: /from/, longer_alt: Identifier });
354
- var Kw_into = createToken({ name: "Kw_into", pattern: /into/, longer_alt: Identifier });
355
- var Kw_with = createToken({ name: "Kw_with", pattern: /with/, longer_alt: Identifier });
356
- var Kw_have = createToken({ name: "Kw_have", pattern: /have/, longer_alt: Identifier });
357
- var Kw_last = createToken({ name: "Kw_last", pattern: /last/, longer_alt: Identifier });
358
- var Kw_next = createToken({ name: "Kw_next", pattern: /next/, longer_alt: Identifier });
359
- var Kw_true = createToken({ name: "Kw_true", pattern: /true/, longer_alt: Identifier });
360
- var Kw_null = createToken({ name: "Kw_null", pattern: /null/, longer_alt: Identifier });
361
- var Kw_end = createToken({ name: "Kw_end", pattern: /end/, longer_alt: Identifier });
362
- var Kw_for = createToken({ name: "Kw_for", pattern: /for/, longer_alt: Identifier });
363
- var Kw_and = createToken({ name: "Kw_and", pattern: /and/, longer_alt: Identifier });
364
- var Kw_not = createToken({ name: "Kw_not", pattern: /not/, longer_alt: Identifier });
365
- var Kw_has = createToken({ name: "Kw_has", pattern: /has/, longer_alt: Identifier });
366
- var Kw_you = createToken({ name: "Kw_you", pattern: /you/, longer_alt: Identifier });
367
- var Kw_its = createToken({ name: "Kw_its", pattern: /its/, longer_alt: Identifier });
368
- var Kw_the = createToken({ name: "Kw_the", pattern: /the/, longer_alt: Identifier });
369
- var Kw_on = createToken({ name: "Kw_on", pattern: /on/, longer_alt: Identifier });
370
- var Kw_if = createToken({ name: "Kw_if", pattern: /if/, longer_alt: Identifier });
371
- var Kw_to = createToken({ name: "Kw_to", pattern: /to/, longer_alt: Identifier });
372
- var Kw_in = createToken({ name: "Kw_in", pattern: /in/, longer_alt: Identifier });
373
- var Kw_of = createToken({ name: "Kw_of", pattern: /of/, longer_alt: Identifier });
374
- var Kw_as = createToken({ name: "Kw_as", pattern: /as/, longer_alt: Identifier });
375
- var Kw_at = createToken({ name: "Kw_at", pattern: /at/, longer_alt: Identifier });
376
- var Kw_by = createToken({ name: "Kw_by", pattern: /by/, longer_alt: Identifier });
377
- var Kw_or = createToken({ name: "Kw_or", pattern: /or/, longer_alt: Identifier });
378
- var Kw_is = createToken({ name: "Kw_is", pattern: /is/, longer_alt: Identifier });
379
- var Kw_me = createToken({ name: "Kw_me", pattern: /me/, longer_alt: Identifier });
380
- var Kw_it = createToken({ name: "Kw_it", pattern: /it/, longer_alt: Identifier });
381
- var Kw_my = createToken({ name: "Kw_my", pattern: /my/, longer_alt: Identifier });
382
- var Kw_no = createToken({ name: "Kw_no", pattern: /no/, longer_alt: Identifier });
383
- var Cmd_transition = createToken({ name: "Cmd_transition", pattern: /transition/, longer_alt: Identifier });
384
- var Cmd_show = createToken({ name: "Cmd_show", pattern: /show/, longer_alt: Identifier });
385
- var Cmd_hide = createToken({ name: "Cmd_hide", pattern: /hide/, longer_alt: Identifier });
386
- var Cmd_increment = createToken({ name: "Cmd_increment", pattern: /increment/, longer_alt: Identifier });
387
- var Cmd_decrement = createToken({ name: "Cmd_decrement", pattern: /decrement/, longer_alt: Identifier });
388
- var Cmd_default = createToken({ name: "Cmd_default", pattern: /default/, longer_alt: Identifier });
389
- var Cmd_beep = createToken({ name: "Cmd_beep", pattern: /beep/, longer_alt: Identifier });
390
- var Cmd_pick = createToken({ name: "Cmd_pick", pattern: /pick/, longer_alt: Identifier });
391
- var Cmd_set = createToken({ name: "Cmd_set", pattern: /set/, longer_alt: Identifier });
392
- var Cmd_log = createToken({ name: "Cmd_log", pattern: /log/, longer_alt: Identifier });
393
- var Cmd_get = createToken({ name: "Cmd_get", pattern: /get/, longer_alt: Identifier });
394
- var Cmd_continue = createToken({ name: "Cmd_continue", pattern: /continue/, longer_alt: Identifier });
395
- var Cmd_behavior = createToken({ name: "Cmd_behavior", pattern: /behavior/, longer_alt: Identifier });
396
- var Cmd_compound = createToken({ name: "Cmd_compound", pattern: /compound/, longer_alt: Identifier });
397
- var Cmd_install = createToken({ name: "Cmd_install", pattern: /install/, longer_alt: Identifier });
398
- var Cmd_unless = createToken({ name: "Cmd_unless", pattern: /unless/, longer_alt: Identifier });
399
- var Cmd_repeat = createToken({ name: "Cmd_repeat", pattern: /repeat/, longer_alt: Identifier });
400
- var Cmd_return = createToken({ name: "Cmd_return", pattern: /return/, longer_alt: Identifier });
401
- var Cmd_throw = createToken({ name: "Cmd_throw", pattern: /throw/, longer_alt: Identifier });
402
- var Cmd_break = createToken({ name: "Cmd_break", pattern: /break/, longer_alt: Identifier });
403
- var Cmd_halt = createToken({ name: "Cmd_halt", pattern: /halt/, longer_alt: Identifier });
404
- var Cmd_call = createToken({ name: "Cmd_call", pattern: /call/, longer_alt: Identifier });
405
- var Cmd_tell = createToken({ name: "Cmd_tell", pattern: /tell/, longer_alt: Identifier });
406
- var Cmd_init = createToken({ name: "Cmd_init", pattern: /init/, longer_alt: Identifier });
407
- var Cmd_exit = createToken({ name: "Cmd_exit", pattern: /exit/, longer_alt: Identifier });
408
- var Cmd_js = createToken({ name: "Cmd_js", pattern: /js/, longer_alt: Identifier });
409
- var Cmd_trigger = createToken({ name: "Cmd_trigger", pattern: /trigger/, longer_alt: Identifier });
410
- var Cmd_send = createToken({ name: "Cmd_send", pattern: /send/, longer_alt: Identifier });
411
- var Cmd_prepend = createToken({ name: "Cmd_prepend", pattern: /prepend/, longer_alt: Identifier });
412
- var Cmd_measure = createToken({ name: "Cmd_measure", pattern: /measure/, longer_alt: Identifier });
413
- var Cmd_append = createToken({ name: "Cmd_append", pattern: /append/, longer_alt: Identifier });
414
- var Cmd_render = createToken({ name: "Cmd_render", pattern: /render/, longer_alt: Identifier });
415
- var Cmd_clone = createToken({ name: "Cmd_clone", pattern: /clone/, longer_alt: Identifier });
416
- var Cmd_focus = createToken({ name: "Cmd_focus", pattern: /focus/, longer_alt: Identifier });
417
- var Cmd_morph = createToken({ name: "Cmd_morph", pattern: /morph/, longer_alt: Identifier });
418
- var Cmd_take = createToken({ name: "Cmd_take", pattern: /take/, longer_alt: Identifier });
419
- var Cmd_make = createToken({ name: "Cmd_make", pattern: /make/, longer_alt: Identifier });
420
- var Cmd_blur = createToken({ name: "Cmd_blur", pattern: /blur/, longer_alt: Identifier });
421
- var Cmd_swap = createToken({ name: "Cmd_swap", pattern: /swap/, longer_alt: Identifier });
422
- var Cmd_copy = createToken({ name: "Cmd_copy", pattern: /copy/, longer_alt: Identifier });
423
- var Cmd_put = createToken({ name: "Cmd_put", pattern: /put/, longer_alt: Identifier });
424
- var Cmd_toggle = createToken({ name: "Cmd_toggle", pattern: /toggle/, longer_alt: Identifier });
425
- var Cmd_remove = createToken({ name: "Cmd_remove", pattern: /remove/, longer_alt: Identifier });
426
- var Cmd_add = createToken({ name: "Cmd_add", pattern: /add/, longer_alt: Identifier });
427
- var Cmd_settle = createToken({ name: "Cmd_settle", pattern: /settle/, longer_alt: Identifier });
428
- var Cmd_fetch = createToken({ name: "Cmd_fetch", pattern: /fetch/, longer_alt: Identifier });
429
- var Cmd_async = createToken({ name: "Cmd_async", pattern: /async/, longer_alt: Identifier });
430
- var Cmd_wait = createToken({ name: "Cmd_wait", pattern: /wait/, longer_alt: Identifier });
431
- var Cmd_go = createToken({ name: "Cmd_go", pattern: /go/, longer_alt: Identifier });
432
- var Event_intersection = createToken({ name: "Event_intersection", pattern: /intersection/, longer_alt: Identifier });
433
- var Event_mouseenter = createToken({ name: "Event_mouseenter", pattern: /mouseenter/, longer_alt: Identifier });
434
- var Event_mouseleave = createToken({ name: "Event_mouseleave", pattern: /mouseleave/, longer_alt: Identifier });
435
- var Event_mousedown = createToken({ name: "Event_mousedown", pattern: /mousedown/, longer_alt: Identifier });
436
- var Event_mousemove = createToken({ name: "Event_mousemove", pattern: /mousemove/, longer_alt: Identifier });
437
- var Event_dblclick = createToken({ name: "Event_dblclick", pattern: /dblclick/, longer_alt: Identifier });
438
- var Event_keypress = createToken({ name: "Event_keypress", pattern: /keypress/, longer_alt: Identifier });
439
- var Event_mutation = createToken({ name: "Event_mutation", pattern: /mutation/, longer_alt: Identifier });
440
- var Event_mouseup = createToken({ name: "Event_mouseup", pattern: /mouseup/, longer_alt: Identifier });
441
- var Event_keydown = createToken({ name: "Event_keydown", pattern: /keydown/, longer_alt: Identifier });
442
- var Event_change = createToken({ name: "Event_change", pattern: /change/, longer_alt: Identifier });
443
- var Event_submit = createToken({ name: "Event_submit", pattern: /submit/, longer_alt: Identifier });
444
- var Event_scroll = createToken({ name: "Event_scroll", pattern: /scroll/, longer_alt: Identifier });
445
- var Event_resize = createToken({ name: "Event_resize", pattern: /resize/, longer_alt: Identifier });
446
- var Event_click = createToken({ name: "Event_click", pattern: /click/, longer_alt: Identifier });
447
- var Event_keyup = createToken({ name: "Event_keyup", pattern: /keyup/, longer_alt: Identifier });
448
- var Event_input = createToken({ name: "Event_input", pattern: /input/, longer_alt: Identifier });
449
- var Event_every = createToken({ name: "Event_every", pattern: /every/, longer_alt: Identifier });
450
- var Event_load = createToken({ name: "Event_load", pattern: /load/, longer_alt: Identifier });
451
- var allTokens = [
452
- // Whitespace
453
- WhiteSpace,
454
- // Literals
455
- StringLiteral,
456
- NumberLiteral,
457
- URLLiteral,
458
- // Selectors
459
- CSSSelector,
460
- AttributeSelector,
461
- HTMLSelector,
462
- // Variables
463
- LocalVariable,
464
- GlobalVariable,
465
- // Punctuation
466
- LParen,
467
- RParen,
468
- Comma,
469
- Dot,
470
- // Event names
471
- Event_intersection,
472
- Event_mouseenter,
473
- Event_mouseleave,
474
- Event_mousedown,
475
- Event_mousemove,
476
- Event_dblclick,
477
- Event_keypress,
478
- Event_mutation,
479
- Event_mouseup,
480
- Event_keydown,
481
- Event_change,
482
- Event_submit,
483
- Event_scroll,
484
- Event_resize,
485
- Event_click,
486
- Event_keyup,
487
- Event_input,
488
- Event_every,
489
- Event_load,
490
- // Command keywords
491
- Cmd_transition,
492
- Cmd_increment,
493
- Cmd_decrement,
494
- Cmd_continue,
495
- Cmd_behavior,
496
- Cmd_compound,
497
- Cmd_trigger,
498
- Cmd_prepend,
499
- Cmd_default,
500
- Cmd_install,
501
- Cmd_measure,
502
- Cmd_toggle,
503
- Cmd_remove,
504
- Cmd_append,
505
- Cmd_settle,
506
- Cmd_unless,
507
- Cmd_repeat,
508
- Cmd_return,
509
- Cmd_render,
510
- Cmd_fetch,
511
- Cmd_throw,
512
- Cmd_clone,
513
- Cmd_focus,
514
- Cmd_async,
515
- Cmd_morph,
516
- Cmd_break,
517
- Cmd_show,
518
- Cmd_hide,
519
- Cmd_wait,
520
- Cmd_take,
521
- Cmd_make,
522
- Cmd_halt,
523
- Cmd_send,
524
- Cmd_blur,
525
- Cmd_call,
526
- Cmd_tell,
527
- Cmd_init,
528
- Cmd_swap,
529
- Cmd_beep,
530
- Cmd_copy,
531
- Cmd_exit,
532
- Cmd_pick,
533
- Cmd_add,
534
- Cmd_put,
535
- Cmd_set,
536
- Cmd_log,
537
- Cmd_get,
538
- Cmd_go,
539
- Cmd_js,
540
- // Structural keywords
541
- Kw_previous,
542
- Kw_closest,
543
- Kw_while,
544
- Kw_until,
545
- Kw_times,
546
- Kw_first,
547
- Kw_false,
548
- Kw_then,
549
- Kw_else,
550
- Kw_from,
551
- Kw_into,
552
- Kw_with,
553
- Kw_have,
554
- Kw_last,
555
- Kw_next,
556
- Kw_true,
557
- Kw_null,
558
- Kw_end,
559
- Kw_for,
560
- Kw_and,
561
- Kw_not,
562
- Kw_has,
563
- Kw_you,
564
- Kw_its,
565
- Kw_the,
566
- Kw_on,
567
- Kw_if,
568
- Kw_to,
569
- Kw_in,
570
- Kw_of,
571
- Kw_as,
572
- Kw_at,
573
- Kw_by,
574
- Kw_or,
575
- Kw_is,
576
- Kw_me,
577
- Kw_it,
578
- Kw_my,
579
- Kw_no,
580
- // Identifier (catch-all)
581
- Identifier
582
- ];
583
- var hyperscriptLexer = new Lexer(allTokens, {
584
- positionTracking: "full",
585
- ensureOptimizations: true
586
- });
587
- var COMMAND_SCHEMAS = [
588
- { action: "toggle", category: "dom-class", roles: [{ role: "patient", required: true, expectedTypes: ["selector"] }, { role: "destination", required: false, expectedTypes: ["selector", "reference"] }], primaryRole: "patient" },
589
- { action: "add", category: "dom-class", roles: [{ role: "patient", required: true, expectedTypes: ["selector"] }, { role: "destination", required: false, expectedTypes: ["selector", "reference"] }], primaryRole: "patient" },
590
- { action: "remove", category: "dom-class", roles: [{ role: "patient", required: true, expectedTypes: ["selector"] }, { role: "source", required: false, expectedTypes: ["selector", "reference"] }], primaryRole: "patient" },
591
- { action: "put", category: "dom-content", roles: [{ role: "patient", required: true, expectedTypes: ["literal", "selector", "reference", "expression"] }, { role: "destination", required: true, expectedTypes: ["selector", "reference"] }], primaryRole: "patient" },
592
- { action: "set", category: "variable", roles: [{ role: "destination", required: true, expectedTypes: ["selector", "reference", "expression"] }, { role: "patient", required: true, expectedTypes: ["literal", "expression", "reference"] }], primaryRole: "destination" },
593
- { action: "show", category: "dom-visibility", roles: [{ role: "patient", required: true, expectedTypes: ["selector", "reference"] }, { role: "style", required: false, expectedTypes: ["literal"] }], primaryRole: "patient" },
594
- { action: "hide", category: "dom-visibility", roles: [{ role: "patient", required: true, expectedTypes: ["selector", "reference"] }, { role: "style", required: false, expectedTypes: ["literal"] }], primaryRole: "patient" },
595
- { action: "on", category: "event", roles: [{ role: "event", required: true, expectedTypes: ["literal"] }, { role: "source", required: false, expectedTypes: ["selector", "reference"] }], primaryRole: "event" },
596
- { action: "trigger", category: "event", roles: [{ role: "event", required: true, expectedTypes: ["literal", "expression"] }, { role: "destination", required: false, expectedTypes: ["selector", "reference"] }], primaryRole: "event" },
597
- { action: "wait", category: "async", roles: [{ role: "patient", required: true, expectedTypes: ["literal", "expression"] }], primaryRole: "patient" },
598
- { action: "fetch", category: "async", roles: [{ role: "source", required: true, expectedTypes: ["literal", "expression"] }, { role: "responseType", required: false, expectedTypes: ["literal", "expression"] }, { role: "method", required: false, expectedTypes: ["literal"] }, { role: "destination", required: false, expectedTypes: ["selector", "reference"] }], primaryRole: "source" },
599
- { action: "increment", category: "variable", roles: [{ role: "patient", required: true, expectedTypes: ["selector", "reference", "expression"] }, { role: "quantity", required: false, expectedTypes: ["literal"] }], primaryRole: "patient" },
600
- { action: "decrement", category: "variable", roles: [{ role: "patient", required: true, expectedTypes: ["selector", "reference", "expression"] }, { role: "quantity", required: false, expectedTypes: ["literal"] }], primaryRole: "patient" },
601
- { action: "append", category: "dom-content", roles: [{ role: "patient", required: true, expectedTypes: ["literal", "selector", "expression"] }, { role: "destination", required: true, expectedTypes: ["selector", "reference"] }], primaryRole: "patient" },
602
- { action: "prepend", category: "dom-content", roles: [{ role: "patient", required: true, expectedTypes: ["literal", "selector", "expression"] }, { role: "destination", required: true, expectedTypes: ["selector", "reference"] }], primaryRole: "patient" },
603
- { action: "log", category: "variable", roles: [{ role: "patient", required: true, expectedTypes: ["literal", "selector", "reference", "expression"] }], primaryRole: "patient" },
604
- { action: "get", category: "variable", roles: [{ role: "source", required: true, expectedTypes: ["selector", "reference", "expression"] }, { role: "destination", required: false, expectedTypes: ["reference"] }], primaryRole: "source" },
605
- { action: "take", category: "dom-content", roles: [{ role: "patient", required: true, expectedTypes: ["selector"] }, { role: "source", required: false, expectedTypes: ["selector", "reference"] }], primaryRole: "patient" },
606
- { action: "make", category: "dom-content", roles: [{ role: "patient", required: true, expectedTypes: ["literal", "selector"] }], primaryRole: "patient" },
607
- { action: "halt", category: "control-flow", roles: [{ role: "patient", required: false, expectedTypes: ["literal", "reference", "expression"] }], primaryRole: "patient" },
608
- { action: "settle", category: "async", roles: [{ role: "patient", required: false, expectedTypes: ["selector", "reference"] }], primaryRole: "patient" },
609
- { action: "throw", category: "control-flow", roles: [{ role: "patient", required: true, expectedTypes: ["literal", "expression"] }], primaryRole: "patient" },
610
- { action: "send", category: "event", roles: [{ role: "event", required: true, expectedTypes: ["literal", "expression"] }, { role: "destination", required: false, expectedTypes: ["selector", "reference"] }], primaryRole: "event" },
611
- { action: "if", category: "control-flow", roles: [{ role: "condition", required: true, expectedTypes: ["expression"] }], primaryRole: "condition" },
612
- { action: "unless", category: "control-flow", roles: [{ role: "condition", required: true, expectedTypes: ["expression"] }], primaryRole: "condition" },
613
- { action: "else", category: "control-flow", roles: [], primaryRole: "patient" },
614
- { action: "repeat", category: "control-flow", roles: [{ role: "loopType", required: true, expectedTypes: ["literal"] }, { role: "quantity", required: false, expectedTypes: ["literal", "expression"] }, { role: "event", required: false, expectedTypes: ["literal", "expression"] }, { role: "source", required: false, expectedTypes: ["selector", "reference"] }], primaryRole: "loopType" },
615
- { action: "for", category: "control-flow", roles: [{ role: "patient", required: true, expectedTypes: ["reference"] }, { role: "source", required: true, expectedTypes: ["selector", "reference", "expression"] }], primaryRole: "patient" },
616
- { action: "while", category: "control-flow", roles: [{ role: "condition", required: true, expectedTypes: ["expression"] }], primaryRole: "condition" },
617
- { action: "continue", category: "control-flow", roles: [], primaryRole: "patient" },
618
- { action: "go", category: "navigation", roles: [{ role: "destination", required: true, expectedTypes: ["literal", "expression"] }], primaryRole: "destination" },
619
- { action: "transition", category: "dom-visibility", roles: [{ role: "patient", required: true, expectedTypes: ["literal"] }, { role: "goal", required: true, expectedTypes: ["literal", "expression"] }, { role: "destination", required: false, expectedTypes: ["selector", "reference"] }, { role: "duration", required: false, expectedTypes: ["literal"] }, { role: "style", required: false, expectedTypes: ["literal"] }], primaryRole: "patient" },
620
- { action: "clone", category: "dom-content", roles: [{ role: "patient", required: true, expectedTypes: ["selector", "reference"] }, { role: "destination", required: false, expectedTypes: ["selector", "reference"] }], primaryRole: "patient" },
621
- { action: "focus", category: "dom-content", roles: [{ role: "patient", required: false, expectedTypes: ["selector", "reference"] }], primaryRole: "patient" },
622
- { action: "blur", category: "dom-content", roles: [{ role: "patient", required: false, expectedTypes: ["selector", "reference"] }], primaryRole: "patient" },
623
- { action: "call", category: "control-flow", roles: [{ role: "patient", required: true, expectedTypes: ["expression", "reference"] }], primaryRole: "patient" },
624
- { action: "return", category: "control-flow", roles: [{ role: "patient", required: false, expectedTypes: ["literal", "expression", "reference"] }], primaryRole: "patient" },
625
- { action: "js", category: "control-flow", roles: [{ role: "patient", required: false, expectedTypes: ["expression"] }], primaryRole: "patient" },
626
- { action: "async", category: "async", roles: [], primaryRole: "patient" },
627
- { action: "tell", category: "control-flow", roles: [{ role: "destination", required: true, expectedTypes: ["selector", "reference"] }], primaryRole: "destination" },
628
- { action: "default", category: "variable", roles: [{ role: "destination", required: true, expectedTypes: ["reference"] }, { role: "patient", required: true, expectedTypes: ["literal", "expression"] }], primaryRole: "destination" },
629
- { action: "init", category: "control-flow", roles: [], primaryRole: "patient" },
630
- { action: "behavior", category: "control-flow", roles: [{ role: "patient", required: true, expectedTypes: ["literal", "reference", "expression"] }], primaryRole: "patient" },
631
- { action: "install", category: "control-flow", roles: [{ role: "patient", required: true, expectedTypes: ["literal", "reference", "expression"] }, { role: "destination", required: false, expectedTypes: ["selector", "reference"] }], primaryRole: "patient" },
632
- { action: "measure", category: "dom-content", roles: [{ role: "patient", required: false, expectedTypes: ["literal", "expression"] }, { role: "source", required: false, expectedTypes: ["selector", "reference"] }], primaryRole: "patient" },
633
- { action: "swap", category: "dom-content", roles: [{ role: "method", required: false, expectedTypes: ["literal"] }, { role: "destination", required: true, expectedTypes: ["selector", "reference"] }, { role: "patient", required: false, expectedTypes: ["literal", "expression", "selector"] }], primaryRole: "destination" },
634
- { action: "morph", category: "dom-content", roles: [{ role: "destination", required: true, expectedTypes: ["selector", "reference"] }, { role: "patient", required: true, expectedTypes: ["literal", "expression", "selector"] }], primaryRole: "destination" },
635
- { action: "beep", category: "variable", roles: [{ role: "patient", required: false, expectedTypes: ["literal", "selector", "reference", "expression"] }], primaryRole: "patient" },
636
- { action: "break", category: "control-flow", roles: [], primaryRole: "patient" },
637
- { action: "copy", category: "dom-content", roles: [{ role: "patient", required: true, expectedTypes: ["literal", "selector", "reference", "expression"] }], primaryRole: "patient" },
638
- { action: "exit", category: "control-flow", roles: [], primaryRole: "patient" },
639
- { action: "pick", category: "variable", roles: [{ role: "patient", required: true, expectedTypes: ["literal", "expression", "reference"] }, { role: "source", required: false, expectedTypes: ["reference", "expression"] }], primaryRole: "patient" },
640
- { action: "render", category: "dom-content", roles: [{ role: "patient", required: true, expectedTypes: ["selector", "reference", "expression"] }, { role: "style", required: false, expectedTypes: ["expression", "reference"] }], primaryRole: "patient" },
641
- { action: "compound", category: "control-flow", roles: [], primaryRole: "patient" }
642
- ];
643
-
644
- // src/chevrotain-parser.ts
645
- var HyperscriptParser = class extends CstParser {
646
- constructor() {
647
- super(allTokens, {
648
- recoveryEnabled: true,
649
- maxLookahead: 3
650
- });
651
- this.performSelfAnalysis();
652
- }
653
- // -------------------------------------------------------------------------
654
- // Top-level rules
655
- // -------------------------------------------------------------------------
656
- program = this.RULE("program", () => {
657
- this.MANY(() => {
658
- this.SUBRULE(this.statement);
659
- });
660
- });
661
- statement = this.RULE("statement", () => {
662
- this.OR([
663
- { ALT: () => this.SUBRULE(this.eventHandler) },
664
- { ALT: () => this.SUBRULE(this.commandChain) },
665
- { ALT: () => this.SUBRULE(this.behaviorDef) }
666
- ]);
667
- });
668
- // -------------------------------------------------------------------------
669
- // Event handlers
670
- // -------------------------------------------------------------------------
671
- eventHandler = this.RULE("eventHandler", () => {
672
- this.CONSUME(Kw_on);
673
- this.SUBRULE(this.eventName);
674
- this.OPTION(() => {
675
- this.SUBRULE(this.eventModifiers);
676
- });
677
- this.OPTION2(() => {
678
- this.SUBRULE(this.commandChain);
679
- });
680
- this.OPTION3(() => {
681
- this.CONSUME(Kw_end);
682
- });
683
- });
684
- eventName = this.RULE("eventName", () => {
685
- this.OR([
686
- { ALT: () => this.CONSUME(Event_click) },
687
- { ALT: () => this.CONSUME(Event_dblclick) },
688
- { ALT: () => this.CONSUME(Event_mousedown) },
689
- { ALT: () => this.CONSUME(Event_mouseup) },
690
- { ALT: () => this.CONSUME(Event_mousemove) },
691
- { ALT: () => this.CONSUME(Event_mouseenter) },
692
- { ALT: () => this.CONSUME(Event_mouseleave) },
693
- { ALT: () => this.CONSUME(Event_keydown) },
694
- { ALT: () => this.CONSUME(Event_keyup) },
695
- { ALT: () => this.CONSUME(Event_input) },
696
- { ALT: () => this.CONSUME(Event_change) },
697
- { ALT: () => this.CONSUME(Event_submit) },
698
- { ALT: () => this.CONSUME(Event_load) },
699
- { ALT: () => this.CONSUME(Event_scroll) },
700
- { ALT: () => this.CONSUME(Event_resize) },
701
- { ALT: () => this.CONSUME(Event_mutation) },
702
- { ALT: () => this.CONSUME(Event_intersection) },
703
- { ALT: () => this.CONSUME(Event_every) },
704
- { ALT: () => this.CONSUME(Identifier) }
705
- // Custom event name
706
- ]);
707
- });
708
- eventModifiers = this.RULE("eventModifiers", () => {
709
- this.MANY(() => {
710
- this.CONSUME(Dot);
711
- this.CONSUME(Identifier);
712
- this.OPTION(() => {
713
- this.CONSUME(LParen);
714
- this.SUBRULE(this.expression);
715
- this.CONSUME(RParen);
716
- });
717
- });
718
- });
719
- // -------------------------------------------------------------------------
720
- // Command chain (commands connected by 'then')
721
- // -------------------------------------------------------------------------
722
- commandChain = this.RULE("commandChain", () => {
723
- this.SUBRULE(this.command);
724
- this.MANY(() => {
725
- this.CONSUME(Kw_then);
726
- this.SUBRULE2(this.command);
727
- });
728
- });
729
- // -------------------------------------------------------------------------
730
- // Commands (grouped by category for error messages)
731
- // -------------------------------------------------------------------------
732
- command = this.RULE("command", () => {
733
- this.OR([
734
- // DOM class/attribute
735
- { ALT: () => this.SUBRULE(this.toggleCommand) },
736
- { ALT: () => this.SUBRULE(this.addCommand) },
737
- { ALT: () => this.SUBRULE(this.removeCommand) },
738
- // DOM content
739
- { ALT: () => this.SUBRULE(this.putCommand) },
740
- { ALT: () => this.SUBRULE(this.setCommand) },
741
- { ALT: () => this.SUBRULE(this.appendCommand) },
742
- { ALT: () => this.SUBRULE(this.prependCommand) },
743
- // DOM visibility
744
- { ALT: () => this.SUBRULE(this.showCommand) },
745
- { ALT: () => this.SUBRULE(this.hideCommand) },
746
- { ALT: () => this.SUBRULE(this.transitionCommand) },
747
- // Variable
748
- { ALT: () => this.SUBRULE(this.logCommand) },
749
- { ALT: () => this.SUBRULE(this.incrementCommand) },
750
- { ALT: () => this.SUBRULE(this.decrementCommand) },
751
- // Async
752
- { ALT: () => this.SUBRULE(this.fetchCommand) },
753
- { ALT: () => this.SUBRULE(this.waitCommand) },
754
- // Event
755
- { ALT: () => this.SUBRULE(this.sendCommand) },
756
- { ALT: () => this.SUBRULE(this.triggerCommand) },
757
- // Navigation
758
- { ALT: () => this.SUBRULE(this.goCommand) },
759
- // Control flow
760
- { ALT: () => this.SUBRULE(this.ifCommand) },
761
- { ALT: () => this.SUBRULE(this.repeatCommand) },
762
- // Execution
763
- { ALT: () => this.SUBRULE(this.callCommand) },
764
- { ALT: () => this.SUBRULE(this.jsCommand) },
765
- { ALT: () => this.SUBRULE(this.returnCommand) },
766
- { ALT: () => this.SUBRULE(this.throwCommand) },
767
- { ALT: () => this.SUBRULE(this.haltCommand) }
768
- ]);
769
- });
770
- // --- DOM class/attribute ---
771
- toggleCommand = this.RULE("toggleCommand", () => {
772
- this.CONSUME(Cmd_toggle);
773
- this.SUBRULE(this.selectorOrExpression);
774
- this.OPTION(() => {
775
- this.CONSUME(Kw_on);
776
- this.SUBRULE2(this.selectorOrExpression);
777
- });
778
- });
779
- addCommand = this.RULE("addCommand", () => {
780
- this.CONSUME(Cmd_add);
781
- this.SUBRULE(this.selectorOrExpression);
782
- this.OPTION(() => {
783
- this.CONSUME(Kw_to);
784
- this.SUBRULE2(this.selectorOrExpression);
785
- });
786
- });
787
- removeCommand = this.RULE("removeCommand", () => {
788
- this.CONSUME(Cmd_remove);
789
- this.SUBRULE(this.selectorOrExpression);
790
- this.OPTION(() => {
791
- this.CONSUME(Kw_from);
792
- this.SUBRULE2(this.selectorOrExpression);
793
- });
794
- });
795
- // --- DOM content ---
796
- putCommand = this.RULE("putCommand", () => {
797
- this.CONSUME(Cmd_put);
798
- this.SUBRULE(this.expression);
799
- this.OPTION(() => {
800
- this.OR([
801
- { ALT: () => this.CONSUME(Kw_into) },
802
- { ALT: () => this.CONSUME(Identifier, { LABEL: "putMarker" }) }
803
- // before/after
804
- ]);
805
- this.SUBRULE2(this.selectorOrExpression);
806
- });
807
- });
808
- setCommand = this.RULE("setCommand", () => {
809
- this.CONSUME(Cmd_set);
810
- this.SUBRULE(this.selectorOrExpression);
811
- this.CONSUME(Kw_to);
812
- this.SUBRULE2(this.expression);
813
- });
814
- appendCommand = this.RULE("appendCommand", () => {
815
- this.CONSUME(Cmd_append);
816
- this.SUBRULE(this.expression);
817
- this.CONSUME(Kw_to);
818
- this.SUBRULE2(this.selectorOrExpression);
819
- });
820
- prependCommand = this.RULE("prependCommand", () => {
821
- this.CONSUME(Cmd_prepend);
822
- this.SUBRULE(this.expression);
823
- this.CONSUME(Kw_to);
824
- this.SUBRULE2(this.selectorOrExpression);
825
- });
826
- // --- DOM visibility ---
827
- showCommand = this.RULE("showCommand", () => {
828
- this.CONSUME(Cmd_show);
829
- this.OPTION(() => this.SUBRULE(this.selectorOrExpression));
830
- });
831
- hideCommand = this.RULE("hideCommand", () => {
832
- this.CONSUME(Cmd_hide);
833
- this.OPTION(() => this.SUBRULE(this.selectorOrExpression));
834
- });
835
- transitionCommand = this.RULE("transitionCommand", () => {
836
- this.CONSUME(Cmd_transition);
837
- this.SUBRULE(this.selectorOrExpression);
838
- });
839
- // --- Variable ---
840
- logCommand = this.RULE("logCommand", () => {
841
- this.CONSUME(Cmd_log);
842
- this.SUBRULE(this.expression);
843
- });
844
- incrementCommand = this.RULE("incrementCommand", () => {
845
- this.CONSUME(Cmd_increment);
846
- this.SUBRULE(this.selectorOrExpression);
847
- this.OPTION(() => {
848
- this.CONSUME(Kw_by);
849
- this.SUBRULE2(this.expression);
850
- });
851
- });
852
- decrementCommand = this.RULE("decrementCommand", () => {
853
- this.CONSUME(Cmd_decrement);
854
- this.SUBRULE(this.selectorOrExpression);
855
- this.OPTION(() => {
856
- this.CONSUME(Kw_by);
857
- this.SUBRULE2(this.expression);
858
- });
859
- });
860
- // --- Async ---
861
- fetchCommand = this.RULE("fetchCommand", () => {
862
- this.CONSUME(Cmd_fetch);
863
- this.SUBRULE(this.expression);
864
- this.OPTION(() => {
865
- this.CONSUME(Kw_as);
866
- this.CONSUME(Identifier, { LABEL: "responseType" });
867
- });
868
- });
869
- waitCommand = this.RULE("waitCommand", () => {
870
- this.CONSUME(Cmd_wait);
871
- this.OPTION(() => this.SUBRULE(this.expression));
872
- });
873
- // --- Event ---
874
- sendCommand = this.RULE("sendCommand", () => {
875
- this.CONSUME(Cmd_send);
876
- this.CONSUME(Identifier, { LABEL: "eventName" });
877
- this.OPTION(() => {
878
- this.CONSUME(Kw_to);
879
- this.SUBRULE(this.selectorOrExpression);
880
- });
881
- });
882
- triggerCommand = this.RULE("triggerCommand", () => {
883
- this.CONSUME(Cmd_trigger);
884
- this.CONSUME(Identifier, { LABEL: "eventName" });
885
- this.OPTION(() => {
886
- this.CONSUME(Kw_on);
887
- this.SUBRULE(this.selectorOrExpression);
888
- });
889
- });
890
- // --- Navigation ---
891
- goCommand = this.RULE("goCommand", () => {
892
- this.CONSUME(Cmd_go);
893
- this.OPTION(() => this.CONSUME(Kw_to));
894
- this.SUBRULE(this.expression);
895
- });
896
- // --- Control flow ---
897
- ifCommand = this.RULE("ifCommand", () => {
898
- this.CONSUME(Kw_if);
899
- this.SUBRULE(this.expression);
900
- this.OPTION(() => this.SUBRULE(this.commandChain));
901
- this.OPTION2(() => {
902
- this.CONSUME(Kw_else);
903
- this.OPTION3(() => this.SUBRULE2(this.commandChain));
904
- });
905
- this.OPTION4(() => this.CONSUME(Kw_end));
906
- });
907
- repeatCommand = this.RULE("repeatCommand", () => {
908
- this.CONSUME(Cmd_repeat);
909
- this.OR([
910
- {
911
- ALT: () => {
912
- this.SUBRULE(this.expression);
913
- this.CONSUME(Kw_times);
914
- }
915
- },
916
- {
917
- ALT: () => {
918
- this.CONSUME(Kw_for);
919
- this.CONSUME(Identifier, { LABEL: "loopVar" });
920
- this.CONSUME(Kw_in);
921
- this.SUBRULE2(this.expression);
922
- }
923
- },
924
- {
925
- ALT: () => {
926
- this.CONSUME(Kw_while);
927
- this.SUBRULE3(this.expression);
928
- }
929
- },
930
- {
931
- ALT: () => {
932
- this.CONSUME(Kw_until);
933
- this.SUBRULE4(this.expression);
344
+ // src/simple-diagnostics.ts
345
+ import { DiagnosticSeverity } from "vscode-languageserver/node.js";
346
+ function isPossessive(code, i) {
347
+ if (i === 0) return false;
348
+ const prev = code[i - 1];
349
+ if (!/[\w\-#.]/.test(prev)) return false;
350
+ if (i + 1 >= code.length || code[i + 1] !== "s") return false;
351
+ if (i + 2 >= code.length) return true;
352
+ const after = code[i + 2];
353
+ return /[\s),.\]}|@*]/.test(after);
354
+ }
355
+ function runSimpleDiagnostics(code, _language = "en") {
356
+ const diagnostics = [];
357
+ let state = 0 /* NORMAL */;
358
+ let quoteStartLine = 0;
359
+ let quoteStartChar = 0;
360
+ let parenDepth = 0;
361
+ let bracketDepth = 0;
362
+ let line = 0;
363
+ let col = 0;
364
+ for (let i = 0; i < code.length; i++) {
365
+ const ch = code[i];
366
+ if (ch === "\n") {
367
+ line++;
368
+ col = 0;
369
+ continue;
370
+ }
371
+ if (state !== 0 /* NORMAL */) {
372
+ if (ch === "\\") {
373
+ i++;
374
+ col += 2;
375
+ continue;
376
+ }
377
+ if (state === 1 /* IN_SINGLE */ && ch === "'" || state === 2 /* IN_DOUBLE */ && ch === '"' || state === 3 /* IN_BACKTICK */ && ch === "`") {
378
+ state = 0 /* NORMAL */;
379
+ }
380
+ col++;
381
+ continue;
382
+ }
383
+ switch (ch) {
384
+ case "'":
385
+ if (isPossessive(code, i)) {
386
+ i++;
387
+ col += 2;
388
+ continue;
934
389
  }
390
+ state = 1 /* IN_SINGLE */;
391
+ quoteStartLine = line;
392
+ quoteStartChar = col;
393
+ break;
394
+ case '"':
395
+ state = 2 /* IN_DOUBLE */;
396
+ quoteStartLine = line;
397
+ quoteStartChar = col;
398
+ break;
399
+ case "`":
400
+ state = 3 /* IN_BACKTICK */;
401
+ quoteStartLine = line;
402
+ quoteStartChar = col;
403
+ break;
404
+ case "(":
405
+ parenDepth++;
406
+ break;
407
+ case ")":
408
+ parenDepth--;
409
+ break;
410
+ case "[":
411
+ bracketDepth++;
412
+ break;
413
+ case "]":
414
+ bracketDepth--;
415
+ break;
416
+ }
417
+ col++;
418
+ }
419
+ if (state !== 0 /* NORMAL */) {
420
+ const quoteType = state === 1 /* IN_SINGLE */ ? "single" : state === 2 /* IN_DOUBLE */ ? "double" : "backtick";
421
+ diagnostics.push({
422
+ range: {
423
+ start: { line: quoteStartLine, character: quoteStartChar },
424
+ end: { line: quoteStartLine, character: quoteStartChar + 1 }
935
425
  },
936
- {
937
- ALT: () => {
938
- }
939
- }
940
- ]);
941
- this.OPTION(() => this.SUBRULE(this.commandChain));
942
- this.OPTION2(() => this.CONSUME(Kw_end));
943
- });
944
- // --- Execution ---
945
- callCommand = this.RULE("callCommand", () => {
946
- this.CONSUME(Cmd_call);
947
- this.SUBRULE(this.expression);
948
- });
949
- jsCommand = this.RULE("jsCommand", () => {
950
- this.CONSUME(Cmd_js);
951
- this.MANY(() => {
952
- this.OR([
953
- { ALT: () => this.CONSUME(Identifier) },
954
- { ALT: () => this.CONSUME(StringLiteral) },
955
- { ALT: () => this.CONSUME(NumberLiteral) },
956
- { ALT: () => this.CONSUME(CSSSelector) },
957
- { ALT: () => this.CONSUME(LocalVariable) },
958
- { ALT: () => this.CONSUME(LParen) },
959
- { ALT: () => this.CONSUME(RParen) },
960
- { ALT: () => this.CONSUME(Comma) },
961
- { ALT: () => this.CONSUME(Dot) }
962
- ]);
963
- });
964
- this.CONSUME(Kw_end);
965
- });
966
- returnCommand = this.RULE("returnCommand", () => {
967
- this.CONSUME(Cmd_return);
968
- this.OPTION(() => this.SUBRULE(this.expression));
969
- });
970
- throwCommand = this.RULE("throwCommand", () => {
971
- this.CONSUME(Cmd_throw);
972
- this.SUBRULE(this.expression);
973
- });
974
- haltCommand = this.RULE("haltCommand", () => {
975
- this.CONSUME(Cmd_halt);
976
- });
977
- // -------------------------------------------------------------------------
978
- // Behavior definitions
979
- // -------------------------------------------------------------------------
980
- behaviorDef = this.RULE("behaviorDef", () => {
981
- this.CONSUME(Cmd_behavior);
982
- this.CONSUME(Identifier, { LABEL: "behaviorName" });
983
- this.OPTION(() => {
984
- this.CONSUME(LParen);
985
- this.AT_LEAST_ONE_SEP({
986
- SEP: Comma,
987
- DEF: () => this.CONSUME2(Identifier, { LABEL: "param" })
988
- });
989
- this.CONSUME(RParen);
426
+ severity: DiagnosticSeverity.Error,
427
+ code: "unmatched-quote",
428
+ source: "lokascript",
429
+ message: `Unmatched ${quoteType} quote`
990
430
  });
991
- this.MANY(() => this.SUBRULE(this.statement));
992
- this.CONSUME(Kw_end);
993
- });
994
- // -------------------------------------------------------------------------
995
- // Expressions (simplified for LSP — full expression parsing is in core)
996
- // -------------------------------------------------------------------------
997
- expression = this.RULE("expression", () => {
998
- this.SUBRULE(this.primaryExpression);
999
- this.MANY(() => {
1000
- this.OR([
1001
- {
1002
- ALT: () => {
1003
- this.OR2([
1004
- { ALT: () => this.CONSUME(Kw_is) },
1005
- { ALT: () => this.CONSUME(Kw_and) },
1006
- { ALT: () => this.CONSUME(Kw_or) }
1007
- ]);
1008
- this.SUBRULE2(this.primaryExpression);
1009
- }
1010
- },
1011
- {
1012
- ALT: () => {
1013
- this.CONSUME(Dot);
1014
- this.CONSUME(Identifier);
1015
- }
1016
- }
1017
- ]);
431
+ }
432
+ if (parenDepth !== 0) {
433
+ diagnostics.push({
434
+ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 1 } },
435
+ severity: DiagnosticSeverity.Error,
436
+ code: "unbalanced-parens",
437
+ source: "lokascript",
438
+ message: `Unbalanced parentheses`
1018
439
  });
1019
- });
1020
- primaryExpression = this.RULE("primaryExpression", () => {
1021
- this.OPTION(() => {
1022
- this.OR([
1023
- { ALT: () => this.CONSUME(Kw_not) },
1024
- { ALT: () => this.CONSUME(Kw_the) },
1025
- { ALT: () => this.CONSUME(Kw_no) }
1026
- ]);
440
+ }
441
+ if (bracketDepth !== 0) {
442
+ diagnostics.push({
443
+ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 1 } },
444
+ severity: DiagnosticSeverity.Error,
445
+ code: "unbalanced-brackets",
446
+ source: "lokascript",
447
+ message: `Unbalanced brackets`
1027
448
  });
1028
- this.OR2([
1029
- { ALT: () => this.CONSUME(StringLiteral) },
1030
- { ALT: () => this.CONSUME(NumberLiteral) },
1031
- { ALT: () => this.CONSUME(CSSSelector) },
1032
- { ALT: () => this.CONSUME(AttributeSelector) },
1033
- { ALT: () => this.CONSUME(HTMLSelector) },
1034
- { ALT: () => this.CONSUME(LocalVariable) },
1035
- { ALT: () => this.CONSUME(GlobalVariable) },
1036
- { ALT: () => this.CONSUME(Kw_me) },
1037
- { ALT: () => this.CONSUME(Kw_it) },
1038
- { ALT: () => this.CONSUME(Kw_true) },
1039
- { ALT: () => this.CONSUME(Kw_false) },
1040
- { ALT: () => this.CONSUME(Identifier) },
1041
- {
1042
- ALT: () => {
1043
- this.CONSUME(LParen);
1044
- this.SUBRULE(this.expression);
1045
- this.CONSUME(RParen);
1046
- }
1047
- }
1048
- ]);
1049
- });
1050
- // -------------------------------------------------------------------------
1051
- // Selectors or expressions
1052
- // -------------------------------------------------------------------------
1053
- selectorOrExpression = this.RULE("selectorOrExpression", () => {
1054
- this.OR([
1055
- { ALT: () => this.CONSUME(CSSSelector) },
1056
- { ALT: () => this.CONSUME(AttributeSelector) },
1057
- { ALT: () => this.CONSUME(HTMLSelector) },
1058
- { ALT: () => this.CONSUME(Kw_me) },
1059
- { ALT: () => this.CONSUME(Kw_it) },
1060
- { ALT: () => this.CONSUME(LocalVariable) },
1061
- { ALT: () => this.CONSUME(GlobalVariable) },
1062
- { ALT: () => this.CONSUME(Identifier) }
1063
- ]);
1064
- });
1065
- };
1066
- var parserInstance = new HyperscriptParser();
1067
- function parseWithRecovery(code) {
1068
- const lexResult = hyperscriptLexer.tokenize(code);
1069
- const diagnostics = lexResult.errors.map((err) => ({
1070
- message: err.message,
1071
- severity: "error",
1072
- startOffset: err.offset,
1073
- endOffset: err.offset + (err.length || 1),
1074
- startLine: err.line ?? 1,
1075
- startColumn: err.column ?? 1,
1076
- code: "lex-error"
1077
- }));
1078
- const nonWS = lexResult.tokens.filter((t) => t.tokenType !== WhiteSpace);
1079
- parserInstance.input = nonWS;
1080
- const cst = parserInstance.program();
1081
- for (const err of parserInstance.errors) {
1082
- diagnostics.push(recognitionExceptionToDiagnostic(err));
1083
449
  }
1084
- return {
1085
- cst,
1086
- diagnostics,
1087
- tokens: lexResult.tokens
1088
- };
450
+ return diagnostics;
1089
451
  }
1090
- function getContentAssist(code, offset) {
1091
- const lexResult = hyperscriptLexer.tokenize(code);
1092
- const nonWS = lexResult.tokens.filter((t) => t.tokenType !== WhiteSpace);
1093
- const suggestions = [];
1094
- const tokenBefore = findTokenBefore(nonWS, offset);
1095
- const context = inferContextFromTokens(nonWS, tokenBefore);
1096
- switch (context) {
1097
- case "start":
1098
- case "after-then":
1099
- suggestions.push(...getCommandSuggestions());
1100
- suggestions.push({
1101
- label: "on",
1102
- kind: "keyword",
1103
- detail: "Event handler",
1104
- insertText: "on "
1105
- });
1106
- break;
1107
- case "after-on":
1108
- suggestions.push(...getEventSuggestions());
1109
- break;
1110
- case "after-command":
1111
- if (tokenBefore) {
1112
- suggestions.push(...getRoleMarkerSuggestions(tokenBefore));
452
+ var DIRECTIVE_IF = /^#if\b/;
453
+ var DIRECTIVE_FOR = /^#for\b/;
454
+ var DIRECTIVE_ELSE = /^#else\s*$/;
455
+ var DIRECTIVE_END = /^#end\s*$/;
456
+ var DIRECTIVE_CONTINUE = /^#continue\s*$/;
457
+ var DIRECTIVE_FOR_FORM = /^#for\s+([A-Za-z_$][\w$]*)\s+in\s+.+$/;
458
+ function runDirectiveDiagnostics(code) {
459
+ const diagnostics = [];
460
+ const lines = code.split("\n");
461
+ const stack = [];
462
+ for (let i = 0; i < lines.length; i++) {
463
+ const raw = lines[i];
464
+ const trimmed = raw.trim();
465
+ if (!trimmed.startsWith("#")) continue;
466
+ const indent = raw.indexOf(trimmed.charAt(0));
467
+ const range = {
468
+ start: { line: i, character: Math.max(0, indent) },
469
+ end: { line: i, character: raw.length }
470
+ };
471
+ if (DIRECTIVE_IF.test(trimmed)) {
472
+ stack.push({ kind: "if", line: i, hasElse: false });
473
+ } else if (DIRECTIVE_FOR.test(trimmed)) {
474
+ if (!DIRECTIVE_FOR_FORM.test(trimmed)) {
475
+ diagnostics.push({
476
+ range,
477
+ severity: DiagnosticSeverity.Error,
478
+ code: "directive-malformed-for",
479
+ source: "lokascript",
480
+ message: "#for must be `#for <name> in <expr>`"
481
+ });
1113
482
  }
1114
- break;
1115
- case "after-marker":
1116
- suggestions.push(...getSelectorSuggestions());
1117
- break;
1118
- default:
1119
- suggestions.push(...getCommandSuggestions());
1120
- suggestions.push(...getSelectorSuggestions());
1121
- break;
483
+ stack.push({ kind: "for", line: i, hasElse: false });
484
+ } else if (DIRECTIVE_ELSE.test(trimmed)) {
485
+ const top = stack[stack.length - 1];
486
+ if (!top) {
487
+ diagnostics.push({
488
+ range,
489
+ severity: DiagnosticSeverity.Error,
490
+ code: "directive-stray-else",
491
+ source: "lokascript",
492
+ message: "#else outside of #if or #for block"
493
+ });
494
+ } else if (top.hasElse) {
495
+ diagnostics.push({
496
+ range,
497
+ severity: DiagnosticSeverity.Error,
498
+ code: "directive-duplicate-else",
499
+ source: "lokascript",
500
+ message: "duplicate #else in this block"
501
+ });
502
+ } else {
503
+ top.hasElse = true;
504
+ }
505
+ } else if (DIRECTIVE_END.test(trimmed)) {
506
+ if (stack.length === 0) {
507
+ diagnostics.push({
508
+ range,
509
+ severity: DiagnosticSeverity.Error,
510
+ code: "directive-stray-end",
511
+ source: "lokascript",
512
+ message: "#end without matching #if or #for"
513
+ });
514
+ } else {
515
+ stack.pop();
516
+ }
517
+ } else if (DIRECTIVE_CONTINUE.test(trimmed)) {
518
+ const inFor = stack.some((b) => b.kind === "for");
519
+ if (!inFor) {
520
+ diagnostics.push({
521
+ range,
522
+ severity: DiagnosticSeverity.Error,
523
+ code: "directive-stray-continue",
524
+ source: "lokascript",
525
+ message: "#continue outside of #for block"
526
+ });
527
+ }
528
+ }
1122
529
  }
1123
- return suggestions;
1124
- }
1125
- function recognitionExceptionToDiagnostic(err) {
1126
- const token = err.token;
1127
- const startOffset = token.startOffset ?? 0;
1128
- const endOffset = token.endOffset ?? startOffset + 1;
1129
- let message = err.message;
1130
- const match = message.match(/Expecting (.*?) but found/);
1131
- if (match) {
1132
- const expected = match[1].replace(/token of type --> /g, "").replace(/ <--/g, "").replace(/one of these possible Token Types:\s*/g, "").replace(/\[.*?\]/g, (m) => {
1133
- const names = m.slice(1, -1).split(",").map((n) => n.trim());
1134
- return names.slice(0, 5).join(", ") + (names.length > 5 ? "..." : "");
530
+ for (const open of stack) {
531
+ diagnostics.push({
532
+ range: {
533
+ start: { line: open.line, character: 0 },
534
+ end: { line: open.line, character: lines[open.line]?.length ?? 0 }
535
+ },
536
+ severity: DiagnosticSeverity.Error,
537
+ code: "directive-unclosed",
538
+ source: "lokascript",
539
+ message: `Unclosed #${open.kind} \u2014 missing #end`
1135
540
  });
1136
- message = `Expected ${expected}`;
1137
- if (token.image) {
1138
- message += ` but found '${token.image}'`;
1139
- }
1140
541
  }
1141
- return {
1142
- message,
1143
- severity: "error",
1144
- startOffset,
1145
- endOffset,
1146
- startLine: token.startLine ?? 1,
1147
- startColumn: token.startColumn ?? 1,
1148
- code: "parse-error"
1149
- };
542
+ return diagnostics;
1150
543
  }
1151
- function findTokenBefore(tokens, offset) {
1152
- let best = null;
1153
- for (const t of tokens) {
1154
- if (t.startOffset <= offset) {
1155
- best = t;
544
+
545
+ // src/symbol-table.ts
546
+ function buildSymbolTable(regions, getKeywordVariants2) {
547
+ const symbols = /* @__PURE__ */ new Map();
548
+ const locationIndex = [];
549
+ const variants = (eng) => getKeywordVariants2 ? getKeywordVariants2(eng) : [eng];
550
+ const behaviorKws = variants("behavior");
551
+ const defKws = variants("def");
552
+ const setKws = variants("set");
553
+ const defaultKws = variants("default");
554
+ const installKws = variants("install");
555
+ const callKws = variants("call");
556
+ const behaviorDefPat = new RegExp(`\\b(${behaviorKws.join("|")})\\s+(\\w+)`, "gi");
557
+ const funcDefPat = new RegExp(`\\b(${defKws.join("|")})\\s+(\\w+)`, "gi");
558
+ const varDefPat = new RegExp(`\\b(${[...setKws, ...defaultKws].join("|")})\\s+([:$]\\w+)`, "gi");
559
+ const installPat = new RegExp(`\\b(${installKws.join("|")})\\s+(\\w+)`, "gi");
560
+ const callPat = new RegExp(`\\b(${callKws.join("|")})\\s+(\\w+)`, "gi");
561
+ const varRefPat = /[:$]\w+/g;
562
+ const funcCallPat = /\b(\w+)\s*\(/g;
563
+ const caretWritePat = new RegExp(`\\b(${setKws.join("|")})\\s+\\^([A-Za-z_]\\w*)`, "gi");
564
+ const caretRefPat = /\^([A-Za-z_]\w*)/g;
565
+ const attrsRefPat = /\battrs\.([A-Za-z_]\w*)/g;
566
+ function getOrCreate(name, kind, scope) {
567
+ let entry = symbols.get(name);
568
+ if (!entry) {
569
+ entry = { name, kind, scope, definitions: [], usages: [] };
570
+ symbols.set(name, entry);
571
+ }
572
+ return entry;
573
+ }
574
+ function toDocLine(region, localLine) {
575
+ return region.startLine + localLine;
576
+ }
577
+ function toDocChar(region, localLine, localChar) {
578
+ return localLine === 0 ? region.startChar + localChar : localChar;
579
+ }
580
+ function addLocation(entry, kind, region, localLine, localChar, length) {
581
+ const loc = {
582
+ line: toDocLine(region, localLine),
583
+ character: toDocChar(region, localLine, localChar),
584
+ length
585
+ };
586
+ if (kind === "def") {
587
+ entry.definitions.push(loc);
1156
588
  } else {
1157
- break;
589
+ entry.usages.push(loc);
1158
590
  }
591
+ locationIndex.push({
592
+ line: loc.line,
593
+ start: loc.character,
594
+ end: loc.character + length,
595
+ name: entry.name
596
+ });
1159
597
  }
1160
- return best;
1161
- }
1162
- function inferContextFromTokens(tokens, tokenBefore) {
1163
- if (!tokenBefore || tokens.length === 0) return "start";
1164
- const type = tokenBefore.tokenType;
1165
- if (type === Kw_on) return "after-on";
1166
- if (type === Kw_then) return "after-then";
1167
- if (type.name.startsWith("Cmd_")) return "after-command";
1168
- const markers = [Kw_to, Kw_from, Kw_into, Kw_in, Kw_of, Kw_on, Kw_as, Kw_with];
1169
- if (markers.includes(type)) return "after-marker";
1170
- return "general";
1171
- }
1172
- function getCommandSuggestions() {
1173
- return COMMAND_SCHEMAS.map((schema) => ({
1174
- label: schema.action,
1175
- kind: "command",
1176
- detail: getCategoryLabel(schema.category),
1177
- insertText: schema.action + " ",
1178
- category: schema.category
1179
- }));
1180
- }
1181
- function getEventSuggestions() {
1182
- const events = [
1183
- "click",
1184
- "dblclick",
1185
- "mousedown",
1186
- "mouseup",
1187
- "mousemove",
1188
- "mouseenter",
1189
- "mouseleave",
1190
- "keydown",
1191
- "keyup",
1192
- "keypress",
1193
- "input",
1194
- "change",
1195
- "submit",
1196
- "focus",
1197
- "blur",
1198
- "load",
1199
- "scroll",
1200
- "resize",
1201
- "mutation",
1202
- "intersection",
1203
- "every"
1204
- ];
1205
- return events.map((e) => ({
1206
- label: e,
1207
- kind: "event",
1208
- detail: `${e} event`,
1209
- insertText: e + " "
1210
- }));
1211
- }
1212
- function getRoleMarkerSuggestions(commandToken) {
1213
- const cmdName = commandToken.tokenType.name.replace("Cmd_", "");
1214
- const schema = COMMAND_SCHEMAS.find((s) => s.action === cmdName);
1215
- if (!schema) return [];
1216
- const suggestions = [];
1217
- for (const role of schema.roles) {
1218
- if (role.role === schema.primaryRole) continue;
1219
- const markers = getRoleMarkers(role.role);
1220
- for (const marker of markers) {
1221
- suggestions.push({
1222
- label: marker,
1223
- kind: "keyword",
1224
- detail: `${role.role} marker`,
1225
- insertText: marker + " "
1226
- });
598
+ function findLocalLine(code, offset) {
599
+ let line = 0;
600
+ for (let i = 0; i < offset && i < code.length; i++) {
601
+ if (code[i] === "\n") line++;
1227
602
  }
603
+ return line;
1228
604
  }
1229
- return suggestions;
1230
- }
1231
- function getSelectorSuggestions() {
1232
- return [
1233
- { label: "me", kind: "keyword", detail: "Current element" },
1234
- { label: "it", kind: "keyword", detail: "Last result" },
1235
- { label: ".", kind: "selector", detail: "CSS class selector", insertText: "." },
1236
- { label: "#", kind: "selector", detail: "CSS ID selector", insertText: "#" },
1237
- { label: "@", kind: "selector", detail: "Attribute selector", insertText: "@" },
1238
- { label: ":", kind: "variable", detail: "Local variable", insertText: ":" },
1239
- { label: "$", kind: "variable", detail: "Global variable", insertText: "$" }
1240
- ];
1241
- }
1242
- function getRoleMarkers(role) {
1243
- switch (role) {
1244
- case "destination":
1245
- return ["to", "into"];
1246
- case "source":
1247
- return ["from"];
1248
- case "target":
1249
- return ["on"];
1250
- case "container":
1251
- return ["in"];
1252
- case "instrument":
1253
- return ["with"];
1254
- case "beneficiary":
1255
- return ["for"];
1256
- default:
1257
- return [];
605
+ function findLocalChar(code, offset) {
606
+ let lastNewline = -1;
607
+ for (let i = 0; i < offset && i < code.length; i++) {
608
+ if (code[i] === "\n") lastNewline = i;
609
+ }
610
+ return offset - lastNewline - 1;
1258
611
  }
1259
- }
1260
- function getCategoryLabel(category) {
1261
- const labels = {
1262
- "dom-class": "DOM Class/Attribute",
1263
- "dom-content": "DOM Content",
1264
- "dom-visibility": "DOM Visibility",
1265
- variable: "Variable",
1266
- event: "Event",
1267
- async: "Async",
1268
- navigation: "Navigation",
1269
- "control-flow": "Control Flow"
612
+ const defVarOffsets = /* @__PURE__ */ new Map();
613
+ const caretDefOffsets = /* @__PURE__ */ new Map();
614
+ for (const region of regions) {
615
+ const code = region.code;
616
+ const regionDefOffsets = /* @__PURE__ */ new Set();
617
+ defVarOffsets.set(region, regionDefOffsets);
618
+ const regionCaretDefOffsets = /* @__PURE__ */ new Set();
619
+ caretDefOffsets.set(region, regionCaretDefOffsets);
620
+ for (const m of code.matchAll(behaviorDefPat)) {
621
+ const name = m[2];
622
+ const nameOffset = (m.index ?? 0) + m[0].indexOf(name);
623
+ const localLine = findLocalLine(code, nameOffset);
624
+ const localChar = findLocalChar(code, nameOffset);
625
+ addLocation(
626
+ getOrCreate(name, "behavior", "global"),
627
+ "def",
628
+ region,
629
+ localLine,
630
+ localChar,
631
+ name.length
632
+ );
633
+ }
634
+ for (const m of code.matchAll(funcDefPat)) {
635
+ const name = m[2];
636
+ const nameOffset = (m.index ?? 0) + m[0].indexOf(name);
637
+ const localLine = findLocalLine(code, nameOffset);
638
+ const localChar = findLocalChar(code, nameOffset);
639
+ addLocation(
640
+ getOrCreate(name, "function", "global"),
641
+ "def",
642
+ region,
643
+ localLine,
644
+ localChar,
645
+ name.length
646
+ );
647
+ }
648
+ for (const m of code.matchAll(varDefPat)) {
649
+ const varName = m[2];
650
+ const varOffset = (m.index ?? 0) + m[0].indexOf(varName);
651
+ const localLine = findLocalLine(code, varOffset);
652
+ const localChar = findLocalChar(code, varOffset);
653
+ const scope = varName.startsWith("$") ? "global" : "local";
654
+ addLocation(
655
+ getOrCreate(varName, "variable", scope),
656
+ "def",
657
+ region,
658
+ localLine,
659
+ localChar,
660
+ varName.length
661
+ );
662
+ regionDefOffsets.add(varOffset);
663
+ }
664
+ for (const m of code.matchAll(installPat)) {
665
+ const name = m[2];
666
+ const nameOffset = (m.index ?? 0) + m[0].indexOf(name);
667
+ const localLine = findLocalLine(code, nameOffset);
668
+ const localChar = findLocalChar(code, nameOffset);
669
+ const entry = getOrCreate(name, "behavior", "global");
670
+ addLocation(entry, "use", region, localLine, localChar, name.length);
671
+ }
672
+ for (const m of code.matchAll(callPat)) {
673
+ const name = m[2];
674
+ const nameOffset = (m.index ?? 0) + m[0].indexOf(name);
675
+ const localLine = findLocalLine(code, nameOffset);
676
+ const localChar = findLocalChar(code, nameOffset);
677
+ const entry = getOrCreate(name, "function", "global");
678
+ addLocation(entry, "use", region, localLine, localChar, name.length);
679
+ }
680
+ for (const m of code.matchAll(varRefPat)) {
681
+ const varName = m[0];
682
+ const varOffset = m.index ?? 0;
683
+ if (regionDefOffsets.has(varOffset)) continue;
684
+ const localLine = findLocalLine(code, varOffset);
685
+ const localChar = findLocalChar(code, varOffset);
686
+ const scope = varName.startsWith("$") ? "global" : "local";
687
+ const entry = getOrCreate(varName, "variable", scope);
688
+ addLocation(entry, "use", region, localLine, localChar, varName.length);
689
+ }
690
+ for (const m of code.matchAll(caretWritePat)) {
691
+ const name = m[2];
692
+ const caretOffset = (m.index ?? 0) + m[0].lastIndexOf("^");
693
+ const localLine = findLocalLine(code, caretOffset);
694
+ const localChar = findLocalChar(code, caretOffset);
695
+ const symbolName = `^${name}`;
696
+ addLocation(
697
+ getOrCreate(symbolName, "caretVar", "caret"),
698
+ "def",
699
+ region,
700
+ localLine,
701
+ localChar,
702
+ symbolName.length
703
+ );
704
+ regionCaretDefOffsets.add(caretOffset);
705
+ }
706
+ for (const m of code.matchAll(caretRefPat)) {
707
+ const caretOffset = m.index ?? 0;
708
+ if (regionCaretDefOffsets.has(caretOffset)) continue;
709
+ const name = m[1];
710
+ const localLine = findLocalLine(code, caretOffset);
711
+ const localChar = findLocalChar(code, caretOffset);
712
+ const symbolName = `^${name}`;
713
+ addLocation(
714
+ getOrCreate(symbolName, "caretVar", "caret"),
715
+ "use",
716
+ region,
717
+ localLine,
718
+ localChar,
719
+ symbolName.length
720
+ );
721
+ }
722
+ for (const m of code.matchAll(attrsRefPat)) {
723
+ const name = m[1];
724
+ const attrsStart = m.index ?? 0;
725
+ const localLine = findLocalLine(code, attrsStart);
726
+ const localChar = findLocalChar(code, attrsStart);
727
+ const fullLength = m[0].length;
728
+ addLocation(
729
+ getOrCreate(`attrs.${name}`, "componentAttr", "attr"),
730
+ "use",
731
+ region,
732
+ localLine,
733
+ localChar,
734
+ fullLength
735
+ );
736
+ }
737
+ const keywords = /* @__PURE__ */ new Set([
738
+ "if",
739
+ "else",
740
+ "end",
741
+ "on",
742
+ "then",
743
+ "repeat",
744
+ "for",
745
+ "while",
746
+ "return",
747
+ "set",
748
+ "get",
749
+ "put",
750
+ "add",
751
+ "remove",
752
+ "toggle",
753
+ "show",
754
+ "hide",
755
+ "fetch",
756
+ "send",
757
+ "trigger",
758
+ "wait",
759
+ "log",
760
+ "call",
761
+ "default",
762
+ "behavior",
763
+ "def",
764
+ "install",
765
+ "init",
766
+ "async",
767
+ ...behaviorKws,
768
+ ...defKws,
769
+ ...setKws,
770
+ ...installKws,
771
+ ...callKws
772
+ ]);
773
+ for (const m of code.matchAll(funcCallPat)) {
774
+ const name = m[1];
775
+ if (keywords.has(name.toLowerCase())) continue;
776
+ const nameOffset = m.index ?? 0;
777
+ const localLine = findLocalLine(code, nameOffset);
778
+ const localChar = findLocalChar(code, nameOffset);
779
+ const entry = getOrCreate(name, "function", "global");
780
+ addLocation(entry, "use", region, localLine, localChar, name.length);
781
+ }
782
+ }
783
+ return {
784
+ symbols,
785
+ symbolAt(line, character) {
786
+ for (const loc of locationIndex) {
787
+ if (loc.line === line && character >= loc.start && character < loc.end) {
788
+ return symbols.get(loc.name);
789
+ }
790
+ }
791
+ return void 0;
792
+ }
1270
793
  };
1271
- return labels[category] || category;
794
+ }
795
+ var cache = /* @__PURE__ */ new Map();
796
+ function getSymbolTable(uri, version, regions, getKeywordVariants2) {
797
+ const cached = cache.get(uri);
798
+ if (cached && cached.version === version) {
799
+ return cached.table;
800
+ }
801
+ const table = buildSymbolTable(regions, getKeywordVariants2);
802
+ cache.set(uri, { version, table });
803
+ return table;
1272
804
  }
1273
805
 
1274
806
  // src/localized-descriptions.ts
@@ -1326,7 +858,41 @@ var DESCRIPTIONS = {
1326
858
  documentation: "Waits for CSS transitions/animations to complete."
1327
859
  },
1328
860
  go: { detail: "Navigate", documentation: "Navigates to a URL or page location." },
1329
- throw: { detail: "Throw error", documentation: "Throws an error with a specified message." }
861
+ throw: { detail: "Throw error", documentation: "Throws an error with a specified message." },
862
+ // v2 / v2.1 component + reactive (English only — localized translations
863
+ // can be added as language coverage rolls out).
864
+ "^": {
865
+ detail: "Reactive variable (^var)",
866
+ documentation: 'Reads or writes a DOM-scoped reactive variable. `^name` walks ancestors until it finds an owner, or stops at the nearest `dom-scope="isolated"` boundary.'
867
+ },
868
+ attrs: {
869
+ detail: "Component attributes",
870
+ documentation: 'Proxy over the component element\'s HTML attributes. `attrs.fooBar` reads `foo-bar="..."`, with number/boolean coercion. Available inside `<template _="...">` init scripts and `${...}` interpolation.'
871
+ },
872
+ "dom-scope": {
873
+ detail: "Reactive scope boundary",
874
+ documentation: '`dom-scope="isolated"` on a host element bounds `^var` lookup. Set automatically on `<template component>` instances; set manually for ad-hoc isolation.'
875
+ },
876
+ "#if": {
877
+ detail: "Conditional template block",
878
+ documentation: "Line-oriented template directive. Renders the contained block when the condition is truthy. Optional `#else` branch; closes with `#end`."
879
+ },
880
+ "#else": {
881
+ detail: "Alternative branch",
882
+ documentation: "Else branch of a `#if`, or empty-collection fallback for a `#for`."
883
+ },
884
+ "#end": {
885
+ detail: "Close directive block",
886
+ documentation: "Closes the most recent `#if` or `#for`. Must occupy its own line."
887
+ },
888
+ "#for": {
889
+ detail: "Iteration template block",
890
+ documentation: "`#for <name> in <expr>` iterates over `<expr>` with `<name>` bound to each element. Optional `#else` branch fires when the collection is empty."
891
+ },
892
+ "#continue": {
893
+ detail: "Skip iteration",
894
+ documentation: "Skip to the next iteration of the enclosing `#for`. Available v2.1+."
895
+ }
1330
896
  },
1331
897
  ja: {
1332
898
  toggle: {
@@ -1571,7 +1137,7 @@ try {
1571
1137
  }
1572
1138
  var frameworkIR = null;
1573
1139
  try {
1574
- const fw = await import("./dist-V7SKUZF5.js");
1140
+ const fw = await import("./dist-NRPF26M4.js");
1575
1141
  frameworkIR = { fromInterchangeNode: fw.fromInterchangeNode, renderExplicit: fw.renderExplicit };
1576
1142
  console.error(
1577
1143
  "[lokascript-ls] @lokascript/framework loaded \u2014 LSE bracket notation in hover enabled"
@@ -1724,6 +1290,91 @@ var FALLBACK_HOVER_DOCS = {
1724
1290
  title: "result",
1725
1291
  description: "Alias for it - the result of the last expression.",
1726
1292
  example: "call myFunction() then log result"
1293
+ },
1294
+ increment: {
1295
+ title: "increment",
1296
+ description: "Increases a numeric value by 1 (or a specified amount).",
1297
+ example: "increment #count"
1298
+ },
1299
+ decrement: {
1300
+ title: "decrement",
1301
+ description: "Decreases a numeric value by 1 (or a specified amount).",
1302
+ example: "decrement #count"
1303
+ },
1304
+ append: {
1305
+ title: "append",
1306
+ description: "Appends content to an element.",
1307
+ example: 'append "<li>item</li>" to #list'
1308
+ },
1309
+ prepend: {
1310
+ title: "prepend",
1311
+ description: "Prepends content to an element.",
1312
+ example: 'prepend "<li>first</li>" to #list'
1313
+ },
1314
+ take: {
1315
+ title: "take",
1316
+ description: "Takes a class from sibling elements (exclusive toggle).",
1317
+ example: "take .active from .tabs"
1318
+ },
1319
+ transition: {
1320
+ title: "transition",
1321
+ description: "Applies a CSS transition to an element.",
1322
+ example: "transition opacity to 0 over 500ms"
1323
+ },
1324
+ settle: {
1325
+ title: "settle",
1326
+ description: "Waits for CSS transitions to complete.",
1327
+ example: "add .fade then settle"
1328
+ },
1329
+ halt: {
1330
+ title: "halt",
1331
+ description: "Stops the current event from propagating.",
1332
+ example: "halt the event"
1333
+ },
1334
+ throw: {
1335
+ title: "throw",
1336
+ description: "Throws an error.",
1337
+ example: 'throw "Something went wrong"'
1338
+ },
1339
+ return: {
1340
+ title: "return",
1341
+ description: "Returns a value from a function or behavior.",
1342
+ example: "return 42"
1343
+ },
1344
+ install: {
1345
+ title: "install",
1346
+ description: "Installs a behavior on an element.",
1347
+ example: "install Draggable(snap: true)"
1348
+ },
1349
+ default: {
1350
+ title: "default",
1351
+ description: "Sets a variable to a value only if it is not already set.",
1352
+ example: "default :count to 0"
1353
+ },
1354
+ then: {
1355
+ title: "then",
1356
+ description: "Chains commands together sequentially.",
1357
+ example: "add .active then wait 1s then remove .active"
1358
+ },
1359
+ end: {
1360
+ title: "end",
1361
+ description: "Closes a block (event handler, behavior, if, repeat).",
1362
+ example: "on click\\n toggle .active\\nend"
1363
+ },
1364
+ async: {
1365
+ title: "async",
1366
+ description: "Runs a command asynchronously without blocking.",
1367
+ example: "async fetch /api/data"
1368
+ },
1369
+ tell: {
1370
+ title: "tell",
1371
+ description: "Sets the default target for subsequent commands.",
1372
+ example: "tell #modal then add .visible"
1373
+ },
1374
+ copy: {
1375
+ title: "copy",
1376
+ description: "Copies text to the clipboard.",
1377
+ example: 'copy "Hello" to the clipboard'
1727
1378
  }
1728
1379
  };
1729
1380
  var FALLBACK_EVENT_NAMES = [
@@ -1744,7 +1395,7 @@ function resolveMode(settings) {
1744
1395
  if (settings.mode === "hyperscript") return "hyperscript";
1745
1396
  if (settings.mode === "hyperscript-i18n") return "hyperscript-i18n";
1746
1397
  if (settings.mode === "lokascript") return "lokascript";
1747
- return semanticPackage ? "lokascript" : "hyperscript";
1398
+ return semanticPackage?.getRegisteredLanguages ? "lokascript" : "hyperscript";
1748
1399
  }
1749
1400
  function getBranding(mode) {
1750
1401
  return mode === "lokascript" ? "lokascript" : "hyperscript";
@@ -1763,7 +1414,29 @@ var cachedAnalyzer = null;
1763
1414
  function getSemanticAnalyzer() {
1764
1415
  if (!semanticPackage) return null;
1765
1416
  if (!cachedAnalyzer) {
1766
- cachedAnalyzer = semanticPackage.createSemanticAnalyzer();
1417
+ cachedAnalyzer = {
1418
+ analyze(code, language) {
1419
+ if (!semanticPackage.isLanguageRegistered(language)) {
1420
+ return {
1421
+ confidence: 0,
1422
+ errors: [`Language '${language}' is not supported for semantic parsing`]
1423
+ };
1424
+ }
1425
+ const result = semanticPackage.parseSemantic(code, language);
1426
+ const out = { confidence: result.confidence };
1427
+ if (result.node) {
1428
+ out.command = { name: result.node.action, roles: result.node.roles };
1429
+ }
1430
+ if (result.error) out.errors = [result.error];
1431
+ return out;
1432
+ },
1433
+ supportsLanguage(language) {
1434
+ return semanticPackage.isLanguageRegistered(language);
1435
+ },
1436
+ supportedLanguages() {
1437
+ return semanticPackage.getRegisteredLanguages();
1438
+ }
1439
+ };
1767
1440
  }
1768
1441
  return cachedAnalyzer;
1769
1442
  }
@@ -1786,6 +1459,7 @@ connection.onInitialize((params) => {
1786
1459
  },
1787
1460
  definitionProvider: true,
1788
1461
  referencesProvider: true,
1462
+ renameProvider: { prepareProvider: true },
1789
1463
  documentFormattingProvider: true
1790
1464
  }
1791
1465
  };
@@ -1818,7 +1492,10 @@ async function validateDocument(document) {
1818
1492
  if (isHtmlDocument(uri, text)) {
1819
1493
  const regions = extractHyperscriptRegions(text);
1820
1494
  for (const region of regions) {
1821
- const diagnostics = await getDiagnostics(region.code, globalSettings.language);
1495
+ const diagnostics = region.type === "template" ? runDirectiveDiagnostics(region.code).map((d) => ({
1496
+ ...d,
1497
+ source: getBranding(resolvedMode)
1498
+ })) : await getDiagnostics(region.code, globalSettings.language);
1822
1499
  for (const diag of diagnostics) {
1823
1500
  allDiagnostics.push({
1824
1501
  ...diag,
@@ -1851,7 +1528,7 @@ async function getDiagnostics(code, language) {
1851
1528
  start: { line: 0, character: 0 },
1852
1529
  end: { line: 0, character: code.split("\n")[0]?.length || code.length }
1853
1530
  },
1854
- severity: DiagnosticSeverity.Error,
1531
+ severity: DiagnosticSeverity2.Error,
1855
1532
  code: "lokascript-only",
1856
1533
  source: brand,
1857
1534
  message: `${feature.description} (not compatible with _hyperscript)`
@@ -1886,7 +1563,7 @@ async function getDiagnostics(code, language) {
1886
1563
  start: { line: 0, character: 0 },
1887
1564
  end: { line: 0, character: code.split("\n")[0]?.length || code.length }
1888
1565
  },
1889
- severity: DiagnosticSeverity.Error,
1566
+ severity: DiagnosticSeverity2.Error,
1890
1567
  code: "semantic-error",
1891
1568
  source: brand,
1892
1569
  message: err
@@ -1899,7 +1576,7 @@ async function getDiagnostics(code, language) {
1899
1576
  start: { line: 0, character: 0 },
1900
1577
  end: { line: 0, character: code.length }
1901
1578
  },
1902
- severity: DiagnosticSeverity.Warning,
1579
+ severity: DiagnosticSeverity2.Warning,
1903
1580
  code: "low-confidence",
1904
1581
  source: brand,
1905
1582
  message: `Code could not be fully parsed (${(result.confidence * 100).toFixed(0)}% confidence)`
@@ -1911,7 +1588,7 @@ async function getDiagnostics(code, language) {
1911
1588
  if (cmd === "toggle" && !roles.has("patient")) {
1912
1589
  diagnostics.push({
1913
1590
  range: { start: { line: 0, character: 0 }, end: { line: 0, character: code.length } },
1914
- severity: DiagnosticSeverity.Warning,
1591
+ severity: DiagnosticSeverity2.Warning,
1915
1592
  code: "missing-role",
1916
1593
  source: brand,
1917
1594
  message: "toggle command missing target (add .class, @attr, or selector)"
@@ -1920,7 +1597,7 @@ async function getDiagnostics(code, language) {
1920
1597
  if (cmd === "put" && !roles.has("destination")) {
1921
1598
  diagnostics.push({
1922
1599
  range: { start: { line: 0, character: 0 }, end: { line: 0, character: code.length } },
1923
- severity: DiagnosticSeverity.Warning,
1600
+ severity: DiagnosticSeverity2.Warning,
1924
1601
  code: "missing-role",
1925
1602
  source: brand,
1926
1603
  message: 'put command missing destination (add "into #element")'
@@ -1931,108 +1608,58 @@ async function getDiagnostics(code, language) {
1931
1608
  connection.console.log(`[${brand}-ls] Semantic parsing failed, trying fallback: ${e}`);
1932
1609
  }
1933
1610
  }
1934
- if (diagnostics.length === 0 && interchangeLSP && parseFunction && fromCoreASTFn) {
1611
+ if (parseFunction && fromCoreASTFn) {
1935
1612
  try {
1936
- const ast = parseFunction(code);
1937
- if (ast) {
1938
- const interchange = fromCoreASTFn(ast);
1939
- const nodes = interchange.type === "event" ? [interchange] : [interchange];
1940
- const astDiagnostics = interchangeLSP.interchangeToLSPDiagnostics(nodes, { source: brand });
1941
- diagnostics.push(...astDiagnostics);
1613
+ const parseResult = parseFunction(code);
1614
+ if (parseResult.errors?.length) {
1615
+ for (const err of parseResult.errors) {
1616
+ diagnostics.push({
1617
+ range: {
1618
+ start: {
1619
+ line: Math.max(0, (err.line ?? 1) - 1),
1620
+ character: Math.max(0, err.column ?? 0)
1621
+ },
1622
+ end: {
1623
+ line: Math.max(0, (err.line ?? 1) - 1),
1624
+ character: Math.max(0, err.column ?? 0) + 10
1625
+ }
1626
+ },
1627
+ severity: DiagnosticSeverity2.Error,
1628
+ code: "parse-error",
1629
+ source: brand,
1630
+ message: err.message
1631
+ });
1632
+ }
1633
+ }
1634
+ if (parseResult.node && interchangeLSP) {
1635
+ const interchange = fromCoreASTFn(parseResult.node);
1636
+ if (interchange && interchange.type !== "literal") {
1637
+ const nodes = Array.isArray(interchange) ? interchange : [interchange];
1638
+ const astDiagnostics = interchangeLSP.interchangeToLSPDiagnostics(nodes, {
1639
+ source: brand
1640
+ });
1641
+ diagnostics.push(...astDiagnostics);
1642
+ }
1942
1643
  }
1943
1644
  } catch (parseError) {
1944
1645
  diagnostics.push({
1945
1646
  range: {
1946
1647
  start: { line: 0, character: 0 },
1947
- end: { line: 0, character: code.length }
1648
+ end: { line: 0, character: Math.min(code.length, 80) }
1948
1649
  },
1949
- severity: DiagnosticSeverity.Error,
1650
+ severity: DiagnosticSeverity2.Error,
1950
1651
  code: "parse-error",
1951
1652
  source: brand,
1952
1653
  message: parseError.message || "Failed to parse"
1953
1654
  });
1954
1655
  }
1955
1656
  }
1956
- if (diagnostics.length === 0) {
1957
- try {
1958
- const chevrotainResult = parseWithRecovery(code);
1959
- for (const diag of chevrotainResult.diagnostics) {
1960
- const lines = code.split("\n");
1961
- const { line: startLine, character: startChar } = offsetToLineChar(code, diag.startOffset);
1962
- const { line: endLine, character: endChar } = offsetToLineChar(code, diag.endOffset);
1963
- diagnostics.push({
1964
- range: {
1965
- start: { line: startLine, character: startChar },
1966
- end: { line: endLine, character: endChar }
1967
- },
1968
- severity: diag.severity === "error" ? DiagnosticSeverity.Error : diag.severity === "warning" ? DiagnosticSeverity.Warning : DiagnosticSeverity.Information,
1969
- code: diag.code,
1970
- source: brand,
1971
- message: diag.message
1972
- });
1973
- }
1974
- } catch (e) {
1975
- connection.console.log(`[${brand}-ls] Chevrotain parsing failed: ${e}`);
1976
- }
1977
- }
1978
1657
  if (diagnostics.length === 0) {
1979
1658
  const simpleDiagnostics = runSimpleDiagnostics(code, language);
1980
1659
  diagnostics.push(...simpleDiagnostics.map((d) => ({ ...d, source: brand })));
1981
1660
  }
1982
1661
  return diagnostics.slice(0, globalSettings.maxDiagnostics);
1983
1662
  }
1984
- function offsetToLineChar(text, offset) {
1985
- let line = 0;
1986
- let col = 0;
1987
- for (let i = 0; i < offset && i < text.length; i++) {
1988
- if (text[i] === "\n") {
1989
- line++;
1990
- col = 0;
1991
- } else {
1992
- col++;
1993
- }
1994
- }
1995
- return { line, character: col };
1996
- }
1997
- function runSimpleDiagnostics(code, _language) {
1998
- const diagnostics = [];
1999
- const lines = code.split("\n");
2000
- for (let i = 0; i < lines.length; i++) {
2001
- const line = lines[i];
2002
- const singleQuotes = (line.match(/'/g) || []).length;
2003
- const doubleQuotes = (line.match(/"/g) || []).length;
2004
- if (singleQuotes % 2 !== 0) {
2005
- diagnostics.push({
2006
- range: { start: { line: i, character: 0 }, end: { line: i, character: line.length } },
2007
- severity: DiagnosticSeverity.Error,
2008
- code: "unmatched-quote",
2009
- source: "lokascript",
2010
- message: "Unmatched single quote"
2011
- });
2012
- }
2013
- if (doubleQuotes % 2 !== 0) {
2014
- diagnostics.push({
2015
- range: { start: { line: i, character: 0 }, end: { line: i, character: line.length } },
2016
- severity: DiagnosticSeverity.Error,
2017
- code: "unmatched-quote",
2018
- source: "lokascript",
2019
- message: "Unmatched double quote"
2020
- });
2021
- }
2022
- }
2023
- const openParens = (code.match(/\(/g) || []).length;
2024
- const closeParens = (code.match(/\)/g) || []).length;
2025
- if (openParens !== closeParens) {
2026
- diagnostics.push({
2027
- range: { start: { line: 0, character: 0 }, end: { line: 0, character: 1 } },
2028
- severity: DiagnosticSeverity.Error,
2029
- code: "unbalanced-parens",
2030
- source: "lokascript",
2031
- message: `Unbalanced parentheses: ${openParens} open, ${closeParens} close`
2032
- });
2033
- }
2034
- return diagnostics;
2035
- }
2036
1663
  connection.onCompletion((params) => {
2037
1664
  const document = documents.get(params.textDocument.uri);
2038
1665
  if (!document) return [];
@@ -2055,31 +1682,110 @@ connection.onCompletion((params) => {
2055
1682
  beforeCursor += regionLines[i] + "\n";
2056
1683
  }
2057
1684
  }
1685
+ if (found.region.type === "template") {
1686
+ const currentLine = regionLines[found.localLine] ?? "";
1687
+ const linePrefix = currentLine.slice(0, found.localChar);
1688
+ if (/^\s*#\w*$/.test(linePrefix)) {
1689
+ return getDirectiveCompletions();
1690
+ }
1691
+ const sigilContext = inferContext(beforeCursor);
1692
+ if (sigilContext === "caret") {
1693
+ return getCaretVarCompletions(document);
1694
+ }
1695
+ if (sigilContext === "attrs-property") {
1696
+ return getAttrsCompletions();
1697
+ }
1698
+ return [];
1699
+ }
2058
1700
  const context = inferContext(beforeCursor);
1701
+ if (context === "caret") return getCaretVarCompletions(document);
1702
+ if (context === "attrs-property") return getAttrsCompletions();
2059
1703
  return getContextualCompletions(context, language);
2060
1704
  } else {
2061
1705
  const offset = document.offsetAt(position);
2062
1706
  const beforeCursor = text.slice(0, offset);
2063
1707
  const context = inferContext(beforeCursor);
2064
- const completions = getContextualCompletions(context, language);
2065
- try {
2066
- const chevrotainSuggestions = getContentAssist(beforeCursor, offset);
2067
- const existingLabels = new Set(completions.map((c) => c.label));
2068
- for (const s of chevrotainSuggestions) {
2069
- if (existingLabels.has(s.label)) continue;
2070
- completions.push({
2071
- label: s.label,
2072
- kind: s.kind === "command" ? CompletionItemKind.Method : s.kind === "event" ? CompletionItemKind.Event : s.kind === "selector" ? CompletionItemKind.Field : s.kind === "variable" ? CompletionItemKind.Variable : CompletionItemKind.Keyword,
2073
- detail: s.detail,
2074
- insertText: s.insertText
2075
- });
2076
- }
2077
- } catch {
2078
- }
2079
- return completions;
1708
+ if (context === "caret") return getCaretVarCompletions(document);
1709
+ if (context === "attrs-property") return getAttrsCompletions();
1710
+ return getContextualCompletions(context, language);
2080
1711
  }
2081
1712
  });
1713
+ function getDirectiveCompletions() {
1714
+ return [
1715
+ {
1716
+ label: "#if",
1717
+ kind: CompletionItemKind.Snippet,
1718
+ detail: "Conditional template block",
1719
+ insertText: "#if ${1:condition}\n ${2}\n#end"
1720
+ },
1721
+ {
1722
+ label: "#if/#else",
1723
+ kind: CompletionItemKind.Snippet,
1724
+ detail: "Conditional with else branch",
1725
+ insertText: "#if ${1:condition}\n ${2}\n#else\n ${3}\n#end"
1726
+ },
1727
+ {
1728
+ label: "#for",
1729
+ kind: CompletionItemKind.Snippet,
1730
+ detail: "Iteration template block",
1731
+ insertText: "#for ${1:item} in ${2:collection}\n ${3}\n#end"
1732
+ },
1733
+ {
1734
+ label: "#else",
1735
+ kind: CompletionItemKind.Keyword,
1736
+ detail: "Else branch"
1737
+ },
1738
+ {
1739
+ label: "#end",
1740
+ kind: CompletionItemKind.Keyword,
1741
+ detail: "Close #if or #for block"
1742
+ },
1743
+ {
1744
+ label: "#continue",
1745
+ kind: CompletionItemKind.Keyword,
1746
+ detail: "Skip to next #for iteration (v2.1+)"
1747
+ }
1748
+ ];
1749
+ }
1750
+ function getCaretVarCompletions(document) {
1751
+ const items = [];
1752
+ try {
1753
+ const { table } = getDocSymbolTable(document);
1754
+ for (const [name, entry] of table.symbols) {
1755
+ if (entry.kind !== "caretVar") continue;
1756
+ const bare = name.startsWith("^") ? name.slice(1) : name;
1757
+ items.push({
1758
+ label: bare,
1759
+ kind: CompletionItemKind.Variable,
1760
+ detail: "Reactive variable (^var)",
1761
+ insertText: bare
1762
+ });
1763
+ }
1764
+ } catch {
1765
+ }
1766
+ if (items.length === 0) {
1767
+ items.push({
1768
+ label: "name",
1769
+ kind: CompletionItemKind.Snippet,
1770
+ detail: "New reactive variable",
1771
+ insertText: "${1:name}"
1772
+ });
1773
+ }
1774
+ return items;
1775
+ }
1776
+ function getAttrsCompletions() {
1777
+ return [
1778
+ {
1779
+ label: "attribute",
1780
+ kind: CompletionItemKind.Snippet,
1781
+ detail: "Component attribute (attrs.X)",
1782
+ insertText: "${1:propertyName}"
1783
+ }
1784
+ ];
1785
+ }
2082
1786
  function inferContext(beforeCursor) {
1787
+ if (/\^\w*$/.test(beforeCursor)) return "caret";
1788
+ if (/\battrs\.\w*$/.test(beforeCursor)) return "attrs-property";
2083
1789
  const trimmed = beforeCursor.trim();
2084
1790
  if (/\bon\s*$/.test(trimmed)) return "event";
2085
1791
  if (/\bthen\s*$/.test(trimmed)) return "command";
@@ -2097,7 +1803,7 @@ function getContextualCompletions(context, language) {
2097
1803
  return eng;
2098
1804
  }
2099
1805
  try {
2100
- const profile = semanticPackage.getProfile(effectiveLanguage);
1806
+ const profile = semanticPackage.tryGetProfile(effectiveLanguage);
2101
1807
  if (profile?.keywords?.[eng]?.primary) {
2102
1808
  return profile.keywords[eng].primary;
2103
1809
  }
@@ -2236,6 +1942,24 @@ function getContextualCompletions(context, language) {
2236
1942
  }
2237
1943
  );
2238
1944
  }
1945
+ completions.push(
1946
+ { label: getKeyword("to"), kind: CompletionItemKind.Keyword, detail: "Target destination" },
1947
+ { label: getKeyword("from"), kind: CompletionItemKind.Keyword, detail: "Source element" },
1948
+ {
1949
+ label: getKeyword("into"),
1950
+ kind: CompletionItemKind.Keyword,
1951
+ detail: "Insert destination"
1952
+ },
1953
+ { label: getKeyword("on"), kind: CompletionItemKind.Keyword, detail: "Target element" },
1954
+ { label: getKeyword("with"), kind: CompletionItemKind.Keyword, detail: "Options/data" },
1955
+ { label: getKeyword("by"), kind: CompletionItemKind.Keyword, detail: "Amount/method" },
1956
+ { label: getKeyword("as"), kind: CompletionItemKind.Keyword, detail: "Type/format" },
1957
+ {
1958
+ label: getKeyword("then"),
1959
+ kind: CompletionItemKind.Keyword,
1960
+ detail: "Chain next command"
1961
+ }
1962
+ );
2239
1963
  break;
2240
1964
  case "expression":
2241
1965
  completions.push(
@@ -2305,15 +2029,25 @@ connection.onHover((params) => {
2305
2029
  if (!currentLine2) return null;
2306
2030
  const word2 = getWordAtPosition(currentLine2, found.localChar);
2307
2031
  if (!word2) return null;
2308
- let doc2 = getHoverDocumentation(word2.text, globalSettings.language);
2032
+ const expanded2 = expandSigilPrefix(currentLine2, word2);
2033
+ let lookupText2 = expanded2?.text ?? word2.text;
2034
+ let lookupStart2 = expanded2?.start ?? word2.start;
2035
+ let doc2 = getHoverDocumentation(lookupText2, globalSettings.language);
2036
+ if (!doc2) {
2037
+ lookupText2 = word2.text;
2038
+ lookupStart2 = word2.start;
2039
+ doc2 = getHoverDocumentation(lookupText2, globalSettings.language);
2040
+ }
2309
2041
  if (!doc2) return null;
2310
- const lse2 = tryGetLSE(found.region.code);
2311
- if (lse2) {
2312
- doc2 += `
2042
+ if (found.region.type !== "template") {
2043
+ const lse2 = tryGetLSE(found.region.code);
2044
+ if (lse2) {
2045
+ doc2 += `
2313
2046
 
2314
2047
  **LSE:** \`${lse2}\``;
2048
+ }
2315
2049
  }
2316
- const startChar = found.localLine === 0 ? found.region.startChar + word2.start : word2.start;
2050
+ const startChar = found.localLine === 0 ? found.region.startChar + lookupStart2 : lookupStart2;
2317
2051
  const endChar = found.localLine === 0 ? found.region.startChar + word2.end : word2.end;
2318
2052
  return {
2319
2053
  contents: { kind: MarkupKind.Markdown, value: doc2 },
@@ -2328,7 +2062,15 @@ connection.onHover((params) => {
2328
2062
  if (!currentLine) return null;
2329
2063
  const word = getWordAtPosition(currentLine, position.character);
2330
2064
  if (!word) return null;
2331
- let doc = getHoverDocumentation(word.text, globalSettings.language);
2065
+ const expanded = expandSigilPrefix(currentLine, word);
2066
+ let lookupText = expanded?.text ?? word.text;
2067
+ let lookupStart = expanded?.start ?? word.start;
2068
+ let doc = getHoverDocumentation(lookupText, globalSettings.language);
2069
+ if (!doc) {
2070
+ lookupText = word.text;
2071
+ lookupStart = word.start;
2072
+ doc = getHoverDocumentation(lookupText, globalSettings.language);
2073
+ }
2332
2074
  if (!doc) return null;
2333
2075
  const lse = tryGetLSE(text);
2334
2076
  if (lse) {
@@ -2339,7 +2081,7 @@ connection.onHover((params) => {
2339
2081
  return {
2340
2082
  contents: { kind: MarkupKind.Markdown, value: doc },
2341
2083
  range: {
2342
- start: { line: position.line, character: word.start },
2084
+ start: { line: position.line, character: lookupStart },
2343
2085
  end: { line: position.line, character: word.end }
2344
2086
  }
2345
2087
  };
@@ -2351,9 +2093,9 @@ connection.onHover((params) => {
2351
2093
  function tryGetLSE(code) {
2352
2094
  if (!interchangeLSP || !parseFunction || !fromCoreASTFn || !frameworkIR) return null;
2353
2095
  try {
2354
- const ast = parseFunction(code);
2355
- if (!ast) return null;
2356
- const interchange = fromCoreASTFn(ast);
2096
+ const parseResult = parseFunction(code);
2097
+ if (!parseResult.node) return null;
2098
+ const interchange = fromCoreASTFn(parseResult.node);
2357
2099
  if (!interchange) return null;
2358
2100
  const semanticNode = frameworkIR.fromInterchangeNode(interchange);
2359
2101
  return frameworkIR.renderExplicit(semanticNode);
@@ -2361,7 +2103,30 @@ function tryGetLSE(code) {
2361
2103
  return null;
2362
2104
  }
2363
2105
  }
2106
+ function expandSigilPrefix(line, word) {
2107
+ if (word.start === 0) return null;
2108
+ const prev = line[word.start - 1];
2109
+ if (prev !== "^" && prev !== "#") return null;
2110
+ return { text: prev + word.text, start: word.start - 1 };
2111
+ }
2364
2112
  function getHoverDocumentation(word, language) {
2113
+ const docsPre = lspMetadata?.HOVER_DOCS ?? FALLBACK_HOVER_DOCS;
2114
+ if (word.startsWith("^")) {
2115
+ if (docsPre["^"]) {
2116
+ return formatHoverEntry(docsPre["^"], word, language);
2117
+ }
2118
+ }
2119
+ if (word.startsWith("#")) {
2120
+ const direct = docsPre[word.toLowerCase()];
2121
+ if (direct) {
2122
+ return formatHoverEntry(direct, word, language);
2123
+ }
2124
+ }
2125
+ if (word.startsWith("attrs.")) {
2126
+ if (docsPre.attrs) {
2127
+ return formatHoverEntry(docsPre.attrs, "attrs", language);
2128
+ }
2129
+ }
2365
2130
  const wordLower = word.toLowerCase();
2366
2131
  const canonicalKey = isMultilingualEnabled(resolvedMode) ? resolveCanonicalKeyword(wordLower, language) : wordLower;
2367
2132
  const docs = lspMetadata?.HOVER_DOCS ?? FALLBACK_HOVER_DOCS;
@@ -2391,10 +2156,25 @@ ${doc.example}
2391
2156
  }
2392
2157
  return content;
2393
2158
  }
2159
+ function formatHoverEntry(doc, displayName, _language) {
2160
+ let content = `**${doc.title}**`;
2161
+ if (displayName !== doc.title) {
2162
+ content = `**${displayName}** (${doc.title})`;
2163
+ }
2164
+ content += `
2165
+
2166
+ ${doc.description}`;
2167
+ content += `
2168
+
2169
+ \`\`\`hyperscript
2170
+ ${doc.example}
2171
+ \`\`\``;
2172
+ return content;
2173
+ }
2394
2174
  var translationCache = null;
2395
2175
  function buildTranslationCache() {
2396
- const cache = /* @__PURE__ */ new Map();
2397
- if (!semanticPackage) return cache;
2176
+ const cache2 = /* @__PURE__ */ new Map();
2177
+ if (!semanticPackage) return cache2;
2398
2178
  const langMap = {
2399
2179
  es: "Spanish",
2400
2180
  ja: "Japanese",
@@ -2431,7 +2211,7 @@ function buildTranslationCache() {
2431
2211
  const translations = [];
2432
2212
  for (const [lang, langName] of Object.entries(langMap)) {
2433
2213
  try {
2434
- const profile = semanticPackage.getProfile(lang);
2214
+ const profile = semanticPackage.tryGetProfile(lang);
2435
2215
  const kw = profile?.keywords?.[canonicalKey];
2436
2216
  if (kw) {
2437
2217
  const trans = kw;
@@ -2444,10 +2224,10 @@ function buildTranslationCache() {
2444
2224
  }
2445
2225
  }
2446
2226
  if (translations.length > 0) {
2447
- cache.set(canonicalKey, translations.join(" \xB7 "));
2227
+ cache2.set(canonicalKey, translations.join(" \xB7 "));
2448
2228
  }
2449
2229
  }
2450
- return cache;
2230
+ return cache2;
2451
2231
  }
2452
2232
  function getKeywordTranslations(canonicalKey) {
2453
2233
  if (!translationCache) {
@@ -2457,21 +2237,21 @@ function getKeywordTranslations(canonicalKey) {
2457
2237
  }
2458
2238
  var reverseKeywordCache = null;
2459
2239
  function buildReverseKeywordCache() {
2460
- const cache = /* @__PURE__ */ new Map();
2461
- if (!semanticPackage) return cache;
2240
+ const cache2 = /* @__PURE__ */ new Map();
2241
+ if (!semanticPackage) return cache2;
2462
2242
  const languagesToTry = getOtherLanguages();
2463
2243
  for (const lang of languagesToTry) {
2464
2244
  try {
2465
- const profile = semanticPackage.getProfile(lang);
2245
+ const profile = semanticPackage.tryGetProfile(lang);
2466
2246
  if (profile?.keywords) {
2467
2247
  for (const [canonicalKey, translation] of Object.entries(profile.keywords)) {
2468
2248
  const trans = translation;
2469
2249
  if (trans.primary) {
2470
- cache.set(trans.primary.toLowerCase(), canonicalKey);
2250
+ cache2.set(trans.primary.toLowerCase(), canonicalKey);
2471
2251
  }
2472
2252
  if (trans.alternatives) {
2473
2253
  for (const alt of trans.alternatives) {
2474
- cache.set(alt.toLowerCase(), canonicalKey);
2254
+ cache2.set(alt.toLowerCase(), canonicalKey);
2475
2255
  }
2476
2256
  }
2477
2257
  }
@@ -2479,7 +2259,7 @@ function buildReverseKeywordCache() {
2479
2259
  if (profile?.references) {
2480
2260
  for (const [engRef, translatedRef] of Object.entries(profile.references)) {
2481
2261
  if (typeof translatedRef === "string") {
2482
- cache.set(translatedRef.toLowerCase(), engRef);
2262
+ cache2.set(translatedRef.toLowerCase(), engRef);
2483
2263
  }
2484
2264
  }
2485
2265
  }
@@ -2487,7 +2267,7 @@ function buildReverseKeywordCache() {
2487
2267
  connection.console.log(`[lokascript-ls] Reverse keyword cache: profile '${lang}' not found`);
2488
2268
  }
2489
2269
  }
2490
- return cache;
2270
+ return cache2;
2491
2271
  }
2492
2272
  function resolveCanonicalKeyword(word, _language) {
2493
2273
  const canonicalKeywords = lspMetadata?.ALL_KEYWORDS ?? FALLBACK_KEYWORDS;
@@ -2497,120 +2277,12 @@ function resolveCanonicalKeyword(word, _language) {
2497
2277
  }
2498
2278
  return reverseKeywordCache.get(word) || word;
2499
2279
  }
2500
- connection.onDefinition((params) => {
2501
- try {
2502
- const document = documents.get(params.textDocument.uri);
2503
- if (!document) return null;
2504
- const text = document.getText();
2505
- const uri = document.uri;
2506
- const position = params.position;
2507
- let targetWord = null;
2508
- if (isHtmlDocument(uri, text)) {
2509
- const regions = extractHyperscriptRegions(text);
2510
- const found = findRegionAtPosition(regions, position.line, position.character);
2511
- if (!found) {
2512
- return null;
2513
- }
2514
- const regionLines = found.region.code.split("\n");
2515
- const currentLine = regionLines[found.localLine];
2516
- if (!currentLine) return null;
2517
- const word = getWordAtPosition(currentLine, found.localChar);
2518
- if (!word) return null;
2519
- targetWord = word.text;
2520
- } else {
2521
- const lines = text.split("\n");
2522
- const currentLine = lines[position.line];
2523
- if (!currentLine) return null;
2524
- const word = getWordAtPosition(currentLine, position.character);
2525
- if (!word) return null;
2526
- targetWord = word.text;
2527
- }
2528
- if (!targetWord) return null;
2529
- const locations = [];
2530
- const behaviorVariants = getKeywordVariants("behavior");
2531
- const defVariants = getKeywordVariants("def");
2532
- const behaviorPattern = new RegExp(
2533
- `\\b(${behaviorVariants.join("|")})\\s+(${escapeRegExp(targetWord)})\\b`,
2534
- "gi"
2535
- );
2536
- const defPattern = new RegExp(
2537
- `\\b(${defVariants.join("|")})\\s+(${escapeRegExp(targetWord)})\\b`,
2538
- "gi"
2539
- );
2540
- if (isHtmlDocument(uri, text)) {
2541
- const regions = extractHyperscriptRegions(text);
2542
- for (const region of regions) {
2543
- for (const match of region.code.matchAll(behaviorPattern)) {
2544
- const matchLine = findLineInRegion(region.code, match.index ?? 0);
2545
- const matchChar = findCharInLine(region.code, match.index ?? 0);
2546
- locations.push({
2547
- uri: params.textDocument.uri,
2548
- range: {
2549
- start: {
2550
- line: region.startLine + matchLine,
2551
- character: matchLine === 0 ? region.startChar + matchChar : matchChar
2552
- },
2553
- end: {
2554
- line: region.startLine + matchLine,
2555
- character: (matchLine === 0 ? region.startChar + matchChar : matchChar) + match[0].length
2556
- }
2557
- }
2558
- });
2559
- }
2560
- for (const match of region.code.matchAll(defPattern)) {
2561
- const matchLine = findLineInRegion(region.code, match.index ?? 0);
2562
- const matchChar = findCharInLine(region.code, match.index ?? 0);
2563
- locations.push({
2564
- uri: params.textDocument.uri,
2565
- range: {
2566
- start: {
2567
- line: region.startLine + matchLine,
2568
- character: matchLine === 0 ? region.startChar + matchChar : matchChar
2569
- },
2570
- end: {
2571
- line: region.startLine + matchLine,
2572
- character: (matchLine === 0 ? region.startChar + matchChar : matchChar) + match[0].length
2573
- }
2574
- }
2575
- });
2576
- }
2577
- }
2578
- } else {
2579
- for (const match of text.matchAll(behaviorPattern)) {
2580
- const pos = offsetToPosition(text, match.index ?? 0);
2581
- locations.push({
2582
- uri: params.textDocument.uri,
2583
- range: {
2584
- start: pos,
2585
- end: { line: pos.line, character: pos.character + match[0].length }
2586
- }
2587
- });
2588
- }
2589
- for (const match of text.matchAll(defPattern)) {
2590
- const pos = offsetToPosition(text, match.index ?? 0);
2591
- locations.push({
2592
- uri: params.textDocument.uri,
2593
- range: {
2594
- start: pos,
2595
- end: { line: pos.line, character: pos.character + match[0].length }
2596
- }
2597
- });
2598
- }
2599
- }
2600
- if (locations.length === 0) return null;
2601
- if (locations.length === 1) return locations[0];
2602
- return locations;
2603
- } catch (error) {
2604
- connection.console.error(`Go to Definition error: ${error}`);
2605
- return null;
2606
- }
2607
- });
2608
2280
  function getKeywordVariants(eng) {
2609
2281
  const variants = [eng];
2610
2282
  if (!semanticPackage) return variants;
2611
2283
  for (const lang of getOtherLanguages()) {
2612
2284
  try {
2613
- const profile = semanticPackage.getProfile(lang);
2285
+ const profile = semanticPackage.tryGetProfile(lang);
2614
2286
  if (profile?.keywords?.[eng]) {
2615
2287
  const trans = profile.keywords[eng];
2616
2288
  if (trans.primary && !variants.includes(trans.primary)) {
@@ -2629,71 +2301,94 @@ function getKeywordVariants(eng) {
2629
2301
  }
2630
2302
  return variants;
2631
2303
  }
2304
+ function getDocSymbolTable(document) {
2305
+ const text = document.getText();
2306
+ const uri = document.uri;
2307
+ const regions = isHtmlDocument(uri, text) ? extractHyperscriptRegions(text) : [{ code: text, startLine: 0, startChar: 0, endLine: 0, endChar: 0, type: "script" }];
2308
+ return { table: getSymbolTable(uri, document.version, regions, getKeywordVariants), uri };
2309
+ }
2310
+ function symbolLocToLocation(uri, loc) {
2311
+ return {
2312
+ uri,
2313
+ range: {
2314
+ start: { line: loc.line, character: loc.character },
2315
+ end: { line: loc.line, character: loc.character + loc.length }
2316
+ }
2317
+ };
2318
+ }
2319
+ connection.onDefinition((params) => {
2320
+ try {
2321
+ const document = documents.get(params.textDocument.uri);
2322
+ if (!document) return null;
2323
+ const { table, uri } = getDocSymbolTable(document);
2324
+ const entry = table.symbolAt(params.position.line, params.position.character);
2325
+ if (!entry || entry.definitions.length === 0) return null;
2326
+ const locations = entry.definitions.map((loc) => symbolLocToLocation(uri, loc));
2327
+ return locations.length === 1 ? locations[0] : locations;
2328
+ } catch (error) {
2329
+ connection.console.error(`Go to Definition error: ${error}`);
2330
+ return null;
2331
+ }
2332
+ });
2632
2333
  connection.onReferences((params) => {
2633
2334
  try {
2634
2335
  const document = documents.get(params.textDocument.uri);
2635
2336
  if (!document) return null;
2636
- const text = document.getText();
2637
- const uri = document.uri;
2638
- const position = params.position;
2639
- let targetWord = null;
2640
- if (isHtmlDocument(uri, text)) {
2641
- const regions = extractHyperscriptRegions(text);
2642
- const found = findRegionAtPosition(regions, position.line, position.character);
2643
- if (!found) return null;
2644
- const regionLines = found.region.code.split("\n");
2645
- const currentLine = regionLines[found.localLine];
2646
- if (!currentLine) return null;
2647
- const word = getWordAtPosition(currentLine, found.localChar);
2648
- if (!word) return null;
2649
- targetWord = word.text;
2650
- } else {
2651
- const lines = text.split("\n");
2652
- const currentLine = lines[position.line];
2653
- if (!currentLine) return null;
2654
- const word = getWordAtPosition(currentLine, position.character);
2655
- if (!word) return null;
2656
- targetWord = word.text;
2657
- }
2658
- if (!targetWord) return null;
2659
- const locations = [];
2660
- const wordPattern = new RegExp(`\\b${escapeRegExp(targetWord)}\\b`, "gi");
2661
- if (isHtmlDocument(uri, text)) {
2662
- const regions = extractHyperscriptRegions(text);
2663
- for (const region of regions) {
2664
- for (const match of region.code.matchAll(wordPattern)) {
2665
- const matchLine = findLineInRegion(region.code, match.index ?? 0);
2666
- const matchChar = findCharInLine(region.code, match.index ?? 0);
2667
- locations.push({
2668
- uri: params.textDocument.uri,
2669
- range: {
2670
- start: {
2671
- line: region.startLine + matchLine,
2672
- character: matchLine === 0 ? region.startChar + matchChar : matchChar
2673
- },
2674
- end: {
2675
- line: region.startLine + matchLine,
2676
- character: (matchLine === 0 ? region.startChar + matchChar : matchChar) + match[0].length
2677
- }
2678
- }
2679
- });
2680
- }
2681
- }
2682
- } else {
2683
- for (const match of text.matchAll(wordPattern)) {
2684
- const pos = offsetToPosition(text, match.index ?? 0);
2685
- locations.push({
2686
- uri: params.textDocument.uri,
2337
+ const { table, uri } = getDocSymbolTable(document);
2338
+ const entry = table.symbolAt(params.position.line, params.position.character);
2339
+ if (!entry) return null;
2340
+ const allLocs = [...entry.definitions, ...entry.usages];
2341
+ if (allLocs.length === 0) return null;
2342
+ return allLocs.map((loc) => symbolLocToLocation(uri, loc));
2343
+ } catch (error) {
2344
+ connection.console.error(`Find References error: ${error}`);
2345
+ return null;
2346
+ }
2347
+ });
2348
+ connection.onPrepareRename((params) => {
2349
+ try {
2350
+ const document = documents.get(params.textDocument.uri);
2351
+ if (!document) return null;
2352
+ const { table } = getDocSymbolTable(document);
2353
+ const entry = table.symbolAt(params.position.line, params.position.character);
2354
+ if (!entry) return null;
2355
+ const allLocs = [...entry.definitions, ...entry.usages];
2356
+ for (const loc of allLocs) {
2357
+ if (loc.line === params.position.line && params.position.character >= loc.character && params.position.character < loc.character + loc.length) {
2358
+ return {
2687
2359
  range: {
2688
- start: pos,
2689
- end: { line: pos.line, character: pos.character + match[0].length }
2690
- }
2691
- });
2360
+ start: { line: loc.line, character: loc.character },
2361
+ end: { line: loc.line, character: loc.character + loc.length }
2362
+ },
2363
+ placeholder: entry.name
2364
+ };
2692
2365
  }
2693
2366
  }
2694
- return locations.length > 0 ? locations : null;
2367
+ return null;
2695
2368
  } catch (error) {
2696
- connection.console.error(`Find References error: ${error}`);
2369
+ connection.console.error(`Prepare Rename error: ${error}`);
2370
+ return null;
2371
+ }
2372
+ });
2373
+ connection.onRenameRequest((params) => {
2374
+ try {
2375
+ const document = documents.get(params.textDocument.uri);
2376
+ if (!document) return null;
2377
+ const { table, uri } = getDocSymbolTable(document);
2378
+ const entry = table.symbolAt(params.position.line, params.position.character);
2379
+ if (!entry) return null;
2380
+ const allLocs = [...entry.definitions, ...entry.usages];
2381
+ if (allLocs.length === 0) return null;
2382
+ const edits = allLocs.map((loc) => ({
2383
+ range: {
2384
+ start: { line: loc.line, character: loc.character },
2385
+ end: { line: loc.line, character: loc.character + loc.length }
2386
+ },
2387
+ newText: params.newName
2388
+ }));
2389
+ return { changes: { [uri]: edits } };
2390
+ } catch (error) {
2391
+ connection.console.error(`Rename error: ${error}`);
2697
2392
  return null;
2698
2393
  }
2699
2394
  });
@@ -2776,7 +2471,7 @@ function extractSymbols(code, language) {
2776
2471
  const variants = [eng];
2777
2472
  if (!semanticPackage || language === "en") return variants;
2778
2473
  try {
2779
- const profile = semanticPackage.getProfile(language);
2474
+ const profile = semanticPackage.tryGetProfile(language);
2780
2475
  if (profile?.keywords?.[eng]) {
2781
2476
  const trans = profile.keywords[eng];
2782
2477
  if (trans.primary) variants.push(trans.primary);