@cratis/screenplay-language 0.1.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 (147) hide show
  1. package/README.md +52 -0
  2. package/completion-items.ts +128 -0
  3. package/completion-planner.ts +111 -0
  4. package/completions.ts +112 -0
  5. package/diagnostics.ts +52 -0
  6. package/dist/completion-items.d.ts +23 -0
  7. package/dist/completion-items.d.ts.map +1 -0
  8. package/dist/completion-items.js +105 -0
  9. package/dist/completion-items.js.map +1 -0
  10. package/dist/completion-planner.d.ts +25 -0
  11. package/dist/completion-planner.d.ts.map +1 -0
  12. package/dist/completion-planner.js +90 -0
  13. package/dist/completion-planner.js.map +1 -0
  14. package/dist/completions.d.ts +4 -0
  15. package/dist/completions.d.ts.map +1 -0
  16. package/dist/completions.js +78 -0
  17. package/dist/completions.js.map +1 -0
  18. package/dist/diagnostics.d.ts +5 -0
  19. package/dist/diagnostics.d.ts.map +1 -0
  20. package/dist/diagnostics.js +47 -0
  21. package/dist/diagnostics.js.map +1 -0
  22. package/dist/document-context.d.ts +5 -0
  23. package/dist/document-context.d.ts.map +1 -0
  24. package/dist/document-context.js +45 -0
  25. package/dist/document-context.js.map +1 -0
  26. package/dist/hover-content.d.ts +2 -0
  27. package/dist/hover-content.d.ts.map +1 -0
  28. package/dist/hover-content.js +64 -0
  29. package/dist/hover-content.js.map +1 -0
  30. package/dist/hover.d.ts +3 -0
  31. package/dist/hover.d.ts.map +1 -0
  32. package/dist/hover.js +15 -0
  33. package/dist/hover.js.map +1 -0
  34. package/dist/index.d.ts +23 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +54 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/keyword-docs.d.ts +4 -0
  39. package/dist/keyword-docs.d.ts.map +1 -0
  40. package/dist/keyword-docs.js +100 -0
  41. package/dist/keyword-docs.js.map +1 -0
  42. package/dist/language.d.ts +12 -0
  43. package/dist/language.d.ts.map +1 -0
  44. package/dist/language.js +117 -0
  45. package/dist/language.js.map +1 -0
  46. package/dist/sub-language-registry.d.ts +20 -0
  47. package/dist/sub-language-registry.d.ts.map +1 -0
  48. package/dist/sub-language-registry.js +18 -0
  49. package/dist/sub-language-registry.js.map +1 -0
  50. package/dist/sub-languages/capture/CompletionProvider.d.ts +32 -0
  51. package/dist/sub-languages/capture/CompletionProvider.d.ts.map +1 -0
  52. package/dist/sub-languages/capture/CompletionProvider.js +332 -0
  53. package/dist/sub-languages/capture/CompletionProvider.js.map +1 -0
  54. package/dist/sub-languages/capture/HoverProvider.d.ts +8 -0
  55. package/dist/sub-languages/capture/HoverProvider.d.ts.map +1 -0
  56. package/dist/sub-languages/capture/HoverProvider.js +87 -0
  57. package/dist/sub-languages/capture/HoverProvider.js.map +1 -0
  58. package/dist/sub-languages/capture/Validator.d.ts +6 -0
  59. package/dist/sub-languages/capture/Validator.d.ts.map +1 -0
  60. package/dist/sub-languages/capture/Validator.js +72 -0
  61. package/dist/sub-languages/capture/Validator.js.map +1 -0
  62. package/dist/sub-languages/capture/index.d.ts +6 -0
  63. package/dist/sub-languages/capture/index.d.ts.map +1 -0
  64. package/dist/sub-languages/capture/index.js +58 -0
  65. package/dist/sub-languages/capture/index.js.map +1 -0
  66. package/dist/sub-languages/capture/language.d.ts +6 -0
  67. package/dist/sub-languages/capture/language.d.ts.map +1 -0
  68. package/dist/sub-languages/capture/language.js +160 -0
  69. package/dist/sub-languages/capture/language.js.map +1 -0
  70. package/dist/sub-languages/cdl.d.ts +3 -0
  71. package/dist/sub-languages/cdl.d.ts.map +1 -0
  72. package/dist/sub-languages/cdl.js +74 -0
  73. package/dist/sub-languages/cdl.js.map +1 -0
  74. package/dist/sub-languages/pdl.d.ts +3 -0
  75. package/dist/sub-languages/pdl.d.ts.map +1 -0
  76. package/dist/sub-languages/pdl.js +67 -0
  77. package/dist/sub-languages/pdl.js.map +1 -0
  78. package/dist/sub-languages/projection/CodeActionProvider.d.ts +18 -0
  79. package/dist/sub-languages/projection/CodeActionProvider.d.ts.map +1 -0
  80. package/dist/sub-languages/projection/CodeActionProvider.js +93 -0
  81. package/dist/sub-languages/projection/CodeActionProvider.js.map +1 -0
  82. package/dist/sub-languages/projection/CompletionProvider.d.ts +41 -0
  83. package/dist/sub-languages/projection/CompletionProvider.d.ts.map +1 -0
  84. package/dist/sub-languages/projection/CompletionProvider.js +591 -0
  85. package/dist/sub-languages/projection/CompletionProvider.js.map +1 -0
  86. package/dist/sub-languages/projection/HoverProvider.d.ts +24 -0
  87. package/dist/sub-languages/projection/HoverProvider.d.ts.map +1 -0
  88. package/dist/sub-languages/projection/HoverProvider.js +298 -0
  89. package/dist/sub-languages/projection/HoverProvider.js.map +1 -0
  90. package/dist/sub-languages/projection/Validator.d.ts +22 -0
  91. package/dist/sub-languages/projection/Validator.d.ts.map +1 -0
  92. package/dist/sub-languages/projection/Validator.js +339 -0
  93. package/dist/sub-languages/projection/Validator.js.map +1 -0
  94. package/dist/sub-languages/projection/index.d.ts +49 -0
  95. package/dist/sub-languages/projection/index.d.ts.map +1 -0
  96. package/dist/sub-languages/projection/index.js +160 -0
  97. package/dist/sub-languages/projection/index.js.map +1 -0
  98. package/dist/sub-languages/projection/language.d.ts +5 -0
  99. package/dist/sub-languages/projection/language.d.ts.map +1 -0
  100. package/dist/sub-languages/projection/language.js +171 -0
  101. package/dist/sub-languages/projection/language.js.map +1 -0
  102. package/dist/symbols.d.ts +45 -0
  103. package/dist/symbols.d.ts.map +1 -0
  104. package/dist/symbols.js +100 -0
  105. package/dist/symbols.js.map +1 -0
  106. package/dist/themes/screenplay-dark.d.ts +4 -0
  107. package/dist/themes/screenplay-dark.d.ts.map +1 -0
  108. package/dist/themes/screenplay-dark.js +29 -0
  109. package/dist/themes/screenplay-dark.js.map +1 -0
  110. package/dist/themes/screenplay-light.d.ts +4 -0
  111. package/dist/themes/screenplay-light.d.ts.map +1 -0
  112. package/dist/themes/screenplay-light.js +29 -0
  113. package/dist/themes/screenplay-light.js.map +1 -0
  114. package/dist/tokens.d.ts +5 -0
  115. package/dist/tokens.d.ts.map +1 -0
  116. package/dist/tokens.js +121 -0
  117. package/dist/tokens.js.map +1 -0
  118. package/dist/validation.d.ts +10 -0
  119. package/dist/validation.d.ts.map +1 -0
  120. package/dist/validation.js +101 -0
  121. package/dist/validation.js.map +1 -0
  122. package/document-context.ts +51 -0
  123. package/hover-content.ts +76 -0
  124. package/hover.ts +23 -0
  125. package/index.ts +98 -0
  126. package/keyword-docs.ts +102 -0
  127. package/language.ts +130 -0
  128. package/package.json +44 -0
  129. package/sub-language-registry.ts +42 -0
  130. package/sub-languages/capture/CompletionProvider.ts +365 -0
  131. package/sub-languages/capture/HoverProvider.ts +99 -0
  132. package/sub-languages/capture/Validator.ts +97 -0
  133. package/sub-languages/capture/index.ts +72 -0
  134. package/sub-languages/capture/language.ts +176 -0
  135. package/sub-languages/cdl.ts +76 -0
  136. package/sub-languages/pdl.ts +69 -0
  137. package/sub-languages/projection/CodeActionProvider.ts +123 -0
  138. package/sub-languages/projection/CompletionProvider.ts +653 -0
  139. package/sub-languages/projection/HoverProvider.ts +338 -0
  140. package/sub-languages/projection/Validator.ts +387 -0
  141. package/sub-languages/projection/index.ts +223 -0
  142. package/sub-languages/projection/language.ts +193 -0
  143. package/symbols.ts +161 -0
  144. package/themes/screenplay-dark.ts +32 -0
  145. package/themes/screenplay-light.ts +32 -0
  146. package/tokens.ts +145 -0
  147. package/validation.ts +145 -0
