@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
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { VmContext } from '@mirascript/mirascript';
|
|
2
|
+
import { DefaultVmContext } from '@mirascript/mirascript/subtle';
|
|
3
|
+
import type { VmContextProvider } from '../../index.js';
|
|
4
|
+
import { type editor, Emitter, type IEvent } from '../../monaco-api.js';
|
|
5
|
+
import type { CompileResult } from '../compile-result.js';
|
|
6
|
+
import { compile } from '../worker-helper.js';
|
|
7
|
+
|
|
8
|
+
let contextProvider: VmContextProvider | undefined;
|
|
9
|
+
/** 设置全局变量提供者 */
|
|
10
|
+
export function setContextProvider(provider: VmContextProvider | undefined): void {
|
|
11
|
+
contextProvider = provider;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** 提供编辑器 LSP 支持 */
|
|
15
|
+
export abstract class Provider {
|
|
16
|
+
/** 获取编译结果 */
|
|
17
|
+
async getCompileResult(model: editor.ITextModel): Promise<CompileResult | undefined> {
|
|
18
|
+
if (model.uri.scheme === 'mirascript') {
|
|
19
|
+
return undefined; // 不处理标准库
|
|
20
|
+
}
|
|
21
|
+
return await compile(model);
|
|
22
|
+
}
|
|
23
|
+
/** 获取执行上下文(全局变量) */
|
|
24
|
+
async getContext(model: editor.ITextModel): Promise<Readonly<VmContext>> {
|
|
25
|
+
return (await contextProvider?.(model)) ?? DefaultVmContext;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
readonly displayName = 'MiraScript LSP';
|
|
29
|
+
readonly _debugDisplayName = 'MiraScript LSP';
|
|
30
|
+
private _onDidChange: Emitter<this> | null = null;
|
|
31
|
+
/** @inheritdoc */
|
|
32
|
+
get onDidChange(): IEvent<this> & IEvent<void> {
|
|
33
|
+
this._onDidChange ??= new Emitter<this>();
|
|
34
|
+
return this._onDidChange.event as IEvent<this> & IEvent<void>;
|
|
35
|
+
}
|
|
36
|
+
/** 触发 onDidChange */
|
|
37
|
+
emitDidChange(): void {
|
|
38
|
+
this._onDidChange?.fire(this);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { editor, languages, Range, CancellationToken } from '../../monaco-api.js';
|
|
2
|
+
import { Provider } from './base.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 代码操作
|
|
6
|
+
*/
|
|
7
|
+
export class CodeActionProvider extends Provider implements languages.CodeActionProvider {
|
|
8
|
+
/** @inheritdoc */
|
|
9
|
+
provideCodeActions(
|
|
10
|
+
model: editor.ITextModel,
|
|
11
|
+
range: Range,
|
|
12
|
+
context: languages.CodeActionContext,
|
|
13
|
+
token: CancellationToken,
|
|
14
|
+
): languages.ProviderResult<languages.CodeActionList> {
|
|
15
|
+
return {
|
|
16
|
+
actions: [],
|
|
17
|
+
dispose: () => void 0,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** @inheritdoc */
|
|
22
|
+
resolveCodeAction?(
|
|
23
|
+
codeAction: languages.CodeAction,
|
|
24
|
+
token: CancellationToken,
|
|
25
|
+
): languages.ProviderResult<languages.CodeAction> {
|
|
26
|
+
throw new Error('Method not implemented.');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { editor, languages, CancellationToken } from '../../monaco-api.js';
|
|
2
|
+
import { Provider } from './base.js';
|
|
3
|
+
import { DiagnosticCode } from '@mirascript/wasm';
|
|
4
|
+
|
|
5
|
+
const REG_COLOR_STR = /^(@*)(['"`])(#(?:[0-9a-f]{6}|[0-9a-f]{3}|[0-9a-f]{8}|[0-9a-f]{4}))\2\1$/iu;
|
|
6
|
+
|
|
7
|
+
/** 解析颜色 */
|
|
8
|
+
function parseColorString(text: string):
|
|
9
|
+
| {
|
|
10
|
+
ats: string;
|
|
11
|
+
quote: string;
|
|
12
|
+
colorString: string;
|
|
13
|
+
color: languages.IColor;
|
|
14
|
+
}
|
|
15
|
+
| undefined {
|
|
16
|
+
const colorMatch = REG_COLOR_STR.exec(text);
|
|
17
|
+
if (!colorMatch) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const ats = colorMatch[1] || '';
|
|
21
|
+
const quote = colorMatch[2] || '';
|
|
22
|
+
const colorString = colorMatch[3]!;
|
|
23
|
+
let color: languages.IColor;
|
|
24
|
+
if (colorString.startsWith('#')) {
|
|
25
|
+
const colorCode = colorString.slice(1);
|
|
26
|
+
|
|
27
|
+
if (colorCode.length === 3) {
|
|
28
|
+
// 处理 #RGB 格式
|
|
29
|
+
color = {
|
|
30
|
+
red: Number.parseInt(colorCode[0]! + colorCode[0]!, 16) / 255,
|
|
31
|
+
green: Number.parseInt(colorCode[1]! + colorCode[1]!, 16) / 255,
|
|
32
|
+
blue: Number.parseInt(colorCode[2]! + colorCode[2]!, 16) / 255,
|
|
33
|
+
alpha: 1,
|
|
34
|
+
};
|
|
35
|
+
} else if (colorCode.length === 6) {
|
|
36
|
+
color = {
|
|
37
|
+
red: Number.parseInt(colorCode.slice(0, 2), 16) / 255,
|
|
38
|
+
green: Number.parseInt(colorCode.slice(2, 4), 16) / 255,
|
|
39
|
+
blue: Number.parseInt(colorCode.slice(4, 6), 16) / 255,
|
|
40
|
+
alpha: 1, // 假设不透明
|
|
41
|
+
};
|
|
42
|
+
} else if (colorCode.length === 8) {
|
|
43
|
+
// 处理 #RRGGBBAA 格式
|
|
44
|
+
color = {
|
|
45
|
+
red: Number.parseInt(colorCode.slice(0, 2), 16) / 255,
|
|
46
|
+
green: Number.parseInt(colorCode.slice(2, 4), 16) / 255,
|
|
47
|
+
blue: Number.parseInt(colorCode.slice(4, 6), 16) / 255,
|
|
48
|
+
alpha: Number.parseInt(colorCode.slice(6, 8), 16) / 255,
|
|
49
|
+
};
|
|
50
|
+
} else if (colorCode.length === 4) {
|
|
51
|
+
// 处理 #RGBA 格式
|
|
52
|
+
color = {
|
|
53
|
+
red: Number.parseInt(colorCode[0]! + colorCode[0]!, 16) / 255,
|
|
54
|
+
green: Number.parseInt(colorCode[1]! + colorCode[1]!, 16) / 255,
|
|
55
|
+
blue: Number.parseInt(colorCode[2]! + colorCode[2]!, 16) / 255,
|
|
56
|
+
alpha: Number.parseInt(colorCode[3]! + colorCode[3]!, 16) / 255,
|
|
57
|
+
};
|
|
58
|
+
} else {
|
|
59
|
+
return undefined; // 不支持的颜色格式
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
return undefined; // 不是以 # 开头的颜色字符串
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
ats,
|
|
67
|
+
quote,
|
|
68
|
+
colorString,
|
|
69
|
+
color,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** @inheritdoc */
|
|
74
|
+
export class ColorProvider extends Provider implements languages.DocumentColorProvider {
|
|
75
|
+
/** @inheritdoc */
|
|
76
|
+
async provideDocumentColors(
|
|
77
|
+
model: editor.ITextModel,
|
|
78
|
+
token: CancellationToken,
|
|
79
|
+
): Promise<languages.IColorInformation[] | undefined> {
|
|
80
|
+
const compiled = await this.getCompileResult(model);
|
|
81
|
+
if (!compiled) return undefined;
|
|
82
|
+
const info: languages.IColorInformation[] = [];
|
|
83
|
+
for (const { range, code } of compiled.groupedTags(model).ranges) {
|
|
84
|
+
if (code !== DiagnosticCode.String) continue;
|
|
85
|
+
if (range.startLineNumber !== range.endLineNumber) {
|
|
86
|
+
// 只处理单行字符串
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const text = model.getValueInRange(range);
|
|
90
|
+
const parsed = parseColorString(text);
|
|
91
|
+
if (!parsed) continue;
|
|
92
|
+
info.push({
|
|
93
|
+
range: {
|
|
94
|
+
startLineNumber: range.startLineNumber,
|
|
95
|
+
startColumn: range.startColumn + parsed.ats.length + parsed.quote.length,
|
|
96
|
+
endLineNumber: range.endLineNumber,
|
|
97
|
+
endColumn: range.endColumn - parsed.ats.length - parsed.quote.length,
|
|
98
|
+
},
|
|
99
|
+
color: parsed.color,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return info;
|
|
103
|
+
}
|
|
104
|
+
/** @inheritdoc */
|
|
105
|
+
provideColorPresentations(
|
|
106
|
+
model: editor.ITextModel,
|
|
107
|
+
colorInfo: languages.IColorInformation,
|
|
108
|
+
token: CancellationToken,
|
|
109
|
+
): languages.ProviderResult<languages.IColorPresentation[]> {
|
|
110
|
+
const { color } = colorInfo;
|
|
111
|
+
return [
|
|
112
|
+
{
|
|
113
|
+
label: `#${Math.round(color.red * 255)
|
|
114
|
+
.toString(16)
|
|
115
|
+
.padStart(2, '0')}${Math.round(color.green * 255)
|
|
116
|
+
.toString(16)
|
|
117
|
+
.padStart(2, '0')}${Math.round(color.blue * 255)
|
|
118
|
+
.toString(16)
|
|
119
|
+
.padStart(2, '0')}${
|
|
120
|
+
color.alpha >= 1
|
|
121
|
+
? ''
|
|
122
|
+
: Math.round(color.alpha * 255)
|
|
123
|
+
.toString(16)
|
|
124
|
+
.padStart(2, '0')
|
|
125
|
+
}`,
|
|
126
|
+
},
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getVmFunctionInfo,
|
|
3
|
+
type VmValue,
|
|
4
|
+
isVmExtern,
|
|
5
|
+
isVmModule,
|
|
6
|
+
type VmFunctionInfo,
|
|
7
|
+
type VmExtern,
|
|
8
|
+
} from '@mirascript/mirascript';
|
|
9
|
+
import { DiagnosticCode, lib, operations } from '@mirascript/mirascript/subtle';
|
|
10
|
+
import {
|
|
11
|
+
type editor,
|
|
12
|
+
languages,
|
|
13
|
+
type CancellationToken,
|
|
14
|
+
type IPosition,
|
|
15
|
+
type IRange,
|
|
16
|
+
type Position,
|
|
17
|
+
Range,
|
|
18
|
+
} from '../../monaco-api.js';
|
|
19
|
+
import { Provider } from './base.js';
|
|
20
|
+
import { codeblock, getDeep, valueDoc, paramsList, strictContainsPosition, wordAt } from '../utils.js';
|
|
21
|
+
import { keywords, reservedKeywords } from '../../constants.js';
|
|
22
|
+
import type { LocalDefinition } from '../compile-result.js';
|
|
23
|
+
|
|
24
|
+
const DESC_GLOBAL = '(global)';
|
|
25
|
+
const DESC_LOCAL = '(local)';
|
|
26
|
+
const DESC_FIELD = '(field)';
|
|
27
|
+
|
|
28
|
+
const SUGGEST_KEYWORDS: string[] = [];
|
|
29
|
+
|
|
30
|
+
const loadSuggestKeywords = () => {
|
|
31
|
+
if (SUGGEST_KEYWORDS.length > 0) return SUGGEST_KEYWORDS; // 已加载过
|
|
32
|
+
const reserved = reservedKeywords();
|
|
33
|
+
for (const kw of keywords()) {
|
|
34
|
+
if (reserved.includes(kw)) continue; // 跳过保留关键字
|
|
35
|
+
SUGGEST_KEYWORDS.push(kw);
|
|
36
|
+
}
|
|
37
|
+
return SUGGEST_KEYWORDS;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const COMMON_GLOBAL_SUGGESTIONS = (
|
|
41
|
+
range: languages.CompletionItemRanges,
|
|
42
|
+
extension: boolean,
|
|
43
|
+
): languages.CompletionItem[] => {
|
|
44
|
+
const suggestions: languages.CompletionItem[] = [
|
|
45
|
+
{
|
|
46
|
+
label: 'type',
|
|
47
|
+
kind: languages.CompletionItemKind.Keyword,
|
|
48
|
+
insertText: 'type',
|
|
49
|
+
commitCharacters: ['('],
|
|
50
|
+
documentation: {
|
|
51
|
+
value: `使用 \`type()\` 调用获取表达式的类型。${codeblock('type(expression);\nexpression::type();')}`,
|
|
52
|
+
},
|
|
53
|
+
range,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
label: 'global',
|
|
57
|
+
kind: languages.CompletionItemKind.Keyword,
|
|
58
|
+
insertText: 'global',
|
|
59
|
+
commitCharacters: ['.', '['],
|
|
60
|
+
documentation: {
|
|
61
|
+
value: `使用 \`global\` 获取全局变量。${codeblock('global.variableName;\nglobal["variableName"];\n"variableName" in global;')}`,
|
|
62
|
+
},
|
|
63
|
+
range,
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
if (!extension) {
|
|
67
|
+
suggestions.push(
|
|
68
|
+
{
|
|
69
|
+
label: { label: 'if', description: 'If 表达式' },
|
|
70
|
+
kind: languages.CompletionItemKind.Snippet,
|
|
71
|
+
insertText: 'if ${1:condition} {\n\t$0\n}',
|
|
72
|
+
insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
73
|
+
documentation: {
|
|
74
|
+
value: `使用 \`if\` 表达式进行条件判断。${codeblock('if condition {\n\t// code\n}')}`,
|
|
75
|
+
},
|
|
76
|
+
range,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
label: { label: 'ifelse', description: 'If-Else 表达式' },
|
|
80
|
+
kind: languages.CompletionItemKind.Snippet,
|
|
81
|
+
insertText: 'if ${1:condition} {\n\t$0\n} else {\n\t\n}',
|
|
82
|
+
insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
83
|
+
documentation: {
|
|
84
|
+
value: `使用 \`ifelse\` 表达式进行条件判断。${codeblock('if condition {\n\t// code\n} else {\n\t// code\n}')}`,
|
|
85
|
+
},
|
|
86
|
+
range,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
label: { label: 'match', description: 'Match 表达式' },
|
|
90
|
+
kind: languages.CompletionItemKind.Snippet,
|
|
91
|
+
insertText:
|
|
92
|
+
'match ${1:value} {\n\tcase ${2:case1} {\n\t\t$0\n\t}\n\tcase ${3:case2} {\n\t\t\n\t}\n\tcase _ {\n\t\t\n\t}\n}',
|
|
93
|
+
insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
94
|
+
documentation: {
|
|
95
|
+
value: `使用 \`match\` 表达式进行模式匹配。${codeblock('match value {\n\tcase case1 {\n\t\t// code\n\t}\n\tcase case2 {\n\t\t// code\n\t}\n\tcase _ {\n\t\t// code\n\t}\n}')}`,
|
|
96
|
+
},
|
|
97
|
+
range,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
label: { label: 'loop', description: 'Loop 表达式' },
|
|
101
|
+
kind: languages.CompletionItemKind.Snippet,
|
|
102
|
+
insertText: 'loop {\n\t$0\n}',
|
|
103
|
+
insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
104
|
+
documentation: {
|
|
105
|
+
value: `使用 \`loop\` 表达式进行无限循环。${codeblock('loop {\n\t// code\n}')}`,
|
|
106
|
+
},
|
|
107
|
+
range,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
label: { label: 'while', description: 'While 表达式' },
|
|
111
|
+
kind: languages.CompletionItemKind.Snippet,
|
|
112
|
+
insertText: 'while ${1:condition} {\n\t$0\n}',
|
|
113
|
+
insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
114
|
+
documentation: {
|
|
115
|
+
value: `使用 \`while\` 表达式进行条件循环。${codeblock('while condition {\n\t// code\n}')}`,
|
|
116
|
+
},
|
|
117
|
+
range,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
label: { label: 'whileelse', description: 'While-Else 表达式' },
|
|
121
|
+
kind: languages.CompletionItemKind.Snippet,
|
|
122
|
+
insertText: 'while ${1:condition} {\n\t$0\n} else {\n\t\n}',
|
|
123
|
+
insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
124
|
+
documentation: {
|
|
125
|
+
value: `使用 \`whileelse\` 表达式进行条件循环。${codeblock('while condition {\n\t// code\n} else {\n\t// code\n}')}`,
|
|
126
|
+
},
|
|
127
|
+
range,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
label: { label: 'for', description: 'For 表达式' },
|
|
131
|
+
kind: languages.CompletionItemKind.Snippet,
|
|
132
|
+
insertText: 'for ${1:item} in ${2:collection} {\n\t$0\n}',
|
|
133
|
+
insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
134
|
+
documentation: {
|
|
135
|
+
value: `使用 \`for\` 表达式进行迭代循环。${codeblock('for item in collection {\n\t// code\n}')}`,
|
|
136
|
+
},
|
|
137
|
+
range,
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
label: { label: 'forelse', description: 'For-Else 表达式' },
|
|
141
|
+
kind: languages.CompletionItemKind.Snippet,
|
|
142
|
+
insertText: 'for ${1:item} in ${2:collection} {\n\t$0\n} else {\n\t\n}',
|
|
143
|
+
insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
144
|
+
documentation: {
|
|
145
|
+
value: `使用 \`forelse\` 表达式进行迭代循环。${codeblock('for item in collection {\n\t// code\n} else {\n\t// code\n}')}`,
|
|
146
|
+
},
|
|
147
|
+
range,
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
label: { label: 'fn', description: 'Fn 语句' },
|
|
151
|
+
kind: languages.CompletionItemKind.Snippet,
|
|
152
|
+
insertText: 'fn ${1:name}(${2:params}) {\n\t$0\n}',
|
|
153
|
+
insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
|
154
|
+
documentation: {
|
|
155
|
+
value: `使用 \`fn\` 语句进行函数声明。${codeblock('fn name(params) {\n\t// code\n}')}`,
|
|
156
|
+
},
|
|
157
|
+
range,
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
for (const kw of loadSuggestKeywords()) {
|
|
162
|
+
const exist = suggestions.find(
|
|
163
|
+
(item) => item.label === kw && item.kind === languages.CompletionItemKind.Keyword,
|
|
164
|
+
);
|
|
165
|
+
if (exist) continue;
|
|
166
|
+
suggestions.push(kwSuggestion(kw, range));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return suggestions;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/** 构造关键字选项 */
|
|
173
|
+
function kwSuggestion(kw: string, range: languages.CompletionItemRanges): languages.CompletionItem {
|
|
174
|
+
return {
|
|
175
|
+
label: kw,
|
|
176
|
+
kind: languages.CompletionItemKind.Keyword,
|
|
177
|
+
insertText: kw,
|
|
178
|
+
range,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** 扩展完成项 */
|
|
183
|
+
interface CustomCompletionItem extends languages.CompletionItem {
|
|
184
|
+
/** 是否为字段 */
|
|
185
|
+
isField: boolean;
|
|
186
|
+
/** 对应的变量值 */
|
|
187
|
+
vmValue?: VmValue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** 构造 filterText */
|
|
191
|
+
function filterText(key: string, char: string | undefined): string {
|
|
192
|
+
if (char == null || key.startsWith(char)) return key;
|
|
193
|
+
return key.startsWith('@') || key.startsWith('$') ? key.slice(1) : key;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** 构造选项 */
|
|
197
|
+
function completion(
|
|
198
|
+
model: editor.ITextModel,
|
|
199
|
+
description: string,
|
|
200
|
+
key: string,
|
|
201
|
+
value: VmValue | undefined,
|
|
202
|
+
fn: VmFunctionInfo | LocalDefinition['fn'] | undefined,
|
|
203
|
+
field: boolean,
|
|
204
|
+
): Pick<CustomCompletionItem, 'label' | 'kind' | 'commitCharacters' | 'vmValue' | 'isField'> {
|
|
205
|
+
let detail = '';
|
|
206
|
+
let kind: languages.CompletionItemKind;
|
|
207
|
+
if (fn == null && typeof value == 'function') {
|
|
208
|
+
fn = getVmFunctionInfo(value);
|
|
209
|
+
}
|
|
210
|
+
if (fn != null) {
|
|
211
|
+
detail = paramsList(model, fn);
|
|
212
|
+
kind = field ? languages.CompletionItemKind.Function : languages.CompletionItemKind.Method;
|
|
213
|
+
} else if (isVmModule(value)) {
|
|
214
|
+
kind = languages.CompletionItemKind.Module;
|
|
215
|
+
} else if (isVmExtern(value) && typeof value.value == 'function') {
|
|
216
|
+
if (value.value.prototype != null && (key[0] ?? '').toUpperCase() === key[0]) {
|
|
217
|
+
kind = languages.CompletionItemKind.Class;
|
|
218
|
+
} else {
|
|
219
|
+
detail = '(..)';
|
|
220
|
+
kind = value.caller ? languages.CompletionItemKind.Method : languages.CompletionItemKind.Function;
|
|
221
|
+
}
|
|
222
|
+
} else if (!field && key.startsWith('@')) {
|
|
223
|
+
kind = languages.CompletionItemKind.Constant;
|
|
224
|
+
} else {
|
|
225
|
+
kind = field ? languages.CompletionItemKind.Field : languages.CompletionItemKind.Variable;
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
label: { label: key, description, detail },
|
|
229
|
+
kind,
|
|
230
|
+
commitCharacters: fn ? ['!', '('] : ['!', '.', '[', '('],
|
|
231
|
+
vmValue: value,
|
|
232
|
+
isField: field,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** 获取所有键 */
|
|
237
|
+
function externKeys(value: VmExtern): string[] {
|
|
238
|
+
const keys = new Set<string>();
|
|
239
|
+
let e: unknown = value.value;
|
|
240
|
+
while (e && (typeof e == 'object' || typeof e == 'function')) {
|
|
241
|
+
for (const key of Object.getOwnPropertyNames(e)) {
|
|
242
|
+
if (value.has(key)) {
|
|
243
|
+
keys.add(key);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
e = Object.getPrototypeOf(e);
|
|
247
|
+
}
|
|
248
|
+
return Array.from(keys);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 自动完成
|
|
253
|
+
*/
|
|
254
|
+
export class CompletionItemProvider extends Provider implements languages.CompletionItemProvider {
|
|
255
|
+
readonly triggerCharacters: string[] = ['.', ':'];
|
|
256
|
+
/** 查找全局变量 */
|
|
257
|
+
private async completeGlobal(
|
|
258
|
+
model: editor.ITextModel,
|
|
259
|
+
char: string | undefined,
|
|
260
|
+
locals: readonly CustomCompletionItem[],
|
|
261
|
+
range: languages.CompletionItemRanges,
|
|
262
|
+
): Promise<CustomCompletionItem[]> {
|
|
263
|
+
const global = await this.getContext(model);
|
|
264
|
+
const suggestions: CustomCompletionItem[] = [];
|
|
265
|
+
const localKeys = new Set(locals.map((item) => item.insertText));
|
|
266
|
+
for (const key of new Set(global.keys())) {
|
|
267
|
+
const element = global.get(key);
|
|
268
|
+
if (element === undefined) continue;
|
|
269
|
+
|
|
270
|
+
if (isVmModule(element)) {
|
|
271
|
+
for (const f of element.keys()) {
|
|
272
|
+
if (char && !f.toLowerCase().includes(char)) {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const field = element.get(f);
|
|
276
|
+
if (field === undefined) continue;
|
|
277
|
+
|
|
278
|
+
suggestions.push({
|
|
279
|
+
insertText: localKeys.has(key) ? `global.${key}.${f}` : `${key}.${f}`,
|
|
280
|
+
filterText: filterText(f, char),
|
|
281
|
+
range,
|
|
282
|
+
...completion(model, DESC_GLOBAL, `${key}.${f}`, field, undefined, true),
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (char && !key.toLowerCase().includes(char)) {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
suggestions.push({
|
|
292
|
+
insertText: localKeys.has(key) ? `global.${key}` : key, // 如果有同名局部变量,使用 global. 前缀
|
|
293
|
+
filterText: filterText(key, char),
|
|
294
|
+
range,
|
|
295
|
+
...completion(model, DESC_GLOBAL, key, element, undefined, false),
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
return suggestions;
|
|
299
|
+
}
|
|
300
|
+
/** 查找局部变量 */
|
|
301
|
+
private async completeLocal(
|
|
302
|
+
model: editor.ITextModel,
|
|
303
|
+
position: IPosition,
|
|
304
|
+
char: string | undefined,
|
|
305
|
+
range: languages.CompletionItemRanges,
|
|
306
|
+
): Promise<CustomCompletionItem[]> {
|
|
307
|
+
const compiled = await this.getCompileResult(model);
|
|
308
|
+
if (!compiled) return [];
|
|
309
|
+
const suggestions: CustomCompletionItem[] = [];
|
|
310
|
+
|
|
311
|
+
let scope = compiled.scopeAt(model, position);
|
|
312
|
+
const locals = new Set<string>();
|
|
313
|
+
while (scope) {
|
|
314
|
+
for (const { definition, fn } of scope.locals) {
|
|
315
|
+
const name = model.getValueInRange(definition.range);
|
|
316
|
+
if (locals.has(name)) continue; // 子作用域可能会覆盖父作用域的变量
|
|
317
|
+
if (char && !name.toLowerCase().includes(char)) {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
locals.add(name);
|
|
321
|
+
suggestions.push({
|
|
322
|
+
insertText: name,
|
|
323
|
+
filterText: filterText(name, char),
|
|
324
|
+
range,
|
|
325
|
+
...completion(model, DESC_LOCAL, name, undefined, fn, false),
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
if (!scope.parent) break;
|
|
329
|
+
scope = scope.parent;
|
|
330
|
+
}
|
|
331
|
+
return suggestions;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** 查找变量字段 */
|
|
335
|
+
private async completeFields(
|
|
336
|
+
model: editor.ITextModel,
|
|
337
|
+
position: Position,
|
|
338
|
+
char: string | undefined,
|
|
339
|
+
range: languages.CompletionItemRanges,
|
|
340
|
+
): Promise<CustomCompletionItem[]> {
|
|
341
|
+
const compiled = await this.getCompileResult(model);
|
|
342
|
+
if (!compiled) return [];
|
|
343
|
+
const access = compiled.fieldAccessAt(model, position);
|
|
344
|
+
if (!access || access.fields.length === 0) return [];
|
|
345
|
+
const { def, fields } = access;
|
|
346
|
+
if ('definition' in def.def) {
|
|
347
|
+
// TODO: suggests local item fields
|
|
348
|
+
return [];
|
|
349
|
+
}
|
|
350
|
+
const vmGlobal = await this.getContext(model);
|
|
351
|
+
fields.pop(); // 移除最后一个部分,因为它是当前输入位置的字段名
|
|
352
|
+
const value = getDeep(vmGlobal.get(def.def.name), fields);
|
|
353
|
+
if (value == null || typeof value != 'object') {
|
|
354
|
+
return [];
|
|
355
|
+
}
|
|
356
|
+
const keys = isVmExtern(value) ? externKeys(value) : lib.keys(value);
|
|
357
|
+
const result: CustomCompletionItem[] = [];
|
|
358
|
+
for (const k of keys) {
|
|
359
|
+
const key = String(k);
|
|
360
|
+
if (char && !String(key).toLowerCase().includes(char)) {
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
const field = operations.$Get(value, key);
|
|
364
|
+
result.push({
|
|
365
|
+
insertText: key,
|
|
366
|
+
range,
|
|
367
|
+
...completion(model, DESC_FIELD, key, field, undefined, true),
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
return result;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** 获取完成范围 */
|
|
374
|
+
private toCompletionItemRanges(position: IPosition, range: IRange): languages.CompletionItemRanges {
|
|
375
|
+
return {
|
|
376
|
+
replace: range,
|
|
377
|
+
insert: Range.fromPositions(Range.getStartPosition(range), position),
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** @inheritdoc */
|
|
382
|
+
async provideCompletionItems(
|
|
383
|
+
model: editor.ITextModel,
|
|
384
|
+
position: Position,
|
|
385
|
+
context: languages.CompletionContext,
|
|
386
|
+
token: CancellationToken,
|
|
387
|
+
): Promise<languages.CompletionList | undefined> {
|
|
388
|
+
const compiled = await this.getCompileResult(model);
|
|
389
|
+
if (!compiled) return undefined;
|
|
390
|
+
|
|
391
|
+
if (context.triggerCharacter === '.') {
|
|
392
|
+
const prevWord = model.getWordAtPosition({
|
|
393
|
+
lineNumber: position.lineNumber,
|
|
394
|
+
column: position.column - 1,
|
|
395
|
+
});
|
|
396
|
+
if (prevWord?.word === 'global') {
|
|
397
|
+
const globals = await this.completeGlobal(
|
|
398
|
+
model,
|
|
399
|
+
undefined,
|
|
400
|
+
[],
|
|
401
|
+
undefined as unknown as languages.CompletionItemRanges,
|
|
402
|
+
);
|
|
403
|
+
return { suggestions: globals };
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const word = wordAt(model, position);
|
|
408
|
+
const prev = model.getValueInRange({
|
|
409
|
+
startLineNumber: position.lineNumber,
|
|
410
|
+
startColumn: (word?.range.startColumn ?? position.column) - 2,
|
|
411
|
+
endLineNumber: position.lineNumber,
|
|
412
|
+
endColumn: word?.range.startColumn ?? position.column,
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
if (context.triggerCharacter === ':' && prev !== '::') {
|
|
416
|
+
return undefined; // 不是 :: 触发的
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// suggest variables
|
|
420
|
+
let char: string | undefined;
|
|
421
|
+
let range: IRange;
|
|
422
|
+
const def = compiled.variableAccessAt(model, position);
|
|
423
|
+
if (def) {
|
|
424
|
+
if (def.ref == null) {
|
|
425
|
+
// 输入位置是变量定义
|
|
426
|
+
const suggestions: languages.CompletionItem[] = [];
|
|
427
|
+
if (
|
|
428
|
+
word &&
|
|
429
|
+
compiled.tags.some(
|
|
430
|
+
(t) => strictContainsPosition(t.range, position) && t.code === DiagnosticCode.MatchExpression,
|
|
431
|
+
)
|
|
432
|
+
) {
|
|
433
|
+
suggestions.push(
|
|
434
|
+
kwSuggestion('case', this.toCompletionItemRanges(position, word.range)),
|
|
435
|
+
kwSuggestion('if', this.toCompletionItemRanges(position, word.range)),
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
return { suggestions };
|
|
439
|
+
}
|
|
440
|
+
const d = def.def;
|
|
441
|
+
range = d.references[def.ref]!.range;
|
|
442
|
+
char = model.getValueInRange({
|
|
443
|
+
startLineNumber: range.startLineNumber,
|
|
444
|
+
startColumn: range.startColumn,
|
|
445
|
+
endLineNumber: range.startLineNumber,
|
|
446
|
+
endColumn: range.startColumn + 1,
|
|
447
|
+
});
|
|
448
|
+
} else if (word) {
|
|
449
|
+
range = word.range;
|
|
450
|
+
char = word.word[0];
|
|
451
|
+
} else {
|
|
452
|
+
range = {
|
|
453
|
+
startLineNumber: position.lineNumber,
|
|
454
|
+
startColumn: position.column,
|
|
455
|
+
endLineNumber: position.lineNumber,
|
|
456
|
+
endColumn: position.column,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
char = char?.toLowerCase();
|
|
460
|
+
|
|
461
|
+
const completionRange = this.toCompletionItemRanges(position, range);
|
|
462
|
+
|
|
463
|
+
if (/[^.]\.$/u.test(prev)) {
|
|
464
|
+
const suggestions = await this.completeFields(model, position, char, completionRange);
|
|
465
|
+
return { suggestions };
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const suggestions = COMMON_GLOBAL_SUGGESTIONS(completionRange, prev === '::');
|
|
469
|
+
const locals = await this.completeLocal(model, position, char, completionRange);
|
|
470
|
+
const globals = await this.completeGlobal(model, char, locals, completionRange);
|
|
471
|
+
suggestions.push(...locals, ...globals);
|
|
472
|
+
|
|
473
|
+
return { suggestions };
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/** @inheritdoc */
|
|
477
|
+
resolveCompletionItem(
|
|
478
|
+
item: languages.CompletionItem,
|
|
479
|
+
token: CancellationToken,
|
|
480
|
+
): languages.CompletionItem | undefined {
|
|
481
|
+
if (typeof item.label == 'string') {
|
|
482
|
+
// not a dynamic completion item
|
|
483
|
+
return item;
|
|
484
|
+
}
|
|
485
|
+
const { vmValue, isField } = item as CustomCompletionItem;
|
|
486
|
+
const { label } = item.label;
|
|
487
|
+
if (vmValue != null) {
|
|
488
|
+
if (item.documentation) return item;
|
|
489
|
+
const last = label.split('.').pop()!;
|
|
490
|
+
const def = valueDoc(last, vmValue, isField ? 'field' : 'hint');
|
|
491
|
+
item.documentation = {
|
|
492
|
+
value: `${codeblock('\0' + def.script)}\n${def.doc.join('\n')}`,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
return item;
|
|
496
|
+
}
|
|
497
|
+
}
|