@purview-dev/astroc4 0.0.0-stage → 1.0.0-prerelease.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.
- package/LICENSE.md +9 -0
- package/README.md +179 -2
- package/package.json +96 -4
- package/src/components/DiagramFrame.astro +72 -0
- package/src/components/DiagramShell.astro +121 -0
- package/src/components/ErrorBoundary.tsx +52 -0
- package/src/components/LikeC4Fence.astro +62 -0
- package/src/components/LikeC4InlineView.tsx +67 -0
- package/src/components/LikeC4ProjectView.tsx +153 -0
- package/src/components/LikeC4View.astro +80 -0
- package/src/components/LoadingPlaceholder.astro +67 -0
- package/src/components/icon-source.ts +68 -0
- package/src/components/labels.ts +62 -0
- package/src/components/render-icon.tsx +41 -0
- package/src/components/utils.ts +69 -0
- package/src/expressive-code.d.mts +28 -0
- package/src/expressive-code.mjs +46 -0
- package/src/fences.mjs +288 -0
- package/src/grammar/langium.mjs +559 -0
- package/src/grammar/like-c4.langium +1357 -0
- package/src/grammar/likec4.tmLanguage.json +327 -0
- package/src/grammar/source.mjs +410 -0
- package/src/grammar/textmate.mjs +439 -0
- package/src/index.d.mts +64 -0
- package/src/index.mjs +228 -0
- package/src/translations.mjs +33 -0
- package/src/types.d.ts +12 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
// textmate.mjs
|
|
2
|
+
// path: src/grammar/textmate.mjs
|
|
3
|
+
// Builds the Shiki / TextMate grammar for LikeC4 out of the vocabulary read
|
|
4
|
+
// from the live Langium grammar (./langium.mjs).
|
|
5
|
+
//
|
|
6
|
+
// Two layers:
|
|
7
|
+
// - generated — every word list: blocks, keywords, property names and closed
|
|
8
|
+
// value domains. These are exactly the lists that go stale when the
|
|
9
|
+
// language grows (`agent` was added to `ElementShape` after v1.59.4).
|
|
10
|
+
// - structural — how LikeC4 source is *shaped* rather than which words it
|
|
11
|
+
// uses: comments, quoted and triple-quoted strings, `#tag` vs `#hex`,
|
|
12
|
+
// `tech:astro` icons, `-[https]->` arrows, `element system` declarations.
|
|
13
|
+
// These are curated here because a Langium rule does not say how a token
|
|
14
|
+
// should look, only that it is one.
|
|
15
|
+
//
|
|
16
|
+
// The result must stay plain JSON: Expressive Code JSON-round-trips integration
|
|
17
|
+
// options before the `<Code>` component renders them, and throws on functions
|
|
18
|
+
// or RegExp instances.
|
|
19
|
+
|
|
20
|
+
/** Scope name of the grammar, as upstream's VS Code extension uses it. */
|
|
21
|
+
export const SCOPE_NAME = 'source.likec4';
|
|
22
|
+
|
|
23
|
+
/** Shiki language id, plus the short alias used by ` ```c4 ` fences. */
|
|
24
|
+
export const LANGUAGE_NAME = 'likec4';
|
|
25
|
+
export const LANGUAGE_ALIASES = ['c4'];
|
|
26
|
+
|
|
27
|
+
const TMLANGUAGE_SCHEMA =
|
|
28
|
+
'https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Closed value domains: they colour as values, not as keywords.
|
|
32
|
+
* Anything the grammar adds under these names is picked up automatically.
|
|
33
|
+
*/
|
|
34
|
+
const VALUE_DOMAINS = [
|
|
35
|
+
'BorderStyleValue',
|
|
36
|
+
'DynamicViewDisplayVariantValue',
|
|
37
|
+
'EdgeRoutingValue',
|
|
38
|
+
'ElementShape',
|
|
39
|
+
'IconPositionValue',
|
|
40
|
+
'LineOptions',
|
|
41
|
+
'ArrowType',
|
|
42
|
+
'RankValue',
|
|
43
|
+
'SizeValue',
|
|
44
|
+
'SizingValue',
|
|
45
|
+
'ThemeColor',
|
|
46
|
+
'ViewLayoutDirection',
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
/** Control-flow keywords read as keywords, not as a value domain. */
|
|
50
|
+
const KEYWORD_DOMAINS = ['DynamicViewFlowKeyword'];
|
|
51
|
+
|
|
52
|
+
/** `source` / `target` are the only value-like variables. */
|
|
53
|
+
const VARIABLE_DOMAINS = ['Participant'];
|
|
54
|
+
|
|
55
|
+
/** Operators whose scope is a word, kept curated: they are not all literals. */
|
|
56
|
+
const OPERATOR_WORDS = ['and', 'or', 'not', 'is'];
|
|
57
|
+
|
|
58
|
+
/** Used when the grammar cannot be read — never expected, never silent. */
|
|
59
|
+
const FALLBACK_ICON_PACKS = ['aws', 'azure', 'bootstrap', 'gcp', 'tech'];
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @typedef {object} TmPattern
|
|
63
|
+
* @property {string} [name]
|
|
64
|
+
* @property {string} [comment]
|
|
65
|
+
* @property {string} [include]
|
|
66
|
+
* @property {string} [match]
|
|
67
|
+
* @property {string} [begin]
|
|
68
|
+
* @property {string} [end]
|
|
69
|
+
* @property {string} [contentName]
|
|
70
|
+
* @property {Record<string, { name: string }>} [captures]
|
|
71
|
+
* @property {Record<string, { name: string }>} [beginCaptures]
|
|
72
|
+
* @property {Record<string, { name: string }>} [endCaptures]
|
|
73
|
+
* @property {TmPattern[]} [patterns]
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* A TextMate grammar: JSON all the way down, never a `RegExp`.
|
|
78
|
+
* @typedef {object} TmGrammar
|
|
79
|
+
* @property {string} $schema
|
|
80
|
+
* @property {string} name
|
|
81
|
+
* @property {string} displayName
|
|
82
|
+
* @property {string} scopeName
|
|
83
|
+
* @property {string[]} fileTypes
|
|
84
|
+
* @property {TmPattern[]} patterns
|
|
85
|
+
* @property {Record<string, any>} repository
|
|
86
|
+
* @property {{ repo: string, ref: string, sha?: string, by: string }} [$generated]
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {import('./langium.mjs').Vocabulary} vocabulary
|
|
91
|
+
* @param {{ repo?: string, ref?: string, sha?: string }} [meta] Provenance.
|
|
92
|
+
* @returns {TmGrammar} A TextMate grammar, JSON-serialisable.
|
|
93
|
+
*/
|
|
94
|
+
export function buildTmLanguage(vocabulary, meta = {}) {
|
|
95
|
+
const control = uniqueDomain(vocabulary, KEYWORD_DOMAINS);
|
|
96
|
+
const variables = uniqueDomain(vocabulary, VARIABLE_DOMAINS);
|
|
97
|
+
const icons = vocabulary.icons.length > 0 ? vocabulary.icons : FALLBACK_ICON_PACKS;
|
|
98
|
+
|
|
99
|
+
// Words that are always syntax, never the name of a kind: `deployed =
|
|
100
|
+
// instanceOf a` must not paint `instanceOf` as a type.
|
|
101
|
+
const reserved = alternationOf([...vocabulary.keywords, ...vocabulary.blocks, ...control]);
|
|
102
|
+
|
|
103
|
+
/** @type {TmGrammar} */
|
|
104
|
+
const grammar = {
|
|
105
|
+
$schema: TMLANGUAGE_SCHEMA,
|
|
106
|
+
...(meta.repo && meta.ref
|
|
107
|
+
? {
|
|
108
|
+
$generated: {
|
|
109
|
+
repo: meta.repo,
|
|
110
|
+
ref: meta.ref,
|
|
111
|
+
...(meta.sha ? { sha: meta.sha } : {}),
|
|
112
|
+
by: 'astroc4/scripts/sync-grammar.mjs',
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
: {}),
|
|
116
|
+
name: LANGUAGE_NAME,
|
|
117
|
+
displayName: 'LikeC4',
|
|
118
|
+
scopeName: SCOPE_NAME,
|
|
119
|
+
fileTypes: ['c4', 'likec4'],
|
|
120
|
+
patterns: [{ include: '#everything' }],
|
|
121
|
+
repository: {
|
|
122
|
+
everything: {
|
|
123
|
+
patterns: [
|
|
124
|
+
'comments',
|
|
125
|
+
'strings',
|
|
126
|
+
'urls',
|
|
127
|
+
'icons',
|
|
128
|
+
'hex-colors',
|
|
129
|
+
'tags',
|
|
130
|
+
'typed-arrow',
|
|
131
|
+
'declarations',
|
|
132
|
+
'keywords',
|
|
133
|
+
'numbers',
|
|
134
|
+
'operators',
|
|
135
|
+
'punctuation',
|
|
136
|
+
].map((name) => ({ include: `#${name}` })),
|
|
137
|
+
},
|
|
138
|
+
comments: comments(),
|
|
139
|
+
strings: strings(),
|
|
140
|
+
urls: urls(),
|
|
141
|
+
icons: iconIds(icons),
|
|
142
|
+
'hex-colors': hexColours(),
|
|
143
|
+
tags: tags(),
|
|
144
|
+
'typed-arrow': typedArrow(),
|
|
145
|
+
declarations: declarations(reserved),
|
|
146
|
+
keywords: keywords({ ...vocabulary, control, variables }),
|
|
147
|
+
numbers: numbers(),
|
|
148
|
+
operators: operators(vocabulary.arrows),
|
|
149
|
+
punctuation: punctuation(),
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
if (meta.repo && meta.ref) {
|
|
154
|
+
grammar.$generated = {
|
|
155
|
+
repo: meta.repo,
|
|
156
|
+
ref: meta.ref,
|
|
157
|
+
...(meta.sha ? { sha: meta.sha } : {}),
|
|
158
|
+
by: 'astroc4/scripts/sync-grammar.mjs',
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return grammar;
|
|
163
|
+
}
|
|
164
|
+
// region structural patterns
|
|
165
|
+
|
|
166
|
+
function comments() {
|
|
167
|
+
return {
|
|
168
|
+
patterns: [
|
|
169
|
+
{
|
|
170
|
+
name: 'comment.block.likec4',
|
|
171
|
+
begin: '/\\*',
|
|
172
|
+
end: '\\*/',
|
|
173
|
+
captures: { 0: { name: 'punctuation.definition.comment.likec4' } },
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: 'comment.line.double-slash.likec4',
|
|
177
|
+
match: '//.*$',
|
|
178
|
+
captures: { 0: { name: 'punctuation.definition.comment.likec4' } },
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* `'…'`, `"…"`, and the markdown strings `''' … '''` / `""" … """` that
|
|
186
|
+
* LikeC4 uses for descriptions.
|
|
187
|
+
*/
|
|
188
|
+
function strings() {
|
|
189
|
+
const triple = (quote) => ({
|
|
190
|
+
name: 'string.quoted.triple.likec4',
|
|
191
|
+
begin: quote.repeat(3),
|
|
192
|
+
end: quote.repeat(3),
|
|
193
|
+
beginCaptures: { 0: { name: 'punctuation.definition.string.begin.likec4' } },
|
|
194
|
+
endCaptures: { 0: { name: 'punctuation.definition.string.end.likec4' } },
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const single = (quote, name) => ({
|
|
198
|
+
name,
|
|
199
|
+
begin: quote,
|
|
200
|
+
end: `${quote}|$`,
|
|
201
|
+
beginCaptures: { 0: { name: 'punctuation.definition.string.begin.likec4' } },
|
|
202
|
+
endCaptures: { 0: { name: 'punctuation.definition.string.end.likec4' } },
|
|
203
|
+
patterns: [{ name: 'constant.character.escape.likec4', match: '\\\\.' }],
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
patterns: [
|
|
208
|
+
triple("'"),
|
|
209
|
+
triple('"'),
|
|
210
|
+
single("'", 'string.quoted.single.likec4'),
|
|
211
|
+
single('"', 'string.quoted.double.likec4'),
|
|
212
|
+
],
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function urls() {
|
|
217
|
+
return { name: 'markup.underline.link.likec4', match: "\\bhttps?://[^\\s'\"]+" };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Icon ids (`tech:astro`, `aws:lambda`). The packs come from the `LIB_ICON`
|
|
222
|
+
* terminal, so a pack added upstream highlights without touching this package.
|
|
223
|
+
* @param {string[]} packs
|
|
224
|
+
*/
|
|
225
|
+
function iconIds(packs) {
|
|
226
|
+
return {
|
|
227
|
+
comment: 'Bundled icon ids: `tech:nginx`, `aws:…`, `gcp:…`, …',
|
|
228
|
+
match: `\\b(${alternationOf(packs)})(:)([\\w-]+)`,
|
|
229
|
+
captures: {
|
|
230
|
+
1: { name: 'support.constant.icon-pack.likec4' },
|
|
231
|
+
2: { name: 'punctuation.separator.likec4' },
|
|
232
|
+
3: { name: 'support.constant.icon.likec4' },
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** `#0046BE` is a colour, `#compute` is a tag — hex first, so it wins. */
|
|
238
|
+
function hexColours() {
|
|
239
|
+
return {
|
|
240
|
+
name: 'constant.other.color.rgb-value.hex.likec4',
|
|
241
|
+
match: '#(?:[0-9A-Fa-f]{8}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})(?![\\w-])',
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function tags() {
|
|
246
|
+
return { name: 'entity.name.tag.likec4', match: '#[A-Za-z_][\\w-]*' };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** `a -[https]-> b` — the relationship kind inside the connector. */
|
|
250
|
+
function typedArrow() {
|
|
251
|
+
return {
|
|
252
|
+
comment: '`a -[https]-> b`',
|
|
253
|
+
match: '(-\\[)\\s*([A-Za-z_][\\w-]*)\\s*(\\]->)',
|
|
254
|
+
captures: {
|
|
255
|
+
1: { name: 'keyword.operator.arrow.likec4' },
|
|
256
|
+
2: { name: 'entity.name.type.relationship.likec4' },
|
|
257
|
+
3: { name: 'keyword.operator.arrow.likec4' },
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Declarations: the name being introduced is scoped, the name only being
|
|
264
|
+
* referred to is not. Runs before the keyword patterns so `element system`
|
|
265
|
+
* reads as a declaration rather than as two keywords.
|
|
266
|
+
*
|
|
267
|
+
* @param {string} reserved Alternation of syntax words (`instanceOf`, `extend`,
|
|
268
|
+
* …), so `deployed = instanceOf a` scopes `instanceOf` as a keyword rather
|
|
269
|
+
* than as the element kind the name is being assigned.
|
|
270
|
+
*/
|
|
271
|
+
function declarations(reserved) {
|
|
272
|
+
const notReserved = reserved ? `(?!(?:${reserved})\\b)` : '';
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
patterns: [
|
|
276
|
+
{
|
|
277
|
+
comment:
|
|
278
|
+
'`element system`, `deploymentNode region`, `tag tls`, `relationship https`. Not `where tag is #x`.',
|
|
279
|
+
match: '\\b(element|deploymentNode|tag|relationship)\\s+(?!is\\b)([A-Za-z_][\\w-]*)(?=\\s*(?:\\{|$|//))',
|
|
280
|
+
captures: {
|
|
281
|
+
1: { name: 'storage.type.likec4' },
|
|
282
|
+
2: { name: 'entity.name.type.likec4' },
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
comment:
|
|
287
|
+
'`color testingBlue #0046BE` declares; `color testingBlue` inside a style block only refers.',
|
|
288
|
+
match: '\\b(color)\\s+([A-Za-z_][\\w-]*)(?=\\s+#)',
|
|
289
|
+
captures: {
|
|
290
|
+
1: { name: 'storage.type.likec4' },
|
|
291
|
+
2: { name: 'entity.name.type.likec4' },
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
comment: '`view index`, `dynamic view firstRequest`, `deployment view prod`, `view internals of site`',
|
|
296
|
+
match: '\\b(?:(dynamic|deployment)\\s+)?(view)\\s+([A-Za-z_]\\w*)',
|
|
297
|
+
captures: {
|
|
298
|
+
1: { name: 'storage.modifier.likec4' },
|
|
299
|
+
2: { name: 'storage.type.view.likec4' },
|
|
300
|
+
3: { name: 'entity.name.function.view.likec4' },
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
comment: "`reader = actor 'Engineer'`",
|
|
305
|
+
match: `\\b([A-Za-z_]\\w*)\\s*(=)(?!=)\\s*${notReserved}([A-Za-z_]\\w*)?`,
|
|
306
|
+
captures: {
|
|
307
|
+
1: { name: 'variable.other.definition.likec4' },
|
|
308
|
+
2: { name: 'keyword.operator.assignment.likec4' },
|
|
309
|
+
3: { name: 'entity.name.type.likec4' },
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// endregion
|
|
317
|
+
|
|
318
|
+
// region generated keyword patterns
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Order matters here: blocks before plain keywords (so `model {` is a block),
|
|
322
|
+
* operator words before keywords (`is`, `not`), variables before value domains
|
|
323
|
+
* (so `source`/`target` stay variables even though `RankValue` reuses `source`).
|
|
324
|
+
*
|
|
325
|
+
* @param {import('./langium.mjs').Vocabulary & { control: string[], variables: string[] }} vocabulary
|
|
326
|
+
*/
|
|
327
|
+
function keywords(vocabulary) {
|
|
328
|
+
/** @type {Record<string, unknown>[]} */
|
|
329
|
+
const patterns = [];
|
|
330
|
+
|
|
331
|
+
const blocks = alternationOf(vocabulary.blocks);
|
|
332
|
+
if (blocks) {
|
|
333
|
+
patterns.push({
|
|
334
|
+
comment: 'Keywords that open a block: `model { … }`.',
|
|
335
|
+
name: 'keyword.control.block.likec4',
|
|
336
|
+
match: `\\b(?:${blocks})\\b(?=\\s*\\{)`,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Blocks repeat in the keyword list on purpose: `predicateGroup mine { … }`
|
|
341
|
+
// is not followed by `{`, and still has to colour as a keyword. The block
|
|
342
|
+
// pattern above wins wherever the brace is there.
|
|
343
|
+
const control = alternationOf([...vocabulary.keywords, ...vocabulary.control, ...vocabulary.blocks]);
|
|
344
|
+
|
|
345
|
+
// Word operators (`is`, `not`) come before the keyword list, which also
|
|
346
|
+
// contains them: `where tag is #x` reads as an operator, not a keyword.
|
|
347
|
+
patterns.push({
|
|
348
|
+
name: 'keyword.operator.word.likec4',
|
|
349
|
+
match: `\\b(?:${alternationOf(OPERATOR_WORDS)})\\b`,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
if (control) {
|
|
353
|
+
patterns.push({ name: 'keyword.control.likec4', match: `\\b(?:${control})\\b` });
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const variables = alternationOf(vocabulary.variables);
|
|
357
|
+
if (variables) {
|
|
358
|
+
patterns.push({ name: 'variable.language.likec4', match: `\\b(?:${variables})\\b` });
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const properties = alternationOf(vocabulary.properties);
|
|
362
|
+
if (properties) {
|
|
363
|
+
patterns.push({ name: 'support.type.property-name.likec4', match: `\\b(?:${properties})\\b` });
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const values = alternationOf(uniqueDomain(vocabulary, VALUE_DOMAINS));
|
|
367
|
+
if (values) {
|
|
368
|
+
patterns.push({ name: 'constant.language.likec4', match: `\\b(?:${values})\\b` });
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return { patterns };
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function numbers() {
|
|
375
|
+
return { name: 'constant.numeric.likec4', match: '\\b\\d+(?:\\.\\d+)?%?' };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Relationship connectors, longest first so `]->` wins over `->`.
|
|
380
|
+
* @param {string[]} arrows
|
|
381
|
+
*/
|
|
382
|
+
function operators(arrows) {
|
|
383
|
+
const patterns = [];
|
|
384
|
+
|
|
385
|
+
const connectors = alternationOf(arrows);
|
|
386
|
+
if (connectors) {
|
|
387
|
+
patterns.push({ name: 'keyword.operator.arrow.likec4', match: connectors });
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
patterns.push({ name: 'keyword.operator.wildcard.likec4', match: '\\.\\*\\*|\\.\\*|\\*\\*|\\*' });
|
|
391
|
+
|
|
392
|
+
return { patterns };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function punctuation() {
|
|
396
|
+
return {
|
|
397
|
+
patterns: [
|
|
398
|
+
{ name: 'punctuation.section.block.begin.likec4', match: '\\{' },
|
|
399
|
+
{ name: 'punctuation.section.block.end.likec4', match: '\\}' },
|
|
400
|
+
{ name: 'punctuation.separator.comma.likec4', match: ',' },
|
|
401
|
+
{ name: 'punctuation.accessor.likec4', match: '\\.' },
|
|
402
|
+
],
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// endregion
|
|
407
|
+
|
|
408
|
+
// region helpers
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Every value of the named domains, deduplicated and sorted. Missing domains
|
|
412
|
+
* are skipped rather than failing: upstream is free to rename a rule, and a
|
|
413
|
+
* renamed domain still reaches the grammar through `keywords`.
|
|
414
|
+
* @param {import('./langium.mjs').Vocabulary} vocabulary
|
|
415
|
+
* @param {string[]} names
|
|
416
|
+
*/
|
|
417
|
+
function uniqueDomain(vocabulary, names) {
|
|
418
|
+
const values = names.flatMap((name) => vocabulary.enums[name] ?? []);
|
|
419
|
+
|
|
420
|
+
return [...new Set(values)].sort((a, b) => a.localeCompare(b));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* `['a', 'b']` -> `a|b`, escaped for a TextMate regex. Returns '' for an empty
|
|
425
|
+
* list so callers can skip the pattern instead of emitting `\b(?:)\b`.
|
|
426
|
+
* @param {string[]} values
|
|
427
|
+
*/
|
|
428
|
+
function alternationOf(values) {
|
|
429
|
+
return values.map(escapeRegExp).join('|');
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function escapeRegExp(value) {
|
|
433
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// endregion
|
|
437
|
+
|
|
438
|
+
export default buildTmLanguage;
|
|
439
|
+
|
package/src/index.d.mts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// index.d.mts
|
|
2
|
+
// path: src/index.d.mts
|
|
3
|
+
// Types for src/index.mjs. TypeScript does not read JSDoc from a package under
|
|
4
|
+
// node_modules, so a site's astro.config.mjs needs these to type-check.
|
|
5
|
+
|
|
6
|
+
import type { StarlightPlugin } from '@astrojs/starlight/types';
|
|
7
|
+
import type { LikeC4VitePlugin } from 'likec4/vite-plugin';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* LikeC4 syntax highlighting, derived from the Langium grammar in
|
|
11
|
+
* `likec4/likec4` and registered with the site's Expressive Code.
|
|
12
|
+
*/
|
|
13
|
+
export interface LikeC4HighlightingOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Branch, tag or commit SHA to read `like-c4.langium` from.
|
|
16
|
+
* Defaults to `main`; pin a tag (`v1.59.4`) or a SHA for reproducible CI.
|
|
17
|
+
* Also read from `ASTROC4_REF`.
|
|
18
|
+
*/
|
|
19
|
+
ref?: string;
|
|
20
|
+
/** Repository to read from. Defaults to `likec4/likec4`. */
|
|
21
|
+
repo?: string;
|
|
22
|
+
/** Use the bundled snapshot and never touch the network. Also `ASTROC4_OFFLINE=1`. */
|
|
23
|
+
offline?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* What to do when the grammar cannot be fetched. Defaults to `fail`, so
|
|
26
|
+
* `astro build` and `astro check` fail like any other build error.
|
|
27
|
+
* A `ref` that does not exist always fails.
|
|
28
|
+
*/
|
|
29
|
+
onError?: 'fail' | 'warn' | 'silent';
|
|
30
|
+
/** Seconds a cached fetch stays fresh. Defaults to an hour. */
|
|
31
|
+
cacheTtl?: number;
|
|
32
|
+
/** Ignore the cache and refetch. */
|
|
33
|
+
refresh?: boolean;
|
|
34
|
+
/** Where to cache. Defaults to `node_modules/.cache/astroc4`. */
|
|
35
|
+
cacheDir?: string;
|
|
36
|
+
/** Raw host to read from, for mirrors. Defaults to raw.githubusercontent.com. */
|
|
37
|
+
baseUrl?: string;
|
|
38
|
+
/** Request timeout in milliseconds. Defaults to 15000. */
|
|
39
|
+
timeout?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface StarlightLikeC4Options {
|
|
43
|
+
/**
|
|
44
|
+
* Passed through to `LikeC4VitePlugin` — e.g. `{ workspace: 'diagrams' }` to
|
|
45
|
+
* look for .c4 files somewhere other than the site root.
|
|
46
|
+
*/
|
|
47
|
+
vitePlugin?: Parameters<typeof LikeC4VitePlugin>[0];
|
|
48
|
+
/** Render ```likec4 fences in .mdx pages as diagrams. Defaults to `true`. */
|
|
49
|
+
fences?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Highlight ```c4 / ```likec4 source. Defaults to `true`; `false` leaves
|
|
52
|
+
* Expressive Code entirely to the site.
|
|
53
|
+
*/
|
|
54
|
+
highlighting?: boolean | LikeC4HighlightingOptions;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default function starlightLikeC4(options?: StarlightLikeC4Options): StarlightPlugin;
|
|
58
|
+
|
|
59
|
+
/** Adds the LikeC4 language to a site's Expressive Code options, preserving the rest. */
|
|
60
|
+
export declare function withLikeC4Language(
|
|
61
|
+
expressiveCode: unknown,
|
|
62
|
+
registration: Record<string, unknown>,
|
|
63
|
+
): Record<string, unknown> & { shiki: { langs: unknown[] } };
|
|
64
|
+
|