@airalogy/aimd-editor 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +59 -0
  2. package/README.zh-CN.md +43 -0
  3. package/dist/AimdEditorTopBar.vue_vue_type_script_setup_true_lang-gbfMDZSh.js +1131 -0
  4. package/dist/AimdSourceEditor.vue_vue_type_script_setup_true_lang-t_sUoXky.js +274 -0
  5. package/dist/AimdWysiwygEditor.vue_vue_type_script_setup_true_lang-B8o1VbUH.js +25012 -0
  6. package/dist/aimd-editor.css +1 -0
  7. package/dist/embedded.js +11 -0
  8. package/dist/index.js +44 -0
  9. package/dist/monaco.js +16 -0
  10. package/dist/theme-B8dCnOx-.js +583 -0
  11. package/dist/vue.js +30 -0
  12. package/dist/wysiwyg.js +9 -0
  13. package/package.json +90 -0
  14. package/src/__tests__/editor.test.ts +296 -0
  15. package/src/embedded.ts +18 -0
  16. package/src/index.ts +10 -0
  17. package/src/language-config.ts +152 -0
  18. package/src/monaco.ts +19 -0
  19. package/src/theme.ts +166 -0
  20. package/src/tokens.ts +120 -0
  21. package/src/vue/AimdEditor.vue +715 -0
  22. package/src/vue/AimdEditorToolbar.vue +83 -0
  23. package/src/vue/AimdEditorTopBar.vue +39 -0
  24. package/src/vue/AimdFieldDialog.vue +1102 -0
  25. package/src/vue/AimdSourceEditor.vue +330 -0
  26. package/src/vue/AimdWysiwygEditor.vue +569 -0
  27. package/src/vue/aimdInlineMarkdownNormalization.ts +10 -0
  28. package/src/vue/comparableAimdMarkdown.ts +6 -0
  29. package/src/vue/env.d.ts +7 -0
  30. package/src/vue/index.ts +45 -0
  31. package/src/vue/locales.ts +667 -0
  32. package/src/vue/milkdown-aimd-plugin.ts +378 -0
  33. package/src/vue/programmaticMarkdownSyncGuard.ts +66 -0
  34. package/src/vue/types.ts +449 -0
  35. package/src/vue/useEditorContent.ts +252 -0
  36. package/src/wysiwyg.ts +17 -0
