@zenithbuild/language 0.6.17 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,3 +7,17 @@ This package provides:
7
7
  - the `text.html.zenith` TextMate grammar
8
8
  - canonical Zenith snippets
9
9
  - VS Code integration with `@zenithbuild/language-server`
10
+ - compiler-backed diagnostics plus the first `ZEN-DOM-*` quick fixes
11
+ - doc-backed hover and completion coverage for canonical Zenith APIs and documented `on:*` events
12
+
13
+ Build the packaged extension bundle:
14
+
15
+ ```bash
16
+ bun run build
17
+ ```
18
+
19
+ Package a downloadable VS Code-compatible artifact:
20
+
21
+ ```bash
22
+ bun run package:vsix
23
+ ```
@@ -14,13 +14,40 @@
14
14
  { "open": "[", "close": "]" },
15
15
  { "open": "(", "close": ")" },
16
16
  { "open": "\"", "close": "\"" },
17
- { "open": "'", "close": "'" }
17
+ { "open": "'", "close": "'" },
18
+ { "open": "`", "close": "`" },
19
+ { "open": "<!--", "close": "-->", "notIn": ["string"] }
18
20
  ],
19
21
  "surroundingPairs": [
20
22
  ["{", "}"],
21
23
  ["[", "]"],
22
24
  ["(", ")"],
23
25
  ["\"", "\""],
24
- ["'", "'"]
25
- ]
26
+ ["'", "'"],
27
+ ["`", "`"],
28
+ ["<", ">"]
29
+ ],
30
+ "colorizedBracketPairs": [
31
+ ["{", "}"],
32
+ ["[", "]"],
33
+ ["(", ")"]
34
+ ],
35
+ "wordPattern": "[A-Za-z_$][\\w$]*(?::[A-Za-z_][\\w-]*)*",
36
+ "indentationRules": {
37
+ "increaseIndentPattern": "<(?!area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr|/|!)[A-Za-z][\\w:-]*[^/>]*>(?!.*</)|\\{[^}]*$|\\([^)]*$",
38
+ "decreaseIndentPattern": "^\\s*(<\\/[A-Za-z][\\w:-]*>|\\}|\\))"
39
+ },
40
+ "onEnterRules": [
41
+ {
42
+ "beforeText": "<(?!area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)([A-Za-z][\\w:-]*)(?:[^/>]*?)>",
43
+ "afterText": "</\\1>",
44
+ "action": { "indent": "indentOutdent", "appendText": "\t" }
45
+ }
46
+ ],
47
+ "folding": {
48
+ "markers": {
49
+ "start": "^\\s*<!--\\s*#region\\b",
50
+ "end": "^\\s*<!--\\s*#endregion\\b"
51
+ }
52
+ }
26
53
  }
