@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.
- package/MIGRATION.md +56 -34
- package/README.md +83 -50
- package/dist/crowdy-client.d.ts +3 -0
- package/dist/crowdy-client.d.ts.map +1 -1
- package/dist/crowdy-client.js +2 -0
- package/dist/domains/playerCodeProjects.d.ts +25 -0
- package/dist/domains/playerCodeProjects.d.ts.map +1 -0
- package/dist/domains/playerCodeProjects.js +299 -0
- package/dist/domains/playerCompute.d.ts +1 -1
- package/dist/domains/playerCompute.js +1 -1
- package/dist/generated/graphql.d.ts +792 -7
- package/dist/generated/graphql.d.ts.map +1 -1
- package/dist/generated/graphql.js +57 -0
- package/dist/index.d.ts +3 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -5
- package/dist/kit/npcs.d.ts.map +1 -1
- package/dist/kit/social.d.ts.map +1 -1
- package/dist/live-coding/rust-analysis.d.ts.map +1 -1
- package/dist/live-coding/rust-analysis.js +64 -1
- package/dist/live-coding/worker-transport.d.ts +3 -0
- package/dist/live-coding/worker-transport.d.ts.map +1 -1
- package/dist/live-coding/worker-transport.js +13 -0
- package/dist/mod-studio/controller.d.ts +188 -0
- package/dist/mod-studio/controller.d.ts.map +1 -0
- package/dist/mod-studio/controller.js +915 -0
- package/dist/mod-studio/diagnostics.d.ts +22 -0
- package/dist/mod-studio/diagnostics.d.ts.map +1 -0
- package/dist/mod-studio/diagnostics.js +141 -0
- package/dist/mod-studio/dom-shell.d.ts +55 -0
- package/dist/mod-studio/dom-shell.d.ts.map +1 -0
- package/dist/mod-studio/dom-shell.js +527 -0
- package/dist/mod-studio/editor.d.ts +17 -0
- package/dist/mod-studio/editor.d.ts.map +1 -0
- package/dist/mod-studio/editor.js +1 -0
- package/dist/mod-studio/index.d.ts +10 -0
- package/dist/mod-studio/index.d.ts.map +1 -0
- package/dist/mod-studio/index.js +9 -0
- package/dist/mod-studio/models.d.ts +149 -0
- package/dist/mod-studio/models.d.ts.map +1 -0
- package/dist/mod-studio/models.js +62 -0
- package/dist/mod-studio/monaco-editor.d.ts +16 -0
- package/dist/mod-studio/monaco-editor.d.ts.map +1 -0
- package/dist/mod-studio/monaco-editor.js +548 -0
- package/dist/mod-studio/mount.d.ts +17 -0
- package/dist/mod-studio/mount.d.ts.map +1 -0
- package/dist/mod-studio/mount.js +86 -0
- package/dist/mod-studio/starter-projects.d.ts +11 -0
- package/dist/mod-studio/starter-projects.d.ts.map +1 -0
- package/dist/mod-studio/starter-projects.js +102 -0
- package/dist/mod-studio/styles.d.ts +2 -0
- package/dist/mod-studio/styles.d.ts.map +1 -0
- package/dist/mod-studio/styles.js +14 -0
- package/dist/mod-studio/textarea-editor.d.ts +8 -0
- package/dist/mod-studio/textarea-editor.d.ts.map +1 -0
- package/dist/mod-studio/textarea-editor.js +71 -0
- package/package.json +4 -4
- package/dist/live-coding/ide.d.ts +0 -23
- package/dist/live-coding/ide.d.ts.map +0 -1
- package/dist/live-coding/ide.js +0 -514
- package/dist/live-coding/index.d.ts +0 -8
- package/dist/live-coding/index.d.ts.map +0 -1
- package/dist/live-coding/index.js +0 -7
- package/dist/live-coding/live-coding-controller.d.ts +0 -75
- package/dist/live-coding/live-coding-controller.d.ts.map +0 -1
- package/dist/live-coding/live-coding-controller.js +0 -141
- package/dist/live-coding/mount.d.ts +0 -26
- package/dist/live-coding/mount.d.ts.map +0 -1
- package/dist/live-coding/mount.js +0 -108
- package/dist/live-coding/templates.d.ts +0 -17
- package/dist/live-coding/templates.d.ts.map +0 -1
- 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"}
|