@@ -0,0 +1,193 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import type { languages } from 'monaco-editor';
5
+
6
+ export const languageId = 'pdl';
7
+
8
+ // Keywords for the Projection Declaration Language
9
+ const KEYWORDS = [
10
+ 'projection',
11
+ 'sequence',
12
+ 'all',
13
+ 'every',
14
+ 'on',
15
+ 'from',
16
+ 'key',
17
+ 'parent',
18
+ 'join',
19
+ 'automap',
20
+ 'no',
21
+ 'children',
22
+ 'nested',
23
+ 'identified',
24
+ 'by',
25
+ 'add',
26
+ 'subtract',
27
+ 'remove',
28
+ 'via',
29
+ 'with',
30
+ 'default',
31
+ 'increment',
32
+ 'decrement',
33
+ 'count',
34
+ 'set',
35
+ 'unset',
36
+ 'clear',
37
+ 'events',
38
+ 'id',
39
+ 'exclude',
40
+ ] as const;
41
+
42
+ // Built-in expressions and functions
43
+ const BUILTINS = [
44
+ '$eventSourceId',
45
+ '$causedBy',
46
+ '$occurred',
47
+ '$namespace',
48
+ '$eventContext',
49
+ ] as const;
50
+
51
+ // Operators
52
+ const OPERATORS = ['=', '+=', '-=', '=>', '.'];
53
+
54
+ export const configuration: languages.LanguageConfiguration = {
55
+ comments: {
56
+ lineComment: '#',
57
+ },
58
+ brackets: [
59
+ ['{', '}'],
60
+ ['[', ']'],
61
+ ['(', ')'],
62
+ ],
63
+ autoClosingPairs: [
64
+ { open: '{', close: '}' },
65
+ { open: '[', close: ']' },
66
+ { open: '(', close: ')' },
67
+ { open: '"', close: '"' },
68
+ { open: "'", close: "'" },
69
+ { open: '`', close: '`' },
70
+ ],
71
+ surroundingPairs: [
72
+ { open: '{', close: '}' },
73
+ { open: '[', close: ']' },
74
+ { open: '(', close: ')' },
75
+ { open: '"', close: '"' },
76
+ { open: "'", close: "'" },
77
+ { open: '`', close: '`' },
78
+ ],
79
+ indentationRules: {
80
+ increaseIndentPattern: /^.*(:|\bon\b|\ball\b|\bevery\b|\bchildren\b|\bnested\b|\bparent\b)\s*$/,
81
+ decreaseIndentPattern: /^.*\}.*$/,
82
+ },
83
+ folding: {
84
+ offSide: true,
85
+ markers: {
86
+ start: /^\s*(projection|all|every|on|children|nested|parent)/,
87
+ end: /^\s*$/,
88
+ },
89
+ },
90
+ wordPattern: /@?[a-zA-Z_$][\w$-]*/,
91
+ };
92
+
93
+ export const monarchLanguage: languages.IMonarchLanguage = {
94
+ defaultToken: '',
95
+ tokenPostfix: '.pdl',
96
+
97
+ keywords: KEYWORDS,
98
+ builtins: BUILTINS,
99
+ operators: OPERATORS,
100
+
101
+ // Symbols
102
+ symbols: /[=><!~?:&|+\-*/^%]+/,
103
+ escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
104
+
105
+ tokenizer: {
106
+ root: [
107
+ // Whitespace
108
+ { include: '@whitespace' },
109
+
110
+ // Template strings with ${...} interpolation
111
+ [/`/, { token: 'string.template', next: '@templateString' }],
112
+
113
+ // Escaped identifiers (@ prefix allows using keywords as identifiers)
114
+ [/@[a-zA-Z_$][\w$]*/, 'identifier.escape'],
115
+
116
+ // Check for uppercase keywords (error case - keywords must be lowercase)
117
+ [
118
+ /[A-Z][a-zA-Z_$][\w$]*/,
119
+ {
120
+ cases: {
121
+ '@keywords': 'invalid',
122
+ '@default': 'identifier',
123
+ },
124
+ },
125
+ ],
126
+
127
+ // Identifiers and keywords (lowercase only for keywords)
128
+ [
129
+ /[a-z_$][\w$]*/,
130
+ {
131
+ cases: {
132
+ '@keywords': 'keyword',
133
+ '@builtins': 'variable.predefined',
134
+ '@default': 'identifier',
135
+ },
136
+ },
137
+ ],
138
+
139
+ // Delimiters and operators
140
+ [/[{}()[\]]/, '@brackets'],
141
+ [/=>/, 'operator.arrow'],
142
+ [
143
+ /@symbols/,
144
+ {
145
+ cases: {
146
+ '@operators': 'operator',
147
+ '@default': '',
148
+ },
149
+ },
150
+ ],
151
+
152
+ // Numbers
153
+ [/\d*\.\d+([eE][-+]?\d+)?/, 'number.float'],
154
+ [/0[xX][0-9a-fA-F]+/, 'number.hex'],
155
+ [/\d+/, 'number'],
156
+
157
+ // Delimiter: after number because of .\d floats
158
+ [/[;,.]/, 'delimiter'],
159
+
160
+ // Strings
161
+ [/"([^"\\]|\\.)*$/, 'string.invalid'],
162
+ [/"/, { token: 'string.quote', next: '@string' }],
163
+ ],
164
+
165
+ whitespace: [
166
+ [/[ \t\r\n]+/, ''],
167
+ [/#.*$/, 'comment'],
168
+ ],
169
+
170
+ // Template string state
171
+ templateString: [
172
+ [/\$\{/, { token: 'delimiter.bracket', next: '@templateExpression' }],
173
+ [/[^\\`$]+/, 'string.template'],
174
+ [/@escapes/, 'string.template.escape'],
175
+ [/\\./, 'string.template.escape.invalid'],
176
+ [/`/, { token: 'string.template', next: '@pop' }],
177
+ ],
178
+
179
+ // Template expression inside ${}
180
+ templateExpression: [
181
+ [/\}/, { token: 'delimiter.bracket', next: '@pop' }],
182
+ { include: '@root' },
183
+ ],
184
+
185
+ // Regular string state
186
+ string: [
187
+ [/[^\\"]+/, 'string'],
188
+ [/@escapes/, 'string.escape'],
189
+ [/\\./, 'string.escape.invalid'],
190
+ [/"/, { token: 'string.quote', next: '@pop' }],
191
+ ],
192
+ },
193
+ };
package/symbols.ts ADDED
@@ -0,0 +1,161 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { fenceMap, indentOf } from './document-context';
5
+
6
+ export interface PropertySymbol {
7
+ name: string;
8
+ type: string;
9
+ }
10
+
11
+ export interface ConceptSymbol {
12
+ name: string;
13
+ primitive: string;
14
+ attributes: string[];
15
+ enumValues: string[];
16
+ line: number;
17
+ }
18
+
19
+ export interface PolicySymbol {
20
+ name: string;
21
+ requires: string[];
22
+ line: number;
23
+ }
24
+
25
+ export interface EventSymbol {
26
+ name: string;
27
+ properties: PropertySymbol[];
28
+ line: number;
29
+ }
30
+
31
+ export interface NamedSymbol {
32
+ name: string;
33
+ line: number;
34
+ }
35
+
36
+ export interface QuerySymbol extends NamedSymbol {
37
+ returnType: string;
38
+ }
39
+
40
+ export interface ImportSymbol {
41
+ qualifiedName: string;
42
+ shortName: string;
43
+ line: number;
44
+ }
45
+
46
+ export interface DocumentSymbols {
47
+ imports: ImportSymbol[];
48
+ concepts: ConceptSymbol[];
49
+ policies: PolicySymbol[];
50
+ events: EventSymbol[];
51
+ commands: NamedSymbol[];
52
+ queries: QuerySymbol[];
53
+ screens: NamedSymbol[];
54
+ }
55
+
56
+ const conceptPattern = /^concept\s+(\w+)\s*:\s*(\w+)((?:\s+@\w+)*)\s*$/;
57
+ const propertyPattern = /^\s*([a-z_]\w*)\s+([\w.]+(?:\[\])?\??)\s*$/;
58
+
59
+ function collectBody(lines: string[], fences: boolean[], start: number, indent: number): number[] {
60
+ const body: number[] = [];
61
+ for (let index = start + 1; index < lines.length; index++) {
62
+ if (fences[index]) {
63
+ body.push(index);
64
+ continue;
65
+ }
66
+ const line = lines[index];
67
+ if (line.trim().length === 0) continue;
68
+ if (indentOf(line) <= indent) break;
69
+ body.push(index);
70
+ }
71
+ return body;
72
+ }
73
+
74
+ export function scanDocument(lines: string[]): DocumentSymbols {
75
+ const symbols: DocumentSymbols = {
76
+ imports: [],
77
+ concepts: [],
78
+ policies: [],
79
+ events: [],
80
+ commands: [],
81
+ queries: [],
82
+ screens: [],
83
+ };
84
+ const fences = fenceMap(lines);
85
+
86
+ for (let index = 0; index < lines.length; index++) {
87
+ if (fences[index]) continue;
88
+ const line = lines[index];
89
+ const trimmed = line.trim();
90
+ const indent = indentOf(line);
91
+
92
+ const importMatch = trimmed.match(/^import\s+([\w.]+)\s*$/);
93
+ if (importMatch && indent === 0) {
94
+ const qualifiedName = importMatch[1];
95
+ const shortName = qualifiedName.split('.').pop() ?? qualifiedName;
96
+ symbols.imports.push({ qualifiedName, shortName, line: index });
97
+ continue;
98
+ }
99
+
100
+ const conceptMatch = trimmed.match(conceptPattern);
101
+ if (conceptMatch) {
102
+ const enumValues =
103
+ conceptMatch[2] === 'Enum'
104
+ ? collectBody(lines, fences, index, indent).map((i) => lines[i].trim())
105
+ : [];
106
+ symbols.concepts.push({
107
+ name: conceptMatch[1],
108
+ primitive: conceptMatch[2],
109
+ attributes: conceptMatch[3].trim().split(/\s+/).filter(Boolean),
110
+ enumValues,
111
+ line: index,
112
+ });
113
+ continue;
114
+ }
115
+
116
+ const policyMatch = trimmed.match(/^policy\s+(\w+)\s*$/);
117
+ if (policyMatch) {
118
+ const requires = collectBody(lines, fences, index, indent)
119
+ .filter((i) => !fences[i])
120
+ .map((i) => lines[i].trim());
121
+ symbols.policies.push({ name: policyMatch[1], requires, line: index });
122
+ continue;
123
+ }
124
+
125
+ const eventMatch = trimmed.match(/^event\s+(\w+)\s*$/);
126
+ if (eventMatch) {
127
+ const properties = collectBody(lines, fences, index, indent)
128
+ .map((i) => lines[i].match(propertyPattern))
129
+ .filter((match): match is RegExpMatchArray => match !== null)
130
+ .map((match) => ({ name: match[1], type: match[2] }));
131
+ symbols.events.push({ name: eventMatch[1], properties, line: index });
132
+ continue;
133
+ }
134
+
135
+ const commandMatch = trimmed.match(/^command\s+(\w+)\s*$/);
136
+ if (commandMatch) {
137
+ symbols.commands.push({ name: commandMatch[1], line: index });
138
+ continue;
139
+ }
140
+
141
+ const queryMatch = trimmed.match(/^query\s+(\w+)\s*=>\s*([\w[\]]+)\s*$/);
142
+ if (queryMatch) {
143
+ symbols.queries.push({ name: queryMatch[1], returnType: queryMatch[2], line: index });
144
+ continue;
145
+ }
146
+
147
+ const screenMatch = trimmed.match(/^screen\s+(\w+)\s*$/);
148
+ if (screenMatch) {
149
+ symbols.screens.push({ name: screenMatch[1], line: index });
150
+ }
151
+ }
152
+
153
+ return symbols;
154
+ }
155
+
156
+ export function knownEventNames(symbols: DocumentSymbols): string[] {
157
+ return [
158
+ ...symbols.events.map((event) => event.name),
159
+ ...symbols.imports.map((imported) => imported.shortName),
160
+ ];
161
+ }
@@ -0,0 +1,32 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import type { editor } from 'monaco-editor';
5
+
6
+ export const screenplayDarkThemeName = 'screenplay-dark';
7
+
8
+ export const screenplayDark: editor.IStandaloneThemeData = {
9
+ base: 'vs-dark',
10
+ inherit: true,
11
+ rules: [
12
+ { token: 'keyword.play', foreground: 'C586C0' },
13
+ { token: 'keyword.tag.play', foreground: '569CD6', fontStyle: 'italic' },
14
+ { token: 'keyword.type.play', foreground: '4FC1FF', fontStyle: 'bold' },
15
+ { token: 'type.play', foreground: '4EC9B0' },
16
+ { token: 'type.identifier.play', foreground: '4EC9B0' },
17
+ { token: 'identifier.play', foreground: '9CDCFE' },
18
+ { token: 'annotation.play', foreground: 'DCDCAA' },
19
+ { token: 'variable.predefined.play', foreground: 'D7BA7D' },
20
+ { token: 'string.play', foreground: 'CE9178' },
21
+ { token: 'string.invalid.play', foreground: 'F44747' },
22
+ { token: 'string.quote.play', foreground: '6A9955' },
23
+ { token: 'number.play', foreground: 'B5CEA8' },
24
+ { token: 'number.float.play', foreground: 'B5CEA8' },
25
+ { token: 'comment.play', foreground: '6A9955', fontStyle: 'italic' },
26
+ { token: 'operator.play', foreground: 'D4D4D4' },
27
+ { token: 'delimiter.play', foreground: 'CCCCCC' },
28
+ ],
29
+ colors: {
30
+ 'editor.background': '#1E1E1E',
31
+ },
32
+ };
@@ -0,0 +1,32 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import type { editor } from 'monaco-editor';
5
+
6
+ export const screenplayLightThemeName = 'screenplay-light';
7
+
8
+ export const screenplayLight: editor.IStandaloneThemeData = {
9
+ base: 'vs',
10
+ inherit: true,
11
+ rules: [
12
+ { token: 'keyword.play', foreground: 'AF00DB' },
13
+ { token: 'keyword.tag.play', foreground: '0000FF', fontStyle: 'italic' },
14
+ { token: 'keyword.type.play', foreground: '0070C1', fontStyle: 'bold' },
15
+ { token: 'type.play', foreground: '267F99' },
16
+ { token: 'type.identifier.play', foreground: '267F99' },
17
+ { token: 'identifier.play', foreground: '001080' },
18
+ { token: 'annotation.play', foreground: '795E26' },
19
+ { token: 'variable.predefined.play', foreground: 'B45309' },
20
+ { token: 'string.play', foreground: 'A31515' },
21
+ { token: 'string.invalid.play', foreground: 'CD3131' },
22
+ { token: 'string.quote.play', foreground: '008000' },
23
+ { token: 'number.play', foreground: '098658' },
24
+ { token: 'number.float.play', foreground: '098658' },
25
+ { token: 'comment.play', foreground: '008000', fontStyle: 'italic' },
26
+ { token: 'operator.play', foreground: '000000' },
27
+ { token: 'delimiter.play', foreground: '000000' },
28
+ ],
29
+ colors: {
30
+ 'editor.background': '#FFFFFF',
31
+ },
32
+ };
package/tokens.ts ADDED
@@ -0,0 +1,145 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import type { languages } from 'monaco-editor';
5
+ import {
6
+ clauseKeywords,
7
+ codeBlockTags,
8
+ constructKeywords,
9
+ primitiveTypes,
10
+ sliceTypes,
11
+ } from './language';
12
+ import { MonarchTokenRules, SubLanguage } from './sub-language-registry';
13
+
14
+ // Maps a Screenplay inline code tag to the Monaco language id used for embedded highlighting.
15
+ const embeddedLanguages: Record<string, string> = {
16
+ csharp: 'csharp',
17
+ typescript: 'typescript',
18
+ react: 'typescript',
19
+ html: 'html',
20
+ };
21
+
22
+ function subLanguageState(keyword: string): string {
23
+ return `subLanguage_${keyword}`;
24
+ }
25
+
26
+ // A construct keyword at the start of a line ends any indented sub-language block.
27
+ function subLanguageExitRule(subLanguages: SubLanguage[]): MonarchTokenRules[number] {
28
+ const exitKeywords = [...constructKeywords, ...subLanguages.map((subLanguage) => subLanguage.keyword)];
29
+ return [
30
+ new RegExp(`^\\s*(?:${exitKeywords.join('|')})\\b`),
31
+ { token: '@rematch', next: '@pop' },
32
+ ];
33
+ }
34
+
35
+ // Shared across the composite `.play` tokenizer and every standalone sub-language tokenizer
36
+ // (comments, `$`-prefixed context variables, strings, numbers, operators, delimiters, whitespace).
37
+ export const commonTokenRules: MonarchTokenRules = [
38
+ [/\/\/.*$/, 'comment'],
39
+ [/\$(?:context|eventContext|eventSourceId|causedBy)(?:\.\w+)*/, 'variable.predefined'],
40
+ [/\$(?:env|secrets|strings)\.[\w.]+/, 'variable.predefined'],
41
+ [/\$\.[\w.]*/, 'variable.predefined'],
42
+ [/"(?:[^"\\]|\\.)*"/, 'string'],
43
+ [/"(?:[^"\\]|\\.)*$/, 'string.invalid'],
44
+ [/\d+(?:ms|s|m|h|d)\b/, 'number'],
45
+ [/-?\d+\.\d+/, 'number.float'],
46
+ [/-?\d+/, 'number'],
47
+ [/=>|==|!=|>=|<=|[><=:?]/, 'operator'],
48
+ [/[[\](),.]/, 'delimiter'],
49
+ [/\s+/, 'white'],
50
+ ];
51
+
52
+ export function createTokensProvider(subLanguages: SubLanguage[]): languages.IMonarchLanguage {
53
+ const tokenizer: Record<string, MonarchTokenRules> = {
54
+ root: [
55
+ // Inline code block tags at end of line open an embedded code block.
56
+ ...codeBlockTags.map(
57
+ (tag): MonarchTokenRules[number] => [
58
+ new RegExp(`\\b${tag}\\b(?=\\s*$)`),
59
+ { token: 'keyword.tag', next: `@codeBlockPending.${embeddedLanguages[tag]}` },
60
+ ],
61
+ ),
62
+ // Sub-language constructs switch to the registered sub-language's token rules.
63
+ ...subLanguages.map(
64
+ (subLanguage): MonarchTokenRules[number] => [
65
+ new RegExp(`\\b${subLanguage.keyword}\\b`),
66
+ { token: 'keyword', next: `@${subLanguageState(subLanguage.keyword)}` },
67
+ ],
68
+ ),
69
+ // A bare description keyword at end of line opens a fenced plain-text block.
70
+ [/\bdescription\b(?=\s*$)/, { token: 'keyword', next: '@descriptionBlockPending' }],
71
+ [/\brow-click\b/, 'keyword'],
72
+ [/@\w+/, 'annotation'],
73
+ [
74
+ /[A-Z]\w*/,
75
+ {
76
+ cases: {
77
+ '@sliceTypes': 'keyword.type',
78
+ '@primitiveTypes': 'type',
79
+ '@default': 'type.identifier',
80
+ },
81
+ },
82
+ ],
83
+ [
84
+ /[a-z_]\w*/,
85
+ {
86
+ cases: {
87
+ '@keywords': 'keyword',
88
+ '@default': 'identifier',
89
+ },
90
+ },
91
+ ],
92
+ { include: '@common' },
93
+ ],
94
+
95
+ common: commonTokenRules,
96
+
97
+ // After a code tag, the only thing allowed before the opening fence is whitespace.
98
+ codeBlockPending: [
99
+ [
100
+ /^\s*```\s*$/,
101
+ { token: 'string.quote', switchTo: '@codeBlock.$S2', nextEmbedded: '$S2' },
102
+ ],
103
+ [/^\s*[^\s`].*$/, { token: '@rematch', next: '@pop' }],
104
+ [/\s+/, 'white'],
105
+ ],
106
+
107
+ codeBlock: [
108
+ [/^\s*```\s*$/, { token: 'string.quote', next: '@pop', nextEmbedded: '@pop' }],
109
+ [/[^`]+/, ''],
110
+ [/`/, ''],
111
+ ],
112
+
113
+ // After a bare description, the only thing allowed before the opening fence is whitespace.
114
+ descriptionBlockPending: [
115
+ [/^\s*```\s*$/, { token: 'string.quote', switchTo: '@descriptionBlock' }],
116
+ [/^\s*[^\s`].*$/, { token: '@rematch', next: '@pop' }],
117
+ [/\s+/, 'white'],
118
+ ],
119
+
120
+ descriptionBlock: [
121
+ [/^\s*```\s*$/, { token: 'string.quote', next: '@pop' }],
122
+ [/.+/, 'string'],
123
+ ],
124
+ };
125
+
126
+ for (const subLanguage of subLanguages) {
127
+ tokenizer[subLanguageState(subLanguage.keyword)] = [
128
+ subLanguageExitRule(subLanguages),
129
+ ...subLanguage.tokens,
130
+ { include: '@common' },
131
+ [/[A-Z]\w*/, 'type.identifier'],
132
+ [/[a-z_]\w*/, 'identifier'],
133
+ ];
134
+ }
135
+
136
+ return {
137
+ defaultToken: '',
138
+ tokenPostfix: '.play',
139
+ ignoreCase: false,
140
+ keywords: [...constructKeywords, ...clauseKeywords, ...codeBlockTags],
141
+ sliceTypes,
142
+ primitiveTypes,
143
+ tokenizer,
144
+ } as languages.IMonarchLanguage;
145
+ }