@mirascript/monaco 0.1.90 → 0.1.92

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.
@@ -1,7 +1,7 @@
1
1
  // src/lsp/worker-core.ts
2
2
  var { loadModule } = await import("@mirascript/bindings/wasm");
3
- var mod = await loadModule();
4
- var { wasm, createConfig } = mod;
3
+ var module = await loadModule();
4
+ var { wasm, createConfig } = module;
5
5
  var configTemplate = createConfig({
6
6
  diagnostic_position_encoding: "Utf16",
7
7
  diagnostic_tag: true,
@@ -16,30 +16,25 @@ var configScript = createConfig({
16
16
  trivia: true,
17
17
  input_mode: "Script"
18
18
  });
19
+ function config(mode) {
20
+ return mode === "Script" ? configScript : configTemplate;
21
+ }
19
22
  function compile(script, mode) {
20
- const config = mode === "Script" ? configScript : configTemplate;
21
- const compiler = new wasm.MonacoCompiler(script, config);
23
+ const compiler = new wasm.MonacoCompiler(script, config(mode));
22
24
  try {
23
25
  const parseOk = compiler.parse();
24
- if (!parseOk) {
25
- return { diagnostics: compiler.diagnostics(), chunk: void 0 };
26
- }
26
+ if (!parseOk) return { diagnostics: compiler.diagnostics(), chunk: void 0 };
27
27
  const chunk = compiler.emit();
28
- const formatted = compiler.format();
29
- return {
30
- diagnostics: compiler.diagnostics(),
31
- chunk,
32
- formatted
33
- };
28
+ return { diagnostics: compiler.diagnostics(), chunk };
34
29
  } finally {
35
- try {
36
- compiler.free();
37
- } catch (ex) {
38
- console.error(ex);
39
- }
30
+ compiler.free();
40
31
  }
41
32
  }
33
+ function formatSource(script, mode, options, ranges) {
34
+ return module.formatSync(script, config(mode), options, ranges);
35
+ }
42
36
  export {
43
- compile
37
+ compile,
38
+ formatSource
44
39
  };
45
- //# sourceMappingURL=worker-core-MG2FDDNN.js.map
40
+ //# sourceMappingURL=worker-core-IBUPKM67.js.map
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/lsp/worker-core.ts"],
4
+ "mappings": ";AAIA,IAAM,EAAE,WAAW,IAAI,MAAM,OAAO,2BAA2B;AAC/D,IAAM,SAAS,MAAM,WAAW;AAChC,IAAM,EAAE,MAAM,aAAa,IAAI;AAwE/B,IAAM,iBAAiB,aAAa;AAAA,EAChC,8BAA8B;AAAA,EAC9B,gBAAgB;AAAA,EAChB,sBAAsB;AAAA,EACtB,QAAQ;AAAA,EACR,YAAY;AAChB,CAAC;AACD,IAAM,eAAe,aAAa;AAAA,EAC9B,8BAA8B;AAAA,EAC9B,gBAAgB;AAAA,EAChB,sBAAsB;AAAA,EACtB,QAAQ;AAAA,EACR,YAAY;AAChB,CAAC;AAGD,SAAS,OAAO,MAA8C;AAC1D,SAAO,SAAS,WAAW,eAAe;AAC9C;AAGO,SAAS,QAAQ,QAAgB,MAA+B;AACnE,QAAM,WAAW,IAAI,KAAK,eAAe,QAAQ,OAAO,IAAI,CAAC;AAC7D,MAAI;AACA,UAAM,UAAU,SAAS,MAAM;AAC/B,QAAI,CAAC,QAAS,QAAO,EAAE,aAAa,SAAS,YAAY,GAAG,OAAO,OAAU;AAC7E,UAAM,QAAQ,SAAS,KAAK;AAC5B,WAAO,EAAE,aAAa,SAAS,YAAY,GAAG,MAAM;AAAA,EACxD,UAAE;AACE,aAAS,KAAK;AAAA,EAClB;AACJ;AAGO,SAAS,aACZ,QACA,MACA,SACA,QACqB;AACrB,SAAO,OAAO,WAAW,QAAQ,OAAO,IAAI,GAAG,SAAS,MAAM;AAClE;",
5
+ "names": []
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mirascript/monaco",
3
- "version": "0.1.90",
3
+ "version": "0.1.92",
4
4
  "author": "CloudPSS",
5
5
  "license": "MIT",
6
6
  "description": "Monaco Editor integration for Mirascript.",
@@ -41,11 +41,11 @@
41
41
  }
42
42
  },
