@mirascript/monaco 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.
- package/dist/basic/index.d.ts +6 -0
- package/dist/basic/index.d.ts.map +1 -0
- package/dist/basic/index.js +10 -0
- package/dist/basic/index.js.map +6 -0
- package/dist/basic/language-configuration.d.ts +5 -0
- package/dist/basic/language-configuration.d.ts.map +1 -0
- package/dist/basic/tokens-provider.d.ts +4 -0
- package/dist/basic/tokens-provider.d.ts.map +1 -0
- package/dist/chunk-CEFEXBF7.js +65 -0
- package/dist/chunk-CEFEXBF7.js.map +6 -0
- package/dist/chunk-PTNWRTNM.js +474 -0
- package/dist/chunk-PTNWRTNM.js.map +6 -0
- package/dist/constants.d.ts +20 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/contribute.d.ts +3 -0
- package/dist/contribute.d.ts.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +83 -0
- package/dist/index.js.map +6 -0
- package/dist/lsp/compile-result.d.ts +150 -0
- package/dist/lsp/compile-result.d.ts.map +1 -0
- package/dist/lsp/diagnostics.d.ts +5 -0
- package/dist/lsp/diagnostics.d.ts.map +1 -0
- package/dist/lsp/index.d.ts +21 -0
- package/dist/lsp/index.d.ts.map +1 -0
- package/dist/lsp/index.js +2285 -0
- package/dist/lsp/index.js.map +6 -0
- package/dist/lsp/providers/base.d.ts +21 -0
- package/dist/lsp/providers/base.d.ts.map +1 -0
- package/dist/lsp/providers/code-action-provider.d.ts +12 -0
- package/dist/lsp/providers/code-action-provider.d.ts.map +1 -0
- package/dist/lsp/providers/color-provider.d.ts +10 -0
- package/dist/lsp/providers/color-provider.d.ts.map +1 -0
- package/dist/lsp/providers/completion-item-provider.d.ts +21 -0
- package/dist/lsp/providers/completion-item-provider.d.ts.map +1 -0
- package/dist/lsp/providers/definition-reference-provider.d.ts +16 -0
- package/dist/lsp/providers/definition-reference-provider.d.ts.map +1 -0
- package/dist/lsp/providers/document-highlight-provider.d.ts +12 -0
- package/dist/lsp/providers/document-highlight-provider.d.ts.map +1 -0
- package/dist/lsp/providers/document-symbol-provider.d.ts +10 -0
- package/dist/lsp/providers/document-symbol-provider.d.ts.map +1 -0
- package/dist/lsp/providers/formatter-provider.d.ts +18 -0
- package/dist/lsp/providers/formatter-provider.d.ts.map +1 -0
- package/dist/lsp/providers/hover-provider.d.ts +12 -0
- package/dist/lsp/providers/hover-provider.d.ts.map +1 -0
- package/dist/lsp/providers/inlay-hints-provider.d.ts +10 -0
- package/dist/lsp/providers/inlay-hints-provider.d.ts.map +1 -0
- package/dist/lsp/providers/range-provider.d.ts +10 -0
- package/dist/lsp/providers/range-provider.d.ts.map +1 -0
- package/dist/lsp/providers/rename-provider.d.ts +12 -0
- package/dist/lsp/providers/rename-provider.d.ts.map +1 -0
- package/dist/lsp/providers/semantic-tokens-provider.d.ts +12 -0
- package/dist/lsp/providers/semantic-tokens-provider.d.ts.map +1 -0
- package/dist/lsp/providers/signature-help-provider.d.ts +12 -0
- package/dist/lsp/providers/signature-help-provider.d.ts.map +1 -0
- package/dist/lsp/utils.d.ts +37 -0
- package/dist/lsp/utils.d.ts.map +1 -0
- package/dist/lsp/worker-helper.d.ts +5 -0
- package/dist/lsp/worker-helper.d.ts.map +1 -0
- package/dist/lsp/worker.d.ts +22 -0
- package/dist/lsp/worker.d.ts.map +1 -0
- package/dist/lsp/worker.js +62 -0
- package/dist/lsp/worker.js.map +6 -0
- package/dist/monaco-api.d.ts +16 -0
- package/dist/monaco-api.d.ts.map +1 -0
- package/package.json +39 -0
- package/src/basic/index.ts +11 -0
- package/src/basic/language-configuration.ts +90 -0
- package/src/basic/tokens-provider.ts +358 -0
- package/src/constants.ts +56 -0
- package/src/contribute.ts +18 -0
- package/src/index.ts +71 -0
- package/src/lsp/compile-result.ts +518 -0
- package/src/lsp/diagnostics.ts +83 -0
- package/src/lsp/index.ts +84 -0
- package/src/lsp/providers/base.ts +40 -0
- package/src/lsp/providers/code-action-provider.ts +28 -0
- package/src/lsp/providers/color-provider.ts +129 -0
- package/src/lsp/providers/completion-item-provider.ts +497 -0
- package/src/lsp/providers/definition-reference-provider.ts +107 -0
- package/src/lsp/providers/document-highlight-provider.ts +71 -0
- package/src/lsp/providers/document-symbol-provider.ts +65 -0
- package/src/lsp/providers/formatter-provider.ts +81 -0
- package/src/lsp/providers/hover-provider.ts +150 -0
- package/src/lsp/providers/inlay-hints-provider.ts +121 -0
- package/src/lsp/providers/range-provider.ts +37 -0
- package/src/lsp/providers/rename-provider.ts +144 -0
- package/src/lsp/providers/semantic-tokens-provider.ts +166 -0
- package/src/lsp/providers/signature-help-provider.ts +119 -0
- package/src/lsp/utils.ts +322 -0
- package/src/lsp/worker-helper.ts +119 -0
- package/src/lsp/worker.ts +83 -0
- package/src/monaco-api.js +66 -0
- package/src/monaco-api.ts +18 -0
package/src/lsp/utils.ts
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { DiagnosticCode } from '@mirascript/wasm';
|
|
2
|
+
import { type editor, Range, type IPosition, type IRange } from '../monaco-api.js';
|
|
3
|
+
import {
|
|
4
|
+
getVmFunctionInfo,
|
|
5
|
+
isVmArray,
|
|
6
|
+
isVmExtern,
|
|
7
|
+
isVmFunction,
|
|
8
|
+
isVmModule,
|
|
9
|
+
isVmPrimitive,
|
|
10
|
+
isVmRecord,
|
|
11
|
+
serialize,
|
|
12
|
+
type VmAny,
|
|
13
|
+
type VmFunctionInfo,
|
|
14
|
+
type VmValue,
|
|
15
|
+
} from '@mirascript/mirascript';
|
|
16
|
+
import { operations, serializePropName, serializeString } from '@mirascript/mirascript/subtle';
|
|
17
|
+
import type { LocalDefinition } from './compile-result.js';
|
|
18
|
+
|
|
19
|
+
/** 参数签名 */
|
|
20
|
+
export type ParamSignature = [name: string, sig: string, doc: string];
|
|
21
|
+
|
|
22
|
+
/** 生成参数签名 */
|
|
23
|
+
function globalParamsSignature(info: VmFunctionInfo | undefined): ParamSignature[] {
|
|
24
|
+
if (info == null || (!info.params && !info.paramsType)) return [['..', '..', '']];
|
|
25
|
+
const paramItems: ParamSignature[] = [];
|
|
26
|
+
const params = Object.keys(info.paramsType ?? {});
|
|
27
|
+
for (const key of Object.keys(info.params ?? {})) {
|
|
28
|
+
if (params.includes(key)) continue;
|
|
29
|
+
params.push(key);
|
|
30
|
+
}
|
|
31
|
+
for (const key of params) {
|
|
32
|
+
const type = info.paramsType?.[key] ?? '';
|
|
33
|
+
const doc = info.params?.[key] ?? '';
|
|
34
|
+
paramItems.push([key, type ? `${key}: ${type}` : key, doc ? `\`${key}\`: ${doc}` : '']);
|
|
35
|
+
}
|
|
36
|
+
return paramItems;
|
|
37
|
+
}
|
|
38
|
+
const SIG_WIDTH = 60;
|
|
39
|
+
/** 生成函数签名 */
|
|
40
|
+
export function fnSignature(
|
|
41
|
+
id: string | undefined,
|
|
42
|
+
info: VmFunctionInfo,
|
|
43
|
+
): {
|
|
44
|
+
params: ParamSignature[];
|
|
45
|
+
returns: string;
|
|
46
|
+
/** @inheritdoc */
|
|
47
|
+
toString(): string;
|
|
48
|
+
} {
|
|
49
|
+
const prefix = id ? `fn ${id}` : 'fn';
|
|
50
|
+
const params = globalParamsSignature(info);
|
|
51
|
+
const returns = info.returnsType ? ` -> ${info.returnsType}` : '';
|
|
52
|
+
return {
|
|
53
|
+
params,
|
|
54
|
+
returns,
|
|
55
|
+
toString() {
|
|
56
|
+
let p;
|
|
57
|
+
if (
|
|
58
|
+
this.params.length >= 1 &&
|
|
59
|
+
(prefix.length + this.returns.length > SIG_WIDTH ||
|
|
60
|
+
prefix.length + this.returns.length + params.reduce((a, b) => a + b[1].length, 0) > SIG_WIDTH)
|
|
61
|
+
) {
|
|
62
|
+
p = `(\n${params.map((item) => ` ${item[1]},`).join('\n')}\n)`;
|
|
63
|
+
} else {
|
|
64
|
+
p = `(${params.map((item) => item[1]).join(', ')})`;
|
|
65
|
+
}
|
|
66
|
+
return `${prefix}${p}${this.returns}`;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** 生成函数参数 */
|
|
71
|
+
export function localParamSignature(
|
|
72
|
+
model: editor.ITextModel,
|
|
73
|
+
info: NonNullable<LocalDefinition['fn']>,
|
|
74
|
+
): ParamSignature[] {
|
|
75
|
+
const {
|
|
76
|
+
args,
|
|
77
|
+
scope: { params },
|
|
78
|
+
} = info;
|
|
79
|
+
if (params[0]?.code === DiagnosticCode.ParameterIt) {
|
|
80
|
+
return params[0].references.length ? [['it', 'it', '']] : [];
|
|
81
|
+
}
|
|
82
|
+
return params.map((a, i) => {
|
|
83
|
+
const rest =
|
|
84
|
+
a.code === DiagnosticCode.ParameterRestPattern ||
|
|
85
|
+
a.code === DiagnosticCode.ParameterMutableRest ||
|
|
86
|
+
a.code === DiagnosticCode.ParameterImmutableRest;
|
|
87
|
+
const argsInParam = args.filter((arg) => Range.containsRange(a.range, arg.definition.range));
|
|
88
|
+
const argName =
|
|
89
|
+
argsInParam.length === 0
|
|
90
|
+
? `arg_${i}`
|
|
91
|
+
: argsInParam.map((arg) => model.getValueInRange(arg.definition.range)).join('_');
|
|
92
|
+
if (rest) return [`..${argName}`, `..${argName}`, ''];
|
|
93
|
+
return [argName, argName, ''];
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** 生成函数参数列表 */
|
|
98
|
+
export function paramsList(model: editor.ITextModel, info: VmFunctionInfo | LocalDefinition['fn'] | undefined): string {
|
|
99
|
+
if (!info) return '(..)';
|
|
100
|
+
if ('scope' in info) {
|
|
101
|
+
return `(${localParamSignature(model, info)
|
|
102
|
+
.map((p) => p[1])
|
|
103
|
+
.join(', ')})`;
|
|
104
|
+
} else {
|
|
105
|
+
if (!info.params) return '(..)';
|
|
106
|
+
const paramItems = Object.keys(info.params).join(', ');
|
|
107
|
+
return `(${paramItems})`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** 生成函数文档 */
|
|
112
|
+
export function globalFnDoc(info: VmFunctionInfo): string[] {
|
|
113
|
+
const doc = [];
|
|
114
|
+
if (info.summary) {
|
|
115
|
+
doc.push(info.summary);
|
|
116
|
+
}
|
|
117
|
+
const paramDoc = [];
|
|
118
|
+
if (info.params) {
|
|
119
|
+
for (const [key, value] of Object.entries(info.params)) {
|
|
120
|
+
if (!value) continue;
|
|
121
|
+
paramDoc.push(`- \`${key}\`: ${value}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (info.returns) {
|
|
125
|
+
paramDoc.push(`- **返回值**: ${info.returns}`);
|
|
126
|
+
}
|
|
127
|
+
if (paramDoc.length) {
|
|
128
|
+
doc.push(paramDoc.join('\n'));
|
|
129
|
+
}
|
|
130
|
+
if (info.examples?.length) {
|
|
131
|
+
let exp = `### 示例`;
|
|
132
|
+
for (const example of info.examples) {
|
|
133
|
+
exp += codeblock(example);
|
|
134
|
+
}
|
|
135
|
+
doc.push(exp);
|
|
136
|
+
}
|
|
137
|
+
return doc;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** 获取代码块格式化字符串 */
|
|
141
|
+
export function codeblock(value: string): string {
|
|
142
|
+
const includeFences = /`{3,}/.exec(value);
|
|
143
|
+
const CODEBLOCK_FENCE = includeFences ? '`'.repeat(includeFences[0].length + 1) : '```';
|
|
144
|
+
return `\n${CODEBLOCK_FENCE}mirascript\n${value}\n${CODEBLOCK_FENCE}\n`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** 检查位置是否在范围内,且范围非空 */
|
|
148
|
+
export function strictContainsPosition(range: IRange, position: IPosition): boolean {
|
|
149
|
+
return !Range.isEmpty(range) && Range.containsPosition(range, position);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** 获取单词 */
|
|
153
|
+
export function wordAt(model: editor.ITextModel, position: IPosition): { word: string; range: Range } | undefined {
|
|
154
|
+
const word = model.getWordAtPosition(position);
|
|
155
|
+
if (!word) return undefined;
|
|
156
|
+
const range = new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn);
|
|
157
|
+
return { word: word.word, range };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** 将值序列化为便于展示的字符串 */
|
|
161
|
+
function serializeForDisplayInner(value: VmValue, maxWidth: number): string {
|
|
162
|
+
if (maxWidth < 10) maxWidth = 10;
|
|
163
|
+
if (typeof value === 'string') {
|
|
164
|
+
if (value.length < maxWidth) {
|
|
165
|
+
return serializeString(value);
|
|
166
|
+
}
|
|
167
|
+
return `${serializeString(value.slice(0, maxWidth))}..`;
|
|
168
|
+
}
|
|
169
|
+
if (isVmPrimitive(value)) {
|
|
170
|
+
return serialize(value);
|
|
171
|
+
}
|
|
172
|
+
if (isVmArray(value)) {
|
|
173
|
+
const len = value.length;
|
|
174
|
+
if (!len) return '[]';
|
|
175
|
+
return `[../* x${len} */]`;
|
|
176
|
+
}
|
|
177
|
+
if (isVmRecord(value)) {
|
|
178
|
+
const len = Object.keys(value).length;
|
|
179
|
+
if (!len) return '()';
|
|
180
|
+
return `(../* x${len} */)`;
|
|
181
|
+
}
|
|
182
|
+
return `/* ${operations.$ToString(value)} */`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** 将值序列化为便于展示的字符串 */
|
|
186
|
+
export function serializeForDisplay(value: VmValue, maxEntries = 100, maxWidth = 40): string {
|
|
187
|
+
if (isVmPrimitive(value) || isVmFunction(value)) {
|
|
188
|
+
return serializeForDisplayInner(value, maxWidth);
|
|
189
|
+
}
|
|
190
|
+
let begin, end;
|
|
191
|
+
const entries = [];
|
|
192
|
+
let resultLength = 0;
|
|
193
|
+
if (isVmArray(value)) {
|
|
194
|
+
begin = '[';
|
|
195
|
+
end = ']';
|
|
196
|
+
for (const v of value) {
|
|
197
|
+
if (entries.length > maxEntries) {
|
|
198
|
+
entries.push(`../* x${value.length - entries.length} */`);
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
const entry = serializeForDisplayInner(v ?? null, maxWidth - 2);
|
|
202
|
+
entries.push(entry);
|
|
203
|
+
resultLength += entry.length;
|
|
204
|
+
}
|
|
205
|
+
} else if (isVmRecord(value)) {
|
|
206
|
+
begin = '(';
|
|
207
|
+
end = ')';
|
|
208
|
+
const e = Object.entries(value);
|
|
209
|
+
for (const [key, value] of e) {
|
|
210
|
+
if (entries.length > maxEntries) {
|
|
211
|
+
entries.push(`../* x${e.length - entries.length} */`);
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
const sk = serializePropName(key);
|
|
215
|
+
const entry = `${sk}: ${serializeForDisplayInner(value ?? null, maxWidth - sk.length - 4)}`;
|
|
216
|
+
entries.push(entry);
|
|
217
|
+
resultLength += entry.length;
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
const hint = serializeForDisplayInner(value, 100);
|
|
221
|
+
const isArray = isVmExtern(value) && Array.isArray(value.value);
|
|
222
|
+
begin = `${hint} ${isArray ? '[' : '('}`;
|
|
223
|
+
end = isArray ? ']' : ')';
|
|
224
|
+
const keys = value.keys();
|
|
225
|
+
for (const [index, key] of keys.entries()) {
|
|
226
|
+
if (entries.length > maxEntries) {
|
|
227
|
+
entries.push(`../* x${keys.length - entries.length} */`);
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
let entry;
|
|
231
|
+
if (isArray && String(index) === key) {
|
|
232
|
+
// 数组索引
|
|
233
|
+
entry = serializeForDisplayInner(value.get(key) ?? null, maxWidth - 2);
|
|
234
|
+
} else {
|
|
235
|
+
const sk = serializePropName(key);
|
|
236
|
+
entry = `${sk}: ${serializeForDisplayInner(value.get(key) ?? null, maxWidth - sk.length - 4)}`;
|
|
237
|
+
}
|
|
238
|
+
entries.push(entry);
|
|
239
|
+
resultLength += entry.length;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (resultLength >= maxWidth) {
|
|
243
|
+
return `${begin}\n ${entries.join(',\n ')}\n${end}`;
|
|
244
|
+
}
|
|
245
|
+
return `${begin}${entries.join(', ')}${end}`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** 获取变量文档 */
|
|
249
|
+
export function valueDoc(
|
|
250
|
+
name: string,
|
|
251
|
+
value: VmAny,
|
|
252
|
+
type: 'field' | 'declare' | 'hint',
|
|
253
|
+
): { script: string; doc: string[] } {
|
|
254
|
+
const info = getVmFunctionInfo(value);
|
|
255
|
+
if (info) {
|
|
256
|
+
return {
|
|
257
|
+
script: fnSignature(name, info).toString() + (type === 'declare' ? ';' : ''),
|
|
258
|
+
doc: globalFnDoc(info),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
let prefix;
|
|
262
|
+
let suffix = '';
|
|
263
|
+
if (type === 'hint') {
|
|
264
|
+
prefix = `${name} = `;
|
|
265
|
+
} else if (type === 'declare') {
|
|
266
|
+
if (name.startsWith('@')) {
|
|
267
|
+
prefix = `const ${name} = `;
|
|
268
|
+
} else {
|
|
269
|
+
prefix = `let ${name} = `;
|
|
270
|
+
}
|
|
271
|
+
suffix = ';';
|
|
272
|
+
} else if (/^\d/.test(name)) {
|
|
273
|
+
prefix = `[${name}]: `;
|
|
274
|
+
} else {
|
|
275
|
+
prefix = `${name}: `;
|
|
276
|
+
}
|
|
277
|
+
if (isVmModule(value)) {
|
|
278
|
+
const doc = `模块 \`${value.name}\``;
|
|
279
|
+
let script;
|
|
280
|
+
if (type === 'declare') {
|
|
281
|
+
const exports = value.keys();
|
|
282
|
+
script = '\n';
|
|
283
|
+
for (const k of exports) {
|
|
284
|
+
const v = value.get(k);
|
|
285
|
+
const vDoc = valueDoc(k, v, isVmModule(v) ? 'field' : 'declare');
|
|
286
|
+
const code = [
|
|
287
|
+
`/**`,
|
|
288
|
+
...vDoc.doc.flatMap((sec) => sec.split('\n')).map((line) => ` * ${line}`),
|
|
289
|
+
` */`,
|
|
290
|
+
'export ' + vDoc.script,
|
|
291
|
+
'',
|
|
292
|
+
'',
|
|
293
|
+
];
|
|
294
|
+
script += code.join('\n');
|
|
295
|
+
}
|
|
296
|
+
script = script.trimEnd();
|
|
297
|
+
} else {
|
|
298
|
+
script = `(module) ${value.name}`;
|
|
299
|
+
if (value.name !== name) {
|
|
300
|
+
script = `${prefix}${script}`;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return { script, doc: doc ? [doc] : [] };
|
|
304
|
+
}
|
|
305
|
+
let valueStr;
|
|
306
|
+
if (value === undefined) {
|
|
307
|
+
valueStr = '/* ... */';
|
|
308
|
+
} else {
|
|
309
|
+
valueStr = serializeForDisplay(value, type === 'declare' ? 1000 : 100, type === 'declare' ? 80 : 40);
|
|
310
|
+
}
|
|
311
|
+
return { script: `${prefix}${valueStr}${suffix}`, doc: [] };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** 获取深层属性 */
|
|
315
|
+
export function getDeep(value: VmAny, path: readonly string[]): VmValue {
|
|
316
|
+
let current: VmAny = value;
|
|
317
|
+
for (const key of path) {
|
|
318
|
+
if (current == null) return null;
|
|
319
|
+
current = operations.$Get(current, key);
|
|
320
|
+
}
|
|
321
|
+
return current ?? null;
|
|
322
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { editor } from '../monaco-api.js';
|
|
2
|
+
import type { Ready, CacheKey, Req, Res, ResOk } from './worker.js';
|
|
3
|
+
import { CompileResult } from './compile-result.js';
|
|
4
|
+
import { setMarkers } from './diagnostics.js';
|
|
5
|
+
|
|
6
|
+
/** 缓存 */
|
|
7
|
+
type CacheValue = {
|
|
8
|
+
readonly version: number;
|
|
9
|
+
readonly result: Promise<CompileResult>;
|
|
10
|
+
lastAccess: number;
|
|
11
|
+
};
|
|
12
|
+
/** 编译结果缓存,避免重复编译 */
|
|
13
|
+
const cache = new Map<CacheKey, CacheValue>();
|
|
14
|
+
let worker: Promise<Worker> | undefined = undefined;
|
|
15
|
+
|
|
16
|
+
// 清理缓存
|
|
17
|
+
const CACHE_MAX_AGE = 30000;
|
|
18
|
+
setInterval(() => {
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
for (const [key, { lastAccess }] of cache) {
|
|
21
|
+
if (now - lastAccess > CACHE_MAX_AGE) {
|
|
22
|
+
cache.delete(key);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}, CACHE_MAX_AGE);
|
|
26
|
+
|
|
27
|
+
/** 使用 worker 进行编译 */
|
|
28
|
+
async function compileWorker(req: Req): Promise<CompileResult> {
|
|
29
|
+
if (!worker) {
|
|
30
|
+
const w = new Worker(new URL('#lsp/worker', import.meta.url), {
|
|
31
|
+
type: 'module',
|
|
32
|
+
name: '@mirascript/lsp-server',
|
|
33
|
+
});
|
|
34
|
+
worker = new Promise((resolve, reject) => {
|
|
35
|
+
const onError = (e: ErrorEvent) => {
|
|
36
|
+
cleanUp();
|
|
37
|
+
reject(new Error(`Worker failed to start: ${e.message}`));
|
|
38
|
+
};
|
|
39
|
+
const onMessage = (e: MessageEvent<Ready | Error>) => {
|
|
40
|
+
if (e.data === 'mirascript lsp ready') {
|
|
41
|
+
cleanUp();
|
|
42
|
+
resolve(w);
|
|
43
|
+
} else if (e.data instanceof Error) {
|
|
44
|
+
cleanUp();
|
|
45
|
+
reject(e.data);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
w.addEventListener('error', onError);
|
|
49
|
+
w.addEventListener('message', onMessage);
|
|
50
|
+
const cleanUp = () => {
|
|
51
|
+
w.removeEventListener('error', onError);
|
|
52
|
+
w.removeEventListener('message', onMessage);
|
|
53
|
+
};
|
|
54
|
+
setTimeout(() => {
|
|
55
|
+
onError(new ErrorEvent('error', { message: 'Worker did not respond in time' }));
|
|
56
|
+
}, 30000);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const instance = await worker;
|
|
61
|
+
instance.postMessage(req);
|
|
62
|
+
const [key, version, source] = req;
|
|
63
|
+
const [_key, _version, result] = await new Promise<ResOk>((resolve, reject) => {
|
|
64
|
+
const onMessage = (e: MessageEvent<Res>) => {
|
|
65
|
+
if (e.data[0] === key && e.data[1] === version) {
|
|
66
|
+
instance.removeEventListener('message', onMessage);
|
|
67
|
+
if (e.data[2] instanceof Error) {
|
|
68
|
+
reject(e.data[2]);
|
|
69
|
+
} else {
|
|
70
|
+
resolve(e.data as ResOk);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
instance.addEventListener('message', onMessage);
|
|
75
|
+
});
|
|
76
|
+
return new CompileResult(key, version, source, result);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** 使用当前线程编译 */
|
|
80
|
+
async function compileSync(req: Req): Promise<CompileResult> {
|
|
81
|
+
const [key, version, script, mode] = req;
|
|
82
|
+
const { compile } = await import('./worker.js');
|
|
83
|
+
const result = compile(script, mode);
|
|
84
|
+
return new CompileResult(key, version, script, result);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const USE_WORKER = typeof Worker === 'function';
|
|
88
|
+
/** 注册 worker */
|
|
89
|
+
export async function compile(model: editor.ITextModel): Promise<CompileResult> {
|
|
90
|
+
const uri = model.uri.toString();
|
|
91
|
+
const version = model.getVersionId();
|
|
92
|
+
const mode = model.getLanguageId() === 'mirascript-template' ? 'Template' : 'Script';
|
|
93
|
+
const cacheKey = `${model.id}\0${uri}\0${mode}` as const;
|
|
94
|
+
const cached = cache.get(cacheKey);
|
|
95
|
+
if (cached?.version === version) {
|
|
96
|
+
cached.lastAccess = Date.now();
|
|
97
|
+
return cached.result;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const value = model.getValue();
|
|
101
|
+
const req: Req = [cacheKey, version, value, mode];
|
|
102
|
+
const res = USE_WORKER ? compileWorker(req) : compileSync(req);
|
|
103
|
+
void res.then((result) => {
|
|
104
|
+
setMarkers(model, result);
|
|
105
|
+
});
|
|
106
|
+
const item: CacheValue = {
|
|
107
|
+
version,
|
|
108
|
+
lastAccess: Date.now(),
|
|
109
|
+
result: res,
|
|
110
|
+
};
|
|
111
|
+
cache.set(cacheKey, item);
|
|
112
|
+
res.catch(() => {
|
|
113
|
+
const current = cache.get(cacheKey);
|
|
114
|
+
if (current === item) {
|
|
115
|
+
cache.delete(cacheKey);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
return res;
|
|
119
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { createConfig, type CompileResult, wasm, ready } from '@mirascript/wasm';
|
|
2
|
+
import type { InputMode } from '@mirascript/mirascript';
|
|
3
|
+
await ready;
|
|
4
|
+
|
|
5
|
+
/** Monaco 编译结果 */
|
|
6
|
+
export interface MonacoResult extends CompileResult {
|
|
7
|
+
/** 格式化结果 */
|
|
8
|
+
formatted?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** 缓存 Key (id, uri, inputMode) */
|
|
12
|
+
export type CacheKey = `${string}\0${string}\0${InputMode}`;
|
|
13
|
+
/** 请求参数 */
|
|
14
|
+
export type Req = [key: CacheKey, version: number, script: string, mode: InputMode];
|
|
15
|
+
/** 编译结果 */
|
|
16
|
+
export type ResOk = [key: CacheKey, version: number, result: MonacoResult];
|
|
17
|
+
/** 编译结果 */
|
|
18
|
+
export type ResErr = [key: CacheKey, version: number, error: Error];
|
|
19
|
+
/** 编译结果 */
|
|
20
|
+
export type Res = ResOk | ResErr;
|
|
21
|
+
/** Ready */
|
|
22
|
+
export type Ready = 'mirascript lsp ready';
|
|
23
|
+
|
|
24
|
+
const configTemplate = createConfig({
|
|
25
|
+
diagnostic_position_encoding: 'Utf16',
|
|
26
|
+
track_references: true,
|
|
27
|
+
diagnostic_other: true,
|
|
28
|
+
trivia: true,
|
|
29
|
+
input_mode: 'Template',
|
|
30
|
+
});
|
|
31
|
+
const configScript = createConfig({
|
|
32
|
+
diagnostic_position_encoding: 'Utf16',
|
|
33
|
+
track_references: true,
|
|
34
|
+
diagnostic_other: true,
|
|
35
|
+
trivia: true,
|
|
36
|
+
input_mode: 'Script',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/** 编译 */
|
|
40
|
+
export function compile(script: string, mode: InputMode): MonacoResult {
|
|
41
|
+
const config = mode === 'Script' ? configScript : configTemplate;
|
|
42
|
+
const compiler = new wasm.MonacoCompiler(script, config);
|
|
43
|
+
try {
|
|
44
|
+
const parseOk = compiler.parse();
|
|
45
|
+
if (!parseOk) {
|
|
46
|
+
return { diagnostics: compiler.diagnostics(), chunk: undefined };
|
|
47
|
+
}
|
|
48
|
+
const chunk = compiler.emit();
|
|
49
|
+
const formatted = compiler.format();
|
|
50
|
+
return {
|
|
51
|
+
diagnostics: compiler.diagnostics(),
|
|
52
|
+
chunk,
|
|
53
|
+
formatted,
|
|
54
|
+
};
|
|
55
|
+
} finally {
|
|
56
|
+
try {
|
|
57
|
+
compiler.free();
|
|
58
|
+
} catch (ex) {
|
|
59
|
+
/* 忽略错误 */
|
|
60
|
+
// eslint-disable-next-line no-console
|
|
61
|
+
console.error(ex);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (typeof Worker == 'function' && typeof addEventListener == 'function' && typeof postMessage == 'function') {
|
|
67
|
+
addEventListener('message', (event: MessageEvent) => {
|
|
68
|
+
const data = event.data as Req;
|
|
69
|
+
if (!Array.isArray(data)) return;
|
|
70
|
+
const [key, version, script, mode] = data;
|
|
71
|
+
try {
|
|
72
|
+
const result = compile(script, mode);
|
|
73
|
+
const transfer = [];
|
|
74
|
+
if (result.chunk) transfer.push(result.chunk.buffer);
|
|
75
|
+
if (result.diagnostics) transfer.push(result.diagnostics.buffer);
|
|
76
|
+
postMessage([key, version, result] satisfies ResOk, { transfer });
|
|
77
|
+
} catch (error) {
|
|
78
|
+
const e = error instanceof Error ? error : new Error(String(error));
|
|
79
|
+
postMessage([key, version, e] satisfies ResErr);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
postMessage('mirascript lsp ready' satisfies Ready);
|
|
83
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
let monaco = null;
|
|
2
|
+
export default monaco;
|
|
3
|
+
|
|
4
|
+
export let CancellationTokenSource,
|
|
5
|
+
Emitter,
|
|
6
|
+
KeyCode,
|
|
7
|
+
KeyMod,
|
|
8
|
+
MarkerSeverity,
|
|
9
|
+
MarkerTag,
|
|
10
|
+
Position,
|
|
11
|
+
Range,
|
|
12
|
+
Selection,
|
|
13
|
+
SelectionDirection,
|
|
14
|
+
Token,
|
|
15
|
+
Uri,
|
|
16
|
+
editor,
|
|
17
|
+
languages,
|
|
18
|
+
service,
|
|
19
|
+
utils;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 注册 MiraScript Monaco API,不实际启用任何功能。
|
|
23
|
+
* @param {object} monacoApi - Monaco API 对象,必须包含特定方法和属性。
|
|
24
|
+
*/
|
|
25
|
+
export function registerMonacoApi(monacoApi) {
|
|
26
|
+
if (monaco) {
|
|
27
|
+
throw new Error('MiraScriptMonacoLoader has already been registered.');
|
|
28
|
+
}
|
|
29
|
+
if (
|
|
30
|
+
!monacoApi ||
|
|
31
|
+
typeof monacoApi !== 'object' ||
|
|
32
|
+
// !monaco.languages ||
|
|
33
|
+
// 'function' != typeof monaco.languages.register ||
|
|
34
|
+
// 'function' != typeof monaco.languages.onLanguage ||
|
|
35
|
+
// !monaco.editor ||
|
|
36
|
+
// 'function' != typeof monaco.editor.create ||
|
|
37
|
+
// 'function' != typeof monaco.editor.createModel ||
|
|
38
|
+
// 'function' != typeof monaco.editor.createWebWorker ||
|
|
39
|
+
'function' != typeof monacoApi.Uri ||
|
|
40
|
+
'function' != typeof monacoApi.Range ||
|
|
41
|
+
'function' != typeof monacoApi.Position ||
|
|
42
|
+
'function' != typeof monacoApi.CancellationTokenSource ||
|
|
43
|
+
'function' != typeof monacoApi.Emitter
|
|
44
|
+
) {
|
|
45
|
+
throw new TypeError('Invalid Monaco editor instance provided.');
|
|
46
|
+
}
|
|
47
|
+
monaco = monacoApi;
|
|
48
|
+
({
|
|
49
|
+
CancellationTokenSource,
|
|
50
|
+
Emitter,
|
|
51
|
+
KeyCode,
|
|
52
|
+
KeyMod,
|
|
53
|
+
MarkerSeverity,
|
|
54
|
+
MarkerTag,
|
|
55
|
+
Position,
|
|
56
|
+
Range,
|
|
57
|
+
Selection,
|
|
58
|
+
SelectionDirection,
|
|
59
|
+
Token,
|
|
60
|
+
Uri,
|
|
61
|
+
editor,
|
|
62
|
+
languages,
|
|
63
|
+
service,
|
|
64
|
+
utils,
|
|
65
|
+
} = monacoApi);
|
|
66
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type * as monaco from 'monaco-editor';
|
|
2
|
+
|
|
3
|
+
export * from 'monaco-editor';
|
|
4
|
+
export * as monaco from 'monaco-editor';
|
|
5
|
+
|
|
6
|
+
/** Monaco Api 属性 */
|
|
7
|
+
export type MonacoApi = {
|
|
8
|
+
Range: typeof monaco.Range;
|
|
9
|
+
Position: typeof monaco.Position;
|
|
10
|
+
Emitter: typeof monaco.Emitter;
|
|
11
|
+
CancellationTokenSource: typeof monaco.CancellationTokenSource;
|
|
12
|
+
Uri: typeof monaco.Uri;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 注册 MiraScript Monaco API,不实际启用任何功能。
|
|
17
|
+
*/
|
|
18
|
+
export declare function registerMonacoApi(monacoApi: MonacoApi): void;
|