@crowdedkingdoms/crowdyjs 9.0.0 → 10.0.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 (72) hide show
  1. package/MIGRATION.md +56 -34
  2. package/README.md +83 -50
  3. package/dist/crowdy-client.d.ts +3 -0
  4. package/dist/crowdy-client.d.ts.map +1 -1
  5. package/dist/crowdy-client.js +2 -0
  6. package/dist/domains/playerCodeProjects.d.ts +25 -0
  7. package/dist/domains/playerCodeProjects.d.ts.map +1 -0
  8. package/dist/domains/playerCodeProjects.js +299 -0
  9. package/dist/domains/playerCompute.d.ts +1 -1
  10. package/dist/domains/playerCompute.js +1 -1
  11. package/dist/generated/graphql.d.ts +792 -7
  12. package/dist/generated/graphql.d.ts.map +1 -1
  13. package/dist/generated/graphql.js +57 -0
  14. package/dist/index.d.ts +3 -5
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +3 -5
  17. package/dist/kit/npcs.d.ts.map +1 -1
  18. package/dist/kit/social.d.ts.map +1 -1
  19. package/dist/live-coding/rust-analysis.d.ts.map +1 -1
  20. package/dist/live-coding/rust-analysis.js +64 -1
  21. package/dist/live-coding/worker-transport.d.ts +3 -0
  22. package/dist/live-coding/worker-transport.d.ts.map +1 -1
  23. package/dist/live-coding/worker-transport.js +13 -0
  24. package/dist/mod-studio/controller.d.ts +188 -0
  25. package/dist/mod-studio/controller.d.ts.map +1 -0
  26. package/dist/mod-studio/controller.js +915 -0
  27. package/dist/mod-studio/diagnostics.d.ts +22 -0
  28. package/dist/mod-studio/diagnostics.d.ts.map +1 -0
  29. package/dist/mod-studio/diagnostics.js +141 -0
  30. package/dist/mod-studio/dom-shell.d.ts +55 -0
  31. package/dist/mod-studio/dom-shell.d.ts.map +1 -0
  32. package/dist/mod-studio/dom-shell.js +527 -0
  33. package/dist/mod-studio/editor.d.ts +17 -0
  34. package/dist/mod-studio/editor.d.ts.map +1 -0
  35. package/dist/mod-studio/editor.js +1 -0
  36. package/dist/mod-studio/index.d.ts +10 -0
  37. package/dist/mod-studio/index.d.ts.map +1 -0
  38. package/dist/mod-studio/index.js +9 -0
  39. package/dist/mod-studio/models.d.ts +149 -0
  40. package/dist/mod-studio/models.d.ts.map +1 -0
  41. package/dist/mod-studio/models.js +62 -0
  42. package/dist/mod-studio/monaco-editor.d.ts +16 -0
  43. package/dist/mod-studio/monaco-editor.d.ts.map +1 -0
  44. package/dist/mod-studio/monaco-editor.js +548 -0
  45. package/dist/mod-studio/mount.d.ts +17 -0
  46. package/dist/mod-studio/mount.d.ts.map +1 -0
  47. package/dist/mod-studio/mount.js +86 -0
  48. package/dist/mod-studio/starter-projects.d.ts +11 -0
  49. package/dist/mod-studio/starter-projects.d.ts.map +1 -0
  50. package/dist/mod-studio/starter-projects.js +102 -0
  51. package/dist/mod-studio/styles.d.ts +2 -0
  52. package/dist/mod-studio/styles.d.ts.map +1 -0
  53. package/dist/mod-studio/styles.js +14 -0
  54. package/dist/mod-studio/textarea-editor.d.ts +8 -0
  55. package/dist/mod-studio/textarea-editor.d.ts.map +1 -0
  56. package/dist/mod-studio/textarea-editor.js +71 -0
  57. package/package.json +4 -4
  58. package/dist/live-coding/ide.d.ts +0 -23
  59. package/dist/live-coding/ide.d.ts.map +0 -1
  60. package/dist/live-coding/ide.js +0 -514
  61. package/dist/live-coding/index.d.ts +0 -8
  62. package/dist/live-coding/index.d.ts.map +0 -1
  63. package/dist/live-coding/index.js +0 -7
  64. package/dist/live-coding/live-coding-controller.d.ts +0 -75
  65. package/dist/live-coding/live-coding-controller.d.ts.map +0 -1
  66. package/dist/live-coding/live-coding-controller.js +0 -141
  67. package/dist/live-coding/mount.d.ts +0 -26
  68. package/dist/live-coding/mount.d.ts.map +0 -1
  69. package/dist/live-coding/mount.js +0 -108
  70. package/dist/live-coding/templates.d.ts +0 -17
  71. package/dist/live-coding/templates.d.ts.map +0 -1
  72. package/dist/live-coding/templates.js +0 -59