43
43
  "dependencies": {
44
- "@mirascript/bindings": "~0.1.90",
45
- "@mirascript/constants": "~0.1.90",
46
- "@mirascript/help": "~0.1.90",
47
- "@mirascript/mirascript": "~0.1.90",
48
- "@mirascript/textmate": "~0.1.90",
44
+ "@mirascript/bindings": "~0.1.92",
45
+ "@mirascript/constants": "~0.1.92",
46
+ "@mirascript/help": "~0.1.92",
47
+ "@mirascript/mirascript": "~0.1.92",
48
+ "@mirascript/textmate": "~0.1.92",
49
49
  "@shikijs/core": "^4.4.3",
50
50
  "@shikijs/engine-javascript": "^4.4.3",
51
51
  "@shikijs/vscode-textmate": "^10.0.2"
@@ -59,7 +59,7 @@
59
59
  }
60
60
  },
61
61
  "devDependencies": {
62
- "type-fest": "^5.8.0"
62
+ "type-fest": "^5.9.0"
63
63
  },
64
64
  "scripts": {
65
65
  "build": "pnpm clean && tsc && node ./esbuild.config.js",
package/src/lsp/index.ts CHANGED
@@ -94,8 +94,7 @@ export async function registerLSP(
94
94
  registerIfEnabled('formatter', FormatterProvider, (language, provider) => {
95
95
  return [
96
96
  languages.registerDocumentFormattingEditProvider(language, provider),
97
- // languages.registerDocumentRangeFormattingEditProvider(language, provider),
98
- // languages.registerOnTypeFormattingEditProvider(language, provider),
97
+ languages.registerDocumentRangeFormattingEditProvider(language, provider),
99
98
  ];
100
99
  });
101
100
 
@@ -1,87 +1,93 @@
1
- import {
2
- type languages,
3
- type editor,
4
- type IRange,
5
- Range,
6
- type CancellationToken,
7
- type Position,
8
- } from '../../monaco-api.js';
1
+ import { type languages, type editor, type IRange, Range, type CancellationToken } from '../../monaco-api.js';
2
+ import { parseDiagnostics } from '@mirascript/mirascript/subtle';
3
+ import { formatModel } from '../worker-helper.js';
9
4
  import { Provider } from './base.js';
10
5
 
11
- /** 进行格式化 */
6
+ const DEFAULT_PRINT_WIDTH = 80;
7
+
8
+ /** 请求格式化并将 UTF-16 偏移编辑转换为 Monaco 编辑。 */
12
9
  async function format(
13
10
  model: editor.ITextModel,
14
- ranges: readonly IRange[],
11
+ ranges: readonly IRange[] | undefined,
15
12
  options: languages.FormattingOptions | undefined,
16
- hint: 'full' | 'range' | 'expression' | 'statement',
17
13
  token: CancellationToken | undefined,
18
- ): Promise<languages.TextEdit[]> {
19
- if (hint !== 'full') return [];
20
- const compiled = await Provider.getCompileResult(model);
21
- if (compiled?.result.formatted == null) return [];
22
- return [
23
- {
24
- range: model.getFullModelRange(),
25
- text: compiled.result.formatted,
26
- },
27
- ];
14
+ ): Promise<languages.TextEdit[] | null> {
15
+ if (model.uri.scheme === 'mirascript' || token?.isCancellationRequested) return [];
16
+ if (ranges?.length === 0) return [];
17
+ const version = model.getVersionId();
18
+ const offsetRanges = ranges?.map((range) => ({
19
+ start: model.getOffsetAt(Range.getStartPosition(range)),
20
+ end: model.getOffsetAt(Range.getEndPosition(range)),
21
+ }));
22
+ try {
23
+ const result = await formatModel(model, offsetRanges, {
24
+ tabSize: options?.tabSize ?? 2,
25
+ insertSpaces: options?.insertSpaces ?? true,
26
+ printWidth: DEFAULT_PRINT_WIDTH,
27
+ });
28
+ if (model.isDisposed() || token?.isCancellationRequested || model.getVersionId() !== version) return [];
29
+ if (ranges == null && parseDiagnostics(model.getValue(), result.diagnostics).errors.length > 0) return null;
30
+ return result.edits.map((edit) => ({
31
+ range: Range.fromPositions(model.getPositionAt(edit.start), model.getPositionAt(edit.end)),
32
+ text: edit.text,
33
+ }));
34
+ } catch (error) {
35
+ // eslint-disable-next-line no-console
36
+ console.error('MiraScript formatting failed:', error);
37
+ return null;
38
+ }
28
39
  }
29
40
 
30
- /** 格式化 */
41
+ /** MiraScript 整文及选区格式化。 */
31
42
  export class FormatterProvider
32
43
  extends Provider
33
- implements
34
- languages.DocumentFormattingEditProvider,
35
- languages.DocumentRangeFormattingEditProvider,
36
- languages.OnTypeFormattingEditProvider
44
+ implements languages.DocumentFormattingEditProvider, languages.DocumentRangeFormattingEditProvider
37
45
  {
38
- /** 进行完整格式化 */
46
+ /** 返回完整格式化文本;源码已规范时返回原文本。 */
39
47
  static async format(model: editor.ITextModel, options?: languages.FormattingOptions): Promise<string | null> {
40
- const formatted = await format(model, [], options, 'full', undefined);
41
- return formatted.length === 1 ? formatted[0]!.text : null;
48
+ const edits = await format(model, undefined, options, undefined);
49
+ if (edits == null) return null;
50
+ if (edits.length === 0) return model.getValue();
51
+ let output = model.getValue();
52
+ const offsetEdits = edits
53
+ .map((edit) => ({
54
+ start: model.getOffsetAt(Range.getStartPosition(edit.range)),
55
+ end: model.getOffsetAt(Range.getEndPosition(edit.range)),
56
+ text: edit.text,
57
+ }))
58
+ .toSorted((left, right) => right.start - left.start);
59
+ for (const edit of offsetEdits) {
60
+ output = output.slice(0, edit.start) + edit.text + output.slice(edit.end);
61
+ }
62
+ return output;
42
63
  }
43
- /** @inheritdoc */
64
+
65
+ /** 提供整篇文档格式化编辑。 */
44
66
  provideDocumentFormattingEdits(
45
67
  model: editor.ITextModel,
46
68
  options: languages.FormattingOptions,
47
69
  token: CancellationToken,
48
70
  ): languages.ProviderResult<languages.TextEdit[]> {
49
- return format(model, [], options, 'full', token);
71
+ return format(model, undefined, options, token).then((edits) => edits ?? []);
50
72
  }
51
- /** @inheritdoc */
73
+
74
+ /** 提供单个选区格式化编辑。 */
52
75
  provideDocumentRangeFormattingEdits(
53
76
  model: editor.ITextModel,
54
77
  range: Range,
55
78
  options: languages.FormattingOptions,
56
79
  token: CancellationToken,
57
80
  ): languages.ProviderResult<languages.TextEdit[]> {
58
- return format(model, [range], options, 'range', token);
81
+ return format(model, [range], options, token).then((edits) => edits ?? []);
59
82
  }
60
- /** @inheritdoc */
83
+
84
+ /** 提供多个选区格式化编辑。 */
61
85
  provideDocumentRangesFormattingEdits(
62
86
  model: editor.ITextModel,
63
87
  ranges: Range[],
64
88
  options: languages.FormattingOptions,
65
89
  token: CancellationToken,
66
90
  ): languages.ProviderResult<languages.TextEdit[]> {
67
- return format(model, ranges, options, 'range', token);
68
- }
69
- /** @inheritdoc */
70
- readonly autoFormatTriggerCharacters = [';', '}', ']', ')'];
71
- /** @inheritdoc */
72
- provideOnTypeFormattingEdits(
73
- model: editor.ITextModel,
74
- position: Position,
75
- ch: string,
76
- options: languages.FormattingOptions,
77
- token: CancellationToken,
78
- ): languages.ProviderResult<languages.TextEdit[]> {
79
- return format(
80
- model,
81
- [Range.fromPositions(position, position)],
82
- options,
83
- ch === ';' ? 'statement' : 'expression',
84
- token,
85
- );
91
+ return format(model, ranges, options, token).then((edits) => edits ?? []);
86
92
  }
87
93
  }
@@ -3,26 +3,77 @@ import type { BcModule } from '@mirascript/bindings/wasm';
3
3
  import type { Tagged } from 'type-fest';
4
4
 
5
5
  const { loadModule } = await import('@mirascript/bindings/wasm');
6
- const mod = await loadModule();
7
- const { wasm, createConfig } = mod;
6
+ const module = await loadModule();
7
+ const { wasm, createConfig } = module;
8
8
 
9
9
  /** Monaco 编译结果 */
10
- export interface MonacoResult extends BcModule.CompileResult {
11
- /** 格式化结果 */
12
- formatted?: string;
13
- }
14
-
10
+ export type MonacoResult = BcModule.CompileResult;
15
11
  /** 缓存 Key (model id) */
16
12
  export type CacheKey = Tagged<string, 'modelId'>;
17
- /** 请求参数 */
18
- export type Req = [key: CacheKey, version: number, script: string, mode: InputMode];
19
- /** 编译结果 */
20
- export type ResOk = [key: CacheKey, version: number, result: MonacoResult];
21
- /** 编译结果 */
22
- export type ResErr = [key: CacheKey, version: number, error: Error];
23
- /** 编译结果 */
24
- export type Res = ResOk | ResErr;
25
- /** Ready */
13
+ /** Worker 请求标识。 */
14
+ export type RequestId = Tagged<number, 'requestId'>;
15
+
16
+ /** UTF-16 绝对偏移范围。 */
17
+ export interface OffsetRange {
18
+ /** 起始偏移。 */
19
+ readonly start: number;
20
+ /** 结束偏移。 */
21
+ readonly end: number;
22
+ }
23
+
24
+ /** Worker 格式化选项。 */
25
+ export interface WorkerFormatOptions {
26
+ /** 制表符宽度。 */
27
+ readonly tabSize: number;
28
+ /** 是否使用空格缩进。 */
29
+ readonly insertSpaces: boolean;
30
+ /** 最大打印宽度。 */
31
+ readonly printWidth: number;
32
+ }
33
+
34
+ /** Worker 请求公共字段。 */
35
+ interface BaseRequest {
36
+ /** 请求标识。 */
37
+ readonly id: RequestId;
38
+ /** Monaco model 缓存键。 */
39
+ readonly key: CacheKey;
40
+ /** Model 版本。 */
41
+ readonly version: number;
42
+ /** 源码文本。 */
43
+ readonly script: string;
44
+ /** 编译输入模式。 */
45
+ readonly mode: InputMode;
46
+ }
47
+
48
+ /** 编译请求。 */
49
+ export interface CompileRequest extends BaseRequest {
50
+ /** 请求种类。 */
51
+ readonly kind: 'compile';
52
+ }
53
+
54
+ /** 格式化请求。 */
55
+ export interface FormatRequest extends BaseRequest {
56
+ /** 请求种类。 */
57
+ readonly kind: 'format';
58
+ /** 待格式化的 UTF-16 范围;省略时格式化整篇文档。 */
59
+ readonly ranges?: readonly OffsetRange[];
60
+ /** 格式化选项。 */
61
+ readonly options: WorkerFormatOptions;
62
+ }
63
+
64
+ /** Worker 请求联合类型。 */
65
+ export type Req = CompileRequest | FormatRequest;
66
+ /** Worker 响应联合类型。 */
67
+ export type Res =
68
+ | { readonly id: RequestId; readonly ok: true; readonly kind: 'compile'; readonly result: MonacoResult }
69
+ | {
70
+ readonly id: RequestId;
71
+ readonly ok: true;
72
+ readonly kind: 'format';
73
+ readonly result: BcModule.FormatResult;
74
+ }
75
+ | { readonly id: RequestId; readonly ok: false; readonly error: string };
76
+ /** Worker 就绪消息。 */
26
77
  export type Ready = 'mirascript lsp ready';
27
78
 
28
79
  const configTemplate = createConfig({
@@ -40,29 +91,30 @@ const configScript = createConfig({
40
91
  input_mode: 'Script',
41
92
  });
42
93
 
94
+ /** 选择与输入模式对应的可复用编译配置。 */
95
+ function config(mode: InputMode): BcModule.wasm.CompileConfig {
96
+ return mode === 'Script' ? configScript : configTemplate;
97
+ }
98
+
43
99
  /** 编译 */
44
100
  export function compile(script: string, mode: InputMode): MonacoResult {
45
- const config = mode === 'Script' ? configScript : configTemplate;
46
- const compiler = new wasm.MonacoCompiler(script, config);
101
+ const compiler = new wasm.MonacoCompiler(script, config(mode));
47
102
  try {
48
103
  const parseOk = compiler.parse();
49
- if (!parseOk) {
50
- return { diagnostics: compiler.diagnostics(), chunk: undefined };
51
- }
104
+ if (!parseOk) return { diagnostics: compiler.diagnostics(), chunk: undefined };
52
105
  const chunk = compiler.emit();
53
- const formatted = compiler.format();
54
- return {
55
- diagnostics: compiler.diagnostics(),
56
- chunk,
57
- formatted,
58
- };
106
+ return { diagnostics: compiler.diagnostics(), chunk };
59
107
  } finally {
60
- try {
61
- compiler.free();
62
- } catch (ex) {
63
- /* 忽略错误 */
64
- // eslint-disable-next-line no-console
65
- console.error(ex);
66
- }
108
+ compiler.free();
67
109
  }
68
110
  }
111
+
112
+ /** 按需格式化,不进入普通编译缓存。 */
113
+ export function formatSource(
114
+ script: string,
115
+ mode: InputMode,
116
+ options: WorkerFormatOptions,
117
+ ranges?: readonly OffsetRange[],
118
+ ): BcModule.FormatResult {
119
+ return module.formatSync(script, config(mode), options, ranges);
120
+ }
@@ -1,141 +1,188 @@
1
1
  import type { InputMode } from '@mirascript/constants';
2
2
  import { editor } from '../monaco-api.js';
3
- import type { Ready, CacheKey, Req, Res, ResOk } from './worker-core.js';
3
+ import type {
4
+ CacheKey,
5
+ CompileRequest,
6
+ FormatRequest,
7
+ OffsetRange,
8
+ Ready,
9
+ Req,
10
+ Res,
11
+ RequestId,
12
+ WorkerFormatOptions,
13
+ } from './worker-core.js';
4
14
  import { CompileResult } from './compile-result.js';
5
15
  import { makeModelMarkers } from './diagnostics.js';
6
16
 
7
- /** 缓存 */
17
+ /** 编译缓存项。 */
8
18
  type CacheValue = {
9
19
  readonly version: number;
10
20
  readonly result: Promise<CompileResult>;
11
21
  readonly mode: InputMode;
12
22
  lastAccess: number;
13
23
  };
14
- /** 编译结果缓存,避免重复编译 */
24
+
15
25
  const cache = new Map<CacheKey, CacheValue>();
16
- let worker: Promise<Worker> | undefined = undefined;
26
+ let worker: Promise<Worker> | undefined;
27
+ let requestSequence = 0;
17
28
 
18
- // 清理缓存
19
29
  const CACHE_MAX_AGE = 30000;
20
30
  setInterval(() => {
21
31
  const now = Date.now();
22
32
  for (const [key, { lastAccess }] of cache) {
23
- if (now - lastAccess > CACHE_MAX_AGE) {
24
- cache.delete(key);
25
- }
33
+ if (now - lastAccess > CACHE_MAX_AGE) cache.delete(key);
26
34
  }
27
35
  }, CACHE_MAX_AGE);
28
36
 
29
- /** 使用 worker 进行编译 */
30
- async function compileWorker(req: Req): Promise<CompileResult> {
37
+ /** 生成当前进程内唯一的请求标识。 */
38
+ function nextRequestId(): RequestId {
39
+ requestSequence = (requestSequence + 1) % Number.MAX_SAFE_INTEGER;
40
+ return requestSequence as RequestId;
41
+ }
42
+
43
+ /** 从 Monaco model 推断编译输入模式。 */
44
+ function modeOf(model: editor.ITextModel): InputMode {
45
+ return model.getLanguageId() === 'mirascript-template' ? 'Template' : 'Script';
46
+ }
47
+
48
+ /** 创建并等待 LSP worker 就绪。 */
49
+ async function getWorker(): Promise<Worker> {
31
50
  if (!worker) {
32
- const w = new Worker(new URL('#lsp/worker', import.meta.url), {
51
+ const instance = new Worker(new URL('#lsp/worker', import.meta.url), {
33
52
  type: 'module',
34
53
  name: '@mirascript/lsp-server',
35
54
  });
36
55
  worker = new Promise((resolve, reject) => {
37
- const onError = (e: ErrorEvent) => {
56
+ const timeout = setTimeout(() => {
38
57
  cleanUp();
39
- reject(new Error(`Worker failed to start: ${e.message}`));
58
+ reject(new Error('Worker did not respond in time'));
59
+ }, 30000);
60
+ const cleanUp = () => {
61
+ clearTimeout(timeout);
62
+ instance.removeEventListener('error', onError);
63
+ instance.removeEventListener('message', onMessage);
40
64
  };
41
- const onMessage = (e: MessageEvent<Ready | Error>) => {
42
- if (e.data === 'mirascript lsp ready') {
43
- cleanUp();
44
- resolve(w);
45
- } else if (e.data instanceof Error) {
46
- cleanUp();
47
- reject(e.data);
48
- }
65
+ const onError = (event: ErrorEvent) => {
66
+ cleanUp();
67
+ reject(new Error(`Worker failed to start: ${event.message}`));
49
68
  };
50
- w.addEventListener('error', onError);
51
- w.addEventListener('message', onMessage);
52
- const cleanUp = () => {
53
- w.removeEventListener('error', onError);
54
- w.removeEventListener('message', onMessage);
69
+ const onMessage = (event: MessageEvent<Ready>) => {
70
+ if (event.data !== 'mirascript lsp ready') return;
71
+ cleanUp();
72
+ resolve(instance);
55
73
  };
56
- setTimeout(() => {
57
- onError(new ErrorEvent('error', { message: 'Worker did not respond in time' }));
58
- }, 30000);
74
+ instance.addEventListener('error', onError);
75
+ instance.addEventListener('message', onMessage);
59
76
  });
60
77
  }
78
+ return worker;
79
+ }
61
80
 
62
- try {
63
- await worker;
64
- } catch (ex) {
65
- // eslint-disable-next-line no-console
66
- console.error('Failed to initialize worker, use sync compile:', ex);
67
- USE_WORKER = false;
68
- return compileSync(req);
69
- }
70
-
71
- const instance = await worker;
72
- instance.postMessage(req);
73
- const [key, version, source] = req;
74
- const [_key, _version, result] = await new Promise<ResOk>((resolve, reject) => {
75
- const onMessage = (e: MessageEvent<Res>) => {
76
- if (!(e.data[0] === key && e.data[1] === version)) {
77
- return;
78
- }
79
-
81
+ /** 发送带标识的 worker 请求并等待对应响应。 */
82
+ async function requestWorker(request: Req): Promise<Res> {
83
+ const instance = await getWorker();
84
+ return new Promise<Res>((resolve) => {
85
+ const onMessage = (event: MessageEvent<Res>) => {
86
+ if (event.data?.id !== request.id) return;
80
87
  instance.removeEventListener('message', onMessage);
81
- if (e.data[2] instanceof Error) {
82
- reject(e.data[2]);
83
- } else {
84
- resolve(e.data as ResOk);
85
- }
88
+ resolve(event.data);
86
89
  };
87
90
  instance.addEventListener('message', onMessage);
91
+ instance.postMessage(request);
88
92
  });
89
- return new CompileResult(key, version, source, result);
90
93
  }
91
94
 
92
95
  let compileImpl: typeof import('./worker-core.js').compile;
93
- /** 使用当前线程编译 */
94
- async function compileSync(req: Req): Promise<CompileResult> {
95
- const [key, version, script, mode] = req;
96
- if (compileImpl == null) {
97
- const mod = await import('./worker-core.js');
98
- compileImpl = mod.compile;
96
+ /** 在当前线程执行编译。 */
97
+ async function compileSync(request: CompileRequest): Promise<CompileResult> {
98
+ compileImpl ??= (await import('./worker-core.js')).compile;
99
+ const result = compileImpl(request.script, request.mode);
100
+ return new CompileResult(request.key, request.version, request.script, result);
101
+ }
102
+
103
+ /** 优先使用 worker 编译,失败时回退当前线程。 */
104
+ async function compileWorker(request: CompileRequest): Promise<CompileResult> {
105
+ try {
106
+ const response = await requestWorker(request);
107
+ if (!response.ok) throw new Error(response.error);
108
+ if (response.kind !== 'compile') throw new Error('Unexpected formatter response to compile request');
109
+ return new CompileResult(request.key, request.version, request.script, response.result);
110
+ } catch (error) {
111
+ // eslint-disable-next-line no-console
112
+ console.error('Failed to use MiraScript worker, falling back to the current thread:', error);
113
+ USE_WORKER = false;
114
+ worker = undefined;
115
+ return compileSync(request);
99
116
  }
100
- const result = compileImpl(script, mode);
101
- return new CompileResult(key, version, script, result);
102
117
  }
103
118
 
104
119
  let USE_WORKER = typeof Worker === 'function';
120
+
105
121
  /** 编译并设置缓存 */
106
122
  export async function compile(model: editor.ITextModel): Promise<CompileResult> {
107
123
  const version = model.getVersionId();
108
- const mode = model.getLanguageId() === 'mirascript-template' ? 'Template' : 'Script';
109
- const cacheKey = model.id as CacheKey;
110
- const cached = cache.get(cacheKey);
124
+ const mode = modeOf(model);
125
+ const key = model.id as CacheKey;
126
+ const cached = cache.get(key);
111
127
  if (cached?.version === version && cached.mode === mode) {
112
128
  cached.lastAccess = Date.now();
113
129
  return cached.result;
114
130
  }
115
131
 
116
- const value = model.getValue();
117
- const req: Req = [cacheKey, version, value, mode];
118
- const res = USE_WORKER ? compileWorker(req) : compileSync(req);
119
- void res.then(async (result) => {
132
+ const script = model.getValue();
133
+ const request: CompileRequest = { kind: 'compile', id: nextRequestId(), key, version, script, mode };
134
+ const result = USE_WORKER ? compileWorker(request) : compileSync(request);
135
+ void result.then(async (compiled) => {
120
136
  if (model.isDisposed()) return;
121
137
  const setModelMarkers = editor?.setModelMarkers;
122
- if (typeof setModelMarkers != 'function') return;
123
- const markers = await makeModelMarkers(model, result);
124
- if (!markers) return;
125
- setModelMarkers(model, 'mirascript', markers);
138
+ if (typeof setModelMarkers !== 'function') return;
139
+ const markers = await makeModelMarkers(model, compiled);
140
+ if (markers) setModelMarkers(model, 'mirascript', markers);
126
141
  });
127
- const item: CacheValue = {
128
- version,
129
- lastAccess: Date.now(),
130
- mode,
131
- result: res,
132
- };
133
- cache.set(cacheKey, item);
134
- res.catch(() => {
135
- const current = cache.get(cacheKey);
136
- if (current === item) {
137
- cache.delete(cacheKey);
138
- }
142
+ const item: CacheValue = { version, lastAccess: Date.now(), mode, result };
143
+ cache.set(key, item);
144
+ result.catch(() => {
145
+ if (cache.get(key) === item) cache.delete(key);
139
146
  });
140
- return res;
147
+ return result;
148
+ }
149
+
150
+ let formatImpl: typeof import('./worker-core.js').formatSource;
151
+
152
+ /** 对指定 model 发起独立的按需格式化请求。 */
153
+ export async function formatModel(
154
+ model: editor.ITextModel,
155
+ ranges: readonly OffsetRange[] | undefined,
156
+ options: WorkerFormatOptions,
157
+ ): Promise<{
158
+ readonly diagnostics: Uint32Array;
159
+ readonly edits: ReadonlyArray<{ start: number; end: number; text: string }>;
160
+ }> {
161
+ const request: FormatRequest = {
162
+ kind: 'format',
163
+ id: nextRequestId(),
164
+ key: model.id as CacheKey,
165
+ version: model.getVersionId(),
166
+ script: model.getValue(),
167
+ mode: modeOf(model),
168
+ ranges,
169
+ options,
170
+ };
171
+ if (!USE_WORKER) {
172
+ formatImpl ??= (await import('./worker-core.js')).formatSource;
173
+ return formatImpl(request.script, request.mode, request.options, request.ranges);
174
+ }
175
+ try {
176
+ const response = await requestWorker(request);
177
+ if (!response.ok) throw new Error(response.error);
178
+ if (response.kind !== 'format') throw new Error('Unexpected compile response to format request');
179
+ return response.result;
180
+ } catch (error) {
181
+ // eslint-disable-next-line no-console
182
+ console.error('Failed to use MiraScript worker, formatting on the current thread:', error);
183
+ USE_WORKER = false;
184
+ worker = undefined;
185
+ formatImpl ??= (await import('./worker-core.js')).formatSource;
186
+ return formatImpl(request.script, request.mode, request.options, request.ranges);
187
+ }
141
188
  }