package/dist/vue.js ADDED
@@ -0,0 +1,30 @@
1
+ import { _ as e, a as d, b as s, c as r, u as o } from "./AimdEditorTopBar.vue_vue_type_script_setup_true_lang-gbfMDZSh.js";
2
+ import { _ as m } from "./AimdSourceEditor.vue_vue_type_script_setup_true_lang-t_sUoXky.js";
3
+ import { A as E, a as l, _ as I, D as _, M as T, b as D, c as F, d as M, e as u, f as c, g as n, h as g, i as L, j as O, k as p, l as S, m as y, n as b, r as f } from "./AimdWysiwygEditor.vue_vue_type_script_setup_true_lang-B8o1VbUH.js";
4
+ export {
5
+ E as AIMD_FIELD_TYPES,
6
+ l as AIMD_FIELD_TYPE_DEFINITIONS,
7
+ e as AimdEditor,
8
+ d as AimdEditorToolbar,
9
+ s as AimdEditorTopBar,
10
+ r as AimdFieldDialog,
11
+ m as AimdSourceEditor,
12
+ I as AimdWysiwygEditor,
13
+ _ as DEFAULT_AIMD_EDITOR_LOCALE,
14
+ T as MD_TOOLBAR_ITEMS,
15
+ D as MD_TOOLBAR_ITEM_DEFINITIONS,
16
+ F as aimdFieldInputRule,
17
+ M as aimdFieldNode,
18
+ u as aimdFieldView,
19
+ c as aimdMilkdownPlugins,
20
+ n as aimdRemarkPlugin,
21
+ g as buildAimdSyntax,
22
+ L as createAimdEditorMessages,
23
+ O as createAimdFieldTypes,
24
+ p as createAimdVarTypePresets,
25
+ S as createMdToolbarItems,
26
+ y as getDefaultAimdFields,
27
+ b as getQuickAimdSyntax,
28
+ f as resolveAimdEditorLocale,
29
+ o as useEditorContent
30
+ };
@@ -0,0 +1,9 @@
1
+ import { _ as a, D as r, i, j as d, k as t, r as A } from "./AimdWysiwygEditor.vue_vue_type_script_setup_true_lang-B8o1VbUH.js";
2
+ export {
3
+ a as AimdWysiwygEditor,
4
+ r as DEFAULT_AIMD_EDITOR_LOCALE,
5
+ i as createAimdEditorMessages,
6
+ d as createAimdFieldTypes,
7
+ t as createAimdVarTypePresets,
8
+ A as resolveAimdEditorLocale
9
+ };
package/package.json ADDED
@@ -0,0 +1,90 @@
1
+ {
2
+ "name": "@airalogy/aimd-editor",
3
+ "type": "module",
4
+ "version": "1.7.1",
5
+ "description": "AIMD editor with Monaco source mode and Milkdown WYSIWYG mode",
6
+ "license": "Apache-2.0",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/airalogy/aimd.git",
10
+ "directory": "packages/aimd-editor"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/airalogy/aimd/issues"
14
+ },
15
+ "homepage": "https://github.com/airalogy/aimd/tree/main/packages/aimd-editor#readme",
16
+ "keywords": [
17
+ "aimd",
18
+ "monaco",
19
+ "milkdown",
20
+ "editor",
21
+ "wysiwyg",
22
+ "markdown",
23
+ "vue"
24
+ ],
25
+ "exports": {
26
+ ".": {
27
+ "types": "./src/index.ts",
28
+ "import": "./src/index.ts"
29
+ },
30
+ "./monaco": {
31
+ "types": "./src/monaco.ts",
32
+ "import": "./src/monaco.ts"
33
+ },
34
+ "./vue": {
35
+ "types": "./src/vue/index.ts",
36
+ "import": "./src/vue/index.ts"
37
+ },
38
+ "./embedded": {
39
+ "types": "./src/embedded.ts",
40
+ "import": "./src/embedded.ts"
41
+ },
42
+ "./wysiwyg": {
43
+ "types": "./src/wysiwyg.ts",
44
+ "import": "./src/wysiwyg.ts"
45
+ }
46
+ },
47
+ "main": "./src/index.ts",
48
+ "types": "./src/index.ts",
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "files": [
53
+ "dist",
54
+ "src"
55
+ ],
56
+ "scripts": {
57
+ "type-check": "tsc --noEmit",
58
+ "build": "vite build",
59
+ "dev": "vite build --watch",
60
+ "test": "node --test ./tests/*.test.mjs"
61
+ },
62
+ "dependencies": {
63
+ "@airalogy/aimd-core": "workspace:^",
64
+ "@airalogy/aimd-renderer": "workspace:^",
65
+ "@codingame/monaco-vscode-editor-api": "^20.2.1",
66
+ "@codingame/monaco-vscode-standalone-languages": "^20.2.1",
67
+ "@milkdown/kit": "^7.18.0",
68
+ "@milkdown/theme-nord": "^7.18.0",
69
+ "@milkdown/vue": "^7.18.0"
70
+ },
71
+ "peerDependencies": {
72
+ "monaco-editor": ">=0.50.0",
73
+ "vue": ">=3.3.0"
74
+ },
75
+ "peerDependenciesMeta": {
76
+ "monaco-editor": {
77
+ "optional": true
78
+ }
79
+ },
80
+ "devDependencies": {
81
+ "@types/node": "^24.3.0",
82
+ "monaco-editor": "^0.52.2",
83
+ "shiki": "^2.3.2",
84
+ "typescript": "5.8.3",
85
+ "vite": "^7.1.3",
86
+ "vite-tsconfig-paths": "^5.1.4",
87
+ "vue": "^3.5.17",
88
+ "@vitejs/plugin-vue": "^6.0.1"
89
+ }
90
+ }
@@ -0,0 +1,296 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import {
4
+ AimdToken,
5
+ AimdTokenDefinition,
6
+ AimdSuffix,
7
+ MarkupDefinition,
8
+ KeywordDefinition,
9
+ DelimiterDefinition,
10
+ scopeName,
11
+ } from '../tokens'
12
+ import { aimdTheme, aimdTokenColors } from '../theme'
13
+ import {
14
+ getDefaultAimdFields,
15
+ buildAimdSyntax,
16
+ createAimdFieldTypes,
17
+ createMdToolbarItems,
18
+ createAimdVarTypePresets,
19
+ AIMD_FIELD_TYPE_DEFINITIONS,
20
+ MD_TOOLBAR_ITEM_DEFINITIONS,
21
+ } from '../vue/types'
22
+ import { resolveAimdEditorLocale, createAimdEditorMessages } from '../vue/locales'
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Token definitions
26
+ // ---------------------------------------------------------------------------
27
+
28
+ describe('AimdToken definitions', () => {
29
+ it('has markup tokens', () => {
30
+ expect(AimdToken).toHaveProperty('MARKUP_AIMD_VARIABLE')
31
+ expect(AimdToken).toHaveProperty('MARKUP_AIMD_STEP')
32
+ expect(AimdToken).toHaveProperty('MARKUP_AIMD_CHECKPOINT')
33
+ })
34
+
35
+ it('has keyword tokens', () => {
36
+ expect(AimdToken).toHaveProperty('KEYWORD_VARIABLE_AIMD')
37
+ expect(AimdToken).toHaveProperty('KEYWORD_STEP_AIMD')
38
+ })
39
+
40
+ it('has delimiter tokens', () => {
41
+ expect(AimdToken).toHaveProperty('DELIMITER_PIPE_AIMD')
42
+ expect(AimdToken).toHaveProperty('DELIMITER_COLON_AIMD')
43
+ })
44
+
45
+ it('token suffix is "aimd"', () => {
46
+ expect(AimdSuffix).toBe('aimd')
47
+ })
48
+
49
+ it('scope name follows TextMate convention', () => {
50
+ expect(scopeName).toBe('text.html.markdown.aimd')
51
+ })
52
+
53
+ it('MarkupDefinition has variable and step entries', () => {
54
+ expect(MarkupDefinition).toHaveProperty('MARKUP_AIMD_VARIABLE')
55
+ expect(MarkupDefinition).toHaveProperty('MARKUP_AIMD_STEP')
56
+ })
57
+
58
+ it('KeywordDefinition has entries', () => {
59
+ expect(Object.keys(KeywordDefinition).length).toBeGreaterThan(0)
60
+ })
61
+
62
+ it('DelimiterDefinition has entries', () => {
63
+ expect(Object.keys(DelimiterDefinition).length).toBeGreaterThan(0)
64
+ })
65
+
66
+ it('AimdTokenDefinition has type tokens', () => {
67
+ expect(AimdTokenDefinition).toHaveProperty('KEYWORD_CONTROL_AIMD')
68
+ expect(AimdTokenDefinition).toHaveProperty('VARIABLE_PARAMETER_AIMD')
69
+ })
70
+ })
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // Theme (no Monaco dependency)
74
+ // ---------------------------------------------------------------------------
75
+
76
+ describe('AIMD theme', () => {
77
+ it('aimdTheme has proper structure', () => {
78
+ expect(aimdTheme).toHaveProperty('name')
79
+ expect(aimdTheme).toHaveProperty('settings')
80
+ })
81
+
82
+ it('aimdTokenColors is an array', () => {
83
+ expect(Array.isArray(aimdTokenColors)).toBe(true)
84
+ expect(aimdTokenColors.length).toBeGreaterThan(0)
85
+ })
86
+
87
+ it('token colors have scope and settings', () => {
88
+ for (const color of aimdTokenColors) {
89
+ expect(color).toHaveProperty('scope')
90
+ expect(color).toHaveProperty('settings')
91
+ }
92
+ })
93
+ })
94
+
95
+ // ---------------------------------------------------------------------------
96
+ // getDefaultAimdFields
97
+ // ---------------------------------------------------------------------------
98
+
99
+ describe('getDefaultAimdFields', () => {
100
+ it('returns var defaults', () => {
101
+ const fields = getDefaultAimdFields('var')
102
+ expect(fields).toHaveProperty('name')
103
+ expect(fields).toHaveProperty('type')
104
+ expect(fields.type).toBe('str')
105
+ })
106
+
107
+ it('returns step defaults with level 1', () => {
108
+ const fields = getDefaultAimdFields('step')
109
+ expect(fields).toHaveProperty('name')
110
+ expect(fields).toHaveProperty('level')
111
+ expect(fields.level).toBe('1')
112
+ })
113
+
114
+ it('returns check defaults', () => {
115
+ const fields = getDefaultAimdFields('check')
116
+ expect(fields).toHaveProperty('name')
117
+ })
118
+
119
+ it('returns quiz defaults', () => {
120
+ const fields = getDefaultAimdFields('quiz')
121
+ expect(fields).toHaveProperty('id')
122
+ expect(fields).toHaveProperty('quizType')
123
+ expect(fields.quizType).toBe('choice')
124
+ })
125
+
126
+ it('returns var_table defaults', () => {
127
+ const fields = getDefaultAimdFields('var_table')
128
+ expect(fields).toHaveProperty('name')
129
+ expect(fields).toHaveProperty('subvars')
130
+ })
131
+
132
+ it('returns generic defaults for unknown type', () => {
133
+ const fields = getDefaultAimdFields('unknown')
134
+ expect(fields).toHaveProperty('name')
135
+ })
136
+ })
137
+
138
+ // ---------------------------------------------------------------------------
139
+ // buildAimdSyntax
140
+ // ---------------------------------------------------------------------------
141
+
142
+ describe('buildAimdSyntax', () => {
143
+ it('builds simple var syntax', () => {
144
+ const syntax = buildAimdSyntax('var', { name: 'temperature', type: 'float', default: '36.5', title: '' })
145
+ expect(syntax).toBe('{{var|temperature: float = 36.5}}')
146
+ })
147
+
148
+ it('builds var with title', () => {
149
+ const syntax = buildAimdSyntax('var', { name: 'temp', type: 'float', default: '', title: 'Temperature' })
150
+ expect(syntax).toContain('title = "Temperature"')
151
+ })
152
+
153
+ it('builds var without type or default', () => {
154
+ const syntax = buildAimdSyntax('var', { name: 'note', type: '', default: '', title: '' })
155
+ expect(syntax).toBe('{{var|note}}')
156
+ })
157
+
158
+ it('uses fallback name when empty', () => {
159
+ const syntax = buildAimdSyntax('var', { name: '', type: '', default: '', title: '' })
160
+ expect(syntax).toContain('my_var')
161
+ })
162
+
163
+ it('builds step syntax', () => {
164
+ const syntax = buildAimdSyntax('step', { name: 'wash_hands', level: '1' })
165
+ expect(syntax).toBe('{{step|wash_hands}}')
166
+ })
167
+
168
+ it('builds step with level > 1', () => {
169
+ const syntax = buildAimdSyntax('step', { name: 'substep', level: '2' })
170
+ expect(syntax).toBe('{{step|substep, 2}}')
171
+ })
172
+
173
+ it('builds var_table syntax', () => {
174
+ const syntax = buildAimdSyntax('var_table', { name: 'measurements', subvars: 'temp, pressure' })
175
+ expect(syntax).toContain('var_table|measurements')
176
+ expect(syntax).toContain('subvars=[temp, pressure]')
177
+ })
178
+
179
+ it('builds check syntax', () => {
180
+ const syntax = buildAimdSyntax('check', { name: 'verify_result' })
181
+ expect(syntax).toBe('{{check|verify_result}}')
182
+ })
183
+
184
+ it('builds ref_step syntax', () => {
185
+ const syntax = buildAimdSyntax('ref_step', { name: 'step1' })
186
+ expect(syntax).toContain('ref_step')
187
+ expect(syntax).toContain('step1')
188
+ })
189
+
190
+ it('builds quiz syntax', () => {
191
+ const syntax = buildAimdSyntax('quiz', {
192
+ id: 'q1',
193
+ quizType: 'choice',
194
+ mode: 'single',
195
+ stem: 'What is 1+1?',
196
+ options: 'A:One, B:Two',
197
+ answer: 'B',
198
+ blanks: '',
199
+ rubric: '',
200
+ score: '',
201
+ })
202
+ expect(syntax).toContain('```quiz')
203
+ expect(syntax).toContain('id: q1')
204
+ expect(syntax).toContain('type: choice')
205
+ expect(syntax).toContain('mode: single')
206
+ expect(syntax).toContain('What is 1+1?')
207
+ })
208
+ })
209
+
210
+ // ---------------------------------------------------------------------------
211
+ // Locale helpers
212
+ // ---------------------------------------------------------------------------
213
+
214
+ describe('resolveAimdEditorLocale', () => {
215
+ it('defaults to en-US', () => {
216
+ expect(resolveAimdEditorLocale()).toBe('en-US')
217
+ })
218
+
219
+ it('resolves zh to zh-CN', () => {
220
+ expect(resolveAimdEditorLocale('zh')).toBe('zh-CN')
221
+ expect(resolveAimdEditorLocale('zh-CN')).toBe('zh-CN')
222
+ })
223
+
224
+ it('resolves en to en-US', () => {
225
+ expect(resolveAimdEditorLocale('en')).toBe('en-US')
226
+ })
227
+ })
228
+
229
+ describe('createAimdEditorMessages', () => {
230
+ it('creates messages with expected sections', () => {
231
+ const messages = createAimdEditorMessages('en-US')
232
+ expect(messages).toHaveProperty('defaults')
233
+ expect(messages).toHaveProperty('fieldTypes')
234
+ expect(messages).toHaveProperty('mdToolbar')
235
+ })
236
+
237
+ it('merges custom overrides', () => {
238
+ const messages = createAimdEditorMessages('en-US', {
239
+ defaults: { questionStem: 'Custom stem' },
240
+ })
241
+ expect(messages.defaults.questionStem).toBe('Custom stem')
242
+ })
243
+ })
244
+
245
+ // ---------------------------------------------------------------------------
246
+ // Field type and toolbar definitions
247
+ // ---------------------------------------------------------------------------
248
+
249
+ describe('AIMD field type definitions', () => {
250
+ it('has expected field types', () => {
251
+ const types = AIMD_FIELD_TYPE_DEFINITIONS.map(d => d.type)
252
+ expect(types).toContain('var')
253
+ expect(types).toContain('step')
254
+ expect(types).toContain('check')
255
+ expect(types).toContain('quiz')
256
+ })
257
+
258
+ it('createAimdFieldTypes localizes definitions', () => {
259
+ const messages = createAimdEditorMessages('en-US')
260
+ const types = createAimdFieldTypes(messages)
261
+ expect(types.length).toBe(AIMD_FIELD_TYPE_DEFINITIONS.length)
262
+ expect(types[0]).toHaveProperty('label')
263
+ })
264
+ })
265
+
266
+ describe('MD toolbar item definitions', () => {
267
+ it('has expected toolbar actions', () => {
268
+ const actions = MD_TOOLBAR_ITEM_DEFINITIONS.map(d => d.action)
269
+ expect(actions).toContain('h1')
270
+ expect(actions).toContain('bold')
271
+ })
272
+
273
+ it('createMdToolbarItems localizes items', () => {
274
+ const messages = createAimdEditorMessages('en-US')
275
+ const items = createMdToolbarItems(messages)
276
+ expect(items.length).toBe(MD_TOOLBAR_ITEM_DEFINITIONS.length)
277
+ const nonSeparator = items.find(i => !i.action.startsWith('sep'))
278
+ expect(nonSeparator).toHaveProperty('title')
279
+ })
280
+ })
281
+
282
+ describe('AIMD var type presets', () => {
283
+ it('includes built-in recorder-aware code string presets', () => {
284
+ const messages = createAimdEditorMessages('en-US')
285
+ const presets = createAimdVarTypePresets(messages)
286
+ const presetValues = presets.map(preset => preset.value)
287
+
288
+ expect(presetValues).toContain('CodeStr')
289
+ expect(presetValues).toContain('PyStr')
290
+ expect(presetValues).toContain('JsStr')
291
+ expect(presetValues).toContain('TsStr')
292
+ expect(presetValues).toContain('JsonStr')
293
+ expect(presetValues).toContain('TomlStr')
294
+ expect(presetValues).toContain('YamlStr')
295
+ })
296
+ })
@@ -0,0 +1,18 @@
1
+ export { default as AimdSourceEditor } from './vue/AimdSourceEditor.vue'
2
+ export { default as AimdWysiwygEditor } from './vue/AimdWysiwygEditor.vue'
3
+ export {
4
+ createAimdEditorMessages,
5
+ DEFAULT_AIMD_EDITOR_LOCALE,
6
+ resolveAimdEditorLocale,
7
+ } from './vue/locales'
8
+ export {
9
+ createAimdFieldTypes,
10
+ createAimdVarTypePresets,
11
+ } from './vue/types'
12
+ export type {
13
+ AimdFieldType,
14
+ AimdVarTypePresetOption,
15
+ AimdEditorMessages,
16
+ AimdEditorMessagesInput,
17
+ AimdEditorLocale,
18
+ } from './vue/index'
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ /// <reference path="./vue/env.d.ts" />
2
+
3
+ /**
4
+ * @airalogy/aimd-editor
5
+ *
6
+ * Root entry that re-exports Monaco integration and Vue editor APIs.
7
+ */
8
+
9
+ export * from './monaco'
10
+ export * from './vue/index'
@@ -0,0 +1,152 @@
1
+ import { languages } from "@codingame/monaco-vscode-editor-api"
2
+
3
+ // @ts-ignore - No type definitions available
4
+ import { type languages as languagesNS, conf as markdownConf, language as markdownLanguage } from "@codingame/monaco-vscode-standalone-languages/markdown/markdown"
5
+ import { AimdToken } from "./tokens"
6
+
7
+ const autoClosingPairs: languages.IAutoClosingPairConditional[] = [
8
+ ...(markdownConf.autoClosingPairs as any[] || []),
9
+ ]
10
+ const surroundingPairs = autoClosingPairs
11
+
12
+ function getTokens(tokens: string, divider = "|"): string[] {
13
+ return tokens.split(divider)
14
+ }
15
+ const keywords: string[] = getTokens("var|var_table|check|step|ref_step|ref_var|ref_table|table_link")
16
+
17
+ export const language: languagesNS.IMonarchLanguage = {
18
+ ...markdownLanguage,
19
+ tokenPostfix: ".aimd",
20
+ keywords,
21
+ tokenizer: {
22
+ ...markdownLanguage.tokenizer!,
23
+ root: [
24
+ [/^\s*(```|~~~)\s*quiz(?:\s+.*)?\s*$/, {
25
+ token: "string",
26
+ next: "@quizCodeblock",
27
+ nextEmbedded: "yaml",
28
+ }],
29
+ [/^\s*(```|~~~)\s*assigner(?:\s+.*\bruntime\s*=\s*(?:"client"|'client'|client)\b.*)\s*$/, {
30
+ token: "string",
31
+ next: "@assignerCodeblock",
32
+ nextEmbedded: "javascript",
33
+ }],
34
+ [/^\s*(```|~~~)\s*assigner(?:\s+.*)?\s*$/, {
35
+ token: "string",
36
+ next: "@assignerCodeblock",
37
+ nextEmbedded: "python",
38
+ }],
39
+ ...markdownLanguage.tokenizer.root,
40
+ { include: "@aimd" },
41
+ ],
42
+ table_body: [...markdownLanguage.tokenizer.table_body, { include: "@aimd" }],
43
+ assignerCodeblock: [
44
+ [/^\s*(```|~~~)\s*$/, { token: "string", next: "@pop", nextEmbedded: "@pop" }],
45
+ [/.*$/, ""],
46
+ ],
47
+ quizCodeblock: [
48
+ [/^\s*(```|~~~)\s*$/, { token: "string", next: "@pop", nextEmbedded: "@pop" }],
49
+ [/.*$/, ""],
50
+ ],
51
+ aimd: [
52
+ // 1. AIMD Protocol Fields: {{keyword|content}}
53
+ // This rule finds the opening '{{' and switches to the 'protocol' state
54
+ // to handle the special syntax. This has the highest priority.
55
+ [/\{\{/, {
56
+ token: AimdToken.PUNCTUATION_DEFINITION_BEGIN_AIMD,
57
+ bracket: "@open",
58
+ next: "@protocol",
59
+ }],
60
+
61
+ // Links: [text](url)
62
+ [/\[([^\]]+)\]\s*\(([^)]+)\)/, [
63
+ { token: AimdToken.METATAG_LINK_AIMD }, // [
64
+ { token: AimdToken.STRING_LINK_DESCRIPTION_AIMD }, // text
65
+ { token: AimdToken.METATAG_LINK_AIMD }, // ](
66
+ { token: AimdToken.STRING_LINK_URL_AIMD }, // url
67
+ { token: AimdToken.METATAG_LINK_AIMD }, // )
68
+ ]],
69
+
70
+ // Images: ![alt](src)
71
+ [/!\[([^\]]+)\]\s*\(([^)]+)\)/, [
72
+ { token: AimdToken.METATAG_IMAGE_AIMD }, // ![
73
+ { token: AimdToken.STRING_LINK_DESCRIPTION_AIMD }, // alt
74
+ { token: AimdToken.METATAG_IMAGE_AIMD }, // ](
75
+ { token: AimdToken.STRING_LINK_URL_AIMD }, // src
76
+ { token: AimdToken.METATAG_IMAGE_AIMD }, // )
77
+ ]],
78
+ ],
79
+ // This state is active inside {{ ... }}
80
+ protocol: [
81
+ // Match the keywords from your regexes
82
+ // [/(var_table|var|quiz|step|check|ref_step|ref_var|ref_fig|cite|fig)/, AimdToken.KEYWORD_CONTROL_AIMD],
83
+ [/var(\s*\|)/, AimdToken.KEYWORD_VARIABLE_AIMD],
84
+ [/var_table(\s*\|)/, AimdToken.KEYWORD_VARIABLE_TABLE_AIMD],
85
+ [/step(\s*\|)/, AimdToken.KEYWORD_STEP_AIMD],
86
+ [/check(\s*\|)/, AimdToken.KEYWORD_CHECKPOINT_AIMD],
87
+ [/ref_var(\s*\|)/, AimdToken.KEYWORD_REFERENCE_VARIABLE_AIMD],
88
+ [/ref_step(\s*\|)/, AimdToken.KEYWORD_REFERENCE_STEP_AIMD],
89
+ // Match the pipe delimiter
90
+ [/\|/, { token: AimdToken.DELIMITER_PIPE_AIMD, next: "@protocolContent" }],
91
+ // Match the closing '}}' and pop back to the root state
92
+ [/\}\}/, { token: AimdToken.PUNCTUATION_DEFINITION_END_AIMD, bracket: "@close", next: "@pop" }],
93
+ ],
94
+ // Content inside protocol after the pipe delimiter - supports type syntax
95
+ protocolContent: [
96
+ // Match the closing '}}' and pop back to the root state
97
+ [/\}\}/, { token: AimdToken.PUNCTUATION_DEFINITION_END_AIMD, bracket: "@close", next: "@popall" }],
98
+ // Match subvars keyword
99
+ [/\bsubvars\b/, AimdToken.KEYWORD_OTHER_SUBVARS_AIMD],
100
+ // Match var keyword (for nested var() calls in subvars)
101
+ [/\bvar\b/, AimdToken.KEYWORD_VARIABLE_AIMD],
102
+ // Match type annotations after colon (e.g., : str, : int, : list[Student])
103
+ [/:/, { token: AimdToken.DELIMITER_COLON_AIMD, next: "@typeAnnotation" }],
104
+ // Match string literals (double or single quoted)
105
+ [/"([^"\\]|\\.)*"/, AimdToken.STRING_QUOTED_AIMD],
106
+ [/'([^'\\]|\\.)*'/, AimdToken.STRING_QUOTED_AIMD],
107
+ // Match numbers (integer and float)
108
+ [/-?\d+\.?\d*/, AimdToken.CONSTANT_NUMERIC_AIMD],
109
+ // Match boolean/null literals
110
+ [/\b(true|false|True|False|null|None)\b/, AimdToken.CONSTANT_LANGUAGE_AIMD],
111
+ // Match equals signs and commas used for parameters
112
+ [/=/, AimdToken.DELIMITER_PARAMETER_AIMD],
113
+ [/,/, AimdToken.DELIMITER_PARAMETER_AIMD],
114
+ // Match brackets for subvars and type params
115
+ [/[[\]()]/, AimdToken.DELIMITER_BRACKET_AIMD],
116
+ // Match variable name (identifier at start)
117
+ [/\b\w+\b/, AimdToken.VARIABLE_OTHER_AIMD],
118
+ // Skip whitespace
119
+ [/\s+/, ""],
120
+ ],
121
+ // Type annotation state (after colon)
122
+ typeAnnotation: [
123
+ // Match type with generic params like list[Student]
124
+ [/\w+(?:\[[\w,\s]+\])?/, { token: AimdToken.SUPPORT_TYPE_AIMD, next: "@pop" }],
125
+ // Skip whitespace
126
+ [/\s+/, ""],
127
+ // Pop on other characters
128
+ [/./, { token: "@rematch", next: "@pop" }],
129
+ ],
130
+ },
131
+ }
132
+
133
+ export const conf: languagesNS.LanguageConfiguration = {
134
+ ...markdownConf,
135
+ autoClosingPairs,
136
+ surroundingPairs,
137
+ }
138
+
139
+ export const completionItemProvider: languagesNS.CompletionItemProvider = {
140
+ provideCompletionItems: (doc: any, position: any) => {
141
+ const suggestions = keywords.map((value) => {
142
+ return {
143
+ label: value,
144
+ kind: languages.CompletionItemKind.Keyword,
145
+ insertText: value,
146
+ insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet,
147
+ } as languagesNS.CompletionItem
148
+ })
149
+
150
+ return { suggestions } as languagesNS.ProviderResult<languagesNS.CompletionList>
151
+ },
152
+ }
package/src/monaco.ts ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @airalogy/aimd-editor/monaco
3
+ *
4
+ * AIMD Monaco editor integration
5
+ *
6
+ * This entry provides language config and themes for AIMD.
7
+ */
8
+
9
+ export { language, conf, completionItemProvider } from './language-config'
10
+ export { aimdTokenColors, aimdTheme, createAimdExtendedTheme } from './theme'
11
+ export {
12
+ AimdToken,
13
+ AimdTokenDefinition,
14
+ AimdSuffix,
15
+ DelimiterDefinition,
16
+ KeywordDefinition,
17
+ MarkupDefinition,
18
+ scopeName,
19
+ } from './tokens'