@@ -0,0 +1,548 @@
1
+ import { modStudioFileUri, } from './models.js';
2
+ import { isRecord, } from '../live-coding/lsp-protocol.js';
3
+ import { loadPlatformIndex, } from '../live-coding/platform-index.js';
4
+ import { acquireMonacoWorkspace, ensureMonacoServicesInitialized, } from '../live-coding/monaco-services.js';
5
+ let rustLanguagesRegistered = false;
6
+ /**
7
+ * Create the Monaco adapter backed by the browser-local Rust LSP worker. Every
8
+ * loaded project/reference file is opened in the worker; project URIs include
9
+ * `/server/` or `/client/`, so duplicate Cargo and lib paths never collide.
10
+ */
11
+ export async function createMonacoModStudioEditor(host, options, callbacks) {
12
+ if (!options.languageWorkerFactory && typeof Worker === 'undefined') {
13
+ throw new Error('Worker is unavailable');
14
+ }
15
+ if (options.editorWorkerFactory) {
16
+ globalThis.MonacoEnvironment = { getWorker: options.editorWorkerFactory };
17
+ }
18
+ const platformIndex = options.platformIndex === undefined
19
+ ? undefined
20
+ : loadPlatformIndex(options.platformIndex);
21
+ let languageWorker = null;
22
+ let languageClient = null;
23
+ let releaseWorkspace = null;
24
+ let editor = null;
25
+ const models = new Map();
26
+ const localDiagnostics = new Map();
27
+ const disposables = [];
28
+ try {
29
+ const workerTransportPromise = import('../live-coding/worker-transport.js');
30
+ await ensureMonacoServicesInitialized();
31
+ const [monaco, workerTransport] = await Promise.all([
32
+ import('@codingame/monaco-vscode-editor-api'),
33
+ workerTransportPromise,
34
+ ]);
35
+ registerRustLanguages(monaco);
36
+ const workspace = acquireMonacoWorkspace();
37
+ releaseWorkspace = workspace.release;
38
+ const workspaceUri = workspace.uri;
39
+ const ownsModel = (uri) => uri.toString().startsWith(`${workspaceUri}/`);
40
+ editor = monaco.editor.create(host, {
41
+ automaticLayout: true,
42
+ minimap: { enabled: false },
43
+ theme: 'vs-dark',
44
+ fontSize: 14,
45
+ tabSize: 2,
46
+ });
47
+ languageWorker =
48
+ options.languageWorkerFactory?.() ??
49
+ workerTransport.createDefaultRustLanguageWorker();
50
+ languageClient = new workerTransport.WorkerLanguageClient(languageWorker, {
51
+ requestTimeoutMs: options.languageRequestTimeoutMs,
52
+ });
53
+ let failureReported = false;
54
+ disposables.push(languageClient.onError((error) => {
55
+ if (failureReported)
56
+ return;
57
+ failureReported = true;
58
+ callbacks.onFailure?.(error);
59
+ }));
60
+ disposables.push(languageClient.onNotification('textDocument/publishDiagnostics', (params) => {
61
+ if (!isRecord(params) || typeof params.uri !== 'string')
62
+ return;
63
+ const entry = [...models.values()].find((candidate) => candidate.model.uri.toString() === params.uri);
64
+ if (!entry)
65
+ return;
66
+ if (!isCurrentDiagnosticVersion(params.version, entry.model.getVersionId())) {
67
+ return;
68
+ }
69
+ const diagnostics = Array.isArray(params.diagnostics)
70
+ ? params.diagnostics
71
+ : [];
72
+ monaco.editor.setModelMarkers(entry.model, 'crowdy-rust-advisory', diagnostics.map((diagnostic) => ({
73
+ ...toMonacoRange(diagnostic.range),
74
+ severity: diagnosticSeverity(monaco, diagnostic.severity),
75
+ message: diagnostic.message,
76
+ code: diagnostic.code,
77
+ source: 'local advisory',
78
+ })));
79
+ const projectDiagnostics = entry.ref.source === 'PROJECT' && entry.ref.target
80
+ ? diagnostics.map((diagnostic) => ({
81
+ target: entry.ref.target,
82
+ path: entry.ref.path,
83
+ line: diagnostic.range.start.line + 1,
84
+ column: diagnostic.range.start.character + 1,
85
+ endLine: diagnostic.range.end.line + 1,
86
+ endColumn: diagnostic.range.end.character + 1,
87
+ severity: lspSeverity(diagnostic.severity),
88
+ message: diagnostic.message,
89
+ ...(diagnostic.code !== undefined
90
+ ? { code: String(diagnostic.code) }
91
+ : {}),
92
+ source: 'local-advisory',
93
+ }))
94
+ : [];
95
+ localDiagnostics.set(entry.key, projectDiagnostics);
96
+ callbacks.onLocalDiagnostics([...localDiagnostics.values()].flat());
97
+ }));
98
+ await languageClient.initialize({
99
+ processId: null,
100
+ clientInfo: { name: 'CrowdyJS Mod Studio', version: '1' },
101
+ rootUri: workspaceUri,
102
+ capabilities: {
103
+ general: { positionEncodings: ['utf-16'] },
104
+ textDocument: {
105
+ completion: {},
106
+ hover: { contentFormat: ['markdown', 'plaintext'] },
107
+ documentSymbol: {},
108
+ definition: {},
109
+ publishDiagnostics: { versionSupport: true },
110
+ },
111
+ },
112
+ initializationOptions: {
113
+ ...(platformIndex === undefined ? {} : { platformIndex }),
114
+ },
115
+ });
116
+ disposables.push(monaco.languages.registerCompletionItemProvider('rust', {
117
+ triggerCharacters: [':', '.'],
118
+ provideCompletionItems: async (model, position) => {
119
+ if (!ownsModel(model.uri))
120
+ return { suggestions: [] };
121
+ const word = model.getWordUntilPosition(position);
122
+ const response = (await languageClient?.request('textDocument/completion', lspDocumentPosition(model.uri.toString(), position)));
123
+ return {
124
+ suggestions: (response ?? []).map((item) => ({
125
+ label: item.label,
126
+ kind: completionKind(monaco, item.kind),
127
+ detail: item.detail,
128
+ documentation: item.documentation,
129
+ insertText: item.insertText ?? item.label,
130
+ insertTextRules: item.kind === 15
131
+ ? monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
132
+ : undefined,
133
+ range: {
134
+ startLineNumber: position.lineNumber,
135
+ startColumn: word.startColumn,
136
+ endLineNumber: position.lineNumber,
137
+ endColumn: position.column,
138
+ },
139
+ })),
140
+ };
141
+ },
142
+ }), monaco.languages.registerHoverProvider('rust', {
143
+ provideHover: async (model, position) => {
144
+ if (!ownsModel(model.uri))
145
+ return null;
146
+ const response = (await languageClient?.request('textDocument/hover', lspDocumentPosition(model.uri.toString(), position)));
147
+ if (!response)
148
+ return null;
149
+ return {
150
+ contents: [{ value: response.contents.value }],
151
+ range: response.range
152
+ ? new monaco.Range(response.range.start.line + 1, response.range.start.character + 1, response.range.end.line + 1, response.range.end.character + 1)
153
+ : undefined,
154
+ };
155
+ },
156
+ }), monaco.languages.registerDocumentSymbolProvider('rust', {
157
+ provideDocumentSymbols: async (model) => {
158
+ if (!ownsModel(model.uri))
159
+ return [];
160
+ const response = (await languageClient?.request('textDocument/documentSymbol', { textDocument: { uri: model.uri.toString() } }));
161
+ return (response ?? []).map((symbol) => ({
162
+ name: symbol.name,
163
+ detail: symbol.detail ?? '',
164
+ kind: symbolKind(monaco, symbol.kind),
165
+ tags: [],
166
+ range: toMonacoRange(symbol.range),
167
+ selectionRange: toMonacoRange(symbol.selectionRange),
168
+ }));
169
+ },
170
+ }), monaco.languages.registerDefinitionProvider('rust', {
171
+ provideDefinition: async (model, position) => {
172
+ if (!ownsModel(model.uri))
173
+ return null;
174
+ const response = (await languageClient?.request('textDocument/definition', lspDocumentPosition(model.uri.toString(), position)));
175
+ if (!response)
176
+ return null;
177
+ return {
178
+ uri: monaco.Uri.parse(response.uri),
179
+ range: toMonacoRange(response.range),
180
+ };
181
+ },
182
+ }));
183
+ let disposed = false;
184
+ let previousActiveKey = null;
185
+ const adapter = {
186
+ mode: 'monaco',
187
+ sync(state) {
188
+ if (disposed || !editor)
189
+ return;
190
+ const desired = collectFiles(state, workspaceUri);
191
+ const desiredKeys = new Set(desired.map((file) => file.key));
192
+ let removedDiagnostics = false;
193
+ for (const [key, entry] of models) {
194
+ if (desiredKeys.has(key))
195
+ continue;
196
+ closeModel(entry, languageClient, monaco);
197
+ models.delete(key);
198
+ removedDiagnostics = localDiagnostics.delete(key) || removedDiagnostics;
199
+ }
200
+ if (removedDiagnostics) {
201
+ callbacks.onLocalDiagnostics([...localDiagnostics.values()].flat());
202
+ }
203
+ for (const file of desired) {
204
+ const existing = models.get(file.key);
205
+ if (existing) {
206
+ existing.ref = file.ref;
207
+ if (existing.model.getValue() !== file.content) {
208
+ existing.suppressChanges = true;
209
+ existing.model.setValue(file.content);
210
+ existing.suppressChanges = false;
211
+ }
212
+ continue;
213
+ }
214
+ const uri = monaco.Uri.parse(file.uri);
215
+ const model = monaco.editor.createModel(file.content, languageFor(file.ref.path), uri);
216
+ const entry = {
217
+ key: file.key,
218
+ ref: file.ref,
219
+ model,
220
+ suppressChanges: false,
221
+ changeSubscription: { dispose() { } },
222
+ };
223
+ entry.changeSubscription = model.onDidChangeContent(() => {
224
+ if (entry.ref.path.endsWith('.rs')) {
225
+ void languageClient
226
+ ?.notify('textDocument/didChange', {
227
+ textDocument: {
228
+ uri: model.uri.toString(),
229
+ version: model.getVersionId(),
230
+ },
231
+ contentChanges: [{ text: model.getValue() }],
232
+ })
233
+ .catch(() => { });
234
+ }
235
+ if (!entry.suppressChanges &&
236
+ entry.ref.source === 'PROJECT' &&
237
+ entry.ref.target) {
238
+ callbacks.onProjectFileChange(entry.ref.target, entry.ref.path, model.getValue());
239
+ }
240
+ });
241
+ models.set(file.key, entry);
242
+ openModel(entry, languageClient);
243
+ }
244
+ for (const entry of models.values()) {
245
+ const markers = entry.ref.source === 'PROJECT' && entry.ref.target
246
+ ? state.authoritativeDiagnostics
247
+ .filter((diagnostic) => diagnostic.target === entry.ref.target &&
248
+ diagnostic.path === entry.ref.path)
249
+ .map((diagnostic) => ({
250
+ startLineNumber: diagnostic.line,
251
+ startColumn: diagnostic.column,
252
+ endLineNumber: diagnostic.endLine ?? diagnostic.line,
253
+ endColumn: diagnostic.endColumn ?? diagnostic.column + 1,
254
+ severity: publicSeverity(monaco, diagnostic.severity),
255
+ message: diagnostic.message,
256
+ code: diagnostic.code,
257
+ source: 'rustc (authoritative)',
258
+ }))
259
+ : [];
260
+ monaco.editor.setModelMarkers(entry.model, 'crowdy-rustc', markers);
261
+ }
262
+ const activeKey = state.activeFile
263
+ ? keyForRef(state.activeFile)
264
+ : null;
265
+ if (activeKey !== previousActiveKey) {
266
+ const active = activeKey ? models.get(activeKey) : undefined;
267
+ editor.setModel((active?.model ?? null));
268
+ editor.updateOptions({ readOnly: active?.ref.source !== 'PROJECT' });
269
+ previousActiveKey = activeKey;
270
+ }
271
+ },
272
+ layout() {
273
+ editor?.layout();
274
+ },
275
+ dispose() {
276
+ if (disposed)
277
+ return;
278
+ disposed = true;
279
+ for (const disposable of disposables)
280
+ disposable.dispose();
281
+ for (const entry of models.values()) {
282
+ closeModel(entry, languageClient, monaco);
283
+ }
284
+ models.clear();
285
+ localDiagnostics.clear();
286
+ languageClient?.shutdown();
287
+ editor?.dispose();
288
+ editor = null;
289
+ releaseWorkspace?.();
290
+ releaseWorkspace = null;
291
+ host.replaceChildren();
292
+ },
293
+ };
294
+ return adapter;
295
+ }
296
+ catch (error) {
297
+ for (const disposable of disposables)
298
+ disposable.dispose();
299
+ for (const entry of models.values()) {
300
+ entry.changeSubscription.dispose();
301
+ entry.model.dispose();
302
+ }
303
+ languageClient?.dispose();
304
+ if (!languageClient)
305
+ languageWorker?.terminate();
306
+ editor?.dispose();
307
+ releaseWorkspace?.();
308
+ host.replaceChildren();
309
+ throw error;
310
+ }
311
+ }
312
+ function collectFiles(state, workspaceUri) {
313
+ const projectFiles = state.project?.files.map((file) => {
314
+ const ref = {
315
+ source: 'PROJECT',
316
+ target: file.target,
317
+ path: file.path,
318
+ };
319
+ return {
320
+ key: keyForRef(ref),
321
+ ref,
322
+ content: file.content,
323
+ uri: modStudioFileUri(workspaceUri, file.target, file.path),
324
+ };
325
+ }) ?? [];
326
+ return [
327
+ ...projectFiles,
328
+ ...state.personalLibraryFiles.map((file) => collectReference(workspaceUri, file)),
329
+ ...state.commonFiles.map((file) => collectReference(workspaceUri, file)),
330
+ ];
331
+ }
332
+ function collectReference(workspaceUri, file) {
333
+ const ref = {
334
+ source: file.source,
335
+ target: file.target,
336
+ path: file.path,
337
+ referenceId: file.id,
338
+ };
339
+ const bucket = file.source === 'PERSONAL_LIBRARY' ? 'library' : 'common';
340
+ const target = file.target?.toLowerCase() ?? 'shared';
341
+ const id = file.id.replace(/[^A-Za-z0-9._-]/gu, '_') || 'file';
342
+ const path = file.path
343
+ .split('/')
344
+ .map(encodeURIComponent)
345
+ .join('/');
346
+ return {
347
+ key: keyForRef(ref),
348
+ ref,
349
+ content: file.content,
350
+ uri: `${workspaceUri}/${bucket}/${target}/${id}/${path}`,
351
+ };
352
+ }
353
+ function keyForRef(ref) {
354
+ return [
355
+ ref.source,
356
+ ref.target ?? 'SHARED',
357
+ ref.referenceId ?? '',
358
+ ref.path,
359
+ ].join(':');
360
+ }
361
+ function openModel(entry, client) {
362
+ if (!entry.ref.path.endsWith('.rs'))
363
+ return;
364
+ void client
365
+ ?.notify('textDocument/didOpen', {
366
+ textDocument: {
367
+ uri: entry.model.uri.toString(),
368
+ languageId: 'rust',
369
+ version: entry.model.getVersionId(),
370
+ text: entry.model.getValue(),
371
+ },
372
+ })
373
+ .catch(() => { });
374
+ }
375
+ function closeModel(entry, client, monaco) {
376
+ if (entry.ref.path.endsWith('.rs')) {
377
+ void client
378
+ ?.notify('textDocument/didClose', {
379
+ textDocument: { uri: entry.model.uri.toString() },
380
+ })
381
+ .catch(() => { });
382
+ }
383
+ monaco.editor.setModelMarkers(entry.model, 'crowdy-rustc', []);
384
+ monaco.editor.setModelMarkers(entry.model, 'crowdy-rust-advisory', []);
385
+ entry.changeSubscription.dispose();
386
+ entry.model.dispose();
387
+ }
388
+ export function isCurrentDiagnosticVersion(version, currentVersion) {
389
+ return (version === undefined ||
390
+ (Number.isSafeInteger(version) && version === currentVersion));
391
+ }
392
+ function lspDocumentPosition(uri, position) {
393
+ return {
394
+ textDocument: { uri },
395
+ position: {
396
+ line: position.lineNumber - 1,
397
+ character: position.column - 1,
398
+ },
399
+ };
400
+ }
401
+ function toMonacoRange(range) {
402
+ return {
403
+ startLineNumber: range.start.line + 1,
404
+ startColumn: range.start.character + 1,
405
+ endLineNumber: range.end.line + 1,
406
+ endColumn: range.end.character + 1,
407
+ };
408
+ }
409
+ function diagnosticSeverity(monaco, severity) {
410
+ return severity === 1
411
+ ? monaco.MarkerSeverity.Error
412
+ : severity === 2
413
+ ? monaco.MarkerSeverity.Warning
414
+ : severity === 3
415
+ ? monaco.MarkerSeverity.Info
416
+ : monaco.MarkerSeverity.Hint;
417
+ }
418
+ function lspSeverity(severity) {
419
+ if (severity === 1)
420
+ return 'error';
421
+ if (severity === 2)
422
+ return 'warning';
423
+ if (severity === 3)
424
+ return 'info';
425
+ return 'hint';
426
+ }
427
+ function publicSeverity(monaco, severity) {
428
+ if (severity === 'error')
429
+ return monaco.MarkerSeverity.Error;
430
+ if (severity === 'warning')
431
+ return monaco.MarkerSeverity.Warning;
432
+ if (severity === 'info')
433
+ return monaco.MarkerSeverity.Info;
434
+ return monaco.MarkerSeverity.Hint;
435
+ }
436
+ function completionKind(monaco, kind) {
437
+ const kinds = monaco.languages.CompletionItemKind;
438
+ return ({
439
+ 3: kinds.Function,
440
+ 5: kinds.Field,
441
+ 6: kinds.Variable,
442
+ 7: kinds.Class,
443
+ 9: kinds.Module,
444
+ 13: kinds.Enum,
445
+ 14: kinds.Keyword,
446
+ 15: kinds.Snippet,
447
+ 21: kinds.Constant,
448
+ }[kind] ?? kinds.Text);
449
+ }
450
+ function symbolKind(monaco, kind) {
451
+ const kinds = monaco.languages.SymbolKind;
452
+ return ({
453
+ 2: kinds.Module,
454
+ 5: kinds.Class,
455
+ 6: kinds.Method,
456
+ 10: kinds.Enum,
457
+ 11: kinds.Interface,
458
+ 12: kinds.Function,
459
+ 13: kinds.Variable,
460
+ 14: kinds.Constant,
461
+ 23: kinds.Struct,
462
+ }[kind] ?? kinds.Object);
463
+ }
464
+ function languageFor(path) {
465
+ if (path.endsWith('.rs'))
466
+ return 'rust';
467
+ if (path.endsWith('.toml'))
468
+ return 'toml';
469
+ if (path.endsWith('.json'))
470
+ return 'json';
471
+ return 'plaintext';
472
+ }
473
+ function registerRustLanguages(monaco) {
474
+ if (rustLanguagesRegistered)
475
+ return;
476
+ monaco.languages.register({ id: 'rust', extensions: ['.rs'] });
477
+ installRustSyntaxHighlighting(monaco);
478
+ monaco.languages.register({ id: 'toml', extensions: ['.toml'] });
479
+ rustLanguagesRegistered = true;
480
+ }
481
+ function installRustSyntaxHighlighting(monaco) {
482
+ const keywords = new Set([
483
+ 'as', 'async', 'await', 'break', 'const', 'continue', 'crate', 'dyn',
484
+ 'else', 'enum', 'extern', 'false', 'fn', 'for', 'if', 'impl', 'in',
485
+ 'let', 'loop', 'match', 'mod', 'move', 'mut', 'pub', 'ref', 'return',
486
+ 'self', 'static', 'struct', 'super', 'trait', 'true', 'type', 'unsafe',
487
+ 'use', 'where', 'while',
488
+ ]);
489
+ const types = new Set([
490
+ 'Self', 'String', 'Vec', 'Option', 'Result', 'bool', 'char', 'str',
491
+ 'usize', 'isize', 'u8', 'u16', 'u32', 'u64', 'u128', 'i8', 'i16',
492
+ 'i32', 'i64', 'i128', 'f32', 'f64',
493
+ ]);
494
+ const state = {
495
+ clone() {
496
+ return this;
497
+ },
498
+ equals(other) {
499
+ return other === this;
500
+ },
501
+ };
502
+ monaco.languages.setTokensProvider('rust', {
503
+ getInitialState: () => state,
504
+ tokenize: (line, currentState) => {
505
+ const tokens = [];
506
+ let index = 0;
507
+ while (index < line.length) {
508
+ const rest = line.slice(index);
509
+ let match;
510
+ if ((match = rest.match(/^\s+/u))) {
511
+ tokens.push({ startIndex: index, scopes: '' });
512
+ }
513
+ else if ((match = rest.match(/^\/\/.*$/u))) {
514
+ tokens.push({ startIndex: index, scopes: 'comment.rust' });
515
+ }
516
+ else if ((match = rest.match(/^\/\*.*?(?:\*\/|$)/u))) {
517
+ tokens.push({ startIndex: index, scopes: 'comment.rust' });
518
+ }
519
+ else if ((match = rest.match(/^"(?:\\.|[^"\\])*"?/u))) {
520
+ tokens.push({ startIndex: index, scopes: 'string.rust' });
521
+ }
522
+ else if ((match = rest.match(/^(?:0x[\da-fA-F_]+|\d[\d_]*)/u))) {
523
+ tokens.push({ startIndex: index, scopes: 'number.rust' });
524
+ }
525
+ else if ((match = rest.match(/^[A-Za-z_]\w*!/u))) {
526
+ tokens.push({ startIndex: index, scopes: 'macro.rust' });
527
+ }
528
+ else if ((match = rest.match(/^[A-Za-z_]\w*/u))) {
529
+ const word = match[0];
530
+ tokens.push({
531
+ startIndex: index,
532
+ scopes: keywords.has(word)
533
+ ? 'keyword.rust'
534
+ : types.has(word)
535
+ ? 'type.rust'
536
+ : 'identifier.rust',
537
+ });
538
+ }
539
+ else {
540
+ match = rest.match(/^./u);
541
+ tokens.push({ startIndex: index, scopes: 'delimiter.rust' });
542
+ }
543
+ index += match?.[0].length ?? 1;
544
+ }
545
+ return { tokens, endState: currentState };
546
+ },
547
+ });
548
+ }
@@ -0,0 +1,17 @@
1
+ import { ModStudioController, type ModStudioControllerOptions } from './controller.js';
2
+ import type { ModStudioEditorMode } from './editor.js';
3
+ import { type MonacoModStudioEditorOptions } from './monaco-editor.js';
4
+ export interface MountModStudioOptions extends ModStudioControllerOptions, MonacoModStudioEditorOptions {
5
+ }
6
+ export interface ModStudioHandle {
7
+ controller: ModStudioController;
8
+ editorMode: ModStudioEditorMode;
9
+ destroy(): void;
10
+ }
11
+ /**
12
+ * Mount the project-first Mod Studio. Monaco and its browser Rust worker are
13
+ * loaded lazily; any editor/worker/WASM startup failure keeps the full project
14
+ * UI and swaps in the target/file-aware textarea editor.
15
+ */
16
+ export declare function mountModStudio(host: HTMLElement, options: MountModStudioOptions): Promise<ModStudioHandle>;
17
+ //# sourceMappingURL=mount.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../../src/mod-studio/mount.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,KAAK,0BAA0B,EAChC,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAGV,mBAAmB,EACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,oBAAoB,CAAC;AAG5B,MAAM,WAAW,qBACf,SAAQ,0BAA0B,EAChC,4BAA4B;CAAG;AAEnC,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,mBAAmB,CAAC;IAChC,UAAU,EAAE,mBAAmB,CAAC;IAChC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC,CAgG1B"}
@@ -0,0 +1,86 @@
1
+ import { ModStudioController, } from './controller.js';
2
+ import { ModStudioDomShell } from './dom-shell.js';
3
+ import { createMonacoModStudioEditor, } from './monaco-editor.js';
4
+ import { createTextareaModStudioEditor } from './textarea-editor.js';
5
+ /**
6
+ * Mount the project-first Mod Studio. Monaco and its browser Rust worker are
7
+ * loaded lazily; any editor/worker/WASM startup failure keeps the full project
8
+ * UI and swaps in the target/file-aware textarea editor.
9
+ */
10
+ export async function mountModStudio(host, options) {
11
+ if (typeof document === 'undefined') {
12
+ throw new Error('mountModStudio requires a DOM document');
13
+ }
14
+ const controller = new ModStudioController(options);
15
+ const shell = new ModStudioDomShell(host, controller);
16
+ let editor = null;
17
+ let destroyed = false;
18
+ let recoveringEditor = false;
19
+ const callbacks = {
20
+ onProjectFileChange: (target, path, content) => controller.updateFile(target, path, content),
21
+ onLocalDiagnostics: (diagnostics) => controller.setLocalDiagnostics(diagnostics),
22
+ onOpenFile: (ref) => controller.openFile(ref),
23
+ onFailure: (error) => {
24
+ queueMicrotask(() => {
25
+ if (destroyed ||
26
+ recoveringEditor ||
27
+ editor?.mode !== 'monaco') {
28
+ return;
29
+ }
30
+ recoveringEditor = true;
31
+ console.warn('Mod Studio Rust worker failed; switching to the file-aware fallback', error);
32
+ editor.dispose();
33
+ controller.setLocalDiagnostics([]);
34
+ editor = createTextareaModStudioEditor(shell.editorHost, callbacks);
35
+ editor.sync(controller.getState());
36
+ recoveringEditor = false;
37
+ });
38
+ },
39
+ };
40
+ const unsubscribe = controller.subscribe((state) => {
41
+ shell.render(state);
42
+ editor?.sync(state);
43
+ });
44
+ const onVisibilityChange = () => {
45
+ controller.setPageVisible(document.visibilityState !== 'hidden');
46
+ };
47
+ document.addEventListener('visibilitychange', onVisibilityChange);
48
+ onVisibilityChange();
49
+ try {
50
+ await controller.initialize();
51
+ try {
52
+ editor = await createMonacoModStudioEditor(shell.editorHost, options, callbacks);
53
+ }
54
+ catch (error) {
55
+ console.warn('Mod Studio Monaco editor unavailable; using the file-aware fallback', error);
56
+ editor = createTextareaModStudioEditor(shell.editorHost, callbacks);
57
+ }
58
+ editor.sync(controller.getState());
59
+ }
60
+ catch (error) {
61
+ document.removeEventListener('visibilitychange', onVisibilityChange);
62
+ unsubscribe();
63
+ const failedEditor = editor;
64
+ failedEditor?.dispose();
65
+ shell.dispose();
66
+ controller.destroy();
67
+ throw error;
68
+ }
69
+ return {
70
+ controller,
71
+ get editorMode() {
72
+ return editor?.mode ?? 'textarea';
73
+ },
74
+ destroy() {
75
+ if (destroyed)
76
+ return;
77
+ destroyed = true;
78
+ document.removeEventListener('visibilitychange', onVisibilityChange);
79
+ unsubscribe();
80
+ editor?.dispose();
81
+ editor = null;
82
+ shell.dispose();
83
+ controller.destroy();
84
+ },
85
+ };
86
+ }
@@ -0,0 +1,11 @@
1
+ import { type CreateModStudioProjectInput, type ModStudioProjectKind } from './models.js';
2
+ export interface ModStudioNewProjectOptions {
3
+ appId: string;
4
+ gridId: string;
5
+ name: string;
6
+ kind: ModStudioProjectKind;
7
+ description?: string;
8
+ }
9
+ /** Create a compile-oriented starter without introducing a raw JSON source map. */
10
+ export declare function createModStudioStarterProject(options: ModStudioNewProjectOptions): CreateModStudioProjectInput;
11
+ //# sourceMappingURL=starter-projects.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"starter-projects.d.ts","sourceRoot":"","sources":["../../src/mod-studio/starter-projects.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,2BAA2B,EAEhC,KAAK,oBAAoB,EAG1B,MAAM,aAAa,CAAC;AAIrB,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,oBAAoB,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,mFAAmF;AACnF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,0BAA0B,GAClC,2BAA2B,CA2B7B"}