package/out/extension.cjs CHANGED
@@ -56,14 +56,13 @@ async function startLanguageClient(context) {
56
56
  const serverPath = getConfiguredServerPath(context);
57
57
  const serverOptions = {
58
58
  run: {
59
- command: process.execPath,
60
- args: [serverPath],
61
- options: { env: process.env }
59
+ module: serverPath,
60
+ transport: import_node.TransportKind.stdio
62
61
  },
63
62
  debug: {
64
- command: process.execPath,
65
- args: ["--inspect=6010", serverPath],
66
- options: { env: process.env }
63
+ module: serverPath,
64
+ transport: import_node.TransportKind.stdio,
65
+ options: { execArgv: ["--inspect=6010"] }
67
66
  }
68
67
  };
69
68
  const clientOptions = {
@@ -84,10 +83,7 @@ async function restartLanguageClient(context) {
84
83
  }
85
84
  await startLanguageClient(context);
86
85
  }
87
- function activate(context) {
88
- void startLanguageClient(context).catch((error) => {
89
- void vscode.window.showErrorMessage(`Zenith: failed to start language server: ${String(error)}`);
90
- });
86
+ async function activate(context) {
91
87
  context.subscriptions.push(
92
88
  vscode.commands.registerCommand("zenith.restartServer", async () => {
93
89
  await restartLanguageClient(context);
@@ -102,6 +98,12 @@ function activate(context) {
102
98
  await restartLanguageClient(context);
103
99
  })
104
100
  );
101
+ try {
102
+ await startLanguageClient(context);
103
+ } catch (error) {
104
+ void vscode.window.showErrorMessage(`Zenith: failed to start language server: ${String(error)}`);
105
+ throw error;
106
+ }
105
107
  }
106
108
  function deactivate() {
107
109
  return client?.stop();
package/out/server.mjs CHANGED
@@ -4,18 +4,175 @@ import {
4
4
  DidChangeConfigurationNotification,
5
5
  ProposedFeatures,
6
6
  TextDocumentSyncKind
7
- } from "vscode-languageserver/node";
7
+ } from "vscode-languageserver/node.js";
8
8
  import { TextDocument } from "vscode-languageserver-textdocument";
9
9
  import { TextDocuments } from "vscode-languageserver";
10
10
 
11
+ // src/code-actions.ts
12
+ import {
13
+ CodeActionKind
14
+ } from "vscode-languageserver/node.js";
15
+ var DOM_QUERY_SUPPRESS = "// zen-allow:dom-query explain interop reason";
16
+ var DOM_LISTENER_TODO = "// TODO(zenith): replace addEventListener with zenOn(target, eventName, handler)";
17
+ var DOM_LISTENER_CLEANUP = "// TODO(zenith): register the disposer with ctx.cleanup(...) inside zenMount";
18
+ var DOM_WRAPPER_TODO = "// TODO(zenith): replace this guard with zenWindow() / zenDocument()";
19
+ function getCodeActions(text, uri, diagnostics) {
20
+ const actions = [];
21
+ for (const diagnostic of diagnostics) {
22
+ const code = typeof diagnostic.code === "string" ? diagnostic.code : "";
23
+ if (!code) {
24
+ continue;
25
+ }
26
+ if (code === "ZEN-DOM-QUERY") {
27
+ const action = createDomQuerySuppressAction(text, uri, diagnostic);
28
+ if (action) {
29
+ actions.push(action);
30
+ }
31
+ continue;
32
+ }
33
+ if (code === "ZEN-DOM-LISTENER") {
34
+ const action = createCommentInsertionAction(
35
+ text,
36
+ uri,
37
+ diagnostic,
38
+ [DOM_LISTENER_TODO, DOM_LISTENER_CLEANUP],
39
+ "Zenith: Add zenOn migration note"
40
+ );
41
+ if (action) {
42
+ actions.push(action);
43
+ }
44
+ continue;
45
+ }
46
+ if (code === "ZEN-DOM-WRAPPER") {
47
+ const replacementAction = createGlobalThisReplacementAction(text, uri, diagnostic);
48
+ if (replacementAction) {
49
+ actions.push(replacementAction);
50
+ }
51
+ const noteAction = createCommentInsertionAction(
52
+ text,
53
+ uri,
54
+ diagnostic,
55
+ [DOM_WRAPPER_TODO],
56
+ "Zenith: Add zenWindow/zenDocument migration note"
57
+ );
58
+ if (noteAction) {
59
+ actions.push(noteAction);
60
+ }
61
+ }
62
+ }
63
+ return actions;
64
+ }
65
+ function createDomQuerySuppressAction(text, uri, diagnostic) {
66
+ const lineIndex = diagnostic.range.start.line;
67
+ if (lineIndex > 0) {
68
+ const previousLine = getLine(text, lineIndex - 1);
69
+ if (previousLine?.includes("zen-allow:dom-query")) {
70
+ return null;
71
+ }
72
+ }
73
+ return createCommentInsertionAction(
74
+ text,
75
+ uri,
76
+ diagnostic,
77
+ [DOM_QUERY_SUPPRESS],
78
+ "Zenith: Suppress DOM query with zen-allow comment"
79
+ );
80
+ }
81
+ function createGlobalThisReplacementAction(text, uri, diagnostic) {
82
+ const lineIndex = diagnostic.range.start.line;
83
+ const line = getLine(text, lineIndex);
84
+ if (!line) {
85
+ return null;
86
+ }
87
+ if (line.includes("globalThis.window")) {
88
+ return createReplacementAction(
89
+ uri,
90
+ diagnostic,
91
+ {
92
+ start: { line: lineIndex, character: line.indexOf("globalThis.window") },
93
+ end: { line: lineIndex, character: line.indexOf("globalThis.window") + "globalThis.window".length }
94
+ },
95
+ "zenWindow()",
96
+ "Zenith: Replace globalThis.window with zenWindow()"
97
+ );
98
+ }
99
+ if (line.includes("globalThis.document")) {
100
+ return createReplacementAction(
101
+ uri,
102
+ diagnostic,
103
+ {
104
+ start: { line: lineIndex, character: line.indexOf("globalThis.document") },
105
+ end: { line: lineIndex, character: line.indexOf("globalThis.document") + "globalThis.document".length }
106
+ },
107
+ "zenDocument()",
108
+ "Zenith: Replace globalThis.document with zenDocument()"
109
+ );
110
+ }
111
+ return null;
112
+ }
113
+ function createCommentInsertionAction(text, uri, diagnostic, commentLines, title) {
114
+ const lineIndex = diagnostic.range.start.line;
115
+ const line = getLine(text, lineIndex);
116
+ if (line === void 0) {
117
+ return null;
118
+ }
119
+ const indent = line.match(/^\s*/)?.[0] ?? "";
120
+ const previousLine = lineIndex > 0 ? getLine(text, lineIndex - 1) : void 0;
121
+ if (previousLine && commentLines.every((commentLine) => previousLine.includes(commentLine.trim()))) {
122
+ return null;
123
+ }
124
+ const newText = commentLines.map((commentLine) => `${indent}${commentLine}`).join("\n") + "\n";
125
+ return createReplacementAction(
126
+ uri,
127
+ diagnostic,
128
+ {
129
+ start: { line: lineIndex, character: 0 },
130
+ end: { line: lineIndex, character: 0 }
131
+ },
132
+ newText,
133
+ title
134
+ );
135
+ }
136
+ function createReplacementAction(uri, diagnostic, range, newText, title) {
137
+ const edits = [{ range, newText }];
138
+ const documentEdit = {
139
+ textDocument: {
140
+ uri,
141
+ version: null
142
+ },
143
+ edits
144
+ };
145
+ return {
146
+ title,
147
+ kind: CodeActionKind.QuickFix,
148
+ diagnostics: [diagnostic],
149
+ edit: {
150
+ documentChanges: [documentEdit]
151
+ }
152
+ };
153
+ }
154
+ function getLine(text, lineIndex) {
155
+ const lines = text.split("\n");
156
+ return lines[lineIndex];
157
+ }
158
+
11
159
  // src/completions.ts
12
160
  import {
13
161
  CompletionItemKind,
14
- InsertTextFormat
15
- } from "vscode-languageserver/node";
162
+ InsertTextFormat,
163
+ MarkupKind
164
+ } from "vscode-languageserver/node.js";
16
165
 
17
166
  // src/docs.ts
18
167
  var DOCS_BASE_URL = "https://github.com/zenithbuild/framework/blob/master/";
168
+ function createEventDoc(label, summary, example) {
169
+ return {
170
+ label,
171
+ summary,
172
+ example,
173
+ docPath: "docs/documentation/syntax/events.md"
174
+ };
175
+ }
19
176
  var SYMBOL_DOCS = {
20
177
  zenEffect: {
21
178
  label: "zenEffect",
@@ -76,7 +233,128 @@ var SYMBOL_DOCS = {
76
233
  summary: "Deterministic multi-node collection helper that replaces selector scans.",
77
234
  example: "const nodes = collectRefs(linkRefA, linkRefB, linkRefC)",
78
235
  docPath: "docs/documentation/reactivity/dom-and-environment.md"
79
- }
236
+ },
237
+ "on:esc": {
238
+ ...createEventDoc(
239
+ "on:esc",
240
+ "Escape-filtered keydown alias that routes through Zenith\u2019s document-level esc dispatch.",
241
+ "<button on:esc={closeMenu}>Close</button>"
242
+ )
243
+ },
244
+ "on:hoverin": {
245
+ ...createEventDoc(
246
+ "on:hoverin",
247
+ "Hover sugar alias for pointerenter when hover logic needs real event wiring.",
248
+ "<div on:hoverin={handleEnter}></div>"
249
+ )
250
+ },
251
+ "on:hoverout": {
252
+ ...createEventDoc(
253
+ "on:hoverout",
254
+ "Hover sugar alias for pointerleave when hover logic needs real event wiring.",
255
+ "<div on:hoverout={handleLeave}></div>"
256
+ )
257
+ },
258
+ "on:click": createEventDoc(
259
+ "on:click",
260
+ "Canonical mouse click binding in Zenith\u2019s universal on:* event model.",
261
+ "<button on:click={handleClick}>Press</button>"
262
+ ),
263
+ "on:doubleclick": createEventDoc(
264
+ "on:doubleclick",
265
+ "Canonical alias that normalizes doubleclick bindings to the emitted dblclick event.",
266
+ "<button on:doubleclick={handleDoubleClick}>Press</button>"
267
+ ),
268
+ "on:dblclick": createEventDoc(
269
+ "on:dblclick",
270
+ "Canonical double-click binding using the normalized dblclick event name.",
271
+ "<button on:dblclick={handleDoubleClick}>Press</button>"
272
+ ),
273
+ "on:keydown": createEventDoc(
274
+ "on:keydown",
275
+ "Canonical keyboard keydown binding in Zenith\u2019s universal on:* event model.",
276
+ "<div on:keydown={handleKeydown}></div>"
277
+ ),
278
+ "on:keyup": createEventDoc(
279
+ "on:keyup",
280
+ "Canonical keyboard keyup binding in Zenith\u2019s universal on:* event model.",
281
+ "<div on:keyup={handleKeyup}></div>"
282
+ ),
283
+ "on:submit": createEventDoc(
284
+ "on:submit",
285
+ "Canonical form submit binding in Zenith\u2019s universal on:* event model.",
286
+ "<form on:submit={handleSubmit}></form>"
287
+ ),
288
+ "on:input": createEventDoc(
289
+ "on:input",
290
+ "Canonical input binding for immediate form-value updates.",
291
+ "<input on:input={handleInput} />"
292
+ ),
293
+ "on:change": createEventDoc(
294
+ "on:change",
295
+ "Canonical change binding for committed form-value updates.",
296
+ "<input on:change={handleChange} />"
297
+ ),
298
+ "on:focus": createEventDoc(
299
+ "on:focus",
300
+ "Canonical focus binding for element focus transitions.",
301
+ "<input on:focus={handleFocus} />"
302
+ ),
303
+ "on:blur": createEventDoc(
304
+ "on:blur",
305
+ "Canonical blur binding for element focus exit transitions.",
306
+ "<input on:blur={handleBlur} />"
307
+ ),
308
+ "on:pointerdown": createEventDoc(
309
+ "on:pointerdown",
310
+ "Canonical pointerdown binding from the recommended pointer event set.",
311
+ "<div on:pointerdown={handlePointerDown}></div>"
312
+ ),
313
+ "on:pointerup": createEventDoc(
314
+ "on:pointerup",
315
+ "Canonical pointerup binding from the recommended pointer event set.",
316
+ "<div on:pointerup={handlePointerUp}></div>"
317
+ ),
318
+ "on:pointermove": createEventDoc(
319
+ "on:pointermove",
320
+ "Canonical pointermove binding from the recommended pointer event set.",
321
+ "<svg on:pointermove={handlePointerMove}></svg>"
322
+ ),
323
+ "on:pointerenter": createEventDoc(
324
+ "on:pointerenter",
325
+ "Direct pointerenter binding remains fully supported alongside on:hoverin.",
326
+ "<div on:pointerenter={handleEnter}></div>"
327
+ ),
328
+ "on:pointerleave": createEventDoc(
329
+ "on:pointerleave",
330
+ "Direct pointerleave binding remains fully supported alongside on:hoverout.",
331
+ "<div on:pointerleave={handleLeave}></div>"
332
+ ),
333
+ "on:dragstart": createEventDoc(
334
+ "on:dragstart",
335
+ "Canonical dragstart binding from Zenith\u2019s recommended drag event set.",
336
+ '<div draggable="true" on:dragstart={handleDragStart}></div>'
337
+ ),
338
+ "on:dragover": createEventDoc(
339
+ "on:dragover",
340
+ "Canonical dragover binding from Zenith\u2019s recommended drag event set.",
341
+ "<div on:dragover={handleDragOver}></div>"
342
+ ),
343
+ "on:drop": createEventDoc(
344
+ "on:drop",
345
+ "Canonical drop binding from Zenith\u2019s recommended drag event set.",
346
+ "<div on:drop={handleDrop}></div>"
347
+ ),
348
+ "on:scroll": createEventDoc(
349
+ "on:scroll",
350
+ "Canonical scroll binding from Zenith\u2019s recommended event set.",
351
+ "<div on:scroll={handleScroll}></div>"
352
+ ),
353
+ "on:contextmenu": createEventDoc(
354
+ "on:contextmenu",
355
+ "Canonical contextmenu binding from Zenith\u2019s recommended mouse event set.",
356
+ "<div on:contextmenu={handleContextMenu}></div>"
357
+ )
80
358
  };
81
359
  var canonicalScriptSymbols = [
82
360
  "zenMount",
@@ -92,6 +370,7 @@ var canonicalScriptSymbols = [
92
370
  ];
93
371
  var canonicalEventAttributes = [
94
372
  "on:click",
373
+ "on:doubleclick",
95
374
  "on:dblclick",
96
375
  "on:keydown",
97
376
  "on:keyup",
@@ -264,20 +543,34 @@ function positionAt(text, offset) {
264
543
  // src/completions.ts
265
544
  var blockedFrameworkTokens = ["react", "vue", "svelte", "rfce"];
266
545
  function createScriptCompletion(label) {
546
+ const docs = getSymbolDoc(label);
267
547
  return {
268
548
  label,
269
549
  kind: CompletionItemKind.Function,
270
- detail: "Zenith canonical primitive",
550
+ detail: docs?.summary ?? "Zenith canonical primitive",
551
+ ...docs ? {
552
+ documentation: {
553
+ kind: MarkupKind.Markdown,
554
+ value: `Docs: [${docs.docPath}](${getDocUrl(docs.docPath)})`
555
+ }
556
+ } : {},
271
557
  insertText: label
272
558
  };
273
559
  }
274
560
  function createEventCompletion(label) {
561
+ const docs = getSymbolDoc(label);
275
562
  return {
276
563
  label,
277
564
  kind: CompletionItemKind.Property,
278
- detail: "Canonical Zenith DOM event binding",
565
+ detail: docs?.summary ?? "Canonical Zenith DOM event binding",
566
+ ...docs ? {
567
+ documentation: {
568
+ kind: MarkupKind.Markdown,
569
+ value: `Docs: [${docs.docPath}](${getDocUrl(docs.docPath)})`
570
+ }
571
+ } : {},
279
572
  insertTextFormat: InsertTextFormat.Snippet,
280
- insertText: `${label}={$1:handler}`
573
+ insertText: `${label}={\${1:handler}}`
281
574
  };
282
575
  }
283
576
  function createPropHandlerCompletion() {
@@ -286,7 +579,7 @@ function createPropHandlerCompletion() {
286
579
  kind: CompletionItemKind.Snippet,
287
580
  detail: "Pass handler props through components, then bind them back to on:* in component markup.",
288
581
  insertTextFormat: InsertTextFormat.Snippet,
289
- insertText: "onClick={$1:handler}"
582
+ insertText: "onClick={${1:handler}}"
290
583
  };
291
584
  }
292
585
  function getCompletionItems(text, position) {
@@ -309,7 +602,6 @@ function filterFrameworkNoise(items, typedPrefix) {
309
602
  return items.filter((item) => {
310
603
  const haystack = [
311
604
  item.label,
312
- item.detail ?? "",
313
605
  typeof item.insertText === "string" ? item.insertText : ""
314
606
  ].join(" ").toLowerCase();
315
607
  return blockedFrameworkTokens.every((token) => !haystack.includes(token));
@@ -319,46 +611,80 @@ function filterFrameworkNoise(items, typedPrefix) {
319
611
  // src/diagnostics.ts
320
612
  import { fileURLToPath } from "node:url";
321
613
  import { compile } from "@zenithbuild/compiler";
322
- import { DiagnosticSeverity } from "vscode-languageserver/node";
614
+ import {
615
+ DiagnosticSeverity,
616
+ DiagnosticTag
617
+ } from "vscode-languageserver/node.js";
323
618
  async function collectDiagnosticsFromSource(source, filePath, strictDomLints) {
324
619
  try {
325
620
  const result = compile({ source, filePath });
326
621
  if (result.schemaVersion !== 1) {
327
622
  return [compilerContractDiagnostic(`Unsupported compiler schemaVersion: ${String(result.schemaVersion)}`)];
328
623
  }
329
- const warnings = Array.isArray(result.warnings) ? result.warnings : [];
330
- return warnings.map((warning) => ({
331
- source: "zenith",
332
- code: warning.code,
333
- message: warning.message,
334
- severity: resolveSeverity(warning, strictDomLints),
335
- range: toRange(warning.range)
336
- }));
624
+ return mapCompilerEnvelopeToDiagnostics(result, strictDomLints);
337
625
  } catch (error) {
338
626
  return [compilerContractDiagnostic(String(error))];
339
627
  }
340
628
  }
629
+ function mapCompilerEnvelopeToDiagnostics(result, strictDomLints) {
630
+ const diagnostics = Array.isArray(result.diagnostics) ? result.diagnostics : [];
631
+ if (diagnostics.length > 0) {
632
+ return diagnostics.map((diagnostic) => {
633
+ const tags = mapTags(diagnostic.tags);
634
+ return {
635
+ source: diagnostic.source ?? "zenith",
636
+ code: diagnostic.code,
637
+ message: diagnostic.message,
638
+ severity: resolveSeverity(diagnostic, strictDomLints),
639
+ range: toRange(diagnostic.range),
640
+ ...tags ? { tags } : {}
641
+ };
642
+ });
643
+ }
644
+ const warnings = Array.isArray(result.warnings) ? result.warnings : [];
645
+ return warnings.map((warning) => ({
646
+ source: "zenith",
647
+ code: warning.code,
648
+ message: warning.message,
649
+ severity: resolveSeverity(warning, strictDomLints),
650
+ range: toRange(warning.range)
651
+ }));
652
+ }
341
653
  function resolveDocumentPath(uri) {
342
654
  if (uri.startsWith("file://")) {
343
655
  return fileURLToPath(uri);
344
656
  }
345
657
  return uri.replace(/^[a-z]+:\/\//i, "/virtual/");
346
658
  }
347
- function resolveSeverity(warning, strictDomLints) {
348
- if (strictDomLints && warning.code.startsWith("ZEN-DOM-")) {
659
+ function resolveSeverity(diagnostic, strictDomLints) {
660
+ if (strictDomLints && diagnostic.code.startsWith("ZEN-DOM-")) {
349
661
  return DiagnosticSeverity.Error;
350
662
  }
351
- if (warning.severity === "error") {
663
+ if (diagnostic.severity === "error") {
352
664
  return DiagnosticSeverity.Error;
353
665
  }
354
- if (warning.severity === "hint") {
666
+ if (diagnostic.severity === "hint") {
355
667
  return DiagnosticSeverity.Hint;
356
668
  }
357
- if (warning.severity === "info") {
669
+ if (diagnostic.severity === "information") {
358
670
  return DiagnosticSeverity.Information;
359
671
  }
360
672
  return DiagnosticSeverity.Warning;
361
673
  }
674
+ function mapTags(tags) {
675
+ if (!Array.isArray(tags) || tags.length === 0) {
676
+ return void 0;
677
+ }
678
+ return tags.flatMap((tag) => {
679
+ if (tag === "deprecated") {
680
+ return [DiagnosticTag.Deprecated];
681
+ }
682
+ if (tag === "unnecessary") {
683
+ return [DiagnosticTag.Unnecessary];
684
+ }
685
+ return [];
686
+ });
687
+ }
362
688
  function toRange(range) {
363
689
  if (!range) {
364
690
  return {
@@ -391,7 +717,7 @@ function compilerContractDiagnostic(message) {
391
717
  }
392
718
 
393
719
  // src/hover.ts
394
- import { MarkupKind } from "vscode-languageserver/node";
720
+ import { MarkupKind as MarkupKind2 } from "vscode-languageserver/node.js";
395
721
  function getHover(text, position) {
396
722
  const symbol = getWord(text, position);
397
723
  const docs = getSymbolDoc(symbol);
@@ -414,7 +740,7 @@ function getHover(text, position) {
414
740
  return {
415
741
  range,
416
742
  contents: {
417
- kind: MarkupKind.Markdown,
743
+ kind: MarkupKind2.Markdown,
418
744
  value: markdown
419
745
  }
420
746
  };
@@ -546,7 +872,8 @@ function startLanguageServer() {
546
872
  completionProvider: {
547
873
  triggerCharacters: [":", "<", "{"]
548
874
  },
549
- hoverProvider: true
875
+ hoverProvider: true,
876
+ codeActionProvider: true
550
877
  }
551
878
  };
552
879
  });
@@ -575,6 +902,13 @@ function startLanguageServer() {
575
902
  }
576
903
  return getHover(document.getText(), params.position);
577
904
  });
905
+ connection.onCodeAction((params) => {
906
+ const document = documents.get(params.textDocument.uri);
907
+ if (!document) {
908
+ return [];
909
+ }
910
+ return getCodeActions(document.getText(), params.textDocument.uri, params.context.diagnostics);
911
+ });
578
912
  documents.onDidOpen((event) => {
579
913
  void scheduler.flush(event.document.uri);
580
914
  });
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@zenithbuild/language",
3
3
  "displayName": "Zenith Language Support",
4
4
  "description": "Canonical Zenith syntax highlighting, snippets, and VS Code integration",
5
- "version": "0.6.17",
5
+ "version": "0.7.0",
6
6
  "publisher": "ZenithBuild",
7
7
  "license": "MIT",
8
8
  "private": false,
@@ -48,11 +48,15 @@
48
48
  "scripts": {
49
49
  "build": "bun run build:server && rm -rf out && mkdir -p out && bun x esbuild src/extension.ts --bundle --outfile=out/extension.cjs --external:vscode --external:vscode-languageclient/node --format=cjs --platform=node && cp ../language-server/dist/server.mjs out/server.mjs",
50
50
  "build:server": "bun run --cwd ../language-server build",
51
+ "package:vsix": "bun run build && node scripts/package-vsix.mjs",
51
52
  "typecheck": "tsc -p tsconfig.json --noEmit",
52
53
  "test": "node --test test/*.spec.mjs",
53
54
  "prepublishOnly": "bun run build"
54
55
  },
55
56
  "dependencies": {
57
+ "@zenithbuild/compiler": "0.7.0",
58
+ "vscode-languageserver": "^9.0.1",
59
+ "vscode-languageserver-textdocument": "^1.0.11",
56
60
  "vscode-languageclient": "^9.0.1"
57
61
  },
58
62
  "devDependencies": {
@@ -1,4 +1,56 @@
1
1
  {
2
+ "zen-component": {
3
+ "prefix": "zen-component",
4
+ "body": [
5
+ "<script lang=\"ts\">",
6
+ "\t$0",
7
+ "</script>",
8
+ "",
9
+ "<${1:main} class=\"${2:root}\">",
10
+ "\t",
11
+ "</${1:main}>"
12
+ ],
13
+ "description": "Zenith component skeleton with script and markup"
14
+ },
15
+ "zen-server-script": {
16
+ "prefix": "zen-server-script",
17
+ "body": [
18
+ "<script server lang=\"ts\">",
19
+ "\t$0",
20
+ "</script>"
21
+ ],
22
+ "description": "Server-side script block"
23
+ },
24
+ "zen-setup-script": {
25
+ "prefix": "zen-setup-script",
26
+ "body": [
27
+ "<script setup=\"ts\">",
28
+ "import ${1:Component} from \"${2:@/components/${1:Component}.zen}\"",
29
+ "",
30
+ "$0",
31
+ "</script>"
32
+ ],
33
+ "description": "Setup script block with component import"
34
+ },
35
+ "zen-guard-load": {
36
+ "prefix": "zen-guard-load",
37
+ "body": [
38
+ "<script server lang=\"ts\">",
39
+ "import { allow, redirect, deny, data } from '@zenithbuild/core/server'",
40
+ "",
41
+ "export const guard = async (ctx) => {",
42
+ "\t${1:// return allow(), redirect(url), or deny()}",
43
+ "\treturn allow()",
44
+ "}",
45
+ "",
46
+ "export const load = async (ctx) => {",
47
+ "\t$0",
48
+ "\treturn data({})",
49
+ "}",
50
+ "</script>"
51
+ ],
52
+ "description": "Route guard and load pattern for protected routes"
53
+ },
2
54
  "state-toggle": {
3
55
  "prefix": "state-toggle",
4
56
  "body": [
@@ -4,7 +4,10 @@
4
4
  "scopeName": "text.html.zenith",
5
5
  "patterns": [
6
6
  { "include": "#comment" },
7
+ { "include": "#script-server-typescript" },
8
+ { "include": "#script-setup-typescript" },
7
9
  { "include": "#script-typescript" },
10
+ { "include": "#script-plain" },
8
11
  { "include": "#style-tag" },
9
12
  { "include": "#closing-tag" },
10
13
  { "include": "#opening-tag" },
@@ -16,6 +19,44 @@
16
19
  "end": "-->",
17
20
  "name": "comment.block.zenith"
18
21
  },
22
+ "script-server-typescript": {
23
+ "begin": "(<)(script)\\b(?=[^>]*\\bserver\\b)(?=[^>]*\\blang\\s*=\\s*(['\"])(?:ts|typescript)\\3)([^>]*)(>)",
24
+ "beginCaptures": {
25
+ "1": { "name": "punctuation.definition.tag.begin.zenith" },
26
+ "2": { "name": "entity.name.tag.script.zenith" },
27
+ "4": { "name": "meta.tag.attributes.zenith" },
28
+ "5": { "name": "punctuation.definition.tag.end.zenith" }
29
+ },
30
+ "end": "(</)(script)(>)",
31
+ "endCaptures": {
32
+ "1": { "name": "punctuation.definition.tag.begin.zenith" },
33
+ "2": { "name": "entity.name.tag.script.zenith" },
34
+ "3": { "name": "punctuation.definition.tag.end.zenith" }
35
+ },
36
+ "contentName": "meta.embedded.block.typescript source.ts",
37
+ "patterns": [
38
+ { "include": "source.ts" }
39
+ ]
40
+ },
41
+ "script-setup-typescript": {
42
+ "begin": "(<)(script)\\b(?=[^>]*\\bsetup\\s*=\\s*(['\"])(?:ts|typescript)\\3)([^>]*)(>)",
43
+ "beginCaptures": {
44
+ "1": { "name": "punctuation.definition.tag.begin.zenith" },
45
+ "2": { "name": "entity.name.tag.script.zenith" },
46
+ "4": { "name": "meta.tag.attributes.zenith" },
47
+ "5": { "name": "punctuation.definition.tag.end.zenith" }
48
+ },
49
+ "end": "(</)(script)(>)",
50
+ "endCaptures": {
51
+ "1": { "name": "punctuation.definition.tag.begin.zenith" },
52
+ "2": { "name": "entity.name.tag.script.zenith" },
53
+ "3": { "name": "punctuation.definition.tag.end.zenith" }
54
+ },
55
+ "contentName": "meta.embedded.block.typescript source.ts",
56
+ "patterns": [
57
+ { "include": "source.ts" }
58
+ ]
59
+ },
19
60
  "script-typescript": {
20
61
  "begin": "(<)(script)\\b(?=[^>]*\\blang\\s*=\\s*(['\"])(?:ts|typescript)\\3)([^>]*)(>)",
21
62
  "beginCaptures": {
@@ -35,6 +76,23 @@
35
76
  { "include": "source.ts" }
36
77
  ]
37
78
  },
79
+ "script-plain": {
80
+ "begin": "(<)(script)\\b([^>]*)(>)",
81
+ "beginCaptures": {
82
+ "1": { "name": "punctuation.definition.tag.begin.zenith" },
83
+ "2": { "name": "entity.name.tag.script.zenith" },
84
+ "3": { "name": "meta.tag.attributes.zenith" },
85
+ "4": { "name": "punctuation.definition.tag.end.zenith" }
86
+ },
87
+ "end": "(</)(script)(>)",
88
+ "endCaptures": {
89
+ "1": { "name": "punctuation.definition.tag.begin.zenith" },
90
+ "2": { "name": "entity.name.tag.script.zenith" },
91
+ "3": { "name": "punctuation.definition.tag.end.zenith" }
92
+ },
93
+ "contentName": "meta.embedded.block.script.zenith",
94
+ "patterns": []
95
+ },
38
96
  "style-tag": {
39
97
  "begin": "(<)(style)\\b([^>]*)(>)",
40
98
  "beginCaptures": {
@@ -55,7 +113,7 @@
55
113
  ]
56
114
  },
57
115
  "closing-tag": {
58
- "match": "(</)([A-Za-z][\\w:-]*)(>)",
116
+ "match": "(</)([-A-Za-z][\\w:-]*)(>)",
59
117
  "captures": {
60
118
  "1": { "name": "punctuation.definition.tag.begin.zenith" },
61
119
  "2": { "name": "entity.name.tag.zenith" },
@@ -63,7 +121,7 @@
63
121
  }
64
122
  },
65
123
  "opening-tag": {
66
- "begin": "(<)([A-Za-z][\\w:-]*)",
124
+ "begin": "(<)([-A-Za-z][\\w:-]*)",
67
125
  "beginCaptures": {
68
126
  "1": { "name": "punctuation.definition.tag.begin.zenith" },
69
127
  "2": { "name": "entity.name.tag.zenith" }