tjs-lang 0.2.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.
Files changed (91) hide show
  1. package/CONTEXT.md +594 -0
  2. package/LICENSE +190 -0
  3. package/README.md +220 -0
  4. package/bin/benchmarks.ts +351 -0
  5. package/bin/dev.ts +205 -0
  6. package/bin/docs.js +170 -0
  7. package/bin/install-cursor.sh +71 -0
  8. package/bin/install-vscode.sh +71 -0
  9. package/bin/select-local-models.d.ts +1 -0
  10. package/bin/select-local-models.js +28 -0
  11. package/bin/select-local-models.ts +31 -0
  12. package/demo/autocomplete.test.ts +232 -0
  13. package/demo/docs.json +186 -0
  14. package/demo/examples.test.ts +598 -0
  15. package/demo/index.html +91 -0
  16. package/demo/src/autocomplete.ts +482 -0
  17. package/demo/src/capabilities.ts +859 -0
  18. package/demo/src/demo-nav.ts +2097 -0
  19. package/demo/src/examples.test.ts +161 -0
  20. package/demo/src/examples.ts +476 -0
  21. package/demo/src/imports.test.ts +196 -0
  22. package/demo/src/imports.ts +421 -0
  23. package/demo/src/index.ts +639 -0
  24. package/demo/src/module-store.ts +635 -0
  25. package/demo/src/module-sw.ts +132 -0
  26. package/demo/src/playground.ts +949 -0
  27. package/demo/src/service-host.ts +389 -0
  28. package/demo/src/settings.ts +440 -0
  29. package/demo/src/style.ts +280 -0
  30. package/demo/src/tjs-playground.ts +1605 -0
  31. package/demo/src/ts-examples.ts +478 -0
  32. package/demo/src/ts-playground.ts +1092 -0
  33. package/demo/static/favicon.svg +30 -0
  34. package/demo/static/photo-1.jpg +0 -0
  35. package/demo/static/photo-2.jpg +0 -0
  36. package/demo/static/texts/ai-history.txt +9 -0
  37. package/demo/static/texts/coffee-origins.txt +9 -0
  38. package/demo/static/texts/renewable-energy.txt +9 -0
  39. package/dist/index.js +256 -0
  40. package/dist/index.js.map +37 -0
  41. package/dist/tjs-batteries.js +4 -0
  42. package/dist/tjs-batteries.js.map +15 -0
  43. package/dist/tjs-full.js +256 -0
  44. package/dist/tjs-full.js.map +37 -0
  45. package/dist/tjs-transpiler.js +220 -0
  46. package/dist/tjs-transpiler.js.map +21 -0
  47. package/dist/tjs-vm.js +4 -0
  48. package/dist/tjs-vm.js.map +14 -0
  49. package/docs/CNAME +1 -0
  50. package/docs/favicon.svg +30 -0
  51. package/docs/index.html +91 -0
  52. package/docs/index.js +10468 -0
  53. package/docs/index.js.map +92 -0
  54. package/docs/photo-1.jpg +0 -0
  55. package/docs/photo-1.webp +0 -0
  56. package/docs/photo-2.jpg +0 -0
  57. package/docs/photo-2.webp +0 -0
  58. package/docs/texts/ai-history.txt +9 -0
  59. package/docs/texts/coffee-origins.txt +9 -0
  60. package/docs/texts/renewable-energy.txt +9 -0
  61. package/docs/tjs-lang.svg +31 -0
  62. package/docs/tosijs-agent.svg +31 -0
  63. package/editors/README.md +325 -0
  64. package/editors/ace/ajs-mode.js +328 -0
  65. package/editors/ace/ajs-mode.ts +269 -0
  66. package/editors/ajs-syntax.ts +212 -0
  67. package/editors/build-grammars.ts +510 -0
  68. package/editors/codemirror/ajs-language.js +287 -0
  69. package/editors/codemirror/ajs-language.ts +1447 -0
  70. package/editors/codemirror/autocomplete.test.ts +531 -0
  71. package/editors/codemirror/component.ts +404 -0
  72. package/editors/monaco/ajs-monarch.js +243 -0
  73. package/editors/monaco/ajs-monarch.ts +225 -0
  74. package/editors/tjs-syntax.ts +115 -0
  75. package/editors/vscode/language-configuration.json +37 -0
  76. package/editors/vscode/package.json +65 -0
  77. package/editors/vscode/syntaxes/ajs-injection.tmLanguage.json +107 -0
  78. package/editors/vscode/syntaxes/ajs.tmLanguage.json +252 -0
  79. package/editors/vscode/syntaxes/tjs.tmLanguage.json +333 -0
  80. package/package.json +83 -0
  81. package/src/cli/commands/check.ts +41 -0
  82. package/src/cli/commands/convert.ts +133 -0
  83. package/src/cli/commands/emit.ts +260 -0
  84. package/src/cli/commands/run.ts +68 -0
  85. package/src/cli/commands/test.ts +194 -0
  86. package/src/cli/commands/types.ts +20 -0
  87. package/src/cli/create-app.ts +236 -0
  88. package/src/cli/playground.ts +250 -0
  89. package/src/cli/tjs.ts +166 -0
  90. package/src/cli/tjsx.ts +160 -0
  91. package/tjs-lang.svg +31 -0
@@ -0,0 +1,404 @@
1
+ /**
2
+ * CodeMirror 6 Web Component
3
+ *
4
+ * A light-DOM web component wrapper for CodeMirror 6.
5
+ * Supports multiple language modes including AJS, TJS, JavaScript, CSS, HTML, and Markdown.
6
+ *
7
+ * Usage:
8
+ * ```html
9
+ * <code-mirror mode="tjs">function foo(x: 0) { return x }</code-mirror>
10
+ * ```
11
+ *
12
+ * Attributes:
13
+ * - mode: Language mode (ajs, tjs, js, css, html, markdown)
14
+ * - disabled: Make editor read-only
15
+ * - name: Tab name (for use with tab-selector)
16
+ *
17
+ * Properties:
18
+ * - value: Get/set editor content
19
+ * - editor: Access the underlying CodeMirror EditorView
20
+ *
21
+ * Events:
22
+ * - change: Fired when content changes, detail contains { value }
23
+ */
24
+
25
+ import { Component, ElementCreator } from 'tosijs'
26
+ import { EditorView, minimalSetup } from 'codemirror'
27
+ import { EditorState, Extension, Compartment } from '@codemirror/state'
28
+ import { javascript } from '@codemirror/lang-javascript'
29
+ import { css } from '@codemirror/lang-css'
30
+ import { html } from '@codemirror/lang-html'
31
+ import { markdown } from '@codemirror/lang-markdown'
32
+ import { oneDark } from '@codemirror/theme-one-dark'
33
+ import {
34
+ lineNumbers,
35
+ highlightActiveLineGutter,
36
+ highlightSpecialChars,
37
+ drawSelection,
38
+ dropCursor,
39
+ rectangularSelection,
40
+ crosshairCursor,
41
+ highlightActiveLine,
42
+ keymap,
43
+ } from '@codemirror/view'
44
+ import {
45
+ foldGutter,
46
+ indentOnInput,
47
+ syntaxHighlighting,
48
+ defaultHighlightStyle,
49
+ bracketMatching,
50
+ foldKeymap,
51
+ } from '@codemirror/language'
52
+ import { history, defaultKeymap, historyKeymap } from '@codemirror/commands'
53
+ import { highlightSelectionMatches, searchKeymap } from '@codemirror/search'
54
+ import {
55
+ closeBrackets,
56
+ closeBracketsKeymap,
57
+ completionKeymap,
58
+ } from '@codemirror/autocomplete'
59
+ import {
60
+ lintKeymap,
61
+ lintGutter,
62
+ setDiagnostics,
63
+ Diagnostic,
64
+ } from '@codemirror/lint'
65
+ import {
66
+ ajsEditorExtension,
67
+ tjsEditorExtension,
68
+ AutocompleteConfig,
69
+ } from './ajs-language'
70
+
71
+ // Custom setup without autocompletion (we add our own via ajsEditorExtension)
72
+ // Based on basicSetup but excludes autocompletion()
73
+ const customSetup: Extension = [
74
+ lineNumbers(),
75
+ highlightActiveLineGutter(),
76
+ highlightSpecialChars(),
77
+ history(),
78
+ foldGutter(),
79
+ drawSelection(),
80
+ dropCursor(),
81
+ EditorState.allowMultipleSelections.of(true),
82
+ indentOnInput(),
83
+ syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
84
+ bracketMatching(),
85
+ closeBrackets(),
86
+ rectangularSelection(),
87
+ crosshairCursor(),
88
+ highlightActiveLine(),
89
+ highlightSelectionMatches(),
90
+ keymap.of([
91
+ ...closeBracketsKeymap,
92
+ ...defaultKeymap,
93
+ ...searchKeymap,
94
+ ...historyKeymap,
95
+ ...foldKeymap,
96
+ ...completionKeymap,
97
+ ...lintKeymap,
98
+ ]),
99
+ ]
100
+
101
+ // Note: Compartments must be per-instance, not module-level
102
+ // Each editor needs its own compartments to avoid interference
103
+
104
+ // Map of mode names to extensions
105
+ function getLanguageExtension(
106
+ mode: string,
107
+ autocomplete?: AutocompleteConfig
108
+ ): Extension {
109
+ switch (mode) {
110
+ case 'ajs':
111
+ return ajsEditorExtension({ autocomplete })
112
+ case 'tjs':
113
+ // TJS is less restrictive than AJS - allows import/export, async/await, throw
114
+ return tjsEditorExtension({ autocomplete })
115
+ case 'js':
116
+ case 'javascript':
117
+ return javascript()
118
+ case 'ts':
119
+ case 'typescript':
120
+ return javascript({ typescript: true })
121
+ case 'css':
122
+ return css()
123
+ case 'html':
124
+ return html()
125
+ case 'md':
126
+ case 'markdown':
127
+ return markdown()
128
+ default:
129
+ return javascript()
130
+ }
131
+ }
132
+
133
+ export class CodeMirror extends Component {
134
+ // Shadow DOM styles - matching xin-codemirror blueprint pattern
135
+ static styleSpec = {
136
+ ':host': {
137
+ display: 'block',
138
+ width: '100%',
139
+ height: '100%',
140
+ position: 'relative',
141
+ textAlign: 'left',
142
+ fontSize: '14px',
143
+ overflow: 'hidden',
144
+ // Let CodeMirror theme control background, or fall back to transparent
145
+ backgroundColor: 'transparent',
146
+ },
147
+ '.cm-editor': {
148
+ height: '100%',
149
+ },
150
+ '.cm-scroller': {
151
+ outline: 'none',
152
+ fontFamily: "Menlo, Monaco, Consolas, 'Courier New', monospace",
153
+ },
154
+ }
155
+
156
+ private _source: string = ''
157
+ private _editor: EditorView | undefined
158
+ private _autocompleteConfig: AutocompleteConfig = {}
159
+ private _darkModeObserver: MutationObserver | null = null
160
+
161
+ // Per-instance compartments for language, readonly state, and theme
162
+ private languageCompartment = new Compartment()
163
+ private readonlyCompartment = new Compartment()
164
+ private themeCompartment = new Compartment()
165
+
166
+ mode = 'javascript'
167
+ disabled = false
168
+ role = 'code editor'
169
+
170
+ /** Configure autocomplete callbacks for metadata extraction */
171
+ set autocomplete(config: AutocompleteConfig) {
172
+ this._autocompleteConfig = config
173
+ // Reconfigure if editor exists
174
+ if (this._editor) {
175
+ this._editor.dispatch({
176
+ effects: [
177
+ this.languageCompartment.reconfigure(
178
+ getLanguageExtension(this.mode, this._autocompleteConfig)
179
+ ),
180
+ ],
181
+ })
182
+ }
183
+ }
184
+
185
+ get autocomplete(): AutocompleteConfig {
186
+ return this._autocompleteConfig
187
+ }
188
+
189
+ get value(): string {
190
+ return this._editor !== undefined
191
+ ? this._editor.state.doc.toString()
192
+ : this._source
193
+ }
194
+
195
+ set value(text: string) {
196
+ if (this._editor !== undefined) {
197
+ const currentValue = this._editor.state.doc.toString()
198
+ if (currentValue !== text) {
199
+ this._editor.dispatch({
200
+ changes: {
201
+ from: 0,
202
+ to: this._editor.state.doc.length,
203
+ insert: text,
204
+ },
205
+ })
206
+ }
207
+ } else {
208
+ this._source = text
209
+ }
210
+ }
211
+
212
+ get editor(): EditorView | undefined {
213
+ return this._editor
214
+ }
215
+
216
+ constructor() {
217
+ super()
218
+ this.initAttributes('mode', 'disabled')
219
+ }
220
+
221
+ private isDarkMode(): boolean {
222
+ return document.body.classList.contains('darkmode')
223
+ }
224
+
225
+ private getThemeExtension(): Extension {
226
+ return this.isDarkMode() ? oneDark : []
227
+ }
228
+
229
+ private updateTheme() {
230
+ if (!this._editor) return
231
+ this._editor.dispatch({
232
+ effects: this.themeCompartment.reconfigure(this.getThemeExtension()),
233
+ })
234
+ }
235
+
236
+ connectedCallback() {
237
+ super.connectedCallback()
238
+
239
+ // Get initial value from textContent if not already set
240
+ if (
241
+ this._source === '' &&
242
+ this.textContent &&
243
+ this.textContent.trim().length > 0
244
+ ) {
245
+ this._source = this.textContent.trim()
246
+ this.textContent = ''
247
+ }
248
+
249
+ if (!this._editor) {
250
+ this.createEditor()
251
+ }
252
+ }
253
+
254
+ private createEditor() {
255
+ const startState = EditorState.create({
256
+ doc: this._source,
257
+ extensions: [
258
+ customSetup,
259
+ lintGutter(),
260
+ this.languageCompartment.of(
261
+ getLanguageExtension(this.mode, this._autocompleteConfig)
262
+ ),
263
+ this.readonlyCompartment.of(EditorState.readOnly.of(this.disabled)),
264
+ this.themeCompartment.of(this.getThemeExtension()),
265
+ EditorView.updateListener.of((update) => {
266
+ if (update.docChanged) {
267
+ this.dispatchEvent(new Event('change', { bubbles: true }))
268
+ }
269
+ }),
270
+ ],
271
+ })
272
+
273
+ this._editor = new EditorView({
274
+ state: startState,
275
+ parent: this.shadowRoot || this,
276
+ })
277
+
278
+ // Watch for dark mode changes on body
279
+ this._darkModeObserver = new MutationObserver((mutations) => {
280
+ for (const mutation of mutations) {
281
+ if (mutation.attributeName === 'class') {
282
+ this.updateTheme()
283
+ }
284
+ }
285
+ })
286
+ this._darkModeObserver.observe(document.body, { attributes: true })
287
+ }
288
+
289
+ disconnectedCallback() {
290
+ super.disconnectedCallback()
291
+ this._darkModeObserver?.disconnect()
292
+ }
293
+
294
+ onResize() {
295
+ if (this._editor) this._editor.requestMeasure()
296
+ }
297
+
298
+ render(): void {
299
+ super.render()
300
+
301
+ // Update language and readonly state when properties change
302
+ if (this._editor) {
303
+ this._editor.dispatch({
304
+ effects: [
305
+ this.languageCompartment.reconfigure(
306
+ getLanguageExtension(this.mode, this._autocompleteConfig)
307
+ ),
308
+ this.readonlyCompartment.reconfigure(
309
+ EditorState.readOnly.of(this.disabled)
310
+ ),
311
+ ],
312
+ })
313
+ }
314
+ }
315
+
316
+ // Focus the editor
317
+ focus() {
318
+ this._editor?.focus()
319
+ }
320
+
321
+ // Insert text at cursor
322
+ insert(text: string) {
323
+ if (!this._editor) return
324
+ const { from } = this._editor.state.selection.main
325
+ this._editor.dispatch({
326
+ changes: { from, insert: text },
327
+ })
328
+ }
329
+
330
+ // Get selected text
331
+ getSelection(): string {
332
+ if (!this._editor) return ''
333
+ const { from, to } = this._editor.state.selection.main
334
+ return this._editor.state.doc.sliceString(from, to)
335
+ }
336
+
337
+ // Replace selection
338
+ replaceSelection(text: string) {
339
+ if (!this._editor) return
340
+ const { from, to } = this._editor.state.selection.main
341
+ this._editor.dispatch({
342
+ changes: { from, to, insert: text },
343
+ })
344
+ }
345
+
346
+ // Go to a specific line (1-indexed) and optionally column
347
+ goToLine(line: number, column: number = 1) {
348
+ if (!this._editor) return
349
+ const doc = this._editor.state.doc
350
+ // Clamp line to valid range
351
+ const lineNum = Math.max(1, Math.min(line, doc.lines))
352
+ const lineInfo = doc.line(lineNum)
353
+ // Calculate position: start of line + column offset (clamped)
354
+ const col = Math.max(1, Math.min(column, lineInfo.length + 1))
355
+ const pos = lineInfo.from + col - 1
356
+
357
+ this._editor.dispatch({
358
+ selection: { anchor: pos },
359
+ scrollIntoView: true,
360
+ })
361
+ this._editor.focus()
362
+ }
363
+
364
+ // Set diagnostics (errors/warnings) to show in the gutter
365
+ // Each marker has: line (1-indexed), message, severity ('error' | 'warning' | 'info')
366
+ setMarkers(
367
+ markers: Array<{
368
+ line: number
369
+ message: string
370
+ severity?: 'error' | 'warning' | 'info'
371
+ }>
372
+ ) {
373
+ if (!this._editor) return
374
+ const doc = this._editor.state.doc
375
+
376
+ const diagnostics: Diagnostic[] = markers
377
+ .filter((m) => m.line >= 1 && m.line <= doc.lines)
378
+ .map((m) => {
379
+ const lineInfo = doc.line(m.line)
380
+ return {
381
+ from: lineInfo.from,
382
+ to: lineInfo.to,
383
+ severity: m.severity || 'error',
384
+ message: m.message,
385
+ }
386
+ })
387
+
388
+ this._editor.dispatch(setDiagnostics(this._editor.state, diagnostics))
389
+ }
390
+
391
+ // Clear all diagnostics
392
+ clearMarkers() {
393
+ if (!this._editor) return
394
+ this._editor.dispatch(setDiagnostics(this._editor.state, []))
395
+ }
396
+ }
397
+
398
+ // Element creator for tosijs - shadow DOM for proper CodeMirror style encapsulation
399
+ // CodeMirror's StyleModule.mount needs to inject styles into the shadow root
400
+ export const codeMirror: ElementCreator<CodeMirror> = CodeMirror.elementCreator(
401
+ {
402
+ tag: 'code-mirror',
403
+ }
404
+ )
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Monaco Editor Monarch Tokenizer for AsyncJS
3
+ *
4
+ * Usage in browser:
5
+ * ```html
6
+ * <script src="https://unpkg.com/monaco-editor/min/vs/loader.js"></script>
7
+ * <script src="ajs-monarch.js"></script>
8
+ * <script>
9
+ * require(['vs/editor/editor.main'], function() {
10
+ * registerAjsLanguage(monaco);
11
+ * monaco.editor.create(document.getElementById('container'), {
12
+ * value: 'function agent(topic: "string") { ... }',
13
+ * language: 'ajs'
14
+ * });
15
+ * });
16
+ * </script>
17
+ * ```
18
+ */
19
+
20
+ const ajsLanguageId = 'ajs'
21
+
22
+ const ajsLanguageConfiguration = {
23
+ comments: {
24
+ lineComment: '//',
25
+ blockComment: ['/*', '*/'],
26
+ },
27
+ brackets: [
28
+ ['{', '}'],
29
+ ['[', ']'],
30
+ ['(', ')'],
31
+ ],
32
+ autoClosingPairs: [
33
+ { open: '{', close: '}' },
34
+ { open: '[', close: ']' },
35
+ { open: '(', close: ')' },
36
+ { open: "'", close: "'", notIn: ['string', 'comment'] },
37
+ { open: '"', close: '"', notIn: ['string', 'comment'] },
38
+ { open: '`', close: '`', notIn: ['string', 'comment'] },
39
+ ],
40
+ surroundingPairs: [
41
+ { open: '{', close: '}' },
42
+ { open: '[', close: ']' },
43
+ { open: '(', close: ')' },
44
+ { open: "'", close: "'" },
45
+ { open: '"', close: '"' },
46
+ { open: '`', close: '`' },
47
+ ],
48
+ }
49
+
50
+ const ajsMonarchLanguage = {
51
+ defaultToken: 'source',
52
+ ignoreCase: false,
53
+
54
+ keywords: [
55
+ 'function',
56
+ 'return',
57
+ 'if',
58
+ 'else',
59
+ 'while',
60
+ 'for',
61
+ 'of',
62
+ 'in',
63
+ 'try',
64
+ 'catch',
65
+ 'finally',
66
+ 'throw',
67
+ 'let',
68
+ 'const',
69
+ 'true',
70
+ 'false',
71
+ 'null',
72
+ ],
73
+
74
+ // Forbidden keywords - highlighted as errors
75
+ forbidden: [
76
+ 'new',
77
+ 'class',
78
+ 'async',
79
+ 'await',
80
+ 'var',
81
+ 'this',
82
+ 'super',
83
+ 'extends',
84
+ 'implements',
85
+ 'interface',
86
+ 'type',
87
+ 'yield',
88
+ 'import',
89
+ 'export',
90
+ 'require',
91
+ ],
92
+
93
+ typeKeywords: ['Date', 'Set', 'Map', 'Array', 'Object', 'String', 'Number'],
94
+
95
+ operators: [
96
+ '=',
97
+ '>',
98
+ '<',
99
+ '!',
100
+ '~',
101
+ '?',
102
+ ':',
103
+ '==',
104
+ '<=',
105
+ '>=',
106
+ '!=',
107
+ '&&',
108
+ '||',
109
+ '++',
110
+ '--',
111
+ '+',
112
+ '-',
113
+ '*',
114
+ '/',
115
+ '&',
116
+ '|',
117
+ '^',
118
+ '%',
119
+ ],
120
+
121
+ symbols: /[=><!~?:&|+\-*\/\^%]+/,
122
+ escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4})/,
123
+
124
+ tokenizer: {
125
+ root: [
126
+ [
127
+ /[a-zA-Z_$][\w$]*/,
128
+ {
129
+ cases: {
130
+ '@forbidden': 'invalid',
131
+ '@keywords': 'keyword',
132
+ '@typeKeywords': 'type.identifier',
133
+ '@default': 'identifier',
134
+ },
135
+ },
136
+ ],
137
+
138
+ { include: '@whitespace' },
139
+
140
+ [/[{}()\[\]]/, '@brackets'],
141
+ [
142
+ /@symbols/,
143
+ {
144
+ cases: {
145
+ '@operators': 'operator',
146
+ '@default': '',
147
+ },
148
+ },
149
+ ],
150
+
151
+ [/\d*\.\d+([eE][\-+]?\d+)?/, 'number.float'],
152
+ [/0[xX][0-9a-fA-F]+/, 'number.hex'],
153
+ [/\d+/, 'number'],
154
+
155
+ [/[;,.]/, 'delimiter'],
156
+
157
+ [/'/, { token: 'string.quote', bracket: '@open', next: '@stringSingle' }],
158
+ [/"/, { token: 'string.quote', bracket: '@open', next: '@stringDouble' }],
159
+ [
160
+ /`/,
161
+ { token: 'string.quote', bracket: '@open', next: '@stringBacktick' },
162
+ ],
163
+ ],
164
+
165
+ stringSingle: [
166
+ [/[^\\']+/, 'string'],
167
+ [/@escapes/, 'string.escape'],
168
+ [/\\./, 'string.escape.invalid'],
169
+ [/'/, { token: 'string.quote', bracket: '@close', next: '@pop' }],
170
+ ],
171
+
172
+ stringDouble: [
173
+ [/[^\\"]+/, 'string'],
174
+ [/@escapes/, 'string.escape'],
175
+ [/\\./, 'string.escape.invalid'],
176
+ [/"/, { token: 'string.quote', bracket: '@close', next: '@pop' }],
177
+ ],
178
+
179
+ stringBacktick: [
180
+ [/[^\\`$]+/, 'string'],
181
+ [/@escapes/, 'string.escape'],
182
+ [/\\./, 'string.escape.invalid'],
183
+ [
184
+ /\$\{/,
185
+ { token: 'delimiter.bracket', next: '@stringTemplateExpression' },
186
+ ],
187
+ [/`/, { token: 'string.quote', bracket: '@close', next: '@pop' }],
188
+ ],
189
+
190
+ stringTemplateExpression: [
191
+ [/[^}]+/, 'identifier'],
192
+ [/\}/, { token: 'delimiter.bracket', next: '@pop' }],
193
+ ],
194
+
195
+ whitespace: [
196
+ [/[ \t\r\n]+/, 'white'],
197
+ [/\/\*\*(?!\/)/, 'comment.doc', '@docComment'],
198
+ [/\/\*/, 'comment', '@comment'],
199
+ [/\/\/.*$/, 'comment'],
200
+ ],
201
+
202
+ comment: [
203
+ [/[^\/*]+/, 'comment'],
204
+ [/\*\//, 'comment', '@pop'],
205
+ [/[\/*]/, 'comment'],
206
+ ],
207
+
208
+ docComment: [
209
+ [/@\w+/, 'comment.doc.tag'],
210
+ [/[^\/*]+/, 'comment.doc'],
211
+ [/\*\//, 'comment.doc', '@pop'],
212
+ [/[\/*]/, 'comment.doc'],
213
+ ],
214
+ },
215
+ }
216
+
217
+ /**
218
+ * Register AsyncJS language with Monaco editor
219
+ * @param {typeof import('monaco-editor')} monaco
220
+ */
221
+ function registerAjsLanguage(monaco) {
222
+ monaco.languages.register({ id: ajsLanguageId })
223
+ monaco.languages.setLanguageConfiguration(
224
+ ajsLanguageId,
225
+ ajsLanguageConfiguration
226
+ )
227
+ monaco.languages.setMonarchTokensProvider(ajsLanguageId, ajsMonarchLanguage)
228
+ }
229
+
230
+ // Export for different module systems
231
+ if (typeof module !== 'undefined' && module.exports) {
232
+ module.exports = {
233
+ languageId: ajsLanguageId,
234
+ languageConfiguration: ajsLanguageConfiguration,
235
+ monarchLanguage: ajsMonarchLanguage,
236
+ registerAjsLanguage,
237
+ }
238
+ }
239
+
240
+ if (typeof window !== 'undefined') {
241
+ window.registerAjsLanguage = registerAjsLanguage
242
+ window.ajsMonarchLanguage = ajsMonarchLanguage
243
+ }